Merge branch 'main' into pr/154

* main: (32 commits)
  Inline default as with other options
  Symbols!
  Fix tests
  test stop with custom stop wait time
  No need to replicate Docker default
  Describe purpose rather than elements
  Style and ordering
  Customizable stop wait time
  Fix tests
  Ensure it also works when configuring just log options without setting a driver
  Add accessory test
  Undo change
  Improve test
  Update README
  Ensure default log option `max-size=10m`
  #142 Allow to customize container options in accessories
  Fix flaky test
  Fix tests
  More resilient tests
  Fix other tests
  ...
This commit is contained in:
David Heinemeier Hansson
2023-03-24 15:43:17 +01:00
24 changed files with 375 additions and 201 deletions

View File

@@ -7,7 +7,7 @@ class CliAccessoryTest < CliTestCase
run_command("boot", "mysql").tap do |output|
assert_match /docker login.*on 1.1.1.3/, output
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=10m --publish 3306:3306 -e [REDACTED] -e MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 -e [REDACTED] -e MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
end
end
@@ -20,8 +20,8 @@ class CliAccessoryTest < CliTestCase
run_command("boot", "all").tap do |output|
assert_match /docker login.*on 1.1.1.3/, output
assert_match /docker login.*on 1.1.1.4/, output
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=10m --publish 3306:3306 -e [REDACTED] -e MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
assert_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=10m --publish 6379:6379 --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.4", output
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 -e [REDACTED] -e MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
assert_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.4", output
end
end

View File

@@ -3,14 +3,11 @@ require_relative "cli_test_case"
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

View File

@@ -90,7 +90,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
@@ -101,7 +101,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
@@ -122,8 +122,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

View File

@@ -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

View File

@@ -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

View File

@@ -3,7 +3,7 @@ require_relative "cli_test_case"
class CliTraefikTest < CliTestCase
test "boot" do
run_command("boot").tap do |output|
assert_match "docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG", output
assert_match "docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik --providers.docker --log.level=DEBUG", output
end
end

View File

@@ -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

View File

@@ -40,11 +40,6 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
}
}
@config = Mrsk::Configuration.new(@config)
@mysql = Mrsk::Commands::Accessory.new(@config, name: :mysql)
@redis = Mrsk::Commands::Accessory.new(@config, name: :redis)
@busybox = Mrsk::Commands::Accessory.new(@config, name: :busybox)
ENV["MYSQL_ROOT_PASSWORD"] = "secret123"
end
@@ -54,60 +49,68 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
test "run" do
assert_equal \
"docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=10m --publish 3306:3306 -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" --label service=\"app-mysql\" private.registry/mysql:8.0",
@mysql.run.join(" ")
"docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 3306:3306 -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" --label service=\"app-mysql\" private.registry/mysql:8.0",
new_command(:mysql).run.join(" ")
assert_equal \
"docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=10m --publish 6379:6379 -e SOMETHING=\"else\" --volume /var/lib/redis:/data --label service=\"app-redis\" --label cache=\"true\" redis:latest",
@redis.run.join(" ")
"docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 -e SOMETHING=\"else\" --volume /var/lib/redis:/data --label service=\"app-redis\" --label cache=\"true\" redis:latest",
new_command(:redis).run.join(" ")
assert_equal \
"docker run --name app-busybox --detach --restart unless-stopped --log-opt max-size=10m --label service=\"app-busybox\" busybox:latest",
@busybox.run.join(" ")
"docker run --name app-busybox --detach --restart unless-stopped --log-opt max-size=\"10m\" --label service=\"app-busybox\" busybox:latest",
new_command(:busybox).run.join(" ")
end
test "run with logging config" do
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
assert_equal \
"docker run --name app-busybox --detach --restart unless-stopped --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"app-busybox\" busybox:latest",
new_command(:busybox).run.join(" ")
end
test "start" do
assert_equal \
"docker container start app-mysql",
@mysql.start.join(" ")
new_command(:mysql).start.join(" ")
end
test "stop" do
assert_equal \
"docker container stop app-mysql",
@mysql.stop.join(" ")
new_command(:mysql).stop.join(" ")
end
test "info" do
assert_equal \
"docker ps --filter label=service=app-mysql",
@mysql.info.join(" ")
new_command(:mysql).info.join(" ")
end
test "execute in new container" do
assert_equal \
"docker run --rm -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" private.registry/mysql:8.0 mysql -u root",
@mysql.execute_in_new_container("mysql", "-u", "root").join(" ")
new_command(:mysql).execute_in_new_container("mysql", "-u", "root").join(" ")
end
test "execute in existing container" do
assert_equal \
"docker exec app-mysql mysql -u root",
@mysql.execute_in_existing_container("mysql", "-u", "root").join(" ")
new_command(:mysql).execute_in_existing_container("mysql", "-u", "root").join(" ")
end
test "execute in new container over ssh" do
@mysql.stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do
new_command(:mysql).stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do
assert_match %r|docker run -it --rm -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" private.registry/mysql:8.0 mysql -u root|,
@mysql.execute_in_new_container_over_ssh("mysql", "-u", "root")
new_command(:mysql).execute_in_new_container_over_ssh("mysql", "-u", "root")
end
end
test "execute in existing container over ssh" do
@mysql.stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do
new_command(:mysql).stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do
assert_match %r|docker exec -it app-mysql mysql -u root|,
@mysql.execute_in_existing_container_over_ssh("mysql", "-u", "root")
new_command(:mysql).execute_in_existing_container_over_ssh("mysql", "-u", "root")
end
end
@@ -116,28 +119,33 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
test "logs" do
assert_equal \
"docker logs app-mysql --timestamps 2>&1",
@mysql.logs.join(" ")
new_command(:mysql).logs.join(" ")
assert_equal \
"docker logs app-mysql --since 5m --tail 100 --timestamps 2>&1 | grep 'thing'",
@mysql.logs(since: "5m", lines: 100, grep: "thing").join(" ")
new_command(:mysql).logs(since: "5m", lines: 100, grep: "thing").join(" ")
end
test "follow logs" do
assert_equal \
"ssh -t root@1.1.1.5 'docker logs app-mysql --timestamps --tail 10 --follow 2>&1'",
@mysql.follow_logs
new_command(:mysql).follow_logs
end
test "remove container" do
assert_equal \
"docker container prune --force --filter label=service=app-mysql",
@mysql.remove_container.join(" ")
new_command(:mysql).remove_container.join(" ")
end
test "remove image" do
assert_equal \
"docker image rm --force private.registry/mysql:8.0",
@mysql.remove_image.join(" ")
new_command(:mysql).remove_image.join(" ")
end
private
def new_command(accessory)
Mrsk::Commands::Accessory.new(Mrsk::Configuration.new(@config), name: accessory)
end
end

View File

@@ -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 --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(" ")
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 --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(" ")
end
@@ -29,90 +29,104 @@ 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 --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(" ")
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 --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(role: "jobs").run.join(" ")
end
test "run with logging config" do
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
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 --name app-web-999 -e MRSK_CONTAINER_NAME=\"app-web-999\" -e RAILS_MASTER_KEY=\"456\" --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --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
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 custom stop wait time" do
@config[:stop_wait_time] = 30
assert_equal \
"docker ps --quiet --filter label=service=app --filter label=role=web | xargs docker stop -t 30",
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 +139,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 +149,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 +180,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 +199,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 +256,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

View File

@@ -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

View File

@@ -10,45 +10,45 @@ class CommandsTraefikTest < ActiveSupport::TestCase
test "run" do
assert_equal \
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
new_command.run.join(" ")
@config[:traefik]["host_port"] = "8080"
assert_equal \
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 8080:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
"docker run --name traefik --detach --restart unless-stopped --publish 8080:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
new_command.run.join(" ")
end
test "run with ports configured" do
assert_equal \
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
new_command.run.join(" ")
@config[:traefik]["options"] = {"publish" => %w[9000:9000 9001:9001]}
assert_equal \
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --publish \"9000:9000\" --publish \"9001:9001\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --publish \"9000:9000\" --publish \"9001:9001\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
new_command.run.join(" ")
end
test "run with volumes configured" do
assert_equal \
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
new_command.run.join(" ")
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json] }
assert_equal \
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
new_command.run.join(" ")
end
test "run with several options configured" do
assert_equal \
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
new_command.run.join(" ")
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json], "publish" => %w[8080:8080], "memory" => "512m"}
assert_equal \
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" --publish \"8080:8080\" --memory \"512m\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" --publish \"8080:8080\" --memory \"512m\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
new_command.run.join(" ")
end
@@ -56,7 +56,15 @@ class CommandsTraefikTest < ActiveSupport::TestCase
@config.delete(:traefik)
assert_equal \
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG",
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-opt max-size=\"10m\" traefik --providers.docker --log.level=DEBUG",
new_command.run.join(" ")
end
test "run with logging config" do
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
assert_equal \
"docker run --name traefik --detach --restart unless-stopped --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
new_command.run.join(" ")
end

View File

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

View File

@@ -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
@@ -209,6 +209,20 @@ class ConfigurationTest < ActiveSupport::TestCase
assert_equal ["--volume", "/local/path:/container/path"], @config.volume_args
end
test "logging args default" do
assert_equal ["--log-opt", "max-size=\"10m\""], @config.logging_args
end
test "logging args with configured options" do
config = Mrsk::Configuration.new(@deploy.tap { |c| c.merge!(logging: { "options" => { "max-size" => "100m", "max-file" => 5 } }) })
assert_equal ["--log-opt", "max-size=\"100m\"", "--log-opt", "max-file=\"5\""], @config.logging_args
end
test "logging args with configured driver and options" do
config = Mrsk::Configuration.new(@deploy.tap { |c| c.merge!(logging: { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => 5 } }) })
assert_equal ["--log-driver", "\"local\"", "--log-opt", "max-size=\"100m\"", "--log-opt", "max-file=\"5\""], @config.logging_args
end
test "erb evaluation of yml config" do
config = Mrsk::Configuration.create_from config_file: Pathname.new(File.expand_path("fixtures/deploy.erb.yml", __dir__))
assert_equal "my-user", config.registry["username"]
@@ -233,6 +247,6 @@ class ConfigurationTest < ActiveSupport::TestCase
end
test "to_h" do
assert_equal({ :roles=>["web"], :hosts=>["1.1.1.1", "1.1.1.2"], :primary_host=>"1.1.1.1", :version=>"missing", :repository=>"dhh/app", :absolute_image=>"dhh/app:missing", :service_with_version=>"app-missing", :env_args=>["-e", "REDIS_URL=\"redis://x/y\""], :ssh_options=>{:user=>"root", :auth_methods=>["publickey"]}, :volume_args=>["--volume", "/local/path:/container/path"], :healthcheck=>{"path"=>"/up", "port"=>3000 }}, @config.to_h)
assert_equal({ :roles=>["web"], :hosts=>["1.1.1.1", "1.1.1.2"], :primary_host=>"1.1.1.1", :version=>"missing", :repository=>"dhh/app", :absolute_image=>"dhh/app:missing", :service_with_version=>"app-missing", :env_args=>["-e", "REDIS_URL=\"redis://x/y\""], :ssh_options=>{:user=>"root", :auth_methods=>["publickey"]}, :volume_args=>["--volume", "/local/path:/container/path"], :logging=>["--log-opt", "max-size=\"10m\""], :healthcheck=>{"path"=>"/up", "port"=>3000 }}, @config.to_h)
end
end

View File

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