mrsk 0.9.1 → 0.10.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.
data/lib/mrsk/cli/app.rb CHANGED
@@ -1,32 +1,31 @@
1
1
  class Mrsk::Cli::App < Mrsk::Cli::Base
2
2
  desc "boot", "Boot app on servers (or reboot app if already running)"
3
3
  def boot
4
- say "Get most recent version available as an image...", :magenta unless options[:version]
5
- using_version(options[:version] || most_recent_version_available) do |version|
6
- say "Start container with version #{version} using a #{MRSK.config.readiness_delay}s readiness delay (or reboot if already running)...", :magenta
4
+ with_lock do
5
+ say "Get most recent version available as an image...", :magenta unless options[:version]
6
+ using_version(version_or_latest) do |version|
7
+ say "Start container with version #{version} using a #{MRSK.config.readiness_delay}s readiness delay (or reboot if already running)...", :magenta
7
8
 
8
- cli = self
9
-
10
- MRSK.config.roles.each do |role|
11
- on(role.hosts) do |host|
12
- execute *MRSK.auditor.record("Booted app version #{version}"), verbosity: :debug
9
+ cli = self
13
10
 
14
- begin
15
- old_version = capture_with_info(*MRSK.app.current_running_version).strip
16
- execute *MRSK.app.run(role: role.name)
17
- sleep MRSK.config.readiness_delay
18
- execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false if old_version.present?
19
-
20
- rescue SSHKit::Command::Failed => e
21
- if e.message =~ /already in use/
22
- error "Rebooting container with same version #{version} already deployed on #{host} (may cause gap in zero-downtime promise!)"
23
- execute *MRSK.auditor.record("Rebooted app version #{version}"), verbosity: :debug
24
-
25
- execute *MRSK.app.stop(version: version)
26
- execute *MRSK.app.remove_container(version: version)
27
- execute *MRSK.app.run(role: role.name)
28
- else
29
- raise
11
+ on(MRSK.hosts) do |host|
12
+ roles = MRSK.roles_on(host)
13
+
14
+ roles.each do |role|
15
+ execute *MRSK.auditor(role: role).record("Booted app version #{version}"), verbosity: :debug
16
+
17
+ begin
18
+ if capture_with_info(*MRSK.app(role: role).container_id_for_version(version)).present?
19
+ tmp_version = "#{version}_#{SecureRandom.hex(8)}"
20
+ info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
21
+ execute *MRSK.auditor(role: role).record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
22
+ execute *MRSK.app(role: role).rename_container(version: version, new_version: tmp_version)
23
+ end
24
+
25
+ old_version = capture_with_info(*MRSK.app(role: role).current_running_version).strip
26
+ execute *MRSK.app(role: role).run
27
+ sleep MRSK.config.readiness_delay
28
+ execute *MRSK.app(role: role).stop(version: old_version), raise_on_non_zero_exit: false if old_version.present?
30
29
  end
31
30
  end
32
31
  end
@@ -36,24 +35,42 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
36
35
 
37
36
  desc "start", "Start existing app container on servers"
38
37
  def start
39
- on(MRSK.hosts) do
40
- execute *MRSK.auditor.record("Started app version #{MRSK.version}"), verbosity: :debug
41
- execute *MRSK.app.start, raise_on_non_zero_exit: false
38
+ with_lock do
39
+ on(MRSK.hosts) do |host|
40
+ roles = MRSK.roles_on(host)
41
+
42
+ roles.each do |role|
43
+ execute *MRSK.auditor.record("Started app version #{MRSK.config.version}"), verbosity: :debug
44
+ execute *MRSK.app(role: role).start, raise_on_non_zero_exit: false
45
+ end
46
+ end
42
47
  end
43
48
  end
44
49
 
45
50
  desc "stop", "Stop app container on servers"
46
51
  def stop
47
- on(MRSK.hosts) do
48
- execute *MRSK.auditor.record("Stopped app"), verbosity: :debug
49
- execute *MRSK.app.stop, raise_on_non_zero_exit: false
52
+ with_lock do
53
+ on(MRSK.hosts) do |host|
54
+ roles = MRSK.roles_on(host)
55
+
56
+ roles.each do |role|
57
+ execute *MRSK.auditor(role: role).record("Stopped app"), verbosity: :debug
58
+ execute *MRSK.app(role: role).stop, raise_on_non_zero_exit: false
59
+ end
60
+ end
50
61
  end
51
62
  end
52
63
 
53
64
  # FIXME: Drop in favor of just containers?
54
65
  desc "details", "Show details about app containers"
55
66
  def details
56
- on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.info) }
67
+ on(MRSK.hosts) do |host|
68
+ roles = MRSK.roles_on(host)
69
+
70
+ roles.each do |role|
71
+ puts_by_host host, capture_with_info(*MRSK.app(role: role).info)
72
+ end
73
+ end
57
74
  end
58
75
 
59
76
  desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)"
@@ -65,12 +82,12 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
65
82
  say "Get current version of running container...", :magenta unless options[:version]
66
83
  using_version(options[:version] || current_running_version) do |version|
67
84
  say "Launching interactive command with version #{version} via SSH from existing container on #{MRSK.primary_host}...", :magenta
68
- run_locally { exec MRSK.app.execute_in_existing_container_over_ssh(cmd, host: MRSK.primary_host) }
85
+ run_locally { exec MRSK.app(role: "web").execute_in_existing_container_over_ssh(cmd, host: MRSK.primary_host) }
69
86
  end
70
87
 
71
88
  when options[:interactive]
72
89
  say "Get most recent version available as an image...", :magenta unless options[:version]
73
- using_version(options[:version] || most_recent_version_available) do |version|
90
+ using_version(version_or_latest) do |version|
74
91
  say "Launching interactive command with version #{version} via SSH from new container on #{MRSK.primary_host}...", :magenta
75
92
  run_locally { exec MRSK.app.execute_in_new_container_over_ssh(cmd, host: MRSK.primary_host) }
76
93
  end
@@ -81,14 +98,18 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
81
98
  say "Launching command with version #{version} from existing container...", :magenta
82
99
 
83
100
  on(MRSK.hosts) do |host|
84
- execute *MRSK.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
85
- puts_by_host host, capture_with_info(*MRSK.app.execute_in_existing_container(cmd))
101
+ roles = MRSK.roles_on(host)
102
+
103
+ roles.each do |role|
104
+ execute *MRSK.auditor(role: role).record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
105
+ puts_by_host host, capture_with_info(*MRSK.app(role: role).execute_in_existing_container(cmd))
106
+ end
86
107
  end
87
108
  end
88
109
 
89
110
  else
90
111
  say "Get most recent version available as an image...", :magenta unless options[:version]
91
- using_version(options[:version] || most_recent_version_available) do |version|
112
+ using_version(version_or_latest) do |version|
92
113
  say "Launching command with version #{version} from new container...", :magenta
93
114
  on(MRSK.hosts) do |host|
94
115
  execute *MRSK.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
@@ -121,18 +142,26 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
121
142
  if options[:follow]
122
143
  run_locally do
123
144
  info "Following logs on #{MRSK.primary_host}..."
124
- info MRSK.app.follow_logs(host: MRSK.primary_host, grep: grep)
125
- exec MRSK.app.follow_logs(host: MRSK.primary_host, grep: grep)
145
+
146
+ MRSK.specific_roles ||= ["web"]
147
+ role = MRSK.roles_on(MRSK.primary_host).first
148
+
149
+ info MRSK.app(role: role).follow_logs(host: MRSK.primary_host, grep: grep)
150
+ exec MRSK.app(role: role).follow_logs(host: MRSK.primary_host, grep: grep)
126
151
  end
127
152
  else
128
153
  since = options[:since]
129
154
  lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
130
155
 
131
156
  on(MRSK.hosts) do |host|
132
- begin
133
- puts_by_host host, capture_with_info(*MRSK.app.logs(since: since, lines: lines, grep: grep))
134
- rescue SSHKit::Command::Failed
135
- puts_by_host host, "Nothing found"
157
+ roles = MRSK.roles_on(host)
158
+
159
+ roles.each do |role|
160
+ begin
161
+ puts_by_host host, capture_with_info(*MRSK.app(role: role).logs(since: since, lines: lines, grep: grep))
162
+ rescue SSHKit::Command::Failed
163
+ puts_by_host host, "Nothing found"
164
+ end
136
165
  end
137
166
  end
138
167
  end
@@ -140,32 +169,48 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
140
169
 
141
170
  desc "remove", "Remove app containers and images from servers"
142
171
  def remove
143
- stop
144
- remove_containers
145
- remove_images
172
+ with_lock do
173
+ stop
174
+ remove_containers
175
+ remove_images
176
+ end
146
177
  end
147
178
 
148
179
  desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
149
180
  def remove_container(version)
150
- on(MRSK.hosts) do
151
- execute *MRSK.auditor.record("Removed app container with version #{version}"), verbosity: :debug
152
- execute *MRSK.app.remove_container(version: version)
181
+ with_lock do
182
+ on(MRSK.hosts) do |host|
183
+ roles = MRSK.roles_on(host)
184
+
185
+ roles.each do |role|
186
+ execute *MRSK.auditor(role: role).record("Removed app container with version #{version}"), verbosity: :debug
187
+ execute *MRSK.app(role: role).remove_container(version: version)
188
+ end
189
+ end
153
190
  end
154
191
  end
155
192
 
156
193
  desc "remove_containers", "Remove all app containers from servers", hide: true
157
194
  def remove_containers
158
- on(MRSK.hosts) do
159
- execute *MRSK.auditor.record("Removed all app containers"), verbosity: :debug
160
- execute *MRSK.app.remove_containers
195
+ with_lock do
196
+ on(MRSK.hosts) do |host|
197
+ roles = MRSK.roles_on(host)
198
+
199
+ roles.each do |role|
200
+ execute *MRSK.auditor(role: role).record("Removed all app containers"), verbosity: :debug
201
+ execute *MRSK.app(role: role).remove_containers
202
+ end
203
+ end
161
204
  end
162
205
  end
163
206
 
164
207
  desc "remove_images", "Remove all app images from servers", hide: true
165
208
  def remove_images
166
- on(MRSK.hosts) do
167
- execute *MRSK.auditor.record("Removed all app images"), verbosity: :debug
168
- execute *MRSK.app.remove_images
209
+ with_lock do
210
+ on(MRSK.hosts) do
211
+ execute *MRSK.auditor.record("Removed all app images"), verbosity: :debug
212
+ execute *MRSK.app.remove_images
213
+ end
169
214
  end
170
215
  end
171
216
 
@@ -189,20 +234,13 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
189
234
  end
190
235
  end
191
236
 
192
- def most_recent_version_available(host: MRSK.primary_host)
193
- version = nil
194
- on(host) { version = capture_with_info(*MRSK.app.most_recent_version_from_available_images).strip }
195
-
196
- if version == "<none>"
197
- raise "Most recent image available was not tagged with a version (returned <none>)"
198
- else
199
- version.presence
200
- end
201
- end
202
-
203
237
  def current_running_version(host: MRSK.primary_host)
204
238
  version = nil
205
239
  on(host) { version = capture_with_info(*MRSK.app.current_running_version).strip }
206
240
  version.presence
207
241
  end
242
+
243
+ def version_or_latest
244
+ options[:version] || "latest"
245
+ end
208
246
  end
data/lib/mrsk/cli/base.rb CHANGED
@@ -6,6 +6,8 @@ module Mrsk::Cli
6
6
  class Base < Thor
7
7
  include SSHKit::DSL
8
8
 
9
+ class LockError < StandardError; end
10
+
9
11
  def self.exit_on_failure?() true end
10
12
 
11
13
  class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
@@ -25,7 +27,7 @@ module Mrsk::Cli
25
27
  def initialize(*)
26
28
  super
27
29
  load_envs
28
- initialize_commander(options)
30
+ initialize_commander(options_with_subcommand_class_options)
29
31
  end
30
32
 
31
33
  private
@@ -37,16 +39,12 @@ module Mrsk::Cli
37
39
  end
38
40
  end
39
41
 
42
+ def options_with_subcommand_class_options
43
+ options.merge(@_initializer.last[:class_options] || {})
44
+ end
45
+
40
46
  def initialize_commander(options)
41
47
  MRSK.tap do |commander|
42
- commander.config_file = Pathname.new(File.expand_path(options[:config_file]))
43
- commander.destination = options[:destination]
44
- commander.version = options[:version]
45
-
46
- commander.specific_hosts = options[:hosts]&.split(",")
47
- commander.specific_roles = options[:roles]&.split(",")
48
- commander.specific_primary! if options[:primary]
49
-
50
48
  if options[:verbose]
51
49
  ENV["VERBOSE"] = "1" # For backtraces via cli/start
52
50
  commander.verbosity = :debug
@@ -55,6 +53,15 @@ module Mrsk::Cli
55
53
  if options[:quiet]
56
54
  commander.verbosity = :error
57
55
  end
56
+
57
+ commander.configure \
58
+ config_file: Pathname.new(File.expand_path(options[:config_file])),
59
+ destination: options[:destination],
60
+ version: options[:version]
61
+
62
+ commander.specific_hosts = options[:hosts]&.split(",")
63
+ commander.specific_roles = options[:roles]&.split(",")
64
+ commander.specific_primary! if options[:primary]
58
65
  end
59
66
  end
60
67
 
@@ -70,5 +77,35 @@ module Mrsk::Cli
70
77
  def audit_broadcast(line)
71
78
  run_locally { execute *MRSK.auditor.broadcast(line), verbosity: :debug }
72
79
  end
80
+
81
+ def with_lock
82
+ acquire_lock
83
+
84
+ yield
85
+ ensure
86
+ release_lock
87
+ end
88
+
89
+ def acquire_lock
90
+ if MRSK.lock_count == 0
91
+ say "Acquiring the deploy lock"
92
+ on(MRSK.primary_host) { execute *MRSK.lock.acquire("Automatic deploy lock", MRSK.config.version) }
93
+ end
94
+ MRSK.lock_count += 1
95
+ rescue SSHKit::Runner::ExecuteError => e
96
+ if e.message =~ /cannot create directory/
97
+ invoke "mrsk:cli:lock:status", []
98
+ end
99
+
100
+ raise LockError, "Deploy lock found"
101
+ end
102
+
103
+ def release_lock
104
+ MRSK.lock_count -= 1
105
+ if MRSK.lock_count == 0
106
+ say "Releasing the deploy lock"
107
+ on(MRSK.primary_host) { execute *MRSK.lock.release }
108
+ end
109
+ end
73
110
  end
74
111
  end
@@ -1,26 +1,30 @@
1
1
  class Mrsk::Cli::Build < Mrsk::Cli::Base
2
2
  desc "deliver", "Build app and push app image to registry then pull image on servers"
3
3
  def deliver
4
- push
5
- pull
4
+ with_lock do
5
+ push
6
+ pull
7
+ end
6
8
  end
7
9
 
8
10
  desc "push", "Build and push app image to registry"
9
11
  def push
10
- cli = self
12
+ with_lock do
13
+ cli = self
11
14
 
12
- run_locally do
13
- begin
14
- MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
15
- rescue SSHKit::Command::Failed => e
16
- if e.message =~ /(no builder)|(no such file or directory)/
17
- error "Missing compatible builder, so creating a new one first"
15
+ run_locally do
16
+ begin
17
+ MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
18
+ rescue SSHKit::Command::Failed => e
19
+ if e.message =~ /(no builder)|(no such file or directory)/
20
+ error "Missing compatible builder, so creating a new one first"
18
21
 
19
- if cli.create
20
- MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
22
+ if cli.create
23
+ MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
24
+ end
25
+ else
26
+ raise
21
27
  end
22
- else
23
- raise
24
28
  end
25
29
  end
26
30
  end
@@ -28,25 +32,29 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
28
32
 
29
33
  desc "pull", "Pull app image from registry onto servers"
30
34
  def pull
31
- on(MRSK.hosts) do
32
- execute *MRSK.auditor.record("Pulled image with version #{MRSK.version}"), verbosity: :debug
33
- execute *MRSK.builder.clean, raise_on_non_zero_exit: false
34
- execute *MRSK.builder.pull
35
+ with_lock do
36
+ on(MRSK.hosts) do
37
+ execute *MRSK.auditor.record("Pulled image with version #{MRSK.config.version}"), verbosity: :debug
38
+ execute *MRSK.builder.clean, raise_on_non_zero_exit: false
39
+ execute *MRSK.builder.pull
40
+ end
35
41
  end
36
42
  end
37
43
 
38
44
  desc "create", "Create a build setup"
39
45
  def create
40
- run_locally do
41
- begin
42
- debug "Using builder: #{MRSK.builder.name}"
43
- execute *MRSK.builder.create
44
- rescue SSHKit::Command::Failed => e
45
- if e.message =~ /stderr=(.*)/
46
- error "Couldn't create remote builder: #{$1}"
47
- false
48
- else
49
- raise
46
+ with_lock do
47
+ run_locally do
48
+ begin
49
+ debug "Using builder: #{MRSK.builder.name}"
50
+ execute *MRSK.builder.create
51
+ rescue SSHKit::Command::Failed => e
52
+ if e.message =~ /stderr=(.*)/
53
+ error "Couldn't create remote builder: #{$1}"
54
+ false
55
+ else
56
+ raise
57
+ end
50
58
  end
51
59
  end
52
60
  end
@@ -54,9 +62,11 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
54
62
 
55
63
  desc "remove", "Remove build setup"
56
64
  def remove
57
- run_locally do
58
- debug "Using builder: #{MRSK.builder.name}"
59
- execute *MRSK.builder.remove
65
+ with_lock do
66
+ run_locally do
67
+ debug "Using builder: #{MRSK.builder.name}"
68
+ execute *MRSK.builder.remove
69
+ end
60
70
  end
61
71
  end
62
72
 
@@ -0,0 +1,37 @@
1
+ class Mrsk::Cli::Lock < Mrsk::Cli::Base
2
+ desc "status", "Report lock status"
3
+ def status
4
+ handle_missing_lock do
5
+ on(MRSK.primary_host) { puts capture_with_info(*MRSK.lock.status) }
6
+ end
7
+ end
8
+
9
+ desc "acquire", "Acquire the deploy lock"
10
+ option :message, aliases: "-m", type: :string, desc: "A lock mesasge", required: true
11
+ def acquire
12
+ message = options[:message]
13
+ handle_missing_lock do
14
+ on(MRSK.primary_host) { execute *MRSK.lock.acquire(message, MRSK.config.version) }
15
+ say "Set the deploy lock"
16
+ end
17
+ end
18
+
19
+ desc "release", "Release the deploy lock"
20
+ def release
21
+ handle_missing_lock do
22
+ on(MRSK.primary_host) { execute *MRSK.lock.release }
23
+ say "Removed the deploy lock"
24
+ end
25
+ end
26
+
27
+ private
28
+ def handle_missing_lock
29
+ yield
30
+ rescue SSHKit::Runner::ExecuteError => e
31
+ if e.message =~ /No such file or directory/
32
+ say "There is no deploy lock"
33
+ else
34
+ raise
35
+ end
36
+ end
37
+ end