77 "github.com/loft-sh/devspace/pkg/devspace/config/loader/variable/runtime"
88 "github.com/loft-sh/devspace/pkg/devspace/config/localcache"
99 "github.com/loft-sh/devspace/pkg/devspace/config/remotecache"
10+ "github.com/loft-sh/devspace/pkg/devspace/dependency/graph"
1011 "path/filepath"
1112 "regexp"
1213 "strings"
@@ -21,7 +22,7 @@ import (
2122var AlwaysResolvePredefinedVars = []string {"devspace.version" , "devspace.random" , "devspace.profile" , "devspace.userHome" , "devspace.timestamp" , "devspace.context" , "devspace.namespace" }
2223
2324// NewResolver creates a new resolver that caches resolved variables in memory and in the provided cache
24- func NewResolver (localCache localcache.Cache , remoteCache remotecache.Cache , predefinedVariableOptions * PredefinedVariableOptions , vars [ ]* latest.Variable , log log.Logger ) Resolver {
25+ func NewResolver (localCache localcache.Cache , remoteCache remotecache.Cache , predefinedVariableOptions * PredefinedVariableOptions , vars map [ string ]* latest.Variable , log log.Logger ) Resolver {
2526 return & resolver {
2627 memoryCache : map [string ]interface {}{},
2728 localCache : localCache ,
@@ -33,7 +34,7 @@ func NewResolver(localCache localcache.Cache, remoteCache remotecache.Cache, pre
3334}
3435
3536type resolver struct {
36- vars [ ]* latest.Variable
37+ vars map [ string ]* latest.Variable
3738 memoryCache map [string ]interface {}
3839 localCache localcache.Cache
3940 remoteCache remotecache.Cache
@@ -45,11 +46,11 @@ func varMatchFn(key, value string) bool {
4546 return varspkg .VarMatchRegex .MatchString (value )
4647}
4748
48- func (r * resolver ) DefinedVars () [ ]* latest.Variable {
49+ func (r * resolver ) DefinedVars () map [ string ]* latest.Variable {
4950 return r .vars
5051}
5152
52- func (r * resolver ) UpdateVars (vars [ ]* latest.Variable ) {
53+ func (r * resolver ) UpdateVars (vars map [ string ]* latest.Variable ) {
5354 r .vars = vars
5455}
5556
@@ -96,10 +97,9 @@ func (r *resolver) replaceString(ctx context.Context, str string) (interface{},
9697 })
9798}
9899
99- func (r * resolver ) FindVariables (haystack interface {}) (map [ string ] bool , error ) {
100+ func (r * resolver ) FindVariables (haystack interface {}) ([] * latest. Variable , error ) {
100101 // find out what vars are really used
101102 varsUsed := map [string ]bool {}
102-
103103 switch t := haystack .(type ) {
104104 case string :
105105 _ , _ = varspkg .ParseString (t , func (v string ) (interface {}, error ) {
@@ -120,22 +120,90 @@ func (r *resolver) FindVariables(haystack interface{}) (map[string]bool, error)
120120 }
121121 }
122122
123- // find out what vars are used within other vars definition
123+ // add always resolve variables
124124 for _ , v := range r .vars {
125- varsUsedInDefinition := r .findVariablesInDefinition (v )
126- for usedVar := range varsUsedInDefinition {
127- varsUsed [usedVar ] = true
125+ if v .AlwaysResolve {
126+ varsUsed [v .Name ] = true
128127 }
129128 }
130129
131130 // filter out runtime environment variables
132131 for k := range varsUsed {
133- if strings .HasPrefix (k , "runtime." ) {
132+ if r . vars [ k ] == nil || IsPredefinedVariable ( k ) || strings .HasPrefix (k , "runtime." ) {
134133 delete (varsUsed , k )
135134 }
136135 }
137136
138- return varsUsed , nil
137+ return r .orderVariables (varsUsed )
138+ }
139+
140+ func (r * resolver ) orderVariables (vars map [string ]bool ) ([]* latest.Variable , error ) {
141+ root := graph .NewNode ("root" , nil )
142+ g := graph .NewGraphOf (root , "variable" )
143+ for name := range vars {
144+ // check if has definition
145+ definition , ok := r .vars [name ]
146+ if ! ok {
147+ continue
148+ }
149+
150+ err := r .insertVariableGraph (g , definition )
151+ if err != nil {
152+ return nil , err
153+ }
154+ }
155+
156+ // now get all the leaf nodes
157+ retVars := []* latest.Variable {}
158+ for {
159+ nextLeaf := g .GetNextLeaf (root )
160+ if nextLeaf == root {
161+ break
162+ }
163+
164+ retVars = append (retVars , nextLeaf .Data .(* latest.Variable ))
165+ err := g .RemoveNode (nextLeaf .ID )
166+ if err != nil {
167+ return nil , err
168+ }
169+ }
170+
171+ // reverse the slice
172+ for i , j := 0 , len (retVars )- 1 ; i < j ; i , j = i + 1 , j - 1 {
173+ retVars [i ], retVars [j ] = retVars [j ], retVars [i ]
174+ }
175+ return retVars , nil
176+ }
177+
178+ func (r * resolver ) insertVariableGraph (g * graph.Graph , node * latest.Variable ) error {
179+ if _ , ok := g .Nodes [node .Name ]; ! ok {
180+ _ , err := g .InsertNodeAt ("root" , node .Name , node )
181+ if err != nil {
182+ return err
183+ }
184+ }
185+
186+ parents := r .findVariablesInDefinition (node )
187+ for parent := range parents {
188+ parentDefinition , ok := r .vars [parent ]
189+ if ! ok {
190+ continue
191+ }
192+
193+ if _ , ok := g .Nodes [parentDefinition .Name ]; ! ok {
194+ err := r .insertVariableGraph (g , parentDefinition )
195+ if err != nil {
196+ return err
197+ }
198+ }
199+
200+ err := g .AddEdge (parent , node .Name )
201+ if err != nil {
202+ return err
203+ }
204+ }
205+
206+ return nil
139207}
140208
141209func (r * resolver ) FillVariablesExclude (ctx context.Context , haystack interface {}, excludedPaths []string ) (interface {}, error ) {
@@ -178,19 +246,6 @@ func (r *resolver) findAndFillVariables(ctx context.Context, haystack interface{
178246 return nil , err
179247 }
180248
181- // resolve used defined variables
182- for _ , v := range r .vars {
183- if v .AlwaysResolve || varsUsed [strings .TrimSpace (v .Name )] {
184- name := strings .TrimSpace (v .Name )
185-
186- // resolve the variable with definition
187- _ , err := r .resolve (ctx , name , v )
188- if err != nil {
189- return nil , err
190- }
191- }
192- }
193-
194249 // try resolving predefined variables
195250 for _ , name := range AlwaysResolvePredefinedVars {
196251 // ignore errors here as those variables are probably not used anyways
@@ -200,6 +255,14 @@ func (r *resolver) findAndFillVariables(ctx context.Context, haystack interface{
200255 }
201256 }
202257
258+ // resolve used defined variables
259+ for _ , v := range varsUsed {
260+ _ , err := r .resolve (ctx , v .Name , v )
261+ if err != nil {
262+ return nil , err
263+ }
264+ }
265+
203266 return r .fillVariables (ctx , haystack , exclude )
204267}
205268
@@ -316,6 +379,13 @@ func (r *resolver) findVariablesInDefinition(definition *latest.Variable) map[st
316379 }
317380 }
318381
382+ // filter out runtime environment variables and non existing ones
383+ for k := range varsUsed {
384+ if r .vars [k ] == nil || IsPredefinedVariable (k ) || strings .HasPrefix (k , "runtime." ) {
385+ delete (varsUsed , k )
386+ }
387+ }
388+
319389 return varsUsed
320390}
321391
@@ -393,6 +463,10 @@ func (r *resolver) resolveDefinitionString(ctx context.Context, str string, defi
393463 // check if its a predefined variable
394464 variable , err := NewPredefinedVariable (varName , r .options )
395465 if err != nil {
466+ if r .vars [varName ] == nil {
467+ return "${" + varName + "}" , nil
468+ }
469+
396470 return nil , errors .Errorf ("variable '%s' was not resolved yet, however is used in the definition of variable '%s' as '%s'. Please make sure you define '%s' before '%s' in the vars array" , varName , definition .Name , str , varName , definition .Name )
397471 }
398472
0 commit comments