diff --git a/lib/kamal/cli/accessory.rb b/lib/kamal/cli/accessory.rb index d707820a..4de8832b 100644 --- a/lib/kamal/cli/accessory.rb +++ b/lib/kamal/cli/accessory.rb @@ -18,6 +18,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base execute *accessory.ensure_env_directory upload! accessory.secrets_io, accessory.secrets_path, mode: "0600" execute *accessory.run + + if accessory.running_proxy? + target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip + execute *accessory.deploy(target: target) + end end end end @@ -75,6 +80,10 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base on(hosts) do execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug execute *accessory.start + if accessory.running_proxy? + target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip + execute *accessory.deploy(target: target) + end end end end @@ -87,6 +96,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base on(hosts) do execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug execute *accessory.stop, raise_on_non_zero_exit: false + + if accessory.running_proxy? + target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip + execute *accessory.remove if target + end end end end diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index 9abb6dfc..281b8713 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -1,9 +1,13 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base + include Proxy + attr_reader :accessory_config delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd, :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args, - :secrets_io, :secrets_path, :env_directory, + :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, to: :accessory_config + delegate :proxy_container_name, to: :config + def initialize(config, name:) super(config) diff --git a/lib/kamal/commands/accessory/proxy.rb b/lib/kamal/commands/accessory/proxy.rb new file mode 100644 index 00000000..195a321b --- /dev/null +++ b/lib/kamal/commands/accessory/proxy.rb @@ -0,0 +1,16 @@ +module Kamal::Commands::Accessory::Proxy + delegate :proxy_container_name, to: :config + + def deploy(target:) + proxy_exec :deploy, service_name, *proxy.deploy_command_args(target: target) + end + + def remove + proxy_exec :remove, service_name + end + + private + def proxy_exec(*command) + docker :exec, proxy_container_name, "kamal-proxy", *command + end +end diff --git a/lib/kamal/configuration/accessory.rb b/lib/kamal/configuration/accessory.rb index aeb5f334..2728607d 100644 --- a/lib/kamal/configuration/accessory.rb +++ b/lib/kamal/configuration/accessory.rb @@ -5,7 +5,7 @@ class Kamal::Configuration::Accessory delegate :argumentize, :optionize, to: Kamal::Utils - attr_reader :name, :accessory_config, :env + attr_reader :name, :accessory_config, :env, :proxy def initialize(name, config:) @name, @config, @accessory_config = name.inquiry, config, config.raw_config["accessories"][name] @@ -20,6 +20,8 @@ class Kamal::Configuration::Accessory config: accessory_config.fetch("env", {}), secrets: config.secrets, context: "accessories/#{name}/env" + + initialize_proxy if running_proxy? end def service_name @@ -106,6 +108,17 @@ class Kamal::Configuration::Accessory accessory_config["cmd"] end + def running_proxy? + @accessory_config["proxy"].present? + end + + def initialize_proxy + @proxy = Kamal::Configuration::Proxy.new \ + config: config, + proxy_config: accessory_config["proxy"], + context: "accessories/#{name}/proxy" + end + private attr_accessor :config diff --git a/lib/kamal/configuration/docs/accessory.yml b/lib/kamal/configuration/docs/accessory.yml index e77bf754..fab2989f 100644 --- a/lib/kamal/configuration/docs/accessory.yml +++ b/lib/kamal/configuration/docs/accessory.yml @@ -98,3 +98,7 @@ accessories: # Defaults to kamal: network: custom + # Proxy + # + proxy: + ... \ No newline at end of file diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index 1befd9e6..b9bcca7e 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -39,7 +39,10 @@ class CommandsAccessoryTest < ActiveSupport::TestCase "busybox" => { "service" => "custom-busybox", "image" => "busybox:latest", - "host" => "1.1.1.7" + "host" => "1.1.1.7", + "proxy" => { + "host" => "busybox.example.com" + } } } } @@ -166,6 +169,18 @@ class CommandsAccessoryTest < ActiveSupport::TestCase new_command(:mysql).remove_image.join(" ") end + test "deploy" do + assert_equal \ + "docker exec kamal-proxy kamal-proxy deploy custom-busybox --target=\"172.1.0.2:80\" --host=\"busybox.example.com\" --deploy-timeout=\"30s\" --drain-timeout=\"30s\" --buffer-requests --buffer-responses --log-request-header=\"Cache-Control\" --log-request-header=\"Last-Modified\" --log-request-header=\"User-Agent\"", + new_command(:busybox).deploy(target: "172.1.0.2").join(" ") + end + + test "remove" do + assert_equal \ + "docker exec kamal-proxy kamal-proxy remove custom-busybox", + new_command(:busybox).remove.join(" ") + end + private def new_command(accessory) Kamal::Commands::Accessory.new(Kamal::Configuration.new(@config), name: accessory) diff --git a/test/configuration/accessory_test.rb b/test/configuration/accessory_test.rb index f5220902..d15a48ad 100644 --- a/test/configuration/accessory_test.rb +++ b/test/configuration/accessory_test.rb @@ -63,6 +63,9 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase "options" => { "cpus" => "4", "memory" => "2GB" + }, + "proxy" => { + "host" => "monitoring.example.com" } } } @@ -161,4 +164,9 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase @deploy[:accessories]["mysql"]["network"] = "database" assert_equal [ "--network", "database" ], @config.accessory(:mysql).network_args end + + test "proxy" do + assert @config.accessory(:monitoring).running_proxy? + assert_equal [ "monitoring.example.com" ], @config.accessory(:monitoring).proxy.hosts + end end diff --git a/test/integration/docker/deployer/Dockerfile b/test/integration/docker/deployer/Dockerfile index c7132861..c25747a2 100644 --- a/test/integration/docker/deployer/Dockerfile +++ b/test/integration/docker/deployer/Dockerfile @@ -20,6 +20,7 @@ COPY *.sh . COPY app/ app/ COPY app_with_roles/ app_with_roles/ COPY app_with_traefik/ app_with_traefik/ +COPY app_with_proxied_accessory/ app_with_proxied_accessory/ RUN rm -rf /root/.ssh RUN ln -s /shared/ssh /root/.ssh @@ -30,6 +31,7 @@ RUN git config --global user.name "Deployer" RUN cd app && git init && git add . && git commit -am "Initial version" RUN cd app_with_roles && git init && git add . && git commit -am "Initial version" RUN cd app_with_traefik && git init && git add . && git commit -am "Initial version" +RUN cd app_with_proxied_accessory && git init && git add . && git commit -am "Initial version" HEALTHCHECK --interval=1s CMD pgrep sleep diff --git a/test/integration/docker/deployer/app_with_proxied_accessory/Dockerfile b/test/integration/docker/deployer/app_with_proxied_accessory/Dockerfile new file mode 100644 index 00000000..0e6237df --- /dev/null +++ b/test/integration/docker/deployer/app_with_proxied_accessory/Dockerfile @@ -0,0 +1,9 @@ +FROM registry:4443/nginx:1-alpine-slim + +COPY default.conf /etc/nginx/conf.d/default.conf + +ARG COMMIT_SHA +RUN echo $COMMIT_SHA > /usr/share/nginx/html/version +RUN mkdir -p /usr/share/nginx/html/versions && echo "version" > /usr/share/nginx/html/versions/$COMMIT_SHA +RUN mkdir -p /usr/share/nginx/html/versions && echo "hidden" > /usr/share/nginx/html/versions/.hidden +RUN echo "Up!" > /usr/share/nginx/html/up diff --git a/test/integration/docker/deployer/app_with_proxied_accessory/config/deploy.yml b/test/integration/docker/deployer/app_with_proxied_accessory/config/deploy.yml new file mode 100644 index 00000000..bdb547ae --- /dev/null +++ b/test/integration/docker/deployer/app_with_proxied_accessory/config/deploy.yml @@ -0,0 +1,44 @@ +service: app_with_proxied_accessory +image: app_with_proxied_accessory +servers: + - vm1 +env: + clear: + CLEAR_TOKEN: 4321 + CLEAR_TAG: "" + HOST_TOKEN: "${HOST_TOKEN}" +asset_path: /usr/share/nginx/html/versions +proxy: + host: 127.0.0.1 +registry: + server: registry:4443 + username: root + password: root +builder: + driver: docker + arch: <%= Kamal::Utils.docker_arch %> + args: + COMMIT_SHA: <%= `git rev-parse HEAD` %> +accessories: + busybox: + service: custom-busybox + image: registry:4443/busybox:1.36.0 + cmd: sh -c 'echo "Starting busybox..."; trap exit term; while true; do sleep 1; done' + roles: + - web + netcat: + service: netcat + image: registry:4443/busybox:1.36.0 + cmd: > + sh -c 'echo "Starting netcat..."; while true; do echo -e "HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nHello Ruby" | nc -l -p 80; done' + roles: + - web + port: 12345:80 + proxy: + host: netcat + ssl: false + healthcheck: + interval: 1 + timeout: 1 + path: "/" + diff --git a/test/integration/docker/deployer/app_with_proxied_accessory/default.conf b/test/integration/docker/deployer/app_with_proxied_accessory/default.conf new file mode 100644 index 00000000..e37a9bc1 --- /dev/null +++ b/test/integration/docker/deployer/app_with_proxied_accessory/default.conf @@ -0,0 +1,17 @@ +server { + listen 80; + listen [::]:80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/test/integration/proxied_accessory_test.rb b/test/integration/proxied_accessory_test.rb new file mode 100644 index 00000000..10f3cff8 --- /dev/null +++ b/test/integration/proxied_accessory_test.rb @@ -0,0 +1,63 @@ +require_relative "integration_test" + +class ProxiedAccessoryTest < IntegrationTest + test "boot, stop, start, restart, logs, remove" do + @app = "app_with_proxied_accessory" + + kamal :deploy + + kamal :accessory, :boot, :netcat + assert_accessory_running :netcat + assert_netcat_is_up + + kamal :accessory, :stop, :netcat + assert_accessory_not_running :netcat + assert_netcat_not_found + + kamal :accessory, :start, :netcat + assert_accessory_running :netcat + assert_netcat_is_up + + kamal :accessory, :restart, :netcat + assert_accessory_running :netcat + assert_netcat_is_up + + kamal :accessory, :remove, :netcat, "-y" + assert_accessory_not_running :netcat + assert_netcat_not_found + end + + private + def assert_accessory_running(name) + assert_match /registry:4443\/busybox:1.36.0 "sh -c 'echo \\"Start/, accessory_details(name) + end + + def assert_accessory_not_running(name) + assert_no_match /registry:4443\/busybox:1.36.0 "sh -c 'echo \\"Start/, accessory_details(name) + end + + def accessory_details(name) + kamal :accessory, :details, name, capture: true + end + + def assert_netcat_is_up + response = netcat_response + debug_response_code(response, "200") + assert_equal "200", response.code + end + + def assert_netcat_not_found + response = netcat_response + debug_response_code(response, "404") + assert_equal "404", response.code + end + + def netcat_response + uri = URI.parse("http://127.0.0.1:12345/up") + http = Net::HTTP.new(uri.host, uri.port) + request = Net::HTTP::Get.new(uri) + request["Host"] = "netcat" + + http.request(request) + end +end