Skip to content

Commit 790a899

Browse files
committed
refactor: make vars a map
1 parent 9d2548f commit 790a899

File tree

15 files changed

+215
-72
lines changed

15 files changed

+215
-72
lines changed

cmd/init.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,13 @@ func (cmd *InitCmd) Run(f factory.Factory) error {
277277

278278
if config.Images != nil && config.Images[imageName] != nil {
279279
// Move full image name to variables
280-
config.Vars = append(config.Vars, &latest.Variable{
280+
if config.Vars == nil {
281+
config.Vars = map[string]*latest.Variable{}
282+
}
283+
config.Vars[imageVarName] = &latest.Variable{
281284
Name: imageVarName,
282285
Value: config.Images[imageName].Image,
283-
})
286+
}
284287

285288
// Use variable in images section
286289
config.Images[imageName].Image = imageVar

cmd/set/var.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,19 @@ func (cmd *varCmd) RunSetVar(f factory.Factory, cobraCmd *cobra.Command, args []
8282
return errors.Errorf("Unexpected variable format. Expected key=value, got %s", v)
8383
} else if variable.IsPredefinedVariable(splitted[0]) {
8484
return errors.Errorf("cannot set predefined variable %s", splitted[0])
85-
} else if !variableParser.Used[splitted[0]] {
86-
allowedVarsArr := []string{}
87-
for k := range variableParser.Used {
88-
if variable.IsPredefinedVariable(k) {
89-
continue
90-
}
85+
}
9186

92-
allowedVarsArr = append(allowedVarsArr, k)
87+
found := false
88+
for _, u := range variableParser.Used {
89+
if u.Name == splitted[0] {
90+
found = true
91+
break
92+
}
93+
}
94+
if !found {
95+
allowedVarsArr := []string{}
96+
for _, v := range variableParser.Used {
97+
allowedVarsArr = append(allowedVarsArr, v.Name)
9398
}
9499

95100
return errors.Errorf("variable %s is not allowed. Allowed vars: %+v", splitted[0], allowedVarsArr)
@@ -105,7 +110,7 @@ func (cmd *varCmd) RunSetVar(f factory.Factory, cobraCmd *cobra.Command, args []
105110
}
106111

107112
// only overwrite it if the flag is true and value is not set yet
108-
_, found := c.LocalCache().GetVar(splitted[0])
113+
_, found = c.LocalCache().GetVar(splitted[0])
109114
if cmd.Overwrite || !found {
110115
c.LocalCache().SetVar(splitted[0], splitted[1])
111116
} else {
@@ -124,8 +129,8 @@ func (cmd *varCmd) RunSetVar(f factory.Factory, cobraCmd *cobra.Command, args []
124129
}
125130

126131
type variableParser struct {
127-
Definitions []*latest.Variable
128-
Used map[string]bool
132+
Definitions map[string]*latest.Variable
133+
Used []*latest.Variable
129134
}
130135

131136
func (v *variableParser) Parse(ctx context.Context, originalRawConfig map[string]interface{}, rawConfig map[string]interface{}, resolver variable.Resolver, log log.Logger) (*latest.Config, map[string]interface{}, error) {

e2e/tests/config/config.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1864,7 +1864,7 @@ var _ = DevSpaceDescribe("config", func() {
18641864
framework.ExpectEqual(deployment1.Name, "development1")
18651865
framework.ExpectEqual(deployment1.Helm.CleanupOnFail, false)
18661866
framework.ExpectEqual(deployment1.Helm.Timeout, "1000s")
1867-
gomega.Expect(*deployment1.Helm.ComponentChart).To(gomega.BeTrue())
1867+
gomega.Expect(deployment1.Helm).ToNot(gomega.BeNil())
18681868
})
18691869

18701870
ginkgo.It("should apply patch to some deployments using wildcard profile patches", func() {
@@ -1882,7 +1882,7 @@ var _ = DevSpaceDescribe("config", func() {
18821882
deployment1 := config.Config().Deployments["test"]
18831883
framework.ExpectEqual(deployment1.Name, "test")
18841884
gomega.Expect(deployment1.Kubectl).To(gomega.BeNil())
1885-
gomega.Expect(*deployment1.Helm.ComponentChart).To(gomega.BeTrue())
1885+
gomega.Expect(deployment1.Helm).ToNot(gomega.BeNil())
18861886

18871887
deployment2 := config.Config().Deployments["test2"]
18881888
framework.ExpectEqual(deployment2.Name, "test2")
@@ -1906,7 +1906,7 @@ var _ = DevSpaceDescribe("config", func() {
19061906
deployment1 := config.Config().Deployments["backend"]
19071907
framework.ExpectEqual(deployment1.Name, "backend")
19081908
gomega.Expect(deployment1.Kubectl).To(gomega.BeNil())
1909-
gomega.Expect(*deployment1.Helm.ComponentChart).To(gomega.BeTrue())
1909+
gomega.Expect(deployment1.Helm).ToNot(gomega.BeNil())
19101910
})
19111911

19121912
ginkgo.It("should apply patch even value is empty", func() {
@@ -1924,7 +1924,7 @@ var _ = DevSpaceDescribe("config", func() {
19241924
deployment1 := config.Config().Deployments["test-sigsegv"]
19251925
framework.ExpectEqual(deployment1.Name, "test-sigsegv")
19261926
gomega.Expect(deployment1.Kubectl).To(gomega.BeNil())
1927-
gomega.Expect(*deployment1.Helm.ComponentChart).To(gomega.BeTrue())
1927+
gomega.Expect(deployment1.Helm).ToNot(gomega.BeNil())
19281928
})
19291929

19301930
// regression test for issue: https://github.com/loft-sh/devspace/issues/1835
@@ -1953,6 +1953,6 @@ var _ = DevSpaceDescribe("config", func() {
19531953
framework.ExpectEqual(v["name"], "replace-0")
19541954

19551955
gomega.Expect(deployment.Kubectl).To(gomega.BeNil())
1956-
gomega.Expect(*deployment.Helm.ComponentChart).To(gomega.BeTrue())
1956+
gomega.Expect(deployment.Helm).ToNot(gomega.BeNil())
19571957
})
19581958
})

examples/pipelines/devspace.yaml

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
version: v2beta1
22
name: pipelines
33

4+
vars:
5+
MELLO: $(echo "TEST")
6+
DELLO: ${MELLO}-${CHELLO}
7+
HELLO: ${devspace.namespace}-${DELLO}
8+
49
pipelines:
510
dev:
611
steps:
712
- run: |-
8-
create_deployments test --set helm.componentChart=true \
9-
--set helm.values.containers[0].image=nginx
10-
11-
start_dev test --set imageSelector=nginx \
12-
--set logs.disabled=false \
13-
--set command[0]=sh \
14-
--set command[1]=-c \
15-
--set 'command[2]=while true; do sleep 2 && echo Hello; done' \
16-
--set sync[0].containerPath=/app \
17-
--set sync[0].onUpload.restartContainer=true
13+
echo ${HELLO}

pkg/devspace/config/loader/imports.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
)
1515

1616
var ImportSections = []string{
17+
"vars",
1718
"dev",
1819
"deployments",
1920
"images",

pkg/devspace/config/loader/loader.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ func (l *configLoader) applyProfiles(ctx context.Context, data map[string]interf
572572
return data, nil
573573
}
574574

575-
func (l *configLoader) newVariableResolver(localCache localcache.Cache, remoteCache remotecache.Cache, client kubectl.Client, options *ConfigOptions, vars []*latest.Variable, log log.Logger) variable.Resolver {
575+
func (l *configLoader) newVariableResolver(localCache localcache.Cache, remoteCache remotecache.Cache, client kubectl.Client, options *ConfigOptions, vars map[string]*latest.Variable, log log.Logger) variable.Resolver {
576576
return variable.NewResolver(localCache, remoteCache, &variable.PredefinedVariableOptions{
577577
ConfigPath: l.absConfigPath,
578578
KubeClient: client,

pkg/devspace/config/loader/validate.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,13 @@ func Validate(config *latest.Config) error {
8181
return nil
8282
}
8383

84-
func validateVars(vars []*latest.Variable) error {
84+
func validateVars(vars map[string]*latest.Variable) error {
8585
for i, v := range vars {
86-
if v.Name == "" {
86+
if i == "" {
8787
return fmt.Errorf("vars[*].name has to be specified")
8888
}
8989
if encoding.IsUnsafeUpperName(v.Name) {
90-
return fmt.Errorf("vars[%d].name %s has to match the following regex: %v", i, v.Name, encoding.UnsafeUpperNameRegEx.String())
91-
}
92-
93-
// make sure is unique
94-
for j, v2 := range vars {
95-
if i != j && v.Name == v2.Name {
96-
return fmt.Errorf("multiple definitions for variable %s found", v.Name)
97-
}
90+
return fmt.Errorf("vars[%s].name %s has to match the following regex: %v", i, v.Name, encoding.UnsafeUpperNameRegEx.String())
9891
}
9992
}
10093

pkg/devspace/config/loader/variable/resolver.go

Lines changed: 99 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
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 (
2122
var 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

3536
type 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

141209
func (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

pkg/devspace/config/loader/variable/types.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ type Resolver interface {
2727
ConvertFlags(flags []string) (map[string]interface{}, error)
2828

2929
// DefinedVars returns the defined variables
30-
DefinedVars() []*latest.Variable
30+
DefinedVars() map[string]*latest.Variable
3131

3232
// UpdateVars sets the defined variables to use in the resolver
33-
UpdateVars(vars []*latest.Variable)
33+
UpdateVars(vars map[string]*latest.Variable)
3434

3535
// FindVariables returns all variable names that were found in the given map
36-
FindVariables(haystack interface{}) (map[string]bool, error)
36+
FindVariables(haystack interface{}) ([]*latest.Variable, error)
3737

3838
// FillVariables finds the used variables first and then fills in those in the haystack
3939
FillVariables(ctx context.Context, haystack interface{}) (interface{}, error)

0 commit comments

Comments
 (0)