@@ -26,19 +26,12 @@ var warnFileIsExperimental = sync.OnceFunc(func() {
26
26
// Embed will recursively resolve all "base" dependencies and update the
27
27
// template with the merged result. It also inlines all external provisioning
28
28
// and probe scripts.
29
- func (tmpl * Template ) Embed (ctx context.Context ) error {
29
+ func (tmpl * Template ) Embed (ctx context.Context , embedAll , defaultBase bool ) error {
30
30
if err := tmpl .UseAbsLocators (); err != nil {
31
31
return err
32
32
}
33
- return tmpl .EmbedImpl (ctx , true )
34
- }
35
-
36
- // EmbedImpl is called with defaultBase set to false during testing, so that
37
- // an existing $LIMA_HOME/_config/base.yaml doesn't interfere with expected
38
- // test results.
39
- func (tmpl * Template ) EmbedImpl (ctx context.Context , defaultBase bool ) error {
40
33
seen := make (map [string ]bool )
41
- err := tmpl .embed (ctx , defaultBase , seen )
34
+ err := tmpl .embedAllBases (ctx , embedAll , defaultBase , seen )
42
35
// additionalDisks, mounts, and networks may combine entries based on a shared key
43
36
// This must be done after **all** base templates have been merged, so that wildcard keys can match
44
37
// against all earlier list entries, and not just against the direct parent template.
@@ -48,47 +41,83 @@ func (tmpl *Template) EmbedImpl(ctx context.Context, defaultBase bool) error {
48
41
return tmpl .ClearOnError (err )
49
42
}
50
43
51
- func (tmpl * Template ) embed (ctx context.Context , defaultBase bool , seen map [string ]bool ) error {
52
- logrus .Debugf ("Embedding template %q" , tmpl .Locator )
53
- if seen [tmpl .Locator ] {
54
- logrus .Infof ("Template %q already included" , tmpl .Locator )
55
- return nil
56
- }
57
- seen [tmpl .Locator ] = true
58
-
59
- if err := tmpl .Unmarshal (); err != nil {
60
- return err
61
- }
62
- bases := tmpl .Config .Base
44
+ func (tmpl * Template ) embedAllBases (ctx context.Context , embedAll , defaultBase bool , seen map [string ]bool ) error {
45
+ logrus .Debugf ("Embedding templates into %q" , tmpl .Locator )
63
46
if defaultBase {
64
- // Prepend $LIMA_HOME/_config/base.yaml to bases list.
65
47
configDir , err := dirnames .LimaConfigDir ()
66
48
if err != nil {
67
49
return err
68
50
}
69
51
defaultBaseFilename := filepath .Join (configDir , filenames .Base )
70
52
if _ , err := os .Stat (defaultBaseFilename ); err == nil {
71
- bases = append ([]string {defaultBaseFilename }, bases ... )
53
+ // turn string into single element list
54
+ // empty concatenation works around bug https://github.com/mikefarah/yq/issues/2269
55
+ tmpl .expr .WriteString ("| ($a.base | select(type == \" !!str\" )) |= [\" \" + .]\n " )
56
+ tmpl .expr .WriteString ("| ($a.base | select(type == \" !!map\" )) |= [[] + .]\n " )
57
+ // prepend base template at the beginning of the list
58
+ tmpl .expr .WriteString (fmt .Sprintf ("| $a.base = [%q, $a.base[]]\n " , defaultBaseFilename ))
59
+ if err := tmpl .evalExpr (); err != nil {
60
+ return err
61
+ }
72
62
}
73
63
}
74
- for _ , baseLocator := range bases {
75
- warnBaseIsExperimental ()
76
- logrus .Debugf ("Merging base template %q" , baseLocator )
77
- base , err := Read (ctx , "" , baseLocator )
78
- if err != nil {
64
+ for {
65
+ if err := tmpl .Unmarshal (); err != nil {
79
66
return err
80
67
}
81
- if err := base . UseAbsLocators (); err != nil {
82
- return err
68
+ if len ( tmpl . Config . Base ) == 0 {
69
+ break
83
70
}
84
- if err := base .embed (ctx , false , seen ); err != nil {
85
- return err
71
+ baseLocator := tmpl .Config .Base [0 ]
72
+ isTemplate , _ := SeemsTemplateURL (baseLocator )
73
+ if isTemplate && ! embedAll {
74
+ // Once we skip a template:// URL we can no longer embed any other base template
75
+ for i := 1 ; i < len (tmpl .Config .Base ); i ++ {
76
+ isTemplate , _ = SeemsTemplateURL (tmpl .Config .Base [i ])
77
+ if ! isTemplate {
78
+ return fmt .Errorf ("cannot embed template %q after not embedding %q" , tmpl .Config .Base [i ], baseLocator )
79
+ }
80
+ }
81
+ break
82
+ // TODO should we track embedding of template:// URLs so we can warn if we embed a non-template:// URL afterwards?
83
+ }
84
+
85
+ if seen [baseLocator ] {
86
+ return fmt .Errorf ("base template loop detected: template %q already included" , baseLocator )
86
87
}
87
- if err := tmpl .merge (base ); err != nil {
88
+ seen [baseLocator ] = true
89
+
90
+ // remove base[0] from template before merging
91
+ if err := tmpl .embedBase (ctx , baseLocator , embedAll , seen ); err != nil {
88
92
return err
89
93
}
90
94
}
91
- if err := tmpl .embedAllScripts (ctx ); err != nil {
95
+ if err := tmpl .embedAllScripts (ctx , embedAll ); err != nil {
96
+ return err
97
+ }
98
+ if len (tmpl .Bytes ) > yBytesLimit {
99
+ return fmt .Errorf ("template %q embedding exceeded the size limit (%d bytes)" , tmpl .Locator , yBytesLimit )
100
+ }
101
+ return nil
102
+ }
103
+
104
+ func (tmpl * Template ) embedBase (ctx context.Context , baseLocator string , embedAll bool , seen map [string ]bool ) error {
105
+ warnBaseIsExperimental ()
106
+ logrus .Debugf ("Embedding base %q in template %q" , baseLocator , tmpl .Locator )
107
+ if err := tmpl .Unmarshal (); err != nil {
108
+ return err
109
+ }
110
+ base , err := Read (ctx , "" , baseLocator )
111
+ if err != nil {
112
+ return err
113
+ }
114
+ if err := base .UseAbsLocators (); err != nil {
115
+ return err
116
+ }
117
+ if err := base .embedAllBases (ctx , embedAll , false , seen ); err != nil {
118
+ return err
119
+ }
120
+ if err := tmpl .merge (base ); err != nil {
92
121
return err
93
122
}
94
123
if len (tmpl .Bytes ) > yBytesLimit {
@@ -171,12 +200,21 @@ const mergeDocuments = `
171
200
# $c will be mutilated to implement our own "merge only new fields" logic.
172
201
| $b as $c
173
202
203
+ # Delete the base that is being merged right now
204
+ | $a | select(.base | tag == "!!seq") | del(.base[0])
205
+ | $a | select(.base | (tag == "!!seq" and length == 0)) | del(.base)
206
+ | $a | select(.base | tag == "!!str") | del(.base)
207
+
208
+ # If $a.base is a list, then $b.base must be a list as well
209
+ # (note $b, not $c, because we merge lists from $b)
210
+ | $b | select((.base | tag == "!!str") and ($a.base | tag == "!!seq")) | .base = [ "" + .base ]
211
+
174
212
# Delete base DNS entries if the template list is not empty.
175
213
| $a | select(.dns) | del($b.dns, $c.dns)
176
214
177
215
# Mark all new list fields with a custom tag. This is needed to avoid appending
178
216
# newly copied lists to themselves again when we merge lists.
179
- | ( $c | .. | select(tag == "!!seq") | tag) = "!!tag"
217
+ | $c | .. | select(tag == "!!seq") tag = "!!tag"
180
218
181
219
# Delete all nodes in $c that are in $a and not a map. This is necessary because
182
220
# the yq "*n" operator (merge only new fields) does not copy all comments across.
@@ -188,10 +226,10 @@ const mergeDocuments = `
188
226
# Find all elements that are existing lists. This will not match newly
189
227
# copied lists because they have a custom !!tag instead of !!seq.
190
228
# Append the elements from the same path in $b.
191
- # Exception: provision scripts and probes are prepended instead.
192
- | $a | (.. | select(tag == "!!seq" and (path[0] | test("^(provision|probes)$") | not))) |=
229
+ # Exception: base templates, provision scripts and probes are prepended instead.
230
+ | $a | (.. | select(tag == "!!seq" and (path[0] | test("^(base| provision|probes)$") | not))) |=
193
231
(. + (path[] as $p ireduce ($b; .[$p])))
194
- | $a | (.. | select(tag == "!!seq" and (path[0] | test("^(provision|probes)$")))) |=
232
+ | $a | (.. | select(tag == "!!seq" and (path[0] | test("^(base| provision|probes)$")))) |=
195
233
((path[] as $p ireduce ($b; .[$p])) + .)
196
234
197
235
# Copy head and line comments for existing lists that do not already have comments.
@@ -204,8 +242,6 @@ const mergeDocuments = `
204
242
# Make sure mountTypesUnsupported elements are unique.
205
243
| $a | (select(.mountTypesUnsupported) | .mountTypesUnsupported) |= unique
206
244
207
- | del($a.base)
208
-
209
245
# Remove the custom tags again so they do not clutter up the YAML output.
210
246
| $a | .. tag = ""
211
247
`
@@ -513,29 +549,35 @@ func (tmpl *Template) updateScript(field string, idx int, script string) {
513
549
}
514
550
515
551
// embedAllScripts replaces all "provision" and "probes" file references with the actual script.
516
- func (tmpl * Template ) embedAllScripts (ctx context.Context ) error {
552
+ func (tmpl * Template ) embedAllScripts (ctx context.Context , embedAll bool ) error {
517
553
if err := tmpl .Unmarshal (); err != nil {
518
554
return err
519
555
}
520
556
for i , p := range tmpl .Config .Probes {
521
- // Don't overwrite existing file . This should throw an error during validation.
557
+ // Don't overwrite existing script . This should throw an error during validation.
522
558
if p .File != nil && p .Script == "" {
523
559
warnFileIsExperimental ()
524
- scriptTmpl , err := Read (ctx , "" , * p .File )
525
- if err != nil {
526
- return err
560
+ isTemplate , _ := SeemsTemplateURL (* p .File )
561
+ if embedAll || ! isTemplate {
562
+ scriptTmpl , err := Read (ctx , "" , * p .File )
563
+ if err != nil {
564
+ return err
565
+ }
566
+ tmpl .updateScript ("probes" , i , string (scriptTmpl .Bytes ))
527
567
}
528
- tmpl .updateScript ("probes" , i , string (scriptTmpl .Bytes ))
529
568
}
530
569
}
531
570
for i , p := range tmpl .Config .Provision {
532
571
if p .File != nil && p .Script == "" {
533
572
warnFileIsExperimental ()
534
- scriptTmpl , err := Read (ctx , "" , * p .File )
535
- if err != nil {
536
- return err
573
+ isTemplate , _ := SeemsTemplateURL (* p .File )
574
+ if embedAll || ! isTemplate {
575
+ scriptTmpl , err := Read (ctx , "" , * p .File )
576
+ if err != nil {
577
+ return err
578
+ }
579
+ tmpl .updateScript ("provision" , i , string (scriptTmpl .Bytes ))
537
580
}
538
- tmpl .updateScript ("provision" , i , string (scriptTmpl .Bytes ))
539
581
}
540
582
}
541
583
return tmpl .evalExpr ()
0 commit comments