diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue index c85099800bd795afdd28d8f90047569f35d4c57b..57ac96edd1ae6dbaee93a6d345da081a424899ba 100644 --- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue +++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue @@ -253,6 +253,7 @@ export default { > <gl-form-input id="policyName" + data-testid="policy-name-text" :disabled="hasParsingError" :state="hasValidName || !showValidation" :value="policy.name" @@ -268,6 +269,7 @@ export default { > <gl-form-textarea id="policyDescription" + data-testid="policy-description-text" :disabled="hasParsingError" :value="policy.description" no-resize diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue index c378ac7db3f89a298357f986354d86bb9d7caba5..b056f9facc7ed4eb699f0aadadee203d25ddccf5 100644 --- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue +++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_file_path.vue @@ -193,6 +193,7 @@ export default { <template #projectSelector> <group-projects-dropdown class="gl-max-w-20" + data-testid="pipeline-execution-project-dropdown" :group-full-path="groupProjectsPath" :selected="selectedProjectId" :multiple="false" @@ -235,6 +236,7 @@ export default { <gl-form-group class="gl-mb-0 gl-w-full" label-sr-only + data-testid="ci-file-path-text" :label="$options.i18n.formGroupLabel" :optional="false" :invalid-feedback="filePathValidationError" diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue index 59e97730c463e9563a37bab8fddcf7b01794b496..b5d32a9d510ec7198203134ea40376a33d996c59 100644 --- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue +++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action/code_block_strategy_selector.vue @@ -32,6 +32,7 @@ export default { <template> <gl-collapsible-listbox label-for="file-path" + data-testid="strategy-selector-dropdown" :items="$options.CUSTOM_STRATEGY_OPTIONS_LISTBOX_ITEMS" :toggle-text="toggleText" :selected="strategy" diff --git a/qa/qa/ee/page/group/policies/security_policies.rb b/qa/qa/ee/page/group/policies/security_policies.rb new file mode 100644 index 0000000000000000000000000000000000000000..d75d17af170b207ba5f87a17b31b342baa68a9ad --- /dev/null +++ b/qa/qa/ee/page/group/policies/security_policies.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module QA + module EE + module Page + module Group + module Policies + class SecurityPolicies < QA::Page::Base + view 'ee/app/assets/javascripts/security_orchestration/components/policies/list_header.vue' do + element 'new-policy-button' + end + + view 'ee/app/assets/javascripts/security_orchestration/components/policy_editor/pipeline_execution/action' \ + '/code_block_file_path.vue' do + element 'pipeline-execution-project-dropdown' + element 'ci-file-path-text' + end + + view 'ee/app/assets/javascripts/security_orchestration/components/policy_editor/editor_layout.vue' do + element 'policy-name-text' + element 'policy-description-text' + element 'save-policy' + end + + def click_new_policy + click_element('new-policy-button') + wait_for_requests + end + + def click_pipeline_execution_policy + click_element('select-policy-pipeline_execution_policy') + wait_for_requests + end + + def set_policy_name(policy_name) + fill_element('policy-name-text', policy_name) + end + + def select_strategy(override = false) + click_element('strategy-selector-dropdown') + + if override + click_element('listbox-item-override_project_ci') + else + click_element('listbox-item-inject_ci') + end + end + + def set_ci_file_path(file_path) + within_element('ci-file-path-text') do + find('[id="file-path"]').set(file_path) + end + end + + def set_policy_description(description) + fill_element('policy-description-text', description) + end + + def select_project(project_id) + click_element('pipeline-execution-project-dropdown') + find_element(project_listbox_item(project_id)).click + end + + def save_policy + click_element('save-policy') + end + + def project_listbox_item(project_id) + "listbox-item-gid://gitlab/Project/#{project_id}" + end + end + end + end + end + end +end diff --git a/qa/qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb b/qa/qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b53a456ca0d1bbd77d797b041ecbd82a1668899a --- /dev/null +++ b/qa/qa/specs/features/ee/browser_ui/10_govern/group_pipeline_execution_policy_spec.rb @@ -0,0 +1,193 @@ +# frozen_string_literal: true + +module QA + RSpec.describe 'Govern', :runner, product_group: :security_policies do + describe 'Group Pipeline Execution Policy' do + let(:executor) { "qa-runner-#{Faker::Alphanumeric.alphanumeric(number: 8)}" } + let(:group) { create(:group) } + let(:policy_name) { 'Greyhound' } + let(:template_project) do + create(:project, name: 'template-project-pipeline-exec-policy', + group: group, initialize_with_readme: true) + end + + let(:template_project_ci_path) { "policy-ci-#{Faker::Alphanumeric.alphanumeric(number: 4)}.yml" } + + let(:project) do + create(:project, name: 'project-test-pipeline-exec-policy', group: group, initialize_with_readme: true) + end + + let(:merge_request) do + create(:merge_request, + project: project, + description: Faker::Lorem.sentence, + target_new_branch: false, + file_name: Faker::File.unique.file_name, + file_content: Faker::Lorem.sentence) + end + + let!(:runner) { create(:group_runner, group: group, name: executor, tags: [executor]) } + + let!(:pipeline_exec_policy_ci_file) do + create(:commit, project: template_project, commit_message: 'Add policy-ci.yml', actions: [ + { action: 'create', file_path: template_project_ci_path, content: pipeline_execution_policy_yaml } + ]) + end + + let!(:project_ci_file) do + create(:commit, project: project, commit_message: 'Add project .gitlab-ci.yml', actions: [ + { action: 'create', file_path: '.gitlab-ci.yml', content: project_ci_yaml } + ]) + end + + let(:expected_job_log) { "This job is due to pipeline execution policy for #{group.path}" } + + before do + Flow::Login.sign_in + end + + after do + runner.remove_via_api! + end + + it 'executes jobs as per inject strategy', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/499398' do + create_pipeline_execution_policy + merge_pipeline_execution_policy + + merge_request.visit! + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) + Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'success') + + Flow::Pipeline.visit_latest_pipeline + aggregate_failures do + Page::Project::Pipeline::Show.perform do |pipeline| + expect(pipeline).to have_job('pipeline_exec_policy_build_job') + expect(pipeline).to have_job('pipeline_exec_policy_pre_job') + expect(pipeline).to have_job('pipeline_exec_policy_post_job') + expect(pipeline).to have_job('project_job') + end + end + + project.visit_job('pipeline_exec_policy_build_job') + + verify_expected_job_log(job_name: 'pipeline_exec_policy_build_job', expected_text: expected_job_log) + + project.visit_job('pipeline_exec_policy_pre_job') + + verify_expected_job_log(job_name: 'pipeline_exec_policy_pre_job', expected_text: expected_job_log) + + project.visit_job('pipeline_exec_policy_post_job') + + verify_expected_job_log(job_name: 'pipeline_exec_policy_post_job', expected_text: expected_job_log) + + project.visit_job('project_job') + + verify_expected_job_log(job_name: 'project_job', expected_text: 'This is the project job. Colour grey') + end + + it 'executes jobs as per override strategy', + testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/499418' do + create_pipeline_execution_policy(override: true) + merge_pipeline_execution_policy + + merge_request.visit! + Flow::Pipeline.wait_for_pipeline_creation_via_api(project: project) + Flow::Pipeline.wait_for_latest_pipeline_to_have_status(project: project, status: 'success') + + Flow::Pipeline.visit_latest_pipeline + aggregate_failures do + Page::Project::Pipeline::Show.perform do |pipeline| + expect(pipeline).to have_job('pipeline_exec_policy_build_job') + expect(pipeline).to have_job('pipeline_exec_policy_pre_job') + expect(pipeline).to have_job('pipeline_exec_policy_post_job') + expect(pipeline).not_to have_job('project_job') + end + end + + project.visit_job('pipeline_exec_policy_build_job') + + verify_expected_job_log(job_name: 'pipeline_exec_policy_build_job', expected_text: expected_job_log) + + project.visit_job('pipeline_exec_policy_pre_job') + + verify_expected_job_log(job_name: 'pipeline_exec_policy_pre_job', expected_text: expected_job_log) + + project.visit_job('pipeline_exec_policy_post_job') + + verify_expected_job_log(job_name: 'pipeline_exec_policy_post_job', expected_text: expected_job_log) + end + + private + + def create_pipeline_execution_policy(override: false) + group.visit! + Page::Group::Menu.perform(&:go_to_policies) + + EE::Page::Group::Policies::SecurityPolicies.perform do |new_policy_page| + new_policy_page.click_new_policy + new_policy_page.click_pipeline_execution_policy + new_policy_page.set_policy_name(policy_name) + new_policy_page.set_policy_description('To test pipeline execution policy') + new_policy_page.select_strategy(override) if override + new_policy_page.select_project(template_project.id) + new_policy_page.set_ci_file_path(template_project_ci_path) + new_policy_page.save_policy + end + end + + def merge_pipeline_execution_policy + Support::Waiter.wait_until(message: 'Wait for policy MR page', max_duration: 80) do + Page::MergeRequest::Show.perform(&:has_merge_button?) + end + + Support::Retrier.retry_on_exception(sleep_interval: 2, message: "Retrying policy merge") do + Page::MergeRequest::Show.perform(&:merge!) + end + end + + def verify_expected_job_log(job_name:, expected_text:) + project.visit_job(job_name) + Page::Project::Job::Show.perform do |show| + expect(show.output).to have_content(expected_text), + "Didn't find '#{expected_text}' within #{job_name}'s log:\n#{show.output}." + end + end + + def pipeline_execution_policy_yaml + <<~YAML + default: + tags: ["#{executor}"] + + pipeline_exec_policy_build_job: + stage: build + script: + - echo "#{expected_job_log}" + + pipeline_exec_policy_pre_job: + stage: .pipeline-policy-pre + script: + - echo "#{expected_job_log}" + + pipeline_exec_policy_post_job: + stage: .pipeline-policy-post + script: + - echo "#{expected_job_log}" + YAML + end + + def project_ci_yaml + <<~YAML + stages: + - build + + project_job: + stage: build + tags: ["#{executor}"] + script: + - echo "This is the project job. Colour grey" + YAML + end + end + end +end