Allow hosts to be tagged so we can have host specific env variables.
We might want host specific env variables for things like datacenter
specific tags or testing GC settings on a specific host.
Right now you either need to set up a separate role, or have the app
be host aware.
Now you can define tag env variables and assign those to hosts.
For example:
```
servers:
- 1.1.1.1
- 1.1.1.2: tag1
- 1.1.1.2: tag2
- 1.1.1.3: [ tag1, tag2 ]
env_tags:
tag1:
ENV1: value1
tag2:
ENV2: value2
```
The tag env supports the full env format, allowing you to set secret and
clear values.
124 lines
3.3 KiB
Ruby
124 lines
3.3 KiB
Ruby
class Kamal::Commands::App < Kamal::Commands::Base
|
|
include Assets, Containers, Cord, Execution, Images, Logging
|
|
|
|
ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
|
|
|
|
attr_reader :role, :host
|
|
|
|
def initialize(config, role: nil, host: nil)
|
|
super(config)
|
|
@role = role
|
|
@host = host
|
|
end
|
|
|
|
def run(hostname: nil)
|
|
docker :run,
|
|
"--detach",
|
|
"--restart unless-stopped",
|
|
"--name", container_name,
|
|
*([ "--hostname", hostname ] if hostname),
|
|
"-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
|
"-e", "KAMAL_VERSION=\"#{config.version}\"",
|
|
*role.env_args(host),
|
|
*role.health_check_args,
|
|
*role.logging_args,
|
|
*config.volume_args,
|
|
*role.asset_volume_args,
|
|
*role.label_args,
|
|
*role.option_args,
|
|
config.absolute_image,
|
|
role.cmd
|
|
end
|
|
|
|
def start
|
|
docker :start, container_name
|
|
end
|
|
|
|
def status(version:)
|
|
pipe container_id_for_version(version), xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
|
|
end
|
|
|
|
def stop(version: nil)
|
|
pipe \
|
|
version ? container_id_for_version(version) : current_running_container_id,
|
|
xargs(config.stop_wait_time ? docker(:stop, "-t", config.stop_wait_time) : docker(:stop))
|
|
end
|
|
|
|
def info
|
|
docker :ps, *filter_args
|
|
end
|
|
|
|
|
|
def current_running_container_id
|
|
current_running_container(format: "--quiet")
|
|
end
|
|
|
|
def container_id_for_version(version, only_running: false)
|
|
container_id_for(container_name: container_name(version), only_running: only_running)
|
|
end
|
|
|
|
def current_running_version
|
|
pipe \
|
|
current_running_container(format: "--format '{{.Names}}'"),
|
|
extract_version_from_name
|
|
end
|
|
|
|
def list_versions(*docker_args, statuses: nil)
|
|
pipe \
|
|
docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
|
extract_version_from_name
|
|
end
|
|
|
|
|
|
def make_env_directory
|
|
make_directory role.env(host).secrets_directory
|
|
end
|
|
|
|
def remove_env_file
|
|
[ :rm, "-f", role.env(host).secrets_file ]
|
|
end
|
|
|
|
|
|
private
|
|
def container_name(version = nil)
|
|
[ role.container_prefix, version || config.version ].compact.join("-")
|
|
end
|
|
|
|
def latest_image_id
|
|
docker :image, :ls, *argumentize("--filter", "reference=#{config.latest_image}"), "--format", "'{{.ID}}'"
|
|
end
|
|
|
|
def current_running_container(format:)
|
|
pipe \
|
|
shell(chain(latest_image_container(format: format), latest_container(format: format))),
|
|
[ :head, "-1" ]
|
|
end
|
|
|
|
def latest_image_container(format:)
|
|
latest_container format: format, filters: [ "ancestor=$(#{latest_image_id.join(" ")})" ]
|
|
end
|
|
|
|
def latest_container(format:, filters: nil)
|
|
docker :ps, "--latest", *format, *filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters)
|
|
end
|
|
|
|
def filter_args(statuses: nil)
|
|
argumentize "--filter", filters(statuses: statuses)
|
|
end
|
|
|
|
def extract_version_from_name
|
|
# Extract SHA from "service-role-dest-SHA"
|
|
%(while read line; do echo ${line##{role.container_prefix}-}; done)
|
|
end
|
|
|
|
def filters(statuses: nil)
|
|
[ "label=service=#{config.service}" ].tap do |filters|
|
|
filters << "label=destination=#{config.destination}" if config.destination
|
|
filters << "label=role=#{role}" if role
|
|
statuses&.each do |status|
|
|
filters << "status=#{status}"
|
|
end
|
|
end
|
|
end
|
|
end
|