shipit-engine 0.21.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
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