Skip to content

Commit ad2f434

Browse files
authored
Merge pull request #4873 from jandubois/limactl-param-flag
limactl: add --param shortcut for template parameters
2 parents 0fa14f2 + d7121f2 commit ad2f434

6 files changed

Lines changed: 149 additions & 6 deletions

File tree

cmd/limactl/clone.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func cloneOrRenameAction(cmd *cobra.Command, args []string) error {
7777
return err
7878
}
7979

80-
yqExprs, err := editflags.YQExpressions(flags, false)
80+
yqExprs, err := editflags.YQExpressions(flags, false, newInst.Config.Param)
8181
if err != nil {
8282
return err
8383
}

cmd/limactl/edit.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,15 @@ func editAction(cmd *cobra.Command, args []string) error {
8585
if err != nil {
8686
return err
8787
}
88-
yqExprs, err := editflags.YQExpressions(flags, false)
88+
var params map[string]string
89+
if flags.Changed("param") {
90+
var y limatype.LimaYAML
91+
if err := limayaml.Unmarshal(yContent, &y, filePath); err != nil {
92+
return err
93+
}
94+
params = y.Param
95+
}
96+
yqExprs, err := editflags.YQExpressions(flags, false, params)
8997
if err != nil {
9098
return err
9199
}

cmd/limactl/editflags/editflags.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func RegisterEdit(cmd *cobra.Command, commentPrefix string) {
6969
flags.Bool("rosetta", false, commentPrefix+"Enable Rosetta (for vz instances)")
7070

7171
flags.StringArray("set", []string{}, commentPrefix+"Modify the template inplace, using yq syntax. Can be passed multiple times.")
72+
flags.StringArray("param", []string{}, commentPrefix+"Set a template parameter, e.g. name=value. Can be passed multiple times.")
7273

7374
flags.Uint16("ssh-port", 0, commentPrefix+"SSH port (0 for random)") // colima-compatible
7475
_ = cmd.RegisterFlagCompletionFunc("ssh-port", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
@@ -169,6 +170,21 @@ func BuildPortForwardExpression(portForwards []string) (string, error) {
169170
return expr, nil
170171
}
171172

173+
func BuildParamExpressions(params []string, allowedParams map[string]string) ([]string, error) {
174+
exprs := make([]string, len(params))
175+
for i, param := range params {
176+
key, value, ok := strings.Cut(param, "=")
177+
if !ok {
178+
return nil, fmt.Errorf("invalid parameter %q, expected NAME=VALUE", param)
179+
}
180+
if _, ok := allowedParams[key]; !ok {
181+
return nil, fmt.Errorf("template does not define param %q", key)
182+
}
183+
exprs[i] = fmt.Sprintf(".param[%q] = %q", key, value)
184+
}
185+
return exprs, nil
186+
}
187+
172188
func buildMountListExpression(ss []string) (string, error) {
173189
mounts := make([]string, len(ss))
174190
for i, s := range ss {
@@ -184,7 +200,7 @@ func buildMountListExpression(ss []string) (string, error) {
184200
}
185201

186202
// YQExpressions returns YQ expressions.
187-
func YQExpressions(flags *flag.FlagSet, newInstance bool) ([]string, error) {
203+
func YQExpressions(flags *flag.FlagSet, newInstance bool, params map[string]string) ([]string, error) {
188204
type def struct {
189205
flagName string
190206
exprFunc func(*flag.Flag) ([]string, error)
@@ -335,6 +351,13 @@ func YQExpressions(flags *flag.FlagSet, newInstance bool) ([]string, error) {
335351
{"set", func(v *flag.Flag) ([]string, error) {
336352
return v.Value.(flag.SliceValue).GetSlice(), nil
337353
}, false, false},
354+
{"param", func(_ *flag.Flag) ([]string, error) {
355+
ss, err := flags.GetStringArray("param")
356+
if err != nil {
357+
return nil, err
358+
}
359+
return BuildParamExpressions(ss, params)
360+
}, false, false},
338361
{
339362
"video",
340363
func(_ *flag.Flag) ([]string, error) {

cmd/limactl/editflags/editflags_test.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,53 @@ func TestParsePortForward(t *testing.T) {
162162
}
163163
}
164164

165+
func TestBuildParamExpressions(t *testing.T) {
166+
tests := []struct {
167+
name string
168+
params []string
169+
allowedParams map[string]string
170+
expected []string
171+
expectError string
172+
}{
173+
{
174+
name: "single param",
175+
params: []string{"version=v1.35"},
176+
allowedParams: map[string]string{"version": ""},
177+
expected: []string{`.param["version"] = "v1.35"`},
178+
},
179+
{
180+
name: "value contains equals",
181+
params: []string{"token=a=b"},
182+
allowedParams: map[string]string{"token": ""},
183+
expected: []string{`.param["token"] = "a=b"`},
184+
},
185+
{
186+
name: "invalid format",
187+
params: []string{"version"},
188+
allowedParams: map[string]string{"version": ""},
189+
expectError: `invalid parameter "version", expected NAME=VALUE`,
190+
},
191+
{
192+
name: "undefined param",
193+
params: []string{"missing=value"},
194+
allowedParams: map[string]string{"version": ""},
195+
expectError: `template does not define param "missing"`,
196+
},
197+
}
198+
199+
for _, tt := range tests {
200+
t.Run(tt.name, func(t *testing.T) {
201+
result, err := BuildParamExpressions(tt.params, tt.allowedParams)
202+
if tt.expectError != "" {
203+
assert.ErrorContains(t, err, tt.expectError)
204+
} else {
205+
assert.NilError(t, err)
206+
assert.DeepEqual(t, tt.expected, result)
207+
}
208+
})
209+
}
210+
}
211+
165212
func TestYQExpressions(t *testing.T) {
166213
expand := func(s string) string {
167214
s, err := localpathutil.Expand(s)
@@ -225,6 +272,18 @@ func TestYQExpressions(t *testing.T) {
225272
newInstance: false,
226273
expected: []string{`.nestedVirtualization = true`},
227274
},
275+
{
276+
name: "param",
277+
args: []string{"--param", "version=v1.35"},
278+
newInstance: false,
279+
expected: []string{`.param["version"] = "v1.35"`},
280+
},
281+
{
282+
name: "undefined param",
283+
args: []string{"--param", "missing=value"},
284+
newInstance: false,
285+
expectError: `template does not define param "missing"`,
286+
},
228287
{
229288
name: "invalid network",
230289
args: []string{"--network", "invalid"},
@@ -237,7 +296,7 @@ func TestYQExpressions(t *testing.T) {
237296
cmd := &cobra.Command{}
238297
RegisterEdit(cmd, "")
239298
assert.NilError(t, cmd.ParseFlags(tt.args))
240-
expr, err := YQExpressions(cmd.Flags(), tt.newInstance)
299+
expr, err := YQExpressions(cmd.Flags(), tt.newInstance, map[string]string{"version": ""})
241300
if tt.expectError != "" {
242301
assert.ErrorContains(t, err, tt.expectError)
243302
} else {

cmd/limactl/start.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ func newCreateCommand() *cobra.Command {
5858
To create an instance "default" with yq expressions:
5959
$ limactl create --set='.cpus = 2 | .memory = "2GiB"'
6060
61+
To create an instance "default" with a template parameter:
62+
$ limactl create --name=default --param containerdSnapshotter=false template:docker
63+
6164
To see the template list:
6265
$ limactl create --list-templates
6366
@@ -282,7 +285,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (*
282285
return nil, fmt.Errorf("instance %q already exists", tmpl.Name)
283286
}
284287
logrus.Infof("Using the existing instance %q", tmpl.Name)
285-
yqExprs, err := editflags.YQExpressions(flags, false)
288+
yqExprs, err := editflags.YQExpressions(flags, false, inst.Config.Param)
286289
if err != nil {
287290
return nil, err
288291
}
@@ -331,7 +334,7 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string, createOnly bool) (*
331334
if tmpl.Config != nil && tmpl.Config.OS != nil && *tmpl.Config.OS != limatype.LINUX {
332335
logrus.Warn("Support for non-Linux guests is experimental")
333336
}
334-
yqExprs, err := editflags.YQExpressions(flags, true)
337+
yqExprs, err := editflags.YQExpressions(flags, true, tmpl.Config.Param)
335338
if err != nil {
336339
return nil, err
337340
}

hack/bats/tests/param.bats

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# SPDX-FileCopyrightText: Copyright The Lima Authors
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
load "../helpers/load"
5+
6+
LOCAL_LIMA_HOME="${LIMA_HOME:?}/_bats_param"
7+
8+
local_setup_file() {
9+
export LIMA_HOME="${LOCAL_LIMA_HOME:?}"
10+
rm -rf "${LOCAL_LIMA_HOME:?}"
11+
}
12+
13+
local_setup() {
14+
export LIMA_HOME="${LOCAL_LIMA_HOME:?}"
15+
}
16+
17+
param_template() {
18+
cat <<'EOF'
19+
images:
20+
- location: /etc/profile
21+
plain: true
22+
param:
23+
release: ""
24+
provision:
25+
- mode: system
26+
script: |
27+
echo "$PARAM_release"
28+
EOF
29+
}
30+
31+
@test 'create accepts --param shortcut' {
32+
run -0 limactl create --name param-create --param=release=v1.35 - <<<"$(param_template)"
33+
34+
run -0 limactl yq -r .param.release <"${LIMA_HOME}/param-create/lima.yaml"
35+
assert_output "v1.35"
36+
}
37+
38+
@test 'create rejects undefined --param' {
39+
run_e -1 limactl create --name param-create-invalid --param missing=value - <<<"$(param_template)"
40+
assert_fatal 'error while processing flag "param": template does not define param "missing"'
41+
}
42+
43+
@test 'edit accepts --param shortcut' {
44+
run -0 limactl create --name param-edit - <<<"$(param_template)"
45+
46+
run -0 limactl edit --param release=v1.36 param-edit
47+
48+
run -0 limactl yq -r .param.release <"${LIMA_HOME}/param-edit/lima.yaml"
49+
assert_output "v1.36"
50+
}

0 commit comments

Comments
 (0)