shipit-engine 0.24.0 → 0.25.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/shipit/stacks.js.coffee +15 -1
  3. data/app/assets/stylesheets/_base/_icons.scss +18 -0
  4. data/app/assets/stylesheets/_base/_status-items.scss +28 -0
  5. data/app/assets/stylesheets/_pages/_commits.scss +1 -5
  6. data/app/assets/stylesheets/_pages/_deploy.scss +60 -3
  7. data/app/controllers/concerns/shipit/authentication.rb +1 -1
  8. data/app/controllers/shipit/merge_status_controller.rb +2 -0
  9. data/app/controllers/shipit/release_statuses_controller.rb +36 -0
  10. data/app/jobs/shipit/append_delayed_release_status_job.rb +17 -0
  11. data/app/jobs/shipit/cache_deploy_spec_job.rb +2 -0
  12. data/app/jobs/shipit/clear_git_cache_job.rb +1 -1
  13. data/app/jobs/shipit/create_release_statuses_job.rb +11 -0
  14. data/app/jobs/shipit/deferred_touch_job.rb +2 -0
  15. data/app/jobs/shipit/deliver_hook_job.rb +1 -0
  16. data/app/jobs/shipit/merge_pull_requests_job.rb +2 -0
  17. data/app/jobs/shipit/perform_commit_checks_job.rb +2 -0
  18. data/app/jobs/shipit/perform_task_job.rb +2 -4
  19. data/app/jobs/shipit/refresh_pull_request_job.rb +2 -0
  20. data/app/models/shipit/anonymous_user.rb +1 -1
  21. data/app/models/shipit/commit.rb +36 -3
  22. data/app/models/shipit/deploy.rb +43 -0
  23. data/app/models/shipit/deploy_spec.rb +16 -2
  24. data/app/models/shipit/deploy_spec/bundler_discovery.rb +5 -1
  25. data/app/models/shipit/deploy_spec/file_system.rb +4 -0
  26. data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +1 -1
  27. data/app/models/shipit/ephemeral_commit_checks.rb +2 -4
  28. data/app/models/shipit/hook.rb +36 -3
  29. data/app/models/shipit/pull_request.rb +4 -2
  30. data/app/models/shipit/release_status.rb +41 -0
  31. data/app/models/shipit/rollback.rb +9 -0
  32. data/app/models/shipit/stack.rb +4 -9
  33. data/app/models/shipit/status/common.rb +4 -0
  34. data/app/models/shipit/status/group.rb +2 -1
  35. data/app/models/shipit/status/missing.rb +4 -0
  36. data/app/models/shipit/status/unknown.rb +15 -0
  37. data/app/models/shipit/task.rb +4 -0
  38. data/app/models/shipit/user.rb +16 -3
  39. data/app/serializers/shipit/stack_serializer.rb +1 -1
  40. data/app/views/shipit/deploys/_deploy.html.erb +18 -2
  41. data/config/locales/en.yml +3 -0
  42. data/config/routes.rb +2 -0
  43. data/db/migrate/20180802172632_allow_commit_without_author.rb +6 -0
  44. data/db/migrate/20180906083930_create_release_statuses.rb +21 -0
  45. data/lib/shipit.rb +5 -0
  46. data/lib/shipit/command.rb +14 -18
  47. data/lib/shipit/deploy_commands.rb +0 -4
  48. data/lib/shipit/engine.rb +1 -1
  49. data/lib/shipit/first_parent_commits_iterator.rb +1 -1
  50. data/lib/shipit/flock.rb +43 -0
  51. data/lib/shipit/github_app.rb +5 -3
  52. data/lib/shipit/rollback_commands.rb +6 -0
  53. data/lib/shipit/task_commands.rb +1 -5
  54. data/lib/shipit/version.rb +1 -1
  55. data/test/controllers/release_statuses_controller_test.rb +23 -0
  56. data/test/dummy/db/schema.rb +18 -3
  57. data/test/dummy/db/seeds.rb +4 -0
  58. data/test/fixtures/shipit/commits.yml +13 -0
  59. data/test/fixtures/shipit/release_statuses.yml +16 -0
  60. data/test/fixtures/shipit/stacks.yml +4 -0
  61. data/test/jobs/append_delayed_release_status_job_test.rb +25 -0
  62. data/test/jobs/cache_deploy_spec_job_test.rb +1 -2
  63. data/test/jobs/emit_event_job_test.rb +1 -1
  64. data/test/jobs/github_sync_job_test.rb +1 -0
  65. data/test/models/commits_test.rb +54 -1
  66. data/test/models/deploy_spec_test.rb +83 -11
  67. data/test/models/deploys_test.rb +52 -0
  68. data/test/models/hook_test.rb +1 -28
  69. data/test/models/pull_request_test.rb +19 -0
  70. data/test/models/release_statuses_test.rb +28 -0
  71. data/test/models/rollbacks_test.rb +2 -0
  72. data/test/models/stacks_test.rb +1 -1
  73. data/test/test_helper.rb +5 -0
  74. data/test/unit/rollback_commands_test.rb +35 -0
  75. metadata +121 -104
@@ -5,7 +5,7 @@ module Shipit
5
5
  has_one :lock_author
6
6
  attributes :id, :repo_owner, :repo_name, :environment, :html_url, :url, :tasks_url, :deploy_url, :pull_requests_url,
7
7
  :deploy_spec, :undeployed_commits_count, :is_locked, :lock_reason, :continuous_deployment, :created_at,
8
- :updated_at, :locked_since, :last_deployed_at, :branch
8
+ :updated_at, :locked_since, :last_deployed_at, :branch, :merge_queue_enabled
9
9
 
10
10
  def url
11
11
  api_stack_url(object)
@@ -1,6 +1,6 @@
1
1
  <%- read_only ||= false -%>
2
2
 
3
- <li class="task deploy" id="task-<%= deploy.id %>" data-status="<%= deploy.status %>">
3
+ <li class="task deploy" id="task-<%= deploy.id %>" data-status="<%= deploy.status %>" data-release-status="<%= deploy.last_release_status.state %>">
4
4
  <% cache deploy do %>
5
5
  <% cache deploy.author do %>
6
6
  <%= render 'shipit/shared/author', author: deploy.author %>
@@ -16,6 +16,11 @@
16
16
  </a>
17
17
  </span>
18
18
  <p class="commit-meta">
19
+ <% if @stack.release_status? %>
20
+ <%= link_to '#', data: {tooltip: deploy.last_release_status.description.presence} do %>
21
+ <i class="deploy-status__icon"></i>
22
+ <% end %>
23
+ <% end %>
19
24
  <%= deploy.rollback? ? 'rolled back' : 'deployed' %>
20
25
  <span class="sha"><%= link_to_github_deploy(deploy) %></span>
21
26
  <span class="code-additions">+<%= deploy.additions %></span>
@@ -34,10 +39,21 @@
34
39
  <% end %>
35
40
  </p>
36
41
  </div>
42
+
43
+ <% if @stack.release_status? %>
44
+ <div class="release-validation" >
45
+ <%= link_to stack_deploy_release_statuses_path(@stack, deploy), class: 'action-set-release-status action-reject-release', data: {tooltip: t('release.reject'), status: 'failure'} do %>
46
+ <i class="icon icon--reject"></i>
47
+ <% end %>
48
+ <%= link_to stack_deploy_release_statuses_path(@stack, deploy), class: 'action-set-release-status action-validate-release', data: {tooltip: t('release.validate'), status: 'success'} do %>
49
+ <i class="icon icon--validate"></i>
50
+ <% end %>
51
+ </div>
52
+ <% end %>
37
53
  <% end %>
38
54
 
39
55
  <% unless read_only %>
40
- <div class="commit-actions">
56
+ <div class="deploy-actions">
41
57
  <% if deploy.rollbackable? %>
42
58
  <% if deploy.stack.active_task? %>
43
59
  <%= link_to 'Deploy in progress...', '#', class: 'btn disabled deploy-action' %>
@@ -24,6 +24,9 @@ en:
24
24
  lock: This commit is safe to deploy. Click to mark it as unsafe.
25
25
  unlock: This commit is unsafe to deploy. Click to mark it as safe.
26
26
  confirm_unlock: Mark this commit as safe to deploy?
27
+ release:
28
+ validate: Mark the release as healthy
29
+ reject: Mark the release as faulty
27
30
  deploy_button:
28
31
  hint:
29
32
  max_commits: It is recommended not to deploy more than %{maximum} commits at once.
data/config/routes.rb CHANGED
@@ -88,6 +88,8 @@ Shipit::Engine.routes.draw do
88
88
  get :rollback
89
89
  get :revert
90
90
  end
91
+
92
+ resources :release_statuses, only: %i(create)
91
93
  end
92
94
 
93
95
  resources :pull_requests, only: %i(index destroy create)
@@ -0,0 +1,6 @@
1
+ class AllowCommitWithoutAuthor < ActiveRecord::Migration[5.1]
2
+ def change
3
+ change_column_null(:commits, :author_id, true)
4
+ change_column_null(:commits, :committer_id, true)
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ class CreateReleaseStatuses < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :release_statuses do |t|
4
+ t.references :stack, foreign_key: false, null: false, index: false
5
+ t.references :commit, foreign_key: false, null: false, index: false
6
+ t.references :user, foreign_key: false, null: true
7
+
8
+ t.string :state, limit: 10, null: false
9
+ t.string :description, limit: 1024, null: true
10
+ t.string :target_url, limit: 1024, null: true
11
+
12
+ t.bigint :github_id, null: true
13
+
14
+ t.timestamps
15
+
16
+ t.index %i(commit_id github_id)
17
+ t.index %i(stack_id commit_id)
18
+ end
19
+ end
20
+ end
21
+
data/lib/shipit.rb CHANGED
@@ -30,6 +30,7 @@ require 'faraday-http-cache'
30
30
 
31
31
  require 'shipit/version'
32
32
 
33
+ require 'shipit/flock'
33
34
  require 'shipit/github_app'
34
35
  require 'shipit/paginator'
35
36
  require 'shipit/null_serializer'
@@ -59,6 +60,10 @@ module Shipit
59
60
 
60
61
  self.timeout_exit_codes = [].freeze
61
62
 
63
+ def authentication_disabled?
64
+ ENV['SHIPIT_DISABLE_AUTH'].present?
65
+ end
66
+
62
67
  def app_name
63
68
  @app_name ||= secrets.app_name || Rails.application.class.name.split(':').first || 'Shipit'
64
69
  end
@@ -12,6 +12,8 @@ module Shipit
12
12
  Denied = Class.new(Error)
13
13
  TimedOut = Class.new(Error)
14
14
 
15
+ BASE_ENV = Bundler.clean_env.merge((ENV.keys - Bundler.clean_env.keys).map { |k| [k, nil] }.to_h)
16
+
15
17
  class Failed < Error
16
18
  attr_reader :exit_code
17
19
 
@@ -75,14 +77,6 @@ module Shipit
75
77
  output.join
76
78
  end
77
79
 
78
- def with_full_path
79
- old_path = ENV['PATH']
80
- ENV['PATH'] = "#{ENV['PATH']}:#{Shipit.shell_paths.join(':')}"
81
- yield
82
- ensure
83
- ENV['PATH'] = old_path
84
- end
85
-
86
80
  def interpolated_arguments
87
81
  interpolate_environment_variables(@args)
88
82
  end
@@ -90,22 +84,24 @@ module Shipit
90
84
  def start(&block)
91
85
  return if @started
92
86
  @control_block = block
93
- child_in = @out = @pid = nil
87
+ @out = @pid = nil
94
88
  FileUtils.mkdir_p(@chdir)
95
- with_full_path do
96
- begin
97
- @out, child_in, @pid = PTY.spawn(@env.stringify_keys, *interpolated_arguments, chdir: @chdir)
98
- child_in.close
99
- rescue Errno::ENOENT
100
- raise NotFound, "#{Shellwords.split(interpolated_arguments.first).first}: command not found"
101
- rescue Errno::EACCES
102
- raise Denied, "#{Shellwords.split(interpolated_arguments.first).first}: Permission denied"
103
- end
89
+ begin
90
+ @out, child_in, @pid = PTY.spawn(clean_env, *interpolated_arguments, chdir: @chdir)
91
+ child_in.close
92
+ rescue Errno::ENOENT
93
+ raise NotFound, "#{Shellwords.split(interpolated_arguments.first).first}: command not found"
94
+ rescue Errno::EACCES
95
+ raise Denied, "#{Shellwords.split(interpolated_arguments.first).first}: Permission denied"
104
96
  end
105
97
  @started = true
106
98
  self
107
99
  end
108
100
 
101
+ def clean_env
102
+ BASE_ENV.merge('PATH' => "#{ENV['PATH']}:#{Shipit.shell_paths.join(':')}").merge(@env.stringify_keys)
103
+ end
104
+
109
105
  def stream(&block)
110
106
  start
111
107
  begin
@@ -18,9 +18,5 @@ module Shipit
18
18
  def diff_url
19
19
  Shipit::GithubUrlHelper.github_commit_range_url(@stack, *@task.commit_range)
20
20
  end
21
-
22
- def permalink
23
- Shipit::Engine.routes.url_helpers.stack_deploy_url(@stack, @task)
24
- end
25
21
  end
26
22
  end
data/lib/shipit/engine.rb CHANGED
@@ -22,7 +22,7 @@ module Shipit
22
22
  path =~ %r{\Aplugins/[\-\w]+\.(js|css)\Z}
23
23
  end
24
24
  app.config.assets.precompile << proc do |path|
25
- path.start_with?('emoji/') && path.end_with?('.png')
25
+ path.end_with?('.svg') || (path.start_with?('emoji/') && path.end_with?('.png'))
26
26
  end
27
27
 
28
28
  ActionDispatch::ExceptionWrapper.rescue_responses[Shipit::TaskDefinition::NotFound.name] = :not_found
@@ -8,7 +8,7 @@ module Shipit
8
8
  next
9
9
  end
10
10
 
11
- if last_ancestor.parents.first.sha == commit.sha
11
+ if last_ancestor.parents.empty? || last_ancestor.parents.first.sha == commit.sha
12
12
  yield last_ancestor = commit
13
13
  end
14
14
  end
@@ -0,0 +1,43 @@
1
+ require 'English'
2
+ require 'timeout'
3
+ require 'pathname'
4
+
5
+ module Shipit
6
+ class Flock
7
+ TimeoutError = Class.new(::Timeout::Error)
8
+
9
+ attr_reader :path
10
+
11
+ def initialize(path)
12
+ @path = Pathname.new(path)
13
+ end
14
+
15
+ def lock(timeout:)
16
+ path.parent.mkpath
17
+ path.open('w') do |file|
18
+ if retrying(timeout: timeout) { file.flock(File::LOCK_EX | File::LOCK_NB) }
19
+ file.write($PROCESS_ID.to_s)
20
+ return yield
21
+ else
22
+ raise TimeoutError, "Couldn't acquire lock for #{path} in #{timeout} seconds"
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def retrying(timeout:, breathing_time: 0.01)
30
+ started_at = Time.now.to_f
31
+
32
+ loop do
33
+ if yield
34
+ return true
35
+ elsif Time.now.to_f - started_at < timeout
36
+ sleep(breathing_time)
37
+ else
38
+ return false
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -22,8 +22,11 @@ module Shipit
22
22
  end
23
23
 
24
24
  def api
25
- @client = new_client(access_token: token) if !defined?(@client) || @client.access_token != token
26
- @client
25
+ client = Thread.current[:github_client]
26
+ if !client || client.access_token != token
27
+ client = Thread.current[:github_client] = new_client(access_token: token)
28
+ end
29
+ client
27
30
  end
28
31
 
29
32
  def verify_webhook_signature(signature, message)
@@ -66,7 +69,6 @@ module Shipit
66
69
  [
67
70
  oauth_id,
68
71
  oauth_secret,
69
- scope: 'email,repo_deployment',
70
72
  client_options: options,
71
73
  ]
72
74
  end
@@ -3,5 +3,11 @@ module Shipit
3
3
  def steps
4
4
  deploy_spec.rollback_steps!
5
5
  end
6
+
7
+ def env
8
+ super.merge(
9
+ 'ROLLBACK' => '1',
10
+ )
11
+ end
6
12
  end
7
13
  end
@@ -35,7 +35,7 @@ module Shipit
35
35
  'SHIPIT_USER' => "#{@task.author.login} (#{normalized_name}) via Shipit",
36
36
  'EMAIL' => @task.author.email,
37
37
  'BUNDLE_PATH' => Rails.root.join('data', 'bundler').to_s,
38
- 'SHIPIT_LINK' => permalink,
38
+ 'SHIPIT_LINK' => @task.permalink,
39
39
  'LAST_DEPLOYED_SHA' => @stack.last_deployed_commit.sha,
40
40
  'TASK_ID' => @task.id.to_s,
41
41
  'IGNORED_SAFETIES' => @task.ignored_safeties? ? '1' : '0',
@@ -80,9 +80,5 @@ module Shipit
80
80
  @task.working_directory
81
81
  end
82
82
  end
83
-
84
- def permalink
85
- Shipit::Engine.routes.url_helpers.stack_task_url(@stack, @task)
86
- end
87
83
  end
88
84
  end
@@ -1,3 +1,3 @@
1
1
  module Shipit
2
- VERSION = '0.24.0'.freeze
2
+ VERSION = '0.25.0'.freeze
3
3
  end
@@ -0,0 +1,23 @@
1
+ require 'test_helper'
2
+
3
+ module Shipit
4
+ class ReleaseStatusesControllerTest < ActionController::TestCase
5
+ setup do
6
+ @stack = shipit_stacks(:shipit)
7
+ @deploy = shipit_deploys(:shipit)
8
+ session[:user_id] = shipit_users(:walrus).id
9
+ end
10
+
11
+ test ":create allow users to append release statuses" do
12
+ assert_difference -> { ReleaseStatus.count }, +1 do
13
+ post :create, params: {stack_id: @stack, deploy_id: @deploy.id, status: 'success'}
14
+ assert_response :created
15
+ end
16
+
17
+ status = ReleaseStatus.last
18
+ assert_equal 'success', status.state
19
+ assert_equal '@walrus signaled this release as healthy.', status.description
20
+ assert_equal @deploy.permalink, status.target_url
21
+ end
22
+ end
23
+ end
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 20180417130436) do
13
+ ActiveRecord::Schema.define(version: 20180906083930) do
14
14
 
15
15
  create_table "api_clients", force: :cascade do |t|
16
16
  t.text "permissions", limit: 65535
@@ -45,8 +45,8 @@ ActiveRecord::Schema.define(version: 20180417130436) do
45
45
 
46
46
  create_table "commits", force: :cascade do |t|
47
47
  t.integer "stack_id", limit: 4, null: false
48
- t.integer "author_id", limit: 4, null: false
49
- t.integer "committer_id", limit: 4, null: false
48
+ t.integer "author_id", limit: 4
49
+ t.integer "committer_id", limit: 4
50
50
  t.string "sha", limit: 40, null: false
51
51
  t.text "message", limit: 65535, null: false
52
52
  t.datetime "created_at"
@@ -158,6 +158,21 @@ ActiveRecord::Schema.define(version: 20180417130436) do
158
158
  t.index ["stack_id"], name: "index_pull_requests_on_stack_id"
159
159
  end
160
160
 
161
+ create_table "release_statuses", force: :cascade do |t|
162
+ t.integer "stack_id", null: false
163
+ t.integer "commit_id", null: false
164
+ t.integer "user_id"
165
+ t.string "state", limit: 10, null: false
166
+ t.string "description", limit: 1024
167
+ t.string "target_url", limit: 1024
168
+ t.bigint "github_id"
169
+ t.datetime "created_at", null: false
170
+ t.datetime "updated_at", null: false
171
+ t.index ["commit_id", "github_id"], name: "index_deploy_statuses_on_commit_id_and_github_id"
172
+ t.index ["stack_id", "commit_id"], name: "index_deploy_statuses_on_stack_id_and_commit_id"
173
+ t.index ["user_id"], name: "index_deploy_statuses_on_user_id"
174
+ end
175
+
161
176
  create_table "stacks", force: :cascade do |t|
162
177
  t.string "repo_name", limit: 100, null: false
163
178
  t.string "repo_owner", limit: 39, null: false
@@ -75,6 +75,10 @@ module Shipit
75
75
  "Eat some chips"
76
76
  ]
77
77
  },
78
+ "status": {
79
+ "context": "shipit/pre-production",
80
+ "delay": 60
81
+ },
78
82
  "deploy": {
79
83
  "max_commits": 3,
80
84
  "override": [
@@ -117,6 +117,19 @@ cyclimse_merged:
117
117
  updated_at: <%= 8.days.ago.to_s(:db) %>
118
118
  pull_request_id: 99
119
119
 
120
+ anonymous:
121
+ id: 10
122
+ sha: 216100bc78431fd73cac00592cd016d0d508458e
123
+ message: "whoami"
124
+ stack: shipit
125
+ author:
126
+ committer:
127
+ authored_at: <%= 10.days.ago.to_s(:db) %>
128
+ committed_at: <%= 9.days.ago.to_s(:db) %>
129
+ additions: 42
130
+ deletions: 24
131
+ updated_at: <%= 8.days.ago.to_s(:db) %>
132
+
120
133
  soc_first:
121
134
  id: 101
122
135
  sha: 5e9278037b872fd9a6690523e411ecb3aa181355
@@ -0,0 +1,16 @@
1
+ to_be_created:
2
+ stack: shipit
3
+ commit_id: 4
4
+ user: walrus
5
+ state: pending
6
+ description: Deploy started
7
+ target_url: https://example.com/deploys/42
8
+
9
+ created:
10
+ stack: shipit
11
+ commit_id: 1
12
+ user: walrus
13
+ state: pending
14
+ description: Deploy started
15
+ target_url: https://example.com/deploys/24
16
+ github_id: 234234234
@@ -20,6 +20,10 @@ shipit:
20
20
  "deploy": {"override": null, "interval": 60, "max_commits": 3, "variables": [{"name": "SAFETY_DISABLED", "title": "Set to 1 to do dangerous things", "default": 0}]},
21
21
  "rollback": {"override": ["echo 'Rollback!'"]},
22
22
  "fetch": ["echo '42'"],
23
+ "status": {
24
+ "context": "shipit/production",
25
+ "delay": 60
26
+ },
23
27
  "tasks": {
24
28
  "restart": {
25
29
  "action": "Restart application",