shipit-engine 0.27.1 → 0.28.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +30 -1
- data/app/assets/stylesheets/_pages/_commits.scss +2 -0
- data/app/assets/stylesheets/_pages/_deploy.scss +6 -0
- data/app/controllers/shipit/api/release_statuses_controller.rb +22 -0
- data/app/controllers/shipit/api/stacks_controller.rb +6 -1
- data/app/controllers/shipit/commits_controller.rb +12 -1
- data/app/controllers/shipit/deploys_controller.rb +11 -0
- data/app/controllers/shipit/stacks_controller.rb +29 -1
- data/app/controllers/shipit/tasks_controller.rb +13 -1
- data/app/helpers/shipit/merge_status_helper.rb +2 -2
- data/app/helpers/shipit/stacks_helper.rb +10 -4
- data/app/jobs/shipit/perform_task_job.rb +1 -0
- data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +41 -0
- data/app/models/shipit/command_line_user.rb +58 -0
- data/app/models/shipit/commit.rb +42 -2
- data/app/models/shipit/deploy.rb +31 -2
- data/app/models/shipit/deploy_spec/lerna_discovery.rb +43 -19
- data/app/models/shipit/deploy_spec/pypi_discovery.rb +5 -1
- data/app/models/shipit/rollback.rb +4 -2
- data/app/models/shipit/stack.rb +72 -15
- data/app/models/shipit/task.rb +30 -0
- data/app/models/shipit/undeployed_commit.rb +10 -1
- data/app/serializers/shipit/command_line_user_serializer.rb +4 -0
- data/app/views/layouts/shipit.html.erb +5 -1
- data/app/views/shipit/commits/_commit.html.erb +7 -2
- data/app/views/shipit/merge_status/_commit_count_warning.html.erb +1 -5
- data/app/views/shipit/merge_status/backlogged.html.erb +1 -1
- data/app/views/shipit/merge_status/failure.html.erb +1 -1
- data/app/views/shipit/merge_status/locked.html.erb +1 -1
- data/app/views/shipit/merge_status/success.html.erb +1 -1
- data/app/views/shipit/stacks/show.html.erb +10 -1
- data/app/views/shipit/tasks/_task_output.html.erb +2 -2
- data/config/locales/en.yml +2 -1
- data/config/routes.rb +8 -1
- data/db/migrate/20190502020249_add_lock_author_id_to_commits.rb +5 -0
- data/lib/shipit.rb +14 -2
- data/lib/shipit/cast_value.rb +9 -0
- data/lib/shipit/command.rb +62 -16
- data/lib/shipit/line_buffer.rb +42 -0
- data/lib/shipit/version.rb +1 -1
- data/lib/tasks/shipit.rake +27 -0
- data/test/controllers/api/release_statuses_controller_test.rb +66 -0
- data/test/controllers/api/stacks_controller_test.rb +19 -0
- data/test/controllers/commits_controller_test.rb +30 -6
- data/test/controllers/deploys_controller_test.rb +51 -2
- data/test/controllers/tasks_controller_test.rb +24 -0
- data/test/dummy/db/schema.rb +2 -1
- data/test/dummy/db/seeds.rb +2 -0
- data/test/fixtures/shipit/check_runs.yml +11 -0
- data/test/fixtures/shipit/commits.yml +104 -0
- data/test/fixtures/shipit/stacks.yml +98 -3
- data/test/fixtures/shipit/tasks.yml +42 -0
- data/test/jobs/update_github_last_deployed_ref_job_test.rb +88 -0
- data/test/models/commits_test.rb +88 -1
- data/test/models/deploy_spec_test.rb +34 -6
- data/test/models/deploys_test.rb +308 -6
- data/test/models/rollbacks_test.rb +17 -11
- data/test/models/stacks_test.rb +217 -4
- data/test/models/tasks_test.rb +13 -0
- data/test/models/undeployed_commits_test.rb +62 -3
- data/test/test_helper.rb +0 -1
- data/test/unit/command_test.rb +55 -0
- data/test/unit/line_buffer_test.rb +20 -0
- metadata +142 -128
data/app/models/shipit/deploy.rb
CHANGED
@@ -36,9 +36,30 @@ module Shipit
|
|
36
36
|
after_create :create_commit_deployments
|
37
37
|
after_create :update_release_status
|
38
38
|
after_commit :broadcast_update
|
39
|
+
after_commit :update_latest_deployed_ref, on: :update
|
39
40
|
|
40
41
|
delegate :broadcast_update, :filter_deploy_envs, to: :stack
|
41
42
|
|
43
|
+
def self.newer_than(deploy)
|
44
|
+
return all unless deploy
|
45
|
+
where('id > ?', deploy.try(:id) || deploy)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.older_than(deploy)
|
49
|
+
return all unless deploy
|
50
|
+
where('id < ?', deploy.try(:id) || deploy)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.since(deploy)
|
54
|
+
return all unless deploy
|
55
|
+
where('id >= ?', deploy.try(:id) || deploy)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.until(deploy)
|
59
|
+
return all unless deploy
|
60
|
+
where('id <= ?', deploy.try(:id) || deploy)
|
61
|
+
end
|
62
|
+
|
42
63
|
def build_rollback(user = nil, env: nil, force: false)
|
43
64
|
Rollback.new(
|
44
65
|
user_id: user&.id,
|
@@ -65,16 +86,19 @@ module Shipit
|
|
65
86
|
rollback
|
66
87
|
end
|
67
88
|
|
68
|
-
# Rolls the stack back to the **previous** deploy
|
89
|
+
# Rolls the stack back to the most recent **previous** successful deploy
|
69
90
|
def trigger_revert(force: false)
|
91
|
+
previous_successful_commit = commit_to_rollback_to
|
92
|
+
|
70
93
|
rollback = Rollback.create!(
|
71
94
|
user_id: user_id,
|
72
95
|
stack_id: stack_id,
|
73
96
|
parent_id: id,
|
74
97
|
since_commit: until_commit,
|
75
|
-
until_commit:
|
98
|
+
until_commit: previous_successful_commit,
|
76
99
|
allow_concurrency: force,
|
77
100
|
)
|
101
|
+
|
78
102
|
rollback.enqueue
|
79
103
|
lock_reason = "A rollback for #{until_commit.sha} has been triggered. " \
|
80
104
|
"Please make sure the reason for the rollback has been addressed before deploying again."
|
@@ -257,5 +281,10 @@ module Shipit
|
|
257
281
|
def update_last_deploy_time
|
258
282
|
stack.update(last_deployed_at: ended_at)
|
259
283
|
end
|
284
|
+
|
285
|
+
def update_latest_deployed_ref
|
286
|
+
return unless previous_changes.include?(:status)
|
287
|
+
stack.update_latest_deployed_ref if previous_changes[:status].last == 'success'
|
288
|
+
end
|
260
289
|
end
|
261
290
|
end
|
@@ -3,6 +3,8 @@ require 'json'
|
|
3
3
|
module Shipit
|
4
4
|
class DeploySpec
|
5
5
|
module LernaDiscovery
|
6
|
+
LATEST_MAJOR_VERSION = Gem::Version.new('3.0.0')
|
7
|
+
|
6
8
|
def discover_dependencies_steps
|
7
9
|
discover_lerna_json || super
|
8
10
|
end
|
@@ -21,15 +23,21 @@ module Shipit
|
|
21
23
|
|
22
24
|
def discover_lerna_checklist
|
23
25
|
if lerna?
|
26
|
+
command = if lerna_lerna >= LATEST_MAJOR_VERSION
|
27
|
+
'lerna version'
|
28
|
+
else
|
29
|
+
%(
|
30
|
+
lerna publish --skip-npm
|
31
|
+
&& git add -A
|
32
|
+
&& git push --follow-tags
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
24
36
|
[%(
|
25
37
|
<strong>Don't forget version and tag before publishing!</strong>
|
26
38
|
You can do this with:<br/>
|
27
|
-
<pre>
|
28
|
-
|
29
|
-
&& git add -A
|
30
|
-
&& git push --follow-tags
|
31
|
-
</pre>
|
32
|
-
)]
|
39
|
+
<pre>#{command}</pre>
|
40
|
+
)]
|
33
41
|
end
|
34
42
|
end
|
35
43
|
|
@@ -41,9 +49,16 @@ module Shipit
|
|
41
49
|
file('lerna.json')
|
42
50
|
end
|
43
51
|
|
52
|
+
def lerna_config
|
53
|
+
@_lerna_config ||= JSON.parse(lerna_json.read)
|
54
|
+
end
|
55
|
+
|
56
|
+
def lerna_lerna
|
57
|
+
Gem::Version.new(lerna_config['lerna'])
|
58
|
+
end
|
59
|
+
|
44
60
|
def lerna_version
|
45
|
-
lerna_config
|
46
|
-
JSON.parse(lerna_config)['version']
|
61
|
+
lerna_config['version']
|
47
62
|
end
|
48
63
|
|
49
64
|
def discover_lerna_packages
|
@@ -68,18 +83,27 @@ module Shipit
|
|
68
83
|
|
69
84
|
def publish_fixed_version_packages
|
70
85
|
check_tags = 'assert-lerna-fixed-version-tag'
|
71
|
-
# `yarn publish` requires user input, so always use npm.
|
72
86
|
version = lerna_version
|
73
|
-
publish =
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
87
|
+
publish = if lerna_lerna >= LATEST_MAJOR_VERSION
|
88
|
+
%W(
|
89
|
+
node_modules/.bin/lerna publish
|
90
|
+
from-git
|
91
|
+
--yes
|
92
|
+
--dist-tag #{dist_tag(version)}
|
93
|
+
).join(" ")
|
94
|
+
else
|
95
|
+
# `yarn publish` requires user input, so always use npm.
|
96
|
+
%W(
|
97
|
+
node_modules/.bin/lerna publish
|
98
|
+
--yes
|
99
|
+
--skip-git
|
100
|
+
--repo-version #{version}
|
101
|
+
--force-publish=*
|
102
|
+
--npm-tag #{dist_tag(version)}
|
103
|
+
--npm-client=npm
|
104
|
+
--skip-npm=false
|
105
|
+
).join(" ")
|
106
|
+
end
|
83
107
|
|
84
108
|
[check_tags, publish]
|
85
109
|
end
|
@@ -29,7 +29,11 @@ module Shipit
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def publish_egg
|
32
|
-
[
|
32
|
+
[
|
33
|
+
"assert-egg-version-tag #{setup_dot_py}",
|
34
|
+
'python setup.py register sdist',
|
35
|
+
'twine upload dist/*',
|
36
|
+
]
|
33
37
|
end
|
34
38
|
end
|
35
39
|
end
|
@@ -36,9 +36,11 @@ module Shipit
|
|
36
36
|
|
37
37
|
def update_release_status
|
38
38
|
return unless stack.release_status?
|
39
|
-
return unless status == 'pending'
|
40
39
|
|
41
|
-
|
40
|
+
# When we rollback to a certain revision, assume that all later deploys were faulty
|
41
|
+
stack.deploys.newer_than(deploy.id).until(stack.last_completed_deploy.id).to_a.each do |deploy|
|
42
|
+
deploy.report_faulty!(description: "A rollback of #{stack.to_param} was triggered")
|
43
|
+
end
|
42
44
|
end
|
43
45
|
|
44
46
|
def lock_reverted_commits
|
data/app/models/shipit/stack.rb
CHANGED
@@ -13,6 +13,10 @@ module Shipit
|
|
13
13
|
''
|
14
14
|
end
|
15
15
|
|
16
|
+
def short_sha
|
17
|
+
''
|
18
|
+
end
|
19
|
+
|
16
20
|
def blank?
|
17
21
|
true
|
18
22
|
end
|
@@ -124,10 +128,11 @@ module Shipit
|
|
124
128
|
)
|
125
129
|
end
|
126
130
|
|
127
|
-
def trigger_deploy(*args)
|
128
|
-
|
131
|
+
def trigger_deploy(*args, **kwargs)
|
132
|
+
run_now = kwargs.delete(:run_now)
|
133
|
+
deploy = build_deploy(*args, **kwargs)
|
129
134
|
deploy.save!
|
130
|
-
deploy.enqueue
|
135
|
+
run_now ? deploy.run_now! : deploy.enqueue
|
131
136
|
continuous_delivery_resumed!
|
132
137
|
deploy
|
133
138
|
end
|
@@ -182,6 +187,12 @@ module Shipit
|
|
182
187
|
end
|
183
188
|
|
184
189
|
def async_refresh_deployed_revision
|
190
|
+
async_refresh_deployed_revision!
|
191
|
+
rescue => error
|
192
|
+
logger.warn "Failed to dispatch FetchDeployedRevisionJob: [#{error.class.name}] #{error.message}"
|
193
|
+
end
|
194
|
+
|
195
|
+
def async_refresh_deployed_revision!
|
185
196
|
FetchDeployedRevisionJob.perform_later(self)
|
186
197
|
end
|
187
198
|
|
@@ -234,33 +245,57 @@ module Shipit
|
|
234
245
|
end
|
235
246
|
|
236
247
|
def lock_reverted_commits!
|
237
|
-
commits_to_lock = []
|
238
|
-
|
239
248
|
backlog = undeployed_commits.to_a
|
249
|
+
affected_rows = 0
|
250
|
+
|
240
251
|
until backlog.empty?
|
241
252
|
backlog = backlog.drop_while { |c| !c.revert? }
|
242
|
-
|
243
|
-
|
244
|
-
|
253
|
+
revert = backlog.shift
|
254
|
+
next if revert.nil?
|
255
|
+
|
256
|
+
commits_to_lock = backlog.reverse.drop_while { |c| !revert.revert_of?(c) }
|
257
|
+
next if commits_to_lock.empty?
|
258
|
+
|
259
|
+
affected_rows += commits
|
260
|
+
.where(id: commits_to_lock.map(&:id).uniq)
|
261
|
+
.lock_all(revert.author)
|
245
262
|
end
|
246
263
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
264
|
+
touch if affected_rows > 1
|
265
|
+
end
|
266
|
+
|
267
|
+
def next_expected_commit_to_deploy(commits: nil)
|
268
|
+
commits ||= undeployed_commits do |scope|
|
269
|
+
scope.preload(:statuses, :check_runs)
|
251
270
|
end
|
271
|
+
|
272
|
+
commits_to_deploy = commits.reject(&:active?)
|
273
|
+
if maximum_commits_per_deploy
|
274
|
+
commits_to_deploy = commits_to_deploy.reverse.slice(0, maximum_commits_per_deploy).reverse
|
275
|
+
end
|
276
|
+
commits_to_deploy.find(&:deployable?)
|
252
277
|
end
|
253
278
|
|
254
279
|
def undeployed_commits
|
255
280
|
scope = commits.reachable.newer_than(last_deployed_commit).order(id: :asc)
|
256
|
-
|
257
|
-
scope
|
281
|
+
|
282
|
+
scope = yield scope if block_given?
|
283
|
+
|
284
|
+
scope.to_a.reverse
|
258
285
|
end
|
259
286
|
|
260
287
|
def last_completed_deploy
|
261
288
|
deploys_and_rollbacks.last_completed
|
262
289
|
end
|
263
290
|
|
291
|
+
def last_successful_deploy_commit
|
292
|
+
deploys_and_rollbacks.last_successful&.until_commit
|
293
|
+
end
|
294
|
+
|
295
|
+
def previous_successful_deploy(deploy_id)
|
296
|
+
deploys_and_rollbacks.success.where("id < ?", deploy_id).last
|
297
|
+
end
|
298
|
+
|
264
299
|
def last_active_task
|
265
300
|
tasks.exclusive.last
|
266
301
|
end
|
@@ -269,6 +304,10 @@ module Shipit
|
|
269
304
|
last_completed_deploy&.until_commit || NoDeployedCommit
|
270
305
|
end
|
271
306
|
|
307
|
+
def previous_successful_deploy_commit(deploy_id)
|
308
|
+
previous_successful_deploy(deploy_id)&.until_commit || NoDeployedCommit
|
309
|
+
end
|
310
|
+
|
272
311
|
def deployable?
|
273
312
|
!locked? && !active_task?
|
274
313
|
end
|
@@ -379,6 +418,15 @@ module Shipit
|
|
379
418
|
[repo_owner, repo_name, environment].join('/')
|
380
419
|
end
|
381
420
|
|
421
|
+
def self.run_deploy_in_foreground(stack:, revision:)
|
422
|
+
stack = Shipit::Stack.from_param!(stack)
|
423
|
+
until_commit = stack.commits.where(sha: revision).limit(1).first
|
424
|
+
env = stack.cached_deploy_spec.default_deploy_env
|
425
|
+
current_user = Shipit::CommandLineUser.new
|
426
|
+
|
427
|
+
stack.trigger_deploy(until_commit, current_user, env: env, force: true, run_now: true)
|
428
|
+
end
|
429
|
+
|
382
430
|
def self.from_param!(param)
|
383
431
|
repo_owner, repo_name, environment = param.split('/')
|
384
432
|
where(
|
@@ -414,6 +462,10 @@ module Shipit
|
|
414
462
|
update(undeployed_commits_count: undeployed_commits)
|
415
463
|
end
|
416
464
|
|
465
|
+
def update_latest_deployed_ref
|
466
|
+
UpdateGithubLastDeployedRefJob.perform_later(self)
|
467
|
+
end
|
468
|
+
|
417
469
|
def broadcast_update
|
418
470
|
Pubsubstub.publish(
|
419
471
|
"stack.#{id}",
|
@@ -502,7 +554,12 @@ module Shipit
|
|
502
554
|
|
503
555
|
def emit_lock_hooks
|
504
556
|
return unless previous_changes.include?('lock_reason')
|
505
|
-
|
557
|
+
|
558
|
+
lock_details = if previous_changes['lock_reason'].last.blank?
|
559
|
+
{from: previous_changes['locked_since'].first, until: Time.zone.now}
|
560
|
+
end
|
561
|
+
|
562
|
+
Hook.emit(:lock, self, locked: locked?, lock_details: lock_details, stack: self)
|
506
563
|
end
|
507
564
|
|
508
565
|
def emit_added_hooks
|
data/app/models/shipit/task.rb
CHANGED
@@ -47,6 +47,10 @@ module Shipit
|
|
47
47
|
completed.last
|
48
48
|
end
|
49
49
|
|
50
|
+
def last_successful
|
51
|
+
success.last
|
52
|
+
end
|
53
|
+
|
50
54
|
def current
|
51
55
|
active.exclusive.last
|
52
56
|
end
|
@@ -176,7 +180,13 @@ module Shipit
|
|
176
180
|
PerformTaskJob.perform_later(self)
|
177
181
|
end
|
178
182
|
|
183
|
+
def run_now!
|
184
|
+
raise "only persisted jobs can be run" unless persisted?
|
185
|
+
PerformTaskJob.perform_now(self)
|
186
|
+
end
|
187
|
+
|
179
188
|
def write(text)
|
189
|
+
log_output(text)
|
180
190
|
chunks.create!(text: text)
|
181
191
|
end
|
182
192
|
|
@@ -307,6 +317,16 @@ module Shipit
|
|
307
317
|
Shipit::Engine.routes.url_helpers.stack_task_url(stack, self)
|
308
318
|
end
|
309
319
|
|
320
|
+
def commit_to_rollback_to
|
321
|
+
previous_deployed_commit = stack.previous_successful_deploy_commit(id)
|
322
|
+
|
323
|
+
if previous_deployed_commit == Shipit::Stack::NoDeployedCommit
|
324
|
+
since_commit
|
325
|
+
else
|
326
|
+
previous_deployed_commit
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
310
330
|
private
|
311
331
|
|
312
332
|
def prevent_concurrency
|
@@ -320,5 +340,15 @@ module Shipit
|
|
320
340
|
def abort_key
|
321
341
|
"#{status_key}:aborting"
|
322
342
|
end
|
343
|
+
|
344
|
+
def log_output(text)
|
345
|
+
output_line_buffer.buffer(text) do |line|
|
346
|
+
Shipit.task_logger.info("[#{stack.repo_name}##{id}] #{line}")
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def output_line_buffer
|
351
|
+
@output_line_buffer ||= LineBuffer.new
|
352
|
+
end
|
323
353
|
end
|
324
354
|
end
|
@@ -2,9 +2,10 @@ module Shipit
|
|
2
2
|
class UndeployedCommit < DelegateClass(Commit)
|
3
3
|
attr_reader :index
|
4
4
|
|
5
|
-
def initialize(commit, index)
|
5
|
+
def initialize(commit, index:, next_expected_commit_to_deploy: nil)
|
6
6
|
super(commit)
|
7
7
|
@index = index
|
8
|
+
@next_expected_commit_to_deploy = next_expected_commit_to_deploy
|
8
9
|
end
|
9
10
|
|
10
11
|
def deploy_state(bypass_safeties = false)
|
@@ -38,6 +39,14 @@ module Shipit
|
|
38
39
|
stack.maximum_commits_per_deploy && index >= stack.maximum_commits_per_deploy
|
39
40
|
end
|
40
41
|
|
42
|
+
def expected_to_be_deployed?
|
43
|
+
return false if @next_expected_commit_to_deploy.nil?
|
44
|
+
return false unless stack.continuous_deployment
|
45
|
+
return false if active?
|
46
|
+
|
47
|
+
id <= @next_expected_commit_to_deploy.id
|
48
|
+
end
|
49
|
+
|
41
50
|
def blocked?
|
42
51
|
return @blocked if defined?(@blocked)
|
43
52
|
@blocked = super
|
@@ -1,7 +1,11 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<html lang="<%= I18n.locale %>" data-controller="<%= controller_name %>" data-action="<%= action_name %>">
|
3
3
|
<head>
|
4
|
-
|
4
|
+
<% if @stack %>
|
5
|
+
<title><%= Shipit.app_name %> - <%= @stack.repo_name %>/<%= @stack.environment %></title>
|
6
|
+
<% else %>
|
7
|
+
<title><%= Shipit.app_name %></title>
|
8
|
+
<% end %>
|
5
9
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
10
|
<%= favicon_link_tag %>
|
7
11
|
<%= stylesheet_link_tag :shipit, media: 'all' %>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<li class="commit <%= 'locked' if commit.locked? %>" id="commit-<%= commit.id %>">
|
2
|
-
<% cache commit do %>
|
2
|
+
<% cache [commit, commit.expected_to_be_deployed?] do %>
|
3
3
|
<% cache commit.author do %>
|
4
4
|
<%= render 'shipit/shared/author', author: commit.author %>
|
5
5
|
<% end %>
|
@@ -17,12 +17,17 @@
|
|
17
17
|
<p class="commit-meta">
|
18
18
|
<%= timeago_tag(commit.committed_at, force: true) %>
|
19
19
|
</p>
|
20
|
+
<% if commit.expected_to_be_deployed? %>
|
21
|
+
<p class="commit-meta">
|
22
|
+
<span class="scheduled">expected to be deployed next</span>
|
23
|
+
</p>
|
24
|
+
<% end %>
|
20
25
|
</div>
|
21
26
|
<div class="commit-lock" >
|
22
27
|
<%= link_to stack_commit_path(commit.stack, commit), class: 'action-lock-commit', data: {tooltip: t('commit.lock')} do %>
|
23
28
|
<i class="icon icon--lock"></i>
|
24
29
|
<% end %>
|
25
|
-
<%= link_to stack_commit_path(commit.stack, commit), class: 'action-unlock-commit', data: {tooltip:
|
30
|
+
<%= link_to stack_commit_path(commit.stack, commit), class: 'action-unlock-commit', data: {tooltip: unlock_commit_tooltip(commit), confirm: t('commit.confirm_unlock')} do %>
|
26
31
|
<i class="icon icon--lock"></i>
|
27
32
|
<% end %>
|
28
33
|
</div>
|