Merge branch 'basecamp:main' into feat-add-aws-secrets-manager-adapter
This commit is contained in:
@@ -14,7 +14,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
|
||||
version = capture_with_info(*KAMAL.proxy.version).strip.presence
|
||||
|
||||
if version && Kamal::Utils.older_version?(version, Kamal::Configuration::PROXY_MINIMUM_VERSION)
|
||||
raise "kamal-proxy version #{version} is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}"
|
||||
raise "kamal-proxy version #{version} is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}"
|
||||
end
|
||||
execute *KAMAL.proxy.start_or_run
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class Kamal::Commands::Accessory < Kamal::Commands::Base
|
||||
attr_reader :accessory_config
|
||||
delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
|
||||
:publish_args, :env_args, :volume_args, :label_args, :option_args,
|
||||
:network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
|
||||
:secrets_io, :secrets_path, :env_directory,
|
||||
to: :accessory_config
|
||||
|
||||
@@ -15,7 +15,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
||||
"--name", service_name,
|
||||
"--detach",
|
||||
"--restart", "unless-stopped",
|
||||
"--network", "kamal",
|
||||
*network_args,
|
||||
*config.logging_args,
|
||||
*publish_args,
|
||||
*env_args,
|
||||
@@ -64,7 +64,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
|
||||
docker :run,
|
||||
("-it" if interactive),
|
||||
"--rm",
|
||||
"--network", "kamal",
|
||||
*network_args,
|
||||
*env_args,
|
||||
*volume_args,
|
||||
image,
|
||||
|
||||
@@ -47,7 +47,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
||||
end
|
||||
|
||||
def info
|
||||
docker :ps, *filter_args
|
||||
docker :ps, *container_filter_args
|
||||
end
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
||||
|
||||
def list_versions(*docker_args, statuses: nil)
|
||||
pipe \
|
||||
docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
||||
docker(:ps, *container_filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
||||
extract_version_from_name
|
||||
end
|
||||
|
||||
@@ -91,11 +91,15 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
||||
end
|
||||
|
||||
def latest_container(format:, filters: nil)
|
||||
docker :ps, "--latest", *format, *filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters)
|
||||
docker :ps, "--latest", *format, *container_filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters)
|
||||
end
|
||||
|
||||
def filter_args(statuses: nil)
|
||||
argumentize "--filter", filters(statuses: statuses)
|
||||
def container_filter_args(statuses: nil)
|
||||
argumentize "--filter", container_filters(statuses: statuses)
|
||||
end
|
||||
|
||||
def image_filter_args
|
||||
argumentize "--filter", image_filters
|
||||
end
|
||||
|
||||
def extract_version_from_name
|
||||
@@ -103,13 +107,17 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
||||
%(while read line; do echo ${line##{role.container_prefix}-}; done)
|
||||
end
|
||||
|
||||
def filters(statuses: nil)
|
||||
def container_filters(statuses: nil)
|
||||
[ "label=service=#{config.service}" ].tap do |filters|
|
||||
filters << "label=destination=#{config.destination}" if config.destination
|
||||
filters << "label=destination=#{config.destination}"
|
||||
filters << "label=role=#{role}" if role
|
||||
statuses&.each do |status|
|
||||
filters << "status=#{status}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def image_filters
|
||||
[ "label=service=#{config.service}" ]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ module Kamal::Commands::App::Containers
|
||||
DOCKER_HEALTH_LOG_FORMAT = "'{{json .State.Health}}'"
|
||||
|
||||
def list_containers
|
||||
docker :container, :ls, "--all", *filter_args
|
||||
docker :container, :ls, "--all", *container_filter_args
|
||||
end
|
||||
|
||||
def list_container_names
|
||||
@@ -20,7 +20,7 @@ module Kamal::Commands::App::Containers
|
||||
end
|
||||
|
||||
def remove_containers
|
||||
docker :container, :prune, "--force", *filter_args
|
||||
docker :container, :prune, "--force", *container_filter_args
|
||||
end
|
||||
|
||||
def container_health_log(version:)
|
||||
|
||||
@@ -4,7 +4,7 @@ module Kamal::Commands::App::Images
|
||||
end
|
||||
|
||||
def remove_images
|
||||
docker :image, :prune, "--all", "--force", *filter_args
|
||||
docker :image, :prune, "--all", "--force", *image_filter_args
|
||||
end
|
||||
|
||||
def tag_latest_image
|
||||
|
||||
@@ -6,7 +6,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||
delegate :argumentize, to: Kamal::Utils
|
||||
delegate \
|
||||
:args, :secrets, :dockerfile, :target, :arches, :local_arches, :remote_arches, :remote,
|
||||
:cache_from, :cache_to, :ssh, :driver, :docker_driver?,
|
||||
:cache_from, :cache_to, :ssh, :provenance, :driver, :docker_driver?,
|
||||
to: :builder_config
|
||||
|
||||
def clean
|
||||
@@ -37,7 +37,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 ]
|
||||
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance ]
|
||||
end
|
||||
|
||||
def build_context
|
||||
@@ -97,6 +97,10 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
||||
argumentize "--ssh", ssh if ssh.present?
|
||||
end
|
||||
|
||||
def builder_provenance
|
||||
argumentize "--provenance", provenance unless provenance.nil?
|
||||
end
|
||||
|
||||
def builder_config
|
||||
config.builder
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@ class Kamal::Configuration
|
||||
|
||||
include Validation
|
||||
|
||||
PROXY_MINIMUM_VERSION = "v0.8.1"
|
||||
PROXY_MINIMUM_VERSION = "v0.8.2"
|
||||
PROXY_HTTP_PORT = 80
|
||||
PROXY_HTTPS_PORT = 443
|
||||
PROXY_LOG_MAX_SIZE = "10m"
|
||||
@@ -254,7 +254,7 @@ class Kamal::Configuration
|
||||
end
|
||||
|
||||
def proxy_logging_args(max_size)
|
||||
argumentize "--log-opt", "max-size=#{max_size}"
|
||||
argumentize "--log-opt", "max-size=#{max_size}" if max_size.present?
|
||||
end
|
||||
|
||||
def proxy_options_default
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
class Kamal::Configuration::Accessory
|
||||
include Kamal::Configuration::Validation
|
||||
|
||||
DEFAULT_NETWORK = "kamal"
|
||||
|
||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||
|
||||
attr_reader :name, :accessory_config, :env
|
||||
@@ -38,6 +40,10 @@ class Kamal::Configuration::Accessory
|
||||
end
|
||||
end
|
||||
|
||||
def network_args
|
||||
argumentize "--network", network
|
||||
end
|
||||
|
||||
def publish_args
|
||||
argumentize "--publish", port if port
|
||||
end
|
||||
@@ -173,4 +179,8 @@ class Kamal::Configuration::Accessory
|
||||
accessory_config["roles"].flat_map { |role| config.role(role).hosts }
|
||||
end
|
||||
end
|
||||
|
||||
def network
|
||||
accessory_config["network"] || DEFAULT_NETWORK
|
||||
end
|
||||
end
|
||||
|
||||
@@ -111,6 +111,10 @@ class Kamal::Configuration::Builder
|
||||
builder_config["ssh"]
|
||||
end
|
||||
|
||||
def provenance
|
||||
builder_config["provenance"]
|
||||
end
|
||||
|
||||
def git_clone?
|
||||
Kamal::Git.used? && builder_config["context"].nil?
|
||||
end
|
||||
@@ -166,7 +170,7 @@ class Kamal::Configuration::Builder
|
||||
end
|
||||
|
||||
def cache_to_config_for_registry
|
||||
[ "type=registry", builder_config["cache"]&.fetch("options", nil), "ref=#{cache_image_ref}" ].compact.join(",")
|
||||
[ "type=registry", "ref=#{cache_image_ref}", builder_config["cache"]&.fetch("options", nil) ].compact.join(",")
|
||||
end
|
||||
|
||||
def repo_basename
|
||||
|
||||
@@ -90,3 +90,11 @@ accessories:
|
||||
# They are not created or copied before mounting:
|
||||
volumes:
|
||||
- /path/to/mysql-logs:/var/log/mysql
|
||||
|
||||
# Network
|
||||
#
|
||||
# The network the accessory will be attached to.
|
||||
#
|
||||
# Defaults to kamal:
|
||||
network: custom
|
||||
|
||||
|
||||
@@ -102,3 +102,9 @@ builder:
|
||||
#
|
||||
# The build driver to use, defaults to `docker-container`:
|
||||
driver: docker
|
||||
|
||||
# Provenance
|
||||
#
|
||||
# It is used to configure provenance attestations for the build result.
|
||||
# The value can also be a boolean to enable or disable provenance attestations.
|
||||
provenance: mode=max
|
||||
|
||||
@@ -37,6 +37,8 @@ class Kamal::EnvFile
|
||||
def escape_docker_env_file_ascii_value(value)
|
||||
# Doublequotes are treated literally in docker env files
|
||||
# so remove leading and trailing ones and unescape any others
|
||||
value.to_s.dump[1..-2].gsub(/\\"/, "\"")
|
||||
value.to_s.dump[1..-2]
|
||||
.gsub(/\\"/, "\"")
|
||||
.gsub(/\\#/, "#")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
require "dotenv"
|
||||
|
||||
class Kamal::Secrets
|
||||
attr_reader :secrets_files
|
||||
|
||||
Kamal::Secrets::Dotenv::InlineCommandSubstitution.install!
|
||||
|
||||
def initialize(destination: nil)
|
||||
@secrets_files = \
|
||||
[ ".kamal/secrets-common", ".kamal/secrets#{(".#{destination}" if destination)}" ].select { |f| File.exist?(f) }
|
||||
@destination = destination
|
||||
@mutex = Mutex.new
|
||||
end
|
||||
|
||||
@@ -17,10 +14,10 @@ class Kamal::Secrets
|
||||
secrets.fetch(key)
|
||||
end
|
||||
rescue KeyError
|
||||
if secrets_files
|
||||
if secrets_files.present?
|
||||
raise Kamal::ConfigurationError, "Secret '#{key}' not found in #{secrets_files.join(", ")}"
|
||||
else
|
||||
raise Kamal::ConfigurationError, "Secret '#{key}' not found, no secret files provided"
|
||||
raise Kamal::ConfigurationError, "Secret '#{key}' not found, no secret files (#{secrets_filenames.join(", ")}) provided"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,10 +25,18 @@ class Kamal::Secrets
|
||||
secrets
|
||||
end
|
||||
|
||||
def secrets_files
|
||||
@secrets_files ||= secrets_filenames.select { |f| File.exist?(f) }
|
||||
end
|
||||
|
||||
private
|
||||
def secrets
|
||||
@secrets ||= secrets_files.inject({}) do |secrets, secrets_file|
|
||||
secrets.merge!(::Dotenv.parse(secrets_file))
|
||||
end
|
||||
end
|
||||
|
||||
def secrets_filenames
|
||||
[ ".kamal/secrets-common", ".kamal/secrets#{(".#{@destination}" if @destination)}" ]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,7 @@ class Kamal::Secrets::Adapters::Base
|
||||
delegate :optionize, to: Kamal::Utils
|
||||
|
||||
def fetch(secrets, account:, from: nil)
|
||||
check_dependencies!
|
||||
session = login(account)
|
||||
full_secrets = secrets.map { |secret| [ from, secret ].compact.join("/") }
|
||||
fetch_secrets(full_secrets, account: account, session: session)
|
||||
@@ -15,4 +16,8 @@ class Kamal::Secrets::Adapters::Base
|
||||
def fetch_secrets(...)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def check_dependencies!
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,18 +25,15 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
|
||||
{}.tap do |results|
|
||||
items_fields(secrets).each do |item, fields|
|
||||
item_json = run_command("get item #{item.shellescape}", session: session, raw: true)
|
||||
raise RuntimeError, "Could not read #{secret} from Bitwarden" unless $?.success?
|
||||
raise RuntimeError, "Could not read #{item} from Bitwarden" unless $?.success?
|
||||
item_json = JSON.parse(item_json)
|
||||
|
||||
if fields.any?
|
||||
fields.each do |field|
|
||||
item_field = item_json["fields"].find { |f| f["name"] == field }
|
||||
raise RuntimeError, "Could not find field #{field} in item #{item} in Bitwarden" unless item_field
|
||||
value = item_field["value"]
|
||||
results["#{item}/#{field}"] = value
|
||||
end
|
||||
results.merge! fetch_secrets_from_fields(fields, item, item_json)
|
||||
elsif item_json.dig("login", "password")
|
||||
results[item] = item_json.dig("login", "password")
|
||||
elsif item_json["fields"]&.any?
|
||||
fields = item_json["fields"].pluck("name")
|
||||
results.merge! fetch_secrets_from_fields(fields, item, item_json)
|
||||
else
|
||||
raise RuntimeError, "Item #{item} is not a login type item and no fields were specified"
|
||||
end
|
||||
@@ -44,6 +41,15 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_secrets_from_fields(fields, item, item_json)
|
||||
fields.to_h do |field|
|
||||
item_field = item_json["fields"].find { |f| f["name"] == field }
|
||||
raise RuntimeError, "Could not find field #{field} in item #{item} in Bitwarden" unless item_field
|
||||
value = item_field["value"]
|
||||
[ "#{item}/#{field}", value ]
|
||||
end
|
||||
end
|
||||
|
||||
def items_fields(secrets)
|
||||
{}.tap do |items|
|
||||
secrets.each do |secret|
|
||||
@@ -63,4 +69,13 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base
|
||||
result = `#{full_command}`.strip
|
||||
raw ? result : JSON.parse(result)
|
||||
end
|
||||
|
||||
def check_dependencies!
|
||||
raise RuntimeError, "Bitwarden CLI is not installed" unless cli_installed?
|
||||
end
|
||||
|
||||
def cli_installed?
|
||||
`bw --version 2> /dev/null`
|
||||
$?.success?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -27,4 +27,13 @@ class Kamal::Secrets::Adapters::LastPass < Kamal::Secrets::Adapters::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_dependencies!
|
||||
raise RuntimeError, "LastPass CLI is not installed" unless cli_installed?
|
||||
end
|
||||
|
||||
def cli_installed?
|
||||
`lpass --version 2> /dev/null`
|
||||
$?.success?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -58,4 +58,13 @@ class Kamal::Secrets::Adapters::OnePassword < Kamal::Secrets::Adapters::Base
|
||||
raise RuntimeError, "Could not read #{fields.join(", ")} from #{item} in the #{vault} 1Password vault" unless $?.success?
|
||||
end
|
||||
end
|
||||
|
||||
def check_dependencies!
|
||||
raise RuntimeError, "1Password CLI is not installed" unless cli_installed?
|
||||
end
|
||||
|
||||
def cli_installed?
|
||||
`op --version 2> /dev/null`
|
||||
$?.success?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,4 +7,8 @@ class Kamal::Secrets::Adapters::Test < Kamal::Secrets::Adapters::Base
|
||||
def fetch_secrets(secrets, account:, session:)
|
||||
secrets.to_h { |secret| [ secret, secret.reverse ] }
|
||||
end
|
||||
|
||||
def check_dependencies!
|
||||
# no op
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,6 +12,8 @@ module Kamal::Utils
|
||||
attr = "#{key}=#{escape_shell_value(value)}"
|
||||
attr = self.sensitive(attr, redaction: "#{key}=[REDACTED]") if sensitive
|
||||
[ argument, attr ]
|
||||
elsif value == false
|
||||
[ argument, "#{key}=false" ]
|
||||
else
|
||||
[ argument, key ]
|
||||
end
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module Kamal
|
||||
VERSION = "2.2.2"
|
||||
VERSION = "2.3.0"
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user