shipit-engine 0.24.0 → 0.25.0

Sign up to get free protection for your applications and to get access to all the features.
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",