shipit-engine 0.24.0 → 0.25.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/shipit/stacks.js.coffee +15 -1
- data/app/assets/stylesheets/_base/_icons.scss +18 -0
- data/app/assets/stylesheets/_base/_status-items.scss +28 -0
- data/app/assets/stylesheets/_pages/_commits.scss +1 -5
- data/app/assets/stylesheets/_pages/_deploy.scss +60 -3
- data/app/controllers/concerns/shipit/authentication.rb +1 -1
- data/app/controllers/shipit/merge_status_controller.rb +2 -0
- data/app/controllers/shipit/release_statuses_controller.rb +36 -0
- data/app/jobs/shipit/append_delayed_release_status_job.rb +17 -0
- data/app/jobs/shipit/cache_deploy_spec_job.rb +2 -0
- data/app/jobs/shipit/clear_git_cache_job.rb +1 -1
- data/app/jobs/shipit/create_release_statuses_job.rb +11 -0
- data/app/jobs/shipit/deferred_touch_job.rb +2 -0
- data/app/jobs/shipit/deliver_hook_job.rb +1 -0
- data/app/jobs/shipit/merge_pull_requests_job.rb +2 -0
- data/app/jobs/shipit/perform_commit_checks_job.rb +2 -0
- data/app/jobs/shipit/perform_task_job.rb +2 -4
- data/app/jobs/shipit/refresh_pull_request_job.rb +2 -0
- data/app/models/shipit/anonymous_user.rb +1 -1
- data/app/models/shipit/commit.rb +36 -3
- data/app/models/shipit/deploy.rb +43 -0
- data/app/models/shipit/deploy_spec.rb +16 -2
- data/app/models/shipit/deploy_spec/bundler_discovery.rb +5 -1
- data/app/models/shipit/deploy_spec/file_system.rb +4 -0
- data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +1 -1
- data/app/models/shipit/ephemeral_commit_checks.rb +2 -4
- data/app/models/shipit/hook.rb +36 -3
- data/app/models/shipit/pull_request.rb +4 -2
- data/app/models/shipit/release_status.rb +41 -0
- data/app/models/shipit/rollback.rb +9 -0
- data/app/models/shipit/stack.rb +4 -9
- data/app/models/shipit/status/common.rb +4 -0
- data/app/models/shipit/status/group.rb +2 -1
- data/app/models/shipit/status/missing.rb +4 -0
- data/app/models/shipit/status/unknown.rb +15 -0
- data/app/models/shipit/task.rb +4 -0
- data/app/models/shipit/user.rb +16 -3
- data/app/serializers/shipit/stack_serializer.rb +1 -1
- data/app/views/shipit/deploys/_deploy.html.erb +18 -2
- data/config/locales/en.yml +3 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20180802172632_allow_commit_without_author.rb +6 -0
- data/db/migrate/20180906083930_create_release_statuses.rb +21 -0
- data/lib/shipit.rb +5 -0
- data/lib/shipit/command.rb +14 -18
- data/lib/shipit/deploy_commands.rb +0 -4
- data/lib/shipit/engine.rb +1 -1
- data/lib/shipit/first_parent_commits_iterator.rb +1 -1
- data/lib/shipit/flock.rb +43 -0
- data/lib/shipit/github_app.rb +5 -3
- data/lib/shipit/rollback_commands.rb +6 -0
- data/lib/shipit/task_commands.rb +1 -5
- data/lib/shipit/version.rb +1 -1
- data/test/controllers/release_statuses_controller_test.rb +23 -0
- data/test/dummy/db/schema.rb +18 -3
- data/test/dummy/db/seeds.rb +4 -0
- data/test/fixtures/shipit/commits.yml +13 -0
- data/test/fixtures/shipit/release_statuses.yml +16 -0
- data/test/fixtures/shipit/stacks.yml +4 -0
- data/test/jobs/append_delayed_release_status_job_test.rb +25 -0
- data/test/jobs/cache_deploy_spec_job_test.rb +1 -2
- data/test/jobs/emit_event_job_test.rb +1 -1
- data/test/jobs/github_sync_job_test.rb +1 -0
- data/test/models/commits_test.rb +54 -1
- data/test/models/deploy_spec_test.rb +83 -11
- data/test/models/deploys_test.rb +52 -0
- data/test/models/hook_test.rb +1 -28
- data/test/models/pull_request_test.rb +19 -0
- data/test/models/release_statuses_test.rb +28 -0
- data/test/models/rollbacks_test.rb +2 -0
- data/test/models/stacks_test.rb +1 -1
- data/test/test_helper.rb +5 -0
- data/test/unit/rollback_commands_test.rb +35 -0
- metadata +121 -104
data/app/models/shipit/deploy.rb
CHANGED
@@ -9,6 +9,7 @@ module Shipit
|
|
9
9
|
after_transition to: :success, do: :schedule_merges
|
10
10
|
after_transition to: :success, do: :update_undeployed_commits_count
|
11
11
|
after_transition to: :aborted, do: :trigger_revert_if_required
|
12
|
+
after_transition any => any, do: :update_release_status
|
12
13
|
after_transition any => any, do: :update_commit_deployments
|
13
14
|
after_transition any => any, do: :update_last_deploy_time
|
14
15
|
end
|
@@ -33,6 +34,7 @@ module Shipit
|
|
33
34
|
|
34
35
|
before_create :denormalize_commit_stats
|
35
36
|
after_create :create_commit_deployments
|
37
|
+
after_create :update_release_status
|
36
38
|
after_commit :broadcast_update
|
37
39
|
|
38
40
|
delegate :broadcast_update, :filter_deploy_envs, to: :stack
|
@@ -143,6 +145,21 @@ module Shipit
|
|
143
145
|
confirmations.abs >= CONFIRMATIONS_REQUIRED
|
144
146
|
end
|
145
147
|
|
148
|
+
delegate :last_release_status, to: :until_commit
|
149
|
+
def append_release_status(state, description, user: self.user)
|
150
|
+
status = until_commit.create_release_status!(
|
151
|
+
state,
|
152
|
+
user: user.presence,
|
153
|
+
target_url: permalink,
|
154
|
+
description: description,
|
155
|
+
)
|
156
|
+
status
|
157
|
+
end
|
158
|
+
|
159
|
+
def permalink
|
160
|
+
Shipit::Engine.routes.url_helpers.stack_deploy_url(stack, self)
|
161
|
+
end
|
162
|
+
|
146
163
|
private
|
147
164
|
|
148
165
|
def create_commit_deployments
|
@@ -151,6 +168,32 @@ module Shipit
|
|
151
168
|
end
|
152
169
|
end
|
153
170
|
|
171
|
+
def update_release_status
|
172
|
+
return unless stack.release_status?
|
173
|
+
|
174
|
+
case status
|
175
|
+
when 'pending'
|
176
|
+
append_release_status('pending', "A deploy was triggered on #{stack.environment}")
|
177
|
+
when 'failed', 'error', 'timedout'
|
178
|
+
append_release_status('error', "The deploy on #{stack.environment} did not succeed (#{status})")
|
179
|
+
when 'aborted', 'aborting'
|
180
|
+
append_release_status('failure', "The deploy on #{stack.environment} was canceled")
|
181
|
+
when 'success'
|
182
|
+
delay = stack.release_status_delay
|
183
|
+
if delay.zero?
|
184
|
+
append_release_status('success', "The deploy on #{stack.environment} succeeded")
|
185
|
+
elsif delay.positive?
|
186
|
+
append_release_status('pending', "The deploy on #{stack.environment} succeeded")
|
187
|
+
AppendDelayedReleaseStatusJob.set(wait: delay).perform_later(
|
188
|
+
self,
|
189
|
+
cursor: until_commit.release_statuses.last,
|
190
|
+
status: 'success',
|
191
|
+
description: "No issue were signaled after #{delay}",
|
192
|
+
)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
154
197
|
def update_commit_deployments
|
155
198
|
commit_deployments.append_status(status)
|
156
199
|
end
|
@@ -67,6 +67,20 @@ module Shipit
|
|
67
67
|
config('deploy', 'max_commits') { 8 }
|
68
68
|
end
|
69
69
|
|
70
|
+
def release_status?
|
71
|
+
!!release_status_context
|
72
|
+
end
|
73
|
+
|
74
|
+
def release_status_context
|
75
|
+
config('status', 'context')
|
76
|
+
end
|
77
|
+
|
78
|
+
def release_status_delay
|
79
|
+
if delay = config('status', 'delay') { config('deploy', 'interval') }
|
80
|
+
Duration.parse(delay)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
70
84
|
def pause_between_deploys
|
71
85
|
Duration.parse(config('deploy', 'interval') { 0 })
|
72
86
|
end
|
@@ -131,7 +145,7 @@ module Shipit
|
|
131
145
|
end
|
132
146
|
|
133
147
|
def hidden_statuses
|
134
|
-
Array.wrap(config('ci', 'hide'))
|
148
|
+
Array.wrap(config('ci', 'hide')) + [release_status_context].compact
|
135
149
|
end
|
136
150
|
|
137
151
|
def required_statuses
|
@@ -161,7 +175,7 @@ module Shipit
|
|
161
175
|
|
162
176
|
def pull_request_ignored_statuses
|
163
177
|
if config('merge', 'require') || config('merge', 'ignore')
|
164
|
-
Array.wrap(config('merge', 'ignore'))
|
178
|
+
Array.wrap(config('merge', 'ignore')) + [release_status_context].compact
|
165
179
|
else
|
166
180
|
soft_failing_statuses | hidden_statuses
|
167
181
|
end
|
@@ -19,8 +19,12 @@ module Shipit
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
def discover_machine_env
|
23
|
+
super.merge('BUNDLE_PATH' => bundle_path.to_s)
|
24
|
+
end
|
25
|
+
|
22
26
|
def bundle_install
|
23
|
-
bundle = %(bundle
|
27
|
+
bundle = %(bundle install #{frozen_flag} --jobs 4 --path #{bundle_path} --retry 2)
|
24
28
|
bundle += " --without=#{bundler_without.join(':')}" unless bundler_without.empty?
|
25
29
|
[remove_ruby_version_from_gemfile, bundle]
|
26
30
|
end
|
@@ -57,6 +57,10 @@ module Shipit
|
|
57
57
|
'checks' => review_checks,
|
58
58
|
},
|
59
59
|
'plugins' => plugins,
|
60
|
+
'status' => {
|
61
|
+
'context' => release_status_context,
|
62
|
+
'delay' => release_status_delay,
|
63
|
+
},
|
60
64
|
'dependencies' => {'override' => dependencies_steps},
|
61
65
|
'deploy' => {
|
62
66
|
'override' => deploy_steps,
|
@@ -14,10 +14,8 @@ module Shipit
|
|
14
14
|
commands = StackCommands.new(stack)
|
15
15
|
commands.with_temporary_working_directory(commit: commit) do |directory|
|
16
16
|
deploy_spec = DeploySpec::FileSystem.new(directory, stack.environment)
|
17
|
-
|
18
|
-
|
19
|
-
capture_all(build_commands(deploy_spec.review_checks, chdir: directory))
|
20
|
-
end
|
17
|
+
capture_all(build_commands(deploy_spec.dependencies_steps, chdir: directory))
|
18
|
+
capture_all(build_commands(deploy_spec.review_checks, chdir: directory))
|
21
19
|
end
|
22
20
|
self
|
23
21
|
rescue Command::Error
|
data/app/models/shipit/hook.rb
CHANGED
@@ -1,5 +1,38 @@
|
|
1
1
|
module Shipit
|
2
2
|
class Hook < ActiveRecord::Base
|
3
|
+
class DeliverySpec
|
4
|
+
def initialize(event:, url:, content_type:, payload:)
|
5
|
+
@event = event
|
6
|
+
@url = url
|
7
|
+
@content_type = content_type
|
8
|
+
@payload = payload
|
9
|
+
end
|
10
|
+
|
11
|
+
def send!
|
12
|
+
http.post(url, payload)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :event, :url, :content_type, :payload
|
18
|
+
|
19
|
+
def http
|
20
|
+
Faraday::Connection.new do |connection|
|
21
|
+
connection.headers = headers
|
22
|
+
connection.adapter Faraday.default_adapter
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def headers
|
27
|
+
{
|
28
|
+
'User-Agent' => 'Shipit Webhook',
|
29
|
+
'Content-Type' => content_type,
|
30
|
+
'X-Shipit-Event' => event,
|
31
|
+
'Accept' => '*/*',
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
3
36
|
default_scope { order :id }
|
4
37
|
|
5
38
|
DELIVERIES_LOG_SIZE = 500
|
@@ -71,12 +104,12 @@ module Shipit
|
|
71
104
|
end
|
72
105
|
|
73
106
|
def deliver!(event, payload)
|
74
|
-
|
75
|
-
event: event,
|
107
|
+
DeliverHookJob.perform_later(
|
108
|
+
event: event.to_s,
|
76
109
|
url: delivery_url,
|
77
110
|
content_type: CONTENT_TYPES[content_type],
|
78
111
|
payload: serialize_payload(payload),
|
79
|
-
)
|
112
|
+
)
|
80
113
|
end
|
81
114
|
|
82
115
|
def purge_old_deliveries!(keep: DELIVERIES_LOG_SIZE)
|
@@ -2,6 +2,8 @@ module Shipit
|
|
2
2
|
class PullRequest < ApplicationRecord
|
3
3
|
include DeferredTouch
|
4
4
|
|
5
|
+
MERGE_REQUEST_FIELD = 'Merge-Requested-By'.freeze
|
6
|
+
|
5
7
|
WAITING_STATUSES = %w(fetching pending).freeze
|
6
8
|
QUEUED_STATUSES = %w(pending revalidating).freeze
|
7
9
|
REJECTION_REASONS = %w(ci_failing merge_conflict requires_rebase).freeze
|
@@ -188,7 +190,7 @@ module Shipit
|
|
188
190
|
|
189
191
|
def any_status_checks_failed?
|
190
192
|
status = StatusChecker.new(head, head.statuses, stack.cached_deploy_spec)
|
191
|
-
status.failure? || status.error?
|
193
|
+
status.failure? || status.error? || status.missing?
|
192
194
|
end
|
193
195
|
|
194
196
|
def waiting?
|
@@ -245,7 +247,7 @@ module Shipit
|
|
245
247
|
|
246
248
|
def merge_message
|
247
249
|
return title unless merge_requested_by
|
248
|
-
"#{title}\n\
|
250
|
+
"#{title}\n\n#{MERGE_REQUEST_FIELD}: #{merge_requested_by.login}\n"
|
249
251
|
end
|
250
252
|
|
251
253
|
def stale?
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Shipit
|
2
|
+
class ReleaseStatus < ActiveRecord::Base
|
3
|
+
MAX_DESCRIPTION_LENGTH = 140
|
4
|
+
include DeferredTouch
|
5
|
+
|
6
|
+
belongs_to :stack
|
7
|
+
belongs_to :commit
|
8
|
+
belongs_to :user, optional: true
|
9
|
+
|
10
|
+
deferred_touch stack: :updated_at, commit: :updated_at
|
11
|
+
after_commit :schedule_create_release_statuses, on: :create
|
12
|
+
|
13
|
+
scope :to_be_created, -> { where(github_id: nil).order(id: :asc) }
|
14
|
+
|
15
|
+
STATES = %w(pending success failure error).freeze
|
16
|
+
validates :state, presence: true, inclusion: {in: STATES}
|
17
|
+
|
18
|
+
def create_status_on_github!
|
19
|
+
return true if github_id?
|
20
|
+
|
21
|
+
update!(github_id: create_status_on_github.id)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def create_status_on_github
|
27
|
+
Shipit.github.api.create_status(
|
28
|
+
stack.github_repo_name,
|
29
|
+
commit.sha,
|
30
|
+
state,
|
31
|
+
context: stack.release_status_context,
|
32
|
+
target_url: target_url,
|
33
|
+
description: description&.truncate(MAX_DESCRIPTION_LENGTH),
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def schedule_create_release_statuses
|
38
|
+
CreateReleaseStatusesJob.perform_later(commit)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -34,6 +34,15 @@ module Shipit
|
|
34
34
|
|
35
35
|
private
|
36
36
|
|
37
|
+
def update_release_status
|
38
|
+
return unless stack.release_status?
|
39
|
+
|
40
|
+
case status
|
41
|
+
when 'pending'
|
42
|
+
deploy.append_release_status('failure', "A rollback #{stack.to_param} was triggered")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
37
46
|
def lock_reverted_commits
|
38
47
|
stack.lock_reverted_commits!
|
39
48
|
end
|
data/app/models/shipit/stack.rb
CHANGED
@@ -73,8 +73,8 @@ module Shipit
|
|
73
73
|
validates :lock_reason, length: {maximum: 4096}
|
74
74
|
|
75
75
|
serialize :cached_deploy_spec, DeploySpec
|
76
|
-
delegate :find_task_definition, :supports_rollback?, :links,
|
77
|
-
:supports_fetch_deployed_revision?, to: :cached_deploy_spec, allow_nil: true
|
76
|
+
delegate :find_task_definition, :supports_rollback?, :links, :release_status?, :release_status_delay,
|
77
|
+
:release_status_context, :supports_fetch_deployed_revision?, to: :cached_deploy_spec, allow_nil: true
|
78
78
|
|
79
79
|
def self.refresh_deployed_revisions
|
80
80
|
find_each.select(&:supports_fetch_deployed_revision?).each(&:async_refresh_deployed_revision)
|
@@ -307,13 +307,8 @@ module Shipit
|
|
307
307
|
File.join(base_path, "git")
|
308
308
|
end
|
309
309
|
|
310
|
-
def acquire_git_cache_lock(timeout: 15,
|
311
|
-
|
312
|
-
"stack:#{id}:git-cache-lock",
|
313
|
-
Shipit.redis,
|
314
|
-
timeout: timeout,
|
315
|
-
expiration: expiration,
|
316
|
-
).lock(&block)
|
310
|
+
def acquire_git_cache_lock(timeout: 15, &block)
|
311
|
+
Flock.new(git_path.to_s + '.lock').lock(timeout: timeout, &block)
|
317
312
|
end
|
318
313
|
|
319
314
|
def clear_git_cache!
|
@@ -29,7 +29,8 @@ module Shipit
|
|
29
29
|
@statuses = visible_statuses.sort_by!(&:context)
|
30
30
|
end
|
31
31
|
|
32
|
-
delegate :pending?, :success?, :error?, :failure?, :unknown?, :state, :simple_state,
|
32
|
+
delegate :pending?, :success?, :error?, :failure?, :unknown?, :missing?, :state, :simple_state,
|
33
|
+
to: :significant_status
|
33
34
|
delegate :each, :size, :map, to: :statuses
|
34
35
|
delegate :required_statuses, to: :commit
|
35
36
|
|
@@ -1,18 +1,33 @@
|
|
1
1
|
module Shipit
|
2
2
|
class Status
|
3
3
|
class Unknown
|
4
|
+
include GlobalID::Identification
|
4
5
|
include Common
|
5
6
|
|
7
|
+
class << self
|
8
|
+
def find(id)
|
9
|
+
new(Commit.find(id))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
6
13
|
attr_reader :commit
|
7
14
|
|
8
15
|
def initialize(commit)
|
9
16
|
@commit = commit
|
10
17
|
end
|
11
18
|
|
19
|
+
def id
|
20
|
+
commit.id
|
21
|
+
end
|
22
|
+
|
12
23
|
def state
|
13
24
|
'unknown'.freeze
|
14
25
|
end
|
15
26
|
|
27
|
+
def missing?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
16
31
|
def target_url
|
17
32
|
nil
|
18
33
|
end
|
data/app/models/shipit/task.rb
CHANGED
data/app/models/shipit/user.rb
CHANGED
@@ -8,6 +8,8 @@ module Shipit
|
|
8
8
|
has_many :commits, foreign_key: :committer_id, inverse_of: :committer
|
9
9
|
has_many :tasks
|
10
10
|
|
11
|
+
validates :name, presence: true
|
12
|
+
|
11
13
|
attr_encrypted :github_access_token, key: Shipit.user_access_tokens_key
|
12
14
|
|
13
15
|
def self.find_or_create_by_login!(login)
|
@@ -16,8 +18,19 @@ module Shipit
|
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
21
|
+
def self.find_or_create_committer_from_github_commit(github_commit)
|
22
|
+
find_or_create_from_github(github_commit.committer.presence || github_commit.commit.committer.presence)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.find_or_create_author_from_github_commit(github_commit)
|
26
|
+
if github_commit.commit.message =~ /^#{PullRequest::MERGE_REQUEST_FIELD}: ([\w\-\.]+)$/
|
27
|
+
return find_or_create_by_login!($1)
|
28
|
+
end
|
29
|
+
find_or_create_from_github(github_commit.author.presence || github_commit.commit.author.presence)
|
30
|
+
end
|
31
|
+
|
19
32
|
def self.find_or_create_from_github(github_user)
|
20
|
-
find_from_github(github_user) || create_from_github
|
33
|
+
find_from_github(github_user) || create_from_github(github_user)
|
21
34
|
end
|
22
35
|
|
23
36
|
def self.find_from_github(github_user)
|
@@ -25,8 +38,8 @@ module Shipit
|
|
25
38
|
find_by(github_id: github_user.id)
|
26
39
|
end
|
27
40
|
|
28
|
-
def self.create_from_github
|
29
|
-
create
|
41
|
+
def self.create_from_github(github_user)
|
42
|
+
create(github_user: github_user)
|
30
43
|
end
|
31
44
|
|
32
45
|
def self.refresh_shard(shard_index, shards_count)
|