diff --git a/lib/mrsk/cli/app.rb b/lib/mrsk/cli/app.rb index f009d5f77..25e82dce7 100644 --- a/lib/mrsk/cli/app.rb +++ b/lib/mrsk/cli/app.rb @@ -7,24 +7,26 @@ def boot cli = self - MRSK.config.roles.each do |role| - on(role.hosts) do |host| - execute *MRSK.auditor.record("Booted app version #{version}"), verbosity: :debug + on(MRSK.hosts) do |host| + roles = MRSK.roles_on(host) + + roles.each do |role| + execute *MRSK.auditor(role: role).record("Booted app version #{version}"), verbosity: :debug begin - old_version = capture_with_info(*MRSK.app.current_running_version).strip - execute *MRSK.app.run(role: role.name) + old_version = capture_with_info(*MRSK.app(role: role).current_running_version).strip + execute *MRSK.app(role: role).run sleep MRSK.config.readiness_delay - execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false if old_version.present? + execute *MRSK.app(role: role).stop(version: old_version), raise_on_non_zero_exit: false if old_version.present? rescue SSHKit::Command::Failed => e if e.message =~ /already in use/ error "Rebooting container with same version #{version} already deployed on #{host} (may cause gap in zero-downtime promise!)" - execute *MRSK.auditor.record("Rebooted app version #{version}"), verbosity: :debug + execute *MRSK.auditor(role: role).record("Rebooted app version #{version}"), verbosity: :debug - execute *MRSK.app.stop(version: version) - execute *MRSK.app.remove_container(version: version) - execute *MRSK.app.run(role: role.name) + execute *MRSK.app(role: role).stop(version: version) + execute *MRSK.app(role: role).remove_container(version: version) + execute *MRSK.app(role: role).run else raise end @@ -36,24 +38,38 @@ def boot desc "start", "Start existing app container on servers" def start - on(MRSK.hosts) do - execute *MRSK.auditor.record("Started app version #{MRSK.config.version}"), verbosity: :debug - execute *MRSK.app.start, raise_on_non_zero_exit: false + on(MRSK.hosts) do |host| + roles = MRSK.roles_on(host) + + roles.each do |role| + execute *MRSK.auditor.record("Started app version #{MRSK.config.version}"), verbosity: :debug + execute *MRSK.app(role: role).start, raise_on_non_zero_exit: false + end end end desc "stop", "Stop app container on servers" def stop - on(MRSK.hosts) do - execute *MRSK.auditor.record("Stopped app"), verbosity: :debug - execute *MRSK.app.stop, raise_on_non_zero_exit: false + on(MRSK.hosts) do |host| + roles = MRSK.roles_on(host) + + roles.each do |role| + execute *MRSK.auditor(role: role).record("Stopped app"), verbosity: :debug + execute *MRSK.app(role: role).stop, raise_on_non_zero_exit: false + end end end # FIXME: Drop in favor of just containers? desc "details", "Show details about app containers" def details - on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.info) } + on(MRSK.hosts) do |host| + roles = MRSK.roles_on(host) + + roles.each do |role| + puts_by_host host, capture_with_info(*MRSK.app(role: role).info) + end + end end desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)" @@ -65,7 +81,7 @@ def exec(cmd) say "Get current version of running container...", :magenta unless options[:version] using_version(options[:version] || current_running_version) do |version| say "Launching interactive command with version #{version} via SSH from existing container on #{MRSK.primary_host}...", :magenta - run_locally { exec MRSK.app.execute_in_existing_container_over_ssh(cmd, host: MRSK.primary_host) } + run_locally { exec MRSK.app(role: "web").execute_in_existing_container_over_ssh(cmd, host: MRSK.primary_host) } end when options[:interactive] @@ -81,8 +97,12 @@ def exec(cmd) say "Launching command with version #{version} from existing container...", :magenta on(MRSK.hosts) do |host| - execute *MRSK.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug - puts_by_host host, capture_with_info(*MRSK.app.execute_in_existing_container(cmd)) + roles = MRSK.roles_on(host) + + roles.each do |role| + execute *MRSK.auditor(role: role).record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug + puts_by_host host, capture_with_info(*MRSK.app(role: role).execute_in_existing_container(cmd)) + end end end @@ -147,17 +167,25 @@ def remove desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true def remove_container(version) - on(MRSK.hosts) do - execute *MRSK.auditor.record("Removed app container with version #{version}"), verbosity: :debug - execute *MRSK.app.remove_container(version: version) + on(MRSK.hosts) do |host| + roles = MRSK.roles_on(host) + + roles.each do |role| + execute *MRSK.auditor(role: role).record("Removed app container with version #{version}"), verbosity: :debug + execute *MRSK.app(role: role).remove_container(version: version) + end end end desc "remove_containers", "Remove all app containers from servers", hide: true def remove_containers - on(MRSK.hosts) do - execute *MRSK.auditor.record("Removed all app containers"), verbosity: :debug - execute *MRSK.app.remove_containers + on(MRSK.hosts) do |host| + roles = MRSK.roles_on(host) + + roles.each do |role| + execute *MRSK.auditor(role: role).record("Removed all app containers"), verbosity: :debug + execute *MRSK.app(role: role).remove_containers + end end end diff --git a/lib/mrsk/commander.rb b/lib/mrsk/commander.rb index 5aa24fc78..47a4bf262 100644 --- a/lib/mrsk/commander.rb +++ b/lib/mrsk/commander.rb @@ -8,7 +8,6 @@ def initialize self.verbosity = :info end - def config @config ||= Mrsk::Configuration.create_from(**@config_kwargs).tap do |config| @config_kwargs = nil @@ -20,23 +19,38 @@ def configure(**kwargs) @config, @config_kwargs = nil, kwargs end - - attr_accessor :specific_hosts + attr_reader :specific_roles, :specific_hosts def specific_primary! self.specific_hosts = [ config.primary_web_host ] end def specific_roles=(role_names) - self.specific_hosts = config.roles.select { |r| role_names.include?(r.name) }.flat_map(&:hosts) if role_names.present? + @specific_roles = config.roles.select { |r| role_names.include?(r.name) } if role_names.present? + end + + def specific_hosts=(hosts) + @specific_hosts = config.all_hosts & hosts if hosts.present? end def primary_host specific_hosts&.first || config.primary_web_host end + def roles + (specific_roles || config.roles).select do |role| + ((specific_hosts || config.all_hosts) & role.hosts).any? + end + end + def hosts - specific_hosts || config.all_hosts + (specific_hosts || config.all_hosts).select do |host| + (specific_roles || config.roles).flat_map(&:hosts).include?(host) + end + end + + def roles_on(host) + roles.select { |role| role.hosts.include?(host.to_s) }.map(&:name) end def traefik_hosts @@ -52,16 +66,16 @@ def accessory_names end - def app - @app ||= Mrsk::Commands::App.new(config) + def app(role: nil) + Mrsk::Commands::App.new(config, role: role) end def accessory(name) Mrsk::Commands::Accessory.new(config, name: name) end - def auditor - @auditor ||= Mrsk::Commands::Auditor.new(config) + def auditor(role: nil) + Mrsk::Commands::Auditor.new(config, role: role) end def builder diff --git a/lib/mrsk/commands/app.rb b/lib/mrsk/commands/app.rb index ad0a21d7c..9f9e987cb 100644 --- a/lib/mrsk/commands/app.rb +++ b/lib/mrsk/commands/app.rb @@ -1,13 +1,20 @@ class Mrsk::Commands::App < Mrsk::Commands::Base - def run(role: :web) - role = config.role(role) + attr_reader :role + + def initialize(config, role: nil) + super(config) + @role = role + end + + def run + role = config.role(self.role) docker :run, "--detach", "--restart unless-stopped", "--log-opt", "max-size=#{MAX_LOG_SIZE}", - "--name", service_with_version_and_destination, - "-e", "MRSK_CONTAINER_NAME=\"#{service_with_version_and_destination}\"", + "--name", service_with_version_and_destination_and_role, + "-e", "MRSK_CONTAINER_NAME=\"#{service_with_version_and_destination_and_role}\"", *role.env_args, *config.volume_args, *role.label_args, @@ -17,7 +24,7 @@ def run(role: :web) end def start - docker :start, service_with_version_and_destination + docker :start, service_with_version_and_destination_and_role end def stop(version: nil) @@ -52,7 +59,7 @@ def follow_logs(host:, grep: nil) def execute_in_existing_container(*command, interactive: false) docker :exec, ("-it" if interactive), - service_with_version_and_destination, + service_with_version_and_destination_and_role, *command end @@ -97,7 +104,7 @@ def list_container_names def remove_container(version:) pipe \ - container_id_for(container_name: service_with_version_and_destination(version)), + container_id_for(container_name: service_with_version_and_destination_and_role(version)), xargs(docker(:container, :rm)) end @@ -115,12 +122,12 @@ def remove_images private - def service_with_version_and_destination(version = nil) - [ config.service, config.destination, version || config.version ].compact.join("-") + def service_with_version_and_destination_and_role(version = nil) + [ config.service, role, config.destination, version || config.version ].compact.join("-") end def container_id_for_version(version) - container_id_for(container_name: service_with_version_and_destination(version)) + container_id_for(container_name: service_with_version_and_destination_and_role(version)) end def filter_args @@ -130,6 +137,7 @@ def filter_args def filters [ "label=service=#{config.service}" ].tap do |filters| filters << "label=destination=#{config.destination}" if config.destination + filters << "label=role=#{role}" if role end end end diff --git a/lib/mrsk/commands/auditor.rb b/lib/mrsk/commands/auditor.rb index a0bc00765..9d6cf4145 100644 --- a/lib/mrsk/commands/auditor.rb +++ b/lib/mrsk/commands/auditor.rb @@ -1,6 +1,13 @@ require "active_support/core_ext/time/conversions" class Mrsk::Commands::Auditor < Mrsk::Commands::Base + attr_reader :role + + def initialize(config, role: nil) + super(config) + @role = role + end + # Runs remotely def record(line) append \ @@ -25,11 +32,15 @@ def audit_log_file end def tagged_record_line(line) - "'#{recorded_at_tag} #{performer_tag} #{line}'" + quote [recorded_at_tag, performer_tag, role_tag, line].compact.join(" ") end def tagged_broadcast_line(line) - "'#{performer_tag} #{line}'" + quote [performer_tag, role_tag, line].compact.join(" ") + end + + def role_tag + "[#{role}]" if role end def performer_tag @@ -39,4 +50,8 @@ def performer_tag def recorded_at_tag "[#{Time.now.to_fs(:db)}]" end + + def quote(tagged_line) + "'#{tagged_line}'" + end end diff --git a/lib/mrsk/configuration.rb b/lib/mrsk/configuration.rb index 074c7cf32..71f56b375 100644 --- a/lib/mrsk/configuration.rb +++ b/lib/mrsk/configuration.rb @@ -76,7 +76,7 @@ def accessory(name) def all_hosts - roles.flat_map(&:hosts) + roles.flat_map(&:hosts).uniq end def primary_web_host @@ -84,7 +84,7 @@ def primary_web_host end def traefik_hosts - roles.select(&:running_traefik?).flat_map(&:hosts) + roles.select(&:running_traefik?).flat_map(&:hosts).uniq end diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index b2b6786a1..fa1320078 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -3,14 +3,11 @@ class CliAppTest < CliTestCase test "boot" do # Stub current version fetch - SSHKit::Backend::Abstract.any_instance.stubs(:capture) - .returns("999") # new version - .then - .returns("123") # old version + SSHKit::Backend::Abstract.any_instance.stubs(:capture).returns("123") # old version run_command("boot").tap do |output| assert_match "docker run --detach --restart unless-stopped", output - assert_match "docker container ls --all --filter name=app-123 --quiet | xargs docker stop", output + assert_match "docker container ls --all --filter name=app-web-123 --quiet | xargs docker stop", output end end @@ -20,16 +17,14 @@ class CliAppTest < CliTestCase # Prevent expected failures from outputting to terminal Thread.report_on_exception = false - MRSK.app.stubs(:run) - .raises(SSHKit::Command::Failed.new("already in use")) - .then + Mrsk::Commands::App.any_instance.stubs(:run) .raises(SSHKit::Command::Failed.new("already in use")) .then .returns([ :docker, :run ]) run_command("boot").tap do |output| assert_match "Rebooting container with same version latest already deployed", output # Can't start what's already running - assert_match "docker container ls --all --filter name=app-latest --quiet | xargs docker container rm", output # Remove old container + assert_match "docker container ls --all --filter name=app-web-latest --quiet | xargs docker container rm", output # Remove old container assert_match "docker run", output # Start new container end ensure @@ -38,32 +33,58 @@ class CliAppTest < CliTestCase test "start" do run_command("start").tap do |output| - assert_match "docker start app-999", output + assert_match "docker start app-web-999", output end end test "stop" do run_command("stop").tap do |output| - assert_match "docker ps --quiet --filter label=service=app | xargs docker stop", output + assert_match "docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker stop", output end end test "details" do run_command("details").tap do |output| - assert_match "docker ps --filter label=service=app", output + assert_match "docker ps --filter label=service=app --filter label=role=web", output + end + end + + test "remove" do + run_command("remove").tap do |output| + assert_match /#{Regexp.escape("docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker stop")}/, output + assert_match /#{Regexp.escape("docker container prune --force --filter label=service=app")}/, output + assert_match /#{Regexp.escape("docker image prune --all --force --filter label=service=app")}/, output + end + end + + test "remove_container" do + run_command("remove_container", "1234567").tap do |output| + assert_match "docker container ls --all --filter name=app-web-1234567 --quiet | xargs docker container rm", output + end + end + + test "remove_containers" do + run_command("remove_containers").tap do |output| + assert_match "docker container prune --force --filter label=service=app", output + end + end + + test "remove_images" do + run_command("remove_images").tap do |output| + assert_match "docker image prune --all --force --filter label=service=app", output end end test "exec" do run_command("exec", "ruby -v").tap do |output| - assert_match "ruby -v", output + assert_match "docker run --rm dhh/app:latest ruby -v", output end end test "exec with reuse" do run_command("exec", "--reuse", "ruby -v").tap do |output| assert_match "docker ps --filter label=service=app --format \"{{.Names}}\" | sed 's/-/\\n/g' | tail -n 1", output # Get current version - assert_match "docker exec app-999 ruby -v", output + assert_match "docker exec app-web-999 ruby -v", output end end @@ -93,32 +114,6 @@ class CliAppTest < CliTestCase assert_match "docker ps --quiet --filter label=service=app | xargs docker logs --timestamps --tail 10 --follow 2>&1", run_command("logs", "--follow") end - test "remove" do - Mrsk::Cli::App.any_instance.expects(:stop) - Mrsk::Cli::App.any_instance.expects(:remove_containers) - Mrsk::Cli::App.any_instance.expects(:remove_images) - - run_command("remove") - end - - test "remove_container" do - run_command("remove_container", "1234567").tap do |output| - assert_match "docker container ls --all --filter name=app-1234567 --quiet | xargs docker container rm", output - end - end - - test "remove_containers" do - run_command("remove_containers").tap do |output| - assert_match "docker container prune --force --filter label=service=app", output - end - end - - test "remove_images" do - run_command("remove_images").tap do |output| - assert_match "docker image prune --all --force --filter label=service=app", output - end - end - test "version" do run_command("version").tap do |output| assert_match "docker ps --filter label=service=app --format \"{{.Names}}\" | sed 's/-/\\n/g' | tail -n 1", output @@ -127,6 +122,6 @@ class CliAppTest < CliTestCase private def run_command(*command) - stdouted { Mrsk::Cli::App.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) } + stdouted { Mrsk::Cli::App.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml", "--hosts", "1.1.1.1"]) } end end diff --git a/test/cli/main_test.rb b/test/cli/main_test.rb index d4aa3d820..8c2834a6c 100644 --- a/test/cli/main_test.rb +++ b/test/cli/main_test.rb @@ -88,7 +88,7 @@ class CliMainTest < CliTestCase test "rollback good version" do Mrsk::Cli::Main.any_instance.stubs(:container_name_available?).returns(true) - SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info).with(:docker, :ps, "--filter", "label=service=app", "--format", "\"{{.Names}}\"", "|", "sed 's/-/\\n/g'", "|", "tail -n 1").returns("version-to-rollback\n").times(2) + SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info).with(:docker, :ps, "--filter", "label=service=app", "--format", "\"{{.Names}}\"", "|", "sed 's/-/\\n/g'", "|", "tail -n 1").returns("version-to-rollback\n").at_least_once run_command("rollback", "123").tap do |output| assert_match "Start version 123", output @@ -99,7 +99,7 @@ class CliMainTest < CliTestCase test "rollback without old version" do Mrsk::Cli::Main.any_instance.stubs(:container_name_available?).returns(true) - SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info).with(:docker, :ps, "--filter", "label=service=app", "--format", "\"{{.Names}}\"", "|", "sed 's/-/\\n/g'", "|", "tail -n 1").returns("").times(2) + SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info).with(:docker, :ps, "--filter", "label=service=app", "--format", "\"{{.Names}}\"", "|", "sed 's/-/\\n/g'", "|", "tail -n 1").returns("").at_least_once run_command("rollback", "123").tap do |output| assert_match "Start version 123", output @@ -120,8 +120,6 @@ class CliMainTest < CliTestCase run_command("audit").tap do |output| assert_match /tail -n 50 mrsk-app-audit.log on 1.1.1.1/, output assert_match /App Host: 1.1.1.1/, output - assert_match /tail -n 50 mrsk-app-audit.log on 1.1.1.2/, output - assert_match /App Host: 1.1.1.2/, output end end diff --git a/test/cli/prune_test.rb b/test/cli/prune_test.rb index 15e545756..4ff9eedc4 100644 --- a/test/cli/prune_test.rb +++ b/test/cli/prune_test.rb @@ -10,15 +10,13 @@ class CliPruneTest < CliTestCase test "images" do run_command("images").tap do |output| - assert_match "docker image prune --all --force --filter label=service=app --filter until=168h on 1.1.1.1", output - assert_match "docker image prune --all --force --filter label=service=app --filter until=168h on 1.1.1.2", output + assert_match /docker image prune --all --force --filter label=service=app --filter until=168h on 1.1.1.\d/, output end end test "containers" do run_command("containers").tap do |output| - assert_match "docker container prune --force --filter label=service=app --filter until=72h on 1.1.1.1", output - assert_match "docker container prune --force --filter label=service=app --filter until=72h on 1.1.1.2", output + assert_match /docker container prune --force --filter label=service=app --filter until=72h on 1.1.1.\d/, output end end diff --git a/test/cli/registry_test.rb b/test/cli/registry_test.rb index d100402cc..0647f8737 100644 --- a/test/cli/registry_test.rb +++ b/test/cli/registry_test.rb @@ -4,15 +4,13 @@ class CliRegistryTest < CliTestCase test "login" do run_command("login").tap do |output| assert_match /docker login -u \[REDACTED\] -p \[REDACTED\] as .*@localhost/, output - assert_match "docker login -u [REDACTED] -p [REDACTED] on 1.1.1.1", output - assert_match "docker login -u [REDACTED] -p [REDACTED] on 1.1.1.2", output + assert_match /docker login -u \[REDACTED\] -p \[REDACTED\] on 1.1.1.\d/, output end end test "logout" do run_command("logout").tap do |output| - assert_match "docker logout on 1.1.1.1", output - assert_match "docker logout on 1.1.1.2", output + assert_match /docker logout on 1.1.1.\d/, output end end diff --git a/test/commander_test.rb b/test/commander_test.rb index 278d9896f..245350c2f 100644 --- a/test/commander_test.rb +++ b/test/commander_test.rb @@ -14,18 +14,29 @@ class CommanderTest < ActiveSupport::TestCase test "overwriting hosts" do assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @mrsk.hosts - @mrsk.specific_hosts = [ "1.2.3.4", "1.2.3.5" ] - assert_equal [ "1.2.3.4", "1.2.3.5" ], @mrsk.hosts + @mrsk.specific_hosts = [ "1.1.1.1", "1.1.1.2" ] + assert_equal [ "1.1.1.1", "1.1.1.2" ], @mrsk.hosts end - test "overwriting hosts with roles" do + test "filtering hosts by filtering roles" do assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @mrsk.hosts - @mrsk.specific_roles = [ "workers", "web" ] - assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @mrsk.hosts + @mrsk.specific_roles = [ "web" ] + assert_equal [ "1.1.1.1", "1.1.1.2" ], @mrsk.hosts + end + + test "filtering roles" do + assert_equal [ "web", "workers" ], @mrsk.roles.map(&:name) @mrsk.specific_roles = [ "workers" ] - assert_equal [ "1.1.1.3", "1.1.1.4" ], @mrsk.hosts + assert_equal [ "workers" ], @mrsk.roles.map(&:name) + end + + test "filtering roles by filtering hosts" do + assert_equal [ "web", "workers" ], @mrsk.roles.map(&:name) + + @mrsk.specific_hosts = [ "1.1.1.3" ] + assert_equal [ "workers" ], @mrsk.roles.map(&:name) end test "overwriting hosts with primary" do @@ -39,4 +50,9 @@ class CommanderTest < ActiveSupport::TestCase @mrsk.specific_roles = "web" assert_equal "1.1.1.1", @mrsk.primary_host end + + test "roles_on" do + assert_equal [ "web" ], @mrsk.roles_on("1.1.1.1") + assert_equal [ "workers" ], @mrsk.roles_on("1.1.1.3") + end end diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 2651abdcd..2a18b9c6d 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -13,7 +13,7 @@ class CommandsAppTest < ActiveSupport::TestCase test "run" do assert_equal \ - "docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999", new_command.run.join(" ") end @@ -21,7 +21,7 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:volumes] = ["/local/path:/container/path" ] assert_equal \ - "docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --volume /local/path:/container/path --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --volume /local/path:/container/path --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999", new_command.run.join(" ") end @@ -29,90 +29,89 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:healthcheck] = { "path" => "/healthz" } assert_equal \ - "docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/healthz\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999", + "docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/healthz\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999", new_command.run.join(" ") end test "run with custom options" do @config[:servers] = { "web" => [ "1.1.1.1" ], "jobs" => { "hosts" => [ "1.1.1.2" ], "cmd" => "bin/jobs", "options" => { "mount" => "somewhere", "cap-add" => true } } } - assert_equal \ - "docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"jobs\" --mount \"somewhere\" --cap-add dhh/app:999 bin/jobs", - new_command.run(role: :jobs).join(" ") + "docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-jobs-999 -e MRSK_CONTAINER_NAME=\"app-jobs-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"jobs\" --mount \"somewhere\" --cap-add dhh/app:999 bin/jobs", + new_command(role: "jobs").run.join(" ") end test "start" do assert_equal \ - "docker start app-999", + "docker start app-web-999", new_command.start.join(" ") end test "start with destination" do @destination = "staging" assert_equal \ - "docker start app-staging-999", + "docker start app-web-staging-999", new_command.start.join(" ") end test "stop" do assert_equal \ - "docker ps --quiet --filter label=service=app | xargs docker stop", + "docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker stop", new_command.stop.join(" ") end test "stop with version" do assert_equal \ - "docker container ls --all --filter name=app-123 --quiet | xargs docker stop", + "docker container ls --all --filter name=app-web-123 --quiet | xargs docker stop", new_command.stop(version: "123").join(" ") end test "info" do assert_equal \ - "docker ps --filter label=service=app", + "docker ps --filter label=service=app --filter label=role=web", new_command.info.join(" ") end test "info with destination" do @destination = "staging" assert_equal \ - "docker ps --filter label=service=app --filter label=destination=staging", + "docker ps --filter label=service=app --filter label=destination=staging --filter label=role=web", new_command.info.join(" ") end test "logs" do assert_equal \ - "docker ps --quiet --filter label=service=app | xargs docker logs 2>&1", + "docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker logs 2>&1", new_command.logs.join(" ") assert_equal \ - "docker ps --quiet --filter label=service=app | xargs docker logs --since 5m 2>&1", + "docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker logs --since 5m 2>&1", new_command.logs(since: "5m").join(" ") assert_equal \ - "docker ps --quiet --filter label=service=app | xargs docker logs --tail 100 2>&1", + "docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker logs --tail 100 2>&1", new_command.logs(lines: "100").join(" ") assert_equal \ - "docker ps --quiet --filter label=service=app | xargs docker logs --since 5m --tail 100 2>&1", + "docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker logs --since 5m --tail 100 2>&1", new_command.logs(since: "5m", lines: "100").join(" ") assert_equal \ - "docker ps --quiet --filter label=service=app | xargs docker logs 2>&1 | grep 'my-id'", + "docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker logs 2>&1 | grep 'my-id'", new_command.logs(grep: "my-id").join(" ") assert_equal \ - "docker ps --quiet --filter label=service=app | xargs docker logs --since 5m 2>&1 | grep 'my-id'", + "docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker logs --since 5m 2>&1 | grep 'my-id'", new_command.logs(since: "5m", grep: "my-id").join(" ") end test "follow logs" do assert_match \ - "docker ps --quiet --filter label=service=app | xargs docker logs --timestamps --tail 10 --follow 2>&1", + "docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker logs --timestamps --tail 10 --follow 2>&1", new_command.follow_logs(host: "app-1") assert_match \ - "docker ps --quiet --filter label=service=app | xargs docker logs --timestamps --tail 10 --follow 2>&1 | grep \"Completed\"", + "docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker logs --timestamps --tail 10 --follow 2>&1 | grep \"Completed\"", new_command.follow_logs(host: "app-1", grep: "Completed") end @@ -125,7 +124,7 @@ class CommandsAppTest < ActiveSupport::TestCase test "execute in existing container" do assert_equal \ - "docker exec app-999 bin/rails db:setup", + "docker exec app-web-999 bin/rails db:setup", new_command.execute_in_existing_container("bin/rails", "db:setup").join(" ") end @@ -135,7 +134,7 @@ class CommandsAppTest < ActiveSupport::TestCase end test "execute in existing container over ssh" do - assert_match %r|docker exec -it app-999 bin/rails c|, + assert_match %r|docker exec -it app-web-999 bin/rails c|, new_command.execute_in_existing_container_over_ssh("bin/rails", "c", host: "app-1") end @@ -166,14 +165,14 @@ class CommandsAppTest < ActiveSupport::TestCase test "current_container_id" do assert_equal \ - "docker ps --quiet --filter label=service=app", + "docker ps --quiet --filter label=service=app --filter label=role=web", new_command.current_container_id.join(" ") end test "current_container_id with destination" do @destination = "staging" assert_equal \ - "docker ps --quiet --filter label=service=app --filter label=destination=staging", + "docker ps --quiet --filter label=service=app --filter label=destination=staging --filter label=role=web", new_command.current_container_id.join(" ") end @@ -185,52 +184,52 @@ class CommandsAppTest < ActiveSupport::TestCase test "current_running_version" do assert_equal \ - "docker ps --filter label=service=app --format \"{{.Names}}\" | sed 's/-/\\n/g' | tail -n 1", + "docker ps --filter label=service=app --filter label=role=web --format \"{{.Names}}\" | sed 's/-/\\n/g' | tail -n 1", new_command.current_running_version.join(" ") end test "list_containers" do assert_equal \ - "docker container ls --all --filter label=service=app", + "docker container ls --all --filter label=service=app --filter label=role=web", new_command.list_containers.join(" ") end test "list_containers with destination" do @destination = "staging" assert_equal \ - "docker container ls --all --filter label=service=app --filter label=destination=staging", + "docker container ls --all --filter label=service=app --filter label=destination=staging --filter label=role=web", new_command.list_containers.join(" ") end test "list_container_names" do assert_equal \ - "docker container ls --all --filter label=service=app --format '{{ .Names }}'", + "docker container ls --all --filter label=service=app --filter label=role=web --format '{{ .Names }}'", new_command.list_container_names.join(" ") end test "remove_container" do assert_equal \ - "docker container ls --all --filter name=app-999 --quiet | xargs docker container rm", + "docker container ls --all --filter name=app-web-999 --quiet | xargs docker container rm", new_command.remove_container(version: "999").join(" ") end test "remove_container with destination" do @destination = "staging" assert_equal \ - "docker container ls --all --filter name=app-staging-999 --quiet | xargs docker container rm", + "docker container ls --all --filter name=app-web-staging-999 --quiet | xargs docker container rm", new_command.remove_container(version: "999").join(" ") end test "remove_containers" do assert_equal \ - "docker container prune --force --filter label=service=app", + "docker container prune --force --filter label=service=app --filter label=role=web", new_command.remove_containers.join(" ") end test "remove_containers with destination" do @destination = "staging" assert_equal \ - "docker container prune --force --filter label=service=app --filter label=destination=staging", + "docker container prune --force --filter label=service=app --filter label=destination=staging --filter label=role=web", new_command.remove_containers.join(" ") end @@ -242,19 +241,19 @@ class CommandsAppTest < ActiveSupport::TestCase test "remove_images" do assert_equal \ - "docker image prune --all --force --filter label=service=app", + "docker image prune --all --force --filter label=service=app --filter label=role=web", new_command.remove_images.join(" ") end test "remove_images with destination" do @destination = "staging" assert_equal \ - "docker image prune --all --force --filter label=service=app --filter label=destination=staging", + "docker image prune --all --force --filter label=service=app --filter label=destination=staging --filter label=role=web", new_command.remove_images.join(" ") end private - def new_command - Mrsk::Commands::App.new(Mrsk::Configuration.new(@config, destination: @destination, version: "999")) + def new_command(role: "web") + Mrsk::Commands::App.new(Mrsk::Configuration.new(@config, destination: @destination, version: "999"), role: role) end end diff --git a/test/commands/auditor_test.rb b/test/commands/auditor_test.rb index f35e110c0..55c3a32e1 100644 --- a/test/commands/auditor_test.rb +++ b/test/commands/auditor_test.rb @@ -22,6 +22,14 @@ class CommandsAuditorTest < ActiveSupport::TestCase new_command.record("app removed container").join(" ") end + test "record with role" do + @role = "web" + + assert_match \ + /echo '.* \[web\] app removed container' >> mrsk-app-audit.log/, + new_command.record("app removed container").join(" ") + end + test "broadcast" do assert_match \ /bin\/audit_broadcast '\[.*\] app removed container'/, @@ -30,6 +38,6 @@ class CommandsAuditorTest < ActiveSupport::TestCase private def new_command - Mrsk::Commands::Auditor.new(Mrsk::Configuration.new(@config, destination: @destination, version: "123")) + Mrsk::Commands::Auditor.new(Mrsk::Configuration.new(@config, destination: @destination, version: "123"), role: @role) end end diff --git a/test/configuration_test.rb b/test/configuration_test.rb index ee3814492..12b3b9187 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -16,7 +16,7 @@ class ConfigurationTest < ActiveSupport::TestCase @config = Mrsk::Configuration.new(@deploy) @deploy_with_roles = @deploy.dup.merge({ - servers: { "web" => [ "1.1.1.1", "1.1.1.2" ], "workers" => { "hosts" => [ "1.1.1.3", "1.1.1.4" ] } } }) + servers: { "web" => [ "1.1.1.1", "1.1.1.2" ], "workers" => { "hosts" => [ "1.1.1.1", "1.1.1.3" ] } } }) @config_with_roles = Mrsk::Configuration.new(@deploy_with_roles) end @@ -55,7 +55,7 @@ class ConfigurationTest < ActiveSupport::TestCase test "all hosts" do assert_equal [ "1.1.1.1", "1.1.1.2"], @config.all_hosts - assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @config_with_roles.all_hosts + assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3" ], @config_with_roles.all_hosts end test "primary web host" do @@ -69,7 +69,7 @@ class ConfigurationTest < ActiveSupport::TestCase @deploy_with_roles[:servers]["workers"]["traefik"] = true config = Mrsk::Configuration.new(@deploy_with_roles) - assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], config.traefik_hosts + assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3" ], config.traefik_hosts end test "version" do diff --git a/test/fixtures/deploy_with_accessories.yml b/test/fixtures/deploy_with_accessories.yml index 0d0c86f31..4440a1f9c 100644 --- a/test/fixtures/deploy_with_accessories.yml +++ b/test/fixtures/deploy_with_accessories.yml @@ -28,4 +28,4 @@ accessories: directories: - data:/data -readiness_delay: 0 \ No newline at end of file +readiness_delay: 0