Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/cmd/limactl/template.go
2609 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package main
5
6
import (
7
"context"
8
"errors"
9
"fmt"
10
"os"
11
"path/filepath"
12
13
"github.com/sirupsen/logrus"
14
"github.com/spf13/cobra"
15
16
"github.com/lima-vm/lima/v2/pkg/driverutil"
17
"github.com/lima-vm/lima/v2/pkg/limatmpl"
18
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
19
"github.com/lima-vm/lima/v2/pkg/limayaml"
20
"github.com/lima-vm/lima/v2/pkg/uiutil"
21
"github.com/lima-vm/lima/v2/pkg/yqutil"
22
)
23
24
func newTemplateCommand() *cobra.Command {
25
templateCommand := &cobra.Command{
26
Use: "template",
27
Aliases: []string{"tmpl"},
28
Short: "Lima template management (EXPERIMENTAL)",
29
SilenceUsage: true,
30
SilenceErrors: true,
31
GroupID: advancedCommand,
32
PreRun: func(*cobra.Command, []string) {
33
logrus.Warn("`limactl template` is experimental")
34
},
35
}
36
templateCommand.AddCommand(
37
newTemplateCopyCommand(),
38
newTemplateValidateCommand(),
39
newTemplateYQCommand(),
40
newTemplateURLCommand(),
41
)
42
return templateCommand
43
}
44
45
// The validate command exists for backwards compatibility, and because the template command is still hidden.
46
func newValidateCommand() *cobra.Command {
47
validateCommand := newTemplateValidateCommand()
48
validateCommand.GroupID = advancedCommand
49
return validateCommand
50
}
51
52
var templateCopyExample = ` Template locators are local files, file://, https://, or template: URLs
53
54
# Copy default template to STDOUT
55
limactl template copy template:default -
56
57
# Copy template from web location to local file and embed all external references
58
# (this does not embed template: references)
59
limactl template copy --embed https://example.com/lima.yaml mighty-machine.yaml
60
`
61
62
func newTemplateCopyCommand() *cobra.Command {
63
templateCopyCommand := &cobra.Command{
64
Use: "copy [OPTIONS] TEMPLATE DEST",
65
Short: "Copy template",
66
Long: "Copy a template via locator to a local file",
67
Example: templateCopyExample,
68
Args: WrapArgsError(cobra.ExactArgs(2)),
69
RunE: templateCopyAction,
70
}
71
templateCopyCommand.Flags().Bool("embed", false, "Embed external dependencies into template")
72
templateCopyCommand.Flags().Bool("embed-all", false, "Embed all dependencies into template")
73
templateCopyCommand.Flags().Bool("fill", false, "Fill defaults")
74
templateCopyCommand.Flags().Bool("verbatim", false, "Don't make locators absolute")
75
return templateCopyCommand
76
}
77
78
func fillDefaults(ctx context.Context, tmpl *limatmpl.Template) error {
79
limaDir, err := dirnames.LimaDir()
80
if err != nil {
81
return err
82
}
83
// Load() will merge the template with override.yaml and default.yaml via FillDefaults().
84
// FillDefaults() needs the potential instance directory to validate host templates using {{.Dir}}.
85
filePath := filepath.Join(limaDir, tmpl.Name+".yaml")
86
tmpl.Config, err = limayaml.Load(ctx, tmpl.Bytes, filePath)
87
if err == nil {
88
tmpl.Bytes, err = limayaml.Marshal(tmpl.Config, false)
89
}
90
if err := driverutil.ResolveVMType(ctx, tmpl.Config, filePath); err != nil {
91
logrus.Warnf("failed to resolve VM type for %q: %v", filePath, err)
92
return nil
93
}
94
return err
95
}
96
97
func templateCopyAction(cmd *cobra.Command, args []string) error {
98
ctx := cmd.Context()
99
source := args[0]
100
target := args[1]
101
embed, err := cmd.Flags().GetBool("embed")
102
if err != nil {
103
return err
104
}
105
embedAll, err := cmd.Flags().GetBool("embed-all")
106
if err != nil {
107
return err
108
}
109
fill, err := cmd.Flags().GetBool("fill")
110
if err != nil {
111
return err
112
}
113
verbatim, err := cmd.Flags().GetBool("verbatim")
114
if err != nil {
115
return err
116
}
117
if fill {
118
embedAll = true
119
}
120
if embedAll {
121
embed = true
122
}
123
if embed && verbatim {
124
return errors.New("--verbatim cannot be used with any of --embed, --embed-all, or --fill")
125
}
126
tmpl, err := limatmpl.Read(cmd.Context(), "", source)
127
if err != nil {
128
return err
129
}
130
if len(tmpl.Bytes) == 0 {
131
return fmt.Errorf("don't know how to interpret %q as a template locator", source)
132
}
133
if !verbatim {
134
if embed {
135
// Embed default base.yaml only when fill is true.
136
if err := tmpl.Embed(cmd.Context(), embedAll, fill); err != nil {
137
return err
138
}
139
} else {
140
if err := tmpl.UseAbsLocators(); err != nil {
141
return err
142
}
143
}
144
}
145
if fill {
146
if err := fillDefaults(ctx, tmpl); err != nil {
147
return err
148
}
149
}
150
if target == "-" && uiutil.OutputIsTTY(cmd.OutOrStdout()) {
151
// run the output through YQ to colorize it
152
out, err := yqutil.EvaluateExpressionPlain(".", string(tmpl.Bytes), true)
153
if err == nil {
154
_, err = fmt.Fprint(cmd.OutOrStdout(), out)
155
}
156
return err
157
}
158
159
writer := cmd.OutOrStdout()
160
if target != "-" {
161
file, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
162
if err != nil {
163
return err
164
}
165
defer file.Close()
166
writer = file
167
}
168
_, err = fmt.Fprint(writer, string(tmpl.Bytes))
169
return err
170
}
171
172
const templateYQHelp = `Use the builtin YQ evaluator to extract information from a template.
173
External references are embedded and default values are filled in
174
before the YQ expression is evaluated.
175
176
Example:
177
limactl template yq template:default '.images[].location'
178
179
The example command is equivalent to using an external yq command like this:
180
limactl template copy --fill template:default - | yq '.images[].location'
181
`
182
183
func newTemplateYQCommand() *cobra.Command {
184
templateYQCommand := &cobra.Command{
185
Use: "yq TEMPLATE EXPR",
186
Short: "Query template expressions",
187
Long: templateYQHelp,
188
Args: WrapArgsError(cobra.ExactArgs(2)),
189
RunE: templateYQAction,
190
}
191
return templateYQCommand
192
}
193
194
func templateYQAction(cmd *cobra.Command, args []string) error {
195
ctx := cmd.Context()
196
locator := args[0]
197
expr := args[1]
198
tmpl, err := limatmpl.Read(cmd.Context(), "", locator)
199
if err != nil {
200
return err
201
}
202
if len(tmpl.Bytes) == 0 {
203
return fmt.Errorf("don't know how to interpret %q as a template locator", locator)
204
}
205
if err := tmpl.Embed(cmd.Context(), true, true); err != nil {
206
return err
207
}
208
if err := fillDefaults(ctx, tmpl); err != nil {
209
return err
210
}
211
colorsEnabled := uiutil.OutputIsTTY(cmd.OutOrStdout())
212
out, err := yqutil.EvaluateExpressionPlain(expr, string(tmpl.Bytes), colorsEnabled)
213
if err == nil {
214
_, err = fmt.Fprint(cmd.OutOrStdout(), out)
215
}
216
return err
217
}
218
219
func newTemplateValidateCommand() *cobra.Command {
220
templateValidateCommand := &cobra.Command{
221
Use: "validate TEMPLATE [TEMPLATE, ...]",
222
Short: "Validate YAML templates",
223
Args: WrapArgsError(cobra.MinimumNArgs(1)),
224
RunE: templateValidateAction,
225
}
226
templateValidateCommand.Flags().Bool("fill", false, "Fill defaults")
227
return templateValidateCommand
228
}
229
230
func templateValidateAction(cmd *cobra.Command, args []string) error {
231
ctx := cmd.Context()
232
fill, err := cmd.Flags().GetBool("fill")
233
if err != nil {
234
return err
235
}
236
limaDir, err := dirnames.LimaDir()
237
if err != nil {
238
return err
239
}
240
241
for _, arg := range args {
242
tmpl, err := limatmpl.Read(cmd.Context(), "", arg)
243
if err != nil {
244
return err
245
}
246
if len(tmpl.Bytes) == 0 {
247
return fmt.Errorf("don't know how to interpret %q as a template locator", arg)
248
}
249
if tmpl.Name == "" {
250
return fmt.Errorf("can't determine instance name from template locator %q", arg)
251
}
252
// Embed default base.yaml only when fill is true.
253
if err := tmpl.Embed(cmd.Context(), true, fill); err != nil {
254
return err
255
}
256
// Load() will merge the template with override.yaml and default.yaml via FillDefaults().
257
// FillDefaults() needs the potential instance directory to validate host templates using {{.Dir}}.
258
filePath := filepath.Join(limaDir, tmpl.Name+".yaml")
259
y, err := limayaml.Load(ctx, tmpl.Bytes, filePath)
260
if err != nil {
261
return err
262
}
263
if err := driverutil.ResolveVMType(ctx, y, filePath); err != nil {
264
logrus.Warnf("failed to resolve VM type for %q: %v", filePath, err)
265
}
266
if err := limayaml.Validate(y, false); err != nil {
267
return fmt.Errorf("failed to validate YAML file %q: %w", arg, err)
268
}
269
logrus.Infof("%q: OK", arg)
270
if fill {
271
b, err := limayaml.Marshal(y, len(args) > 1)
272
if err != nil {
273
return fmt.Errorf("failed to marshal template %q again after filling defaults: %w", arg, err)
274
}
275
fmt.Fprint(cmd.OutOrStdout(), string(b))
276
}
277
}
278
279
return nil
280
}
281
282
func newTemplateURLCommand() *cobra.Command {
283
templateURLCommand := &cobra.Command{
284
Use: "url CUSTOM_URL",
285
Short: "Transform custom template URLs to regular file or https URLs",
286
Args: WrapArgsError(cobra.ExactArgs(1)),
287
RunE: templateURLAction,
288
}
289
return templateURLCommand
290
}
291
292
func templateURLAction(cmd *cobra.Command, args []string) error {
293
url, err := limatmpl.TransformCustomURL(cmd.Context(), args[0])
294
if err != nil {
295
return err
296
}
297
_, err = fmt.Fprintln(cmd.OutOrStdout(), url)
298
return err
299
}
300
301