Recursive Variable Expansion Logic
What does this MR do?
This Merge Request adds support for recursive environment variable expansion for environment variables.
It is specifically trying to fix gitlab-foss#43406 (moved) -- now #21218 (closed).
This is a resubmission of gitlab-foss!32181 (closed), which was closed due to the recent gitlab repo migration. As per the discussion on the previous MR, this MR does not contain "default value" support, which will be submitted in a separate MR.
variables
Expansion Support
Recursive If $SOME_VAR
is set to abc${SOME_OTHER_VAR}def
(both within the variables
sections of CI), all variables within variables will be expanded (no depth limit).
Infinite Recursion Protection
There is also infinite recursion protection. If an environment variable contains itself at some depth (e.g.: $SOME_VAR
is set to ~${SOME_VAR}~
, it will expand to ''
upon subsequent expansion attempts (the given example will expand to ~~
).
Test cases were added checking for recursion, default values, and infinite recursion protection.
Cases for Strange/Unsupported Behavior
Self Loop Inconsistent Depending on Order
If an indirect self-loop exists, it will be expanded differently depending on which variable is first used in the loop and if the loop had prior expansion. Consider the following test cases. Note that different expansions happen depending on order and start of loop.
"3-loop self reference expansion different starts": {
value: '~${variable}~${variable2}~',
result: '~abc123xyzzyx321cba~123xyzzyx321~',
variables: [
{ key: 'variable', value: 'abc${variable2}cba' },
{ key: 'variable2', value: '123${variable3}321' },
{ key: 'variable3', value: 'xyz${variable}zyx' }
]
},
"3-loop self reference expansion different starts 2": {
value: '~${variable2}~${variable}~',
result: '~123xyzabccbazyx321~abccba~',
variables: [
{ key: 'variable', value: 'abc${variable2}cba' },
{ key: 'variable2', value: '123${variable3}321' },
{ key: 'variable3', value: 'xyz${variable}zyx' }
]
},
This is not truly an issue, as it is contained within the scope of self-reference loops. Loops have no expectation of expanding properly (as that is impossible), and perhaps may lead to CI config errors (part of discussion below, !18112 (comment 228429884)). However, documenting this strange behavior is valuable.
Mecha Shiva!
When expanding variables, the string is parsed/expanded depth-first. At a given level (at a given node, if thought of as a tree), each variable is fully expanded. No post-combined-expansion of the current node occurs. This means that environment variables cannot be created dynamically and expanded. Consider the following example (named Mecha Shiva):
"mecha shiva": {
value: '~${MECHA}_${SHIVA}~',
result: '~Mecha Shiva!~',
variables: [
{ key: 'MECHA', value: '${MECHA' },
{ key: 'SHIVA', value: 'SHIVA}' },
{ key: 'MECHA_SHIVA', value: 'Mecha Shiva!' }
]
},
The actual expansion, of '~${MECHA}_${SHIVA}~'
, results in '~${MECHA_SHIVA}~'
instead of its expanded form 'Mecha Shiva!'
. The current algorithm does not do a post-processing expansion of the current node, so it does not detect the newly formed environment variable MECHA_SHIVA
. Note that this is true anywhere in the expansion tree, not only the root. This actually does sorta violate the "fully expand guarantee" (::ExpandVariables.expand(::ExpandVariables.expand(value, all_variables), all_variables)
should always be equal to ::ExpandVariables.expand(value, all_variables)
) -- discussed in !18112 (comment 227769151). Mecha Shiva functionality could be added in... I just kinda chose to leave it out, since its kinda awkward behavior. Still wanted to document its absence.
Screenshots
N/A
Does this MR meet the acceptance criteria?
Conformity
-
Changelog entry -
Documentation created/updated or follow-up review issue created -
Code review guidelines -
Merge request performance guidelines -
Style guides -
Database guides -
Separation of EE specific content
Performance and Testing
Potential Performance Impact
Now that environment variable expansion is done in a loop, it is possible for ExpandVariables.expand()
to take longer than before (when only expanding a depth of 1 or 2).
However, extending this loop requires manually adding unique recursive environment variables -- which is unlikely. The infinite recursion protection should prevent any non-linear recursion. It is also worth noting that the expansion algorithm employs memoization; if the same variable is encountered multiple times, subsequent expansions will be O(1) via a lookup hash.
Additional Testing
As mentioned above, test cases we added in order to check for different forms of looping environment variable expansion. Since this is a back-end change, no extra cross-browser testing should be necessary.
-
Review and add/update tests for this feature/bug. Consider all test levels. See the Test Planning Process.
Security
If this MR contains changes to processing or storing of credentials or tokens, authorization and authentication methods and other items described in the security review guidelines:
-
Label as security and @ mention @gitlab-com/gl-security/appsec
-
The MR includes necessary changes to maintain consistency between UI, API, email, or other methods -
Security reports checked/validated by a reviewer from the AppSec team