Skip to content

Commit 09b2a29

Browse files
committed
Add builtin yq support to lima start command
Allows modifying the template inplace for a single instance, if you don't want to change the default or override for all instances. Signed-off-by: Anders F Björklund <[email protected]>
1 parent 5491b37 commit 09b2a29

File tree

5 files changed

+150
-0
lines changed

5 files changed

+150
-0
lines changed

cmd/limactl/start.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/lima-vm/lima/pkg/store"
2222
"github.com/lima-vm/lima/pkg/store/filenames"
2323
"github.com/lima-vm/lima/pkg/templatestore"
24+
"github.com/lima-vm/lima/pkg/yqutil"
2425
"github.com/mattn/go-isatty"
2526
"github.com/sirupsen/logrus"
2627
"github.com/spf13/cobra"
@@ -36,6 +37,9 @@ $ limactl start
3637
To create an instance "default" from a template "docker":
3738
$ limactl start --name=default template://docker
3839
40+
To create an instance "default" with modified parameters:
41+
$ limactl start --yq '.cpus = 2 | .memory = "2GiB"'
42+
3943
To see the template list:
4044
$ limactl start --list-templates
4145
@@ -56,6 +60,7 @@ $ cat template.yaml | limactl start --name=local -
5660
// TODO: "survey" does not support using cygwin terminal on windows yet
5761
startCommand.Flags().Bool("tty", isatty.IsTerminal(os.Stdout.Fd()), "enable TUI interactions such as opening an editor, defaults to true when stdout is a terminal")
5862
startCommand.Flags().String("name", "", "override the instance name")
63+
startCommand.Flags().String("yq", "", "modify the template inplace")
5964
startCommand.Flags().Bool("list-templates", false, "list available templates and exit")
6065
startCommand.Flags().Duration("timeout", start.DefaultWatchHostAgentEventsTimeout, "duration to wait for the instance to be running before timing out")
6166
return startCommand
@@ -82,6 +87,10 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string) (*store.Instance, e
8287
if err != nil {
8388
return nil, err
8489
}
90+
st.yq, err = cmd.Flags().GetString("yq")
91+
if err != nil {
92+
return nil, err
93+
}
8594
const yBytesLimit = 4 * 1024 * 1024 // 4MiB
8695

8796
if ok, u := guessarg.SeemsTemplateURL(arg); ok {
@@ -206,6 +215,9 @@ func loadOrCreateInstance(cmd *cobra.Command, args []string) (*store.Instance, e
206215
}
207216
} else {
208217
logrus.Info("Terminal is not available, proceeding without opening an editor")
218+
if err := modifyInPlace(st); err != nil {
219+
return nil, err
220+
}
209221
}
210222
saveBrokenEditorBuffer := tty
211223
return createInstance(st, saveBrokenEditorBuffer)
@@ -261,10 +273,27 @@ func createInstance(st *creatorState, saveBrokenEditorBuffer bool) (*store.Insta
261273
type creatorState struct {
262274
instName string // instance name
263275
yBytes []byte // yaml bytes
276+
yq string // yq expression
277+
}
278+
279+
func modifyInPlace(st *creatorState) error {
280+
if st.yq == "" {
281+
return nil
282+
}
283+
out, err := yqutil.EvaluateExpression(st.yq, st.yBytes)
284+
if err != nil {
285+
return err
286+
}
287+
st.yBytes = out
288+
return nil
264289
}
265290

266291
func chooseNextCreatorState(st *creatorState) (*creatorState, error) {
267292
for {
293+
if err := modifyInPlace(st); err != nil {
294+
logrus.WithError(err).Warn("Failed to evaluate yq expression")
295+
return st, err
296+
}
268297
var ans string
269298
prompt := &survey.Select{
270299
Message: fmt.Sprintf("Creating an instance %q", st.instName),

go.mod

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ require (
2525
github.com/mattn/go-isatty v0.0.17
2626
github.com/mattn/go-shellwords v1.0.12
2727
github.com/miekg/dns v1.1.50
28+
github.com/mikefarah/yq/v4 v4.30.8
2829
github.com/norouter/norouter v0.6.3
2930
github.com/nxadm/tail v1.4.8
3031
github.com/opencontainers/go-digest v1.0.0
@@ -41,18 +42,25 @@ require (
4142
require (
4243
github.com/Microsoft/go-winio v0.5.2 // indirect
4344
github.com/VividCortex/ewma v1.1.1 // indirect
45+
github.com/a8m/envsubst v1.3.0 // indirect
46+
github.com/alecthomas/participle/v2 v2.0.0-beta.5 // indirect
4447
github.com/apparentlymart/go-cidr v1.1.0 // indirect
4548
github.com/digitalocean/go-libvirt v0.0.0-20201209184759-e2a69bcd5bd1 // indirect
49+
github.com/dimchansky/utfbom v1.1.1 // indirect
50+
github.com/elliotchance/orderedmap v1.5.0 // indirect
4651
github.com/fatih/color v1.13.0 // indirect
4752
github.com/fsnotify/fsnotify v1.5.1 // indirect
53+
github.com/goccy/go-json v0.10.0 // indirect
4854
github.com/golang/protobuf v1.5.2 // indirect
4955
github.com/google/btree v1.0.1 // indirect
5056
github.com/google/gopacket v1.1.19 // indirect
5157
github.com/hashicorp/errwrap v1.1.0 // indirect
5258
github.com/inconshreveable/mousetrap v1.0.1 // indirect
5359
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f // indirect
60+
github.com/jinzhu/copier v0.3.5 // indirect
5461
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
5562
github.com/kr/fs v0.1.0 // indirect
63+
github.com/magiconair/properties v1.8.7 // indirect
5664
github.com/mattn/go-colorable v0.1.12 // indirect
5765
github.com/mattn/go-runewidth v0.0.12 // indirect
5866
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
@@ -75,6 +83,8 @@ require (
7583
google.golang.org/grpc v1.47.0 // indirect
7684
google.golang.org/protobuf v1.28.0 // indirect
7785
gopkg.in/djherbis/times.v1 v1.2.0 // indirect
86+
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 // indirect
7887
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
88+
gopkg.in/yaml.v3 v3.0.1 // indirect
7989
gvisor.dev/gvisor v0.0.0-20220908032458-edc830a43ba6 // indirect
8090
)

go.sum

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
6262
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
6363
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
6464
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
65+
github.com/a8m/envsubst v1.3.0 h1:GmXKmVssap0YtlU3E230W98RWtWCyIZzjtf1apWWyAg=
66+
github.com/a8m/envsubst v1.3.0/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
67+
github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35a3WghArZ/Hbebrjo=
68+
github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
6569
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
6670
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
6771
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
@@ -160,6 +164,8 @@ github.com/digitalocean/go-libvirt v0.0.0-20201209184759-e2a69bcd5bd1 h1:j6vGfla
160164
github.com/digitalocean/go-libvirt v0.0.0-20201209184759-e2a69bcd5bd1/go.mod h1:QS1XzqZLcDniNYrN7EZefq3wIyb/M2WmJbql4ZKoc1Q=
161165
github.com/digitalocean/go-qemu v0.0.0-20210326154740-ac9e0b687001 h1:WAg57gnaAWWjMAELcwHjc2xy0PoXQ5G+vn3+XS6s1jI=
162166
github.com/digitalocean/go-qemu v0.0.0-20210326154740-ac9e0b687001/go.mod h1:IetBE52JfFxK46p2n2Rqm+p5Gx1gpu2hRHsrbnPOWZQ=
167+
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
168+
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
163169
github.com/diskfs/go-diskfs v1.3.0 h1:D3IVe1y7ybB5SjCO0pOmkWThL9lZEWeanp8rRa0q0sk=
164170
github.com/diskfs/go-diskfs v1.3.0/go.mod h1:3pUpCAz75Q11om5RsGpVKUgXp2Z+ATw1xV500glmCP0=
165171
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
@@ -177,6 +183,8 @@ github.com/elastic/go-libaudit/v2 v2.3.2/go.mod h1:+ZE0czqmbqtnRkl0fNgpI+HvVVRo/
177183
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
178184
github.com/elazarl/goproxy v0.0.0-20210110162100-a92cc753f88e/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
179185
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
186+
github.com/elliotchance/orderedmap v1.5.0 h1:1IsExUsjv5XNBD3ZdC7jkAAqLWOOKdbPTmkHx63OsBg=
187+
github.com/elliotchance/orderedmap v1.5.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
180188
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
181189
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
182190
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -225,6 +233,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
225233
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
226234
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
227235
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
236+
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
237+
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
228238
github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ=
229239
github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE=
230240
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@@ -348,6 +358,8 @@ github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7P
348358
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
349359
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f h1:l1QCwn715k8nYkj4Ql50rzEog3WnMdrd4YYMMwemxEo=
350360
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
361+
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
362+
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
351363
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
352364
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
353365
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
@@ -381,6 +393,8 @@ github.com/lima-vm/sshocker v0.3.1 h1:MxqWJVsNZ2AKw/mWJ3n2DBNxFABZpaR79olLIjAQK8
381393
github.com/lima-vm/sshocker v0.3.1/go.mod h1:Pv7KiykFCequiWTPMytCbAC92ZT2TSFPgmMqGgh3vm0=
382394
github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2/go.mod h1:SWzULI85WerrFt3u+nIm5F9l7EvxZTKQvd0InF3nmgM=
383395
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
396+
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
397+
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
384398
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
385399
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0=
386400
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -415,6 +429,8 @@ github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju
415429
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
416430
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
417431
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
432+
github.com/mikefarah/yq/v4 v4.30.8 h1:EHovseqMJs9kvE25/2k6VnDs4CrBZN+DFbybUhpPAGM=
433+
github.com/mikefarah/yq/v4 v4.30.8/go.mod h1:8D30GDxhu3+KXll0aFV5msGcdgYRZSPOPVBTbgUQ7Dc=
418434
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
419435
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
420436
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -1009,6 +1025,8 @@ gopkg.in/djherbis/times.v1 v1.2.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv
10091025
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
10101026
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
10111027
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
1028+
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
1029+
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
10121030
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
10131031
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
10141032
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=

pkg/yqutil/yqutil.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package yqutil
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os"
7+
8+
"github.com/mikefarah/yq/v4/pkg/yqlib"
9+
"github.com/sirupsen/logrus"
10+
logging "gopkg.in/op/go-logging.v1"
11+
)
12+
13+
// EvaluateExpression evaluates the yq expression, and returns the modified yaml.
14+
func EvaluateExpression(expression string, content []byte) ([]byte, error) {
15+
tmpYAMLFile, err := os.CreateTemp("", "lima-yq-*.yaml")
16+
if err != nil {
17+
return nil, err
18+
}
19+
tmpYAMLPath := tmpYAMLFile.Name()
20+
defer os.RemoveAll(tmpYAMLPath)
21+
err = os.WriteFile(tmpYAMLPath, content, 0o600)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
memory := logging.NewMemoryBackend(0)
27+
backend := logging.AddModuleLevel(memory)
28+
logging.SetBackend(backend)
29+
yqlib.InitExpressionParser()
30+
31+
indent := 2
32+
encoder := yqlib.NewYamlEncoder(indent, false, yqlib.ConfiguredYamlPreferences)
33+
out := new(bytes.Buffer)
34+
printer := yqlib.NewPrinter(encoder, yqlib.NewSinglePrinterWriter(out))
35+
decoder := yqlib.NewYamlDecoder(yqlib.ConfiguredYamlPreferences)
36+
37+
streamEvaluator := yqlib.NewStreamEvaluator()
38+
files := []string{tmpYAMLPath}
39+
err = streamEvaluator.EvaluateFiles(expression, files, printer, decoder)
40+
if err != nil {
41+
logger := logrus.StandardLogger()
42+
for node := memory.Head(); node != nil; node = node.Next() {
43+
entry := logrus.NewEntry(logger).WithTime(node.Record.Time)
44+
prefix := fmt.Sprintf("[%s] ", node.Record.Module)
45+
message := prefix + node.Record.Message()
46+
switch node.Record.Level {
47+
case logging.CRITICAL:
48+
entry.Fatal(message)
49+
case logging.ERROR:
50+
entry.Error(message)
51+
case logging.WARNING:
52+
entry.Warn(message)
53+
case logging.NOTICE:
54+
case logging.INFO:
55+
entry.Info(message)
56+
case logging.DEBUG:
57+
entry.Debug(message)
58+
}
59+
}
60+
return nil, err
61+
}
62+
63+
return out.Bytes(), nil
64+
65+
}

pkg/yqutil/yqutil_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package yqutil
2+
3+
import (
4+
"testing"
5+
6+
"gotest.tools/v3/assert"
7+
)
8+
9+
func TestEvaluateExpression(t *testing.T) {
10+
expression := `.cpus = 2 | .memory = "2GiB"`
11+
content := `
12+
# CPUs
13+
cpus: null
14+
15+
# Memory size
16+
memory: null
17+
`
18+
// Note: yq currently removes empty lines, but not comments
19+
expected := `
20+
# CPUs
21+
cpus: 2
22+
# Memory size
23+
memory: 2GiB
24+
`
25+
out, err := EvaluateExpression(expression, []byte(content))
26+
assert.NilError(t, err)
27+
assert.Equal(t, expected, string(out))
28+
}

0 commit comments

Comments
 (0)