Class: Deployment

Inherits:
ApplicationRecord show all
Includes:
AfterCommitQueue, AtomicInternalId, FastDestroyAll, Gitlab::Utils::StrongMemoize, IidRoutes, Importable, UpdatedAtFilterable
Defined in:
app/models/deployment.rb

Constant Summary collapse

FINISHED_STATUSES =
%i[success failed canceled].freeze

Constants included from FastDestroyAll

FastDestroyAll::ForbiddenActionError

Instance Attribute Summary

Attributes included from Importable

#imported, #importing

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::Utils::StrongMemoize

#clear_memoization, #strong_memoize, #strong_memoized?

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from IidRoutes

#to_param

Methods included from AtomicInternalId

group_init, #internal_id_read_scope, #internal_id_scope_attrs, #internal_id_scope_usage, project_init, scope_attrs, scope_usage

Methods inherited from ApplicationRecord

at_most, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, underscore, where_exists, with_fast_statement_timeout, without_order

Class Method Details

.begin_fast_destroyObject

FastDestroyAll concerns


137
138
139
140
141
# File 'app/models/deployment.rb', line 137

def begin_fast_destroy
  preload(:project).find_each.map do |deployment|
    [deployment.project, deployment.ref_path]
  end
end

.distinct_on_environmentObject


125
126
127
128
# File 'app/models/deployment.rb', line 125

def self.distinct_on_environment
  order('environment_id, deployments.id DESC')
    .select('DISTINCT ON (environment_id) deployments.*')
end

.finalize_fast_destroy(params) ⇒ Object

FastDestroyAll concerns


145
146
147
148
149
150
151
# File 'app/models/deployment.rb', line 145

def finalize_fast_destroy(params)
  by_project = params.group_by(&:shift)

  by_project.each do |project, ref_paths|
    project.repository.delete_refs(*ref_paths.flatten)
  end
end

.find_successful_deployment!(iid) ⇒ Object


130
131
132
# File 'app/models/deployment.rb', line 130

def self.find_successful_deployment!(iid)
  success.find_by!(iid: iid)
end

.last_for_environment(environment) ⇒ Object


116
117
118
119
120
121
122
123
# File 'app/models/deployment.rb', line 116

def self.last_for_environment(environment)
  ids = self
    .for_environment(environment)
    .select('MAX(id) AS id')
    .group(:environment_id)
    .map(&:id)
  find(ids)
end

.latest_for_sha(sha) ⇒ Object


153
154
155
# File 'app/models/deployment.rb', line 153

def latest_for_sha(sha)
  where(sha: sha).order(id: :desc).take
end

Instance Method Details

#commitObject


158
159
160
# File 'app/models/deployment.rb', line 158

def commit
  project.commit(sha)
end

#commit_titleObject


162
163
164
# File 'app/models/deployment.rb', line 162

def commit_title
  commit.try(:title)
end

#create_refObject


180
181
182
# File 'app/models/deployment.rb', line 180

def create_ref
  project.repository.create_ref(sha, ref_path)
end

#deployed_atObject


256
257
258
259
260
# File 'app/models/deployment.rb', line 256

def deployed_at
  return unless success?

  finished_at
end

#deployed_byObject


266
267
268
269
270
271
272
# File 'app/models/deployment.rb', line 266

def deployed_by
  # We use deployable's user if available because Ci::PlayBuildService
  # does not update the deployment's user, just the one for the deployable.
  # TODO: use deployment's user once https://gitlab.com/gitlab-org/gitlab-foss/issues/66442
  # is completed.
  deployable&.user || user
end

#execute_hooksObject


170
171
172
173
174
# File 'app/models/deployment.rb', line 170

def execute_hooks
  deployment_data = Gitlab::DataBuilder::Deployment.build(self)
  project.execute_hooks(deployment_data, :deployment_hooks)
  project.execute_services(deployment_data, :deployment_hooks)
end

#finished_atObject


252
253
254
# File 'app/models/deployment.rb', line 252

def finished_at
  read_attribute(:finished_at) || legacy_finished_at
end

#formatted_deployment_timeObject


262
263
264
# File 'app/models/deployment.rb', line 262

def formatted_deployment_time
  deployed_at&.to_time&.in_time_zone&.to_s(:medium)
end

#includes_commit?(commit) ⇒ Boolean

Returns:

  • (Boolean)

202
203
204
205
206
# File 'app/models/deployment.rb', line 202

def includes_commit?(commit)
  return false unless commit

  project.repository.ancestor?(commit.id, sha)
end

#invalidate_cacheObject


184
185
186
# File 'app/models/deployment.rb', line 184

def invalidate_cache
  environment.expire_etag_cache
end

#last?Boolean

Returns:

  • (Boolean)

176
177
178
# File 'app/models/deployment.rb', line 176

def last?
  self == environment.last_deployment
end

274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'app/models/deployment.rb', line 274

def link_merge_requests(relation)
  # NOTE: relation.select will perform column deduplication,
  # when id == environment_id it will outputs 2 columns instead of 3
  # i.e.:
  # MergeRequest.select(1, 2).to_sql #=> SELECT 1, 2 FROM "merge_requests"
  # MergeRequest.select(1, 1).to_sql #=> SELECT 1 FROM "merge_requests"
  select = relation.select('merge_requests.id',
                           "#{id} as deployment_id",
                           "#{environment_id} as environment_id").to_sql

  # We don't use `Gitlab::Database.bulk_insert` here so that we don't need to
  # first pluck lots of IDs into memory.
  #
  # We also ignore any duplicates so this method can be called multiple times
  # for the same deployment, only inserting any missing merge requests.
  DeploymentMergeRequest.connection.execute(<<~SQL)
    INSERT INTO #{DeploymentMergeRequest.table_name}
    (merge_request_id, deployment_id, environment_id)
    #{select}
    ON CONFLICT DO NOTHING
  SQL
end

#manual_actionsObject


188
189
190
# File 'app/models/deployment.rb', line 188

def manual_actions
  @manual_actions ||= deployable.try(:other_manual_actions)
end

#playable_buildObject


196
197
198
199
200
# File 'app/models/deployment.rb', line 196

def playable_build
  strong_memoize(:playable_build) do
    deployable.try(:playable?) ? deployable : nil
  end
end

#previous_deploymentObject


225
226
227
228
229
230
231
232
# File 'app/models/deployment.rb', line 225

def previous_deployment
  @previous_deployment ||=
    project.deployments.joins(:environment)
    .where(environments: { name: self.environment.name }, ref: self.ref)
    .where.not(id: self.id)
    .order(id: :desc)
    .take
end

#previous_environment_deploymentObject


234
235
236
237
238
239
240
241
242
243
# File 'app/models/deployment.rb', line 234

def previous_environment_deployment
  project
    .deployments
    .success
    .joins(:environment)
    .where(environments: { name: environment.name })
    .where.not(id: self.id)
    .order(id: :desc)
    .take
end

#ref_pathObject


328
329
330
# File 'app/models/deployment.rb', line 328

def ref_path
  File.join(environment.ref_path, 'deployments', iid.to_s)
end

#scheduled_actionsObject


192
193
194
# File 'app/models/deployment.rb', line 192

def scheduled_actions
  @scheduled_actions ||= deployable.try(:other_scheduled_actions)
end

#short_shaObject


166
167
168
# File 'app/models/deployment.rb', line 166

def short_sha
  Commit.truncate_sha(sha)
end

#stop_actionObject


245
246
247
248
249
250
# File 'app/models/deployment.rb', line 245

def stop_action
  return unless on_stop.present?
  return unless manual_actions

  @stop_action ||= manual_actions.find_by(name: on_stop)
end

#update_merge_request_metrics!Object


208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'app/models/deployment.rb', line 208

def update_merge_request_metrics!
  return unless environment.update_merge_request_metrics? && success?

  merge_requests = project.merge_requests
                   .joins(:metrics)
                   .where(target_branch: self.ref, merge_request_metrics: { first_deployed_to_production_at: nil })
                   .where("merge_request_metrics.merged_at <= ?", finished_at)

  if previous_deployment
    merge_requests = merge_requests.where("merge_request_metrics.merged_at >= ?", previous_deployment.finished_at)
  end

  MergeRequest::Metrics
    .where(merge_request_id: merge_requests.select(:id), first_deployed_to_production_at: nil)
    .update_all(first_deployed_to_production_at: finished_at)
end

#update_status(status) ⇒ Object

Changes the status of a deployment and triggers the corresponding state machine events.


299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'app/models/deployment.rb', line 299

def update_status(status)
  case status
  when 'running'
    run
  when 'success'
    succeed
  when 'failed'
    drop
  when 'canceled'
    cancel
  when 'skipped'
    skip
  else
    raise ArgumentError, "The status #{status.inspect} is invalid"
  end
end

#valid_refObject


322
323
324
325
326
# File 'app/models/deployment.rb', line 322

def valid_ref
  return if project&.commit(ref)

  errors.add(:ref, _('The branch or tag does not exist'))
end

#valid_shaObject


316
317
318
319
320
# File 'app/models/deployment.rb', line 316

def valid_sha
  return if project&.commit(sha)

  errors.add(:sha, _('The commit does not exist'))
end