Skip to content

Commit 8bee02c

Browse files
committed
Add --embed-all option to limactl tmpl copy command
The default `copy --embed` option will no longer embed template:// urls. Signed-off-by: Jan Dubois <[email protected]>
1 parent d4879f6 commit 8bee02c

File tree

4 files changed

+169
-65
lines changed

4 files changed

+169
-65
lines changed

cmd/limactl/start.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (*
202202
return nil, err
203203
}
204204
}
205-
if err := tmpl.Embed(cmd.Context()); err != nil {
205+
if err := tmpl.Embed(cmd.Context(), true, true); err != nil {
206206
return nil, err
207207
}
208208
yqExprs, err := editflags.YQExpressions(flags, true)

cmd/limactl/template.go

+15-7
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ func newTemplateCopyCommand() *cobra.Command {
6060
Args: WrapArgsError(cobra.ExactArgs(2)),
6161
RunE: templateCopyAction,
6262
}
63-
templateCopyCommand.Flags().Bool("embed", false, "embed dependencies into template")
63+
templateCopyCommand.Flags().Bool("embed", false, "embed external dependencies into template")
64+
templateCopyCommand.Flags().Bool("embed-all", false, "embed all dependencies into template")
6465
templateCopyCommand.Flags().Bool("fill", false, "fill defaults")
6566
templateCopyCommand.Flags().Bool("verbatim", false, "don't make locators absolute")
6667
return templateCopyCommand
@@ -71,6 +72,10 @@ func templateCopyAction(cmd *cobra.Command, args []string) error {
7172
if err != nil {
7273
return err
7374
}
75+
embedAll, err := cmd.Flags().GetBool("embed-all")
76+
if err != nil {
77+
return err
78+
}
7479
fill, err := cmd.Flags().GetBool("fill")
7580
if err != nil {
7681
return err
@@ -79,13 +84,15 @@ func templateCopyAction(cmd *cobra.Command, args []string) error {
7984
if err != nil {
8085
return err
8186
}
82-
if embed && verbatim {
83-
return errors.New("--embed and --verbatim cannot be used together")
87+
if fill {
88+
embedAll = true
8489
}
85-
if fill && verbatim {
86-
return errors.New("--fill and --verbatim cannot be used together")
90+
if embedAll {
91+
embed = true
92+
}
93+
if embed && verbatim {
94+
return errors.New("--verbatim cannot be used with any of --embed, --embed-all, or --fill")
8795
}
88-
8996
tmpl, err := limatmpl.Read(cmd.Context(), "", args[0])
9097
if err != nil {
9198
return err
@@ -95,7 +102,8 @@ func templateCopyAction(cmd *cobra.Command, args []string) error {
95102
}
96103
if !verbatim {
97104
if embed {
98-
if err := tmpl.Embed(cmd.Context()); err != nil {
105+
// Embed default base.yaml only when fill is true.
106+
if err := tmpl.Embed(cmd.Context(), embedAll, fill); err != nil {
99107
return err
100108
}
101109
} else {

pkg/limatmpl/embed.go

+92-50
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,12 @@ var warnFileIsExperimental = sync.OnceFunc(func() {
2626
// Embed will recursively resolve all "base" dependencies and update the
2727
// template with the merged result. It also inlines all external provisioning
2828
// and probe scripts.
29-
func (tmpl *Template) Embed(ctx context.Context) error {
29+
func (tmpl *Template) Embed(ctx context.Context, embedAll, defaultBase bool) error {
3030
if err := tmpl.UseAbsLocators(); err != nil {
3131
return err
3232
}
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 {
4033
seen := make(map[string]bool)
41-
err := tmpl.embed(ctx, defaultBase, seen)
34+
err := tmpl.embedAllBases(ctx, embedAll, defaultBase, seen)
4235
// additionalDisks, mounts, and networks may combine entries based on a shared key
4336
// This must be done after **all** base templates have been merged, so that wildcard keys can match
4437
// 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 {
4841
return tmpl.ClearOnError(err)
4942
}
5043

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)
6346
if defaultBase {
64-
// Prepend $LIMA_HOME/_config/base.yaml to bases list.
6547
configDir, err := dirnames.LimaConfigDir()
6648
if err != nil {
6749
return err
6850
}
6951
defaultBaseFilename := filepath.Join(configDir, filenames.Base)
7052
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+
}
7262
}
7363
}
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 {
7966
return err
8067
}
81-
if err := base.UseAbsLocators(); err != nil {
82-
return err
68+
if len(tmpl.Config.Base) == 0 {
69+
break
8370
}
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)
8687
}
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 {
8892
return err
8993
}
9094
}
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 {
92121
return err
93122
}
94123
if len(tmpl.Bytes) > yBytesLimit {
@@ -171,12 +200,21 @@ const mergeDocuments = `
171200
# $c will be mutilated to implement our own "merge only new fields" logic.
172201
| $b as $c
173202
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+
174212
# Delete base DNS entries if the template list is not empty.
175213
| $a | select(.dns) | del($b.dns, $c.dns)
176214
177215
# Mark all new list fields with a custom tag. This is needed to avoid appending
178216
# newly copied lists to themselves again when we merge lists.
179-
| ($c | .. | select(tag == "!!seq") | tag) = "!!tag"
217+
| $c | .. | select(tag == "!!seq") tag = "!!tag"
180218
181219
# Delete all nodes in $c that are in $a and not a map. This is necessary because
182220
# the yq "*n" operator (merge only new fields) does not copy all comments across.
@@ -188,10 +226,10 @@ const mergeDocuments = `
188226
# Find all elements that are existing lists. This will not match newly
189227
# copied lists because they have a custom !!tag instead of !!seq.
190228
# 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))) |=
193231
(. + (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)$")))) |=
195233
((path[] as $p ireduce ($b; .[$p])) + .)
196234
197235
# Copy head and line comments for existing lists that do not already have comments.
@@ -204,8 +242,6 @@ const mergeDocuments = `
204242
# Make sure mountTypesUnsupported elements are unique.
205243
| $a | (select(.mountTypesUnsupported) | .mountTypesUnsupported) |= unique
206244
207-
| del($a.base)
208-
209245
# Remove the custom tags again so they do not clutter up the YAML output.
210246
| $a | .. tag = ""
211247
`
@@ -513,29 +549,35 @@ func (tmpl *Template) updateScript(field string, idx int, script string) {
513549
}
514550

515551
// 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 {
517553
if err := tmpl.Unmarshal(); err != nil {
518554
return err
519555
}
520556
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.
522558
if p.File != nil && p.Script == "" {
523559
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))
527567
}
528-
tmpl.updateScript("probes", i, string(scriptTmpl.Bytes))
529568
}
530569
}
531570
for i, p := range tmpl.Config.Provision {
532571
if p.File != nil && p.Script == "" {
533572
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))
537580
}
538-
tmpl.updateScript("provision", i, string(scriptTmpl.Bytes))
539581
}
540582
}
541583
return tmpl.evalExpr()

0 commit comments

Comments
 (0)