mrsk 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7f288f243a0192ea977464282b13b71f4ba585db01db7e7d7ae805e5509cffe
4
- data.tar.gz: e96ababf967b92aa6236339d7eb24382207789a3b2c2b64d95caff08fabda32c
3
+ metadata.gz: 8bb523ac89ed682e739e8312da6ee60bc45baace7f1d3b836d3fb62b9c75539f
4
+ data.tar.gz: 5f3552e54648aa2f0c7c795b921c0523a3deca6aeb22d5bd2fa6901dec169e33
5
5
  SHA512:
6
- metadata.gz: b1e33f7d1598785b107a4c7610df678c341c2d26899b2c6e67739991ed573f9fcfeb511ac422f78b1447c5952f8e90d4c581795729f3503bc18f89b6334c3686
7
- data.tar.gz: 1d3db7364c2574a3222ec1ab5c03c91ad1c1d55c83d0d397075b9d86fd41d2dc3e9d67068b619a3d5495036357301b7d2d2be618e58e2b8e40c972534da95c89
6
+ metadata.gz: afd16123f8681337a1f30c0908221b96c8e9540485126f20a63741ed524bd9f282cc7bdaa1aad50f61f68887f7fe1176435f5dd2f58605780de2866ee141c40c
7
+ data.tar.gz: e40f284df37ec58990c4a2e2ebd8765641eb797fb83abeed8d02aa857dd40a079793a62c5c98bfe3204c9bddeb8ebf1e2a33dde09cf337050cb4283eb05485df
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # MRSK
2
2
 
3
- 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.
3
+ 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.
4
4
 
5
5
  ## Installation
6
6
 
7
- 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:
7
+ 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:
8
8
 
9
9
  ```yaml
10
10
  service: hey
@@ -15,12 +15,17 @@ servers:
15
15
  registry:
16
16
  username: registry-user-name
17
17
  password: <%= ENV.fetch("MRSK_REGISTRY_PASSWORD") %>
18
+ env:
19
+ secret:
20
+ - RAILS_MASTER_KEY
18
21
  ```
19
22
 
20
- Now you're ready to deploy a multi-arch image to the servers:
23
+ 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).
24
+
25
+ Now you're ready to deploy to the servers:
21
26
 
22
27
  ```
23
- MRSK_REGISTRY_PASSWORD=pw mrsk deploy
28
+ mrsk deploy
24
29
  ```
25
30
 
26
31
  This will:
@@ -68,10 +73,27 @@ registry:
68
73
 
69
74
  ### Using a different SSH user than root
70
75
 
71
- The default SSH user is root, but you can change it using `ssh_user`:
76
+ The default SSH user is root, but you can change it using `ssh/user`:
77
+
78
+ ```yaml
79
+ ssh:
80
+ user: app
81
+ ```
82
+
83
+ ### Using a proxy SSH host
84
+
85
+ If you need to connect to server through a proxy host, you can use `ssh/proxy`:
86
+
87
+ ```yaml
88
+ ssh:
89
+ proxy: "192.168.0.1" # defaults to root as the user
90
+ ```
91
+
92
+ Or with specific user:
72
93
 
73
94
  ```yaml
74
- ssh_user: app
95
+ ssh:
96
+ proxy: "app@192.168.0.1"
75
97
  ```
76
98
 
77
99
  ### Using env variables
@@ -265,14 +287,6 @@ ARG RUBY_VERSION
265
287
  FROM ruby:$RUBY_VERSION-slim as base
266
288
  ```
267
289
 
268
- ### Using without RAILS_MASTER_KEY
269
-
270
- 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:
271
-
272
- ```yaml
273
- skip_master_key: true
274
- ```
275
-
276
290
  ### Using accessories for database, cache, search services
277
291
 
278
292
  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:
300
314
 
301
315
  Now run `mrsk accessory start mysql` to start the MySQL server on 1.1.1.3. See `mrsk accessory` for all the commands possible.
302
316
 
317
+ ### Using a generated .env file
318
+
319
+ 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:
320
+
321
+ ```erb
322
+ <% if (session_token = `op signin --account my-one-password-account --raw`.strip) != "" %># Generated by mrsk envify
323
+ GITHUB_TOKEN=<%= `gh config get -h github.com oauth_token`.strip %>
324
+ MRSK_REGISTRY_PASSWORD=<%= `op read "op://Vault/Docker Hub/password" -n --session #{session_token}` %>
325
+ RAILS_MASTER_KEY=<%= `op read "op://Vault/My App/RAILS_MASTER_SECRET" -n --session #{session_token}` %>
326
+ MYSQL_ROOT_PASSWORD=<%= `op read "op://Vault/My App/MYSQL_ROOT_PASSWORD" -n --session #{session_token}` %>
327
+ <% else raise ArgumentError, "Session token missing" end %>
328
+ ```
329
+
330
+ 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.
331
+
332
+ 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`.
333
+
303
334
  ## Commands
304
335
 
305
336
  ### Running commands on servers
data/bin/mrsk CHANGED
@@ -3,8 +3,7 @@
3
3
  # Prevent failures from being reported twice.
4
4
  Thread.report_on_exception = false
5
5
 
6
- require "dotenv/load"
7
- require "mrsk/cli"
6
+ require "mrsk"
8
7
 
9
8
  begin
10
9
  Mrsk::Cli::Main.start(ARGV)
@@ -1,5 +1,3 @@
1
- require "mrsk/cli/base"
2
-
3
1
  class Mrsk::Cli::Accessory < Mrsk::Cli::Base
4
2
  desc "boot [NAME]", "Boot accessory service on host (use NAME=all to boot all accessories)"
5
3
  def boot(name)
@@ -9,7 +7,11 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
9
7
  with_accessory(name) do |accessory|
10
8
  directories(name)
11
9
  upload(name)
12
- on(accessory.host) { execute *accessory.run }
10
+
11
+ on(accessory.host) do
12
+ execute *MRSK.auditor.record("accessory #{name} boot"), verbosity: :debug
13
+ execute *accessory.run
14
+ end
13
15
  end
14
16
  end
15
17
  end
@@ -18,6 +20,8 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
18
20
  def upload(name)
19
21
  with_accessory(name) do |accessory|
20
22
  on(accessory.host) do
23
+ execute *MRSK.auditor.record("accessory #{name} upload files"), verbosity: :debug
24
+
21
25
  accessory.files.each do |(local, remote)|
22
26
  accessory.ensure_local_file_present(local)
23
27
 
@@ -33,6 +37,8 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
33
37
  def directories(name)
34
38
  with_accessory(name) do |accessory|
35
39
  on(accessory.host) do
40
+ execute *MRSK.auditor.record("accessory #{name} create directories"), verbosity: :debug
41
+
36
42
  accessory.directories.keys.each do |host_path|
37
43
  execute *accessory.make_directory(host_path)
38
44
  end
@@ -52,14 +58,20 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
52
58
  desc "start [NAME]", "Start existing accessory on host"
53
59
  def start(name)
54
60
  with_accessory(name) do |accessory|
55
- on(accessory.host) { execute *accessory.start }
61
+ on(accessory.host) do
62
+ execute *MRSK.auditor.record("accessory #{name} start"), verbosity: :debug
63
+ execute *accessory.start
64
+ end
56
65
  end
57
66
  end
58
67
 
59
68
  desc "stop [NAME]", "Stop accessory on host"
60
69
  def stop(name)
61
70
  with_accessory(name) do |accessory|
62
- on(accessory.host) { execute *accessory.stop, raise_on_non_zero_exit: false }
71
+ on(accessory.host) do
72
+ execute *MRSK.auditor.record("accessory #{name} stop"), verbosity: :debug
73
+ execute *accessory.stop, raise_on_non_zero_exit: false
74
+ end
63
75
  end
64
76
  end
65
77
 
@@ -98,11 +110,17 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
98
110
 
99
111
  when options[:reuse]
100
112
  say "Launching command from existing container...", :magenta
101
- on(accessory.host) { capture_with_info(*accessory.execute_in_existing_container(cmd)) }
113
+ on(accessory.host) do
114
+ execute *MRSK.auditor.record("accessory #{name} cmd '#{cmd}'"), verbosity: :debug
115
+ capture_with_info(*accessory.execute_in_existing_container(cmd))
116
+ end
102
117
 
103
118
  else
104
119
  say "Launching command from new container...", :magenta
105
- on(accessory.host) { capture_with_info(*accessory.execute_in_new_container(cmd)) }
120
+ on(accessory.host) do
121
+ execute *MRSK.auditor.record("accessory #{name} cmd '#{cmd}'"), verbosity: :debug
122
+ capture_with_info(*accessory.execute_in_new_container(cmd))
123
+ end
106
124
  end
107
125
  end
108
126
  end
@@ -150,21 +168,30 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
150
168
  desc "remove_container [NAME]", "Remove accessory container from host"
151
169
  def remove_container(name)
152
170
  with_accessory(name) do |accessory|
153
- on(accessory.host) { execute *accessory.remove_container }
171
+ on(accessory.host) do
172
+ execute *MRSK.auditor.record("accessory #{name} remove container"), verbosity: :debug
173
+ execute *accessory.remove_container
174
+ end
154
175
  end
155
176
  end
156
177
 
157
178
  desc "remove_image [NAME]", "Remove accessory image from host"
158
179
  def remove_image(name)
159
180
  with_accessory(name) do |accessory|
160
- on(accessory.host) { execute *accessory.remove_image }
181
+ on(accessory.host) do
182
+ execute *MRSK.auditor.record("accessory #{name} remove image"), verbosity: :debug
183
+ execute *accessory.remove_image
184
+ end
161
185
  end
162
186
  end
163
187
 
164
188
  desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host"
165
189
  def remove_service_directory(name)
166
190
  with_accessory(name) do |accessory|
167
- on(accessory.host) { execute *accessory.remove_service_directory }
191
+ on(accessory.host) do
192
+ execute *MRSK.auditor.record("accessory #{name} remove service directory"), verbosity: :debug
193
+ execute *accessory.remove_service_directory
194
+ end
168
195
  end
169
196
  end
170
197
 
data/lib/mrsk/cli/app.rb CHANGED
@@ -1,5 +1,3 @@
1
- require "mrsk/cli/base"
2
-
3
1
  class Mrsk::Cli::App < Mrsk::Cli::Base
4
2
  desc "boot", "Boot app on servers (or reboot app if already running)"
5
3
  def boot
@@ -14,6 +12,8 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
14
12
 
15
13
  MRSK.config.roles.each do |role|
16
14
  on(role.hosts) do |host|
15
+ execute *MRSK.auditor.record("app boot version #{version}"), verbosity: :debug
16
+
17
17
  begin
18
18
  execute *MRSK.app.run(role: role.name)
19
19
  rescue SSHKit::Command::Failed => e
@@ -33,19 +33,25 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
33
33
 
34
34
  desc "start", "Start existing app on servers (use --version=<git-hash> to designate specific version)"
35
35
  def start
36
- on(MRSK.hosts) { execute *MRSK.app.start, raise_on_non_zero_exit: false }
36
+ on(MRSK.hosts) do
37
+ execute *MRSK.auditor.record("app start version #{MRSK.version}"), verbosity: :debug
38
+ execute *MRSK.app.start, raise_on_non_zero_exit: false
39
+ end
37
40
  end
38
-
41
+
39
42
  desc "stop", "Stop app on servers"
40
43
  def stop
41
- on(MRSK.hosts) { execute *MRSK.app.stop, raise_on_non_zero_exit: false }
44
+ on(MRSK.hosts) do
45
+ execute *MRSK.auditor.record("app stop"), verbosity: :debug
46
+ execute *MRSK.app.stop, raise_on_non_zero_exit: false
47
+ end
42
48
  end
43
-
49
+
44
50
  desc "details", "Display details about app containers"
45
51
  def details
46
52
  on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.info) }
47
53
  end
48
-
54
+
49
55
  desc "exec [CMD]", "Execute a custom command on servers"
50
56
  option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
51
57
  option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
@@ -68,15 +74,22 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
68
74
  when options[:reuse]
69
75
  say "Get current version of running container...", :magenta unless options[:version]
70
76
  using_version(options[:version] || current_running_version) do |version|
71
- say "Launching command with version #{version} from existing container on #{MRSK.primary_host}...", :magenta
72
- on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.execute_in_existing_container(cmd)) }
77
+ say "Launching command with version #{version} from existing container...", :magenta
78
+
79
+ on(MRSK.hosts) do |host|
80
+ execute *MRSK.auditor.record("app cmd '#{cmd}' with version #{version}"), verbosity: :debug
81
+ puts_by_host host, capture_with_info(*MRSK.app.execute_in_existing_container(cmd))
82
+ end
73
83
  end
74
84
 
75
85
  else
76
86
  say "Get most recent version available as an image...", :magenta unless options[:version]
77
87
  using_version(options[:version] || most_recent_version_available) do |version|
78
- say "Launching command with version #{version} from new container on #{MRSK.primary_host}...", :magenta
79
- on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.execute_in_new_container(cmd)) }
88
+ say "Launching command with version #{version} from new container...", :magenta
89
+ on(MRSK.hosts) do |host|
90
+ execute *MRSK.auditor.record("app cmd '#{cmd}' with version #{version}"), verbosity: :debug
91
+ puts_by_host host, capture_with_info(*MRSK.app.execute_in_new_container(cmd))
92
+ end
80
93
  end
81
94
  end
82
95
  end
@@ -91,11 +104,6 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
91
104
  on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_images) }
92
105
  end
93
106
 
94
- desc "current", "Return the current running container ID"
95
- def current
96
- on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_container_id) }
97
- end
98
-
99
107
  desc "logs", "Show lines from app on servers"
100
108
  option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
101
109
  option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
@@ -134,17 +142,26 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
134
142
 
135
143
  desc "remove_container [VERSION]", "Remove app container with given version from servers"
136
144
  def remove_container(version)
137
- on(MRSK.hosts) { execute *MRSK.app.remove_container(version: version) }
145
+ on(MRSK.hosts) do
146
+ execute *MRSK.auditor.record("app remove container #{version}"), verbosity: :debug
147
+ execute *MRSK.app.remove_container(version: version)
148
+ end
138
149
  end
139
150
 
140
151
  desc "remove_containers", "Remove all app containers from servers"
141
152
  def remove_containers
142
- on(MRSK.hosts) { execute *MRSK.app.remove_containers }
153
+ on(MRSK.hosts) do
154
+ execute *MRSK.auditor.record("app remove containers"), verbosity: :debug
155
+ execute *MRSK.app.remove_containers
156
+ end
143
157
  end
144
158
 
145
159
  desc "remove_images", "Remove all app images from servers"
146
160
  def remove_images
147
- on(MRSK.hosts) { execute *MRSK.app.remove_images }
161
+ on(MRSK.hosts) do
162
+ execute *MRSK.auditor.record("app remove images"), verbosity: :debug
163
+ execute *MRSK.app.remove_images
164
+ end
148
165
  end
149
166
 
150
167
  desc "current_version", "Shows the version currently running"
data/lib/mrsk/cli/base.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "thor"
2
+ require "dotenv"
2
3
  require "mrsk/sshkit_with_ext"
3
4
 
4
5
  module Mrsk::Cli
@@ -21,10 +22,19 @@ module Mrsk::Cli
21
22
 
22
23
  def initialize(*)
23
24
  super
25
+ load_envs
24
26
  initialize_commander(options)
25
27
  end
26
28
 
27
29
  private
30
+ def load_envs
31
+ if destination = options[:destination]
32
+ Dotenv.load(".env.#{destination}", ".env")
33
+ else
34
+ Dotenv.load(".env")
35
+ end
36
+ end
37
+
28
38
  def initialize_commander(options)
29
39
  MRSK.tap do |commander|
30
40
  commander.config_file = Pathname.new(File.expand_path(options[:config_file]))
@@ -1,5 +1,3 @@
1
- require "mrsk/cli/base"
2
-
3
1
  class Mrsk::Cli::Build < Mrsk::Cli::Base
4
2
  desc "deliver", "Deliver a newly built app image to servers"
5
3
  def deliver
@@ -11,7 +9,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
11
9
  def push
12
10
  cli = self
13
11
 
14
- run_locally do
12
+ run_locally do
15
13
  begin
16
14
  MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
17
15
  rescue SSHKit::Command::Failed => e
@@ -30,7 +28,10 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
30
28
 
31
29
  desc "pull", "Pull app image from the registry onto servers"
32
30
  def pull
33
- on(MRSK.hosts) { execute *MRSK.builder.pull }
31
+ on(MRSK.hosts) do
32
+ execute *MRSK.auditor.record("build pull image #{MRSK.version}"), verbosity: :debug
33
+ execute *MRSK.builder.pull
34
+ end
34
35
  end
35
36
 
36
37
  desc "create", "Create a local build setup"
data/lib/mrsk/cli/main.rb CHANGED
@@ -1,13 +1,3 @@
1
- require "mrsk/cli/base"
2
-
3
- require "mrsk/cli/accessory"
4
- require "mrsk/cli/app"
5
- require "mrsk/cli/build"
6
- require "mrsk/cli/prune"
7
- require "mrsk/cli/registry"
8
- require "mrsk/cli/server"
9
- require "mrsk/cli/traefik"
10
-
11
1
  class Mrsk::Cli::Main < Mrsk::Cli::Base
12
2
  desc "setup", "Setup all accessories and deploy the app to servers"
13
3
  def setup
@@ -70,6 +60,13 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
70
60
  invoke "mrsk:cli:accessory:details", [ "all" ]
71
61
  end
72
62
 
63
+ desc "audit", "Show audit log from servers"
64
+ def audit
65
+ on(MRSK.hosts) do |host|
66
+ puts_by_host host, capture_with_info(*MRSK.auditor.reveal)
67
+ end
68
+ end
69
+
73
70
  desc "config", "Show combined config"
74
71
  def config
75
72
  run_locally do
@@ -77,22 +74,29 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
77
74
  end
78
75
  end
79
76
 
80
- desc "install", "Create config stub in config/deploy.yml and binstub in bin/mrsk"
81
- option :skip_binstub, type: :boolean, default: false, desc: "Skip adding MRSK to the Gemfile and creating bin/mrsk binstub"
82
- def install
77
+ desc "init", "Create config stub in config/deploy.yml and env stub in .env"
78
+ option :bundle, type: :boolean, default: false, desc: "Add MRSK to the Gemfile and create a bin/mrsk binstub"
79
+ def init
83
80
  require "fileutils"
84
81
 
85
82
  if (deploy_file = Pathname.new(File.expand_path("config/deploy.yml"))).exist?
86
83
  puts "Config file already exists in config/deploy.yml (remove first to create a new one)"
87
84
  else
85
+ FileUtils.mkdir_p deploy_file.dirname
88
86
  FileUtils.cp_r Pathname.new(File.expand_path("templates/deploy.yml", __dir__)), deploy_file
89
87
  puts "Created configuration file in config/deploy.yml"
90
88
  end
91
89
 
92
- unless options[:skip_binstub]
90
+ unless (deploy_file = Pathname.new(File.expand_path(".env"))).exist?
91
+ FileUtils.cp_r Pathname.new(File.expand_path("templates/template.env", __dir__)), deploy_file
92
+ puts "Created .env file"
93
+ end
94
+
95
+ if options[:bundle]
93
96
  if (binstub = Pathname.new(File.expand_path("bin/mrsk"))).exist?
94
97
  puts "Binstub already exists in bin/mrsk (remove first to create a new one)"
95
98
  else
99
+ puts "Adding MRSK to Gemfile and bundle..."
96
100
  `bundle add mrsk`
97
101
  `bundle binstubs mrsk`
98
102
  puts "Created binstub file in bin/mrsk"
@@ -100,6 +104,15 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
100
104
  end
101
105
  end
102
106
 
107
+ desc "envify", "Create .env by evaluating .env.erb (or .env.staging.erb -> .env.staging when using -d staging)"
108
+ def envify
109
+ if destination = options[:destination]
110
+ File.write(".env.#{destination}", ERB.new(IO.read(Pathname.new(File.expand_path(".env.#{destination}.erb")))).result)
111
+ else
112
+ File.write(".env", ERB.new(IO.read(Pathname.new(File.expand_path(".env.erb")))).result)
113
+ end
114
+ end
115
+
103
116
  desc "remove", "Remove Traefik, app, and registry session from servers"
104
117
  def remove
105
118
  invoke "mrsk:cli:traefik:remove"
@@ -1,5 +1,3 @@
1
- require "mrsk/cli/base"
2
-
3
1
  class Mrsk::Cli::Prune < Mrsk::Cli::Base
4
2
  desc "all", "Prune unused images and stopped containers"
5
3
  def all
@@ -9,11 +7,17 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
9
7
 
10
8
  desc "images", "Prune unused images older than 30 days"
11
9
  def images
12
- on(MRSK.hosts) { execute *MRSK.prune.images }
10
+ on(MRSK.hosts) do
11
+ execute *MRSK.auditor.record("prune images"), verbosity: :debug
12
+ execute *MRSK.prune.images
13
+ end
13
14
  end
14
15
 
15
16
  desc "containers", "Prune stopped containers for the service older than 3 days"
16
17
  def containers
17
- on(MRSK.hosts) { execute *MRSK.prune.containers }
18
+ on(MRSK.hosts) do
19
+ execute *MRSK.auditor.record("prune containers"), verbosity: :debug
20
+ execute *MRSK.prune.containers
21
+ end
18
22
  end
19
23
  end
@@ -1,9 +1,7 @@
1
- require "mrsk/cli/base"
2
-
3
1
  class Mrsk::Cli::Registry < Mrsk::Cli::Base
4
2
  desc "login", "Login to the registry locally and remotely"
5
3
  def login
6
- run_locally { execute *MRSK.registry.login }
4
+ run_locally { execute *MRSK.registry.login }
7
5
  on(MRSK.hosts) { execute *MRSK.registry.login }
8
6
  rescue ArgumentError => e
9
7
  puts e.message
@@ -1,5 +1,3 @@
1
- require "mrsk/cli/base"
2
-
3
1
  class Mrsk::Cli::Server < Mrsk::Cli::Base
4
2
  desc "bootstrap", "Ensure Docker is installed on the servers"
5
3
  def bootstrap
@@ -0,0 +1,2 @@
1
+ MRSK_REGISTRY_PASSWORD=change-this
2
+ RAILS_MASTER_KEY=another-env
@@ -1,5 +1,3 @@
1
- require "mrsk/cli/base"
2
-
3
1
  class Mrsk::Cli::Traefik < Mrsk::Cli::Base
4
2
  desc "boot", "Boot Traefik on servers"
5
3
  def boot
@@ -15,12 +13,18 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
15
13
 
16
14
  desc "start", "Start existing Traefik on servers"
17
15
  def start
18
- on(MRSK.traefik_hosts) { execute *MRSK.traefik.start, raise_on_non_zero_exit: false }
16
+ on(MRSK.traefik_hosts) do
17
+ execute *MRSK.auditor.record("traefik start"), verbosity: :debug
18
+ execute *MRSK.traefik.start, raise_on_non_zero_exit: false
19
+ end
19
20
  end
20
21
 
21
22
  desc "stop", "Stop Traefik on servers"
22
23
  def stop
23
- on(MRSK.traefik_hosts) { execute *MRSK.traefik.stop, raise_on_non_zero_exit: false }
24
+ on(MRSK.traefik_hosts) do
25
+ execute *MRSK.auditor.record("traefik stop"), verbosity: :debug
26
+ execute *MRSK.traefik.stop, raise_on_non_zero_exit: false
27
+ end
24
28
  end
25
29
 
26
30
  desc "restart", "Restart Traefik on servers"
@@ -67,11 +71,17 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
67
71
 
68
72
  desc "remove_container", "Remove Traefik container from servers"
69
73
  def remove_container
70
- on(MRSK.traefik_hosts) { execute *MRSK.traefik.remove_container }
74
+ on(MRSK.traefik_hosts) do
75
+ execute *MRSK.auditor.record("traefik remove container"), verbosity: :debug
76
+ execute *MRSK.traefik.remove_container
77
+ end
71
78
  end
72
79
 
73
80
  desc "remove_container", "Remove Traefik image from servers"
74
81
  def remove_image
75
- on(MRSK.traefik_hosts) { execute *MRSK.traefik.remove_image }
82
+ on(MRSK.traefik_hosts) do
83
+ execute *MRSK.auditor.record("traefik remove image"), verbosity: :debug
84
+ execute *MRSK.traefik.remove_image
85
+ end
76
86
  end
77
87
  end
data/lib/mrsk/cli.rb CHANGED
@@ -1,9 +1,5 @@
1
- require "mrsk"
2
-
3
1
  module Mrsk::Cli
4
2
  end
5
3
 
6
4
  # SSHKit uses instance eval, so we need a global const for ergonomics
7
5
  MRSK = Mrsk::Commander.new
8
-
9
- require "mrsk/cli/main"
@@ -1,13 +1,5 @@
1
1
  require "active_support/core_ext/enumerable"
2
2
 
3
- require "mrsk/configuration"
4
- require "mrsk/commands/accessory"
5
- require "mrsk/commands/app"
6
- require "mrsk/commands/builder"
7
- require "mrsk/commands/prune"
8
- require "mrsk/commands/traefik"
9
- require "mrsk/commands/registry"
10
-
11
3
  class Mrsk::Commander
12
4
  attr_accessor :config_file, :destination, :verbosity, :version
13
5
 
@@ -77,8 +69,12 @@ class Mrsk::Commander
77
69
  Mrsk::Commands::Accessory.new(config, name: name)
78
70
  end
79
71
 
72
+ def auditor
73
+ @auditor ||= Mrsk::Commands::Auditor.new(config)
74
+ end
75
+
80
76
 
81
- def with_verbosity(level)
77
+ def with_verbosity(level)
82
78
  old_level = SSHKit.config.output_verbosity
83
79
  SSHKit.config.output_verbosity = level
84
80
  yield
@@ -89,7 +85,7 @@ class Mrsk::Commander
89
85
  # Test-induced damage!
90
86
  def reset
91
87
  @config = @config_file = @destination = @version = nil
92
- @app = @builder = @traefik = @registry = @prune = nil
88
+ @app = @builder = @traefik = @registry = @prune = @auditor = nil
93
89
  @verbosity = :info
94
90
  end
95
91
 
@@ -1,5 +1,3 @@
1
- require "mrsk/commands/base"
2
-
3
1
  class Mrsk::Commands::Accessory < Mrsk::Commands::Base
4
2
  attr_reader :accessory_config
5
3
  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
10
8
  end
11
9
 
12
10
  def run
13
- docker :run,
11
+ docker :run,
14
12
  "--name", service_name,
15
13
  "-d",
16
14
  "--restart", "unless-stopped",
@@ -41,10 +39,10 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
41
39
  end
42
40
 
43
41
  def follow_logs(grep: nil)
44
- run_over_ssh pipe(
45
- docker(:logs, service_name, "-t", "-n", "10", "-f", "2>&1"),
46
- (%(grep "#{grep}") if grep)
47
- ).join(" ")
42
+ run_over_ssh \
43
+ pipe \
44
+ docker(:logs, service_name, "-t", "-n", "10", "-f", "2>&1"),
45
+ (%(grep "#{grep}") if grep)
48
46
  end
49
47
 
50
48
 
@@ -66,11 +64,11 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
66
64
  end
67
65
 
68
66
  def execute_in_existing_container_over_ssh(*command)
69
- run_over_ssh execute_in_existing_container(*command, interactive: true).join(" ")
67
+ run_over_ssh execute_in_existing_container(*command, interactive: true)
70
68
  end
71
69
 
72
70
  def execute_in_new_container_over_ssh(*command)
73
- run_over_ssh execute_in_new_container(*command, interactive: true).join(" ")
71
+ run_over_ssh execute_in_new_container(*command, interactive: true)
74
72
  end
75
73
 
76
74
  def run_over_ssh(command)
@@ -1,5 +1,3 @@
1
- require "mrsk/commands/base"
2
-
3
1
  class Mrsk::Commands::App < Mrsk::Commands::Base
4
2
  def run(role: :web)
5
3
  role = config.role(role)
@@ -8,7 +6,6 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
8
6
  "-d",
9
7
  "--restart unless-stopped",
10
8
  "--name", service_with_version,
11
- *rails_master_key_arg,
12
9
  *role.env_args,
13
10
  *config.volume_args,
14
11
  *role.label_args,
@@ -37,11 +34,13 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
37
34
  end
38
35
 
39
36
  def follow_logs(host:, grep: nil)
40
- run_over_ssh pipe(
41
- current_container_id,
42
- "xargs docker logs -t -n 10 -f 2>&1",
43
- (%(grep "#{grep}") if grep)
44
- ).join(" "), host: host
37
+ run_over_ssh \
38
+ pipe(
39
+ current_container_id,
40
+ "xargs docker logs -t -n 10 -f 2>&1",
41
+ (%(grep "#{grep}") if grep)
42
+ ),
43
+ host: host
45
44
  end
46
45
 
47
46
 
@@ -56,7 +55,6 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
56
55
  docker :run,
57
56
  ("-it" if interactive),
58
57
  "--rm",
59
- *rails_master_key_arg,
60
58
  *config.env_args,
61
59
  *config.volume_args,
62
60
  config.absolute_image,
@@ -64,11 +62,11 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
64
62
  end
65
63
 
66
64
  def execute_in_existing_container_over_ssh(*command, host:)
67
- run_over_ssh execute_in_existing_container(*command, interactive: true).join(" "), host: host
65
+ run_over_ssh execute_in_existing_container(*command, interactive: true), host: host
68
66
  end
69
67
 
70
68
  def execute_in_new_container_over_ssh(*command, host:)
71
- run_over_ssh execute_in_new_container(*command, interactive: true).join(" "), host: host
69
+ run_over_ssh execute_in_new_container(*command, interactive: true), host: host
72
70
  end
73
71
 
74
72
 
@@ -130,12 +128,4 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
130
128
  def service_filter
131
129
  [ "--filter", "label=service=#{config.service}" ]
132
130
  end
133
-
134
- def rails_master_key_arg
135
- if master_key = config.master_key
136
- [ "-e", redact("RAILS_MASTER_KEY=#{master_key}") ]
137
- else
138
- []
139
- end
140
- end
141
131
  end
@@ -0,0 +1,34 @@
1
+ require "active_support/core_ext/time/conversions"
2
+
3
+ class Mrsk::Commands::Auditor < Mrsk::Commands::Base
4
+ def record(line)
5
+ append \
6
+ [ :echo, tagged_line(line) ],
7
+ audit_log_file
8
+ end
9
+
10
+ def reveal
11
+ [ :tail, "-n", 50, audit_log_file ]
12
+ end
13
+
14
+ private
15
+ def audit_log_file
16
+ "mrsk-#{config.service}-audit.log"
17
+ end
18
+
19
+ def tagged_line(line)
20
+ "'#{tags} #{line}'"
21
+ end
22
+
23
+ def tags
24
+ "[#{timestamp}] [#{performer}]"
25
+ end
26
+
27
+ def performer
28
+ `whoami`.strip
29
+ end
30
+
31
+ def timestamp
32
+ Time.now.to_fs(:db)
33
+ end
34
+ end
@@ -8,8 +8,11 @@ module Mrsk::Commands
8
8
  @config = config
9
9
  end
10
10
 
11
- def run_over_ssh(command, host:)
12
- "ssh -t #{config.ssh_user}@#{host} '#{command}'"
11
+ def run_over_ssh(*command, host:)
12
+ "ssh".tap do |cmd|
13
+ cmd << " -J #{config.ssh_proxy.jump_proxies}" if config.ssh_proxy
14
+ cmd << " -t #{config.ssh_user}@#{host} '#{command.join(" ")}'"
15
+ end
13
16
  end
14
17
 
15
18
  private
@@ -28,6 +31,10 @@ module Mrsk::Commands
28
31
  combine *commands, by: "|"
29
32
  end
30
33
 
34
+ def append(*commands)
35
+ combine *commands, by: ">>"
36
+ end
37
+
31
38
  def xargs(command)
32
39
  [ :xargs, command ].flatten
33
40
  end
@@ -1,5 +1,3 @@
1
- require "mrsk/commands/base"
2
-
3
1
  class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
4
2
  delegate :argumentize, to: Mrsk::Utils
5
3
 
@@ -1,5 +1,3 @@
1
- require "mrsk/commands/builder/multiarch"
2
-
3
1
  class Mrsk::Commands::Builder::Multiarch::Remote < Mrsk::Commands::Builder::Multiarch
4
2
  def create
5
3
  combine \
@@ -1,5 +1,3 @@
1
- require "mrsk/commands/builder/base"
2
-
3
1
  class Mrsk::Commands::Builder::Multiarch < Mrsk::Commands::Builder::Base
4
2
  def create
5
3
  docker :buildx, :create, "--use", "--name", builder_name
@@ -1,5 +1,3 @@
1
- require "mrsk/commands/builder/native"
2
-
3
1
  class Mrsk::Commands::Builder::Native::Remote < Mrsk::Commands::Builder::Native
4
2
  def create
5
3
  chain \
@@ -1,5 +1,3 @@
1
- require "mrsk/commands/builder/base"
2
-
3
1
  class Mrsk::Commands::Builder::Native < Mrsk::Commands::Builder::Base
4
2
  def create
5
3
  # No-op on native
@@ -1,5 +1,3 @@
1
- require "mrsk/commands/base"
2
-
3
1
  class Mrsk::Commands::Builder < Mrsk::Commands::Base
4
2
  delegate :create, :remove, :push, :pull, :info, to: :target
5
3
 
@@ -36,8 +34,3 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
36
34
  @multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config)
37
35
  end
38
36
  end
39
-
40
- require "mrsk/commands/builder/native"
41
- require "mrsk/commands/builder/native/remote"
42
- require "mrsk/commands/builder/multiarch"
43
- require "mrsk/commands/builder/multiarch/remote"
@@ -1,4 +1,3 @@
1
- require "mrsk/commands/base"
2
1
  require "active_support/duration"
3
2
  require "active_support/core_ext/numeric/time"
4
3
 
@@ -1,5 +1,3 @@
1
- require "mrsk/commands/base"
2
-
3
1
  class Mrsk::Commands::Registry < Mrsk::Commands::Base
4
2
  delegate :registry, to: :config
5
3
 
@@ -1,5 +1,3 @@
1
- require "mrsk/commands/base"
2
-
3
1
  class Mrsk::Commands::Traefik < Mrsk::Commands::Base
4
2
  def run
5
3
  docker :run, "--name traefik",
@@ -96,7 +96,12 @@ class Mrsk::Configuration::Role
96
96
  def merged_env_with_secrets
97
97
  merged_env.tap do |new_env|
98
98
  new_env["secret"] = Array(config.env["secret"]) + Array(specialized_env["secret"])
99
- new_env["clear"] = (Array(config.env["clear"] || config.env) + Array(specialized_env["clear"] || specialized_env)).uniq
99
+
100
+ # If there's no secret/clear split, everything is clear
101
+ clear_app_env = config.env["secret"] ? Array(config.env["clear"]) : Array(config.env["clear"] || config.env)
102
+ clear_role_env = specialized_env["secret"] ? Array(specialized_env["clear"]) : Array(specialized_env["clear"] || specialized_env)
103
+
104
+ new_env["clear"] = (clear_app_env + clear_role_env).uniq
100
105
  end
101
106
  end
102
107
  end
@@ -3,7 +3,7 @@ require "active_support/core_ext/string/inquiry"
3
3
  require "active_support/core_ext/module/delegation"
4
4
  require "pathname"
5
5
  require "erb"
6
- require "mrsk/utils"
6
+ require "net/ssh/proxy/jump"
7
7
 
8
8
  class Mrsk::Configuration
9
9
  delegate :service, :image, :servers, :env, :labels, :registry, :builder, to: :raw_config, allow_nil: true
@@ -104,17 +104,22 @@ class Mrsk::Configuration
104
104
  end
105
105
 
106
106
  def ssh_user
107
- raw_config.ssh_user || "root"
107
+ if raw_config.ssh.present?
108
+ raw_config.ssh["user"] || "root"
109
+ else
110
+ "root"
111
+ end
108
112
  end
109
113
 
110
- def ssh_options
111
- { user: ssh_user, auth_methods: [ "publickey" ] }
114
+ def ssh_proxy
115
+ if raw_config.ssh.present? && raw_config.ssh["proxy"]
116
+ Net::SSH::Proxy::Jump.new \
117
+ raw_config.ssh["proxy"].include?("@") ? raw_config.ssh["proxy"] : "root@#{raw_config.ssh["proxy"]}"
118
+ end
112
119
  end
113
120
 
114
- def master_key
115
- unless raw_config.skip_master_key
116
- ENV["RAILS_MASTER_KEY"] || File.read(Pathname.new(File.expand_path("config/master.key")))
117
- end
121
+ def ssh_options
122
+ { user: ssh_user, proxy: ssh_proxy, auth_methods: [ "publickey" ] }.compact
118
123
  end
119
124
 
120
125
 
@@ -171,6 +176,3 @@ class Mrsk::Configuration
171
176
  raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
172
177
  end
173
178
  end
174
-
175
- require "mrsk/configuration/role"
176
- require "mrsk/configuration/accessory"
data/lib/mrsk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mrsk
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/mrsk.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  module Mrsk
2
2
  end
3
3
 
4
- require "mrsk/version"
5
- require "mrsk/commander"
4
+ require "zeitwerk"
5
+
6
+ loader = Zeitwerk::Loader.for_gem
7
+ loader.ignore("#{__dir__}/mrsk/sshkit_with_ext.rb")
8
+ loader.setup
9
+ loader.eager_load # We need all commands loaded.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mrsk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-03 00:00:00.000000000 Z
11
+ date: 2023-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '2.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: zeitwerk
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.5'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.5'
69
83
  description:
70
84
  email: dhh@hey.com
71
85
  executables:
@@ -87,11 +101,13 @@ files:
87
101
  - lib/mrsk/cli/registry.rb
88
102
  - lib/mrsk/cli/server.rb
89
103
  - lib/mrsk/cli/templates/deploy.yml
104
+ - lib/mrsk/cli/templates/template.env
90
105
  - lib/mrsk/cli/traefik.rb
91
106
  - lib/mrsk/commander.rb
92
107
  - lib/mrsk/commands.rb
93
108
  - lib/mrsk/commands/accessory.rb
94
109
  - lib/mrsk/commands/app.rb
110
+ - lib/mrsk/commands/auditor.rb
95
111
  - lib/mrsk/commands/base.rb
96
112
  - lib/mrsk/commands/builder.rb
97
113
  - lib/mrsk/commands/builder/base.rb