mrsk 0.7.2 → 0.8.1

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: 5d90c03e2bcea1e39e38d773fd79d4154f24ec3297c5e31d0240e23d25adc6af
4
- data.tar.gz: 68aac28eaef8b0246a4cef80d18da9f37069af8740cde1dd7505ef7bad7df8f1
3
+ metadata.gz: 0a560b5048509237f8c8440a41b35d674915ac493bcf0176256b1006a6aef004
4
+ data.tar.gz: d69f0682c43e62fd0574d626456bcb62452ec972b8dd1182b1780958a65aadbd
5
5
  SHA512:
6
- metadata.gz: 1d491ed1430092b12de3e3a4e765f8da11f4a68c62e86b280368a96389829e1fd0b0238ff11f4811f974dc5aee3e166a5cff9cc3e85ce3ad11e34e4d37def484
7
- data.tar.gz: 6e876c4c838c8ca8bcd28f1aa465d97190f6580bcad9942d2c6149346163c1289df18903cdb129c34715a06927d5945dcd988751ec2da397cf6c313781eb0911
6
+ metadata.gz: d670dd54f94d64b2e35f5082f3a5cd78100780b0e0c5d51d050dc25f00ca4cdca26d0a4d797c01860d04205a2cd0fa4285089a944f72977d287e1c6fd313845e
7
+ data.tar.gz: 6d28b436a2a0d33da1e32af340e5cd80972b57b946b6b51ec6481b5c832020867075ac53f24dd53f0d86e0d1738a78fb233986530a4ad0d9be1e83e73136522b
data/README.md CHANGED
@@ -14,7 +14,8 @@ servers:
14
14
  - 192.168.0.2
15
15
  registry:
16
16
  username: registry-user-name
17
- password: <%= ENV.fetch("MRSK_REGISTRY_PASSWORD") %>
17
+ password:
18
+ - MRSK_REGISTRY_PASSWORD
18
19
  env:
19
20
  secret:
20
21
  - RAILS_MASTER_KEY
@@ -447,7 +448,7 @@ mrsk app exec -i 'bin/rails console'
447
448
  ```
448
449
 
449
450
 
450
- ### Running details to see state of containers
451
+ ### Running details to show state of containers
451
452
 
452
453
  You can see the state of your servers by running `mrsk details`:
453
454
 
@@ -1,5 +1,5 @@
1
1
  class Mrsk::Cli::Accessory < Mrsk::Cli::Base
2
- desc "boot [NAME]", "Boot accessory service on host (use NAME=all to boot all accessories)"
2
+ desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
3
3
  def boot(name)
4
4
  if name == "all"
5
5
  MRSK.accessory_names.each { |accessory_name| boot(accessory_name) }
@@ -18,7 +18,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
18
18
  end
19
19
  end
20
20
 
21
- desc "upload [NAME]", "Upload accessory files to host"
21
+ desc "upload [NAME]", "Upload accessory files to host", hide: true
22
22
  def upload(name)
23
23
  with_accessory(name) do |accessory|
24
24
  on(accessory.host) do
@@ -33,7 +33,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
33
33
  end
34
34
  end
35
35
 
36
- desc "directories [NAME]", "Create accessory directories on host"
36
+ desc "directories [NAME]", "Create accessory directories on host", hide: true
37
37
  def directories(name)
38
38
  with_accessory(name) do |accessory|
39
39
  on(accessory.host) do
@@ -44,7 +44,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
44
44
  end
45
45
  end
46
46
 
47
- desc "reboot [NAME]", "Reboot accessory on host (stop container, remove container, start new container)"
47
+ desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container)"
48
48
  def reboot(name)
49
49
  with_accessory(name) do |accessory|
50
50
  stop(name)
@@ -53,7 +53,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
53
53
  end
54
54
  end
55
55
 
56
- desc "start [NAME]", "Start existing accessory on host"
56
+ desc "start [NAME]", "Start existing accessory container on host"
57
57
  def start(name)
58
58
  with_accessory(name) do |accessory|
59
59
  on(accessory.host) do
@@ -63,7 +63,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
63
63
  end
64
64
  end
65
65
 
66
- desc "stop [NAME]", "Stop accessory on host"
66
+ desc "stop [NAME]", "Stop existing accessory container on host"
67
67
  def stop(name)
68
68
  with_accessory(name) do |accessory|
69
69
  on(accessory.host) do
@@ -73,7 +73,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
73
73
  end
74
74
  end
75
75
 
76
- desc "restart [NAME]", "Restart accessory on host"
76
+ desc "restart [NAME]", "Restart existing accessory container on host"
77
77
  def restart(name)
78
78
  with_accessory(name) do
79
79
  stop(name)
@@ -81,7 +81,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
81
81
  end
82
82
  end
83
83
 
84
- desc "details [NAME]", "Display details about accessory on host (use NAME=all to boot all accessories)"
84
+ desc "details [NAME]", "Show details about accessory on host (use NAME=all to show all accessories)"
85
85
  def details(name)
86
86
  if name == "all"
87
87
  MRSK.accessory_names.each { |accessory_name| details(accessory_name) }
@@ -92,7 +92,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
92
92
  end
93
93
  end
94
94
 
95
- desc "exec [NAME] [CMD]", "Execute a custom command on servers"
95
+ desc "exec [NAME] [CMD]", "Execute a custom command on servers (use --help to show options)"
96
96
  option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
97
97
  option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
98
98
  def exec(name, cmd)
@@ -123,7 +123,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
123
123
  end
124
124
  end
125
125
 
126
- desc "logs [NAME]", "Show log lines from accessory on host"
126
+ desc "logs [NAME]", "Show log lines from accessory on host (use --help to show options)"
127
127
  option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
128
128
  option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
129
129
  option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
@@ -149,21 +149,26 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
149
149
  end
150
150
  end
151
151
 
152
- desc "remove [NAME]", "Remove accessory container and image from host (use NAME=all to boot all accessories)"
152
+ desc "remove [NAME]", "Remove accessory container and image from host (use NAME=all to remove all accessories)"
153
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
153
154
  def remove(name)
154
155
  if name == "all"
155
- MRSK.accessory_names.each { |accessory_name| remove(accessory_name) }
156
+ if options[:confirmed] || ask("This will remove all containers and images for all accessories. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
157
+ MRSK.accessory_names.each { |accessory_name| remove(accessory_name) }
158
+ end
156
159
  else
157
- with_accessory(name) do
158
- stop(name)
159
- remove_container(name)
160
- remove_image(name)
161
- remove_service_directory(name)
160
+ if options[:confirmed] || ask("This will remove all containers and images for #{name}. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
161
+ with_accessory(name) do
162
+ stop(name)
163
+ remove_container(name)
164
+ remove_image(name)
165
+ remove_service_directory(name)
166
+ end
162
167
  end
163
168
  end
164
169
  end
165
170
 
166
- desc "remove_container [NAME]", "Remove accessory container from host"
171
+ desc "remove_container [NAME]", "Remove accessory container from host", hide: true
167
172
  def remove_container(name)
168
173
  with_accessory(name) do |accessory|
169
174
  on(accessory.host) do
@@ -173,7 +178,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
173
178
  end
174
179
  end
175
180
 
176
- desc "remove_image [NAME]", "Remove accessory image from host"
181
+ desc "remove_image [NAME]", "Remove accessory image from host", hide: true
177
182
  def remove_image(name)
178
183
  with_accessory(name) do |accessory|
179
184
  on(accessory.host) do
@@ -183,7 +188,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
183
188
  end
184
189
  end
185
190
 
186
- desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host"
191
+ desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
187
192
  def remove_service_directory(name)
188
193
  with_accessory(name) do |accessory|
189
194
  on(accessory.host) do
data/lib/mrsk/cli/app.rb CHANGED
@@ -28,7 +28,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
28
28
  end
29
29
  end
30
30
 
31
- desc "start", "Start existing app on servers (use --version=<git-hash> to designate specific version)"
31
+ desc "start", "Start existing app container on servers"
32
32
  def start
33
33
  on(MRSK.hosts) do
34
34
  execute *MRSK.auditor.record("Started app version #{MRSK.version}"), verbosity: :debug
@@ -36,7 +36,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
36
36
  end
37
37
  end
38
38
 
39
- desc "stop", "Stop app on servers"
39
+ desc "stop", "Stop app container on servers"
40
40
  def stop
41
41
  on(MRSK.hosts) do
42
42
  execute *MRSK.auditor.record("Stopped app"), verbosity: :debug
@@ -44,12 +44,13 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
44
44
  end
45
45
  end
46
46
 
47
- desc "details", "Display details about app containers"
47
+ # FIXME: Drop in favor of just containers?
48
+ desc "details", "Show details about app containers"
48
49
  def details
49
50
  on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.info) }
50
51
  end
51
52
 
52
- desc "exec [CMD]", "Execute a custom command on servers"
53
+ desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)"
53
54
  option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
54
55
  option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
55
56
  def exec(cmd)
@@ -91,21 +92,21 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
91
92
  end
92
93
  end
93
94
 
94
- desc "containers", "List all the app containers currently on servers"
95
+ desc "containers", "Show app containers on servers"
95
96
  def containers
96
97
  on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_containers) }
97
98
  end
98
99
 
99
- desc "images", "List all the app images currently on servers"
100
+ desc "images", "Show app images on servers"
100
101
  def images
101
102
  on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_images) }
102
103
  end
103
104
 
104
- desc "logs", "Show lines from app on servers"
105
- option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
106
- option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
105
+ desc "logs", "Show log lines from app on servers (use --help to show options)"
106
+ option :since, aliases: "-s", desc: "Show lines since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
107
+ option :lines, type: :numeric, aliases: "-n", desc: "Number of lines to show from each server"
107
108
  option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
108
- option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
109
+ option :follow, aliases: "-f", desc: "Follow log on primary server (or specific host set by --hosts)"
109
110
  def logs
110
111
  # FIXME: Catch when app containers aren't running
111
112
 
@@ -133,11 +134,12 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
133
134
 
134
135
  desc "remove", "Remove app containers and images from servers"
135
136
  def remove
137
+ stop
136
138
  remove_containers
137
139
  remove_images
138
140
  end
139
141
 
140
- desc "remove_container [VERSION]", "Remove app container with given version from servers"
142
+ desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
141
143
  def remove_container(version)
142
144
  on(MRSK.hosts) do
143
145
  execute *MRSK.auditor.record("Removed app container with version #{version}"), verbosity: :debug
@@ -145,7 +147,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
145
147
  end
146
148
  end
147
149
 
148
- desc "remove_containers", "Remove all app containers from servers"
150
+ desc "remove_containers", "Remove all app containers from servers", hide: true
149
151
  def remove_containers
150
152
  on(MRSK.hosts) do
151
153
  execute *MRSK.auditor.record("Removed all app containers"), verbosity: :debug
@@ -153,7 +155,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
153
155
  end
154
156
  end
155
157
 
156
- desc "remove_images", "Remove all app images from servers"
158
+ desc "remove_images", "Remove all app images from servers", hide: true
157
159
  def remove_images
158
160
  on(MRSK.hosts) do
159
161
  execute *MRSK.auditor.record("Removed all app images"), verbosity: :debug
@@ -161,8 +163,8 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
161
163
  end
162
164
  end
163
165
 
164
- desc "current_version", "Shows the version currently running"
165
- def current_version
166
+ desc "version", "Show app version currently running on servers"
167
+ def version
166
168
  on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_running_version).strip }
167
169
  end
168
170
 
@@ -184,7 +186,12 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
184
186
  def most_recent_version_available(host: MRSK.primary_host)
185
187
  version = nil
186
188
  on(host) { version = capture_with_info(*MRSK.app.most_recent_version_from_available_images).strip }
187
- version.presence
189
+
190
+ if version == "<none>"
191
+ raise "Most recent image available was not tagged with a version (returned <none>)"
192
+ else
193
+ version.presence
194
+ end
188
195
  end
189
196
 
190
197
  def current_running_version(host: MRSK.primary_host)
data/lib/mrsk/cli/base.rb CHANGED
@@ -17,8 +17,8 @@ module Mrsk::Cli
17
17
  class_option :hosts, aliases: "-h", desc: "Run commands on these hosts instead of all (separate by comma)"
18
18
  class_option :roles, aliases: "-r", desc: "Run commands on these roles instead of all (separate by comma)"
19
19
 
20
- class_option :config_file, aliases: "-c", default: "config/deploy.yml", desc: "Path to config file (default: config/deploy.yml)"
21
- class_option :destination, aliases: "-d", desc: "Specify destination to be used for config file (west -> deploy.west.yml)"
20
+ class_option :config_file, aliases: "-c", default: "config/deploy.yml", desc: "Path to config file"
21
+ class_option :destination, aliases: "-d", desc: "Specify destination to be used for config file (staging -> deploy.staging.yml)"
22
22
 
23
23
  def initialize(*)
24
24
  super
@@ -1,11 +1,11 @@
1
1
  class Mrsk::Cli::Build < Mrsk::Cli::Base
2
- desc "deliver", "Deliver a newly built app image to servers"
2
+ desc "deliver", "Build app and push app image to registry then pull image on servers"
3
3
  def deliver
4
- invoke :push
5
- invoke :pull
4
+ push
5
+ pull
6
6
  end
7
7
 
8
- desc "push", "Build locally and push app image to registry"
8
+ desc "push", "Build and push app image to registry"
9
9
  def push
10
10
  cli = self
11
11
 
@@ -26,15 +26,16 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
26
26
  end
27
27
  end
28
28
 
29
- desc "pull", "Pull app image from the registry onto servers"
29
+ desc "pull", "Pull app image from registry onto servers"
30
30
  def pull
31
31
  on(MRSK.hosts) do
32
32
  execute *MRSK.auditor.record("Pulled image with version #{MRSK.version}"), verbosity: :debug
33
+ execute *MRSK.builder.clean, raise_on_non_zero_exit: false
33
34
  execute *MRSK.builder.pull
34
35
  end
35
36
  end
36
37
 
37
- desc "create", "Create a local build setup"
38
+ desc "create", "Create a build setup"
38
39
  def create
39
40
  run_locally do
40
41
  begin
@@ -51,7 +52,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
51
52
  end
52
53
  end
53
54
 
54
- desc "remove", "Remove local build setup"
55
+ desc "remove", "Remove build setup"
55
56
  def remove
56
57
  run_locally do
57
58
  debug "Using builder: #{MRSK.builder.name}"
@@ -59,7 +60,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
59
60
  end
60
61
  end
61
62
 
62
- desc "details", "Show the name of the configured builder"
63
+ desc "details", "Show build setup"
63
64
  def details
64
65
  run_locally do
65
66
  puts "Builder: #{MRSK.builder.name}"
@@ -1,21 +1,40 @@
1
1
  class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
2
- desc "perform", "Health check the current version of the app"
2
+ MAX_ATTEMPTS = 5
3
+
4
+ default_command :perform
5
+
6
+ desc "perform", "Health check current app version"
3
7
  def perform
4
8
  on(MRSK.primary_host) do
5
9
  begin
6
10
  execute *MRSK.healthcheck.run
7
11
 
8
12
  target = "Health check against #{MRSK.config.healthcheck["path"]}"
13
+ attempt = 1
9
14
 
10
- if capture_with_info(*MRSK.healthcheck.curl) == "200"
11
- info "#{target} succeeded with 200 OK!"
12
- else
13
- # Catches 1xx, 2xx, 3xx
14
- raise SSHKit::Command::Failed, "#{target} failed to return 200 OK!"
15
+ begin
16
+ status = capture_with_info(*MRSK.healthcheck.curl)
17
+
18
+ if status == "200"
19
+ info "#{target} succeeded with 200 OK!"
20
+ else
21
+ raise "#{target} failed with status #{status}"
22
+ end
23
+ rescue SSHKit::Command::Failed
24
+ if attempt <= MAX_ATTEMPTS
25
+ info "#{target} failed to respond, retrying in #{attempt}s..."
26
+ sleep attempt
27
+ attempt += 1
28
+
29
+ retry
30
+ else
31
+ raise
32
+ end
15
33
  end
16
34
  rescue SSHKit::Command::Failed => e
35
+ error capture_with_info(*MRSK.healthcheck.logs)
36
+
17
37
  if e.message =~ /curl/
18
- # Catches 4xx, 5xx
19
38
  raise SSHKit::Command::Failed, "#{target} failed to return 200 OK!"
20
39
  else
21
40
  raise
data/lib/mrsk/cli/main.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  class Mrsk::Cli::Main < Mrsk::Cli::Base
2
- desc "setup", "Setup all accessories and deploy the app to servers"
2
+ desc "setup", "Setup all accessories and deploy app to servers"
3
3
  def setup
4
4
  print_runtime do
5
5
  invoke "mrsk:cli:server:bootstrap"
@@ -8,7 +8,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
8
8
  end
9
9
  end
10
10
 
11
- desc "deploy", "Deploy the app to servers"
11
+ desc "deploy", "Deploy app to servers"
12
12
  def deploy
13
13
  runtime = print_runtime do
14
14
  say "Ensure Docker is installed...", :magenta
@@ -35,7 +35,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
35
35
  audit_broadcast "Deployed app in #{runtime.to_i} seconds"
36
36
  end
37
37
 
38
- desc "redeploy", "Deploy new version of the app to servers (without bootstrapping servers, starting Traefik, pruning, and registry login)"
38
+ desc "redeploy", "Deploy app to servers without bootstrapping servers, starting Traefik, pruning, and registry login"
39
39
  def redeploy
40
40
  runtime = print_runtime do
41
41
  say "Build and push app image...", :magenta
@@ -50,7 +50,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
50
50
  audit_broadcast "Redeployed app in #{runtime.to_i} seconds"
51
51
  end
52
52
 
53
- desc "rollback [VERSION]", "Rollback the app to VERSION"
53
+ desc "rollback [VERSION]", "Rollback app to VERSION"
54
54
  def rollback(version)
55
55
  MRSK.version = version
56
56
 
@@ -68,7 +68,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
68
68
  end
69
69
  end
70
70
 
71
- desc "details", "Display details about Traefik and app containers"
71
+ desc "details", "Show details about all containers"
72
72
  def details
73
73
  invoke "mrsk:cli:traefik:details"
74
74
  invoke "mrsk:cli:app:details"
@@ -82,7 +82,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
82
82
  end
83
83
  end
84
84
 
85
- desc "config", "Show combined config"
85
+ desc "config", "Show combined config (including secrets!)"
86
86
  def config
87
87
  run_locally do
88
88
  puts MRSK.config.to_h.to_yaml
@@ -132,40 +132,44 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
132
132
  File.write(env_path, ERB.new(File.read(env_template_path)).result, perm: 0600)
133
133
  end
134
134
 
135
- desc "remove", "Remove Traefik, app, and registry session from servers"
135
+ desc "remove", "Remove Traefik, app, accessories, and registry session from servers"
136
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
136
137
  def remove
137
- invoke "mrsk:cli:traefik:remove"
138
- invoke "mrsk:cli:app:remove"
139
- invoke "mrsk:cli:registry:logout"
138
+ if options[:confirmed] || ask(remove_confirmation_question, limited_to: %w( y N ), default: "N") == "y"
139
+ invoke "mrsk:cli:traefik:remove", [], options.without(:confirmed)
140
+ invoke "mrsk:cli:app:remove", [], options.without(:confirmed)
141
+ invoke "mrsk:cli:accessory:remove", [ "all" ]
142
+ invoke "mrsk:cli:registry:logout", [], options.without(:confirmed)
143
+ end
140
144
  end
141
145
 
142
- desc "version", "Display the MRSK version"
146
+ desc "version", "Show MRSK version"
143
147
  def version
144
148
  puts Mrsk::VERSION
145
149
  end
146
150
 
147
- desc "accessory", "Manage the accessories"
151
+ desc "accessory", "Manage accessories (db/redis/search)"
148
152
  subcommand "accessory", Mrsk::Cli::Accessory
149
153
 
150
- desc "app", "Manage the application"
154
+ desc "app", "Manage application"
151
155
  subcommand "app", Mrsk::Cli::App
152
156
 
153
- desc "build", "Build the application image"
157
+ desc "build", "Build application image"
154
158
  subcommand "build", Mrsk::Cli::Build
155
159
 
156
- desc "healthcheck", "Healthcheck the application"
160
+ desc "healthcheck", "Healthcheck application"
157
161
  subcommand "healthcheck", Mrsk::Cli::Healthcheck
158
162
 
159
163
  desc "prune", "Prune old application images and containers"
160
164
  subcommand "prune", Mrsk::Cli::Prune
161
165
 
162
- desc "registry", "Login and out of the image registry"
166
+ desc "registry", "Login and -out of the image registry"
163
167
  subcommand "registry", Mrsk::Cli::Registry
164
168
 
165
169
  desc "server", "Bootstrap servers with Docker"
166
170
  subcommand "server", Mrsk::Cli::Server
167
171
 
168
- desc "traefik", "Manage the Traefik load balancer"
172
+ desc "traefik", "Manage Traefik load balancer"
169
173
  subcommand "traefik", Mrsk::Cli::Traefik
170
174
 
171
175
  private
@@ -174,4 +178,10 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
174
178
  on(host) { container_names = capture_with_info(*MRSK.app.list_container_names).split("\n") }
175
179
  Array(container_names).include?(container_name)
176
180
  end
181
+
182
+ def remove_confirmation_question
183
+ "This will remove all containers and images. " +
184
+ (MRSK.config.accessories.any? ? "Including #{MRSK.config.accessories.collect(&:name).to_sentence}. " : "") +
185
+ "Are you sure?"
186
+ end
177
187
  end
@@ -1,8 +1,8 @@
1
1
  class Mrsk::Cli::Prune < Mrsk::Cli::Base
2
2
  desc "all", "Prune unused images and stopped containers"
3
3
  def all
4
- invoke :containers
5
- invoke :images
4
+ containers
5
+ images
6
6
  end
7
7
 
8
8
  desc "images", "Prune unused images older than 7 days"
@@ -13,7 +13,7 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
13
13
  end
14
14
  end
15
15
 
16
- desc "containers", "Prune stopped containers for the service older than 3 days"
16
+ desc "containers", "Prune stopped containers older than 3 days"
17
17
  def containers
18
18
  on(MRSK.hosts) do
19
19
  execute *MRSK.auditor.record("Pruned containers"), verbosity: :debug
@@ -1,15 +1,17 @@
1
1
  class Mrsk::Cli::Registry < Mrsk::Cli::Base
2
- desc "login", "Login to the registry locally and remotely"
2
+ desc "login", "Log in to registry locally and remotely"
3
3
  def login
4
4
  run_locally { execute *MRSK.registry.login }
5
5
  on(MRSK.hosts) { execute *MRSK.registry.login }
6
+ # FIXME: This rescue needed?
6
7
  rescue ArgumentError => e
7
8
  puts e.message
8
9
  end
9
10
 
10
- desc "logout", "Logout of the registry remotely"
11
+ desc "logout", "Log out of registry remotely"
11
12
  def logout
12
13
  on(MRSK.hosts) { execute *MRSK.registry.logout }
14
+ # FIXME: This rescue needed?
13
15
  rescue ArgumentError => e
14
16
  puts e.message
15
17
  end
@@ -1,5 +1,5 @@
1
1
  class Mrsk::Cli::Server < Mrsk::Cli::Base
2
- desc "bootstrap", "Ensure Docker is installed on the servers"
2
+ desc "bootstrap", "Ensure Docker is installed on servers"
3
3
  def bootstrap
4
4
  on(MRSK.hosts + MRSK.accessory_hosts) { execute "which docker || (apt-get update -y && apt-get install docker.io -y)" }
5
5
  end
@@ -1,5 +1,4 @@
1
- # Name of your application. Used to uniquely configuring Traefik and app containers.
2
- # Your Dockerfile should set LABEL service=the-same-value to ensure image pruning works.
1
+ # Name of your application. Used to uniquely configure containers.
3
2
  service: my-app
4
3
 
5
4
  # Name of the container image.
@@ -14,4 +13,64 @@ registry:
14
13
  # Specify the registry server, if you're not using Docker Hub
15
14
  # server: registry.digitalocean.com / ghcr.io / ...
16
15
  username: my-user
17
- password: my-password-should-go-somewhere-safe
16
+ password:
17
+ - MRSK_REGISTRY_PASSWORD
18
+
19
+ # Inject ENV variables into containers (secrets come from .env).
20
+ # env:
21
+ # clear:
22
+ # DB_HOST: 192.168.0.2
23
+ # secret:
24
+ # - RAILS_MASTER_KEY
25
+
26
+ # Call a broadcast command on deploys.
27
+ # audit_broadcast_cmd:
28
+ # bin/broadcast_to_bc
29
+
30
+ # Use a different ssh user than root
31
+ # ssh:
32
+ # user: app
33
+
34
+ # Configure builder setup.
35
+ # builder:
36
+ # args:
37
+ # RUBY_VERSION: 3.2.0
38
+ # secrets:
39
+ # - GITHUB_TOKEN
40
+ # remote:
41
+ # arch: amd64
42
+ # host: ssh://app@192.168.0.1
43
+
44
+ # Use accessory services (secrets come from .env).
45
+ # accessories:
46
+ # db:
47
+ # image: mysql:8.0
48
+ # host: 192.168.0.2
49
+ # port: 3306
50
+ # env:
51
+ # clear:
52
+ # MYSQL_ROOT_HOST: '%'
53
+ # secret:
54
+ # - MYSQL_ROOT_PASSWORD
55
+ # files:
56
+ # - config/mysql/production.cnf:/etc/mysql/my.cnf
57
+ # - db/production.sql.erb:/docker-entrypoint-initdb.d/setup.sql
58
+ # directories:
59
+ # - data:/var/lib/mysql
60
+ # redis:
61
+ # image: redis:7.0
62
+ # host: 192.168.0.2
63
+ # port: 6379
64
+ # directories:
65
+ # - data:/data
66
+
67
+ # Configure custom arguments for Traefik
68
+ # traefik:
69
+ # args:
70
+ # accesslog: true
71
+ # accesslog.format: json
72
+
73
+ # Configure a custom healthcheck (default is /up on port 3000)
74
+ # healthcheck:
75
+ # path: /healthz
76
+ # port: 4000
@@ -6,12 +6,12 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
6
6
 
7
7
  desc "reboot", "Reboot Traefik on servers (stop container, remove container, start new container)"
8
8
  def reboot
9
- invoke :stop
10
- invoke :remove_container
11
- invoke :boot
9
+ stop
10
+ remove_container
11
+ boot
12
12
  end
13
13
 
14
- desc "start", "Start existing Traefik on servers"
14
+ desc "start", "Start existing Traefik container on servers"
15
15
  def start
16
16
  on(MRSK.traefik_hosts) do
17
17
  execute *MRSK.auditor.record("Started traefik"), verbosity: :debug
@@ -19,7 +19,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
19
19
  end
20
20
  end
21
21
 
22
- desc "stop", "Stop Traefik on servers"
22
+ desc "stop", "Stop existing Traefik container on servers"
23
23
  def stop
24
24
  on(MRSK.traefik_hosts) do
25
25
  execute *MRSK.auditor.record("Stopped traefik"), verbosity: :debug
@@ -27,13 +27,13 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
27
27
  end
28
28
  end
29
29
 
30
- desc "restart", "Restart Traefik on servers"
30
+ desc "restart", "Restart existing Traefik container on servers"
31
31
  def restart
32
- invoke :stop
33
- invoke :start
32
+ stop
33
+ start
34
34
  end
35
35
 
36
- desc "details", "Display details about Traefik containers from servers"
36
+ desc "details", "Show details about Traefik container from servers"
37
37
  def details
38
38
  on(MRSK.traefik_hosts) { |host| puts_by_host host, capture_with_info(*MRSK.traefik.info), type: "Traefik" }
39
39
  end
@@ -64,12 +64,12 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
64
64
 
65
65
  desc "remove", "Remove Traefik container and image from servers"
66
66
  def remove
67
- invoke :stop
68
- invoke :remove_container
69
- invoke :remove_image
67
+ stop
68
+ remove_container
69
+ remove_image
70
70
  end
71
71
 
72
- desc "remove_container", "Remove Traefik container from servers"
72
+ desc "remove_container", "Remove Traefik container from servers", hide: true
73
73
  def remove_container
74
74
  on(MRSK.traefik_hosts) do
75
75
  execute *MRSK.auditor.record("Removed traefik container"), verbosity: :debug
@@ -77,7 +77,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
77
77
  end
78
78
  end
79
79
 
80
- desc "remove_container", "Remove Traefik image from servers"
80
+ desc "remove_container", "Remove Traefik image from servers", hide: true
81
81
  def remove_image
82
82
  on(MRSK.traefik_hosts) do
83
83
  execute *MRSK.auditor.record("Removed traefik image"), verbosity: :debug
@@ -49,32 +49,32 @@ class Mrsk::Commander
49
49
  @app ||= Mrsk::Commands::App.new(config)
50
50
  end
51
51
 
52
- def builder
53
- @builder ||= Mrsk::Commands::Builder.new(config)
52
+ def accessory(name)
53
+ Mrsk::Commands::Accessory.new(config, name: name)
54
54
  end
55
55
 
56
- def traefik
57
- @traefik ||= Mrsk::Commands::Traefik.new(config)
56
+ def auditor
57
+ @auditor ||= Mrsk::Commands::Auditor.new(config)
58
58
  end
59
59
 
60
- def registry
61
- @registry ||= Mrsk::Commands::Registry.new(config)
60
+ def builder
61
+ @builder ||= Mrsk::Commands::Builder.new(config)
62
62
  end
63
63
 
64
- def prune
65
- @prune ||= Mrsk::Commands::Prune.new(config)
64
+ def healthcheck
65
+ @healthcheck ||= Mrsk::Commands::Healthcheck.new(config)
66
66
  end
67
67
 
68
- def accessory(name)
69
- Mrsk::Commands::Accessory.new(config, name: name)
68
+ def prune
69
+ @prune ||= Mrsk::Commands::Prune.new(config)
70
70
  end
71
71
 
72
- def auditor
73
- @auditor ||= Mrsk::Commands::Auditor.new(config)
72
+ def registry
73
+ @registry ||= Mrsk::Commands::Registry.new(config)
74
74
  end
75
75
 
76
- def healthcheck
77
- @healthcheck ||= Mrsk::Commands::Healthcheck.new(config)
76
+ def traefik
77
+ @traefik ||= Mrsk::Commands::Traefik.new(config)
78
78
  end
79
79
 
80
80
 
@@ -10,10 +10,10 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
10
10
  def run
11
11
  docker :run,
12
12
  "--name", service_name,
13
- "-d",
13
+ "--detach",
14
14
  "--restart", "unless-stopped",
15
15
  "--log-opt", "max-size=#{MAX_LOG_SIZE}",
16
- "-p", port,
16
+ "--publish", port,
17
17
  *env_args,
18
18
  *volume_args,
19
19
  *label_args,
@@ -35,14 +35,14 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
35
35
 
36
36
  def logs(since: nil, lines: nil, grep: nil)
37
37
  pipe \
38
- docker(:logs, service_name, (" --since #{since}" if since), (" -n #{lines}" if lines), "-t", "2>&1"),
38
+ docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), "--timestamps", "2>&1"),
39
39
  ("grep '#{grep}'" if grep)
40
40
  end
41
41
 
42
42
  def follow_logs(grep: nil)
43
43
  run_over_ssh \
44
44
  pipe \
45
- docker(:logs, service_name, "-t", "-n", "10", "-f", "2>&1"),
45
+ docker(:logs, service_name, "--timestamps", "--tail", "10", "--follow", "2>&1"),
46
46
  (%(grep "#{grep}") if grep)
47
47
  end
48
48
 
@@ -96,11 +96,11 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
96
96
  end
97
97
 
98
98
  def remove_container
99
- docker :container, :prune, "-f", *service_filter
99
+ docker :container, :prune, "--force", *service_filter
100
100
  end
101
101
 
102
102
  def remove_image
103
- docker :image, :prune, "-a", "-f", *service_filter
103
+ docker :image, :prune, "--all", "--force", *service_filter
104
104
  end
105
105
 
106
106
  private
@@ -3,7 +3,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
3
3
  role = config.role(role)
4
4
 
5
5
  docker :run,
6
- "-d",
6
+ "--detach",
7
7
  "--restart unless-stopped",
8
8
  "--log-opt", "max-size=#{MAX_LOG_SIZE}",
9
9
  "--name", service_with_version,
@@ -30,7 +30,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
30
30
  def logs(since: nil, lines: nil, grep: nil)
31
31
  pipe \
32
32
  current_container_id,
33
- "xargs docker logs#{" --since #{since}" if since}#{" -n #{lines}" if lines} 2>&1",
33
+ "xargs docker logs#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1",
34
34
  ("grep '#{grep}'" if grep)
35
35
  end
36
36
 
@@ -38,7 +38,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
38
38
  run_over_ssh \
39
39
  pipe(
40
40
  current_container_id,
41
- "xargs docker logs -t -n 10 -f 2>&1",
41
+ "xargs docker logs --timestamps --tail 10 --follow 2>&1",
42
42
  (%(grep "#{grep}") if grep)
43
43
  ),
44
44
  host: host
@@ -72,7 +72,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
72
72
 
73
73
 
74
74
  def current_container_id
75
- docker :ps, "-q", *service_filter
75
+ docker :ps, "--quiet", *service_filter
76
76
  end
77
77
 
78
78
  def current_running_version
@@ -97,7 +97,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
97
97
 
98
98
 
99
99
  def list_containers
100
- docker :container, :ls, "-a", *service_filter
100
+ docker :container, :ls, "--all", *service_filter
101
101
  end
102
102
 
103
103
  def list_container_names
@@ -111,7 +111,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
111
111
  end
112
112
 
113
113
  def remove_containers
114
- docker :container, :prune, "-f", *service_filter
114
+ docker :container, :prune, "--force", *service_filter
115
115
  end
116
116
 
117
117
  def list_images
@@ -119,7 +119,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
119
119
  end
120
120
 
121
121
  def remove_images
122
- docker :image, :prune, "-a", "-f", *service_filter
122
+ docker :image, :prune, "--all", "--force", *service_filter
123
123
  end
124
124
 
125
125
 
@@ -18,7 +18,7 @@ module Mrsk::Commands
18
18
  end
19
19
 
20
20
  def container_id_for(container_name:)
21
- docker :container, :ls, "-a", "-f", "name=#{container_name}", "-q"
21
+ docker :container, :ls, "--all", "--filter", "name=#{container_name}", "--quiet"
22
22
  end
23
23
 
24
24
  private
@@ -1,6 +1,10 @@
1
1
  class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
2
2
  delegate :argumentize, to: Mrsk::Utils
3
3
 
4
+ def clean
5
+ docker :image, :rm, "--force", config.absolute_image
6
+ end
7
+
4
8
  def pull
5
9
  docker :pull, config.absolute_image
6
10
  end
@@ -1,5 +1,5 @@
1
1
  class Mrsk::Commands::Builder < Mrsk::Commands::Base
2
- delegate :create, :remove, :push, :pull, :info, to: :target
2
+ delegate :create, :remove, :push, :clean, :pull, :info, to: :target
3
3
 
4
4
  def name
5
5
  target.class.to_s.remove("Mrsk::Commands::Builder::").underscore
@@ -5,9 +5,9 @@ class Mrsk::Commands::Healthcheck < Mrsk::Commands::Base
5
5
  web = config.role(:web)
6
6
 
7
7
  docker :run,
8
- "-d",
8
+ "--detach",
9
9
  "--name", container_name_with_version,
10
- "-p", "#{EXPOSED_PORT}:#{config.healthcheck["port"]}",
10
+ "--publish", "#{EXPOSED_PORT}:#{config.healthcheck["port"]}",
11
11
  "--label", "service=#{container_name}",
12
12
  *web.env_args,
13
13
  *config.volume_args,
@@ -16,19 +16,19 @@ class Mrsk::Commands::Healthcheck < Mrsk::Commands::Base
16
16
  end
17
17
 
18
18
  def curl
19
- [ :curl, "--silent", "--output", "/dev/null", "--write-out", "'%{http_code}'", health_url ]
19
+ [ :curl, "--silent", "--output", "/dev/null", "--write-out", "'%{http_code}'", "--max-time", "2", health_url ]
20
+ end
21
+
22
+ def logs
23
+ pipe container_id, xargs(docker(:logs, "--tail", 50, "2>&1"))
20
24
  end
21
25
 
22
26
  def stop
23
- pipe \
24
- container_id_for(container_name: container_name),
25
- xargs(docker(:stop))
27
+ pipe container_id, xargs(docker(:stop))
26
28
  end
27
29
 
28
30
  def remove
29
- pipe \
30
- container_id_for(container_name: container_name),
31
- xargs(docker(:container, :rm))
31
+ pipe container_id, xargs(docker(:container, :rm))
32
32
  end
33
33
 
34
34
  private
@@ -40,6 +40,10 @@ class Mrsk::Commands::Healthcheck < Mrsk::Commands::Base
40
40
  "healthcheck-#{config.service_with_version}"
41
41
  end
42
42
 
43
+ def container_id
44
+ container_id_for(container_name: container_name)
45
+ end
46
+
43
47
  def health_url
44
48
  "http://localhost:#{EXPOSED_PORT}#{config.healthcheck["path"]}"
45
49
  end
@@ -2,10 +2,19 @@ class Mrsk::Commands::Registry < Mrsk::Commands::Base
2
2
  delegate :registry, to: :config
3
3
 
4
4
  def login
5
- docker :login, registry["server"], "-u", redact(registry["username"]), "-p", redact(registry["password"])
5
+ docker :login, registry["server"], "-u", redact(registry["username"]), "-p", redact(lookup_password)
6
6
  end
7
7
 
8
8
  def logout
9
9
  docker :logout, registry["server"]
10
10
  end
11
+
12
+ private
13
+ def lookup_password
14
+ if registry["password"].is_a?(Array)
15
+ ENV.fetch(registry["password"].first).dup
16
+ else
17
+ registry["password"]
18
+ end
19
+ end
11
20
  end
@@ -1,11 +1,11 @@
1
1
  class Mrsk::Commands::Traefik < Mrsk::Commands::Base
2
2
  def run
3
3
  docker :run, "--name traefik",
4
- "-d",
4
+ "--detach",
5
5
  "--restart", "unless-stopped",
6
6
  "--log-opt", "max-size=#{MAX_LOG_SIZE}",
7
- "-p 80:80",
8
- "-v /var/run/docker.sock:/var/run/docker.sock",
7
+ "--publish", "80:80",
8
+ "--volume", "/var/run/docker.sock:/var/run/docker.sock",
9
9
  "traefik",
10
10
  "--providers.docker",
11
11
  "--log.level=DEBUG",
@@ -26,23 +26,23 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
26
26
 
27
27
  def logs(since: nil, lines: nil, grep: nil)
28
28
  pipe \
29
- docker(:logs, "traefik", (" --since #{since}" if since), (" -n #{lines}" if lines), "-t", "2>&1"),
29
+ docker(:logs, "traefik", (" --since #{since}" if since), (" --tail #{lines}" if lines), "--timestamps", "2>&1"),
30
30
  ("grep '#{grep}'" if grep)
31
31
  end
32
32
 
33
33
  def follow_logs(host:, grep: nil)
34
34
  run_over_ssh pipe(
35
- docker(:logs, "traefik", "-t", "-n", "10", "-f", "2>&1"),
35
+ docker(:logs, "traefik", "--timestamps", "--tail", "10", "--follow", "2>&1"),
36
36
  (%(grep "#{grep}") if grep)
37
37
  ).join(" "), host: host
38
38
  end
39
39
 
40
40
  def remove_container
41
- docker :container, :prune, "-f", "--filter", "label=org.opencontainers.image.title=Traefik"
41
+ docker :container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=Traefik"
42
42
  end
43
43
 
44
44
  def remove_image
45
- docker :image, :prune, "-a", "-f", "--filter", "label=org.opencontainers.image.title=Traefik"
45
+ docker :image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=Traefik"
46
46
  end
47
47
 
48
48
  private
@@ -58,7 +58,7 @@ class Mrsk::Configuration::Role
58
58
  def traefik_labels
59
59
  if running_traefik?
60
60
  {
61
- "traefik.http.routers.#{config.service}.rule" => "'PathPrefix(`/`)'",
61
+ "traefik.http.routers.#{config.service}.rule" => "PathPrefix(`/`)",
62
62
  "traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
63
63
  "traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
64
64
  "traefik.http.middlewares.#{config.service}.retry.attempts" => "3",
data/lib/mrsk/utils.rb CHANGED
@@ -1,13 +1,14 @@
1
1
  module Mrsk::Utils
2
2
  extend self
3
3
 
4
- # Return a list of shell arguments using the same named argument against the passed attributes (hash or array).
4
+ # Return a list of escaped shell arguments using the same named argument against the passed attributes (hash or array).
5
5
  def argumentize(argument, attributes, redacted: false)
6
- Array(attributes).flat_map do |k, v|
7
- if v.present?
8
- [ argument, redacted ? redact("#{k}=#{v}") : "#{k}=#{v}" ]
6
+ Array(attributes).flat_map do |key, value|
7
+ if value.present?
8
+ escaped_pair = [ key, value.to_s.dump.gsub(/`/, '\\\\`') ].join("=")
9
+ [ argument, redacted ? redact(escaped_pair) : escaped_pair ]
9
10
  else
10
- [ argument, k ]
11
+ [ argument, key ]
11
12
  end
12
13
  end
13
14
  end
data/lib/mrsk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mrsk
2
- VERSION = "0.7.2"
2
+ VERSION = "0.8.1"
3
3
  end
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.7.2
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-18 00:00:00.000000000 Z
11
+ date: 2023-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport