shipit-engine 0.35.1 → 0.37.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -7
  3. data/app/controllers/concerns/shipit/authentication.rb +5 -1
  4. data/app/controllers/shipit/api/base_controller.rb +13 -1
  5. data/app/controllers/shipit/api/rollbacks_controller.rb +1 -1
  6. data/app/controllers/shipit/api/stacks_controller.rb +10 -2
  7. data/app/controllers/shipit/api/tasks_controller.rb +19 -2
  8. data/app/controllers/shipit/rollbacks_controller.rb +5 -1
  9. data/app/helpers/shipit/stacks_helper.rb +11 -0
  10. data/app/models/concerns/shipit/deferred_touch.rb +3 -3
  11. data/app/models/shipit/anonymous_user.rb +4 -0
  12. data/app/models/shipit/api_client.rb +1 -1
  13. data/app/models/shipit/commit_checks.rb +3 -3
  14. data/app/models/shipit/delivery.rb +1 -1
  15. data/app/models/shipit/deploy.rb +1 -0
  16. data/app/models/shipit/deploy_spec/file_system.rb +32 -4
  17. data/app/models/shipit/pull_request.rb +1 -1
  18. data/app/models/shipit/stack.rb +10 -10
  19. data/app/models/shipit/task.rb +31 -4
  20. data/app/models/shipit/user.rb +23 -9
  21. data/app/serializers/shipit/stack_serializer.rb +1 -1
  22. data/app/views/shipit/deploys/_deploy.html.erb +1 -5
  23. data/app/views/shipit/stacks/_banners.html.erb +1 -1
  24. data/app/views/shipit/stacks/_settings_form.erb +55 -0
  25. data/app/views/shipit/stacks/settings.html.erb +1 -55
  26. data/app/views/shipit/stacks/show.html.erb +1 -1
  27. data/config/locales/en.yml +1 -1
  28. data/config/routes.rb +4 -0
  29. data/db/migrate/20211103154121_increase_github_team_slug_size.rb +5 -0
  30. data/lib/shipit/engine.rb +15 -5
  31. data/lib/shipit/stack_commands.rb +9 -2
  32. data/lib/shipit/task_commands.rb +8 -1
  33. data/lib/shipit/version.rb +1 -1
  34. data/lib/shipit.rb +55 -3
  35. data/lib/snippets/fetch-gem-version +1 -1
  36. data/test/controllers/api/hooks_controller_test.rb +1 -1
  37. data/test/controllers/api/rollback_controller_test.rb +1 -0
  38. data/test/controllers/api/stacks_controller_test.rb +34 -0
  39. data/test/controllers/api/tasks_controller_test.rb +56 -0
  40. data/test/controllers/stacks_controller_test.rb +11 -0
  41. data/test/dummy/config/application.rb +1 -2
  42. data/test/dummy/db/schema.rb +2 -2
  43. data/test/fixtures/shipit/check_runs.yml +3 -3
  44. data/test/fixtures/shipit/commits.yml +101 -101
  45. data/test/fixtures/shipit/deliveries.yml +1 -1
  46. data/test/fixtures/shipit/merge_requests.yml +19 -19
  47. data/test/fixtures/shipit/stacks.yml +28 -28
  48. data/test/fixtures/shipit/statuses.yml +16 -16
  49. data/test/fixtures/shipit/tasks.yml +77 -65
  50. data/test/fixtures/shipit/users.yml +2 -5
  51. data/test/models/commits_test.rb +6 -6
  52. data/test/models/deploy_spec_test.rb +0 -23
  53. data/test/models/deploys_test.rb +26 -0
  54. data/test/models/shipit/deploy_spec/file_system_test.rb +81 -0
  55. data/test/models/tasks_test.rb +14 -2
  56. data/test/models/team_test.rb +21 -2
  57. data/test/models/users_test.rb +29 -9
  58. data/test/unit/deploy_commands_test.rb +6 -2
  59. metadata +189 -185
@@ -6,61 +6,7 @@
6
6
  <h2>Settings (Stack #<%= @stack.id %>)</h2>
7
7
  </header>
8
8
 
9
- <div class="setting-section">
10
- <%= form_with scope: :stack, url: stack_path(@stack), method: :patch do |f| %>
11
- <div class="field-wrapper">
12
- <%= f.label :environment %>
13
- <%= f.text_field :environment, placeholder: 'production' %>
14
- </div>
15
-
16
- <div class="field-wrapper">
17
- <span>Branch: <%= @stack.branch %></span>
18
- </div>
19
-
20
- <div class="field-wrapper">
21
- <%= f.label :deploy_url, 'Deploy URL (Where is this stack deployed to?)' %>
22
- <%= f.text_field :deploy_url, placeholder: 'https://' %>
23
- </div>
24
-
25
- <div class="field-wrapper">
26
- <%= f.check_box :continuous_deployment %>
27
- <%= f.label :continuous_deployment, 'Enable continuous deployment' %>
28
- </div>
29
-
30
- <div class="field-wrapper">
31
- <%= f.check_box :merge_queue_enabled %>
32
- <%= f.label :merge_queue_enabled, 'Enable merge queue' %>
33
- </div>
34
-
35
- <div class="field-wrapper">
36
- <%= f.check_box :ignore_ci %>
37
- <%= f.label :ignore_ci, "Don't require CI to deploy" %>
38
- </div>
39
-
40
- <%= f.submit class: "btn", value: "Save" %>
41
- <% end %>
42
- </div>
43
-
44
- <div class="setting-section">
45
- <h5>Lock deploys</h5>
46
- <%= form_with scope: :stack, url: stack_path(@stack), method: :patch do |f| %>
47
- <div class="field-wrapper">
48
- <%= f.label :lock_reason, 'Reason for lock' %>
49
- <%= f.text_area :lock_reason %>
50
- </div>
51
- <% if @stack.locked? %>
52
- <%= f.submit class: "btn", value: "Update Reason" %>
53
- <% else %>
54
- <%= f.submit class: "btn", value: "Lock" %>
55
- <% end %>
56
- <% end %>
57
- <% if @stack.locked? %>
58
- <%= form_with scope: :stack, url: stack_path(@stack), method: :patch do |f| %>
59
- <%= f.hidden_field :lock_reason, value: nil %>
60
- <%= f.submit class: "btn btn--primary", value: "Unlock" %>
61
- <%- end -%>
62
- <% end %>
63
- </div>
9
+ <%= render partial: 'shipit/stacks/settings_form', locals: { stack: @stack } %>
64
10
 
65
11
  <div class="setting-section">
66
12
  <h5>Resynchronize this stack</h5>
@@ -30,7 +30,7 @@
30
30
  </ul>
31
31
  </section>
32
32
 
33
- <% cache @stack do %>
33
+ <% cache [@stack, params[:force]] do %>
34
34
  <section>
35
35
  <header class="section-header">
36
36
  <h2>Previous Deploys</h2>
@@ -41,7 +41,7 @@ en:
41
41
  reject: Mark the release as faulty
42
42
  deploy_button:
43
43
  hint:
44
- max_commits: It is recommended not to deploy more than %{maximum} commits at once.
44
+ max_commits: Use caution when deploying more than %{maximum} commits at once.
45
45
  blocked: This commit range includes a commit that can't be deployed.
46
46
  caption:
47
47
  pending: Pending CI
data/config/routes.rb CHANGED
@@ -20,6 +20,7 @@ Shipit::Engine.routes.draw do
20
20
  get '/' => 'stacks#show'
21
21
  delete '/' => 'stacks#destroy'
22
22
  patch '/' => 'stacks#update'
23
+ post '/refresh' => 'stacks#refresh'
23
24
  end
24
25
 
25
26
  scope '/stacks/*stack_id', stack_id: stack_id_format, as: :stack do
@@ -27,6 +28,9 @@ Shipit::Engine.routes.draw do
27
28
  resource :lock, only: %i(create update destroy)
28
29
  resources :tasks, only: %i(index show) do
29
30
  resource :output, only: :show
31
+ member do
32
+ put :abort
33
+ end
30
34
  end
31
35
  resources :deploys, only: %i(index create) do
32
36
  resources :release_statuses, only: %i(create)
@@ -0,0 +1,5 @@
1
+ class IncreaseGithubTeamSlugSize < ActiveRecord::Migration[6.1]
2
+ def change
3
+ change_column :teams, :slug, :string, limit: 255, null: true
4
+ end
5
+ end
data/lib/shipit/engine.rb CHANGED
@@ -5,6 +5,17 @@ module Shipit
5
5
 
6
6
  paths['app/models'] << 'app/serializers' << 'app/serializers/concerns'
7
7
 
8
+ initializer 'shipit.encryption_config', before: 'active_record_encryption.configuration' do |app|
9
+ if app.credentials.active_record_encryption.blank? && Shipit.user_access_tokens_key.present?
10
+ # For ease of upgrade, we derive an Active Record encryption config automatically.
11
+ # But if AR Encryption is already configured, we just use that
12
+ app.credentials[:active_record_encryption] = {
13
+ primary_key: Shipit.user_access_tokens_key,
14
+ key_derivation_salt: Digest::SHA256.digest("salt:".b + Shipit.user_access_tokens_key),
15
+ }
16
+ end
17
+ end
18
+
8
19
  initializer 'shipit.config' do |app|
9
20
  Rails.application.routes.default_url_options[:host] = Shipit.host
10
21
  Shipit::Engine.routes.default_url_options[:host] = Shipit.host
@@ -28,8 +39,6 @@ module Shipit
28
39
  path.end_with?('.svg') || (path.start_with?('emoji/') && path.end_with?('.png'))
29
40
  end
30
41
 
31
- ActionDispatch::ExceptionWrapper.rescue_responses[Shipit::TaskDefinition::NotFound.name] = :not_found
32
-
33
42
  ActiveModel::Serializer._root = false
34
43
  ActiveModel::ArraySerializer._root = false
35
44
  ActiveModel::Serializer.include(Engine.routes.url_helpers)
@@ -44,10 +53,11 @@ module Shipit
44
53
  if Shipit.enable_samesite_middleware?
45
54
  app.config.middleware.insert_after(::Rack::Runtime, Shipit::SameSiteCookieMiddleware)
46
55
  end
56
+ end
47
57
 
48
- app.config.after_initialize do
49
- ActionController::Base.include(Shipit::ActiveModelSerializersPatch)
50
- end
58
+ config.after_initialize do
59
+ ActionDispatch::ExceptionWrapper.rescue_responses[Shipit::TaskDefinition::NotFound.name] = :not_found
60
+ ActionController::Base.include(Shipit::ActiveModelSerializersPatch)
51
61
  end
52
62
  end
53
63
  end
@@ -16,7 +16,7 @@ module Shipit
16
16
  def fetch
17
17
  create_directories
18
18
  if valid_git_repository?(@stack.git_path)
19
- git('fetch', 'origin', '--tags', @stack.branch, env: env, chdir: @stack.git_path)
19
+ git('fetch', 'origin', '--quiet', '--tags', @stack.branch, env: env, chdir: @stack.git_path)
20
20
  else
21
21
  @stack.clear_git_cache!
22
22
  git_clone(@stack.repo_git_url, @stack.git_path, branch: @stack.branch, env: env, chdir: @stack.deploys_path)
@@ -72,7 +72,14 @@ module Shipit
72
72
  ).run!
73
73
 
74
74
  git_dir = File.join(dir, @stack.repo_name)
75
- git('-c', 'advice.detachedHead=false', 'checkout', commit.sha, chdir: git_dir).run! if commit
75
+ git(
76
+ '-c',
77
+ 'advice.detachedHead=false',
78
+ 'checkout',
79
+ '--quiet',
80
+ commit.sha,
81
+ chdir: git_dir
82
+ ).run! if commit
76
83
  yield Pathname.new(git_dir)
77
84
  end
78
85
  end
@@ -47,7 +47,14 @@ module Shipit
47
47
  end
48
48
 
49
49
  def checkout(commit)
50
- git('-c', 'advice.detachedHead=false', 'checkout', commit.sha, chdir: @task.working_directory)
50
+ git(
51
+ '-c',
52
+ 'advice.detachedHead=false',
53
+ 'checkout',
54
+ '--quiet',
55
+ commit.sha,
56
+ chdir: @task.working_directory
57
+ )
51
58
  end
52
59
 
53
60
  def clone
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Shipit
3
- VERSION = '0.35.1'
3
+ VERSION = '0.37.0'
4
4
  end
data/lib/shipit.rb CHANGED
@@ -5,7 +5,7 @@ require 'state_machines-activerecord'
5
5
  require 'validate_url'
6
6
  require 'responders'
7
7
  require 'explicit-parameters'
8
- require 'attr_encrypted'
8
+ require 'paquito'
9
9
 
10
10
  require 'sass-rails'
11
11
  require 'coffee-rails'
@@ -64,7 +64,8 @@ module Shipit
64
64
 
65
65
  delegate :table_name_prefix, to: :secrets
66
66
 
67
- attr_accessor :disable_api_authentication, :timeout_exit_codes, :deployment_checks
67
+ attr_accessor :disable_api_authentication, :timeout_exit_codes, :deployment_checks, :respect_bare_shipit_file,
68
+ :database_serializer
68
69
  attr_writer(
69
70
  :internal_hook_receivers,
70
71
  :preferred_org_emails,
@@ -77,6 +78,9 @@ module Shipit
77
78
  end
78
79
 
79
80
  self.timeout_exit_codes = [].freeze
81
+ self.respect_bare_shipit_file = true
82
+
83
+ alias_method :respect_bare_shipit_file?, :respect_bare_shipit_file
80
84
 
81
85
  def authentication_disabled?
82
86
  ENV['SHIPIT_DISABLE_AUTH'].present?
@@ -104,6 +108,50 @@ module Shipit
104
108
  )
105
109
  end
106
110
 
111
+ module SafeJSON
112
+ class << self
113
+ def load(serial)
114
+ return nil if serial.nil?
115
+ # JSON.load is unsafe, we should use parse instead
116
+ JSON.parse(serial)
117
+ end
118
+
119
+ def dump(object)
120
+ JSON.dump(object)
121
+ end
122
+ end
123
+ end
124
+
125
+ module TransitionalSerializer
126
+ SafeYAML = Paquito::SafeYAML.new(deprecated_classes: ["ActiveSupport::HashWithIndifferentAccess"])
127
+
128
+ class << self
129
+ def load(serial)
130
+ return if serial.nil?
131
+
132
+ JSON.parse(serial)
133
+ rescue JSON::ParserError
134
+ SafeYAML.load(serial)
135
+ end
136
+
137
+ def dump(object)
138
+ return if object.nil?
139
+ JSON.dump(object)
140
+ end
141
+ end
142
+ end
143
+
144
+ self.database_serializer = TransitionalSerializer
145
+
146
+ def serialized_column(attribute_name, type: nil, coder: nil)
147
+ column = Paquito::SerializedColumn.new(database_serializer, type, attribute_name: attribute_name)
148
+ if coder
149
+ Paquito.chain(coder, column)
150
+ else
151
+ column
152
+ end
153
+ end
154
+
107
155
  def github(organization: github_default_organization)
108
156
  # Backward compatibility
109
157
  # nil signifies the single github app config schema is being used
@@ -153,7 +201,11 @@ module Shipit
153
201
  end
154
202
 
155
203
  def user_access_tokens_key
156
- (secrets.user_access_tokens_key.presence || secrets.secret_key_base).byteslice(0, 32)
204
+ if secrets.user_access_tokens_key.present?
205
+ secrets.user_access_tokens_key
206
+ elsif secrets.secret_key_base
207
+ Digest::SHA256.digest("user_access_tokens_key" + secrets.secret_key_base)
208
+ end
157
209
  end
158
210
 
159
211
  def host
@@ -10,7 +10,7 @@ github_repository = ARGV[1]
10
10
  def get_json(url)
11
11
  uri = URI.parse(url)
12
12
  response = Net::HTTP.get_response(uri)
13
- versions = JSON.load(response.body)
13
+ versions = JSON.parse(response.body)
14
14
  end
15
15
 
16
16
  versions = get_json("https://rubygems.org/api/v1/versions/#{gem_name}.json")
@@ -61,7 +61,7 @@ module Shipit
61
61
  post :create, params: {
62
62
  delivery_url: 'https://example.com/hook',
63
63
  events: %w(deploy rollback),
64
- created_at: 2.months.ago.to_s(:db),
64
+ created_at: 2.months.ago.to_formatted_s(:db),
65
65
  }
66
66
  Hook.last.created_at > 2.seconds.ago
67
67
  end
@@ -107,6 +107,7 @@ module Shipit
107
107
  assert_response :accepted
108
108
  refute_predicate last_deploy, :active?
109
109
  assert_json 'rollback_once_aborted_to.revision.sha', @commit.sha
110
+ assert last_deploy.rollback_once_aborted?
110
111
  end
111
112
  end
112
113
  end
@@ -96,6 +96,24 @@ module Shipit
96
96
  assert_equal 'test', @stack.branch
97
97
  end
98
98
 
99
+ test "#update updates the stack when nil deploy_url" do
100
+ @stack.update(deploy_url: nil)
101
+ @stack.update(continuous_deployment: true)
102
+ assert_nil @stack.deploy_url
103
+ assert @stack.continuous_deployment
104
+
105
+ patch :update, params: {
106
+ id: @stack.to_param,
107
+ continuous_deployment: false,
108
+ }
109
+
110
+ assert_response :ok
111
+ @stack.reload
112
+
113
+ assert_nil @stack.deploy_url
114
+ refute @stack.continuous_deployment
115
+ end
116
+
99
117
  test "#index returns a list of stacks" do
100
118
  stack = Stack.last
101
119
  get :index
@@ -189,6 +207,22 @@ module Shipit
189
207
  assert_response :forbidden
190
208
  assert_json 'message', 'This operation requires the `write:stack` permission'
191
209
  end
210
+
211
+ test "#refresh queues a GithubSyncJob" do
212
+ assert_enqueued_with(job: GithubSyncJob, args: [stack_id: @stack.id]) do
213
+ post :refresh, params: { id: @stack.to_param }
214
+ end
215
+ assert_response :accepted
216
+ end
217
+
218
+ test "#refresh queues a RefreshStatusesJob and RefreshCheckRunsJob" do
219
+ assert_enqueued_with(job: RefreshStatusesJob, args: [stack_id: @stack.id]) do
220
+ assert_enqueued_with(job: RefreshCheckRunsJob, args: [stack_id: @stack.id]) do
221
+ post :refresh, params: { id: @stack.to_param }
222
+ end
223
+ end
224
+ assert_response :accepted
225
+ end
192
226
  end
193
227
  end
194
228
  end
@@ -6,6 +6,7 @@ module Shipit
6
6
  class TasksControllerTest < ActionController::TestCase
7
7
  setup do
8
8
  @stack = shipit_stacks(:shipit)
9
+ @user = shipit_users(:walrus)
9
10
  authenticate!
10
11
  end
11
12
 
@@ -90,6 +91,61 @@ module Shipit
90
91
  assert_response :conflict
91
92
  assert_json 'message', 'A task is already running.'
92
93
  end
94
+
95
+ test "#trigger fails when user does not have deploy permission" do
96
+ @client.permissions.delete('deploy:stack')
97
+ @client.save!
98
+
99
+ assert_no_difference 'Task.count' do
100
+ post :trigger, params: { stack_id: @stack.to_param, task_name: 'restart' }
101
+ end
102
+
103
+ assert_response :forbidden
104
+ assert_json 'message', 'This operation requires the `deploy:stack` permission'
105
+ end
106
+
107
+ test "#abort aborts the task" do
108
+ task = shipit_deploys(:shipit_running)
109
+ task.ping
110
+
111
+ put :abort, params: { stack_id: @stack.to_param, id: task.id }
112
+
113
+ assert_response :accepted
114
+ assert_equal 'aborting', task.reload.status
115
+ end
116
+
117
+ test "#abort sets `aborted_by` to the current user" do
118
+ task = shipit_deploys(:shipit_running)
119
+ task.ping
120
+ request.headers['X-Shipit-User'] = @user.login
121
+
122
+ put :abort, params: { stack_id: @stack.to_param, id: task.id }
123
+
124
+ assert_equal task.reload.aborted_by, @user
125
+ end
126
+
127
+ test "#abort responds with method_not_allowed if the task is not currently running" do
128
+ task = shipit_deploys(:shipit_aborted)
129
+ task.ping
130
+ put :abort, params: { stack_id: @stack.to_param, id: task.id }
131
+
132
+ assert_response :method_not_allowed
133
+ assert_json 'message', 'This task is not currently running.'
134
+ end
135
+
136
+ test "#abort fails when user does not have deploy permission" do
137
+ @client.permissions.delete('deploy:stack')
138
+ @client.save!
139
+ task = shipit_deploys(:shipit_running)
140
+ task.ping
141
+
142
+ assert_no_difference 'Task.count' do
143
+ put :abort, params: { stack_id: @stack.to_param, id: task.id }
144
+ end
145
+
146
+ assert_response :forbidden
147
+ assert_json 'message', 'This operation requires the `deploy:stack` permission'
148
+ end
93
149
  end
94
150
  end
95
151
  end
@@ -36,6 +36,17 @@ module Shipit
36
36
  assert_redirected_to '/github/auth/github?origin=http%3A%2F%2Ftest.host%2F'
37
37
  end
38
38
 
39
+ test "users which require a fresh login are redirected" do
40
+ user = shipit_users(:walrus)
41
+ user.update!(github_access_token: 'some_legacy_value')
42
+ assert_predicate user, :requires_fresh_login?
43
+
44
+ get :index
45
+
46
+ assert_redirected_to '/github/auth/github?origin=http%3A%2F%2Ftest.host%2F'
47
+ assert_nil session[:user_id]
48
+ end
49
+
39
50
  test "current_user must be a member of at least a Shipit.github_teams" do
40
51
  session[:user_id] = shipit_users(:bob).id
41
52
  Shipit.stubs(:github_teams).returns([shipit_teams(:cyclimse_cooks), shipit_teams(:shopify_developers)])
@@ -17,7 +17,6 @@ end
17
17
 
18
18
  module Shipit
19
19
  class Application < Rails::Application
20
- config.load_defaults 6.1
20
+ config.load_defaults 7.0
21
21
  end
22
22
  end
23
-
@@ -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: 2021_08_23_075617) do
13
+ ActiveRecord::Schema.define(version: 2021_11_03_154121) do
14
14
 
15
15
  create_table "api_clients", force: :cascade do |t|
16
16
  t.text "permissions", limit: 65535
@@ -315,7 +315,7 @@ ActiveRecord::Schema.define(version: 2021_08_23_075617) do
315
315
  create_table "teams", force: :cascade do |t|
316
316
  t.integer "github_id", limit: 4
317
317
  t.string "api_url", limit: 255
318
- t.string "slug", limit: 50
318
+ t.string "slug", limit: 255
319
319
  t.string "name", limit: 255
320
320
  t.string "organization", limit: 39
321
321
  t.datetime "created_at", null: false
@@ -7,7 +7,7 @@ second_pending_travis:
7
7
  conclusion: success
8
8
  html_url: "http://www.example.com/run/424242"
9
9
  details_url: "http://www.example.com/build/424242"
10
- created_at: <%= 10.days.ago.to_s(:db) %>
10
+ created_at: <%= 10.days.ago.to_formatted_s(:db) %>
11
11
 
12
12
  check_runs_first_pending_coveralls:
13
13
  stack: check_runs
@@ -15,7 +15,7 @@ check_runs_first_pending_coveralls:
15
15
  github_id: 43
16
16
  title: lets go
17
17
  name: Coverage metrics
18
- created_at: <%= 10.days.ago.to_s(:db) %>
18
+ created_at: <%= 10.days.ago.to_formatted_s(:db) %>
19
19
  conclusion: pending
20
20
  html_url: "http://www.example.com/run/434343"
21
21
  details_url: "http://www.example.com/build/434343"
@@ -26,7 +26,7 @@ check_runs_first_success_coveralls:
26
26
  github_id: 434343
27
27
  title: lets go
28
28
  name: Coverage metrics
29
- created_at: <%= 9.days.ago.to_s(:db) %>
29
+ created_at: <%= 9.days.ago.to_formatted_s(:db) %>
30
30
  conclusion: success
31
31
  html_url: "http://www.example.com/run/434343"
32
32
  details_url: "http://www.example.com/build/434343"