Class: KubeDeployTools::DeployConfigFile

Inherits:
Object
  • Object
show all
Includes:
DeployConfigFileUtil
Defined in:
lib/kube_deploy_tools/deploy_config_file.rb

Overview

Read-only model for the deploy.yaml configuration file.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DeployConfigFileUtil

#check_and_err, #check_and_warn, #load_library

Constructor Details

#initialize(filename) ⇒ DeployConfigFile

TODO(joshk): Refactor into initialize(fp) which takes a file-like object; after this, auto discovery should go into DeployConfigFile.locate classmethod. This would require erasing auto-upgrade capability, which should be possible if we major version bump.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 28

def initialize(filename)
  config = nil
  if !filename.nil? && Pathname.new(filename).absolute?
    config = YAML.load_file(filename)
  else
    original_dir = Dir.pwd
    changed_dir = false
    until Dir.pwd == '/'
      # Try looking for filename specified by user.
      # If no filename was specified by the user, then look for
      # deploy.yml or deploy.yaml.
      if !filename.nil? && File.exist?(filename)
        config = YAML.load_file(filename)
        break
      elsif filename.nil? && File.exist?(DEPLOY_YAML)
        filename = DEPLOY_YAML
        config = YAML.load_file(filename)
        break
      end

      # KDT should run in the directory containing the deploy config file.
      changed_dir = true
      Dir.chdir('..')
    end
    if config.nil?
      Dir.chdir(original_dir)
      if ! filename.nil?
        raise "Could not locate file: config file '#{filename}' in any directory"
      else
        raise "Could not locate file: config file '#{DEPLOY_YAML}' in any directory"
      end
    end
    if changed_dir
      Logger.warn "Changed directory to #{Dir.pwd} (location of #{filename})"
    end
  end
  @filename = filename
  @original_config = config

  version = config.fetch('version', 1)
  check_and_warn(
    config.has_key?('version'),
    'Expected .version to be specified, but .version is missing. Falling back to version 1 config schema')
  check_and_err([1, 2].include?(version), "Expected valid version, but received unsupported version '#{version}'")

  case version
  when 2
    fetch_and_parse_version2_config!
  else
    raise "Unsupported version #{version}"
  end
end

Instance Attribute Details

#artifact_registriesObject

Returns the value of attribute artifact_registries.



20
21
22
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 20

def artifact_registries
  @artifact_registries
end

#artifact_registryObject

Returns the value of attribute artifact_registry.



20
21
22
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 20

def artifact_registry
  @artifact_registry
end

#artifactsObject

Returns the value of attribute artifacts.



20
21
22
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 20

def artifacts
  @artifacts
end

#default_flagsObject

Returns the value of attribute default_flags.



20
21
22
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 20

def default_flags
  @default_flags
end

#expirationObject

Returns the value of attribute expiration.



20
21
22
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 20

def expiration
  @expiration
end

#flavorsObject

Returns the value of attribute flavors.



20
21
22
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 20

def flavors
  @flavors
end

#hooksObject

Returns the value of attribute hooks.



20
21
22
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 20

def hooks
  @hooks
end

#image_registriesObject

Returns the value of attribute image_registries.



20
21
22
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 20

def image_registries
  @image_registries
end

#valid_image_registriesObject

Returns the value of attribute valid_image_registries.



20
21
22
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 20

def valid_image_registries
  @valid_image_registries
end

Class Method Details

.deep_merge(h, other) ⇒ Object



286
287
288
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 286

def self.deep_merge(h, other)

end

Instance Method Details

#extend!(other) ⇒ Object

Extend this DeployConfigFile with another instance.



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 245

def extend!(other)
  # Any image_registries entry in |self| should take precedence
  # over any identical key in |other|. The behavior of merge is that
  # the 'other' hash wins.
  @image_registries = other.image_registries.merge(@image_registries)

  @artifact_registries = other.artifact_registries.merge(@artifact_registries)

  @artifact_registry = other.artifact_registry if @artifact_registry.empty?

  # Same behavior as above for #default_flags.
  @default_flags = other.default_flags.merge(@default_flags)

  # artifacts should be merged by 'name'. In other words, if |self| and |other|
  # specify the same 'name' of a registry, self's config for that registry
  # should win wholesale (no merging of flags.)
  @artifacts = (@artifacts + other.artifacts).uniq { |h| h.fetch('name') }

  # Same behavior as for flags and registries, but the flags within the flavor
  # are in a Hash, so we need a deep merge.
  @flavors = other.flavors.deep_merge(@flavors)

  # A break from the preceding merging logic - Dependent hooks have to come
  # first and a given named hook can only be run once. But seriously, you
  # probably don't want to make a library that specifies hooks.
  @hooks = (other.hooks + @hooks).uniq

  @expiration = (@expiration + other.expiration).uniq { |h| h.fetch('repository') }
end

#fetch_and_parse_version2_config!Object



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
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 81

def fetch_and_parse_version2_config!
  # The literal contents of your deploy.yaml are now populated into |self|.
  config = @original_config
  @image_registries = parse_image_registries(config.fetch('image_registries', []))
  @default_flags = config.fetch('default_flags', {})
  @artifacts = config.fetch('artifacts', [])
  @flavors = config.fetch('flavors', {})
  @hooks = config.fetch('hooks', ['default'])
  @expiration = config.fetch('expiration', [])
  @artifact_registries = parse_artifact_registries(config.fetch('artifact_registries', []))
  @artifact_registry = parse_artifact_registry(config.fetch('artifact_registry', ''), @artifact_registries)

  validate_default_flags
  validate_flavors
  validate_hooks
  validate_expiration

  # Augment these literal contents by resolving all libraries.
  # extend! typically gives the current file precedence when merge conflicts occur,
  # but the expected precedence of library inclusion is the reverse (library 2 should
  # overwrite what library 1 specifies), so reverse the libraries list first.
  config.fetch('libraries', []).reverse.each do |libfn|
    extend!(load_library(libfn))
  end

  # Now that we have a complete list of image registries, validation is now possible.
  # Note that this also populates @valid_image_registries.
  validate_artifacts!
end

#map_image_registry(image_registries) ⇒ Object



127
128
129
130
131
132
133
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 127

def map_image_registry(image_registries)
  valid_image_registries = {}
  image_registries.each do |reg_name, reg_info|
    valid_image_registries[reg_name] = reg_info.prefix
  end
  valid_image_registries
end

#parse_artifact_registries(artifact_registries) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 187

def parse_artifact_registries(artifact_registries)
  check_and_err(artifact_registries.is_a?(Array), '.artifact_registries is not an Array')
  artifact_registries = artifact_registries.map { |r| ArtifactRegistry.new(r) }

  # Validate that each artifact registry is named uniquely
  duplicates = select_duplicates(artifact_registries.map { |r| r.name })
  check_and_err(
    duplicates.count == 0,
    "Expected .artifact_registries names to be unique, but found duplicates: #{duplicates}"
  )

  unsupported_drivers = artifact_registries.
    select { |r| !ArtifactRegistry::Driver::MAPPINGS.key? r.driver_name }.
    map { |r| r.driver_name }
  check_and_err(
    unsupported_drivers.count == 0,
    "Expected .artifact_registries drivers to be valid, but found unsupported drivers: #{unsupported_drivers}. Must be a driver in: #{ArtifactRegistry::Driver::MAPPINGS.keys}",
  )

  artifact_registries
    .select { |r| r.driver_name == "gcs" }
    .select { |r| !r.config.has_key? "bucket" }
    .each { |r| check_and_err(false, "Expected .artifact_registries['#{r.config.name}'].config.bucket to exist, but no GCS bucket is specified") }


  artifact_registries
    .map { |r| [r.name, r] }
    .to_h
end

#parse_artifact_registry(artifact_registry, artifact_registries) ⇒ Object



217
218
219
220
221
222
223
224
225
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 217

def parse_artifact_registry(artifact_registry, artifact_registries)
  check_and_err(artifact_registry.is_a?(String), '.artifact_registry is not a String')
  check_and_err(
    artifact_registry.empty? || artifact_registries.key?(artifact_registry),
    "#{artifact_registry} is not a valid Artifact Registry. Has to be one of #{artifact_registries.keys}"
  )

  artifact_registry
end

#parse_image_registries(image_registries) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 111

def parse_image_registries(image_registries)
  check_and_err(image_registries.is_a?(Array), '.image_registries is not an Array')
  image_registries = image_registries.map { |i| ImageRegistry.new(i) }

  # Validate that only one instance of each driver is registered
  duplicates = select_duplicates(image_registries.map { |i| i.name })
  check_and_err(
    duplicates.count == 0,
    "Expected .image_registries names to be unique, but found duplicates: #{duplicates}"
  )

  image_registries
    .map { |i| [i.name, i] }
    .to_h
end

#select_duplicates(array) ⇒ Object



240
241
242
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 240

def select_duplicates(array)
  array.select { |n| array.count(n) > 1 }.uniq
end

#to_hObject



275
276
277
278
279
280
281
282
283
284
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 275

def to_h
  {
    'image_registries' => @image_registries.values.map(&:to_h),
    'default_flags' => @default_flags,
    'artifacts' => @artifacts,
    'flavors' => @flavors,
    'hooks' => @hooks,
    'expiration' => @expiration,
  }
end

#upgrade!Object

upgrade! converts the config to a YAML string in the format of the latest supported version e.g. with the latest supported version as v2, to_yaml will always print a valid v2 YAML



231
232
233
234
235
236
237
238
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 231

def upgrade!
  version = @original_config.fetch('version', 1)
  case version
  when 2
    # TODO(joshk): Any required updates to v3 or remove this entire method
    true
  end
end

#validate_artifacts!Object

.artifacts depends on .default_flags and .image_registries



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 136

def validate_artifacts!
  check_and_err(artifacts.is_a?(Array), '.artifacts is not an Array')

  duplicates = select_duplicates(artifacts.map { |i| i.fetch('name') })
  check_and_err(
    duplicates.count == 0,
    "Expected .artifacts names to be unique, but found duplicates: #{duplicates}"
  )

  @valid_image_registries = map_image_registry(@image_registries)

  artifacts.each_with_index { |artifact, index|
    check_and_err(
      artifact.key?('name'),
      "Expected .artifacts[#{index}].name key to exist, but .name is missing"
    )
    name = artifact.fetch('name')
    check_and_err(
      artifact.key?('image_registry'),
      "Expected .artifacts[#{index}].image_registry key to exist, but .image_registry is missing"
    )

    image_registry = artifact.fetch('image_registry')
    check_and_err(
      @valid_image_registries.key?(image_registry),
      "#{image_registry} is not a valid Image Registry. Has to be one of #{@valid_image_registries.keys}"
    )

    check_and_err(
      artifact.key?('flags'),
      "Expected .artifacts.#{name}.flags key to exist, but .flags is missing"
    )
  }
end

#validate_default_flagsObject



171
172
173
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 171

def validate_default_flags
  check_and_err(@default_flags.is_a?(Hash), '.default_flags is not a Hash')
end

#validate_expirationObject



183
184
185
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 183

def validate_expiration
  check_and_err(@expiration.is_a?(Array), '.expiration is not an Array')
end

#validate_flavorsObject



175
176
177
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 175

def validate_flavors
  check_and_err(@flavors.is_a?(Hash), '.flavors is not a Hash')
end

#validate_hooksObject



179
180
181
# File 'lib/kube_deploy_tools/deploy_config_file.rb', line 179

def validate_hooks
  check_and_err(@hooks.is_a?(Array), '.hooks is not an Array')
end