shipit-engine 0.21.0 → 0.22.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -106
  3. data/app/assets/images/timedout.svg +14 -0
  4. data/app/assets/stylesheets/_pages/_commits.scss +11 -2
  5. data/app/controllers/shipit/stacks_controller.rb +1 -7
  6. data/app/controllers/shipit/webhooks_controller.rb +102 -66
  7. data/app/helpers/shipit/github_url_helper.rb +2 -2
  8. data/app/helpers/shipit/shipit_helper.rb +3 -31
  9. data/app/jobs/shipit/destroy_job.rb +9 -0
  10. data/app/jobs/shipit/github_sync_job.rb +1 -1
  11. data/app/jobs/shipit/setup_github_hook_job.rb +1 -3
  12. data/app/models/shipit/anonymous_user.rb +4 -1
  13. data/app/models/shipit/commit.rb +8 -8
  14. data/app/models/shipit/commit_deployment.rb +3 -3
  15. data/app/models/shipit/commit_deployment_status.rb +2 -2
  16. data/app/models/shipit/deploy.rb +3 -3
  17. data/app/models/shipit/deploy_spec/file_system.rb +3 -3
  18. data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +10 -2
  19. data/app/models/shipit/github_hook.rb +2 -99
  20. data/app/models/shipit/github_status.rb +1 -1
  21. data/app/models/shipit/hook.rb +1 -1
  22. data/app/models/shipit/pull_request.rb +10 -10
  23. data/app/models/shipit/rollback.rb +1 -1
  24. data/app/models/shipit/stack.rb +27 -26
  25. data/app/models/shipit/task.rb +2 -2
  26. data/app/models/shipit/team.rb +4 -17
  27. data/app/models/shipit/user.rb +3 -3
  28. data/app/serializers/shipit/task_serializer.rb +2 -2
  29. data/app/serializers/shipit/user_serializer.rb +1 -1
  30. data/app/views/shipit/missing_settings.html.erb +5 -36
  31. data/app/views/shipit/stacks/new.html.erb +1 -1
  32. data/app/views/shipit/stacks/settings.html.erb +0 -4
  33. data/config/routes.rb +3 -13
  34. data/config/secrets.development.shopify.yml +10 -15
  35. data/config/secrets.development.yml +1 -1
  36. data/db/migrate/20180417130436_remove_all_github_hooks.rb +11 -0
  37. data/lib/shipit.rb +13 -56
  38. data/lib/shipit/command.rb +1 -1
  39. data/lib/shipit/engine.rb +2 -8
  40. data/lib/shipit/github_app.rb +122 -0
  41. data/lib/shipit/octokit_bot_users_patch.rb +25 -0
  42. data/lib/shipit/octokit_iterator.rb +2 -2
  43. data/lib/shipit/version.rb +1 -1
  44. data/lib/tasks/teams.rake +8 -24
  45. data/test/controllers/stacks_controller_test.rb +3 -29
  46. data/test/controllers/webhooks_controller_test.rb +29 -46
  47. data/test/dummy/config/secrets.yml +40 -10
  48. data/test/dummy/db/development.sqlite3 +0 -0
  49. data/test/dummy/db/schema.rb +1 -1
  50. data/test/dummy/db/seeds.rb +0 -1
  51. data/test/dummy/db/test.sqlite3 +0 -0
  52. data/test/fixtures/payloads/push_master.json +7 -6
  53. data/test/fixtures/payloads/push_not_master.json +7 -6
  54. data/test/fixtures/shipit/users.yml +2 -2
  55. data/test/helpers/hooks_helper.rb +1 -1
  56. data/test/helpers/payloads_helper.rb +1 -2
  57. data/test/jobs/destroy_stack_job_test.rb +1 -1
  58. data/test/models/commits_test.rb +5 -5
  59. data/test/models/deploy_spec_test.rb +17 -5
  60. data/test/models/github_hook_test.rb +1 -40
  61. data/test/models/pull_request_test.rb +11 -11
  62. data/test/models/stacks_test.rb +4 -10
  63. data/test/models/team_test.rb +3 -3
  64. data/test/models/users_test.rb +7 -7
  65. data/test/test_helper.rb +1 -1
  66. data/test/unit/github_app_test.rb +44 -0
  67. data/test/unit/shipit_test.rb +2 -49
  68. metadata +9 -3
  69. data/lib/tasks/webhook.rake +0 -6
@@ -0,0 +1,9 @@
1
+ module Shipit
2
+ class DestroyJob < BackgroundJob
3
+ queue_as :default
4
+
5
+ def perform(instance)
6
+ instance.destroy
7
+ end
8
+ end
9
+ end
@@ -15,7 +15,7 @@ module Shipit
15
15
  new_commits, shared_parent = fetch_missing_commits { @stack.github_commits }
16
16
 
17
17
  @stack.transaction do
18
- shared_parent.try!(:detach_children!)
18
+ shared_parent&.detach_children!
19
19
  appended_commits = new_commits.map do |gh_commit|
20
20
  append_commit(gh_commit)
21
21
  end
@@ -1,11 +1,9 @@
1
1
  module Shipit
2
2
  class SetupGithubHookJob < BackgroundJob
3
- include BackgroundJob::Unique
4
-
5
3
  queue_as :default
6
4
 
7
5
  def perform(hook)
8
- hook.setup!
6
+ # TODO: app-migration, delete this job
9
7
  end
10
8
  end
11
9
  end
@@ -23,6 +23,9 @@ module Shipit
23
23
  def id
24
24
  end
25
25
 
26
+ def github_id
27
+ end
28
+
26
29
  def logged_in?
27
30
  false
28
31
  end
@@ -49,7 +52,7 @@ module Shipit
49
52
  end
50
53
 
51
54
  def github_api
52
- Shipit.github_api
55
+ Shipit.github.api
53
56
  end
54
57
  end
55
58
  end
@@ -6,7 +6,7 @@ module Shipit
6
6
 
7
7
  belongs_to :stack
8
8
  has_many :deploys
9
- has_many :statuses, -> { order(created_at: :desc) }, dependent: :destroy
9
+ has_many :statuses, -> { order(created_at: :desc) }, dependent: :destroy, inverse_of: :commit
10
10
  has_many :commit_deployments, dependent: :destroy
11
11
  belongs_to :pull_request, inverse_of: :merge_commit, optional: true
12
12
 
@@ -71,8 +71,8 @@ module Shipit
71
71
  committer: User.find_or_create_from_github(commit.committer || commit.commit.committer),
72
72
  committed_at: commit.commit.committer.date,
73
73
  authored_at: commit.commit.author.date,
74
- additions: commit.stats.try!(:additions),
75
- deletions: commit.stats.try!(:deletions),
74
+ additions: commit.stats&.additions,
75
+ deletions: commit.stats&.deletions,
76
76
  )
77
77
  end
78
78
 
@@ -92,7 +92,7 @@ module Shipit
92
92
  end
93
93
 
94
94
  def refresh_statuses!
95
- github_statuses = stack.handle_github_redirections { Shipit.github_api.statuses(github_repo_name, sha) }
95
+ github_statuses = stack.handle_github_redirections { Shipit.github.api.statuses(github_repo_name, sha) }
96
96
  github_statuses.each do |status|
97
97
  create_status_from_github!(status)
98
98
  end
@@ -170,7 +170,7 @@ module Shipit
170
170
  end
171
171
 
172
172
  def github_commit
173
- @github_commit ||= Shipit.github_api.commit(github_repo_name, sha)
173
+ @github_commit ||= Shipit.github.api.commit(github_repo_name, sha)
174
174
  end
175
175
 
176
176
  def schedule_fetch_stats!
@@ -179,8 +179,8 @@ module Shipit
179
179
 
180
180
  def fetch_stats!
181
181
  update!(
182
- additions: github_commit.stats.try!(:additions),
183
- deletions: github_commit.stats.try!(:deletions),
182
+ additions: github_commit.stats&.additions,
183
+ deletions: github_commit.stats&.deletions,
184
184
  )
185
185
  end
186
186
 
@@ -210,7 +210,7 @@ module Shipit
210
210
  end
211
211
 
212
212
  def deploy_requested_at
213
- if pull_request.try!(:merged?)
213
+ if pull_request&.merged?
214
214
  pull_request.merge_requested_at
215
215
  else
216
216
  created_at
@@ -23,19 +23,19 @@ module Shipit
23
23
  response = begin
24
24
  create_deployment_on_github(author.github_api)
25
25
  rescue Octokit::ClientError
26
- raise if Shipit.github_api == author.github_api
26
+ raise if Shipit.github.api == author.github_api
27
27
  # If the deploy author didn't gave us the permission to create the deployment we falback the the main shipit
28
28
  # user.
29
29
  #
30
30
  # Octokit currently raise NotFound, but I'm convinced it should be Forbidden if the user can see the repository.
31
31
  # So to be future proof I catch boths.
32
- create_deployment_on_github(Shipit.github_api)
32
+ create_deployment_on_github(Shipit.github.api)
33
33
  end
34
34
  update!(github_id: response.id, api_url: response.url)
35
35
  end
36
36
 
37
37
  def pull_request_head
38
- pull_request = Shipit.github_api.pull_request(stack.github_repo_name, commit.pull_request_number)
38
+ pull_request = Shipit.github.api.pull_request(stack.github_repo_name, commit.pull_request_number)
39
39
  pull_request.head.sha
40
40
  end
41
41
 
@@ -11,13 +11,13 @@ module Shipit
11
11
  response = begin
12
12
  create_status_on_github(author.github_api)
13
13
  rescue Octokit::ClientError
14
- raise if Shipit.github_api == author.github_api
14
+ raise if Shipit.github.api == author.github_api
15
15
  # If the deploy author didn't gave us the permission to create the deployment we falback the the main shipit
16
16
  # user.
17
17
  #
18
18
  # Octokit currently raise NotFound, but I'm convinced it should be Forbidden if the user can see the repository.
19
19
  # So to be future proof I catch boths.
20
- create_status_on_github(Shipit.github_api)
20
+ create_status_on_github(Shipit.github.api)
21
21
  end
22
22
  update!(github_id: response.id, api_url: response.url)
23
23
  end
@@ -39,12 +39,12 @@ module Shipit
39
39
 
40
40
  def build_rollback(user = nil, env: nil, force: false)
41
41
  Rollback.new(
42
- user_id: user.try!(:id),
42
+ user_id: user&.id,
43
43
  stack_id: stack_id,
44
44
  parent_id: id,
45
45
  since_commit: stack.last_deployed_commit,
46
46
  until_commit: until_commit,
47
- env: env.try!(:to_h) || {},
47
+ env: env&.to_h || {},
48
48
  allow_concurrency: force,
49
49
  ignored_safeties: force,
50
50
  )
@@ -159,7 +159,7 @@ module Shipit
159
159
 
160
160
  def default_since_commit_id
161
161
  return unless stack
162
- @default_since_commit_id ||= last_successful_deploy.try!(:until_commit_id)
162
+ @default_since_commit_id ||= last_successful_deploy&.until_commit_id
163
163
  end
164
164
 
165
165
  def denormalize_commit_stats
@@ -33,10 +33,10 @@ module Shipit
33
33
  'merge' => {
34
34
  'require' => pull_request_required_statuses,
35
35
  'ignore' => pull_request_ignored_statuses,
36
- 'revalidate_after' => revalidate_pull_requests_after.try!(:to_i),
36
+ 'revalidate_after' => revalidate_pull_requests_after&.to_i,
37
37
  'max_divergence' => {
38
- 'commits' => max_divergence_commits.try!(:to_i),
39
- 'age' => max_divergence_age.try!(:to_i),
38
+ 'commits' => max_divergence_commits&.to_i,
39
+ 'age' => max_divergence_age&.to_i,
40
40
  },
41
41
  },
42
42
  'ci' => {
@@ -25,10 +25,16 @@ module Shipit
25
25
 
26
26
  private
27
27
 
28
+ def timeout_duration
29
+ duration = kube_config.fetch('timeout', '900s')
30
+ Duration.parse(duration).to_i if duration.present?
31
+ end
32
+
28
33
  def discover_kubernetes
29
34
  return if kube_config.blank?
30
35
 
31
36
  cmd = ["kubernetes-deploy"]
37
+ cmd += ["--max-watch-seconds", timeout_duration] if timeout_duration
32
38
  if kube_config['template_dir']
33
39
  cmd << '--template-dir'
34
40
  cmd << kube_config['template_dir']
@@ -45,11 +51,13 @@ module Shipit
45
51
  end
46
52
 
47
53
  def kubernetes_restart_cmd
48
- Shellwords.join([
54
+ cmd = [
49
55
  "kubernetes-restart",
50
56
  kube_config.fetch('namespace'),
51
57
  kube_config.fetch('context'),
52
- ])
58
+ ]
59
+ cmd += ["--max-watch-seconds", timeout_duration] if timeout_duration
60
+ Shellwords.join(cmd)
53
61
  end
54
62
  end
55
63
  end
@@ -1,28 +1,11 @@
1
1
  module Shipit
2
2
  class GithubHook < ActiveRecord::Base
3
- include SecureCompare
4
-
3
+ # TODO: app-migration, delete class
5
4
  belongs_to :stack, required: false # Required for fixtures
6
5
 
7
- before_create :generate_secret
8
6
  before_destroy :teardown!
9
7
 
10
- def verify_signature(signature, message)
11
- algorithm, signature = signature.split("=", 2)
12
- return false unless algorithm == 'sha1'
13
-
14
- secure_compare(signature, OpenSSL::HMAC.hexdigest(algorithm, secret, message))
15
- end
16
-
17
8
  delegate :github_repo_name, to: :stack
18
- def setup!
19
- hook = already_setup? ? update_hook! : create_hook!
20
- update!(github_id: hook.id, api_url: hook.rels[:self].href)
21
- end
22
-
23
- def schedule_setup!
24
- SetupGithubHookJob.perform_later(self)
25
- end
26
9
 
27
10
  def teardown!
28
11
  destroy_hook! if already_setup?
@@ -37,36 +20,8 @@ module Shipit
37
20
  github_id?
38
21
  end
39
22
 
40
- private
41
-
42
- def update_hook!
43
- edit_hook!
44
- rescue Octokit::NotFound
45
- create_hook!
46
- end
47
-
48
- def endpoint_url
49
- raise NotImplementedError.new('Subclasses must implement a `endpoint_url` method')
50
- end
51
-
52
- def hook_properties
53
- {url: endpoint_url, content_type: 'json', secret: secret}
54
- end
55
-
56
- def generate_secret
57
- self.secret = SecureRandom.hex
58
- end
59
-
60
- def url_helpers
61
- Shipit::Engine.routes.url_helpers
62
- end
63
-
64
- def host
65
- Shipit.host
66
- end
67
-
68
23
  def api
69
- Shipit.github_api
24
+ Shipit.legacy_github_api
70
25
  end
71
26
 
72
27
  class Repo < GithubHook
@@ -74,37 +29,10 @@ module Shipit
74
29
 
75
30
  private
76
31
 
77
- def create_hook!
78
- api.create_hook(github_repo_name, 'web', properties, events: [event], active: true)
79
- end
80
-
81
- def edit_hook!
82
- api.edit_hook(github_repo_name, github_id, 'web', properties, events: [event], active: true)
83
- end
84
-
85
32
  def destroy_hook!
86
33
  api.remove_hook(github_repo_name, github_id)
87
34
  rescue Octokit::NotFound
88
35
  end
89
-
90
- def properties
91
- {
92
- url: endpoint_url,
93
- content_type: 'json',
94
- secret: secret,
95
- }
96
- end
97
-
98
- def endpoint_url
99
- case event
100
- when 'push'
101
- url_helpers.push_stack_webhooks_url(stack_id, host: host)
102
- when 'status'
103
- url_helpers.state_stack_webhooks_url(stack_id, host: host)
104
- else
105
- raise ArgumentError, "Unknown GithubHook::Repo event: `#{event.inspect}`"
106
- end
107
- end
108
36
  end
109
37
 
110
38
  class Organization < GithubHook
@@ -112,35 +40,10 @@ module Shipit
112
40
 
113
41
  private
114
42
 
115
- def create_hook!
116
- api.create_org_hook(organization, properties, events: [event], active: true)
117
- end
118
-
119
- def edit_hook!
120
- api.edit_org_hook(organization, github_id, properties, events: [event], active: true)
121
- end
122
-
123
43
  def destroy_hook!
124
44
  api.remove_org_hook(organization, github_id)
125
45
  rescue Octokit::NotFound
126
46
  end
127
-
128
- def properties
129
- {
130
- url: endpoint_url,
131
- content_type: 'json',
132
- secret: secret,
133
- }
134
- end
135
-
136
- def endpoint_url
137
- case event
138
- when 'membership'
139
- url_helpers.membership_webhooks_url(host: host)
140
- else
141
- raise ArgumentError, "Unknown GithubHook::Organization event: `#{event.inspect}`"
142
- end
143
- end
144
47
  end
145
48
  end
146
49
  end
@@ -8,7 +8,7 @@ module Shipit
8
8
  end
9
9
 
10
10
  def refresh_status
11
- Rails.cache.write(CACHE_KEY, Shipit.github_api.github_status)
11
+ Rails.cache.write(CACHE_KEY, Shipit.github.api.github_status)
12
12
  rescue Faraday::Error, Octokit::ServerError
13
13
  end
14
14
  end
@@ -39,7 +39,7 @@ module Shipit
39
39
  raise "#{event} is not declared in Shipit::Hook::EVENTS" unless EVENTS.include?(event.to_s)
40
40
  Shipit::EmitEventJob.perform_later(
41
41
  event: event.to_s,
42
- stack_id: stack.try!(:id),
42
+ stack_id: stack&.id,
43
43
  payload: coerce_payload(payload),
44
44
  )
45
45
  end
@@ -27,11 +27,11 @@ module Shipit
27
27
  end
28
28
 
29
29
  def ignored_statuses
30
- deploy_spec.try!(:pull_request_ignored_statuses) || []
30
+ deploy_spec&.pull_request_ignored_statuses || []
31
31
  end
32
32
 
33
33
  def required_statuses
34
- deploy_spec.try!(:pull_request_required_statuses) || []
34
+ deploy_spec&.pull_request_required_statuses || []
35
35
  end
36
36
  end
37
37
 
@@ -110,7 +110,7 @@ module Shipit
110
110
  case number_or_url
111
111
  when /\A#?(\d+)\z/
112
112
  $1.to_i
113
- when %r{\Ahttps://#{Regexp.escape(Shipit.github_domain)}/([^/]+)/([^/]+)/pull/(\d+)}
113
+ when %r{\Ahttps://#{Regexp.escape(Shipit.github.domain)}/([^/]+)/([^/]+)/pull/(\d+)}
114
114
  return unless $1.downcase == stack.repo_owner.downcase
115
115
  return unless $2.downcase == stack.repo_name.downcase
116
116
  $3.to_i
@@ -157,7 +157,7 @@ module Shipit
157
157
 
158
158
  raise NotReady if not_mergeable_yet?
159
159
 
160
- Shipit.github_api.merge_pull_request(
160
+ Shipit.github.api.merge_pull_request(
161
161
  stack.github_repo_name,
162
162
  number,
163
163
  merge_message,
@@ -166,8 +166,8 @@ module Shipit
166
166
  merge_method: 'merge',
167
167
  )
168
168
  begin
169
- if Shipit.github_api.pull_requests(stack.github_repo_name, base: branch).empty?
170
- Shipit.github_api.delete_branch(stack.github_repo_name, branch)
169
+ if Shipit.github.api.pull_requests(stack.github_repo_name, base: branch).empty?
170
+ Shipit.github.api.delete_branch(stack.github_repo_name, branch)
171
171
  end
172
172
  rescue Octokit::UnprocessableEntity
173
173
  # branch was already deleted somehow
@@ -196,7 +196,7 @@ module Shipit
196
196
  end
197
197
 
198
198
  def need_revalidation?
199
- timeout = stack.cached_deploy_spec.try!(:revalidate_pull_requests_after)
199
+ timeout = stack.cached_deploy_spec&.revalidate_pull_requests_after
200
200
  return false unless timeout
201
201
  (revalidated_at + timeout).past?
202
202
  end
@@ -222,7 +222,7 @@ module Shipit
222
222
  end
223
223
 
224
224
  def refresh!
225
- update!(github_pull_request: Shipit.github_api.pull_request(stack.github_repo_name, number))
225
+ update!(github_pull_request: Shipit.github.api.pull_request(stack.github_repo_name, number))
226
226
  head.refresh_statuses!
227
227
  fetched! if fetching?
228
228
  @comparison = nil
@@ -261,7 +261,7 @@ module Shipit
261
261
  end
262
262
 
263
263
  def comparison
264
- @comparison ||= Shipit.github_api.compare(
264
+ @comparison ||= Shipit.github.api.compare(
265
265
  stack.github_repo_name,
266
266
  base_ref,
267
267
  head.sha,
@@ -284,7 +284,7 @@ module Shipit
284
284
  if commit = stack.commits.by_sha(sha)
285
285
  return commit
286
286
  else
287
- github_commit = Shipit.github_api.commit(stack.github_repo_name, sha)
287
+ github_commit = Shipit.github.api.commit(stack.github_repo_name, sha)
288
288
  stack.commits.create_from_github!(github_commit, attributes)
289
289
  end
290
290
  rescue ActiveRecord::RecordNotUnique