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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7ab32590ea528100fc22999f40a17d9ffcefb72313672eb4858befdb9a6932e1
4
- data.tar.gz: fe6fe3d4b026e0e2d0ecb067639d97027649d26fd785c90b9e198ce0e478c2a9
3
+ metadata.gz: 174fee8bf3ed460fa137cbe666f639fec2218d0617b4860dcede08d38ed6d93d
4
+ data.tar.gz: 739d8f122ead25b8f1053f954686e4ddd574bc17b544de9b69dce5af7635425c
5
5
  SHA512:
6
- metadata.gz: a51d622ab862751d118d2d0995361366d8035481f090ddfcaa09f01b672c326ed1708c4085831a195899500762cd29b6247c01710e08260b5baf263d2363cc33
7
- data.tar.gz: 327aac9271b84975c388c0dbb42c380f99c00d15b1ce6930bba6877066d0333b3c60efcaf8ab56e925d17e90ac1462c48cbccb9ec5da1537c04f45fe6149c49b
6
+ metadata.gz: 7b3118fb7bc39e0c8cddad0444d34c39e935bcc192010dec3b27b2bddd9947a770573a959b13fe26661eb8874963b10cdb8ab2382d6eec811ebd8cd87322cc41
7
+ data.tar.gz: cd173c7dc66f31a90e878ed9a2da9724132286c93f2a9c168a5593f81ffbfe3c6f25161095fafe7df1e9d7c2561339ca0c4051ae856c78d8698d382e8d65818c
data/README.md CHANGED
@@ -134,6 +134,19 @@ Lastly, if you override the `app_name` configuration in your Shipit deployment,
134
134
 
135
135
  * * *
136
136
 
137
+ <h3 id="respecting-bare-files">Respecting bare <code>shipit.yml</code> files</h3>
138
+
139
+ Shipit will, by default, respect the "bare" <code>shipit.yml</code> file as a fallback option if no more specifically-named file exists (such as <code>shipit.staging.yml</code>).
140
+
141
+ You can configure this behavior via the attribute <code>Shipit.respect_bare_shipit_file</code>.
142
+
143
+ - The value <code>false</code> will disable this behavior and instead cause Shipit to emit an error upon deploy if Shipit cannot find a more specifically-named file.
144
+ - Setting this attribute to any other value (**including <code>nil</code>**), or not setting this attribute, will cause Shipit to use the default behavior of respecting bare <code>shipit.yml</code> files.
145
+
146
+ You can determine if Shipit is configured to respect bare files using <code>Shipit.respect_bare_shipit_file?</code>.
147
+
148
+ * * *
149
+
137
150
  <h3 id="installing-dependencies">Installing dependencies</h3>
138
151
 
139
152
  The **<code>dependencies</code>** step allows you to install all the packages your deploy script needs.
@@ -390,7 +403,7 @@ machine:
390
403
 
391
404
  <h3 id="ci">CI</h3>
392
405
 
393
- **<code>ci.require</code>** contains an array of the [statuses context](https://developer.github.com/v3/repos/statuses/) you want Shipit to disallow deploys if any of them is missing on the commit being deployed.
406
+ **<code>ci.require</code>** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want Shipit to disallow deploys if any of them is missing on the commit being deployed.
394
407
 
395
408
  For example:
396
409
  ```yml
@@ -399,7 +412,7 @@ ci:
399
412
  - ci/circleci
400
413
  ```
401
414
 
402
- **<code>ci.hide</code>** contains an array of the [statuses context](https://developer.github.com/v3/repos/statuses/) you want Shipit to ignore.
415
+ **<code>ci.hide</code>** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want Shipit to ignore.
403
416
 
404
417
  For example:
405
418
  ```yml
@@ -408,7 +421,7 @@ ci:
408
421
  - ci/circleci
409
422
  ```
410
423
 
411
- **<code>ci.allow_failures</code>** contains an array of the [statuses context](https://developer.github.com/v3/repos/statuses/) you want to be visible but not to required for deploy.
424
+ **<code>ci.allow_failures</code>** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want to be visible but not to required for deploy.
412
425
 
413
426
  For example:
414
427
  ```yml
@@ -417,7 +430,7 @@ ci:
417
430
  - ci/circleci
418
431
  ```
419
432
 
420
- **<code>ci.blocking</code>** contains an array of the [statuses context](https://developer.github.com/v3/repos/statuses/) you want to disallow deploys if any of them is missing or failing on any of the commits being deployed.
433
+ **<code>ci.blocking</code>** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-statuses) you want to disallow deploys if any of them is missing or failing on any of the commits being deployed.
421
434
 
422
435
  For example:
423
436
  ```yml
@@ -440,7 +453,7 @@ merge:
440
453
  revalidate_after: 12m30s
441
454
  ```
442
455
 
443
- **<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.
456
+ **<code>merge.require</code>** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-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.
444
457
 
445
458
  For example:
446
459
  ```yml
@@ -449,7 +462,7 @@ merge:
449
462
  - continuous-integration/travis-ci/push
450
463
  ```
451
464
 
452
- **<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.
465
+ **<code>merge.ignore</code>** contains an array of the [statuses context](https://docs.github.com/en/rest/reference/commits#commit-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.
453
466
 
454
467
  For example:
455
468
  ```yml
@@ -458,7 +471,7 @@ merge:
458
471
  - codeclimate
459
472
  ```
460
473
 
461
- **<code>merge.method</code>** the [merge method](https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button) to use for this stack. If it's not set the default merge method will be used. Can be either `merge`, `squash` or `rebase`.
474
+ **<code>merge.method</code>** the [merge method](https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request--parameters) to use for this stack. If it's not set the default merge method will be used. Can be either `merge`, `squash` or `rebase`.
462
475
 
463
476
  For example:
464
477
  ```yml
@@ -17,7 +17,11 @@ module Shipit
17
17
  private
18
18
 
19
19
  def force_github_authentication
20
- if Shipit.authentication_disabled? || current_user.logged_in?
20
+ if current_user.logged_in? && current_user.requires_fresh_login?
21
+ Rails.logger.warn("User #{current_user.id} requires a fresh login, logging out...")
22
+ reset_session
23
+ redirect_to(Shipit::Engine.routes.url_helpers.github_authentication_path(origin: request.original_url))
24
+ elsif Shipit.authentication_disabled? || current_user.logged_in?
21
25
  unless current_user.authorized?
22
26
  team_handles = Shipit.github_teams.map(&:handle)
23
27
  team_list = team_handles.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ')
@@ -28,6 +28,18 @@ module Shipit
28
28
 
29
29
  private
30
30
 
31
+ module BasicAuth
32
+ # Workaround for https://github.com/rails/rails/pull/44610
33
+ extend ActionController::HttpAuthentication::Basic
34
+ extend self
35
+
36
+ private
37
+
38
+ def has_basic_credentials?(request)
39
+ request.authorization.present? && (auth_scheme(request).downcase == "basic")
40
+ end
41
+ end
42
+
31
43
  def namespace_for_serializer
32
44
  nil
33
45
  end
@@ -36,7 +48,7 @@ module Shipit
36
48
  @current_api_client = if Shipit.disable_api_authentication
37
49
  UnlimitedApiClient.new
38
50
  else
39
- authenticate_with_http_basic do |*parts|
51
+ BasicAuth.authenticate(request) do |*parts|
40
52
  token = parts.select(&:present?).join('--')
41
53
  ApiClient.authenticate(token)
42
54
  end
@@ -21,7 +21,7 @@ module Shipit
21
21
  param_error!(:force, "Can't rollback, deploy in progress")
22
22
  elsif stack.active_task?
23
23
  active_task = stack.active_task
24
- active_task.abort!(aborted_by: current_user, rollback_once_aborted_to: deploy)
24
+ active_task.abort!(aborted_by: current_user, rollback_once_aborted_to: deploy, rollback_once_aborted: true)
25
25
  response = active_task
26
26
  else
27
27
  response = deploy.trigger_rollback(current_user, env: deploy_env, force: params.force, lock: params.lock)
@@ -27,7 +27,7 @@ module Shipit
27
27
  requires :repo_name, String
28
28
  accepts :environment, String
29
29
  accepts :branch, String
30
- accepts :deploy_url, String
30
+ accepts :deploy_url, String, allow_nil: true
31
31
  accepts :ignore_ci, Boolean
32
32
  accepts :merge_queue_enabled, Boolean
33
33
  accepts :continuous_deployment, Boolean
@@ -40,8 +40,9 @@ module Shipit
40
40
  end
41
41
 
42
42
  params do
43
+ accepts :environment, String
43
44
  accepts :branch, String
44
- accepts :deploy_url, String
45
+ accepts :deploy_url, String, allow_nil: true
45
46
  accepts :ignore_ci, Boolean
46
47
  accepts :merge_queue_enabled, Boolean
47
48
  accepts :continuous_deployment, Boolean
@@ -60,6 +61,13 @@ module Shipit
60
61
  head(:accepted)
61
62
  end
62
63
 
64
+ def refresh
65
+ RefreshStatusesJob.perform_later(stack_id: stack.id)
66
+ RefreshCheckRunsJob.perform_later(stack_id: stack.id)
67
+ GithubSyncJob.perform_later(stack_id: stack.id)
68
+ render_resource(stack, status: :accepted)
69
+ end
70
+
63
71
  private
64
72
 
65
73
  def create_params
@@ -3,14 +3,14 @@ module Shipit
3
3
  module Api
4
4
  class TasksController < BaseController
5
5
  require_permission :read, :stack
6
- require_permission :deploy, :stack, only: :trigger
6
+ require_permission :deploy, :stack, only: %i(trigger abort)
7
7
 
8
8
  def index
9
9
  render_resources(stack.tasks)
10
10
  end
11
11
 
12
12
  def show
13
- render_resource(stack.tasks.find(params[:id]))
13
+ render_resource(task)
14
14
  end
15
15
 
16
16
  params do
@@ -23,6 +23,23 @@ module Shipit
23
23
  message: 'A task is already running.',
24
24
  })
25
25
  end
26
+
27
+ def abort
28
+ if task.active?
29
+ task.abort!(aborted_by: current_user)
30
+ head(:accepted)
31
+ else
32
+ render(status: :method_not_allowed, json: {
33
+ message: "This task is not currently running.",
34
+ })
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def task
41
+ stack.tasks.find(params[:id])
42
+ end
26
43
  end
27
44
  end
28
45
  end
@@ -5,7 +5,11 @@ module Shipit
5
5
  before_action :load_deploy
6
6
 
7
7
  def create
8
- @rollback = @deploy.trigger_rollback(current_user, env: rollback_params[:env], force: params[:force].present?)
8
+ @rollback = @deploy.trigger_rollback(
9
+ current_user,
10
+ env: rollback_params[:env]&.to_unsafe_hash,
11
+ force: params[:force].present?,
12
+ )
9
13
  redirect_to(stack_deploy_path(@stack, @rollback))
10
14
  rescue Task::ConcurrentTaskRunning
11
15
  redirect_to(rollback_stack_deploy_path(@stack, @deploy))
@@ -36,6 +36,17 @@ module Shipit
36
36
  link_to(t("deploy_button.caption.#{deploy_state}"), url, class: classes, data: data)
37
37
  end
38
38
 
39
+ def rollback_button(deploy)
40
+ if deploy.stack.active_task?
41
+ link_to('Deploy in progress...', '#', class: 'btn disabled deploy-action')
42
+ else
43
+ url = rollback_stack_deploy_path(deploy.stack, deploy)
44
+ classes = %w(btn btn--delete deploy-action rollback-action)
45
+
46
+ link_to('Rollback to this deploy...', url, class: classes)
47
+ end
48
+ end
49
+
39
50
  def github_change_url(commit)
40
51
  if commit.pull_request?
41
52
  github_pull_request_url(commit)
@@ -49,9 +49,9 @@ module Shipit
49
49
  end
50
50
 
51
51
  def fetch_members
52
- Shipit.redis.multi do
53
- Shipit.redis.sunionstore(TMP_KEY, SET_KEY)
54
- Shipit.redis.del(SET_KEY)
52
+ Shipit.redis.multi do |transaction|
53
+ transaction.sunionstore(TMP_KEY, SET_KEY)
54
+ transaction.del(SET_KEY)
55
55
  end
56
56
 
57
57
  yield Shipit.redis.smembers(TMP_KEY).map { |r| r.split('|') }
@@ -31,6 +31,10 @@ module Shipit
31
31
  false
32
32
  end
33
33
 
34
+ def requires_fresh_login?
35
+ false
36
+ end
37
+
34
38
  def authorized?
35
39
  Shipit.authentication_disabled?
36
40
  end
@@ -8,7 +8,7 @@ module Shipit
8
8
 
9
9
  validates :creator, :name, presence: true
10
10
 
11
- serialize :permissions, Array
11
+ serialize :permissions, Shipit.serialized_column(:permissions, type: Array)
12
12
  PERMISSIONS = %w(
13
13
  read:stack
14
14
  write:stack
@@ -44,9 +44,9 @@ module Shipit
44
44
  end
45
45
 
46
46
  def write(output)
47
- Shipit.redis.pipelined do
48
- Shipit.redis.append(key('output'), output)
49
- Shipit.redis.expire(key('output'), OUTPUT_TTL)
47
+ Shipit.redis.pipelined do |pipeline|
48
+ pipeline.append(key('output'), output)
49
+ pipeline.expire(key('output'), OUTPUT_TTL)
50
50
  end
51
51
  end
52
52
 
@@ -9,7 +9,7 @@ module Shipit
9
9
  validates :url, presence: true, url: { no_local: true, allow_blank: true }
10
10
  validates :content_type, presence: true
11
11
 
12
- serialize :response_headers, JSON
12
+ serialize :response_headers, SafeJSON
13
13
 
14
14
  after_commit :purge_old_deliveries, on: :create
15
15
 
@@ -128,6 +128,7 @@ module Shipit
128
128
  lock_reason = "A rollback for #{until_commit.sha} has been triggered. " \
129
129
  "Please make sure the reason for the rollback has been addressed before deploying again."
130
130
  stack.update!(lock_reason: lock_reason, lock_author_id: user_id)
131
+ stack.emit_lock_hooks
131
132
  rollback
132
133
  end
133
134
 
@@ -91,10 +91,30 @@ module Shipit
91
91
  end
92
92
 
93
93
  def load_config
94
- read_config(file("#{app_name}.#{@env}.yml", root: true)) ||
95
- read_config(file("#{app_name}.yml", root: true)) ||
96
- read_config(file("shipit.#{@env}.yml", root: true)) ||
97
- read_config(file('shipit.yml', root: true))
94
+ return if config_file_path.nil?
95
+
96
+ if !Shipit.respect_bare_shipit_file? && config_file_path.to_s.end_with?(*bare_shipit_filenames)
97
+ return { 'deploy' => { 'pre' => [shipit_not_obeying_bare_file_echo_command, 'exit 1'] } }
98
+ end
99
+
100
+ read_config(config_file_path)
101
+ end
102
+
103
+ def shipit_file_names_in_priority_order
104
+ ["#{app_name}.#{@env}.yml", "#{app_name}.yml", "shipit.#{@env}.yml", "shipit.yml"].uniq
105
+ end
106
+
107
+ def bare_shipit_filenames
108
+ ["#{app_name}.yml", "shipit.yml"].uniq
109
+ end
110
+
111
+ def config_file_path
112
+ shipit_file_names_in_priority_order.each do |filename|
113
+ path = file(filename, root: true)
114
+ return path if path.exist?
115
+ end
116
+
117
+ nil
98
118
  end
99
119
 
100
120
  def app_name
@@ -104,6 +124,14 @@ module Shipit
104
124
  def read_config(path)
105
125
  SafeYAML.load(path.read) if path.exist?
106
126
  end
127
+
128
+ def shipit_not_obeying_bare_file_echo_command
129
+ <<~EOM
130
+ echo \"\e[1;31mShipit is configured to ignore the bare '#{app_name}.yml' file.
131
+ Please rename this file to more specifically include the environment name.
132
+ Deployments will fail until a valid '#{app_name}.#{@env}.yml' file is found.\e[0m\"
133
+ EOM
134
+ end
107
135
  end
108
136
  end
109
137
  end
@@ -11,7 +11,7 @@ module Shipit
11
11
  has_many :pull_request_assignments
12
12
  has_many :assignees, class_name: :User, through: :pull_request_assignments, source: :user
13
13
 
14
- serialize :labels, Array
14
+ serialize :labels, Shipit.serialized_column(:labels, type: Array)
15
15
 
16
16
  after_create_commit :emit_create_hooks
17
17
  after_update_commit :emit_update_hooks
@@ -608,6 +608,16 @@ module Shipit
608
608
  Shipit.deployment_checks.call(self)
609
609
  end
610
610
 
611
+ def emit_lock_hooks
612
+ return unless previous_changes.include?('lock_reason')
613
+
614
+ lock_details = if previous_changes['lock_reason'].last.blank?
615
+ { from: previous_changes['locked_since'].first, until: Time.zone.now }
616
+ end
617
+
618
+ Hook.emit(:lock, self, locked: locked?, lock_details: lock_details, stack: self)
619
+ end
620
+
611
621
  private
612
622
 
613
623
  def clear_cache
@@ -641,16 +651,6 @@ module Shipit
641
651
  end
642
652
  end
643
653
 
644
- def emit_lock_hooks
645
- return unless previous_changes.include?('lock_reason')
646
-
647
- lock_details = if previous_changes['lock_reason'].last.blank?
648
- { from: previous_changes['locked_since'].first, until: Time.zone.now }
649
- end
650
-
651
- Hook.emit(:lock, self, locked: locked?, lock_details: lock_details, stack: self)
652
- end
653
-
654
654
  def emit_added_hooks
655
655
  Hook.emit(:stack, self, action: :added, stack: self)
656
656
  end
@@ -27,8 +27,35 @@ module Shipit
27
27
 
28
28
  deferred_touch stack: :updated_at
29
29
 
30
+ module EnvHash
31
+ class << self
32
+ def dump(hash)
33
+ raise TypeError, "Task#env should be a Hash[String => String]" unless hash.is_a?(Hash)
34
+ hash = hash.to_h.stringify_keys
35
+ hash.transform_values! do |value|
36
+ case value
37
+ when String, Symbol, Numeric
38
+ value.to_s
39
+ else
40
+ raise TypeError, "Task#env should be a Hash[String => String]" unless hash.is_a?(Hash)
41
+ end
42
+ end
43
+
44
+ hash unless hash.empty?
45
+ end
46
+
47
+ def load(hash)
48
+ hash&.to_h || {} # cast back to a real hash
49
+ end
50
+
51
+ def new
52
+ nil
53
+ end
54
+ end
55
+ end
56
+
30
57
  serialize :definition, TaskDefinition
31
- serialize :env, Hash
58
+ serialize :env, Shipit.serialized_column(:env, coder: EnvHash)
32
59
 
33
60
  scope :success, -> { where(status: 'success') }
34
61
  scope :completed, -> { where(status: COMPLETED_STATUSES) }
@@ -313,9 +340,9 @@ module Shipit
313
340
  end
314
341
 
315
342
  def request_abort
316
- Shipit.redis.pipelined do
317
- Shipit.redis.incr(abort_key)
318
- Shipit.redis.expire(abort_key, 1.month.to_i)
343
+ Shipit.redis.pipelined do |pipeline|
344
+ pipeline.incr(abort_key)
345
+ pipeline.expire(abort_key, 1.month.to_i)
319
346
  end
320
347
  end
321
348
 
@@ -3,6 +3,8 @@ module Shipit
3
3
  class User < Record
4
4
  DEFAULT_AVATAR = URI.parse('https://avatars.githubusercontent.com/u/583231?')
5
5
 
6
+ self.ignored_columns = %w(encrypted_github_access_token_iv)
7
+
6
8
  has_many :memberships
7
9
  has_many :teams, through: :memberships
8
10
  has_many :authored_commits, class_name: :Commit, foreign_key: :author_id, inverse_of: :author
@@ -11,7 +13,10 @@ module Shipit
11
13
 
12
14
  validates :name, presence: true
13
15
 
14
- attr_encrypted :github_access_token, key: Shipit.user_access_tokens_key
16
+ encrypts :encrypted_github_access_token
17
+ alias_attribute :github_access_token, :encrypted_github_access_token
18
+
19
+ after_find :discard_outdated_credentials!
15
20
 
16
21
  def self.find_or_create_by_login!(login)
17
22
  find_or_create_by!(login: login) do |user|
@@ -56,14 +61,6 @@ module Shipit
56
61
  end
57
62
  end
58
63
 
59
- alias_method :original_github_access_token, :github_access_token
60
- def github_access_token
61
- original_github_access_token
62
- rescue OpenSSL::Cipher::CipherError
63
- update_columns(encrypted_github_access_token: nil, encrypted_github_access_token_iv: nil)
64
- nil
65
- end
66
-
67
64
  def github_api
68
65
  return Shipit.github.api unless github_access_token
69
66
 
@@ -123,8 +120,25 @@ module Shipit
123
120
  DEFAULT_AVATAR.dup
124
121
  end
125
122
 
123
+ # https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats
124
+ GITHUB_TOKEN_FORMAT = /^gh[a-z]_/
125
+
126
+ def requires_fresh_login?
127
+ github_access_token.present? && !github_access_token.match(GITHUB_TOKEN_FORMAT)
128
+ end
129
+
126
130
  private
127
131
 
132
+ def discard_outdated_credentials!
133
+ if encrypted_github_access_token_before_type_cast.present?
134
+ begin
135
+ encrypted_github_access_token
136
+ rescue ActiveRecord::Encryption::Errors::Decryption
137
+ update_column(:encrypted_github_access_token, nil)
138
+ end
139
+ end
140
+ end
141
+
128
142
  def identify_renamed_user!
129
143
  last_commit = commits.last
130
144
  return unless last_commit
@@ -8,7 +8,7 @@ module Shipit
8
8
  attributes :id, :repo_owner, :repo_name, :environment, :html_url, :url, :tasks_url, :deploy_url,
9
9
  :merge_requests_url, :deploy_spec, :undeployed_commits_count, :is_locked, :lock_reason, :continuous_deployment,
10
10
  :created_at, :updated_at, :locked_since, :last_deployed_at, :branch, :merge_queue_enabled, :is_archived,
11
- :archived_since
11
+ :archived_since, :ignore_ci
12
12
 
13
13
  def url
14
14
  api_stack_url(object)
@@ -55,11 +55,7 @@
55
55
  <% unless read_only %>
56
56
  <div class="deploy-actions">
57
57
  <% if deploy.rollbackable? %>
58
- <% if deploy.stack.active_task? %>
59
- <%= link_to 'Deploy in progress...', '#', class: 'btn disabled deploy-action' %>
60
- <% else %>
61
- <%= link_to 'Rollback to this deploy...', rollback_stack_deploy_path(@stack, deploy), class: 'btn btn--delete deploy-action rollback-action' %>
62
- <% end %>
58
+ <%= rollback_button(deploy) %>
63
59
  <% elsif deploy.currently_deployed? && !deploy.stack.active_task? %>
64
60
  <%= redeploy_button(deploy.until_commit) %>
65
61
  <% end %>
@@ -23,7 +23,7 @@
23
23
  <%= link_to stack.github_repo_name, github_repo_url(stack.repo_owner, stack.repo_name) %>
24
24
  has been inaccessible for <%= time_ago_in_words(stack.inaccessible_since) %>.
25
25
 
26
- This could be a permission issue, or the repository have been deleted on GitHub.
26
+ This could be a permission issue, or the repo could have changed on GitHub.
27
27
  </p>
28
28
  </div>
29
29
  </div>
@@ -0,0 +1,55 @@
1
+ <div class="setting-section">
2
+ <%= form_with scope: :stack, url: stack_path(stack), method: :patch do |f| %>
3
+ <div class="field-wrapper">
4
+ <%= f.label :environment %>
5
+ <%= f.text_field :environment, placeholder: 'production' %>
6
+ </div>
7
+
8
+ <div class="field-wrapper">
9
+ <span>Branch: <%= stack.branch %></span>
10
+ </div>
11
+
12
+ <div class="field-wrapper">
13
+ <%= f.label :deploy_url, 'Deploy URL (Where is this stack deployed to?)' %>
14
+ <%= f.text_field :deploy_url, placeholder: 'https://' %>
15
+ </div>
16
+
17
+ <div class="field-wrapper">
18
+ <%= f.check_box :continuous_deployment %>
19
+ <%= f.label :continuous_deployment, 'Enable continuous deployment' %>
20
+ </div>
21
+
22
+ <div class="field-wrapper">
23
+ <%= f.check_box :merge_queue_enabled %>
24
+ <%= f.label :merge_queue_enabled, 'Enable merge queue' %>
25
+ </div>
26
+
27
+ <div class="field-wrapper">
28
+ <%= f.check_box :ignore_ci %>
29
+ <%= f.label :ignore_ci, "Don't require CI to deploy" %>
30
+ </div>
31
+
32
+ <%= f.submit class: "btn", value: "Save" %>
33
+ <% end %>
34
+ </div>
35
+
36
+ <div class="setting-section">
37
+ <h5>Lock deploys</h5>
38
+ <%= form_with scope: :stack, url: stack_path(@stack), method: :patch do |f| %>
39
+ <div class="field-wrapper">
40
+ <%= f.label :lock_reason, 'Reason for lock' %>
41
+ <%= f.text_area :lock_reason %>
42
+ </div>
43
+ <% if @stack.locked? %>
44
+ <%= f.submit class: "btn", value: "Update Reason" %>
45
+ <% else %>
46
+ <%= f.submit class: "btn", value: "Lock" %>
47
+ <% end %>
48
+ <% end %>
49
+ <% if @stack.locked? %>
50
+ <%= form_with scope: :stack, url: stack_path(@stack), method: :patch do |f| %>
51
+ <%= f.hidden_field :lock_reason, value: nil %>
52
+ <%= f.submit class: "btn btn--primary", value: "Unlock" %>
53
+ <%- end -%>
54
+ <% end %>
55
+ </div>