shipit-engine 0.35.1 → 0.37.0

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