shipit-engine 0.10.0 → 0.11.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 +33 -11
- data/app/assets/javascripts/shipit.js.coffee +7 -1
- data/app/assets/stylesheets/_base/_buttons.scss +3 -0
- data/app/assets/stylesheets/_base/_colors.scss +1 -1
- data/app/assets/stylesheets/_base/_status-items.scss +1 -1
- data/app/assets/stylesheets/_pages/_deploy.scss +1 -1
- data/app/assets/stylesheets/_structure/_main.scss +1 -1
- data/app/controllers/shipit/stacks_controller.rb +1 -5
- data/app/helpers/shipit/shipit_helper.rb +7 -9
- data/app/helpers/shipit/stacks_helper.rb +14 -28
- data/app/jobs/shipit/continuous_delivery_job.rb +1 -1
- data/app/jobs/shipit/update_estimated_deploy_duration.rb +9 -0
- data/app/models/shipit/commit_checks.rb +1 -1
- data/app/models/shipit/deploy.rb +1 -1
- data/app/models/shipit/deploy_spec/bundler_discovery.rb +1 -1
- data/app/models/shipit/deploy_spec/file_system.rb +6 -1
- data/app/models/shipit/deploy_spec.rb +12 -0
- data/app/models/shipit/duration.rb +29 -9
- data/app/models/shipit/github_hook.rb +0 -2
- data/app/models/shipit/stack.rb +48 -11
- data/app/models/shipit/task.rb +13 -2
- data/app/models/shipit/team.rb +1 -1
- data/app/models/shipit/undeployed_commit.rb +36 -0
- data/app/views/layouts/shipit.html.erb +6 -4
- data/app/views/shipit/deploys/show.html.erb +1 -1
- data/app/views/shipit/stacks/_header.html.erb +7 -0
- data/app/views/shipit/stacks/show.html.erb +1 -1
- data/config/locales/en.yml +9 -3
- data/config/secrets.development.example.yml +19 -0
- data/config/secrets.development.shopify.yml +19 -0
- data/config/secrets.development.yml +16 -0
- data/db/migrate/20160502150713_add_estimated_deploy_duration_to_stacks.rb +5 -0
- data/db/migrate/20160526192650_reorder_active_tasks_index.rb +7 -0
- data/lib/shipit/command.rb +15 -2
- data/lib/shipit/engine.rb +2 -1
- data/lib/shipit/stat.rb +13 -0
- data/lib/shipit/version.rb +1 -1
- data/lib/shipit.rb +13 -0
- data/lib/tasks/cron.rake +1 -0
- data/test/controllers/github_authentication_controller_test.rb +5 -5
- data/test/dummy/config/initializers/0_load_development_secrets.rb +9 -0
- data/test/dummy/config/secrets.yml +5 -16
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/schema.rb +14 -14
- data/test/dummy/db/seeds.rb +1 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/fixtures/shipit/stacks.yml +2 -2
- data/test/fixtures/shipit/tasks.yml +3 -1
- data/test/fixtures/shipit/users.yml +5 -0
- data/test/helpers/queries_helper.rb +1 -1
- data/test/models/deploys_test.rb +7 -0
- data/test/models/duration_test.rb +23 -0
- data/test/models/stacks_test.rb +93 -4
- data/test/models/undeployed_commits_test.rb +100 -0
- data/test/test_helper.rb +1 -0
- data/test/unit/deploy_spec_test.rb +1 -1
- data/test/unit/shipit_test.rb +2 -1
- metadata +19 -10
- data/db/schema.rb +0 -188
- data/test/dummy/config/secrets.example.yml +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4329e9e481f6b7065a401570ebd011d1ea4fb19
|
4
|
+
data.tar.gz: ef736fcc3a561d2db82c35795092388abbbfc90a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e79a3a01a5d2c71d48dc0bc0ec8ae032721046cccecb5680c876b82c0c246da5ce75a4812ee24afe9aeeaca06d5749637bffb6cb2f9b9aa374fe96f2c790cbff
|
7
|
+
data.tar.gz: 60bde57fcfbd57ff17bc6d0d64785b81bf2e2cfd9ee69d5c9849911a5a6936ceccd9ac80fb2a50bb10254a9789084b26f1095acc43b7c7593ebcd19255d93554
|
data/README.md
CHANGED
@@ -33,7 +33,7 @@ This guide aims to help you [set up](#installation-and-setup), [use](#using-ship
|
|
33
33
|
* [Format and content of shipit.yml](#configuring-shipit)
|
34
34
|
* [Format and content of secrets.yml](#configuring-secrets)
|
35
35
|
* [Script parameters](#script-parameters)
|
36
|
-
* [Free samples](
|
36
|
+
* [Free samples](/examples/shipit.yml)
|
37
37
|
|
38
38
|
* * *
|
39
39
|
|
@@ -45,8 +45,8 @@ This guide aims to help you [set up](#installation-and-setup), [use](#using-ship
|
|
45
45
|
|
46
46
|
Shipit provides you with a Rails template. To bootstrap your Shipit installation:
|
47
47
|
|
48
|
-
1. If you don't have Rails installed, run this command: `gem install rails`
|
49
|
-
2. Run this command: `rails new shipit -m https://raw.githubusercontent.com/Shopify/shipit-engine/master/template.rb`
|
48
|
+
1. If you don't have Rails installed, run this command: `gem install rails -v 4.2.6`
|
49
|
+
2. Run this command: `rails _4.2.6_ new shipit -m https://raw.githubusercontent.com/Shopify/shipit-engine/master/template.rb`
|
50
50
|
3. Enter your **Client ID**, **Client Secret**, and **GitHub API access token** when prompted. These can be found on your application's GitHub page.
|
51
51
|
4. To setup the database, run this command: `rake db:setup`
|
52
52
|
|
@@ -250,6 +250,18 @@ deploy:
|
|
250
250
|
```
|
251
251
|
<br>
|
252
252
|
|
253
|
+
**<code>deploy.max_commits</code>** allow to set a limit to the number of commits being shipped per deploys.
|
254
|
+
|
255
|
+
Human users will be warned that they are not respecting the recommandation, but allowed to continue.
|
256
|
+
|
257
|
+
For example:
|
258
|
+
|
259
|
+
```yaml
|
260
|
+
deploy:
|
261
|
+
max_commits: 5
|
262
|
+
```
|
263
|
+
<br>
|
264
|
+
|
253
265
|
**<code>rollback.override</code>** contains an array of the shell commands required to rollback the application to a previous state. Shipit will try to infer it from the repository structure, but you can change the default inference. This key defaults to disabled unless Capistrano is detected.
|
254
266
|
|
255
267
|
For example:
|
@@ -360,7 +372,7 @@ You can create custom tasks that users execute directly from a stack's overview
|
|
360
372
|
tasks:
|
361
373
|
restart:
|
362
374
|
action: "Restart Application"
|
363
|
-
description: "Sometimes needed if you the application to restart but don't want to ship any new code."
|
375
|
+
description: "Sometimes needed if you want the application to restart but don't want to ship any new code."
|
364
376
|
steps:
|
365
377
|
- ssh deploy@myserver.example.com 'touch myapp/restart.txt'
|
366
378
|
```
|
@@ -382,7 +394,7 @@ Tasks like deploys can prompt for user defined environment variables:
|
|
382
394
|
tasks:
|
383
395
|
restart:
|
384
396
|
action: "Restart Application"
|
385
|
-
description: "Sometimes needed if you the application to restart but don't want to ship any new code."
|
397
|
+
description: "Sometimes needed if you want the application to restart but don't want to ship any new code."
|
386
398
|
steps:
|
387
399
|
- ssh deploy@myserver.example.com 'touch myapp/restart.txt'
|
388
400
|
variables:
|
@@ -444,7 +456,7 @@ The settings in the `secrets.yml` file relate to the ways that GitHub connects w
|
|
444
456
|
For example:
|
445
457
|
|
446
458
|
```yml
|
447
|
-
|
459
|
+
production:
|
448
460
|
secret_key_base: s3cr3t # This needs to be a very long, fully random
|
449
461
|
```
|
450
462
|
<br>
|
@@ -462,7 +474,7 @@ After you change the list of time you have to invoke `bin/rake teams:fetch` in p
|
|
462
474
|
For example:
|
463
475
|
|
464
476
|
```yml
|
465
|
-
|
477
|
+
production:
|
466
478
|
github_oauth:
|
467
479
|
id: (your application's Client ID)
|
468
480
|
secret: (your application's Client Secret)
|
@@ -479,7 +491,7 @@ If you specify an `access_token`, you don't need a `login` and `password`. The o
|
|
479
491
|
For example:
|
480
492
|
|
481
493
|
```yml
|
482
|
-
|
494
|
+
production:
|
483
495
|
github_api:
|
484
496
|
access_token: 10da65c687f6degaf5475ce12a980d5vd8c44d2a
|
485
497
|
```
|
@@ -489,7 +501,7 @@ development:
|
|
489
501
|
|
490
502
|
For example:
|
491
503
|
```yml
|
492
|
-
|
504
|
+
production:
|
493
505
|
host: 'http://localhost:3000'
|
494
506
|
```
|
495
507
|
<br>
|
@@ -499,7 +511,7 @@ development:
|
|
499
511
|
For example:
|
500
512
|
|
501
513
|
```yml
|
502
|
-
|
514
|
+
production:
|
503
515
|
redis_url: "redis://127.0.0.1:6379/7"
|
504
516
|
```
|
505
517
|
|
@@ -509,11 +521,21 @@ If you use GitHub Enterprise, you must also specify the `github_domain`.
|
|
509
521
|
|
510
522
|
For example:
|
511
523
|
```yml
|
512
|
-
|
524
|
+
production:
|
513
525
|
github_domain: "github.example.com"
|
514
526
|
|
515
527
|
```
|
516
528
|
|
529
|
+
<br>
|
530
|
+
|
531
|
+
**`commands_inactivity_timeout`** is the duration after which shipit will terminate a command if no ouput was received. Default is `300` (5 minutes).
|
532
|
+
|
533
|
+
For example:
|
534
|
+
```yml
|
535
|
+
production:
|
536
|
+
commands_inactivity_timeout: 900 # 15 minutes
|
537
|
+
```
|
538
|
+
|
517
539
|
<h2 id="script-parameters">Script parameters</h2>
|
518
540
|
|
519
541
|
Your deploy scripts have access to the following environment variables:
|
@@ -20,8 +20,14 @@
|
|
20
20
|
$(document).on 'click', '.disabled, .btn--disabled', (event) ->
|
21
21
|
event.preventDefault()
|
22
22
|
|
23
|
+
$(document).on 'click', '.enable-notifications .banner__dismiss', (event) ->
|
24
|
+
$(event.target).closest('.banner').addClass('hidden')
|
25
|
+
localStorage.setItem("dismissed-enable-notifications", true)
|
26
|
+
|
23
27
|
jQuery ->
|
24
|
-
|
28
|
+
if(localStorage.getItem("dismissed-enable-notifications"))
|
29
|
+
return
|
30
|
+
$notificationNotice = $('.enable-notifications')
|
25
31
|
|
26
32
|
if $.notifyCheck() == $.NOTIFY_NOT_ALLOWED
|
27
33
|
$button = $notificationNotice.find('button')
|
@@ -17,11 +17,7 @@ module Shipit
|
|
17
17
|
return if flash.empty? && !stale?(last_modified: @stack.updated_at)
|
18
18
|
|
19
19
|
@tasks = @stack.tasks.order(id: :desc).preload(:since_commit, :until_commit, :user).limit(10)
|
20
|
-
@commits = @stack.
|
21
|
-
if deployed_commit = @stack.last_deployed_commit
|
22
|
-
@commits = @commits.where('id > ?', deployed_commit.id)
|
23
|
-
end
|
24
|
-
@commits = @commits.to_a
|
20
|
+
@commits = @stack.undeployed_commits { |scope| scope.preload(:author, :statuses) }
|
25
21
|
end
|
26
22
|
|
27
23
|
def lookup
|
@@ -19,9 +19,7 @@ module Shipit
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def include_plugins(stack)
|
22
|
-
stack.plugins.flat_map
|
23
|
-
plugin_tags(plugin, config)
|
24
|
-
end.join.html_safe
|
22
|
+
safe_join(stack.plugins.flat_map { |plugin, config| plugin_tags(plugin, config) })
|
25
23
|
end
|
26
24
|
|
27
25
|
def plugin_tags(plugin, config)
|
@@ -33,7 +31,7 @@ module Shipit
|
|
33
31
|
end
|
34
32
|
|
35
33
|
def missing_github_oauth_message
|
36
|
-
|
34
|
+
<<-MESSAGE.html_safe
|
37
35
|
Shipit requires a GitHub application to authenticate users.
|
38
36
|
If you haven't created an application on GitHub yet, you can do so in the
|
39
37
|
#{link_to 'Settings', Shipit.github_url('/settings/applications/new'), target: '_blank'}
|
@@ -42,21 +40,21 @@ module Shipit
|
|
42
40
|
end
|
43
41
|
|
44
42
|
def missing_github_oauth_id_message
|
45
|
-
|
43
|
+
<<-MESSAGE.html_safe
|
46
44
|
Copy the Client ID from your GitHub application,
|
47
45
|
and paste it into the secrets.yml file under <code>github_oauth.id</code>.
|
48
46
|
MESSAGE
|
49
47
|
end
|
50
48
|
|
51
49
|
def missing_github_oauth_secret_message
|
52
|
-
|
50
|
+
<<-MESSAGE.html_safe
|
53
51
|
Copy the Client Secret from your GitHub application,
|
54
52
|
and paste it into the secrets.yml file under <code>github_oauth.secret</code>.
|
55
53
|
MESSAGE
|
56
54
|
end
|
57
55
|
|
58
56
|
def missing_github_api_credentials_message
|
59
|
-
|
57
|
+
<<-MESSAGE.html_safe
|
60
58
|
Shipit needs API access to GitHub. You can
|
61
59
|
#{link_to 'create an access token', Shipit.github_url('/settings/tokens'), target: '_blank'}
|
62
60
|
with the following permissions:
|
@@ -66,14 +64,14 @@ module Shipit
|
|
66
64
|
end
|
67
65
|
|
68
66
|
def missing_redis_url_message
|
69
|
-
|
67
|
+
<<-MESSAGE.html_safe
|
70
68
|
Shipit needs a Redis server. Please configure the Redis URL in the secrets.yml file of your app,
|
71
69
|
under the key <code>redis_url</code>.
|
72
70
|
MESSAGE
|
73
71
|
end
|
74
72
|
|
75
73
|
def missing_host_message
|
76
|
-
|
74
|
+
<<-MESSAGE.html_safe
|
77
75
|
Shipit needs the host of the application before generating links in background jobs.
|
78
76
|
Add the host name to the secrets.yml file, under the <code>host</code> key.
|
79
77
|
MESSAGE
|
@@ -2,33 +2,34 @@ module Shipit
|
|
2
2
|
module StacksHelper
|
3
3
|
COMMIT_TITLE_LENGTH = 79
|
4
4
|
|
5
|
-
def redeploy_button(
|
6
|
-
|
5
|
+
def redeploy_button(deployed_commit)
|
6
|
+
commit = UndeployedCommit.new(deployed_commit, 0)
|
7
|
+
url = new_stack_deploy_path(commit.stack, sha: commit.sha)
|
7
8
|
classes = %W(btn btn--primary deploy-action #{commit.state})
|
8
9
|
|
9
10
|
unless commit.stack.deployable?
|
10
|
-
classes.push(
|
11
|
+
classes.push(bypass_safeties? ? 'btn--warning' : 'btn--disabled')
|
11
12
|
end
|
12
13
|
|
13
|
-
caption
|
14
|
-
caption = 'Locked' if commit.stack.locked? && !ignore_lock?
|
15
|
-
caption = 'Deploy in progress...' if commit.stack.active_task?
|
16
|
-
|
17
|
-
link_to(caption, url, class: classes)
|
14
|
+
link_to(t("redeploy_button.caption.#{commit.redeploy_state(bypass_safeties?)}"), url, class: classes)
|
18
15
|
end
|
19
16
|
|
20
|
-
def
|
17
|
+
def bypass_safeties?
|
21
18
|
params[:force].present?
|
22
19
|
end
|
23
20
|
|
24
21
|
def deploy_button(commit)
|
25
|
-
url = new_stack_deploy_path(
|
22
|
+
url = new_stack_deploy_path(commit.stack, sha: commit.sha)
|
26
23
|
classes = %W(btn btn--primary deploy-action #{commit.state})
|
27
|
-
|
28
|
-
|
24
|
+
data = {}
|
25
|
+
if commit.deploy_disallowed?
|
26
|
+
classes.push(bypass_safeties? ? 'btn--warning' : 'btn--disabled')
|
27
|
+
elsif commit.deploy_discouraged?
|
28
|
+
classes.push('btn--warning')
|
29
|
+
data[:tooltip] = t('deploy_button.hint.max_commits', maximum: commit.stack.maximum_commits_per_deploy)
|
29
30
|
end
|
30
31
|
|
31
|
-
link_to(
|
32
|
+
link_to(t("deploy_button.caption.#{commit.deploy_state(bypass_safeties?)}"), url, class: classes, data: data)
|
32
33
|
end
|
33
34
|
|
34
35
|
def github_change_url(commit)
|
@@ -59,20 +60,5 @@ module Shipit
|
|
59
60
|
def render_raw_commit_id_link(commit)
|
60
61
|
link_to(commit.short_sha, github_commit_url(commit), target: '_blank', class: 'number')
|
61
62
|
end
|
62
|
-
|
63
|
-
private
|
64
|
-
|
65
|
-
def deploy_button_disabled?(commit)
|
66
|
-
!commit.deployable? || !commit.stack.deployable?
|
67
|
-
end
|
68
|
-
|
69
|
-
def deploy_button_caption(commit)
|
70
|
-
state = commit.status.state
|
71
|
-
state = 'locked' if commit.stack.locked? && !ignore_lock?
|
72
|
-
if commit.deployable?
|
73
|
-
state = commit.stack.active_task? ? 'deploying' : 'enabled'
|
74
|
-
end
|
75
|
-
t("deploy_button.caption.#{state}")
|
76
|
-
end
|
77
63
|
end
|
78
64
|
end
|
data/app/models/shipit/deploy.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Shipit
|
2
2
|
class DeploySpec
|
3
3
|
module BundlerDiscovery
|
4
|
-
DEFAULT_BUNDLER_WITHOUT = %w(default production development test staging benchmark debug)
|
4
|
+
DEFAULT_BUNDLER_WITHOUT = %w(default production development test staging benchmark debug).freeze
|
5
5
|
|
6
6
|
def discover_dependencies_steps
|
7
7
|
discover_bundler || super
|
@@ -44,7 +44,12 @@ module Shipit
|
|
44
44
|
},
|
45
45
|
'plugins' => plugins,
|
46
46
|
'dependencies' => {'override' => dependencies_steps},
|
47
|
-
'deploy' => {
|
47
|
+
'deploy' => {
|
48
|
+
'override' => deploy_steps,
|
49
|
+
'variables' => deploy_variables.map(&:to_h),
|
50
|
+
'max_commits' => maximum_commits_per_deploy,
|
51
|
+
'interval' => pause_between_deploys,
|
52
|
+
},
|
48
53
|
'rollback' => {'override' => rollback_steps},
|
49
54
|
'fetch' => fetch_deployed_revision_steps,
|
50
55
|
'tasks' => cacheable_tasks,
|
@@ -63,6 +63,14 @@ module Shipit
|
|
63
63
|
end
|
64
64
|
alias_method :dependencies_steps!, :dependencies_steps
|
65
65
|
|
66
|
+
def maximum_commits_per_deploy
|
67
|
+
config('deploy', 'max_commits')
|
68
|
+
end
|
69
|
+
|
70
|
+
def pause_between_deploys
|
71
|
+
Duration.parse(config('deploy', 'interval') { 0 })
|
72
|
+
end
|
73
|
+
|
66
74
|
def deploy_steps
|
67
75
|
around_steps('deploy') do
|
68
76
|
config('deploy', 'override') { discover_deploy_steps }
|
@@ -143,6 +151,10 @@ module Shipit
|
|
143
151
|
config('machine', 'cleanup') { true }
|
144
152
|
end
|
145
153
|
|
154
|
+
def links
|
155
|
+
config('links') { {} }
|
156
|
+
end
|
157
|
+
|
146
158
|
private
|
147
159
|
|
148
160
|
def around_steps(section)
|
@@ -1,5 +1,13 @@
|
|
1
1
|
module Shipit
|
2
|
-
class Duration
|
2
|
+
class Duration < ActiveSupport::Duration
|
3
|
+
FORMAT = /
|
4
|
+
\A
|
5
|
+
(?<days>\d+d)?
|
6
|
+
(?<hours>\d+h)?
|
7
|
+
(?<minutes>\d+m)?
|
8
|
+
(?<seconds>\d+s?)?
|
9
|
+
\z
|
10
|
+
/x
|
3
11
|
UNITS = {
|
4
12
|
's' => :seconds,
|
5
13
|
'm' => :minutes,
|
@@ -7,21 +15,33 @@ module Shipit
|
|
7
15
|
'd' => :days,
|
8
16
|
}.freeze
|
9
17
|
|
10
|
-
|
11
|
-
|
18
|
+
class << self
|
19
|
+
def parse(value)
|
20
|
+
unless match = FORMAT.match(value.to_s)
|
21
|
+
raise ArgumentError, "not a duration: #{value.inspect}"
|
22
|
+
end
|
23
|
+
parts = []
|
24
|
+
UNITS.values.each do |unit|
|
25
|
+
if value = match[unit]
|
26
|
+
parts << [unit, value.to_i]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
time = ::Time.current
|
31
|
+
new(time.advance(parts.to_h) - time, parts)
|
32
|
+
end
|
12
33
|
end
|
13
34
|
|
14
|
-
def
|
15
|
-
|
35
|
+
def initialize(value, parts = [[:seconds, value]])
|
36
|
+
super
|
16
37
|
end
|
17
38
|
|
18
39
|
def to_s
|
19
|
-
|
20
|
-
days, seconds = seconds.divmod(1.day.to_i)
|
40
|
+
days, seconds_left = value.divmod(1.day.to_i)
|
21
41
|
if days > 0
|
22
|
-
"#{days}d#{Time.at(
|
42
|
+
"#{days}d#{Time.at(seconds_left).utc.strftime('%Hh%Mm%Ss')}"
|
23
43
|
else
|
24
|
-
Time.at(
|
44
|
+
Time.at(value).utc.strftime('%Hh%Mm%Ss')[/[^0a-z]\w+/] || '0s'
|
25
45
|
end
|
26
46
|
end
|
27
47
|
end
|
data/app/models/shipit/stack.rb
CHANGED
@@ -21,7 +21,7 @@ module Shipit
|
|
21
21
|
REPO_OWNER_MAX_SIZE = 39
|
22
22
|
REPO_NAME_MAX_SIZE = 100
|
23
23
|
ENVIRONMENT_MAX_SIZE = 50
|
24
|
-
REQUIRED_HOOKS = %i(push status)
|
24
|
+
REQUIRED_HOOKS = %i(push status).freeze
|
25
25
|
|
26
26
|
has_many :commits, dependent: :destroy
|
27
27
|
has_many :tasks, dependent: :destroy
|
@@ -62,13 +62,19 @@ module Shipit
|
|
62
62
|
validates :lock_reason, length: {maximum: 4096}
|
63
63
|
|
64
64
|
serialize :cached_deploy_spec, DeploySpec
|
65
|
-
delegate :find_task_definition, :supports_rollback?,
|
65
|
+
delegate :find_task_definition, :supports_rollback?, :links,
|
66
66
|
:supports_fetch_deployed_revision?, to: :cached_deploy_spec, allow_nil: true
|
67
67
|
|
68
68
|
def self.refresh_deployed_revisions
|
69
69
|
find_each.select(&:supports_fetch_deployed_revision?).each(&:async_refresh_deployed_revision)
|
70
70
|
end
|
71
71
|
|
72
|
+
def self.schedule_continuous_delivery
|
73
|
+
where(continuous_deployment: true).find_each do |stack|
|
74
|
+
ContinuousDeliveryJob.perform_later(stack)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
72
78
|
def undeployed_commits?
|
73
79
|
undeployed_commits_count > 0
|
74
80
|
end
|
@@ -103,11 +109,25 @@ module Shipit
|
|
103
109
|
deploy
|
104
110
|
end
|
105
111
|
|
106
|
-
def
|
112
|
+
def trigger_continuous_delivery
|
107
113
|
return unless deployable?
|
108
|
-
if
|
114
|
+
return if deployed_too_recently?
|
115
|
+
|
116
|
+
if commit = next_commit_to_deploy
|
109
117
|
return if commit.deployed?
|
110
|
-
trigger_deploy(commit,
|
118
|
+
trigger_deploy(commit, Shipit.user)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def next_commit_to_deploy
|
123
|
+
commits_to_deploy = commits.order(id: :asc).newer_than(last_deployed_commit).reachable.preload(:statuses)
|
124
|
+
commits_to_deploy = commits_to_deploy.limit(maximum_commits_per_deploy) if maximum_commits_per_deploy
|
125
|
+
commits_to_deploy.to_a.reverse.find(&:deployable?)
|
126
|
+
end
|
127
|
+
|
128
|
+
def deployed_too_recently?
|
129
|
+
if task = last_active_task
|
130
|
+
task.ended_at? && (task.ended_at + pause_between_deploys).future?
|
111
131
|
end
|
112
132
|
end
|
113
133
|
|
@@ -144,6 +164,8 @@ module Shipit
|
|
144
164
|
'locked'
|
145
165
|
else
|
146
166
|
significant_statuses = undeployed_commits.map(&:significant_status)
|
167
|
+
significant_statuses << last_deployed_commit.significant_status unless last_deployed_commit.blank?
|
168
|
+
|
147
169
|
last_finalized_status = significant_statuses.reject { |s| %w(pending unknown).include?(s.state) }.first
|
148
170
|
last_finalized_status.try!(:simple_state) || 'pending'
|
149
171
|
end
|
@@ -155,13 +177,19 @@ module Shipit
|
|
155
177
|
end
|
156
178
|
|
157
179
|
def undeployed_commits
|
158
|
-
commits.reachable.newer_than(last_deployed_commit).order(id: :
|
180
|
+
scope = commits.reachable.newer_than(last_deployed_commit).order(id: :asc)
|
181
|
+
yield scope if block_given?
|
182
|
+
scope.map.with_index { |c, i| UndeployedCommit.new(c, i) }.reverse
|
159
183
|
end
|
160
184
|
|
161
185
|
def last_successful_deploy
|
162
186
|
deploys_and_rollbacks.success.order(created_at: :desc).first
|
163
187
|
end
|
164
188
|
|
189
|
+
def last_active_task
|
190
|
+
tasks.exclusive.last
|
191
|
+
end
|
192
|
+
|
165
193
|
def last_deployed_commit
|
166
194
|
if deploy = last_successful_deploy
|
167
195
|
deploy.until_commit
|
@@ -170,10 +198,6 @@ module Shipit
|
|
170
198
|
end
|
171
199
|
end
|
172
200
|
|
173
|
-
def last_deployable_commit
|
174
|
-
commits.order(id: :desc).newer_than(last_deployed_commit).reachable.preload(:statuses).to_a.find(&:deployable?)
|
175
|
-
end
|
176
|
-
|
177
201
|
def filter_visible_statuses(statuses)
|
178
202
|
statuses.reject { |s| hidden_statuses.include?(s.context) }
|
179
203
|
end
|
@@ -288,7 +312,8 @@ module Shipit
|
|
288
312
|
end
|
289
313
|
|
290
314
|
delegate :plugins, :task_definitions, :hidden_statuses, :required_statuses, :soft_failing_statuses,
|
291
|
-
:deploy_variables, :filter_task_envs, :filter_deploy_envs,
|
315
|
+
:deploy_variables, :filter_task_envs, :filter_deploy_envs, :maximum_commits_per_deploy,
|
316
|
+
:pause_between_deploys, to: :cached_deploy_spec
|
292
317
|
|
293
318
|
def monitoring?
|
294
319
|
monitoring.present?
|
@@ -356,6 +381,18 @@ module Shipit
|
|
356
381
|
super
|
357
382
|
end
|
358
383
|
|
384
|
+
def async_update_estimated_deploy_duration
|
385
|
+
UpdateEstimatedDeployDurationJob.perform_later(self)
|
386
|
+
end
|
387
|
+
|
388
|
+
def update_estimated_deploy_duration!
|
389
|
+
update!(estimated_deploy_duration: Stat.p90(recent_deploys_durations) || 1)
|
390
|
+
end
|
391
|
+
|
392
|
+
def recent_deploys_durations
|
393
|
+
tasks.where(type: 'Shipit::Deploy').success.order(id: :desc).limit(100).durations
|
394
|
+
end
|
395
|
+
|
359
396
|
private
|
360
397
|
|
361
398
|
def clear_cache
|
data/app/models/shipit/task.rb
CHANGED
@@ -13,7 +13,7 @@ module Shipit
|
|
13
13
|
belongs_to :until_commit, class_name: 'Commit'
|
14
14
|
belongs_to :since_commit, class_name: 'Commit'
|
15
15
|
|
16
|
-
has_many :chunks, -> { order(:id) }, class_name: 'OutputChunk', dependent: :
|
16
|
+
has_many :chunks, -> { order(:id) }, class_name: 'OutputChunk', dependent: :delete_all
|
17
17
|
|
18
18
|
serialize :definition, TaskDefinition
|
19
19
|
serialize :env, Hash
|
@@ -28,6 +28,12 @@ module Shipit
|
|
28
28
|
after_save :record_status_change
|
29
29
|
after_commit :emit_hooks
|
30
30
|
|
31
|
+
class << self
|
32
|
+
def durations
|
33
|
+
pluck(:started_at, :ended_at).select { |s, e| s && e }.map { |s, e| e - s }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
31
37
|
state_machine :status, initial: :pending do
|
32
38
|
before_transition any => :running do |task|
|
33
39
|
task.started_at ||= Time.now.utc
|
@@ -45,6 +51,10 @@ module Shipit
|
|
45
51
|
task.update!(confirmations: 0)
|
46
52
|
end
|
47
53
|
|
54
|
+
after_transition any => :success do |task|
|
55
|
+
task.async_update_estimated_deploy_duration
|
56
|
+
end
|
57
|
+
|
48
58
|
event :run do
|
49
59
|
transition pending: :running
|
50
60
|
end
|
@@ -101,7 +111,8 @@ module Shipit
|
|
101
111
|
error!
|
102
112
|
end
|
103
113
|
|
104
|
-
delegate :acquire_git_cache_lock, :async_refresh_deployed_revision,
|
114
|
+
delegate :acquire_git_cache_lock, :async_refresh_deployed_revision, :async_update_estimated_deploy_duration,
|
115
|
+
to: :stack
|
105
116
|
|
106
117
|
delegate :checklist, to: :definition
|
107
118
|
|