mrsk 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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