From 76026b4e931ba2695b3c8db7f5200dd82a5553eb Mon Sep 17 00:00:00 2001 From: lma-git Date: Mon, 11 Sep 2023 19:48:31 -0700 Subject: [PATCH 1/6] Add interpolation method expand_vars Adds support for a new function called 'expand_vars' which expands the variables within the input value of an interpolation block. If a masked variable is used in the input, raises an error. --- doc/ci/yaml/inputs.md | 14 +++ lib/expand_variables.rb | 9 ++ lib/gitlab/ci/config/external/file/base.rb | 4 +- lib/gitlab/ci/config/interpolation/block.rb | 7 +- .../ci/config/interpolation/functions/base.rb | 6 +- .../interpolation/functions/expand_vars.rb | 33 +++++++ .../interpolation/functions/truncate.rb | 1 - .../config/interpolation/functions_stack.rb | 10 ++- .../ci/config/interpolation/interpolator.rb | 7 +- .../ci/config/interpolation/template.rb | 7 +- lib/gitlab/ci/config/yaml/loader.rb | 7 +- lib/gitlab/ci/variables/collection/item.rb | 4 + spec/lib/expand_variables_spec.rb | 30 +++++++ .../functions/expand_vars_spec.rb | 90 +++++++++++++++++++ .../ci/variables/collection/item_spec.rb | 21 ++++- 15 files changed, 229 insertions(+), 21 deletions(-) create mode 100644 lib/gitlab/ci/config/interpolation/functions/expand_vars.rb create mode 100644 spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb diff --git a/doc/ci/yaml/inputs.md b/doc/ci/yaml/inputs.md index 1af53d666ce407..6582b63c69ba79 100644 --- a/doc/ci/yaml/inputs.md +++ b/doc/ci/yaml/inputs.md @@ -154,6 +154,20 @@ In this example: ### Predefined interpolation functions +#### `expand_vars` + +Use `expand_vars` to expand [CI/CD variables](../variables/index.md) in the input value. +Only variables that are **not** masked can be expanded. + +Example: + +```yaml +$[[ inputs.test | expand_vars ]] +``` + +Assuming the value of `inputs.test` is `test $MY_VAR`, and the variable `$MY_VAR` is unmasked +and has the value `my value`, then the output would be `test my value`. + #### `truncate` Use `truncate` to shorten the interpolated value. For example: diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb index f565eb105ae2bb..15f153fa515d98 100644 --- a/lib/expand_variables.rb +++ b/lib/expand_variables.rb @@ -22,6 +22,15 @@ def possible_var_reference?(value) %w[$ %].any? { |symbol| value.include?(symbol) } end + def has_masked_var_reference?(value, collection) + return false unless value + + value.scan(VARIABLES_REGEXP).flatten.any? do |match| + item = collection[match] + item.masked? if item + end + end + private def replace_with(value, variables) diff --git a/lib/gitlab/ci/config/external/file/base.rb b/lib/gitlab/ci/config/external/file/base.rb index e3a87c8576f216..b3c802e565786b 100644 --- a/lib/gitlab/ci/config/external/file/base.rb +++ b/lib/gitlab/ci/config/external/file/base.rb @@ -114,7 +114,9 @@ def content_inputs def content_result context.logger.instrument(:config_file_fetch_content_hash) do - ::Gitlab::Ci::Config::Yaml::Loader.new(content, inputs: content_inputs).load + ::Gitlab::Ci::Config::Yaml::Loader.new( + content, inputs: content_inputs, variables: context.variables + ).load end end strong_memoize_attr :content_result diff --git a/lib/gitlab/ci/config/interpolation/block.rb b/lib/gitlab/ci/config/interpolation/block.rb index cf8420f924ef40..4729c9e7f6c5f6 100644 --- a/lib/gitlab/ci/config/interpolation/block.rb +++ b/lib/gitlab/ci/config/interpolation/block.rb @@ -17,12 +17,13 @@ class Block PATTERN = /(?\$\[\[\s*(?.*?)\s*\]\])/ MAX_FUNCTIONS = 3 - attr_reader :block, :data, :ctx, :errors + attr_reader :block, :data, :ctx, :variables, :errors - def initialize(block, data, ctx) + def initialize(block, data, ctx, variables: nil) @block = block @data = data @ctx = ctx + @variables = variables @errors = [] @value = nil @@ -62,7 +63,7 @@ def evaluate! return @errors.concat(access.errors) unless access.valid? return @errors.push('too many functions in interpolation block') if functions.count > MAX_FUNCTIONS - result = Interpolation::FunctionsStack.new(functions).evaluate(access.value) + result = Interpolation::FunctionsStack.new(functions, variables: variables).evaluate(access.value) if result.success? @value = result.value diff --git a/lib/gitlab/ci/config/interpolation/functions/base.rb b/lib/gitlab/ci/config/interpolation/functions/base.rb index b9ce8cdc5bc7e2..145377cde6ae22 100644 --- a/lib/gitlab/ci/config/interpolation/functions/base.rb +++ b/lib/gitlab/ci/config/interpolation/functions/base.rb @@ -20,9 +20,10 @@ def self.matches?(function_expression) function_expression_pattern.match?(function_expression) end - def initialize(function_expression) + def initialize(function_expression, variables: nil) @errors = [] @function_args = parse_args(function_expression) + @variables = variables || Ci::Variables::Collection.new end def valid? @@ -35,10 +36,11 @@ def execute(_input_value) private - attr_reader :function_args + attr_reader :function_args, :variables def error(message) errors << "error in `#{self.class.name}` function: #{message}" + nil end def parse_args(function_expression) diff --git a/lib/gitlab/ci/config/interpolation/functions/expand_vars.rb b/lib/gitlab/ci/config/interpolation/functions/expand_vars.rb new file mode 100644 index 00000000000000..ee79dc13b6496a --- /dev/null +++ b/lib/gitlab/ci/config/interpolation/functions/expand_vars.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module Interpolation + module Functions + class ExpandVars < Base + def self.function_expression_pattern + /^#{name}$/ + end + + def self.name + 'expand_vars' + end + + def execute(input_value) + unless input_value.is_a?(String) + return error("invalid input type: #{self.class.name} can only be used with string inputs") + end + + if ExpandVariables.has_masked_var_reference?(input_value, variables) + return error('invalid variable type: masked variables cannot be expanded') + end + + ExpandVariables.expand_existing(input_value, variables) + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/interpolation/functions/truncate.rb b/lib/gitlab/ci/config/interpolation/functions/truncate.rb index 3771756ba4864a..915c11511021dc 100644 --- a/lib/gitlab/ci/config/interpolation/functions/truncate.rb +++ b/lib/gitlab/ci/config/interpolation/functions/truncate.rb @@ -19,7 +19,6 @@ def execute(input_value) input_value[offset, length].to_s else error('invalid input type: truncate can only be used with string inputs') - nil end end diff --git a/lib/gitlab/ci/config/interpolation/functions_stack.rb b/lib/gitlab/ci/config/interpolation/functions_stack.rb index 951d1121d4f130..612e36a5f38c15 100644 --- a/lib/gitlab/ci/config/interpolation/functions_stack.rb +++ b/lib/gitlab/ci/config/interpolation/functions_stack.rb @@ -16,13 +16,15 @@ def success? end FUNCTIONS = [ - Functions::Truncate + Functions::Truncate, + Functions::ExpandVars ].freeze attr_reader :errors - def initialize(function_expressions) + def initialize(function_expressions, variables: nil) @errors = [] + @variables = variables @functions = build_stack(function_expressions) end @@ -48,14 +50,14 @@ def evaluate(input_value) private - attr_reader :functions + attr_reader :functions, :variables def build_stack(function_expressions) function_expressions.map do |function_expression| matching_function = FUNCTIONS.find { |function| function.matches?(function_expression) } if matching_function.present? - matching_function.new(function_expression) + matching_function.new(function_expression, variables: variables) else message = "no function matching `#{function_expression}`: " \ 'check that the function name, arguments, and types are correct' diff --git a/lib/gitlab/ci/config/interpolation/interpolator.rb b/lib/gitlab/ci/config/interpolation/interpolator.rb index 95c419d74278ee..c17c1e943961a8 100644 --- a/lib/gitlab/ci/config/interpolation/interpolator.rb +++ b/lib/gitlab/ci/config/interpolation/interpolator.rb @@ -8,11 +8,12 @@ module Interpolation # Performs CI config file interpolation, and surfaces all possible interpolation errors. # class Interpolator - attr_reader :config, :args, :errors + attr_reader :config, :args, :variables, :errors - def initialize(config, args) + def initialize(config, args, variables: nil) @config = config @args = args.to_h + @variables = variables @errors = [] @interpolated = false end @@ -90,7 +91,7 @@ def context end def template - @template ||= Template.new(content, context) + @template ||= Template.new(content, context, variables: variables) end end end diff --git a/lib/gitlab/ci/config/interpolation/template.rb b/lib/gitlab/ci/config/interpolation/template.rb index ece2a4756aafdf..59b7ee5a2691c1 100644 --- a/lib/gitlab/ci/config/interpolation/template.rb +++ b/lib/gitlab/ci/config/interpolation/template.rb @@ -7,16 +7,17 @@ module Interpolation class Template include Gitlab::Utils::StrongMemoize - attr_reader :blocks, :ctx + attr_reader :blocks, :ctx, :variables TooManyBlocksError = Class.new(StandardError) InvalidBlockError = Class.new(StandardError) MAX_BLOCKS = 10_000 - def initialize(config, ctx) + def initialize(config, ctx, variables: nil) @config = Interpolation::Config.fabricate(config) @ctx = Interpolation::Context.fabricate(ctx) + @variables = variables @errors = [] @blocks = {} @@ -55,7 +56,7 @@ def interpolate! strong_memoize_attr :interpolate! def evaluate_block(block, data) - block = (@blocks[block] ||= Interpolation::Block.new(block, data, ctx)) + block = (@blocks[block] ||= Interpolation::Block.new(block, data, ctx, variables: variables)) raise TooManyBlocksError if @blocks.count > MAX_BLOCKS raise InvalidBlockError unless block.valid? diff --git a/lib/gitlab/ci/config/yaml/loader.rb b/lib/gitlab/ci/config/yaml/loader.rb index 5d56061a8bb471..96465ed8b4f72e 100644 --- a/lib/gitlab/ci/config/yaml/loader.rb +++ b/lib/gitlab/ci/config/yaml/loader.rb @@ -10,9 +10,10 @@ class Loader AVAILABLE_TAGS = [Config::Yaml::Tags::Reference].freeze MAX_DOCUMENTS = 2 - def initialize(content, inputs: {}) + def initialize(content, inputs: {}, variables: nil) @content = content @inputs = inputs + @variables = variables end def load @@ -20,7 +21,7 @@ def load return yaml_result unless yaml_result.valid? - interpolator = Interpolation::Interpolator.new(yaml_result, inputs) + interpolator = Interpolation::Interpolator.new(yaml_result, inputs, variables: variables) interpolator.interpolate! @@ -34,7 +35,7 @@ def load private - attr_reader :content, :inputs + attr_reader :content, :inputs, :variables def load_uninterpolated_yaml Yaml::Result.new(config: load_yaml!, error: nil) diff --git a/lib/gitlab/ci/variables/collection/item.rb b/lib/gitlab/ci/variables/collection/item.rb index dc810d51eb444b..2334db0718fa8f 100644 --- a/lib/gitlab/ci/variables/collection/item.rb +++ b/lib/gitlab/ci/variables/collection/item.rb @@ -34,6 +34,10 @@ def file? @variable.fetch(:file) end + def masked? + @variable.fetch(:masked) + end + def [](key) @variable.fetch(key) end diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb index ad73665326ab34..5306c479fca341 100644 --- a/spec/lib/expand_variables_spec.rb +++ b/spec/lib/expand_variables_spec.rb @@ -326,4 +326,34 @@ end end end + + describe '.has_masked_var_reference?' do + using RSpec::Parameterized::TableSyntax + + let(:variables_array) do + [ + { key: 'MY_VAR', value: 'value', masked: false }, + { key: 'MASKED_VAR', value: 'masked', masked: true } + ] + end + + let(:collection) { Gitlab::Ci::Variables::Collection.new(variables_array) } + + subject(:result) { described_class.has_masked_var_reference?(value, collection) } + + where(:value, :expected_result) do + 'test$MASKED_VAR' | true + 'test${MY_VAR}${MASKED_VAR}' | true + 'test$MY_VAR%MASKED_VAR%' | true + 'test$UNKNOWN_VAR' | false + '$MY_VAR test %MASKED_VAR' | false + 'MASKED_VAR' | false + end + + with_them do + it 'correctly detects when a masked variable is referenced in the value' do + expect(result).to eq(expected_result) + end + end + end end diff --git a/spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb new file mode 100644 index 00000000000000..e02298b0f625f9 --- /dev/null +++ b/spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe Gitlab::Ci::Config::Interpolation::Functions::ExpandVars, feature_category: :pipeline_composition do + let(:variables_array) do + [ + { key: 'VAR1', value: 'value1', masked: false }, + { key: 'VAR2', value: 'value2', masked: false }, + { key: 'NESTED_VAR', value: '$MY_VAR', masked: false }, + { key: 'MASKED_VAR', value: 'masked', masked: true } + ] + end + + let(:variables) { Gitlab::Ci::Variables::Collection.new(variables_array) } + let(:function_expression) { 'expand_vars' } + + subject(:function) { described_class.new(function_expression, variables: variables) } + + describe '#execute' do + let(:input_value) { '$VAR1' } + + subject(:execute) { function.execute(input_value) } + + it 'expands the variable' do + expect(execute).to eq('value1') + expect(function).to be_valid + end + + context 'when the variable contains another variable' do + let(:input_value) { '$NESTED_VAR' } + + it 'does not expand the inner variable' do + expect(execute).to eq('$MY_VAR') + expect(function).to be_valid + end + end + + context 'when the variable is masked' do + let(:input_value) { '$MASKED_VAR' } + + it 'returns an error' do + expect(execute).to be_nil + expect(function).not_to be_valid + expect(function.errors).to contain_exactly( + 'error in `expand_vars` function: invalid variable type: masked variables cannot be expanded' + ) + end + end + + context 'when the variable is unknown' do + let(:input_value) { '$UNKNOWN_VAR' } + + it 'does not expand the variable' do + expect(execute).to eq('$UNKNOWN_VAR') + expect(function).to be_valid + end + end + + context 'when there are multiple variables' do + let(:input_value) { '${VAR1} $VAR2 %VAR1%' } + + it 'expands the variables' do + expect(execute).to eq('value1 value2 value1') + expect(function).to be_valid + end + end + + context 'when the input is not a string' do + let(:input_value) { 100 } + + it 'returns an error' do + expect(execute).to be_nil + expect(function).not_to be_valid + expect(function.errors).to contain_exactly( + 'error in `expand_vars` function: invalid input type: expand_vars can only be used with string inputs' + ) + end + end + end + + describe '.matches?' do + it 'matches exactly the expand_vars function with no arguments' do + expect(described_class.matches?('expand_vars')).to be_truthy + expect(described_class.matches?('expand_vars()')).to be_falsey + expect(described_class.matches?('expand_vars(1)')).to be_falsey + expect(described_class.matches?('unknown')).to be_falsey + end + end +end diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb index f7c6f7f51df4ed..d96c8f1bd0cf6c 100644 --- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Variables::Collection::Item do +RSpec.describe Gitlab::Ci::Variables::Collection::Item, feature_category: :secrets_management do let(:variable_key) { 'VAR' } let(:variable_value) { 'something' } let(:expected_value) { variable_value } @@ -217,6 +217,25 @@ end end + describe '#masked?' do + let(:variable_hash) { { key: variable_key, value: variable_value } } + let(:item) { described_class.new(**variable_hash) } + + context 'when :masked is not specified' do + it 'returns false' do + expect(item.masked?).to eq(false) + end + end + + context 'when :masked is specified as true' do + let(:variable_hash) { { key: variable_key, value: variable_value, masked: true } } + + it 'returns true' do + expect(item.masked?).to eq(true) + end + end + end + describe '#to_runner_variable' do context 'when variable is not a file-related' do it 'returns a runner-compatible hash representation' do -- GitLab From 2f41a8e59d621b972a9fd539582686bbcd6c64ea Mon Sep 17 00:00:00 2001 From: Marcel Amirault Date: Thu, 14 Sep 2023 15:28:44 +0000 Subject: [PATCH 2/6] Add version history to the interpolation functions --- doc/ci/yaml/inputs.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/ci/yaml/inputs.md b/doc/ci/yaml/inputs.md index 6582b63c69ba79..67f76a6274e53b 100644 --- a/doc/ci/yaml/inputs.md +++ b/doc/ci/yaml/inputs.md @@ -156,6 +156,8 @@ In this example: #### `expand_vars` +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/387632) in GitLab 16.4. + Use `expand_vars` to expand [CI/CD variables](../variables/index.md) in the input value. Only variables that are **not** masked can be expanded. @@ -170,6 +172,8 @@ and has the value `my value`, then the output would be `test my value`. #### `truncate` +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/409462) in GitLab 16.3. + Use `truncate` to shorten the interpolated value. For example: - `truncate(,)` -- GitLab From 49fd8d497c55f63cfb20ff20afb2a35029999a0f Mon Sep 17 00:00:00 2001 From: lma-git Date: Fri, 15 Sep 2023 10:16:11 -0700 Subject: [PATCH 3/6] Documentation update and refactor Updated documentation. Renamed and moved 'has_masked_var_reference?' from lib/expand_variables.rb to the Variables Collection class. --- doc/ci/yaml/inputs.md | 5 ++-- lib/expand_variables.rb | 9 ------ .../interpolation/functions/expand_vars.rb | 2 +- lib/gitlab/ci/variables/collection.rb | 9 ++++++ spec/lib/expand_variables_spec.rb | 30 ------------------- .../gitlab/ci/variables/collection_spec.rb | 28 +++++++++++++++++ 6 files changed, 41 insertions(+), 42 deletions(-) diff --git a/doc/ci/yaml/inputs.md b/doc/ci/yaml/inputs.md index 67f76a6274e53b..2959be59988dd0 100644 --- a/doc/ci/yaml/inputs.md +++ b/doc/ci/yaml/inputs.md @@ -156,10 +156,11 @@ In this example: #### `expand_vars` -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/387632) in GitLab 16.4. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/387632) in GitLab 16.5. Use `expand_vars` to expand [CI/CD variables](../variables/index.md) in the input value. -Only variables that are **not** masked can be expanded. +Only variables that are **not** [masked](../variables/index.md#mask-a-cicd-variable) can be expanded. +[Nested variable expansion](../variables/where_variables_can_be_used.md#nested-variable-expansion) is not supported. Example: diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb index 15f153fa515d98..f565eb105ae2bb 100644 --- a/lib/expand_variables.rb +++ b/lib/expand_variables.rb @@ -22,15 +22,6 @@ def possible_var_reference?(value) %w[$ %].any? { |symbol| value.include?(symbol) } end - def has_masked_var_reference?(value, collection) - return false unless value - - value.scan(VARIABLES_REGEXP).flatten.any? do |match| - item = collection[match] - item.masked? if item - end - end - private def replace_with(value, variables) diff --git a/lib/gitlab/ci/config/interpolation/functions/expand_vars.rb b/lib/gitlab/ci/config/interpolation/functions/expand_vars.rb index ee79dc13b6496a..cb653c3022b4e6 100644 --- a/lib/gitlab/ci/config/interpolation/functions/expand_vars.rb +++ b/lib/gitlab/ci/config/interpolation/functions/expand_vars.rb @@ -19,7 +19,7 @@ def execute(input_value) return error("invalid input type: #{self.class.name} can only be used with string inputs") end - if ExpandVariables.has_masked_var_reference?(input_value, variables) + if variables.masked_variable_in?(input_value) return error('invalid variable type: masked variables cannot be expanded') end diff --git a/lib/gitlab/ci/variables/collection.rb b/lib/gitlab/ci/variables/collection.rb index f77ef262236280..c8d220be6a3f98 100644 --- a/lib/gitlab/ci/variables/collection.rb +++ b/lib/gitlab/ci/variables/collection.rb @@ -114,6 +114,15 @@ def to_s "#{@variables_by_key.keys}, @errors='#{@errors}'" end + def masked_variable_in?(value) + return false unless value + + value.scan(Item::VARIABLES_REGEXP).flatten.any? do |match| + item = self[match] + item.masked? if item + end + end + protected def expand_value(value, keep_undefined: false, expand_file_refs: true, expand_raw_refs: true) diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb index 5306c479fca341..ad73665326ab34 100644 --- a/spec/lib/expand_variables_spec.rb +++ b/spec/lib/expand_variables_spec.rb @@ -326,34 +326,4 @@ end end end - - describe '.has_masked_var_reference?' do - using RSpec::Parameterized::TableSyntax - - let(:variables_array) do - [ - { key: 'MY_VAR', value: 'value', masked: false }, - { key: 'MASKED_VAR', value: 'masked', masked: true } - ] - end - - let(:collection) { Gitlab::Ci::Variables::Collection.new(variables_array) } - - subject(:result) { described_class.has_masked_var_reference?(value, collection) } - - where(:value, :expected_result) do - 'test$MASKED_VAR' | true - 'test${MY_VAR}${MASKED_VAR}' | true - 'test$MY_VAR%MASKED_VAR%' | true - 'test$UNKNOWN_VAR' | false - '$MY_VAR test %MASKED_VAR' | false - 'MASKED_VAR' | false - end - - with_them do - it 'correctly detects when a masked variable is referenced in the value' do - expect(result).to eq(expected_result) - end - end - end end diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index d21190ae297c29..d7ad68bb9bbf65 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -632,4 +632,32 @@ it { is_expected.to eq("[\"VAR\", \"VAR2\"], @errors='circular variable reference detected'") } end + + describe '#masked_variable_in?' do + using RSpec::Parameterized::TableSyntax + + let(:variables) do + [ + { key: 'MY_VAR', value: 'value', masked: false }, + { key: 'MASKED_VAR', value: 'masked', masked: true } + ] + end + + subject(:result) { described_class.new(variables).masked_variable_in?(value) } + + where(:value, :expected_result) do + 'test$MASKED_VAR' | true + 'test${MY_VAR}${MASKED_VAR}' | true + 'test$MY_VAR%MASKED_VAR%' | true + 'test$UNKNOWN_VAR' | false + '$MY_VAR test %MASKED_VAR' | false + 'MASKED_VAR' | false + end + + with_them do + it 'correctly detects when a masked variable is present in the value' do + is_expected.to eq(expected_result) + end + end + end end -- GitLab From b821d5af9e827d35eeab03a50f8aa544fded47c6 Mon Sep 17 00:00:00 2001 From: lma-git Date: Wed, 20 Sep 2023 09:55:39 -0700 Subject: [PATCH 4/6] Rebase and resolve MR conflict Rebase and resolve minor MR conflict in lib/expand_variables.rb --- lib/expand_variables.rb | 24 ++-- lib/gitlab/ci/config/interpolation/block.rb | 7 +- lib/gitlab/ci/config/interpolation/context.rb | 23 ++-- .../ci/config/interpolation/functions/base.rb | 6 +- .../interpolation/functions/expand_vars.rb | 12 +- .../interpolation/functions/truncate.rb | 1 + .../config/interpolation/functions_stack.rb | 8 +- .../ci/config/interpolation/interpolator.rb | 6 +- .../ci/config/interpolation/template.rb | 7 +- lib/gitlab/ci/config/yaml/loader.rb | 4 +- lib/gitlab/ci/variables/collection.rb | 9 -- spec/lib/expand_variables_spec.rb | 104 ++++++++++++++++++ .../ci/config/interpolation/context_spec.rb | 6 + .../interpolation/functions/base_spec.rb | 2 +- .../functions/expand_vars_spec.rb | 12 +- .../interpolation/functions/truncate_spec.rb | 4 +- .../interpolation/functions_stack_spec.rb | 4 +- .../config/interpolation/interpolator_spec.rb | 2 +- .../gitlab/ci/variables/collection_spec.rb | 28 ----- 19 files changed, 177 insertions(+), 92 deletions(-) diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb index f565eb105ae2bb..ad5aabfa1f3b18 100644 --- a/lib/expand_variables.rb +++ b/lib/expand_variables.rb @@ -1,18 +1,24 @@ # frozen_string_literal: true module ExpandVariables + VariableExpansionError = Class.new(StandardError) + VARIABLES_REGEXP = /\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/ class << self - def expand(value, variables, expand_file_refs: true) + def expand(value, variables, expand_file_refs: true, fail_on_masked: false) replace_with(value, variables) do |collection, last_match| - match_or_blank_value(collection, last_match, expand_file_refs: expand_file_refs) + match_or_blank_value( + collection, last_match, expand_file_refs: expand_file_refs, fail_on_masked: fail_on_masked + ) end end - def expand_existing(value, variables, expand_file_refs: true) + def expand_existing(value, variables, expand_file_refs: true, fail_on_masked: false) replace_with(value, variables) do |collection, last_match| - match_or_original_value(collection, last_match, expand_file_refs: expand_file_refs) + match_or_original_value( + collection, last_match, expand_file_refs: expand_file_refs, fail_on_masked: fail_on_masked + ) end end @@ -36,12 +42,14 @@ def replace_with(value, variables) end end - def match_or_blank_value(collection, last_match, expand_file_refs:) + def match_or_blank_value(collection, last_match, expand_file_refs:, fail_on_masked:) match = last_match[1] || last_match[2] replacement = collection[match] if replacement.nil? nil + elsif fail_on_masked && replacement.masked? + raise VariableExpansionError, 'masked variables cannot be expanded' elsif replacement.file? expand_file_refs ? replacement.value : last_match else @@ -49,8 +57,10 @@ def match_or_blank_value(collection, last_match, expand_file_refs:) end end - def match_or_original_value(collection, last_match, expand_file_refs:) - match_or_blank_value(collection, last_match, expand_file_refs: expand_file_refs) || last_match[0] + def match_or_original_value(collection, last_match, expand_file_refs:, fail_on_masked:) + match_or_blank_value( + collection, last_match, expand_file_refs: expand_file_refs, fail_on_masked: fail_on_masked + ) || last_match[0] end end end diff --git a/lib/gitlab/ci/config/interpolation/block.rb b/lib/gitlab/ci/config/interpolation/block.rb index 4729c9e7f6c5f6..aec19299e86fec 100644 --- a/lib/gitlab/ci/config/interpolation/block.rb +++ b/lib/gitlab/ci/config/interpolation/block.rb @@ -17,13 +17,12 @@ class Block PATTERN = /(?\$\[\[\s*(?.*?)\s*\]\])/ MAX_FUNCTIONS = 3 - attr_reader :block, :data, :ctx, :variables, :errors + attr_reader :block, :data, :ctx, :errors - def initialize(block, data, ctx, variables: nil) + def initialize(block, data, ctx) @block = block @data = data @ctx = ctx - @variables = variables @errors = [] @value = nil @@ -63,7 +62,7 @@ def evaluate! return @errors.concat(access.errors) unless access.valid? return @errors.push('too many functions in interpolation block') if functions.count > MAX_FUNCTIONS - result = Interpolation::FunctionsStack.new(functions, variables: variables).evaluate(access.value) + result = Interpolation::FunctionsStack.new(functions, ctx).evaluate(access.value) if result.success? @value = result.value diff --git a/lib/gitlab/ci/config/interpolation/context.rb b/lib/gitlab/ci/config/interpolation/context.rb index f5e7db032915fd..19ea619f7da375 100644 --- a/lib/gitlab/ci/config/interpolation/context.rb +++ b/lib/gitlab/ci/config/interpolation/context.rb @@ -14,8 +14,11 @@ class Context MAX_DEPTH = 3 - def initialize(hash) - @context = hash + attr_reader :variables + + def initialize(data, variables: []) + @data = data + @variables = Ci::Variables::Collection.fabricate(variables) raise ContextTooComplexError if depth > MAX_DEPTH end @@ -32,25 +35,25 @@ def errors end def depth - deep_depth(@context) + deep_depth(@data) end def fetch(field) - @context.fetch(field) + @data.fetch(field) end def key?(name) - @context.key?(name) + @data.key?(name) end def to_h - @context.to_h + @data.to_h end private - def deep_depth(context, depth = 0) - values = context.values.map do |value| + def deep_depth(data, depth = 0) + values = data.values.map do |value| if value.is_a?(Hash) deep_depth(value, depth + 1) else @@ -61,10 +64,10 @@ def deep_depth(context, depth = 0) values.max.to_i end - def self.fabricate(context) + def self.fabricate(context, variables: []) case context when Hash - new(context) + new(context, variables: variables) when Interpolation::Context context else diff --git a/lib/gitlab/ci/config/interpolation/functions/base.rb b/lib/gitlab/ci/config/interpolation/functions/base.rb index 145377cde6ae22..b04152a1558b13 100644 --- a/lib/gitlab/ci/config/interpolation/functions/base.rb +++ b/lib/gitlab/ci/config/interpolation/functions/base.rb @@ -20,10 +20,10 @@ def self.matches?(function_expression) function_expression_pattern.match?(function_expression) end - def initialize(function_expression, variables: nil) + def initialize(function_expression, ctx) @errors = [] @function_args = parse_args(function_expression) - @variables = variables || Ci::Variables::Collection.new + @ctx = ctx end def valid? @@ -36,7 +36,7 @@ def execute(_input_value) private - attr_reader :function_args, :variables + attr_reader :function_args, :ctx def error(message) errors << "error in `#{self.class.name}` function: #{message}" diff --git a/lib/gitlab/ci/config/interpolation/functions/expand_vars.rb b/lib/gitlab/ci/config/interpolation/functions/expand_vars.rb index cb653c3022b4e6..658964018b53a0 100644 --- a/lib/gitlab/ci/config/interpolation/functions/expand_vars.rb +++ b/lib/gitlab/ci/config/interpolation/functions/expand_vars.rb @@ -16,14 +16,14 @@ def self.name def execute(input_value) unless input_value.is_a?(String) - return error("invalid input type: #{self.class.name} can only be used with string inputs") + error("invalid input type: #{self.class.name} can only be used with string inputs") + return end - if variables.masked_variable_in?(input_value) - return error('invalid variable type: masked variables cannot be expanded') - end - - ExpandVariables.expand_existing(input_value, variables) + ExpandVariables.expand_existing(input_value, ctx.variables, fail_on_masked: true) + rescue ExpandVariables::VariableExpansionError => e + error("variable expansion error: #{e.message}") + nil end end end diff --git a/lib/gitlab/ci/config/interpolation/functions/truncate.rb b/lib/gitlab/ci/config/interpolation/functions/truncate.rb index 915c11511021dc..3771756ba4864a 100644 --- a/lib/gitlab/ci/config/interpolation/functions/truncate.rb +++ b/lib/gitlab/ci/config/interpolation/functions/truncate.rb @@ -19,6 +19,7 @@ def execute(input_value) input_value[offset, length].to_s else error('invalid input type: truncate can only be used with string inputs') + nil end end diff --git a/lib/gitlab/ci/config/interpolation/functions_stack.rb b/lib/gitlab/ci/config/interpolation/functions_stack.rb index 612e36a5f38c15..4cb3e67b3e3dab 100644 --- a/lib/gitlab/ci/config/interpolation/functions_stack.rb +++ b/lib/gitlab/ci/config/interpolation/functions_stack.rb @@ -22,9 +22,9 @@ def success? attr_reader :errors - def initialize(function_expressions, variables: nil) + def initialize(function_expressions, ctx) + @ctx = ctx @errors = [] - @variables = variables @functions = build_stack(function_expressions) end @@ -50,14 +50,14 @@ def evaluate(input_value) private - attr_reader :functions, :variables + attr_reader :functions, :ctx def build_stack(function_expressions) function_expressions.map do |function_expression| matching_function = FUNCTIONS.find { |function| function.matches?(function_expression) } if matching_function.present? - matching_function.new(function_expression, variables: variables) + matching_function.new(function_expression, ctx) else message = "no function matching `#{function_expression}`: " \ 'check that the function name, arguments, and types are correct' diff --git a/lib/gitlab/ci/config/interpolation/interpolator.rb b/lib/gitlab/ci/config/interpolation/interpolator.rb index c17c1e943961a8..5b21b777c1d7de 100644 --- a/lib/gitlab/ci/config/interpolation/interpolator.rb +++ b/lib/gitlab/ci/config/interpolation/interpolator.rb @@ -10,7 +10,7 @@ module Interpolation class Interpolator attr_reader :config, :args, :variables, :errors - def initialize(config, args, variables: nil) + def initialize(config, args, variables) @config = config @args = args.to_h @variables = variables @@ -87,11 +87,11 @@ def inputs end def context - @context ||= Context.new({ inputs: inputs.to_hash }) + @context ||= Context.new({ inputs: inputs.to_hash }, variables: variables) end def template - @template ||= Template.new(content, context, variables: variables) + @template ||= Template.new(content, context) end end end diff --git a/lib/gitlab/ci/config/interpolation/template.rb b/lib/gitlab/ci/config/interpolation/template.rb index 59b7ee5a2691c1..ece2a4756aafdf 100644 --- a/lib/gitlab/ci/config/interpolation/template.rb +++ b/lib/gitlab/ci/config/interpolation/template.rb @@ -7,17 +7,16 @@ module Interpolation class Template include Gitlab::Utils::StrongMemoize - attr_reader :blocks, :ctx, :variables + attr_reader :blocks, :ctx TooManyBlocksError = Class.new(StandardError) InvalidBlockError = Class.new(StandardError) MAX_BLOCKS = 10_000 - def initialize(config, ctx, variables: nil) + def initialize(config, ctx) @config = Interpolation::Config.fabricate(config) @ctx = Interpolation::Context.fabricate(ctx) - @variables = variables @errors = [] @blocks = {} @@ -56,7 +55,7 @@ def interpolate! strong_memoize_attr :interpolate! def evaluate_block(block, data) - block = (@blocks[block] ||= Interpolation::Block.new(block, data, ctx, variables: variables)) + block = (@blocks[block] ||= Interpolation::Block.new(block, data, ctx)) raise TooManyBlocksError if @blocks.count > MAX_BLOCKS raise InvalidBlockError unless block.valid? diff --git a/lib/gitlab/ci/config/yaml/loader.rb b/lib/gitlab/ci/config/yaml/loader.rb index 96465ed8b4f72e..c659ad5b8d1b65 100644 --- a/lib/gitlab/ci/config/yaml/loader.rb +++ b/lib/gitlab/ci/config/yaml/loader.rb @@ -10,7 +10,7 @@ class Loader AVAILABLE_TAGS = [Config::Yaml::Tags::Reference].freeze MAX_DOCUMENTS = 2 - def initialize(content, inputs: {}, variables: nil) + def initialize(content, inputs: {}, variables: []) @content = content @inputs = inputs @variables = variables @@ -21,7 +21,7 @@ def load return yaml_result unless yaml_result.valid? - interpolator = Interpolation::Interpolator.new(yaml_result, inputs, variables: variables) + interpolator = Interpolation::Interpolator.new(yaml_result, inputs, variables) interpolator.interpolate! diff --git a/lib/gitlab/ci/variables/collection.rb b/lib/gitlab/ci/variables/collection.rb index c8d220be6a3f98..f77ef262236280 100644 --- a/lib/gitlab/ci/variables/collection.rb +++ b/lib/gitlab/ci/variables/collection.rb @@ -114,15 +114,6 @@ def to_s "#{@variables_by_key.keys}, @errors='#{@errors}'" end - def masked_variable_in?(value) - return false unless value - - value.scan(Item::VARIABLES_REGEXP).flatten.any? do |match| - item = self[match] - item.masked? if item - end - end - protected def expand_value(value, keep_undefined: false, expand_file_refs: true, expand_raw_refs: true) diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb index ad73665326ab34..695e63b6db16b0 100644 --- a/spec/lib/expand_variables_spec.rb +++ b/spec/lib/expand_variables_spec.rb @@ -187,6 +187,102 @@ end end + shared_examples 'masked variable expansion with fail_on_masked true' do |expander| + using RSpec::Parameterized::TableSyntax + + subject { expander.call(value, variables, fail_on_masked: true) } + + where do + { + 'simple expansion with a masked variable': { + value: 'key$variable', + variables: [ + { key: 'variable', value: 'value', masked: true } + ] + }, + 'complex expansion with a masked variable': { + value: 'key${variable}${variable2}', + variables: [ + { key: 'variable', value: 'value', masked: true }, + { key: 'variable2', value: 'result', masked: false } + ] + }, + 'expansion using % with a masked variable': { + value: 'key%variable%', + variables: [ + { key: 'variable', value: 'value', masked: true } + ] + } + } + end + + with_them do + it 'raises an error' do + expect { subject }.to raise_error( + ExpandVariables::VariableExpansionError, /masked variables cannot be expanded/ + ) + end + end + + context 'expansion without a masked variable' do + let(:value) { 'key$variable${variable2}' } + + let(:variables) do + [ + { key: 'variable', value: 'value', masked: false }, + { key: 'variable2', value: 'result', masked: false } + ] + end + + it { is_expected.to eq('keyvalueresult') } + end + end + + shared_examples 'masked variable expansion with fail_on_masked false' do |expander| + using RSpec::Parameterized::TableSyntax + + subject { expander.call(value, variables, fail_on_masked: false) } + + where do + { + 'simple expansion with a masked variable': { + value: 'key$variable', + result: 'keyvalue', + variables: [ + { key: 'variable', value: 'value', masked: true } + ] + }, + 'complex expansion with a masked variable': { + value: 'key${variable}${variable2}', + result: 'keyvalueresult', + variables: [ + { key: 'variable', value: 'value', masked: true }, + { key: 'variable2', value: 'result', masked: false } + ] + }, + 'expansion using % with a masked variable': { + value: 'key%variable%', + result: 'keyvalue', + variables: [ + { key: 'variable', value: 'value', masked: true } + ] + }, + 'expansion without a masked variable': { + value: 'key$variable${variable2}', + result: 'keyvalueresult', + variables: [ + { key: 'variable', value: 'value', masked: false }, + { key: 'variable2', value: 'result', masked: false } + ] + } + } + end + + with_them do + it { is_expected.to eq(result) } + end + end + describe '#expand' do context 'table tests' do it_behaves_like 'common variable expansion', described_class.method(:expand) @@ -195,6 +291,10 @@ it_behaves_like 'file variable expansion with expand_file_refs false', described_class.method(:expand) + it_behaves_like 'masked variable expansion with fail_on_masked true', described_class.method(:expand) + + it_behaves_like 'masked variable expansion with fail_on_masked false', described_class.method(:expand) + context 'with missing variables' do using RSpec::Parameterized::TableSyntax @@ -265,6 +365,10 @@ it_behaves_like 'file variable expansion with expand_file_refs false', described_class.method(:expand_existing) + it_behaves_like 'masked variable expansion with fail_on_masked true', described_class.method(:expand) + + it_behaves_like 'masked variable expansion with fail_on_masked false', described_class.method(:expand) + context 'with missing variables' do using RSpec::Parameterized::TableSyntax diff --git a/spec/lib/gitlab/ci/config/interpolation/context_spec.rb b/spec/lib/gitlab/ci/config/interpolation/context_spec.rb index c90866c986a33f..56a572312ebd52 100644 --- a/spec/lib/gitlab/ci/config/interpolation/context_spec.rb +++ b/spec/lib/gitlab/ci/config/interpolation/context_spec.rb @@ -17,6 +17,12 @@ end end + describe '.new' do + it 'returns variables as a Variables::Collection object' do + expect(subject.variables.class).to eq(Gitlab::Ci::Variables::Collection) + end + end + describe '#to_h' do it 'returns the context hash' do expect(subject.to_h).to eq(ctx) diff --git a/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb index c193e88dbe2e72..a2b575afb6ffa0 100644 --- a/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb +++ b/spec/lib/gitlab/ci/config/interpolation/functions/base_spec.rb @@ -18,6 +18,6 @@ def self.name it 'defines an expected interface for child classes' do expect { described_class.function_expression_pattern }.to raise_error(NotImplementedError) expect { described_class.name }.to raise_error(NotImplementedError) - expect { custom_function_klass.new('test').execute('input') }.to raise_error(NotImplementedError) + expect { custom_function_klass.new('test', nil).execute('input') }.to raise_error(NotImplementedError) end end diff --git a/spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb index e02298b0f625f9..2a627b435d3a9e 100644 --- a/spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb +++ b/spec/lib/gitlab/ci/config/interpolation/functions/expand_vars_spec.rb @@ -3,19 +3,19 @@ require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Config::Interpolation::Functions::ExpandVars, feature_category: :pipeline_composition do - let(:variables_array) do - [ + let(:variables) do + Gitlab::Ci::Variables::Collection.new([ { key: 'VAR1', value: 'value1', masked: false }, { key: 'VAR2', value: 'value2', masked: false }, { key: 'NESTED_VAR', value: '$MY_VAR', masked: false }, { key: 'MASKED_VAR', value: 'masked', masked: true } - ] + ]) end - let(:variables) { Gitlab::Ci::Variables::Collection.new(variables_array) } let(:function_expression) { 'expand_vars' } + let(:ctx) { Gitlab::Ci::Config::Interpolation::Context.new({}, variables: variables) } - subject(:function) { described_class.new(function_expression, variables: variables) } + subject(:function) { described_class.new(function_expression, ctx) } describe '#execute' do let(:input_value) { '$VAR1' } @@ -43,7 +43,7 @@ expect(execute).to be_nil expect(function).not_to be_valid expect(function.errors).to contain_exactly( - 'error in `expand_vars` function: invalid variable type: masked variables cannot be expanded' + 'error in `expand_vars` function: variable expansion error: masked variables cannot be expanded' ) end end diff --git a/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb index c521eff9811b36..93e5d4ef48cf49 100644 --- a/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb +++ b/spec/lib/gitlab/ci/config/interpolation/functions/truncate_spec.rb @@ -12,7 +12,7 @@ end it 'truncates the given input' do - function = described_class.new('truncate(1,2)') + function = described_class.new('truncate(1,2)', nil) output = function.execute('test') @@ -22,7 +22,7 @@ context 'when given a non-string input' do it 'returns an error' do - function = described_class.new('truncate(1,2)') + function = described_class.new('truncate(1,2)', nil) function.execute(100) diff --git a/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb b/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb index 881f092c440ec5..9ac0ef05c619ca 100644 --- a/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb +++ b/spec/lib/gitlab/ci/config/interpolation/functions_stack_spec.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require 'spec_helper' +require 'fast_spec_helper' RSpec.describe Gitlab::Ci::Config::Interpolation::FunctionsStack, feature_category: :pipeline_composition do let(:functions) { ['truncate(0,4)', 'truncate(1,2)'] } let(:input_value) { 'test_input_value' } - subject { described_class.new(functions).evaluate(input_value) } + subject { described_class.new(functions, nil).evaluate(input_value) } it 'modifies the given input value according to the function expressions' do expect(subject).to be_success diff --git a/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb index 804164c933a801..c924323837b642 100644 --- a/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb +++ b/spec/lib/gitlab/ci/config/interpolation/interpolator_spec.rb @@ -7,7 +7,7 @@ let(:result) { ::Gitlab::Ci::Config::Yaml::Result.new(config: [header, content]) } - subject { described_class.new(result, arguments) } + subject { described_class.new(result, arguments, []) } context 'when input data is valid' do let(:header) do diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index d7ad68bb9bbf65..d21190ae297c29 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -632,32 +632,4 @@ it { is_expected.to eq("[\"VAR\", \"VAR2\"], @errors='circular variable reference detected'") } end - - describe '#masked_variable_in?' do - using RSpec::Parameterized::TableSyntax - - let(:variables) do - [ - { key: 'MY_VAR', value: 'value', masked: false }, - { key: 'MASKED_VAR', value: 'masked', masked: true } - ] - end - - subject(:result) { described_class.new(variables).masked_variable_in?(value) } - - where(:value, :expected_result) do - 'test$MASKED_VAR' | true - 'test${MY_VAR}${MASKED_VAR}' | true - 'test$MY_VAR%MASKED_VAR%' | true - 'test$UNKNOWN_VAR' | false - '$MY_VAR test %MASKED_VAR' | false - 'MASKED_VAR' | false - end - - with_them do - it 'correctly detects when a masked variable is present in the value' do - is_expected.to eq(expected_result) - end - end - end end -- GitLab From 3420616e01b48fe7421f3efc2cc94bc4e75a4a37 Mon Sep 17 00:00:00 2001 From: lma-git Date: Tue, 19 Sep 2023 10:42:41 -0700 Subject: [PATCH 5/6] Documentation updates to clarify Updated documentation to further clarify input interpolation functions --- doc/ci/yaml/inputs.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/doc/ci/yaml/inputs.md b/doc/ci/yaml/inputs.md index 2959be59988dd0..3499472df118eb 100644 --- a/doc/ci/yaml/inputs.md +++ b/doc/ci/yaml/inputs.md @@ -140,17 +140,18 @@ Details: spec: inputs: test: - default: '0123456789' + default: 'test $MY_VAR' --- test-job: - script: echo $[[ inputs.test | truncate(1,3) ]] + script: echo $[[ inputs.test | expand_vars | truncate(5,8) ]] ``` -In this example: +In this example, assuming the input value is defaulted and `$MY_VAR` is an unmasked project variable with value `my value`: -- The function [`truncate`](#truncate) applies to the value of `inputs.test`. -- Assuming the value of `inputs.test` is `0123456789`, then the output of `script` would be `echo 123`. +- First, the function [`expand_vars`](#expand_vars) expands the value to `test my value`. +- Then [`truncate`](#truncate) applies to `test my value` with a character offset of `5` and length `8`. +- The output of `script` would be `echo my value`. ### Predefined interpolation functions @@ -159,7 +160,9 @@ In this example: > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/387632) in GitLab 16.5. Use `expand_vars` to expand [CI/CD variables](../variables/index.md) in the input value. -Only variables that are **not** [masked](../variables/index.md#mask-a-cicd-variable) can be expanded. + +Only variables you can [use in `include`](includes.md#use-variables-with-include) and which are +**not** [masked](../variables/index.md#mask-a-cicd-variable) can be expanded. [Nested variable expansion](../variables/where_variables_can_be_used.md#nested-variable-expansion) is not supported. Example: @@ -169,7 +172,7 @@ $[[ inputs.test | expand_vars ]] ``` Assuming the value of `inputs.test` is `test $MY_VAR`, and the variable `$MY_VAR` is unmasked -and has the value `my value`, then the output would be `test my value`. +with value `my value`, then the output would be `test my value`. #### `truncate` -- GitLab From 1e58ffbc419aa0188c3986667461233e856db11b Mon Sep 17 00:00:00 2001 From: Marcel Amirault Date: Thu, 21 Sep 2023 15:34:21 +0000 Subject: [PATCH 6/6] Update wording in documentation with suggestions --- doc/ci/yaml/inputs.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/ci/yaml/inputs.md b/doc/ci/yaml/inputs.md index 3499472df118eb..26d0711066889a 100644 --- a/doc/ci/yaml/inputs.md +++ b/doc/ci/yaml/inputs.md @@ -147,11 +147,11 @@ test-job: script: echo $[[ inputs.test | expand_vars | truncate(5,8) ]] ``` -In this example, assuming the input value is defaulted and `$MY_VAR` is an unmasked project variable with value `my value`: +In this example, assuming the input uses the default value and `$MY_VAR` is an unmasked project variable with value `my value`: -- First, the function [`expand_vars`](#expand_vars) expands the value to `test my value`. -- Then [`truncate`](#truncate) applies to `test my value` with a character offset of `5` and length `8`. -- The output of `script` would be `echo my value`. +1. First, the function [`expand_vars`](#expand_vars) expands the value to `test my value`. +1. Then [`truncate`](#truncate) applies to `test my value` with a character offset of `5` and length `8`. +1. The output of `script` would be `echo my value`. ### Predefined interpolation functions @@ -161,7 +161,7 @@ In this example, assuming the input value is defaulted and `$MY_VAR` is an unmas Use `expand_vars` to expand [CI/CD variables](../variables/index.md) in the input value. -Only variables you can [use in `include`](includes.md#use-variables-with-include) and which are +Only variables you can [use with the `include` keyword](includes.md#use-variables-with-include) and which are **not** [masked](../variables/index.md#mask-a-cicd-variable) can be expanded. [Nested variable expansion](../variables/where_variables_can_be_used.md#nested-variable-expansion) is not supported. @@ -172,7 +172,7 @@ $[[ inputs.test | expand_vars ]] ``` Assuming the value of `inputs.test` is `test $MY_VAR`, and the variable `$MY_VAR` is unmasked -with value `my value`, then the output would be `test my value`. +with a value of `my value`, then the output would be `test my value`. #### `truncate` -- GitLab