Merge branch 'feature/gb/persist-pipeline-stages' into 'master'
Persist stages in the database Closes #26481 See merge request !11790
This commit is contained in:
commit
5c26f4718b
|
@ -99,7 +99,7 @@ class Projects::PipelinesController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def stage
|
def stage
|
||||||
@stage = pipeline.stage(params[:stage])
|
@stage = pipeline.legacy_stage(params[:stage])
|
||||||
return not_found unless @stage
|
return not_found unless @stage
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
module Ci
|
||||||
|
# Currently this is artificial object, constructed dynamically
|
||||||
|
# We should migrate this object to actual database record in the future
|
||||||
|
class LegacyStage
|
||||||
|
include StaticModel
|
||||||
|
|
||||||
|
attr_reader :pipeline, :name
|
||||||
|
|
||||||
|
delegate :project, to: :pipeline
|
||||||
|
|
||||||
|
def initialize(pipeline, name:, status: nil, warnings: nil)
|
||||||
|
@pipeline = pipeline
|
||||||
|
@name = name
|
||||||
|
@status = status
|
||||||
|
@warnings = warnings
|
||||||
|
end
|
||||||
|
|
||||||
|
def groups
|
||||||
|
@groups ||= statuses.ordered.latest
|
||||||
|
.sort_by(&:sortable_name).group_by(&:group_name)
|
||||||
|
.map do |group_name, grouped_statuses|
|
||||||
|
Ci::Group.new(self, name: group_name, jobs: grouped_statuses)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_param
|
||||||
|
name
|
||||||
|
end
|
||||||
|
|
||||||
|
def statuses_count
|
||||||
|
@statuses_count ||= statuses.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def status
|
||||||
|
@status ||= statuses.latest.status
|
||||||
|
end
|
||||||
|
|
||||||
|
def detailed_status(current_user)
|
||||||
|
Gitlab::Ci::Status::Stage::Factory
|
||||||
|
.new(self, current_user)
|
||||||
|
.fabricate!
|
||||||
|
end
|
||||||
|
|
||||||
|
def statuses
|
||||||
|
@statuses ||= pipeline.statuses.where(stage: name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def builds
|
||||||
|
@builds ||= pipeline.builds.where(stage: name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def success?
|
||||||
|
status.to_s == 'success'
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_warnings?
|
||||||
|
if @warnings.is_a?(Integer)
|
||||||
|
@warnings > 0
|
||||||
|
else
|
||||||
|
statuses.latest.failed_but_allowed.any?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,9 +11,7 @@ module Ci
|
||||||
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
|
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
|
||||||
belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
|
belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
|
||||||
|
|
||||||
has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
|
has_many :stages
|
||||||
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
|
|
||||||
|
|
||||||
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
|
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
|
||||||
has_many :builds, foreign_key: :commit_id
|
has_many :builds, foreign_key: :commit_id
|
||||||
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
|
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id
|
||||||
|
@ -28,6 +26,9 @@ module Ci
|
||||||
has_many :manual_actions, -> { latest.manual_actions }, foreign_key: :commit_id, class_name: 'Ci::Build'
|
has_many :manual_actions, -> { latest.manual_actions }, foreign_key: :commit_id, class_name: 'Ci::Build'
|
||||||
has_many :artifacts, -> { latest.with_artifacts_not_expired }, foreign_key: :commit_id, class_name: 'Ci::Build'
|
has_many :artifacts, -> { latest.with_artifacts_not_expired }, foreign_key: :commit_id, class_name: 'Ci::Build'
|
||||||
|
|
||||||
|
has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
|
||||||
|
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
|
||||||
|
|
||||||
delegate :id, to: :project, prefix: true
|
delegate :id, to: :project, prefix: true
|
||||||
|
|
||||||
validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
|
validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
|
||||||
|
@ -162,21 +163,21 @@ module Ci
|
||||||
where.not(duration: nil).sum(:duration)
|
where.not(duration: nil).sum(:duration)
|
||||||
end
|
end
|
||||||
|
|
||||||
def stage(name)
|
|
||||||
stage = Ci::Stage.new(self, name: name)
|
|
||||||
stage unless stage.statuses_count.zero?
|
|
||||||
end
|
|
||||||
|
|
||||||
def stages_count
|
def stages_count
|
||||||
statuses.select(:stage).distinct.count
|
statuses.select(:stage).distinct.count
|
||||||
end
|
end
|
||||||
|
|
||||||
def stages_name
|
def stages_names
|
||||||
statuses.order(:stage_idx).distinct.
|
statuses.order(:stage_idx).distinct.
|
||||||
pluck(:stage, :stage_idx).map(&:first)
|
pluck(:stage, :stage_idx).map(&:first)
|
||||||
end
|
end
|
||||||
|
|
||||||
def stages
|
def legacy_stage(name)
|
||||||
|
stage = Ci::LegacyStage.new(self, name: name)
|
||||||
|
stage unless stage.statuses_count.zero?
|
||||||
|
end
|
||||||
|
|
||||||
|
def legacy_stages
|
||||||
# TODO, this needs refactoring, see gitlab-ce#26481.
|
# TODO, this needs refactoring, see gitlab-ce#26481.
|
||||||
|
|
||||||
stages_query = statuses
|
stages_query = statuses
|
||||||
|
@ -191,7 +192,7 @@ module Ci
|
||||||
.pluck('sg.stage', status_sql, "(#{warnings_sql})")
|
.pluck('sg.stage', status_sql, "(#{warnings_sql})")
|
||||||
|
|
||||||
stages_with_statuses.map do |stage|
|
stages_with_statuses.map do |stage|
|
||||||
Ci::Stage.new(self, Hash[%i[name status warnings].zip(stage)])
|
Ci::LegacyStage.new(self, Hash[%i[name status warnings].zip(stage)])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -291,12 +292,14 @@ module Ci
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def config_builds_attributes
|
def stage_seeds
|
||||||
return [] unless config_processor
|
return [] unless config_processor
|
||||||
|
|
||||||
config_processor.
|
@stage_seeds ||= config_processor.stage_seeds(self)
|
||||||
builds_for_ref(ref, tag?, trigger_requests.first).
|
end
|
||||||
sort_by { |build| build[:stage_idx] }
|
|
||||||
|
def has_stage_seeds?
|
||||||
|
stage_seeds.any?
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_warnings?
|
def has_warnings?
|
||||||
|
@ -304,7 +307,7 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
def config_processor
|
def config_processor
|
||||||
return nil unless ci_yaml_file
|
return unless ci_yaml_file
|
||||||
return @config_processor if defined?(@config_processor)
|
return @config_processor if defined?(@config_processor)
|
||||||
|
|
||||||
@config_processor ||= begin
|
@config_processor ||= begin
|
||||||
|
|
|
@ -1,64 +1,11 @@
|
||||||
module Ci
|
module Ci
|
||||||
# Currently this is artificial object, constructed dynamically
|
class Stage < ActiveRecord::Base
|
||||||
# We should migrate this object to actual database record in the future
|
extend Ci::Model
|
||||||
class Stage
|
|
||||||
include StaticModel
|
|
||||||
|
|
||||||
attr_reader :pipeline, :name
|
belongs_to :project
|
||||||
|
belongs_to :pipeline
|
||||||
|
|
||||||
delegate :project, to: :pipeline
|
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
|
||||||
|
has_many :builds, foreign_key: :commit_id
|
||||||
def initialize(pipeline, name:, status: nil, warnings: nil)
|
|
||||||
@pipeline = pipeline
|
|
||||||
@name = name
|
|
||||||
@status = status
|
|
||||||
@warnings = warnings
|
|
||||||
end
|
|
||||||
|
|
||||||
def groups
|
|
||||||
@groups ||= statuses.ordered.latest
|
|
||||||
.sort_by(&:sortable_name).group_by(&:group_name)
|
|
||||||
.map do |group_name, grouped_statuses|
|
|
||||||
Ci::Group.new(self, name: group_name, jobs: grouped_statuses)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_param
|
|
||||||
name
|
|
||||||
end
|
|
||||||
|
|
||||||
def statuses_count
|
|
||||||
@statuses_count ||= statuses.count
|
|
||||||
end
|
|
||||||
|
|
||||||
def status
|
|
||||||
@status ||= statuses.latest.status
|
|
||||||
end
|
|
||||||
|
|
||||||
def detailed_status(current_user)
|
|
||||||
Gitlab::Ci::Status::Stage::Factory
|
|
||||||
.new(self, current_user)
|
|
||||||
.fabricate!
|
|
||||||
end
|
|
||||||
|
|
||||||
def statuses
|
|
||||||
@statuses ||= pipeline.statuses.where(stage: name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def builds
|
|
||||||
@builds ||= pipeline.builds.where(stage: name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def success?
|
|
||||||
status.to_s == 'success'
|
|
||||||
end
|
|
||||||
|
|
||||||
def has_warnings?
|
|
||||||
if @warnings.is_a?(Integer)
|
|
||||||
@warnings > 0
|
|
||||||
else
|
|
||||||
statuses.latest.failed_but_allowed.any?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,10 @@ class CommitStatus < ActiveRecord::Base
|
||||||
|
|
||||||
self.table_name = 'ci_builds'
|
self.table_name = 'ci_builds'
|
||||||
|
|
||||||
|
belongs_to :user
|
||||||
belongs_to :project
|
belongs_to :project
|
||||||
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
|
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id
|
||||||
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
|
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
|
||||||
belongs_to :user
|
|
||||||
|
|
||||||
delegate :commit, to: :pipeline
|
delegate :commit, to: :pipeline
|
||||||
delegate :sha, :short_sha, to: :pipeline
|
delegate :sha, :short_sha, to: :pipeline
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class PipelineDetailsEntity < PipelineEntity
|
class PipelineDetailsEntity < PipelineEntity
|
||||||
expose :details do
|
expose :details do
|
||||||
expose :stages, using: StageEntity
|
expose :legacy_stages, as: :stages, using: StageEntity
|
||||||
expose :artifacts, using: BuildArtifactEntity
|
expose :artifacts, using: BuildArtifactEntity
|
||||||
expose :manual_actions, using: BuildActionEntity
|
expose :manual_actions, using: BuildActionEntity
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
module Ci
|
|
||||||
class CreatePipelineBuildsService < BaseService
|
|
||||||
attr_reader :pipeline
|
|
||||||
|
|
||||||
def execute(pipeline)
|
|
||||||
@pipeline = pipeline
|
|
||||||
|
|
||||||
new_builds.map do |build_attributes|
|
|
||||||
create_build(build_attributes)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
delegate :project, to: :pipeline
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def create_build(build_attributes)
|
|
||||||
build_attributes = build_attributes.merge(
|
|
||||||
pipeline: pipeline,
|
|
||||||
project: project,
|
|
||||||
ref: pipeline.ref,
|
|
||||||
tag: pipeline.tag,
|
|
||||||
user: current_user,
|
|
||||||
trigger_request: trigger_request
|
|
||||||
)
|
|
||||||
build = pipeline.builds.create(build_attributes)
|
|
||||||
|
|
||||||
# Create the environment before the build starts. This sets its slug and
|
|
||||||
# makes it available as an environment variable
|
|
||||||
project.environments.find_or_create_by(name: build.expanded_environment_name) if
|
|
||||||
build.has_environment?
|
|
||||||
|
|
||||||
build
|
|
||||||
end
|
|
||||||
|
|
||||||
def new_builds
|
|
||||||
@new_builds ||= pipeline.config_builds_attributes.
|
|
||||||
reject { |build| existing_build_names.include?(build[:name]) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def existing_build_names
|
|
||||||
@existing_build_names ||= pipeline.builds.pluck(:name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def trigger_request
|
|
||||||
return @trigger_request if defined?(@trigger_request)
|
|
||||||
|
|
||||||
@trigger_request ||= pipeline.trigger_requests.first
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -43,14 +43,14 @@ module Ci
|
||||||
return pipeline
|
return pipeline
|
||||||
end
|
end
|
||||||
|
|
||||||
unless pipeline.config_builds_attributes.present?
|
unless pipeline.has_stage_seeds?
|
||||||
return error('No builds for this pipeline.')
|
return error('No stages / jobs for this pipeline.')
|
||||||
end
|
end
|
||||||
|
|
||||||
Ci::Pipeline.transaction do
|
Ci::Pipeline.transaction do
|
||||||
update_merge_requests_head_pipeline if pipeline.save
|
update_merge_requests_head_pipeline if pipeline.save
|
||||||
|
|
||||||
Ci::CreatePipelineBuildsService
|
Ci::CreatePipelineStagesService
|
||||||
.new(project, current_user)
|
.new(project, current_user)
|
||||||
.execute(pipeline)
|
.execute(pipeline)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
module Ci
|
||||||
|
class CreatePipelineStagesService < BaseService
|
||||||
|
def execute(pipeline)
|
||||||
|
pipeline.stage_seeds.each do |seed|
|
||||||
|
seed.user = current_user
|
||||||
|
|
||||||
|
seed.create! do |build|
|
||||||
|
##
|
||||||
|
# Create the environment before the build starts. This sets its slug and
|
||||||
|
# makes it available as an environment variable
|
||||||
|
#
|
||||||
|
if build.has_environment?
|
||||||
|
environment_name = build.expanded_environment_name
|
||||||
|
project.environments.find_or_create_by(name: environment_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,7 +1,7 @@
|
||||||
module Ci
|
module Ci
|
||||||
class RetryBuildService < ::BaseService
|
class RetryBuildService < ::BaseService
|
||||||
CLONE_ACCESSORS = %i[pipeline project ref tag options commands name
|
CLONE_ACCESSORS = %i[pipeline project ref tag options commands name
|
||||||
allow_failure stage stage_idx trigger_request
|
allow_failure stage_id stage stage_idx trigger_request
|
||||||
yaml_variables when environment coverage_regex
|
yaml_variables when environment coverage_regex
|
||||||
description tag_list].freeze
|
description tag_list].freeze
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,8 @@
|
||||||
Pipeline
|
Pipeline
|
||||||
= link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id)
|
= link_to "##{last_pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, last_pipeline.id)
|
||||||
= ci_label_for_status(last_pipeline.status)
|
= ci_label_for_status(last_pipeline.status)
|
||||||
- if last_pipeline.stages.any?
|
- if last_pipeline.stages_count.nonzero?
|
||||||
with #{"stage".pluralize(last_pipeline.stages.count)}
|
with #{"stage".pluralize(last_pipeline.stages_count)}
|
||||||
.mr-widget-pipeline-graph
|
.mr-widget-pipeline-graph
|
||||||
= render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph'
|
= render 'shared/mini_pipeline_graph', pipeline: last_pipeline, klass: 'js-commit-pipeline-graph'
|
||||||
in
|
in
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
%span.stage-selection More
|
%span.stage-selection More
|
||||||
= icon('chevron-down')
|
= icon('chevron-down')
|
||||||
%ul.dropdown-menu
|
%ul.dropdown-menu
|
||||||
- @build.pipeline.stages.each do |stage|
|
- @build.pipeline.legacy_stages.each do |stage|
|
||||||
%li
|
%li
|
||||||
%a.stage-item= stage.name
|
%a.stage-item= stage.name
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
%th
|
%th
|
||||||
%th Coverage
|
%th Coverage
|
||||||
%th
|
%th
|
||||||
= render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage
|
= render partial: "projects/stage/stage", collection: pipeline.legacy_stages, as: :stage
|
||||||
- if failed_builds.present?
|
- if failed_builds.present?
|
||||||
#js-tab-failures.build-failures.tab-pane
|
#js-tab-failures.build-failures.tab-pane
|
||||||
- failed_builds.each_with_index do |build, index|
|
- failed_builds.each_with_index do |build, index|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
.stage-cell
|
.stage-cell
|
||||||
- pipeline.stages.each do |stage|
|
- pipeline.legacy_stages.each do |stage|
|
||||||
- if stage.status
|
- if stage.status
|
||||||
- detailed_status = stage.detailed_status(current_user)
|
- detailed_status = stage.detailed_status(current_user)
|
||||||
- icon_status = "#{detailed_status.icon}_borderless"
|
- icon_status = "#{detailed_status.icon}_borderless"
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: Persist pipeline stages in the database
|
||||||
|
merge_request: 11790
|
||||||
|
author:
|
|
@ -50,10 +50,23 @@ module Ci
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def stage_seeds(pipeline)
|
||||||
|
trigger_request = pipeline.trigger_requests.first
|
||||||
|
|
||||||
|
seeds = @stages.uniq.map do |stage|
|
||||||
|
builds = builds_for_stage_and_ref(
|
||||||
|
stage, pipeline.ref, pipeline.tag?, trigger_request)
|
||||||
|
|
||||||
|
Gitlab::Ci::Stage::Seed.new(pipeline, stage, builds) if builds.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
seeds.compact
|
||||||
|
end
|
||||||
|
|
||||||
def build_attributes(name)
|
def build_attributes(name)
|
||||||
job = @jobs[name.to_sym] || {}
|
job = @jobs[name.to_sym] || {}
|
||||||
{
|
|
||||||
stage_idx: @stages.index(job[:stage]),
|
{ stage_idx: @stages.index(job[:stage]),
|
||||||
stage: job[:stage],
|
stage: job[:stage],
|
||||||
commands: job[:commands],
|
commands: job[:commands],
|
||||||
tag_list: job[:tags] || [],
|
tag_list: job[:tags] || [],
|
||||||
|
@ -71,8 +84,7 @@ module Ci
|
||||||
dependencies: job[:dependencies],
|
dependencies: job[:dependencies],
|
||||||
after_script: job[:after_script],
|
after_script: job[:after_script],
|
||||||
environment: job[:environment]
|
environment: job[:environment]
|
||||||
}.compact
|
}.compact }
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.validation_message(content)
|
def self.validation_message(content)
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
module Gitlab
|
||||||
|
module Ci
|
||||||
|
module Stage
|
||||||
|
class Seed
|
||||||
|
attr_reader :pipeline
|
||||||
|
delegate :project, to: :pipeline
|
||||||
|
|
||||||
|
def initialize(pipeline, stage, jobs)
|
||||||
|
@pipeline = pipeline
|
||||||
|
@stage = { name: stage }
|
||||||
|
@jobs = jobs.to_a.dup
|
||||||
|
end
|
||||||
|
|
||||||
|
def user=(current_user)
|
||||||
|
@jobs.map! do |attributes|
|
||||||
|
attributes.merge(user: current_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def stage
|
||||||
|
@stage.merge(project: project)
|
||||||
|
end
|
||||||
|
|
||||||
|
def builds
|
||||||
|
trigger = pipeline.trigger_requests.first
|
||||||
|
|
||||||
|
@jobs.map do |attributes|
|
||||||
|
attributes.merge(project: project,
|
||||||
|
ref: pipeline.ref,
|
||||||
|
tag: pipeline.tag,
|
||||||
|
trigger_request: trigger)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create!
|
||||||
|
pipeline.stages.create!(stage).tap do |stage|
|
||||||
|
builds_attributes = builds.map do |attributes|
|
||||||
|
attributes.merge(stage_id: stage.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline.builds.create!(builds_attributes).each do |build|
|
||||||
|
yield build if block_given?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -22,7 +22,7 @@ module Gitlab
|
||||||
sha: pipeline.sha,
|
sha: pipeline.sha,
|
||||||
before_sha: pipeline.before_sha,
|
before_sha: pipeline.before_sha,
|
||||||
status: pipeline.status,
|
status: pipeline.status,
|
||||||
stages: pipeline.stages_name,
|
stages: pipeline.stages_names,
|
||||||
created_at: pipeline.created_at,
|
created_at: pipeline.created_at,
|
||||||
finished_at: pipeline.finished_at,
|
finished_at: pipeline.finished_at,
|
||||||
duration: pipeline.duration
|
duration: pipeline.duration
|
||||||
|
|
|
@ -38,6 +38,7 @@ project_tree:
|
||||||
- notes:
|
- notes:
|
||||||
- :author
|
- :author
|
||||||
- :events
|
- :events
|
||||||
|
- :stages
|
||||||
- :statuses
|
- :statuses
|
||||||
- :triggers
|
- :triggers
|
||||||
- :pipeline_schedules
|
- :pipeline_schedules
|
||||||
|
|
|
@ -3,6 +3,7 @@ module Gitlab
|
||||||
class RelationFactory
|
class RelationFactory
|
||||||
OVERRIDES = { snippets: :project_snippets,
|
OVERRIDES = { snippets: :project_snippets,
|
||||||
pipelines: 'Ci::Pipeline',
|
pipelines: 'Ci::Pipeline',
|
||||||
|
stages: 'Ci::Stage',
|
||||||
statuses: 'commit_status',
|
statuses: 'commit_status',
|
||||||
triggers: 'Ci::Trigger',
|
triggers: 'Ci::Trigger',
|
||||||
pipeline_schedules: 'Ci::PipelineSchedule',
|
pipeline_schedules: 'Ci::PipelineSchedule',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
FactoryGirl.define do
|
FactoryGirl.define do
|
||||||
factory :ci_stage, class: Ci::Stage do
|
factory :ci_stage, class: Ci::LegacyStage do
|
||||||
skip_create
|
skip_create
|
||||||
|
|
||||||
transient do
|
transient do
|
||||||
|
@ -10,7 +10,9 @@ FactoryGirl.define do
|
||||||
end
|
end
|
||||||
|
|
||||||
initialize_with do
|
initialize_with do
|
||||||
Ci::Stage.new(pipeline, name: name, status: status, warnings: warnings)
|
Ci::LegacyStage.new(pipeline, name: name,
|
||||||
|
status: status,
|
||||||
|
warnings: warnings)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
module Ci
|
module Ci
|
||||||
describe GitlabCiYamlProcessor, lib: true do
|
describe GitlabCiYamlProcessor, :lib do
|
||||||
|
subject { described_class.new(config, path) }
|
||||||
let(:path) { 'path' }
|
let(:path) { 'path' }
|
||||||
|
|
||||||
describe 'our current .gitlab-ci.yml' do
|
describe 'our current .gitlab-ci.yml' do
|
||||||
|
@ -82,6 +83,48 @@ module Ci
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#stage_seeds' do
|
||||||
|
context 'when no refs policy is specified' do
|
||||||
|
let(:config) do
|
||||||
|
YAML.dump(production: { stage: 'deploy', script: 'cap prod' },
|
||||||
|
rspec: { stage: 'test', script: 'rspec' },
|
||||||
|
spinach: { stage: 'test', script: 'spinach' })
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:pipeline) { create(:ci_empty_pipeline) }
|
||||||
|
|
||||||
|
it 'correctly fabricates a stage seeds object' do
|
||||||
|
seeds = subject.stage_seeds(pipeline)
|
||||||
|
|
||||||
|
expect(seeds.size).to eq 2
|
||||||
|
expect(seeds.first.stage[:name]).to eq 'test'
|
||||||
|
expect(seeds.second.stage[:name]).to eq 'deploy'
|
||||||
|
expect(seeds.first.builds.dig(0, :name)).to eq 'rspec'
|
||||||
|
expect(seeds.first.builds.dig(1, :name)).to eq 'spinach'
|
||||||
|
expect(seeds.second.builds.dig(0, :name)).to eq 'production'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when refs policy is specified' do
|
||||||
|
let(:config) do
|
||||||
|
YAML.dump(production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
|
||||||
|
spinach: { stage: 'test', script: 'spinach', only: ['tags'] })
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:pipeline) do
|
||||||
|
create(:ci_empty_pipeline, ref: 'feature', tag: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns stage seeds only assigned to master to master' do
|
||||||
|
seeds = subject.stage_seeds(pipeline)
|
||||||
|
|
||||||
|
expect(seeds.size).to eq 1
|
||||||
|
expect(seeds.first.stage[:name]).to eq 'test'
|
||||||
|
expect(seeds.first.builds.dig(0, :name)).to eq 'spinach'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#builds_for_ref" do
|
describe "#builds_for_ref" do
|
||||||
let(:type) { 'test' }
|
let(:type) { 'test' }
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Gitlab::Ci::Stage::Seed do
|
||||||
|
let(:pipeline) { create(:ci_empty_pipeline) }
|
||||||
|
|
||||||
|
let(:builds) do
|
||||||
|
[{ name: 'rspec' }, { name: 'spinach' }]
|
||||||
|
end
|
||||||
|
|
||||||
|
subject do
|
||||||
|
described_class.new(pipeline, 'test', builds)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#stage' do
|
||||||
|
it 'returns hash attributes of a stage' do
|
||||||
|
expect(subject.stage).to be_a Hash
|
||||||
|
expect(subject.stage).to include(:name, :project)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#builds' do
|
||||||
|
it 'returns hash attributes of all builds' do
|
||||||
|
expect(subject.builds.size).to eq 2
|
||||||
|
expect(subject.builds).to all(include(ref: 'master'))
|
||||||
|
expect(subject.builds).to all(include(tag: false))
|
||||||
|
expect(subject.builds).to all(include(project: pipeline.project))
|
||||||
|
expect(subject.builds)
|
||||||
|
.to all(include(trigger_request: pipeline.trigger_requests.first))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#user=' do
|
||||||
|
let(:user) { build(:user) }
|
||||||
|
|
||||||
|
it 'assignes relevant pipeline attributes' do
|
||||||
|
subject.user = user
|
||||||
|
|
||||||
|
expect(subject.builds).to all(include(user: user))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#create!' do
|
||||||
|
it 'creates all stages and builds' do
|
||||||
|
subject.create!
|
||||||
|
|
||||||
|
expect(pipeline.reload.stages.count).to eq 1
|
||||||
|
expect(pipeline.reload.builds.count).to eq 2
|
||||||
|
expect(pipeline.builds).to all(satisfy { |job| job.stage_id.present? })
|
||||||
|
expect(pipeline.builds).to all(satisfy { |job| job.pipeline.present? })
|
||||||
|
expect(pipeline.builds).to all(satisfy { |job| job.project.present? })
|
||||||
|
expect(pipeline.stages)
|
||||||
|
.to all(satisfy { |stage| stage.pipeline.present? })
|
||||||
|
expect(pipeline.stages)
|
||||||
|
.to all(satisfy { |stage| stage.project.present? })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -91,6 +91,7 @@ merge_request_diff:
|
||||||
pipelines:
|
pipelines:
|
||||||
- project
|
- project
|
||||||
- user
|
- user
|
||||||
|
- stages
|
||||||
- statuses
|
- statuses
|
||||||
- builds
|
- builds
|
||||||
- trigger_requests
|
- trigger_requests
|
||||||
|
@ -104,9 +105,15 @@ pipelines:
|
||||||
- artifacts
|
- artifacts
|
||||||
- pipeline_schedule
|
- pipeline_schedule
|
||||||
- merge_requests
|
- merge_requests
|
||||||
|
stages:
|
||||||
|
- project
|
||||||
|
- pipeline
|
||||||
|
- statuses
|
||||||
|
- builds
|
||||||
statuses:
|
statuses:
|
||||||
- project
|
- project
|
||||||
- pipeline
|
- pipeline
|
||||||
|
- stage
|
||||||
- user
|
- user
|
||||||
- auto_canceled_by
|
- auto_canceled_by
|
||||||
variables:
|
variables:
|
||||||
|
|
|
@ -175,6 +175,7 @@ MergeRequestDiff:
|
||||||
Ci::Pipeline:
|
Ci::Pipeline:
|
||||||
- id
|
- id
|
||||||
- project_id
|
- project_id
|
||||||
|
- source
|
||||||
- ref
|
- ref
|
||||||
- sha
|
- sha
|
||||||
- before_sha
|
- before_sha
|
||||||
|
@ -192,7 +193,13 @@ Ci::Pipeline:
|
||||||
- lock_version
|
- lock_version
|
||||||
- auto_canceled_by_id
|
- auto_canceled_by_id
|
||||||
- pipeline_schedule_id
|
- pipeline_schedule_id
|
||||||
- source
|
Ci::Stage:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
- project_id
|
||||||
|
- pipeline_id
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
CommitStatus:
|
CommitStatus:
|
||||||
- id
|
- id
|
||||||
- project_id
|
- project_id
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Ci::Stage, models: true do
|
describe Ci::LegacyStage, :models do
|
||||||
let(:stage) { build(:ci_stage) }
|
let(:stage) { build(:ci_stage) }
|
||||||
let(:pipeline) { stage.pipeline }
|
let(:pipeline) { stage.pipeline }
|
||||||
let(:stage_name) { stage.name }
|
let(:stage_name) { stage.name }
|
|
@ -224,8 +224,19 @@ describe Ci::Pipeline, models: true do
|
||||||
status: 'success')
|
status: 'success')
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#stages' do
|
describe '#stage_seeds' do
|
||||||
subject { pipeline.stages }
|
let(:pipeline) do
|
||||||
|
create(:ci_pipeline, config: { rspec: { script: 'rake' } })
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns preseeded stage seeds object' do
|
||||||
|
expect(pipeline.stage_seeds).to all(be_a Gitlab::Ci::Stage::Seed)
|
||||||
|
expect(pipeline.stage_seeds.count).to eq 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#legacy_stages' do
|
||||||
|
subject { pipeline.legacy_stages }
|
||||||
|
|
||||||
context 'stages list' do
|
context 'stages list' do
|
||||||
it 'returns ordered list of stages' do
|
it 'returns ordered list of stages' do
|
||||||
|
@ -274,7 +285,7 @@ describe Ci::Pipeline, models: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'populates stage with correct number of warnings' do
|
it 'populates stage with correct number of warnings' do
|
||||||
deploy_stage = pipeline.stages.third
|
deploy_stage = pipeline.legacy_stages.third
|
||||||
|
|
||||||
expect(deploy_stage).not_to receive(:statuses)
|
expect(deploy_stage).not_to receive(:statuses)
|
||||||
expect(deploy_stage).to have_warnings
|
expect(deploy_stage).to have_warnings
|
||||||
|
@ -288,22 +299,22 @@ describe Ci::Pipeline, models: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#stages_name' do
|
describe '#stages_names' do
|
||||||
it 'returns a valid names of stages' do
|
it 'returns a valid names of stages' do
|
||||||
expect(pipeline.stages_name).to eq(%w(build test deploy))
|
expect(pipeline.stages_names).to eq(%w(build test deploy))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#stage' do
|
describe '#legacy_stage' do
|
||||||
subject { pipeline.stage('test') }
|
subject { pipeline.legacy_stage('test') }
|
||||||
|
|
||||||
context 'with status in stage' do
|
context 'with status in stage' do
|
||||||
before do
|
before do
|
||||||
create(:commit_status, pipeline: pipeline, stage: 'test')
|
create(:commit_status, pipeline: pipeline, stage: 'test')
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect(subject).to be_a Ci::Stage }
|
it { expect(subject).to be_a Ci::LegacyStage }
|
||||||
it { expect(subject.name).to eq 'test' }
|
it { expect(subject.name).to eq 'test' }
|
||||||
it { expect(subject.statuses).not_to be_empty }
|
it { expect(subject.statuses).not_to be_empty }
|
||||||
end
|
end
|
||||||
|
@ -524,6 +535,20 @@ describe Ci::Pipeline, models: true do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#has_stage_seeds?' do
|
||||||
|
context 'when pipeline has stage seeds' do
|
||||||
|
subject { build(:ci_pipeline_with_one_job) }
|
||||||
|
|
||||||
|
it { is_expected.to have_stage_seeds }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when pipeline does not have stage seeds' do
|
||||||
|
subject { create(:ci_pipeline_without_jobs) }
|
||||||
|
|
||||||
|
it { is_expected.not_to have_stage_seeds }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#has_warnings?' do
|
describe '#has_warnings?' do
|
||||||
subject { pipeline.has_warnings? }
|
subject { pipeline.has_warnings? }
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Ci::CreatePipelineService, services: true do
|
describe Ci::CreatePipelineService, :services do
|
||||||
let(:project) { create(:project, :repository) }
|
let(:project) { create(:project, :repository) }
|
||||||
let(:user) { create(:admin) }
|
let(:user) { create(:admin) }
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ describe Ci::CreatePipelineService, services: true do
|
||||||
it 'creates a pipeline' do
|
it 'creates a pipeline' do
|
||||||
expect(pipeline).to be_kind_of(Ci::Pipeline)
|
expect(pipeline).to be_kind_of(Ci::Pipeline)
|
||||||
expect(pipeline).to be_valid
|
expect(pipeline).to be_valid
|
||||||
|
expect(pipeline).to be_persisted
|
||||||
expect(pipeline).to be_push
|
expect(pipeline).to be_push
|
||||||
expect(pipeline).to eq(project.pipelines.last)
|
expect(pipeline).to eq(project.pipelines.last)
|
||||||
expect(pipeline).to have_attributes(user: user)
|
expect(pipeline).to have_attributes(user: user)
|
||||||
|
|
|
@ -18,20 +18,31 @@ describe Ci::RetryBuildService, :services do
|
||||||
updated_at started_at finished_at queued_at erased_by
|
updated_at started_at finished_at queued_at erased_by
|
||||||
erased_at auto_canceled_by].freeze
|
erased_at auto_canceled_by].freeze
|
||||||
|
|
||||||
# TODO, move stage_id accessor to CLONE_ACCESSOR in a follow-up MR.
|
|
||||||
IGNORE_ACCESSORS =
|
IGNORE_ACCESSORS =
|
||||||
%i[type lock_version target_url base_tags
|
%i[type lock_version target_url base_tags
|
||||||
commit_id deployments erased_by_id last_deployment project_id
|
commit_id deployments erased_by_id last_deployment project_id
|
||||||
runner_id tag_taggings taggings tags trigger_request_id
|
runner_id tag_taggings taggings tags trigger_request_id
|
||||||
user_id auto_canceled_by_id retried stage_id].freeze
|
user_id auto_canceled_by_id retried].freeze
|
||||||
|
|
||||||
shared_examples 'build duplication' do
|
shared_examples 'build duplication' do
|
||||||
|
let(:stage) do
|
||||||
|
# TODO, we still do not have factory for new stages, we will need to
|
||||||
|
# switch existing factory to persist stages, instead of using LegacyStage
|
||||||
|
#
|
||||||
|
Ci::Stage.create!(project: project, pipeline: pipeline, name: 'test')
|
||||||
|
end
|
||||||
|
|
||||||
let(:build) do
|
let(:build) do
|
||||||
create(:ci_build, :failed, :artifacts_expired, :erased,
|
create(:ci_build, :failed, :artifacts_expired, :erased,
|
||||||
:queued, :coverage, :tags, :allowed_to_fail, :on_tag,
|
:queued, :coverage, :tags, :allowed_to_fail, :on_tag,
|
||||||
:teardown_environment, :triggered, :trace,
|
:triggered, :trace, :teardown_environment,
|
||||||
description: 'some build', pipeline: pipeline,
|
description: 'my-job', stage: 'test', pipeline: pipeline,
|
||||||
auto_canceled_by: create(:ci_empty_pipeline))
|
auto_canceled_by: create(:ci_empty_pipeline)) do |build|
|
||||||
|
##
|
||||||
|
# TODO, workaround for FactoryGirl limitation when having both
|
||||||
|
# stage (text) and stage_id (integer) columns in the table.
|
||||||
|
build.stage_id = stage.id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'clone accessors' do
|
describe 'clone accessors' do
|
||||||
|
|
Loading…
Reference in New Issue