shipit-engine 0.15.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +34 -1
- data/app/assets/javascripts/shipit/page_updater.js.coffee +63 -0
- data/app/assets/javascripts/shipit/stacks.js.coffee +9 -21
- data/app/assets/stylesheets/_base/_base.scss +2 -2
- data/app/assets/stylesheets/_base/_colors.scss +0 -1
- data/app/assets/stylesheets/_base/_forms.scss +14 -0
- data/app/assets/stylesheets/_pages/_commits.scss +16 -6
- data/app/assets/stylesheets/_pages/_settings.scss +8 -0
- data/app/assets/stylesheets/_pages/_stacks.scss +1 -1
- data/app/controllers/shipit/api/base_controller.rb +7 -3
- data/app/controllers/shipit/api/ccmenu_controller.rb +33 -0
- data/app/controllers/shipit/api/pull_requests_controller.rb +36 -0
- data/app/controllers/shipit/api/stacks_controller.rb +1 -0
- data/app/controllers/shipit/ccmenu_url_controller.rb +22 -0
- data/app/controllers/shipit/pull_requests_controller.rb +30 -0
- data/app/controllers/shipit/stacks_controller.rb +7 -2
- data/app/controllers/shipit/webhooks_controller.rb +1 -2
- data/app/helpers/shipit/github_url_helper.rb +8 -2
- data/app/helpers/shipit/shipit_helper.rb +9 -0
- data/app/helpers/shipit/stacks_helper.rb +22 -7
- data/app/jobs/shipit/background_job/unique.rb +19 -1
- data/app/jobs/shipit/cache_deploy_spec_job.rb +1 -1
- data/app/jobs/shipit/merge_pull_requests_job.rb +26 -0
- data/app/jobs/shipit/perform_task_job.rb +1 -1
- data/app/jobs/shipit/refresh_pull_request_job.rb +8 -0
- data/app/models/concerns/shipit/deferred_touch.rb +6 -1
- data/app/models/shipit/anonymous_user.rb +4 -0
- data/app/models/shipit/application_record.rb +5 -0
- data/app/models/shipit/commit.rb +51 -49
- data/app/models/shipit/commit_message.rb +32 -0
- data/app/models/shipit/deploy.rb +5 -0
- data/app/models/shipit/deploy_spec.rb +26 -1
- data/app/models/shipit/deploy_spec/file_system.rb +6 -1
- data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +10 -13
- data/app/models/shipit/deploy_spec/npm_discovery.rb +2 -1
- data/app/models/shipit/duration.rb +3 -1
- data/app/models/shipit/hook.rb +1 -0
- data/app/models/shipit/pull_request.rb +252 -0
- data/app/models/shipit/stack.rb +33 -17
- data/app/models/shipit/status.rb +1 -16
- data/app/models/shipit/status/common.rb +45 -0
- data/app/models/shipit/status/group.rb +82 -0
- data/app/models/shipit/status/missing.rb +30 -0
- data/app/models/shipit/status/unknown.rb +33 -0
- data/app/models/shipit/unlimited_api_client.rb +10 -0
- data/app/serializers/shipit/commit_serializer.rb +1 -1
- data/app/serializers/shipit/pull_request_serializer.rb +20 -0
- data/app/serializers/shipit/stack_serializer.rb +6 -2
- data/app/views/layouts/shipit.html.erb +41 -39
- data/app/views/shipit/ccmenu/project.xml.builder +13 -0
- data/app/views/shipit/commits/_commit.html.erb +1 -1
- data/app/views/shipit/deploys/_deploy.html.erb +1 -1
- data/app/views/shipit/pull_requests/_pull_request.html.erb +29 -0
- data/app/views/shipit/pull_requests/index.html.erb +20 -0
- data/app/views/shipit/shared/_author.html.erb +7 -0
- data/app/views/shipit/stacks/_header.html.erb +5 -0
- data/app/views/shipit/stacks/settings.html.erb +13 -0
- data/app/views/shipit/stacks/show.html.erb +3 -2
- data/app/views/shipit/statuses/_group.html.erb +1 -1
- data/app/views/shipit/tasks/_task.html.erb +1 -1
- data/config/initializers/inflections.rb +3 -0
- data/config/locales/en.yml +1 -3
- data/config/routes.rb +8 -0
- data/db/migrate/20170130113633_create_shipit_pull_requests.rb +25 -0
- data/db/migrate/20170208143657_add_pull_request_number_and_title_to_commits.rb +7 -0
- data/db/migrate/20170208154609_backfill_merge_commits.rb +13 -0
- data/db/migrate/20170209160355_add_branch_to_pull_requests.rb +5 -0
- data/db/migrate/20170215123538_add_merge_queue_enabled_to_stacks.rb +5 -0
- data/db/migrate/20170220152410_improve_users_indexing.rb +6 -0
- data/db/migrate/20170221102128_improve_tasks_indexing.rb +8 -0
- data/db/migrate/20170221130336_add_last_revalidated_at_on_pull_requests.rb +10 -0
- data/lib/shipit.rb +2 -0
- data/lib/shipit/version.rb +1 -1
- data/lib/tasks/cron.rake +1 -0
- data/test/controllers/api/ccmenu_controller_test.rb +57 -0
- data/test/controllers/api/commits_controller_test.rb +1 -1
- data/test/controllers/api/pull_requests_controller_test.rb +59 -0
- data/test/controllers/ccmenu_controller_test.rb +33 -0
- data/test/controllers/pull_requests_controller_test.rb +31 -0
- data/test/controllers/webhooks_controller_test.rb +3 -4
- data/test/dummy/config/environments/development.rb +3 -1
- data/test/dummy/data/stacks/shopify/junk/production/git/README.md +8 -0
- data/test/dummy/data/stacks/shopify/junk/production/git/circle.yml +4 -0
- data/test/dummy/data/stacks/shopify/junk/production/git/shipit.yml +4 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/schema.rb +45 -11
- data/test/dummy/db/seeds.rb +33 -10
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/fixtures/shipit/commits.yml +14 -0
- data/test/fixtures/shipit/pull_requests.yml +56 -0
- data/test/fixtures/shipit/stacks.yml +5 -1
- data/test/fixtures/shipit/statuses.yml +8 -0
- data/test/helpers/json_helper.rb +16 -14
- data/test/jobs/merge_pull_requests_job_test.rb +59 -0
- data/test/models/commits_test.rb +104 -49
- data/test/{unit → models}/deploy_spec_test.rb +138 -12
- data/test/models/deploys_test.rb +10 -4
- data/test/models/pull_request_test.rb +197 -0
- data/test/models/stacks_test.rb +46 -53
- data/test/models/status/group_test.rb +44 -0
- data/test/models/status/missing_test.rb +23 -0
- data/test/models/status_test.rb +3 -6
- data/test/unit/csv_serializer_test.rb +10 -2
- metadata +57 -12
- data/app/models/shipit/missing_status.rb +0 -21
- data/app/models/shipit/status_group.rb +0 -35
- data/app/models/shipit/unknown_status.rb +0 -48
- data/app/views/shipit/commits/_commit_author.html.erb +0 -7
- data/test/models/missing_status_test.rb +0 -23
- data/test/models/status_group_test.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 728cef09e4ae36c728e2a5d933f5967f4dcbe757
|
4
|
+
data.tar.gz: e35599675982e893d70e0c25d678da7e8910e7f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc3ecc88e8e13e99dca6f5789d364de99a21374fa566cd0273ed013f2f2b8da1c9a007fe9931daa76f66c12ba0ff9890cd2c8b4110d28f35510a7e3d29a3cef0
|
7
|
+
data.tar.gz: fde31379bbe98eb1dfb6af272a54a1b6b9e1a3e171d1dcc1588f8e67a257fe633117d1c35251f3543d3a5d585039c68e0e21dcd2ce8f945f009deee0bd722695
|
data/README.md
CHANGED
@@ -268,9 +268,10 @@ deploy:
|
|
268
268
|
```
|
269
269
|
<br>
|
270
270
|
|
271
|
-
**<code>deploy.max_commits</code>**
|
271
|
+
**<code>deploy.max_commits</code>** define the maximum number of commits that should be shipped per deploys. Defaults to `8`.
|
272
272
|
|
273
273
|
Human users will be warned that they are not respecting the recommendation, but allowed to continue.
|
274
|
+
However continuous delivery will respect this limit. If there is no deployable commits in this range, a human intervention will be required.
|
274
275
|
|
275
276
|
For example:
|
276
277
|
|
@@ -393,6 +394,38 @@ ci:
|
|
393
394
|
- ci/circleci
|
394
395
|
```
|
395
396
|
|
397
|
+
<h3 id="merge-queue">Merge Queue</h3>
|
398
|
+
|
399
|
+
The merge queue allow to register pull requests for them to be merged by Shipit once the stack is clear (no lock, no failing CI, no backlog). It can be enabled on a per stack basis via the settings page.
|
400
|
+
|
401
|
+
It can be customized via several `shipit.yml` properties:
|
402
|
+
|
403
|
+
**<code>merge.revalidate_after</code>** a duration after which pull requests that couldn't be merged are rejected from the queue. Defaults to unlimited.
|
404
|
+
|
405
|
+
For example:
|
406
|
+
```yml
|
407
|
+
merge:
|
408
|
+
revalidate_after: 12m30s
|
409
|
+
```
|
410
|
+
|
411
|
+
**<code>merge.require</code>** contains an array of the [statuses context](https://developer.github.com/v3/repos/statuses/) that you want Shipit to consider as failing if they aren't present on the pull request. Defaults to `ci.require` if present, or empty otherwise.
|
412
|
+
|
413
|
+
For example:
|
414
|
+
```yml
|
415
|
+
merge:
|
416
|
+
require:
|
417
|
+
- continuous-integration/travis-ci/push
|
418
|
+
```
|
419
|
+
|
420
|
+
**<code>merge.ignore</code>** contains an array of the [statuses context](https://developer.github.com/v3/repos/statuses/) that you want Shipit not to consider when merging pull requests. Defaults to the union of `ci.allow_failures` and `ci.hide` if any is present or empty otherwise.
|
421
|
+
|
422
|
+
For example:
|
423
|
+
```yml
|
424
|
+
merge:
|
425
|
+
ignore:
|
426
|
+
- codeclimate
|
427
|
+
```
|
428
|
+
|
396
429
|
<h3 id="custom-tasks">Custom tasks</h3>
|
397
430
|
|
398
431
|
You can create custom tasks that users execute directly from a stack's overview page in Shipit. To create a new custom task, specify its parameters in the `tasks` section of the `shipit.yml` file. For example:
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class PageUpdater
|
2
|
+
DEBOUNCE = 100
|
3
|
+
RETRY_DELAY = 5000
|
4
|
+
MAX_RETRIES = 5
|
5
|
+
|
6
|
+
@callbacks: []
|
7
|
+
@afterUpdate: (callback) ->
|
8
|
+
@callbacks.push(callback)
|
9
|
+
|
10
|
+
constructor: (@channel, @selectors) ->
|
11
|
+
@parser = new DOMParser()
|
12
|
+
@source = @listen()
|
13
|
+
@previousLastModified = null
|
14
|
+
|
15
|
+
requestUpdate: =>
|
16
|
+
@updateRequested = true
|
17
|
+
@scheduleUpdate()
|
18
|
+
|
19
|
+
scheduleUpdate: =>
|
20
|
+
return if @updateScheduled
|
21
|
+
return unless @updateRequested
|
22
|
+
setTimeout(@fetchPage, DEBOUNCE)
|
23
|
+
@updateScheduled = true
|
24
|
+
|
25
|
+
fetchPage: (message) =>
|
26
|
+
@updateRequested = false
|
27
|
+
jQuery.get(window.location.toString()).done(@updatePage).fail(=> @updateScheduled = false)
|
28
|
+
|
29
|
+
updatePage: (html, status, response) =>
|
30
|
+
lastModified = response.getResponseHeader('last-modified')
|
31
|
+
if lastModified? and lastModified != @previousLastModified
|
32
|
+
@previousLastModified = lastModified
|
33
|
+
|
34
|
+
newDocument = @parser.parseFromString(html, 'text/html')
|
35
|
+
for selector in @selectors
|
36
|
+
$(selector).html(newDocument.querySelectorAll("#{selector} > *"))
|
37
|
+
for callback in PageUpdater.callbacks
|
38
|
+
callback()
|
39
|
+
|
40
|
+
@updateScheduled = false
|
41
|
+
|
42
|
+
listen: ->
|
43
|
+
@source = new EventSource(@channel)
|
44
|
+
@source.addEventListener('update', @requestUpdate)
|
45
|
+
@retries = MAX_RETRIES
|
46
|
+
@interval = setInterval =>
|
47
|
+
switch @source.readyState
|
48
|
+
when @source.CLOSED
|
49
|
+
clearInterval(@interval)
|
50
|
+
if @retries > 0
|
51
|
+
@retries -= 1
|
52
|
+
@listen()
|
53
|
+
else
|
54
|
+
@retries = MAX_RETRIES
|
55
|
+
, RETRY_DELAY
|
56
|
+
|
57
|
+
jQuery ($) ->
|
58
|
+
PageUpdater.afterUpdate -> $('time[data-time-ago]').timeago()
|
59
|
+
|
60
|
+
channel = $('meta[name=subscription-channel]').attr('content')
|
61
|
+
selectors = (e.content for e in $('meta[name=subscription-selector]'))
|
62
|
+
if channel and selectors
|
63
|
+
new PageUpdater(channel, selectors)
|
@@ -20,24 +20,12 @@ jQuery ($) ->
|
|
20
20
|
|
21
21
|
displayIgnoreCiMessage()
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
$(
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
switch source.readyState
|
33
|
-
when source.CLOSED
|
34
|
-
clearInterval(interval)
|
35
|
-
if retries > 0
|
36
|
-
retries -= 1
|
37
|
-
listenToEventSource(url)
|
38
|
-
else
|
39
|
-
retries = 2
|
40
|
-
, 30000
|
41
|
-
|
42
|
-
$('[data-event-stream]').each ->
|
43
|
-
listenToEventSource($(this).data('event-stream'))
|
23
|
+
$(document).on 'click', '.setting-ccmenu input[type=submit]', (event) ->
|
24
|
+
event.preventDefault()
|
25
|
+
$(event.target).prop('disabled', true)
|
26
|
+
$.get(event.target.dataset.remote).done((data) ->
|
27
|
+
$('#ccmenu-url').val(data.ccmenu_url).removeClass('hidden')
|
28
|
+
$(event.target).addClass('hidden')
|
29
|
+
).fail(->
|
30
|
+
$(event.target).prop('disabled', false)
|
31
|
+
)
|
@@ -26,6 +26,20 @@ textarea {
|
|
26
26
|
|
27
27
|
.field-wrapper {
|
28
28
|
margin-bottom: 1.5rem;
|
29
|
+
input:focus {
|
30
|
+
border-color: $blue;
|
31
|
+
}
|
32
|
+
&.inline {
|
33
|
+
input {
|
34
|
+
&.btn {
|
35
|
+
height: 2.5rem;
|
36
|
+
}
|
37
|
+
display: inline;
|
38
|
+
&:focus {
|
39
|
+
padding-right: 30px;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
29
43
|
}
|
30
44
|
|
31
45
|
.field_with_errors {
|
@@ -2,7 +2,7 @@
|
|
2
2
|
// COMMIT LIST
|
3
3
|
// =============================================================================
|
4
4
|
|
5
|
-
.commit-list, .task-list {
|
5
|
+
.commit-list, .task-list, .pr-list {
|
6
6
|
list-style-type: none;
|
7
7
|
margin: 0; padding: 0;
|
8
8
|
}
|
@@ -11,7 +11,7 @@
|
|
11
11
|
float: right;
|
12
12
|
}
|
13
13
|
|
14
|
-
.commit, .task {
|
14
|
+
.commit, .task, .pr {
|
15
15
|
padding: .75rem 0;
|
16
16
|
display: flex;
|
17
17
|
flex-direction: column;
|
@@ -70,7 +70,7 @@
|
|
70
70
|
// COMMIT DETAILS
|
71
71
|
// -----------------------------------------------------------------------------
|
72
72
|
|
73
|
-
.commit-details {
|
73
|
+
.commit-details, .pr-details {
|
74
74
|
flex-grow: 1;
|
75
75
|
|
76
76
|
@include media(tablet-down) {
|
@@ -88,11 +88,17 @@
|
|
88
88
|
}
|
89
89
|
}
|
90
90
|
|
91
|
-
.commit-title
|
92
|
-
|
91
|
+
.commit-title, .pr-title {
|
92
|
+
a {
|
93
|
+
color: #333;
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
.pr-details .pr-number .number {
|
98
|
+
color: $blue;
|
93
99
|
}
|
94
100
|
|
95
|
-
.commit-meta {
|
101
|
+
.commit-meta, .pr-meta {
|
96
102
|
font-size: 0.8em;
|
97
103
|
color: #999;
|
98
104
|
margin: 0;
|
@@ -100,6 +106,10 @@
|
|
100
106
|
@include media(desktop) {
|
101
107
|
@include truncate;
|
102
108
|
}
|
109
|
+
|
110
|
+
.warning {
|
111
|
+
color: $orange;
|
112
|
+
}
|
103
113
|
}
|
104
114
|
|
105
115
|
.utc-timecode {
|
@@ -25,9 +25,13 @@ module Shipit
|
|
25
25
|
private
|
26
26
|
|
27
27
|
def authenticate_api_client
|
28
|
-
@current_api_client =
|
29
|
-
|
30
|
-
|
28
|
+
@current_api_client = if Shipit.disable_api_authentication
|
29
|
+
UnlimitedApiClient.new
|
30
|
+
else
|
31
|
+
authenticate_with_http_basic do |*parts|
|
32
|
+
token = parts.select(&:present?).join('--')
|
33
|
+
ApiClient.authenticate(token)
|
34
|
+
end
|
31
35
|
end
|
32
36
|
return if @current_api_client
|
33
37
|
headers['WWW-Authenticate'] = 'Basic realm="Authentication token"'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Shipit
|
2
|
+
module Api
|
3
|
+
class CCMenuController < BaseController
|
4
|
+
require_permission :read, :stack
|
5
|
+
|
6
|
+
class NoDeploy
|
7
|
+
def id
|
8
|
+
0
|
9
|
+
end
|
10
|
+
|
11
|
+
def ended_at
|
12
|
+
Time.now.utc
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def show
|
17
|
+
latest_deploy = stack.deploys_and_rollbacks.last || NoDeploy.new
|
18
|
+
render 'shipit/ccmenu/project.xml.builder', formats: [:xml], locals: {stack: stack, deploy: latest_deploy}
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def stack
|
24
|
+
@stack ||= Stack.from_param!(params[:stack_id])
|
25
|
+
end
|
26
|
+
|
27
|
+
def authenticate_api_client
|
28
|
+
@current_api_client = ApiClient.authenticate(params[:token])
|
29
|
+
super unless @current_api_client
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Shipit
|
2
|
+
module Api
|
3
|
+
class PullRequestsController < BaseController
|
4
|
+
require_permission :read, :stack
|
5
|
+
require_permission :deploy, :stack, only: %i(update destroy)
|
6
|
+
|
7
|
+
def index
|
8
|
+
render_resources stack.pull_requests.includes(:head).order(id: :desc)
|
9
|
+
end
|
10
|
+
|
11
|
+
def show
|
12
|
+
render_resource stack.pull_requests.find_by_number!(params[:id])
|
13
|
+
end
|
14
|
+
|
15
|
+
def update
|
16
|
+
pull_request = PullRequest.request_merge!(stack, params[:id], current_user)
|
17
|
+
if pull_request.waiting?
|
18
|
+
head :accepted
|
19
|
+
elsif pull_request.merged?
|
20
|
+
render status: :method_not_allowed, json: {
|
21
|
+
message: "This pull request was already merged.",
|
22
|
+
}
|
23
|
+
else
|
24
|
+
raise "Pull Request is neither waiting nor merged, this should be impossible"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def destroy
|
29
|
+
if pull_request = stack.pull_requests.where(number: params[:id]).first
|
30
|
+
pull_request.cancel! if pull_request.waiting?
|
31
|
+
end
|
32
|
+
head :no_content
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Shipit
|
4
|
+
class CCMenuUrlController < ShipitController
|
5
|
+
def fetch
|
6
|
+
uri = URI(api_stack_ccmenu_url(stack_id: stack.to_param))
|
7
|
+
uri.query = {'token' => client.authentication_token}.to_query
|
8
|
+
render json: {ccmenu_url: uri.to_s}
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def client
|
14
|
+
@client ||= ApiClient.create_with(permissions: %w(read:stack))
|
15
|
+
.find_or_create_by!(creator: current_user, name: 'CCMenu Client')
|
16
|
+
end
|
17
|
+
|
18
|
+
def stack
|
19
|
+
@stack ||= Stack.from_param!(params[:stack_id])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Shipit
|
2
|
+
class PullRequestsController < ShipitController
|
3
|
+
def index
|
4
|
+
@pull_requests = stack.pull_requests.queued
|
5
|
+
end
|
6
|
+
|
7
|
+
def create
|
8
|
+
if pr_number = PullRequest.extract_number(stack, params[:number_or_url])
|
9
|
+
pull_request = PullRequest.request_merge!(stack, pr_number, current_user)
|
10
|
+
flash[:success] = "Pull request ##{pull_request.number} added to the queue."
|
11
|
+
else
|
12
|
+
flash[:warning] = "Invalid or missing pull request number."
|
13
|
+
end
|
14
|
+
redirect_to stack_pull_requests_path
|
15
|
+
end
|
16
|
+
|
17
|
+
def destroy
|
18
|
+
pull_request = stack.pull_requests.find(params[:id])
|
19
|
+
pull_request.cancel!
|
20
|
+
flash[:success] = 'Merge canceled'
|
21
|
+
redirect_to stack_pull_requests_path
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def stack
|
27
|
+
@stack ||= Stack.from_param!(params[:stack_id])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -85,8 +85,13 @@ module Shipit
|
|
85
85
|
end
|
86
86
|
|
87
87
|
def update_params
|
88
|
-
params.require(:stack).permit(
|
89
|
-
|
88
|
+
params.require(:stack).permit(
|
89
|
+
:deploy_url,
|
90
|
+
:environment,
|
91
|
+
:continuous_deployment,
|
92
|
+
:ignore_ci,
|
93
|
+
:merge_queue_enabled,
|
94
|
+
)
|
90
95
|
end
|
91
96
|
end
|
92
97
|
end
|