Merge branch 'main' into introduce-git-gateway
* main: No longer used Fix env validation Fix tests Fix test Extract Kamal::EnvFile
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
class Kamal::Commands::Traefik < Kamal::Commands::Base
|
class Kamal::Commands::Traefik < Kamal::Commands::Base
|
||||||
delegate :argumentize, :env_file_with_secrets, :optionize, to: Kamal::Utils
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
DEFAULT_IMAGE = "traefik:v2.9"
|
DEFAULT_IMAGE = "traefik:v2.9"
|
||||||
CONTAINER_PORT = 80
|
CONTAINER_PORT = 80
|
||||||
@@ -64,7 +64,7 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def env_file
|
def env_file
|
||||||
env_file_with_secrets config.traefik.fetch("env", {})
|
Kamal::EnvFile.new(config.traefik.fetch("env", {}))
|
||||||
end
|
end
|
||||||
|
|
||||||
def host_env_file_path
|
def host_env_file_path
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ class Kamal::Configuration
|
|||||||
|
|
||||||
# Will raise KeyError if any secret ENVs are missing
|
# Will raise KeyError if any secret ENVs are missing
|
||||||
def ensure_env_available
|
def ensure_env_available
|
||||||
roles.each(&:env_file)
|
roles.collect(&:env_file).each(&:to_s)
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class Kamal::Configuration::Accessory
|
class Kamal::Configuration::Accessory
|
||||||
delegate :argumentize, :env_file_with_secrets, :optionize, to: Kamal::Utils
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
attr_accessor :name, :specifics
|
attr_accessor :name, :specifics
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ class Kamal::Configuration::Accessory
|
|||||||
end
|
end
|
||||||
|
|
||||||
def env_file
|
def env_file
|
||||||
env_file_with_secrets env
|
Kamal::EnvFile.new(env)
|
||||||
end
|
end
|
||||||
|
|
||||||
def host_env_directory
|
def host_env_directory
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class Kamal::Configuration::Role
|
class Kamal::Configuration::Role
|
||||||
CORD_FILE = "cord"
|
CORD_FILE = "cord"
|
||||||
delegate :argumentize, :env_file_with_secrets, :optionize, to: Kamal::Utils
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
attr_accessor :name
|
attr_accessor :name
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ class Kamal::Configuration::Role
|
|||||||
end
|
end
|
||||||
|
|
||||||
def env_file
|
def env_file
|
||||||
env_file_with_secrets env
|
Kamal::EnvFile.new(env)
|
||||||
end
|
end
|
||||||
|
|
||||||
def host_env_directory
|
def host_env_directory
|
||||||
|
|||||||
41
lib/kamal/env_file.rb
Normal file
41
lib/kamal/env_file.rb
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Encode an env hash as a string where secret values have been looked up and all values escaped for Docker.
|
||||||
|
class Kamal::EnvFile
|
||||||
|
def initialize(env)
|
||||||
|
@env = env
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
env_file = StringIO.new.tap do |contents|
|
||||||
|
if (secrets = @env["secret"]).present?
|
||||||
|
@env.fetch("secret", @env)&.each do |key|
|
||||||
|
contents << docker_env_file_line(key, ENV.fetch(key))
|
||||||
|
end
|
||||||
|
|
||||||
|
@env["clear"]&.each do |key, value|
|
||||||
|
contents << docker_env_file_line(key, value)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
@env.fetch("clear", @env)&.each do |key, value|
|
||||||
|
contents << docker_env_file_line(key, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end.string
|
||||||
|
|
||||||
|
# Ensure the file has some contents to avoid the SSHKIT empty file warning
|
||||||
|
env_file.presence || "\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
alias to_str to_s
|
||||||
|
|
||||||
|
private
|
||||||
|
def docker_env_file_line(key, value)
|
||||||
|
"#{key.to_s}=#{escape_docker_env_file_value(value)}\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Escape a value to make it safe to dump in a docker file.
|
||||||
|
def escape_docker_env_file_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(/\\"/, "\"")
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -16,26 +16,6 @@ module Kamal::Utils
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def env_file_with_secrets(env)
|
|
||||||
env_file = StringIO.new.tap do |contents|
|
|
||||||
if (secrets = env["secret"]).present?
|
|
||||||
env.fetch("secret", env)&.each do |key|
|
|
||||||
contents << docker_env_file_line(key, ENV.fetch(key))
|
|
||||||
end
|
|
||||||
env["clear"]&.each do |key, value|
|
|
||||||
contents << docker_env_file_line(key, value)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
env.fetch("clear", env)&.each do |key, value|
|
|
||||||
contents << docker_env_file_line(key, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end.string
|
|
||||||
|
|
||||||
# Ensure the file has some contents to avoid the SSHKIT empty file warning
|
|
||||||
env_file.presence || "\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a list of shell-dashed option arguments. If the value is true, it's treated like a value-less option.
|
# Returns a list of shell-dashed option arguments. If the value is true, it's treated like a value-less option.
|
||||||
def optionize(args, with: nil)
|
def optionize(args, with: nil)
|
||||||
options = if with
|
options = if with
|
||||||
@@ -78,15 +58,4 @@ module Kamal::Utils
|
|||||||
.gsub(/`/, '\\\\`')
|
.gsub(/`/, '\\\\`')
|
||||||
.gsub(DOLLAR_SIGN_WITHOUT_SHELL_EXPANSION_REGEX, '\$')
|
.gsub(DOLLAR_SIGN_WITHOUT_SHELL_EXPANSION_REGEX, '\$')
|
||||||
end
|
end
|
||||||
|
|
||||||
# Escape a value to make it safe to dump in a docker file.
|
|
||||||
def escape_docker_env_file_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(/\\"/, "\"")
|
|
||||||
end
|
|
||||||
|
|
||||||
def docker_env_file_line(key, value)
|
|
||||||
"#{key.to_s}=#{escape_docker_env_file_value(value)}\n"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
|||||||
test "env_file" do
|
test "env_file" do
|
||||||
@config[:traefik]["env"] = { "secret" => %w[EXAMPLE_API_KEY] }
|
@config[:traefik]["env"] = { "secret" => %w[EXAMPLE_API_KEY] }
|
||||||
|
|
||||||
assert_equal "EXAMPLE_API_KEY=456\n", new_command.env_file
|
assert_equal "EXAMPLE_API_KEY=456\n", new_command.env_file.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
test "host_env_file_path" do
|
test "host_env_file_path" do
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
|
|||||||
MYSQL_ROOT_HOST=%
|
MYSQL_ROOT_HOST=%
|
||||||
ENV
|
ENV
|
||||||
|
|
||||||
assert_equal expected, @config.accessory(:mysql).env_file
|
assert_equal expected, @config.accessory(:mysql).env_file.to_s
|
||||||
ensure
|
ensure
|
||||||
ENV["MYSQL_ROOT_PASSWORD"] = nil
|
ENV["MYSQL_ROOT_PASSWORD"] = nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
WEB_CONCURRENCY=4
|
WEB_CONCURRENCY=4
|
||||||
ENV
|
ENV
|
||||||
|
|
||||||
assert_equal expected_env, @config_with_roles.role(:workers).env_file
|
assert_equal expected_env, @config_with_roles.role(:workers).env_file.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
test "container name" do
|
test "container name" do
|
||||||
@@ -123,7 +123,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
WEB_CONCURRENCY=4
|
WEB_CONCURRENCY=4
|
||||||
ENV
|
ENV
|
||||||
|
|
||||||
assert_equal expected, @config_with_roles.role(:workers).env_file
|
assert_equal expected, @config_with_roles.role(:workers).env_file.to_s
|
||||||
ensure
|
ensure
|
||||||
ENV["REDIS_PASSWORD"] = nil
|
ENV["REDIS_PASSWORD"] = nil
|
||||||
ENV["DB_PASSWORD"] = nil
|
ENV["DB_PASSWORD"] = nil
|
||||||
@@ -148,7 +148,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
WEB_CONCURRENCY=4
|
WEB_CONCURRENCY=4
|
||||||
ENV
|
ENV
|
||||||
|
|
||||||
assert_equal expected, @config_with_roles.role(:workers).env_file
|
assert_equal expected, @config_with_roles.role(:workers).env_file.to_s
|
||||||
ensure
|
ensure
|
||||||
ENV["DB_PASSWORD"] = nil
|
ENV["DB_PASSWORD"] = nil
|
||||||
end
|
end
|
||||||
@@ -171,7 +171,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
WEB_CONCURRENCY=4
|
WEB_CONCURRENCY=4
|
||||||
ENV
|
ENV
|
||||||
|
|
||||||
assert_equal expected, @config_with_roles.role(:workers).env_file
|
assert_equal expected, @config_with_roles.role(:workers).env_file.to_s
|
||||||
ensure
|
ensure
|
||||||
ENV["REDIS_PASSWORD"] = nil
|
ENV["REDIS_PASSWORD"] = nil
|
||||||
end
|
end
|
||||||
|
|||||||
102
test/env_file_test.rb
Normal file
102
test/env_file_test.rb
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class EnvFileTest < ActiveSupport::TestCase
|
||||||
|
test "env file simple" do
|
||||||
|
env = {
|
||||||
|
"foo" => "bar",
|
||||||
|
"baz" => "haz"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal "foo=bar\nbaz=haz\n", \
|
||||||
|
Kamal::EnvFile.new(env).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env file clear" do
|
||||||
|
env = {
|
||||||
|
"clear" => {
|
||||||
|
"foo" => "bar",
|
||||||
|
"baz" => "haz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal "foo=bar\nbaz=haz\n", \
|
||||||
|
Kamal::EnvFile.new(env).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env file empty" do
|
||||||
|
assert_equal "\n", Kamal::EnvFile.new({}).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env file secret" do
|
||||||
|
ENV["PASSWORD"] = "hello"
|
||||||
|
env = {
|
||||||
|
"secret" => [ "PASSWORD" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal "PASSWORD=hello\n", \
|
||||||
|
Kamal::EnvFile.new(env).to_s
|
||||||
|
ensure
|
||||||
|
ENV.delete "PASSWORD"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env file secret escaped newline" do
|
||||||
|
ENV["PASSWORD"] = "hello\\nthere"
|
||||||
|
env = {
|
||||||
|
"secret" => [ "PASSWORD" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal "PASSWORD=hello\\\\nthere\n", \
|
||||||
|
Kamal::EnvFile.new(env).to_s
|
||||||
|
ensure
|
||||||
|
ENV.delete "PASSWORD"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env file secret newline" do
|
||||||
|
ENV["PASSWORD"] = "hello\nthere"
|
||||||
|
env = {
|
||||||
|
"secret" => [ "PASSWORD" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal "PASSWORD=hello\\nthere\n", \
|
||||||
|
Kamal::EnvFile.new(env).to_s
|
||||||
|
ensure
|
||||||
|
ENV.delete "PASSWORD"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env file missing secret" do
|
||||||
|
env = {
|
||||||
|
"secret" => [ "PASSWORD" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_raises(KeyError) { Kamal::EnvFile.new(env).to_s }
|
||||||
|
|
||||||
|
ensure
|
||||||
|
ENV.delete "PASSWORD"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "env file secret and clear" do
|
||||||
|
ENV["PASSWORD"] = "hello"
|
||||||
|
env = {
|
||||||
|
"secret" => [ "PASSWORD" ],
|
||||||
|
"clear" => {
|
||||||
|
"foo" => "bar",
|
||||||
|
"baz" => "haz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal "PASSWORD=hello\nfoo=bar\nbaz=haz\n", \
|
||||||
|
Kamal::EnvFile.new(env).to_s
|
||||||
|
ensure
|
||||||
|
ENV.delete "PASSWORD"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "stringIO conversion" do
|
||||||
|
env = {
|
||||||
|
"foo" => "bar",
|
||||||
|
"baz" => "haz"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_equal "foo=bar\nbaz=haz\n", \
|
||||||
|
StringIO.new(Kamal::EnvFile.new(env)).read
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -11,95 +11,6 @@ class UtilsTest < ActiveSupport::TestCase
|
|||||||
Kamal::Utils.argumentize("--label", { foo: "bar" }, sensitive: true).last
|
Kamal::Utils.argumentize("--label", { foo: "bar" }, sensitive: true).last
|
||||||
end
|
end
|
||||||
|
|
||||||
test "env file simple" do
|
|
||||||
env = {
|
|
||||||
"foo" => "bar",
|
|
||||||
"baz" => "haz"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_equal "foo=bar\nbaz=haz\n", \
|
|
||||||
Kamal::Utils.env_file_with_secrets(env)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "env file clear" do
|
|
||||||
env = {
|
|
||||||
"clear" => {
|
|
||||||
"foo" => "bar",
|
|
||||||
"baz" => "haz"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_equal "foo=bar\nbaz=haz\n", \
|
|
||||||
Kamal::Utils.env_file_with_secrets(env)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "env file empty" do
|
|
||||||
assert_equal "\n", Kamal::Utils.env_file_with_secrets({})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "env file secret" do
|
|
||||||
ENV["PASSWORD"] = "hello"
|
|
||||||
env = {
|
|
||||||
"secret" => [ "PASSWORD" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_equal "PASSWORD=hello\n", \
|
|
||||||
Kamal::Utils.env_file_with_secrets(env)
|
|
||||||
ensure
|
|
||||||
ENV.delete "PASSWORD"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "env file secret escaped newline" do
|
|
||||||
ENV["PASSWORD"] = "hello\\nthere"
|
|
||||||
env = {
|
|
||||||
"secret" => [ "PASSWORD" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_equal "PASSWORD=hello\\\\nthere\n", \
|
|
||||||
Kamal::Utils.env_file_with_secrets(env)
|
|
||||||
ensure
|
|
||||||
ENV.delete "PASSWORD"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "env file secret newline" do
|
|
||||||
ENV["PASSWORD"] = "hello\nthere"
|
|
||||||
env = {
|
|
||||||
"secret" => [ "PASSWORD" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_equal "PASSWORD=hello\\nthere\n", \
|
|
||||||
Kamal::Utils.env_file_with_secrets(env)
|
|
||||||
ensure
|
|
||||||
ENV.delete "PASSWORD"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "env file missing secret" do
|
|
||||||
env = {
|
|
||||||
"secret" => [ "PASSWORD" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_raises(KeyError) { Kamal::Utils.env_file_with_secrets(env) }
|
|
||||||
|
|
||||||
ensure
|
|
||||||
ENV.delete "PASSWORD"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "env file secret and clear" do
|
|
||||||
ENV["PASSWORD"] = "hello"
|
|
||||||
env = {
|
|
||||||
"secret" => [ "PASSWORD" ],
|
|
||||||
"clear" => {
|
|
||||||
"foo" => "bar",
|
|
||||||
"baz" => "haz"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_equal "PASSWORD=hello\nfoo=bar\nbaz=haz\n", \
|
|
||||||
Kamal::Utils.env_file_with_secrets(env)
|
|
||||||
ensure
|
|
||||||
ENV.delete "PASSWORD"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "optionize" do
|
test "optionize" do
|
||||||
assert_equal [ "--foo", "\"bar\"", "--baz", "\"qux\"", "--quux" ], \
|
assert_equal [ "--foo", "\"bar\"", "--baz", "\"qux\"", "--quux" ], \
|
||||||
Kamal::Utils.optionize({ foo: "bar", baz: "qux", quux: true })
|
Kamal::Utils.optionize({ foo: "bar", baz: "qux", quux: true })
|
||||||
|
|||||||
Reference in New Issue
Block a user