require_relative 'yaml/reader' require_relative '../services/services_helper' require_relative 'service_generator_v2' require_relative '../../stacks_client' module Kontena::Cli::Stacks module Common include Kontena::Cli::Services::ServicesHelper module StackNameParam attr_accessor :stack_version 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 end end module StackFileOrNameParam attr_accessor :from_registry def self.included(where) where.parameter "[FILE]", "Kontena stack file or a registry stack name (user/stack or user/stack:version)", default: "kontena.yml", attribute_name: :filename do |filename| if !File.exist?(filename) && filename =~ /\A[a-zA-Z0-9\_\.\-]+\/[a-zA-Z0-9\_\.\-]+(?::.*)?\z/ @from_registry = true else @from_registry = false require_config_file(filename) end filename end end end module StackNameOption def self.included(where) where.option ['-n', '--name'], 'NAME', 'Define stack name (by default comes from stack file)' end end module StackValuesFromOption attr_accessor :values def self.included(where) 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 end end end def stack_name @stack_name ||= self.name || stack_name_from_yaml(filename) end def reader_from_yaml(filename, from_registry: false, name: nil, values: nil) reader = Kontena::Cli::Stacks::YAML::Reader.new(filename, from_registry: from_registry, values: values) if reader.stack_name.nil? exit_with_error "Stack MUST have stack name in YAML top level field 'stack'! Aborting." end set_env_variables(name || reader.stack_name, current_grid) reader end def stack_from_yaml(filename, from_registry: false, name: nil, values: nil) reader = reader_from_yaml(filename, from_registry: from_registry, name: name, values: values) outcome = reader.execute hint_on_validation_notifications(outcome[:notifications]) if outcome[:notifications].size > 0 abort_on_validation_errors(outcome[:errors]) if outcome[:errors].size > 0 kontena_services = generate_services(outcome[:services], outcome[:version]) stack = { 'name' => outcome[:name], 'stack' => outcome[:stack], 'expose' => outcome[:expose], 'version' => outcome[:version], 'source' => reader.raw_content, 'registry' => 'file://', 'services' => kontena_services, 'variables' => outcome[:variables], 'vault_keys' => outcome[:vault_keys] } stack end def require_config_file(filename) exit_with_error("File #{filename} does not exist") unless File.exists?(filename) end ## # @param [Hash] yaml # @param [String] version # @return [Hash] def generate_services(yaml_services, version) services = [] generator_klass = ServiceGeneratorV2 yaml_services.each do |service_name, config| exit_with_error("Image is missing for #{service_name}. Aborting.") unless config['image'] service = generator_klass.new(config).generate service['name'] = service_name services << service end services end def set_env_variables(stack, grid) ENV['STACK'] = stack ENV['GRID'] = grid end # @return [String] def current_dir File.basename(Dir.getwd) end def display_notifications(messages, color = :yellow) messages.each do |files| files.each do |file, services| STDERR.puts "#{file}:".colorize(color) services.each do |service| service.each do |name, errors| STDERR.puts " #{name}:".colorize(color) if errors.is_a?(String) STDERR.puts " - #{errors}".colorize(color) else errors.each do |key, error| STDERR.puts " - #{key}: #{error.to_json}".colorize(color) end end end end end end end def hint_on_validation_notifications(errors) STDERR.puts "YAML contains the following unsupported options and they were rejected:".colorize(:yellow) display_notifications(errors) end def abort_on_validation_errors(errors) STDERR.puts "YAML validation failed! Aborting.".colorize(:red) display_notifications(errors, :red) abort end def stacks_client return @stacks_client if @stacks_client Kontena.run('cloud login') unless cloud_auth? config.reset_instance @stacks_client = Kontena::StacksClient.new(kontena_account.stacks_url, kontena_account.token) end end end