diff --git a/README.md b/README.md index e70b0670..027f954e 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,15 @@ servers: registry: username: registry-user-name password: <%= ENV.fetch("MRSK_REGISTRY_PASSWORD") %> +env: + secret: + - RAILS_MASTER_KEY ``` Now you're ready to deploy a multi-arch image to the servers: ``` -MRSK_REGISTRY_PASSWORD=pw mrsk deploy +RAILS_MASTER_KEY=123 MRSK_REGISTRY_PASSWORD=pw mrsk deploy ``` This will: @@ -282,14 +285,6 @@ ARG RUBY_VERSION FROM ruby:$RUBY_VERSION-slim as base ``` -### Using without RAILS_MASTER_KEY - -If you're using MRSK with older Rails apps that predate RAILS_MASTER_KEY, or with a non-Rails app, you can skip the default usage and reference: - -```yaml -skip_master_key: true -``` - ### Using accessories for database, cache, search services You can manage your accessory services via MRSK as well. The services will build off public images, and will not be automatically updated when you deploy: diff --git a/lib/mrsk/cli/accessory.rb b/lib/mrsk/cli/accessory.rb index 5bf9ff9b..97e16d37 100644 --- a/lib/mrsk/cli/accessory.rb +++ b/lib/mrsk/cli/accessory.rb @@ -7,6 +7,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base with_accessory(name) do |accessory| directories(name) upload(name) + on(accessory.host) do execute *MRSK.auditor.record("accessory #{name} boot"), verbosity: :debug execute *accessory.run diff --git a/lib/mrsk/cli/app.rb b/lib/mrsk/cli/app.rb index e4c71493..eb4e77f4 100644 --- a/lib/mrsk/cli/app.rb +++ b/lib/mrsk/cli/app.rb @@ -104,11 +104,6 @@ class Mrsk::Cli::App < Mrsk::Cli::Base on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_images) } end - desc "current", "Return the current running container ID" - def current - on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_container_id) } - end - desc "logs", "Show lines from app on servers" option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)" option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server" diff --git a/lib/mrsk/commands/accessory.rb b/lib/mrsk/commands/accessory.rb index 0e9c5c12..16e3b854 100644 --- a/lib/mrsk/commands/accessory.rb +++ b/lib/mrsk/commands/accessory.rb @@ -39,10 +39,10 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base end def follow_logs(grep: nil) - run_over_ssh pipe( - docker(:logs, service_name, "-t", "-n", "10", "-f", "2>&1"), - (%(grep "#{grep}") if grep) - ).join(" ") + run_over_ssh \ + pipe \ + docker(:logs, service_name, "-t", "-n", "10", "-f", "2>&1"), + (%(grep "#{grep}") if grep) end @@ -64,11 +64,11 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base end def execute_in_existing_container_over_ssh(*command) - run_over_ssh execute_in_existing_container(*command, interactive: true).join(" ") + run_over_ssh execute_in_existing_container(*command, interactive: true) end def execute_in_new_container_over_ssh(*command) - run_over_ssh execute_in_new_container(*command, interactive: true).join(" ") + run_over_ssh execute_in_new_container(*command, interactive: true) end def run_over_ssh(command) diff --git a/lib/mrsk/commands/app.rb b/lib/mrsk/commands/app.rb index ae4fce97..9c8407ac 100644 --- a/lib/mrsk/commands/app.rb +++ b/lib/mrsk/commands/app.rb @@ -6,7 +6,6 @@ class Mrsk::Commands::App < Mrsk::Commands::Base "-d", "--restart unless-stopped", "--name", service_with_version, - *rails_master_key_arg, *role.env_args, *config.volume_args, *role.label_args, @@ -35,11 +34,13 @@ class Mrsk::Commands::App < Mrsk::Commands::Base end def follow_logs(host:, grep: nil) - run_over_ssh pipe( - current_container_id, - "xargs docker logs -t -n 10 -f 2>&1", - (%(grep "#{grep}") if grep) - ).join(" "), host: host + run_over_ssh \ + pipe( + current_container_id, + "xargs docker logs -t -n 10 -f 2>&1", + (%(grep "#{grep}") if grep) + ), + host: host end @@ -54,7 +55,6 @@ class Mrsk::Commands::App < Mrsk::Commands::Base docker :run, ("-it" if interactive), "--rm", - *rails_master_key_arg, *config.env_args, *config.volume_args, config.absolute_image, @@ -62,11 +62,11 @@ class Mrsk::Commands::App < Mrsk::Commands::Base end def execute_in_existing_container_over_ssh(*command, host:) - run_over_ssh execute_in_existing_container(*command, interactive: true).join(" "), host: host + run_over_ssh execute_in_existing_container(*command, interactive: true), host: host end def execute_in_new_container_over_ssh(*command, host:) - run_over_ssh execute_in_new_container(*command, interactive: true).join(" "), host: host + run_over_ssh execute_in_new_container(*command, interactive: true), host: host end @@ -128,12 +128,4 @@ class Mrsk::Commands::App < Mrsk::Commands::Base def service_filter [ "--filter", "label=service=#{config.service}" ] end - - def rails_master_key_arg - if master_key = config.master_key - [ "-e", redact("RAILS_MASTER_KEY=#{master_key}") ] - else - [] - end - end end diff --git a/lib/mrsk/commands/base.rb b/lib/mrsk/commands/base.rb index b4d145e6..4abd70c1 100644 --- a/lib/mrsk/commands/base.rb +++ b/lib/mrsk/commands/base.rb @@ -8,14 +8,11 @@ module Mrsk::Commands @config = config end - def run_over_ssh(command, host:) - ssh_command = "ssh" - - if config.ssh_proxy - ssh_command << " -J #{config.ssh_proxy.jump_proxies}" + def run_over_ssh(*command, host:) + "ssh".tap do |cmd| + cmd << " -J #{config.ssh_proxy.jump_proxies}" if config.ssh_proxy + cmd << " -t #{config.ssh_user}@#{host} '#{command.join(" ")}'" end - - ssh_command << " -t #{config.ssh_user}@#{host} '#{command}'" end private diff --git a/lib/mrsk/configuration.rb b/lib/mrsk/configuration.rb index 4ce9caec..0a39f89d 100644 --- a/lib/mrsk/configuration.rb +++ b/lib/mrsk/configuration.rb @@ -117,12 +117,6 @@ class Mrsk::Configuration { user: ssh_user, proxy: ssh_proxy, auth_methods: [ "publickey" ] }.compact end - def master_key - unless raw_config.skip_master_key - ENV["RAILS_MASTER_KEY"] || File.read(Pathname.new(File.expand_path("config/master.key"))) - end - end - def valid? ensure_required_keys_present && ensure_env_available diff --git a/lib/mrsk/configuration/role.rb b/lib/mrsk/configuration/role.rb index f8e15da5..dbac24bc 100644 --- a/lib/mrsk/configuration/role.rb +++ b/lib/mrsk/configuration/role.rb @@ -96,7 +96,12 @@ class Mrsk::Configuration::Role def merged_env_with_secrets merged_env.tap do |new_env| new_env["secret"] = Array(config.env["secret"]) + Array(specialized_env["secret"]) - new_env["clear"] = (Array(config.env["clear"] || config.env) + Array(specialized_env["clear"] || specialized_env)).uniq + + # If there's no secret/clear split, everything is clear + clear_app_env = config.env["secret"] ? Array(config.env["clear"]) : Array(config.env["clear"] || config.env) + clear_role_env = specialized_env["secret"] ? Array(specialized_env["clear"]) : Array(specialized_env["clear"] || specialized_env) + + new_env["clear"] = (clear_app_env + clear_role_env).uniq end end end diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index 8a68cbb7..60add274 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -81,14 +81,14 @@ class CommandsAccessoryTest < ActiveSupport::TestCase end test "execute in new container over ssh" do - @mysql.stub(:run_over_ssh, ->(cmd) { cmd }) do + @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=% mysql:8.0 mysql -u root|, @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 }) do + @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") end diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index aab55244..e668dc68 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -4,59 +4,135 @@ class CommandsAppTest < ActiveSupport::TestCase setup do ENV["RAILS_MASTER_KEY"] = "456" - @config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ] } - @app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config) + @config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], env: { "secret" => [ "RAILS_MASTER_KEY" ] } } + @app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config).tap { |c| c.version = "999" } end teardown do - ENV["RAILS_MASTER_KEY"] = nil + ENV.delete("RAILS_MASTER_KEY") end test "run" do assert_equal \ - [:docker, :run, "-d", "--restart unless-stopped", "--name", "app-missing", "-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=3", "--label", "traefik.http.middlewares.app.retry.initialinterval=500ms", "dhh/app:missing"], @app.run + "docker run -d --restart unless-stopped --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.attempts=3 --label traefik.http.middlewares.app.retry.initialinterval=500ms dhh/app:999", + @app.run.join(" ") end test "run with volumes" do @config[:volumes] = ["/local/path:/container/path" ] assert_equal \ - [:docker, :run, "-d", "--restart unless-stopped", "--name", "app-missing", "-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=3", "--label", "traefik.http.middlewares.app.retry.initialinterval=500ms", "dhh/app:missing"], @app.run + "docker run -d --restart unless-stopped --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.attempts=3 --label traefik.http.middlewares.app.retry.initialinterval=500ms dhh/app:999", + @app.run.join(" ") + end + + test "start" do + assert_equal \ + "docker start app-999", + @app.start.join(" ") + end + + test "stop" do + assert_equal \ + "docker ps -q --filter label=service=app | xargs docker stop", + @app.stop.join(" ") + end + + test "info" do + assert_equal \ + "docker ps --filter label=service=app", + @app.info.join(" ") + end + + + test "logs" do + assert_equal \ + "docker ps -q --filter label=service=app | xargs docker logs 2>&1", + @app.logs.join(" ") + + assert_equal \ + "docker ps -q --filter label=service=app | xargs docker logs --since 5m 2>&1", + @app.logs(since: "5m").join(" ") + + assert_equal \ + "docker ps -q --filter label=service=app | xargs docker logs -n 100 2>&1", + @app.logs(lines: "100").join(" ") + + assert_equal \ + "docker ps -q --filter label=service=app | xargs docker logs --since 5m -n 100 2>&1", + @app.logs(since: "5m", lines: "100").join(" ") + + assert_equal \ + "docker ps -q --filter label=service=app | xargs docker logs 2>&1 | grep 'my-id'", + @app.logs(grep: "my-id").join(" ") + + assert_equal \ + "docker ps -q --filter label=service=app | xargs docker logs --since 5m 2>&1 | grep 'my-id'", + @app.logs(since: "5m", grep: "my-id").join(" ") + end + + test "follow logs" do + @app.stub(:run_over_ssh, ->(cmd, host:) { cmd.join(" ") }) do + assert_equal \ + "docker ps -q --filter label=service=app | xargs docker logs -t -n 10 -f 2>&1", + @app.follow_logs(host: "app-1") + + assert_equal \ + "docker ps -q --filter label=service=app | xargs docker logs -t -n 10 -f 2>&1 | grep \"Completed\"", + @app.follow_logs(host: "app-1", grep: "Completed") + end end test "execute in new container" do assert_equal \ - [ :docker, :run, "--rm", "-e", "RAILS_MASTER_KEY=456", "dhh/app:missing", "bin/rails", "db:setup" ], - @app.execute_in_new_container("bin/rails", "db:setup") + "docker run --rm -e RAILS_MASTER_KEY=456 dhh/app:999 bin/rails db:setup", + @app.execute_in_new_container("bin/rails", "db:setup").join(" ") end test "execute in existing container" do assert_equal \ - [ :docker, :exec, "app-missing", "bin/rails", "db:setup" ], - @app.execute_in_existing_container("bin/rails", "db:setup") + "docker exec app-999 bin/rails db:setup", + @app.execute_in_existing_container("bin/rails", "db:setup").join(" ") end test "execute in new container over ssh" do - @app.stub(:run_over_ssh, ->(cmd, host:) { cmd }) do - assert_match %r|docker run -it --rm -e RAILS_MASTER_KEY=456 dhh/app:missing bin/rails c|, + @app.stub(:run_over_ssh, ->(cmd, host:) { cmd.join(" ") }) do + assert_match %r|docker run -it --rm -e RAILS_MASTER_KEY=456 dhh/app:999 bin/rails c|, @app.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1") end end test "execute in existing container over ssh" do - @app.stub(:run_over_ssh, ->(cmd, host:) { cmd }) do - assert_match %r|docker exec -it app-missing bin/rails c|, + @app.stub(:run_over_ssh, ->(cmd, host:) { cmd.join(" ") }) do + assert_match %r|docker exec -it app-999 bin/rails c|, @app.execute_in_existing_container_over_ssh("bin/rails", "c", host: "app-1") end end - test "run without master key" do - ENV["RAILS_MASTER_KEY"] = nil - @app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config.tap { |c| c[:skip_master_key] = true }) + test "current_container_id" do + assert_equal \ + "docker ps -q --filter label=service=app", + @app.current_container_id.join(" ") + end - assert @app.run.exclude?("RAILS_MASTER_KEY=456") + test "container_id_for" do + assert_equal \ + "docker container ls -a -f name=app-999 -q", + @app.container_id_for(container_name: "app-999").join(" ") + end + + test "current_running_version" do + assert_equal \ + "docker ps --filter label=service=app --format \"{{.Names}}\" | sed 's/-/\\n/g' | tail -n 1", + @app.current_running_version.join(" ") + end + + test "most_recent_version_from_available_images" do + assert_equal \ + "docker image ls --format \"{{.Tag}}\" dhh/app | head -n 1", + @app.most_recent_version_from_available_images.join(" ") end test "exec_over_ssh" do