shipit-engine 0.29.0 → 0.30.0

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