mrsk 0.3.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +38 -5
- data/bin/mrsk +4 -0
- data/lib/mrsk/cli/accessory.rb +20 -14
- data/lib/mrsk/cli/app.rb +107 -63
- data/lib/mrsk/cli/base.rb +10 -1
- data/lib/mrsk/cli/build.rb +2 -3
- data/lib/mrsk/cli/main.rb +19 -4
- data/lib/mrsk/cli/traefik.rb +1 -1
- data/lib/mrsk/commander.rb +12 -5
- data/lib/mrsk/commands/accessory.rb +17 -12
- data/lib/mrsk/commands/app.rb +60 -26
- data/lib/mrsk/commands/base.rb +8 -4
- data/lib/mrsk/commands/traefik.rb +8 -2
- data/lib/mrsk/configuration/accessory.rb +9 -2
- data/lib/mrsk/configuration.rb +20 -6
- data/lib/mrsk/utils.rb +1 -1
- data/lib/mrsk/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7f288f243a0192ea977464282b13b71f4ba585db01db7e7d7ae805e5509cffe
|
4
|
+
data.tar.gz: e96ababf967b92aa6236339d7eb24382207789a3b2c2b64d95caff08fabda32c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1e33f7d1598785b107a4c7610df678c341c2d26899b2c6e67739991ed573f9fcfeb511ac422f78b1447c5952f8e90d4c581795729f3503bc18f89b6334c3686
|
7
|
+
data.tar.gz: 1d3db7364c2574a3222ec1ab5c03c91ad1c1d55c83d0d397075b9d86fd41d2dc3e9d67068b619a3d5495036357301b7d2d2be618e58e2b8e40c972534da95c89
|
data/README.md
CHANGED
@@ -46,6 +46,15 @@ Kubernetes is a beast. Running it yourself on your own hardware is not for the f
|
|
46
46
|
|
47
47
|
## Configuration
|
48
48
|
|
49
|
+
### Using .env file to load required environment variables
|
50
|
+
|
51
|
+
MRSK uses [dotenv](https://github.com/bkeepers/dotenv) to automatically load environment variables set in the `.env` file present in the application root. This file can be used to set variables like `MRSK_REGISTRY_PASSWORD` or database passwords. But for this reason you must ensure that .env files are not checked into Git or included in your Dockerfile! The format is just key-value like:
|
52
|
+
|
53
|
+
```bash
|
54
|
+
MRSK_REGISTRY_PASSWORD=pw
|
55
|
+
DB_PASSWORD=secret123
|
56
|
+
```
|
57
|
+
|
49
58
|
### Using another registry than Docker Hub
|
50
59
|
|
51
60
|
The default registry is Docker Hub, but you can change it using `registry/server`:
|
@@ -226,6 +235,18 @@ RUN --mount=type=secret,id=GITHUB_TOKEN \
|
|
226
235
|
bundle install
|
227
236
|
```
|
228
237
|
|
238
|
+
### Using command arguments for Traefik
|
239
|
+
|
240
|
+
You can customize the traefik command line:
|
241
|
+
|
242
|
+
```yaml
|
243
|
+
traefik:
|
244
|
+
accesslog: true
|
245
|
+
accesslog.format: json
|
246
|
+
metrics.prometheus: true
|
247
|
+
metrics.prometheus.buckets: 0.1,0.3,1.2,5.0
|
248
|
+
```
|
249
|
+
|
229
250
|
### Configuring build args for new images
|
230
251
|
|
231
252
|
Build arguments that aren't secret can also be configured:
|
@@ -281,9 +302,9 @@ Now run `mrsk accessory start mysql` to start the MySQL server on 1.1.1.3. See `
|
|
281
302
|
|
282
303
|
## Commands
|
283
304
|
|
284
|
-
### Running
|
305
|
+
### Running commands on servers
|
285
306
|
|
286
|
-
|
307
|
+
You can execute one-off commands on the servers:
|
287
308
|
|
288
309
|
```bash
|
289
310
|
# Runs command on all servers
|
@@ -326,13 +347,25 @@ Database adapter sqlite3
|
|
326
347
|
Database schema version 20221231233303
|
327
348
|
|
328
349
|
# Run Rails runner on primary server
|
329
|
-
mrsk app
|
350
|
+
mrsk app exec -p 'bin/rails runner "puts Rails.application.config.time_zone"'
|
330
351
|
UTC
|
331
352
|
```
|
332
353
|
|
333
|
-
### Running
|
354
|
+
### Running interactive commands over SSH
|
355
|
+
|
356
|
+
You can run interactive commands, like a Rails console or a bash session, on a server (default is primary, use `--hosts` to connect to another):
|
357
|
+
|
358
|
+
```bash
|
359
|
+
# Starts a bash session in a new container made from the most recent app image
|
360
|
+
mrsk app exec -i bash
|
361
|
+
|
362
|
+
# Starts a bash session in the currently running container for the app
|
363
|
+
mrsk app exec -i --reuse bash
|
364
|
+
|
365
|
+
# Starts a Rails console in a new container made from the most recent app image
|
366
|
+
mrsk app exec -i 'bin/rails console'
|
367
|
+
```
|
334
368
|
|
335
|
-
If you need to interact with the production console for the app, you can use `mrsk app console`, which will start a Rails console session on the primary host. You can start the console on a different host using `mrsk app console --host 192.168.0.2`. Be mindful that this is a live wire! Any changes made to the production database will take effect immeditately.
|
336
369
|
|
337
370
|
### Running details to see state of containers
|
338
371
|
|
data/bin/mrsk
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
# Prevent failures from being reported twice.
|
4
4
|
Thread.report_on_exception = false
|
5
5
|
|
6
|
+
require "dotenv/load"
|
6
7
|
require "mrsk/cli"
|
7
8
|
|
8
9
|
begin
|
@@ -10,4 +11,7 @@ begin
|
|
10
11
|
rescue SSHKit::Runner::ExecuteError => e
|
11
12
|
puts " \e[31mERROR (#{e.cause.class}): #{e.cause.message}\e[0m"
|
12
13
|
puts e.cause.backtrace if ENV["VERBOSE"]
|
14
|
+
rescue => e
|
15
|
+
puts " \e[31mERROR (#{e.class}): #{e.message}\e[0m"
|
16
|
+
puts e.backtrace if ENV["VERBOSE"]
|
13
17
|
end
|
data/lib/mrsk/cli/accessory.rb
CHANGED
@@ -82,21 +82,27 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
desc "exec [NAME] [CMD]", "Execute a custom command on
|
86
|
-
option :
|
85
|
+
desc "exec [NAME] [CMD]", "Execute a custom command on servers"
|
86
|
+
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
87
|
+
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
87
88
|
def exec(name, cmd)
|
88
89
|
with_accessory(name) do |accessory|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
90
|
+
case
|
91
|
+
when options[:interactive] && options[:reuse]
|
92
|
+
say "Launching interactive command with via SSH from existing container...", :magenta
|
93
|
+
run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
|
93
94
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
95
|
+
when options[:interactive]
|
96
|
+
say "Launching interactive command via SSH from new container...", :magenta
|
97
|
+
run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
|
98
|
+
|
99
|
+
when options[:reuse]
|
100
|
+
say "Launching command from existing container...", :magenta
|
101
|
+
on(accessory.host) { capture_with_info(*accessory.execute_in_existing_container(cmd)) }
|
102
|
+
|
103
|
+
else
|
104
|
+
say "Launching command from new container...", :magenta
|
105
|
+
on(accessory.host) { capture_with_info(*accessory.execute_in_new_container(cmd)) }
|
100
106
|
end
|
101
107
|
end
|
102
108
|
end
|
@@ -118,7 +124,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
118
124
|
end
|
119
125
|
else
|
120
126
|
since = options[:since]
|
121
|
-
lines = options[:lines]
|
127
|
+
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
122
128
|
|
123
129
|
on(accessory.host) do
|
124
130
|
puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep))
|
@@ -148,7 +154,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
148
154
|
end
|
149
155
|
end
|
150
156
|
|
151
|
-
desc "
|
157
|
+
desc "remove_image [NAME]", "Remove accessory image from host"
|
152
158
|
def remove_image(name)
|
153
159
|
with_accessory(name) do |accessory|
|
154
160
|
on(accessory.host) { execute *accessory.remove_image }
|
data/lib/mrsk/cli/app.rb
CHANGED
@@ -1,32 +1,39 @@
|
|
1
1
|
require "mrsk/cli/base"
|
2
2
|
|
3
3
|
class Mrsk::Cli::App < Mrsk::Cli::Base
|
4
|
-
desc "boot", "Boot app on servers (or
|
4
|
+
desc "boot", "Boot app on servers (or reboot app if already running)"
|
5
5
|
def boot
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
6
|
+
cli = self
|
7
|
+
|
8
|
+
say "Ensure no other version of the app is running...", :magenta
|
9
|
+
stop
|
10
|
+
|
11
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
12
|
+
using_version(options[:version] || most_recent_version_available) do |version|
|
13
|
+
say "Start container with version #{version} (or reboot if already running)...", :magenta
|
14
|
+
|
15
|
+
MRSK.config.roles.each do |role|
|
16
|
+
on(role.hosts) do |host|
|
17
|
+
begin
|
18
|
+
execute *MRSK.app.run(role: role.name)
|
19
|
+
rescue SSHKit::Command::Failed => e
|
20
|
+
if e.message =~ /already in use/
|
21
|
+
error "Rebooting container with same version already deployed on #{host}"
|
22
|
+
|
23
|
+
cli.remove_container version
|
24
|
+
execute *MRSK.app.run(role: role.name)
|
25
|
+
else
|
26
|
+
raise
|
27
|
+
end
|
16
28
|
end
|
17
29
|
end
|
18
30
|
end
|
19
31
|
end
|
20
32
|
end
|
21
|
-
|
33
|
+
|
22
34
|
desc "start", "Start existing app on servers (use --version=<git-hash> to designate specific version)"
|
23
|
-
option :version, desc: "Defaults to the most recent git-hash in local repository"
|
24
35
|
def start
|
25
|
-
|
26
|
-
on(MRSK.hosts) { execute *MRSK.app.start(version: version) }
|
27
|
-
else
|
28
|
-
on(MRSK.hosts) { execute *MRSK.app.start, raise_on_non_zero_exit: false }
|
29
|
-
end
|
36
|
+
on(MRSK.hosts) { execute *MRSK.app.start, raise_on_non_zero_exit: false }
|
30
37
|
end
|
31
38
|
|
32
39
|
desc "stop", "Stop app on servers"
|
@@ -40,45 +47,38 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
40
47
|
end
|
41
48
|
|
42
49
|
desc "exec [CMD]", "Execute a custom command on servers"
|
43
|
-
option :
|
50
|
+
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
51
|
+
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
44
52
|
def exec(cmd)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end.inquiry
|
52
|
-
|
53
|
-
if runner.exec_over_ssh?
|
54
|
-
run_locally do
|
55
|
-
info "Launching command on #{MRSK.primary_host}"
|
56
|
-
exec MRSK.app.exec_over_ssh(cmd, host: MRSK.primary_host)
|
53
|
+
case
|
54
|
+
when options[:interactive] && options[:reuse]
|
55
|
+
say "Get current version of running container...", :magenta unless options[:version]
|
56
|
+
using_version(options[:version] || current_running_version) do |version|
|
57
|
+
say "Launching interactive command with version #{version} via SSH from existing container on #{MRSK.primary_host}...", :magenta
|
58
|
+
run_locally { exec MRSK.app.execute_in_existing_container_over_ssh(cmd, host: MRSK.primary_host) }
|
57
59
|
end
|
58
|
-
else
|
59
|
-
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.send(runner, cmd)) }
|
60
|
-
end
|
61
|
-
end
|
62
60
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
61
|
+
when options[:interactive]
|
62
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
63
|
+
using_version(options[:version] || most_recent_version_available) do |version|
|
64
|
+
say "Launching interactive command with version #{version} via SSH from new container on #{MRSK.primary_host}...", :magenta
|
65
|
+
run_locally { exec MRSK.app.execute_in_new_container_over_ssh(cmd, host: MRSK.primary_host) }
|
66
|
+
end
|
70
67
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
end
|
68
|
+
when options[:reuse]
|
69
|
+
say "Get current version of running container...", :magenta unless options[:version]
|
70
|
+
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)) }
|
73
|
+
end
|
78
74
|
|
79
|
-
|
80
|
-
|
81
|
-
|
75
|
+
else
|
76
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
77
|
+
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)) }
|
80
|
+
end
|
81
|
+
end
|
82
82
|
end
|
83
83
|
|
84
84
|
desc "containers", "List all the app containers currently on servers"
|
@@ -86,6 +86,11 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
86
86
|
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_containers) }
|
87
87
|
end
|
88
88
|
|
89
|
+
desc "images", "List all the app images currently on servers"
|
90
|
+
def images
|
91
|
+
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_images) }
|
92
|
+
end
|
93
|
+
|
89
94
|
desc "current", "Return the current running container ID"
|
90
95
|
def current
|
91
96
|
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_container_id) }
|
@@ -109,7 +114,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
109
114
|
end
|
110
115
|
else
|
111
116
|
since = options[:since]
|
112
|
-
lines = options[:lines]
|
117
|
+
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
113
118
|
|
114
119
|
on(MRSK.hosts) do |host|
|
115
120
|
begin
|
@@ -122,16 +127,55 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
122
127
|
end
|
123
128
|
|
124
129
|
desc "remove", "Remove app containers and images from servers"
|
125
|
-
option :only, default: "", desc: "Use 'containers' or 'images'"
|
126
130
|
def remove
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
on(MRSK.hosts) { execute *MRSK.app.remove_images }
|
135
|
-
end
|
131
|
+
remove_containers
|
132
|
+
remove_images
|
133
|
+
end
|
134
|
+
|
135
|
+
desc "remove_container [VERSION]", "Remove app container with given version from servers"
|
136
|
+
def remove_container(version)
|
137
|
+
on(MRSK.hosts) { execute *MRSK.app.remove_container(version: version) }
|
136
138
|
end
|
139
|
+
|
140
|
+
desc "remove_containers", "Remove all app containers from servers"
|
141
|
+
def remove_containers
|
142
|
+
on(MRSK.hosts) { execute *MRSK.app.remove_containers }
|
143
|
+
end
|
144
|
+
|
145
|
+
desc "remove_images", "Remove all app images from servers"
|
146
|
+
def remove_images
|
147
|
+
on(MRSK.hosts) { execute *MRSK.app.remove_images }
|
148
|
+
end
|
149
|
+
|
150
|
+
desc "current_version", "Shows the version currently running"
|
151
|
+
def current_version
|
152
|
+
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_running_version).strip }
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
def using_version(new_version)
|
157
|
+
if new_version
|
158
|
+
begin
|
159
|
+
old_version = MRSK.config.version
|
160
|
+
MRSK.config.version = new_version
|
161
|
+
yield new_version
|
162
|
+
ensure
|
163
|
+
MRSK.config.version = old_version
|
164
|
+
end
|
165
|
+
else
|
166
|
+
yield MRSK.config.version
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def most_recent_version_available(host: MRSK.primary_host)
|
171
|
+
version = nil
|
172
|
+
on(host) { version = capture_with_info(*MRSK.app.most_recent_version_from_available_images).strip }
|
173
|
+
version.presence
|
174
|
+
end
|
175
|
+
|
176
|
+
def current_running_version(host: MRSK.primary_host)
|
177
|
+
version = nil
|
178
|
+
on(host) { version = capture_with_info(*MRSK.app.current_running_version).strip }
|
179
|
+
version.presence
|
180
|
+
end
|
137
181
|
end
|
data/lib/mrsk/cli/base.rb
CHANGED
@@ -8,6 +8,7 @@ module Mrsk::Cli
|
|
8
8
|
def self.exit_on_failure?() true end
|
9
9
|
|
10
10
|
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
|
11
|
+
class_option :quiet, type: :boolean, aliases: "-q", desc: "Minimal logging"
|
11
12
|
|
12
13
|
class_option :version, desc: "Run commands against a specific app version"
|
13
14
|
|
@@ -28,12 +29,20 @@ module Mrsk::Cli
|
|
28
29
|
MRSK.tap do |commander|
|
29
30
|
commander.config_file = Pathname.new(File.expand_path(options[:config_file]))
|
30
31
|
commander.destination = options[:destination]
|
31
|
-
commander.verbose = options[:verbose]
|
32
32
|
commander.version = options[:version]
|
33
33
|
|
34
34
|
commander.specific_hosts = options[:hosts]&.split(",")
|
35
35
|
commander.specific_roles = options[:roles]&.split(",")
|
36
36
|
commander.specific_primary! if options[:primary]
|
37
|
+
|
38
|
+
if options[:verbose]
|
39
|
+
ENV["VERBOSE"] = "1" # For backtraces via cli/start
|
40
|
+
commander.verbosity = :debug
|
41
|
+
end
|
42
|
+
|
43
|
+
if options[:quiet]
|
44
|
+
commander.verbosity = :error
|
45
|
+
end
|
37
46
|
end
|
38
47
|
end
|
39
48
|
|
data/lib/mrsk/cli/build.rb
CHANGED
@@ -9,18 +9,17 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
|
9
9
|
|
10
10
|
desc "push", "Build locally and push app image to registry"
|
11
11
|
def push
|
12
|
-
verbose = options[:verbose]
|
13
12
|
cli = self
|
14
13
|
|
15
14
|
run_locally do
|
16
15
|
begin
|
17
|
-
MRSK.
|
16
|
+
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
|
18
17
|
rescue SSHKit::Command::Failed => e
|
19
18
|
if e.message =~ /(no builder)|(no such file or directory)/
|
20
19
|
error "Missing compatible builder, so creating a new one first"
|
21
20
|
|
22
21
|
if cli.create
|
23
|
-
MRSK.
|
22
|
+
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
|
24
23
|
end
|
25
24
|
else
|
26
25
|
raise
|
data/lib/mrsk/cli/main.rb
CHANGED
@@ -21,12 +21,21 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
21
21
|
desc "deploy", "Deploy the app to servers"
|
22
22
|
def deploy
|
23
23
|
print_runtime do
|
24
|
+
say "Ensure Docker is installed...", :magenta
|
24
25
|
invoke "mrsk:cli:server:bootstrap"
|
26
|
+
|
27
|
+
say "Log into image registry...", :magenta
|
25
28
|
invoke "mrsk:cli:registry:login"
|
29
|
+
|
30
|
+
say "Build and push app image...", :magenta
|
26
31
|
invoke "mrsk:cli:build:deliver"
|
32
|
+
|
33
|
+
say "Ensure Traefik is running...", :magenta
|
27
34
|
invoke "mrsk:cli:traefik:boot"
|
28
|
-
|
35
|
+
|
29
36
|
invoke "mrsk:cli:app:boot"
|
37
|
+
|
38
|
+
say "Prune old containers and images...", :magenta
|
30
39
|
invoke "mrsk:cli:prune:all"
|
31
40
|
end
|
32
41
|
end
|
@@ -34,17 +43,23 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
34
43
|
desc "redeploy", "Deploy new version of the app to servers (without bootstrapping servers, starting Traefik, pruning, and registry login)"
|
35
44
|
def redeploy
|
36
45
|
print_runtime do
|
46
|
+
say "Build and push app image...", :magenta
|
37
47
|
invoke "mrsk:cli:build:deliver"
|
38
|
-
|
48
|
+
|
39
49
|
invoke "mrsk:cli:app:boot"
|
40
50
|
end
|
41
51
|
end
|
42
52
|
|
43
|
-
desc "rollback [VERSION]", "Rollback the app to VERSION
|
53
|
+
desc "rollback [VERSION]", "Rollback the app to VERSION"
|
44
54
|
def rollback(version)
|
55
|
+
MRSK.version = version
|
56
|
+
|
57
|
+
cli = self
|
58
|
+
|
59
|
+
cli.say "Stop current version, then start version #{version}...", :magenta
|
45
60
|
on(MRSK.hosts) do
|
46
61
|
execute *MRSK.app.stop, raise_on_non_zero_exit: false
|
47
|
-
execute *MRSK.app.start
|
62
|
+
execute *MRSK.app.start
|
48
63
|
end
|
49
64
|
end
|
50
65
|
|
data/lib/mrsk/cli/traefik.rb
CHANGED
@@ -50,7 +50,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
|
50
50
|
end
|
51
51
|
else
|
52
52
|
since = options[:since]
|
53
|
-
lines = options[:lines]
|
53
|
+
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
54
54
|
|
55
55
|
on(MRSK.traefik_hosts) do |host|
|
56
56
|
puts_by_host host, capture(*MRSK.traefik.logs(since: since, lines: lines, grep: grep)), type: "Traefik"
|
data/lib/mrsk/commander.rb
CHANGED
@@ -9,10 +9,10 @@ require "mrsk/commands/traefik"
|
|
9
9
|
require "mrsk/commands/registry"
|
10
10
|
|
11
11
|
class Mrsk::Commander
|
12
|
-
attr_accessor :config_file, :destination, :
|
12
|
+
attr_accessor :config_file, :destination, :verbosity, :version
|
13
13
|
|
14
|
-
def initialize(config_file: nil, destination: nil,
|
15
|
-
@config_file, @destination, @
|
14
|
+
def initialize(config_file: nil, destination: nil, verbosity: :info)
|
15
|
+
@config_file, @destination, @verbosity = config_file, destination, verbosity
|
16
16
|
end
|
17
17
|
|
18
18
|
def config
|
@@ -78,7 +78,7 @@ class Mrsk::Commander
|
|
78
78
|
end
|
79
79
|
|
80
80
|
|
81
|
-
def
|
81
|
+
def with_verbosity(level)
|
82
82
|
old_level = SSHKit.config.output_verbosity
|
83
83
|
SSHKit.config.output_verbosity = level
|
84
84
|
yield
|
@@ -86,6 +86,13 @@ class Mrsk::Commander
|
|
86
86
|
SSHKit.config.output_verbosity = old_level
|
87
87
|
end
|
88
88
|
|
89
|
+
# Test-induced damage!
|
90
|
+
def reset
|
91
|
+
@config = @config_file = @destination = @version = nil
|
92
|
+
@app = @builder = @traefik = @registry = @prune = nil
|
93
|
+
@verbosity = :info
|
94
|
+
end
|
95
|
+
|
89
96
|
private
|
90
97
|
def cascading_version
|
91
98
|
version.presence || ENV["VERSION"] || `git rev-parse HEAD`.strip
|
@@ -95,6 +102,6 @@ class Mrsk::Commander
|
|
95
102
|
def configure_sshkit_with(config)
|
96
103
|
SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = config.ssh_options }
|
97
104
|
SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
|
98
|
-
SSHKit.config.output_verbosity =
|
105
|
+
SSHKit.config.output_verbosity = verbosity
|
99
106
|
end
|
100
107
|
end
|
@@ -33,6 +33,7 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
|
33
33
|
docker :ps, *service_filter
|
34
34
|
end
|
35
35
|
|
36
|
+
|
36
37
|
def logs(since: nil, lines: nil, grep: nil)
|
37
38
|
pipe \
|
38
39
|
docker(:logs, service_name, (" --since #{since}" if since), (" -n #{lines}" if lines), "-t", "2>&1"),
|
@@ -42,20 +43,19 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
|
42
43
|
def follow_logs(grep: nil)
|
43
44
|
run_over_ssh pipe(
|
44
45
|
docker(:logs, service_name, "-t", "-n", "10", "-f", "2>&1"),
|
45
|
-
(
|
46
|
-
).join(" ")
|
46
|
+
(%(grep "#{grep}") if grep)
|
47
|
+
).join(" ")
|
47
48
|
end
|
48
49
|
|
49
|
-
|
50
|
+
|
51
|
+
def execute_in_existing_container(*command, interactive: false)
|
50
52
|
docker :exec,
|
51
53
|
("-it" if interactive),
|
52
|
-
*env_args,
|
53
|
-
*volume_args,
|
54
54
|
service_name,
|
55
55
|
*command
|
56
56
|
end
|
57
57
|
|
58
|
-
def
|
58
|
+
def execute_in_new_container(*command, interactive: false)
|
59
59
|
docker :run,
|
60
60
|
("-it" if interactive),
|
61
61
|
"--rm",
|
@@ -65,10 +65,19 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
|
65
65
|
*command
|
66
66
|
end
|
67
67
|
|
68
|
-
def
|
69
|
-
|
68
|
+
def execute_in_existing_container_over_ssh(*command)
|
69
|
+
run_over_ssh execute_in_existing_container(*command, interactive: true).join(" ")
|
70
|
+
end
|
71
|
+
|
72
|
+
def execute_in_new_container_over_ssh(*command)
|
73
|
+
run_over_ssh execute_in_new_container(*command, interactive: true).join(" ")
|
74
|
+
end
|
75
|
+
|
76
|
+
def run_over_ssh(command)
|
77
|
+
super command, host: host
|
70
78
|
end
|
71
79
|
|
80
|
+
|
72
81
|
def ensure_local_file_present(local_file)
|
73
82
|
if !local_file.is_a?(StringIO) && !Pathname.new(local_file).exist?
|
74
83
|
raise "Missing file: #{local_file}"
|
@@ -96,10 +105,6 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
|
96
105
|
end
|
97
106
|
|
98
107
|
private
|
99
|
-
def exec_over_ssh(*command, host:)
|
100
|
-
run_over_ssh run_exec(*command, interactive: true).join(" "), host: host
|
101
|
-
end
|
102
|
-
|
103
108
|
def service_filter
|
104
109
|
[ "--filter", "label=service=#{service_name}" ]
|
105
110
|
end
|
data/lib/mrsk/commands/app.rb
CHANGED
@@ -7,7 +7,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
7
7
|
docker :run,
|
8
8
|
"-d",
|
9
9
|
"--restart unless-stopped",
|
10
|
-
"--name",
|
10
|
+
"--name", service_with_version,
|
11
11
|
*rails_master_key_arg,
|
12
12
|
*role.env_args,
|
13
13
|
*config.volume_args,
|
@@ -16,40 +16,43 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
16
16
|
role.cmd
|
17
17
|
end
|
18
18
|
|
19
|
-
def start
|
20
|
-
docker :start,
|
21
|
-
end
|
22
|
-
|
23
|
-
def current_container_id
|
24
|
-
docker :ps, "-q", *service_filter
|
19
|
+
def start
|
20
|
+
docker :start, service_with_version
|
25
21
|
end
|
26
22
|
|
27
23
|
def stop
|
28
|
-
pipe current_container_id,
|
24
|
+
pipe current_container_id, xargs(docker(:stop))
|
29
25
|
end
|
30
26
|
|
31
27
|
def info
|
32
28
|
docker :ps, *service_filter
|
33
29
|
end
|
34
30
|
|
31
|
+
|
35
32
|
def logs(since: nil, lines: nil, grep: nil)
|
36
33
|
pipe \
|
37
34
|
current_container_id,
|
38
|
-
"xargs docker logs#{" --since #{since}" if since}#{" -n #{lines}" if lines}
|
35
|
+
"xargs docker logs#{" --since #{since}" if since}#{" -n #{lines}" if lines} 2>&1",
|
39
36
|
("grep '#{grep}'" if grep)
|
40
37
|
end
|
41
38
|
|
42
|
-
def
|
39
|
+
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
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def execute_in_existing_container(*command, interactive: false)
|
43
49
|
docker :exec,
|
44
50
|
("-it" if interactive),
|
45
|
-
*rails_master_key_arg,
|
46
|
-
*config.env_args,
|
47
|
-
*config.volume_args,
|
48
51
|
config.service_with_version,
|
49
52
|
*command
|
50
53
|
end
|
51
54
|
|
52
|
-
def
|
55
|
+
def execute_in_new_container(*command, interactive: false)
|
53
56
|
docker :run,
|
54
57
|
("-it" if interactive),
|
55
58
|
"--rm",
|
@@ -60,39 +63,70 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
60
63
|
*command
|
61
64
|
end
|
62
65
|
|
63
|
-
def
|
64
|
-
run_over_ssh
|
66
|
+
def execute_in_existing_container_over_ssh(*command, host:)
|
67
|
+
run_over_ssh execute_in_existing_container(*command, interactive: true).join(" "), host: host
|
65
68
|
end
|
66
69
|
|
67
|
-
def
|
68
|
-
run_over_ssh
|
69
|
-
current_container_id,
|
70
|
-
"xargs docker logs -t -n 10 -f 2>&1",
|
71
|
-
("grep '#{grep}'" if grep)
|
72
|
-
).join(" "), host: host
|
70
|
+
def execute_in_new_container_over_ssh(*command, host:)
|
71
|
+
run_over_ssh execute_in_new_container(*command, interactive: true).join(" "), host: host
|
73
72
|
end
|
74
73
|
|
75
|
-
|
76
|
-
|
74
|
+
|
75
|
+
def current_container_id
|
76
|
+
docker :ps, "-q", *service_filter
|
77
77
|
end
|
78
78
|
|
79
|
-
def
|
80
|
-
|
79
|
+
def container_id_for(container_name:)
|
80
|
+
docker :container, :ls, "-a", "-f", "name=#{container_name}", "-q"
|
81
81
|
end
|
82
82
|
|
83
|
+
def current_running_version
|
84
|
+
# FIXME: Find more graceful way to extract the version from "app-version" than using sed and tail!
|
85
|
+
pipe \
|
86
|
+
docker(:ps, "--filter", "label=service=#{config.service}", "--format", '"{{.Names}}"'),
|
87
|
+
%(sed 's/-/\\n/g'),
|
88
|
+
"tail -n 1"
|
89
|
+
end
|
90
|
+
|
91
|
+
def most_recent_version_from_available_images
|
92
|
+
pipe \
|
93
|
+
docker(:image, :ls, "--format", '"{{.Tag}}"', config.repository),
|
94
|
+
"head -n 1"
|
95
|
+
end
|
96
|
+
|
97
|
+
|
83
98
|
def list_containers
|
84
99
|
docker :container, :ls, "-a", *service_filter
|
85
100
|
end
|
86
101
|
|
102
|
+
def remove_container(version:)
|
103
|
+
pipe \
|
104
|
+
container_id_for(container_name: service_with_version(version)),
|
105
|
+
xargs(docker(:container, :rm))
|
106
|
+
end
|
107
|
+
|
87
108
|
def remove_containers
|
88
109
|
docker :container, :prune, "-f", *service_filter
|
89
110
|
end
|
90
111
|
|
112
|
+
def list_images
|
113
|
+
docker :image, :ls, config.repository
|
114
|
+
end
|
115
|
+
|
91
116
|
def remove_images
|
92
117
|
docker :image, :prune, "-a", "-f", *service_filter
|
93
118
|
end
|
94
119
|
|
120
|
+
|
95
121
|
private
|
122
|
+
def service_with_version(version = nil)
|
123
|
+
if version
|
124
|
+
"#{config.service}-#{version}"
|
125
|
+
else
|
126
|
+
config.service_with_version
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
96
130
|
def service_filter
|
97
131
|
[ "--filter", "label=service=#{config.service}" ]
|
98
132
|
end
|
data/lib/mrsk/commands/base.rb
CHANGED
@@ -8,6 +8,10 @@ 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}'"
|
13
|
+
end
|
14
|
+
|
11
15
|
private
|
12
16
|
def combine(*commands, by: "&&")
|
13
17
|
commands
|
@@ -24,12 +28,12 @@ module Mrsk::Commands
|
|
24
28
|
combine *commands, by: "|"
|
25
29
|
end
|
26
30
|
|
27
|
-
def
|
28
|
-
|
31
|
+
def xargs(command)
|
32
|
+
[ :xargs, command ].flatten
|
29
33
|
end
|
30
34
|
|
31
|
-
def
|
32
|
-
|
35
|
+
def docker(*args)
|
36
|
+
args.compact.unshift :docker
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
@@ -9,7 +9,8 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
9
9
|
"-v /var/run/docker.sock:/var/run/docker.sock",
|
10
10
|
"traefik",
|
11
11
|
"--providers.docker",
|
12
|
-
"--log.level=DEBUG"
|
12
|
+
"--log.level=DEBUG",
|
13
|
+
*cmd_args
|
13
14
|
end
|
14
15
|
|
15
16
|
def start
|
@@ -33,7 +34,7 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
33
34
|
def follow_logs(host:, grep: nil)
|
34
35
|
run_over_ssh pipe(
|
35
36
|
docker(:logs, "traefik", "-t", "-n", "10", "-f", "2>&1"),
|
36
|
-
(
|
37
|
+
(%(grep "#{grep}") if grep)
|
37
38
|
).join(" "), host: host
|
38
39
|
end
|
39
40
|
|
@@ -44,4 +45,9 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
44
45
|
def remove_image
|
45
46
|
docker :image, :prune, "-a", "-f", "--filter", "label=org.opencontainers.image.title=Traefik"
|
46
47
|
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def cmd_args
|
51
|
+
(config.raw_config.dig(:traefik, "args") || { }).collect { |(key, value)| [ "--#{key}", value ] }.flatten
|
52
|
+
end
|
47
53
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class Mrsk::Configuration::
|
1
|
+
class Mrsk::Configuration::Accessory
|
2
2
|
delegate :argumentize, :argumentize_env_with_secrets, to: Mrsk::Utils
|
3
3
|
|
4
4
|
attr_accessor :name, :specifics
|
@@ -74,12 +74,19 @@ class Mrsk::Configuration::Assessory
|
|
74
74
|
|
75
75
|
def expand_local_file(local_file)
|
76
76
|
if local_file.end_with?("erb")
|
77
|
-
read_dynamic_file(local_file)
|
77
|
+
with_clear_env_loaded { read_dynamic_file(local_file) }
|
78
78
|
else
|
79
79
|
Pathname.new(File.expand_path(local_file)).to_s
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
|
+
def with_clear_env_loaded
|
84
|
+
(env["clear"] || env).each { |k, v| ENV[k] = v }
|
85
|
+
yield
|
86
|
+
ensure
|
87
|
+
(env["clear"] || env).each { |k, v| ENV.delete(k) }
|
88
|
+
end
|
89
|
+
|
83
90
|
def read_dynamic_file(local_file)
|
84
91
|
StringIO.new(ERB.new(IO.read(local_file)).result)
|
85
92
|
end
|
data/lib/mrsk/configuration.rb
CHANGED
@@ -9,6 +9,7 @@ class Mrsk::Configuration
|
|
9
9
|
delegate :service, :image, :servers, :env, :labels, :registry, :builder, to: :raw_config, allow_nil: true
|
10
10
|
delegate :argumentize, :argumentize_env_with_secrets, to: Mrsk::Utils
|
11
11
|
|
12
|
+
attr_accessor :version
|
12
13
|
attr_accessor :raw_config
|
13
14
|
|
14
15
|
class << self
|
@@ -39,7 +40,7 @@ class Mrsk::Configuration
|
|
39
40
|
def initialize(raw_config, version: "missing", validate: true)
|
40
41
|
@raw_config = ActiveSupport::InheritableOptions.new(raw_config)
|
41
42
|
@version = version
|
42
|
-
|
43
|
+
valid? if validate
|
43
44
|
end
|
44
45
|
|
45
46
|
|
@@ -52,7 +53,7 @@ class Mrsk::Configuration
|
|
52
53
|
end
|
53
54
|
|
54
55
|
def accessories
|
55
|
-
@accessories ||= raw_config.accessories&.keys&.collect { |name| Mrsk::Configuration::
|
56
|
+
@accessories ||= raw_config.accessories&.keys&.collect { |name| Mrsk::Configuration::Accessory.new(name, config: self) } || []
|
56
57
|
end
|
57
58
|
|
58
59
|
def accessory(name)
|
@@ -73,10 +74,6 @@ class Mrsk::Configuration
|
|
73
74
|
end
|
74
75
|
|
75
76
|
|
76
|
-
def version
|
77
|
-
@version
|
78
|
-
end
|
79
|
-
|
80
77
|
def repository
|
81
78
|
[ raw_config.registry["server"], image ].compact.join("/")
|
82
79
|
end
|
@@ -120,6 +117,12 @@ class Mrsk::Configuration
|
|
120
117
|
end
|
121
118
|
end
|
122
119
|
|
120
|
+
|
121
|
+
def valid?
|
122
|
+
ensure_required_keys_present && ensure_env_available
|
123
|
+
end
|
124
|
+
|
125
|
+
|
123
126
|
def to_h
|
124
127
|
{
|
125
128
|
roles: role_names,
|
@@ -139,6 +142,7 @@ class Mrsk::Configuration
|
|
139
142
|
|
140
143
|
|
141
144
|
private
|
145
|
+
# Will raise ArgumentError if any required config keys are missing
|
142
146
|
def ensure_required_keys_present
|
143
147
|
%i[ service image registry servers ].each do |key|
|
144
148
|
raise ArgumentError, "Missing required configuration for #{key}" unless raw_config[key].present?
|
@@ -151,6 +155,16 @@ class Mrsk::Configuration
|
|
151
155
|
if raw_config.registry["password"].blank?
|
152
156
|
raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
|
153
157
|
end
|
158
|
+
|
159
|
+
true
|
160
|
+
end
|
161
|
+
|
162
|
+
# Will raise KeyError if any secret ENVs are missing
|
163
|
+
def ensure_env_available
|
164
|
+
env_args
|
165
|
+
roles.each(&:env_args)
|
166
|
+
|
167
|
+
true
|
154
168
|
end
|
155
169
|
|
156
170
|
def role_names
|
data/lib/mrsk/utils.rb
CHANGED
@@ -18,7 +18,7 @@ module Mrsk::Utils
|
|
18
18
|
if (secrets = env["secret"]).present?
|
19
19
|
argumentize("-e", secrets.to_h { |key| [ key, ENV.fetch(key) ] }, redacted: true) + argumentize("-e", env["clear"])
|
20
20
|
else
|
21
|
-
argumentize "-e", env
|
21
|
+
argumentize "-e", env.fetch("clear", env)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
data/lib/mrsk/version.rb
CHANGED
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.
|
4
|
+
version: 0.5.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-
|
11
|
+
date: 2023-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: dotenv
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.8'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.8'
|
55
69
|
description:
|
56
70
|
email: dhh@hey.com
|
57
71
|
executables:
|
@@ -113,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
127
|
- !ruby/object:Gem::Version
|
114
128
|
version: '0'
|
115
129
|
requirements: []
|
116
|
-
rubygems_version: 3.4.
|
130
|
+
rubygems_version: 3.4.6
|
117
131
|
signing_key:
|
118
132
|
specification_version: 4
|
119
133
|
summary: Deploy Rails apps in containers to servers running Docker with zero downtime.
|