From b89a9a33db09ab7815fa8b2a81ef400fcfa88c25 Mon Sep 17 00:00:00 2001 From: Furkan Ayhan Date: Wed, 24 Sep 2025 13:38:59 +0200 Subject: [PATCH] Implement name and sha component context interpolation This is the second step of adding component context to the CI interpolation. In the first step, we've prepared the codebase by refactoring YAML context passing. In this step, we are adding `component.name` and `component.sha` to the interpolation. In the next step, we'll add `component.version`. The documentation will be added in the final step. This change is behind the feature flag `ci_component_context_interpolation`. --- .rubocop_todo/rspec/spec_file_path_format.yml | 1 + .../ci_component_context_interpolation.yml | 10 ++ lib/gitlab/ci/config/external/context.rb | 8 +- lib/gitlab/ci/config/external/file/base.rb | 8 +- .../ci/config/external/file/component.rb | 14 +- lib/gitlab/ci/config/header/component.rb | 32 +++++ lib/gitlab/ci/config/header/root.rb | 4 + lib/gitlab/ci/config/header/spec.rb | 7 +- .../ci/config/interpolation/interpolator.rb | 6 +- lib/gitlab/ci/config/yaml/context.rb | 5 +- .../gitlab/ci/config/external/context_spec.rb | 17 +++ .../ci/config/external/file/component_spec.rb | 23 +++- .../gitlab/ci/config/header/component_spec.rb | 103 ++++++++++++++ spec/lib/gitlab/ci/config/header/root_spec.rb | 64 +++++++++ spec/lib/gitlab/ci/config/header/spec_spec.rb | 73 ++++++++++ .../config/interpolation/interpolator_spec.rb | 70 ++++++++++ .../lib/gitlab/ci/config/yaml/context_spec.rb | 24 ++++ .../component_interpolation_spec.rb | 127 ++++++++++++++++++ 18 files changed, 586 insertions(+), 10 deletions(-) create mode 100644 config/feature_flags/beta/ci_component_context_interpolation.yml create mode 100644 lib/gitlab/ci/config/header/component.rb create mode 100644 spec/lib/gitlab/ci/config/header/component_spec.rb create mode 100644 spec/services/ci/create_pipeline_service/component_interpolation_spec.rb diff --git a/.rubocop_todo/rspec/spec_file_path_format.yml b/.rubocop_todo/rspec/spec_file_path_format.yml index 0ed9f1b5458128..55d22be40e7851 100644 --- a/.rubocop_todo/rspec/spec_file_path_format.yml +++ b/.rubocop_todo/rspec/spec_file_path_format.yml @@ -34,6 +34,7 @@ RSpec/SpecFilePathFormat: - 'spec/requests/api/pages/pages_spec.rb' - 'spec/services/ci/create_pipeline_service/artifacts_spec.rb' - 'spec/services/ci/create_pipeline_service/cache_spec.rb' + - 'spec/services/ci/create_pipeline_service/component_interpolation_spec.rb' - 'spec/services/ci/create_pipeline_service/composite_identity_spec.rb' - 'spec/services/ci/create_pipeline_service/creation_errors_and_warnings_spec.rb' - 'spec/services/ci/create_pipeline_service/cross_project_pipeline_spec.rb' diff --git a/config/feature_flags/beta/ci_component_context_interpolation.yml b/config/feature_flags/beta/ci_component_context_interpolation.yml new file mode 100644 index 00000000000000..36cafc6a3d2094 --- /dev/null +++ b/config/feature_flags/beta/ci_component_context_interpolation.yml @@ -0,0 +1,10 @@ +--- +name: ci_component_context_interpolation +description: Implementing component context interpolation in CI. +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/438275 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/206183 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/571986 +milestone: '18.5' +group: group::pipeline authoring +type: beta +default_enabled: false diff --git a/lib/gitlab/ci/config/external/context.rb b/lib/gitlab/ci/config/external/context.rb index d3f6faee4b86e9..f7978b5822386d 100644 --- a/lib/gitlab/ci/config/external/context.rb +++ b/lib/gitlab/ci/config/external/context.rb @@ -13,7 +13,7 @@ class Context attr_reader :project, :sha, :user, :parent_pipeline, :variables, :pipeline_config, :parallel_requests, :pipeline, :expandset, :execution_deadline, :logger, :max_includes, :max_total_yaml_size_bytes, - :pipeline_policy_context + :pipeline_policy_context, :component_data attr_accessor :total_file_size_in_bytes @@ -25,7 +25,7 @@ class Context # rubocop:disable Metrics/ParameterLists -- all arguments needed def initialize( project: nil, pipeline: nil, sha: nil, user: nil, parent_pipeline: nil, variables: nil, - pipeline_config: nil, logger: nil, pipeline_policy_context: nil + pipeline_config: nil, logger: nil, pipeline_policy_context: nil, component_data: nil ) @project = project @pipeline = pipeline @@ -35,6 +35,7 @@ def initialize( @variables = variables || Ci::Variables::Collection.new @pipeline_config = pipeline_config @pipeline_policy_context = pipeline_policy_context + @component_data = component_data || {} @expandset = [] @parallel_requests = [] @execution_deadline = 0 @@ -86,6 +87,7 @@ def mutate(attrs = {}) ctx.max_includes = max_includes ctx.max_total_yaml_size_bytes = max_total_yaml_size_bytes ctx.parallel_requests = parallel_requests + ctx.component_data = component_data end end @@ -138,7 +140,7 @@ def internal_include? protected attr_writer :pipeline, :expandset, :execution_deadline, :logger, :max_includes, :max_total_yaml_size_bytes, - :parallel_requests + :parallel_requests, :component_data private diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb index 9dc77252638b31..c81186b2fe2ec4 100644 --- a/lib/gitlab/ci/config/external/file/base.rb +++ b/lib/gitlab/ci/config/external/file/base.rb @@ -129,7 +129,8 @@ def yaml_context def yaml_context_attributes { - variables: context.variables + variables: context.variables, + component: (ci_component_context_interpolation_enabled? ? context.component_data : {}) } end @@ -160,6 +161,11 @@ def masked_location context.mask_variables_from(location) end end + + def ci_component_context_interpolation_enabled? + ::Feature.enabled?(:ci_component_context_interpolation, context.project) + end + strong_memoize_attr :ci_component_context_interpolation_enabled? end end end diff --git a/lib/gitlab/ci/config/external/file/component.rb b/lib/gitlab/ci/config/external/file/component.rb index b34c303785b7b0..8d5061c5455947 100644 --- a/lib/gitlab/ci/config/external/file/component.rb +++ b/lib/gitlab/ci/config/external/file/component.rb @@ -83,10 +83,16 @@ def expand_context_attrs project: component_payload.fetch(:project), sha: component_payload.fetch(:sha), user: context.user, - variables: context.variables + variables: context.variables, + component_data: component_yaml_context } end + override :yaml_context_attributes + def yaml_context_attributes + super.merge(component: component_yaml_context) + end + def masked_blob return unless component_payload @@ -114,6 +120,12 @@ def component_attrs name: component_payload.fetch(:name) } end + + def component_yaml_context + return {} unless ci_component_context_interpolation_enabled? + + component_attrs.slice(*Config::Header::Component::ALLOWED_VALUES) + end end end end diff --git a/lib/gitlab/ci/config/header/component.rb b/lib/gitlab/ci/config/header/component.rb new file mode 100644 index 00000000000000..96ddbd750762ff --- /dev/null +++ b/lib/gitlab/ci/config/header/component.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module Header + ## + # Component context configuration used for interpolation with the CI configuration. + # + # This class defines the available component context information that can be used + # in CI configuration interpolation. + # + class Component < ::Gitlab::Config::Entry::Node + include ::Gitlab::Config::Entry::Validatable + + ALLOWED_VALUES = %i[name sha].freeze + ALLOWED_VALUES_TO_S = ALLOWED_VALUES.map(&:to_s).freeze + + validations do + validates :config, type: Array, array_of_strings: true, allowed_array_values: { in: ALLOWED_VALUES_TO_S } + end + + def value + return [] unless valid? + + config.uniq.map(&:to_sym) + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/header/root.rb b/lib/gitlab/ci/config/header/root.rb index fc5a6fcd95ab66..ebedd851a97561 100644 --- a/lib/gitlab/ci/config/header/root.rb +++ b/lib/gitlab/ci/config/header/root.rb @@ -29,6 +29,10 @@ class Root < ::Gitlab::Config::Entry::Node def spec_inputs_value spec_entry.inputs_value end + + def spec_component_value + spec_entry.component_value + end end end end diff --git a/lib/gitlab/ci/config/header/spec.rb b/lib/gitlab/ci/config/header/spec.rb index 4753c1eb4412b1..32e034e319774f 100644 --- a/lib/gitlab/ci/config/header/spec.rb +++ b/lib/gitlab/ci/config/header/spec.rb @@ -7,7 +7,7 @@ module Header class Spec < ::Gitlab::Config::Entry::Node include ::Gitlab::Config::Entry::Configurable - ALLOWED_KEYS = %i[inputs].freeze + ALLOWED_KEYS = %i[inputs component].freeze validations do validates :config, allowed_keys: ALLOWED_KEYS @@ -17,6 +17,11 @@ class Spec < ::Gitlab::Config::Entry::Node description: 'Allowed input parameters used for interpolation.', inherit: false, metadata: { composable_class: ::Gitlab::Ci::Config::Header::Input } + + entry :component, Header::Component, + description: 'The available component context used for interpolation.', + inherit: false, + default: [] end end end diff --git a/lib/gitlab/ci/config/interpolation/interpolator.rb b/lib/gitlab/ci/config/interpolation/interpolator.rb index 9a5ce5b415023c..4589be2a452082 100644 --- a/lib/gitlab/ci/config/interpolation/interpolator.rb +++ b/lib/gitlab/ci/config/interpolation/interpolator.rb @@ -89,13 +89,17 @@ def inputs def context @context ||= Context.new( - { inputs: inputs.to_hash }, variables: yaml_context.variables + { inputs: inputs.to_hash, component: component_data }, variables: yaml_context.variables ) end def template @template ||= Template.new(content, context) end + + def component_data + yaml_context.component.slice(*header.spec_component_value) + end end end end diff --git a/lib/gitlab/ci/config/yaml/context.rb b/lib/gitlab/ci/config/yaml/context.rb index 84c29eac766032..79922c478690cf 100644 --- a/lib/gitlab/ci/config/yaml/context.rb +++ b/lib/gitlab/ci/config/yaml/context.rb @@ -5,10 +5,11 @@ module Ci class Config module Yaml class Context - attr_reader :variables + attr_reader :variables, :component - def initialize(variables: []) + def initialize(variables: [], component: {}) @variables = variables + @component = component end end end diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb index 13cb3ae613093b..1ad7febaca81e7 100644 --- a/spec/lib/gitlab/ci/config/external/context_spec.rb +++ b/spec/lib/gitlab/ci/config/external/context_spec.rb @@ -31,6 +31,7 @@ it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } it { expect(subject.variables_hash).to include('a' => 'b') } it { expect(subject.pipeline_config).to eq(pipeline_config) } + it { expect(subject.component_data).to eq({}) } end context 'without values' do @@ -42,6 +43,21 @@ it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } it { expect(subject.pipeline_config).to be_nil } + it { expect(subject.component_data).to eq({}) } + end + + context 'with component_data' do + let(:component_data) { { name: 'my-component', sha: 'abc123' } } + let(:attributes) do + { + project: project, + user: user, + sha: sha, + component_data: component_data + } + end + + it { expect(subject.component_data).to eq(component_data) } end describe 'max_includes' do @@ -177,6 +193,7 @@ it { expect(mutated.execution_deadline).to eq(subject.execution_deadline) } it { expect(mutated.logger).to eq(subject.logger) } it { expect(mutated.parallel_requests).to eq(subject.parallel_requests) } + it { expect(mutated.component_data).to eq(subject.component_data) } end context 'with attributes' do diff --git a/spec/lib/gitlab/ci/config/external/file/component_spec.rb b/spec/lib/gitlab/ci/config/external/file/component_spec.rb index d254c276dfc8c1..075bd7665b580c 100644 --- a/spec/lib/gitlab/ci/config/external/file/component_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/component_spec.rb @@ -184,7 +184,28 @@ project: project, sha: 'my_component_sha', user: context.user, - variables: context.variables) + variables: context.variables, + component_data: { + name: 'my_component', + sha: 'my_component_sha' + } + ) + end + + context 'when the FF ci_component_context_interpolation is disabled' do + before do + stub_feature_flags(ci_component_context_interpolation: false) + end + + it 'inherits user and variables while changes project and sha without component_data' do + is_expected.to include( + project: project, + sha: 'my_component_sha', + user: context.user, + variables: context.variables, + component_data: {} + ) + end end end diff --git a/spec/lib/gitlab/ci/config/header/component_spec.rb b/spec/lib/gitlab/ci/config/header/component_spec.rb new file mode 100644 index 00000000000000..927e1f23d17817 --- /dev/null +++ b/spec/lib/gitlab/ci/config/header/component_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Config::Header::Component, feature_category: :pipeline_composition do + let(:component) { described_class.new(config) } + + describe 'validations' do + context 'when config is an array of valid strings' do + let(:config) { %w[name sha] } + + it 'passes validations' do + expect(component).to be_valid + expect(component.errors).to be_empty + end + end + + context 'when config is an empty array' do + let(:config) { [] } + + it 'passes validations' do + expect(component).to be_valid + expect(component.errors).to be_empty + end + end + + context 'when config contains invalid values' do + let(:config) { %w[name invalid_key] } + + it 'fails validations' do + expect(component).not_to be_valid + expect(component.errors).to include(/component config contains unknown values: invalid_key/) + end + end + + context 'when config is not an array' do + let(:config) { 'name' } + + it 'fails validations' do + expect(component).not_to be_valid + expect(component.errors).to include(/component config should be an array/) + end + end + + context 'when config is a hash' do + let(:config) { { name: true } } + + it 'fails validations' do + expect(component).not_to be_valid + expect(component.errors).to include(/component config should be an array/) + end + end + + context 'when config contains non-string values' do + let(:config) { ['name', 123] } + + it 'fails validations' do + expect(component).not_to be_valid + expect(component.errors).to include(/component config should be an array of strings/) + end + end + end + + describe '#value' do + subject(:value) { component.value } + + context 'when config is valid' do + let(:config) { %w[name sha] } + + it { is_expected.to match_array([:name, :sha]) } + end + + context 'when config has duplicate values' do + let(:config) { %w[name name sha] } + + it { is_expected.to match_array([:name, :sha]) } + end + + context 'when config has invalid values' do + let(:config) { %w[name invalid] } + + it { is_expected.to be_empty } + end + + context 'when config is empty array' do + let(:config) { [] } + + it { is_expected.to be_empty } + end + + context 'when config is not an array' do + let(:config) { 'invalid' } + + it { is_expected.to be_empty } + end + + context 'when config is nil' do + let(:config) { nil } + + it { is_expected.to be_empty } + end + end +end diff --git a/spec/lib/gitlab/ci/config/header/root_spec.rb b/spec/lib/gitlab/ci/config/header/root_spec.rb index a481d3f2c09621..2fa9ab98d1385e 100644 --- a/spec/lib/gitlab/ci/config/header/root_spec.rb +++ b/spec/lib/gitlab/ci/config/header/root_spec.rb @@ -130,4 +130,68 @@ }) end end + + describe '#spec_component_value' do + context 'when component is specified' do + let(:header_hash) do + { + spec: { + component: %w[name sha] + } + } + end + + it 'returns the component value as symbols' do + expect(config.spec_component_value).to match_array([:name, :sha]) + end + end + + context 'when component is empty' do + let(:header_hash) do + { + spec: { + component: [] + } + } + end + + it 'returns empty array' do + expect(config.spec_component_value).to be_empty + end + end + + context 'when component is not specified' do + let(:header_hash) do + { + spec: { + inputs: { + foo: { default: 'bar' } + } + } + } + end + + it 'returns empty array by default' do + expect(config.spec_component_value).to be_empty + end + end + + context 'when both inputs and component are specified' do + let(:header_hash) do + { + spec: { + inputs: { + foo: { default: 'bar' } + }, + component: %w[name] + } + } + end + + it 'returns both values correctly' do + expect(config.spec_inputs_value).to eq({ foo: { default: 'bar' } }) + expect(config.spec_component_value).to match_array([:name]) + end + end + end end diff --git a/spec/lib/gitlab/ci/config/header/spec_spec.rb b/spec/lib/gitlab/ci/config/header/spec_spec.rb index 74cfb39dfd509c..8c1b081cc4caec 100644 --- a/spec/lib/gitlab/ci/config/header/spec_spec.rb +++ b/spec/lib/gitlab/ci/config/header/spec_spec.rb @@ -28,6 +28,79 @@ end end + context 'when spec contains component configuration' do + let(:spec_hash) do + { + component: %w[name sha] + } + end + + it 'passes validations' do + expect(config).to be_valid + expect(config.errors).to be_empty + end + + it 'returns the component value' do + expect(config.component_value).to match_array([:name, :sha]) + end + end + + context 'when spec contains both inputs and component' do + let(:spec_hash) do + { + inputs: { + foo: { + default: 'bar' + } + }, + component: %w[name] + } + end + + it 'passes validations' do + expect(config).to be_valid + expect(config.errors).to be_empty + end + + it 'returns both values correctly' do + expect(config.inputs_value).to eq({ foo: { default: 'bar' } }) + expect(config.component_value).to match_array([:name]) + end + end + + context 'when spec contains empty component array' do + let(:spec_hash) do + { + component: [] + } + end + + it 'passes validations' do + expect(config).to be_valid + expect(config.errors).to be_empty + end + + it 'returns empty component value' do + expect(config.component_value).to eq([]) + end + end + + context 'when spec component is not specified' do + let(:spec_hash) do + { + inputs: { + foo: { + default: 'bar' + } + } + } + end + + it 'returns default empty array for component' do + expect(config.component_value).to eq([]) + end + end + context 'when spec contains a required value' do let(:spec_hash) do { inputs: { foo: nil } } diff --git a/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb index 6ed4d70c9abd38..5d7be700073cfb 100644 --- a/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb +++ b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb @@ -172,6 +172,76 @@ end end + context 'when using component interpolation' do + let(:yaml_context) do + ::Gitlab::Ci::Config::Yaml::Context.new( + variables: [], + component: { name: 'my-component', sha: 'abc123' } + ) + end + + context 'when component values are specified in spec' do + let(:header) do + { spec: { component: %w[name sha] } } + end + + let(:content) do + { test: 'Component $[[ component.name ]] at $[[ component.sha ]]' } + end + + let(:arguments) { {} } + + it 'correctly interpolates component data' do + subject.interpolate! + + expect(subject).to be_interpolated + expect(subject).to be_valid + expect(subject.to_hash).to eq({ test: 'Component my-component at abc123' }) + end + end + + context 'when component value not in spec is accessed' do + let(:header) do + { spec: { component: %w[name] } } + end + + let(:content) do + { test: 'Component $[[ component.name ]] at $[[ component.sha ]]' } + end + + let(:arguments) { {} } + + it 'returns an error for unspecified component value' do + subject.interpolate! + + expect(subject).not_to be_valid + expect(subject.errors).to include 'unknown interpolation provided: `sha` in `component.sha`' + end + end + + context 'when both inputs and component are used' do + let(:header) do + { spec: { inputs: { env: nil }, component: %w[name] } } + end + + let(:content) do + { + test: 'Deploy to $[[ inputs.env ]] using $[[ component.name ]]' + } + end + + let(:arguments) { { env: 'production' } } + + it 'correctly interpolates both inputs and component data' do + subject.interpolate! + + expect(subject).to be_interpolated + expect(subject).to be_valid + expect(subject.to_hash).to eq({ test: 'Deploy to production using my-component' }) + end + end + end + describe '#to_hash' do context 'when interpolation is not used' do let(:result) do diff --git a/spec/lib/gitlab/ci/config/yaml/context_spec.rb b/spec/lib/gitlab/ci/config/yaml/context_spec.rb index eca928f8bc5f51..ccb535ba327c18 100644 --- a/spec/lib/gitlab/ci/config/yaml/context_spec.rb +++ b/spec/lib/gitlab/ci/config/yaml/context_spec.rb @@ -26,6 +26,18 @@ expect(context.variables).to eq([]) end end + + context 'with component data provided' do + let(:component_data) do + { name: 'my-component', sha: 'abc123' } + end + + subject(:context) { described_class.new(component: component_data) } + + it 'stores the component data' do + expect(context.component).to eq(component_data) + end + end end describe '#variables' do @@ -41,4 +53,16 @@ expect(context.variables).to eq(variables) end end + + describe '#component' do + let(:component_data) do + { name: 'test-component', sha: 'def456' } + end + + subject(:context) { described_class.new(component: component_data) } + + it 'returns the component data' do + expect(context.component).to eq(component_data) + end + end end diff --git a/spec/services/ci/create_pipeline_service/component_interpolation_spec.rb b/spec/services/ci/create_pipeline_service/component_interpolation_spec.rb new file mode 100644 index 00000000000000..39e9c1c645da26 --- /dev/null +++ b/spec/services/ci/create_pipeline_service/component_interpolation_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::CreatePipelineService, feature_category: :pipeline_composition do + context 'for spec:component' do + let_it_be(:project) { create(:project, :small_repo) } + let_it_be(:user) { project.first_owner } + + let_it_be(:components_project) do + create(:project, :small_repo, creator: user, namespace: user.namespace) + end + + let_it_be(:catalog_resource) { create(:ci_catalog_resource, :published, project: components_project) } + + let_it_be(:component_name) { 'my-component' } + let_it_be(:component_version) { '0.1.1' } + let_it_be(:component_file_path) { "templates/#{component_name}/template.yml" } + + let_it_be(:component_yaml) do + <<~YAML + spec: + component: [name, sha] + inputs: + compiler: + default: gcc + optimization_level: + type: number + default: 2 + + --- + + test: + script: + - echo "Building with $[[ inputs.compiler ]] and optimization level $[[ inputs.optimization_level ]]" + - echo "Component $[[ component.name ]] / $[[ component.sha ]]" + YAML + end + + let_it_be(:component_sha) do + components_project.repository.create_file( + user, component_file_path, component_yaml, message: 'Add my first CI component', branch_name: 'master' + ) + end + + let(:service) { described_class.new(project, user, { ref: 'refs/heads/master' }) } + + subject(:execute) { service.execute(:push, content: project_ci_yaml) } + + before_all do + components_project.repository.add_tag(user, component_version, component_sha) + + create(:release, :with_catalog_resource_version, + tag: component_version, author: user, project: components_project, sha: component_sha + ) + end + + context 'when the component file is included as include:component' do + let(:project_ci_yaml) do + <<~YAML + include: + - component: #{component_path} + YAML + end + + context 'when the component path is with a full version' do + let_it_be(:component_path) do + "#{Gitlab.config.gitlab.host}/#{components_project.full_path}/#{component_name}@#{component_version}" + end + + it 'creates a pipeline with correct jobs' do + response = execute + pipeline = response.payload + + expect(response).to be_success + expect(pipeline).to be_created_successfully + + expect(pipeline.builds.map(&:name)).to contain_exactly('test') + + test_job = pipeline.builds.find { |build| build.name == 'test' } + expect(test_job.options[:script]).to eq([ + 'echo "Building with gcc and optimization level 2"', + "echo \"Component #{component_name} / #{component_sha}\"" + ]) + end + + context 'when the FF ci_component_context_interpolation is disabled' do + before do + stub_feature_flags(ci_component_context_interpolation: false) + end + + it 'does not create a pipeline' do + response = execute + pipeline = response.payload + + expect(response).to be_error + expect(response.message).to include('unknown interpolation provided: `name` in `component.name`') + + expect(pipeline).not_to be_created_successfully + end + end + end + end + + context 'when the component file is included as include:project:file' do + let(:project_ci_yaml) do + <<~YAML + include: + - project: #{components_project.full_path} + file: #{component_file_path} + YAML + end + + it 'does not interpolate and returns errors' do + response = execute + pipeline = response.payload + + expect(response).not_to be_success + expect(pipeline).not_to be_created_successfully + + expect(response.message).to eq( + "`templates/my-component/template.yml`: unknown interpolation provided: `name` in `component.name`" + ) + end + end + end +end -- GitLab