deployment.rb 4.7 KB
Newer Older
1 2
# frozen_string_literal: true

3
class Deployment < ActiveRecord::Base
4
  include AtomicInternalId
S
Shinya Maeda 已提交
5
  include IidRoutes
S
Shinya Maeda 已提交
6
  include AfterCommitQueue
7

8 9
  belongs_to :project, required: true
  belongs_to :environment, required: true
10
  belongs_to :user
11
  belongs_to :deployable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
12

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

K
Kamil Trzcinski 已提交
15 16
  validates :sha, presence: true
  validates :ref, presence: true
17 18 19

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

20
  scope :for_environment, -> (environment) { where(environment_id: environment) }
S
Shinya Maeda 已提交
21 22
  scope :deployed, -> { success.start }
  scope :stopped, -> { success.stop }
23

S
Shinya Maeda 已提交
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
  state_machine :status, initial: :created do
    event :run do
      transition created: :running
    end

    event :succeed do
      transition any - [:success] => :success
    end

    event :drop do
      transition any - [:failed] => :failed
    end

    event :cancel do
      transition any - [:canceled] => :canceled
    end

    before_transition any => [:success, :failed, :canceled] do |deployment|
      deployment.finished_at = Time.now
    end

    after_transition any => :success do |deployment|
      deployment.run_after_commit do
        Deployments::SuccessWorker.perform_async(id)
      end
    end
  end

  enum status: {
    created: 0,
    running: 1,
    success: 2,
    failed: 3,
    canceled: 4
  }

S
Shinya Maeda 已提交
60 61 62 63 64
  enum action: {
    start: 1,
    stop: 2
  }

65 66 67 68 69 70 71 72 73
  def self.last_for_environment(environment)
    ids = self
      .for_environment(environment)
      .select('MAX(id) AS id')
      .group(:environment_id)
      .map(&:id)
    find(ids)
  end

74 75 76 77 78 79 80 81 82
  def commit
    project.commit(sha)
  end

  def commit_title
    commit.try(:title)
  end

  def short_sha
K
Kamil Trzcinski 已提交
83
    Commit.truncate_sha(sha)
84
  end
85 86 87 88

  def last?
    self == environment.last_deployment
  end
89

Z
Zeger-Jan van de Weg 已提交
90
  def create_ref
91
    project.repository.create_ref(ref, ref_path)
92
  end
93

94 95 96 97
  def invalidate_cache
    environment.expire_etag_cache
  end

98
  def manual_actions
99 100 101 102 103
    @manual_actions ||= deployable.try(:other_manual_actions)
  end

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

106
  def includes_commit?(commit)
107 108
    return false unless commit

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

112
  def update_merge_request_metrics!
S
Shinya Maeda 已提交
113
    return unless environment.update_merge_request_metrics? && success?
114

115 116 117
    merge_requests = project.merge_requests
                     .joins(:metrics)
                     .where(target_branch: self.ref, merge_request_metrics: { first_deployed_to_production_at: nil })
S
Shinya Maeda 已提交
118
                     .where("merge_request_metrics.merged_at <= ?", finished_at)
119

120
    if previous_deployment
S
Shinya Maeda 已提交
121
      merge_requests = merge_requests.where("merge_request_metrics.merged_at >= ?", previous_deployment.finished_at)
122
    end
123 124 125 126 127 128 129 130 131 132

    # 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

133 134
    MergeRequest::Metrics
      .where(merge_request_id: merge_request_ids, first_deployed_to_production_at: nil)
S
Shinya Maeda 已提交
135
      .update_all(first_deployed_to_production_at: finished_at)
136 137 138 139
  end

  def previous_deployment
    @previous_deployment ||=
S
Shinya Maeda 已提交
140
      project.deployments.deployed.joins(:environment)
141 142 143
      .where(environments: { name: self.environment.name }, ref: self.ref)
      .where.not(id: self.id)
      .take
144
  end
145

146
  def stop_action
147 148
    return unless on_stop.present?
    return unless manual_actions
149

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

S
Shinya Maeda 已提交
153 154 155 156 157 158 159 160 161 162
  def finished_at
    read_attribute(:finished_at) || legacy_finished_at
  end

  def deployed_at
    return unless success?

    finished_at
  end

Z
Z.J. van de Weg 已提交
163
  def formatted_deployment_time
S
Shinya Maeda 已提交
164
    deployed_at&.to_time&.in_time_zone&.to_s(:medium)
Z
Z.J. van de Weg 已提交
165 166
  end

F
Fatih Acet 已提交
167
  def has_metrics?
168
    prometheus_adapter&.can_query?
F
Fatih Acet 已提交
169 170
  end

P
Pawel Chojnacki 已提交
171
  def metrics
S
Shinya Maeda 已提交
172
    return {} unless has_metrics? && success?
F
Fatih Acet 已提交
173

174
    metrics = prometheus_adapter.query(:deployment, self)
S
Shinya Maeda 已提交
175
    metrics&.merge(deployment_time: finished_at.to_i) || {}
176 177
  end

178
  def additional_metrics
S
Shinya Maeda 已提交
179
    return {} unless has_metrics? && success?
180

181
    metrics = prometheus_adapter.query(:additional_metrics_deployment, self)
S
Shinya Maeda 已提交
182
    metrics&.merge(deployment_time: finished_at.to_i) || {}
183 184
  end

S
Shinya Maeda 已提交
185 186 187 188 189 190 191 192
  def deployed?
    success? && start?
  end

  def stopped?
    success? && stop?
  end

193 194
  private

195 196 197 198
  def prometheus_adapter
    environment.prometheus_adapter
  end

199
  def ref_path
200
    File.join(environment.ref_path, 'deployments', iid.to_s)
201
  end
S
Shinya Maeda 已提交
202 203 204 205

  def legacy_finished_at
    self.created_at if success? && !read_attribute(:finished_at)
  end
206
end