Class: Kontena::Cli::Stacks::YAML::Reader
- Inherits:
-
Object
- Object
- Kontena::Cli::Stacks::YAML::Reader
- Defined in:
- lib/kontena/cli/stacks/yaml/reader.rb
Instance Attribute Summary collapse
-
#errors ⇒ Object
readonly
Returns the value of attribute errors.
-
#file ⇒ Object
readonly
Returns the value of attribute file.
-
#loader ⇒ Object
readonly
Returns the value of attribute loader.
-
#notifications ⇒ Object
readonly
Returns the value of attribute notifications.
Instance Method Summary collapse
-
#create_dependency_variables(dependencies, name) ⇒ Object
Creates a set of variables using the ‘depends’ section.
-
#create_parent_variable(parent_name) ⇒ Object
If this stack is a part of a dependency chain and has a parent, the variable $PARENT_STACK will interpolate to the name of the parent stack.
-
#default_envs ⇒ Hash
Values that are set always when parsing stacks.
-
#default_envs_to_options ⇒ Hash
Creates an opto option definition compatible hash from the #default_envs hash.
-
#dependencies ⇒ Array<Hash>
Returns an array of hashes containing the dependency tree starting from this file.
- #execute(service_name = nil, name: loader.stack_name.stack, parent_name: nil, skip_validation: false, values: nil, defaults: nil) ⇒ Hash
- #from_external_stack(name, service_name) ⇒ Object
-
#from_file? ⇒ Boolean
Did this stack come from a local file?.
-
#fully_interpolated_yaml ⇒ Hash
Uses variable interpolation, prompts as needed, liquid interpolation.
- #initialize(file) ⇒ Reader constructor
-
#internals_interpolated_yaml ⇒ Hash
Only uses the values from #default_envs to provide a hash from minimally interpolated YAML file.
-
#interpolate_liquid(content, vars) ⇒ String
Interpolate any Liquid templating in the YAML content.
- #parse_services(service_name = nil) ⇒ Hash
- #parse_volumes ⇒ Object
- #process_config(service_config, name = nil) ⇒ Object
-
#process_hash?(hash) ⇒ Boolean
If the supplied hash contains skip_if/only_if conditionals, process that conditional and return true/false.
- #process_volume(name, volume_config) ⇒ Object
-
#raw_content ⇒ Object
The YAML file raw content.
-
#raw_yaml ⇒ Hash
With zero interpolation/processing.
-
#services ⇒ Hash
-
services from YAML file.
-
-
#set_variable_defaults(defaults) ⇒ Object
Accepts a hash of variable_name => variable_value pairs and sets the values as variable default values Used when previous answers are read from master and passed as default values for upgrade.
-
#set_variable_values(values) ⇒ Object
Set values from a hash to values of the variables.
-
#validate ⇒ Array<Hash>
Array of validation errors.
- #validator ⇒ Kontena::Cli::Stacks::YAML::ValidatorV3
-
#variable_values(without_defaults: false, without_vault: false, with_errors: false) ⇒ Hash
A hash of key value pairs representing the values of stack variables.
-
#variables ⇒ Opto::Group
Accessor to the Opto variable handler.
- #volumes ⇒ Object
Methods included from Common
#access_token=, #add_master, #any_key_to_continue, #any_key_to_continue_with_timeout, #api_url, #api_url=, #caret, #clear_current_grid, #client, #cloud_auth?, #cloud_client, #config, #confirm, #confirm_command, #current_grid, #current_master_index, #debug?, #display_account_login_info, #display_login_info, display_logo, #display_master_login_info, #error, exit_with_error, #kontena_account, #logger, #pastel, #print, #prompt, #puts, #require_api_url, #require_token, #reset_client, #reset_cloud_client, #running_quiet?, #running_silent?, #running_verbose?, #spin_if, #spinner, #sprint, #sputs, #stdin_input, #use_refresh_token, #vfakespinner, #vputs, #vspinner, #warning
Methods included from Util
included, #longest_string_in_array, #safe_dig, #seconds_to_human, stringify_keys, stringify_keys!, symbolize_keys, symbolize_keys!, #time_ago, #time_until, which
Constructor Details
#initialize(file) ⇒ Reader
36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 36 def initialize(file) if file.kind_of?(StackFileLoader) @file = file.source @loader = file else @file = file @loader = StackFileLoader.for(file) end @errors = [] @notifications = [] end |
Instance Attribute Details
#errors ⇒ Object (readonly)
Returns the value of attribute errors.
32 33 34 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 32 def errors @errors end |
#file ⇒ Object (readonly)
Returns the value of attribute file.
32 33 34 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 32 def file @file end |
#loader ⇒ Object (readonly)
Returns the value of attribute loader.
32 33 34 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 32 def loader @loader end |
#notifications ⇒ Object (readonly)
Returns the value of attribute notifications.
32 33 34 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 32 def notifications @notifications end |
Instance Method Details
#create_dependency_variables(dependencies, name) ⇒ Object
Creates a set of variables using the ‘depends’ section. The variable name is the name of the dependency and the variable value is the generated child stack name. For example,.have something like: depends:
redis:
stack: foo/redis
you will get a new variable called “redis” and its value will be “this-stack-name-redis”. This variable can be used to interpolate for example a hostname to some environment variable: environment:
- "REDIS_HOST=redis.${REDIS}"
167 168 169 170 171 172 173 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 167 def create_dependency_variables(dependencies, name) return if dependencies.nil? dependencies.each do || variables.build_option(name: ['name'].to_s, type: :string, value: "#{name}-#{['name']}") create_dependency_variables(['depends'], "#{name}.#{['name']}") end end |
#create_parent_variable(parent_name) ⇒ Object
If this stack is a part of a dependency chain and has a parent, the variable $PARENT_STACK will interpolate to the name of the parent stack.
177 178 179 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 177 def create_parent_variable(parent_name) variables.build_option(name: 'PARENT_STACK', type: :string, value: parent_name) end |
#default_envs ⇒ Hash
Values that are set always when parsing stacks
65 66 67 68 69 70 71 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 65 def default_envs { 'GRID' => env['GRID'], 'STACK' => env['STACK'], 'PLATFORM' => env['PLATFORM'] || env['GRID'] } end |
#default_envs_to_options ⇒ Hash
Creates an opto option definition compatible hash from the #default_envs hash
125 126 127 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 125 def default_envs.each_with_object({}) { |env, obj| obj[env[0]] = { type: :string, value: env[1] } } end |
#dependencies ⇒ Array<Hash>
Returns an array of hashes containing the dependency tree starting from this file
241 242 243 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 241 def dependencies @dependencies ||= loader.dependencies end |
#execute(service_name = nil, name: loader.stack_name.stack, parent_name: nil, skip_validation: false, values: nil, defaults: nil) ⇒ Hash
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 193 def execute(service_name = nil, name: loader.stack_name.stack, parent_name: nil, skip_validation: false, values: nil, defaults: nil) set_variable_defaults(defaults) if defaults set_variable_values(values) if values create_dependency_variables(dependencies, name) create_parent_variable(parent_name) if parent_name variables.run if !skip_validation && !variables.valid? stringify_keys(variables.errors).each do |k,v| errors << { 'variables' => { k => v } } end end validate unless skip_validation result = {} Dir.chdir(from_file? ? File.dirname(File.(file)) : Dir.pwd) do result['stack'] = raw_yaml['stack'] result['version'] = loader.stack_name.version || '0.0.1' result['name'] = name result['labels'] = fully_interpolated_yaml['labels'] || [] result['registry'] = loader.registry result['expose'] = fully_interpolated_yaml['expose'] result['services'] = errors.empty? ? parse_services(service_name) : {} result['volumes'] = errors.empty? ? parse_volumes : {} result['dependencies'] = dependencies result['source'] = raw_content result['variables'] = variable_values(without_defaults: true, without_vault: true) result['metadata'] = raw_yaml['meta'] || {} end if parent_name result['parent'] = { 'name' => parent_name } else result['parent'] = nil end if service_name.nil? result['services'].each do |service| errors << { 'services' => { service['name'] => { 'image' => "image is missing" } } } if service['image'].to_s.empty? end errors << { file => { 'stack' => 'Required field missing' } } if result['stack'].nil? end result end |
#from_external_stack(name, service_name) ⇒ Object
365 366 367 368 369 370 371 372 373 374 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 365 def from_external_stack(name, service_name) external_reader = StackFileLoader.for(name, loader).reader variables.to_a(with_value: true).each do |var| external_reader.variables.build_option(var) end outcome = external_reader.execute(service_name) errors.concat external_reader.errors unless external_reader.errors.empty? || errors.include?(external_reader.errors) notifications.concat external_reader.notifications unless external_reader.notifications.empty? || notifications.include?(external_reader.notifications) outcome['services'] end |
#from_file? ⇒ Boolean
Returns did this stack come from a local file?.
182 183 184 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 182 def from_file? loader.origin == 'file' end |
#fully_interpolated_yaml ⇒ Hash
Uses variable interpolation, prompts as needed, liquid interpolation
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 95 def fully_interpolated_yaml return @fully_interpolated_yaml if @fully_interpolated_yaml @fully_interpolated_yaml = ::YAML.safe_load( replace_dollar_dollars( interpolate( interpolate_liquid( raw_content, variable_values ), use_opto: true, raise_on_unknown: true ) ), [], [], true, file ) rescue Psych::SyntaxError => ex raise ex, "Error while parsing #{file} : #{ex.}" end |
#internals_interpolated_yaml ⇒ Hash
Only uses the values from #default_envs to provide a hash from minimally interpolated YAML file. Useful for accessing some parts of the YAML without asking any questions.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 77 def internals_interpolated_yaml @internals_interpolated_yaml ||= ::YAML.safe_load( replace_dollar_dollars( interpolate( raw_content, use_opto: false, substitutions: default_envs, warnings: false ) ), [], [], true, file ) rescue Psych::SyntaxError => ex raise ex, "Error while parsing #{file} : #{ex.}" end |
#interpolate_liquid(content, vars) ⇒ String
Interpolate any Liquid templating in the YAML content
250 251 252 253 254 255 256 257 258 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 250 def interpolate_liquid(content, vars) Liquid::Template.error_mode = :strict template = Liquid::Template.parse(content) # Wrap nil values in LiquidNull to not have Liquid consider them as undefined vars = vars.map {|key, value| [key, value.nil? ? LiquidNull.new : value]}.to_h template.render!(vars, strict_variables: true, strict_filters: true) end |
#parse_services(service_name = nil) ⇒ Hash
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 288 def parse_services(service_name = nil) services = self.services.dup # do not modify the fully_interpolated_yaml['services'] hash in-place if service_name.nil? services.each do |name, config| services[name] = process_config(config, name) if process_hash?(config) services[name].delete('only_if') services[name].delete('skip_if') else services.delete(name) end end services.map { |name, svc| svc.merge('name' => name) } else raise ("Service '#{service_name}' not found in #{file}") unless services.key?(service_name) process_config(services[service_name], service_name) end end |
#parse_volumes ⇒ Object
272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 272 def parse_volumes volumes.each do |name, config| if process_hash?(config) volumes[name].delete('only_if') volumes[name].delete('skip_if') volumes[name] = process_volume(name, config) else volumes.delete(name) end end volumes.map { |name, vol| vol.merge('name' => name) } end |
#process_config(service_config, name = nil) ⇒ Object
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 329 def process_config(service_config, name=nil) normalize_env_vars(service_config) merge_env_vars(service_config) (service_config) normalize_build_args(service_config) if service_config.key?('extends') service_config = extend_config(service_config) service_config.delete('extends') end if name ServiceGeneratorV2.new(service_config).generate.merge('name' => name) else ServiceGeneratorV2.new(service_config).generate end end |
#process_hash?(hash) ⇒ Boolean
If the supplied hash contains skip_if/only_if conditionals, process that conditional and return true/false
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 311 def process_hash?(hash) return true unless hash['skip_if'] || hash['only_if'] skip_lambdas = normalize_ifs(hash['skip_if']) only_lambdas = normalize_ifs(hash['only_if']) if skip_lambdas return false if skip_lambdas.any? { |s| s.call } end if only_lambdas return false unless only_lambdas.all? { |s| s.call } end true end |
#process_volume(name, volume_config) ⇒ Object
345 346 347 348 349 350 351 352 353 354 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 345 def process_volume(name, volume_config) return [] if volume_config.nil? || volume_config.empty? if volume_config['external'].is_a?(TrueClass) volume_config['external'] = name elsif volume_config['external']['name'] volume_config['external'] = volume_config['external']['name'] end volume_config['name'] = name volume_config end |
#raw_content ⇒ Object
The YAML file raw content
114 115 116 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 114 def raw_content loader.content end |
#raw_yaml ⇒ Hash
Returns with zero interpolation/processing. Will mostly fail.
119 120 121 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 119 def raw_yaml loader.yaml end |
#services ⇒ Hash
Returns - services from YAML file.
361 362 363 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 361 def services @services ||= fully_interpolated_yaml.fetch('services', {}) end |
#set_variable_defaults(defaults) ⇒ Object
Accepts a hash of variable_name => variable_value pairs and sets the values as variable default values Used when previous answers are read from master and passed as default values for upgrade.
141 142 143 144 145 146 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 141 def set_variable_defaults(defaults) defaults.each do |key, val| var = variables.option(key.to_s) var.default = val if var end end |
#set_variable_values(values) ⇒ Object
Set values from a hash to values of the variables. Used when variable values are read from a file or command line parameters or dependency variable injection
151 152 153 154 155 156 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 151 def set_variable_values(values) values.each do |key, val| var = variables.option(key.to_s) var.set(val) if var end end |
#validate ⇒ Array<Hash>
Returns array of validation errors.
261 262 263 264 265 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 261 def validate result = validator.validate(fully_interpolated_yaml) store_failures(result) result end |
#validator ⇒ Kontena::Cli::Stacks::YAML::ValidatorV3
268 269 270 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 268 def validator @validator ||= YAML::ValidatorV3.new end |
#variable_values(without_defaults: false, without_vault: false, with_errors: false) ⇒ Hash
Returns a hash of key value pairs representing the values of stack variables.
52 53 54 55 56 57 58 59 60 61 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 52 def variable_values(without_defaults: false, without_vault: false, with_errors: false) result = variables.to_h(values_only: true, with_errors: with_errors) if without_defaults result.delete_if { |k, _| default_envs.key?(k.to_s) || k.to_s == 'PARENT_STACK' } end if without_vault result.delete_if { |k, _| variables.option(k).from.include?('vault') || variables.option(k).to.include?('vault') } end result end |
#variables ⇒ Opto::Group
Accessor to the Opto variable handler
131 132 133 134 135 136 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 131 def variables @variables ||= ::Opto::Group.new( internals_interpolated_yaml.fetch('variables', {}).merge(), defaults: { from: :env } ) end |
#volumes ⇒ Object
356 357 358 |
# File 'lib/kontena/cli/stacks/yaml/reader.rb', line 356 def volumes @volumes ||= fully_interpolated_yaml.fetch('volumes', {}) end |