From e85bd5ff63d67dac13bb86825df9b89d3e11694e Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Mon, 10 Apr 2023 10:42:29 -0700 Subject: [PATCH 01/13] Bootstrap: use multi-platform installer * Limit auto-install to root users; otherwise, give manual install guidance * Support non-Debian/Ubuntu with the multi-OS get.docker.com installer --- lib/mrsk/cli/main.rb | 3 --- lib/mrsk/cli/server.rb | 22 +++++++++++++--------- lib/mrsk/commander.rb | 4 ++++ lib/mrsk/commands/docker.rb | 21 +++++++++++++++++++++ test/cli/main_test.rb | 7 +------ test/cli/server_test.rb | 27 +++++++++++++++++++++++---- test/commands/docker_test.rb | 26 ++++++++++++++++++++++++++ 7 files changed, 88 insertions(+), 22 deletions(-) create mode 100644 lib/mrsk/commands/docker.rb create mode 100644 test/commands/docker_test.rb diff --git a/lib/mrsk/cli/main.rb b/lib/mrsk/cli/main.rb index 0a3d2ce0..96c78098 100644 --- a/lib/mrsk/cli/main.rb +++ b/lib/mrsk/cli/main.rb @@ -17,9 +17,6 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base invoke_options = deploy_options runtime = print_runtime do - say "Ensure curl and Docker are installed...", :magenta - invoke "mrsk:cli:server:bootstrap", [], invoke_options - say "Log into image registry...", :magenta invoke "mrsk:cli:registry:login", [], invoke_options diff --git a/lib/mrsk/cli/server.rb b/lib/mrsk/cli/server.rb index 0ede5afc..6e05559c 100644 --- a/lib/mrsk/cli/server.rb +++ b/lib/mrsk/cli/server.rb @@ -1,17 +1,21 @@ class Mrsk::Cli::Server < Mrsk::Cli::Base - desc "bootstrap", "Ensure curl and Docker are installed on servers" + desc "bootstrap", "Set up Docker to run MRSK apps" def bootstrap - with_lock do - on(MRSK.hosts + MRSK.accessory_hosts) do - dependencies_to_install = Array.new.tap do |dependencies| - dependencies << "curl" unless execute "which curl", raise_on_non_zero_exit: false - dependencies << "docker.io" unless execute "which docker", raise_on_non_zero_exit: false - end + missing = [] - if dependencies_to_install.any? - execute "apt-get update -y && apt-get install #{dependencies_to_install.join(" ")} -y" + on(MRSK.hosts | MRSK.accessory_hosts) do |host| + unless execute(*MRSK.docker.installed?, raise_on_non_zero_exit: false) + if execute(*MRSK.docker.superuser?, raise_on_non_zero_exit: false) + info "Missing Docker on #{host}. Installing…" + execute *MRSK.docker.install + else + missing << host end end end + + if missing.any? + raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and the `curl` command available. Install Docker manually: https://docs.docker.com/engine/install/" + end end end diff --git a/lib/mrsk/commander.rb b/lib/mrsk/commander.rb index 44c96a2a..14b2c2f8 100644 --- a/lib/mrsk/commander.rb +++ b/lib/mrsk/commander.rb @@ -84,6 +84,10 @@ class Mrsk::Commander @builder ||= Mrsk::Commands::Builder.new(config) end + def docker + @docker ||= Mrsk::Commands::Docker.new(config) + end + def healthcheck @healthcheck ||= Mrsk::Commands::Healthcheck.new(config) end diff --git a/lib/mrsk/commands/docker.rb b/lib/mrsk/commands/docker.rb new file mode 100644 index 00000000..85101d0f --- /dev/null +++ b/lib/mrsk/commands/docker.rb @@ -0,0 +1,21 @@ +class Mrsk::Commands::Docker < Mrsk::Commands::Base + # Install Docker using the https://github.com/docker/docker-install convenience script. + def install + pipe [ :curl, "-fsSL", "https://get.docker.com" ], :sh + end + + # Checks the Docker client version. Fails if Docker is not installed. + def installed? + docker "-v" + end + + # Checks the Docker server version. Fails if Docker is not running. + def running? + docker :version + end + + # Do we have superuser access to install Docker and start system services? + def superuser? + [ '[ "${EUID:-$(id -u)}" -eq 0 ]' ] + end +end diff --git a/test/cli/main_test.rb b/test/cli/main_test.rb index 5cac1132..bcd72813 100644 --- a/test/cli/main_test.rb +++ b/test/cli/main_test.rb @@ -12,7 +12,6 @@ class CliMainTest < CliTestCase test "deploy" do invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "skip_broadcast" => false, "version" => "999" } - 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) @@ -22,7 +21,6 @@ class CliMainTest < CliTestCase 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 @@ -35,7 +33,6 @@ class CliMainTest < CliTestCase test "deploy with skip_push" do invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "skip_broadcast" => false, "version" => "999" } - 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) @@ -46,7 +43,6 @@ class CliMainTest < CliTestCase run_command("deploy", "--skip_push").tap do |output| assert_match /Acquiring the deploy lock/, 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 @@ -87,7 +83,6 @@ class CliMainTest < CliTestCase test "deploy errors during critical section leave lock in place" do invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "skip_broadcast" => false, "version" => "999" } - 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:app:stale_containers", [], invoke_options) @@ -106,7 +101,7 @@ class CliMainTest < CliTestCase invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "skip_broadcast" => false, "version" => "999" } Mrsk::Cli::Main.any_instance.expects(:invoke) - .with("mrsk:cli:server:bootstrap", [], invoke_options) + .with("mrsk:cli:registry:login", [], invoke_options) .raises(RuntimeError) assert !MRSK.holding_lock? diff --git a/test/cli/server_test.rb b/test/cli/server_test.rb index bf0119f4..48d4484f 100644 --- a/test/cli/server_test.rb +++ b/test/cli/server_test.rb @@ -1,11 +1,30 @@ require_relative "cli_test_case" class CliServerTest < CliTestCase - test "bootstrap" do + test "bootstrap already installed" do + SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(true).at_least_once + + assert_equal "", run_command("bootstrap") + end + + test "bootstrap install as non-root user" do + SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(false).at_least_once + SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ]', raise_on_non_zero_exit: false).returns(false).at_least_once + + assert_raise RuntimeError, "Docker is not installed on 1.1.1.1, 1.1.1.3, 1.1.1.4, 1.1.1.2 and can't be automatically intalled without having root access and the `curl` command available. Install Docker manually: https://docs.docker.com/engine/install/" do + run_command("bootstrap") + end + end + + test "bootstrap install as root user" do + SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(false).at_least_once + SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ]', raise_on_non_zero_exit: false).returns(true).at_least_once + SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:curl, "-fsSL", "https://get.docker.com", "|", :sh).at_least_once + run_command("bootstrap").tap do |output| - assert_match /which curl/, output - assert_match /which docker/, output - assert_match /apt-get update -y && apt-get install curl docker.io -y/, output + ("1.1.1.1".."1.1.1.4").map do |host| + assert_match "Missing Docker on #{host}. Installing…", output + end end end diff --git a/test/commands/docker_test.rb b/test/commands/docker_test.rb new file mode 100644 index 00000000..71d1d798 --- /dev/null +++ b/test/commands/docker_test.rb @@ -0,0 +1,26 @@ +require "test_helper" + +class CommandsDockerTest < ActiveSupport::TestCase + setup do + @config = { + service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ] + } + @docker = Mrsk::Commands::Docker.new(Mrsk::Configuration.new(@config)) + end + + test "install" do + assert_equal "curl -fsSL https://get.docker.com | sh", @docker.install.join(" ") + end + + test "installed?" do + assert_equal "docker -v", @docker.installed?.join(" ") + end + + test "running?" do + assert_equal "docker version", @docker.running?.join(" ") + end + + test "superuser?" do + assert_equal '[ "${EUID:-$(id -u)}" -eq 0 ]', @docker.superuser?.join(" ") + end +end From bfb70b211848e55db6b9a3603f28438a1ed28b50 Mon Sep 17 00:00:00 2001 From: Jberczel Date: Mon, 1 May 2023 15:26:50 -0400 Subject: [PATCH 02/13] Add local dependencies check Add checks for: * Docker installed locally * Docker buildx plugin installed locally * Dockerfile exists If checks fail, it will halt deployment and provide more specific error messages. Also adds a cli subcommand: `mrsk build dependencies` Fixes: #109 and #237 --- lib/mrsk/cli/build.rb | 26 +++++++++++++++++++++++--- lib/mrsk/commands/builder.rb | 24 ++++++++++++++++++++++++ lib/mrsk/commands/builder/base.rb | 10 +++++++++- test/cli/build_test.rb | 18 ++++++++++++++++++ test/commands/builder_test.rb | 9 +++++++++ 5 files changed, 83 insertions(+), 4 deletions(-) diff --git a/lib/mrsk/cli/build.rb b/lib/mrsk/cli/build.rb index 289f0bcb..695b0df2 100644 --- a/lib/mrsk/cli/build.rb +++ b/lib/mrsk/cli/build.rb @@ -1,4 +1,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base + + class BuildError < StandardError; end + desc "deliver", "Build app and push app image to registry then pull image on servers" def deliver with_lock do @@ -10,16 +13,18 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base desc "push", "Build and push app image to registry" def push with_lock do - cli = self + cli_build = self run_locally do begin - MRSK.with_verbosity(:debug) { execute *MRSK.builder.push } + if cli_build.dependencies + MRSK.with_verbosity(:debug) { execute *MRSK.builder.push } + end rescue SSHKit::Command::Failed => e if e.message =~ /(no builder)|(no such file or directory)/ error "Missing compatible builder, so creating a new one first" - if cli.create + if cli_build.create MRSK.with_verbosity(:debug) { execute *MRSK.builder.push } end else @@ -77,4 +82,19 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base puts capture(*MRSK.builder.info) end end + + desc "dependencies", "Check local dependencies" + def dependencies + run_locally do + begin + execute *MRSK.builder.dependencies + rescue SSHKit::Command::Failed => e + build_error = e.message =~ /command not found/ ? + "Docker is not installed locally" : + "Docker buildx plugin is not installed locally" + raise BuildError, build_error + end + end + true + end end diff --git a/lib/mrsk/commands/builder.rb b/lib/mrsk/commands/builder.rb index 85dbce0a..5c3f18e4 100644 --- a/lib/mrsk/commands/builder.rb +++ b/lib/mrsk/commands/builder.rb @@ -33,4 +33,28 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base def multiarch_remote @multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config) end + + def native_and_local? + name == 'native' + end + + def dependencies + if native_and_local? + docker_version + else + combine \ + docker_version, + docker_buildx_version + end + end + + private + + def docker_version + docker "--version" + end + + def docker_buildx_version + docker :buildx, "version" + end end diff --git a/lib/mrsk/commands/builder/base.rb b/lib/mrsk/commands/builder/base.rb index 15c6b188..460cf141 100644 --- a/lib/mrsk/commands/builder/base.rb +++ b/lib/mrsk/commands/builder/base.rb @@ -1,6 +1,9 @@ + class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base delegate :argumentize, to: Mrsk::Utils + class BuilderError < StandardError; end + def clean docker :image, :rm, "--force", config.absolute_image end @@ -17,6 +20,7 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base context end + private def build_tags [ "-t", config.absolute_image, "-t", config.latest_image ] @@ -35,7 +39,11 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base end def build_dockerfile - argumentize "--file", dockerfile + if Pathname.new(File.expand_path(dockerfile)).exist? + argumentize "--file", dockerfile + else + raise BuilderError, "Missing Dockerfile" + end end def args diff --git a/test/cli/build_test.rb b/test/cli/build_test.rb index a610d849..fa5c958d 100644 --- a/test/cli/build_test.rb +++ b/test/cli/build_test.rb @@ -9,6 +9,7 @@ class CliBuildTest < CliTestCase end test "push" do + Mrsk::Cli::Build.any_instance.stubs(:dependencies).returns(true) run_command("push").tap do |output| assert_match /docker buildx build --push --platform linux\/amd64,linux\/arm64 --builder mrsk-app-multiarch -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output end @@ -16,6 +17,7 @@ class CliBuildTest < CliTestCase test "push without builder" do stub_locking + Mrsk::Cli::Build.any_instance.stubs(:dependencies).returns(true) SSHKit::Backend::Abstract.any_instance.stubs(:execute) .with { |arg| arg == :docker } .raises(SSHKit::Command::Failed.new("no builder")) @@ -68,6 +70,22 @@ class CliBuildTest < CliTestCase end end + test "dependencies" do + Mrsk::Commands::Builder.any_instance.stubs(:native_and_local?).returns(false) + run_command("dependencies").tap do |output| + assert_match /docker --version && docker buildx version/, output + end + end + + test "dependencies with no buildx plugin" do + SSHKit::Backend::Abstract.any_instance.stubs(:execute) + .with(:docker, "--version", "&&", :docker, :buildx, "version") + .raises(SSHKit::Command::Failed.new("no buildx")) + + Mrsk::Commands::Builder.any_instance.stubs(:native_and_local?).returns(false) + assert_raises(Mrsk::Cli::Build::BuildError) { run_command("dependencies") } + end + private def run_command(*command) stdouted { Mrsk::Cli::Build.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) } diff --git a/test/commands/builder_test.rb b/test/commands/builder_test.rb index 1ecea021..e5f83f1c 100644 --- a/test/commands/builder_test.rb +++ b/test/commands/builder_test.rb @@ -52,12 +52,21 @@ class CommandsBuilderTest < ActiveSupport::TestCase end test "build dockerfile" do + Pathname.any_instance.expects(:exist?).returns(true).once builder = new_builder_command(builder: { "dockerfile" => "Dockerfile.xyz" }) assert_equal \ "-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile.xyz", builder.target.build_options.join(" ") end + test "missing dockerfile" do + Pathname.any_instance.expects(:exist?).returns(false).once + builder = new_builder_command(builder: { "dockerfile" => "Dockerfile.xyz" }) + assert_raises(Mrsk::Commands::Builder::Base::BuilderError) do + builder.target.build_options.join(" ") + end + end + test "build context" do builder = new_builder_command(builder: { "context" => ".." }) assert_equal \ From dc3be30b1606cecbf82c02ce0170352a8b198d8d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 May 2023 10:29:49 +0200 Subject: [PATCH 03/13] Style --- lib/mrsk/cli/build.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mrsk/cli/build.rb b/lib/mrsk/cli/build.rb index 695b0df2..cd2f0602 100644 --- a/lib/mrsk/cli/build.rb +++ b/lib/mrsk/cli/build.rb @@ -1,5 +1,4 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base - class BuildError < StandardError; end desc "deliver", "Build app and push app image to registry then pull image on servers" From cbda851436ee0e876e73cbf3ca7a9d68148c81d1 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 May 2023 13:34:56 +0200 Subject: [PATCH 04/13] Style --- lib/mrsk/commands/builder.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/mrsk/commands/builder.rb b/lib/mrsk/commands/builder.rb index 5c3f18e4..c8fb1c59 100644 --- a/lib/mrsk/commands/builder.rb +++ b/lib/mrsk/commands/builder.rb @@ -42,14 +42,13 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base if native_and_local? docker_version else - combine \ - docker_version, - docker_buildx_version + combine \ + docker_version, + docker_buildx_version end end private - def docker_version docker "--version" end From f719540e0cf0c84d1c0b732a070a3dc0f8fc06ca Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 May 2023 13:35:05 +0200 Subject: [PATCH 05/13] Style --- lib/mrsk/cli/build.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/mrsk/cli/build.rb b/lib/mrsk/cli/build.rb index cd2f0602..0c2bcfde 100644 --- a/lib/mrsk/cli/build.rb +++ b/lib/mrsk/cli/build.rb @@ -12,18 +12,18 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base desc "push", "Build and push app image to registry" def push with_lock do - cli_build = self + cli = self run_locally do begin - if cli_build.dependencies + if cli.dependencies MRSK.with_verbosity(:debug) { execute *MRSK.builder.push } end rescue SSHKit::Command::Failed => e if e.message =~ /(no builder)|(no such file or directory)/ error "Missing compatible builder, so creating a new one first" - if cli_build.create + if cli.create MRSK.with_verbosity(:debug) { execute *MRSK.builder.push } end else @@ -94,6 +94,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base raise BuildError, build_error end end + true end end From ae2effb80cce93b0079e6f71a3ff4056b06ea6da Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 May 2023 14:04:23 +0200 Subject: [PATCH 06/13] Improve clarity and intent --- lib/mrsk/cli/build.rb | 10 ++++++---- lib/mrsk/commands/builder.rb | 19 ++++++++----------- test/cli/build_test.rb | 13 +++++++------ test/configuration_test.rb | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/mrsk/cli/build.rb b/lib/mrsk/cli/build.rb index 0c2bcfde..75558812 100644 --- a/lib/mrsk/cli/build.rb +++ b/lib/mrsk/cli/build.rb @@ -16,7 +16,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base run_locally do begin - if cli.dependencies + if cli.verify_dependencies MRSK.with_verbosity(:debug) { execute *MRSK.builder.push } end rescue SSHKit::Command::Failed => e @@ -82,15 +82,17 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base end end - desc "dependencies", "Check local dependencies" - def dependencies + + desc "", "" # Really a private method, but needed to be invoked from #push + def verify_dependencies run_locally do begin - execute *MRSK.builder.dependencies + execute *MRSK.builder.ensure_dependencies_installed rescue SSHKit::Command::Failed => e build_error = e.message =~ /command not found/ ? "Docker is not installed locally" : "Docker buildx plugin is not installed locally" + raise BuildError, build_error end end diff --git a/lib/mrsk/commands/builder.rb b/lib/mrsk/commands/builder.rb index c8fb1c59..f7710b37 100644 --- a/lib/mrsk/commands/builder.rb +++ b/lib/mrsk/commands/builder.rb @@ -2,7 +2,7 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base delegate :create, :remove, :push, :clean, :pull, :info, to: :target def name - target.class.to_s.remove("Mrsk::Commands::Builder::").underscore + target.class.to_s.remove("Mrsk::Commands::Builder::").underscore.inquiry end def target @@ -34,26 +34,23 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base @multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config) end - def native_and_local? - name == 'native' - end - def dependencies - if native_and_local? - docker_version + def ensure_dependencies_installed + if name.native? + ensure_docker_installed else combine \ - docker_version, - docker_buildx_version + ensure_docker_installed, + ensure_buildx_installed end end private - def docker_version + def ensure_docker_installed docker "--version" end - def docker_buildx_version + def ensure_buildx_installed docker :buildx, "version" end end diff --git a/test/cli/build_test.rb b/test/cli/build_test.rb index fa5c958d..304f65f4 100644 --- a/test/cli/build_test.rb +++ b/test/cli/build_test.rb @@ -9,7 +9,7 @@ class CliBuildTest < CliTestCase end test "push" do - Mrsk::Cli::Build.any_instance.stubs(:dependencies).returns(true) + Mrsk::Cli::Build.any_instance.stubs(:verify_dependencies).returns(true) run_command("push").tap do |output| assert_match /docker buildx build --push --platform linux\/amd64,linux\/arm64 --builder mrsk-app-multiarch -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output end @@ -17,7 +17,7 @@ class CliBuildTest < CliTestCase test "push without builder" do stub_locking - Mrsk::Cli::Build.any_instance.stubs(:dependencies).returns(true) + Mrsk::Cli::Build.any_instance.stubs(:verify_dependencies).returns(true) SSHKit::Backend::Abstract.any_instance.stubs(:execute) .with { |arg| arg == :docker } .raises(SSHKit::Command::Failed.new("no builder")) @@ -70,9 +70,10 @@ class CliBuildTest < CliTestCase end end - test "dependencies" do - Mrsk::Commands::Builder.any_instance.stubs(:native_and_local?).returns(false) - run_command("dependencies").tap do |output| + test "verify dependencies" do + Mrsk::Commands::Builder.any_instance.stubs(:name).returns("remote".inquiry) + + run_command("verify_dependencies").tap do |output| assert_match /docker --version && docker buildx version/, output end end @@ -83,7 +84,7 @@ class CliBuildTest < CliTestCase .raises(SSHKit::Command::Failed.new("no buildx")) Mrsk::Commands::Builder.any_instance.stubs(:native_and_local?).returns(false) - assert_raises(Mrsk::Cli::Build::BuildError) { run_command("dependencies") } + assert_raises(Mrsk::Cli::Build::BuildError) { run_command("verify_dependencies") } end private diff --git a/test/configuration_test.rb b/test/configuration_test.rb index 2697673b..eda85484 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -48,7 +48,7 @@ class ConfigurationTest < ActiveSupport::TestCase end test "role" do - assert_equal "web", @config.role(:web).name + assert @config.role(:web).name.web? assert_equal "workers", @config_with_roles.role(:workers).name assert_nil @config.role(:missing) end From 98e7b995d577f56a51923db4f4784ee366542dc5 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 May 2023 14:04:37 +0200 Subject: [PATCH 07/13] Distinguish from local dependency verification --- lib/mrsk/cli/main.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mrsk/cli/main.rb b/lib/mrsk/cli/main.rb index 0a3d2ce0..3c16f533 100644 --- a/lib/mrsk/cli/main.rb +++ b/lib/mrsk/cli/main.rb @@ -17,7 +17,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base invoke_options = deploy_options runtime = print_runtime do - say "Ensure curl and Docker are installed...", :magenta + say "Ensure curl and Docker are installed on servers...", :magenta invoke "mrsk:cli:server:bootstrap", [], invoke_options say "Log into image registry...", :magenta From c611a1616a5547dbe3b9da1ce40371a599bfe293 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 May 2023 14:06:06 +0200 Subject: [PATCH 08/13] Distinguish from server dependencies --- lib/mrsk/cli/build.rb | 2 +- lib/mrsk/commands/builder.rb | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/mrsk/cli/build.rb b/lib/mrsk/cli/build.rb index 75558812..9831f4e6 100644 --- a/lib/mrsk/cli/build.rb +++ b/lib/mrsk/cli/build.rb @@ -87,7 +87,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base def verify_dependencies run_locally do begin - execute *MRSK.builder.ensure_dependencies_installed + execute *MRSK.builder.ensure_local_dependencies_installed rescue SSHKit::Command::Failed => e build_error = e.message =~ /command not found/ ? "Docker is not installed locally" : diff --git a/lib/mrsk/commands/builder.rb b/lib/mrsk/commands/builder.rb index f7710b37..58ca626e 100644 --- a/lib/mrsk/commands/builder.rb +++ b/lib/mrsk/commands/builder.rb @@ -35,22 +35,22 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base end - def ensure_dependencies_installed + def ensure_local_dependencies_installed if name.native? - ensure_docker_installed + ensure_local_docker_installed else combine \ - ensure_docker_installed, - ensure_buildx_installed + ensure_local_docker_installed, + ensure_local_buildx_installed end end private - def ensure_docker_installed + def ensure_local_docker_installed docker "--version" end - def ensure_buildx_installed + def ensure_local_buildx_installed docker :buildx, "version" end end From bb74a74dc48de7484a065bb12ceefe64f0f58569 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 May 2023 14:07:30 +0200 Subject: [PATCH 09/13] Style --- lib/mrsk/commands/builder/base.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mrsk/commands/builder/base.rb b/lib/mrsk/commands/builder/base.rb index 460cf141..2d257e52 100644 --- a/lib/mrsk/commands/builder/base.rb +++ b/lib/mrsk/commands/builder/base.rb @@ -1,9 +1,9 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base - delegate :argumentize, to: Mrsk::Utils - class BuilderError < StandardError; end + delegate :argumentize, to: Mrsk::Utils + def clean docker :image, :rm, "--force", config.absolute_image end From a22e27dbf809bdd44247f20272a753dd0f8d0a45 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 May 2023 14:07:47 +0200 Subject: [PATCH 10/13] Reveal configured dockerfile path --- lib/mrsk/commands/builder/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mrsk/commands/builder/base.rb b/lib/mrsk/commands/builder/base.rb index 2d257e52..32dcba4f 100644 --- a/lib/mrsk/commands/builder/base.rb +++ b/lib/mrsk/commands/builder/base.rb @@ -42,7 +42,7 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base if Pathname.new(File.expand_path(dockerfile)).exist? argumentize "--file", dockerfile else - raise BuilderError, "Missing Dockerfile" + raise BuilderError, "Missing #{dockerfile}" end end From a2d99e48bf01eccabac94c6322bbea1a68308398 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 May 2023 14:08:29 +0200 Subject: [PATCH 11/13] Naming --- test/cli/build_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cli/build_test.rb b/test/cli/build_test.rb index 304f65f4..d14c1cd4 100644 --- a/test/cli/build_test.rb +++ b/test/cli/build_test.rb @@ -70,7 +70,7 @@ class CliBuildTest < CliTestCase end end - test "verify dependencies" do + test "verify local dependencies" do Mrsk::Commands::Builder.any_instance.stubs(:name).returns("remote".inquiry) run_command("verify_dependencies").tap do |output| @@ -78,7 +78,7 @@ class CliBuildTest < CliTestCase end end - test "dependencies with no buildx plugin" do + test "verify local dependencies with no buildx plugin" do SSHKit::Backend::Abstract.any_instance.stubs(:execute) .with(:docker, "--version", "&&", :docker, :buildx, "version") .raises(SSHKit::Command::Failed.new("no buildx")) From 263a24afe34441699e237eab1c7354b44fb256c5 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 May 2023 14:09:10 +0200 Subject: [PATCH 12/13] Further distinguish dependency verification --- lib/mrsk/cli/build.rb | 4 ++-- test/cli/build_test.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/mrsk/cli/build.rb b/lib/mrsk/cli/build.rb index 9831f4e6..0e6e9a7c 100644 --- a/lib/mrsk/cli/build.rb +++ b/lib/mrsk/cli/build.rb @@ -16,7 +16,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base run_locally do begin - if cli.verify_dependencies + if cli.verify_local_dependencies MRSK.with_verbosity(:debug) { execute *MRSK.builder.push } end rescue SSHKit::Command::Failed => e @@ -84,7 +84,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base desc "", "" # Really a private method, but needed to be invoked from #push - def verify_dependencies + def verify_local_dependencies run_locally do begin execute *MRSK.builder.ensure_local_dependencies_installed diff --git a/test/cli/build_test.rb b/test/cli/build_test.rb index d14c1cd4..0835262c 100644 --- a/test/cli/build_test.rb +++ b/test/cli/build_test.rb @@ -9,7 +9,7 @@ class CliBuildTest < CliTestCase end test "push" do - Mrsk::Cli::Build.any_instance.stubs(:verify_dependencies).returns(true) + Mrsk::Cli::Build.any_instance.stubs(:verify_local_dependencies).returns(true) run_command("push").tap do |output| assert_match /docker buildx build --push --platform linux\/amd64,linux\/arm64 --builder mrsk-app-multiarch -t dhh\/app:999 -t dhh\/app:latest --label service="app" --file Dockerfile \. as .*@localhost/, output end @@ -17,7 +17,7 @@ class CliBuildTest < CliTestCase test "push without builder" do stub_locking - Mrsk::Cli::Build.any_instance.stubs(:verify_dependencies).returns(true) + Mrsk::Cli::Build.any_instance.stubs(:verify_local_dependencies).returns(true) SSHKit::Backend::Abstract.any_instance.stubs(:execute) .with { |arg| arg == :docker } .raises(SSHKit::Command::Failed.new("no builder")) @@ -73,7 +73,7 @@ class CliBuildTest < CliTestCase test "verify local dependencies" do Mrsk::Commands::Builder.any_instance.stubs(:name).returns("remote".inquiry) - run_command("verify_dependencies").tap do |output| + run_command("verify_local_dependencies").tap do |output| assert_match /docker --version && docker buildx version/, output end end @@ -84,7 +84,7 @@ class CliBuildTest < CliTestCase .raises(SSHKit::Command::Failed.new("no buildx")) Mrsk::Commands::Builder.any_instance.stubs(:native_and_local?).returns(false) - assert_raises(Mrsk::Cli::Build::BuildError) { run_command("verify_dependencies") } + assert_raises(Mrsk::Cli::Build::BuildError) { run_command("verify_local_dependencies") } end private From 787ef9663902a1377b76360d4d78f13dc89fbbab Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 2 May 2023 14:41:18 +0200 Subject: [PATCH 13/13] Don't run actions twice on PRs --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d9d2008..f8094c05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,9 @@ name: CI -on: [push, pull_request] +on: + push: + branches: + - main + pull_request: jobs: tests: strategy: