Catch up with 2.5.1

This commit is contained in:
Nick Hammond
2025-02-06 07:12:05 -07:00
34 changed files with 370 additions and 159 deletions

View File

@@ -1,6 +1,7 @@
class Kamal::Cli::Alias::Command < Thor::DynamicCommand
def run(instance, args = [])
if (_alias = KAMAL.config.aliases[name])
KAMAL.reset
Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
else
super

View File

@@ -16,10 +16,18 @@ class Kamal::Cli::App < Kamal::Cli::Base
# Primary hosts and roles are returned first, so they can open the barrier
barrier = Kamal::Cli::Healthcheck::Barrier.new
on(KAMAL.hosts, **KAMAL.boot_strategy) do |host|
KAMAL.roles_on(host).each do |role|
Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
host_boot_groups.each do |hosts|
host_list = Array(hosts).join(",")
run_hook "pre-app-boot", hosts: host_list
on(hosts) do |host|
KAMAL.roles_on(host).each do |role|
Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
end
end
run_hook "post-app-boot", hosts: host_list
sleep KAMAL.config.boot.wait if KAMAL.config.boot.wait
end
# Tag once the app booted on all hosts
@@ -340,4 +348,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
yield
end
end
def host_boot_groups
KAMAL.config.boot.limit ? KAMAL.hosts.each_slice(KAMAL.config.boot.limit).to_a : [ KAMAL.hosts ]
end
end

View File

@@ -31,7 +31,7 @@ module Kamal::Cli
super
end
initialize_commander unless config[:invoked_via_subcommand]
initialize_commander unless KAMAL.configured?
end
private

View File

@@ -5,11 +5,12 @@ class Kamal::Cli::Build < Kamal::Cli::Base
desc "deliver", "Build app and push app image to registry then pull image on servers"
def deliver
push
pull
invoke :push
invoke :pull
end
desc "push", "Build and push app image to registry"
option :output, type: :string, default: "registry", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
def push
cli = self
@@ -49,7 +50,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
end
# Get the command here to ensure the Dir.chdir doesn't interfere with it
push = KAMAL.builder.push
push = KAMAL.builder.push(cli.options[:output])
KAMAL.with_verbosity(:debug) do
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
@@ -108,6 +109,41 @@ class Kamal::Cli::Build < Kamal::Cli::Base
end
end
desc "dev", "Build using the working directory, tag it as dirty, and push to local image store."
option :output, type: :string, default: "docker", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
def dev
cli = self
ensure_docker_installed
docker_included_files = Set.new(Kamal::Docker.included_files)
git_uncommitted_files = Set.new(Kamal::Git.uncommitted_files)
git_untracked_files = Set.new(Kamal::Git.untracked_files)
docker_uncommitted_files = docker_included_files & git_uncommitted_files
if docker_uncommitted_files.any?
say "WARNING: Files with uncommitted changes will be present in the dev container:", :yellow
docker_uncommitted_files.sort.each { |f| say " #{f}", :yellow }
say
end
docker_untracked_files = docker_included_files & git_untracked_files
if docker_untracked_files.any?
say "WARNING: Untracked files will be present in the dev container:", :yellow
docker_untracked_files.sort.each { |f| say " #{f}", :yellow }
say
end
with_env(KAMAL.config.builder.secrets) do
run_locally do
build = KAMAL.builder.push(cli.options[:output], tag_as_dirty: true)
KAMAL.with_verbosity(:debug) do
execute(*build)
end
end
end
end
private
def connect_to_remote_host(remote_host)
remote_uri = URI.parse(remote_host)

View File

@@ -3,7 +3,7 @@ class Kamal::Cli::Registry < Kamal::Cli::Base
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
ensure_docker_installed
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]

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..."

View File

@@ -0,0 +1,3 @@
#!/bin/sh
echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..."

View File

@@ -4,13 +4,20 @@ require "active_support/core_ext/object/blank"
class Kamal::Commander
attr_accessor :verbosity, :holding_lock, :connected
attr_reader :specific_roles, :specific_hosts
delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :proxy_hosts, :accessory_hosts, to: :specifics
def initialize
reset
end
def reset
self.verbosity = :info
self.holding_lock = false
self.connected = false
@specifics = nil
@specifics = @specific_roles = @specific_hosts = nil
@config = @config_kwargs = nil
@commands = {}
end
def config
@@ -28,8 +35,6 @@ class Kamal::Commander
@config || @config_kwargs
end
attr_reader :specific_roles, :specific_hosts
def specific_primary!
@specifics = nil
if specific_roles.present?
@@ -89,35 +94,35 @@ class Kamal::Commander
end
def builder
@builder ||= Kamal::Commands::Builder.new(config)
@commands[:builder] ||= Kamal::Commands::Builder.new(config)
end
def docker
@docker ||= Kamal::Commands::Docker.new(config)
@commands[:docker] ||= Kamal::Commands::Docker.new(config)
end
def hook
@hook ||= Kamal::Commands::Hook.new(config)
@commands[:hook] ||= Kamal::Commands::Hook.new(config)
end
def lock
@lock ||= Kamal::Commands::Lock.new(config)
@commands[:lock] ||= Kamal::Commands::Lock.new(config)
end
def proxy
@proxy ||= Kamal::Commands::Proxy.new(config)
@commands[:proxy] ||= Kamal::Commands::Proxy.new(config)
end
def prune
@prune ||= Kamal::Commands::Prune.new(config)
@commands[:prune] ||= Kamal::Commands::Prune.new(config)
end
def registry
@registry ||= Kamal::Commands::Registry.new(config)
@commands[:registry] ||= Kamal::Commands::Registry.new(config)
end
def server
@server ||= Kamal::Commands::Server.new(config)
@commands[:server] ||= Kamal::Commands::Server.new(config)
end
def alias(name)
@@ -136,14 +141,6 @@ class Kamal::Commander
SSHKit.config.output_verbosity = old_level
end
def boot_strategy
if config.boot.limit.present?
{ in: :groups, limit: config.boot.limit, wait: config.boot.wait }
else
{}
end
end
def holding_lock?
self.holding_lock
end

View File

@@ -1,7 +1,7 @@
require "active_support/core_ext/string/filters"
class Kamal::Commands::Builder < Kamal::Commands::Base
delegate :create, :remove, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
delegate :create, :remove, :dev, :push, :clean, :pull, :info, :inspect_builder, :validate_image, :first_mirror, to: :target
delegate :local?, :remote?, :pack?, :cloud?, to: "config.builder"
include Clone

View File

@@ -14,11 +14,12 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
docker :image, :rm, "--force", config.absolute_image
end
def push
def push(export_action = "registry", tag_as_dirty: false)
docker :buildx, :build,
"--push",
"--output=type=#{export_action}",
*platform_options(arches),
*([ "--builder", builder_name ] unless docker_driver?),
*build_tag_options(tag_as_dirty: tag_as_dirty),
*build_options,
build_context
end
@@ -38,7 +39,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
end
def build_options
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance, *builder_sbom ]
[ *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance, *builder_sbom ]
end
def build_context
@@ -59,8 +60,14 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
end
private
def build_tags
[ "-t", config.absolute_image, "-t", config.latest_image ]
def build_tag_names(tag_as_dirty: false)
tag_names = [ config.absolute_image, config.latest_image ]
tag_names.map! { |t| "#{t}-dirty" } if tag_as_dirty
tag_names
end
def build_tag_options(tag_as_dirty: false)
build_tag_names(tag_as_dirty: tag_as_dirty).flat_map { |name| [ "-t", name ] }
end
def build_cache

View File

@@ -16,6 +16,8 @@ class Kamal::Configuration::Accessory
context: "accessories/#{name}",
with: Kamal::Configuration::Validator::Accessory
ensure_valid_roles
@env = initialize_env
@proxy = initialize_proxy if running_proxy?
@registry = initialize_registry if accessory_config["registry"].present?
@@ -200,13 +202,17 @@ class Kamal::Configuration::Accessory
def hosts_from_roles
if accessory_config.key?("roles")
accessory_config["roles"].flat_map do |role|
config.role(role)&.hosts || raise(Kamal::ConfigurationError, "Unknown role in accessories config: '#{role}'")
end
accessory_config["roles"].flat_map { |role| config.role(role)&.hosts }
end
end
def network
accessory_config["network"] || DEFAULT_NETWORK
end
def ensure_valid_roles
if accessory_config["roles"] && (missing_roles = accessory_config["roles"] - config.roles.map(&:name)).any?
raise Kamal::ConfigurationError, "accessories/#{name}: unknown roles #{missing_roles.join(", ")}"
end
end
end

View File

@@ -118,5 +118,6 @@ accessories:
# Proxy
#
# You can run your accessory behind the Kamal proxy. See kamal docs proxy for more information
proxy:
...

30
lib/kamal/docker.rb Normal file
View File

@@ -0,0 +1,30 @@
require "tempfile"
require "open3"
module Kamal::Docker
extend self
BUILD_CHECK_TAG = "kamal-local-build-check"
def included_files
Tempfile.create do |dockerfile|
dockerfile.write(<<~DOCKERFILE)
FROM busybox
COPY . app
WORKDIR app
CMD find . -type f | sed "s|^\./||"
DOCKERFILE
dockerfile.close
cmd = "docker buildx build -t=#{BUILD_CHECK_TAG} -f=#{dockerfile.path} ."
system(cmd) || raise("failed to build check image")
end
cmd = "docker run --rm #{BUILD_CHECK_TAG}"
out, err, status = Open3.capture3(cmd)
unless status
raise "failed to run check image:\n#{err}"
end
out.lines.map(&:strip)
end
end

View File

@@ -24,4 +24,14 @@ module Kamal::Git
def root
`git rev-parse --show-toplevel`.strip
end
# returns an array of relative path names of files with uncommitted changes
def uncommitted_files
`git ls-files --modified`.lines.map(&:strip)
end
# returns an array of relative path names of untracked files, including gitignored files
def untracked_files
`git ls-files --others`.lines.map(&:strip)
end
end

View File

@@ -1,10 +1,14 @@
class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Base
def requires_account?
false
end
private
def login(_account)
nil
end
def fetch_secrets(secrets, from:, account:, session:)
def fetch_secrets(secrets, from:, account: nil, session:)
{}.tap do |results|
get_from_secrets_manager(prefixed_secrets(secrets, from: from), account: account).each do |secret|
secret_name = secret["Name"]
@@ -19,8 +23,12 @@ class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Ba
end
end
def get_from_secrets_manager(secrets, account:)
`aws secretsmanager batch-get-secret-value --secret-id-list #{secrets.map(&:shellescape).join(" ")} --profile #{account.shellescape}`.tap do |secrets|
def get_from_secrets_manager(secrets, account: nil)
args = [ "aws", "secretsmanager", "batch-get-secret-value", "--secret-id-list" ] + secrets.map(&:shellescape)
args += [ "--profile", account.shellescape ] if account
cmd = args.join(" ")
`#{cmd}`.tap do |secrets|
raise RuntimeError, "Could not read #{secrets} from AWS Secrets Manager" unless $?.success?
secrets = JSON.parse(secrets)

View File

@@ -1,3 +1,3 @@
module Kamal
VERSION = "2.4.0"
VERSION = "2.5.1"
end