@@ -21,33 +21,45 @@ func init() {
2121 }
2222}
2323
24+ var variablesToCheck = []string {
25+ "item" ,
26+ "retries" ,
27+ "lastRetry.exitCode" ,
28+ "lastRetry.status" ,
29+ "lastRetry.duration" ,
30+ "lastRetry.message" ,
31+ "workflow.status" ,
32+ "workflow.failures" ,
33+ }
34+
35+ func anyVarNotInEnv (expression string , env map [string ]interface {}) * string {
36+ for _ , variable := range variablesToCheck {
37+ if hasVariableInExpression (expression , variable ) && ! hasVarInEnv (env , variable ) {
38+ return & variable
39+ }
40+ }
41+ return nil
42+ }
43+
2444func expressionReplace (w io.Writer , expression string , env map [string ]interface {}, allowUnresolved bool ) (int , error ) {
2545 // The template is JSON-marshaled. This JSON-unmarshals the expression to undo any character escapes.
2646 var unmarshalledExpression string
2747 err := json .Unmarshal ([]byte (fmt .Sprintf (`"%s"` , expression )), & unmarshalledExpression )
2848 if err != nil && allowUnresolved {
29- log .WithError (err ).Debug ("unresolved is allowed " )
49+ log .WithError (err ).Debug ("unresolved is allowed" )
3050 return fmt .Fprintf (w , "{{%s%s}}" , kindExpression , expression )
3151 }
3252 if err != nil {
3353 return 0 , fmt .Errorf ("failed to unmarshall JSON expression: %w" , err )
3454 }
3555
36- if _ , ok := env ["retries" ]; ! ok && hasRetries (unmarshalledExpression ) && allowUnresolved {
37- // this is to make sure expressions like `sprig.int(retries)` don't get resolved to 0 when `retries` don't exist in the env
38- // See https://github.com/argoproj/argo-workflows/issues/5388
39- log .WithError (err ).Debug ("Retries are present and unresolved is allowed" )
40- return fmt .Fprintf (w , "{{%s%s}}" , kindExpression , expression )
41- }
42-
43- // This is to make sure expressions which contains `workflow.status` and `work.failures` don't get resolved to nil
44- // when `workflow.status` and `workflow.failures` don't exist in the env.
45- // See https://github.com/argoproj/argo-workflows/issues/10393, https://github.com/expr-lang/expr/issues/330
46- // This issue doesn't happen to other template parameters since `workflow.status` and `workflow.failures` only exist in the env
47- // when the exit handlers complete.
48- if ((hasWorkflowStatus (unmarshalledExpression ) && ! hasVarInEnv (env , "workflow.status" )) ||
49- (hasWorkflowFailures (unmarshalledExpression ) && ! hasVarInEnv (env , "workflow.failures" ))) &&
50- allowUnresolved {
56+ varNameNotInEnv := anyVarNotInEnv (unmarshalledExpression , env )
57+ if varNameNotInEnv != nil && allowUnresolved {
58+ // this is to make sure expressions don't get resolved to nil or an empty string when certain variables
59+ // don't exist in the env during the "global" replacement.
60+ // See https://github.com/argoproj/argo-workflows/issues/5388, https://github.com/argoproj/argo-workflows/issues/15008,
61+ // https://github.com/argoproj/argo-workflows/issues/10393, https://github.com/expr-lang/expr/issues/330
62+ log .WithField ("variable" , * varNameNotInEnv ).Debug ("variable not in env but unresolved is allowed" )
5163 return fmt .Fprintf (w , "{{%s%s}}" , kindExpression , expression )
5264 }
5365
@@ -100,56 +112,55 @@ func EnvMap(replaceMap map[string]string) map[string]interface{} {
100112 return envMap
101113}
102114
103- // hasRetries checks if the variable `retries` exists in the expression template
104- func hasRetries (expression string ) bool {
105- tokens , err := lexer .Lex (file .NewSource (expression ))
106- if err != nil {
115+ func searchTokens (haystack []lexer.Token , needle []lexer.Token ) bool {
116+ if len (needle ) > len (haystack ) {
107117 return false
108118 }
109- for _ , token := range tokens {
110- if token .Kind == lexer .Identifier && token .Value == "retries" {
111- return true
119+ if len (needle ) == 0 {
120+ return true
121+ }
122+ outer:
123+ for i := 0 ; i <= len (haystack )- len (needle ); i ++ {
124+ for j := 0 ; j < len (needle ); j ++ {
125+ if haystack [i + j ].String () != needle [j ].String () {
126+ continue outer
127+ }
112128 }
129+ return true
113130 }
114131 return false
115132}
116133
117- // hasWorkflowStatus checks if expression contains `workflow.status`
118- func hasWorkflowStatus (expression string ) bool {
119- if ! strings .Contains (expression , "workflow.status" ) {
120- return false
121- }
122- // Even if the expression contains `workflow.status`, it could be the case that it represents a string (`"workflow.status"`),
123- // not a variable, so we need to parse it and handle filter the string case.
124- tokens , err := lexer .Lex (file .NewSource (expression ))
125- if err != nil {
126- return false
127- }
128- for i := 0 ; i < len (tokens )- 2 ; i ++ {
129- if tokens [i ].Value + tokens [i + 1 ].Value + tokens [i + 2 ].Value == "workflow.status" {
130- return true
134+ func filterEOF (toks []lexer.Token ) []lexer.Token {
135+ newToks := []lexer.Token {}
136+ for _ , tok := range toks {
137+ if tok .Kind != lexer .EOF {
138+ newToks = append (newToks , tok )
131139 }
132140 }
133- return false
141+ return newToks
134142}
135143
136- // hasWorkflowFailures checks if expression contains `workflow.failures`
137- func hasWorkflowFailures (expression string ) bool {
138- if ! strings .Contains (expression , "workflow.failures" ) {
144+ // hasVariableInExpression checks if an expression contains a variable.
145+ // This function is somewhat cursed and I have attempted my best to
146+ // remove this curse, but it still exists.
147+ // The strings.Contains is needed because the lexer doesn't do
148+ // any whitespace processing (workflow .status will be seen as workflow.status)
149+ func hasVariableInExpression (expression , variable string ) bool {
150+ if ! strings .Contains (expression , variable ) {
139151 return false
140152 }
141- // Even if the expression contains `workflow.failures`, it could be the case that it represents a string (`"workflow.failures"`),
142- // not a variable, so we need to parse it and handle filter the string case.
143153 tokens , err := lexer .Lex (file .NewSource (expression ))
144154 if err != nil {
145155 return false
146156 }
147- for i := 0 ; i < len (tokens )- 2 ; i ++ {
148- if tokens [i ].Value + tokens [i + 1 ].Value + tokens [i + 2 ].Value == "workflow.failures" {
149- return true
150- }
157+ variableTokens , err := lexer .Lex (file .NewSource (variable ))
158+ if err != nil {
159+ return false
151160 }
152- return false
161+ variableTokens = filterEOF (variableTokens )
162+
163+ return searchTokens (tokens , variableTokens )
153164}
154165
155166// hasVarInEnv checks if a parameter is in env or not
0 commit comments