From a37034de9fb0f4ffeb3ca68cc01364c1bd423780 Mon Sep 17 00:00:00 2001 From: Reuben Pereira Date: Wed, 12 Oct 2022 20:17:21 +0530 Subject: [PATCH] Allow pipeline name to be defined under workflow This change is behind a feature flag called `pipeline_name`. When the feature flag is disabled, the name in .gitlab-ci.yml will be ignored and will not be saved to database. --- app/assets/javascripts/editor/schema/ci.json | 7 +++ .../development/pipeline_name.yml | 8 ++++ doc/ci/yaml/index.md | 24 ++++++++++ lib/gitlab/ci/config.rb | 4 ++ lib/gitlab/ci/config/entry/workflow.rb | 7 ++- lib/gitlab/ci/pipeline/chain/populate.rb | 11 +++++ lib/gitlab/ci/yaml_processor/result.rb | 4 ++ .../ci/yaml_tests/positive_tests/rules.yml | 2 + .../gitlab/ci/config/entry/workflow_spec.rb | 48 +++++++++++++++++++ spec/lib/gitlab/ci/config_spec.rb | 27 +++++++++++ .../gitlab/ci/pipeline/chain/populate_spec.rb | 43 +++++++++++++++++ spec/lib/gitlab/ci/yaml_processor_spec.rb | 29 +++++++++++ 12 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 config/feature_flags/development/pipeline_name.yml diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index 52bf9d25e6b4df..df02d3dd1576ef 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -103,6 +103,7 @@ "workflow": { "type": "object", "properties": { + "name": { "$ref": "#/definitions/workflowName" }, "rules": { "type": "array", "items": { @@ -714,6 +715,12 @@ ] } }, + "workflowName": { + "type": "string", + "markdownDescription": "Defines the pipeline name. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#workflowname).", + "minLength": 1, + "maxLength": 255 + }, "globalVariables": { "markdownDescription": "Defines default variables for all jobs. Job level property overrides global variables. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#variables).", "type": "object", diff --git a/config/feature_flags/development/pipeline_name.yml b/config/feature_flags/development/pipeline_name.yml new file mode 100644 index 00000000000000..40557a7d01e5bc --- /dev/null +++ b/config/feature_flags/development/pipeline_name.yml @@ -0,0 +1,8 @@ +--- +name: pipeline_name +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97502 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/376095 +milestone: '15.5' +type: development +group: group::delivery +default_enabled: false diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 054a100574bce3..dd77526b47dc3c 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -398,6 +398,30 @@ Use [`workflow`](workflow.md) to control pipeline behavior. - [`workflow: rules` examples](workflow.md#workflow-rules-examples) - [Switch between branch pipelines and merge request pipelines](workflow.md#switch-between-branch-pipelines-and-merge-request-pipelines) +#### `workflow:name` + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/372538) in GitLab 15.5 [with a flag](../../administration/feature_flags.md) named `pipeline_name`. Disabled by default. + +FLAG: +On self-managed GitLab, by default this feature is not available. To make it available, +ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `pipeline_name`. +The feature is not ready for production use. + +You can use `name` in `workflow:` to define a name for pipelines. + +All pipelines are assigned the defined name. Any leading or trailing spaces in the name are removed. + +**Possible inputs**: + +- A string. + +**Example of `workflow:name`**: + +```yaml +workflow: + name: 'Pipeline name' +``` + #### `workflow:rules` The `rules` keyword in `workflow` is similar to [`rules` defined in jobs](#rules), diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index 438fa1cb3b23de..661c6fb87e3077 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -85,6 +85,10 @@ def workflow_rules root.workflow_entry.rules_value end + def workflow_name + root.workflow_entry.name + end + def normalized_jobs @normalized_jobs ||= Ci::Config::Normalizer.new(jobs).normalize_jobs end diff --git a/lib/gitlab/ci/config/entry/workflow.rb b/lib/gitlab/ci/config/entry/workflow.rb index 5bc992a38a0b64..691d9e2d48b269 100644 --- a/lib/gitlab/ci/config/entry/workflow.rb +++ b/lib/gitlab/ci/config/entry/workflow.rb @@ -6,12 +6,17 @@ class Config module Entry class Workflow < ::Gitlab::Config::Entry::Node include ::Gitlab::Config::Entry::Configurable + include ::Gitlab::Config::Entry::Validatable + include ::Gitlab::Config::Entry::Attributable - ALLOWED_KEYS = %i[rules].freeze + ALLOWED_KEYS = %i[rules name].freeze + + attributes :name validations do validates :config, type: Hash validates :config, allowed_keys: ALLOWED_KEYS + validates :name, allow_nil: true, length: { minimum: 1, maximum: 255 } end entry :rules, Entry::Rules, diff --git a/lib/gitlab/ci/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb index 654e24be8e1112..4bec83557324ab 100644 --- a/lib/gitlab/ci/pipeline/chain/populate.rb +++ b/lib/gitlab/ci/pipeline/chain/populate.rb @@ -25,6 +25,8 @@ def perform! return error('Failed to build the pipeline!') end + set_pipeline_name + raise Populate::PopulateError if pipeline.persisted? end @@ -34,6 +36,15 @@ def break? private + def set_pipeline_name + return if Feature.disabled?(:pipeline_name, pipeline.project) || + @command.yaml_processor_result.workflow_name.blank? + + name = @command.yaml_processor_result.workflow_name + + pipeline.build_pipeline_metadata(project: pipeline.project, title: name) + end + def stage_names # We filter out `.pre/.post` stages, as they alone are not considered # a complete pipeline: diff --git a/lib/gitlab/ci/yaml_processor/result.rb b/lib/gitlab/ci/yaml_processor/result.rb index c0097cd84deb0f..5c3864362dad5c 100644 --- a/lib/gitlab/ci/yaml_processor/result.rb +++ b/lib/gitlab/ci/yaml_processor/result.rb @@ -36,6 +36,10 @@ def workflow_rules @workflow_rules ||= @ci_config.workflow_rules end + def workflow_name + @workflow_name ||= @ci_config.workflow_name&.strip + end + def root_variables @root_variables ||= transform_to_array(@ci_config.variables) end diff --git a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/rules.yml b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/rules.yml index 37cae6b426434a..ef604f707b5c6e 100644 --- a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/rules.yml +++ b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/rules.yml @@ -15,7 +15,9 @@ rules:changes as array of strings: # valid workflow:rules:exists # valid rules:changes:path +# valid workflow:name workflow: + name: 'Pipeline name' rules: - changes: paths: diff --git a/spec/lib/gitlab/ci/config/entry/workflow_spec.rb b/spec/lib/gitlab/ci/config/entry/workflow_spec.rb index 3d19832e13d4e0..97ac199f47de68 100644 --- a/spec/lib/gitlab/ci/config/entry/workflow_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/workflow_spec.rb @@ -65,6 +65,54 @@ end end end + + context 'with workflow name' do + let(:factory) { Gitlab::Config::Entry::Factory.new(described_class).value(workflow_hash) } + + context 'with a blank name' do + let(:workflow_hash) do + { name: '' } + end + + it 'is invalid' do + expect(config).not_to be_valid + end + + it 'returns error about invalid name' do + expect(config.errors).to include('workflow name is too short (minimum is 1 character)') + end + end + + context 'with too long name' do + let(:workflow_hash) do + { name: 'a' * 256 } + end + + it 'is invalid' do + expect(config).not_to be_valid + end + + it 'returns error about invalid name' do + expect(config.errors).to include('workflow name is too long (maximum is 255 characters)') + end + end + + context 'when name is nil' do + let(:workflow_hash) { { name: nil } } + + it 'is valid' do + expect(config).to be_valid + end + end + + context 'when name is not provided' do + let(:workflow_hash) { { rules: [{ if: '$VAR' }] } } + + it 'is valid' do + expect(config).to be_valid + end + end + end end end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 055114769eade8..475503de7da698 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -889,4 +889,31 @@ it { is_expected.to eq([{ if: '$CI_COMMIT_REF_NAME == "master"' }]) } end + + describe '#workflow_name' do + subject(:workflow_name) { config.workflow_name } + + let(:yml) do + <<-EOS + workflow: + name: 'Pipeline name' + + rspec: + script: exit 0 + EOS + end + + it { is_expected.to eq('Pipeline name') } + + context 'with no name' do + let(:yml) do + <<-EOS + rspec: + script: exit 0 + EOS + end + + it { is_expected.to be_nil } + end + end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index 62de4d2e96dd09..51d1661b58674d 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -236,4 +236,47 @@ def run_chain end end end + + context 'with pipeline name' do + let(:config) do + { workflow: { name: ' Pipeline name ' }, rspec: { script: 'rspec' } } + end + + context 'with feature flag disabled' do + before do + stub_feature_flags(pipeline_name: false) + end + + it 'does not build pipeline_metadata' do + run_chain + + expect(pipeline.pipeline_metadata).to be_nil + end + end + + context 'with feature flag enabled' do + before do + stub_feature_flags(pipeline_name: true) + end + + it 'builds pipeline_metadata' do + run_chain + + expect(pipeline.pipeline_metadata.title).to eq('Pipeline name') + expect(pipeline.pipeline_metadata.project).to eq(pipeline.project) + end + + context 'with empty name' do + let(:config) do + { workflow: { name: ' ' }, rspec: { script: 'rspec' } } + end + + it 'strips whitespace from name' do + run_chain + + expect(pipeline.pipeline_metadata).to be_nil + end + end + end + end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 398f8b16f95847..ebf8422489e7e3 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -514,6 +514,35 @@ module Ci expect(subject.root_variables).to eq([]) end end + + context 'with name' do + let(:config) do + <<-EOYML + workflow: + name: 'Pipeline name' + + hello: + script: echo world + EOYML + end + + it 'parses the workflow:name as workflow_name' do + expect(subject.workflow_name).to eq('Pipeline name') + end + end + + context 'with no name' do + let(:config) do + <<-EOYML + hello: + script: echo world + EOYML + end + + it 'parses the workflow:name' do + expect(subject.workflow_name).to be_nil + end + end end describe '#warnings' do -- GitLab