shipit-engine 0.29.0 → 0.30.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 (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