summaryrefslogtreecommitdiff
path: root/app/models/deployment.rb
blob: 62dc0f2cbebde077a05ed8c6e5e941e329576f9a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# frozen_string_literal: true

class Deployment < ActiveRecord::Base
  include AtomicInternalId
  include IidRoutes

  belongs_to :project, required: true
  belongs_to :environment, required: true
  belongs_to :user
  belongs_to :deployable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations

  has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.deployments&.maximum(:iid) }

  validates :sha, presence: true
  validates :ref, presence: true

  delegate :name, to: :environment, prefix: true

  after_create :create_ref
  after_create :invalidate_cache

  scope :for_environment, -> (environment) { where(environment_id: environment) }

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

  def commit
    project.commit(sha)
  end

  def commit_title
    commit.try(:title)
  end

  def short_sha
    Commit.truncate_sha(sha)
  end

  def last?
    self == environment.last_deployment
  end

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

  def invalidate_cache
    environment.expire_etag_cache
  end

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

  def includes_commit?(commit)
    return false unless commit

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

  def update_merge_request_metrics!
    return unless environment.update_merge_request_metrics?

    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 <= ?", self.created_at)

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

    # Need to use `map` instead of `select` because MySQL doesn't allow `SELECT`ing from the same table
    # that we're updating.
    merge_request_ids =
      if Gitlab::Database.postgresql?
        merge_requests.select(:id)
      elsif Gitlab::Database.mysql?
        merge_requests.map(&:id)
      end

    MergeRequest::Metrics
      .where(merge_request_id: merge_request_ids, first_deployed_to_production_at: nil)
      .update_all(first_deployed_to_production_at: self.created_at)
  end

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

  def stop_action
    return unless on_stop.present?
    return unless manual_actions

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

  def formatted_deployment_time
    created_at.to_time.in_time_zone.to_s(:medium)
  end

  def has_metrics?
    prometheus_adapter&.can_query?
  end

  def metrics
    return {} unless has_metrics?

    metrics = prometheus_adapter.query(:deployment, self)
    metrics&.merge(deployment_time: created_at.to_i) || {}
  end

  def additional_metrics
    return {} unless has_metrics?

    metrics = prometheus_adapter.query(:additional_metrics_deployment, self)
    metrics&.merge(deployment_time: created_at.to_i) || {}
  end

  private

  def prometheus_adapter
    environment.prometheus_adapter
  end

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