Commit 97b7355e authored by Etienne Baqué's avatar Etienne Baqué Committed by Shinya Maeda

Added has_valid_deployment? to Ci::Build

Added method to check whether a deployment is valid.
Used method as check to drop a build.
Added tests for this new method.
parent 0a8d441b
...@@ -18,6 +18,7 @@ module CommitStatusEnums ...@@ -18,6 +18,7 @@ module CommitStatusEnums
unmet_prerequisites: 10, unmet_prerequisites: 10,
scheduler_failure: 11, scheduler_failure: 11,
data_integrity_failure: 12, data_integrity_failure: 12,
forward_deployment_failure: 13,
insufficient_bridge_permissions: 1_001, insufficient_bridge_permissions: 1_001,
downstream_bridge_project_not_found: 1_002, downstream_bridge_project_not_found: 1_002,
invalid_bridge_trigger: 1_003, invalid_bridge_trigger: 1_003,
......
...@@ -41,6 +41,9 @@ class Deployment < ApplicationRecord ...@@ -41,6 +41,9 @@ class Deployment < ApplicationRecord
scope :visible, -> { where(status: %i[running success failed canceled]) } scope :visible, -> { where(status: %i[running success failed canceled]) }
scope :stoppable, -> { where.not(on_stop: nil).where.not(deployable_id: nil).success } scope :stoppable, -> { where.not(on_stop: nil).where.not(deployable_id: nil).success }
scope :active, -> { where(status: %i[created running]) }
scope :older_than, -> (deployment) { where('id < ?', deployment.id) }
scope :with_deployable, -> { includes(:deployable).where('deployable_id IS NOT NULL') }
state_machine :status, initial: :created do state_machine :status, initial: :created do
event :run do event :run do
...@@ -74,6 +77,14 @@ class Deployment < ApplicationRecord ...@@ -74,6 +77,14 @@ class Deployment < ApplicationRecord
Deployments::FinishedWorker.perform_async(id) Deployments::FinishedWorker.perform_async(id)
end end
end end
after_transition any => :running do |deployment|
next unless deployment.project.forward_deployment_enabled?
deployment.run_after_commit do
Deployments::ForwardDeploymentWorker.perform_async(id)
end
end
end end
enum status: { enum status: {
......
...@@ -12,6 +12,7 @@ class Environment < ApplicationRecord ...@@ -12,6 +12,7 @@ class Environment < ApplicationRecord
has_many :deployments, -> { visible }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :deployments, -> { visible }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :successful_deployments, -> { success }, class_name: 'Deployment' has_many :successful_deployments, -> { success }, class_name: 'Deployment'
has_many :active_deployments, -> { active }, class_name: 'Deployment'
has_many :prometheus_alerts, inverse_of: :environment has_many :prometheus_alerts, inverse_of: :environment
has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment' has_one :last_deployment, -> { success.order('deployments.id DESC') }, class_name: 'Deployment'
......
...@@ -343,6 +343,7 @@ class Project < ApplicationRecord ...@@ -343,6 +343,7 @@ class Project < ApplicationRecord
delegate :last_pipeline, to: :commit, allow_nil: true delegate :last_pipeline, to: :commit, allow_nil: true
delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true delegate :external_dashboard_url, to: :metrics_setting, allow_nil: true, prefix: true
delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci delegate :default_git_depth, :default_git_depth=, to: :ci_cd_settings, prefix: :ci
delegate :forward_deployment_enabled, :forward_deployment_enabled=, :forward_deployment_enabled?, to: :ci_cd_settings
# Validations # Validations
validates :creator, presence: true, on: :create validates :creator, presence: true, on: :create
......
...@@ -18,6 +18,8 @@ class ProjectCiCdSetting < ApplicationRecord ...@@ -18,6 +18,8 @@ class ProjectCiCdSetting < ApplicationRecord
}, },
allow_nil: true allow_nil: true
default_value_for :forward_deployment_enabled, true
def self.available? def self.available?
@available ||= @available ||=
ActiveRecord::Migrator.current_version >= MINIMUM_SCHEMA_VERSION ActiveRecord::Migrator.current_version >= MINIMUM_SCHEMA_VERSION
...@@ -28,6 +30,10 @@ class ProjectCiCdSetting < ApplicationRecord ...@@ -28,6 +30,10 @@ class ProjectCiCdSetting < ApplicationRecord
super super
end end
def forward_deployment_enabled?
super && ::Feature.enabled?(:forward_deployment_enabled, project)
end
private private
def set_default_git_depth def set_default_git_depth
......
...@@ -14,6 +14,7 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated ...@@ -14,6 +14,7 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
unmet_prerequisites: 'The job failed to complete prerequisite tasks', unmet_prerequisites: 'The job failed to complete prerequisite tasks',
scheduler_failure: 'The scheduler failed to assign job to the runner, please try again or contact system administrator', scheduler_failure: 'The scheduler failed to assign job to the runner, please try again or contact system administrator',
data_integrity_failure: 'There has been a structural integrity problem detected, please contact system administrator', data_integrity_failure: 'There has been a structural integrity problem detected, please contact system administrator',
forward_deployment_failure: 'The deployment job is older than the previously succeeded deployment job, and therefore cannot be run',
invalid_bridge_trigger: 'This job could not be executed because downstream pipeline trigger definition is invalid', invalid_bridge_trigger: 'This job could not be executed because downstream pipeline trigger definition is invalid',
downstream_bridge_project_not_found: 'This job could not be executed because downstream bridge project could not be found', downstream_bridge_project_not_found: 'This job could not be executed because downstream bridge project could not be found',
insufficient_bridge_permissions: 'This job could not be executed because of insufficient permissions to create a downstream pipeline', insufficient_bridge_permissions: 'This job could not be executed because of insufficient permissions to create a downstream pipeline',
......
# frozen_string_literal: true
module Deployments
class OlderDeploymentsDropService
attr_reader :deployment
def initialize(deployment_id)
@deployment = Deployment.find_by_id(deployment_id)
end
def execute
return unless @deployment&.running?
older_deployments.find_each do |older_deployment|
older_deployment.deployable&.drop!(:forward_deployment_failure)
rescue => e
Gitlab::ErrorTracking.track_exception(e, subject_id: @deployment.id, deployment_id: older_deployment.id)
end
end
private
def older_deployments
@deployment
.environment
.active_deployments
.older_than(@deployment)
.with_deployable
end
end
end
...@@ -225,6 +225,12 @@ ...@@ -225,6 +225,12 @@
:latency_sensitive: :latency_sensitive:
:resource_boundary: :cpu :resource_boundary: :cpu
:weight: 3 :weight: 3
- :name: deployment:deployments_forward_deployment
:feature_category: :continuous_delivery
:has_external_dependencies:
:latency_sensitive:
:resource_boundary: :unknown
:weight: 3
- :name: deployment:deployments_success - :name: deployment:deployments_success
:feature_category: :continuous_delivery :feature_category: :continuous_delivery
:has_external_dependencies: :has_external_dependencies:
......
# frozen_string_literal: true
module Deployments
class ForwardDeploymentWorker
include ApplicationWorker
queue_namespace :deployment
feature_category :continuous_delivery
def perform(deployment_id)
Deployments::OlderDeploymentsDropService.new(deployment_id).execute
end
end
end
---
title: Allow to deploy only forward deployments
merge_request: 22959
author:
type: changed
# frozen_string_literal: true
class AddRestrictDeploymentOrderToProjectCiCdSettings < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
add_column :project_ci_cd_settings, :forward_deployment_enabled, :boolean
end
end
...@@ -3163,6 +3163,7 @@ ActiveRecord::Schema.define(version: 2020_02_13_204737) do ...@@ -3163,6 +3163,7 @@ ActiveRecord::Schema.define(version: 2020_02_13_204737) do
t.boolean "group_runners_enabled", default: true, null: false t.boolean "group_runners_enabled", default: true, null: false
t.boolean "merge_pipelines_enabled" t.boolean "merge_pipelines_enabled"
t.integer "default_git_depth" t.integer "default_git_depth"
t.boolean "forward_deployment_enabled"
t.index ["project_id"], name: "index_project_ci_cd_settings_on_project_id", unique: true t.index ["project_id"], name: "index_project_ci_cd_settings_on_project_id", unique: true
end end
......
...@@ -19,6 +19,7 @@ module Gitlab ...@@ -19,6 +19,7 @@ module Gitlab
unmet_prerequisites: 'unmet prerequisites', unmet_prerequisites: 'unmet prerequisites',
scheduler_failure: 'scheduler failure', scheduler_failure: 'scheduler failure',
data_integrity_failure: 'data integrity failure', data_integrity_failure: 'data integrity failure',
forward_deployment_failure: 'forward deployment failure',
invalid_bridge_trigger: 'downstream pipeline trigger definition is invalid', invalid_bridge_trigger: 'downstream pipeline trigger definition is invalid',
downstream_bridge_project_not_found: 'downstream project could not be found', downstream_bridge_project_not_found: 'downstream project could not be found',
insufficient_bridge_permissions: 'no permissions to trigger downstream pipeline', insufficient_bridge_permissions: 'no permissions to trigger downstream pipeline',
......
...@@ -37,6 +37,7 @@ FactoryBot.define do ...@@ -37,6 +37,7 @@ FactoryBot.define do
group_runners_enabled { nil } group_runners_enabled { nil }
import_status { nil } import_status { nil }
import_jid { nil } import_jid { nil }
forward_deployment_enabled { nil }
end end
after(:create) do |project, evaluator| after(:create) do |project, evaluator|
......
...@@ -281,6 +281,45 @@ describe Deployment do ...@@ -281,6 +281,45 @@ describe Deployment do
expect(last_deployments).to match_array(deployments.last(2)) expect(last_deployments).to match_array(deployments.last(2))
end end
end end
describe 'active' do
subject { described_class.active }
it 'retrieves the active deployments' do
deployment1 = create(:deployment, status: :created )
deployment2 = create(:deployment, status: :running )
create(:deployment, status: :failed )
create(:deployment, status: :canceled )
is_expected.to contain_exactly(deployment1, deployment2)
end
end
describe 'older_than' do
let(:deployment) { create(:deployment) }
subject { described_class.older_than(deployment) }
it 'retrives the correct older deployments' do
older_deployment1 = create(:deployment)
older_deployment2 = create(:deployment)
deployment
create(:deployment)
is_expected.to contain_exactly(older_deployment1, older_deployment2)
end
end
describe 'with_deployable' do
subject { described_class.with_deployable }
it 'retrieves deployments with deployable builds' do
with_deployable = create(:deployment)
create(:deployment, deployable: nil)
is_expected.to contain_exactly(with_deployable)
end
end
end end
describe '#includes_commit?' do describe '#includes_commit?' do
......
...@@ -32,6 +32,12 @@ describe ProjectCiCdSetting do ...@@ -32,6 +32,12 @@ describe ProjectCiCdSetting do
end end
end end
describe '#forward_deployment_enabled' do
it 'is true by default' do
expect(described_class.new.forward_deployment_enabled).to be_truthy
end
end
describe '#default_git_depth' do describe '#default_git_depth' do
let(:default_value) { described_class::DEFAULT_GIT_DEPTH } let(:default_value) { described_class::DEFAULT_GIT_DEPTH }
......
# frozen_string_literal: true
require 'spec_helper'
describe Deployments::OlderDeploymentsDropService do
let(:environment) { create(:environment) }
let(:deployment) { create(:deployment, environment: environment) }
let(:service) { described_class.new(deployment) }
describe '#execute' do
subject { service.execute }
shared_examples 'it does not drop any build' do
it do
expect { subject }.to not_change(Ci::Build.failed, :count)
end
end
context 'when deployment is nil' do
let(:deployment) { nil }
it_behaves_like 'it does not drop any build'
end
context 'when a deployment is passed in' do
context 'and there is no active deployment for the related environment' do
let(:deployment) { create(:deployment, :canceled, environment: environment) }
let(:deployment2) { create(:deployment, :canceled, environment: environment) }
before do
deployment
deployment2
end
it_behaves_like 'it does not drop any build'
end
context 'and there are active deployment for the related environment' do
let(:deployment) { create(:deployment, :running, environment: environment) }
let(:deployment2) { create(:deployment, :running, environment: environment) }
context 'and there is no older deployment than "deployment"' do
before do
deployment
deployment2
end
it_behaves_like 'it does not drop any build'
end
context 'and there is an older deployment than "deployment"' do
let(:older_deployment) { create(:deployment, :running, environment: environment) }
before do
older_deployment
deployment
deployment2
end
it 'drops that older deployment' do
deployable = older_deployment.deployable
expect(deployable.failed?).to be_falsey
subject
expect(deployable.reload.failed?).to be_truthy
end
context 'and there is no deployable for that older deployment' do
let(:older_deployment) { create(:deployment, :running, environment: environment, deployable: nil) }
it_behaves_like 'it does not drop any build'
end
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment