shipit-engine 0.33.0 → 0.34.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.
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