Merge branch 'main' into default-to-deploying-config-version
This commit is contained in:
57
Gemfile.lock
57
Gemfile.lock
@@ -13,90 +13,79 @@ PATH
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actionpack (7.0.4)
|
actionpack (7.0.4.3)
|
||||||
actionview (= 7.0.4)
|
actionview (= 7.0.4.3)
|
||||||
activesupport (= 7.0.4)
|
activesupport (= 7.0.4.3)
|
||||||
rack (~> 2.0, >= 2.2.0)
|
rack (~> 2.0, >= 2.2.0)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actionview (7.0.4)
|
actionview (7.0.4.3)
|
||||||
activesupport (= 7.0.4)
|
activesupport (= 7.0.4.3)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||||
activesupport (7.0.4)
|
activesupport (7.0.4.3)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
bcrypt_pbkdf (1.1.0)
|
bcrypt_pbkdf (1.1.0)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
concurrent-ruby (1.1.10)
|
concurrent-ruby (1.2.2)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
debug (1.7.1)
|
debug (1.7.1)
|
||||||
irb (>= 1.5.0)
|
|
||||||
reline (>= 0.3.1)
|
|
||||||
dotenv (2.8.1)
|
dotenv (2.8.1)
|
||||||
ed25519 (1.3.0)
|
ed25519 (1.3.0)
|
||||||
erubi (1.12.0)
|
erubi (1.12.0)
|
||||||
i18n (1.12.0)
|
i18n (1.12.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
io-console (0.6.0)
|
|
||||||
irb (1.6.2)
|
|
||||||
reline (>= 0.3.0)
|
|
||||||
loofah (2.19.1)
|
loofah (2.19.1)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
method_source (1.0.0)
|
method_source (1.0.0)
|
||||||
minitest (5.17.0)
|
minitest (5.18.0)
|
||||||
mocha (2.0.2)
|
mocha (2.0.2)
|
||||||
ruby2_keywords (>= 0.0.5)
|
ruby2_keywords (>= 0.0.5)
|
||||||
net-scp (4.0.0)
|
net-scp (4.0.0)
|
||||||
net-ssh (>= 2.6.5, < 8.0.0)
|
net-ssh (>= 2.6.5, < 8.0.0)
|
||||||
net-ssh (7.0.1)
|
net-ssh (7.1.0)
|
||||||
nokogiri (1.14.0-arm64-darwin)
|
nokogiri (1.14.2-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.14.0-x86_64-darwin)
|
nokogiri (1.14.2-x86_64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.14.0-x86_64-linux)
|
nokogiri (1.14.2-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
racc (1.6.2)
|
racc (1.6.2)
|
||||||
rack (2.2.5)
|
rack (2.2.6.4)
|
||||||
rack-test (2.0.2)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rails-dom-testing (2.0.3)
|
rails-dom-testing (2.0.3)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.4.4)
|
rails-html-sanitizer (1.5.0)
|
||||||
loofah (~> 2.19, >= 2.19.1)
|
loofah (~> 2.19, >= 2.19.1)
|
||||||
railties (7.0.4)
|
railties (7.0.4.3)
|
||||||
actionpack (= 7.0.4)
|
actionpack (= 7.0.4.3)
|
||||||
activesupport (= 7.0.4)
|
activesupport (= 7.0.4.3)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
zeitwerk (~> 2.5)
|
zeitwerk (~> 2.5)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
reline (0.3.2)
|
|
||||||
io-console (~> 0.5)
|
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
sshkit (1.21.3)
|
sshkit (1.21.4)
|
||||||
net-scp (>= 1.1.2)
|
net-scp (>= 1.1.2)
|
||||||
net-ssh (>= 2.8.0)
|
net-ssh (>= 2.8.0)
|
||||||
thor (1.2.1)
|
thor (1.2.1)
|
||||||
tzinfo (2.0.5)
|
tzinfo (2.0.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
zeitwerk (2.6.6)
|
zeitwerk (2.6.7)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
arm64-darwin-20
|
arm64-darwin
|
||||||
arm64-darwin-21
|
x86_64-darwin
|
||||||
arm64-darwin-22
|
|
||||||
x86_64-darwin-20
|
|
||||||
x86_64-darwin-21
|
|
||||||
x86_64-darwin-22
|
|
||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
|||||||
50
README.md
50
README.md
@@ -4,7 +4,7 @@ MRSK deploys web apps anywhere from bare metal to cloud VMs using Docker with ze
|
|||||||
|
|
||||||
Watch the screencast: https://www.youtube.com/watch?v=LL1cV2FXZ5I
|
Watch the screencast: https://www.youtube.com/watch?v=LL1cV2FXZ5I
|
||||||
|
|
||||||
Join us on Discord: https://discord.gg/DQETs3Pm
|
Join us on Discord: https://discord.gg/YgHVT7GCXS
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -426,6 +426,46 @@ traefik:
|
|||||||
host_port: 8080
|
host_port: 8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Configure docker options for traefik
|
||||||
|
|
||||||
|
We allow users to pass additional docker options to the trafik container like
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
traefik:
|
||||||
|
options:
|
||||||
|
publish:
|
||||||
|
- 8080:8080
|
||||||
|
volumes:
|
||||||
|
- /tmp/example.json:/tmp/example.json
|
||||||
|
memory: 512m
|
||||||
|
```
|
||||||
|
|
||||||
|
This will start the traefik container with a command like: `docker run ... --volume /tmp/example.json:/tmp/example.json --publish 8080:8080 `
|
||||||
|
|
||||||
|
|
||||||
|
### Configure alternate entrypoints for traefik
|
||||||
|
|
||||||
|
You can configure multiple entrypoints for traefik like so:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
service: myservice
|
||||||
|
|
||||||
|
labels:
|
||||||
|
traefik.tcp.routers.other.rule: 'HostSNI(`*`)'
|
||||||
|
traefik.tcp.routers.other.entrypoints: otherentrypoint
|
||||||
|
traefik.tcp.services.other.loadbalancer.server.port: 9000
|
||||||
|
traefik.http.routers.myservice.entrypoints: web
|
||||||
|
traefik.http.services.myservice.loadbalancer.server.port: 8080
|
||||||
|
|
||||||
|
traefik:
|
||||||
|
options:
|
||||||
|
publish:
|
||||||
|
- 9000:9000
|
||||||
|
args:
|
||||||
|
entrypoints.web.address: ':80'
|
||||||
|
entrypoints.otherentrypoint.address: ':9000'
|
||||||
|
```
|
||||||
|
|
||||||
### Configuring build args for new images
|
### Configuring build args for new images
|
||||||
|
|
||||||
Build arguments that aren't secret can also be configured:
|
Build arguments that aren't secret can also be configured:
|
||||||
@@ -445,7 +485,7 @@ FROM ruby:$RUBY_VERSION-slim as base
|
|||||||
|
|
||||||
### Using accessories for database, cache, search services
|
### Using accessories for database, cache, search services
|
||||||
|
|
||||||
You can manage your accessory services via MRSK as well. The services will build off public images, and will not be automatically updated when you deploy:
|
You can manage your accessory services via MRSK as well. Accessories are long-lived services that your app depends on. They are not updated when you deploy.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
accessories:
|
accessories:
|
||||||
@@ -466,10 +506,16 @@ accessories:
|
|||||||
port: "36379:6379"
|
port: "36379:6379"
|
||||||
volumes:
|
volumes:
|
||||||
- /var/lib/redis:/data
|
- /var/lib/redis:/data
|
||||||
|
internal-example:
|
||||||
|
image: registry.digitalocean.com/user/otherservice:latest
|
||||||
|
host: 1.1.1.5
|
||||||
|
port: 44444
|
||||||
```
|
```
|
||||||
|
|
||||||
Now run `mrsk accessory start mysql` to start the MySQL server on 1.1.1.3. See `mrsk accessory` for all the commands possible.
|
Now run `mrsk accessory start mysql` to start the MySQL server on 1.1.1.3. See `mrsk accessory` for all the commands possible.
|
||||||
|
|
||||||
|
Accessory images must be public or tagged in your private registry.
|
||||||
|
|
||||||
### Using Cron
|
### Using Cron
|
||||||
|
|
||||||
You can use a specific container to run your Cron jobs:
|
You can use a specific container to run your Cron jobs:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|||||||
upload(name)
|
upload(name)
|
||||||
|
|
||||||
on(accessory.host) do
|
on(accessory.host) do
|
||||||
|
execute *MRSK.registry.login
|
||||||
execute *MRSK.auditor.record("Booted #{name} accessory"), verbosity: :debug
|
execute *MRSK.auditor.record("Booted #{name} accessory"), verbosity: :debug
|
||||||
execute *accessory.run
|
execute *accessory.run
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
invoke "mrsk:cli:prune:all", [], invoke_options
|
invoke "mrsk:cli:prune:all", [], invoke_options
|
||||||
end
|
end
|
||||||
|
|
||||||
audit_broadcast "Deployed app in #{runtime.to_i} seconds" unless options[:skip_broadcast]
|
audit_broadcast "Deployed #{service_version} in #{runtime.round} seconds" unless options[:skip_broadcast]
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "redeploy", "Deploy app to servers without bootstrapping servers, starting Traefik, pruning, and registry login"
|
desc "redeploy", "Deploy app to servers without bootstrapping servers, starting Traefik, pruning, and registry login"
|
||||||
@@ -63,7 +63,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
invoke "mrsk:cli:app:boot", [], invoke_options
|
invoke "mrsk:cli:app:boot", [], invoke_options
|
||||||
end
|
end
|
||||||
|
|
||||||
audit_broadcast "Redeployed app in #{runtime.to_i} seconds" unless options[:skip_broadcast]
|
audit_broadcast "Redeployed #{service_version} in #{runtime.round} seconds" unless options[:skip_broadcast]
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "rollback [VERSION]", "Rollback app to VERSION"
|
desc "rollback [VERSION]", "Rollback app to VERSION"
|
||||||
@@ -74,18 +74,21 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
say "Start version #{version}, then wait #{MRSK.config.readiness_delay}s for app to boot before stopping the old version...", :magenta
|
say "Start version #{version}, then wait #{MRSK.config.readiness_delay}s for app to boot before stopping the old version...", :magenta
|
||||||
|
|
||||||
cli = self
|
cli = self
|
||||||
|
old_version = nil
|
||||||
|
|
||||||
on(MRSK.hosts) do |host|
|
on(MRSK.hosts) do |host|
|
||||||
old_version = capture_with_info(*MRSK.app.current_running_version).strip.presence
|
old_version = capture_with_info(*MRSK.app.current_running_version).strip.presence
|
||||||
|
|
||||||
execute *MRSK.app.start
|
execute *MRSK.app.start
|
||||||
|
|
||||||
sleep MRSK.config.readiness_delay
|
if old_version
|
||||||
|
sleep MRSK.config.readiness_delay
|
||||||
|
|
||||||
execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false
|
execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
audit_broadcast "Rolled back app to version #{version}" unless options[:skip_broadcast]
|
audit_broadcast "Rolled back #{service_version(Mrsk::Utils.abbreviate_version(old_version))} to #{service_version}" unless options[:skip_broadcast]
|
||||||
else
|
else
|
||||||
say "The app version '#{version}' is not available as a container (use 'mrsk app containers' for available versions)", :red
|
say "The app version '#{version}' is not available as a container (use 'mrsk app containers' for available versions)", :red
|
||||||
end
|
end
|
||||||
@@ -207,4 +210,8 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
def deploy_options
|
def deploy_options
|
||||||
{ "version" => MRSK.config.version }.merge(options.without("skip_push"))
|
{ "version" => MRSK.config.version }.merge(options.without("skip_push"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def service_version(version = MRSK.config.abbreviated_version)
|
||||||
|
[ MRSK.config.service, version ].compact.join("@")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
||||||
attr_reader :accessory_config
|
attr_reader :accessory_config
|
||||||
delegate :service_name, :image, :host, :port, :files, :directories, :env_args, :volume_args, :label_args, to: :accessory_config
|
delegate :service_name, :image, :host, :port, :files, :directories, :publish_args, :env_args, :volume_args, :label_args, to: :accessory_config
|
||||||
|
|
||||||
def initialize(config, name:)
|
def initialize(config, name:)
|
||||||
super(config)
|
super(config)
|
||||||
@@ -13,7 +13,7 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
|||||||
"--detach",
|
"--detach",
|
||||||
"--restart", "unless-stopped",
|
"--restart", "unless-stopped",
|
||||||
"--log-opt", "max-size=#{MAX_LOG_SIZE}",
|
"--log-opt", "max-size=#{MAX_LOG_SIZE}",
|
||||||
"--publish", port,
|
*publish_args,
|
||||||
*env_args,
|
*env_args,
|
||||||
*volume_args,
|
*volume_args,
|
||||||
*label_args,
|
*label_args,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def info
|
def info
|
||||||
docker :ps, *service_filter_with_destination
|
docker :ps, *filter_args
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -76,13 +76,13 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|||||||
|
|
||||||
|
|
||||||
def current_container_id
|
def current_container_id
|
||||||
docker :ps, "--quiet", *service_filter_with_destination
|
docker :ps, "--quiet", *filter_args
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_running_version
|
def current_running_version
|
||||||
# FIXME: Find more graceful way to extract the version from "app-version" than using sed and tail!
|
# FIXME: Find more graceful way to extract the version from "app-version" than using sed and tail!
|
||||||
pipe \
|
pipe \
|
||||||
docker(:ps, *service_filter_with_destination, "--format", '"{{.Names}}"'),
|
docker(:ps, *filter_args, "--format", '"{{.Names}}"'),
|
||||||
%(sed 's/-/\\n/g'),
|
%(sed 's/-/\\n/g'),
|
||||||
"tail -n 1"
|
"tail -n 1"
|
||||||
end
|
end
|
||||||
@@ -101,7 +101,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|||||||
|
|
||||||
|
|
||||||
def list_containers
|
def list_containers
|
||||||
docker :container, :ls, "--all", *service_filter_with_destination
|
docker :container, :ls, "--all", *filter_args
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_container_names
|
def list_container_names
|
||||||
@@ -115,7 +115,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def remove_containers
|
def remove_containers
|
||||||
docker :container, :prune, "--force", *service_filter_with_destination
|
docker :container, :prune, "--force", *filter_args
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_images
|
def list_images
|
||||||
@@ -123,7 +123,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def remove_images
|
def remove_images
|
||||||
docker :image, :prune, "--all", "--force", *service_filter
|
docker :image, :prune, "--all", "--force", *filter_args
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -136,15 +136,13 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|||||||
container_id_for(container_name: service_with_version_and_destination(version))
|
container_id_for(container_name: service_with_version_and_destination(version))
|
||||||
end
|
end
|
||||||
|
|
||||||
def service_filter
|
def filter_args
|
||||||
[ "--filter", "label=service=#{config.service}" ]
|
argumentize "--filter", filters
|
||||||
end
|
end
|
||||||
|
|
||||||
def service_filter_with_destination
|
def filters
|
||||||
if config.destination
|
[ "label=service=#{config.service}" ].tap do |filters|
|
||||||
service_filter << "label=destination=#{config.destination}"
|
filters << "label=destination=#{config.destination}" if config.destination
|
||||||
else
|
|
||||||
service_filter
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module Mrsk::Commands
|
module Mrsk::Commands
|
||||||
class Base
|
class Base
|
||||||
delegate :redact, to: Mrsk::Utils
|
delegate :redact, :argumentize, to: Mrsk::Utils
|
||||||
|
|
||||||
MAX_LOG_SIZE = "10m"
|
MAX_LOG_SIZE = "10m"
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|||||||
"--log-opt", "max-size=#{MAX_LOG_SIZE}",
|
"--log-opt", "max-size=#{MAX_LOG_SIZE}",
|
||||||
"--publish", port,
|
"--publish", port,
|
||||||
"--volume", "/var/run/docker.sock:/var/run/docker.sock",
|
"--volume", "/var/run/docker.sock:/var/run/docker.sock",
|
||||||
|
*docker_options_args,
|
||||||
"traefik",
|
"traefik",
|
||||||
"--providers.docker",
|
"--providers.docker",
|
||||||
"--log.level=DEBUG",
|
"--log.level=DEBUG",
|
||||||
@@ -54,6 +55,10 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def docker_options_args
|
||||||
|
optionize(config.traefik["options"] || {})
|
||||||
|
end
|
||||||
|
|
||||||
def cmd_option_args
|
def cmd_option_args
|
||||||
if args = config.traefik["args"]
|
if args = config.traefik["args"]
|
||||||
optionize args, with: "="
|
optionize args, with: "="
|
||||||
|
|||||||
@@ -15,15 +15,16 @@ class Mrsk::Configuration
|
|||||||
|
|
||||||
class << self
|
class << self
|
||||||
def create_from(base_config_file, destination: nil, version: "missing")
|
def create_from(base_config_file, destination: nil, version: "missing")
|
||||||
new(load_config_file(base_config_file).tap do |config|
|
raw_config = load_config_files(base_config_file, *destination_config_file(base_config_file, destination))
|
||||||
if destination
|
|
||||||
config.deep_merge! \
|
new raw_config, destination: destination, version: version
|
||||||
load_config_file destination_config_file(base_config_file, destination)
|
|
||||||
end
|
|
||||||
end, destination: destination, version: version)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
def load_config_files(*files)
|
||||||
|
files.inject({}) { |config, file| config.deep_merge! load_config_file(file) }
|
||||||
|
end
|
||||||
|
|
||||||
def load_config_file(file)
|
def load_config_file(file)
|
||||||
if file.exist?
|
if file.exist?
|
||||||
YAML.load(ERB.new(IO.read(file)).result).symbolize_keys
|
YAML.load(ERB.new(IO.read(file)).result).symbolize_keys
|
||||||
@@ -33,8 +34,7 @@ class Mrsk::Configuration
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destination_config_file(base_config_file, destination)
|
def destination_config_file(base_config_file, destination)
|
||||||
dir, basename = base_config_file.split
|
base_config_file.sub_ext(".#{destination}.yml") if destination
|
||||||
dir.join basename.to_s.remove(".yml") + ".#{destination}.yml"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -46,6 +46,11 @@ class Mrsk::Configuration
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def abbreviated_version
|
||||||
|
Mrsk::Utils.abbreviate_version(version)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def roles
|
def roles
|
||||||
@roles ||= role_names.collect { |role_name| Role.new(role_name, config: self) }
|
@roles ||= role_names.collect { |role_name| Role.new(role_name, config: self) }
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,13 +20,15 @@ class Mrsk::Configuration::Accessory
|
|||||||
end
|
end
|
||||||
|
|
||||||
def port
|
def port
|
||||||
if specifics["port"].to_s.include?(":")
|
if port = specifics["port"]&.to_s
|
||||||
specifics["port"]
|
port.include?(":") ? port : "#{port}:#{port}"
|
||||||
else
|
|
||||||
"#{specifics["port"]}:#{specifics["port"]}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def publish_args
|
||||||
|
argumentize "--publish", port if port
|
||||||
|
end
|
||||||
|
|
||||||
def labels
|
def labels
|
||||||
default_labels.merge(specifics["labels"] || {})
|
default_labels.merge(specifics["labels"] || {})
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -26,14 +26,19 @@ module Mrsk::Utils
|
|||||||
# 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
|
||||||
args.collect { |(key, value)| value == true ? "--#{key}" : "--#{key}#{with}#{escape_shell_value(value)}" }
|
flatten_args(args).collect { |(key, value)| value == true ? "--#{key}" : "--#{key}#{with}#{escape_shell_value(value)}" }
|
||||||
else
|
else
|
||||||
args.collect { |(key, value)| [ "--#{key}", value == true ? nil : escape_shell_value(value) ] }
|
flatten_args(args).collect { |(key, value)| [ "--#{key}", value == true ? nil : escape_shell_value(value) ] }
|
||||||
end
|
end
|
||||||
|
|
||||||
options.flatten.compact
|
options.flatten.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Flattens a one-to-many structure into an array of two-element arrays each containing a key-value pair
|
||||||
|
def flatten_args(args)
|
||||||
|
args.flat_map { |key, value| value.try(:map) { |entry| [key, entry] } || [ [ key, value ] ] }
|
||||||
|
end
|
||||||
|
|
||||||
# Copied from SSHKit::Backend::Abstract#redact to be available inside Commands classes
|
# Copied from SSHKit::Backend::Abstract#redact to be available inside Commands classes
|
||||||
def redact(arg) # Used in execute_command to hide redact() args a user passes in
|
def redact(arg) # Used in execute_command to hide redact() args a user passes in
|
||||||
arg.to_s.extend(SSHKit::Redaction) # to_s due to our inability to extend Integer, etc
|
arg.to_s.extend(SSHKit::Redaction) # to_s due to our inability to extend Integer, etc
|
||||||
@@ -43,4 +48,9 @@ module Mrsk::Utils
|
|||||||
def escape_shell_value(value)
|
def escape_shell_value(value)
|
||||||
value.to_s.dump.gsub(/`/, '\\\\`')
|
value.to_s.dump.gsub(/`/, '\\\\`')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Abbreviate a git revhash for concise display
|
||||||
|
def abbreviate_version(version)
|
||||||
|
version[0...7] if version
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ class CliAccessoryTest < CliTestCase
|
|||||||
Mrsk::Cli::Accessory.any_instance.expects(:upload).with("mysql")
|
Mrsk::Cli::Accessory.any_instance.expects(:upload).with("mysql")
|
||||||
|
|
||||||
run_command("boot", "mysql").tap do |output|
|
run_command("boot", "mysql").tap do |output|
|
||||||
|
assert_match /docker login.*on 1.1.1.3/, output
|
||||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=10m --publish 3306:3306 -e [REDACTED] -e MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=10m --publish 3306:3306 -e [REDACTED] -e MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -17,6 +18,8 @@ class CliAccessoryTest < CliTestCase
|
|||||||
Mrsk::Cli::Accessory.any_instance.expects(:upload).with("redis")
|
Mrsk::Cli::Accessory.any_instance.expects(:upload).with("redis")
|
||||||
|
|
||||||
run_command("boot", "all").tap do |output|
|
run_command("boot", "all").tap do |output|
|
||||||
|
assert_match /docker login.*on 1.1.1.3/, output
|
||||||
|
assert_match /docker login.*on 1.1.1.4/, output
|
||||||
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=10m --publish 3306:3306 -e [REDACTED] -e MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
assert_match "docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=10m --publish 3306:3306 -e [REDACTED] -e MYSQL_ROOT_HOST=\"%\" --volume $PWD/app-mysql/etc/mysql/my.cnf:/etc/mysql/my.cnf --volume $PWD/app-mysql/data:/var/lib/mysql --label service=\"app-mysql\" mysql:5.7 on 1.1.1.3", output
|
||||||
assert_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=10m --publish 6379:6379 --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.4", output
|
assert_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=10m --publish 6379:6379 --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.4", output
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -88,11 +88,23 @@ class CliMainTest < CliTestCase
|
|||||||
|
|
||||||
test "rollback good version" do
|
test "rollback good version" do
|
||||||
Mrsk::Cli::Main.any_instance.stubs(:container_name_available?).returns(true)
|
Mrsk::Cli::Main.any_instance.stubs(:container_name_available?).returns(true)
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info).with(:docker, :ps, "--filter", "label=service=app", "--format", "\"{{.Names}}\"", "|", "sed 's/-/\\n/g'", "|", "tail -n 1").returns("version-to-rollback\n").times(2)
|
||||||
|
|
||||||
run_command("rollback", "123").tap do |output|
|
run_command("rollback", "123").tap do |output|
|
||||||
assert_match /Start version 123/, output
|
assert_match "Start version 123", output
|
||||||
assert_match /docker ps -q --filter label=service=app | xargs docker stop/, output
|
assert_match "docker start app-123", output
|
||||||
assert_match /docker start app-123/, output
|
assert_match "docker container ls --all --filter name=app-version-to-rollback --quiet | xargs docker stop", output, "Should stop the container that was previously running"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rollback without old version" do
|
||||||
|
Mrsk::Cli::Main.any_instance.stubs(:container_name_available?).returns(true)
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info).with(:docker, :ps, "--filter", "label=service=app", "--format", "\"{{.Names}}\"", "|", "sed 's/-/\\n/g'", "|", "tail -n 1").returns("").times(2)
|
||||||
|
|
||||||
|
run_command("rollback", "123").tap do |output|
|
||||||
|
assert_match "Start version 123", output
|
||||||
|
assert_match "docker start app-123", output
|
||||||
|
assert_no_match "docker stop", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ require "test_helper"
|
|||||||
class CommandsAccessoryTest < ActiveSupport::TestCase
|
class CommandsAccessoryTest < ActiveSupport::TestCase
|
||||||
setup do
|
setup do
|
||||||
@config = {
|
@config = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" },
|
service: "app", image: "dhh/app", registry: { "server" => "private.registry", "username" => "dhh", "password" => "secret" },
|
||||||
servers: [ "1.1.1.1" ],
|
servers: [ "1.1.1.1" ],
|
||||||
accessories: {
|
accessories: {
|
||||||
"mysql" => {
|
"mysql" => {
|
||||||
"image" => "mysql:8.0",
|
"image" => "private.registry/mysql:8.0",
|
||||||
"host" => "1.1.1.5",
|
"host" => "1.1.1.5",
|
||||||
"port" => "3306",
|
"port" => "3306",
|
||||||
"env" => {
|
"env" => {
|
||||||
@@ -32,13 +32,18 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
"volumes" => [
|
"volumes" => [
|
||||||
"/var/lib/redis:/data"
|
"/var/lib/redis:/data"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"busybox" => {
|
||||||
|
"image" => "busybox:latest",
|
||||||
|
"host" => "1.1.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@config = Mrsk::Configuration.new(@config)
|
@config = Mrsk::Configuration.new(@config)
|
||||||
@mysql = Mrsk::Commands::Accessory.new(@config, name: :mysql)
|
@mysql = Mrsk::Commands::Accessory.new(@config, name: :mysql)
|
||||||
@redis = Mrsk::Commands::Accessory.new(@config, name: :redis)
|
@redis = Mrsk::Commands::Accessory.new(@config, name: :redis)
|
||||||
|
@busybox = Mrsk::Commands::Accessory.new(@config, name: :busybox)
|
||||||
|
|
||||||
ENV["MYSQL_ROOT_PASSWORD"] = "secret123"
|
ENV["MYSQL_ROOT_PASSWORD"] = "secret123"
|
||||||
end
|
end
|
||||||
@@ -49,12 +54,16 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "run" do
|
test "run" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=10m --publish 3306:3306 -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" --label service=\"app-mysql\" mysql:8.0",
|
"docker run --name app-mysql --detach --restart unless-stopped --log-opt max-size=10m --publish 3306:3306 -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" --label service=\"app-mysql\" private.registry/mysql:8.0",
|
||||||
@mysql.run.join(" ")
|
@mysql.run.join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=10m --publish 6379:6379 -e SOMETHING=\"else\" --volume /var/lib/redis:/data --label service=\"app-redis\" --label cache=\"true\" redis:latest",
|
"docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=10m --publish 6379:6379 -e SOMETHING=\"else\" --volume /var/lib/redis:/data --label service=\"app-redis\" --label cache=\"true\" redis:latest",
|
||||||
@redis.run.join(" ")
|
@redis.run.join(" ")
|
||||||
|
|
||||||
|
assert_equal \
|
||||||
|
"docker run --name app-busybox --detach --restart unless-stopped --log-opt max-size=10m --label service=\"app-busybox\" busybox:latest",
|
||||||
|
@busybox.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "start" do
|
test "start" do
|
||||||
@@ -78,7 +87,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "execute in new container" do
|
test "execute in new container" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --rm -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" mysql:8.0 mysql -u root",
|
"docker run --rm -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" private.registry/mysql:8.0 mysql -u root",
|
||||||
@mysql.execute_in_new_container("mysql", "-u", "root").join(" ")
|
@mysql.execute_in_new_container("mysql", "-u", "root").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -90,7 +99,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "execute in new container over ssh" do
|
test "execute in new container over ssh" do
|
||||||
@mysql.stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do
|
@mysql.stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do
|
||||||
assert_match %r|docker run -it --rm -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" mysql:8.0 mysql -u root|,
|
assert_match %r|docker run -it --rm -e MYSQL_ROOT_PASSWORD=\"secret123\" -e MYSQL_ROOT_HOST=\"%\" private.registry/mysql:8.0 mysql -u root|,
|
||||||
@mysql.execute_in_new_container_over_ssh("mysql", "-u", "root")
|
@mysql.execute_in_new_container_over_ssh("mysql", "-u", "root")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
ENV["RAILS_MASTER_KEY"] = "456"
|
ENV["RAILS_MASTER_KEY"] = "456"
|
||||||
|
|
||||||
@config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], env: { "secret" => [ "RAILS_MASTER_KEY" ] } }
|
@config = { service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ], env: { "secret" => [ "RAILS_MASTER_KEY" ] } }
|
||||||
@app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config).tap { |c| c.version = "999" }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
teardown do
|
teardown do
|
||||||
@@ -15,7 +14,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
test "run" do
|
test "run" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999",
|
"docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999",
|
||||||
@app.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with volumes" do
|
test "run with volumes" do
|
||||||
@@ -23,7 +22,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --volume /local/path:/container/path --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999",
|
"docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --volume /local/path:/container/path --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/up\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999",
|
||||||
@app.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with custom healthcheck path" do
|
test "run with custom healthcheck path" do
|
||||||
@@ -31,148 +30,237 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/healthz\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999",
|
"docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"web\" --label traefik.http.routers.app.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.services.app.loadbalancer.healthcheck.path=\"/healthz\" --label traefik.http.services.app.loadbalancer.healthcheck.interval=\"1s\" --label traefik.http.middlewares.app-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app.middlewares=\"app-retry@docker\" dhh/app:999",
|
||||||
@app.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run with custom options" do
|
test "run with custom options" do
|
||||||
@config[:servers] = { "web" => [ "1.1.1.1" ], "jobs" => { "hosts" => [ "1.1.1.2" ], "cmd" => "bin/jobs", "options" => { "mount" => "somewhere", "cap-add" => true } } }
|
@config[:servers] = { "web" => [ "1.1.1.1" ], "jobs" => { "hosts" => [ "1.1.1.2" ], "cmd" => "bin/jobs", "options" => { "mount" => "somewhere", "cap-add" => true } } }
|
||||||
@app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config).tap { |c| c.version = "999" }
|
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"jobs\" --mount \"somewhere\" --cap-add dhh/app:999 bin/jobs",
|
"docker run --detach --restart unless-stopped --log-opt max-size=10m --name app-999 -e MRSK_CONTAINER_NAME=\"app-999\" -e RAILS_MASTER_KEY=\"456\" --label service=\"app\" --label role=\"jobs\" --mount \"somewhere\" --cap-add dhh/app:999 bin/jobs",
|
||||||
@app.run(role: :jobs).join(" ")
|
new_command.run(role: :jobs).join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "start" do
|
test "start" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker start app-999",
|
"docker start app-999",
|
||||||
@app.start.join(" ")
|
new_command.start.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "start with destination" do
|
||||||
|
@destination = "staging"
|
||||||
|
assert_equal \
|
||||||
|
"docker start app-staging-999",
|
||||||
|
new_command.start.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "stop" do
|
test "stop" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --quiet --filter label=service=app | xargs docker stop",
|
"docker ps --quiet --filter label=service=app | xargs docker stop",
|
||||||
@app.stop.join(" ")
|
new_command.stop.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "stop with version" do
|
||||||
|
assert_equal \
|
||||||
|
"docker container ls --all --filter name=app-123 --quiet | xargs docker stop",
|
||||||
|
new_command.stop(version: "123").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "info" do
|
test "info" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --filter label=service=app",
|
"docker ps --filter label=service=app",
|
||||||
@app.info.join(" ")
|
new_command.info.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "info with destination" do
|
||||||
|
@destination = "staging"
|
||||||
|
assert_equal \
|
||||||
|
"docker ps --filter label=service=app --filter label=destination=staging",
|
||||||
|
new_command.info.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
test "logs" do
|
test "logs" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --quiet --filter label=service=app | xargs docker logs 2>&1",
|
"docker ps --quiet --filter label=service=app | xargs docker logs 2>&1",
|
||||||
@app.logs.join(" ")
|
new_command.logs.join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --quiet --filter label=service=app | xargs docker logs --since 5m 2>&1",
|
"docker ps --quiet --filter label=service=app | xargs docker logs --since 5m 2>&1",
|
||||||
@app.logs(since: "5m").join(" ")
|
new_command.logs(since: "5m").join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --quiet --filter label=service=app | xargs docker logs --tail 100 2>&1",
|
"docker ps --quiet --filter label=service=app | xargs docker logs --tail 100 2>&1",
|
||||||
@app.logs(lines: "100").join(" ")
|
new_command.logs(lines: "100").join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --quiet --filter label=service=app | xargs docker logs --since 5m --tail 100 2>&1",
|
"docker ps --quiet --filter label=service=app | xargs docker logs --since 5m --tail 100 2>&1",
|
||||||
@app.logs(since: "5m", lines: "100").join(" ")
|
new_command.logs(since: "5m", lines: "100").join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --quiet --filter label=service=app | xargs docker logs 2>&1 | grep 'my-id'",
|
"docker ps --quiet --filter label=service=app | xargs docker logs 2>&1 | grep 'my-id'",
|
||||||
@app.logs(grep: "my-id").join(" ")
|
new_command.logs(grep: "my-id").join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --quiet --filter label=service=app | xargs docker logs --since 5m 2>&1 | grep 'my-id'",
|
"docker ps --quiet --filter label=service=app | xargs docker logs --since 5m 2>&1 | grep 'my-id'",
|
||||||
@app.logs(since: "5m", grep: "my-id").join(" ")
|
new_command.logs(since: "5m", grep: "my-id").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "follow logs" do
|
test "follow logs" do
|
||||||
@app.stub(:run_over_ssh, ->(cmd, host:) { cmd.join(" ") }) do
|
assert_match \
|
||||||
assert_equal \
|
"docker ps --quiet --filter label=service=app | xargs docker logs --timestamps --tail 10 --follow 2>&1",
|
||||||
"docker ps --quiet --filter label=service=app | xargs docker logs --timestamps --tail 10 --follow 2>&1",
|
new_command.follow_logs(host: "app-1")
|
||||||
@app.follow_logs(host: "app-1")
|
|
||||||
|
|
||||||
assert_equal \
|
assert_match \
|
||||||
"docker ps --quiet --filter label=service=app | xargs docker logs --timestamps --tail 10 --follow 2>&1 | grep \"Completed\"",
|
"docker ps --quiet --filter label=service=app | xargs docker logs --timestamps --tail 10 --follow 2>&1 | grep \"Completed\"",
|
||||||
@app.follow_logs(host: "app-1", grep: "Completed")
|
new_command.follow_logs(host: "app-1", grep: "Completed")
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
test "execute in new container" do
|
test "execute in new container" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --rm -e RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails db:setup",
|
"docker run --rm -e RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails db:setup",
|
||||||
@app.execute_in_new_container("bin/rails", "db:setup").join(" ")
|
new_command.execute_in_new_container("bin/rails", "db:setup").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "execute in existing container" do
|
test "execute in existing container" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker exec app-999 bin/rails db:setup",
|
"docker exec app-999 bin/rails db:setup",
|
||||||
@app.execute_in_existing_container("bin/rails", "db:setup").join(" ")
|
new_command.execute_in_existing_container("bin/rails", "db:setup").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "execute in new container over ssh" do
|
test "execute in new container over ssh" do
|
||||||
@app.stub(:run_over_ssh, ->(cmd, host:) { cmd.join(" ") }) do
|
assert_match %r|docker run -it --rm -e RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails c|,
|
||||||
assert_match %r|docker run -it --rm -e RAILS_MASTER_KEY=\"456\" dhh/app:999 bin/rails c|,
|
new_command.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1")
|
||||||
@app.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "execute in existing container over ssh" do
|
test "execute in existing container over ssh" do
|
||||||
@app.stub(:run_over_ssh, ->(cmd, host:) { cmd.join(" ") }) do
|
assert_match %r|docker exec -it app-999 bin/rails c|,
|
||||||
assert_match %r|docker exec -it app-999 bin/rails c|,
|
new_command.execute_in_existing_container_over_ssh("bin/rails", "c", host: "app-1")
|
||||||
@app.execute_in_existing_container_over_ssh("bin/rails", "c", host: "app-1")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run over ssh" do
|
test "run over ssh" do
|
||||||
assert_equal "ssh -t root@1.1.1.1 'ls'", @app.run_over_ssh("ls", host: "1.1.1.1")
|
assert_equal "ssh -t root@1.1.1.1 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run over ssh with custom user" do
|
test "run over ssh with custom user" do
|
||||||
@app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config.tap { |c| c[:ssh] = { "user" => "app" } })
|
@config[:ssh] = { "user" => "app" }
|
||||||
assert_equal "ssh -t app@1.1.1.1 'ls'", @app.run_over_ssh("ls", host: "1.1.1.1")
|
assert_equal "ssh -t app@1.1.1.1 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run over ssh with proxy" do
|
test "run over ssh with proxy" do
|
||||||
@app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config.tap { |c| c[:ssh] = { "proxy" => "2.2.2.2" } })
|
@config[:ssh] = { "proxy" => "2.2.2.2" }
|
||||||
assert_equal "ssh -J root@2.2.2.2 -t root@1.1.1.1 'ls'", @app.run_over_ssh("ls", host: "1.1.1.1")
|
assert_equal "ssh -J root@2.2.2.2 -t root@1.1.1.1 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run over ssh with proxy user" do
|
test "run over ssh with proxy user" do
|
||||||
@app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config.tap { |c| c[:ssh] = { "proxy" => "app@2.2.2.2" } })
|
@config[:ssh] = { "proxy" => "app@2.2.2.2" }
|
||||||
assert_equal "ssh -J app@2.2.2.2 -t root@1.1.1.1 'ls'", @app.run_over_ssh("ls", host: "1.1.1.1")
|
assert_equal "ssh -J app@2.2.2.2 -t root@1.1.1.1 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run over ssh with custom user with proxy" do
|
test "run over ssh with custom user with proxy" do
|
||||||
@app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config.tap { |c| c[:ssh] = { "user" => "app", "proxy" => "2.2.2.2" } })
|
@config[:ssh] = { "user" => "app", "proxy" => "2.2.2.2" }
|
||||||
assert_equal "ssh -J root@2.2.2.2 -t app@1.1.1.1 'ls'", @app.run_over_ssh("ls", host: "1.1.1.1")
|
assert_equal "ssh -J root@2.2.2.2 -t app@1.1.1.1 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
test "current_container_id" do
|
test "current_container_id" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --quiet --filter label=service=app",
|
"docker ps --quiet --filter label=service=app",
|
||||||
@app.current_container_id.join(" ")
|
new_command.current_container_id.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "current_container_id with destination" do
|
||||||
|
@destination = "staging"
|
||||||
|
assert_equal \
|
||||||
|
"docker ps --quiet --filter label=service=app --filter label=destination=staging",
|
||||||
|
new_command.current_container_id.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "container_id_for" do
|
test "container_id_for" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker container ls --all --filter name=app-999 --quiet",
|
"docker container ls --all --filter name=app-999 --quiet",
|
||||||
@app.container_id_for(container_name: "app-999").join(" ")
|
new_command.container_id_for(container_name: "app-999").join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "current_running_version" do
|
test "current_running_version" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps --filter label=service=app --format \"{{.Names}}\" | sed 's/-/\\n/g' | tail -n 1",
|
"docker ps --filter label=service=app --format \"{{.Names}}\" | sed 's/-/\\n/g' | tail -n 1",
|
||||||
@app.current_running_version.join(" ")
|
new_command.current_running_version.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "most_recent_version_from_available_images" do
|
test "most_recent_version_from_available_images" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker image ls --format \"{{.Tag}}\" dhh/app | head -n 1",
|
"docker image ls --format \"{{.Tag}}\" dhh/app | head -n 1",
|
||||||
@app.most_recent_version_from_available_images.join(" ")
|
new_command.most_recent_version_from_available_images.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "list_containers" do
|
||||||
|
assert_equal \
|
||||||
|
"docker container ls --all --filter label=service=app",
|
||||||
|
new_command.list_containers.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "list_containers with destination" do
|
||||||
|
@destination = "staging"
|
||||||
|
assert_equal \
|
||||||
|
"docker container ls --all --filter label=service=app --filter label=destination=staging",
|
||||||
|
new_command.list_containers.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "list_container_names" do
|
||||||
|
assert_equal \
|
||||||
|
"docker container ls --all --filter label=service=app --format '{{ .Names }}'",
|
||||||
|
new_command.list_container_names.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "remove_container" do
|
||||||
|
assert_equal \
|
||||||
|
"docker container ls --all --filter name=app-999 --quiet | xargs docker container rm",
|
||||||
|
new_command.remove_container(version: "999").join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "remove_container with destination" do
|
||||||
|
@destination = "staging"
|
||||||
|
assert_equal \
|
||||||
|
"docker container ls --all --filter name=app-staging-999 --quiet | xargs docker container rm",
|
||||||
|
new_command.remove_container(version: "999").join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "remove_containers" do
|
||||||
|
assert_equal \
|
||||||
|
"docker container prune --force --filter label=service=app",
|
||||||
|
new_command.remove_containers.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "remove_containers with destination" do
|
||||||
|
@destination = "staging"
|
||||||
|
assert_equal \
|
||||||
|
"docker container prune --force --filter label=service=app --filter label=destination=staging",
|
||||||
|
new_command.remove_containers.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "list_images" do
|
||||||
|
assert_equal \
|
||||||
|
"docker image ls dhh/app",
|
||||||
|
new_command.list_images.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "remove_images" do
|
||||||
|
assert_equal \
|
||||||
|
"docker image prune --all --force --filter label=service=app",
|
||||||
|
new_command.remove_images.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "remove_images with destination" do
|
||||||
|
@destination = "staging"
|
||||||
|
assert_equal \
|
||||||
|
"docker image prune --all --force --filter label=service=app --filter label=destination=staging",
|
||||||
|
new_command.remove_images.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def new_command
|
||||||
|
Mrsk::Commands::App.new(Mrsk::Configuration.new(@config, destination: @destination, version: "999"))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,6 +19,39 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
|||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "run with ports configured" do
|
||||||
|
assert_equal \
|
||||||
|
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
|
||||||
|
@config[:traefik]["options"] = {"publish" => %w[9000:9000 9001:9001]}
|
||||||
|
assert_equal \
|
||||||
|
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --publish \"9000:9000\" --publish \"9001:9001\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "run with volumes configured" do
|
||||||
|
assert_equal \
|
||||||
|
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
|
||||||
|
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json] }
|
||||||
|
assert_equal \
|
||||||
|
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "run with several options configured" do
|
||||||
|
assert_equal \
|
||||||
|
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
|
||||||
|
@config[:traefik]["options"] = {"volume" => %w[./letsencrypt/acme.json:/letsencrypt/acme.json], "publish" => %w[8080:8080], "memory" => "512m"}
|
||||||
|
assert_equal \
|
||||||
|
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 80:80 --volume /var/run/docker.sock:/var/run/docker.sock --volume \"./letsencrypt/acme.json:/letsencrypt/acme.json\" --publish \"8080:8080\" --memory \"512m\" traefik --providers.docker --log.level=DEBUG --accesslog.format=\"json\" --api.insecure --metrics.prometheus.buckets=\"0.1,0.3,1.2,5.0\"",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
test "run without configuration" do
|
test "run without configuration" do
|
||||||
@config.delete(:traefik)
|
@config.delete(:traefik)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user