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