diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ef7d0aee..e87ec3c17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Ruby and install gems uses: ruby/setup-ruby@v1 with: - ruby-version: 3.3.0 + ruby-version: 3.4.1 bundler-cache: true - name: Run Rubocop run: bundle exec rubocop --parallel @@ -28,7 +28,7 @@ jobs: - "3.1" - "3.2" - "3.3" - - "3.4.0-preview2" + - "3.4" gemfile: - Gemfile - gemfiles/rails_edge.gemfile diff --git a/Gemfile.lock b/Gemfile.lock index 67963d628..217ca1d5c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,9 +16,9 @@ PATH GEM remote: https://rubygems.org/ specs: - actionpack (8.0.0.1) - actionview (= 8.0.0.1) - activesupport (= 8.0.0.1) + actionpack (8.0.1) + actionview (= 8.0.1) + activesupport (= 8.0.1) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -26,13 +26,13 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actionview (8.0.0.1) - activesupport (= 8.0.0.1) + actionview (8.0.1) + activesupport (= 8.0.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activesupport (8.0.0.1) + activesupport (8.0.1) base64 benchmark (>= 0.3) bigdecimal @@ -48,32 +48,30 @@ GEM ast (2.4.2) base64 (0.2.0) bcrypt_pbkdf (1.1.1) - bcrypt_pbkdf (1.1.1-arm64-darwin) - bcrypt_pbkdf (1.1.1-x86_64-darwin) benchmark (0.4.0) - bigdecimal (3.1.8) + bigdecimal (3.1.9) builder (3.3.0) concurrent-ruby (1.3.4) - connection_pool (2.4.1) + connection_pool (2.5.0) crass (1.0.6) date (3.4.1) - debug (1.9.2) + debug (1.10.0) irb (~> 1.10) reline (>= 0.3.8) - dotenv (3.1.5) + dotenv (3.1.7) drb (2.2.1) ed25519 (1.3.0) - erubi (1.13.0) + erubi (1.13.1) i18n (1.14.6) concurrent-ruby (~> 1.0) io-console (0.8.0) - irb (1.14.2) + irb (1.14.3) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.9.0) + json (2.9.1) language_server-protocol (3.17.0.3) - logger (1.6.3) - loofah (2.23.1) + logger (1.6.5) + loofah (2.24.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) minitest (5.25.4) @@ -84,25 +82,26 @@ GEM net-sftp (4.0.0) net-ssh (>= 5.0.0, < 8.0.0) net-ssh (7.3.0) - nokogiri (1.17.2-arm64-darwin) + nokogiri (1.18.1-arm64-darwin) racc (~> 1.4) - nokogiri (1.17.2-x86_64-darwin) + nokogiri (1.18.1-x86_64-darwin) racc (~> 1.4) - nokogiri (1.17.2-x86_64-linux) + nokogiri (1.18.1-x86_64-linux-gnu) racc (~> 1.4) ostruct (0.6.1) parallel (1.26.3) parser (3.3.6.0) ast (~> 2.4.1) racc - psych (5.2.1) + psych (5.2.2) date stringio racc (1.8.1) rack (3.1.8) - rack-session (2.0.0) + rack-session (2.1.0) + base64 (>= 0.1.0) rack (>= 3.0.0) - rack-test (2.1.0) + rack-test (2.2.0) rack (>= 1.3) rackup (2.2.1) rack (>= 3) @@ -113,9 +112,9 @@ GEM rails-html-sanitizer (1.6.2) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (8.0.0.1) - actionpack (= 8.0.0.1) - activesupport (= 8.0.0.1) + railties (8.0.1) + actionpack (= 8.0.1) + activesupport (= 8.0.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -123,12 +122,12 @@ GEM zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) - rdoc (6.8.1) + rdoc (6.10.0) psych (>= 4.0.0) - regexp_parser (2.9.3) - reline (0.5.12) + regexp_parser (2.10.0) + reline (0.6.0) io-console (~> 0.5) - rubocop (1.69.2) + rubocop (1.70.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -138,15 +137,15 @@ GEM rubocop-ast (>= 1.36.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.36.2) + rubocop-ast (1.37.0) parser (>= 3.3.1.0) rubocop-minitest (0.36.0) rubocop (>= 1.61, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-performance (1.23.0) + rubocop-performance (1.23.1) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.27.0) + rubocop-rails (2.28.0) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.52.0, < 2.0) @@ -158,7 +157,7 @@ GEM rubocop-rails ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) - securerandom (0.4.0) + securerandom (0.4.1) sshkit (1.23.2) base64 net-scp (>= 1.1.2) @@ -169,7 +168,7 @@ GEM thor (1.3.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (3.1.2) + unicode-display_width (3.1.4) unicode-emoji (~> 4.0, >= 4.0.4) unicode-emoji (4.0.4) uri (1.0.2) @@ -189,4 +188,4 @@ DEPENDENCIES rubocop-rails-omakase BUNDLED WITH - 2.4.3 + 2.6.2 diff --git a/lib/kamal/cli/build.rb b/lib/kamal/cli/build.rb index 53ecb0bbe..084a894b8 100644 --- a/lib/kamal/cli/build.rb +++ b/lib/kamal/cli/build.rb @@ -60,14 +60,16 @@ def push desc "pull", "Pull app image from registry onto servers" def pull - if (first_hosts = mirror_hosts).any? - #  Pull on a single host per mirror first to seed them - say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta - pull_on_hosts(first_hosts) - say "Pulling image on remaining hosts...", :magenta - pull_on_hosts(KAMAL.hosts - first_hosts) - else - pull_on_hosts(KAMAL.hosts) + Kamal::Cli::PortForwarding.new(KAMAL.hosts, KAMAL.config.registry.local_port).forward do + if (first_hosts = mirror_hosts).any? + #  Pull on a single host per mirror first to seed them + say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta + pull_on_hosts(first_hosts) + say "Pulling image on remaining hosts...", :magenta + pull_on_hosts(KAMAL.hosts - first_hosts) + else + pull_on_hosts(KAMAL.hosts) + end end end diff --git a/lib/kamal/cli/main.rb b/lib/kamal/cli/main.rb index 5fdb5469a..12451afd1 100644 --- a/lib/kamal/cli/main.rb +++ b/lib/kamal/cli/main.rb @@ -22,7 +22,7 @@ def deploy invoke_options = deploy_options say "Log into image registry...", :magenta - invoke "kamal:cli:registry:login", [], invoke_options.merge(skip_local: options[:skip_push]) + invoke "kamal:cli:registry:setup", [], invoke_options.merge(skip_local: options[:skip_push]) if options[:skip_push] say "Pull app image...", :magenta @@ -184,7 +184,7 @@ def remove invoke "kamal:cli:app:remove", [], options.without(:confirmed) invoke "kamal:cli:proxy:remove", [], options.without(:confirmed) invoke "kamal:cli:accessory:remove", [ "all" ], options - invoke "kamal:cli:registry:logout", [], options.without(:confirmed).merge(skip_local: true) + invoke "kamal:cli:registry:remove", [], options.without(:confirmed).merge(skip_local: true) end end end diff --git a/lib/kamal/cli/port_forwarding.rb b/lib/kamal/cli/port_forwarding.rb new file mode 100644 index 000000000..76d0c7f1e --- /dev/null +++ b/lib/kamal/cli/port_forwarding.rb @@ -0,0 +1,44 @@ +class Kamal::Cli::PortForwarding + attr_reader :hosts, :port + + def initialize(hosts, port) + @hosts = hosts + @port = port + end + + def forward + if KAMAL.config.registry.local? + @done = false + forward_ports + end + + yield + ensure + stop + end + + private + + def stop + @done = true + @threads.to_a.each(&:join) + end + + def forward_ports + @threads = hosts.map do |host| + Thread.new do + Net::SSH.start(host, KAMAL.config.ssh.user) do |ssh| + ssh.forward.remote(port, "localhost", port, "localhost") + ssh.loop(0.1) do + if @done + ssh.forward.cancel_remote(port, "localhost") + break + else + true + end + end + end + end + end + end +end diff --git a/lib/kamal/cli/registry.rb b/lib/kamal/cli/registry.rb index 9d5d9d937..7301e8516 100644 --- a/lib/kamal/cli/registry.rb +++ b/lib/kamal/cli/registry.rb @@ -1,17 +1,25 @@ class Kamal::Cli::Registry < Kamal::Cli::Base - desc "login", "Log in to registry locally and remotely" + desc "setup", "Setup local registry or log in to remote registry locally and remotely" option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login" option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login" - def login - run_locally { execute *KAMAL.registry.login } unless options[:skip_local] - on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote] + def setup + if KAMAL.registry.local? + run_locally { execute *KAMAL.registry.setup } unless options[:skip_local] + else + run_locally { execute *KAMAL.registry.login } unless options[:skip_local] + on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote] + end end - desc "logout", "Log out of registry locally and remotely" + desc "remove", "Remove local registry or log out of remote registry locally and remotely" option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login" option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login" - def logout - run_locally { execute *KAMAL.registry.logout } unless options[:skip_local] - on(KAMAL.hosts) { execute *KAMAL.registry.logout } unless options[:skip_remote] + def remove + if KAMAL.registry.local? + run_locally { execute *KAMAL.registry.remove, raise_on_non_zero_exit: false } unless options[:skip_local] + else + run_locally { execute *KAMAL.registry.logout } unless options[:skip_local] + on(KAMAL.hosts) { execute *KAMAL.registry.logout } unless options[:skip_remote] + end end end diff --git a/lib/kamal/commands/builder/local.rb b/lib/kamal/commands/builder/local.rb index c4215e333..2b1f2052d 100644 --- a/lib/kamal/commands/builder/local.rb +++ b/lib/kamal/commands/builder/local.rb @@ -1,6 +1,15 @@ class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base def create - docker :buildx, :create, "--name", builder_name, "--driver=#{driver}" unless docker_driver? + return if docker_driver? + + options = + if KAMAL.registry.local? + "--driver=#{driver} --driver-opt network=host" + else + "--driver=#{driver}" + end + + docker :buildx, :create, "--name", builder_name, options end def remove @@ -9,6 +18,10 @@ def remove private def builder_name - "kamal-local-#{driver}" + if KAMAL.registry.local? + "kamal-local-registry-#{driver}" + else + "kamal-local-#{driver}" + end end end diff --git a/lib/kamal/commands/registry.rb b/lib/kamal/commands/registry.rb index 69f953608..b0d689454 100644 --- a/lib/kamal/commands/registry.rb +++ b/lib/kamal/commands/registry.rb @@ -1,7 +1,10 @@ class Kamal::Commands::Registry < Kamal::Commands::Base delegate :registry, to: :config + delegate :local?, :local_port, to: :registry def login + return if local? + docker :login, registry.server, "-u", sensitive(Kamal::Utils.escape_shell_value(registry.username)), @@ -11,4 +14,18 @@ def login def logout docker :logout, registry.server end + + def setup + combine \ + docker(:start, "kamal-docker-registry"), + docker(:run, "--detach", "-p", "127.0.0.1:#{local_port}:5000", "--name", "kamal-docker-registry", "registry:2"), + by: "||" + end + + def remove + combine \ + docker(:stop, "kamal-docker-registry"), + docker(:rm, "kamal-docker-registry"), + by: "&&" + end end diff --git a/lib/kamal/configuration/registry.rb b/lib/kamal/configuration/registry.rb index 763cf976a..9ed14aa93 100644 --- a/lib/kamal/configuration/registry.rb +++ b/lib/kamal/configuration/registry.rb @@ -21,6 +21,14 @@ def password lookup("password") end + def local? + server&.match?("^localhost[:$]") + end + + def local_port + local? ? (server.split(":").last.to_i || 80) : nil + end + private def lookup(key) if registry_config[key].is_a?(Array) diff --git a/lib/kamal/configuration/validator/registry.rb b/lib/kamal/configuration/validator/registry.rb index 2b9c0859a..55f4d2f67 100644 --- a/lib/kamal/configuration/validator/registry.rb +++ b/lib/kamal/configuration/validator/registry.rb @@ -15,10 +15,12 @@ def validate_string_or_one_item_array!(key) with_context(key) do value = config[key] - error "is required" unless value.present? + unless config["server"]&.match?("^localhost[:$]") + error "is required" unless value.present? - unless value.is_a?(String) || (value.is_a?(Array) && value.size == 1 && value.first.is_a?(String)) - error "should be a string or an array with one string (for secret lookup)" + unless value.is_a?(String) || (value.is_a?(Array) && value.size == 1 && value.first.is_a?(String)) + error "should be a string or an array with one string (for secret lookup)" + end end end end diff --git a/test/cli/build_test.rb b/test/cli/build_test.rb index 4259fa5bb..92d981128 100644 --- a/test/cli/build_test.rb +++ b/test/cli/build_test.rb @@ -112,6 +112,42 @@ class CliBuildTest < CliTestCase end end + test "push without builder for local registry" do + with_build_directory do |build_directory| + stub_setup + + SSHKit::Backend::Abstract.any_instance.expects(:execute) + .with(:docker, "--version", "&&", :docker, :buildx, "version") + + SSHKit::Backend::Abstract.any_instance.expects(:execute) + .with(:docker, :buildx, :rm, "kamal-local-registry-docker-container") + + SSHKit::Backend::Abstract.any_instance.expects(:execute) + .with(:docker, :buildx, :create, "--name", "kamal-local-registry-docker-container", "--driver=docker-container --driver-opt network=host") + + SSHKit::Backend::Abstract.any_instance.expects(:execute) + .with(:docker, :buildx, :inspect, "kamal-local-registry-docker-container") + .raises(SSHKit::Command::Failed.new("no builder")) + + SSHKit::Backend::Abstract.any_instance.expects(:execute).with { |*args| args.first.start_with?("git") } + + SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) + .with(:git, "-C", anything, :"rev-parse", :HEAD) + .returns(Kamal::Git.revision) + + SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) + .with(:git, "-C", anything, :status, "--porcelain") + .returns("") + + SSHKit::Backend::Abstract.any_instance.expects(:execute) + .with(:docker, :buildx, :build, "--push", "--platform", "linux/amd64", "--builder", "kamal-local-registry-docker-container", "-t", "localhost:5000/dhh/app:999", "-t", "localhost:5000/dhh/app:latest", "--label", "service=\"app\"", "--file", "Dockerfile", ".") + + run_command("push", fixture: :with_local_registry_and_accessories).tap do |output| + assert_match /WARN Missing compatible builder, so creating a new one first/, output + end + end + end + test "push without builder" do with_build_directory do |build_directory| stub_setup diff --git a/test/cli/main_test.rb b/test/cli/main_test.rb index cd0efe1f8..b0877e880 100644 --- a/test/cli/main_test.rb +++ b/test/cli/main_test.rb @@ -22,7 +22,7 @@ class CliMainTest < CliTestCase Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options) # deploy - Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: true)) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: true)) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:pull", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)) @@ -42,11 +42,38 @@ class CliMainTest < CliTestCase end end + test "deploy with local registry" do + with_test_secrets("secrets" => "DB_PASSWORD=secret") do + invoke_options = { "config_file" => "test/fixtures/deploy_with_local_registry.yml", "version" => "999", "skip_hooks" => false, "verbose" => true } + + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: false)) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:boot", [], invoke_options) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], invoke_options) + + Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true) + hook_variables = { version: 999, service_version: "app@999", hosts: "1.1.1.1,1.1.1.2", command: "deploy" } + + run_command("deploy", "--verbose", config_file: "deploy_with_local_registry").tap do |output| + assert_hook_ran "pre-connect", output, **hook_variables + assert_match /Log into image registry/, output + assert_match /Build and push app image/, output + assert_hook_ran "pre-deploy", output, **hook_variables, secrets: true + assert_match /Ensure kamal-proxy is running/, output + assert_match /Detect stale containers/, output + assert_match /Prune old containers and images/, output + assert_hook_ran "post-deploy", output, **hook_variables, runtime: true, secrets: true + end + end + end + test "deploy" do with_test_secrets("secrets" => "DB_PASSWORD=secret") do invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, "verbose" => true } - Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false)) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: false)) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)) @@ -72,7 +99,7 @@ class CliMainTest < CliTestCase test "deploy with skip_push" do invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false } - Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: true)) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: true)) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:pull", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)) @@ -159,7 +186,7 @@ class CliMainTest < CliTestCase invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false, :skip_local => false } Kamal::Cli::Main.any_instance.expects(:invoke) - .with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false)) + .with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: false)) .raises(RuntimeError) assert_not KAMAL.holding_lock? @@ -172,7 +199,7 @@ class CliMainTest < CliTestCase test "deploy with skipped hooks" do invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => true } - Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false)) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: false)) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)) @@ -187,7 +214,7 @@ class CliMainTest < CliTestCase test "deploy with missing secrets" do invoke_options = { "config_file" => "test/fixtures/deploy_with_secrets.yml", "version" => "999", "skip_hooks" => false } - Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options.merge(skip_local: false)) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:setup", [], invoke_options.merge(skip_local: false)) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:deliver", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:boot", [], invoke_options) Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)) @@ -289,6 +316,16 @@ class CliMainTest < CliTestCase end end + test "remove" do + options = { "config_file" => "test/fixtures/deploy_simple.yml", "skip_hooks" => false, "confirmed" => true } + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:remove", [], options) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:remove", [], options) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:remove", [ "all" ], options) + Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:remove", [], options.merge(skip_local: true)) + + run_command("remove", "-y") + end + test "details" do Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:proxy:details") Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:details") diff --git a/test/cli/registry_test.rb b/test/cli/registry_test.rb index c5423fe72..858f6e3cc 100644 --- a/test/cli/registry_test.rb +++ b/test/cli/registry_test.rb @@ -1,50 +1,62 @@ require_relative "cli_test_case" class CliRegistryTest < CliTestCase - test "login" do - run_command("login").tap do |output| + test "setup" do + run_command("setup").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.\d/, output end end - test "login skip local" do - run_command("login", "-L").tap do |output| + test "setup skip local" do + run_command("setup", "-L").tap do |output| assert_no_match /docker login -u \[REDACTED\] -p \[REDACTED\] as .*@localhost/, output assert_match /docker login -u \[REDACTED\] -p \[REDACTED\] on 1.1.1.\d/, output end end - test "login skip remote" do - run_command("login", "-R").tap do |output| + test "setup skip remote" do + run_command("setup", "-R").tap do |output| assert_match /docker login -u \[REDACTED\] -p \[REDACTED\] as .*@localhost/, output assert_no_match /docker login -u \[REDACTED\] -p \[REDACTED\] on 1.1.1.\d/, output end end - test "logout" do - run_command("logout").tap do |output| + test "remove" do + run_command("remove").tap do |output| assert_match /docker logout as .*@localhost/, output assert_match /docker logout on 1.1.1.\d/, output end end - test "logout skip local" do - run_command("logout", "-L").tap do |output| + test "remove skip local" do + run_command("remove", "-L").tap do |output| assert_no_match /docker logout as .*@localhost/, output assert_match /docker logout on 1.1.1.\d/, output end end - test "logout skip remote" do - run_command("logout", "-R").tap do |output| + test "remove skip remote" do + run_command("remove", "-R").tap do |output| assert_match /docker logout as .*@localhost/, output assert_no_match /docker logout on 1.1.1.\d/, output end end + test "setup local registry" do + run_command("setup", fixture: :with_local_registry).tap do |output| + assert_match /docker start kamal-docker-registry || docker run --detach -p 127.0.0.1:5000:5000 --name kamal-docker-registry registry:2 as .*@localhost/, output + end + end + + test "remove local registry" do + run_command("remove", fixture: :with_local_registry).tap do |output| + assert_match /docker stop kamal-docker-registry && docker rm kamal-docker-registry as .*@localhost/, output + end + end + private - def run_command(*command) - stdouted { Kamal::Cli::Registry.start([ *command, "-c", "test/fixtures/deploy_with_accessories.yml" ]) } + def run_command(*command, fixture: :with_accessories) + stdouted { Kamal::Cli::Registry.start([ *command, "-c", "test/fixtures/deploy_#{fixture}.yml" ]) } end end diff --git a/test/commands/registry_test.rb b/test/commands/registry_test.rb index cf2734b72..b19978e4b 100755 --- a/test/commands/registry_test.rb +++ b/test/commands/registry_test.rb @@ -55,6 +55,15 @@ class CommandsRegistryTest < ActiveSupport::TestCase registry.logout.join(" ") end + test "registry setup" do + @config[:registry] = { "server" => "localhost:5000" } + assert_equal "docker start kamal-docker-registry || docker run --detach -p 127.0.0.1:5000:5000 --name kamal-docker-registry registry:2", registry.setup.join(" ") + end + + test "registry remove" do + assert_equal "docker stop kamal-docker-registry && docker rm kamal-docker-registry", registry.remove.join(" ") + end + private def registry Kamal::Commands::Registry.new Kamal::Configuration.new(@config) diff --git a/test/fixtures/deploy_with_local_registry.yml b/test/fixtures/deploy_with_local_registry.yml new file mode 100644 index 000000000..80ab6fb88 --- /dev/null +++ b/test/fixtures/deploy_with_local_registry.yml @@ -0,0 +1,10 @@ +service: app +image: dhh/app +servers: + web: + - "1.1.1.1" + - "1.1.1.2" +registry: + server: localhost:5000 +builder: + arch: amd64 diff --git a/test/fixtures/deploy_with_local_registry_and_accessories.yml b/test/fixtures/deploy_with_local_registry_and_accessories.yml new file mode 100644 index 000000000..cf2aa8a42 --- /dev/null +++ b/test/fixtures/deploy_with_local_registry_and_accessories.yml @@ -0,0 +1,37 @@ +service: app +image: dhh/app +servers: + web: + - "1.1.1.1" + - "1.1.1.2" + workers: + - "1.1.1.3" + - "1.1.1.4" +registry: + server: localhost:5000 +builder: + arch: amd64 + +accessories: + mysql: + image: mysql:5.7 + host: 1.1.1.3 + port: 3306 + env: + clear: + MYSQL_ROOT_HOST: '%' + secret: + - MYSQL_ROOT_PASSWORD + files: + - test/fixtures/files/my.cnf:/etc/mysql/my.cnf + directories: + - data:/var/lib/mysql + redis: + image: redis:latest + roles: + - web + port: 6379 + directories: + - data:/data + +readiness_delay: 0 diff --git a/test/integration/app_test.rb b/test/integration/app_test.rb index 039ef012c..cb8ff001e 100644 --- a/test/integration/app_test.rb +++ b/test/integration/app_test.rb @@ -27,14 +27,14 @@ class AppTest < IntegrationTest images = kamal :app, :images, capture: true assert_match "App Host: vm1", images assert_match "App Host: vm2", images - assert_match /registry:4443\/app\s+#{latest_app_version}/, images - assert_match /registry:4443\/app\s+latest/, images + assert_match /localhost:5000\/app\s+#{latest_app_version}/, images + assert_match /localhost:5000\/app\s+latest/, images containers = kamal :app, :containers, capture: true assert_match "App Host: vm1", containers assert_match "App Host: vm2", containers - assert_match "registry:4443/app:#{latest_app_version}", containers - assert_match "registry:4443/app:latest", containers + assert_match "localhost:5000/app:#{latest_app_version}", containers + assert_match "localhost:5000/app:latest", containers exec_output = kamal :app, :exec, :ps, capture: true assert_match "App Host: vm1", exec_output diff --git a/test/integration/docker/deployer/app/config/deploy.yml b/test/integration/docker/deployer/app/config/deploy.yml index 242d893a3..26fc1414f 100644 --- a/test/integration/docker/deployer/app/config/deploy.yml +++ b/test/integration/docker/deployer/app/config/deploy.yml @@ -26,9 +26,7 @@ readiness_delay: 0 proxy: host: 127.0.0.1 registry: - server: registry:4443 - username: root - password: root + server: localhost:5000 builder: driver: docker arch: <%= Kamal::Utils.docker_arch %> diff --git a/test/integration/main_test.rb b/test/integration/main_test.rb index ce32e6404..d5141f07e 100644 --- a/test/integration/main_test.rb +++ b/test/integration/main_test.rb @@ -29,7 +29,7 @@ class MainTest < IntegrationTest assert_match /App Host: vm1/, details assert_match /App Host: vm2/, details assert_match /basecamp\/kamal-proxy:#{Kamal::Configuration::PROXY_MINIMUM_VERSION}/, details - assert_match /registry:4443\/app:#{first_version}/, details + assert_match /localhost:5000\/app:#{first_version}/, details audit = kamal :audit, capture: true assert_match /Booted app version #{first_version}.*Booted app version #{second_version}.*Booted app version #{first_version}.*/m, audit @@ -63,8 +63,8 @@ class MainTest < IntegrationTest assert_equal [ "vm1", "vm2" ], config[:hosts] assert_equal "vm1", config[:primary_host] assert_equal version, config[:version] - assert_equal "registry:4443/app", config[:repository] - assert_equal "registry:4443/app:#{version}", config[:absolute_image] + assert_equal "localhost:5000/app", config[:repository] + assert_equal "localhost:5000/app:#{version}", config[:absolute_image] assert_equal "app-#{version}", config[:service_with_version] assert_equal [], config[:volume_args] assert_equal({ user: "root", port: 22, keepalive: true, keepalive_interval: 30, log_level: :fatal }, config[:ssh_options])