Skip to content
Snippets Groups Projects
Commit a4a389a0 authored by Matija Čupić's avatar Matija Čupić Committed by Eric Eastwood
Browse files

BE for automatic pipeline when enabling Auto DevOps

parent a7f6ab95
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -6,11 +6,18 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
end
 
def update
if @project.update(update_params)
flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
redirect_to project_settings_ci_cd_path(@project)
else
render 'show'
Projects::UpdateService.new(project, current_user, update_params).tap do |service|
if service.execute
flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
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
 
Loading
Loading
@@ -21,6 +28,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
:runners_token, :builds_enabled, :build_allow_git_fetch,
:build_timeout_in_minutes, :build_coverage_regex, :public_builds,
:auto_cancel_pending_pipelines, :ci_config_path,
:run_auto_devops_pipeline_implicit, :run_auto_devops_pipeline_explicit,
auto_devops_attributes: [:id, :domain, :enabled]
)
end
Loading
Loading
Loading
Loading
@@ -8,6 +8,22 @@ module AutoDevopsHelper
!project.ci_service
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)
missing_domain = !project.auto_devops&.has_domain?
missing_service = !project.kubernetes_service&.active?
Loading
Loading
Loading
Loading
@@ -15,7 +15,7 @@ module Projects
return error("Could not set the default branch") unless project.change_head(params[:default_branch])
end
 
if project.update_attributes(params.except(:default_branch))
if project.update_attributes(update_params)
if project.previous_changes.include?('path')
project.rename_repo
else
Loading
Loading
@@ -31,8 +31,16 @@ module Projects
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
 
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?
new_path = params[:path]
 
Loading
Loading
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
Loading
Loading
@@ -28,6 +28,7 @@
- [build, 2]
- [pipeline, 2]
- [pipeline_processing, 5]
- [pipeline_creation, 4]
- [pipeline_default, 3]
- [pipeline_cache, 3]
- [pipeline_hooks, 2]
Loading
Loading
Loading
Loading
@@ -12,19 +12,22 @@ describe Projects::PipelinesSettingsController do
end
 
describe 'PATCH update' do
before do
subject do
patch :update,
namespace_id: project.namespace.to_param,
project_id: project,
project: {
auto_devops_attributes: params
}
project: { auto_devops_attributes: params,
run_auto_devops_pipeline_implicit: 'false',
run_auto_devops_pipeline_explicit: auto_devops_pipeline }
end
 
context 'when updating the auto_devops settings' do
let(:params) { { enabled: '', domain: 'mepmep.md' } }
let(:auto_devops_pipeline) { 'false' }
 
it 'redirects to the settings page' do
subject
expect(response).to have_gitlab_http_status(302)
expect(flash[:notice]).to eq("Pipelines settings for '#{project.name}' were successfully updated.")
end
Loading
Loading
@@ -33,11 +36,32 @@ describe Projects::PipelinesSettingsController do
let(:params) { { enabled: '' } }
 
it 'allows enabled to be set to nil' do
subject
project_auto_devops.reload
 
expect(project_auto_devops.enabled).to be_nil
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
Loading
Loading
@@ -82,4 +82,104 @@ describe AutoDevopsHelper do
it { is_expected.to eq(false) }
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
require 'spec_helper'
 
describe Projects::UpdateService, '#execute' do
describe Projects::UpdateService do
include ProjectForksHelper
 
let(:gitlab_shell) { Gitlab::Shell.new }
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:project) do
create(:project, creator: user, namespace: user.namespace)
end
 
context 'when changing visibility level' do
context 'when visibility_level is INTERNAL' do
it 'updates the project to internal' do
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
describe '#execute' do
let(:gitlab_shell) { Gitlab::Shell.new }
let(:admin) { create(:admin) }
 
context 'when changing visibility level' do
context 'when visibility_level is INTERNAL' do
it 'updates the project to internal' do
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 '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)
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!' })
expect(project).to be_private
context 'when visibility levels are restricted to PUBLIC only' do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
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)
context 'when visibility_level is INTERNAL' do
it 'updates the project to internal' do
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL)
expect(result).to eq({ status: :success })
expect(project).to be_public
expect(project).to be_internal
end
end
end
end
 
context 'When project visibility is higher than parent group' do
let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
context 'when visibility_level is PUBLIC' do
it 'does not update the project to public' do
result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
 
before do
project.update(namespace: group, visibility_level: group.visibility_level)
expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' })
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
 
it 'does not update project visibility level' do
result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
context 'When project visibility is higher than parent group' do
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(project.reload).to be_internal
expect(result).to eq({ status: :error, message: 'Visibility level public is not allowed in a internal group.' })
expect(project.reload).to be_internal
end
end
end
end
 
describe 'when updating project that has forks' do
let(:project) { create(:project, :internal) }
let(:forked_project) { fork_project(project) }
describe 'when updating project that has forks' do
let(:project) { create(:project, :internal) }
let(:forked_project) { fork_project(project) }
 
it 'updates forks visibility level when parent set to more restrictive' do
opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE }
it 'updates forks visibility level when parent set to more restrictive' do
opts = { visibility_level: Gitlab::VisibilityLevel::PRIVATE }
 
expect(project).to be_internal
expect(forked_project).to be_internal
expect(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(forked_project.reload).to be_private
end
expect(project).to be_private
expect(forked_project.reload).to be_private
end
 
it 'does not update forks visibility level when parent set to less restrictive' do
opts = { visibility_level: Gitlab::VisibilityLevel::PUBLIC }
it 'does not update forks visibility level when parent set to less restrictive' do
opts = { visibility_level: Gitlab::VisibilityLevel::PUBLIC }
 
expect(project).to be_internal
expect(forked_project).to be_internal
expect(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(forked_project.reload).to be_internal
expect(project).to be_public
expect(forked_project.reload).to be_internal
end
end
end
 
context 'when updating a default branch' do
let(:project) { create(:project, :repository) }
context 'when updating a default branch' do
let(:project) { create(:project, :repository) }
 
it 'changes a default branch' do
update_project(project, admin, default_branch: 'feature')
it 'changes a default branch' do
update_project(project, admin, default_branch: 'feature')
 
expect(Project.find(project.id).default_branch).to eq 'feature'
end
expect(Project.find(project.id).default_branch).to eq 'feature'
end
 
it 'does not change a default branch' do
# The branch 'unexisted-branch' does not exist.
update_project(project, admin, default_branch: 'unexisted-branch')
it 'does not change a default branch' do
# The branch 'unexisted-branch' does not exist.
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
 
context 'when updating a project that contains container images' do
before do
stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: /image/, tags: %w[rc1])
create(:container_repository, project: project, name: :image)
end
context 'when updating a project that contains container images' do
before do
stub_container_registry_config(enabled: true)
stub_container_registry_tags(repository: /image/, tags: %w[rc1])
create(:container_repository, project: project, name: :image)
end
 
it 'does not allow to rename the project' do
result = update_project(project, admin, path: 'renamed')
it 'does not allow to rename the project' do
result = update_project(project, admin, path: 'renamed')
 
expect(result).to include(status: :error)
expect(result[:message]).to match(/contains container registry tags/)
end
expect(result).to include(status: :error)
expect(result[:message]).to match(/contains container registry tags/)
end
 
it 'allows to update other settings' do
result = update_project(project, admin, public_builds: true)
it 'allows to update other settings' do
result = update_project(project, admin, public_builds: true)
 
expect(result[:status]).to eq :success
expect(project.reload.public_builds).to be true
expect(result[:status]).to eq :success
expect(project.reload.public_builds).to be true
end
end
end
 
context 'when renaming a project' do
let(:repository_storage) { 'default' }
let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
context 'when renaming a project' do
let(:repository_storage) { 'default' }
let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] }
 
context 'with legacy storage' do
before do
gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing")
end
context 'with legacy storage' do
before do
gitlab_shell.add_repository(repository_storage, "#{user.namespace.full_path}/existing")
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
gitlab_shell.remove_repository(repository_storage_path, "#{user.namespace.full_path}/existing")
expect(result).to include(status: :error)
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
 
it 'does not allow renaming when new path matches existing repository on disk' do
result = update_project(project, admin, path: 'existing')
context 'with hashed storage' do
let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
 
expect(result).to include(status: :error)
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')
before do
stub_application_setting(hashed_storage_enabled: true)
end
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
 
context 'with hashed storage' do
let(:project) { create(:project, :repository, creator: user, namespace: user.namespace) }
context 'when passing invalid parameters' do
it 'returns an error result when record cannot be updated' do
result = update_project(project, admin, { name: 'foo&bar' })
 
before do
stub_application_setting(hashed_storage_enabled: true)
expect(result).to eq({
status: :error,
message: "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
})
end
end
end
 
it 'does not check if new path matches existing repository on disk' do
expect(project).not_to receive(:repository_with_same_path_already_exists?)
describe '#run_auto_devops_pipeline?' do
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)
end
it { is_expected.to eq(false) }
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
 
context 'when passing invalid parameters' do
it 'returns an error result when record cannot be updated' do
result = update_project(project, admin, { name: 'foo&bar' })
context 'when run_auto_devops_pipeline_implicit is true' do
let(:params) { { run_auto_devops_pipeline_implicit: 'true' } }
 
expect(result).to eq({
status: :error,
message: "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'."
})
it { is_expected.to eq(true) }
end
end
 
Loading
Loading
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment