Expand variables only when needed

This makes us to expand variables only when needed,
instead of requesting all variables each time.

This specifically helps in situation when explicit name
of `environment: production` is used.
This commit is contained in:
Kamil Trzciński 2019-08-13 17:03:52 +02:00
parent a55869483d
commit 6150c3ff0d
4 changed files with 139 additions and 58 deletions

View file

@ -384,7 +384,7 @@ module Ci
return unless has_environment? return unless has_environment?
strong_memoize(:expanded_environment_name) do strong_memoize(:expanded_environment_name) do
ExpandVariables.expand(environment, simple_variables) ExpandVariables.expand(environment, -> { simple_variables })
end end
end end

View file

@ -42,7 +42,7 @@ class UpdateDeploymentService
return unless environment_url return unless environment_url
@expanded_environment_url = @expanded_environment_url =
ExpandVariables.expand(environment_url, variables) ExpandVariables.expand(environment_url, -> { variables })
end end
def environment_url def environment_url

View file

@ -3,6 +3,20 @@
module ExpandVariables module ExpandVariables
class << self class << self
def expand(value, variables) def expand(value, variables)
variables_hash = nil
value.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
variables_hash ||= transform_variables(variables)
variables_hash[$1 || $2]
end
end
private
def transform_variables(variables)
# Lazily initialise variables
variables = variables.call if variables.is_a?(Proc)
# Convert hash array to variables # Convert hash array to variables
if variables.is_a?(Array) if variables.is_a?(Array)
variables = variables.reduce({}) do |hash, variable| variables = variables.reduce({}) do |hash, variable|
@ -11,9 +25,7 @@ module ExpandVariables
end end
end end
value.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do variables
variables[$1 || $2]
end
end end
end end
end end

View file

@ -4,62 +4,131 @@ require 'spec_helper'
describe ExpandVariables do describe ExpandVariables do
describe '#expand' do describe '#expand' do
subject { described_class.expand(value, variables) } context 'table tests' do
using RSpec::Parameterized::TableSyntax
tests = [ where do
{ value: 'key', {
"no expansion": {
value: 'key',
result: 'key', result: 'key',
variables: [] }, variables: []
{ value: 'key$variable', },
"missing variable": {
value: 'key$variable',
result: 'key', result: 'key',
variables: [] }, variables: []
{ value: 'key$variable', },
"simple expansion": {
value: 'key$variable',
result: 'keyvalue', result: 'keyvalue',
variables: [ variables: [
{ key: 'variable', value: 'value' } { key: 'variable', value: 'value' }
] }, ]
{ value: 'key${variable}', },
"simple with hash of variables": {
value: 'key$variable',
result: 'keyvalue',
variables: {
'variable' => 'value'
}
},
"complex expansion": {
value: 'key${variable}',
result: 'keyvalue', result: 'keyvalue',
variables: [ variables: [
{ key: 'variable', value: 'value' } { key: 'variable', value: 'value' }
] }, ]
{ value: 'key$variable$variable2', },
"simple expansions": {
value: 'key$variable$variable2',
result: 'keyvalueresult', result: 'keyvalueresult',
variables: [ variables: [
{ key: 'variable', value: 'value' }, { key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' } { key: 'variable2', value: 'result' }
] }, ]
{ value: 'key${variable}${variable2}', },
"complex expansions": {
value: 'key${variable}${variable2}',
result: 'keyvalueresult', result: 'keyvalueresult',
variables: [ variables: [
{ key: 'variable', value: 'value' }, { key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' } { key: 'variable2', value: 'result' }
] }, ]
{ value: 'key$variable2$variable', },
"complex expansions with missing variable": {
value: 'key${variable}${variable2}',
result: 'keyvalue',
variables: [
{ key: 'variable', value: 'value' }
]
},
"out-of-order expansion": {
value: 'key$variable2$variable',
result: 'keyresultvalue', result: 'keyresultvalue',
variables: [ variables: [
{ key: 'variable', value: 'value' }, { key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' } { key: 'variable2', value: 'result' }
] }, ]
{ value: 'key${variable2}${variable}', },
"out-of-order complex expansion": {
value: 'key${variable2}${variable}',
result: 'keyresultvalue', result: 'keyresultvalue',
variables: [ variables: [
{ key: 'variable', value: 'value' }, { key: 'variable', value: 'value' },
{ key: 'variable2', value: 'result' } { key: 'variable2', value: 'result' }
] }, ]
{ value: 'review/$CI_COMMIT_REF_NAME', },
"review-apps expansion": {
value: 'review/$CI_COMMIT_REF_NAME',
result: 'review/feature/add-review-apps', result: 'review/feature/add-review-apps',
variables: [ variables: [
{ key: 'CI_COMMIT_REF_NAME', value: 'feature/add-review-apps' } { key: 'CI_COMMIT_REF_NAME', value: 'feature/add-review-apps' }
] }
] ]
},
"do not lazily access variables when no expansion": {
value: 'key',
result: 'key',
variables: -> { raise NotImplementedError }
},
"lazily access variables": {
value: 'key$variable',
result: 'keyvalue',
variables: -> { [{ key: 'variable', value: 'value' }] }
}
}
end
tests.each do |test| with_them do
context "#{test[:value]} resolves to #{test[:result]}" do subject { ExpandVariables.expand(value, variables) } # rubocop:disable RSpec/DescribedClass
let(:value) { test[:value] }
let(:variables) { test[:variables] }
it { is_expected.to eq(test[:result]) } it { is_expected.to eq(result) }
end
end
context 'lazily inits variables' do
let(:variables) { -> { [{ key: 'variable', value: 'result' }] } }
subject { described_class.expand(value, variables) }
context 'when expanding variable' do
let(:value) { 'key$variable$variable2' }
it 'calls block at most once' do
expect(variables).to receive(:call).once.and_call_original
is_expected.to eq('keyresult')
end
end
context 'when no expansion is needed' do
let(:value) { 'key' }
it 'does not call block' do
expect(variables).not_to receive(:call)
is_expected.to eq('key')
end
end end
end end
end end