Class: Deployment

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

Constant Summary

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

#internal_id_read_scope, #internal_id_scope_attrs, #internal_id_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, without_order

Class Method Details

.begin_fast_destroyObject

FastDestroyAll concerns


120
121
122
123
124
# File 'app/models/deployment.rb', line 120

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

.distinct_on_environmentObject


108
109
110
111
# File 'app/models/deployment.rb', line 108

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


128
129
130
131
132
133
134
# File 'app/models/deployment.rb', line 128

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


113
114
115
# File 'app/models/deployment.rb', line 113

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

.last_for_environment(environment) ⇒ Object


99
100
101
102
103
104
105
106
# File 'app/models/deployment.rb', line 99

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

Instance Method Details

#commitObject


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

def commit
  project.commit(sha)
end

#commit_titleObject


141
142
143
# File 'app/models/deployment.rb', line 141

def commit_title
  commit.try(:title)
end

#create_refObject


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

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

#deployed_atObject


235
236
237
238
239
# File 'app/models/deployment.rb', line 235

def deployed_at
  return unless success?

  finished_at
end

#deployed_byObject


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

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


149
150
151
152
153
# File 'app/models/deployment.rb', line 149

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


231
232
233
# File 'app/models/deployment.rb', line 231

def finished_at
  read_attribute(:finished_at) || legacy_finished_at
end

#formatted_deployment_timeObject


241
242
243
# File 'app/models/deployment.rb', line 241

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

#includes_commit?(commit) ⇒ Boolean

Returns:

  • (Boolean)

181
182
183
184
185
# File 'app/models/deployment.rb', line 181

def includes_commit?(commit)
  return false unless commit

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

#invalidate_cacheObject


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

def invalidate_cache
  environment.expire_etag_cache
end

#last?Boolean

Returns:

  • (Boolean)

155
156
157
# File 'app/models/deployment.rb', line 155

def last?
  self == environment.last_deployment
end

253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'app/models/deployment.rb', line 253

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


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

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

#playable_buildObject


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

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

#previous_deploymentObject


204
205
206
207
208
209
210
211
# File 'app/models/deployment.rb', line 204

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


213
214
215
216
217
218
219
220
221
222
# File 'app/models/deployment.rb', line 213

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


305
306
307
# File 'app/models/deployment.rb', line 305

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

#scheduled_actionsObject


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

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

#short_shaObject


145
146
147
# File 'app/models/deployment.rb', line 145

def short_sha
  Commit.truncate_sha(sha)
end

#stop_actionObject


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

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


187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'app/models/deployment.rb', line 187

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 correspinding state machine events.


278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'app/models/deployment.rb', line 278

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

#valid_refObject


299
300
301
302
303
# File 'app/models/deployment.rb', line 299

def valid_ref
  return if project&.commit(ref)

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

#valid_shaObject


293
294
295
296
297
# File 'app/models/deployment.rb', line 293

def valid_sha
  return if project&.commit(sha)

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