mrsk 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|