shipit-engine 0.29.0 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -2
  3. data/app/assets/images/archive-solid.svg +1 -0
  4. data/app/assets/stylesheets/_pages/_stacks.scss +76 -0
  5. data/app/controllers/shipit/api/stacks_controller.rb +20 -1
  6. data/app/controllers/shipit/api_clients_controller.rb +49 -0
  7. data/app/controllers/shipit/merge_status_controller.rb +8 -4
  8. data/app/controllers/shipit/stacks_controller.rb +58 -9
  9. data/app/controllers/shipit/webhooks_controller.rb +2 -130
  10. data/app/helpers/shipit/stacks_helper.rb +4 -0
  11. data/app/jobs/shipit/background_job/unique.rb +3 -1
  12. data/app/jobs/shipit/continuous_delivery_job.rb +1 -0
  13. data/app/jobs/shipit/destroy_stack_job.rb +2 -2
  14. data/app/models/shipit/commit.rb +21 -9
  15. data/app/models/shipit/commit_deployment.rb +15 -11
  16. data/app/models/shipit/commit_deployment_status.rb +6 -2
  17. data/app/models/shipit/deploy.rb +48 -7
  18. data/app/models/shipit/deploy_stats.rb +57 -0
  19. data/app/models/shipit/repository.rb +38 -0
  20. data/app/models/shipit/stack.rb +41 -34
  21. data/app/models/shipit/task.rb +26 -4
  22. data/app/models/shipit/webhooks.rb +32 -0
  23. data/app/models/shipit/webhooks/handlers/check_suite_handler.rb +19 -0
  24. data/app/models/shipit/webhooks/handlers/handler.rb +40 -0
  25. data/app/models/shipit/webhooks/handlers/membership_handler.rb +45 -0
  26. data/app/models/shipit/webhooks/handlers/push_handler.rb +20 -0
  27. data/app/models/shipit/webhooks/handlers/status_handler.rb +26 -0
  28. data/app/serializers/shipit/stack_serializer.rb +6 -1
  29. data/app/validators/ascii_only_validator.rb +3 -3
  30. data/app/views/layouts/_head.html.erb +0 -0
  31. data/app/views/layouts/shipit.html.erb +4 -2
  32. data/app/views/shipit/api_clients/index.html.erb +36 -0
  33. data/app/views/shipit/api_clients/new.html.erb +33 -0
  34. data/app/views/shipit/api_clients/show.html.erb +35 -0
  35. data/app/views/shipit/merge_status/logged_out.erb +1 -1
  36. data/app/views/shipit/stacks/_header.html.erb +12 -7
  37. data/app/views/shipit/stacks/_links.html.erb +1 -0
  38. data/app/views/shipit/stacks/index.html.erb +7 -2
  39. data/app/views/shipit/stacks/settings.html.erb +19 -0
  40. data/app/views/shipit/stacks/statistics.html.erb +82 -0
  41. data/config/locales/en.yml +14 -2
  42. data/config/routes.rb +4 -0
  43. data/db/migrate/20191209231045_create_shipit_repositories.rb +12 -0
  44. data/db/migrate/20191209231307_add_repository_reference_to_stacks.rb +15 -0
  45. data/db/migrate/20191216162728_backfill_repository_data.rb +22 -0
  46. data/db/migrate/20191216163010_remove_repository_information_from_stacks.rb +20 -0
  47. data/db/migrate/20191219205202_add_archived_since_to_stacks.rb +6 -0
  48. data/db/migrate/20200102175621_optional_task_commits.rb +6 -0
  49. data/db/migrate/20200109132519_add_sha_to_commit_deployments.rb +5 -0
  50. data/lib/shipit/github_app.rb +32 -3
  51. data/lib/shipit/task_commands.rb +10 -2
  52. data/lib/shipit/version.rb +1 -1
  53. data/test/controllers/api/ccmenu_controller_test.rb +1 -1
  54. data/test/controllers/api/stacks_controller_test.rb +14 -6
  55. data/test/controllers/api_clients_controller_test.rb +103 -0
  56. data/test/controllers/merge_status_controller_test.rb +21 -4
  57. data/test/controllers/stacks_controller_test.rb +35 -0
  58. data/test/controllers/webhooks_controller_test.rb +26 -0
  59. data/test/dummy/config/environments/development.rb +22 -4
  60. data/test/dummy/db/schema.rb +17 -6
  61. data/test/dummy/db/seeds.rb +20 -6
  62. data/test/fixtures/shipit/commit_deployment_statuses.yml +4 -4
  63. data/test/fixtures/shipit/commit_deployments.yml +8 -8
  64. data/test/fixtures/shipit/commits.yml +23 -0
  65. data/test/fixtures/shipit/repositories.yml +23 -0
  66. data/test/fixtures/shipit/stacks.yml +100 -16
  67. data/test/fixtures/shipit/tasks.yml +66 -3
  68. data/test/jobs/destroy_stack_job_test.rb +9 -0
  69. data/test/models/commit_deployment_status_test.rb +33 -4
  70. data/test/models/commit_deployment_test.rb +8 -11
  71. data/test/models/commits_test.rb +22 -2
  72. data/test/models/deploy_stats_test.rb +112 -0
  73. data/test/models/deploys_test.rb +55 -17
  74. data/test/models/pull_request_test.rb +1 -1
  75. data/test/models/shipit/repository_test.rb +76 -0
  76. data/test/models/shipit/wehbooks/handlers_test.rb +26 -0
  77. data/test/models/stacks_test.rb +44 -51
  78. data/test/models/undeployed_commits_test.rb +13 -0
  79. data/test/test_helper.rb +3 -1
  80. data/test/unit/deploy_commands_test.rb +9 -0
  81. data/test/unit/github_app_test.rb +136 -0
  82. metadata +161 -128
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 842f0db76bf2cd73d310aebd6b3abed371658c76731c383573e4a061de4e64bd
4
- data.tar.gz: 2c640e960809d4a29d441f0bc20f2724d406f60d4f13573aad9f1b7d234b547e
3
+ metadata.gz: a26f88ecb6d3d82e4b537df7cab3cbdd615dc6e2d3663695e18301ccadecb350
4
+ data.tar.gz: d45d1e8cce82d0b3ec920429474deeae101ffe5bbc8d92272897be8bcef5ca4a
5
5
  SHA512:
6
- metadata.gz: faaeb16d6eb385ab64d1035c6d1a08e4d6646482bf4e87bdfe1031edb2955e52a0daa73b1dc5757e6b9253d4c51c3538db7aca388eddc8c20f1b683ee5dcfa67
7
- data.tar.gz: b31c919542c163a44dd30338acdfe19b61cdd5b3bac87a9b3ed87f57e3d0f0b82602869b681e59b5401d1206f8941b44a4599e4859745a6a1dda772fcd40d2d3
6
+ metadata.gz: 179ad0206a3ca03960bdcf444328a0b6c5c5bbc2ae314f7148365c78edb68380a3e804c6e757f08261f13ca24e0d9c604d0730e3f5c046d6fe2270264ea4195d
7
+ data.tar.gz: 52db719ed7037625ecec6bb5b31e1a87493cc595e4f8a2a14a65ef7708ca83bebf2193fe0fa82ea33ea9fa11027061f8fade805d4b24fa79c771c6f69ee5c322
data/README.md CHANGED
@@ -35,7 +35,11 @@ This guide aims to help you [set up](#installation-and-setup), [use](#using-ship
35
35
  * [Configuring providers](#configuring-providers)
36
36
  * [Free samples](/examples/shipit.yml)
37
37
 
38
- **IV. CONTRIBUTING**
38
+ **IV. INTERGRATING**
39
+
40
+ * [Registering webhooks](#integrating-webhooks)
41
+
42
+ **V. CONTRIBUTING**
39
43
 
40
44
  * [Instructions](#contributing-instructions)
41
45
  * [Local development](#contributing-local-dev)
@@ -634,7 +638,36 @@ For Kubernetes, you have to provision Shipit environment with the following tool
634
638
  * `kubectl`
635
639
  * `kubernetes-deploy` [gem](https://github.com/Shopify/kubernetes-deploy)
636
640
 
637
- <h2 id="contributing">IV. CONTRIBUTING</h2>
641
+ <h2 id="integrating">IV. INTERGRATING</h2>
642
+
643
+ <h3 id="integrating-webhooks">Registering webhooks</h3>
644
+
645
+ Shipit handles several webhook types by default, listed in `Shipit::Wehbooks::DEFAULT_HANDLERS`, in order to implement default behaviours. Extra handler blocks can be registered via `Shipit::Webhooks.register_handler`. Valid handlers need only implement the `call` method - meaning any object which implements `call` - blocks, procs, or lambdas are valid. The webhooks controller will pass a `params` argument to the handler. Some examples:
646
+
647
+
648
+ <h4>Registering a Plain old Ruby Object as a handler</h4>
649
+
650
+ ```ruby
651
+ class PullRequestHandler
652
+ def call(params)
653
+ # do something with pull request webhook events
654
+ end
655
+ end
656
+
657
+ Shipit::Webhooks.register_handler('pull_request', PullRequestHandler)
658
+ ```
659
+
660
+ <h4>Registering a Block as a handler</h4>
661
+
662
+ ```ruby
663
+ Shipit::Webhooks.register_handler('pull_request') do |params|
664
+ # do something with pull request webhook events
665
+ end
666
+ ```
667
+
668
+ Multiple handler blocks can be registered. If any raise errors, execution will be halted and the request will be reported failed to github.
669
+
670
+ <h2 id="contributing">V. CONTRIBUTING</h2>
638
671
 
639
672
  <h3 id="contributing-instructions">Instructions</h3>
640
673
 
@@ -657,3 +690,5 @@ Run `./bin/bootstrap` in order to bootstrap the dummy application. The bootstrap
657
690
  Run `./test/dummy/bin/rails server` to run the rails dummy application.
658
691
 
659
692
  Set the environment variable `SHIPIT_DISABLE_AUTH=1` in order to disable authentication.
693
+
694
+ If you need to test caching behaviour in the dummy application, use `bin/rails dev:cache`.
@@ -0,0 +1 @@
1
+ <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="archive" class="svg-inline--fa fa-archive fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M32 448c0 17.7 14.3 32 32 32h384c17.7 0 32-14.3 32-32V160H32v288zm160-212c0-6.6 5.4-12 12-12h104c6.6 0 12 5.4 12 12v8c0 6.6-5.4 12-12 12H204c-6.6 0-12-5.4-12-12v-8zM480 32H32C14.3 32 0 46.3 0 64v48c0 8.8 7.2 16 16 16h480c8.8 0 16-7.2 16-16V64c0-17.7-14.3-32-32-32z"></path></svg>
@@ -49,6 +49,15 @@
49
49
  padding-left: 30px;
50
50
  margin-left: -30px;
51
51
  }
52
+ .archived {
53
+ // From FontAwesome, license @ https://fontawesome.com/license/free
54
+ background-image: asset-data-url('archive-solid.svg');
55
+ background-repeat: no-repeat;
56
+ background-size: 22px 22px;
57
+ background-position: 0px center;
58
+ padding-left: 30px;
59
+ margin-left: -30px;
60
+ }
52
61
  }
53
62
 
54
63
  .stack-lst:empty:before {
@@ -161,3 +170,70 @@
161
170
  .pagination {
162
171
  text-align: center;
163
172
  }
173
+
174
+ .box {
175
+ border-radius: 5px;
176
+ width: 32%;
177
+ float: left;
178
+ padding: 5px;
179
+ }
180
+
181
+ .box__header {
182
+ padding: 15px 25px;
183
+ position: relative;
184
+ }
185
+
186
+ .box__header-title {
187
+ color: #333;
188
+ font-size: 18px;
189
+ }
190
+
191
+ .box__body {
192
+ padding: 0 25px;
193
+ }
194
+
195
+ .row {
196
+ @include clearfix;
197
+ }
198
+
199
+ /* STATS */
200
+
201
+ .stats {
202
+ color: #333;
203
+ position: relative;
204
+ padding-bottom: 25px;
205
+ }
206
+
207
+ .stats__amount {
208
+ font-size: 54px;
209
+ line-height: 1.2;
210
+ }
211
+
212
+ .stats__caption {
213
+ font-size: 18px;
214
+
215
+ }
216
+
217
+ .stats__change {
218
+ position: absolute;
219
+ top: 10px;
220
+ right: 0;
221
+ text-align: right;
222
+ color: #B1B7C8;
223
+ }
224
+
225
+ .stats__value {
226
+ font-size: 18px;
227
+ }
228
+
229
+ .stats__period {
230
+ font-size: 14px;
231
+ }
232
+
233
+ .stats__value--positive {
234
+ color: #AEDC6F;
235
+ }
236
+
237
+ .stats__value--negative {
238
+ color: #FB5055;
239
+ }
@@ -18,7 +18,10 @@ module Shipit
18
18
  accepts :merge_queue_enabled, Boolean
19
19
  end
20
20
  def create
21
- render_resource Stack.create(params)
21
+ stack = Stack.new(create_params)
22
+ stack.repository = repository
23
+ stack.save
24
+ render_resource stack
22
25
  end
23
26
 
24
27
  def show
@@ -32,9 +35,25 @@ module Shipit
32
35
 
33
36
  private
34
37
 
38
+ def create_params
39
+ params.reject { |key, _| %i(repo_owner repo_name).include?(key) }
40
+ end
41
+
35
42
  def stack
36
43
  @stack ||= stacks.from_param!(params[:id])
37
44
  end
45
+
46
+ def repository
47
+ @repository ||= Repository.find_or_create_by(owner: repo_owner, name: repo_name)
48
+ end
49
+
50
+ def repo_owner
51
+ params[:repo_owner]
52
+ end
53
+
54
+ def repo_name
55
+ params[:repo_name]
56
+ end
38
57
  end
39
58
  end
40
59
  end
@@ -0,0 +1,49 @@
1
+ module Shipit
2
+ class ApiClientsController < ShipitController
3
+ include Pagination
4
+
5
+ def index
6
+ paginator = paginate(ApiClient.order(created_at: :desc).all)
7
+ @api_clients = paginator.to_a
8
+ @links = paginator.links
9
+ end
10
+
11
+ def new
12
+ @api_client = ApiClient.new
13
+ end
14
+
15
+ def create
16
+ @api_client = ApiClient.new(create_params.merge(creator_id: current_user.id))
17
+ unless @api_client.save
18
+ flash[:warning] = @api_client.errors.full_messages.to_sentence
19
+ end
20
+
21
+ respond_with(@api_client)
22
+ end
23
+
24
+ def show
25
+ @api_client = ApiClient.find(params[:id])
26
+ end
27
+
28
+ def update
29
+ @api_client = ApiClient.find(params[:id])
30
+ options = if @api_client.update(update_params)
31
+ {flash: {success: 'Successfully updated'}}
32
+ else
33
+ {flash: {warning: @stack.errors.full_messages.to_sentence}}
34
+ end
35
+
36
+ redirect_to(params[:return_to].presence || api_client_path(@api_client), options)
37
+ end
38
+
39
+ private
40
+
41
+ def create_params
42
+ params.require(:api_client).permit(:name, permissions: [])
43
+ end
44
+
45
+ def update_params
46
+ params.require(:api_client).permit(permissions: [])
47
+ end
48
+ end
49
+ end
@@ -60,10 +60,14 @@ module Shipit
60
60
  @stack ||= if params[:stack_id]
61
61
  Stack.from_param!(params[:stack_id])
62
62
  else
63
- scope = Stack.order(merge_queue_enabled: :desc, id: :asc).where(
64
- repo_owner: referrer_parser.repo_owner,
65
- repo_name: referrer_parser.repo_name,
66
- )
63
+ # Null ordering is inconsistent across DBMS's, this case statement is ugly but supported universally
64
+ scope = Stack.order(Arel.sql('CASE WHEN locked_since IS NULL THEN 1 ELSE 0 END, locked_since'))
65
+ .order(merge_queue_enabled: :desc, id: :asc).includes(:repository).where(
66
+ repositories: {
67
+ owner: referrer_parser.repo_owner,
68
+ name: referrer_parser.repo_name,
69
+ },
70
+ )
67
71
  scope = if params[:branch]
68
72
  scope.where(branch: params[:branch])
69
73
  else
@@ -1,6 +1,6 @@
1
1
  module Shipit
2
2
  class StacksController < ShipitController
3
- before_action :load_stack, only: %i(update destroy settings clear_git_cache refresh)
3
+ before_action :load_stack, only: %i(update destroy settings statistics clear_git_cache refresh)
4
4
 
5
5
  def new
6
6
  @stack = Stack.new
@@ -9,7 +9,12 @@ module Shipit
9
9
  def index
10
10
  @user_stacks = current_user.stacks_contributed_to
11
11
 
12
- @stacks = Stack.order(Arel.sql('(undeployed_commits_count > 0) desc'), tasks_count: :desc).to_a
12
+ @stacks = Stack.order(Arel.sql('(undeployed_commits_count > 0) desc'), tasks_count: :desc)
13
+
14
+ @show_archived = params[:show_archived]
15
+ @stacks = @stacks.not_archived unless @show_archived
16
+
17
+ @stacks = @stacks.to_a
13
18
  end
14
19
 
15
20
  def show
@@ -48,6 +53,7 @@ module Shipit
48
53
 
49
54
  def create
50
55
  @stack = Stack.new(create_params)
56
+ @stack.repository = repository
51
57
  unless @stack.save
52
58
  flash[:warning] = @stack.errors.full_messages.to_sentence
53
59
  end
@@ -62,6 +68,16 @@ module Shipit
62
68
  def settings
63
69
  end
64
70
 
71
+ def statistics
72
+ previous_deploy_stats = Shipit::DeployStats.new(@stack.deploys.not_active.previous_seven_days)
73
+ @deploy_stats = Shipit::DeployStats.new(@stack.deploys.not_active.last_seven_days)
74
+ if @deploy_stats.empty?
75
+ flash[:warning] = 'Statistics not available without previous deploys'
76
+ return redirect_to stack_path(@stack)
77
+ end
78
+ @diffs = @deploy_stats.compare(previous_deploy_stats)
79
+ end
80
+
65
81
  def refresh
66
82
  RefreshStatusesJob.perform_later(stack_id: @stack.id)
67
83
  RefreshCheckRunsJob.perform_later(stack_id: @stack.id)
@@ -76,12 +92,8 @@ module Shipit
76
92
  options = {flash: {warning: @stack.errors.full_messages.to_sentence}}
77
93
  end
78
94
 
79
- reason = params[:stack][:lock_reason]
80
- if reason.present?
81
- @stack.lock(reason, current_user)
82
- elsif @stack.locked?
83
- @stack.unlock
84
- end
95
+ update_lock
96
+ update_archived
85
97
 
86
98
  redirect_to(params[:return_to].presence || stack_settings_path(@stack), options)
87
99
  end
@@ -94,6 +106,27 @@ module Shipit
94
106
 
95
107
  private
96
108
 
109
+ def update_lock
110
+ if params[:stack].key?(:lock_reason)
111
+ reason = params[:stack][:lock_reason]
112
+ if reason.present?
113
+ @stack.lock(reason, current_user)
114
+ elsif @stack.locked?
115
+ @stack.unlock
116
+ end
117
+ end
118
+ end
119
+
120
+ def update_archived
121
+ if params[:stack][:archived].present?
122
+ if params[:stack][:archived] == "true"
123
+ @stack.archive!(current_user)
124
+ elsif @stack.archived?
125
+ @stack.unarchive!
126
+ end
127
+ end
128
+ end
129
+
97
130
  def map_to_undeployed_commit(commits, next_expected_commit_to_deploy:)
98
131
  commits.map.with_index do |c, i|
99
132
  index = commits.size - i - 1
@@ -106,7 +139,7 @@ module Shipit
106
139
  end
107
140
 
108
141
  def create_params
109
- params.require(:stack).permit(:repo_name, :repo_owner, :environment, :branch, :deploy_url, :ignore_ci)
142
+ params.require(:stack).permit(:environment, :branch, :deploy_url, :ignore_ci)
110
143
  end
111
144
 
112
145
  def update_params
@@ -118,5 +151,21 @@ module Shipit
118
151
  :merge_queue_enabled,
119
152
  )
120
153
  end
154
+
155
+ def repository
156
+ @repository ||= Repository.find_or_create_by(owner: repo_owner, name: repo_name)
157
+ end
158
+
159
+ def repo_owner
160
+ repository_params[:repo_owner]
161
+ end
162
+
163
+ def repo_name
164
+ repository_params[:repo_name]
165
+ end
166
+
167
+ def repository_params
168
+ params.require(:stack).permit(:repo_owner, :repo_name)
169
+ end
121
170
  end
122
171
  end
@@ -5,137 +5,9 @@ module Shipit
5
5
 
6
6
  respond_to :json
7
7
 
8
- class Handler
9
- class << self
10
- attr_reader :param_parser
11
-
12
- def params(&block)
13
- @param_parser = ExplicitParameters::Parameters.define(&block)
14
- end
15
- end
16
-
17
- attr_reader :params, :payload
18
-
19
- def initialize(payload)
20
- @payload = payload
21
- @params = self.class.param_parser.parse!(payload)
22
- end
23
-
24
- def process
25
- raise NotImplementedError
26
- end
27
-
28
- private
29
-
30
- def stacks
31
- @stacks ||= Stack.repo(repository_name)
32
- end
33
-
34
- def repository_name
35
- payload.dig('repository', 'full_name')
36
- end
37
- end
38
-
39
- class PushHandler < Handler
40
- params do
41
- requires :ref
42
- end
43
- def process
44
- stacks.where(branch: branch).each(&:sync_github)
45
- end
46
-
47
- private
48
-
49
- def branch
50
- params.ref.gsub('refs/heads/', '')
51
- end
52
- end
53
-
54
- class StatusHandler < Handler
55
- params do
56
- requires :sha, String
57
- requires :state, String
58
- accepts :description, String
59
- accepts :target_url, String
60
- accepts :context, String
61
- accepts :created_at, String
62
-
63
- accepts :branches, Array do
64
- requires :name, String
65
- end
66
- end
67
- def process
68
- Commit.where(sha: params.sha).each do |commit|
69
- commit.create_status_from_github!(params)
70
- end
71
- end
72
- end
73
-
74
- class CheckSuiteHandler < Handler
75
- params do
76
- requires :check_suite do
77
- requires :head_sha, String
78
- requires :head_branch, String
79
- end
80
- end
81
- def process
82
- stacks.where(branch: params.check_suite.head_branch).each do |stack|
83
- stack.commits.where(sha: params.check_suite.head_sha).each(&:schedule_refresh_check_runs!)
84
- end
85
- end
86
- end
87
-
88
- class MembershipHandler < Handler
89
- params do
90
- requires :action, String
91
- requires :team do
92
- requires :id, Integer
93
- requires :name, String
94
- requires :slug, String
95
- requires :url, String
96
- end
97
- requires :organization do
98
- requires :login, String
99
- end
100
- requires :member do
101
- requires :login, String
102
- end
103
- end
104
- def process
105
- team = find_or_create_team!
106
- member = User.find_or_create_by_login!(params.member.login)
107
-
108
- case params.action
109
- when 'added'
110
- team.add_member(member)
111
- when 'removed'
112
- team.members.delete(member)
113
- else
114
- raise ArgumentError, "Don't know how to perform action: `#{action.inspect}`"
115
- end
116
- end
117
-
118
- private
119
-
120
- def find_or_create_team!
121
- Team.find_or_create_by!(github_id: params.team.id) do |team|
122
- team.github_team = params.team
123
- team.organization = params.organization.login
124
- end
125
- end
126
- end
127
-
128
- HANDLERS = {
129
- 'push' => PushHandler,
130
- 'status' => StatusHandler,
131
- 'membership' => MembershipHandler,
132
- 'check_suite' => CheckSuiteHandler,
133
- }.freeze
134
-
135
8
  def create
136
- if handler = HANDLERS[event]
137
- handler.new(JSON.parse(request.raw_post)).process
138
- end
9
+ params = JSON.parse(request.raw_post)
10
+ Shipit::Webhooks.for_event(event).each { |handler| handler.call(params) }
139
11
 
140
12
  head :ok
141
13
  end