diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/models/ci/build.rb | 39 | ||||
-rw-r--r-- | app/models/concerns/deployable.rb | 29 | ||||
-rw-r--r-- | app/models/deployment.rb | 76 | ||||
-rw-r--r-- | app/models/environment.rb | 4 | ||||
-rw-r--r-- | app/models/environment_status.rb | 6 | ||||
-rw-r--r-- | app/models/project.rb | 2 | ||||
-rw-r--r-- | app/services/create_deployment_service.rb | 74 | ||||
-rw-r--r-- | app/services/update_deployment_service.rb | 53 | ||||
-rw-r--r-- | app/workers/all_queues.yml | 2 | ||||
-rw-r--r-- | app/workers/build_success_worker.rb | 9 | ||||
-rw-r--r-- | app/workers/deployments/success_worker.rb | 17 |
11 files changed, 200 insertions, 111 deletions
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index d7eab57763e..360c9924a7d 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -9,19 +9,18 @@ module Ci include Presentable include Importable include Gitlab::Utils::StrongMemoize + include Deployable belongs_to :project, inverse_of: :builds belongs_to :runner belongs_to :trigger_request belongs_to :erased_by, class_name: 'User' - has_many :deployments, as: :deployable - RUNNER_FEATURES = { upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? } }.freeze - has_one :last_deployment, -> { order('deployments.id DESC') }, as: :deployable, class_name: 'Deployment' + has_one :deployment, as: :deployable, class_name: 'Deployment' has_many :trace_sections, class_name: 'Ci::BuildTraceSection' has_many :trace_chunks, class_name: 'Ci::BuildTraceChunk', foreign_key: :build_id @@ -195,6 +194,8 @@ module Ci end after_transition pending: :running do |build| + build.deployment&.run + build.run_after_commit do BuildHooksWorker.perform_async(id) end @@ -207,14 +208,18 @@ module Ci end after_transition any => [:success] do |build| + build.deployment&.succeed + build.run_after_commit do - BuildSuccessWorker.perform_async(id) PagesWorker.perform_async(:deploy, id) if build.pages_generator? end end before_transition any => [:failed] do |build| next unless build.project + + build.deployment&.drop + next if build.retries_max.zero? if build.retries_count < build.retries_max @@ -233,6 +238,10 @@ module Ci after_transition running: any do |build| Ci::BuildRunnerSession.where(build: build).delete_all end + + after_transition any => [:skipped, :canceled] do |build| + build.deployment&.cancel + end end def ensure_metadata @@ -342,8 +351,12 @@ module Ci self.options.fetch(:environment, {}).fetch(:action, 'start') if self.options end + def has_deployment? + !!self.deployment + end + def outdated_deployment? - success? && !last_deployment.try(:last?) + success? && !deployment.try(:last?) end def depends_on_builds @@ -358,6 +371,10 @@ module Ci user == current_user end + def on_stop + options&.dig(:environment, :on_stop) + end + # A slugified version of the build ref, suitable for inclusion in URLs and # domain names. Rules: # @@ -725,7 +742,7 @@ module Ci if success? return successful_deployment_status - elsif complete? && !success? + elsif failed? return :failed end @@ -742,13 +759,11 @@ module Ci end def successful_deployment_status - if success? && last_deployment&.last? - return :last - elsif success? && last_deployment.present? - return :out_of_date + if deployment&.last? + :last + else + :out_of_date end - - :creating end def each_report(report_types) diff --git a/app/models/concerns/deployable.rb b/app/models/concerns/deployable.rb new file mode 100644 index 00000000000..ba3abf28ab0 --- /dev/null +++ b/app/models/concerns/deployable.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Deployable + extend ActiveSupport::Concern + + included do + after_create :create_deployment + + def create_deployment + return unless has_environment? && !has_deployment? + + environment = project.environments.find_or_create_by( + name: expanded_environment_name + ) + + environment.deployments.create!( + project_id: environment.project_id, + environment: environment, + ref: ref, + tag: tag, + sha: sha, + user: user, + deployable: self, + on_stop: on_stop).tap do |_| + self.reload # Reload relationships + end + end + end +end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 37efbb04fce..54a900a3b85 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -3,6 +3,7 @@ class Deployment < ActiveRecord::Base include AtomicInternalId include IidRoutes + include AfterCommitQueue belongs_to :project, required: true belongs_to :environment, required: true @@ -16,11 +17,44 @@ class Deployment < ActiveRecord::Base delegate :name, to: :environment, prefix: true - after_create :create_ref - after_create :invalidate_cache - scope :for_environment, -> (environment) { where(environment_id: environment) } + 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 + } + def self.last_for_environment(environment) ids = self .for_environment(environment) @@ -69,15 +103,15 @@ class Deployment < ActiveRecord::Base end def update_merge_request_metrics! - return unless environment.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 <= ?", self.created_at) + .where("merge_request_metrics.merged_at <= ?", finished_at) if previous_deployment - merge_requests = merge_requests.where("merge_request_metrics.merged_at >= ?", previous_deployment.created_at) + merge_requests = merge_requests.where("merge_request_metrics.merged_at >= ?", previous_deployment.finished_at) end # Need to use `map` instead of `select` because MySQL doesn't allow `SELECT`ing from the same table @@ -91,7 +125,7 @@ class Deployment < ActiveRecord::Base 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) + .update_all(first_deployed_to_production_at: finished_at) end def previous_deployment @@ -109,8 +143,18 @@ class Deployment < ActiveRecord::Base @stop_action ||= manual_actions.find_by(name: on_stop) end + def finished_at + read_attribute(:finished_at) || legacy_finished_at + end + + def deployed_at + return unless success? + + finished_at + end + def formatted_deployment_time - created_at.to_time.in_time_zone.to_s(:medium) + deployed_at&.to_time&.in_time_zone&.to_s(:medium) end def has_metrics? @@ -118,21 +162,17 @@ class Deployment < ActiveRecord::Base end def metrics - return {} unless has_metrics? + return {} unless has_metrics? && success? metrics = prometheus_adapter.query(:deployment, self) - metrics&.merge(deployment_time: created_at.to_i) || {} + metrics&.merge(deployment_time: finished_at.to_i) || {} end def additional_metrics - return {} unless has_metrics? + return {} unless has_metrics? && success? metrics = prometheus_adapter.query(:additional_metrics_deployment, self) - metrics&.merge(deployment_time: created_at.to_i) || {} - end - - def status - 'success' + metrics&.merge(deployment_time: finished_at.to_i) || {} end private @@ -144,4 +184,8 @@ class Deployment < ActiveRecord::Base def ref_path File.join(environment.ref_path, 'deployments', iid.to_s) end + + def legacy_finished_at + self.created_at if success? && !read_attribute(:finished_at) + end end diff --git a/app/models/environment.rb b/app/models/environment.rb index 1c31c01eb9f..7d104bb0c25 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -8,9 +8,9 @@ class Environment < ActiveRecord::Base belongs_to :project, required: true - has_many :deployments, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :deployments, -> { success }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_one :last_deployment, -> { order('deployments.id DESC') }, class_name: 'Deployment' + has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment' before_validation :nullify_external_url before_validation :generate_slug, if: ->(env) { env.slug.blank? } diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb index a84871f7253..c9bc9c18dfd 100644 --- a/app/models/environment_status.rb +++ b/app/models/environment_status.rb @@ -8,8 +8,8 @@ class EnvironmentStatus delegate :id, to: :environment delegate :name, to: :environment delegate :project, to: :environment - delegate :deployed_at, to: :deployment, allow_nil: true delegate :status, to: :deployment + delegate :deployed_at, to: :deployment def self.for_merge_request(mr, user) build_environments_status(mr, user, mr.head_pipeline) @@ -33,10 +33,6 @@ class EnvironmentStatus end end - def deployed_at - deployment&.created_at - end - def changes return [] if project.route_map_for(sha).nil? diff --git a/app/models/project.rb b/app/models/project.rb index d3b148d0ac0..d5a4ae79c47 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -254,7 +254,7 @@ class Project < ActiveRecord::Base has_many :variables, class_name: 'Ci::Variable' has_many :triggers, class_name: 'Ci::Trigger' has_many :environments - has_many :deployments + has_many :deployments, -> { success } has_many :pipeline_schedules, class_name: 'Ci::PipelineSchedule' has_many :project_deploy_tokens has_many :deploy_tokens, through: :project_deploy_tokens diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb deleted file mode 100644 index bb3f605da28..00000000000 --- a/app/services/create_deployment_service.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -class CreateDeploymentService - attr_reader :job - - delegate :expanded_environment_name, - :variables, - :project, - to: :job - - def initialize(job) - @job = job - end - - def execute - return unless executable? - - ActiveRecord::Base.transaction do - environment.external_url = expanded_environment_url if - expanded_environment_url - - environment.fire_state_event(action) - - break unless environment.save - break if environment.stopped? - - deploy.tap(&:update_merge_request_metrics!) - end - end - - private - - def executable? - project && job.environment.present? && environment - end - - def deploy - project.deployments.create( - environment: environment, - ref: job.ref, - tag: job.tag, - sha: job.sha, - user: job.user, - deployable: job, - on_stop: on_stop) - end - - def environment - @environment ||= job.persisted_environment - end - - def environment_options - @environment_options ||= job.options&.dig(:environment) || {} - end - - def expanded_environment_url - return @expanded_environment_url if defined?(@expanded_environment_url) - - @expanded_environment_url = - ExpandVariables.expand(environment_url, variables) if environment_url - end - - def environment_url - environment_options[:url] - end - - def on_stop - environment_options[:on_stop] - end - - def action - environment_options[:action] || 'start' - end -end diff --git a/app/services/update_deployment_service.rb b/app/services/update_deployment_service.rb new file mode 100644 index 00000000000..aa7fcca1e2a --- /dev/null +++ b/app/services/update_deployment_service.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +class UpdateDeploymentService + attr_reader :deployment + attr_reader :deployable + + delegate :environment, to: :deployment + delegate :variables, to: :deployable + + def initialize(deployment) + @deployment = deployment + @deployable = deployment.deployable + end + + def execute + deployment.create_ref + deployment.invalidate_cache + + ActiveRecord::Base.transaction do + environment.external_url = expanded_environment_url if + expanded_environment_url + + environment.fire_state_event(action) + + break unless environment.save + break if environment.stopped? + + deployment.tap(&:update_merge_request_metrics!) + end + end + + private + + def environment_options + @environment_options ||= deployable.options&.dig(:environment) || {} + end + + def expanded_environment_url + return @expanded_environment_url if defined?(@expanded_environment_url) + return unless environment_url + + @expanded_environment_url = + ExpandVariables.expand(environment_url, variables) + end + + def environment_url + environment_options[:url] + end + + def action + environment_options[:action] || 'start' + end +end diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index a66a6f4c777..953ab95735b 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -73,6 +73,8 @@ - pipeline_processing:update_head_pipeline_for_merge_request - pipeline_processing:ci_build_schedule +- deployment:deployments_success + - repository_check:repository_check_clear - repository_check:repository_check_batch - repository_check:repository_check_single_repository diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index c17608f7378..f3530317090 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -16,7 +16,14 @@ class BuildSuccessWorker private + ## + # Deprecated: + # As of 11.5, we started creating a deployment record when ci_builds record is created. + # Therefore we no longer need to create a deployment, after a build succeeded. + # We're leaving this code for the transition period, but we can remove this code in 11.6. def create_deployment(build) - CreateDeploymentService.new(build).execute + build.create_deployment.try do |deployment| + deployment.succeed + end end end diff --git a/app/workers/deployments/success_worker.rb b/app/workers/deployments/success_worker.rb new file mode 100644 index 00000000000..da517f3fb26 --- /dev/null +++ b/app/workers/deployments/success_worker.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Deployments + class SuccessWorker + include ApplicationWorker + + queue_namespace :deployment + + def perform(deployment_id) + Deployment.find_by_id(deployment_id).try do |deployment| + break unless deployment.success? + + UpdateDeploymentService.new(deployment).execute + end + end + end +end |