diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb index f476f428fdb..f378f7ac79a 100644 --- a/app/controllers/groups/settings/ci_cd_controller.rb +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -17,6 +17,16 @@ module Groups redirect_to group_settings_ci_cd_path end + def update_auto_devops + if auto_devops_service.execute + flash[:notice] = s_('GroupSettings|Auto DevOps pipeline was updated for the group') + else + flash[:alert] = s_("GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}." % { error_messages: group.errors.full_messages }) + end + + redirect_to group_settings_ci_cd_path + end + private def define_ci_variables @@ -29,6 +39,14 @@ module Groups def authorize_admin_group! return render_404 unless can?(current_user, :admin_group, group) end + + def auto_devops_params + params.require(:group).permit(:auto_devops_enabled) + end + + def auto_devops_service + Groups::AutoDevopsService.new(group, current_user, auto_devops_params) + end end end end diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb index 67e7e475920..0f0d5350df6 100644 --- a/app/helpers/auto_devops_helper.rb +++ b/app/helpers/auto_devops_helper.rb @@ -9,4 +9,17 @@ module AutoDevopsHelper !project.repository.gitlab_ci_yml && !project.ci_service end + + def badge_for_auto_devops_scope(auto_devops_receiver) + return unless auto_devops_receiver.auto_devops_enabled? + + case auto_devops_receiver.first_auto_devops_config[:scope] + when :project + nil + when :group + s_('CICD|group enabled') + when :instance + s_('CICD|instance enabled') + end + end end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index a5c479bdc0c..dea34e812ca 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -11,6 +11,7 @@ class Namespace < ApplicationRecord include IgnorableColumn include FeatureGate include FromUnion + include Gitlab::Utils::StrongMemoize ignore_column :deleted_at @@ -267,6 +268,22 @@ class Namespace < ApplicationRecord owner.refresh_authorized_projects end + def auto_devops_enabled? + first_auto_devops_config[:status] + end + + def first_auto_devops_config + return { scope: :group, status: auto_devops_enabled } unless auto_devops_enabled.nil? + + strong_memoize(:first_auto_devops_config) do + if has_parent? + parent.first_auto_devops_config + else + { scope: :instance, status: Gitlab::CurrentSettings.auto_devops_enabled? } + end + end + end + private def path_or_parent_changed? diff --git a/app/models/project.rb b/app/models/project.rb index 4cc13f372c1..aba63032cdf 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -631,12 +631,21 @@ class Project < ActiveRecord::Base end def has_auto_devops_implicitly_enabled? - auto_devops&.enabled.nil? && - (Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self)) + auto_devops_config = first_auto_devops_config + + auto_devops_config[:scope] != :project && auto_devops_config[:status] end def has_auto_devops_implicitly_disabled? - auto_devops&.enabled.nil? && !(Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self)) + auto_devops_config = first_auto_devops_config + + auto_devops_config[:scope] != :project && !auto_devops_config[:status] + end + + def first_auto_devops_config + return namespace.first_auto_devops_config if auto_devops&.enabled.nil? + + { scope: :project, status: auto_devops&.enabled || Feature.enabled?(:force_autodevops_on_by_default, self) } end def daily_statistics_enabled? diff --git a/app/services/groups/auto_devops_service.rb b/app/services/groups/auto_devops_service.rb new file mode 100644 index 00000000000..1925e0cc0ea --- /dev/null +++ b/app/services/groups/auto_devops_service.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Groups + class AutoDevopsService < Groups::BaseService + def execute + raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_group, group) + + group.update(auto_devops_enabled: auto_devops_enabled) + end + + private + + def auto_devops_enabled + params[:auto_devops_enabled] + end + end +end diff --git a/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml b/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml new file mode 100644 index 00000000000..e7efc0237c8 --- /dev/null +++ b/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml @@ -0,0 +1,15 @@ += form_for group, url: update_auto_devops_group_settings_ci_cd_path(group), method: :patch do |f| + = form_errors(group) + %fieldset + .form-group + .card.auto-devops-card + .card-body + .form-check + = f.check_box :auto_devops_enabled, class: 'form-check-input', checked: group.auto_devops_enabled? + = f.label :auto_devops_enabled, class: 'form-check-label' do + %strong= s_('GroupSettings|Default to Auto DevOps pipeline for all projects within this group') + %span.badge.badge-info#auto-devops-badge= badge_for_auto_devops_scope(group) + .form-text.text-muted + = s_('GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found.') + = link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank' + = f.submit _('Save changes'), class: 'btn btn-success prepend-top-15' diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml index d9332e36ef5..d0f5cd94002 100644 --- a/app/views/groups/settings/ci_cd/show.html.haml +++ b/app/views/groups/settings/ci_cd/show.html.haml @@ -19,3 +19,17 @@ = _('Register and see your runners for this group.') .settings-content = render 'groups/runners/index' + +%section.settings#auto-devops-settings.no-animate{ class: ('expanded' if expanded) } + .settings-header + %h4 + = _('Auto DevOps') + %button.btn.btn-default.js-settings-toggle{ type: "button" } + = expanded ? _('Collapse') : _('Expand') + %p + - auto_devops_url = help_page_path('topics/autodevops/index') + - auto_devops_start = ''.html_safe % { url: auto_devops_url } + = s_('GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: ''.html_safe } + + .settings-content + = render 'groups/settings/ci_cd/auto_devops_form', group: @group diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml index 8c4d1c32ebe..fac68a36e79 100644 --- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -8,15 +8,15 @@ .card.auto-devops-card .card-body .form-check - = form.check_box :enabled, class: 'form-check-input js-toggle-extra-settings', checked: @project.auto_devops_enabled? + = form.check_box :enabled, class: 'form-check-input js-toggle-extra-settings', checked: auto_devops_enabled = form.label :enabled, class: 'form-check-label' do %strong= s_('CICD|Default to Auto DevOps pipeline') - - if @project.has_auto_devops_implicitly_enabled? - %span.badge.badge-info.js-instance-default-badge= s_('CICD|instance enabled') + - if auto_devops_enabled + %span.badge.badge-info.js-instance-default-badge= badge_for_auto_devops_scope(@project) .form-text.text-muted = s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.') = link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank' - .card-footer.js-extra-settings{ class: @project.auto_devops_enabled? || 'hidden' } + .card-footer.js-extra-settings{ class: auto_devops_enabled || 'hidden' } %p.settings-message.text-center - kubernetes_cluster_link = help_page_path('user/project/clusters/index') - kubernetes_cluster_start = ''.html_safe % { url: kubernetes_cluster_link } diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 6966bf96724..548b7c06867 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -26,7 +26,7 @@ = s_('CICD|Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration.') = link_to s_('CICD|Learn more about Auto DevOps'), help_page_path('topics/autodevops/index.md') .settings-content - = render 'autodevops_form' + = render 'autodevops_form', auto_devops_enabled: @project.auto_devops_enabled? = render_if_exists 'projects/settings/ci_cd/protected_environments', expanded: expanded diff --git a/changelogs/unreleased/52447-auto-devops-at-group-level.yml b/changelogs/unreleased/52447-auto-devops-at-group-level.yml new file mode 100644 index 00000000000..0a21c6a2b7b --- /dev/null +++ b/changelogs/unreleased/52447-auto-devops-at-group-level.yml @@ -0,0 +1,5 @@ +--- +title: Enable/disable Auto DevOps at the Group level +merge_request: 25533 +author: +type: added diff --git a/config/routes/group.rb b/config/routes/group.rb index b3015529c6e..f42c1ee6e7d 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -31,6 +31,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do namespace :settings do resource :ci_cd, only: [:show], controller: 'ci_cd' do put :reset_registration_token + patch :update_auto_devops end end diff --git a/db/migrate/20190225152525_add_auto_dev_ops_enabled_to_namespaces.rb b/db/migrate/20190225152525_add_auto_dev_ops_enabled_to_namespaces.rb new file mode 100644 index 00000000000..93e7a84fb02 --- /dev/null +++ b/db/migrate/20190225152525_add_auto_dev_ops_enabled_to_namespaces.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddAutoDevOpsEnabledToNamespaces < ActiveRecord::Migration[5.0] + DOWNTIME = false + + def change + add_column :namespaces, :auto_devops_enabled, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 59a76e21a5f..dda0445e3f2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1377,6 +1377,7 @@ ActiveRecord::Schema.define(version: 20190301182457) do t.integer "cached_markdown_version" t.string "runners_token" t.string "runners_token_encrypted" + t.boolean "auto_devops_enabled" t.index ["created_at"], name: "index_namespaces_on_created_at", using: :btree t.index ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true, using: :btree t.index ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4751b7264a5..90dab37a33b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1332,6 +1332,9 @@ msgstr "" msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly." msgstr "" +msgid "CICD|group enabled" +msgstr "" + msgid "CICD|instance enabled" msgstr "" @@ -3828,18 +3831,33 @@ msgstr "" msgid "Group: %{group_name}" msgstr "" +msgid "GroupSettings|Auto DevOps pipeline was updated for the group" +msgstr "" + +msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}" +msgstr "" + msgid "GroupSettings|Badges" msgstr "" msgid "GroupSettings|Customize your group badges." msgstr "" +msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group" +msgstr "" + msgid "GroupSettings|Learn more about badges." msgstr "" msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" +msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found." +msgstr "" + +msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}." +msgstr "" + msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup." msgstr "" diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb index 40673d10b91..15eb0a442a6 100644 --- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb @@ -66,4 +66,77 @@ describe Groups::Settings::CiCdController do end end end + + describe 'PATCH #update_auto_devops' do + let(:auto_devops_param) { '1' } + + subject do + patch :update_auto_devops, params: { + group_id: group, + group: { auto_devops_enabled: auto_devops_param } + } + end + + context 'when user does not have enough permission' do + before do + group.add_maintainer(user) + end + + it { is_expected.to have_gitlab_http_status(404) } + end + + context 'when user has enough privileges' do + before do + group.add_owner(user) + end + + it { is_expected.to redirect_to(group_settings_ci_cd_path) } + + context 'when service execution went wrong' do + before do + allow_any_instance_of(Groups::AutoDevopsService).to receive(:execute).and_return(false) + allow_any_instance_of(Group).to receive_message_chain(:errors, :full_messages) + .and_return(['Error 1']) + + subject + end + + it 'returns a flash alert' do + expect(response).to set_flash[:alert] + .to eq("There was a problem updating Auto DevOps pipeline: [\"Error 1\"].") + end + end + + context 'when service execution was successful' do + it 'returns a flash notice' do + subject + + expect(response).to set_flash[:notice] + .to eq('Auto DevOps pipeline was updated for the group') + end + end + + context 'when changing auto devops value' do + before do + subject + + group.reload + end + + context 'when explicitly enabling auto devops' do + it 'should update group attribute' do + expect(group.auto_devops_enabled).to eq(true) + end + end + + context 'when explicitly disabling auto devops' do + let(:auto_devops_param) { '0' } + + it 'should update group attribute' do + expect(group.auto_devops_enabled).to eq(false) + end + end + end + end + end end diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index 3b354c0d96b..dcef8571f41 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -36,5 +36,13 @@ FactoryBot.define do trait :nested do parent factory: :group end + + trait :auto_devops_enabled do + auto_devops_enabled true + end + + trait :auto_devops_disabled do + auto_devops_enabled false + end end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 30d3b22d868..ab185ab3972 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -271,6 +271,10 @@ FactoryBot.define do trait :auto_devops do association :auto_devops, factory: :project_auto_devops end + + trait :auto_devops_disabled do + association :auto_devops, factory: [:project_auto_devops, :disabled] + end end # Project with empty repository diff --git a/spec/features/groups/settings/ci_cd_spec.rb b/spec/features/groups/settings/ci_cd_spec.rb index d422fd18346..0f793dbab6e 100644 --- a/spec/features/groups/settings/ci_cd_spec.rb +++ b/spec/features/groups/settings/ci_cd_spec.rb @@ -5,8 +5,8 @@ require 'spec_helper' describe 'Group CI/CD settings' do include WaitForRequests - let(:user) {create(:user)} - let(:group) {create(:group)} + let(:user) { create(:user) } + let(:group) { create(:group) } before do group.add_owner(user) @@ -36,4 +36,45 @@ describe 'Group CI/CD settings' do end end end + + describe 'Auto DevOps form' do + before do + stub_application_setting(auto_devops_enabled: true) + end + + context 'as owner first visiting group settings' do + it 'should see instance enabled badge' do + visit group_settings_ci_cd_path(group) + + page.within '#auto-devops-settings' do + expect(page).to have_content('instance enabled') + end + end + end + + context 'when Auto DevOps group has been enabled' do + it 'should see group enabled badge' do + group.update!(auto_devops_enabled: true) + + visit group_settings_ci_cd_path(group) + + page.within '#auto-devops-settings' do + expect(page).to have_content('group enabled') + end + end + end + + context 'when Auto DevOps group has been disabled' do + it 'should not see a badge' do + group.update!(auto_devops_enabled: false) + + visit group_settings_ci_cd_path(group) + + page.within '#auto-devops-settings' do + expect(page).not_to have_content('instance enabled') + expect(page).not_to have_content('group enabled') + end + end + end + end end diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index 4c85abe9971..bf0c0de89b2 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -110,6 +110,37 @@ describe "Projects > Settings > Pipelines settings" do expect(page).not_to have_content('instance enabled') end end + + context 'when auto devops is turned on group level' do + before do + project.update!(namespace: create(:group, :auto_devops_enabled)) + end + + it 'renders group enabled badge' do + visit project_settings_ci_cd_path(project) + + page.within '#autodevops-settings' do + expect(page).to have_content('group enabled') + expect(find_field('project_auto_devops_attributes_enabled')).to be_checked + end + end + end + + context 'when auto devops is turned on group parent level', :nested_groups do + before do + group = create(:group, parent: create(:group, :auto_devops_enabled)) + project.update!(namespace: group) + end + + it 'renders group enabled badge' do + visit project_settings_ci_cd_path(project) + + page.within '#autodevops-settings' do + expect(page).to have_content('group enabled') + expect(find_field('project_auto_devops_attributes_enabled')).to be_checked + end + end + end end end diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb index 223e562238d..d2540696b17 100644 --- a/spec/helpers/auto_devops_helper_spec.rb +++ b/spec/helpers/auto_devops_helper_spec.rb @@ -29,11 +29,11 @@ describe AutoDevopsHelper do end context 'when the banner is disabled by feature flag' do - it 'allows the feature flag to disable' do + before do Feature.get(:auto_devops_banner_disabled).enable - - expect(subject).to be(false) end + + it { is_expected.to be_falsy } end context 'when dismissed' do @@ -90,4 +90,136 @@ describe AutoDevopsHelper do it { is_expected.to eq(false) } end end + + describe '#badge_for_auto_devops_scope' do + subject { helper.badge_for_auto_devops_scope(receiver) } + + context 'when receiver is a group' do + context 'when explicitly enabled' do + let(:receiver) { create(:group, :auto_devops_enabled) } + + it { is_expected.to eq('group enabled') } + end + + context 'when explicitly disabled' do + let(:receiver) { create(:group, :auto_devops_disabled) } + + it { is_expected.to be_nil } + end + + context 'when auto devops is implicitly enabled' do + let(:receiver) { create(:group) } + + context 'by instance' do + before do + stub_application_setting(auto_devops_enabled: true) + end + + it { is_expected.to eq('instance enabled') } + end + + context 'with groups', :nested_groups do + before do + receiver.update(parent: parent) + end + + context 'when auto devops is enabled on parent' do + let(:parent) { create(:group, :auto_devops_enabled) } + + it { is_expected.to eq('group enabled') } + end + + context 'when auto devops is enabled on parent group' do + let(:root_parent) { create(:group, :auto_devops_enabled) } + let(:parent) { create(:group, parent: root_parent) } + + it { is_expected.to eq('group enabled') } + end + + context 'when auto devops disabled set on parent group' do + let(:root_parent) { create(:group, :auto_devops_disabled) } + let(:parent) { create(:group, parent: root_parent) } + + it { is_expected.to be_nil } + end + end + end + end + + context 'when receiver is a project' do + context 'when auto devops is enabled at project level' do + let(:receiver) { create(:project, :auto_devops) } + + it { is_expected.to be_nil } + end + + context 'when auto devops is disabled at project level' do + let(:receiver) { create(:project, :auto_devops_disabled) } + + it { is_expected.to be_nil } + end + + context 'when auto devops is implicitly enabled' do + let(:receiver) { create(:project) } + + context 'by instance' do + before do + stub_application_setting(auto_devops_enabled: true) + end + + it { is_expected.to eq('instance enabled') } + end + + context 'with groups', :nested_groups do + let(:receiver) { create(:project, :repository, namespace: group) } + + before do + stub_application_setting(auto_devops_enabled: false) + end + + context 'when auto devops is enabled on group level' do + let(:group) { create(:group, :auto_devops_enabled) } + + it { is_expected.to eq('group enabled') } + end + + context 'when auto devops is enabled on root group' do + let(:root_parent) { create(:group, :auto_devops_enabled) } + let(:group) { create(:group, parent: root_parent) } + + it { is_expected.to eq('group enabled') } + end + end + end + + context 'when auto devops is implicitly disabled' do + let(:receiver) { create(:project) } + + context 'by instance' do + before do + stub_application_setting(auto_devops_enabled: false) + end + + it { is_expected.to be_nil } + end + + context 'with groups', :nested_groups do + let(:receiver) { create(:project, :repository, namespace: group) } + + context 'when auto devops is disabled on group level' do + let(:group) { create(:group, :auto_devops_disabled) } + + it { is_expected.to be_nil } + end + + context 'when root group is enabled and parent disabled' do + let(:root_parent) { create(:group, :auto_devops_enabled) } + let(:group) { create(:group, :auto_devops_disabled, parent: root_parent) } + + it { is_expected.to be_nil } + end + end + end + end + end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 9dc32a815d8..16624ce47d0 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -810,4 +810,125 @@ describe Group do it { is_expected.to be_truthy } end end + + describe '#first_auto_devops_config' do + using RSpec::Parameterized::TableSyntax + + let(:group) { create(:group) } + + subject { group.first_auto_devops_config } + + where(:instance_value, :group_value, :config) do + # Instance level enabled + true | nil | { status: true, scope: :instance } + true | true | { status: true, scope: :group } + true | false | { status: false, scope: :group } + + # Instance level disabled + false | nil | { status: false, scope: :instance } + false | true | { status: true, scope: :group } + false | false | { status: false, scope: :group } + end + + with_them do + before do + stub_application_setting(auto_devops_enabled: instance_value) + + group.update_attribute(:auto_devops_enabled, group_value) + end + + it { is_expected.to eq(config) } + end + + context 'with parent groups', :nested_groups do + where(:instance_value, :parent_value, :group_value, :config) do + # Instance level enabled + true | nil | nil | { status: true, scope: :instance } + true | nil | true | { status: true, scope: :group } + true | nil | false | { status: false, scope: :group } + + true | true | nil | { status: true, scope: :group } + true | true | true | { status: true, scope: :group } + true | true | false | { status: false, scope: :group } + + true | false | nil | { status: false, scope: :group } + true | false | true | { status: true, scope: :group } + true | false | false | { status: false, scope: :group } + + # Instance level disable + false | nil | nil | { status: false, scope: :instance } + false | nil | true | { status: true, scope: :group } + false | nil | false | { status: false, scope: :group } + + false | true | nil | { status: true, scope: :group } + false | true | true | { status: true, scope: :group } + false | true | false | { status: false, scope: :group } + + false | false | nil | { status: false, scope: :group } + false | false | true | { status: true, scope: :group } + false | false | false | { status: false, scope: :group } + end + + with_them do + before do + stub_application_setting(auto_devops_enabled: instance_value) + parent = create(:group, auto_devops_enabled: parent_value) + + group.update!( + auto_devops_enabled: group_value, + parent: parent + ) + end + + it { is_expected.to eq(config) } + end + end + end + + describe '#auto_devops_enabled?' do + subject { group.auto_devops_enabled? } + + context 'when auto devops is explicitly enabled on group' do + let(:group) { create(:group, :auto_devops_enabled) } + + it { is_expected.to be_truthy } + end + + context 'when auto devops is explicitly disabled on group' do + let(:group) { create(:group, :auto_devops_disabled) } + + it { is_expected.to be_falsy } + end + + context 'when auto devops is implicitly enabled or disabled' do + before do + stub_application_setting(auto_devops_enabled: false) + + group.update!(parent: parent_group) + end + + context 'when auto devops is enabled on root group' do + let(:root_group) { create(:group, :auto_devops_enabled) } + let(:subgroup) { create(:group, parent: root_group) } + let(:parent_group) { create(:group, parent: subgroup) } + + it { is_expected.to be_truthy } + end + + context 'when auto devops is disabled on root group' do + let(:root_group) { create(:group, :auto_devops_disabled) } + let(:subgroup) { create(:group, parent: root_group) } + let(:parent_group) { create(:group, parent: subgroup) } + + it { is_expected.to be_falsy } + end + + context 'when auto devops is disabled on parent group and enabled on root group' do + let(:root_group) { create(:group, :auto_devops_enabled) } + let(:parent_group) { create(:group, :auto_devops_disabled, parent: root_group) } + + it { is_expected.to be_falsy } + end + end + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 475fbe56e4d..aadc298ae0b 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -775,4 +775,28 @@ describe Namespace do end end end + + describe '#auto_devops_enabled' do + context 'with users' do + let(:user) { create(:user) } + + subject { user.namespace.auto_devops_enabled? } + + before do + user.namespace.update!(auto_devops_enabled: auto_devops_enabled) + end + + context 'when auto devops is explicitly enabled' do + let(:auto_devops_enabled) { true } + + it { is_expected.to eq(true) } + end + + context 'when auto devops is explicitly disabled' do + let(:auto_devops_enabled) { false } + + it { is_expected.to eq(false) } + end + end + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 71bd7972436..5c09faafd83 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3631,28 +3631,28 @@ describe Project do subject { project.auto_devops_enabled? } + context 'when explicitly enabled' do + before do + create(:project_auto_devops, project: project) + end + + it { is_expected.to be_truthy } + end + + context 'when explicitly disabled' do + before do + create(:project_auto_devops, project: project, enabled: false) + end + + it { is_expected.to be_falsey } + end + context 'when enabled in settings' do before do stub_application_setting(auto_devops_enabled: true) end it { is_expected.to be_truthy } - - context 'when explicitly enabled' do - before do - create(:project_auto_devops, project: project) - end - - it { is_expected.to be_truthy } - end - - context 'when explicitly disabled' do - before do - create(:project_auto_devops, project: project, enabled: false) - end - - it { is_expected.to be_falsey } - end end context 'when disabled in settings' do @@ -3670,12 +3670,93 @@ describe Project do it { is_expected.to be_truthy } end - context 'when force_autodevops_on_by_default is enabled for the project' do + context 'when explicitly disabled' do before do - Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100) + create(:project_auto_devops, :disabled, project: project) end - it { is_expected.to be_truthy } + it { is_expected.to be_falsey } + end + end + + context 'when force_autodevops_on_by_default is enabled for the project' do + it { is_expected.to be_truthy } + end + + context 'with group parents' do + let(:instance_enabled) { true } + + before do + stub_application_setting(auto_devops_enabled: instance_enabled) + project.update!(namespace: parent_group) + end + + context 'when enabled on parent' do + let(:parent_group) { create(:group, :auto_devops_enabled) } + + context 'when auto devops instance enabled' do + it { is_expected.to be_truthy } + end + + context 'when auto devops instance disabled' do + let(:instance_disabled) { false } + + it { is_expected.to be_truthy } + end + end + + context 'when disabled on parent' do + let(:parent_group) { create(:group, :auto_devops_disabled) } + + context 'when auto devops instance enabled' do + it { is_expected.to be_falsy } + end + + context 'when auto devops instance disabled' do + let(:instance_disabled) { false } + + it { is_expected.to be_falsy } + end + end + + context 'when enabled on root parent', :nested_groups do + let(:parent_group) { create(:group, parent: create(:group, :auto_devops_enabled)) } + + context 'when auto devops instance enabled' do + it { is_expected.to be_truthy } + end + + context 'when auto devops instance disabled' do + let(:instance_disabled) { false } + + it { is_expected.to be_truthy } + end + + context 'when explicitly disabled on parent' do + let(:parent_group) { create(:group, :auto_devops_disabled, parent: create(:group, :auto_devops_enabled)) } + + it { is_expected.to be_falsy } + end + end + + context 'when disabled on root parent', :nested_groups do + let(:parent_group) { create(:group, parent: create(:group, :auto_devops_disabled)) } + + context 'when auto devops instance enabled' do + it { is_expected.to be_falsy } + end + + context 'when auto devops instance disabled' do + let(:instance_disabled) { false } + + it { is_expected.to be_falsy } + end + + context 'when explicitly disabled on parent' do + let(:parent_group) { create(:group, :auto_devops_disabled, parent: create(:group, :auto_devops_enabled)) } + + it { is_expected.to be_falsy } + end end end end @@ -3722,15 +3803,52 @@ describe Project do end end end + + context 'when enabled on group' do + it 'has auto devops implicitly enabled' do + project.update(namespace: create(:group, :auto_devops_enabled)) + + expect(project).to have_auto_devops_implicitly_enabled + end + end + + context 'when enabled on parent group' do + it 'has auto devops implicitly enabled' do + subgroup = create(:group, parent: create(:group, :auto_devops_enabled)) + project.update(namespace: subgroup) + + expect(project).to have_auto_devops_implicitly_enabled + end + end end describe '#has_auto_devops_implicitly_disabled?' do + set(:project) { create(:project) } + before do allow(Feature).to receive(:enabled?).and_call_original Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(0) end - set(:project) { create(:project) } + context 'when explicitly disabled' do + before do + create(:project_auto_devops, project: project, enabled: false) + end + + it 'does not have auto devops implicitly disabled' do + expect(project).not_to have_auto_devops_implicitly_disabled + end + end + + context 'when explicitly enabled' do + before do + create(:project_auto_devops, project: project, enabled: true) + end + + it 'does not have auto devops implicitly disabled' do + expect(project).not_to have_auto_devops_implicitly_disabled + end + end context 'when enabled in settings' do before do @@ -3753,6 +3871,8 @@ describe Project do context 'when force_autodevops_on_by_default is enabled for the project' do before do + create(:project_auto_devops, project: project, enabled: false) + Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100) end @@ -3761,23 +3881,20 @@ describe Project do end end - context 'when explicitly disabled' do - before do - create(:project_auto_devops, project: project, enabled: false) - end + context 'when disabled on group' do + it 'has auto devops implicitly disabled' do + project.update!(namespace: create(:group, :auto_devops_disabled)) - it 'does not have auto devops implicitly disabled' do - expect(project).not_to have_auto_devops_implicitly_disabled + expect(project).to have_auto_devops_implicitly_disabled end end - context 'when explicitly enabled' do - before do - create(:project_auto_devops, project: project, enabled: true) - end + context 'when disabled on parent group' do + it 'has auto devops implicitly disabled' do + subgroup = create(:group, parent: create(:group, :auto_devops_disabled)) + project.update!(namespace: subgroup) - it 'does not have auto devops implicitly disabled' do - expect(project).not_to have_auto_devops_implicitly_disabled + expect(project).to have_auto_devops_implicitly_disabled end end end diff --git a/spec/services/groups/auto_devops_service_spec.rb b/spec/services/groups/auto_devops_service_spec.rb new file mode 100644 index 00000000000..7f8ab517cef --- /dev/null +++ b/spec/services/groups/auto_devops_service_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Groups::AutoDevopsService, '#execute' do + set(:group) { create(:group) } + set(:user) { create(:user) } + let(:group_params) { { auto_devops_enabled: '0' } } + let(:service) { described_class.new(group, user, group_params) } + + context 'when user does not have enough privileges' do + it 'raises exception' do + group.add_developer(user) + + expect do + service.execute + end.to raise_exception(Gitlab::Access::AccessDeniedError) + end + end + + context 'when user has enough privileges' do + before do + group.add_owner(user) + end + + it 'updates group auto devops enabled accordingly' do + service.execute + + expect(group.auto_devops_enabled).to eq(false) + end + + context 'when group has projects' do + it 'reflects changes on projects' do + project_1 = create(:project, namespace: group) + + service.execute + + expect(project_1).not_to have_auto_devops_implicitly_enabled + end + end + + context 'when group has subgroups' do + it 'reflects changes on subgroups' do + subgroup_1 = create(:group, parent: group) + + service.execute + + expect(subgroup_1.auto_devops_enabled?).to eq(false) + end + + context 'when subgroups have projects', :nested_groups do + it 'reflects changes on projects' do + subgroup_1 = create(:group, parent: group) + project_1 = create(:project, namespace: subgroup_1) + + service.execute + + expect(project_1).not_to have_auto_devops_implicitly_enabled + end + end + end + end +end diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb index 2a2539c80b5..b52fc719a64 100644 --- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb +++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb @@ -5,6 +5,7 @@ describe 'projects/settings/ci_cd/_autodevops_form' do before do assign :project, project + allow(view).to receive(:auto_devops_enabled) { true } end it 'shows a warning message about Kubernetes cluster' do