Commit a4a389a0 authored by Matija Čupić's avatar Matija Čupić Committed by Eric Eastwood

BE for automatic pipeline when enabling Auto DevOps

Fix https://gitlab.com/gitlab-org/gitlab-ce/issues/38962
parent a7f6ab95
...@@ -6,11 +6,18 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -6,11 +6,18 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
end end
def update def update
if @project.update(update_params) Projects::UpdateService.new(project, current_user, update_params).tap do |service|
flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated." if service.execute
redirect_to project_settings_ci_cd_path(@project) flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
else
render 'show' if service.run_auto_devops_pipeline?
CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false)
end
redirect_to project_settings_ci_cd_path(@project)
else
render 'show'
end
end end
end end
...@@ -21,6 +28,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -21,6 +28,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
:runners_token, :builds_enabled, :build_allow_git_fetch, :runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_in_minutes, :build_coverage_regex, :public_builds, :build_timeout_in_minutes, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path, :auto_cancel_pending_pipelines, :ci_config_path,
:run_auto_devops_pipeline_implicit, :run_auto_devops_pipeline_explicit,
auto_devops_attributes: [:id, :domain, :enabled] auto_devops_attributes: [:id, :domain, :enabled]
) )
end end
......
...@@ -8,6 +8,22 @@ module AutoDevopsHelper ...@@ -8,6 +8,22 @@ module AutoDevopsHelper
!project.ci_service !project.ci_service
end end
def show_run_auto_devops_pipeline_checkbox_for_instance_setting?(project)
return false if project.repository.gitlab_ci_yml
if project&.auto_devops&.enabled.present?
!project.auto_devops.enabled && current_application_settings.auto_devops_enabled?
else
current_application_settings.auto_devops_enabled?
end
end
def show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(project)
return false if project.repository.gitlab_ci_yml
!project.auto_devops_enabled?
end
def auto_devops_warning_message(project) def auto_devops_warning_message(project)
missing_domain = !project.auto_devops&.has_domain? missing_domain = !project.auto_devops&.has_domain?
missing_service = !project.kubernetes_service&.active? missing_service = !project.kubernetes_service&.active?
......
...@@ -15,7 +15,7 @@ module Projects ...@@ -15,7 +15,7 @@ module Projects
return error("Could not set the default branch") unless project.change_head(params[:default_branch]) return error("Could not set the default branch") unless project.change_head(params[:default_branch])
end end
if project.update_attributes(params.except(:default_branch)) if project.update_attributes(update_params)
if project.previous_changes.include?('path') if project.previous_changes.include?('path')
project.rename_repo project.rename_repo
else else
...@@ -31,8 +31,16 @@ module Projects ...@@ -31,8 +31,16 @@ module Projects
end end
end end
def run_auto_devops_pipeline?
params.dig(:run_auto_devops_pipeline_explicit) == 'true' || params.dig(:run_auto_devops_pipeline_implicit) == 'true'
end
private private
def update_params
params.except(:default_branch, :run_auto_devops_pipeline_explicit, :run_auto_devops_pipeline_implicit)
end
def renaming_project_with_container_registry_tags? def renaming_project_with_container_registry_tags?
new_path = params[:path] new_path = params[:path]
......
class CreatePipelineWorker
include Sidekiq::Worker
include PipelineQueue
enqueue_in group: :creation
def perform(project_id, user_id, ref, source, params = {})
project = Project.find(project_id)
user = User.find(user_id)
params = params.deep_symbolize_keys
Ci::CreatePipelineService
.new(project, user, ref: ref)
.execute(source, **params)
end
end
---
title: Add the option to automatically run a pipeline after updating AutoDevOps settings
merge_request: 15380
author:
type: changed
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
- [build, 2] - [build, 2]
- [pipeline, 2] - [pipeline, 2]
- [pipeline_processing, 5] - [pipeline_processing, 5]
- [pipeline_creation, 4]
- [pipeline_default, 3] - [pipeline_default, 3]
- [pipeline_cache, 3] - [pipeline_cache, 3]
- [pipeline_hooks, 2] - [pipeline_hooks, 2]
......
...@@ -12,19 +12,22 @@ describe Projects::PipelinesSettingsController do ...@@ -12,19 +12,22 @@ describe Projects::PipelinesSettingsController do
end end
describe 'PATCH update' do describe 'PATCH update' do
before do subject do
patch :update, patch :update,
namespace_id: project.namespace.to_param, namespace_id: project.namespace.to_param,
project_id: project, project_id: project,
project: { project: { auto_devops_attributes: params,
auto_devops_attributes: params run_auto_devops_pipeline_implicit: 'false',
} run_auto_devops_pipeline_explicit: auto_devops_pipeline }
end end
context 'when updating the auto_devops settings' do context 'when updating the auto_devops settings' do
let(:params) { { enabled: '', domain: 'mepmep.md' } } let(:params) { { enabled: '', domain: 'mepmep.md' } }
let(:auto_devops_pipeline) { 'false' }
it 'redirects to the settings page' do it 'redirects to the settings page' do
subject
expect(response).to have_gitlab_http_status(302) expect(response).to have_gitlab_http_status(302)
expect(flash[:notice]).to eq("Pipelines settings for '#{project.name}' were successfully updated.") expect(flash[:notice]).to eq("Pipelines settings for '#{project.name}' were successfully updated.")
end end
...@@ -33,11 +36,32 @@ describe Projects::PipelinesSettingsController do ...@@ -33,11 +36,32 @@ describe Projects::PipelinesSettingsController do
let(:params) { { enabled: '' } } let(:params) { { enabled: '' } }
it 'allows enabled to be set to nil' do it 'allows enabled to be set to nil' do
subject
project_auto_devops.reload project_auto_devops.reload
expect(project_auto_devops.enabled).to be_nil expect(project_auto_devops.enabled).to be_nil
end end
end end
context 'when run_auto_devops_pipeline is true' do
let(:auto_devops_pipeline) { 'true' }
it 'queues a CreatePipelineWorker' do
expect(CreatePipelineWorker).to receive(:perform_async).with(project.id, user.id, project.default_branch, :web, any_args)
subject
end
end
context 'when run_auto_devops_pipeline is not true' do
let(:auto_devops_pipeline) { 'false' }
it 'does not queue a CreatePipelineWorker' do
expect(CreatePipelineWorker).not_to receive(:perform_async).with(project.id, user.id, :web, any_args)
subject
end
end
end end
end end
end end
...@@ -82,4 +82,104 @@ describe AutoDevopsHelper do ...@@ -82,4 +82,104 @@ describe AutoDevopsHelper do
it { is_expected.to eq(false) } it { is_expected.to eq(false) }
end end
end end
describe '.show_run_auto_devops_pipeline_checkbox_for_instance_setting?' do
subject { helper.show_run_auto_devops_pipeline_checkbox_for_instance_setting?(project) }
context 'when master contains a .gitlab-ci.yml file' do
before do
allow(project.repository).to receive(:gitlab_ci_yml).and_return("script: ['test']")
end
it { is_expected.to eq(false) }
end
context 'when auto devops is explicitly enabled' do
before do
project.create_auto_devops!(enabled: true)
end
it { is_expected.to eq(false) }
end
context 'when auto devops is explicitly disabled' do
before do
project.create_auto_devops!(enabled: false)
end
context 'when auto devops is enabled system-wide' do
before do
stub_application_setting(auto_devops_enabled: true)
end
it { is_expected.to eq(true) }
end
context 'when auto devops is disabled system-wide' do
before do
stub_application_setting(auto_devops_enabled: false)
end
it { is_expected.to eq(false) }
end
end
context 'when auto devops is set to instance setting' do
before do
project.create_auto_devops!(enabled: nil)
end
it { is_expected.to eq(false) }
end
end
describe '.show_run_auto_devops_pipeline_checkbox_for_explicit_setting?' do
subject { helper.show_run_auto_devops_pipeline_checkbox_for_explicit_setting?(project) }
context 'when master contains a .gitlab-ci.yml file' do
before do
allow(project.repository).to receive(:gitlab_ci_yml).and_return("script: ['test']")
end
it { is_expected.to eq(false) }
end
context 'when auto devops is explicitly enabled' do
before do
project.create_auto_devops!(enabled: true)
end
it { is_expected.to eq(false) }
end
context 'when auto devops is explicitly disabled' do
before do
project.create_auto_devops!(enabled: false)
end
it { is_expected.to eq(true) }
end
context 'when auto devops is set to instance setting' do
before do
project.create_auto_devops!(enabled: nil)
end
context 'when auto devops is enabled system-wide' do
before do
stub_application_setting(auto_devops_enabled: true)
end
it { is_expected.to eq(false) }
end
context 'when auto devops is disabled system-wide' do
before do
stub_application_setting(auto_devops_enabled: false)
end
it { is_expected.to eq(true) }
end
end
end
end end
require 'spec_helper' require 'spec_helper'
describe Projects::UpdateService, '#execute' do describe Projects::UpdateService do
include ProjectForksHelper include ProjectForksHelper
let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:project) do let(:project) do
create(:project, creator: user, namespace: user.namespace) create(:project, creator: user, namespace: user.namespace)
end end
context 'when changing visibility level' do describe '#execute' do
context 'when visibility_level is INTERNAL' do let(:gitlab_shell) { Gitlab::Shell.new }
it 'updates the project to internal' do let(:admin) { create(:admin) }
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL)
expect(result).to eq({ status: :success })
expect(project).to be_internal
end
end
context 'when visibility_level is PUBLIC' do
it 'updates the project to public' do
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
expect(result).to eq({ status: :success })
expect(project).to be_public
end
end
context 'when visibility levels are restricted to PUBLIC only' do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
context 'when changing visibility level' do
context 'when visibility_level is INTERNAL' do context 'when visibility_level is INTERNAL' do
it 'updates the project to internal' do it 'updates the project to internal' do
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL) result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL)
expect(result).to eq({ status: :success }) expect(result).to eq({ status: :success })
expect(project).to be_internal expect(project).to be_internal
end end
end end
context 'when visibility_level is PUBLIC' do context 'when visibility_level is PUBLIC' do
it 'does not update the project to public' do it 'updates the project to public' do
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC) result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
expect(result).to eq({ status: :success })
expect(project).to be_public
end
end
expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' }) context 'when visibility levels are restricted to PUBLIC only' do
expect(project).to be_private before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end end
context 'when updated by an admin' do context 'when visibility_level is INTERNAL' do
it 'updates the project to public' do it 'updates the project to internal' do
result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC) result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL)
expect(result).to eq({ status: :success }) expect(result).to eq({ status: :success })
expect(project).to be_public expect(project).to be_internal
end end
end end
end
end
context 'When project visibility is higher than parent group' do context 'when visibility_level is PUBLIC' do
let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) } it 'does not update the project to public' do
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
before do expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' })
project.update(namespace: group, visibility_level: group.visibility_level) expect(project).to be_private
end
context 'when updated by an admin' do
it 'updates the project to public' do
result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
expect(result).to eq({ status: :success })
expect(project).to be_public
end
end
end
end end
it 'does not update project visibility level' do context 'When project visibility is higher than parent group' do
result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC) let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
before do
project.update(namespace: group, visibility_level: group.visibility_level)
end
it 'does not update project visibility level' do
result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
expect(result).to eq({ status: :error, message: 'Visibility level public is not allowed in a internal group.' }) expect(result).to eq({ status: :error, message: 'Visibility level public is not allowed in a internal group.' })
expect(project.reload).to be_internal expect(project.reload).to be_internal
end
end end
end end
end
describe 'when updating project that has forks' do describe 'when updating project that has forks' do
let(:project) { create(:project, :internal) } let(:project) { create(:project, :internal) }
let(:forked_project) { fork_project(project) } let(:forked_project) { fork_project(project) }
it 'updates forks visibility level when parent set to more restrictive' do it 'updates forks visibility level when parent set to more restrictive' do
opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE } opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE }
expect(project).to be_internal expect(project).to be_internal
expect(forked_project).to be_internal expect(forked_project).to be_internal
expect(update_project(project, admin, opts)).to eq({ status: :success }) expect(update_project(project, admin, opts)).to eq({ status: :success })
expect(project).to be_private expect(project).to be_private
expect(forked_project.reload).to be_private expect(forked_project.reload).to be_private
end end
it 'does not update forks visibility level when parent set to less restrictive' do it 'does not update forks visibility level when parent set to less restrictive' do
opts = { visibility_level: Gitlab::VisibilityLevel::PUBLIC } opts = { visibility_level: Gitlab::VisibilityLevel::PUBLIC }
expect(project).to be_internal expect(project).to be_internal
expect(forked_project).to be_internal expect(forked_project).to be_internal
expect(update_project(project, admin, opts)).to eq({ status: :success }) expect(update_project(project, admin, opts)).to eq({ status: :success })
expect(project).to be_public expect(project).to be_public
expect(forked_project.reload).to be_internal expect(forked_project.reload).to be_internal
end
end end
end
context 'when updating a default branch' do context 'when updating a default branch' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
it 'changes a default branch' do it 'changes a default branch' do
update_project(project, admin, default_branch: 'feature') update_project(project, admin, default_branch: 'feature')
expect(Project.find(project.id).default_branch).to eq 'feature' expect(Project.find(project.id).default_branch).to eq 'feature'
end end
it 'does not change a default branch' do it 'does not change a default branch' do
# The branch 'unexisted-branch' does not exist. # The branch 'unexisted-branch' does not exist.
update_project(project, admin, default_branch: 'unexisted-branch') update_project(project, admin, default_branch: 'unexisted-branch')
expect(Project.find(project.id).default_branch).to eq 'master' expect(Project.find(project.id).default_branch).to eq 'master'
end
end end
end
context 'when updating a project that contains container images' do context 'when updating a project that contains container images' do
before do before do
stub_container_registry_config(enabled: true) stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: /image/, tags: %w[rc1]) stub_container_registry_tags(repository: /image/, tags: %w[rc1])
create(:container_repository, project: project, name: :image) create(:container_repository, project: project, name: :image)
end end
it 'does not allow to rename the project' do it 'does not allow to rename the project' do
result = update_project(project, admin, path: 'renamed') result = update_project(project, admin, path: 'renamed')
expect(result).to include(status: :error) expect(result).to include(status: :error)
expect(result[:message]).to match(/contains container registry tags/) expect(result[:message]).to match(/contains container registry tags/)
end end
it 'allows to update other settings' do it 'allows to update other settings' do
result = update_project(project, admin, public_builds: true) result = update_project(project, admin, public_builds: true)
expect(result[:status]).to eq :success expect(result[:status]).to eq :success
expect(project.reload.public_builds).to be true expect(project.reload.public_builds).to be true
end
end end
end
context 'when renaming a project' do context 'when renaming a project' do
let(:repository_storage) { 'default' } let(:repository_storage) { 'default' }
let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
context 'with legacy storage' do context 'with legacy storage' do
before do before do
gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing") gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing")
end end
after do
gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing")
end
it 'does not allow renaming when new path matches existing repository on disk' do
result = update_project(project, admin, path: 'existing')
after do expect(result).to include(status: :error)
gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing") expect(result[:message]).to match('There is already a repository with that name on disk')
expect(project).not_to be_valid
expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base]).to include('There is already a repository with that name on disk')
end
end end
it 'does not allow renaming when new path matches existing repository on disk' do context 'with hashed storage' do
result = update_project(project, admin, path: 'existing') let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
expect(result).to include(status: :error) before do
expect(result[:message]).to match('There is already a repository with that name on disk') stub_application_setting(hashed_storage_enabled: true)
expect(project).not_to be_valid end
expect(project.errors.messages).to have_key(:base)
expect(project.errors.messages[:base]).to include('There is already a repository with that name on disk') it 'does not check if new path matches existing repository on disk' do
expect(project).not_to receive(:repository_with_same_path_already_exists?)
result = update_project(project, admin, path: 'existing')
expect(result).to include(status: :success)
end
end end
end end
context 'with hashed storage' do context 'when passing invalid parameters' do
let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } it 'returns an error result when record cannot be updated' do
result = update_project(project, admin, { name: 'foo&bar' })
before do expect(result).to eq({
stub_application_setting(hashed_storage_enabled: true) status: :error,
message: "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
})
end end
end
end
it 'does not check if new path matches existing repository on disk' do describe '#run_auto_devops_pipeline?' do
expect(project).not_to receive(:repository_with_same_path_already_exists?) subject { described_class.new(project, user, params).run_auto_devops_pipeline? }
result = update_project(project, admin, path: 'existing') context 'when neither pipeline setting is true' do
let(:params) { {} }
expect(result).to include(status: :success) it { is_expected.to eq(false) }
end end
context 'when run_auto_devops_pipeline_explicit is true' do
let(:params) { { run_auto_devops_pipeline_explicit: 'true' } }
it { is_expected.to eq(true) }
end end
end
context 'when passing invalid parameters' do context 'when run_auto_devops_pipeline_implicit is true' do
it 'returns an error result when record cannot be updated' do let(:params) { { run_auto_devops_pipeline_implicit: 'true' } }
result = update_project(project, admin, { name: 'foo&bar' })
expect(result).to eq({ it { is_expected.to eq(true) }
status: :error,
message: "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
})
end end
end end
......
require 'spec_helper'
describe CreatePipelineWorker do
describe '#perform' do
let(:worker) { described_class.new }
context 'when a project not found' do
it 'does not call the Service' do
expect(Ci::CreatePipelineService).not_to receive(:new)
expect { worker.perform(99, create(:user).id, 'master', :web) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'when a user not found' do
let(:project) { create(:project) }
it 'does not call the Service' do
expect(Ci::CreatePipelineService).not_to receive(:new)
expect { worker.perform(project.id, 99, project.default_branch, :web) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'when everything is ok' do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:create_pipeline_service) { instance_double(Ci::CreatePipelineService) }
it 'calls the Service' do
expect(Ci::CreatePipelineService).to receive(:new).with(project, user, ref: project.default_branch).and_return(create_pipeline_service)
expect(create_pipeline_service).to receive(:execute).with(:web, any_args)
worker.perform(project.id, user.id, project.default_branch, :web)
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