shipit-engine 0.29.0 → 0.30.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +37 -2
  3. data/app/assets/images/archive-solid.svg +1 -0
  4. data/app/assets/stylesheets/_pages/_stacks.scss +76 -0
  5. data/app/controllers/shipit/api/stacks_controller.rb +20 -1
  6. data/app/controllers/shipit/api_clients_controller.rb +49 -0
  7. data/app/controllers/shipit/merge_status_controller.rb +8 -4
  8. data/app/controllers/shipit/stacks_controller.rb +58 -9
  9. data/app/controllers/shipit/webhooks_controller.rb +2 -130
  10. data/app/helpers/shipit/stacks_helper.rb +4 -0
  11. data/app/jobs/shipit/background_job/unique.rb +3 -1
  12. data/app/jobs/shipit/continuous_delivery_job.rb +1 -0
  13. data/app/jobs/shipit/destroy_stack_job.rb +2 -2
  14. data/app/models/shipit/commit.rb +21 -9
  15. data/app/models/shipit/commit_deployment.rb +15 -11
  16. data/app/models/shipit/commit_deployment_status.rb +6 -2
  17. data/app/models/shipit/deploy.rb +48 -7
  18. data/app/models/shipit/deploy_stats.rb +57 -0
  19. data/app/models/shipit/repository.rb +38 -0
  20. data/app/models/shipit/stack.rb +41 -34
  21. data/app/models/shipit/task.rb +26 -4
  22. data/app/models/shipit/webhooks.rb +32 -0
  23. data/app/models/shipit/webhooks/handlers/check_suite_handler.rb +19 -0
  24. data/app/models/shipit/webhooks/handlers/handler.rb +40 -0
  25. data/app/models/shipit/webhooks/handlers/membership_handler.rb +45 -0
  26. data/app/models/shipit/webhooks/handlers/push_handler.rb +20 -0
  27. data/app/models/shipit/webhooks/handlers/status_handler.rb +26 -0
  28. data/app/serializers/shipit/stack_serializer.rb +6 -1
  29. data/app/validators/ascii_only_validator.rb +3 -3
  30. data/app/views/layouts/_head.html.erb +0 -0
  31. data/app/views/layouts/shipit.html.erb +4 -2
  32. data/app/views/shipit/api_clients/index.html.erb +36 -0
  33. data/app/views/shipit/api_clients/new.html.erb +33 -0
  34. data/app/views/shipit/api_clients/show.html.erb +35 -0
  35. data/app/views/shipit/merge_status/logged_out.erb +1 -1
  36. data/app/views/shipit/stacks/_header.html.erb +12 -7
  37. data/app/views/shipit/stacks/_links.html.erb +1 -0
  38. data/app/views/shipit/stacks/index.html.erb +7 -2
  39. data/app/views/shipit/stacks/settings.html.erb +19 -0
  40. data/app/views/shipit/stacks/statistics.html.erb +82 -0
  41. data/config/locales/en.yml +14 -2
  42. data/config/routes.rb +4 -0
  43. data/db/migrate/20191209231045_create_shipit_repositories.rb +12 -0
  44. data/db/migrate/20191209231307_add_repository_reference_to_stacks.rb +15 -0
  45. data/db/migrate/20191216162728_backfill_repository_data.rb +22 -0
  46. data/db/migrate/20191216163010_remove_repository_information_from_stacks.rb +20 -0
  47. data/db/migrate/20191219205202_add_archived_since_to_stacks.rb +6 -0
  48. data/db/migrate/20200102175621_optional_task_commits.rb +6 -0
  49. data/db/migrate/20200109132519_add_sha_to_commit_deployments.rb +5 -0
  50. data/lib/shipit/github_app.rb +32 -3
  51. data/lib/shipit/task_commands.rb +10 -2
  52. data/lib/shipit/version.rb +1 -1
  53. data/test/controllers/api/ccmenu_controller_test.rb +1 -1
  54. data/test/controllers/api/stacks_controller_test.rb +14 -6
  55. data/test/controllers/api_clients_controller_test.rb +103 -0
  56. data/test/controllers/merge_status_controller_test.rb +21 -4
  57. data/test/controllers/stacks_controller_test.rb +35 -0
  58. data/test/controllers/webhooks_controller_test.rb +26 -0
  59. data/test/dummy/config/environments/development.rb +22 -4
  60. data/test/dummy/db/schema.rb +17 -6
  61. data/test/dummy/db/seeds.rb +20 -6
  62. data/test/fixtures/shipit/commit_deployment_statuses.yml +4 -4
  63. data/test/fixtures/shipit/commit_deployments.yml +8 -8
  64. data/test/fixtures/shipit/commits.yml +23 -0
  65. data/test/fixtures/shipit/repositories.yml +23 -0
  66. data/test/fixtures/shipit/stacks.yml +100 -16
  67. data/test/fixtures/shipit/tasks.yml +66 -3
  68. data/test/jobs/destroy_stack_job_test.rb +9 -0
  69. data/test/models/commit_deployment_status_test.rb +33 -4
  70. data/test/models/commit_deployment_test.rb +8 -11
  71. data/test/models/commits_test.rb +22 -2
  72. data/test/models/deploy_stats_test.rb +112 -0
  73. data/test/models/deploys_test.rb +55 -17
  74. data/test/models/pull_request_test.rb +1 -1
  75. data/test/models/shipit/repository_test.rb +76 -0
  76. data/test/models/shipit/wehbooks/handlers_test.rb +26 -0
  77. data/test/models/stacks_test.rb +44 -51
  78. data/test/models/undeployed_commits_test.rb +13 -0
  79. data/test/test_helper.rb +3 -1
  80. data/test/unit/deploy_commands_test.rb +9 -0
  81. data/test/unit/github_app_test.rb +136 -0
  82. metadata +161 -128
@@ -86,5 +86,9 @@ module Shipit
86
86
  t('commit.unlock')
87
87
  end
88
88
  end
89
+
90
+ def positive_negative_class(value)
91
+ value.to_f >= 0 ? 'positive' : 'negative'
92
+ end
89
93
  end
90
94
  end
@@ -4,6 +4,8 @@ module Shipit
4
4
  extend ActiveSupport::Concern
5
5
  DEFAULT_TIMEOUT = 10
6
6
 
7
+ ConcurrentJobError = Class.new(StandardError)
8
+
7
9
  included do
8
10
  around_perform { |job, block| job.acquire_lock(&block) }
9
11
  cattr_accessor :lock_timeout
@@ -19,7 +21,7 @@ module Shipit
19
21
  )
20
22
  mutex.lock(&block)
21
23
  rescue Redis::Lock::LockTimeout
22
- raise unless self.class.drop_duplicate_jobs?
24
+ raise ConcurrentJobError unless self.class.drop_duplicate_jobs?
23
25
  end
24
26
 
25
27
  def lock_key(*args)
@@ -3,6 +3,7 @@ module Shipit
3
3
  include BackgroundJob::Unique
4
4
 
5
5
  queue_as :default
6
+ on_duplicate :drop
6
7
 
7
8
  def perform(stack)
8
9
  return unless stack.continuous_deployment?
@@ -17,7 +17,8 @@ module Shipit
17
17
  def perform(stack)
18
18
  Shipit::ApiClient.where(stack_id: stack.id).delete_all
19
19
  commits_ids = Shipit::Commit.where(stack_id: stack.id).pluck(:id)
20
- commit_deployments_ids = Shipit::CommitDeployment.where(commit_id: commits_ids).pluck(:id)
20
+ tasks_ids = Shipit::Task.where(stack_id: stack.id).pluck(:id)
21
+ commit_deployments_ids = Shipit::CommitDeployment.where(task_id: tasks_ids).pluck(:id)
21
22
  Shipit::CommitDeploymentStatus.where(commit_deployment_id: commit_deployments_ids).delete_all
22
23
  Shipit::CommitDeployment.where(id: commit_deployments_ids).delete_all
23
24
  Shipit::Status.where(commit_id: commits_ids).delete_all
@@ -25,7 +26,6 @@ module Shipit
25
26
  Shipit::GithubHook.where(stack_id: stack.id).destroy_all
26
27
  Shipit::Hook.where(stack_id: stack.id).delete_all
27
28
  Shipit::PullRequest.where(stack_id: stack.id).delete_all
28
- tasks_ids = Shipit::Task.where(stack_id: stack.id).pluck(:id)
29
29
  tasks_ids.each_slice(100) do |ids|
30
30
  Shipit::OutputChunk.where(task_id: ids).delete_all
31
31
  Shipit::Task.where(id: ids).delete_all
@@ -2,6 +2,8 @@ module Shipit
2
2
  class Commit < ActiveRecord::Base
3
3
  include DeferredTouch
4
4
 
5
+ RECENT_COMMIT_THRESHOLD = 10.seconds
6
+
5
7
  AmbiguousRevision = Class.new(StandardError)
6
8
 
7
9
  belongs_to :stack
@@ -102,6 +104,14 @@ module Shipit
102
104
  )
103
105
  end
104
106
 
107
+ def message=(message)
108
+ limit = self.class.columns_hash['message'].limit
109
+ if limit && message && message.size > limit
110
+ message = message.slice(0, limit)
111
+ end
112
+ super(message)
113
+ end
114
+
105
115
  def reload(*)
106
116
  @status = nil
107
117
  super
@@ -126,7 +136,9 @@ module Shipit
126
136
  end
127
137
 
128
138
  def refresh_statuses!
129
- github_statuses = stack.handle_github_redirections { Shipit.github.api.statuses(github_repo_name, sha) }
139
+ github_statuses = stack.handle_github_redirections do
140
+ Shipit.github.api.statuses(github_repo_name, sha, per_page: 100)
141
+ end
130
142
  github_statuses.each do |status|
131
143
  create_status_from_github!(status)
132
144
  end
@@ -177,13 +189,7 @@ module Shipit
177
189
  def active?
178
190
  return false unless stack.active_task?
179
191
 
180
- active_task = stack.active_task
181
-
182
- if active_task.since_commit == active_task.until_commit
183
- id == active_task.since_commit.id
184
- else
185
- id > active_task.since_commit.id && id <= active_task.until_commit.id
186
- end
192
+ stack.active_task.includes_commit?(self)
187
193
  end
188
194
 
189
195
  def deployable?
@@ -242,7 +248,9 @@ module Shipit
242
248
 
243
249
  def schedule_continuous_delivery
244
250
  return unless deployable? && stack.continuous_deployment? && stack.deployable?
245
- ContinuousDeliveryJob.perform_later(stack)
251
+ # This buffer is to allow for statuses and checks to be refreshed before evaluating if the commit is deployable
252
+ # - e.g. if the commit was fast-forwarded with already passing CI.
253
+ ContinuousDeliveryJob.set(wait: RECENT_COMMIT_THRESHOLD).perform_later(stack)
246
254
  end
247
255
 
248
256
  def github_commit
@@ -311,6 +319,10 @@ module Shipit
311
319
  update!(locked: false, lock_author: nil)
312
320
  end
313
321
 
322
+ def recently_pushed?
323
+ created_at > RECENT_COMMIT_THRESHOLD.ago
324
+ end
325
+
314
326
  private
315
327
 
316
328
  def message_parser
@@ -1,6 +1,5 @@
1
1
  module Shipit
2
2
  class CommitDeployment < ActiveRecord::Base
3
- belongs_to :commit
4
3
  belongs_to :task
5
4
  has_many :statuses, dependent: :destroy, class_name: 'CommitDeploymentStatus'
6
5
 
@@ -9,11 +8,10 @@ module Shipit
9
8
  delegate :stack, :author, to: :task
10
9
 
11
10
  def create_on_github!
12
- return unless commit.pull_request?
13
-
14
11
  create_deployment_on_github!
15
12
  statuses.order(id: :asc).each(&:create_on_github!)
16
- rescue Octokit::NotFound, Octokit::Forbidden
13
+ rescue Octokit::NotFound, Octokit::Forbidden => error
14
+ Rails.logger.warn("Got #{error.class.name} creating deployment or statuses: #{error.message}")
17
15
  # If no one can create the deployment we can only give up
18
16
  end
19
17
 
@@ -34,25 +32,31 @@ module Shipit
34
32
  update!(github_id: response.id, api_url: response.url)
35
33
  end
36
34
 
37
- def pull_request_head
38
- pull_request = Shipit.github.api.pull_request(stack.github_repo_name, commit.pull_request_number)
39
- pull_request.head.sha
40
- end
41
-
42
35
  def schedule_create_on_github
43
36
  CreateOnGithubJob.perform_later(self)
44
37
  end
45
38
 
39
+ def short_sha
40
+ sha[0..9]
41
+ end
42
+
46
43
  private
47
44
 
48
45
  def create_deployment_on_github(client)
49
46
  client.create_deployment(
50
47
  stack.github_repo_name,
51
- pull_request_head,
48
+ sha,
52
49
  auto_merge: false,
53
50
  required_contexts: [],
54
- description: "Via Shipit",
51
+ description: "Via #{Shipit.app_name}",
55
52
  environment: stack.environment,
53
+ payload: {
54
+ shipit: {
55
+ task_id: task.id,
56
+ from_sha: task.since_commit.sha,
57
+ to_sha: task.until_commit.sha,
58
+ },
59
+ },
56
60
  )
57
61
  end
58
62
  end
@@ -1,5 +1,7 @@
1
1
  module Shipit
2
2
  class CommitDeploymentStatus < ActiveRecord::Base
3
+ DESCRIPTION_CHARACTER_LIMIT_ON_GITHUB = 140
4
+
3
5
  belongs_to :commit_deployment
4
6
 
5
7
  after_commit :schedule_create_on_github, on: :create
@@ -25,7 +27,7 @@ module Shipit
25
27
  def description
26
28
  I18n.t(
27
29
  "deployment_description.#{task_type}.#{status}",
28
- sha: task.until_commit.sha,
30
+ sha: task.until_commit.short_sha,
29
31
  author: task.author.login,
30
32
  stack: stack.to_param,
31
33
  )
@@ -45,8 +47,10 @@ module Shipit
45
47
  client.create_deployment_status(
46
48
  commit_deployment.api_url,
47
49
  status,
50
+ accept: 'application/vnd.github.flash-preview+json',
48
51
  target_url: url_helpers.stack_deploy_url(stack, task),
49
- description: description,
52
+ description: description.truncate(DESCRIPTION_CHARACTER_LIMIT_ON_GITHUB),
53
+ environment_url: stack.deploy_url,
50
54
  )
51
55
  end
52
56
 
@@ -14,20 +14,41 @@ module Shipit
14
14
  after_transition any => any, do: :update_last_deploy_time
15
15
  end
16
16
 
17
+ belongs_to :until_commit, class_name: 'Commit', required: true, inverse_of: :deploys
18
+ belongs_to :since_commit, class_name: 'Commit', required: true, inverse_of: :deploys
17
19
  has_many :commit_deployments, dependent: :destroy, inverse_of: :task, foreign_key: :task_id do
18
20
  GITHUB_STATUSES = {
19
21
  'pending' => 'pending',
22
+ 'running' => 'in_progress',
20
23
  'failed' => 'failure',
24
+ 'timedout' => 'failure',
21
25
  'success' => 'success',
26
+ 'faulty' => 'error',
22
27
  'error' => 'error',
23
28
  'aborted' => 'error',
24
29
  }.freeze
25
30
 
26
31
  def append_status(task_status)
27
32
  if github_status = GITHUB_STATUSES[task_status]
28
- each do |deployment|
33
+ # Deployments and statuses are created async, we reload the association to ensure we update all instances
34
+ reload.each do |deployment|
35
+ Rails.logger.info(
36
+ "Creating #{github_status} deploy status for deployment #{deployment.id}. "\
37
+ "Commit: #{deployment.sha}, Github id: #{deployment.github_id}, "\
38
+ "Repo: #{deployment.stack.repo_name}, Environment: #{deployment.stack.environment}, "\
39
+ "API Url: #{deployment.api_url}.",
40
+ )
29
41
  deployment.statuses.create!(status: github_status)
30
42
  end
43
+ else
44
+ each do |deployment|
45
+ Rails.logger.warn(
46
+ "No GitHub status for task status #{task_status}. "\
47
+ "Commit: #{deployment.sha}, Github id: #{deployment.github_id}, "\
48
+ "Repo: #{deployment.stack.repo_name}, Environment: #{deployment.stack.environment}, "\
49
+ "API Url: #{deployment.api_url}.",
50
+ )
51
+ end
31
52
  end
32
53
  end
33
54
  end
@@ -215,12 +236,36 @@ module Shipit
215
236
  end
216
237
  end
217
238
 
239
+ def update_commit_deployments
240
+ commit_deployments.append_status(status)
241
+ end
242
+
218
243
  private
219
244
 
220
245
  def create_commit_deployments
221
- commits.each do |commit|
222
- commit_deployments.create!(commit: commit)
246
+ # Create one deployment for the head of the batch
247
+ commit_deployments.create!(sha: until_commit.sha)
248
+
249
+ # Create one for each pull request in the batch, to give feedback on the PR timeline
250
+ commits.select(&:pull_request?).each do |commit|
251
+ if (pull_request_head = pull_request_head_for_commit(commit))
252
+ commit_deployments.create!(sha: pull_request_head)
253
+ end
223
254
  end
255
+
256
+ # Immediately update to publish the status to the commit deployments
257
+ update_commit_deployments
258
+ rescue Octokit::ClientError => error
259
+ Rails.logger.warn("Got #{error.class.name} (#{error.message}) when creating CommitDeployments for Deploy##{id}")
260
+ end
261
+
262
+ def pull_request_head_for_commit(commit)
263
+ pull_request = Shipit.github.api.pull_request(commit.stack.github_repo_name, commit.pull_request_number)
264
+ pull_request.head.sha
265
+ rescue Octokit::ClientError => error
266
+ pr_ref = "#{commit.stack.github_repo_name}##{commit.pull_request_number}"
267
+ Rails.logger.warn("Got #{error.class.name} (#{error.message}) when loading pull request #{pr_ref}")
268
+ nil
224
269
  end
225
270
 
226
271
  def update_release_status
@@ -245,10 +290,6 @@ module Shipit
245
290
  end
246
291
  end
247
292
 
248
- def update_commit_deployments
249
- commit_deployments.append_status(status)
250
- end
251
-
252
293
  def trigger_revert_if_required
253
294
  return unless rollback_once_aborted?
254
295
  return unless supports_rollback?
@@ -0,0 +1,57 @@
1
+ module Shipit
2
+ class DeployStats
3
+ delegate :empty?, to: :@deploys
4
+
5
+ def initialize(deploys)
6
+ @deploys = deploys
7
+ @durations = @deploys.map { |d| d.duration.value }.compact
8
+ end
9
+
10
+ def count
11
+ @deploys.length
12
+ end
13
+
14
+ def average_duration
15
+ return if empty?
16
+ @durations.sum / @durations.length.to_f
17
+ end
18
+
19
+ def max_duration
20
+ @durations.max
21
+ end
22
+
23
+ def min_duration
24
+ @durations.min
25
+ end
26
+
27
+ def median_duration
28
+ return if @durations.empty?
29
+ (sorted_durations[(@durations.length - 1) / 2] + sorted_durations[@durations.length / 2]) / 2.0
30
+ end
31
+
32
+ def success_rate
33
+ return if empty?
34
+ (@deploys.count(&:success?) / @deploys.length.to_f) * 100
35
+ end
36
+
37
+ def compare(compare_stats)
38
+ {
39
+ count: percent_change(compare_stats.count, count),
40
+ average_duration: percent_change(compare_stats.average_duration, average_duration),
41
+ median_duration: percent_change(compare_stats.median_duration, median_duration),
42
+ }
43
+ end
44
+
45
+ protected
46
+
47
+ def sorted_durations
48
+ @sorted ||= @durations.sort
49
+ end
50
+
51
+ def percent_change(from, to)
52
+ return if to.nil? || from.nil?
53
+ return to * 100 if from.zero?
54
+ ((to - from) / from.to_f) * 100
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,38 @@
1
+ module Shipit
2
+ class Repository < ApplicationRecord
3
+ OWNER_MAX_SIZE = 39
4
+ private_constant :OWNER_MAX_SIZE
5
+
6
+ NAME_MAX_SIZE = 100
7
+ private_constant :NAME_MAX_SIZE
8
+
9
+ validates :name, uniqueness: {scope: %i(owner), case_sensitive: false,
10
+ message: 'cannot be used more than once'}
11
+ validates :owner, :name, presence: true, ascii_only: true
12
+ validates :owner, format: {with: /\A[a-z0-9_\-\.]+\z/}, length: {maximum: OWNER_MAX_SIZE}
13
+ validates :name, format: {with: /\A[a-z0-9_\-\.]+\z/}, length: {maximum: NAME_MAX_SIZE}
14
+
15
+ has_many :stacks, dependent: :destroy
16
+
17
+ def self.from_github_repo_name(github_repo_name)
18
+ repo_owner, repo_name = github_repo_name.downcase.split('/')
19
+ find_by(owner: repo_owner, name: repo_name)
20
+ end
21
+
22
+ def name=(n)
23
+ super(n&.downcase)
24
+ end
25
+
26
+ def owner=(o)
27
+ super(o&.downcase)
28
+ end
29
+
30
+ def http_url
31
+ Shipit.github.url("#{owner}/#{name}")
32
+ end
33
+
34
+ def git_url
35
+ "https://#{Shipit.github.domain}/#{owner}/#{name}.git"
36
+ end
37
+ end
38
+ end
@@ -22,8 +22,6 @@ module Shipit
22
22
  end
23
23
  end
24
24
 
25
- REPO_OWNER_MAX_SIZE = 39
26
- REPO_NAME_MAX_SIZE = 100
27
25
  ENVIRONMENT_MAX_SIZE = 50
28
26
  REQUIRED_HOOKS = %i(push status).freeze
29
27
 
@@ -40,6 +38,14 @@ module Shipit
40
38
  has_many :hooks, dependent: :destroy
41
39
  has_many :api_clients, dependent: :destroy
42
40
  belongs_to :lock_author, class_name: :User, optional: true
41
+ belongs_to :repository
42
+ validates_associated :repository
43
+
44
+ scope :not_archived, -> { where(archived_since: nil) }
45
+
46
+ def repository
47
+ super || build_repository
48
+ end
43
49
 
44
50
  def lock_author(*)
45
51
  super || AnonymousUser.new
@@ -49,11 +55,6 @@ module Shipit
49
55
  super(user&.logged_in? ? user : nil)
50
56
  end
51
57
 
52
- def self.repo(full_name)
53
- repo_owner, repo_name = full_name.downcase.split('/')
54
- where(repo_owner: repo_owner, repo_name: repo_name)
55
- end
56
-
57
58
  before_validation :update_defaults
58
59
  before_destroy :clear_local_files
59
60
  before_save :set_locked_since
@@ -66,11 +67,10 @@ module Shipit
66
67
  after_commit :sync_github, on: :create
67
68
  after_commit :schedule_merges_if_necessary, on: :update
68
69
 
69
- validates :repo_name, uniqueness: {scope: %i(repo_owner environment), case_sensitive: false,
70
- message: 'cannot be used more than once with this environment'}
71
- validates :repo_owner, :repo_name, :environment, presence: true, ascii_only: true
72
- validates :repo_owner, format: {with: /\A[a-z0-9_\-\.]+\z/}, length: {maximum: REPO_OWNER_MAX_SIZE}
73
- validates :repo_name, format: {with: /\A[a-z0-9_\-\.]+\z/}, length: {maximum: REPO_NAME_MAX_SIZE}
70
+ validates :repository, uniqueness: {
71
+ scope: %i(environment), case_sensitive: false,
72
+ message: 'cannot be used more than once with this environment. Check archived stacks.'
73
+ }
74
74
  validates :environment, format: {with: /\A[a-z0-9\-_\:]+\z/}, length: {maximum: ENVIRONMENT_MAX_SIZE}
75
75
  validates :deploy_url, format: {with: URI.regexp(%w(http https ssh))}, allow_blank: true
76
76
 
@@ -157,7 +157,8 @@ module Shipit
157
157
  return
158
158
  end
159
159
 
160
- if commit.deploy_failed? || (checks? && !EphemeralCommitChecks.new(commit).run.success?)
160
+ if commit.deploy_failed? || (checks? && !EphemeralCommitChecks.new(commit).run.success?) ||
161
+ commit.recently_pushed?
161
162
  continuous_delivery_delayed!
162
163
  return
163
164
  end
@@ -320,21 +321,12 @@ module Shipit
320
321
  cached_deploy_spec&.pull_request_merge_method || Shipit.default_merge_method
321
322
  end
322
323
 
323
- def repo_name=(name)
324
- super(name&.downcase)
325
- end
326
-
327
- def repo_owner=(name)
328
- super(name&.downcase)
329
- end
330
-
331
- def repo_http_url
332
- Shipit.github.url("#{repo_owner}/#{repo_name}")
333
- end
334
-
335
- def repo_git_url
336
- "https://#{Shipit.github.domain}/#{repo_owner}/#{repo_name}.git"
337
- end
324
+ delegate :name=, to: :repository, prefix: :repo
325
+ delegate :name, to: :repository, prefix: :repo
326
+ delegate :owner=, to: :repository, prefix: :repo
327
+ delegate :owner, to: :repository, prefix: :repo
328
+ delegate :http_url, to: :repository, prefix: :repo
329
+ delegate :git_url, to: :repository, prefix: :repo
338
330
 
339
331
  def base_path
340
332
  Rails.root.join('data', 'stacks', repo_owner, repo_name, environment)
@@ -389,7 +381,7 @@ module Shipit
389
381
  if resource.try(:message) == 'Moved Permanently'
390
382
  resource = Shipit.github.api.get(resource.url)
391
383
  end
392
- update!(repo_owner: resource.owner.login, repo_name: resource.name)
384
+ repository.update!(owner: resource.owner.login, name: resource.name)
393
385
  end
394
386
 
395
387
  def active_task?
@@ -414,6 +406,18 @@ module Shipit
414
406
  update!(lock_reason: nil, lock_author: nil, locked_since: nil)
415
407
  end
416
408
 
409
+ def archived?
410
+ archived_since.present?
411
+ end
412
+
413
+ def archive!(user)
414
+ update!(archived_since: Time.now, lock_reason: "Archived", lock_author: user)
415
+ end
416
+
417
+ def unarchive!
418
+ update!(archived_since: nil, lock_reason: nil, lock_author: nil, locked_since: nil)
419
+ end
420
+
417
421
  def to_param
418
422
  [repo_owner, repo_name, environment].join('/')
419
423
  end
@@ -429,11 +433,14 @@ module Shipit
429
433
 
430
434
  def self.from_param!(param)
431
435
  repo_owner, repo_name, environment = param.split('/')
432
- where(
433
- repo_owner: repo_owner.downcase,
434
- repo_name: repo_name.downcase,
435
- environment: environment,
436
- ).first!
436
+ includes(:repository)
437
+ .where(
438
+ repositories: {
439
+ owner: repo_owner.downcase,
440
+ name: repo_name.downcase,
441
+ },
442
+ environment: environment,
443
+ ).first!
437
444
  end
438
445
 
439
446
  delegate :plugins, :task_definitions, :hidden_statuses, :required_statuses, :soft_failing_statuses,