From 51c2a614362e29f98a3db5e469dea498769d67b6 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 6 Feb 2023 19:34:35 +0100 Subject: [PATCH 01/13] Implement CI config input interpolation This commit updates the logic that fetches `include`d CI configuration files so that it can parse the new specification section and interpolate inputs. --- spec/lib/gitlab/ci/yaml_processor_spec.rb | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 360686ce65c100..d4e88a475cbfb0 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1429,6 +1429,56 @@ module Ci subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), opts).execute } + context 'when inputs are required' do + let(:project) { create(:project, :repository) } + let(:opts) { { project: project, sha: project.commit.sha } } + + let(:project_files) do + { + 'local.gitlab-ci.yml' => <<~YAML + spec: + inputs: + test_text: + --- + job1: + script: + - echo "$[[ inputs.test_text ]]" + YAML + } + end + + around do |example| + create_and_delete_files(project, project_files) do + example.run + end + end + + context 'when the required inputs are given' do + let(:include_content) do + [{ + local: '/local.gitlab-ci.yml', + with: { test_text: 'hello CI job' } + }] + end + + it 'interpolates the inputs' do + expect(subject.builds).to include(a_hash_including( + name: 'job1', + options: { script: ['echo "hello CI job"'] } + )) + end + end + + context 'when the required inputs are missing' do + let(:include_content) { [{ local: '/local.gitlab-ci.yml' }] } + + it_behaves_like( + 'returns errors', + 'Included file `local.gitlab-ci.yml` has required inputs that are not given!' + ) + end + end + context "when validating a ci config file with no project context" do context "when a single string is provided" do let(:include_content) { "/local.gitlab-ci.yml" } -- GitLab From e72252d0020a6192043ffc442fef9243ca1bbd3e Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Mon, 6 Feb 2023 19:46:40 +0100 Subject: [PATCH 02/13] Refactor `Ci::Config::Yaml` module Encapsulating most of the logic in a class will make it easier to add more complex logic by enabling the use of instance variables and methods --- lib/gitlab/ci/config/yaml.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/config/yaml.rb b/lib/gitlab/ci/config/yaml.rb index 94ef0afe7f9fa4..80271e6547936f 100644 --- a/lib/gitlab/ci/config/yaml.rb +++ b/lib/gitlab/ci/config/yaml.rb @@ -7,8 +7,12 @@ module Yaml AVAILABLE_TAGS = [Config::Yaml::Tags::Reference].freeze MAX_DOCUMENTS = 2 - class << self - def load!(content) + class Loader + def initialize(content) + @content = content + end + + def load! ensure_custom_tags if ::Feature.enabled?(:ci_multi_doc_yaml) @@ -24,6 +28,8 @@ def load!(content) private + attr_reader :content + def ensure_custom_tags @ensure_custom_tags ||= begin AVAILABLE_TAGS.each { |klass| Psych.add_tag(klass.tag, klass) } @@ -32,6 +38,12 @@ def ensure_custom_tags end end end + + class << self + def load!(content) + Loader.new(content).load! + end + end end end end -- GitLab From 51bf8e7c4144fbb50ff18b0f37582e8ae9bdf179 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 7 Feb 2023 11:15:17 +0100 Subject: [PATCH 03/13] Interpolate inputs when loading CI config YAML This commit adds a new `InputInterpolator` class that handles the input interpolation, and updates `Ci::Config::Yaml` to parse `spec` --- lib/gitlab/ci/config/yaml.rb | 50 +++++++++++++++++----- spec/lib/gitlab/ci/config/yaml_spec.rb | 59 +++++++++++++++++++++++--- 2 files changed, 93 insertions(+), 16 deletions(-) diff --git a/lib/gitlab/ci/config/yaml.rb b/lib/gitlab/ci/config/yaml.rb index 80271e6547936f..382c3bff29d634 100644 --- a/lib/gitlab/ci/config/yaml.rb +++ b/lib/gitlab/ci/config/yaml.rb @@ -6,29 +6,27 @@ class Config module Yaml AVAILABLE_TAGS = [Config::Yaml::Tags::Reference].freeze MAX_DOCUMENTS = 2 + MULTI_DOC_DIVIDER = /^---$/.freeze class Loader - def initialize(content) + def initialize(content, input_values: {}) @content = content + @input_values = input_values end def load! ensure_custom_tags if ::Feature.enabled?(:ci_multi_doc_yaml) - Gitlab::Config::Loader::MultiDocYaml.new( - content, - max_documents: MAX_DOCUMENTS, - additional_permitted_classes: AVAILABLE_TAGS - ).load!.first + load_multi_document_yaml! else - Gitlab::Config::Loader::Yaml.new(content, additional_permitted_classes: AVAILABLE_TAGS).load! + yaml_loader(content).load! end end private - attr_reader :content + attr_reader :content, :input_values def ensure_custom_tags @ensure_custom_tags ||= begin @@ -37,11 +35,43 @@ def ensure_custom_tags true end end + + def load_multi_document_yaml! + if contains_config_spec? + InputInterpolator.new( + spec_inputs, + yaml_loader(documents.second).load_raw!, + input_values: input_values + ).interpolate! + else + yaml_loader(documents.first).load! + end + end + + def contains_config_spec? + documents.count == 2 + end + + def spec_inputs + spec.dig(:spec, :inputs) || {} + end + + def spec + yaml_loader(documents.first).load! + end + + def documents + content.split(MULTI_DOC_DIVIDER).reject(&:empty?) + end + + def yaml_loader(yaml_content) + Gitlab::Config::Loader::Yaml.new(yaml_content, additional_permitted_classes: AVAILABLE_TAGS) + end end class << self - def load!(content) - Loader.new(content).load! + def load!(content, input_values: {}) + Loader.new(content, input_values: input_values).load! end end end diff --git a/spec/lib/gitlab/ci/config/yaml_spec.rb b/spec/lib/gitlab/ci/config/yaml_spec.rb index 4b34553f55e586..7718a0e51c2ebb 100644 --- a/spec/lib/gitlab/ci/config/yaml_spec.rb +++ b/spec/lib/gitlab/ci/config/yaml_spec.rb @@ -26,11 +26,10 @@ }) end - it 'loads the first document from a multi-doc YAML file' do + it 'loads the second document from a multi-doc YAML file' do yaml = <<~YAML spec: - inputs: - test_input: + version: 3.0 --- image: 'image:1.0' texts: @@ -42,14 +41,62 @@ config = described_class.load!(yaml) expect(config).to eq({ - spec: { - inputs: { - test_input: nil + image: 'image:1.0', + texts: { + nested_key: 'value1', + more_text: { + more_nested_key: 'value2' } } }) end + context 'when the loaded file accepts inputs' do + it 'interpolates the input values' do + yaml = <<~YAML + spec: + inputs: + job_name: + test_input: + --- + $[[ inputs.job_name ]]: + script: + - echo "$[[ inputs.test_input ]]" + YAML + + config = described_class.load!(yaml, input_values: { + job_name: 'test_job', + test_input: 'hello interpolation' + }) + + expect(config).to eq({ + test_job: { + script: ['echo "hello interpolation"'] + } + }) + end + + context 'when required inputs are missing values' do + it 'raises a RequiredInputsNotMetError' do + yaml = <<~YAML + spec: + inputs: + job_name: + test_input: + --- + $[[ inputs.job_name ]]: + script: + - echo "$[[ inputs.test_input ]]" + YAML + + expect { described_class.load!(yaml) }.to raise_error( + described_class::InputInterpolator::RequiredInputsNotMetError, + 'job_name, test_input' + ) + end + end + end + context 'when ci_multi_doc_yaml is disabled' do before do stub_feature_flags(ci_multi_doc_yaml: false) -- GitLab From a4d27ec33f9571c8595c816d9436df28b16fd24e Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 7 Feb 2023 11:24:57 +0100 Subject: [PATCH 04/13] Pass inputs into YAML loader This commit adds logic to pass input values from `include:with` to the CI config YAML loader --- lib/gitlab/ci/config/external/file/base.rb | 9 ++++++++- spec/lib/gitlab/ci/yaml_processor_spec.rb | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb index c80344bef8aee4..b891376537d3c2 100644 --- a/lib/gitlab/ci/config/external/file/base.rb +++ b/lib/gitlab/ci/config/external/file/base.rb @@ -14,6 +14,7 @@ class Base def initialize(params, context) @params = params + @input_values = params.fetch(:with, {}) @context = context @errors = [] end @@ -101,6 +102,8 @@ def validate_content! protected + attr_reader :input_values + def expanded_content_hash return unless content_hash @@ -111,8 +114,12 @@ def expanded_content_hash def content_hash strong_memoize(:content_hash) do - ::Gitlab::Ci::Config::Yaml.load!(content) + ::Gitlab::Ci::Config::Yaml.load!(content, input_values: input_values) end + rescue Gitlab::Ci::Config::Yaml::InputInterpolator::RequiredInputsNotMetError => e + errors.push("Included file `local.gitlab-ci.yml` has required inputs #{e.message} that are not given!") + + nil rescue Gitlab::Config::Loader::FormatError nil end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index d4e88a475cbfb0..f56138ebdbc495 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1474,7 +1474,7 @@ module Ci it_behaves_like( 'returns errors', - 'Included file `local.gitlab-ci.yml` has required inputs that are not given!' + 'Included file `local.gitlab-ci.yml` has required inputs test_text that are not given!' ) end end -- GitLab From 0af2d9798d081907a4b0de3368b5113ed0d08c65 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 7 Feb 2023 12:12:11 +0100 Subject: [PATCH 05/13] Commit `InputInterpolator` class I forgot to stage it earlier --- .../ci/config/yaml/input_interpolator.rb | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 lib/gitlab/ci/config/yaml/input_interpolator.rb diff --git a/lib/gitlab/ci/config/yaml/input_interpolator.rb b/lib/gitlab/ci/config/yaml/input_interpolator.rb new file mode 100644 index 00000000000000..a881cadad10c75 --- /dev/null +++ b/lib/gitlab/ci/config/yaml/input_interpolator.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module Yaml + class InputInterpolator + RequiredInputsNotMetError = Class.new(StandardError) + + def initialize(spec_inputs, content, input_values: {}) + @spec_inputs = spec_inputs + @content = content + @input_values = input_values + end + + def interpolate! + return content.deep_symbolize_keys! if spec_inputs.empty? + + raise RequiredInputsNotMetError, missing_required_inputs.join(', ') unless required_inputs_satisfied? + + interpolated_content.deep_symbolize_keys! + end + + private + + attr_reader :spec_inputs, :content, :input_values + + def required_inputs_satisfied? + missing_required_inputs.empty? + end + + def missing_required_inputs + required_inputs - input_values.keys + end + + def required_inputs + spec_inputs.keys.filter { |key| !spec_inputs[key]&.has_key?(:default) } + end + + def interpolated_content + ::Gitlab::Ci::Interpolation::Template.new(content, inputs: input_values).interpolated + end + end + end + end + end +end -- GitLab From 6caf4c1102984072abcd73e1fdedbf903794f0a7 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 7 Feb 2023 15:12:42 +0100 Subject: [PATCH 06/13] Before proceeding, fix some specs Sometimes I wonder if life would be easier if we used types --- lib/gitlab/ci/config/external/file/base.rb | 9 ++++++--- lib/gitlab/ci/config/yaml.rb | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb index b891376537d3c2..5ebce9680b4af5 100644 --- a/lib/gitlab/ci/config/external/file/base.rb +++ b/lib/gitlab/ci/config/external/file/base.rb @@ -14,7 +14,6 @@ class Base def initialize(params, context) @params = params - @input_values = params.fetch(:with, {}) @context = context @errors = [] end @@ -102,8 +101,6 @@ def validate_content! protected - attr_reader :input_values - def expanded_content_hash return unless content_hash @@ -156,6 +153,12 @@ def masked_location context.mask_variables_from(location) end end + + def input_values + return {} unless params.is_a?(Hash) + + params.fetch(:with, {}) + end end end end diff --git a/lib/gitlab/ci/config/yaml.rb b/lib/gitlab/ci/config/yaml.rb index 382c3bff29d634..71b1d85ca7aa39 100644 --- a/lib/gitlab/ci/config/yaml.rb +++ b/lib/gitlab/ci/config/yaml.rb @@ -58,6 +58,8 @@ def spec_inputs def spec yaml_loader(documents.first).load! + rescue Gitlab::Config::Loader::FormatError + {} end def documents -- GitLab From 022823a7dbc5d23448272cf36d58590529766cd8 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 7 Feb 2023 15:28:31 +0100 Subject: [PATCH 07/13] Add spec for invalid `spec` --- spec/lib/gitlab/ci/config/yaml_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/lib/gitlab/ci/config/yaml_spec.rb b/spec/lib/gitlab/ci/config/yaml_spec.rb index 7718a0e51c2ebb..b9ff63437e6009 100644 --- a/spec/lib/gitlab/ci/config/yaml_spec.rb +++ b/spec/lib/gitlab/ci/config/yaml_spec.rb @@ -97,6 +97,26 @@ end end + context 'when the YAML file has an invalid `spec`' do + it 'treats the file as if it had no `spec`' do + yaml = <<~YAML + # invalid spec + # only comments :) + --- + test_job: + script: 'echo "hello"' + YAML + + config = described_class.load!(yaml) + + expect(config).to eq({ + test_job: { + script: 'echo "hello"' + } + }) + end + end + context 'when ci_multi_doc_yaml is disabled' do before do stub_feature_flags(ci_multi_doc_yaml: false) -- GitLab From b767752e127e555263b2f15ba4604095be361057 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 7 Feb 2023 16:01:00 +0100 Subject: [PATCH 08/13] Add input specs to `File::Base` These specs caught an error in the error message :) --- lib/gitlab/ci/config/external/file/base.rb | 2 +- .../ci/config/external/file/base_spec.rb | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb index 5ebce9680b4af5..c67d780c603ae8 100644 --- a/lib/gitlab/ci/config/external/file/base.rb +++ b/lib/gitlab/ci/config/external/file/base.rb @@ -114,7 +114,7 @@ def content_hash ::Gitlab::Ci::Config::Yaml.load!(content, input_values: input_values) end rescue Gitlab::Ci::Config::Yaml::InputInterpolator::RequiredInputsNotMetError => e - errors.push("Included file `local.gitlab-ci.yml` has required inputs #{e.message} that are not given!") + errors.push("Included file `#{masked_location}` has required inputs `#{e.message}` that are not given!") nil rescue Gitlab::Config::Loader::FormatError diff --git a/spec/lib/gitlab/ci/config/external/file/base_spec.rb b/spec/lib/gitlab/ci/config/external/file/base_spec.rb index 8475c3a8b19dac..bb513d0ea0762f 100644 --- a/spec/lib/gitlab/ci/config/external/file/base_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/base_spec.rb @@ -117,6 +117,50 @@ def initialize(params, context) expect { valid? }.to raise_error(NotImplementedError) end end + + context 'when the file has required inputs' do + let(:test_class) do + Class.new(described_class) do + def initialize(params, context) + @location = params[:local] + + super + end + + def validate_context! + # no-op + end + end + end + + context 'when the inputs are given' do + let(:location) { { local: 'included.gitlab-ci.yml', with: { test_input: 'hello test' } } } + + before do + allow(::Gitlab::Ci::Config::Yaml).to receive(:load!).and_return({ test_job: { script: 'echo "hello test"' } }) + end + + it 'returns true' do + expect(subject).to be_truthy + end + end + + context 'when the inputs are missing' do + let(:location) { { local: 'included.gitlab-ci.yml' } } + + before do + allow(::Gitlab::Ci::Config::Yaml).to receive(:load!) + .and_raise(::Gitlab::Ci::Config::Yaml::InputInterpolator::RequiredInputsNotMetError, 'test_input') + end + + it 'returns false' do + expect(subject).to be_falsey + expect(file.error_message).to eq( + 'Included file `included.gitlab-ci.yml` has required inputs `test_input` that are not given!' + ) + end + end + end end describe '#to_hash' do -- GitLab From 7b3ddcea145c008b1b68325b1e492b58ddbab9c5 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Tue, 7 Feb 2023 16:04:34 +0100 Subject: [PATCH 09/13] WIP --- .../lib/gitlab/ci/config/yaml/input_interpolator_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 spec/lib/gitlab/ci/config/yaml/input_interpolator_spec.rb diff --git a/spec/lib/gitlab/ci/config/yaml/input_interpolator_spec.rb b/spec/lib/gitlab/ci/config/yaml/input_interpolator_spec.rb new file mode 100644 index 00000000000000..f0afad443ddca5 --- /dev/null +++ b/spec/lib/gitlab/ci/config/yaml/input_interpolator_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Config::Yaml::InputInterpolator, feature_category: :pipeline_authoring do + describe '#interpolate!' do + it 'wip test' + end +end -- GitLab From 07cae59f19292db1193f57d435fbd39fcec62ac7 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Thu, 9 Feb 2023 18:12:06 +0100 Subject: [PATCH 10/13] Make the pipeline green --- lib/gitlab/ci/config/yaml.rb | 2 +- spec/lib/gitlab/ci/yaml_processor_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/config/yaml.rb b/lib/gitlab/ci/config/yaml.rb index 71b1d85ca7aa39..9032a6add781ea 100644 --- a/lib/gitlab/ci/config/yaml.rb +++ b/lib/gitlab/ci/config/yaml.rb @@ -6,7 +6,7 @@ class Config module Yaml AVAILABLE_TAGS = [Config::Yaml::Tags::Reference].freeze MAX_DOCUMENTS = 2 - MULTI_DOC_DIVIDER = /^---$/.freeze + MULTI_DOC_DIVIDER = /^---$/ class Loader def initialize(content, input_values: {}) diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index f56138ebdbc495..964c85dbc09d9c 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1474,7 +1474,7 @@ module Ci it_behaves_like( 'returns errors', - 'Included file `local.gitlab-ci.yml` has required inputs test_text that are not given!' + 'Included file `local.gitlab-ci.yml` has required inputs `test_text` that are not given!' ) end end -- GitLab From d138e44d2da03e39cea6fd9e414b2754a17fe6ff Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Thu, 9 Feb 2023 18:27:45 +0100 Subject: [PATCH 11/13] Improve spec performance --- spec/lib/gitlab/ci/yaml_processor_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index 964c85dbc09d9c..0b652bdc58042e 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1430,7 +1430,8 @@ module Ci subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config), opts).execute } context 'when inputs are required' do - let(:project) { create(:project, :repository) } + let_it_be(:project) { create(:project, :repository) } + let(:opts) { { project: project, sha: project.commit.sha } } let(:project_files) do -- GitLab From f60ba8a054bd2b8915ebfc9eda9117edb8eaaf91 Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Thu, 9 Feb 2023 18:50:45 +0100 Subject: [PATCH 12/13] Fill out tests for InputInterpolator Also moves the responsibility for symbolizing the keys to Ci::Config::Yaml, since it seems out of scope for the interpolator --- lib/gitlab/ci/config/yaml.rb | 2 +- .../ci/config/yaml/input_interpolator.rb | 4 +- .../ci/config/yaml/input_interpolator_spec.rb | 61 ++++++++++++++++++- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/ci/config/yaml.rb b/lib/gitlab/ci/config/yaml.rb index 9032a6add781ea..b8ac9ec6b50513 100644 --- a/lib/gitlab/ci/config/yaml.rb +++ b/lib/gitlab/ci/config/yaml.rb @@ -42,7 +42,7 @@ def load_multi_document_yaml! spec_inputs, yaml_loader(documents.second).load_raw!, input_values: input_values - ).interpolate! + ).interpolate!.deep_symbolize_keys! else yaml_loader(documents.first).load! end diff --git a/lib/gitlab/ci/config/yaml/input_interpolator.rb b/lib/gitlab/ci/config/yaml/input_interpolator.rb index a881cadad10c75..5c1f6f38c7ad8c 100644 --- a/lib/gitlab/ci/config/yaml/input_interpolator.rb +++ b/lib/gitlab/ci/config/yaml/input_interpolator.rb @@ -14,11 +14,11 @@ def initialize(spec_inputs, content, input_values: {}) end def interpolate! - return content.deep_symbolize_keys! if spec_inputs.empty? + return content if spec_inputs.empty? raise RequiredInputsNotMetError, missing_required_inputs.join(', ') unless required_inputs_satisfied? - interpolated_content.deep_symbolize_keys! + interpolated_content end private diff --git a/spec/lib/gitlab/ci/config/yaml/input_interpolator_spec.rb b/spec/lib/gitlab/ci/config/yaml/input_interpolator_spec.rb index f0afad443ddca5..9b670856dec55a 100644 --- a/spec/lib/gitlab/ci/config/yaml/input_interpolator_spec.rb +++ b/spec/lib/gitlab/ci/config/yaml/input_interpolator_spec.rb @@ -4,6 +4,65 @@ RSpec.describe Gitlab::Ci::Config::Yaml::InputInterpolator, feature_category: :pipeline_authoring do describe '#interpolate!' do - it 'wip test' + it 'interpolates all keys and values' do + content = { + '$[[ inputs.job_name ]]' => { + 'script' => 'echo "$[[ inputs.test_output ]]"' + } + } + spec_inputs = { + job_name: nil, + test_output: nil + } + input_values = { + job_name: 'test_job', + test_output: 'hello test' + } + + interpolated_content = described_class.new(spec_inputs, content, input_values: input_values).interpolate! + + expect(interpolated_content).to eq({ + 'test_job' => { + 'script' => 'echo "hello test"' + } + }) + end + + context 'when not given inputs' do + it 'returns the content with no changes' do + content = { + '$[[ inputs.job_name ]]' => { + 'script' => 'echo "$[[ inputs.test_output ]]"' + } + } + + interpolated_content = described_class.new({}, content).interpolate! + + expect(interpolated_content).to eq(content) + end + end + + context 'when there are required inputs missing' do + it 'raises a RequiredInputsNotMetError' do + content = { + '$[[ inputs.job_name ]]' => { + '$[[ inputs.script_keyword ]]' => 'echo "$[[ inputs.test_output ]]"' + } + } + spec_inputs = { + job_name: nil, + script_keyword: nil, + test_output: nil + } + input_values = { job_name: 'test_job' } + + interpolator = described_class.new(spec_inputs, content, input_values: input_values) + + expect { interpolator.interpolate! }.to raise_error( + described_class::RequiredInputsNotMetError, + 'script_keyword, test_output' + ) + end + end end end -- GitLab From 1c7393c121b41e6eaae8f43311edcd04acebeb3b Mon Sep 17 00:00:00 2001 From: Avielle Wolfe Date: Thu, 9 Feb 2023 19:14:18 +0100 Subject: [PATCH 13/13] WIP --- lib/gitlab/ci/config/external/file/base.rb | 2 +- lib/gitlab/ci/config/yaml.rb | 15 ++++++++++----- spec/lib/gitlab/ci/config/yaml_spec.rb | 4 ++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb index c67d780c603ae8..7b0a4add4811c6 100644 --- a/lib/gitlab/ci/config/external/file/base.rb +++ b/lib/gitlab/ci/config/external/file/base.rb @@ -111,7 +111,7 @@ def expanded_content_hash def content_hash strong_memoize(:content_hash) do - ::Gitlab::Ci::Config::Yaml.load!(content, input_values: input_values) + ::Gitlab::Ci::Config::Yaml.load!(content, input_values: input_values, project: project) end rescue Gitlab::Ci::Config::Yaml::InputInterpolator::RequiredInputsNotMetError => e errors.push("Included file `#{masked_location}` has required inputs `#{e.message}` that are not given!") diff --git a/lib/gitlab/ci/config/yaml.rb b/lib/gitlab/ci/config/yaml.rb index b8ac9ec6b50513..357fbf0dfdb0ed 100644 --- a/lib/gitlab/ci/config/yaml.rb +++ b/lib/gitlab/ci/config/yaml.rb @@ -9,15 +9,16 @@ module Yaml MULTI_DOC_DIVIDER = /^---$/ class Loader - def initialize(content, input_values: {}) + def initialize(content, input_values: {}, project: nil) @content = content + @project = project @input_values = input_values end def load! ensure_custom_tags - if ::Feature.enabled?(:ci_multi_doc_yaml) + if multi_document_yaml_loading_enabled? load_multi_document_yaml! else yaml_loader(content).load! @@ -26,7 +27,7 @@ def load! private - attr_reader :content, :input_values + attr_reader :content, :input_values, :project def ensure_custom_tags @ensure_custom_tags ||= begin @@ -36,6 +37,10 @@ def ensure_custom_tags end end + def multi_document_yaml_loading_enabled? + project.present? && ::Feature.enabled?(:ci_multi_doc_yaml, project) + end + def load_multi_document_yaml! if contains_config_spec? InputInterpolator.new( @@ -72,8 +77,8 @@ def yaml_loader(yaml_content) end class << self - def load!(content, input_values: {}) - Loader.new(content, input_values: input_values).load! + def load!(content, input_values: {}, project: nil) + Loader.new(content, input_values: input_values, project: project).load! end end end diff --git a/spec/lib/gitlab/ci/config/yaml_spec.rb b/spec/lib/gitlab/ci/config/yaml_spec.rb index b9ff63437e6009..017aa069b1c3b4 100644 --- a/spec/lib/gitlab/ci/config/yaml_spec.rb +++ b/spec/lib/gitlab/ci/config/yaml_spec.rb @@ -117,6 +117,10 @@ end end + context 'when not given a project' do + it 'only loads ' + end + context 'when ci_multi_doc_yaml is disabled' do before do stub_feature_flags(ci_multi_doc_yaml: false) -- GitLab