88 "reflect"
99 "slices"
1010
11+ "github.com/sirupsen/logrus"
1112 "gitlab.com/gitlab-org/moa"
1213 "gitlab.com/gitlab-org/moa/ast"
1314 "gitlab.com/gitlab-org/moa/value"
@@ -18,6 +19,7 @@ type Inputs struct {
1819 inputs []expression.Input
1920 evaluator * expression.Evaluator
2021 metricsCollector * JobInputsMetricsCollector
22+ logger * logrus.Entry
2123}
2224
2325type JobInput struct {
7880 ErrSensitiveUnsupported = errors .New ("sensitive inputs are unsupported in interpolations yet" )
7981 // errInterpolationFound defines a sentinel error for when an interpolation was detected
8082 errInterpolationFound = errors .New ("interpolation found" )
83+ // errJobInputAccessFound defines a sentinel error for when a job input access pattern is detected
84+ errJobInputAccessFound = errors .New ("job input access found" )
8185)
8286
8387// interpolationDetector is a visitor that detects if the AST contains an Interpolation
@@ -95,6 +99,56 @@ func (v *interpolationDetector) Exit(expr ast.Expr) (ast.Expr, error) {
9599 return expr , nil
96100}
97101
102+ // jobInputDetector is a visitor that detects if the AST contains access to job inputs.
103+ // It looks for the pattern job.inputs.<key> in the expression tree, where <key> is
104+ // accessed via static property access (dot notation).
105+ //
106+ // This detector only finds static access patterns like:
107+ // - job.inputs.username
108+ // - job.inputs.foo.bar (nested access on an input)
109+ // - str(job.inputs.age) (input used as function argument)
110+ //
111+ // It does NOT detect dynamic access patterns like:
112+ // - job.inputs[key] (bracket notation with variable)
113+ // - job["inputs"].foo (bracket notation for "inputs")
114+ // - job["in" + "puts"].foo (computed property access)
115+ //
116+ // The visitor returns a sentinel error as soon as it encounters the first match,
117+ // exiting traversal early.
118+ type jobInputDetector struct {}
119+
120+ func (v * jobInputDetector ) Enter (expr ast.Expr ) (ast.Visitor , error ) {
121+ selector , ok := expr .(* ast.Selector )
122+ if ! ok {
123+ return v , nil
124+ }
125+
126+ fromSelector , ok := selector .From .(* ast.Selector )
127+ if ! ok {
128+ return v , nil
129+ }
130+
131+ ident , ok := fromSelector .From .(* ast.Ident )
132+ if ! ok || ident .Name != "job" {
133+ return v , nil
134+ }
135+
136+ lit , ok := fromSelector .Select .(* ast.Literal )
137+ if ! ok {
138+ return v , nil
139+ }
140+
141+ if lit .String () == "inputs" {
142+ return nil , errJobInputAccessFound
143+ }
144+
145+ return v , nil
146+ }
147+
148+ func (v * jobInputDetector ) Exit (expr ast.Expr ) (ast.Expr , error ) {
149+ return expr , nil
150+ }
151+
98152func (i * JobInput ) UnmarshalJSON (data []byte ) error {
99153 type alias JobInput
100154 if err := json .Unmarshal (data , (* alias )(i )); err != nil {
@@ -235,6 +289,11 @@ func (i *Inputs) SetMetricsCollector(collector *JobInputsMetricsCollector) {
235289 i .metricsCollector = collector
236290}
237291
292+ // SetLogger injects the logger
293+ func (i * Inputs ) SetLogger (logger * logrus.Entry ) {
294+ i .logger = logger
295+ }
296+
238297func (i * Inputs ) Expand (text string ) (string , error ) {
239298 if i == nil || i .evaluator == nil {
240299 return text , nil
@@ -246,6 +305,25 @@ func (i *Inputs) Expand(text string) (string, error) {
246305 return "" , & InputInterpolationError {err : err }
247306 }
248307
308+ // NOTE: check if we don't have any inputs defined to interpolate
309+ // We do this to avoid a breaking change when a user already uses
310+ // job input interpolation syntax (`${{ .. }}`) but doesn't actually
311+ // want to use them. This hides potential errors when a user forgets
312+ // to define inputs - but that's easier to debug and not a breaking
313+ // change once GitLab enables job inputs but rather at the point in
314+ // time when the user wants to use job inputs.
315+ // For context see:
316+ // https://gitlab.com/gitlab-org/step-runner/-/work_items/369
317+ if len (i .inputs ) == 0 {
318+ _ , walkErr := expr .Walk (& jobInputDetector {})
319+ if errors .Is (walkErr , errJobInputAccessFound ) {
320+ if i .logger != nil {
321+ i .logger .Warn ("job input interpolation syntax detected but no inputs are defined, therefore job interpolation is not performed" )
322+ }
323+ }
324+ return text , nil
325+ }
326+
249327 result , err := i .evaluator .Eval (text , expr )
250328 if err != nil {
251329 i .metricsCollector .recordEvalError ()
0 commit comments