diff --git a/Gemfile.lock b/Gemfile.lock index 7bd508e..26a5e99 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - envirobly (1.10.0) + envirobly (1.11.0) activesupport (~> 8.0) aws-sdk-s3 (~> 1.182) concurrent-ruby (~> 1.3) diff --git a/lib/envirobly/cli/main.rb b/lib/envirobly/cli/main.rb index 78ba6d0..a9c8179 100644 --- a/lib/envirobly/cli/main.rb +++ b/lib/envirobly/cli/main.rb @@ -3,13 +3,15 @@ class Envirobly::Cli::Main < Envirobly::Base include Envirobly::Colorize + class_option :unattended, type: :boolean, default: false + desc "version", "Show Envirobly CLI version" method_option :pure, type: :boolean, default: false def version if options.pure - puts Envirobly::VERSION + say Envirobly::VERSION else - puts "envirobly CLI v#{Envirobly::VERSION}" + say "envirobly CLI v#{Envirobly::VERSION}" end end @@ -26,14 +28,28 @@ def signout say "You can sign in again with `envirobly signin`" end - desc "set_default_account", "Choose default account to deploy the current project to" - def set_default_account - Envirobly::Defaults::Account.new(shell:).require_value - end + desc "target [NAME]", "Configure deployment (default) target" + method_option :missing_only, type: :boolean, default: false + def target(name = nil) + Envirobly::AccessToken.new(shell:).require! + + target = Envirobly::Target.new(default_project_name: File.basename(Dir.pwd), shell:) + target.name = name if name.present? + + errors = target.errors :name + + if errors.any? + errors.each do |message| + shell.say_error message + end + + exit 1 + end - desc "set_default_region", "Set default region for the current project when deploying for the first time" - def set_default_region - Envirobly::Defaults::Region.new(shell:).require_value + target.configure!(missing_only: options.missing_only) + + shell.say "#{green_check} " + shell.say "Target configured.", :green end desc "validate", "Validates config (for given environ)" @@ -69,18 +85,17 @@ def instance_types(region = nil) table_data, borders: true end - desc "deploy [ENVIRON_NAME]", <<~TXT + desc "deploy [[TARGET/[ENVIRON_NAME]]", <<~TXT Deploy to environ identified by name. Name can contain letters, numbers, dashes or underscores. If environ name is left blank, current git branch name is used. TXT - method_option :account_id, type: :numeric + method_option :account_url, type: :string method_option :region, type: :string - method_option :project_id, type: :numeric method_option :project_name, type: :string method_option :commit, type: :string, default: "HEAD" method_option :dry_run, type: :boolean, default: false - def deploy(environ_name = nil) + def deploy(path = nil) commit = Envirobly::Git::Commit.new options.commit unless commit.exists? @@ -106,15 +121,8 @@ def deploy(environ_name = nil) Envirobly::AccessToken.new(shell:).require! - deployment = Envirobly::Deployment.new( - account_id: options.account_id, - region: options.region, - project_id: options.project_id, - project_name: options.project_name, - environ_name: environ_name.presence, - commit:, - shell: - ) + target = create_target(path:, commit:) + deployment = Envirobly::Deployment.new(target:, commit:, shell:) deployment.perform(dry_run: options.dry_run) end @@ -130,35 +138,65 @@ def pull(region, bucket, ref, path) Keep in mind, your container might not have a shell installed. In such cases you won't be able to start an interactive session. TXT - method_option :account_id, type: :numeric - method_option :project_id, type: :numeric + method_option :account_url, type: :string method_option :project_name, type: :string method_option :environ_name, type: :string method_option :instance_slot, type: :numeric, default: 0 method_option :shell, type: :string method_option :user, type: :string - def exec(service_name, *command) - Envirobly::ContainerShell.new(service_name, options, shell:).exec(command) + method_option :dry_run, type: :boolean, default: false + def exec(path, *command) + target = create_target(path:, context: :service) + + Envirobly::ContainerShell. + new(target:, instance_slot: options.instance_slot, shell:, exec_shell: options.shell, exec_user: options.user). + exec(command, dry_run: options.dry_run) end desc "rsync [SERVICE_NAME:]SOURCE_PATH [SERVICE_NAME:]DESTINATION_PATH", <<~TXT Synchronize files between you and your service's data volume. TXT - method_option :account_id, type: :numeric - method_option :project_id, type: :numeric + method_option :account_url, type: :string method_option :project_name, type: :string method_option :environ_name, type: :string method_option :args, type: :string, default: "-avzP" + method_option :dry_run, type: :boolean, default: false def rsync(source, destination) - service_name = nil - - [ source, destination ].each do |path| - if path =~ /\A([a-z0-9\-_]+):/i - service_name = $1 + path = nil + [ source, destination ].each do |arg| + if arg =~ /\A([a-z0-9\-_\/]+):/i + path = $1 break end end - Envirobly::ContainerShell.new(service_name, options, shell:).rsync(source, destination) + target = create_target(path:, context: :service) + + Envirobly::ContainerShell. + new(target:, shell:, rsync_args: options.args). + rsync(source, destination, path:, dry_run: options.dry_run) end + + private + def create_target(path:, commit: Envirobly::Git::Commit.new("HEAD"), context: nil) + target = Envirobly::Target.new( + path, + account_url: options.account_url, + project_name: options.project_name, + region: options.region, + default_project_name: File.basename(Dir.pwd), + default_environ_name: commit.current_branch, + shell:, + context: + ) + target.render_and_exit_on_errors! + + if options.unattended + target.save + else + target.configure!(missing_only: true) + end + + target + end end diff --git a/lib/envirobly/config.rb b/lib/envirobly/config.rb index 4568c81..b876183 100644 --- a/lib/envirobly/config.rb +++ b/lib/envirobly/config.rb @@ -4,7 +4,8 @@ module Envirobly class Config DIR = ".envirobly" BASE = "deploy.yml" - OVERRIDES_PATTERN = /deploy\.([a-z0-9\-_]+)\.yml/i + ENVIRON_OVERRIDE_REGEXP = /deploy\.([a-z0-9\-_]+)\.yml/i + TARGETS_PATH = Pathname.new(DIR).join(".targets") attr_reader :errors @@ -43,7 +44,7 @@ def merge(environ_name = nil) private def config_file?(file) - file == BASE || file.match?(OVERRIDES_PATTERN) + file == BASE || file.match?(ENVIRON_OVERRIDE_REGEXP) end def parse(content, path) diff --git a/lib/envirobly/container_shell.rb b/lib/envirobly/container_shell.rb index 1152162..aaaa39a 100644 --- a/lib/envirobly/container_shell.rb +++ b/lib/envirobly/container_shell.rb @@ -17,67 +17,48 @@ class ContainerShell ] USER_AND_HOST = "envirobly-service@%s" - attr_reader :options, :service_name - - def initialize(service_name, options, shell:) - @service_name = service_name - @options = options - - commit = Git::Commit.new "HEAD" - default_account = Defaults::Account.new(shell:) - default_project = Defaults::Project.new(shell:) - - target = Target.new( - default_account_id: default_account.value, - default_project_id: default_project.value, - default_project_name: Defaults::Project.dirname, - default_environ_name: commit.current_branch, - account_id: options.account_id, - project_id: options.project_id, - project_name: options.project_name, - environ_name: options.environ_name - ) - - if target.missing_params.include?(:account_id) - target.account_id = default_account.require_value - end - - target.ignored_params.each do |param| - shell.say "--#{param.to_s.parameterize} ignored, due to other arguments overriding it" - end - + def initialize(target:, shell:, instance_slot: 0, rsync_args: nil, exec_shell: nil, exec_user: nil) + @shell = shell + @rsync_args = rsync_args + @exec_shell = exec_shell + @exec_user = exec_user @params = { account_id: target.account_id, - project_id: target.project_id, project_name: target.project_name, environ_name: target.environ_name, - service_name:, - instance_slot: options.instance_slot || 0 + service_name: target.service_name, + instance_slot: instance_slot } - - if options.project_name.blank? && options.account_id.blank? && options.project_id.blank? - @params[:project_id] = Defaults::Project.new.value - end end - def exec(command = nil) + def exec(command = nil, dry_run: false) + do_dry_run if dry_run + with_private_key do system join(env_vars, ssh, user_and_host, command) end end - def rsync(source, destination) + def rsync(source, destination, path:, dry_run: false) + do_dry_run if dry_run + with_private_key do system join( env_vars, - %(rsync #{options.args} -e "#{ssh}"), - source.sub("#{service_name}:", "#{user_and_host}:"), - destination.sub("#{service_name}:", "#{user_and_host}:") + %(rsync #{@rsync_args} -e "#{ssh}"), + source.sub("#{path}:", "#{user_and_host}:"), + destination.sub("#{path}:", "#{user_and_host}:") ) end end private + def do_dry_run + @shell.say "Dry run", :green + @shell.say @params.to_yaml + exit + end + def join(*parts) parts.flatten.compact.join(" ") end @@ -110,12 +91,12 @@ def env_vars credentials.fetch("session_token") ) - if options.shell.present? - result = join "ENVIROBLY_SERVICE_INTERACTIVE_SHELL='#{options.shell}'", result + if @exec_shell.present? + result = join "ENVIROBLY_SERVICE_INTERACTIVE_SHELL='#{@exec_shell}'", result end - if options.user.present? - result = join "ENVIROBLY_SERVICE_SHELL_USER='#{options.user}'", result + if @exec_user.present? + result = join "ENVIROBLY_SERVICE_SHELL_USER='#{@exec_user}'", result end result diff --git a/lib/envirobly/defaults/account.rb b/lib/envirobly/defaults/account.rb deleted file mode 100644 index b9a6a53..0000000 --- a/lib/envirobly/defaults/account.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -class Envirobly::Defaults::Account < Envirobly::Default - include Envirobly::Colorize - - def require_value - api = Envirobly::Api.new - accounts = api.list_accounts - - if accounts.object.blank? - shell.say_error "Please connect an AWS account to your Envirobly account first." - exit 1 - end - - # If only one account exists, it will be used - id = accounts.object.first.fetch("id") - - if accounts.object.size > 1 - puts "Choose default account to deploy this project to:" - - data = [ [ "ID", "Name", "AWS number", "URL" ] ] + - accounts.object.pluck("id", "name", "aws_id", "url") - - shell.print_table data, borders: true - - limited_to = accounts.object.pluck("id").map(&:to_s) - - begin - id = shell.ask("Type in the account ID:", limited_to:).to_i - rescue Interrupt - shell.say_error "Cancelled" - exit - end - end - - save id - - shell.say "Account ##{id} set as project default " - shell.say green_check - - id - end -end diff --git a/lib/envirobly/defaults/project.rb b/lib/envirobly/defaults/project.rb deleted file mode 100644 index 284f597..0000000 --- a/lib/envirobly/defaults/project.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -class Envirobly::Defaults::Project < Envirobly::Default - def self.dirname - File.basename(Dir.pwd) - end -end diff --git a/lib/envirobly/defaults/region.rb b/lib/envirobly/defaults/region.rb deleted file mode 100644 index 7af25c0..0000000 --- a/lib/envirobly/defaults/region.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -class Envirobly::Defaults::Region < Envirobly::Default - include Envirobly::Colorize - - def require_value - api = Envirobly::Api.new - response = api.list_regions - - shell.say "Choose default project region to deploy to:" - shell.print_table [ [ "Name", "Location", "Group" ] ] + - response.object.pluck("code", "title", "group_title"), borders: true - - code = nil - limited_to = response.object.pluck("code") - - while code.nil? - begin - code = shell.ask("Type in the region name:", default: "us-east-1") - rescue Interrupt - shell.say_error "Cancelled" - exit - end - - unless code.in?(limited_to) - shell.say_error "'#{code}' is not a supported region, please try again" - code = nil - end - end - - save code - - shell.say "Region '#{code}' set as project default " - shell.say green_check - - code - end - - private - def cast_value(value) - value.to_s - end -end diff --git a/lib/envirobly/deployment.rb b/lib/envirobly/deployment.rb index db74e09..3611731 100644 --- a/lib/envirobly/deployment.rb +++ b/lib/envirobly/deployment.rb @@ -1,64 +1,34 @@ # frozen_string_literal: true -require "yaml" - module Envirobly class Deployment include Colorize attr_reader :params, :shell - def initialize(environ_name:, commit:, account_id:, project_name:, project_id:, region:, shell:) + def initialize(target:, commit:, shell:) + @target = target @commit = commit - @config = Config.new - @default_account = Defaults::Account.new(shell:) - @default_project = Defaults::Project.new(shell:) - @default_region = Defaults::Region.new(shell:) @shell = shell + @api = Api.new + @config = Config.new + end - target = Target.new( - default_account_id: @default_account.value, - default_project_id: @default_project.value, - default_region: @default_region.value, - default_project_name: Defaults::Project.dirname, - default_environ_name: commit.current_branch, - account_id:, - project_id:, - region:, - project_name:, - environ_name: - ) - - if target.missing_params.include?(:account_id) - target.account_id = @default_account.require_value - end - - if target.missing_params.include?(:region) - target.region = @default_region.require_value - end - - target.ignored_params.each do |param| - shell.say "--#{param.to_s.parameterize} ignored, due to other arguments overriding it" - end - - @environ_name = target.environ_name - @params = { - account_id: target.account_id, - project_id: target.project_id, - project_name: target.project_name, - region: target.region, + def perform(dry_run:) + params = { + account_id: @target.account_id, + project_name: @target.project_name, + region: @target.region, deployment: { - environ_name: target.environ_name, + environ_name: @target.environ_name, commit_ref: @commit.ref, commit_time: @commit.time, commit_message: @commit.message, object_tree_checksum: @commit.object_tree_checksum, - config: @config.merge(@environ_name).to_yaml + config: @config.merge(@target.environ_name).to_yaml } } - end - def perform(dry_run:) if dry_run shell.say "This is a dry run, nothing will be deployed.", :green end @@ -72,17 +42,16 @@ def perform(dry_run:) if dry_run puts green("Config:") - puts @params[:deployment][:config] + puts params[:deployment][:config] shell.say - shell.say "Targeting:", :green + shell.say "Target:", :green targets_and_values = [ - [ "Account ID", @params[:account_id].to_s ], - [ "Project ID", @params[:project_id].to_s ], - [ "Region", @params[:region] ], - [ "Project Name", @params[:project_name] ], - [ "Environ Name", @params[:deployment][:environ_name] ] + [ "Account", @target.account_url ], + [ "Region", params[:region] ], + [ "Project", params[:project_name] ], + [ "Environ", params[:deployment][:environ_name] ] ] shell.print_table targets_and_values, borders: true @@ -90,36 +59,30 @@ def perform(dry_run:) return end - # Create deployment - api = Api.new - Duration.measure do - response = api.create_deployment @params + # Create deployment + response = @api.create_deployment params print "Preparing project..." - @default_account.save_if_none response.object.fetch("account_id") - @default_project.save_if_none response.object.fetch("project_id") - @default_region.save_if_none response.object.fetch("region") - # Fetch credentials for build context upload @deployment_url = response.object.fetch("url") - @credentials_response = api.get_deployment_with_delay_and_retry @deployment_url + @credentials_response = @api.get_deployment_with_delay_and_retry @deployment_url end credentials = @credentials_response.object.fetch("credentials") region = @credentials_response.object.fetch("region") bucket = @credentials_response.object.fetch("bucket") - watch_deployment_url = @credentials_response.object.fetch("deployment_url") Duration.measure do # Upload build context Aws::S3.new(bucket:, region:, credentials:).push @commit # Perform deployment - api.put_as_json @deployment_url + @api.put_as_json @deployment_url end + watch_deployment_url = @credentials_response.object.fetch("deployment_url") puts "Follow at #{watch_deployment_url}" end end diff --git a/lib/envirobly/name.rb b/lib/envirobly/name.rb new file mode 100644 index 0000000..056d24d --- /dev/null +++ b/lib/envirobly/name.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Envirobly + class Name + NAME_FORMAT = /\A[a-z0-9\-_]+\z/i + ERROR_MESSAGE = "must contain only alphanumerical characters, dashes and underscores" + + attr_reader :error + + def initialize(name) + @name = name + @error = nil + end + + def validate + if @name =~ NAME_FORMAT + true + else + @error = ERROR_MESSAGE + false + end + end + end +end diff --git a/lib/envirobly/target.rb b/lib/envirobly/target.rb index eb7592d..09eb1c7 100644 --- a/lib/envirobly/target.rb +++ b/lib/envirobly/target.rb @@ -2,60 +2,88 @@ module Envirobly class Target - attr_accessor :account_id, :project_id, :region + attr_accessor :account_url, :project_name, :region, :name + attr_reader :service_name, :shell + + DEFAULT_NAME = ".default" def initialize( - default_account_id: nil, - default_project_id: nil, - default_region: nil, - default_project_name: nil, - default_environ_name: nil, - account_id: nil, - project_id: nil, + path = nil, + account_url: nil, region: nil, project_name: nil, - environ_name: nil + default_environ_name: nil, + default_project_name: nil, + config_path: Config::TARGETS_PATH, + context: nil, + shell: nil ) - @default_account_id = default_account_id - @default_project_id = default_project_id - @default_region = default_region - @default_project_name = default_project_name - @default_environ_name = default_environ_name - @account_id = account_id - @project_id = project_id + @account_url = account_url @region = region @project_name = project_name - @environ_name = environ_name + @default_environ_name = default_environ_name + @default_project_name = default_project_name + @config_path = config_path + @context = context + @name = DEFAULT_NAME + @shell = shell + + load_path path + end + + def errors(attributes = %i[ name project_name environ_name ]) + [].tap do |result| + Array(attributes).each_with_index do |attr, index| + value = send attr + + next if index.zero? && value == DEFAULT_NAME + + name = Name.new(value) + + unless name.validate + result << "Name '#{value}' #{name.error}" + end + end + end.uniq + end + + def render_and_exit_on_errors! + messages = errors + return if messages.empty? + + messages.each do |message| + shell.say_error message + end + + exit 1 end def missing_params [].tap do |result| - if project_id.blank? && account_id.blank? - result << :account_id + if account_url.blank? + result << :account_url end - if project_id.blank? && region.blank? + if region.blank? result << :region end end end - def account_id - return if @project_id - - @account_id || @default_account_id + def account_url + @account_url.presence || stored_value_for("account_url") end - def project_id - return if @project_id.blank? && (@account_id.present? || @project_name.present?) - - @project_id || @default_project_id + def account_id + if account_url =~ /accounts\/(\d)+/i + $1.to_i + else + nil + end end def project_name - return if @project_id - - @project_name.presence || @default_project_name + @project_name.presence || stored_value_for("project_name").presence || @default_project_name end def environ_name @@ -63,25 +91,158 @@ def environ_name end def region - return if @project_id + @region.presence || stored_value_for("region") + end - @region || @default_region + def save + save_attribute "account_url" + save_attribute "project_name" + save_attribute "region" end - def ignored_params - [].tap do |result| - if @account_id && @project_id - result << :account_id + def configure!(missing_only: false) + configure_account unless missing_only && stored_value_for("account_url").present? + configure_project_name unless missing_only && stored_value_for("project_name").present? + configure_region unless missing_only && stored_value_for("region").present? + end + + private + def storage_dir + @config_path.join(@name) + end + + def stored_value_for(type) + File.read(storage_dir.join(type)).strip + rescue Errno::ENOENT + nil + end + + def save_attribute(type) + FileUtils.mkdir_p storage_dir + File.write storage_dir.join(type), send(type) + end + + def load_path(path) + return if path.blank? + + parts = path.split("/").map &:strip + + if @context == :service + case parts.size + when 1 + @service_name = parts.first + when 2 + @environ_name, @service_name = parts + when 3 + @name, @environ_name, @service_name = parts + @default_project_name = @name + end + + return end - if @project_id && @region - result << :region + case parts.size + when 1 + if path.end_with?("/") + @name = parts.first + @default_project_name = parts.first + else + @environ_name = parts.first + end + when 2 + @name, @environ_name = parts + @default_project_name = @name end + end - if @project_id && @project_name.present? - result << :project_name + def configure_account + shell.say "Configuring " + shell.say "#{@name} ", :green + shell.say "deploy target" + shell.say + + api = Envirobly::Api.new + accounts = api.list_accounts + + if accounts.object.blank? + shell.say_error "Please connect an AWS account to your Envirobly account first." + exit 1 end + + data = [ [ "ID", "Name", "AWS number", "URL" ] ] + + accounts.object.pluck("id", "name", "aws_id", "url") + + shell.say "Available accounts:" + shell.print_table data, borders: true + + limited_to = accounts.object.pluck("id").map(&:to_s) + account_id = send(:account_id).to_s.presence || limited_to.first + + begin + account_id = shell.ask("Choose Account ID:", limited_to:, default: account_id).to_i + rescue Interrupt + shell.say_error "Cancelled", :red + exit + end + + accounts.object.each do |account| + if account_id == account["id"] + @account_url = account["url"] + break + end + end + + save_attribute "account_url" + end + + def configure_project_name + result = nil + + while result.nil? + begin + result = shell.ask("Name your project:", default: project_name) + rescue interrupt + shell.say_error "cancelled", :red + end + + name = Name.new(result) + unless name.validate + result = nil + shell.say_error "Name #{name.error}" + end + end + + @project_name = result + save_attribute "project_name" + end + + def configure_region + api = Envirobly::Api.new + response = api.list_regions + + shell.say "Choose region:" + shell.print_table [ [ "Name", "Location", "Group" ] ] + + response.object.pluck("code", "title", "group_title"), borders: true + + code = nil + limited_to = response.object.pluck("code") + + while code.nil? + begin + code = shell.ask("Region name:", default: region.presence || "us-east-1") + rescue Interrupt + shell.say_error "Cancelled", :red + exit + end + + unless code.in?(limited_to) + shell.say_error "'#{code}' is not a supported region, please try again" + code = nil + end + end + + @region = code + save_attribute "region" end - end end end diff --git a/lib/envirobly/version.rb b/lib/envirobly/version.rb index 9dacd60..3ef598e 100644 --- a/lib/envirobly/version.rb +++ b/lib/envirobly/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Envirobly - VERSION = "1.10.0" + VERSION = "1.11.0" end diff --git a/test/envirobly/name_test.rb b/test/envirobly/name_test.rb new file mode 100644 index 0000000..80eb691 --- /dev/null +++ b/test/envirobly/name_test.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "test_helper" + +module Envirobly + class NameTest < ActiveSupport::TestCase + test "successful validate" do + name = Name.new("produc-tio_n") + assert name.validate + assert_nil name.error + end + + test "failed validate" do + name = Name.new("!") + assert_not name.validate + assert_not_empty name.error + end + end +end diff --git a/test/envirobly/target_test.rb b/test/envirobly/target_test.rb index 4a60442..ca17ce0 100644 --- a/test/envirobly/target_test.rb +++ b/test/envirobly/target_test.rb @@ -5,134 +5,154 @@ module Envirobly class TargetTest < ActiveSupport::TestCase test "all defaults exist, no overrides" do - target = Target.new( - default_account_id: 1, - default_project_id: 2, - default_region: "eu-north-1" - ) + target = Target.new config_path: Pathname.new("test/fixtures/targets"), default_project_name: "123", default_environ_name: "abcd" + assert_equal "https://example.com/accounts/1", target.account_url assert_equal 1, target.account_id - assert_equal 2, target.project_id + assert_equal "world", target.project_name + assert_equal "abcd", target.environ_name assert_equal "eu-north-1", target.region + assert_nil target.service_name + assert_equal ".default", target.name + assert_empty target.errors end - test "all defaults exist, override account_id" do + test "all defaults exist, override account_url" do target = Target.new( - default_account_id: 1, - default_project_id: 2, - default_region: "eu-north-1", - account_id: 3 + account_url: "https://example.com/accounts/3", + config_path: Pathname.new("test/fixtures/targets") ) + assert_equal "https://example.com/accounts/3", target.account_url assert_equal 3, target.account_id - assert_nil target.project_id + assert_equal "world", target.project_name assert_equal "eu-north-1", target.region end - test "all defaults exist, override project_id" do - target = Target.new( - default_account_id: 1, - default_project_id: 2, - default_region: "eu-north-1", - project_id: 3 - ) - assert_nil target.account_id - assert_equal 3, target.project_id - assert_nil target.region - end - - test "ignored_params when project_id is specified" do + test "all defaults exist, override project_name" do target = Target.new( - default_account_id: 1, - default_project_id: 2, default_project_name: "dirname", - project_name: "custom", - account_id: 6, - project_id: 3, - region: "us-east-2" + project_name: "home", + config_path: Pathname.new("test/fixtures/targets") ) - assert_equal %i[ account_id region project_name ], target.ignored_params - assert_empty target.missing_params - - target = Target.new( - project_id: 3, - region: "us-east-2" - ) - assert_equal %i[ region ], target.ignored_params - assert_empty target.missing_params - - target = Target.new( - project_id: 3 - ) - assert_empty target.ignored_params - assert_empty target.missing_params + assert_equal 1, target.account_id + assert_equal "home", target.project_name + assert_equal "eu-north-1", target.region end test "missing_params" do target = Target.new - assert_equal %i[ account_id region ], target.missing_params + assert_equal %i[ account_url region ], target.missing_params - target.account_id = 6 + target.account_url = "https://example.com/accounts/3" + assert_equal 3, target.account_id assert_equal %i[ region ], target.missing_params target.region = "us-east-2" assert_empty target.missing_params end - test "project_name overrides default_project_id and default_project_name" do + test "default_project_name and default_environ_name and no other defaults" do target = Target.new( - default_account_id: 1, - default_project_id: 2, default_project_name: "dirname", - project_name: "custom" + default_environ_name: "main" ) - assert_equal 1, target.account_id - assert_nil target.project_id - assert_equal "custom", target.project_name - assert_equal %i[ region ], target.missing_params + assert_nil target.account_id + assert_equal "dirname", target.project_name + assert_equal "main", target.environ_name + assert_nil target.region + assert_equal %i[ account_url region ], target.missing_params + end + + test "save default" do + config_path = Pathname.new Dir.mktmpdir + target = Target.new(config_path:, account_url: "https://example.com/accounts/3", region: "us-east-2", project_name: "yes") + target.save + assert_equal "https://example.com/accounts/3", File.read(config_path.join ".default/account_url") + assert_equal "us-east-2", File.read(config_path.join ".default/region") + assert_equal "yes", File.read(config_path.join ".default/project_name") + ensure + FileUtils.rm_rf config_path + end + + test "save custom name" do + config_path = Pathname.new Dir.mktmpdir + target = Target.new("factory/main", + config_path:, account_url: "https://example.com/accounts/3", region: "us-east-2", project_name: "yes") + target.save + assert_equal "https://example.com/accounts/3", File.read(config_path.join "factory/account_url") + assert_equal "us-east-2", File.read(config_path.join "factory/region") + assert_equal "yes", File.read(config_path.join "factory/project_name") + ensure + FileUtils.rm_rf config_path end - test "project_id overrides project_name" do + test "path as environ name overrides default" do target = Target.new( - default_account_id: 1, - default_project_id: 2, + "production", default_project_name: "dirname", - project_name: "custom", - project_id: 3 + default_environ_name: "main" ) + assert_nil target.account_url assert_nil target.account_id - assert_equal 3, target.project_id - assert_nil target.project_name + assert_equal "dirname", target.project_name + assert_equal "production", target.environ_name assert_nil target.region - assert_empty target.missing_params - assert_equal %i[ project_name ], target.ignored_params + assert_equal %i[ account_url region ], target.missing_params + assert_nil target.service_name + assert_equal ".default", target.name end - test "default_project_name and default_environ_name and no other defaults" do + test "target name prefix in path set default project name" do target = Target.new( + "candy/production", default_project_name: "dirname", default_environ_name: "main" ) - assert_nil target.account_id - assert_nil target.project_id - assert_equal "dirname", target.project_name - assert_equal "main", target.environ_name - assert_nil target.region - assert_equal %i[ account_id region ], target.missing_params - assert_empty target.ignored_params + assert_equal "candy", target.project_name + assert_equal "production", target.environ_name end - test "environ_name override" do + test "target name prefix in path set default project name, overriden by project_name arg" do target = Target.new( + "candy/production", default_project_name: "dirname", default_environ_name: "main", - environ_name: "production" + project_name: "stars" ) - assert_nil target.account_id - assert_nil target.project_id - assert_equal "dirname", target.project_name + assert_equal "stars", target.project_name assert_equal "production", target.environ_name - assert_nil target.region - assert_equal %i[ account_id region ], target.missing_params - assert_empty target.ignored_params + end + + test "service name as path in the service context" do + target = Target.new("puma", context: :service) + assert_equal "puma", target.service_name + end + + test "environ name and service name as path in the service context" do + target = Target.new("production/puma", context: :service, default_environ_name: "main") + assert_equal "puma", target.service_name + assert_equal "production", target.environ_name + end + + test "target name, environ name and service name as path in the service context" do + target = Target.new("factory/production/puma", context: :service, default_environ_name: "main", default_project_name: "dir") + assert_equal "factory", target.name + assert_equal "puma", target.service_name + assert_equal "production", target.environ_name + assert_equal "factory", target.project_name + end + + test "path with / suffix means specifying target name only" do + target = Target.new("factory/", default_environ_name: "main", default_project_name: "dir") + assert_equal "factory", target.name + assert_equal "main", target.environ_name + assert_equal "factory", target.project_name + end + + test "name validation" do + target = Target.new("factory!/production@") + assert_equal 2, target.errors.size + assert_equal "Name 'factory!' #{Name::ERROR_MESSAGE}", target.errors.first + assert_equal "Name 'production@' #{Name::ERROR_MESSAGE}", target.errors.second end end end diff --git a/test/fixtures/targets/.default/account_url b/test/fixtures/targets/.default/account_url new file mode 100644 index 0000000..403996f --- /dev/null +++ b/test/fixtures/targets/.default/account_url @@ -0,0 +1 @@ +https://example.com/accounts/1 diff --git a/test/fixtures/targets/.default/project_name b/test/fixtures/targets/.default/project_name new file mode 100644 index 0000000..cc628cc --- /dev/null +++ b/test/fixtures/targets/.default/project_name @@ -0,0 +1 @@ +world diff --git a/test/fixtures/targets/.default/region b/test/fixtures/targets/.default/region new file mode 100644 index 0000000..eaf877f --- /dev/null +++ b/test/fixtures/targets/.default/region @@ -0,0 +1 @@ +eu-north-1