mrsk 0.0.1 → 0.0.3

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: ce03312fed10073e6fd3d8fe805a1088ff759674368032ef27cdc88cf1ed5658
4
- data.tar.gz: e686e5c333552e06af7fedcf25a9d03ab55e0bfc12a92a40782fae4649917a8c
3
+ metadata.gz: 0f5260e6f41ed0ae04931d800b7c2da3b0bc32f0042d2e140fcb2860b1a9e12a
4
+ data.tar.gz: 0c94b286b4a9ce9339678a4a4662c65dcefd6947795227c2464db50e7bf727e0
5
5
  SHA512:
6
- metadata.gz: 5cb4e0842dccb3ebccc1e156e86151d471d3b6ab4f902a0e206f9942ab920df512cbe4c467bd052fc04aec89723c8af27bfdd70b1f6fb37d94a39e44d0fbcf03
7
- data.tar.gz: 79396a77da9f1d7b4bed0658f894f3bec0e8bbf6498f4d54b15b265e599a0d8594f46963b1064d483971b2840df5a4366f3cb880d675fd41433e89bde17f4cd6
6
+ metadata.gz: 0f980ac1468ac3581f1b8bd8206c2471b5e79d7bc84c274f1bef2a761ceccc25905308774770a04156e68e5b1fd73d8d83926d235d287a11118fb9ea8f7fb619
7
+ data.tar.gz: 720ac72cfd88a494049a237140e774b37288f3a142cb70e3a672a91aa7094a019c0eb938c3ca9a146cd64b1d33d811b2c4b8d0373d7b9457bfaf8499bfd86943
data/README.md CHANGED
@@ -1,55 +1,267 @@
1
1
  # MRSK
2
2
 
3
- MRSK lets you do zero-downtime deploys of Rails apps packed as containers to any host running Docker. It uses the dynamic reverse-proxy Traefik to hold requests while the new application container is started and the old one is wound down. It works across multiple hosts at the same time, using SSHKit to execute commands.
3
+ MRSK ships zero-downtime deploys of Rails apps packed as containers to any host. It uses the dynamic reverse-proxy Traefik to hold requests while the new application container is started and the old one is wound down. It works seamlessly across multiple hosts, using SSHKit to execute commands.
4
4
 
5
5
  ## Installation
6
6
 
7
- Create a configuration file for MRSK in `config/deploy.yml` that looks like this:
7
+ Add the gem with `bundle add mrsk`, then run `rake mrsk:init`, and then edit the new file in `config/deploy.yml`. It could look as simple as this:
8
8
 
9
9
  ```yaml
10
- service: my-app
11
- image: name/my-app
10
+ service: hey
11
+ image: 37s/hey
12
12
  servers:
13
- - xxx.xxx.xxx.xxx
14
- - xxx.xxx.xxx.xxx
15
- env:
16
- DATABASE_URL: mysql2://username@localhost/database_name/
17
- REDIS_URL: redis://host:6379/1
13
+ - 192.168.0.1
14
+ - 192.168.0.2
15
+ registry:
16
+ username: <%= Rails.application.credentials.registry["username"] %>
17
+ password: <%= Rails.application.credentials.registry["password"] %>
18
18
  ```
19
19
 
20
- Then first login to the Docker Hub registry on the servers:
20
+ Then ensure your encrypted credentials have the registry username + password by editing them with `rails credentials:edit`:
21
21
 
22
22
  ```
23
- rake mrsk:registry:login DOCKER_USER=name DOCKER_PASSWORD=pw
23
+ registry:
24
+ username: real-user-name
25
+ password: real-registry-password-or-token
24
26
  ```
25
27
 
26
- Now you're ready to deploy a multi-arch image (FIXME: currently you need to manually run `docker buildx create --use` once first):
28
+ Now you're ready to deploy a multi-arch image to the servers:
27
29
 
28
30
  ```
29
- rake mrsk:deploy
31
+ ./bin/mrsk deploy
30
32
  ```
31
33
 
32
34
  This will:
33
35
 
34
- 1. Build the image using the standard Dockerfile in the root of the application.
35
- 2. Push the image to the registry.
36
- 3. Pull the image on all the servers.
37
- 4. Ensure Traefik is running and accepting traffic on port 80.
38
- 5. Stop any containers running a previous versions of the app.
39
- 6. Start a new container with the version of the app that matches the current git version hash.
36
+ 1. Log into the registry both locally and remotely
37
+ 2. Build the image using the standard Dockerfile in the root of the application.
38
+ 3. Push the image to the registry.
39
+ 4. Pull the image from the registry on the servers.
40
+ 5. Ensure Traefik is running and accepting traffic on port 80.
41
+ 6. Stop any containers running a previous versions of the app.
42
+ 7. Start a new container with the version of the app that matches the current git version hash.
43
+ 8. Prune unused images and stopped containers to ensure servers don't fill up.
44
+
45
+ Voila! All the servers are now serving the app on port 80. If you're just running a single server, you're ready to go. If you're running multiple servers, you need to put a load balancer in front of them.
46
+
47
+ ## Configuration
48
+
49
+ ### Using another registry than Docker Hub
50
+
51
+ The default registry for Docker is Docker Hub. If you'd like to use a different one, just configure the server, like so:
52
+
53
+ ```yaml
54
+ registry:
55
+ server: registry.digitalocean.com
56
+ username: <%= Rails.application.credentials.registry["username"] %>
57
+ password: <%= Rails.application.credentials.registry["password"] %>
58
+ ```
59
+
60
+ ### Using a different SSH user than root
61
+
62
+ The default SSH user is root, but you can change it using `ssh_user`:
63
+
64
+ ```yaml
65
+ ssh_user: app
66
+ ```
67
+
68
+ ### Adding custom env variables
69
+
70
+ You can inject custom env variables into the app containers using `env`:
71
+
72
+ ```yaml
73
+ env:
74
+ DATABASE_URL: mysql2://db1/hey_production/
75
+ REDIS_URL: redis://redis1:6379/1
76
+ ```
77
+
78
+ ### Splitting servers into different roles
79
+
80
+ If your application uses separate hosts for running jobs or other roles beyond the default web running, you can specify these hosts and their custom entrypoint command like so:
81
+
82
+ ```yaml
83
+ servers:
84
+ web:
85
+ - 192.168.0.1
86
+ - 192.168.0.2
87
+ job:
88
+ hosts:
89
+ - 192.168.0.3
90
+ - 192.168.0.4
91
+ cmd: bin/jobs
92
+ ```
93
+
94
+ Traefik will only be installed and run on the servers in the `web` role (and on all servers if no roles are defined).
95
+
96
+ ### Adding custom container labels
97
+
98
+ You can specialize the default Traefik rules by setting custom labels on the containers that are being started:
99
+
100
+ ```
101
+ labels:
102
+ traefik.http.routers.hey.rule: '''Host(`app.hey.com`)'''
103
+ ```
104
+
105
+ (Note: The extra quotes are needed to ensure the rule is passed in correctly!)
106
+
107
+ This allows you to run multiple applications on the same server sharing the same Traefik instance and port.
108
+ See https://doc.traefik.io/traefik/routing/routers/#rule for a full list of available routing rules.
109
+
110
+ The labels can even be applied on a per-role basis:
111
+
112
+ ```yaml
113
+ servers:
114
+ web:
115
+ - 192.168.0.1
116
+ - 192.168.0.2
117
+ job:
118
+ hosts:
119
+ - 192.168.0.3
120
+ - 192.168.0.4
121
+ cmd: bin/jobs
122
+ labels:
123
+ my-custom-label: "50"
124
+ ```
125
+
126
+ ### Configuring remote builder for native multi-arch
127
+
128
+ If you're developing on ARM64 (like Apple Silicon), but you want to deploy on AMD64 (x86 64-bit), you have to use multi-archecture images. By default, MRSK will setup a local buildx configuration that allows for this through QEMU emulation. This can be slow, especially on the first build.
129
+
130
+ If you want to speed up this process by using a remote AMD64 host to natively build the AMD64 part of the image, while natively building the ARM64 part locally, you can do so using builder options like follows:
131
+
132
+ ```yaml
133
+ builder:
134
+ local:
135
+ arch: arm64
136
+ host: unix:///Users/dhh/.docker/run/docker.sock
137
+ remote:
138
+ arch: amd64
139
+ host: ssh://root@192.168.0.1
140
+ ```
141
+
142
+ Note: You must have Docker running on the remote host being used as a builder.
143
+
144
+ With that configuration in place, you can setup the local/remote configuration using `./bin/mrsk build:remote:create`. If you wish to remove the contexts and buildx instances again, you can run `./bin/mrsk build:remote:remove`. If you had already built using the standard emulation setup, run `./bin/mrsk build:remove` before doing `./bin/mrsk build:remote:create`.
145
+
146
+ ### Configuring native builder when multi-arch isn't needed
147
+
148
+ If you're developing on the same architecture as the one you're deploying on, you can speed up the build a lot by forgoing a multi-arch image. This can be done by configuring the builder like so:
149
+
150
+ ```yaml
151
+ builder:
152
+ multiarch: false
153
+ ```
154
+
155
+ ## Commands
156
+
157
+ ### Remote execution
158
+
159
+ If you need to execute commands inside the Rails containers, you can use `./bin/mrsk app:exec`, `./bin/mrsk app:exec:once`, `./bin/mrsk app:exec:rails`, and `./bin/mrsk app:exec:once:rails`. Examples:
160
+
161
+ ```bash
162
+ # Runs command on all servers
163
+ ./bin/mrsk app:exec CMD='ruby -v'
164
+ App Host: xxx.xxx.xxx.xxx
165
+ ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-linux]
166
+
167
+ App Host: xxx.xxx.xxx.xxx
168
+ ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-linux]
169
+
170
+ # Runs command on first server
171
+ ./bin/mrsk app:exec:once CMD='cat .ruby-version'
172
+ 3.1.3
173
+
174
+ # Runs Rails command on all servers
175
+ ./bin/mrsk app:exec:rails CMD=about
176
+ App Host: xxx.xxx.xxx.xxx
177
+ About your application's environment
178
+ Rails version 7.1.0.alpha
179
+ Ruby version ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-linux]
180
+ RubyGems version 3.3.26
181
+ Rack version 2.2.5
182
+ Middleware ActionDispatch::HostAuthorization, Rack::Sendfile, ActionDispatch::Static, ActionDispatch::Executor, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, ActionDispatch::RemoteIp, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::Callbacks, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ContentSecurityPolicy::Middleware, ActionDispatch::PermissionsPolicy::Middleware, Rack::Head, Rack::ConditionalGet, Rack::ETag, Rack::TempfileReaper
183
+ Application root /rails
184
+ Environment production
185
+ Database adapter sqlite3
186
+ Database schema version 20221231233303
187
+
188
+ App Host: xxx.xxx.xxx.xxx
189
+ About your application's environment
190
+ Rails version 7.1.0.alpha
191
+ Ruby version ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-linux]
192
+ RubyGems version 3.3.26
193
+ Rack version 2.2.5
194
+ Middleware ActionDispatch::HostAuthorization, Rack::Sendfile, ActionDispatch::Static, ActionDispatch::Executor, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, ActionDispatch::RemoteIp, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::Callbacks, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ContentSecurityPolicy::Middleware, ActionDispatch::PermissionsPolicy::Middleware, Rack::Head, Rack::ConditionalGet, Rack::ETag, Rack::TempfileReaper
195
+ Application root /rails
196
+ Environment production
197
+ Database adapter sqlite3
198
+ Database schema version 20221231233303
199
+
200
+ # Runs Rails command on first server
201
+ ./bin/mrsk app:exec:once:rails CMD='db:version'
202
+ database: storage/production.sqlite3
203
+ Current version: 20221231233303
204
+ ```
205
+
206
+ ### Running a Rails console on the primary host
207
+
208
+ If you need to interact with the production console for the app, you can use `./bin/mrsk app:console`, which will start a Rails console session on the primary host. Be mindful that this is a live wire! Any changes made to the production database will take effect immeditately.
209
+
210
+ ### Inspecting
211
+
212
+ You can see the state of your servers by running `./bin/mrsk info`. It'll show something like this:
213
+
214
+ ```
215
+ Traefik Host: xxx.xxx.xxx.xxx
216
+ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
217
+ 6195b2a28c81 traefik "/entrypoint.sh --pr…" 30 minutes ago Up 19 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp traefik
218
+
219
+ Traefik Host: 164.92.105.119
220
+ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
221
+ de14a335d152 traefik "/entrypoint.sh --pr…" 30 minutes ago Up 19 minutes 0.0.0.0:80->80/tcp, :::80->80/tcp traefik
222
+
223
+ App Host: 164.90.145.60
224
+ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
225
+ badb1aa51db3 registry.digitalocean.com/user/app:6ef8a6a84c525b123c5245345a8483f86d05a123 "/rails/bin/docker-e…" 13 minutes ago Up 13 minutes 3000/tcp chat-6ef8a6a84c525b123c5245345a8483f86d05a123
226
+
227
+ App Host: 164.92.105.119
228
+ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
229
+ 1d3c91ed1f55 registry.digitalocean.com/user/app:6ef8a6a84c525b123c5245345a8483f86d05a123 "/rails/bin/docker-e…" 13 minutes ago Up 13 minutes 3000/tcp chat-6ef8a6a84c525b123c5245345a8483f86d05a123
230
+ ```
231
+
232
+ You can also see just info for app containers with `./bin/mrsk app:info` or just for Traefik with `./bin/mrsk traefik:info`.
233
+
234
+ ### Rollback
235
+
236
+ If you've discovered a bad deploy, you can quickly rollback by reactivating the old, paused container image. You can see what old containers are available for rollback by running `./bin/mrsk app:containers`. It'll give you a presentation similar to `./bin/mrsk app:info`, but include all the old containers as well. Showing something like this:
237
+
238
+ ```
239
+ App Host: 164.92.105.119
240
+ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
241
+ 1d3c91ed1f51 registry.digitalocean.com/user/app:6ef8a6a84c525b123c5245345a8483f86d05a123 "/rails/bin/docker-e…" 19 minutes ago Up 19 minutes 3000/tcp chat-6ef8a6a84c525b123c5245345a8483f86d05a123
242
+ 539f26b28369 registry.digitalocean.com/user/app:e5d9d7c2b898289dfbc5f7f1334140d984eedae4 "/rails/bin/docker-e…" 31 minutes ago Exited (1) 27 minutes ago chat-e5d9d7c2b898289dfbc5f7f1334140d984eedae4
243
+
244
+ App Host: 164.90.145.60
245
+ CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
246
+ badb1aa51db4 registry.digitalocean.com/user/app:6ef8a6a84c525b123c5245345a8483f86d05a123 "/rails/bin/docker-e…" 19 minutes ago Up 19 minutes 3000/tcp chat-6ef8a6a84c525b123c5245345a8483f86d05a123
247
+ 6f170d1172ae registry.digitalocean.com/user/app:e5d9d7c2b898289dfbc5f7f1334140d984eedae4 "/rails/bin/docker-e…" 31 minutes ago Exited (1) 27 minutes ago chat-e5d9d7c2b898289dfbc5f7f1334140d984eedae4
248
+ ```
249
+
250
+ From the example above, we can see that `e5d9d7c2b898289dfbc5f7f1334140d984eedae4` was the last version, so it's available as a rollback target. We can perform this rollback by running `./bin/mrsk rollback VERSION=e5d9d7c2b898289dfbc5f7f1334140d984eedae4`. That'll stop `6ef8a6a84c525b123c5245345a8483f86d05a123` and then start `e5d9d7c2b898289dfbc5f7f1334140d984eedae4`. Because the old container is still available, this is very quick. Nothing to download from the registry.
251
+
252
+ Note that by default old containers are pruned after 3 days when you run `./bin/mrsk deploy`.
253
+
254
+ ### Removing
40
255
 
41
- Voila! All the servers are now serving the app on port 80, and you're ready to put them behind a load balancer to serve live traffic.
256
+ If you wish to remove the entire application, including Traefik, containers, images, and registry session, you can run `./bin/mrsk remove`. This will leave the servers clean.
42
257
 
43
258
  ## Stage of development
44
259
 
45
260
  This is alpha software. Lots of stuff is missing. Here are some of the areas we seek to improve:
46
261
 
47
- - Use of other registries than Docker Hub
48
262
  - Adapterize commands to work with Podman and other container runners
49
- - Better flow for secrets and ENV
50
- - Possibly switching to a bin/mrsk command rather than raw rake
51
- - Integrate wirmth cloud CI pipelines
263
+ - Integrate with cloud CI pipelines
52
264
 
53
265
  ## License
54
266
 
55
- Mrsk is released under the [MIT License](https://opensource.org/licenses/MIT).
267
+ MRSK is released under the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,56 @@
1
+ require "mrsk/configuration"
2
+ require "mrsk/commands/app"
3
+ require "mrsk/commands/builder"
4
+ require "mrsk/commands/prune"
5
+ require "mrsk/commands/traefik"
6
+ require "mrsk/commands/registry"
7
+
8
+ class Mrsk::Commander
9
+ attr_reader :config_file, :config, :verbose
10
+
11
+ def initialize(config_file:, verbose: false)
12
+ @config_file, @verbose = config_file, verbose
13
+ end
14
+
15
+ def config
16
+ @config ||= Mrsk::Configuration.load_file(config_file).tap { |config| setup_with(config) }
17
+ end
18
+
19
+
20
+ def app
21
+ @app ||= Mrsk::Commands::App.new(config)
22
+ end
23
+
24
+ def builder
25
+ @builder ||= Mrsk::Commands::Builder.new(config)
26
+ end
27
+
28
+ def traefik
29
+ @traefik ||= Mrsk::Commands::Traefik.new(config)
30
+ end
31
+
32
+ def registry
33
+ @registry ||= Mrsk::Commands::Registry.new(config)
34
+ end
35
+
36
+ def prune
37
+ @prune ||= Mrsk::Commands::Prune.new(config)
38
+ end
39
+
40
+
41
+ def verbosity(level)
42
+ old_level = SSHKit.config.output_verbosity
43
+ SSHKit.config.output_verbosity = level
44
+ yield
45
+ ensure
46
+ SSHKit.config.output_verbosity = old_level
47
+ end
48
+
49
+ private
50
+ # Lazy setup of SSHKit
51
+ def setup_with(config)
52
+ SSHKit::Backend::Netssh.configure { |ssh| ssh.ssh_options = config.ssh_options }
53
+ SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
54
+ SSHKit.config.output_verbosity = :debug if verbose
55
+ end
56
+ end
@@ -1,22 +1,63 @@
1
+ require "mrsk/commands/base"
2
+
1
3
  class Mrsk::Commands::App < Mrsk::Commands::Base
2
- def push
3
- # TODO: Run 'docker buildx create --use' when needed
4
- "docker buildx build --push --platform=linux/amd64,linux/arm64 -t #{config.absolute_image} ."
5
- end
4
+ def run(role: :web)
5
+ role = config.role(role)
6
6
 
7
- def pull
8
- "docker pull #{config.absolute_image}"
7
+ docker :run,
8
+ "-d",
9
+ "--restart unless-stopped",
10
+ "--name", config.service_with_version,
11
+ "-e", redact("RAILS_MASTER_KEY=#{config.master_key}"),
12
+ *config.env_args,
13
+ *role.label_args,
14
+ config.absolute_image,
15
+ role.cmd
9
16
  end
10
17
 
11
18
  def start
12
- "docker run -d --rm --name #{config.service_with_version} #{config.envs} #{config.labels} #{config.absolute_image}"
19
+ docker :start, config.service_with_version
13
20
  end
14
21
 
15
22
  def stop
16
- "docker ps -q --filter label=service=#{config.service} | xargs docker stop"
23
+ [ "docker ps -q #{service_filter.join(" ")} | xargs docker stop" ]
17
24
  end
18
25
 
19
26
  def info
20
- "docker ps --filter label=service=#{config.service}"
27
+ docker :ps, *service_filter
28
+ end
29
+
30
+ def logs
31
+ [ "docker ps -q #{service_filter.join(" ")} | xargs docker logs -n 100 -t" ]
32
+ end
33
+
34
+ def exec(*command, interactive: false)
35
+ docker :exec,
36
+ ("-it" if interactive),
37
+ "-e", redact("RAILS_MASTER_KEY=#{config.master_key}"),
38
+ *config.env_args,
39
+ config.service_with_version,
40
+ *command
41
+ end
42
+
43
+ def console
44
+ "ssh -t #{config.ssh_user}@#{config.primary_host} '#{exec("bin/rails", "c", interactive: true).join(" ")}'"
21
45
  end
46
+
47
+ def list_containers
48
+ docker :container, :ls, "-a", *service_filter
49
+ end
50
+
51
+ def remove_containers
52
+ docker :container, :prune, "-f", *service_filter
53
+ end
54
+
55
+ def remove_images
56
+ docker :image, :prune, "-a", "-f", *service_filter
57
+ end
58
+
59
+ private
60
+ def service_filter
61
+ [ "--filter", "label=service=#{config.service}" ]
62
+ end
22
63
  end
@@ -0,0 +1,27 @@
1
+ require "sshkit"
2
+
3
+ module Mrsk::Commands
4
+ class Base
5
+ attr_accessor :config
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ end
10
+
11
+ private
12
+ def combine(*commands)
13
+ commands
14
+ .collect { |command| command + [ "&&" ] }.flatten # Join commands with &&
15
+ .tap { |commands| commands.pop } # Remove trailing &&
16
+ end
17
+
18
+ def docker(*args)
19
+ args.compact.unshift :docker
20
+ end
21
+
22
+ # Copied from SSHKit::Backend::Abstract#redact to be available inside Commands classes
23
+ def redact(arg) # Used in execute_command to hide redact() args a user passes in
24
+ arg.to_s.extend(SSHKit::Redaction) # to_s due to our inability to extend Integer, etc
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,53 @@
1
+ require "mrsk/commands/builder/multiarch"
2
+
3
+ class Mrsk::Commands::Builder::Multiarch::Remote < Mrsk::Commands::Builder::Multiarch
4
+ def create
5
+ combine \
6
+ create_contexts,
7
+ create_local_buildx,
8
+ append_remote_buildx
9
+ end
10
+
11
+ def remove
12
+ combine \
13
+ remove_contexts,
14
+ super
15
+ end
16
+
17
+ private
18
+ def create_local_buildx
19
+ docker :buildx, :create, "--use", "--name", "mrsk", "mrsk-#{local["arch"]}", "--platform", "linux/#{local["arch"]}"
20
+ end
21
+
22
+ def append_remote_buildx
23
+ docker :buildx, :create, "--append", "--name", "mrsk", "mrsk-#{remote["arch"]}", "--platform", "linux/#{remote["arch"]}"
24
+ end
25
+
26
+ def create_contexts
27
+ combine \
28
+ create_context(local["arch"], local["host"]),
29
+ create_context(remote["arch"], remote["host"])
30
+ end
31
+
32
+ def create_context(arch, host)
33
+ docker :context, :create, "mrsk-#{arch}", "--description", "'MRSK #{arch} Native Host'", "--docker", "'host=#{host}'"
34
+ end
35
+
36
+ def remove_contexts
37
+ combine \
38
+ remove_context(local["arch"]),
39
+ remove_context(remote["arch"])
40
+ end
41
+
42
+ def remove_context(arch)
43
+ docker :context, :rm, "mrsk-#{arch}"
44
+ end
45
+
46
+ def local
47
+ config.builder["local"]
48
+ end
49
+
50
+ def remote
51
+ config.builder["remote"]
52
+ end
53
+ end
@@ -0,0 +1,25 @@
1
+ require "mrsk/commands/base"
2
+
3
+ class Mrsk::Commands::Builder::Multiarch < Mrsk::Commands::Base
4
+ def create
5
+ docker :buildx, :create, "--use", "--name", "mrsk"
6
+ end
7
+
8
+ def remove
9
+ docker :buildx, :rm, "mrsk"
10
+ end
11
+
12
+ def push
13
+ docker :buildx, :build, "--push", "--platform linux/amd64,linux/arm64", "-t", config.absolute_image, "."
14
+ end
15
+
16
+ def pull
17
+ docker :pull, config.absolute_image
18
+ end
19
+
20
+ def info
21
+ combine \
22
+ docker(:context, :ls),
23
+ docker(:buildx, :ls)
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require "mrsk/commands/base"
2
+
3
+ class Mrsk::Commands::Builder::Native < Mrsk::Commands::Base
4
+ def create
5
+ # No-op on native
6
+ end
7
+
8
+ def remove
9
+ # No-op on native
10
+ end
11
+
12
+ def push
13
+ combine \
14
+ docker(:build, "-t", config.absolute_image, "."),
15
+ docker(:push, config.absolute_image)
16
+ end
17
+
18
+ def pull
19
+ docker :pull, config.absolute_image
20
+ end
21
+
22
+ def info
23
+ # No-op on native
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ require "mrsk/commands/base"
2
+
3
+ class Mrsk::Commands::Builder < Mrsk::Commands::Base
4
+ delegate :create, :remove, :push, :pull, :info, to: :target
5
+ delegate :native?, :multiarch?, :remote?, to: :name
6
+
7
+ def name
8
+ target.class.to_s.demodulize.downcase.inquiry
9
+ end
10
+
11
+ def target
12
+ case
13
+ when config.builder.nil?
14
+ multiarch
15
+ when config.builder["multiarch"] == false
16
+ native
17
+ when config.builder["local"] && config.builder["local"]
18
+ multiarch_remote
19
+ else
20
+ raise ArgumentError, "Builder configuration incorrect: #{config.builder.inspect}"
21
+ end
22
+ end
23
+
24
+ def native
25
+ @native ||= Mrsk::Commands::Builder::Native.new(config)
26
+ end
27
+
28
+ def multiarch
29
+ @multiarch ||= Mrsk::Commands::Builder::Multiarch.new(config)
30
+ end
31
+
32
+ def multiarch_remote
33
+ @multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config)
34
+ end
35
+ end
36
+
37
+ require "mrsk/commands/builder/native"
38
+ require "mrsk/commands/builder/multiarch"
39
+ require "mrsk/commands/builder/multiarch/remote"
@@ -0,0 +1,17 @@
1
+ require "mrsk/commands/base"
2
+ require "active_support/duration"
3
+ require "active_support/core_ext/numeric/time"
4
+
5
+ class Mrsk::Commands::Prune < Mrsk::Commands::Base
6
+ PRUNE_IMAGES_AFTER = 30.days.in_hours.to_i
7
+ PRUNE_CONTAINERS_AFTER = 3.days.in_hours.to_i
8
+
9
+ def images
10
+ docker :image, :prune, "-f", "--filter", "until=#{PRUNE_IMAGES_AFTER}h"
11
+ end
12
+
13
+ def containers
14
+ docker :image, :prune, "-f", "--filter", "until=#{PRUNE_IMAGES_AFTER}h"
15
+ docker :container, :prune, "-f", "--filter", "label=service=#{config.service}", "--filter", "'until=#{PRUNE_CONTAINERS_AFTER}h'"
16
+ end
17
+ end
@@ -1,5 +1,13 @@
1
+ require "mrsk/commands/base"
2
+
1
3
  class Mrsk::Commands::Registry < Mrsk::Commands::Base
4
+ delegate :registry, to: :config
5
+
2
6
  def login
3
- "docker login #{config.registry["server"]} -u #{config.registry["username"]} -p #{config.registry["password"]}"
7
+ docker :login, registry["server"], "-u", redact(registry["username"]), "-p", redact(registry["password"])
8
+ end
9
+
10
+ def logout
11
+ docker :logout, registry["server"]
4
12
  end
5
13
  end