Use local docker registry to push and pull app images
This commit is contained in:
@@ -67,16 +67,18 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
|
||||
desc "pull", "Pull app image from registry onto servers"
|
||||
def pull
|
||||
login_to_registry_remotely
|
||||
login_to_registry_remotely unless KAMAL.registry.local?
|
||||
|
||||
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.app_hosts - first_hosts)
|
||||
else
|
||||
pull_on_hosts(KAMAL.app_hosts)
|
||||
forward_local_registry_port 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.app_hosts - first_hosts)
|
||||
else
|
||||
pull_on_hosts(KAMAL.app_hosts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -192,7 +194,11 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
|
||||
def login_to_registry_locally
|
||||
run_locally do
|
||||
execute *KAMAL.registry.login
|
||||
if KAMAL.registry.local?
|
||||
execute *KAMAL.registry.setup
|
||||
else
|
||||
execute *KAMAL.registry.login
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -201,4 +207,14 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
||||
execute *KAMAL.registry.login
|
||||
end
|
||||
end
|
||||
|
||||
def forward_local_registry_port(&block)
|
||||
if KAMAL.config.registry.local?
|
||||
Kamal::Cli::PortForwarding.
|
||||
new(KAMAL.hosts, KAMAL.config.registry.local_port).
|
||||
forward(&block)
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -182,7 +182,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
||||
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
|
||||
|
||||
42
lib/kamal/cli/port_forwarding.rb
Normal file
42
lib/kamal/cli/port_forwarding.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
class Kamal::Cli::PortForwarding
|
||||
attr_reader :hosts, :port
|
||||
|
||||
def initialize(hosts, port)
|
||||
@hosts = hosts
|
||||
@port = port
|
||||
end
|
||||
|
||||
def forward
|
||||
@done = false
|
||||
forward_ports
|
||||
|
||||
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
|
||||
@@ -1,19 +1,27 @@
|
||||
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
|
||||
def setup
|
||||
ensure_docker_installed unless options[:skip_local]
|
||||
|
||||
run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
|
||||
on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
|
||||
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
|
||||
|
||||
@@ -25,13 +25,14 @@ proxy:
|
||||
|
||||
# Credentials for your image host.
|
||||
registry:
|
||||
server: localhost:5555
|
||||
# Specify the registry server, if you're not using Docker Hub
|
||||
# server: registry.digitalocean.com / ghcr.io / ...
|
||||
username: my-user
|
||||
# username: my-user
|
||||
|
||||
# Always use an access token rather than real password (pulled from .kamal/secrets).
|
||||
password:
|
||||
- KAMAL_REGISTRY_PASSWORD
|
||||
# password:
|
||||
# - KAMAL_REGISTRY_PASSWORD
|
||||
|
||||
# Configure builder setup.
|
||||
builder:
|
||||
@@ -39,7 +40,6 @@ builder:
|
||||
# Pass in additional build args needed for your Dockerfile.
|
||||
# args:
|
||||
# RUBY_VERSION: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
|
||||
|
||||
# Inject ENV variables into containers (secrets come from .kamal/secrets).
|
||||
#
|
||||
# env:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.
|
||||
|
||||
# Option 1: Read secrets from the environment
|
||||
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
|
||||
# KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
|
||||
|
||||
# Option 2: Read secrets via a command
|
||||
# RAILS_MASTER_KEY=$(cat config/master.key)
|
||||
|
||||
@@ -21,7 +21,7 @@ class Kamal::Commander
|
||||
end
|
||||
|
||||
def config
|
||||
@config ||= Kamal::Configuration.create_from(**@config_kwargs).tap do |config|
|
||||
@config ||= Kamal::Configuration.create_from(**@config_kwargs.to_h).tap do |config|
|
||||
@config_kwargs = nil
|
||||
configure_sshkit_with(config)
|
||||
end
|
||||
|
||||
@@ -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 @@ class Kamal::Commands::Builder::Local < Kamal::Commands::Builder::Base
|
||||
|
||||
private
|
||||
def builder_name
|
||||
"kamal-local-#{driver}"
|
||||
if KAMAL.registry.local?
|
||||
"kamal-local-registry-#{driver}"
|
||||
else
|
||||
"kamal-local-#{driver}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,8 @@ class Kamal::Commands::Registry < Kamal::Commands::Base
|
||||
def login(registry_config: nil)
|
||||
registry_config ||= config.registry
|
||||
|
||||
return if registry_config.local?
|
||||
|
||||
docker :login,
|
||||
registry_config.server,
|
||||
"-u", sensitive(Kamal::Utils.escape_shell_value(registry_config.username)),
|
||||
@@ -13,4 +15,24 @@ class Kamal::Commands::Registry < Kamal::Commands::Base
|
||||
|
||||
docker :logout, registry_config.server
|
||||
end
|
||||
|
||||
def setup(registry_config: nil)
|
||||
registry_config ||= config.registry
|
||||
|
||||
combine \
|
||||
docker(:start, "kamal-docker-registry"),
|
||||
docker(:run, "--detach", "-p", "127.0.0.1:#{registry_config.local_port}:5000", "--name", "kamal-docker-registry", "registry:3"),
|
||||
by: "||"
|
||||
end
|
||||
|
||||
def remove
|
||||
combine \
|
||||
docker(:stop, "kamal-docker-registry"),
|
||||
docker(:rm, "kamal-docker-registry"),
|
||||
by: "&&"
|
||||
end
|
||||
|
||||
def local?
|
||||
config.registry.local?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,7 @@ require "erb"
|
||||
require "net/ssh/proxy/jump"
|
||||
|
||||
class Kamal::Configuration
|
||||
delegate :service, :image, :labels, :hooks_path, to: :raw_config, allow_nil: true
|
||||
delegate :service, :labels, :hooks_path, to: :raw_config, allow_nil: true
|
||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||
|
||||
attr_reader :destination, :raw_config, :secrets
|
||||
@@ -157,6 +157,13 @@ class Kamal::Configuration
|
||||
(proxy_roles.flat_map(&:hosts) + proxy_accessories.flat_map(&:hosts)).uniq
|
||||
end
|
||||
|
||||
def image
|
||||
name = raw_config&.image.presence
|
||||
name ||= raw_config&.service if registry.local?
|
||||
|
||||
name
|
||||
end
|
||||
|
||||
def repository
|
||||
[ registry.server, image ].compact.join("/")
|
||||
end
|
||||
@@ -282,10 +289,12 @@ class Kamal::Configuration
|
||||
end
|
||||
|
||||
def ensure_required_keys_present
|
||||
%i[ service image registry ].each do |key|
|
||||
%i[ service registry ].each do |key|
|
||||
raise Kamal::ConfigurationError, "Missing required configuration for #{key}" unless raw_config[key].present?
|
||||
end
|
||||
|
||||
raise Kamal::ConfigurationError, "Missing required configuration for image" if image.blank?
|
||||
|
||||
if raw_config.servers.nil?
|
||||
raise Kamal::ConfigurationError, "No servers or accessories specified" unless raw_config.accessories.present?
|
||||
else
|
||||
|
||||
@@ -19,6 +19,14 @@ class Kamal::Configuration::Registry
|
||||
lookup("password")
|
||||
end
|
||||
|
||||
def local?
|
||||
server.to_s.match?("^localhost[:$]")
|
||||
end
|
||||
|
||||
def local_port
|
||||
local? ? (server.split(":").last.to_i || 80) : nil
|
||||
end
|
||||
|
||||
private
|
||||
attr_reader :registry_config, :secrets
|
||||
|
||||
|
||||
@@ -15,10 +15,12 @@ class Kamal::Configuration::Validator::Registry < Kamal::Configuration::Validato
|
||||
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
|
||||
|
||||
@@ -153,6 +153,48 @@ 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.stubs(:execute)
|
||||
.with { |*args| args[0..1] == [ :docker, :login ] }
|
||||
|
||||
SSHKit::Backend::Abstract.any_instance.expects(:execute)
|
||||
.with(:docker, :start, "kamal-docker-registry", "||", :docker, :run, "--detach", "-p", "127.0.0.1:5000:5000", "--name", "kamal-docker-registry", "registry:3")
|
||||
|
||||
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.to_s.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, "--output=type=registry", "--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", ".", "2>&1")
|
||||
|
||||
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
|
||||
|
||||
@@ -39,6 +39,30 @@ 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: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)
|
||||
|
||||
run_command("deploy", "--verbose", config_file: "deploy_with_local_registry").tap do |output|
|
||||
assert_hook_ran "pre-connect", output
|
||||
assert_match /Build and push app image/, output
|
||||
assert_hook_ran "pre-deploy", output
|
||||
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
|
||||
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 }
|
||||
@@ -302,6 +326,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")
|
||||
|
||||
@@ -1,55 +1,55 @@
|
||||
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 "login with no docker" do
|
||||
test "setup with no docker" do
|
||||
stub_setup
|
||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||
.with(:docker, "--version", "&&", :docker, :buildx, "version")
|
||||
.raises(SSHKit::Command::Failed.new("command not found"))
|
||||
|
||||
assert_raises(Kamal::Cli::DependencyError) { run_command("login") }
|
||||
assert_raises(Kamal::Cli::DependencyError) { run_command("setup") }
|
||||
end
|
||||
|
||||
test "allow remote login with no docker" do
|
||||
@@ -61,12 +61,23 @@ class CliRegistryTest < CliTestCase
|
||||
SSHKit::Backend::Abstract.any_instance.stubs(:execute)
|
||||
.with { |*args| args[0..1] == [ :docker, :login ] }
|
||||
|
||||
assert_nothing_raised { run_command("login", "--skip-local") }
|
||||
assert_nothing_raised { run_command("setup", "--skip-local") }
|
||||
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
|
||||
|
||||
@@ -228,7 +228,11 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
||||
|
||||
private
|
||||
def new_builder_command(additional_config = {})
|
||||
Kamal::Commands::Builder.new(Kamal::Configuration.new(@config.deep_merge(additional_config), version: "123"))
|
||||
Kamal::Configuration.new(@config.deep_merge(additional_config), version: "123").then do |config|
|
||||
KAMAL.reset
|
||||
KAMAL.stubs(:config).returns(config)
|
||||
Kamal::Commands::Builder.new(config)
|
||||
end
|
||||
end
|
||||
|
||||
def local_arch
|
||||
|
||||
@@ -85,6 +85,15 @@ class CommandsRegistryTest < ActiveSupport::TestCase
|
||||
registry.logout(registry_config: accessory_registry_config).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:3", 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 main_config
|
||||
|
||||
@@ -43,6 +43,19 @@ class ConfigurationTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
test "image uses service name if registry is local" do
|
||||
assert_equal "app", Kamal::Configuration.new(@deploy.tap {
|
||||
_1[:registry] = { "server" => "localhost:5000" }
|
||||
_1.delete(:image)
|
||||
}).image
|
||||
end
|
||||
|
||||
test "image uses image if registry is local" do
|
||||
assert_equal "dhh/app", Kamal::Configuration.new(@deploy.tap {
|
||||
_1[:registry] = { "server" => "localhost:5000" }
|
||||
}).image
|
||||
end
|
||||
|
||||
test "service name valid" do
|
||||
assert_nothing_raised do
|
||||
Kamal::Configuration.new(@deploy.tap { |config| config[:service] = "hey-app1_primary" })
|
||||
|
||||
10
test/fixtures/deploy_with_local_registry.yml
vendored
Normal file
10
test/fixtures/deploy_with_local_registry.yml
vendored
Normal file
@@ -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
|
||||
45
test/fixtures/deploy_with_local_registry_and_accessories.yml
vendored
Normal file
45
test/fixtures/deploy_with_local_registry_and_accessories.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
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
|
||||
busybox:
|
||||
service: custom-box
|
||||
image: busybox:latest
|
||||
host: 1.1.1.3
|
||||
registry:
|
||||
server: other.registry
|
||||
username: other_user
|
||||
password: other_pw
|
||||
|
||||
readiness_delay: 0
|
||||
@@ -29,14 +29,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
|
||||
|
||||
@@ -2,12 +2,12 @@ service: app
|
||||
image: app
|
||||
servers:
|
||||
- vm1
|
||||
- vm2: [ tag1, tag2 ]
|
||||
- vm2: [tag1, tag2]
|
||||
env:
|
||||
clear:
|
||||
CLEAR_TOKEN: 4321
|
||||
CLEAR_TAG: ""
|
||||
HOST_TOKEN: "${HOST_TOKEN}"
|
||||
CLEAR_TAG: ''
|
||||
HOST_TOKEN: '${HOST_TOKEN}'
|
||||
secret:
|
||||
- SECRET_TOKEN
|
||||
- INTERPOLATED_SECRET1
|
||||
@@ -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 %>
|
||||
|
||||
@@ -131,7 +131,9 @@ class IntegrationTest < ActiveSupport::TestCase
|
||||
|
||||
def wait_for_healthy(timeout: 30)
|
||||
timeout_at = Time.now + timeout
|
||||
while docker_compose("ps -a | tail -n +2 | grep -v '(healthy)' | wc -l", capture: true) != "0"
|
||||
loop do
|
||||
result = docker_compose("ps -a | tail -n +2 | grep -v '(healthy)' | wc -l", capture: true)
|
||||
break if result.split.last == "0" || result == "0"
|
||||
if timeout_at < Time.now
|
||||
docker_compose("ps -a | tail -n +2 | grep -v '(healthy)'")
|
||||
raise "Container not healthy after #{timeout} seconds" if timeout_at < Time.now
|
||||
|
||||
@@ -33,7 +33,7 @@ class MainTest < IntegrationTest
|
||||
assert_match /App Host: vm1/, details
|
||||
assert_match /App Host: vm2/, details
|
||||
assert_match /basecamp\/kamal-proxy:#{Kamal::Configuration::Proxy::Boot::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
|
||||
@@ -67,8 +67,8 @@ class MainTest < IntegrationTest
|
||||
assert_equal [ "vm1", "vm2", "vm3" ], 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])
|
||||
|
||||
Reference in New Issue
Block a user