Merge branch 'main' into global-logging-config

This commit is contained in:
Samuel Sieg
2023-03-24 15:24:06 +01:00
18 changed files with 252 additions and 154 deletions

View File

@@ -526,6 +526,9 @@ accessories:
- MYSQL_ROOT_PASSWORD - MYSQL_ROOT_PASSWORD
volumes: volumes:
- /var/lib/mysql:/var/lib/mysql - /var/lib/mysql:/var/lib/mysql
options:
cpus: 4
memory: "2GB"
redis: redis:
image: redis:latest image: redis:latest
host: 1.1.1.4 host: 1.1.1.4

View File

@@ -7,24 +7,26 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
cli = self cli = self
MRSK.config.roles.each do |role| on(MRSK.hosts) do |host|
on(role.hosts) do |host| roles = MRSK.roles_on(host)
execute *MRSK.auditor.record("Booted app version #{version}"), verbosity: :debug
roles.each do |role|
execute *MRSK.auditor(role: role).record("Booted app version #{version}"), verbosity: :debug
begin begin
old_version = capture_with_info(*MRSK.app.current_running_version).strip old_version = capture_with_info(*MRSK.app(role: role).current_running_version).strip
execute *MRSK.app.run(role: role.name) execute *MRSK.app(role: role).run
sleep MRSK.config.readiness_delay 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 rescue SSHKit::Command::Failed => e
if e.message =~ /already in use/ if e.message =~ /already in use/
error "Rebooting container with same version #{version} already deployed on #{host} (may cause gap in zero-downtime promise!)" 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(role: role).stop(version: version)
execute *MRSK.app.remove_container(version: version) execute *MRSK.app(role: role).remove_container(version: version)
execute *MRSK.app.run(role: role.name) execute *MRSK.app(role: role).run
else else
raise raise
end end
@@ -36,24 +38,38 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
desc "start", "Start existing app container on servers" desc "start", "Start existing app container on servers"
def start def start
on(MRSK.hosts) do on(MRSK.hosts) do |host|
execute *MRSK.auditor.record("Started app version #{MRSK.config.version}"), verbosity: :debug roles = MRSK.roles_on(host)
execute *MRSK.app.start, raise_on_non_zero_exit: false
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
end end
desc "stop", "Stop app container on servers" desc "stop", "Stop app container on servers"
def stop def stop
on(MRSK.hosts) do on(MRSK.hosts) do |host|
execute *MRSK.auditor.record("Stopped app"), verbosity: :debug roles = MRSK.roles_on(host)
execute *MRSK.app.stop, raise_on_non_zero_exit: false
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
end end
# FIXME: Drop in favor of just containers? # FIXME: Drop in favor of just containers?
desc "details", "Show details about app containers" desc "details", "Show details about app containers"
def details 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 end
desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)" desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)"
@@ -65,7 +81,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
say "Get current version of running container...", :magenta unless options[:version] say "Get current version of running container...", :magenta unless options[:version]
using_version(options[:version] || current_running_version) do |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 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 end
when options[:interactive] when options[:interactive]
@@ -81,8 +97,12 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
say "Launching command with version #{version} from existing container...", :magenta say "Launching command with version #{version} from existing container...", :magenta
on(MRSK.hosts) do |host| on(MRSK.hosts) do |host|
execute *MRSK.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug roles = MRSK.roles_on(host)
puts_by_host host, capture_with_info(*MRSK.app.execute_in_existing_container(cmd))
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
end end
@@ -147,17 +167,25 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
def remove_container(version) def remove_container(version)
on(MRSK.hosts) do on(MRSK.hosts) do |host|
execute *MRSK.auditor.record("Removed app container with version #{version}"), verbosity: :debug roles = MRSK.roles_on(host)
execute *MRSK.app.remove_container(version: version)
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
end end
desc "remove_containers", "Remove all app containers from servers", hide: true desc "remove_containers", "Remove all app containers from servers", hide: true
def remove_containers def remove_containers
on(MRSK.hosts) do on(MRSK.hosts) do |host|
execute *MRSK.auditor.record("Removed all app containers"), verbosity: :debug roles = MRSK.roles_on(host)
execute *MRSK.app.remove_containers
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
end end

View File

@@ -8,7 +8,6 @@ class Mrsk::Commander
self.verbosity = :info self.verbosity = :info
end end
def config def config
@config ||= Mrsk::Configuration.create_from(**@config_kwargs).tap do |config| @config ||= Mrsk::Configuration.create_from(**@config_kwargs).tap do |config|
@config_kwargs = nil @config_kwargs = nil
@@ -20,23 +19,38 @@ class Mrsk::Commander
@config, @config_kwargs = nil, kwargs @config, @config_kwargs = nil, kwargs
end end
attr_reader :specific_roles, :specific_hosts
attr_accessor :specific_hosts
def specific_primary! def specific_primary!
self.specific_hosts = [ config.primary_web_host ] self.specific_hosts = [ config.primary_web_host ]
end end
def specific_roles=(role_names) 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 end
def primary_host def primary_host
specific_hosts&.first || config.primary_web_host specific_hosts&.first || config.primary_web_host
end end
def roles
(specific_roles || config.roles).select do |role|
((specific_hosts || config.all_hosts) & role.hosts).any?
end
end
def hosts 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 end
def traefik_hosts def traefik_hosts
@@ -52,16 +66,16 @@ class Mrsk::Commander
end end
def app def app(role: nil)
@app ||= Mrsk::Commands::App.new(config) Mrsk::Commands::App.new(config, role: role)
end end
def accessory(name) def accessory(name)
Mrsk::Commands::Accessory.new(config, name: name) Mrsk::Commands::Accessory.new(config, name: name)
end end
def auditor def auditor(role: nil)
@auditor ||= Mrsk::Commands::Auditor.new(config) Mrsk::Commands::Auditor.new(config, role: role)
end end
def builder def builder

View File

@@ -1,6 +1,7 @@
class Mrsk::Commands::Accessory < Mrsk::Commands::Base class Mrsk::Commands::Accessory < Mrsk::Commands::Base
attr_reader :accessory_config attr_reader :accessory_config
delegate :service_name, :image, :host, :port, :files, :directories, :publish_args, :env_args, :volume_args, :label_args, to: :accessory_config delegate :service_name, :image, :host, :port, :files, :directories, :publish_args, :env_args, :volume_args,
:label_args, :option_args, to: :accessory_config
def initialize(config, name:) def initialize(config, name:)
super(config) super(config)
@@ -17,6 +18,7 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
*env_args, *env_args,
*volume_args, *volume_args,
*label_args, *label_args,
*option_args,
image image
end end

View File

@@ -1,12 +1,19 @@
class Mrsk::Commands::App < Mrsk::Commands::Base class Mrsk::Commands::App < Mrsk::Commands::Base
def run(role: :web) attr_reader :role
role = config.role(role)
def initialize(config, role: nil)
super(config)
@role = role
end
def run
role = config.role(self.role)
docker :run, docker :run,
"--detach", "--detach",
"--restart unless-stopped", "--restart unless-stopped",
"--name", service_with_version_and_destination, "--name", container_name,
"-e", "MRSK_CONTAINER_NAME=\"#{service_with_version_and_destination}\"", "-e", "MRSK_CONTAINER_NAME=\"#{container_name}\"",
*role.env_args, *role.env_args,
*config.logging_args, *config.logging_args,
*config.volume_args, *config.volume_args,
@@ -17,7 +24,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
end end
def start def start
docker :start, service_with_version_and_destination docker :start, container_name
end end
def stop(version: nil) def stop(version: nil)
@@ -52,7 +59,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
def execute_in_existing_container(*command, interactive: false) def execute_in_existing_container(*command, interactive: false)
docker :exec, docker :exec,
("-it" if interactive), ("-it" if interactive),
service_with_version_and_destination, container_name,
*command *command
end end
@@ -97,7 +104,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
def remove_container(version:) def remove_container(version:)
pipe \ pipe \
container_id_for(container_name: service_with_version_and_destination(version)), container_id_for(container_name: container_name(version)),
xargs(docker(:container, :rm)) xargs(docker(:container, :rm))
end end
@@ -115,12 +122,12 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
private private
def service_with_version_and_destination(version = nil) def container_name(version = nil)
[ config.service, config.destination, version || config.version ].compact.join("-") [ config.service, role, config.destination, version || config.version ].compact.join("-")
end end
def container_id_for_version(version) def container_id_for_version(version)
container_id_for(container_name: service_with_version_and_destination(version)) container_id_for(container_name: container_name(version))
end end
def filter_args def filter_args
@@ -130,6 +137,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
def filters def filters
[ "label=service=#{config.service}" ].tap do |filters| [ "label=service=#{config.service}" ].tap do |filters|
filters << "label=destination=#{config.destination}" if config.destination filters << "label=destination=#{config.destination}" if config.destination
filters << "label=role=#{role}" if role
end end
end end
end end

View File

@@ -1,6 +1,13 @@
require "active_support/core_ext/time/conversions" require "active_support/core_ext/time/conversions"
class Mrsk::Commands::Auditor < Mrsk::Commands::Base class Mrsk::Commands::Auditor < Mrsk::Commands::Base
attr_reader :role
def initialize(config, role: nil)
super(config)
@role = role
end
# Runs remotely # Runs remotely
def record(line) def record(line)
append \ append \
@@ -25,18 +32,26 @@ class Mrsk::Commands::Auditor < Mrsk::Commands::Base
end end
def tagged_record_line(line) def tagged_record_line(line)
"'#{recorded_at_tag} #{performer_tag} #{line}'" tagged_line recorded_at_tag, performer_tag, role_tag, line
end end
def tagged_broadcast_line(line) def tagged_broadcast_line(line)
"'#{performer_tag} #{line}'" tagged_line performer_tag, role_tag, line
end
def tagged_line(*tags_and_line)
"'#{tags_and_line.compact.join(" ")}'"
end
def recorded_at_tag
"[#{Time.now.to_fs(:db)}]"
end end
def performer_tag def performer_tag
"[#{`whoami`.strip}]" "[#{`whoami`.strip}]"
end end
def recorded_at_tag def role_tag
"[#{Time.now.to_fs(:db)}]" "[#{role}]" if role
end end
end end

View File

@@ -78,7 +78,7 @@ class Mrsk::Configuration
def all_hosts def all_hosts
roles.flat_map(&:hosts) roles.flat_map(&:hosts).uniq
end end
def primary_web_host def primary_web_host
@@ -86,7 +86,7 @@ class Mrsk::Configuration
end end
def traefik_hosts def traefik_hosts
roles.select(&:running_traefik?).flat_map(&:hosts) roles.select(&:running_traefik?).flat_map(&:hosts).uniq
end end

View File

@@ -1,5 +1,5 @@
class Mrsk::Configuration::Accessory class Mrsk::Configuration::Accessory
delegate :argumentize, :argumentize_env_with_secrets, to: Mrsk::Utils delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Mrsk::Utils
attr_accessor :name, :specifics attr_accessor :name, :specifics
@@ -67,6 +67,14 @@ class Mrsk::Configuration::Accessory
argumentize "--volume", volumes argumentize "--volume", volumes
end end
def option_args
if args = specifics["options"]
optionize args
else
[]
end
end
private private
attr_accessor :config attr_accessor :config

View File

@@ -3,14 +3,11 @@ require_relative "cli_test_case"
class CliAppTest < CliTestCase class CliAppTest < CliTestCase
test "boot" do test "boot" do
# Stub current version fetch # Stub current version fetch
SSHKit::Backend::Abstract.any_instance.stubs(:capture) SSHKit::Backend::Abstract.any_instance.stubs(:capture).returns("123") # old version
.returns("999") # new version
.then
.returns("123") # old version
run_command("boot").tap do |output| run_command("boot").tap do |output|
assert_match "docker run --detach --restart unless-stopped", 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
end end
@@ -20,16 +17,14 @@ class CliAppTest < CliTestCase
# Prevent expected failures from outputting to terminal # Prevent expected failures from outputting to terminal
Thread.report_on_exception = false Thread.report_on_exception = false
MRSK.app.stubs(:run) Mrsk::Commands::App.any_instance.stubs(:run)
.raises(SSHKit::Command::Failed.new("already in use"))
.then
.raises(SSHKit::Command::Failed.new("already in use")) .raises(SSHKit::Command::Failed.new("already in use"))
.then .then
.returns([ :docker, :run ]) .returns([ :docker, :run ])
run_command("boot").tap do |output| 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 "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 assert_match "docker run", output # Start new container
end end
ensure ensure
@@ -38,32 +33,58 @@ class CliAppTest < CliTestCase
test "start" do test "start" do
run_command("start").tap do |output| run_command("start").tap do |output|
assert_match "docker start app-999", output assert_match "docker start app-web-999", output
end end
end end
test "stop" do test "stop" do
run_command("stop").tap do |output| 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
end end
test "details" do test "details" do
run_command("details").tap do |output| 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
end end
test "exec" do test "exec" do
run_command("exec", "ruby -v").tap do |output| 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
end end
test "exec with reuse" do test "exec with reuse" do
run_command("exec", "--reuse", "ruby -v").tap do |output| 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 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
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") assert_match "docker ps --quiet --filter label=service=app | xargs docker logs --timestamps --tail 10 --follow 2>&1", run_command("logs", "--follow")
end 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 test "version" do
run_command("version").tap do |output| run_command("version").tap do |output|
assert_match "docker ps --filter label=service=app --format \"{{.Names}}\" | sed 's/-/\\n/g' | tail -n 1", 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 private
def run_command(*command) 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
end end

View File

@@ -88,7 +88,7 @@ class CliMainTest < CliTestCase
test "rollback good version" do test "rollback good version" do
Mrsk::Cli::Main.any_instance.stubs(:container_name_available?).returns(true) 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| run_command("rollback", "123").tap do |output|
assert_match "Start version 123", output assert_match "Start version 123", output
@@ -99,7 +99,7 @@ class CliMainTest < CliTestCase
test "rollback without old version" do test "rollback without old version" do
Mrsk::Cli::Main.any_instance.stubs(:container_name_available?).returns(true) 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| run_command("rollback", "123").tap do |output|
assert_match "Start version 123", output assert_match "Start version 123", output
@@ -120,8 +120,6 @@ class CliMainTest < CliTestCase
run_command("audit").tap do |output| run_command("audit").tap do |output|
assert_match /tail -n 50 mrsk-app-audit.log on 1.1.1.1/, 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 /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
end end

View File

@@ -10,15 +10,13 @@ class CliPruneTest < CliTestCase
test "images" do test "images" do
run_command("images").tap do |output| 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.\d/, output
assert_match "docker image prune --all --force --filter label=service=app --filter until=168h on 1.1.1.2", output
end end
end end
test "containers" do test "containers" do
run_command("containers").tap do |output| 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.\d/, output
assert_match "docker container prune --force --filter label=service=app --filter until=72h on 1.1.1.2", output
end end
end end

View File

@@ -4,15 +4,13 @@ class CliRegistryTest < CliTestCase
test "login" do test "login" do
run_command("login").tap do |output| 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\] 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.\d/, output
assert_match "docker login -u [REDACTED] -p [REDACTED] on 1.1.1.2", output
end end
end end
test "logout" do test "logout" do
run_command("logout").tap do |output| run_command("logout").tap do |output|
assert_match "docker logout on 1.1.1.1", output assert_match /docker logout on 1.1.1.\d/, output
assert_match "docker logout on 1.1.1.2", output
end end
end end

View File

@@ -14,18 +14,29 @@ class CommanderTest < ActiveSupport::TestCase
test "overwriting hosts" do test "overwriting hosts" do
assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @mrsk.hosts 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" ] @mrsk.specific_hosts = [ "1.1.1.1", "1.1.1.2" ]
assert_equal [ "1.2.3.4", "1.2.3.5" ], @mrsk.hosts assert_equal [ "1.1.1.1", "1.1.1.2" ], @mrsk.hosts
end 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 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" ] @mrsk.specific_roles = [ "web" ]
assert_equal [ "1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4" ], @mrsk.hosts 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" ] @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 end
test "overwriting hosts with primary" do test "overwriting hosts with primary" do
@@ -39,4 +50,9 @@ class CommanderTest < ActiveSupport::TestCase
@mrsk.specific_roles = "web" @mrsk.specific_roles = "web"
assert_equal "1.1.1.1", @mrsk.primary_host assert_equal "1.1.1.1", @mrsk.primary_host
end 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 end

View File

@@ -13,7 +13,7 @@ class CommandsAppTest < ActiveSupport::TestCase
test "run" do test "run" do
assert_equal \ assert_equal \
"docker run --detach --restart unless-stopped --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --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\" --log-opt max-size=\"10m\" --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(" ") new_command.run.join(" ")
end end
@@ -21,7 +21,7 @@ class CommandsAppTest < ActiveSupport::TestCase
@config[:volumes] = ["/local/path:/container/path" ] @config[:volumes] = ["/local/path:/container/path" ]
assert_equal \ assert_equal \
"docker run --detach --restart unless-stopped --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --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\" --log-opt max-size=\"10m\" --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(" ") new_command.run.join(" ")
end end
@@ -29,16 +29,15 @@ class CommandsAppTest < ActiveSupport::TestCase
@config[:healthcheck] = { "path" => "/healthz" } @config[:healthcheck] = { "path" => "/healthz" }
assert_equal \ assert_equal \
"docker run --detach --restart unless-stopped --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --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\" --log-opt max-size=\"10m\" --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(" ") new_command.run.join(" ")
end end
test "run with custom options" do 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 } } } @config[:servers] = { "web" => [ "1.1.1.1" ], "jobs" => { "hosts" => [ "1.1.1.2" ], "cmd" => "bin/jobs", "options" => { "mount" => "somewhere", "cap-add" => true } } }
assert_equal \ assert_equal \
"docker run --detach --restart unless-stopped --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"jobs\" --mount \"somewhere\" --cap-add dhh/app:999 bin/jobs", "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\" --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"jobs\" --mount \"somewhere\" --cap-add dhh/app:999 bin/jobs",
new_command.run(role: :jobs).join(" ") new_command(role: "jobs").run.join(" ")
end end
test "run with logging config" do test "run with logging config" do
@@ -51,76 +50,76 @@ class CommandsAppTest < ActiveSupport::TestCase
test "start" do test "start" do
assert_equal \ assert_equal \
"docker start app-999", "docker start app-web-999",
new_command.start.join(" ") new_command.start.join(" ")
end end
test "start with destination" do test "start with destination" do
@destination = "staging" @destination = "staging"
assert_equal \ assert_equal \
"docker start app-staging-999", "docker start app-web-staging-999",
new_command.start.join(" ") new_command.start.join(" ")
end end
test "stop" do test "stop" do
assert_equal \ 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(" ") new_command.stop.join(" ")
end end
test "stop with version" do test "stop with version" do
assert_equal \ 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(" ") new_command.stop(version: "123").join(" ")
end end
test "info" do test "info" do
assert_equal \ assert_equal \
"docker ps --filter label=service=app", "docker ps --filter label=service=app --filter label=role=web",
new_command.info.join(" ") new_command.info.join(" ")
end end
test "info with destination" do test "info with destination" do
@destination = "staging" @destination = "staging"
assert_equal \ 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(" ") new_command.info.join(" ")
end end
test "logs" do test "logs" do
assert_equal \ 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(" ") new_command.logs.join(" ")
assert_equal \ 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(" ") new_command.logs(since: "5m").join(" ")
assert_equal \ 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(" ") new_command.logs(lines: "100").join(" ")
assert_equal \ 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(" ") new_command.logs(since: "5m", lines: "100").join(" ")
assert_equal \ 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(" ") new_command.logs(grep: "my-id").join(" ")
assert_equal \ 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(" ") new_command.logs(since: "5m", grep: "my-id").join(" ")
end end
test "follow logs" do test "follow logs" do
assert_match \ 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") new_command.follow_logs(host: "app-1")
assert_match \ 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") new_command.follow_logs(host: "app-1", grep: "Completed")
end end
@@ -133,7 +132,7 @@ class CommandsAppTest < ActiveSupport::TestCase
test "execute in existing container" do test "execute in existing container" do
assert_equal \ 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(" ") new_command.execute_in_existing_container("bin/rails", "db:setup").join(" ")
end end
@@ -143,7 +142,7 @@ class CommandsAppTest < ActiveSupport::TestCase
end end
test "execute in existing container over ssh" do 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") new_command.execute_in_existing_container_over_ssh("bin/rails", "c", host: "app-1")
end end
@@ -174,14 +173,14 @@ class CommandsAppTest < ActiveSupport::TestCase
test "current_container_id" do test "current_container_id" do
assert_equal \ 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(" ") new_command.current_container_id.join(" ")
end end
test "current_container_id with destination" do test "current_container_id with destination" do
@destination = "staging" @destination = "staging"
assert_equal \ 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(" ") new_command.current_container_id.join(" ")
end end
@@ -193,52 +192,52 @@ class CommandsAppTest < ActiveSupport::TestCase
test "current_running_version" do test "current_running_version" do
assert_equal \ 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(" ") new_command.current_running_version.join(" ")
end end
test "list_containers" do test "list_containers" do
assert_equal \ 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(" ") new_command.list_containers.join(" ")
end end
test "list_containers with destination" do test "list_containers with destination" do
@destination = "staging" @destination = "staging"
assert_equal \ 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(" ") new_command.list_containers.join(" ")
end end
test "list_container_names" do test "list_container_names" do
assert_equal \ 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(" ") new_command.list_container_names.join(" ")
end end
test "remove_container" do test "remove_container" do
assert_equal \ 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(" ") new_command.remove_container(version: "999").join(" ")
end end
test "remove_container with destination" do test "remove_container with destination" do
@destination = "staging" @destination = "staging"
assert_equal \ 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(" ") new_command.remove_container(version: "999").join(" ")
end end
test "remove_containers" do test "remove_containers" do
assert_equal \ 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(" ") new_command.remove_containers.join(" ")
end end
test "remove_containers with destination" do test "remove_containers with destination" do
@destination = "staging" @destination = "staging"
assert_equal \ 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(" ") new_command.remove_containers.join(" ")
end end
@@ -250,19 +249,19 @@ class CommandsAppTest < ActiveSupport::TestCase
test "remove_images" do test "remove_images" do
assert_equal \ 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(" ") new_command.remove_images.join(" ")
end end
test "remove_images with destination" do test "remove_images with destination" do
@destination = "staging" @destination = "staging"
assert_equal \ 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(" ") new_command.remove_images.join(" ")
end end
private private
def new_command def new_command(role: "web")
Mrsk::Commands::App.new(Mrsk::Configuration.new(@config, destination: @destination, version: "999")) Mrsk::Commands::App.new(Mrsk::Configuration.new(@config, destination: @destination, version: "999"), role: role)
end end
end end

View File

@@ -22,6 +22,14 @@ class CommandsAuditorTest < ActiveSupport::TestCase
new_command.record("app removed container").join(" ") new_command.record("app removed container").join(" ")
end 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 test "broadcast" do
assert_match \ assert_match \
/bin\/audit_broadcast '\[.*\] app removed container'/, /bin\/audit_broadcast '\[.*\] app removed container'/,
@@ -30,6 +38,6 @@ class CommandsAuditorTest < ActiveSupport::TestCase
private private
def new_command 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
end end

View File

@@ -39,7 +39,11 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
}, },
"volumes" => [ "volumes" => [
"/var/lib/redis:/data" "/var/lib/redis:/data"
] ],
"options" => {
"cpus" => 4,
"memory" => "2GB"
}
} }
} }
} }
@@ -104,4 +108,8 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
test "directories" do test "directories" do
assert_equal({"$PWD/app-mysql/data"=>"/var/lib/mysql"}, @config.accessory(:mysql).directories) assert_equal({"$PWD/app-mysql/data"=>"/var/lib/mysql"}, @config.accessory(:mysql).directories)
end end
test "options" do
assert_equal ["--cpus", "\"4\"", "--memory", "\"2GB\""], @config.accessory(:redis).option_args
end
end end

View File

@@ -16,7 +16,7 @@ class ConfigurationTest < ActiveSupport::TestCase
@config = Mrsk::Configuration.new(@deploy) @config = Mrsk::Configuration.new(@deploy)
@deploy_with_roles = @deploy.dup.merge({ @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) @config_with_roles = Mrsk::Configuration.new(@deploy_with_roles)
end end
@@ -55,7 +55,7 @@ class ConfigurationTest < ActiveSupport::TestCase
test "all hosts" do 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"], @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 end
test "primary web host" do test "primary web host" do
@@ -69,7 +69,7 @@ class ConfigurationTest < ActiveSupport::TestCase
@deploy_with_roles[:servers]["workers"]["traefik"] = true @deploy_with_roles[:servers]["workers"]["traefik"] = true
config = Mrsk::Configuration.new(@deploy_with_roles) 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 end
test "version" do test "version" do

View File

@@ -28,4 +28,4 @@ accessories:
directories: directories:
- data:/data - data:/data
readiness_delay: 0 readiness_delay: 0