lib/kontena/cli/stacks/common.rb in kontena-cli-1.4.0.pre6 vs lib/kontena/cli/stacks/common.rb in kontena-cli-1.4.0.pre7
- old
+ new
@@ -1,159 +1,174 @@
require_relative 'yaml/reader'
require_relative '../services/services_helper'
require_relative 'service_generator_v2'
require_relative '../../stacks_client'
+require_relative 'yaml/stack_file_loader'
require 'yaml'
module Kontena::Cli::Stacks
module Common
include Kontena::Cli::Services::ServicesHelper
- module StackNameParam
- attr_accessor :stack_version
+ # @return [StackFileLoader] a loader for the stack origin defined through command-line options
+ def loader
+ @loader ||= loader_class.for(source)
+ end
+ # @return [YAML::Reader] a YAML reader for the target file
+ def reader
+ @reader ||= loader.reader
+ end
+
+ # Stack name read from -n parameter or the stack file
+ # @return [String]
+ def stack_name
+ @stack_name ||= (self.respond_to?(:name) && self.name) ? self.name : loader.stack_name.stack
+ end
+
+ # An accessor to the YAML Reader outcome. Passes parent name, values from command line and
+ # the stackname to the reader.
+ #
+ # @return [Hash]
+ def stack
+ @stack ||= reader.execute(
+ name: stack_name,
+ parent_name: self.respond_to?(:parent_name) ? self.parent_name : nil,
+ values: (self.respond_to?(:values_from_options) ? self.values_from_options : {})
+ )
+ end
+
+ # @return [Class] an accessor to StackFileLoader constant, for testing purposes
+ def loader_class
+ ::Kontena::Cli::Stacks::YAML::StackFileLoader
+ end
+
+ module RegistryNameParam
+ def stack_name
+ @stack_name ||= Kontena::Cli::Stacks::StackName.new(source)
+ end
+
def self.included(where)
- where.parameter "STACK_NAME", "Stack name, for example user/stackname or user/stackname:version" do |name|
- if name.include?(':')
- name, @stack_version = name.split(':',2 )
- end
- name
- end
+ where.parameter "STACK_NAME", "Stack name, for example user/stackname or user/stackname:version", attribute_name: :source
end
end
+ module StackNameParam
+ # Include to add a STACK_NAME parameter
+ def self.included(where)
+ where.parameter "STACK_NAME", "Stack name, for example user/stackname or user/stackname:version", attribute_name: :source
+ end
+ end
+
module StackFileOrNameParam
+ # Include to add a stack file parameter
def self.included(where)
- where.parameter "[FILE]", "Kontena stack file, registry stack name (user/stack or user/stack:version) or URL", default: "kontena.yml", attribute_name: :filename
+ where.parameter "[FILE]", "Kontena stack file, registry stack name (user/stack or user/stack:version) or URL", default: "kontena.yml", attribute_name: :source
end
end
module StackNameOption
+ # Include to add a stack name parameter
def self.included(where)
where.option ['-n', '--name'], 'NAME', 'Define stack name (by default comes from stack file)'
end
end
module StackValuesToOption
attr_accessor :values
+ # Include to add --values-to variable value dumping feature
def self.included(where)
where.option '--values-to', '[FILE]', 'Output variable values as YAML to file'
end
- def dump_variables(reader)
- vals = reader.variables.to_h(values_only: true).reject {|k,_| k == 'STACK' || k == 'GRID' }
- File.write(values_to, ::YAML.dump(vals))
+ # Writes a YAML file from the values received from YAML::Reader to a file defined through
+ # the --values-to option
+ def dump_variables
+ File.write(values_to, ::YAML.dump(reader.variable_values, without_defaults: true, without_vault: true))
end
end
module StackValuesFromOption
- attr_accessor :values
+ # Include to add --values-from option to read variable values from a YAML file
+ # and the -v variable=value option that can be used to pass variable values
+ # directly from command line
def self.included(where)
+ where.prepend InstanceMethods
+
where.option '--values-from', '[FILE]', 'Read variable values from YAML' do |filename|
- if filename
- require_config_file(filename)
- @values = ::YAML.safe_load(File.read(filename))
- end
- filename
+ values_from_file.merge!(::YAML.safe_load(File.read(filename)))
+ true
end
- end
- end
- def stack_name
- @stack_name ||= self.name || stack_name_from_yaml(filename)
- end
-
- def reader_from_yaml(filename, name: nil, values: nil, defaults: nil)
- reader = Kontena::Cli::Stacks::YAML::Reader.new(filename, values: values, defaults: defaults)
- if reader.stack_name.nil?
- exit_with_error "Stack MUST have stack name in YAML top level field 'stack'! Aborting."
+ where.option '-v', "VARIABLE=VALUE", "Set stack variable values, example: -v domain=example.com. Can be used multiple times.", multivalued: true, attribute_name: :var_option do |var_pair|
+ var_name, var_value = var_pair.split('=', 2)
+ values_from_value_options.merge!(::YAML.safe_load(::YAML.dump(var_name => var_value)))
+ end
end
- set_env_variables(name || reader.stack_name, current_grid)
- reader
- end
- def stack_from_reader(reader)
- outcome = reader.execute
+ module InstanceMethods
+ def values_from_file
+ @values_from_file ||= {}
+ end
- hint_on_validation_notifications(outcome[:notifications]) unless outcome[:notifications].empty?
- abort_on_validation_errors(outcome[:errors]) unless outcome[:errors].empty?
- kontena_services = generate_services(outcome[:services])
- kontena_volumes = generate_volumes(outcome[:volumes])
- stack = {
- 'name' => outcome[:name],
- 'stack' => outcome[:stack],
- 'expose' => outcome[:expose],
- 'version' => outcome[:version],
- 'source' => reader.raw_content,
- 'registry' => outcome[:registry],
- 'services' => kontena_services,
- 'volumes' => kontena_volumes,
- 'variables' => outcome[:variables]
- }
- stack
- end
+ def values_from_value_options
+ @values_from_value_options ||= {}
+ end
- def stack_from_yaml(filename, name: nil, values: nil, defaults: nil)
- reader = reader_from_yaml(filename, name: name, values: values, defaults: defaults)
- stack_from_reader(reader)
- end
+ def values_from_options
+ @values_from_options ||= values_from_file.merge(values_from_value_options)
+ end
- def stack_read_and_dump(filename, name: nil, values: nil, defaults: nil)
- reader = reader_from_yaml(filename, name: name, values: values, defaults: defaults)
- stack = stack_from_reader(reader)
- dump_variables(reader) if values_to
- stack
- end
-
- def require_config_file(filename)
- exit_with_error("File #{filename} does not exist") unless File.exists?(filename)
- end
-
- def generate_volumes(yaml_volumes)
- return [] unless yaml_volumes
- yaml_volumes.map do |name, config|
- if config['external'].is_a?(TrueClass)
- config['external'] = name
- elsif config['external']['name']
- config['external'] = config['external']['name']
+ # Transforms a hash
+ # dependency_values_from_options('foo.bar' => 1, 'foo')
+ # => { 'bar' => 1 }
+ # Used for dependency variable injection
+ def dependency_values_from_options(name)
+ name_with_dot = name.to_s + '.'
+ values_from_options.each_with_object({}) do |kv_pair, obj|
+ key = kv_pair.first.to_s
+ value = kv_pair.last
+ next unless key.start_with?(name_with_dot)
+ obj[key.sub(name_with_dot, '')] = value
+ end
end
- config.merge('name' => name)
end
end
- def generate_services(yaml_services)
- return [] unless yaml_services
- yaml_services.map do |name, config|
- exit_with_error("Image is missing for #{name}. Aborting.") unless config['image'] # why isn't this a validation?
- ServiceGeneratorV2.new(config).generate.merge('name' => name)
- end
- end
-
- def set_env_variables(stack, grid)
+ # Sets environment variables from parameters
+ # @param stack [String] current stack name
+ # @param grid [String] current grid name
+ # @param platform [String] current platform name, defaults to param grid value
+ def set_env_variables(stack, grid, platform = grid)
ENV['STACK'] = stack
ENV['GRID'] = grid
+ ENV['PLATFORM'] = platform
end
# @return [String]
def current_dir
File.basename(Dir.getwd)
end
def display_notifications(messages, color = :yellow)
- $stderr.puts(Kontena.pastel.send(color, messages.to_yaml.gsub(/^---$/, '')))
+ $stderr.puts(pastel.send(color, messages.to_yaml.gsub(/^---$/, '')))
end
- def hint_on_validation_notifications(errors)
- $stderr.puts "YAML contains the following unsupported options and they were rejected:".colorize(:yellow)
- display_notifications(errors)
+ def hint_on_validation_notifications(notifications, filename = nil)
+ return if notifications.nil? || notifications.empty?
+ $stderr.puts pastel.yellow("#{"(#{filename}) " if filename}YAML contains the following unsupported options and they were rejected:")
+ display_notifications(notifications)
end
- def abort_on_validation_errors(errors)
- $stderr.puts "YAML validation failed! Aborting.".colorize(:red)
+ def abort_on_validation_errors(errors, filename = nil)
+ return if errors.nil? || errors.empty?
+ $stderr.puts pastel.red("#{"(#{filename}) " if filename} YAML validation failed! Aborting.")
display_notifications(errors, :red)
abort
end
+ # An accessor to stack registry client
+ # @return [Kontena::StacksClient]
def stacks_client
@stacks_client ||= Kontena::StacksClient.new(current_account.stacks_url, current_account.token, read_requires_token: current_account.stacks_read_authentication)
end
end
end