Class: Kontena::Cli::Stacks::YAML::ValidatorV3

Inherits:
Object
  • Object
show all
Includes:
Validations
Defined in:
lib/kontena/cli/stacks/yaml/validator_v3.rb

Constant Summary collapse

KNOWN_TOP_LEVEL_KEYS =
%w(
  services
  errors
  volumes
  networks
  variables
  stack
  version
  data
  description
  expose
)

Instance Method Summary collapse

Methods included from Validations

#common_validations, #optional, #validate_options, #validate_volume_options, #volume_schema

Constructor Details

#initialize ⇒ ValidatorV3

Returns a new instance of ValidatorV3.



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/kontena/cli/stacks/yaml/validator_v3.rb', line 22

def initialize
  @schema = common_validations
  @schema['build'] = optional('stacks_valid_build')
  @schema['depends_on'] = optional('array')
  @schema['network_mode'] = optional(%w(host bridge))
  @schema['logging'] = optional({
    'driver' => optional('string'),
    'options' => optional(-> (value) { value.is_a?(Hash) })
    })
  Validations::CustomValidators.load
end

Instance Method Details

#parse_volume(vol) ⇒ Object

borrowed from server/app/helpers/volumes_helpers.rb



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/kontena/cli/stacks/yaml/validator_v3.rb', line 35

def parse_volume(vol)
  elements = vol.split(':')
  if elements.size >= 2 # Bind mount or volume used
    if elements[0].start_with?('/') && elements[1] && elements[1].start_with?('/') # Bind mount
      {bind_mount: elements[0], path: elements[1], flags: elements[2..-1].join(',')}
    elsif !elements[0].start_with?('/') && elements[1].start_with?('/') # Real volume
      {volume: elements[0], path: elements[1], flags: elements[2..-1].join(',')}
    else
      {error: "volume definition not in right format: #{vol}" }
    end
  elsif elements.size == 1 && elements[0].start_with?('/') # anon volume
    {bind_mount: nil, path: elements[0], flags: nil} # anon vols do not support flags
  else
    {error: "volume definition not in right format: #{vol}" }
  end
end

#validate(yaml) ⇒ Array

Returns validation_errors.

Parameters:

  • yaml (Hash)
  • strict (TrueClass|FalseClass)

Returns:

  • (Array) —

    validation_errors



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/kontena/cli/stacks/yaml/validator_v3.rb', line 56

def validate(yaml)
  result = {
    errors: [],
    notifications: []
  }

  result[:notifications] += (yaml.keys - KNOWN_TOP_LEVEL_KEYS).map do |key|
    { key => "unknown top level key" }
  end

  if yaml.key?('services')
    if yaml['services'].is_a?(Hash)
      yaml['services'].each do |service, options|
        unless options.is_a?(Hash)
          result[:errors] << { 'services' => { service => { 'options' => "must be a mapping not a #{options.class}"}  } }
          next
        end
        option_errors = validate_options(options)
        result[:errors] << { 'services' => { service => option_errors.errors } } unless option_errors.valid?
        if options['volumes']
          mount_path_occurences = Hash.new(0)
          options['volumes'].each do |volume|
            parsed = parse_volume(volume)
            if parsed[:error]
              result[:errors] << { 'services' => { service => { 'volumes' => { volume => parsed[:error] } } } }
            elsif parsed[:path]
              mount_path_occurences[parsed[:path]] += 1
              volume_name = parsed[:volume]
              if volume_name && !volume_name.start_with?('/')
                if yaml.key?('volumes')
                  unless yaml['volumes'][volume_name]
                    result[:errors] << { 'services' => { service => { 'volumes' => { volume_name => 'not found in top level volumes list' } } } }
                  end
                else
                  result[:errors] << { 'services' => { service => { 'volumes' => { volume => 'defines volume name, but file does not contain volumes definitions' } } } }
                end
              end
            else
              result[:errors] << { 'services' => { service => { 'volumes' => { volume => 'mount point missing' } } } }
            end
          end
          mount_path_occurences.select {|path, occurences| occurences > 1 }.each do |path, occurences|
            result[:errors] << { 'services' => { service => { 'volumes' => { path => "mount point defined #{occurences} times" } } } }
          end
        end
      end
    else
      result[:errors] << { 'services' => "must be a mapping, not #{yaml['services'].class}" }
    end
  else
    result[:notifications] << { 'file' => 'does not define any services' }
  end

  if yaml.key?('volumes')
    if yaml['volumes'].is_a?(Hash)
      yaml['volumes'].each do |volume, options|
        if options.is_a?(Hash)
          option_errors = validate_volume_options(options)
          unless option_errors.valid?
            result[:errors] << { 'volumes' => { volume => option_errors.errors } }
          end
        else
          result[:errors] << { 'volumes' => { volume => { 'options' => "must be a mapping, not #{options.class}" } } }
        end
      end
    else
      result[:errors] << { 'volumes' => "must be a mapping, not #{yaml['volumes'].class}" }
    end
  end

  if yaml.key?('networks')
    result[:notifications] << { 'networks' => 'Kontena does not support multiple networks yet. You can reference services with Kontena\'s internal DNS (service_name.kontena.local)' }
  end

  if (yaml['volumes'].nil? || yaml['volumes'].empty?) && (yaml['services'].nil? || yaml['services'].empty?)
    result[:errors] << { 'file' => 'does not list any services or volumes' }
  end
  result
end