Compare commits

..

29 Commits

Author SHA1 Message Date
David Heinemeier Hansson
2e02b1ed14 Bump version for 0.6.1 2023-02-07 15:05:52 +01:00
David Heinemeier Hansson
fdd98622ac Fix issue with removing containers triggering twice, then ensure app stop runs closer to app run on each host 2023-02-07 15:05:06 +01:00
David Heinemeier Hansson
9aa57dd0c7 Bump version for 0.6.0 2023-02-05 17:53:43 +01:00
David Heinemeier Hansson
cb9f57356e Load destination ENV file also 2023-02-05 17:52:57 +01:00
David Heinemeier Hansson
02a5726072 Allow destination specific envifying 2023-02-05 16:35:37 +01:00
David Heinemeier Hansson
e865e823d5 Add envify for managing .env file 2023-02-05 16:30:56 +01:00
David Heinemeier Hansson
10cad5c459 Create binstub without bundler, document it all agnostically
You can use MRSK with something other than Rails.
2023-02-05 16:23:34 +01:00
David Heinemeier Hansson
ebcb297582 Merge pull request #24 from chrisdebruin/allow-bastion-server
Allow use of bastion host
2023-02-04 15:44:30 +01:00
David Heinemeier Hansson
0a293ae4d6 Fix and expand testing 2023-02-04 15:43:45 +01:00
Chris de Bruin
bdff11e1fc Allow use of bastion host 2023-02-04 15:38:05 +01:00
David Heinemeier Hansson
9cfb6fb0a9 Merge issue 2023-02-04 15:34:48 +01:00
David Heinemeier Hansson
9ec6f9d74f Merge branch 'main' into allow-bastion-server 2023-02-04 15:33:25 +01:00
David Heinemeier Hansson
45207f0c4f Explain the dance 2023-02-04 15:27:41 +01:00
David Heinemeier Hansson
cf9a402ad8 Stop treating RAILS_MASTER_KEY as special 2023-02-04 15:26:59 +01:00
David Heinemeier Hansson
64a5a790a7 Ensure secret can be used alone 2023-02-04 15:26:43 +01:00
David Heinemeier Hansson
78d4e1e1e9 Easier to read 2023-02-04 15:12:06 +01:00
David Heinemeier Hansson
74c7a6d5de Expand app command testing 2023-02-04 10:31:04 +01:00
David Heinemeier Hansson
340929e7e7 Use a version 2023-02-04 10:20:51 +01:00
David Heinemeier Hansson
6f1a3f5524 Don't need this, just use containers 2023-02-04 10:16:24 +01:00
David Heinemeier Hansson
7077da5a64 Spacing 2023-02-04 10:15:43 +01:00
David Heinemeier Hansson
77c63dcd04 Style 2023-02-04 10:14:35 +01:00
David Heinemeier Hansson
e7ac73be5a Join in run_over_ssh instead of all over 2023-02-04 10:14:31 +01:00
David Heinemeier Hansson
dfca9d8c48 Merge branch 'main' into allow-bastion-server 2023-02-04 10:06:15 +01:00
David Heinemeier Hansson
6032d5651a Merge pull request #35 from rails/zeitwerk
Load with Zeitwerk
2023-02-04 10:05:43 +01:00
Xavier Noria
539752e9bd Load with Zeitwerk 2023-02-03 22:45:12 +01:00
David Heinemeier Hansson
94b28a1b29 Extract method 2023-02-03 20:53:33 +01:00
Chris de Bruin
7d95472543 Added -J for ssh proxy 2023-02-03 14:31:09 +01:00
David Heinemeier Hansson
71681cb8be Use single string-based proxy declaration 2023-02-03 14:30:20 +01:00
Chris de Bruin
1fef6ba505 Allow use of bastion host 2023-02-03 14:30:20 +01:00
44 changed files with 278 additions and 191 deletions

View File

@@ -1,11 +1,12 @@
PATH PATH
remote: . remote: .
specs: specs:
mrsk (0.5.1) mrsk (0.6.1)
activesupport (>= 7.0) activesupport (>= 7.0)
dotenv (~> 2.8) dotenv (~> 2.8)
sshkit (~> 1.21) sshkit (~> 1.21)
thor (~> 1.2) thor (~> 1.2)
zeitwerk (~> 2.5)
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
@@ -91,6 +92,7 @@ PLATFORMS
arm64-darwin-22 arm64-darwin-22
x86_64-darwin-20 x86_64-darwin-20
x86_64-darwin-21 x86_64-darwin-21
x86_64-darwin-22
x86_64-linux x86_64-linux
DEPENDENCIES DEPENDENCIES

View File

@@ -1,10 +1,10 @@
# MRSK # MRSK
MRSK deploys Rails apps in containers to servers running Docker with zero downtime. It uses the dynamic reverse-proxy Traefik to hold requests while the new application container is started and the old one is stopped. It works seamlessly across multiple hosts, using SSHKit to execute commands. MRSK deploys web apps in containers to servers running Docker with zero downtime. It uses the dynamic reverse-proxy Traefik to hold requests while the new application container is started and the old one is stopped. It works seamlessly across multiple hosts, using SSHKit to execute commands.
## Installation ## Installation
Install MRSK globally with `gem install mrsk`. Then, inside your app directory, run `mrsk install`. Now edit the new file `config/deploy.yml`. It could look as simple as this: Install MRSK globally with `gem install mrsk`. Then, inside your app directory, run `mrsk init` (or `mrsk init --bundle` within Rails apps where you want a bin/mrsk binstub). Now edit the new file `config/deploy.yml`. It could look as simple as this:
```yaml ```yaml
service: hey service: hey
@@ -15,12 +15,17 @@ servers:
registry: registry:
username: registry-user-name username: registry-user-name
password: <%= ENV.fetch("MRSK_REGISTRY_PASSWORD") %> password: <%= ENV.fetch("MRSK_REGISTRY_PASSWORD") %>
env:
secret:
- RAILS_MASTER_KEY
``` ```
Now you're ready to deploy a multi-arch image to the servers: Then edit your `.env` file to add your registry password as `MRSK_REGISTRY_PASSWORD` (and your `RAILS_MASTER_KEY` for production with a Rails app).
Now you're ready to deploy to the servers:
``` ```
MRSK_REGISTRY_PASSWORD=pw mrsk deploy mrsk deploy
``` ```
This will: This will:
@@ -68,10 +73,27 @@ registry:
### Using a different SSH user than root ### Using a different SSH user than root
The default SSH user is root, but you can change it using `ssh_user`: The default SSH user is root, but you can change it using `ssh/user`:
```yaml ```yaml
ssh_user: app ssh:
user: app
```
### Using a proxy SSH host
If you need to connect to server through a proxy host, you can use `ssh/proxy`:
```yaml
ssh:
proxy: "192.168.0.1" # defaults to root as the user
```
Or with specific user:
```yaml
ssh:
proxy: "app@192.168.0.1"
``` ```
### Using env variables ### Using env variables
@@ -265,14 +287,6 @@ ARG RUBY_VERSION
FROM ruby:$RUBY_VERSION-slim as base FROM ruby:$RUBY_VERSION-slim as base
``` ```
### Using without RAILS_MASTER_KEY
If you're using MRSK with older Rails apps that predate RAILS_MASTER_KEY, or with a non-Rails app, you can skip the default usage and reference:
```yaml
skip_master_key: true
```
### 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. The services will build off public images, and will not be automatically updated when you deploy:
@@ -300,6 +314,23 @@ accessories:
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.
### Using a generated .env file
If you're using a centralized secret store, like 1Password, you can create `.env.erb` as a template which looks up the secrets. Example of a .env.erb file:
```erb
<% if (session_token = `op signin --account my-one-password-account --raw`.strip) != "" %># Generated by mrsk envify
GITHUB_TOKEN=<%= `gh config get -h github.com oauth_token`.strip %>
MRSK_REGISTRY_PASSWORD=<%= `op read "op://Vault/Docker Hub/password" -n --session #{session_token}` %>
RAILS_MASTER_KEY=<%= `op read "op://Vault/My App/RAILS_MASTER_SECRET" -n --session #{session_token}` %>
MYSQL_ROOT_PASSWORD=<%= `op read "op://Vault/My App/MYSQL_ROOT_PASSWORD" -n --session #{session_token}` %>
<% else raise ArgumentError, "Session token missing" end %>
```
This template can safely be checked into git. Then everyone deploying the app can run `mrsk envify` when they setup the app for the first time or passwords change to get the correct `.env` file.
If you need separate env variables for different destinations, you can set them with `.env.destination.erb` for the template, which will generate `.env.staging` when run with `mrsk envify -d staging`.
## Commands ## Commands
### Running commands on servers ### Running commands on servers

View File

@@ -3,8 +3,7 @@
# Prevent failures from being reported twice. # Prevent failures from being reported twice.
Thread.report_on_exception = false Thread.report_on_exception = false
require "dotenv/load" require "mrsk"
require "mrsk/cli"
begin begin
Mrsk::Cli::Main.start(ARGV) Mrsk::Cli::Main.start(ARGV)

View File

@@ -1,5 +1,9 @@
module Mrsk module Mrsk
end end
require "mrsk/version" require "zeitwerk"
require "mrsk/commander"
loader = Zeitwerk::Loader.for_gem
loader.ignore("#{__dir__}/mrsk/sshkit_with_ext.rb")
loader.setup
loader.eager_load # We need all commands loaded.

View File

@@ -1,9 +1,5 @@
require "mrsk"
module Mrsk::Cli module Mrsk::Cli
end end
# SSHKit uses instance eval, so we need a global const for ergonomics # SSHKit uses instance eval, so we need a global const for ergonomics
MRSK = Mrsk::Commander.new MRSK = Mrsk::Commander.new
require "mrsk/cli/main"

View File

@@ -1,5 +1,3 @@
require "mrsk/cli/base"
class Mrsk::Cli::Accessory < Mrsk::Cli::Base class Mrsk::Cli::Accessory < Mrsk::Cli::Base
desc "boot [NAME]", "Boot accessory service on host (use NAME=all to boot all accessories)" desc "boot [NAME]", "Boot accessory service on host (use NAME=all to boot all accessories)"
def boot(name) def boot(name)
@@ -9,6 +7,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
with_accessory(name) do |accessory| with_accessory(name) do |accessory|
directories(name) directories(name)
upload(name) upload(name)
on(accessory.host) do on(accessory.host) do
execute *MRSK.auditor.record("accessory #{name} boot"), verbosity: :debug execute *MRSK.auditor.record("accessory #{name} boot"), verbosity: :debug
execute *accessory.run execute *accessory.run

View File

@@ -1,13 +1,6 @@
require "mrsk/cli/base"
class Mrsk::Cli::App < Mrsk::Cli::Base class Mrsk::Cli::App < Mrsk::Cli::Base
desc "boot", "Boot app on servers (or reboot app if already running)" desc "boot", "Boot app on servers (or reboot app if already running)"
def boot def boot
cli = self
say "Ensure no other version of the app is running...", :magenta
stop
say "Get most recent version available as an image...", :magenta unless options[:version] say "Get most recent version available as an image...", :magenta unless options[:version]
using_version(options[:version] || most_recent_version_available) do |version| using_version(options[:version] || most_recent_version_available) do |version|
say "Start container with version #{version} (or reboot if already running)...", :magenta say "Start container with version #{version} (or reboot if already running)...", :magenta
@@ -17,12 +10,14 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
execute *MRSK.auditor.record("app boot version #{version}"), verbosity: :debug execute *MRSK.auditor.record("app boot version #{version}"), verbosity: :debug
begin begin
execute *MRSK.app.stop, raise_on_non_zero_exit: false
execute *MRSK.app.run(role: role.name) execute *MRSK.app.run(role: role.name)
rescue SSHKit::Command::Failed => e rescue SSHKit::Command::Failed => e
if e.message =~ /already in use/ if e.message =~ /already in use/
error "Rebooting container with same version already deployed on #{host}" error "Rebooting container with same version already deployed on #{host}"
execute *MRSK.auditor.record("app rebooted with version #{version}"), verbosity: :debug
cli.remove_container version execute *MRSK.app.remove_container(version: version)
execute *MRSK.app.run(role: role.name) execute *MRSK.app.run(role: role.name)
else else
raise raise
@@ -40,7 +35,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
execute *MRSK.app.start, raise_on_non_zero_exit: false execute *MRSK.app.start, raise_on_non_zero_exit: false
end end
end end
desc "stop", "Stop app on servers" desc "stop", "Stop app on servers"
def stop def stop
on(MRSK.hosts) do on(MRSK.hosts) do
@@ -48,12 +43,12 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
execute *MRSK.app.stop, raise_on_non_zero_exit: false execute *MRSK.app.stop, raise_on_non_zero_exit: false
end end
end end
desc "details", "Display details about app containers" desc "details", "Display details about app containers"
def details def details
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.info) } on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.info) }
end end
desc "exec [CMD]", "Execute a custom command on servers" desc "exec [CMD]", "Execute a custom command on servers"
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)" option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one" option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
@@ -106,11 +101,6 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_images) } on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_images) }
end end
desc "current", "Return the current running container ID"
def current
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_container_id) }
end
desc "logs", "Show lines from app on servers" desc "logs", "Show lines from app on servers"
option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)" option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server" option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"

View File

@@ -1,4 +1,5 @@
require "thor" require "thor"
require "dotenv"
require "mrsk/sshkit_with_ext" require "mrsk/sshkit_with_ext"
module Mrsk::Cli module Mrsk::Cli
@@ -21,10 +22,19 @@ module Mrsk::Cli
def initialize(*) def initialize(*)
super super
load_envs
initialize_commander(options) initialize_commander(options)
end end
private private
def load_envs
if destination = options[:destination]
Dotenv.load(".env.#{destination}", ".env")
else
Dotenv.load(".env")
end
end
def initialize_commander(options) def initialize_commander(options)
MRSK.tap do |commander| MRSK.tap do |commander|
commander.config_file = Pathname.new(File.expand_path(options[:config_file])) commander.config_file = Pathname.new(File.expand_path(options[:config_file]))

View File

@@ -1,5 +1,3 @@
require "mrsk/cli/base"
class Mrsk::Cli::Build < Mrsk::Cli::Base class Mrsk::Cli::Build < Mrsk::Cli::Base
desc "deliver", "Deliver a newly built app image to servers" desc "deliver", "Deliver a newly built app image to servers"
def deliver def deliver
@@ -11,7 +9,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
def push def push
cli = self cli = self
run_locally do run_locally do
begin begin
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push } MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
rescue SSHKit::Command::Failed => e rescue SSHKit::Command::Failed => e

View File

@@ -1,13 +1,3 @@
require "mrsk/cli/base"
require "mrsk/cli/accessory"
require "mrsk/cli/app"
require "mrsk/cli/build"
require "mrsk/cli/prune"
require "mrsk/cli/registry"
require "mrsk/cli/server"
require "mrsk/cli/traefik"
class Mrsk::Cli::Main < Mrsk::Cli::Base class Mrsk::Cli::Main < Mrsk::Cli::Base
desc "setup", "Setup all accessories and deploy the app to servers" desc "setup", "Setup all accessories and deploy the app to servers"
def setup def setup
@@ -84,22 +74,29 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
end end
end end
desc "install", "Create config stub in config/deploy.yml and binstub in bin/mrsk" desc "init", "Create config stub in config/deploy.yml and env stub in .env"
option :skip_binstub, type: :boolean, default: false, desc: "Skip adding MRSK to the Gemfile and creating bin/mrsk binstub" option :bundle, type: :boolean, default: false, desc: "Add MRSK to the Gemfile and create a bin/mrsk binstub"
def install def init
require "fileutils" require "fileutils"
if (deploy_file = Pathname.new(File.expand_path("config/deploy.yml"))).exist? if (deploy_file = Pathname.new(File.expand_path("config/deploy.yml"))).exist?
puts "Config file already exists in config/deploy.yml (remove first to create a new one)" puts "Config file already exists in config/deploy.yml (remove first to create a new one)"
else else
FileUtils.mkdir_p deploy_file.dirname
FileUtils.cp_r Pathname.new(File.expand_path("templates/deploy.yml", __dir__)), deploy_file FileUtils.cp_r Pathname.new(File.expand_path("templates/deploy.yml", __dir__)), deploy_file
puts "Created configuration file in config/deploy.yml" puts "Created configuration file in config/deploy.yml"
end end
unless options[:skip_binstub] unless (deploy_file = Pathname.new(File.expand_path(".env"))).exist?
FileUtils.cp_r Pathname.new(File.expand_path("templates/template.env", __dir__)), deploy_file
puts "Created .env file"
end
if options[:bundle]
if (binstub = Pathname.new(File.expand_path("bin/mrsk"))).exist? if (binstub = Pathname.new(File.expand_path("bin/mrsk"))).exist?
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..."
`bundle add mrsk` `bundle add mrsk`
`bundle binstubs mrsk` `bundle binstubs mrsk`
puts "Created binstub file in bin/mrsk" puts "Created binstub file in bin/mrsk"
@@ -107,6 +104,15 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
end end
end end
desc "envify", "Create .env by evaluating .env.erb (or .env.staging.erb -> .env.staging when using -d staging)"
def envify
if destination = options[:destination]
File.write(".env.#{destination}", ERB.new(IO.read(Pathname.new(File.expand_path(".env.#{destination}.erb")))).result)
else
File.write(".env", ERB.new(IO.read(Pathname.new(File.expand_path(".env.erb")))).result)
end
end
desc "remove", "Remove Traefik, app, and registry session from servers" desc "remove", "Remove Traefik, app, and registry session from servers"
def remove def remove
invoke "mrsk:cli:traefik:remove" invoke "mrsk:cli:traefik:remove"

View File

@@ -1,5 +1,3 @@
require "mrsk/cli/base"
class Mrsk::Cli::Prune < Mrsk::Cli::Base class Mrsk::Cli::Prune < Mrsk::Cli::Base
desc "all", "Prune unused images and stopped containers" desc "all", "Prune unused images and stopped containers"
def all def all

View File

@@ -1,5 +1,3 @@
require "mrsk/cli/base"
class Mrsk::Cli::Registry < Mrsk::Cli::Base class Mrsk::Cli::Registry < Mrsk::Cli::Base
desc "login", "Login to the registry locally and remotely" desc "login", "Login to the registry locally and remotely"
def login def login

View File

@@ -1,5 +1,3 @@
require "mrsk/cli/base"
class Mrsk::Cli::Server < Mrsk::Cli::Base class Mrsk::Cli::Server < Mrsk::Cli::Base
desc "bootstrap", "Ensure Docker is installed on the servers" desc "bootstrap", "Ensure Docker is installed on the servers"
def bootstrap def bootstrap

View File

@@ -0,0 +1,2 @@
MRSK_REGISTRY_PASSWORD=change-this
RAILS_MASTER_KEY=another-env

View File

@@ -1,5 +1,3 @@
require "mrsk/cli/base"
class Mrsk::Cli::Traefik < Mrsk::Cli::Base class Mrsk::Cli::Traefik < Mrsk::Cli::Base
desc "boot", "Boot Traefik on servers" desc "boot", "Boot Traefik on servers"
def boot def boot

View File

@@ -1,14 +1,5 @@
require "active_support/core_ext/enumerable" require "active_support/core_ext/enumerable"
require "mrsk/configuration"
require "mrsk/commands/accessory"
require "mrsk/commands/app"
require "mrsk/commands/auditor"
require "mrsk/commands/builder"
require "mrsk/commands/prune"
require "mrsk/commands/traefik"
require "mrsk/commands/registry"
class Mrsk::Commander class Mrsk::Commander
attr_accessor :config_file, :destination, :verbosity, :version attr_accessor :config_file, :destination, :verbosity, :version
@@ -83,7 +74,7 @@ class Mrsk::Commander
end end
def with_verbosity(level) def with_verbosity(level)
old_level = SSHKit.config.output_verbosity old_level = SSHKit.config.output_verbosity
SSHKit.config.output_verbosity = level SSHKit.config.output_verbosity = level
yield yield

View File

@@ -1,5 +1,3 @@
require "mrsk/commands/base"
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, :env_args, :volume_args, :label_args, to: :accessory_config
@@ -10,7 +8,7 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
end end
def run def run
docker :run, docker :run,
"--name", service_name, "--name", service_name,
"-d", "-d",
"--restart", "unless-stopped", "--restart", "unless-stopped",
@@ -41,10 +39,10 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
end end
def follow_logs(grep: nil) def follow_logs(grep: nil)
run_over_ssh pipe( run_over_ssh \
docker(:logs, service_name, "-t", "-n", "10", "-f", "2>&1"), pipe \
(%(grep "#{grep}") if grep) docker(:logs, service_name, "-t", "-n", "10", "-f", "2>&1"),
).join(" ") (%(grep "#{grep}") if grep)
end end
@@ -66,11 +64,11 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
end end
def execute_in_existing_container_over_ssh(*command) def execute_in_existing_container_over_ssh(*command)
run_over_ssh execute_in_existing_container(*command, interactive: true).join(" ") run_over_ssh execute_in_existing_container(*command, interactive: true)
end end
def execute_in_new_container_over_ssh(*command) def execute_in_new_container_over_ssh(*command)
run_over_ssh execute_in_new_container(*command, interactive: true).join(" ") run_over_ssh execute_in_new_container(*command, interactive: true)
end end
def run_over_ssh(command) def run_over_ssh(command)

View File

@@ -1,5 +1,3 @@
require "mrsk/commands/base"
class Mrsk::Commands::App < Mrsk::Commands::Base class Mrsk::Commands::App < Mrsk::Commands::Base
def run(role: :web) def run(role: :web)
role = config.role(role) role = config.role(role)
@@ -8,7 +6,6 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
"-d", "-d",
"--restart unless-stopped", "--restart unless-stopped",
"--name", service_with_version, "--name", service_with_version,
*rails_master_key_arg,
*role.env_args, *role.env_args,
*config.volume_args, *config.volume_args,
*role.label_args, *role.label_args,
@@ -37,11 +34,13 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
end end
def follow_logs(host:, grep: nil) def follow_logs(host:, grep: nil)
run_over_ssh pipe( run_over_ssh \
current_container_id, pipe(
"xargs docker logs -t -n 10 -f 2>&1", current_container_id,
(%(grep "#{grep}") if grep) "xargs docker logs -t -n 10 -f 2>&1",
).join(" "), host: host (%(grep "#{grep}") if grep)
),
host: host
end end
@@ -56,7 +55,6 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
docker :run, docker :run,
("-it" if interactive), ("-it" if interactive),
"--rm", "--rm",
*rails_master_key_arg,
*config.env_args, *config.env_args,
*config.volume_args, *config.volume_args,
config.absolute_image, config.absolute_image,
@@ -64,11 +62,11 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
end end
def execute_in_existing_container_over_ssh(*command, host:) def execute_in_existing_container_over_ssh(*command, host:)
run_over_ssh execute_in_existing_container(*command, interactive: true).join(" "), host: host run_over_ssh execute_in_existing_container(*command, interactive: true), host: host
end end
def execute_in_new_container_over_ssh(*command, host:) def execute_in_new_container_over_ssh(*command, host:)
run_over_ssh execute_in_new_container(*command, interactive: true).join(" "), host: host run_over_ssh execute_in_new_container(*command, interactive: true), host: host
end end
@@ -130,12 +128,4 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
def service_filter def service_filter
[ "--filter", "label=service=#{config.service}" ] [ "--filter", "label=service=#{config.service}" ]
end end
def rails_master_key_arg
if master_key = config.master_key
[ "-e", redact("RAILS_MASTER_KEY=#{master_key}") ]
else
[]
end
end
end end

View File

@@ -1,10 +1,9 @@
require "active_support/core_ext/time/conversions" require "active_support/core_ext/time/conversions"
require "mrsk/commands/base"
class Mrsk::Commands::Auditor < Mrsk::Commands::Base class Mrsk::Commands::Auditor < Mrsk::Commands::Base
def record(line) def record(line)
append \ append \
[ :echo, "'#{tags} #{line}'" ], [ :echo, tagged_line(line) ],
audit_log_file audit_log_file
end end
@@ -17,6 +16,10 @@ class Mrsk::Commands::Auditor < Mrsk::Commands::Base
"mrsk-#{config.service}-audit.log" "mrsk-#{config.service}-audit.log"
end end
def tagged_line(line)
"'#{tags} #{line}'"
end
def tags def tags
"[#{timestamp}] [#{performer}]" "[#{timestamp}] [#{performer}]"
end end

View File

@@ -8,8 +8,11 @@ module Mrsk::Commands
@config = config @config = config
end end
def run_over_ssh(command, host:) def run_over_ssh(*command, host:)
"ssh -t #{config.ssh_user}@#{host} '#{command}'" "ssh".tap do |cmd|
cmd << " -J #{config.ssh_proxy.jump_proxies}" if config.ssh_proxy
cmd << " -t #{config.ssh_user}@#{host} '#{command.join(" ")}'"
end
end end
private private

View File

@@ -1,5 +1,3 @@
require "mrsk/commands/base"
class Mrsk::Commands::Builder < Mrsk::Commands::Base class Mrsk::Commands::Builder < Mrsk::Commands::Base
delegate :create, :remove, :push, :pull, :info, to: :target delegate :create, :remove, :push, :pull, :info, to: :target
@@ -36,8 +34,3 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
@multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config) @multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config)
end end
end end
require "mrsk/commands/builder/native"
require "mrsk/commands/builder/native/remote"
require "mrsk/commands/builder/multiarch"
require "mrsk/commands/builder/multiarch/remote"

View File

@@ -1,5 +1,3 @@
require "mrsk/commands/base"
class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
delegate :argumentize, to: Mrsk::Utils delegate :argumentize, to: Mrsk::Utils

View File

@@ -1,5 +1,3 @@
require "mrsk/commands/builder/base"
class Mrsk::Commands::Builder::Multiarch < Mrsk::Commands::Builder::Base class Mrsk::Commands::Builder::Multiarch < Mrsk::Commands::Builder::Base
def create def create
docker :buildx, :create, "--use", "--name", builder_name docker :buildx, :create, "--use", "--name", builder_name

View File

@@ -1,5 +1,3 @@
require "mrsk/commands/builder/multiarch"
class Mrsk::Commands::Builder::Multiarch::Remote < Mrsk::Commands::Builder::Multiarch class Mrsk::Commands::Builder::Multiarch::Remote < Mrsk::Commands::Builder::Multiarch
def create def create
combine \ combine \

View File

@@ -1,5 +1,3 @@
require "mrsk/commands/builder/base"
class Mrsk::Commands::Builder::Native < Mrsk::Commands::Builder::Base class Mrsk::Commands::Builder::Native < Mrsk::Commands::Builder::Base
def create def create
# No-op on native # No-op on native

View File

@@ -1,5 +1,3 @@
require "mrsk/commands/builder/native"
class Mrsk::Commands::Builder::Native::Remote < Mrsk::Commands::Builder::Native class Mrsk::Commands::Builder::Native::Remote < Mrsk::Commands::Builder::Native
def create def create
chain \ chain \

View File

@@ -1,4 +1,3 @@
require "mrsk/commands/base"
require "active_support/duration" require "active_support/duration"
require "active_support/core_ext/numeric/time" require "active_support/core_ext/numeric/time"

View File

@@ -1,5 +1,3 @@
require "mrsk/commands/base"
class Mrsk::Commands::Registry < Mrsk::Commands::Base class Mrsk::Commands::Registry < Mrsk::Commands::Base
delegate :registry, to: :config delegate :registry, to: :config

View File

@@ -1,5 +1,3 @@
require "mrsk/commands/base"
class Mrsk::Commands::Traefik < Mrsk::Commands::Base class Mrsk::Commands::Traefik < Mrsk::Commands::Base
def run def run
docker :run, "--name traefik", docker :run, "--name traefik",

View File

@@ -3,7 +3,7 @@ require "active_support/core_ext/string/inquiry"
require "active_support/core_ext/module/delegation" require "active_support/core_ext/module/delegation"
require "pathname" require "pathname"
require "erb" require "erb"
require "mrsk/utils" require "net/ssh/proxy/jump"
class Mrsk::Configuration class Mrsk::Configuration
delegate :service, :image, :servers, :env, :labels, :registry, :builder, to: :raw_config, allow_nil: true delegate :service, :image, :servers, :env, :labels, :registry, :builder, to: :raw_config, allow_nil: true
@@ -104,17 +104,22 @@ class Mrsk::Configuration
end end
def ssh_user def ssh_user
raw_config.ssh_user || "root" if raw_config.ssh.present?
raw_config.ssh["user"] || "root"
else
"root"
end
end
def ssh_proxy
if raw_config.ssh.present? && raw_config.ssh["proxy"]
Net::SSH::Proxy::Jump.new \
raw_config.ssh["proxy"].include?("@") ? raw_config.ssh["proxy"] : "root@#{raw_config.ssh["proxy"]}"
end
end end
def ssh_options def ssh_options
{ user: ssh_user, auth_methods: [ "publickey" ] } { user: ssh_user, proxy: ssh_proxy, auth_methods: [ "publickey" ] }.compact
end
def master_key
unless raw_config.skip_master_key
ENV["RAILS_MASTER_KEY"] || File.read(Pathname.new(File.expand_path("config/master.key")))
end
end end
@@ -171,6 +176,3 @@ class Mrsk::Configuration
raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
end end
end end
require "mrsk/configuration/role"
require "mrsk/configuration/accessory"

View File

@@ -96,7 +96,12 @@ class Mrsk::Configuration::Role
def merged_env_with_secrets def merged_env_with_secrets
merged_env.tap do |new_env| merged_env.tap do |new_env|
new_env["secret"] = Array(config.env["secret"]) + Array(specialized_env["secret"]) new_env["secret"] = Array(config.env["secret"]) + Array(specialized_env["secret"])
new_env["clear"] = (Array(config.env["clear"] || config.env) + Array(specialized_env["clear"] || specialized_env)).uniq
# If there's no secret/clear split, everything is clear
clear_app_env = config.env["secret"] ? Array(config.env["clear"]) : Array(config.env["clear"] || config.env)
clear_role_env = specialized_env["secret"] ? Array(specialized_env["clear"]) : Array(specialized_env["clear"] || specialized_env)
new_env["clear"] = (clear_app_env + clear_role_env).uniq
end end
end end
end end

View File

@@ -1,3 +1,3 @@
module Mrsk module Mrsk
VERSION = "0.5.1" VERSION = "0.6.1"
end end

View File

@@ -16,4 +16,5 @@ Gem::Specification.new do |spec|
spec.add_dependency "sshkit", "~> 1.21" spec.add_dependency "sshkit", "~> 1.21"
spec.add_dependency "thor", "~> 1.2" spec.add_dependency "thor", "~> 1.2"
spec.add_dependency "dotenv", "~> 2.8" spec.add_dependency "dotenv", "~> 2.8"
spec.add_dependency "zeitwerk", "~> 2.5"
end end

View File

@@ -1,6 +1,5 @@
require "test_helper" require "test_helper"
require "active_support/testing/stream" require "active_support/testing/stream"
require "mrsk/cli"
class CliTestCase < ActiveSupport::TestCase class CliTestCase < ActiveSupport::TestCase
include ActiveSupport::Testing::Stream include ActiveSupport::Testing::Stream

View File

@@ -1,5 +1,4 @@
require "test_helper" require "test_helper"
require "mrsk/commander"
class CommanderTest < ActiveSupport::TestCase class CommanderTest < ActiveSupport::TestCase
setup do setup do

View File

@@ -1,10 +1,8 @@
require "test_helper" require "test_helper"
require "mrsk/configuration"
require "mrsk/commands/accessory"
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: { "username" => "dhh", "password" => "secret" },
servers: [ "1.1.1.1" ], servers: [ "1.1.1.1" ],
accessories: { accessories: {
@@ -83,14 +81,14 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
end end
test "execute in new container over ssh" do test "execute in new container over ssh" do
@mysql.stub(:run_over_ssh, ->(cmd) { cmd }) 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=% 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
test "execute in existing container over ssh" do test "execute in existing container over ssh" do
@mysql.stub(:run_over_ssh, ->(cmd) { cmd }) do @mysql.stub(:run_over_ssh, ->(cmd) { cmd.join(" ") }) do
assert_match %r|docker exec -it app-mysql mysql -u root|, assert_match %r|docker exec -it app-mysql mysql -u root|,
@mysql.execute_in_existing_container_over_ssh("mysql", "-u", "root") @mysql.execute_in_existing_container_over_ssh("mysql", "-u", "root")
end end

View File

@@ -1,63 +1,161 @@
require "test_helper" require "test_helper"
require "mrsk/configuration"
require "mrsk/commands/app"
class CommandsAppTest < ActiveSupport::TestCase class CommandsAppTest < ActiveSupport::TestCase
setup do setup do
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" ] } @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) @app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config).tap { |c| c.version = "999" }
end end
teardown do teardown do
ENV["RAILS_MASTER_KEY"] = nil ENV.delete("RAILS_MASTER_KEY")
end end
test "run" do test "run" do
assert_equal \ assert_equal \
[:docker, :run, "-d", "--restart unless-stopped", "--name", "app-missing", "-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=3", "--label", "traefik.http.middlewares.app.retry.initialinterval=500ms", "dhh/app:missing"], @app.run "docker run -d --restart unless-stopped --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=3 --label traefik.http.middlewares.app.retry.initialinterval=500ms dhh/app:999",
@app.run.join(" ")
end end
test "run with volumes" do test "run with volumes" do
@config[:volumes] = ["/local/path:/container/path" ] @config[:volumes] = ["/local/path:/container/path" ]
assert_equal \ assert_equal \
[:docker, :run, "-d", "--restart unless-stopped", "--name", "app-missing", "-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=3", "--label", "traefik.http.middlewares.app.retry.initialinterval=500ms", "dhh/app:missing"], @app.run "docker run -d --restart unless-stopped --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=3 --label traefik.http.middlewares.app.retry.initialinterval=500ms dhh/app:999",
@app.run.join(" ")
end
test "start" do
assert_equal \
"docker start app-999",
@app.start.join(" ")
end
test "stop" do
assert_equal \
"docker ps -q --filter label=service=app | xargs docker stop",
@app.stop.join(" ")
end
test "info" do
assert_equal \
"docker ps --filter label=service=app",
@app.info.join(" ")
end
test "logs" do
assert_equal \
"docker ps -q --filter label=service=app | xargs docker logs 2>&1",
@app.logs.join(" ")
assert_equal \
"docker ps -q --filter label=service=app | xargs docker logs --since 5m 2>&1",
@app.logs(since: "5m").join(" ")
assert_equal \
"docker ps -q --filter label=service=app | xargs docker logs -n 100 2>&1",
@app.logs(lines: "100").join(" ")
assert_equal \
"docker ps -q --filter label=service=app | xargs docker logs --since 5m -n 100 2>&1",
@app.logs(since: "5m", lines: "100").join(" ")
assert_equal \
"docker ps -q --filter label=service=app | xargs docker logs 2>&1 | grep 'my-id'",
@app.logs(grep: "my-id").join(" ")
assert_equal \
"docker ps -q --filter label=service=app | xargs docker logs --since 5m 2>&1 | grep 'my-id'",
@app.logs(since: "5m", grep: "my-id").join(" ")
end
test "follow logs" do
@app.stub(:run_over_ssh, ->(cmd, host:) { cmd.join(" ") }) do
assert_equal \
"docker ps -q --filter label=service=app | xargs docker logs -t -n 10 -f 2>&1",
@app.follow_logs(host: "app-1")
assert_equal \
"docker ps -q --filter label=service=app | xargs docker logs -t -n 10 -f 2>&1 | grep \"Completed\"",
@app.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:missing", "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") @app.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-missing", "bin/rails", "db:setup" ], "docker exec app-999 bin/rails db:setup",
@app.execute_in_existing_container("bin/rails", "db:setup") @app.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 }) 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:missing bin/rails c|, assert_match %r|docker run -it --rm -e RAILS_MASTER_KEY=456 dhh/app:999 bin/rails c|,
@app.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 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 }) do @app.stub(:run_over_ssh, ->(cmd, host:) { cmd.join(" ") }) do
assert_match %r|docker exec -it app-missing bin/rails c|, assert_match %r|docker exec -it app-999 bin/rails c|,
@app.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 end
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")
end
test "run without master key" do test "run over ssh with custom user" do
ENV["RAILS_MASTER_KEY"] = nil @app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config.tap { |c| c[:ssh] = { "user" => "app" } })
@app = Mrsk::Commands::App.new Mrsk::Configuration.new(@config.tap { |c| c[:skip_master_key] = true }) assert_equal "ssh -t app@1.1.1.1 'ls'", @app.run_over_ssh("ls", host: "1.1.1.1")
end
assert @app.run.exclude?("RAILS_MASTER_KEY=456") 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" } })
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")
end
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" } })
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")
end
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" } })
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")
end
test "current_container_id" do
assert_equal \
"docker ps -q --filter label=service=app",
@app.current_container_id.join(" ")
end
test "container_id_for" do
assert_equal \
"docker container ls -a -f name=app-999 -q",
@app.container_id_for(container_name: "app-999").join(" ")
end
test "current_running_version" do
assert_equal \
"docker ps --filter label=service=app --format \"{{.Names}}\" | sed 's/-/\\n/g' | tail -n 1",
@app.current_running_version.join(" ")
end
test "most_recent_version_from_available_images" do
assert_equal \
"docker image ls --format \"{{.Tag}}\" dhh/app | head -n 1",
@app.most_recent_version_from_available_images.join(" ")
end end
end end

View File

@@ -1,6 +1,4 @@
require "test_helper" require "test_helper"
require "mrsk/configuration"
require "mrsk/commands/builder"
class CommandsBuilderTest < ActiveSupport::TestCase class CommandsBuilderTest < ActiveSupport::TestCase
setup do setup do

View File

@@ -1,10 +1,8 @@
require "test_helper" require "test_helper"
require "mrsk/configuration"
require "mrsk/commands/registry"
class CommandsRegistryTest < ActiveSupport::TestCase class CommandsRegistryTest < ActiveSupport::TestCase
setup do setup do
@config = { service: "app", @config = { service: "app",
image: "dhh/app", image: "dhh/app",
registry: { "username" => "dhh", registry: { "username" => "dhh",
"password" => "secret", "password" => "secret",

View File

@@ -1,6 +1,4 @@
require "test_helper" require "test_helper"
require "mrsk/configuration"
require "mrsk/commands/traefik"
class CommandsTraefikTest < ActiveSupport::TestCase class CommandsTraefikTest < ActiveSupport::TestCase
setup do setup do

View File

@@ -1,5 +1,4 @@
require "test_helper" require "test_helper"
require "mrsk/configuration"
class ConfigurationAccessoryTest < ActiveSupport::TestCase class ConfigurationAccessoryTest < ActiveSupport::TestCase
setup do setup do
@@ -66,7 +65,7 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
test "missing host" do test "missing host" do
@deploy[:accessories]["mysql"]["host"] = nil @deploy[:accessories]["mysql"]["host"] = nil
@config = Mrsk::Configuration.new(@deploy) @config = Mrsk::Configuration.new(@deploy)
assert_raises(ArgumentError) do assert_raises(ArgumentError) do
@config.accessory(:mysql).host @config.accessory(:mysql).host
end end

View File

@@ -1,5 +1,4 @@
require "test_helper" require "test_helper"
require "mrsk/configuration"
class ConfigurationRoleTest < ActiveSupport::TestCase class ConfigurationRoleTest < ActiveSupport::TestCase
setup do setup do
@@ -63,7 +62,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
end end
test "default traefik label on non-web role" do test "default traefik label on non-web role" do
config = Mrsk::Configuration.new(@deploy_with_roles.tap { |c| config = Mrsk::Configuration.new(@deploy_with_roles.tap { |c|
c[:servers]["beta"] = { "traefik" => "true", "hosts" => [ "1.1.1.5" ] } c[:servers]["beta"] = { "traefik" => "true", "hosts" => [ "1.1.1.5" ] }
}) })
@@ -97,7 +96,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
ENV["REDIS_PASSWORD"] = "secret456" ENV["REDIS_PASSWORD"] = "secret456"
ENV["DB_PASSWORD"] = "secret123" ENV["DB_PASSWORD"] = "secret123"
assert_equal ["-e", "REDIS_PASSWORD=secret456", "-e", "DB_PASSWORD=secret123", "-e", "REDIS_URL=redis://a/b", "-e", "WEB_CONCURRENCY=4"], @config_with_roles.role(:workers).env_args assert_equal ["-e", "REDIS_PASSWORD=secret456", "-e", "DB_PASSWORD=secret123", "-e", "REDIS_URL=redis://a/b", "-e", "WEB_CONCURRENCY=4"], @config_with_roles.role(:workers).env_args
ensure ensure
ENV["REDIS_PASSWORD"] = nil ENV["REDIS_PASSWORD"] = nil
@@ -116,7 +115,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
} }
ENV["DB_PASSWORD"] = "secret123" ENV["DB_PASSWORD"] = "secret123"
assert_equal ["-e", "DB_PASSWORD=secret123", "-e", "REDIS_URL=redis://a/b", "-e", "WEB_CONCURRENCY=4"], @config_with_roles.role(:workers).env_args assert_equal ["-e", "DB_PASSWORD=secret123", "-e", "REDIS_URL=redis://a/b", "-e", "WEB_CONCURRENCY=4"], @config_with_roles.role(:workers).env_args
ensure ensure
ENV["DB_PASSWORD"] = nil ENV["DB_PASSWORD"] = nil
@@ -133,7 +132,7 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
} }
ENV["REDIS_PASSWORD"] = "secret456" ENV["REDIS_PASSWORD"] = "secret456"
assert_equal ["-e", "REDIS_PASSWORD=secret456", "-e", "REDIS_URL=redis://a/b", "-e", "WEB_CONCURRENCY=4"], @config_with_roles.role(:workers).env_args assert_equal ["-e", "REDIS_PASSWORD=secret456", "-e", "REDIS_URL=redis://a/b", "-e", "WEB_CONCURRENCY=4"], @config_with_roles.role(:workers).env_args
ensure ensure
ENV["REDIS_PASSWORD"] = nil ENV["REDIS_PASSWORD"] = nil

View File

@@ -1,5 +1,4 @@
require "test_helper" require "test_helper"
require "mrsk/configuration"
class ConfigurationTest < ActiveSupport::TestCase class ConfigurationTest < ActiveSupport::TestCase
setup do setup do
@@ -140,17 +139,18 @@ class ConfigurationTest < ActiveSupport::TestCase
test "ssh options" do test "ssh options" do
assert_equal "root", @config.ssh_options[:user] assert_equal "root", @config.ssh_options[:user]
config = Mrsk::Configuration.new(@deploy.tap { |c| c[:ssh_user] = "app" }) config = Mrsk::Configuration.new(@deploy.tap { |c| c.merge!(ssh: { "user" => "app" }) })
assert_equal "app", @config.ssh_options[:user] assert_equal "app", @config.ssh_options[:user]
end end
test "master key" do test "ssh options with proxy host" do
assert_equal "456", @config.master_key config = Mrsk::Configuration.new(@deploy.tap { |c| c.merge!(ssh: { "proxy" => "1.2.3.4" }) })
assert_equal "root@1.2.3.4", @config.ssh_options[:proxy].jump_proxies
end end
test "skip master key" do test "ssh options with proxy host and user" do
config = Mrsk::Configuration.new(@deploy.tap { |c| c[:skip_master_key] = true }) config = Mrsk::Configuration.new(@deploy.tap { |c| c.merge!(ssh: { "proxy" => "app@1.2.3.4" }) })
assert_nil @config.master_key assert_equal "app@1.2.3.4", @config.ssh_options[:proxy].jump_proxies
end end
test "volume_args" do test "volume_args" do

View File

@@ -5,6 +5,7 @@ require "debug"
require "mocha/minitest" # using #stubs that can alter returns require "mocha/minitest" # using #stubs that can alter returns
require "minitest/autorun" # using #stub that take args require "minitest/autorun" # using #stub that take args
require "sshkit" require "sshkit"
require "mrsk"
ActiveSupport::LogSubscriber.logger = ActiveSupport::Logger.new(STDOUT) if ENV["VERBOSE"] ActiveSupport::LogSubscriber.logger = ActiveSupport::Logger.new(STDOUT) if ENV["VERBOSE"]