Merge branch 'main' into kumulus/docker-in-docker
This commit is contained in:
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -8,12 +8,15 @@ jobs:
|
|||||||
- "2.7"
|
- "2.7"
|
||||||
- "3.1"
|
- "3.1"
|
||||||
- "3.2"
|
- "3.2"
|
||||||
|
gemfile:
|
||||||
|
- Gemfile
|
||||||
|
- gemfiles/rails_edge.gemfile
|
||||||
continue-on-error: [false]
|
continue-on-error: [false]
|
||||||
|
|
||||||
name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }}
|
name: ${{ format('Tests (Ruby {0})', matrix.ruby-version) }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: ${{ matrix.continue-on-error }}
|
continue-on-error: ${{ matrix.continue-on-error }}
|
||||||
|
env:
|
||||||
|
BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/docker-publish.yml
vendored
6
.github/workflows/docker-publish.yml
vendored
@@ -16,10 +16,6 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
-
|
|
||||||
name: Extract Version Number
|
|
||||||
id: extract_version
|
|
||||||
run: echo "::set-output name=version::${{ github.ref | replace('refs/tags/', '') }}"
|
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
@@ -42,4 +38,4 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
ghcr.io/mrsked/mrsk:latest
|
ghcr.io/mrsked/mrsk:latest
|
||||||
ghcr.io/mrsked/mrsk:${{ steps.extract_version.outputs.version }}
|
ghcr.io/mrsked/mrsk:${{ github.ref_name }}
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
*.gem
|
*.gem
|
||||||
coverage/*
|
coverage/*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
gemfiles/*.lock
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# Use the official Ruby 3.2.0 Alpine image as the base image
|
# Use the official Ruby 3.2.0 Alpine image as the base image
|
||||||
FROM ruby:3.2.0-alpine
|
FROM ruby:3.2.0-alpine
|
||||||
|
|
||||||
|
# Install docker/buildx-bin
|
||||||
|
COPY --from=docker/buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx
|
||||||
|
|
||||||
# Set the working directory to /mrsk
|
# Set the working directory to /mrsk
|
||||||
WORKDIR /mrsk
|
WORKDIR /mrsk
|
||||||
|
|
||||||
|
|||||||
4
Gemfile
4
Gemfile
@@ -2,7 +2,3 @@ source 'https://rubygems.org'
|
|||||||
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||||
|
|
||||||
gemspec
|
gemspec
|
||||||
|
|
||||||
gem "debug"
|
|
||||||
gem "mocha"
|
|
||||||
gem "railties"
|
|
||||||
|
|||||||
24
README.md
24
README.md
@@ -4,6 +4,8 @@ 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
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Install MRSK globally with `gem install mrsk` or build a dockerized version:
|
Install MRSK globally with `gem install mrsk` or build a dockerized version:
|
||||||
@@ -79,6 +81,16 @@ Docker Swarm is much simpler than Kubernetes, but it's still built on the same d
|
|||||||
|
|
||||||
Ultimately, there are a myriad of ways to deploy web apps, but this is the toolkit we're using at [37signals](https://37signals.com) to bring [HEY](https://www.hey.com) [home from the cloud](https://world.hey.com/dhh/why-we-re-leaving-the-cloud-654b47e0) without losing the advantages of modern containerization tooling.
|
Ultimately, there are a myriad of ways to deploy web apps, but this is the toolkit we're using at [37signals](https://37signals.com) to bring [HEY](https://www.hey.com) [home from the cloud](https://world.hey.com/dhh/why-we-re-leaving-the-cloud-654b47e0) without losing the advantages of modern containerization tooling.
|
||||||
|
|
||||||
|
## Running MRSK from Docker
|
||||||
|
|
||||||
|
MRSK is packaged up in a Docker container similarly to [rails/docked](https://github.com/rails/docked). This will allow you to run MRSK (from your application directory) without having to install any dependencies other than Docker. Add the following alias to your profile configuration to make working with the container more convenient:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
alias mrsk="docker run -it --rm -v '${PWD}:/workdir' -v '${SSH_AUTH_SOCK}:/ssh-agent' -v /var/run/docker.sock:/var/run/docker.sock -e 'SSH_AUTH_SOCK=/ssh-agent' ghcr.io/mrsked/mrsk:latest"
|
||||||
|
```
|
||||||
|
|
||||||
|
Since MRSK uses SSH to establish a remote connection, it will need access to your SSH agent. The above command uses a volume mount to make it available inside the container and configures the SSH agent inside the container to make use of it.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
### Using .env file to load required environment variables
|
### Using .env file to load required environment variables
|
||||||
@@ -234,6 +246,12 @@ volumes:
|
|||||||
- "/local/path:/container/path"
|
- "/local/path:/container/path"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### MRSK env variables
|
||||||
|
|
||||||
|
The following env variables are set when your container runs:
|
||||||
|
|
||||||
|
`MRSK_CONTAINER_NAME` : this contains the current container name and version
|
||||||
|
|
||||||
### Using different roles for servers
|
### Using different roles for servers
|
||||||
|
|
||||||
If your application uses separate hosts for running jobs or other roles beyond the default web running, you can specify these hosts in a dedicated role with a new entrypoint command like so:
|
If your application uses separate hosts for running jobs or other roles beyond the default web running, you can specify these hosts in a dedicated role with a new entrypoint command like so:
|
||||||
@@ -268,12 +286,12 @@ servers:
|
|||||||
|
|
||||||
You can specialize the default Traefik rules by setting labels on the containers that are being started:
|
You can specialize the default Traefik rules by setting labels on the containers that are being started:
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
labels:
|
labels:
|
||||||
traefik.http.routers.hey.rule: Host(\`app.hey.com\`)
|
traefik.http.routers.hey.rule: Host(`app.hey.com`)
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: The escaped backticks are needed to ensure the rule is passed in correctly and not treated as command substitution by Bash!
|
Note: The backticks are needed to ensure the rule is passed in correctly and not treated as command substitution by Bash!
|
||||||
|
|
||||||
This allows you to run multiple applications on the same server sharing the same Traefik instance and port.
|
This allows you to run multiple applications on the same server sharing the same Traefik instance and port.
|
||||||
See https://doc.traefik.io/traefik/routing/routers/#rule for a full list of available routing rules.
|
See https://doc.traefik.io/traefik/routing/routers/#rule for a full list of available routing rules.
|
||||||
|
|||||||
9
gemfiles/rails_edge.gemfile
Normal file
9
gemfiles/rails_edge.gemfile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
source 'https://rubygems.org'
|
||||||
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||||
|
|
||||||
|
git "https://github.com/rails/rails.git" do
|
||||||
|
gem "railties"
|
||||||
|
gem "activesupport"
|
||||||
|
end
|
||||||
|
|
||||||
|
gemspec path: "../"
|
||||||
@@ -149,13 +149,13 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "remove [NAME]", "Remove accessory container and image from host (use NAME=all to remove all accessories)"
|
desc "remove [NAME]", "Remove accessory container, image and data directory from host (use NAME=all to remove all accessories)"
|
||||||
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
||||||
def remove(name)
|
def remove(name)
|
||||||
if name == "all"
|
if name == "all"
|
||||||
MRSK.accessory_names.each { |accessory_name| remove(accessory_name) }
|
MRSK.accessory_names.each { |accessory_name| remove(accessory_name) }
|
||||||
else
|
else
|
||||||
if options[:confirmed] || ask("This will remove all containers and images for #{name}. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
|
if options[:confirmed] || ask("This will remove all containers, images and data directories for #{name}. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
|
||||||
with_accessory(name) do
|
with_accessory(name) do
|
||||||
stop(name)
|
stop(name)
|
||||||
remove_container(name)
|
remove_container(name)
|
||||||
|
|||||||
@@ -9,42 +9,58 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
desc "deploy", "Deploy app to servers"
|
desc "deploy", "Deploy app to servers"
|
||||||
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
||||||
def deploy
|
def deploy
|
||||||
|
invoke_options = options.without(:skip_push)
|
||||||
|
|
||||||
runtime = print_runtime do
|
runtime = print_runtime do
|
||||||
say "Ensure curl and Docker are installed...", :magenta
|
say "Ensure curl and Docker are installed...", :magenta
|
||||||
invoke "mrsk:cli:server:bootstrap"
|
invoke "mrsk:cli:server:bootstrap", [], invoke_options
|
||||||
|
|
||||||
say "Log into image registry...", :magenta
|
say "Log into image registry...", :magenta
|
||||||
invoke "mrsk:cli:registry:login"
|
invoke "mrsk:cli:registry:login", [], invoke_options
|
||||||
|
|
||||||
|
if options[:skip_push]
|
||||||
|
say "Pull app image...", :magenta
|
||||||
|
invoke "mrsk:cli:build:pull", [], invoke_options
|
||||||
|
else
|
||||||
say "Build and push app image...", :magenta
|
say "Build and push app image...", :magenta
|
||||||
invoke "mrsk:cli:build:deliver"
|
invoke "mrsk:cli:build:deliver", [], invoke_options
|
||||||
|
end
|
||||||
|
|
||||||
say "Ensure Traefik is running...", :magenta
|
say "Ensure Traefik is running...", :magenta
|
||||||
invoke "mrsk:cli:traefik:boot"
|
invoke "mrsk:cli:traefik:boot", [], invoke_options
|
||||||
|
|
||||||
say "Ensure app can pass healthcheck...", :magenta
|
say "Ensure app can pass healthcheck...", :magenta
|
||||||
invoke "mrsk:cli:healthcheck:perform"
|
invoke "mrsk:cli:healthcheck:perform", [], invoke_options
|
||||||
|
|
||||||
invoke "mrsk:cli:app:boot"
|
invoke "mrsk:cli:app:boot", [], invoke_options
|
||||||
|
|
||||||
say "Prune old containers and images...", :magenta
|
say "Prune old containers and images...", :magenta
|
||||||
invoke "mrsk:cli:prune:all"
|
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 app in #{runtime.to_i} 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"
|
||||||
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
||||||
def redeploy
|
def redeploy
|
||||||
|
invoke_options = options.without(:skip_push)
|
||||||
|
|
||||||
runtime = print_runtime do
|
runtime = print_runtime do
|
||||||
|
if options[:skip_push]
|
||||||
|
say "Pull app image...", :magenta
|
||||||
|
invoke "mrsk:cli:build:pull", [], invoke_options
|
||||||
|
else
|
||||||
say "Build and push app image...", :magenta
|
say "Build and push app image...", :magenta
|
||||||
invoke "mrsk:cli:build:deliver"
|
invoke "mrsk:cli:build:deliver", [], invoke_options
|
||||||
|
end
|
||||||
|
|
||||||
say "Ensure app can pass healthcheck...", :magenta
|
say "Ensure app can pass healthcheck...", :magenta
|
||||||
invoke "mrsk:cli:healthcheck:perform"
|
invoke "mrsk:cli:healthcheck:perform", [], invoke_options
|
||||||
|
|
||||||
invoke "mrsk:cli:app:boot"
|
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 app in #{runtime.to_i} seconds" unless options[:skip_broadcast]
|
||||||
@@ -119,8 +135,10 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|||||||
puts "Binstub already exists in bin/mrsk (remove first to create a new one)"
|
puts "Binstub already exists in bin/mrsk (remove first to create a new one)"
|
||||||
else
|
else
|
||||||
puts "Adding MRSK to Gemfile and bundle..."
|
puts "Adding MRSK to Gemfile and bundle..."
|
||||||
`bundle add mrsk`
|
run_locally do
|
||||||
`bundle binstubs mrsk`
|
execute :bundle, :add, :mrsk
|
||||||
|
execute :bundle, :binstubs, :mrsk
|
||||||
|
end
|
||||||
puts "Created binstub file in bin/mrsk"
|
puts "Created binstub file in bin/mrsk"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|||||||
"--restart unless-stopped",
|
"--restart unless-stopped",
|
||||||
"--log-opt", "max-size=#{MAX_LOG_SIZE}",
|
"--log-opt", "max-size=#{MAX_LOG_SIZE}",
|
||||||
"--name", service_with_version_and_destination,
|
"--name", service_with_version_and_destination,
|
||||||
|
"-e", "MRSK_CONTAINER_NAME=\"#{service_with_version_and_destination}\"",
|
||||||
*role.env_args,
|
*role.env_args,
|
||||||
*config.volume_args,
|
*config.volume_args,
|
||||||
*role.label_args,
|
*role.label_args,
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ class Mrsk::Commands::Healthcheck < Mrsk::Commands::Base
|
|||||||
"--name", container_name_with_version,
|
"--name", container_name_with_version,
|
||||||
"--publish", "#{EXPOSED_PORT}:#{config.healthcheck["port"]}",
|
"--publish", "#{EXPOSED_PORT}:#{config.healthcheck["port"]}",
|
||||||
"--label", "service=#{container_name}",
|
"--label", "service=#{container_name}",
|
||||||
|
"-e", "MRSK_CONTAINER_NAME=\"#{container_name}\"",
|
||||||
*web.env_args,
|
*web.env_args,
|
||||||
*config.volume_args,
|
*config.volume_args,
|
||||||
|
*web.option_args,
|
||||||
config.absolute_image,
|
config.absolute_image,
|
||||||
web.cmd
|
web.cmd
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -55,14 +55,14 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|||||||
|
|
||||||
private
|
private
|
||||||
def cmd_option_args
|
def cmd_option_args
|
||||||
if args = config.raw_config.dig(:traefik, "args")
|
if args = config.traefik["args"]
|
||||||
optionize args
|
optionize args, with: "="
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def host_port
|
def host_port
|
||||||
config.raw_config.dig(:traefik, "host_port") || CONTAINER_PORT
|
config.traefik["host_port"] || CONTAINER_PORT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -165,6 +165,9 @@ class Mrsk::Configuration
|
|||||||
}.compact
|
}.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def traefik
|
||||||
|
raw_config.traefik || {}
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Will raise ArgumentError if any required config keys are missing
|
# Will raise ArgumentError if any required config keys are missing
|
||||||
|
|||||||
@@ -73,8 +73,9 @@ class Mrsk::Configuration::Role
|
|||||||
"traefik.http.routers.#{config.service}.rule" => "PathPrefix(`/`)",
|
"traefik.http.routers.#{config.service}.rule" => "PathPrefix(`/`)",
|
||||||
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
|
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
|
||||||
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
|
"traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
|
||||||
"traefik.http.middlewares.#{config.service}.retry.attempts" => "5",
|
"traefik.http.middlewares.#{config.service}-retry.retry.attempts" => "5",
|
||||||
"traefik.http.middlewares.#{config.service}.retry.initialinterval" => "500ms"
|
"traefik.http.middlewares.#{config.service}-retry.retry.initialinterval" => "500ms",
|
||||||
|
"traefik.http.routers.#{config.service}.middlewares" => "#{config.service}-retry@docker"
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{}
|
{}
|
||||||
|
|||||||
@@ -24,8 +24,14 @@ module Mrsk::Utils
|
|||||||
end
|
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)
|
def optionize(args, with: nil)
|
||||||
args.collect { |(key, value)| [ "--#{key}", value == true ? nil : escape_shell_value(value) ] }.flatten.compact
|
options = if with
|
||||||
|
args.collect { |(key, value)| value == true ? "--#{key}" : "--#{key}#{with}#{escape_shell_value(value)}" }
|
||||||
|
else
|
||||||
|
args.collect { |(key, value)| [ "--#{key}", value == true ? nil : escape_shell_value(value) ] }
|
||||||
|
end
|
||||||
|
|
||||||
|
options.flatten.compact
|
||||||
end
|
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
|
||||||
|
|||||||
@@ -19,4 +19,8 @@ Gem::Specification.new do |spec|
|
|||||||
spec.add_dependency "zeitwerk", "~> 2.5"
|
spec.add_dependency "zeitwerk", "~> 2.5"
|
||||||
spec.add_dependency "ed25519", "~> 1.2"
|
spec.add_dependency "ed25519", "~> 1.2"
|
||||||
spec.add_dependency "bcrypt_pbkdf", "~> 1.0"
|
spec.add_dependency "bcrypt_pbkdf", "~> 1.0"
|
||||||
|
|
||||||
|
spec.add_development_dependency "debug"
|
||||||
|
spec.add_development_dependency "mocha"
|
||||||
|
spec.add_development_dependency "railties"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,9 +1,80 @@
|
|||||||
require_relative "cli_test_case"
|
require_relative "cli_test_case"
|
||||||
|
|
||||||
class CliMainTest < CliTestCase
|
class CliMainTest < CliTestCase
|
||||||
test "version" do
|
test "setup" do
|
||||||
version = stdouted { Mrsk::Cli::Main.new.version }
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:server:bootstrap")
|
||||||
assert_equal Mrsk::VERSION, version
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:accessory:boot", [ "all" ])
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:deploy)
|
||||||
|
|
||||||
|
run_command("setup")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deploy" do
|
||||||
|
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_broadcast" => false, "skip_push" => false }
|
||||||
|
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:server:bootstrap", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:registry:login", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:deliver", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:traefik:boot", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:healthcheck:perform", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:boot", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:prune:all", [], invoke_options)
|
||||||
|
|
||||||
|
run_command("deploy").tap do |output|
|
||||||
|
assert_match /Ensure curl and Docker are installed/, output
|
||||||
|
assert_match /Log into image registry/, output
|
||||||
|
assert_match /Build and push app image/, output
|
||||||
|
assert_match /Ensure Traefik is running/, output
|
||||||
|
assert_match /Ensure app can pass healthcheck/, output
|
||||||
|
assert_match /Prune old containers and images/, output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "deploy with skip_push" do
|
||||||
|
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_broadcast" => false, "skip_push" => true }
|
||||||
|
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:server:bootstrap", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:registry:login", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:pull", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:traefik:boot", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:healthcheck:perform", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:boot", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:prune:all", [], invoke_options)
|
||||||
|
|
||||||
|
run_command("deploy", "--skip_push").tap do |output|
|
||||||
|
assert_match /Ensure curl and Docker are installed/, output
|
||||||
|
assert_match /Log into image registry/, output
|
||||||
|
assert_match /Pull app image/, output
|
||||||
|
assert_match /Ensure Traefik is running/, output
|
||||||
|
assert_match /Ensure app can pass healthcheck/, output
|
||||||
|
assert_match /Prune old containers and images/, output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "redeploy" do
|
||||||
|
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_broadcast" => false, "skip_push" => false}
|
||||||
|
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:deliver", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:healthcheck:perform", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:boot", [], invoke_options)
|
||||||
|
|
||||||
|
run_command("redeploy").tap do |output|
|
||||||
|
assert_match /Build and push app image/, output
|
||||||
|
assert_match /Ensure app can pass healthcheck/, output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "redeploy with skip_push" do
|
||||||
|
invoke_options = { "config_file" => "test/fixtures/deploy_with_accessories.yml", "skip_broadcast" => false, "skip_push" => true }
|
||||||
|
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:build:pull", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:healthcheck:perform", [], invoke_options)
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:boot", [], invoke_options)
|
||||||
|
|
||||||
|
run_command("redeploy", "--skip_push").tap do |output|
|
||||||
|
assert_match /Pull app image/, output
|
||||||
|
assert_match /Ensure app can pass healthcheck/, output
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "rollback bad version" do
|
test "rollback bad version" do
|
||||||
@@ -25,6 +96,95 @@ class CliMainTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "details" do
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:traefik:details")
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:app:details")
|
||||||
|
Mrsk::Cli::Main.any_instance.expects(:invoke).with("mrsk:cli:accessory:details", [ "all" ])
|
||||||
|
|
||||||
|
run_command("details")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "audit" do
|
||||||
|
run_command("audit").tap do |output|
|
||||||
|
assert_match /tail -n 50 mrsk-app-audit.log on 1.1.1.1/, output
|
||||||
|
assert_match /App Host: 1.1.1.1/, output
|
||||||
|
assert_match /tail -n 50 mrsk-app-audit.log on 1.1.1.2/, output
|
||||||
|
assert_match /App Host: 1.1.1.2/, output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "config" do
|
||||||
|
run_command("config").tap do |output|
|
||||||
|
config = YAML.load(output)
|
||||||
|
|
||||||
|
assert_equal ["web"], config[:roles]
|
||||||
|
assert_equal ["1.1.1.1", "1.1.1.2"], config[:hosts]
|
||||||
|
assert_equal "999", config[:version]
|
||||||
|
assert_equal "dhh/app", config[:repository]
|
||||||
|
assert_equal "dhh/app:999", config[:absolute_image]
|
||||||
|
assert_equal "app-999", config[:service_with_version]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "init" do
|
||||||
|
Pathname.any_instance.expects(:exist?).returns(false).twice
|
||||||
|
FileUtils.stubs(:mkdir_p)
|
||||||
|
FileUtils.stubs(:cp_r)
|
||||||
|
|
||||||
|
run_command("init").tap do |output|
|
||||||
|
assert_match /Created configuration file in config\/deploy.yml/, output
|
||||||
|
assert_match /Created \.env file/, output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "init with existing config" do
|
||||||
|
Pathname.any_instance.expects(:exist?).returns(true).twice
|
||||||
|
|
||||||
|
run_command("init").tap do |output|
|
||||||
|
assert_match /Config file already exists in config\/deploy.yml \(remove first to create a new one\)/, output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "init with bundle option" do
|
||||||
|
Pathname.any_instance.expects(:exist?).returns(false).times(3)
|
||||||
|
FileUtils.stubs(:mkdir_p)
|
||||||
|
FileUtils.stubs(:cp_r)
|
||||||
|
|
||||||
|
run_command("init", "--bundle").tap do |output|
|
||||||
|
assert_match /Created configuration file in config\/deploy.yml/, output
|
||||||
|
assert_match /Created \.env file/, output
|
||||||
|
assert_match /Adding MRSK to Gemfile and bundle/, output
|
||||||
|
assert_match /bundle add mrsk/, output
|
||||||
|
assert_match /bundle binstubs mrsk/, output
|
||||||
|
assert_match /Created binstub file in bin\/mrsk/, output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "init with bundle option and existing binstub" do
|
||||||
|
Pathname.any_instance.expects(:exist?).returns(true).times(3)
|
||||||
|
FileUtils.stubs(:mkdir_p)
|
||||||
|
FileUtils.stubs(:cp_r)
|
||||||
|
|
||||||
|
run_command("init", "--bundle").tap do |output|
|
||||||
|
assert_match /Config file already exists in config\/deploy.yml \(remove first to create a new one\)/, output
|
||||||
|
assert_match /Binstub already exists in bin\/mrsk \(remove first to create a new one\)/, output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "envify" do
|
||||||
|
File.expects(:read).with(".env.erb").returns("HELLO=<%= 'world' %>")
|
||||||
|
File.expects(:write).with(".env", "HELLO=world", perm: 0600)
|
||||||
|
|
||||||
|
run_command("envify")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "envify with destination" do
|
||||||
|
File.expects(:read).with(".env.staging.erb").returns("HELLO=<%= 'world' %>")
|
||||||
|
File.expects(:write).with(".env.staging", "HELLO=world", perm: 0600)
|
||||||
|
|
||||||
|
run_command("envify", "-d", "staging")
|
||||||
|
end
|
||||||
|
|
||||||
test "remove with confirmation" do
|
test "remove with confirmation" do
|
||||||
run_command("remove", "-y").tap do |output|
|
run_command("remove", "-y").tap do |output|
|
||||||
assert_match /docker container stop traefik/, output
|
assert_match /docker container stop traefik/, output
|
||||||
@@ -49,6 +209,11 @@ class CliMainTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "version" do
|
||||||
|
version = stdouted { Mrsk::Cli::Main.new.version }
|
||||||
|
assert_equal Mrsk::VERSION, version
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command)
|
def run_command(*command)
|
||||||
stdouted { Mrsk::Cli::Main.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
stdouted { Mrsk::Cli::Main.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
||||||
|
|||||||
@@ -14,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 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.attempts=\"5\" --label traefik.http.middlewares.app.retry.initialinterval=\"500ms\" 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(" ")
|
@app.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@config[:volumes] = ["/local/path:/container/path" ]
|
@config[:volumes] = ["/local/path:/container/path" ]
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --log-opt max-size=10m --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.attempts=\"5\" --label traefik.http.middlewares.app.retry.initialinterval=\"500ms\" 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(" ")
|
@app.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@config[:healthcheck] = { "path" => "/healthz" }
|
@config[:healthcheck] = { "path" => "/healthz" }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --restart unless-stopped --log-opt max-size=10m --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.attempts=\"5\" --label traefik.http.middlewares.app.retry.initialinterval=\"500ms\" 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(" ")
|
@app.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
@app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config).tap { |c| c.version = "999" }
|
@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 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(" ")
|
@app.run(role: :jobs).join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class CommandsHealthcheckTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "run" do
|
test "run" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --name healthcheck-app-123 --publish 3999:3000 --label service=healthcheck-app dhh/app:123",
|
"docker run --detach --name healthcheck-app-123 --publish 3999:3000 --label service=healthcheck-app -e MRSK_CONTAINER_NAME=\"healthcheck-app\" dhh/app:123",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ class CommandsHealthcheckTest < ActiveSupport::TestCase
|
|||||||
@config[:healthcheck] = { "port" => 3001 }
|
@config[:healthcheck] = { "port" => 3001 }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --name healthcheck-app-123 --publish 3999:3001 --label service=healthcheck-app dhh/app:123",
|
"docker run --detach --name healthcheck-app-123 --publish 3999:3001 --label service=healthcheck-app -e MRSK_CONTAINER_NAME=\"healthcheck-app\" dhh/app:123",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -26,7 +26,14 @@ class CommandsHealthcheckTest < ActiveSupport::TestCase
|
|||||||
@destination = "staging"
|
@destination = "staging"
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --detach --name healthcheck-app-staging-123 --publish 3999:3000 --label service=healthcheck-app-staging dhh/app:123",
|
"docker run --detach --name healthcheck-app-staging-123 --publish 3999:3000 --label service=healthcheck-app-staging -e MRSK_CONTAINER_NAME=\"healthcheck-app-staging\" dhh/app:123",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "run with custom options" do
|
||||||
|
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere" } } }
|
||||||
|
assert_equal \
|
||||||
|
"docker run --detach --name healthcheck-app-123 --publish 3999:3000 --label service=healthcheck-app -e MRSK_CONTAINER_NAME=\"healthcheck-app\" --mount \"somewhere\" dhh/app:123",
|
||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ class CommandsTraefikTest < ActiveSupport::TestCase
|
|||||||
setup do
|
setup do
|
||||||
@config = {
|
@config = {
|
||||||
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
|
||||||
traefik: { "args" => { "accesslog.format" => "json", "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
traefik: { "args" => { "accesslog.format" => "json", "api.insecure" => true, "metrics.prometheus.buckets" => "0.1,0.3,1.2,5.0" } }
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "run" do
|
test "run" do
|
||||||
assert_equal \
|
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\" --metrics.prometheus.buckets \"0.1,0.3,1.2,5.0\"",
|
"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(" ")
|
new_command.run.join(" ")
|
||||||
|
|
||||||
@config[:traefik]["host_port"] = "8080"
|
@config[:traefik]["host_port"] = "8080"
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 8080:80 --volume /var/run/docker.sock:/var/run/docker.sock traefik --providers.docker --log.level=DEBUG --accesslog.format \"json\" --metrics.prometheus.buckets \"0.1,0.3,1.2,5.0\"",
|
"docker run --name traefik --detach --restart unless-stopped --log-opt max-size=10m --publish 8080: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(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "special label args for web" do
|
test "special label args for web" do
|
||||||
assert_equal [ "--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.attempts=\"5\"", "--label", "traefik.http.middlewares.app.retry.initialinterval=\"500ms\""], @config.role(:web).label_args
|
assert_equal [ "--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\"" ], @config.role(:web).label_args
|
||||||
end
|
end
|
||||||
|
|
||||||
test "custom labels" do
|
test "custom labels" do
|
||||||
@@ -66,7 +66,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
|||||||
c[:servers]["beta"] = { "traefik" => "true", "hosts" => [ "1.1.1.5" ] }
|
c[:servers]["beta"] = { "traefik" => "true", "hosts" => [ "1.1.1.5" ] }
|
||||||
})
|
})
|
||||||
|
|
||||||
assert_equal [ "--label", "service=\"app\"", "--label", "role=\"beta\"", "--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.attempts=\"5\"", "--label", "traefik.http.middlewares.app.retry.initialinterval=\"500ms\"" ], config.role(:beta).label_args
|
assert_equal [ "--label", "service=\"app\"", "--label", "role=\"beta\"", "--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\"" ], config.role(:beta).label_args
|
||||||
end
|
end
|
||||||
|
|
||||||
test "env overwritten by role" do
|
test "env overwritten by role" do
|
||||||
|
|||||||
@@ -9,7 +9,18 @@ require "mrsk"
|
|||||||
|
|
||||||
ActiveSupport::LogSubscriber.logger = ActiveSupport::Logger.new(STDOUT) if ENV["VERBOSE"]
|
ActiveSupport::LogSubscriber.logger = ActiveSupport::Logger.new(STDOUT) if ENV["VERBOSE"]
|
||||||
|
|
||||||
|
# Applies to remote commands only.
|
||||||
SSHKit.config.backend = SSHKit::Backend::Printer
|
SSHKit.config.backend = SSHKit::Backend::Printer
|
||||||
|
|
||||||
|
# Ensure local commands use the printer backend too.
|
||||||
|
# See https://github.com/capistrano/sshkit/blob/master/lib/sshkit/dsl.rb#L9
|
||||||
|
module SSHKit
|
||||||
|
module DSL
|
||||||
|
def run_locally(&block)
|
||||||
|
SSHKit::Backend::Printer.new(SSHKit::Host.new(:local), &block).run
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class ActiveSupport::TestCase
|
class ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user