Commit e89a46a9 authored by Angelo Gulina's avatar Angelo Gulina Committed by Shinya Maeda

Add CI/CD settings for Automatic Deployment Rollbacks

The UI is conditionally rendered based on the licensed feature
The licensed feature is not added at this stage
parent 293a4416
...@@ -75,7 +75,7 @@ module Projects ...@@ -75,7 +75,7 @@ module Projects
[ [
:runners_token, :builds_enabled, :build_allow_git_fetch, :runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_human_readable, :build_coverage_regex, :public_builds, :build_timeout_human_readable, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path, :auto_cancel_pending_pipelines, :ci_config_path, :auto_rollback_enabled,
auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy], auto_devops_attributes: [:id, :domain, :enabled, :deploy_strategy],
ci_cd_settings_attributes: [:default_git_depth, :forward_deployment_enabled] ci_cd_settings_attributes: [:default_git_depth, :forward_deployment_enabled]
].tap do |list| ].tap do |list|
......
...@@ -75,6 +75,8 @@ ...@@ -75,6 +75,8 @@
.settings-content .settings-content
= render 'projects/registry/settings/index' = render 'projects/registry/settings/index'
= render_if_exists 'projects/settings/ci_cd/auto_rollback', expanded: expanded
- if can?(current_user, :create_freeze_period, @project) - if can?(current_user, :create_freeze_period, @project)
%section.settings.no-animate#js-deploy-freeze-settings{ class: ('expanded' if expanded) } %section.settings.no-animate#js-deploy-freeze-settings{ class: ('expanded' if expanded) }
.settings-header .settings-header
......
---
title: Add auto_rollback_enabled column to project_ci_cd_settings table
merge_request: 45816
author:
type: other
---
name: cd_auto_rollback
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45816
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/35404
type: development
group: group::progressive delivery
default_enabled: false
# frozen_string_literal: true
class AddAutoRollbackSetting < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
add_column :project_ci_cd_settings, :auto_rollback_enabled, :boolean, default: false, null: false
end
end
def down
with_lock_retries do
remove_column :project_ci_cd_settings, :auto_rollback_enabled
end
end
end
3f24bfc2d18ffa5f171e027d4e7aaf9994b255e5806e2de57fd36b4a193db122
\ No newline at end of file
...@@ -14828,7 +14828,8 @@ CREATE TABLE project_ci_cd_settings ( ...@@ -14828,7 +14828,8 @@ CREATE TABLE project_ci_cd_settings (
merge_pipelines_enabled boolean, merge_pipelines_enabled boolean,
default_git_depth integer, default_git_depth integer,
forward_deployment_enabled boolean, forward_deployment_enabled boolean,
merge_trains_enabled boolean DEFAULT false merge_trains_enabled boolean DEFAULT false,
auto_rollback_enabled boolean DEFAULT false NOT NULL
); );
CREATE SEQUENCE project_ci_cd_settings_id_seq CREATE SEQUENCE project_ci_cd_settings_id_seq
......
...@@ -91,6 +91,10 @@ module EE ...@@ -91,6 +91,10 @@ module EE
attrs += compliance_framework_params attrs += compliance_framework_params
if ::Gitlab::Ci::Features.auto_rollback_available?(project)
attrs << :auto_rollback_enabled
end
if allow_mirror_params? if allow_mirror_params?
attrs + mirror_params attrs + mirror_params
else else
......
...@@ -178,6 +178,7 @@ module EE ...@@ -178,6 +178,7 @@ module EE
delegate :merge_pipelines_enabled, :merge_pipelines_enabled=, :merge_pipelines_enabled?, :merge_pipelines_were_disabled?, to: :ci_cd_settings delegate :merge_pipelines_enabled, :merge_pipelines_enabled=, :merge_pipelines_enabled?, :merge_pipelines_were_disabled?, to: :ci_cd_settings
delegate :merge_trains_enabled?, to: :ci_cd_settings delegate :merge_trains_enabled?, to: :ci_cd_settings
delegate :auto_rollback_enabled, :auto_rollback_enabled=, :auto_rollback_enabled?, to: :ci_cd_settings
delegate :closest_gitlab_subscription, to: :namespace delegate :closest_gitlab_subscription, to: :namespace
validates :repository_size_limit, validates :repository_size_limit,
......
...@@ -22,5 +22,9 @@ module EE ...@@ -22,5 +22,9 @@ module EE
def merge_pipelines_were_disabled? def merge_pipelines_were_disabled?
saved_change_to_attribute?(:merge_pipelines_enabled, from: true, to: false) saved_change_to_attribute?(:merge_pipelines_enabled, from: true, to: false)
end end
def auto_rollback_enabled?
super && ::Gitlab::Ci::Features.auto_rollback_available?(project)
end
end end
end end
...@@ -124,6 +124,7 @@ class License < ApplicationRecord ...@@ -124,6 +124,7 @@ class License < ApplicationRecord
EEP_FEATURES.freeze EEP_FEATURES.freeze
EEU_FEATURES = EEP_FEATURES + %i[ EEU_FEATURES = EEP_FEATURES + %i[
auto_rollback
container_scanning container_scanning
coverage_fuzzing coverage_fuzzing
credentials_inventory credentials_inventory
......
- if Gitlab::Ci::Features.auto_rollback_available?(@project)
%section.settings.no-animate#auto-rollback-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
= _("Automatic deployment rollbacks")
%button.gl-button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= s_('AutoRollback|Automatically roll back to the last successful deployment when a critical problem is detected.')
.settings-content
.row
.col-lg-12
= form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'auto-rollback-settings') do |f|
= form_errors(@project)
%fieldset.builds-feature
.gl-form-group.form-group
.card
.card-body
.gl-form.form-check
= f.check_box :auto_rollback_enabled, class: 'gl-form-checkbox form-check-input'
= f.label :auto_rollback_enabled, class: 'form-check-label col-form-label' do
= s_('AutoRollback|Enable automatic rollbacks')
%small.form-text.text-gl-muted
= s_('AutoRollback|Automatic rollbacks start when a critical alert is triggered. If the last successful deployment fails to roll back automatically, it can still be done manually.')
-# This will be added once the documentation page has been created
-# = link_to _('More information'), help_page_path('topics/auto_rollback/index.md'), target: '_blank'
= f.submit _('Save changes'), class: "gl-button btn btn-success gl-mt-5", data: { qa_selector: 'save_changes_button' }
...@@ -267,7 +267,7 @@ RSpec.describe ProjectsController do ...@@ -267,7 +267,7 @@ RSpec.describe ProjectsController do
end end
end end
context 'when lisence is not sufficient' do context 'when license is not sufficient' do
before do before do
stub_licensed_features(merge_pipelines: false) stub_licensed_features(merge_pipelines: false)
end end
...@@ -280,6 +280,48 @@ RSpec.describe ProjectsController do ...@@ -280,6 +280,48 @@ RSpec.describe ProjectsController do
end end
end end
context 'when auto_rollback_enabled param is specified' do
let(:params) { { auto_rollback_enabled: true } }
let(:request) do
put :update, params: { namespace_id: project.namespace, id: project, project: params }
end
before do
stub_licensed_features(auto_rollback: true)
end
it 'updates the attribute' do
request
expect(project.reload.auto_rollback_enabled).to be_truthy
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(auto_rollback: false)
end
it 'does not update the attribute' do
request
expect(project.reload.auto_rollback_enabled).to be_falsy
end
end
context 'when license is not sufficient' do
before do
stub_licensed_features(auto_rollback: false)
end
it 'does not update the attribute' do
request
expect(project.reload.auto_rollback_enabled).to be_falsy
end
end
end
context 'repository mirrors' do context 'repository mirrors' do
let(:params) do let(:params) do
{ {
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Automatic Deployment Rollbacks' do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
sign_in(user)
end
context 'when the user is not authorised' do
it 'renders 404 page' do
visit project_settings_ci_cd_path(project)
expect(page).to have_gitlab_http_status(:not_found)
end
end
shared_examples 'the auto rollback feature is not available' do
it 'does not render the Automatic Deployment Rollbacks checkbox' do
project.add_maintainer(user)
visit project_settings_ci_cd_path(project)
expect(page).to have_gitlab_http_status(:ok)
expect(page).not_to have_selector('#auto-rollback-settings')
end
end
context 'when cd_auto_rollback and auto_rollback are disabled' do
before do
stub_feature_flags(cd_auto_rollback: false)
stub_licensed_features(auto_rollback: false)
end
it_behaves_like 'the auto rollback feature is not available'
end
context 'when cd_auto_rollback is disabled and auto_rollback is enabled' do
before do
stub_licensed_features(auto_rollback: true)
stub_feature_flags(cd_auto_rollback: false)
end
it_behaves_like 'the auto rollback feature is not available'
end
context 'when cd_auto_rollback is enabled and auto_rollback is disabled' do
before do
stub_feature_flags(cd_auto_rollback: true)
stub_licensed_features(auto_rollback: false)
end
it_behaves_like 'the auto rollback feature is not available'
end
context 'when cd_auto_rollback and auto_rollback are enabled' do
before do
stub_licensed_features(auto_rollback: true)
project.add_maintainer(user)
visit project_settings_ci_cd_path(project)
end
it 'checks the auto rollback checkbox when the checkbox is checked' do
expect(page.find('#project_auto_rollback_enabled')).not_to be_checked
within('#auto-rollback-settings') do
check('project_auto_rollback_enabled')
click_on('Save changes')
end
visit project_settings_ci_cd_path(project) # Reload from database
expect(page.find('#project_auto_rollback_enabled')).to be_checked
end
end
context 'when the checkbox is checked' do
before do
stub_licensed_features(auto_rollback: true)
project.add_maintainer(user)
project.update!(auto_rollback_enabled: true)
visit project_settings_ci_cd_path(project)
end
it 'unchecks the auto rollback checkbox' do
expect(page.find('#project_auto_rollback_enabled')).to be_checked
within('#auto-rollback-settings') do
uncheck('project_auto_rollback_enabled')
click_on('Save changes')
end
visit project_settings_ci_cd_path(project) # Reload from database
expect(page.find('#project_auto_rollback_enabled')).not_to be_checked
end
end
end
...@@ -69,6 +69,35 @@ RSpec.describe ProjectCiCdSetting do ...@@ -69,6 +69,35 @@ RSpec.describe ProjectCiCdSetting do
end end
end end
describe '#auto_rollback_enabled?' do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project) }
where(:license_feature, :feature_flag, :actual_setting) do
true | true | true
false | true | true
true | false | true
false | false | true
true | true | false
false | true | false
true | false | false
false | false | false
end
with_them do
before do
stub_licensed_features(auto_rollback: license_feature)
stub_feature_flags(cd_auto_rollback: feature_flag)
project.auto_rollback_enabled = actual_setting
end
it 'is only enabled if set and both the license and the feature flag allows' do
expect(project.auto_rollback_enabled?).to be(actual_setting && license_feature && feature_flag)
end
end
end
describe '#merge_pipelines_were_disabled?' do describe '#merge_pipelines_were_disabled?' do
subject { project.merge_pipelines_were_disabled? } subject { project.merge_pipelines_were_disabled? }
......
...@@ -32,7 +32,7 @@ module Gitlab ...@@ -32,7 +32,7 @@ module Gitlab
end end
# NOTE: The feature flag `disallow_to_create_merge_request_pipelines_in_target_project` # NOTE: The feature flag `disallow_to_create_merge_request_pipelines_in_target_project`
# is a safe switch to disable the feature for a parituclar project when something went wrong, # is a safe switch to disable the feature for a particular project when something went wrong,
# therefore it's not supposed to be enabled by default. # therefore it's not supposed to be enabled by default.
def self.disallow_to_create_merge_request_pipelines_in_target_project?(target_project) def self.disallow_to_create_merge_request_pipelines_in_target_project?(target_project)
::Feature.enabled?(:ci_disallow_to_create_merge_request_pipelines_in_target_project, target_project) ::Feature.enabled?(:ci_disallow_to_create_merge_request_pipelines_in_target_project, target_project)
...@@ -63,6 +63,10 @@ module Gitlab ...@@ -63,6 +63,10 @@ module Gitlab
::Feature.enabled?(:ci_manual_bridges, project, default_enabled: true) ::Feature.enabled?(:ci_manual_bridges, project, default_enabled: true)
end end
def self.auto_rollback_available?(project)
::Feature.enabled?(:cd_auto_rollback, project) && project&.feature_available?(:auto_rollback)
end
def self.seed_block_run_before_workflow_rules_enabled?(project) def self.seed_block_run_before_workflow_rules_enabled?(project)
::Feature.enabled?(:ci_seed_block_run_before_workflow_rules, project, default_enabled: false) ::Feature.enabled?(:ci_seed_block_run_before_workflow_rules, project, default_enabled: false)
end end
......
...@@ -3976,6 +3976,15 @@ msgstr "" ...@@ -3976,6 +3976,15 @@ msgstr ""
msgid "AutoRemediation|Introducing GitLab auto-fix" msgid "AutoRemediation|Introducing GitLab auto-fix"
msgstr "" msgstr ""
msgid "AutoRollback|Automatic rollbacks start when a critical alert is triggered. If the last successful deployment fails to roll back automatically, it can still be done manually."
msgstr ""
msgid "AutoRollback|Automatically roll back to the last successful deployment when a critical problem is detected."
msgstr ""
msgid "AutoRollback|Enable automatic rollbacks"
msgstr ""
msgid "Autocomplete" msgid "Autocomplete"
msgstr "" msgstr ""
...@@ -3994,6 +4003,9 @@ msgstr "" ...@@ -3994,6 +4003,9 @@ msgstr ""
msgid "Automatic certificate management using Let's Encrypt" msgid "Automatic certificate management using Let's Encrypt"
msgstr "" msgstr ""
msgid "Automatic deployment rollbacks"
msgstr ""
msgid "Automatically close incident issues when the associated Prometheus alert resolves." msgid "Automatically close incident issues when the associated Prometheus alert resolves."
msgstr "" msgstr ""
......
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