Merge branch 'main' into role-awareness

This commit is contained in:
David Heinemeier Hansson
2023-03-14 19:11:10 -04:00
committed by GitHub
10 changed files with 233 additions and 30 deletions

View File

@@ -4,6 +4,8 @@ MRSK deploys web apps anywhere from bare metal to cloud VMs using Docker with ze
Watch the screencast: https://www.youtube.com/watch?v=LL1cV2FXZ5I Watch the screencast: https://www.youtube.com/watch?v=LL1cV2FXZ5I
Join us on Discord: https://discord.gg/DQETs3Pm
## Installation ## Installation
Install MRSK globally with `gem install mrsk`. Then, inside your app directory, run `mrsk init` (or `mrsk init --bundle` within Rails apps where you want a bin/mrsk binstub). Now edit the new file `config/deploy.yml`. It could look as simple as this: Install MRSK globally with `gem install mrsk`. Then, inside your app directory, run `mrsk init` (or `mrsk init --bundle` within Rails apps where you want a bin/mrsk binstub). Now edit the new file `config/deploy.yml`. It could look as simple as this:

View File

@@ -9,42 +9,58 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
end end
desc "deploy", "Deploy app to servers" desc "deploy", "Deploy app to servers"
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
def deploy def deploy
invoke_options = options.without(:skip_push)
runtime = print_runtime do runtime = print_runtime do
say "Ensure curl and Docker are installed...", :magenta say "Ensure curl and Docker are installed...", :magenta
invoke "mrsk:cli:server:bootstrap" invoke "mrsk:cli:server:bootstrap", [], invoke_options
say "Log into image registry...", :magenta say "Log into image registry...", :magenta
invoke "mrsk:cli:registry:login" invoke "mrsk:cli:registry:login", [], invoke_options
say "Build and push app image...", :magenta if options[:skip_push]
invoke "mrsk:cli:build:deliver" say "Pull app image...", :magenta
invoke "mrsk:cli:build:pull", [], invoke_options
else
say "Build and push app image...", :magenta
invoke "mrsk:cli:build:deliver", [], invoke_options
end
say "Ensure Traefik is running...", :magenta say "Ensure Traefik is running...", :magenta
invoke "mrsk:cli:traefik:boot" invoke "mrsk:cli:traefik:boot", [], invoke_options
say "Ensure app can pass healthcheck...", :magenta say "Ensure app can pass healthcheck...", :magenta
invoke "mrsk:cli:healthcheck:perform" invoke "mrsk:cli:healthcheck:perform", [], invoke_options
invoke "mrsk:cli:app:boot" invoke "mrsk:cli:app:boot", [], invoke_options
say "Prune old containers and images...", :magenta say "Prune old containers and images...", :magenta
invoke "mrsk:cli:prune:all" invoke "mrsk:cli:prune:all", [], invoke_options
end end
audit_broadcast "Deployed app in #{runtime.to_i} seconds" unless options[:skip_broadcast] audit_broadcast "Deployed app in #{runtime.to_i} seconds" unless options[:skip_broadcast]
end end
desc "redeploy", "Deploy app to servers without bootstrapping servers, starting Traefik, pruning, and registry login" desc "redeploy", "Deploy app to servers without bootstrapping servers, starting Traefik, pruning, and registry login"
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
def redeploy def redeploy
invoke_options = options.without(:skip_push)
runtime = print_runtime do runtime = print_runtime do
say "Build and push app image...", :magenta if options[:skip_push]
invoke "mrsk:cli:build:deliver" say "Pull app image...", :magenta
invoke "mrsk:cli:build:pull", [], invoke_options
else
say "Build and push app image...", :magenta
invoke "mrsk:cli:build:deliver", [], invoke_options
end
say "Ensure app can pass healthcheck...", :magenta say "Ensure app can pass healthcheck...", :magenta
invoke "mrsk:cli:healthcheck:perform" invoke "mrsk:cli:healthcheck:perform", [], invoke_options
invoke "mrsk:cli:app:boot" invoke "mrsk:cli:app:boot", [], invoke_options
end end
audit_broadcast "Redeployed app in #{runtime.to_i} seconds" unless options[:skip_broadcast] audit_broadcast "Redeployed app in #{runtime.to_i} seconds" unless options[:skip_broadcast]
@@ -119,8 +135,10 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
puts "Binstub already exists in bin/mrsk (remove first to create a new one)" puts "Binstub already exists in bin/mrsk (remove first to create a new one)"
else else
puts "Adding MRSK to Gemfile and bundle..." puts "Adding MRSK to Gemfile and bundle..."
`bundle add mrsk` run_locally do
`bundle binstubs mrsk` execute :bundle, :add, :mrsk
execute :bundle, :binstubs, :mrsk
end
puts "Created binstub file in bin/mrsk" puts "Created binstub file in bin/mrsk"
end end
end end

View File

@@ -56,7 +56,7 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
private private
def cmd_option_args def cmd_option_args
if args = config.traefik["args"] if args = config.traefik["args"]
optionize args optionize args, with: "="
else else
[] []
end end

View File

@@ -73,8 +73,9 @@ class Mrsk::Configuration::Role
"traefik.http.routers.#{config.service}.rule" => "PathPrefix(`/`)", "traefik.http.routers.#{config.service}.rule" => "PathPrefix(`/`)",
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => config.healthcheck["path"], "traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s", "traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
"traefik.http.middlewares.#{config.service}.retry.attempts" => "5", "traefik.http.middlewares.#{config.service}-retry.retry.attempts" => "5",
"traefik.http.middlewares.#{config.service}.retry.initialinterval" => "500ms" "traefik.http.middlewares.#{config.service}-retry.retry.initialinterval" => "500ms",
"traefik.http.routers.#{config.service}.middlewares" => "#{config.service}-retry@docker"
} }
else else
{} {}

View File

@@ -24,8 +24,14 @@ module Mrsk::Utils
end end
# Returns a list of shell-dashed option arguments. If the value is true, it's treated like a value-less option. # Returns a list of shell-dashed option arguments. If the value is true, it's treated like a value-less option.
def optionize(args) def optionize(args, with: nil)
args.collect { |(key, value)| [ "--#{key}", value == true ? nil : escape_shell_value(value) ] }.flatten.compact options = if with
args.collect { |(key, value)| value == true ? "--#{key}" : "--#{key}#{with}#{escape_shell_value(value)}" }
else
args.collect { |(key, value)| [ "--#{key}", value == true ? nil : escape_shell_value(value) ] }
end
options.flatten.compact
end end
# Copied from SSHKit::Backend::Abstract#redact to be available inside Commands classes # Copied from SSHKit::Backend::Abstract#redact to be available inside Commands classes

View File

@@ -1,9 +1,80 @@
require_relative "cli_test_case" require_relative "cli_test_case"
class CliMainTest < CliTestCase class CliMainTest < CliTestCase
test "version" do test "setup" do
version = stdouted { Mrsk::Cli::Main.new.version } Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:server:bootstrap")
assert_equal Mrsk::VERSION, version Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:accessory:boot", [ "all" ])
Mrsk::Cli::Main.any_instance.expects(:deploy)
run_command("setup")
end
test "deploy" do
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_broadcast" => false, "skip_push" => false }
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:server:bootstrap", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:registry:login", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:deliver", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:traefik:boot", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:healthcheck:perform", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:boot", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:prune:all", [], invoke_options)
run_command("deploy").tap do |output|
assert_match /Ensure curl and Docker are installed/, output
assert_match /Log into image registry/, output
assert_match /Build and push app image/, output
assert_match /Ensure Traefik is running/, output
assert_match /Ensure app can pass healthcheck/, output
assert_match /Prune old containers and images/, output
end
end
test "deploy with skip_push" do
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_broadcast" => false, "skip_push" => true }
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:server:bootstrap", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:registry:login", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:pull", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:traefik:boot", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:healthcheck:perform", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:boot", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:prune:all", [], invoke_options)
run_command("deploy", "--skip_push").tap do |output|
assert_match /Ensure curl and Docker are installed/, output
assert_match /Log into image registry/, output
assert_match /Pull app image/, output
assert_match /Ensure Traefik is running/, output
assert_match /Ensure app can pass healthcheck/, output
assert_match /Prune old containers and images/, output
end
end
test "redeploy" do
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_broadcast" => false, "skip_push" => false}
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:deliver", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:healthcheck:perform", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:boot", [], invoke_options)
run_command("redeploy").tap do |output|
assert_match /Build and push app image/, output
assert_match /Ensure app can pass healthcheck/, output
end
end
test "redeploy with skip_push" do
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_broadcast" => false, "skip_push" => true }
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:pull", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:healthcheck:perform", [], invoke_options)
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:boot", [], invoke_options)
run_command("redeploy", "--skip_push").tap do |output|
assert_match /Pull app image/, output
assert_match /Ensure app can pass healthcheck/, output
end
end end
test "rollback bad version" do test "rollback bad version" do
@@ -25,6 +96,95 @@ class CliMainTest < CliTestCase
end end
end end
test "details" do
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:traefik:details")
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:details")
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:accessory:details", [ "all" ])
run_command("details")
end
test "audit" do
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
test "config" do
run_command("config").tap do |output|
config = YAML.load(output)
assert_equal ["web"], config[:roles]
assert_equal ["1.1.1.1", "1.1.1.2"], config[:hosts]
assert_equal "999", config[:version]
assert_equal "dhh/app", config[:repository]
assert_equal "dhh/app:999", config[:absolute_image]
assert_equal "app-999", config[:service_with_version]
end
end
test "init" do
Pathname.any_instance.expects(:exist?).returns(false).twice
FileUtils.stubs(:mkdir_p)
FileUtils.stubs(:cp_r)
run_command("init").tap do |output|
assert_match /Created configuration file in config\/deploy.yml/, output
assert_match /Created \.env file/, output
end
end
test "init with existing config" do
Pathname.any_instance.expects(:exist?).returns(true).twice
run_command("init").tap do |output|
assert_match /Config file already exists in config\/deploy.yml \(remove first to create a new one\)/, output
end
end
test "init with bundle option" do
Pathname.any_instance.expects(:exist?).returns(false).times(3)
FileUtils.stubs(:mkdir_p)
FileUtils.stubs(:cp_r)
run_command("init", "--bundle").tap do |output|
assert_match /Created configuration file in config\/deploy.yml/, output
assert_match /Created \.env file/, output
assert_match /Adding MRSK to Gemfile and bundle/, output
assert_match /bundle add mrsk/, output
assert_match /bundle binstubs mrsk/, output
assert_match /Created binstub file in bin\/mrsk/, output
end
end
test "init with bundle option and existing binstub" do
Pathname.any_instance.expects(:exist?).returns(true).times(3)
FileUtils.stubs(:mkdir_p)
FileUtils.stubs(:cp_r)
run_command("init", "--bundle").tap do |output|
assert_match /Config file already exists in config\/deploy.yml \(remove first to create a new one\)/, output
assert_match /Binstub already exists in bin\/mrsk \(remove first to create a new one\)/, output
end
end
test "envify" do
File.expects(:read).with(".env.erb").returns("HELLO=<%= 'world' %>")
File.expects(:write).with(".env", "HELLO=world", perm: 0600)
run_command("envify")
end
test "envify with destination" do
File.expects(:read).with(".env.staging.erb").returns("HELLO=<%= 'world' %>")
File.expects(:write).with(".env.staging", "HELLO=world", perm: 0600)
run_command("envify", "-d", "staging")
end
test "remove with confirmation" do test "remove with confirmation" do
run_command("remove", "-y").tap do |output| run_command("remove", "-y").tap do |output|
assert_match /docker container stop traefik/, output assert_match /docker container stop traefik/, output
@@ -49,6 +209,11 @@ class CliMainTest < CliTestCase
end end
end end
test "version" do
version = stdouted { Mrsk::Cli::Main.new.version }
assert_equal Mrsk::VERSION, version
end
private private
def run_command(*command) def run_command(*command)
stdouted { Mrsk::Cli::Main.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) } stdouted { Mrsk::Cli::Main.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }

View File

@@ -14,7 +14,7 @@ class CommandsAppTest < ActiveSupport::TestCase
test "run" do test "run" do
assert_equal \ assert_equal \
"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.attempts=\"5\" --label traefik.http.middlewares.app.retry.initialinterval=\"500ms\" 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",
@app.run.join(" ") @app.run.join(" ")
end end
@@ -22,7 +22,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 --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.attempts=\"5\" --label traefik.http.middlewares.app.retry.initialinterval=\"500ms\" 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",
@app.run.join(" ") @app.run.join(" ")
end end
@@ -30,7 +30,7 @@ class CommandsAppTest < ActiveSupport::TestCase
@config[:healthcheck] = { "path" => "/healthz" } @config[:healthcheck] = { "path" => "/healthz" }
assert_equal \ assert_equal \
"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.attempts=\"5\" --label traefik.http.middlewares.app.retry.initialinterval=\"500ms\" 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",
@app.run.join(" ") @app.run.join(" ")
end end

View File

@@ -4,18 +4,18 @@ class CommandsTraefikTest < ActiveSupport::TestCase
setup do setup do
@config = { @config = {
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } } traefik: { "args" => { "accesslog.format" => "json", "api.insecure" => true, "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
} }
end end
test "run" do test "run" do
assert_equal \ 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\" --metrics.prometheus.buckets \"0.1,0.3,1.2,5.0\"", "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\"",
new_command.run.join(" ") new_command.run.join(" ")
@config[:traefik]["host_port"] = "8080" @config[:traefik]["host_port"] = "8080"
assert_equal \ 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\" --metrics.prometheus.buckets \"0.1,0.3,1.2,5.0\"", "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\"",
new_command.run.join(" ") new_command.run.join(" ")
end end

View File

@@ -42,7 +42,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
end end
test "special label args for web" do test "special label args for web" do
assert_equal [ "--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.attempts=\"5\"", "--label", "traefik.http.middlewares.app.retry.initialinterval=\"500ms\""], @config.role(:web).label_args assert_equal [ "--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\"" ], @config.role(:web).label_args
end end
test "custom labels" do test "custom labels" do
@@ -66,7 +66,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
c[:servers]["beta"] = { "traefik" => "true", "hosts" => [ "1.1.1.5" ] } c[:servers]["beta"] = { "traefik" => "true", "hosts" => [ "1.1.1.5" ] }
}) })
assert_equal [ "--label", "service=\"app\"", "--label", "role=\"beta\"", "--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.attempts=\"5\"", "--label", "traefik.http.middlewares.app.retry.initialinterval=\"500ms\"" ], config.role(:beta).label_args assert_equal [ "--label", "service=\"app\"", "--label", "role=\"beta\"", "--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\"" ], config.role(:beta).label_args
end end
test "env overwritten by role" do test "env overwritten by role" do

View File

@@ -9,7 +9,18 @@ require "mrsk"
ActiveSupport::LogSubscriber.logger = ActiveSupport::Logger.new(STDOUT) if ENV["VERBOSE"] ActiveSupport::LogSubscriber.logger = ActiveSupport::Logger.new(STDOUT) if ENV["VERBOSE"]
# Applies to remote commands only.
SSHKit.config.backend = SSHKit::Backend::Printer SSHKit.config.backend = SSHKit::Backend::Printer
# Ensure local commands use the printer backend too.
# See https://github.com/capistrano/sshkit/blob/master/lib/sshkit/dsl.rb#L9
module SSHKit
module DSL
def run_locally(&block)
SSHKit::Backend::Printer.new(SSHKit::Host.new(:local), &block).run
end
end
end
class ActiveSupport::TestCase class ActiveSupport::TestCase
end end