diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb index 6d76b7c6b0c3fe6b885a33570b9850ee51420fca..8144aec9e96d580628deacfa52a65930bdca80c6 100644 --- a/app/services/git/base_hooks_service.rb +++ b/app/services/git/base_hooks_service.rb @@ -133,7 +133,7 @@ def generate_vars_from_push_options # Accept only valid format. We ignore the following formats # 1. "=123". In this case, `key` will be an empty string # 2. "FOO". In this case, `value` will be nil. - # However, the format "FOO=" will result in key beign `FOO` and value + # However, the format "FOO=" will result in key being `FOO` and value # being an empty string. This is acceptable. next if key.blank? || value.nil? @@ -170,9 +170,29 @@ def push_data @push_data.dup end - # to be overridden in EE + # merges with EE override def pipeline_options - {} + return {} unless ci_inputs_from_push_options + + { + inputs: generate_ci_inputs_from_push_options || {} + } + end + + def ci_inputs_from_push_options + strong_memoize(:ci_inputs_from_push_options) do + push_options&.dig(:ci, :input) + end + end + + def generate_ci_inputs_from_push_options + return {} unless ci_inputs_from_push_options + + params = ci_inputs_from_push_options.map do |input, _| + input.to_s.split("=", 2) + end + + ::Ci::PipelineCreation::Inputs.parse_params(params.to_h) end def log_pipeline_errors(error_message) diff --git a/doc/topics/git/commit.md b/doc/topics/git/commit.md index 21dd4deb0d3dbd4e7ecdb3b1c022633383dd3636..6c1223bd96a1a4dcd948bb8c04ad6680f7913144 100644 --- a/doc/topics/git/commit.md +++ b/doc/topics/git/commit.md @@ -117,6 +117,7 @@ see [issue 373212](https://gitlab.com/gitlab-org/gitlab/-/issues/373212). | Push option | Description | Example | |--------------------------------|-------------|---------| +| `ci.input==` | Creates a pipeline with the specified inputs. | For example: `git push -o ci.input='stage=test' -o ci.input='security_scan=false'`. Example with an array of strings: `ci.input='["string", "double", "quotes"]'` | | `ci.skip` | Do not create a CI/CD pipeline for the latest push. Skips only branch pipelines and not [merge request pipelines](../../ci/pipelines/merge_request_pipelines.md). This does not skip pipelines for CI/CD integrations, such as Jenkins. | `git push -o ci.skip` | | `ci.variable="="` | Provide [CI/CD variables](../../ci/variables/_index.md) to the CI/CD pipeline, if one is created due to the push. Passes variables only to branch pipelines and not [merge request pipelines](../../ci/pipelines/merge_request_pipelines.md). | `git push -o ci.variable="MAX_RETRIES=10" -o ci.variable="MAX_TIME=600"` | diff --git a/ee/app/services/ee/git/branch_hooks_service.rb b/ee/app/services/ee/git/branch_hooks_service.rb index ebdb521928e2a0d7b99ede1fd57aaa7a43948e52..cf27a6bc88ea03de611fe939c1ada096155d1006 100644 --- a/ee/app/services/ee/git/branch_hooks_service.rb +++ b/ee/app/services/ee/git/branch_hooks_service.rb @@ -12,7 +12,7 @@ def pipeline_options mirror_update = project.mirror? && project.repository.up_to_date_with_upstream?(branch_name) - { mirror_update: mirror_update } + super.merge(mirror_update: mirror_update) end end end diff --git a/lib/gitlab/push_options.rb b/lib/gitlab/push_options.rb index 4df9c3d64abb206e26c8d32c1d222aedbd21d2cb..17a37d3c4b4f112dbe3f94c4a36216d0f8ead528 100644 --- a/lib/gitlab/push_options.rb +++ b/lib/gitlab/push_options.rb @@ -22,7 +22,7 @@ class PushOptions ] }, ci: { - keys: [:skip, :variable] + keys: [:skip, :variable, :input] }, integrations: { keys: [:skip_ci] @@ -36,6 +36,7 @@ class PushOptions }).freeze MULTI_VALUE_OPTIONS = [ + %w[ci input], %w[ci variable], %w[merge_request label], %w[merge_request unlabel], diff --git a/spec/services/git/base_hooks_service_spec.rb b/spec/services/git/base_hooks_service_spec.rb index fd3dd2896fcc1db88fe47c4a93b68c7a79626316..a460319be4969a00615545d99b214f5a93225b12 100644 --- a/spec/services/git/base_hooks_service_spec.rb +++ b/spec/services/git/base_hooks_service_spec.rb @@ -152,6 +152,71 @@ def commits_count end end + describe 'Pipeline options' do + context 'when pipeline options contain inputs' do + let(:pipeline_params) do + { + after: newrev, + before: oldrev, + checkout_sha: checkout_sha, + push_options: push_options, + ref: ref, + variables_attributes: variables_attributes + } + end + + let(:push_options) do + { + ci: { + input: { + 'security_scan=false': 1, + 'stage=test': 1, + 'level=20': 1, + 'enviornments=["staging", "production"]': 1, + 'rules=[{"if": "$CI_MERGE_REQUEST_ID"}, {"if": "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"}]': 1 + } + } + } + end + + let(:pipeline_service) { double(execute: service_response) } + let(:service_response) { double(error?: false, payload: pipeline, message: 'message') } + let(:pipeline) { double(persisted?: true) } + let(:variables_attributes) { [] } + + let(:inputs) do + { + security_scan: false, + stage: 'test', + level: 20, + enviornments: %w[ + staging production + ], + rules: [ + { if: "$CI_MERGE_REQUEST_ID" }, + { if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH" } + ] + } + end + + before do + params[:push_options] = push_options + end + + it 'calls the create pipeline service' do + expect(Ci::CreatePipelineService) + .to receive(:new) + .with(project, user, pipeline_params) + .and_return(pipeline_service) + + expect(pipeline_service).to receive(:execute).with(:push, { inputs: inputs }) + expect(subject).not_to receive(:log_pipeline_errors) + + subject.execute + end + end + end + describe 'Generating CI variables from push options' do let(:pipeline_params) do {