shipit-engine 0.33.0 → 0.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -2
  3. data/app/assets/stylesheets/_pages/_deploy.scss +0 -2
  4. data/app/controllers/shipit/api/ccmenu_controller.rb +1 -1
  5. data/app/controllers/shipit/api/deploys_controller.rb +2 -0
  6. data/app/controllers/shipit/api/rollbacks_controller.rb +2 -1
  7. data/app/controllers/shipit/api/stacks_controller.rb +1 -0
  8. data/app/controllers/shipit/deploys_controller.rb +1 -1
  9. data/app/controllers/shipit/stacks_controller.rb +2 -2
  10. data/app/controllers/shipit/tasks_controller.rb +2 -2
  11. data/app/controllers/shipit/webhooks_controller.rb +23 -4
  12. data/app/helpers/shipit/shipit_helper.rb +0 -1
  13. data/app/jobs/shipit/deliver_hook_job.rb +1 -1
  14. data/app/jobs/shipit/github_sync_job.rb +13 -9
  15. data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +1 -1
  16. data/app/models/shipit/anonymous_user.rb +6 -2
  17. data/app/models/shipit/check_run.rb +36 -0
  18. data/app/models/shipit/commit.rb +20 -9
  19. data/app/models/shipit/commit_checks.rb +13 -13
  20. data/app/models/shipit/commit_deployment.rb +3 -3
  21. data/app/models/shipit/commit_deployment_status.rb +3 -3
  22. data/app/models/shipit/deploy.rb +16 -11
  23. data/app/models/shipit/deploy_spec/lerna_discovery.rb +12 -4
  24. data/app/models/shipit/duration.rb +2 -0
  25. data/app/models/shipit/hook.rb +26 -2
  26. data/app/models/shipit/merge_request.rb +9 -7
  27. data/app/models/shipit/pull_request.rb +1 -1
  28. data/app/models/shipit/release_status.rb +1 -1
  29. data/app/models/shipit/repository.rb +9 -3
  30. data/app/models/shipit/review_stack.rb +16 -2
  31. data/app/models/shipit/stack.rb +59 -25
  32. data/app/models/shipit/status/group.rb +1 -1
  33. data/app/models/shipit/task.rb +6 -2
  34. data/app/models/shipit/task_execution_strategy/default.rb +4 -5
  35. data/app/models/shipit/team.rb +4 -2
  36. data/app/models/shipit/user.rb +4 -0
  37. data/app/models/shipit/webhooks/handlers/pull_request/review_stack_adapter.rb +1 -1
  38. data/app/models/shipit/webhooks/handlers/push_handler.rb +4 -1
  39. data/app/serializers/shipit/merge_request_serializer.rb +1 -1
  40. data/app/validators/subset_validator.rb +1 -1
  41. data/app/views/layouts/merge_status.html.erb +1 -1
  42. data/app/views/shipit/stacks/_banners.html.erb +2 -1
  43. data/app/views/shipit/stacks/new.html.erb +1 -1
  44. data/config/secrets.development.example.yml +24 -0
  45. data/config/secrets.development.shopify.yml +20 -9
  46. data/db/migrate/20210325194053_remove_stacks_branch_default.rb +5 -0
  47. data/db/migrate/20210504200438_add_github_updated_at_to_check_runs.rb +5 -0
  48. data/lib/shipit.rb +39 -15
  49. data/lib/shipit/command.rb +7 -6
  50. data/lib/shipit/commands.rb +9 -2
  51. data/lib/shipit/engine.rb +2 -0
  52. data/lib/shipit/flock.rb +8 -1
  53. data/lib/shipit/github_app.rb +7 -5
  54. data/lib/shipit/octokit_iterator.rb +3 -3
  55. data/lib/shipit/simple_message_verifier.rb +2 -2
  56. data/lib/shipit/stack_commands.rb +28 -4
  57. data/lib/shipit/task_commands.rb +6 -0
  58. data/lib/shipit/version.rb +1 -1
  59. data/lib/snippets/publish-lerna-independent-packages +35 -34
  60. data/lib/snippets/publish-lerna-independent-packages-legacy +39 -0
  61. data/test/controllers/api/ccmenu_controller_test.rb +1 -1
  62. data/test/controllers/api/deploys_controller_test.rb +17 -0
  63. data/test/controllers/api/stacks_controller_test.rb +21 -7
  64. data/test/controllers/webhooks_controller_test.rb +26 -11
  65. data/test/dummy/app/assets/config/manifest.js +3 -0
  66. data/test/dummy/config/application.rb +1 -1
  67. data/test/dummy/config/database.yml +9 -0
  68. data/test/dummy/config/environments/development.rb +1 -1
  69. data/test/dummy/config/secrets_double_github_app.yml +79 -0
  70. data/test/dummy/db/schema.rb +5 -4
  71. data/test/dummy/db/seeds.rb +1 -0
  72. data/test/fixtures/payloads/check_suite_master.json +2 -30
  73. data/test/fixtures/payloads/push_master.json +1 -1
  74. data/test/fixtures/payloads/push_not_master.json +1 -1
  75. data/test/fixtures/shipit/commits.yml +2 -2
  76. data/test/fixtures/shipit/hooks.yml +1 -0
  77. data/test/fixtures/shipit/tasks.yml +1 -1
  78. data/test/helpers/json_helper.rb +5 -1
  79. data/test/jobs/github_sync_job_test.rb +2 -1
  80. data/test/models/commit_deployment_status_test.rb +3 -3
  81. data/test/models/commits_test.rb +2 -0
  82. data/test/models/deploy_spec_test.rb +7 -0
  83. data/test/models/deploys_test.rb +18 -0
  84. data/test/models/hook_test.rb +30 -1
  85. data/test/models/merge_request_test.rb +19 -4
  86. data/test/models/shipit/check_run_test.rb +124 -5
  87. data/test/models/shipit/review_stack_test.rb +38 -6
  88. data/test/models/shipit/stacks_test.rb +42 -4
  89. data/test/models/shipit/webhooks/handlers/pull_request/review_stack_adapter_test.rb +24 -0
  90. data/test/models/tasks_test.rb +22 -0
  91. data/test/test_helper.rb +15 -0
  92. data/test/unit/anonymous_user_serializer_test.rb +1 -1
  93. data/test/unit/command_test.rb +5 -0
  94. data/test/unit/commit_serializer_test.rb +1 -1
  95. data/test/unit/deploy_commands_test.rb +70 -14
  96. data/test/unit/deploy_serializer_test.rb +1 -1
  97. data/test/unit/github_app_test.rb +2 -3
  98. data/test/unit/github_apps_test.rb +416 -0
  99. data/test/unit/shipit_deployment_checks_test.rb +77 -0
  100. data/test/unit/shipit_test.rb +14 -0
  101. data/test/unit/user_serializer_test.rb +1 -1
  102. metadata +202 -191
@@ -10,14 +10,14 @@ module Shipit
10
10
  end
11
11
 
12
12
  def synchronize(&block)
13
- @lock ||= Redis::Lock.new('lock', redis, expiration: 1, timeout: 2)
13
+ @lock ||= Redis::Lock.new(key('lock'), Shipit.redis, expiration: 1, timeout: 2)
14
14
  @lock.lock(&block)
15
15
  end
16
16
 
17
17
  def schedule
18
- return false if redis.get('status').present?
18
+ return false if Shipit.redis.get(key('status')).present?
19
19
  synchronize do
20
- return false if redis.get('status').present?
20
+ return false if Shipit.redis.get(key('status')).present?
21
21
 
22
22
  initialize_redis_state
23
23
  end
@@ -26,34 +26,34 @@ module Shipit
26
26
  end
27
27
 
28
28
  def initialize_redis_state
29
- redis.pipelined do
30
- redis.set('output', '', ex: OUTPUT_TTL)
31
- redis.set('status', 'scheduled', ex: OUTPUT_TTL)
32
- end
29
+ Shipit.redis.set(key('status'), 'scheduled', ex: OUTPUT_TTL)
33
30
  @status = 'scheduled'
34
31
  end
35
32
 
36
33
  def status
37
- @status ||= redis.get('status')
34
+ @status ||= Shipit.redis.get(key('status'))
38
35
  end
39
36
 
40
37
  def status=(status)
41
- redis.set('status', status)
38
+ Shipit.redis.set(key('status'), status)
42
39
  @status = status
43
40
  end
44
41
 
45
42
  def output(since: 0)
46
- redis.getrange('output', since, -1)
43
+ Shipit.redis.getrange(key('output'), since, -1)
47
44
  end
48
45
 
49
46
  def write(output)
50
- redis.append('output', output)
47
+ Shipit.redis.pipelined do
48
+ Shipit.redis.append(key('output'), output)
49
+ Shipit.redis.expire(key('output'), OUTPUT_TTL)
50
+ end
51
51
  end
52
52
 
53
53
  private
54
54
 
55
- def redis
56
- @redis ||= Shipit.redis("commit:#{commit.id}:checks")
55
+ def key(key)
56
+ "commit:#{commit.id}:checks:#{key}"
57
57
  end
58
58
  end
59
59
  end
@@ -20,15 +20,15 @@ module Shipit
20
20
  return if github_id?
21
21
 
22
22
  response = begin
23
- create_deployment_on_github(author.github_api)
23
+ create_deployment_on_github(stack.github_api)
24
24
  rescue Octokit::ClientError
25
- raise if Shipit.github.api == author.github_api
25
+ raise if Shipit.github(organization: stack.repository.owner).api == stack.github_api
26
26
  # If the deploy author didn't gave us the permission to create the deployment we falback the the main shipit
27
27
  # user.
28
28
  #
29
29
  # Octokit currently raise NotFound, but I'm convinced it should be Forbidden if the user can see the repository.
30
30
  # So to be future proof I catch boths.
31
- create_deployment_on_github(Shipit.github.api)
31
+ create_deployment_on_github(stack.github_api)
32
32
  end
33
33
  update!(github_id: response.id, api_url: response.url)
34
34
  end
@@ -12,15 +12,15 @@ module Shipit
12
12
  def create_on_github!
13
13
  return if github_id?
14
14
  response = begin
15
- create_status_on_github(author.github_api)
15
+ create_status_on_github(stack.github_api)
16
16
  rescue Octokit::ClientError
17
- raise if Shipit.github.api == author.github_api
17
+ raise if Shipit.github(organization: stack.repository.owner).api == stack.github_api
18
18
  # If the deploy author didn't gave us the permission to create the deployment we falback the the main shipit
19
19
  # user.
20
20
  #
21
21
  # Octokit currently raise NotFound, but I'm convinced it should be Forbidden if the user can see the repository.
22
22
  # So to be future proof I catch boths.
23
- create_status_on_github(Shipit.github.api)
23
+ create_status_on_github(stack.github_api)
24
24
  end
25
25
  update!(github_id: response.id, api_url: response.url)
26
26
  end
@@ -15,8 +15,8 @@ module Shipit
15
15
  after_transition any => any, do: :update_last_deploy_time
16
16
  end
17
17
 
18
- belongs_to :until_commit, class_name: 'Commit', required: true, inverse_of: :deploys
19
- belongs_to :since_commit, class_name: 'Commit', required: true, inverse_of: :deploys
18
+ belongs_to :until_commit, class_name: 'Commit', required: true
19
+ belongs_to :since_commit, class_name: 'Commit', required: true
20
20
  has_many :commit_deployments, dependent: :destroy, inverse_of: :task, foreign_key: :task_id do
21
21
  GITHUB_STATUSES = {
22
22
  'pending' => 'pending',
@@ -97,14 +97,16 @@ module Shipit
97
97
  end
98
98
 
99
99
  # Rolls the stack back to this deploy
100
- def trigger_rollback(user = AnonymousUser.new, env: nil, force: false)
100
+ def trigger_rollback(user = AnonymousUser.new, env: nil, force: false, lock: true)
101
101
  rollback = build_rollback(user, env: env, force: force)
102
102
  rollback.save!
103
103
  rollback.enqueue
104
104
 
105
- lock_reason = "A rollback for #{rollback.since_commit.sha} has been triggered. " \
106
- "Please make sure the reason for the rollback has been addressed before deploying again."
107
- stack.update!(lock_reason: lock_reason, lock_author_id: user.id)
105
+ if lock
106
+ lock_reason = "A rollback for #{rollback.since_commit.sha} has been triggered. " \
107
+ "Please make sure the reason for the rollback has been addressed before deploying again."
108
+ stack.update!(lock_reason: lock_reason, lock_author_id: user.id)
109
+ end
108
110
 
109
111
  rollback
110
112
  end
@@ -209,7 +211,7 @@ module Shipit
209
211
  end
210
212
 
211
213
  def report_complete!
212
- if stack.release_status? && stack.release_status_delay.positive?
214
+ if stack.release_status? && !stack.release_status_delay.zero?
213
215
  enter_validation!
214
216
  else
215
217
  super
@@ -269,10 +271,13 @@ module Shipit
269
271
  when 'aborted', 'aborting'
270
272
  append_release_status('failure', "The deploy on #{stack.environment} was canceled")
271
273
  when 'validating'
272
- if stack.release_status_delay.positive?
273
- append_release_status('pending', "The deploy on #{stack.environment} succeeded")
274
- MarkDeployHealthyJob.set(wait: stack.release_status_delay).perform_later(self)
275
- end
274
+ append_release_status(
275
+ 'pending',
276
+ "The deploy on #{stack.environment} succeeded"
277
+ ) unless stack.release_status_delay.zero?
278
+
279
+ MarkDeployHealthyJob.set(wait: stack.release_status_delay)
280
+ .perform_later(self) if stack.release_status_delay.positive?
276
281
  when 'success'
277
282
  if stack.release_status_delay.zero?
278
283
  append_release_status('success', "The deploy on #{stack.environment} succeeded")
@@ -76,10 +76,18 @@ module Shipit
76
76
  end
77
77
 
78
78
  def publish_independent_packages
79
- [
80
- 'assert-lerna-independent-version-tags',
81
- 'publish-lerna-independent-packages',
82
- ]
79
+ command = if lerna_lerna >= LATEST_MAJOR_VERSION
80
+ [
81
+ 'assert-lerna-independent-version-tags',
82
+ 'publish-lerna-independent-packages',
83
+ ]
84
+ else
85
+ [
86
+ 'assert-lerna-independent-version-tags',
87
+ 'publish-lerna-independent-packages-legacy',
88
+ ]
89
+ end
90
+ command
83
91
  end
84
92
 
85
93
  def publish_fixed_version_packages
@@ -20,6 +20,8 @@ module Shipit
20
20
 
21
21
  class << self
22
22
  def parse(value)
23
+ return new(-1) if value.to_s == "-1"
24
+
23
25
  unless match = FORMAT.match(value.to_s)
24
26
  raise ParseError, "not a duration: #{value.inspect}"
25
27
  end
@@ -1,12 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
  module Shipit
3
3
  class Hook < Record
4
+ class DeliverySigner
5
+ attr_reader :secret
6
+
7
+ ALGORITHM = 'sha256'
8
+
9
+ def initialize(secret)
10
+ @secret = secret
11
+ end
12
+
13
+ def sign(payload)
14
+ hmac = OpenSSL::HMAC.hexdigest(ALGORITHM, secret, payload)
15
+ "#{ALGORITHM}=#{hmac}"
16
+ end
17
+ end
18
+
4
19
  class DeliverySpec
5
- def initialize(event:, url:, content_type:, payload:)
20
+ def initialize(event:, url:, content_type:, payload:, secret:)
6
21
  @event = event
7
22
  @url = url
8
23
  @content_type = content_type
9
24
  @payload = payload
25
+ @secret = secret
10
26
  end
11
27
 
12
28
  def send!
@@ -15,7 +31,7 @@ module Shipit
15
31
 
16
32
  private
17
33
 
18
- attr_reader :event, :url, :content_type, :payload
34
+ attr_reader :event, :url, :content_type, :payload, :secret
19
35
 
20
36
  def http
21
37
  Faraday::Connection.new do |connection|
@@ -29,9 +45,16 @@ module Shipit
29
45
  'User-Agent' => 'Shipit Webhook',
30
46
  'Content-Type' => content_type,
31
47
  'X-Shipit-Event' => event,
48
+ 'X-Shipit-Signature' => signature,
32
49
  'Accept' => '*/*',
33
50
  }
34
51
  end
52
+
53
+ def signature
54
+ return nil if secret.blank?
55
+
56
+ DeliverySigner.new(secret).sign(payload)
57
+ end
35
58
  end
36
59
 
37
60
  default_scope { order :id }
@@ -119,6 +142,7 @@ module Shipit
119
142
  url: delivery_url,
120
143
  content_type: CONTENT_TYPES[content_type],
121
144
  payload: serialize_payload(payload),
145
+ secret: secret,
122
146
  )
123
147
  end
124
148
 
@@ -110,10 +110,11 @@ module Shipit
110
110
  end
111
111
 
112
112
  def self.extract_number(stack, number_or_url)
113
+ org = stack.repository.owner
113
114
  case number_or_url
114
115
  when /\A#?(\d+)\z/
115
116
  $1.to_i
116
- when %r{\Ahttps://#{Regexp.escape(Shipit.github.domain)}/([^/]+)/([^/]+)/pull/(\d+)}
117
+ when %r{\Ahttps://#{Regexp.escape(Shipit.github(organization: org).domain)}/([^/]+)/([^/]+)/pull/(\d+)}
117
118
  return unless $1.downcase == stack.repo_owner.downcase
118
119
  return unless $2.downcase == stack.repo_name.downcase
119
120
  $3.to_i
@@ -161,7 +162,7 @@ module Shipit
161
162
 
162
163
  raise NotReady if not_mergeable_yet?
163
164
 
164
- Shipit.github.api.merge_pull_request(
165
+ stack.github_api.merge_pull_request(
165
166
  stack.github_repo_name,
166
167
  number,
167
168
  merge_message,
@@ -170,8 +171,8 @@ module Shipit
170
171
  merge_method: stack.merge_method,
171
172
  )
172
173
  begin
173
- if Shipit.github.api.pull_requests(stack.github_repo_name, base: branch).empty?
174
- Shipit.github.api.delete_branch(stack.github_repo_name, branch)
174
+ if stack.github_api.pull_requests(stack.github_repo_name, base: branch).empty?
175
+ stack.github_api.delete_branch(stack.github_repo_name, branch)
175
176
  end
176
177
  rescue Octokit::UnprocessableEntity
177
178
  # branch was already deleted somehow
@@ -230,8 +231,9 @@ module Shipit
230
231
  end
231
232
 
232
233
  def refresh!
233
- update!(github_pull_request: Shipit.github.api.pull_request(stack.github_repo_name, number))
234
+ update!(github_pull_request: stack.github_api.pull_request(stack.github_repo_name, number))
234
235
  head.refresh_statuses!
236
+ head.refresh_check_runs!
235
237
  fetched! if fetching?
236
238
  @comparison = nil
237
239
  end
@@ -269,7 +271,7 @@ module Shipit
269
271
  end
270
272
 
271
273
  def comparison
272
- @comparison ||= Shipit.github.api.compare(
274
+ @comparison ||= stack.github_api.compare(
273
275
  stack.github_repo_name,
274
276
  base_ref,
275
277
  head.sha,
@@ -292,7 +294,7 @@ module Shipit
292
294
  if commit = stack.commits.by_sha(sha)
293
295
  commit
294
296
  else
295
- github_commit = Shipit.github.api.commit(stack.github_repo_name, sha)
297
+ github_commit = stack.github_api.commit(stack.github_repo_name, sha)
296
298
  stack.commits.create_from_github!(github_commit, attributes)
297
299
  end
298
300
  rescue ActiveRecord::RecordNotUnique
@@ -53,7 +53,7 @@ module Shipit
53
53
  if commit = stack.commits.by_sha(sha)
54
54
  commit
55
55
  else
56
- github_commit = Shipit.github.api.commit(stack.github_repo_name, sha)
56
+ github_commit = stack.github_api.commit(stack.github_repo_name, sha)
57
57
  stack.commits.create_from_github!(github_commit)
58
58
  end
59
59
  rescue ActiveRecord::RecordNotUnique
@@ -25,7 +25,7 @@ module Shipit
25
25
  private
26
26
 
27
27
  def create_status_on_github
28
- Shipit.github.api.create_status(
28
+ stack.github_api.create_status(
29
29
  stack.github_repo_name,
30
30
  commit.sha,
31
31
  state,
@@ -38,7 +38,7 @@ module Shipit
38
38
  private_constant :NAME_MAX_SIZE
39
39
 
40
40
  validates :name, uniqueness: { scope: %i(owner), case_sensitive: false,
41
- message: 'cannot be used more than once' }
41
+ message: 'cannot be used more than once', }
42
42
  validates :owner, :name, presence: true, ascii_only: true
43
43
  validates :owner, format: { with: /\A[a-z0-9_\-\.]+\z/ }, length: { maximum: OWNER_MAX_SIZE }
44
44
  validates :name, format: { with: /\A[a-z0-9_\-\.]+\z/ }, length: { maximum: NAME_MAX_SIZE }
@@ -67,7 +67,7 @@ module Shipit
67
67
  end
68
68
 
69
69
  def http_url
70
- Shipit.github.url(full_name)
70
+ github_app.url(full_name)
71
71
  end
72
72
 
73
73
  def full_name
@@ -75,7 +75,7 @@ module Shipit
75
75
  end
76
76
 
77
77
  def git_url
78
- "https://#{Shipit.github.domain}/#{owner}/#{name}.git"
78
+ "https://#{github_app.domain}/#{owner}/#{name}.git"
79
79
  end
80
80
 
81
81
  def schedule_for_destroy!
@@ -93,5 +93,11 @@ module Shipit
93
93
  name: repo_name.downcase,
94
94
  ).first!
95
95
  end
96
+
97
+ protected
98
+
99
+ def github_app
100
+ Shipit.github(organization: owner)
101
+ end
96
102
  end
97
103
  end
@@ -7,8 +7,8 @@ module Shipit
7
7
  "archived_since > :earliest AND archived_since < :latest",
8
8
  earliest: 1.day.ago,
9
9
  latest: 1.hour.ago
10
- ).each do |review_stack|
11
- Shipit::ClearGitCacheJob.perform_later(review_stack)
10
+ ).find_each do |review_stack|
11
+ review_stack.clear_local_files
12
12
  end
13
13
  end
14
14
 
@@ -22,6 +22,20 @@ module Shipit
22
22
  end
23
23
  end
24
24
 
25
+ def update_latest_deployed_ref
26
+ # noop: last deployed ref is useless for review stacks
27
+ end
28
+
29
+ model_name.class_eval do
30
+ def route_key
31
+ "stacks"
32
+ end
33
+
34
+ def singular_route_key
35
+ "stack"
36
+ end
37
+ end
38
+
25
39
  has_one :pull_request, foreign_key: :stack_id
26
40
 
27
41
  after_commit :emit_added_hooks, on: :create
@@ -32,9 +32,9 @@ module Shipit
32
32
  has_many :deploys
33
33
  has_many :rollbacks
34
34
  has_many :deploys_and_rollbacks,
35
- -> { where(type: %w(Shipit::Deploy Shipit::Rollback)) },
36
- class_name: 'Task',
37
- inverse_of: :stack
35
+ -> { where(type: %w(Shipit::Deploy Shipit::Rollback)) },
36
+ class_name: 'Task',
37
+ inverse_of: :stack
38
38
  has_many :github_hooks, dependent: :destroy, class_name: 'Shipit::GithubHook::Repo'
39
39
  has_many :hooks, dependent: :destroy
40
40
  has_many :api_clients, dependent: :destroy
@@ -83,13 +83,21 @@ module Shipit
83
83
  after_commit :emit_merge_status_hooks, on: :update
84
84
  after_commit :sync_github, on: :create
85
85
  after_commit :schedule_merges_if_necessary, on: :update
86
+ after_commit :sync_github_if_necessary, on: :update
87
+
88
+ def sync_github_if_necessary
89
+ if (archived_since_previously_changed? && archived_since.nil?) || branch_previously_changed?
90
+ sync_github
91
+ end
92
+ end
86
93
 
87
94
  validates :repository, uniqueness: {
88
95
  scope: %i(environment), case_sensitive: false,
89
- message: 'cannot be used more than once with this environment. Check archived stacks.'
96
+ message: 'cannot be used more than once with this environment. Check archived stacks.',
90
97
  }
91
98
  validates :environment, format: { with: /\A[a-z0-9\-_\:]+\z/ }, length: { maximum: ENVIRONMENT_MAX_SIZE }
92
99
  validates :deploy_url, format: { with: URI.regexp(%w(http https ssh)) }, allow_blank: true
100
+ validates :branch, presence: true
93
101
 
94
102
  validates :lock_reason, length: { maximum: 4096 }
95
103
 
@@ -184,7 +192,7 @@ module Shipit
184
192
  end
185
193
 
186
194
  def continuous_delivery_delayed?
187
- continuous_delivery_delayed_since? && continuous_deployment? && checks?
195
+ continuous_delivery_delayed_since? && continuous_deployment? && (checks? || deployment_checks?)
188
196
  end
189
197
 
190
198
  def continuous_delivery_delayed!
@@ -353,7 +361,7 @@ module Shipit
353
361
  end
354
362
 
355
363
  def deployable?
356
- !locked? && !active_task? && !awaiting_provision?
364
+ !locked? && !active_task? && !awaiting_provision? && deployment_checks_passed?
357
365
  end
358
366
 
359
367
  def allows_merges?
@@ -372,26 +380,27 @@ module Shipit
372
380
  delegate :git_url, to: :repository, prefix: :repo
373
381
 
374
382
  def base_path
375
- Rails.root.join('data', 'stacks', repo_owner, repo_name, environment)
383
+ @base_path ||= Rails.root.join('data', 'stacks', repo_owner, repo_name, environment)
376
384
  end
377
385
 
378
386
  def deploys_path
379
- File.join(base_path, "deploys")
387
+ @deploys_path ||= base_path.join("deploys")
380
388
  end
381
389
 
382
390
  def git_path
383
- File.join(base_path, "git")
391
+ @git_path ||= base_path.join("git")
384
392
  end
385
393
 
386
394
  def acquire_git_cache_lock(timeout: 15, &block)
387
- Flock.new(git_path.to_s + '.lock').lock(timeout: timeout, &block)
395
+ @git_cache_lock ||= Flock.new(git_path.to_s + '.lock')
396
+ @git_cache_lock.lock(timeout: timeout, &block)
388
397
  end
389
398
 
390
399
  def clear_git_cache!
391
400
  tmp_path = "#{git_path}-#{SecureRandom.hex}"
392
- return unless File.exist?(git_path)
401
+ return unless git_path.exist?
393
402
  acquire_git_cache_lock do
394
- File.rename(git_path, tmp_path)
403
+ git_path.rename(tmp_path)
395
404
  end
396
405
  FileUtils.rm_rf(tmp_path)
397
406
  end
@@ -402,12 +411,20 @@ module Shipit
402
411
 
403
412
  def github_commits
404
413
  handle_github_redirections do
405
- Shipit.github.api.commits(github_repo_name, sha: branch)
414
+ github_api.commits(github_repo_name, sha: branch)
406
415
  end
407
416
  rescue Octokit::Conflict
408
417
  [] # Repository is empty...
409
418
  end
410
419
 
420
+ def github_api
421
+ github_app.api
422
+ end
423
+
424
+ def github_app
425
+ Shipit.github(organization: repository.owner)
426
+ end
427
+
411
428
  def handle_github_redirections
412
429
  # https://developer.github.com/v3/#http-redirects
413
430
  resource = yield
@@ -420,9 +437,9 @@ module Shipit
420
437
  end
421
438
 
422
439
  def refresh_repository!
423
- resource = Shipit.github.api.repo(github_repo_name)
440
+ resource = github_api.repo(github_repo_name)
424
441
  if resource.try(:message) == 'Moved Permanently'
425
- resource = Shipit.github.api.get(resource.url)
442
+ resource = github_api.get(resource.url)
426
443
  end
427
444
  repository.update!(owner: resource.owner.login, name: resource.name)
428
445
  end
@@ -487,9 +504,9 @@ module Shipit
487
504
  end
488
505
 
489
506
  delegate :plugins, :task_definitions, :hidden_statuses, :required_statuses, :soft_failing_statuses,
490
- :blocking_statuses, :deploy_variables, :filter_task_envs, :filter_deploy_envs,
491
- :maximum_commits_per_deploy, :pause_between_deploys, :retries_on_deploy, :retries_on_rollback,
492
- to: :cached_deploy_spec
507
+ :blocking_statuses, :deploy_variables, :filter_task_envs, :filter_deploy_envs,
508
+ :maximum_commits_per_deploy, :pause_between_deploys, :retries_on_deploy, :retries_on_rollback,
509
+ to: :cached_deploy_spec
493
510
 
494
511
  def monitoring?
495
512
  monitoring.present?
@@ -581,19 +598,31 @@ module Shipit
581
598
  links_spec.transform_values { |url| context.interpolate(url) }
582
599
  end
583
600
 
601
+ def clear_local_files
602
+ FileUtils.rm_rf(base_path.to_s)
603
+ end
604
+
605
+ def deployment_checks_passed?
606
+ return true unless deployment_checks?
607
+
608
+ Shipit.deployment_checks.call(self)
609
+ end
610
+
584
611
  private
585
612
 
586
613
  def clear_cache
587
614
  remove_instance_variable(:@active_task) if defined?(@active_task)
588
615
  end
589
616
 
590
- def clear_local_files
591
- FileUtils.rm_rf(base_path.to_s)
592
- end
593
-
594
617
  def update_defaults
595
618
  self.environment = 'production' if environment.blank?
596
- self.branch = 'master' if branch.blank?
619
+ self.branch = default_branch_name if branch.blank?
620
+ end
621
+
622
+ def default_branch_name
623
+ Shipit.github.api.repo(github_repo_name).default_branch
624
+ rescue Octokit::NotFound, Octokit::InvalidRepository
625
+ nil
597
626
  end
598
627
 
599
628
  def set_locked_since
@@ -607,7 +636,7 @@ module Shipit
607
636
  end
608
637
 
609
638
  def schedule_merges_if_necessary
610
- if previous_changes.include?('lock_reason') && previous_changes['lock_reason'].last.blank?
639
+ if lock_reason_previously_changed? && lock_reason.blank?
611
640
  schedule_merges
612
641
  end
613
642
  end
@@ -644,7 +673,7 @@ module Shipit
644
673
  end
645
674
 
646
675
  def should_resume_continuous_delivery?(commit)
647
- !deployable? ||
676
+ (deployment_checks_passed? && !deployable?) ||
648
677
  deployed_too_recently? ||
649
678
  commit.nil? ||
650
679
  commit.deployed?
@@ -653,7 +682,12 @@ module Shipit
653
682
  def should_delay_continuous_delivery?(commit)
654
683
  commit.deploy_failed? ||
655
684
  (checks? && !EphemeralCommitChecks.new(commit).run.success?) ||
685
+ !deployment_checks_passed? ||
656
686
  commit.recently_pushed?
657
687
  end
688
+
689
+ def deployment_checks?
690
+ Shipit.deployment_checks.present?
691
+ end
658
692
  end
659
693
  end