diff --git a/Makefile b/Makefile index bef4a146..df44f781 100644 --- a/Makefile +++ b/Makefile @@ -644,10 +644,12 @@ shfmt: .PHONY: go-licenses go-licenses: - # the allow list corresponds to https://github.com/cncf/foundation/blob/e5db022a0009f4db52b89d9875640cf3137153fe/allowed-third-party-license-policy.md + # the allow list corresponds to https://github.com/cncf/foundation/blob/87e70a07d5a0cf06cd6f8208b8d52c16f035724a/policies-guidance/allowed-third-party-license-policy.md # hashicorp/hcl/v2 is MPL-2.0; covered by the CNCF license exception for hashicorp/hcl # see also https://github.com/cncf/foundation/issues/1242 - go-licenses check --include_tests --ignore github.com/hashicorp/hcl/v2 ./... --allowed_licenses=$$(cat ./hack/allowed-licenses.txt) + # segmentio/asm is MIT-0; approved by CNCF, but go-licenses has not support it yet. + # see also https://github.com/google/licenseclassifier/issues/75 + go-licenses check --include_tests --ignore github.com/hashicorp/hcl/v2 --ignore github.com/segmentio/asm ./... --allowed_licenses=$$(cat ./hack/allowed-licenses.txt) .PHONY: gosocialcheck gosocialcheck: diff --git a/cmd/limactl/main_hcs_windows.go b/cmd/limactl/main_hcs_windows.go new file mode 100644 index 00000000..1b8b5a64 --- /dev/null +++ b/cmd/limactl/main_hcs_windows.go @@ -0,0 +1,9 @@ +//go:build !external_hcs + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package main + +// Import hcs driver to register it in the registry on windows. +import _ "github.com/lima-vm/lima/v2/pkg/driver/hcs" diff --git a/cmd/limactl/main_windows.go b/cmd/limactl/main_wsl2_windows.go similarity index 100% rename from cmd/limactl/main_windows.go rename to cmd/limactl/main_wsl2_windows.go diff --git a/go.mod b/go.mod index 20c9ed84..2d2973c1 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,8 @@ require ( al.essio.dev/pkg/shellescape v1.6.0 github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Code-Hex/vz/v3 v3.7.1 // gomodjail:unconfined - github.com/Microsoft/go-winio v0.6.2 // gomodjail:unconfined + github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // gomodjail:unconfined + github.com/Microsoft/hcsshim v0.14.1 github.com/apparentlymart/go-cidr v1.1.1 github.com/balajiv113/fd v0.0.0-20230330094840-143eec500f3e github.com/cheggaaa/pb/v3 v3.2.1 // gomodjail:unconfined @@ -81,7 +82,11 @@ require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect github.com/buger/jsonparser v1.1.2 // indirect + github.com/containerd/cgroups/v3 v3.1.3 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/containerd/typeurl/v2 v2.3.0 // indirect github.com/digitalocean/go-libvirt v0.0.0-20220804181439-8648fbde413e // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/djherbis/times v1.6.0 // indirect @@ -91,6 +96,7 @@ require ( github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/goccy/go-json v0.10.6 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/jsonschema-go v0.4.3 // indirect @@ -114,13 +120,14 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect - github.com/segmentio/asm v1.1.3 // indirect + github.com/segmentio/asm v1.2.1 // indirect github.com/segmentio/encoding v0.5.4 // indirect // gomodjail:unconfined github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/yuin/gopher-lua v1.1.2 // indirect github.com/zclconf/go-cty v1.19.0 // indirect + go.opencensus.io v0.24.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect diff --git a/go.sum b/go.sum index ecb954cd..1cac1ade 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,17 @@ al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA= al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Code-Hex/go-infinity-channel v1.0.0 h1:M8BWlfDOxq9or9yvF9+YkceoTkDI1pFAqvnP87Zh0Nw= github.com/Code-Hex/go-infinity-channel v1.0.0/go.mod h1:5yUVg/Fqao9dAjcpzoQ33WwfdMWmISOrQloDRn3bsvY= github.com/Code-Hex/vz/v3 v3.7.1 h1:EN1yNiyrbPq+dl388nne2NySo8I94EnPppvqypA65XM= github.com/Code-Hex/vz/v3 v3.7.1/go.mod h1:1LsW0jqW0r0cQ+IeR4hHbjdqOtSidNCVMWhStMHGho8= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 h1:0kQAzHq8vLs7Pptv+7TxjdETLf/nIqJpIB4oC6Ba4vY= +github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0= +github.com/Microsoft/hcsshim v0.14.1 h1:CMuB3fqQVfPdhyXhUqYdUmPUIOhJkmghCx3dJet8Cqs= +github.com/Microsoft/hcsshim v0.14.1/go.mod h1:VnzvPLyWUhxiPVsJ31P6XadxCcTogTguBFDy/1GR/OM= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= @@ -39,18 +43,29 @@ github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0 github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb/v3 v3.2.1 h1:aprZbFRG+B7+ug76S8QZ6Y1PW168UHzOmbC3wa+aU6I= github.com/cheggaaa/pb/v3 v3.2.1/go.mod h1:U9hSVxoKqJrZIE3PkFG1xXXNaK/Ilzg+EF/scpXzEdw= github.com/cilium/ebpf v0.22.0 h1:v2ktp0roffpMOj2MMf3idtCQZOsAoC4BJbAJN+ke2bY= github.com/cilium/ebpf v0.22.0/go.mod h1:CDzZbe2hC5JjlDC+CY3KFCzlYwN4gbxppYM+Z10bQt4= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY= github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ= +github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= github.com/containerd/continuity v0.5.0 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg= github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/typeurl/v2 v2.3.0 h1:HZHPhRWo5XMy3QGQoPrUzbW/2ckwjfweHmOwlkIrPAQ= +github.com/containerd/typeurl/v2 v2.3.0/go.mod h1:Qk+PAdUYArVj41TnGi6rJ+48RF0PkcTc4i/taoBcK0w= github.com/containers/gvisor-tap-vsock v0.8.9 h1:6b7pqxFcKJ0EycBt1V4zPo3FQtgLLgs50AYkbFIb9eU= github.com/containers/gvisor-tap-vsock v0.8.9/go.mod h1:OfqLraPkar5xMQcGbl9czDDSM6/xelt0HJpyB3es6v0= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= @@ -84,6 +99,10 @@ github.com/elliotchance/orderedmap v1.8.0 h1:TrOREecvh3JbS+NCgwposXG5ZTFHtEsQiCG github.com/elliotchance/orderedmap v1.8.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys= github.com/elliotwutingfeng/asciiset v0.0.0-20260129054604-cfde2086bc57 h1:x5yxNrq8XffV/OoNUeFPM6hxHVi5OTspSTBxr/9pemg= github.com/elliotwutingfeng/asciiset v0.0.0-20260129054604-cfde2086bc57/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= @@ -107,10 +126,30 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= @@ -119,6 +158,7 @@ github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+ github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/yamlfmt v0.21.0 h1:9FKApQkDpMKgBjwLFytBHUCgqnQgxaQnci0uiESfbzs= @@ -218,6 +258,7 @@ github.com/pkg/sftp v1.13.11/go.mod h1:uNkH9roSXglNJqM+glJJi+TQXQUm0fXFWqCFmT8hs github.com/pkg/xattr v0.4.12 h1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM= github.com/pkg/xattr v0.4.12/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY= github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -228,8 +269,8 @@ github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDj github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28= github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= -github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= -github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/sethvargo/go-password v0.4.0 h1:eSidVKQw5C7CmTDAtH3RipBTSjdU1ZRxQaynD2GWLVU= @@ -242,8 +283,13 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI= github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= @@ -260,6 +306,8 @@ github.com/zclconf/go-cty v1.19.0 h1:IV8WdqYZc2c5rLX9bEoLNXKojBAp0MZPBHMIrCoa/s4 github.com/zclconf/go-cty v1.19.0/go.mod h1:12W89jGn3JCOIQi7infWr9m80rOkb5RNYJqXMZcN4c8= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= @@ -278,12 +326,17 @@ go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -293,8 +346,13 @@ golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -305,8 +363,11 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -316,10 +377,12 @@ golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -357,6 +420,10 @@ golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -368,13 +435,33 @@ golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y= google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= @@ -390,3 +477,5 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= gvisor.dev/gvisor v0.0.0-20240916094835-a174eb65023f h1:O2w2DymsOlM/nv2pLNWCMCYOldgBBMkD7H0/prN5W2k= gvisor.dev/gvisor v0.0.0-20240916094835-a174eb65023f/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/hack/allowed-licenses.txt b/hack/allowed-licenses.txt index 0d34de67..e6a780ba 100644 --- a/hack/allowed-licenses.txt +++ b/hack/allowed-licenses.txt @@ -1 +1 @@ -Apache-2.0,BSD-2-Clause,BSD-2-Clause-FreeBSD,BSD-3-Clause,MIT,ISC,Python-2.0,PostgreSQL,X11,Zlib +Apache-2.0,BSD-2-Clause,BSD-2-Clause-FreeBSD,BSD-3-Clause,MIT,MIT-0,ISC,Python-2.0,PostgreSQL,X11,Zlib diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go index 26c82eda..b1d33390 100644 --- a/pkg/cidata/cidata.go +++ b/pkg/cidata/cidata.go @@ -294,7 +294,7 @@ func templateArgs(ctx context.Context, bootScripts bool, instDir, name string, i } case firstUsernetIndex != -1 || *instConfig.VMType == limatype.VZ: args.DNSAddresses = append(args.DNSAddresses, args.SlirpDNS) - case *instConfig.HostResolver.Enabled: + case limayaml.HostResolverEnabled(instConfig): args.UDPDNSLocalPort = udpDNSLocalPort args.TCPDNSLocalPort = tcpDNSLocalPort args.DNSAddresses = append(args.DNSAddresses, args.SlirpDNS) diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index b8fb2d0f..7f901965 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -119,7 +119,7 @@ type ConfiguredDriver struct { // the instance directory and the driver's capability flags. It is returned by // Driver.Info and, for external drivers, carried as JSON in InfoResponse. type Info struct { - // Name is the driver name, e.g. "qemu", "vz", "wsl2" or "krunkit". + // Name is the driver name, e.g. "qemu", "vz", "wsl2", "hcs" or "krunkit". Name string `json:"name"` // VsockPort is the vsock port used by the guest agent, or 0 when vsock is // not used. diff --git a/pkg/driver/hcs/errors_windows.go b/pkg/driver/hcs/errors_windows.go new file mode 100644 index 00000000..19488b32 --- /dev/null +++ b/pkg/driver/hcs/errors_windows.go @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package hcs + +import "errors" + +var errUnimplemented = errors.New("unimplemented") diff --git a/pkg/driver/hcs/hcs_api_windows.go b/pkg/driver/hcs/hcs_api_windows.go new file mode 100644 index 00000000..5b2c9558 --- /dev/null +++ b/pkg/driver/hcs/hcs_api_windows.go @@ -0,0 +1,286 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package hcs + +import ( + "context" + "fmt" + "syscall" + "time" + "unsafe" + + "github.com/sirupsen/logrus" + "golang.org/x/sys/windows" +) + +// --------------------------------------------------------------------------- +// computecore.dll (HCS) bindings +// --------------------------------------------------------------------------- + +var ( + modcomputecore = windows.NewLazySystemDLL("computecore.dll") + + procHcsCreateOperation = modcomputecore.NewProc("HcsCreateOperation") + procHcsCloseOperation = modcomputecore.NewProc("HcsCloseOperation") + procHcsWaitForOperationResult = modcomputecore.NewProc("HcsWaitForOperationResult") + procHcsCreateComputeSystem = modcomputecore.NewProc("HcsCreateComputeSystem") + procHcsStartComputeSystem = modcomputecore.NewProc("HcsStartComputeSystem") + procHcsTerminateComputeSystem = modcomputecore.NewProc("HcsTerminateComputeSystem") + procHcsCloseComputeSystem = modcomputecore.NewProc("HcsCloseComputeSystem") + procHcsGrantVMAccess = modcomputecore.NewProc("HcsGrantVmAccess") + procHcsOpenComputeSystem = modcomputecore.NewProc("HcsOpenComputeSystem") + procHcsWaitForComputeSystemExit = modcomputecore.NewProc("HcsWaitForComputeSystemExit") + procHcsGetComputeSystemProperties = modcomputecore.NewProc("HcsGetComputeSystemProperties") + + modapiMsWinCoreComL110 = windows.NewLazySystemDLL("api-ms-win-core-com-l1-1-0.dll") + procCoTaskMemFree = modapiMsWinCoreComL110.NewProc("CoTaskMemFree") +) + +type ( + hcsOperation syscall.Handle + hcsSystem syscall.Handle +) + +const ( + infiniteTimeout = 0xFFFFFFFF + syscallWatcherTimeout = 4 * time.Minute +) + +// hresultErr converts an HRESULT return value into a Go error, unwrapping +// FACILITY_WIN32 the same way hcsshim's generated bindings do. +func hresultErr(r0 uintptr) error { + if int32(r0) >= 0 { + return nil + } + if r0&0x1fff0000 == 0x00070000 { + r0 &= 0xffff + } + return syscall.Errno(r0) +} + +// coString copies a CoTaskMem-allocated PWSTR into a Go string and frees it. +func coString(p *uint16) string { + if p == nil { + return "" + } + s := windows.UTF16PtrToString(p) + windows.CoTaskMemFree(unsafe.Pointer(p)) + return s +} + +func hcsCreateOperation() (hcsOperation, error) { + r0, _, e1 := syscall.SyscallN(procHcsCreateOperation.Addr(), 0, 0) + if r0 == 0 { + return 0, fmt.Errorf("HcsCreateOperation: %w", e1) + } + return hcsOperation(r0), nil +} + +func hcsCloseOperation(op hcsOperation) { + _, _, _ = syscall.SyscallN(procHcsCloseOperation.Addr(), uintptr(op)) +} + +// hcsWait drives one async HCS call to completion and returns the result +// document (which carries structured error info on failure). +func hcsWait(op hcsOperation, what string, callErr error) (string, error) { + if callErr != nil { + return "", fmt.Errorf("%s: %w", what, callErr) + } + var result *uint16 + r0, _, _ := syscall.SyscallN(procHcsWaitForOperationResult.Addr(), + uintptr(op), uintptr(infiniteTimeout), uintptr(unsafe.Pointer(&result))) + doc := coString(result) + if err := hresultErr(r0); err != nil { + return doc, fmt.Errorf("%s: %w (result: %s)", what, err, doc) + } + return doc, nil +} + +func hcsCreateComputeSystem(id, configuration string) (hcsSystem, error) { + op, err := hcsCreateOperation() + if err != nil { + return 0, err + } + defer hcsCloseOperation(op) + + idP, err := syscall.UTF16PtrFromString(id) + if err != nil { + return 0, err + } + cfgP, err := syscall.UTF16PtrFromString(configuration) + if err != nil { + return 0, err + } + var system hcsSystem + r0, _, _ := syscall.SyscallN(procHcsCreateComputeSystem.Addr(), + uintptr(unsafe.Pointer(idP)), uintptr(unsafe.Pointer(cfgP)), + uintptr(op), 0, uintptr(unsafe.Pointer(&system))) + if _, err := hcsWait(op, "HcsCreateComputeSystem", hresultErr(r0)); err != nil { + return 0, err + } + return system, nil +} + +func hcsStartComputeSystem(system hcsSystem) error { + op, err := hcsCreateOperation() + if err != nil { + return err + } + defer hcsCloseOperation(op) + r0, _, _ := syscall.SyscallN(procHcsStartComputeSystem.Addr(), + uintptr(system), uintptr(op), 0) + _, err = hcsWait(op, "HcsStartComputeSystem", hresultErr(r0)) + return err +} + +func hcsTerminateComputeSystem(system hcsSystem) error { + op, err := hcsCreateOperation() + if err != nil { + return err + } + defer hcsCloseOperation(op) + r0, _, _ := syscall.SyscallN(procHcsTerminateComputeSystem.Addr(), + uintptr(system), uintptr(op), 0) + _, err = hcsWait(op, "HcsTerminateComputeSystem", hresultErr(r0)) + return err +} + +func execute(ctx context.Context, timeout time.Duration, f func() error) error { + done := make(chan error, 1) + go func() { done <- f() }() + + var watcher <-chan time.Time + if timeout > 0 { + t := time.NewTimer(timeout) + defer t.Stop() + watcher = t.C + } + + for { + select { + case err := <-done: + return err + case <-watcher: + logrus.WithField("timeout", timeout). + Warn("HCS syscall exceeded timeout; still waiting to avoid use-after-free in computecore.dll") + watcher = nil + case <-ctx.Done(): + logrus.WithError(ctx.Err()). + Warn("HCS syscall context canceled; still waiting to avoid use-after-free in computecore.dll") + ctx = context.WithoutCancel(ctx) + } + } +} + +func hcsGetComputeSystemProperties(ctx context.Context, system hcsSystem, operation hcsOperation, propertyQuery string) (hr error) { + return execute(ctx, syscallWatcherTimeout, func() error { + return _hcsGetComputeSystemProperties(system, operation, propertyQuery) + }) +} + +func _hcsGetComputeSystemProperties(system hcsSystem, operation hcsOperation, propertyQuery string) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(propertyQuery) + if hr != nil { + return hr + } + + hr = procHcsGetComputeSystemProperties.Find() + if hr != nil { + return hr + } + r0, _, _ := syscall.SyscallN(procHcsGetComputeSystemProperties.Addr(), uintptr(system), uintptr(operation), uintptr(unsafe.Pointer(_p0))) + if int32(r0) < 0 { + if r0&0x1fff0000 == 0x00070000 { + r0 &= 0xffff + } + hr = syscall.Errno(r0) + } + return hr +} + +func hcsOpenComputeSystem(ctx context.Context, id string, requestedAccess uint32) (computeSystem hcsSystem, hr error) { + hr = execute(ctx, syscallWatcherTimeout, func() error { + return _hcsOpenComputeSystem(id, requestedAccess, &computeSystem) + }) + return computeSystem, hr +} + +func _hcsOpenComputeSystem(id string, requestedAccess uint32, computeSystem *hcsSystem) (hr error) { + var _p0 *uint16 + _p0, hr = syscall.UTF16PtrFromString(id) + if hr != nil { + return hr + } + + hr = procHcsOpenComputeSystem.Find() + if hr != nil { + return hr + } + r0, _, _ := syscall.SyscallN(procHcsOpenComputeSystem.Addr(), uintptr(unsafe.Pointer(_p0)), uintptr(requestedAccess), uintptr(unsafe.Pointer(computeSystem))) + if int32(r0) < 0 { + if r0&0x1fff0000 == 0x00070000 { + r0 &= 0xffff + } + hr = syscall.Errno(r0) + } + + return hr +} + +func coTaskMemFree(buffer unsafe.Pointer) { + _, _, _ = syscall.SyscallN(procCoTaskMemFree.Addr(), uintptr(buffer)) +} + +func convertAndFreeCoTaskMemString(buffer *uint16) string { + str := syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(buffer))[:]) + coTaskMemFree(unsafe.Pointer(buffer)) + return str +} + +func hcsWaitForComputeSystemExit(ctx context.Context, system hcsSystem, timeoutMs uint32) (result string, hr error) { + return result, execute(ctx, syscallWatcherTimeout, func() error { + var resultp *uint16 + err := _hcsWaitForComputeSystemExit(system, timeoutMs, &resultp) + if resultp != nil { + result = convertAndFreeCoTaskMemString(resultp) + } + return err + }) +} + +func _hcsWaitForComputeSystemExit(system hcsSystem, timeoutMs uint32, result **uint16) (hr error) { + hr = procHcsWaitForComputeSystemExit.Find() + if hr != nil { + return hr + } + r0, _, _ := syscall.SyscallN(procHcsWaitForComputeSystemExit.Addr(), uintptr(system), uintptr(timeoutMs), uintptr(unsafe.Pointer(result))) + if int32(r0) < 0 { + if r0&0x1fff0000 == 0x00070000 { + r0 &= 0xffff + } + hr = syscall.Errno(r0) + } + return hr +} + +func hcsCloseComputeSystem(system hcsSystem) { + _, _, _ = syscall.SyscallN(procHcsCloseComputeSystem.Addr(), uintptr(system)) +} + +// hcsGrantVMAccess ACLs a host file so the VM worker process may read it +// (hcsshim does the equivalent before every kernel-direct boot). +func hcsGrantVMAccess(vmID, path string) error { + idP, err := syscall.UTF16PtrFromString(vmID) + if err != nil { + return err + } + pathP, err := syscall.UTF16PtrFromString(path) + if err != nil { + return err + } + r0, _, _ := syscall.SyscallN(procHcsGrantVMAccess.Addr(), + uintptr(unsafe.Pointer(idP)), uintptr(unsafe.Pointer(pathP))) + return hresultErr(r0) +} diff --git a/pkg/driver/hcs/hcs_driver_windows.go b/pkg/driver/hcs/hcs_driver_windows.go new file mode 100644 index 00000000..b8e3aba0 --- /dev/null +++ b/pkg/driver/hcs/hcs_driver_windows.go @@ -0,0 +1,416 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package hcs + +import ( + "context" + "errors" + "fmt" + "net" + "os" + "path/filepath" + "time" + + "github.com/Microsoft/go-winio" + "github.com/Microsoft/hcsshim/hcn" + "github.com/docker/go-units" + "github.com/lima-vm/go-qcow2reader/image/vhdx" + "github.com/sirupsen/logrus" + + "github.com/lima-vm/lima/v2/pkg/driver" + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" + "github.com/lima-vm/lima/v2/pkg/reflectutil" +) + +var knownYamlProperties = []string{ + "AdditionalDisks", + "Arch", + "Audio", + "CACertificates", + "Containerd", + "CopyToHost", + "CPUs", + "CPUType", + "Disk", + "DNS", + "Env", + "Firmware", + "GuestInstallPrefix", + "HostResolver", + "Images", + "Memory", + "Message", + "MinimumLimaVersion", + "Mounts", + "MountType", + "MountTypesUnsupported", + "MountInotify", + "NestedVirtualization", + "Networks", + "OS", + "Param", + "Plain", + "PortForwards", + "Probes", + "PropagateProxyEnv", + "Provision", + "SSH", + "TimeZone", + "TPM", + "UpgradePackages", + "User", + "Video", + "VMType", + "VMOpts", +} + +const Enabled = true + +type LimaHcsDriver struct { + Instance *limatype.Instance + + SSHLocalPort int + vSockPort int + virtioPort string + + system hcsSystem + serialListener net.Listener + sshAddress string +} + +var _ driver.Driver = (*LimaHcsDriver)(nil) + +func New() *LimaHcsDriver { + return &LimaHcsDriver{} +} + +func (l *LimaHcsDriver) Configure(ctx context.Context, inst *limatype.Instance) (*driver.ConfiguredDriver, error) { + cfg := inst.Config + if cfg.VMType == nil { + cfg.VMType = new(limatype.HCS) + } + if cfg.MountType == nil { + cfg.MountType = new(limatype.REVSSHFS) + } + if err := validateConfig(ctx, cfg); err != nil { + return nil, err + } + + l.Instance = inst + l.SSHLocalPort = inst.SSHLocalPort + + return &driver.ConfiguredDriver{ + Driver: l, + }, nil +} + +func (l *LimaHcsDriver) Validate(ctx context.Context) error { + return validateConfig(ctx, l.Instance.Config) +} + +func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error { + if cfg == nil { + return errors.New("configuration is nil") + } + // TODO: revise this list for HCS + if cfg.VMType != nil { + if unknown := reflectutil.UnknownNonEmptyFields(cfg, knownYamlProperties...); len(unknown) > 0 { + logrus.Warnf("Ignoring: vmType %s: %+v", *cfg.VMType, unknown) + } + } + + if cfg.OS != nil && *cfg.OS == limatype.WINDOWS { + return errors.New("currently Windows guest OS is only supported on QEMU") + } + + if !limatype.IsNativeArch(*cfg.Arch) { + return fmt.Errorf("unsupported arch: %#q", *cfg.Arch) + } + + if cfg.TPM != nil && *cfg.TPM { + return errors.New("field `tpm` is not supported on HCS driver") + } + + if cfg.VMType != nil { + if cfg.Mounts != nil { + for i, mount := range cfg.Mounts { + if unknown := reflectutil.UnknownNonEmptyFields(mount); len(unknown) > 0 { + logrus.Warnf("Ignoring: vmType %s: mounts[%d]: %+v", *cfg.VMType, i, unknown) + } + } + } + + if cfg.Networks != nil { + for i, network := range cfg.Networks { + if unknown := reflectutil.UnknownNonEmptyFields(network); len(unknown) > 0 { + logrus.Warnf("Ignoring: vmType %s: networks[%d]: %+v", *cfg.VMType, i, unknown) + } + } + } + + if cfg.Audio.Device != nil { + audioDevice := *cfg.Audio.Device + if audioDevice != "" { + logrus.Warnf("Ignoring: vmType %s: `audio.device`: %+v", *cfg.VMType, audioDevice) + } + } + + if cfg.HostResolver.Enabled == nil { + cfg.HostResolver.Enabled = new(false) + } + if *cfg.HostResolver.Enabled { + return errors.New("the HCS driver does not support HostResolver.Enabled") + } + } + + return nil +} + +func (l *LimaHcsDriver) BootScripts(_ context.Context) (map[string][]byte, error) { + return nil, nil +} + +func (l *LimaHcsDriver) InspectStatus(ctx context.Context, inst *limatype.Instance) string { + inst.SSHLocalPort = 22 + state, err := getComputeSystemState(ctx, "lima-"+inst.Name) + if err != nil { + if errors.Is(err, errNotFound) { + inst.Status = limatype.StatusUnknown + return inst.Status + } + inst.Status = limatype.StatusBroken + inst.Errors = append(inst.Errors, fmt.Errorf("failed to inspect HCS sompute system: %w", err)) + return inst.Status + } + if state == "Running" { + inst.Status = limatype.StatusRunning + e, err := hcn.GetEndpointByName(endpointName(inst.Name)) + if err != nil { + logrus.Errorf("failed to get HCN endpoint: %s", err.Error()) + inst.Status = limatype.StatusBroken + return inst.Status + } + ips := e.IpConfigurations + if len(ips) == 0 { + logrus.Errorf("the HCN endpoint for instance %#q does not have an IP address yet", inst.Name) + inst.Status = limatype.StatusBroken + return inst.Status + } + ip := ips[0] + l.sshAddress = ip.IpAddress + inst.SSHAddress = ip.IpAddress + } else { + inst.Status = limatype.StatusStopped + } + return inst.Status +} + +func (l *LimaHcsDriver) Delete(_ context.Context) error { + if endpoint, err := hcn.GetEndpointByName(endpointName(l.Instance.Name)); err == nil { + if err := endpoint.Delete(); err != nil { + return fmt.Errorf("failed to delete the HCN endpoint: %w", err) + } + } + if network, err := hcn.GetNetworkByName(networkName(l.Instance.Name)); err == nil { + if err := network.Delete(); err != nil { + return fmt.Errorf("failed to delete the HCN network: %w", err) + } + } + return nil +} + +func (l *LimaHcsDriver) Start(ctx context.Context) (chan error, error) { + inst := l.Instance + diskPath := filepath.Join(inst.Dir, filenames.Disk) + ".vhdx" + isoPath := filepath.Join(inst.Dir, filenames.CIDataISO) + + network, err := ensureNetwork(networkName(inst.Name)) + if err != nil { + return nil, err + } + endpoint, err := ensureEndpoint(network, endpointName(inst.Name), inst.Dir) + if err != nil { + return nil, err + } + + pipeName := `\\.\pipe\lima-` + inst.Name + `-com1` + ln, err := winio.ListenPipe(pipeName, nil) + if err != nil { + return nil, fmt.Errorf("failed to listen on %#q: %w", pipeName, err) + } + l.serialListener = ln + go serveSerial(ln, filepath.Join(inst.Dir, filenames.SerialLog)) + + vmID := "lima-" + inst.Name + for _, p := range []string{diskPath, isoPath} { + if err := hcsGrantVMAccess(vmID, p); err != nil { + return nil, fmt.Errorf("failed to grant VM access to %#q: %w", p, err) + } + } + + memBytes, err := units.RAMInBytes(*inst.Config.Memory) + if err != nil { + return nil, err + } + config, err := buildVMConfig(diskPath, isoPath, endpoint.Id, endpoint.MacAddress, pipeName, *inst.Config.CPUs, memBytes>>20) + if err != nil { + return nil, err + } + logrus.Debugf("HCS compute system config: %s", config) + + system, err := createComputeSystem(ctx, "lima-"+inst.Name, config) + if err != nil { + return nil, err + } + l.system = system + if err := startComputeSystem(ctx, system); err != nil { + return nil, err + } + + for i := range 50 { + endpoint, err = hcn.GetEndpointByName(endpointName(inst.Name)) + if err != nil { + logrus.Warnf("failed in GetEndpointByName: %s", err.Error()) + time.Sleep(2 * time.Second) + continue + } + if len(endpoint.IpConfigurations) == 0 { + if i == 49 { + return nil, fmt.Errorf("HNS did not assign an IP address to the endpoint %#q", endpoint.Name) + } + time.Sleep(2 * time.Second) + continue + } + l.sshAddress = endpoint.IpConfigurations[0].IpAddress + logrus.Infof("HCS: the guest is expected to get %s (MAC %s)", l.sshAddress, endpoint.MacAddress) + break + } + + errCh := make(chan error, 1) + go func() { + result, waitErr := hcsWaitForComputeSystemExit(ctx, system, hcsTimeoutInfinite) + if waitErr != nil { + errCh <- fmt.Errorf("HCS compute system exited: %w (result=%s)", waitErr, result) + return + } + errCh <- nil + }() + return errCh, nil +} + +func (l *LimaHcsDriver) RunGUI(_ context.Context) error { + return fmt.Errorf("RunGUI is not supported for the given driver '%s' and display '%s'", "hcs", *l.Instance.Config.Video.Display) +} + +func (l *LimaHcsDriver) Stop(ctx context.Context) error { + if l.system == 0 { + return nil + } + if err := terminateComputeSystem(ctx, l.system); err != nil { + return err + } + if l.serialListener != nil { + _ = l.serialListener.Close() + } + + if endpoint, err := hcn.GetEndpointByName(endpointName(l.Instance.Name)); err == nil { + if err = endpoint.Delete(); err != nil { + return fmt.Errorf("failed to delete the HCN endpoint: %w", err) + } + } + + if network, err := hcn.GetNetworkByName(networkName(l.Instance.Name)); err == nil { + if err := network.Delete(); err != nil { + return fmt.Errorf("failed to delete the HCN network %w", err) + } + } + + return nil +} + +// GuestAgentConn returns the guest agent connection, or nil (if forwarded by ssh). +// As of 08-01-2024, github.com/mdlayher/vsock does not natively support vsock on +// Windows, so use the winio library to create the connection. +func (l *LimaHcsDriver) GuestAgentConn(_ context.Context) (net.Conn, string, error) { + return nil, "", nil +} + +func (l *LimaHcsDriver) Info(_ context.Context) driver.Info { + var info driver.Info + info.Name = "hcs" + if l.Instance != nil { + info.InstanceDir = l.Instance.Dir + } + info.VirtioPort = l.virtioPort + info.VsockPort = l.vSockPort + + info.Features = driver.DriverFeatures{ + DynamicSSHAddress: true, + StaticSSHPort: true, + SkipSocketForwarding: true, + NoCloudInit: false, + CanRunGUI: false, + SupportedImageFormats: []string{string(vhdx.Type)}, + } + return info +} + +func (l *LimaHcsDriver) SSHAddress(_ context.Context) (string, error) { + if l.sshAddress == "" { + return "", errors.New("the SSH address is not known yet") + } + return l.sshAddress, nil +} + +func (l *LimaHcsDriver) Create(_ context.Context) error { + return nil +} + +func (l *LimaHcsDriver) CreateDisk(ctx context.Context) error { + if err := mayConvertQcow2ToVHDX(ctx, l.Instance.Dir); err != nil { + return err + } + disk := filepath.Join(l.Instance.Dir, filenames.Disk) + link := disk + ".vhdx" + if _, err := os.Stat(link); err == nil { + return nil + } else if !errors.Is(err, os.ErrNotExist) { + return err + } + return os.Link(disk, link) +} + +func (l *LimaHcsDriver) ChangeDisplayPassword(_ context.Context, _ string) error { + return nil +} + +func (l *LimaHcsDriver) DisplayConnection(_ context.Context) (string, error) { + return "", nil +} + +func (l *LimaHcsDriver) CreateSnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaHcsDriver) ApplySnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaHcsDriver) DeleteSnapshot(_ context.Context, _ string) error { + return errUnimplemented +} + +func (l *LimaHcsDriver) ListSnapshots(_ context.Context) (string, error) { + return "", errUnimplemented +} + +func (l *LimaHcsDriver) ForwardGuestAgent(_ context.Context) bool { + // If driver is not providing, use host agent + return l.vSockPort == 0 && l.virtioPort == "" +} + +func (l *LimaHcsDriver) AdditionalSetupForSSH(_ context.Context) error { + return nil +} diff --git a/pkg/driver/hcs/hcs_driver_windows_test.go b/pkg/driver/hcs/hcs_driver_windows_test.go new file mode 100644 index 00000000..f76f26c5 --- /dev/null +++ b/pkg/driver/hcs/hcs_driver_windows_test.go @@ -0,0 +1,244 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package hcs + +import ( + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/lima-vm/go-qcow2reader/image/vhdx" + "gotest.tools/v3/assert" + + "github.com/lima-vm/lima/v2/pkg/limatype" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" +) + +func newTestInstance(t *testing.T, cfg *limatype.LimaYAML) *limatype.Instance { + t.Helper() + return &limatype.Instance{ + Name: "hcs", + Dir: t.TempDir(), + SSHLocalPort: 60022, + Config: cfg, + } +} + +func TestValidateConfig(t *testing.T) { + arch := limatype.NewArch(runtime.GOARCH) + + tests := []struct { + name string + cfg *limatype.LimaYAML + expectedErr string + }{ + { + name: "nil config is rejected", + cfg: nil, + expectedErr: "configuration is nil", + }, + { + name: "minimal config is accepted", + cfg: &limatype.LimaYAML{ + VMType: new(limatype.HCS), + Arch: &arch, + }, + }, + { + name: "Linux guest is accepted", + cfg: &limatype.LimaYAML{ + VMType: new(limatype.HCS), + Arch: &arch, + OS: new(limatype.LINUX), + }, + }, + { + name: "Windows guest is rejected", + cfg: &limatype.LimaYAML{ + VMType: new(limatype.HCS), + Arch: &arch, + OS: new(limatype.WINDOWS), + }, + expectedErr: "currently Windows guest OS is only supported on QEMU", + }, + { + name: "emulation of a non-native arch is rejected", + cfg: &limatype.LimaYAML{ + VMType: new(limatype.HCS), + Arch: new(limatype.Arch("unknown")), + }, + expectedErr: "unsupported arch", + }, + { + name: "tpm is rejected", + cfg: &limatype.LimaYAML{ + VMType: new(limatype.HCS), + Arch: &arch, + TPM: new(true), + }, + expectedErr: "field `tpm` is not supported on HCS driver", + }, + { + name: "tpm=false is accepted", + cfg: &limatype.LimaYAML{ + VMType: new(limatype.HCS), + Arch: &arch, + TPM: new(false), + }, + }, + { + name: "unsupported fields are only warned about, not rejected", + cfg: &limatype.LimaYAML{ + VMType: new(limatype.HCS), + Arch: &arch, + Audio: limatype.Audio{Device: new("none")}, + Mounts: []limatype.Mount{{Location: "~"}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateConfig(t.Context(), tt.cfg) + if tt.expectedErr == "" { + assert.NilError(t, err) + } else { + assert.ErrorContains(t, err, tt.expectedErr) + } + }) + } +} + +func TestKnownYamlProperties(t *testing.T) { + yamlType := reflect.TypeFor[limatype.LimaYAML]() + for _, name := range knownYamlProperties { + t.Run(name, func(t *testing.T) { + _, ok := yamlType.FieldByName(name) + assert.Assert(t, ok, "limatype.LimaYAML has no field %#q", name) + }) + } +} + +func TestConfigure(t *testing.T) { + arch := limatype.NewArch(runtime.GOARCH) + + t.Run("fills in the HCS defaults", func(t *testing.T) { + inst := newTestInstance(t, &limatype.LimaYAML{Arch: &arch}) + + l := New() + configured, err := l.Configure(t.Context(), inst) + assert.NilError(t, err) + assert.Assert(t, configured != nil) + + assert.Equal(t, *inst.Config.VMType, limatype.HCS) + assert.Equal(t, *inst.Config.MountType, limatype.REVSSHFS) + assert.Equal(t, l.Instance, inst) + assert.Equal(t, l.SSHLocalPort, inst.SSHLocalPort) + }) + + t.Run("keeps the values that are already set", func(t *testing.T) { + inst := newTestInstance(t, &limatype.LimaYAML{ + Arch: &arch, + VMType: new(limatype.HCS), + MountType: new(limatype.NINEP), + }) + + l := New() + _, err := l.Configure(t.Context(), inst) + assert.NilError(t, err) + assert.Equal(t, *inst.Config.MountType, limatype.NINEP) + }) + + t.Run("propagates a validation error", func(t *testing.T) { + inst := newTestInstance(t, &limatype.LimaYAML{ + Arch: &arch, + TPM: new(true), + }) + + l := New() + configured, err := l.Configure(t.Context(), inst) + assert.ErrorContains(t, err, "field `tpm` is not supported") + assert.Assert(t, configured == nil) + }) +} + +func TestValidate(t *testing.T) { + arch := limatype.NewArch(runtime.GOARCH) + + l := New() + l.Instance = newTestInstance(t, &limatype.LimaYAML{ + Arch: &arch, + VMType: new(limatype.HCS), + }) + assert.NilError(t, l.Validate(t.Context())) + + l.Instance.Config.TPM = new(true) + assert.ErrorContains(t, l.Validate(t.Context()), "field `tpm` is not supported") +} + +func TestInfo(t *testing.T) { + l := New() + + info := l.Info(t.Context()) + assert.Equal(t, info.Name, "hcs") + assert.Equal(t, info.InstanceDir, "") + + assert.Equal(t, info.Features.NoCloudInit, false) + assert.Equal(t, info.Features.CanRunGUI, false) + + assert.Equal(t, info.Features.DynamicSSHAddress, true) + assert.Equal(t, info.Features.StaticSSHPort, true) + assert.DeepEqual(t, info.Features.SupportedImageFormats, []string{string(vhdx.Type)}) + + l.Instance = newTestInstance(t, &limatype.LimaYAML{}) + assert.Equal(t, l.Info(t.Context()).InstanceDir, l.Instance.Dir) +} + +func TestSSHAddress(t *testing.T) { + l := New() + + _, err := l.SSHAddress(t.Context()) + assert.ErrorContains(t, err, "not known yet") + + l.sshAddress = "192.168.10.5" + got, err := l.SSHAddress(t.Context()) + assert.NilError(t, err) + assert.Equal(t, got, "192.168.10.5") +} + +func TestCreateDisk(t *testing.T) { + l := New() + l.Instance = newTestInstance(t, &limatype.LimaYAML{}) + + diskPath := filepath.Join(l.Instance.Dir, filenames.Disk) + const content = "not a real disk" + assert.NilError(t, os.WriteFile(diskPath, []byte(content), 0o600)) + + assert.NilError(t, l.CreateDisk(t.Context())) + + st, err := os.Stat(diskPath + ".vhdx") + assert.NilError(t, err) + assert.Equal(t, st.Size(), int64(len(content))) + + assert.NilError(t, l.CreateDisk(t.Context())) +} + +func TestGuestAgentIsForwardedByHostAgent(t *testing.T) { + l := New() + assert.Equal(t, l.ForwardGuestAgent(t.Context()), true) + + conn, addr, err := l.GuestAgentConn(t.Context()) + assert.NilError(t, err) + assert.Assert(t, conn == nil) + assert.Equal(t, addr, "") +} + +func TestStopBeforeStart(t *testing.T) { + l := New() + l.Instance = newTestInstance(t, &limatype.LimaYAML{}) + + assert.NilError(t, l.Stop(t.Context())) +} diff --git a/pkg/driver/hcs/helper_windows.go b/pkg/driver/hcs/helper_windows.go new file mode 100644 index 00000000..3b459ece --- /dev/null +++ b/pkg/driver/hcs/helper_windows.go @@ -0,0 +1,325 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package hcs + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + + "github.com/Microsoft/hcsshim/hcn" + "github.com/lima-vm/go-qcow2reader/image/qcow2" + "github.com/lima-vm/go-qcow2reader/image/vhdx" + "github.com/sirupsen/logrus" + + "github.com/lima-vm/lima/v2/pkg/imgutil/nativeimgutil" + "github.com/lima-vm/lima/v2/pkg/limatype/filenames" + "github.com/lima-vm/lima/v2/pkg/limayaml" +) + +const ( + // hcsTimeoutInfinite corresponds to Win32 INFINITE. + hcsTimeoutInfinite uint32 = 0xFFFFFFFF + + // These flags are not exported by the hcn package. + netFlagEnableDNS hcn.NetworkFlags = 1 + netFlagEnableDHCP hcn.NetworkFlags = 2 + netFlagIsolateSwitch hcn.NetworkFlags = 32 + + networkAddressPrefix = "192.168.10.0/24" + networkGateway = "192.168.10.1" +) + +type computeSystemProperties struct { + State string `json:"State"` +} + +var errNotFound = errors.New("not found") + +// getComputeSystemState obtains compute system properties. +func getComputeSystemState(ctx context.Context, id string) (string, error) { + system, err := hcsOpenComputeSystem(ctx, id, syscall.GENERIC_ALL) + if err != nil { + return "", errNotFound + } + defer hcsCloseComputeSystem(system) + + op, err := hcsCreateOperation() + if err != nil { + return "", fmt.Errorf("failed to create an HCS operation: %w", err) + } + defer hcsCloseOperation(op) + + if err := hcsGetComputeSystemProperties(ctx, system, op, "{}"); err != nil { + return "", fmt.Errorf("failed to get compute system properties: %w", err) + } + + result, err := hcsWait(op, "hcsGetComputeSystemProperties", nil) + if err != nil { + return "", fmt.Errorf("failed to get compute system properties: %w (result=%s)", err, result) + } + + var properties computeSystemProperties + if err := json.Unmarshal([]byte(result), &properties); err != nil { + return "", fmt.Errorf("failed to decode compute system properties: %w", err) + } + + return properties.State, nil +} + +// buildVMConfig returns the HCS compute system configuration document. +func buildVMConfig( + diskPath, isoPath, endpointID, macAddress, serialPipe string, + cpus int, memoryMiB int64, +) (string, error) { + config := map[string]any{ + "SchemaVersion": map[string]any{ + "Major": 2, + "Minor": 1, + }, + "Owner": "Lima", + "ShouldTerminateOnLastHandleClosed": true, + "VirtualMachine": map[string]any{ + "Chipset": map[string]any{ + "Uefi": map[string]any{ + "Console": "ComPort1", + "BootThis": map[string]any{ + "DevicePath": "Primary disk", + "DiskNumber": 0, + "DeviceType": "ScsiDrive", + }, + }, + }, + "ComputeTopology": map[string]any{ + "Memory": map[string]any{ + "Backing": "Virtual", + "SizeInMB": memoryMiB, + }, + "Processor": map[string]any{ + "Count": cpus, + }, + }, + "Devices": map[string]any{ + "Scsi": map[string]any{ + "Primary disk": map[string]any{ + "Attachments": map[string]any{ + "0": map[string]any{ + "Type": "VirtualDisk", + "Path": diskPath, + }, + }, + }, + "cidata": map[string]any{ + "Attachments": map[string]any{ + "0": map[string]any{ + "Type": "Iso", + "Path": isoPath, + }, + }, + }, + }, + "ComPorts": map[string]any{ + "0": map[string]any{ + "NamedPipe": serialPipe, + }, + }, + "NetworkAdapters": map[string]any{ + endpointID: map[string]any{ + "EndpointId": endpointID, + "MacAddress": macAddress, + }, + }, + }, + }, + } + + configBytes, err := json.Marshal(config) + if err != nil { + return "", err + } + + return string(configBytes), nil +} + +func ensureNetwork(name string) (*hcn.HostComputeNetwork, error) { + network, err := hcn.GetNetworkByName(name) + if err == nil { + return network, nil + } + + if !hcn.IsNotFoundError(err) { + return nil, fmt.Errorf("failed to look up the HCN network %#q: %w", name, err) + } + + network = &hcn.HostComputeNetwork{ + Name: name, + SchemaVersion: hcn.V2SchemaVersion(), + Type: hcn.ICS, + Flags: netFlagEnableDNS | netFlagEnableDHCP | hcn.EnableNonPersistent | netFlagIsolateSwitch, + Ipams: []hcn.Ipam{{ + Type: "Static", + Subnets: []hcn.Subnet{{ + IpAddressPrefix: networkAddressPrefix, + Routes: []hcn.Route{{ + NextHop: networkGateway, + DestinationPrefix: "0.0.0.0/0", + }}, + }}, + }}, + } + + return network.Create() +} + +func ensureEndpoint(network *hcn.HostComputeNetwork, name, instDir string) (*hcn.HostComputeEndpoint, error) { + endpoint, err := hcn.GetEndpointByName(name) + if err == nil { + if endpoint.HostComputeNetwork != network.Id { + return nil, fmt.Errorf("endpoint %#q already exists on a different network", name) + } + return endpoint, nil + } + if !hcn.IsNotFoundError(err) { + return nil, err + } + + endpoint = &hcn.HostComputeEndpoint{ + Name: name, + SchemaVersion: hcn.V2SchemaVersion(), + HostComputeNetwork: network.Id, + MacAddress: hnsMACAddress(instDir), + } + + return endpoint.Create() +} + +func networkName(instName string) string { + return "lima-" + instName + "-net" +} + +func endpointName(instName string) string { + return "lima-" + instName + "-ep" +} + +// hnsMACAddress converts MAC address notation saved in limayaml struct to the one HNS expects. +func hnsMACAddress(instDir string) string { + return strings.ToUpper(strings.ReplaceAll(limayaml.MACAddress(instDir), ":", "-")) +} + +// createComputeSystem creates (but does not start) an HCS compute system. +func createComputeSystem(_ context.Context, id, config string) (hcsSystem, error) { + system, err := hcsCreateComputeSystem(id, config) + if err != nil { + return 0, fmt.Errorf("create compute system error: %w", err) + } + + return system, nil +} + +func startComputeSystem(_ context.Context, system hcsSystem) error { + return hcsStartComputeSystem(system) +} + +func terminateComputeSystem(_ context.Context, system hcsSystem) error { + return hcsTerminateComputeSystem(system) +} + +// serveSerial accepts connections from the guest port. +// It appends everything it reads to logPath and return when ln is closed. +func serveSerial(ln net.Listener, logPath string) { + f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644) + if err != nil { + logrus.WithError(err).Errorf("failed to open the serial log %#q", logPath) + return + } + defer f.Close() + + for { + conn, err := ln.Accept() + if err != nil { + if errors.Is(err, net.ErrClosed) { + return + } + logrus.WithError(err).Warn("failed to accept a serial connection") + return + } + logrus.Debugf("serial: the guest connected to %s", ln.Addr()) + if _, err := io.Copy(f, conn); err != nil { + logrus.WithError(err).Debug("serial: copy finished with an error") + } + conn.Close() + } +} + +// mayConvertQcow2ToVHDX checks `disk` and converts it to .vhdx if it is qcow2. +func mayConvertQcow2ToVHDX(ctx context.Context, instDir string) error { + disk := filepath.Join(instDir, filenames.Disk) + _, err := os.Stat(disk) + if err == nil { + return nil + } + if !os.IsNotExist(err) { + return err + } + + imagePath := filepath.Join(instDir, filenames.Image) + format, err := nativeimgutil.DetectFormat(imagePath) + if err != nil { + return err + } + + switch format { + case string(vhdx.Type): + logrus.Debug("disk is already .vhdx") + if err = os.Rename(imagePath, disk); err != nil { + return fmt.Errorf("failed to rename %#q to %#q: %w", imagePath, disk, err) + } + if err := os.Chmod(disk, 0o644); err != nil { + return fmt.Errorf("failed to chmod %#q: %w", disk, err) + } + return nil + case qcow2.Type, "raw": + if err := execQemuImgConvert(ctx, imagePath, disk); err != nil { + return fmt.Errorf("failed to convert to vhdx: %w", err) + } + if err := execFsutil(ctx, disk); err != nil { + return fmt.Errorf("failed to set disk flag: %w", err) + } + return nil + default: + return fmt.Errorf("disk type %s is not supported", format) + } +} + +func execQemuImgConvert(ctx context.Context, source, dist string) error { + var stdout, stderr bytes.Buffer + cmd := exec.CommandContext(ctx, "qemu-img", "convert", "-O", "vhdx", source, dist) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to run %v: stdout=%#q, stderr=%#q: %w", + cmd.Args, stdout.String(), stderr.String(), err) + } + return nil +} + +func execFsutil(ctx context.Context, disk string) error { + var stdout, stderr bytes.Buffer + cmd := exec.CommandContext(ctx, "fsutil", "sparse", "setflag", disk, "0") + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to run %v: stdout=%#q, stderr=%#q: %w", cmd.Args, stdout.String(), stderr.String(), err) + } + return nil +} diff --git a/pkg/driver/hcs/helper_windows_test.go b/pkg/driver/hcs/helper_windows_test.go new file mode 100644 index 00000000..3b402f2e --- /dev/null +++ b/pkg/driver/hcs/helper_windows_test.go @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package hcs + +import ( + "encoding/json" + "net" + "os" + "path/filepath" + "regexp" + "sync" + "testing" + + "gotest.tools/v3/assert" +) + +const ( + testDiskPath = `C:\Users\lima\.lima\hcs\disk.vhdx` + testISOPath = `C:\Users\lima\.lima\hcs\cidata.iso` + testEndpointID = "11111111-2222-3333-4444-555555555555" + testMACAddress = "52-55-55-AA-BB-CC" + testSerialPipe = `\\.\pipe\lima-hcs-com1` + testCPUs = 2 + testMemoryMiB = 2048 +) + +func TestNetworkAndEndpointName(t *testing.T) { + assert.Equal(t, networkName("test"), "lima-test-net") + assert.Equal(t, endpointName("test"), "lima-test-ep") +} + +func TestHNSMACAddress(t *testing.T) { + dir := t.TempDir() + got := hnsMACAddress(dir) + + re := regexp.MustCompile(`^52-55-55(-[0-9A-F]{2}){3}$`) + assert.Assert(t, re.MatchString(got), "unexpected MAC address %#q", got) + + assert.Equal(t, got, hnsMACAddress(dir), "MAC Address should be identical if input is the same.") + + assert.Assert(t, got != hnsMACAddress(t.TempDir())) +} + +func TestBuildVMConfig(t *testing.T) { + got, err := buildVMConfig(testDiskPath, testISOPath, testEndpointID, testMACAddress, testSerialPipe, testCPUs, testMemoryMiB) + assert.NilError(t, err) + + const want = `{ + "SchemaVersion": {"Major": 2, "Minor": 1}, + "Owner": "Lima", + "ShouldTerminateOnLastHandleClosed": true, + "VirtualMachine": { + "Chipset": { + "Uefi": { + "Console": "ComPort1", + "BootThis": {"DevicePath": "Primary disk", "DiskNumber": 0, "DeviceType": "ScsiDrive"} + } + }, + "ComputeTopology": { + "Memory": {"Backing": "Virtual", "SizeInMB": 2048}, + "Processor": {"Count": 2} + }, + "Devices": { + "Scsi": { + "Primary disk": { + "Attachments": {"0": {"Type": "VirtualDisk", "Path": "C:\\Users\\lima\\.lima\\hcs\\disk.vhdx"}} + }, + "cidata": { + "Attachments": {"0": {"Type": "Iso", "Path": "C:\\Users\\lima\\.lima\\hcs\\cidata.iso"}} + } + }, + "ComPorts": {"0": {"NamedPipe": "\\\\.\\pipe\\lima-hcs-com1"}}, + "NetworkAdapters": { + "11111111-2222-3333-4444-555555555555": { + "EndpointId": "11111111-2222-3333-4444-555555555555", + "MacAddress": "52-55-55-AA-BB-CC" + } + } + } + } +}` + + var gotDoc, wantDoc any + assert.NilError(t, json.Unmarshal([]byte(got), &gotDoc)) + assert.NilError(t, json.Unmarshal([]byte(want), &wantDoc)) + assert.DeepEqual(t, gotDoc, wantDoc) +} + +func TestBuildVMConfigSerialPortNumbering(t *testing.T) { + got, err := buildVMConfig(testDiskPath, testISOPath, testEndpointID, testMACAddress, testSerialPipe, testCPUs, testMemoryMiB) + assert.NilError(t, err) + + var doc struct { + VirtualMachine struct { + Chipset struct { + Uefi struct { + Console string + } + } + Devices struct { + ComPorts map[string]struct { + NamedPipe string + } + } + } + } + assert.NilError(t, json.Unmarshal([]byte(got), &doc)) + + assert.Equal(t, doc.VirtualMachine.Chipset.Uefi.Console, "ComPort1") + assert.Equal(t, doc.VirtualMachine.Devices.ComPorts["0"].NamedPipe, testSerialPipe) +} + +func TestServeSerial(t *testing.T) { + logPath := filepath.Join(t.TempDir(), "serial.log") + ln := newFakeListener() + + done := make(chan struct{}) + go func() { + serveSerial(ln, logPath) + close(done) + }() + + lines := []string{"[ 0.000000] Linux version 6.11\n", "lima login: \n"} + for _, line := range lines { + guest, host := net.Pipe() + ln.conns <- host + _, err := guest.Write([]byte(line)) + assert.NilError(t, err) + assert.NilError(t, guest.Close()) + } + + assert.NilError(t, ln.Close()) + <-done + + b, err := os.ReadFile(logPath) + assert.NilError(t, err) + assert.Equal(t, string(b), lines[0]+lines[1]) +} + +type fakeListener struct { + conns chan net.Conn + done chan struct{} + once sync.Once +} + +func newFakeListener() *fakeListener { + return &fakeListener{ + conns: make(chan net.Conn), + done: make(chan struct{}), + } +} + +func (ln *fakeListener) Accept() (net.Conn, error) { + select { + case conn := <-ln.conns: + return conn, nil + case <-ln.done: + return nil, net.ErrClosed + } +} + +func (ln *fakeListener) Close() error { + ln.once.Do(func() { close(ln.done) }) + return nil +} + +func (ln *fakeListener) Addr() net.Addr { return fakeAddr{} } + +type fakeAddr struct{} + +func (fakeAddr) Network() string { return "fake" } +func (fakeAddr) String() string { return "fake" } diff --git a/pkg/driver/hcs/register.go b/pkg/driver/hcs/register.go new file mode 100644 index 00000000..ea9d104b --- /dev/null +++ b/pkg/driver/hcs/register.go @@ -0,0 +1,16 @@ +//go:build windows && !external_hcs + +// SPDX-FileCopyrightText: Copyright The Lima Authors +// SPDX-License-Identifier: Apache-2.0 + +package hcs + +import ( + "context" + + "github.com/lima-vm/lima/v2/pkg/registry" +) + +func init() { + registry.Register(context.Background(), New()) +} diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go index 67db7775..2c59e0c8 100644 --- a/pkg/hostagent/hostagent.go +++ b/pkg/hostagent/hostagent.go @@ -155,7 +155,7 @@ func New(ctx context.Context, instName string, stdout io.Writer, signalCh chan o } var udpDNSLocalPort, tcpDNSLocalPort int - if *inst.Config.HostResolver.Enabled { + if limayaml.HostResolverEnabled(inst.Config) { udpDNSLocalPort, err = freeport.UDP() if err != nil { return nil, err @@ -408,7 +408,7 @@ func (a *HostAgent) Run(ctx context.Context) error { }() adjustNofileRlimit() - if limayaml.FirstUsernetIndex(a.instConfig) == -1 && *a.instConfig.HostResolver.Enabled { + if limayaml.FirstUsernetIndex(a.instConfig) == -1 && limayaml.HostResolverEnabled(a.instConfig) { hosts := a.instConfig.HostResolver.Hosts if hosts == nil { hosts = make(map[string]string) diff --git a/pkg/instance/start.go b/pkg/instance/start.go index b35c6b00..41caa5be 100644 --- a/pkg/instance/start.go +++ b/pkg/instance/start.go @@ -17,6 +17,7 @@ import ( "github.com/docker/go-units" "github.com/lima-vm/go-qcow2reader" + "github.com/lima-vm/go-qcow2reader/image/vhdx" "github.com/sirupsen/logrus" "github.com/lima-vm/lima/v2/pkg/autostart" @@ -537,6 +538,11 @@ func prepareDisk(ctx context.Context, inst *limatype.Instance) error { diskSize := img.Size() + if img.Type() == vhdx.Type { + logrus.Warn("Skip resizing the instance because go-qcow2reader cannot read vhdx type image") + return nil + } + if inst.Disk == diskSize { return nil } diff --git a/pkg/limatype/lima_yaml.go b/pkg/limatype/lima_yaml.go index fa6b3831..ca1fae32 100644 --- a/pkg/limatype/lima_yaml.go +++ b/pkg/limatype/lima_yaml.go @@ -99,13 +99,14 @@ const ( QEMU VMType = "qemu" VZ VMType = "vz" WSL2 VMType = "wsl2" + HCS VMType = "hcs" ) var ( OSTypes = []OS{LINUX, DARWIN, FREEBSD, WINDOWS} ArchTypes = []Arch{X8664, AARCH64, ARMV7L, PPC64LE, RISCV64, S390X} MountTypes = []MountType{REVSSHFS, NINEP, VIRTIOFS, WSLMount} - VMTypes = []VMType{QEMU, VZ, WSL2} + VMTypes = []VMType{QEMU, VZ, WSL2, HCS} ) type User struct { diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go index e2134809..99e22fc5 100644 --- a/pkg/limayaml/defaults.go +++ b/pkg/limayaml/defaults.go @@ -78,6 +78,14 @@ func FirstUsernetIndex(l *limatype.LimaYAML) int { return slices.IndexFunc(l.Networks, func(network limatype.Network) bool { return networks.IsUsernet(network.Lima) }) } +// HostResolverEnabled returns whether the host resolver is enabled. +// The default value depends on the driver and is filled in the driver-specific FillDefault(). +// A nil value is treated as true to keep HostResolver.Enabled true for drivers which do not fill the field. + +func HostResolverEnabled(l *limatype.LimaYAML) bool { + return l.HostResolver.Enabled == nil || *l.HostResolver.Enabled +} + func MACAddress(uniqueID string) string { sha := sha256.Sum256([]byte(osutil.MachineID() + uniqueID)) // "5" is the magic number in the Lima ecosystem. @@ -616,9 +624,7 @@ func FillDefault(ctx context.Context, y, d, o *limatype.LimaYAML, filePath strin if o.HostResolver.Enabled != nil { y.HostResolver.Enabled = o.HostResolver.Enabled } - if y.HostResolver.Enabled == nil { - y.HostResolver.Enabled = new(true) - } + // y.HostResolver.Enabled default value depends on the driver; filled in driver-specific FillDefault() if y.HostResolver.IPv6 == nil { y.HostResolver.IPv6 = d.HostResolver.IPv6 diff --git a/pkg/limayaml/defaults_test.go b/pkg/limayaml/defaults_test.go index 6b76ac26..fafd2139 100644 --- a/pkg/limayaml/defaults_test.go +++ b/pkg/limayaml/defaults_test.go @@ -140,7 +140,7 @@ func TestFillDefault(t *testing.T) { Display: new("none"), }, HostResolver: limatype.HostResolver{ - Enabled: new(true), + Enabled: nil, IPv6: new(false), }, PropagateProxyEnv: new(true), @@ -539,7 +539,8 @@ func TestFillDefault(t *testing.T) { expect.HostResolver.Hosts["default"] = dExpected.HostResolver.Hosts["default"] - // dExpected.DNS will be ignored, and not appended to y.DNS + // The user-provided default is applied when the config doesn't specify it. + expect.HostResolver.Enabled = dExpected.HostResolver.Enabled // "TWO" does not exist in filledDefaults.Env, so is set from dExpected.Env expect.Env["TWO"] = dExpected.Env["TWO"] diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go index 886c86a8..97cdaa33 100644 --- a/pkg/limayaml/validate.go +++ b/pkg/limayaml/validate.go @@ -401,8 +401,14 @@ func Validate(y *limatype.LimaYAML, warn bool) error { } } - if y.HostResolver.Enabled != nil && *y.HostResolver.Enabled && len(y.DNS) > 0 { - errs = errors.Join(errs, errors.New("field `dns` must be empty when field `HostResolver.Enabled` is true")) + if y.VMType != nil && *y.VMType == limatype.HCS { + if y.HostResolver.Enabled != nil && *y.HostResolver.Enabled && len(y.DNS) > 0 { + errs = errors.Join(errs, errors.New("field `dns` must be empty when field `HostResolver.Enabled` is true")) + } + } else { + if HostResolverEnabled(y) && len(y.DNS) > 0 { + errs = errors.Join(errs, errors.New("field `dns` must be empty when field `HostResolver.Enabled` is true")) + } } err := validateNetwork(y) diff --git a/templates/README.md b/templates/README.md index a5c92130..fe64ab1d 100644 --- a/templates/README.md +++ b/templates/README.md @@ -122,6 +122,7 @@ See . - [`experimental/vnc`](./experimental/vnc.yaml): use vnc display and xorg server - [`experimental/alsa`](./experimental/alsa.yaml): use alsa and default audio device +- [`experimental/hcs`](./experimental/hcs.yaml): use [HCS driver](https://lima-vm.io/docs/config/vmtype/hcs/) on Windows hosts ### Lost+found diff --git a/templates/default.yaml b/templates/default.yaml index 56833a90..0dad1996 100644 --- a/templates/default.yaml +++ b/templates/default.yaml @@ -614,7 +614,7 @@ propagateProxyEnv: null # can only work when using a vmnet network interface and the host has working # IPv6 configured as well. hostResolver: - # 🟢 Builtin default: true + # 🟢 Builtin default: true (except for hcs driver; it should be always false with hcs driver) enabled: null # 🟢 Builtin default: false ipv6: null diff --git a/templates/experimental/hcs.yaml b/templates/experimental/hcs.yaml new file mode 100644 index 00000000..49f54085 --- /dev/null +++ b/templates/experimental/hcs.yaml @@ -0,0 +1,13 @@ +# This template only works on Windows versions with Hyper-V platform enabled. +vmType: hcs + +images: +# The following image is qcow2; the hcs driver converts it into VHDX on the first start using `qemu-img`. +- location: "https://cloud-images.ubuntu.com/releases/resolute/release-20260720/ubuntu-26.04-server-cloudimg-amd64.img" + arch: "x86_64" + digest: "sha256:117816726abbdefc5ef3e38902e81a76f1c76c3610e709999d0885f9d5d9b477" + +plain: true + +dns: +- 8.8.8.8 diff --git a/website/content/en/docs/config/vmtype/hcs.md b/website/content/en/docs/config/vmtype/hcs.md new file mode 100644 index 00000000..a998f917 --- /dev/null +++ b/website/content/en/docs/config/vmtype/hcs.md @@ -0,0 +1,66 @@ +--- +title: HCS +weight: 5 +--- + +> **Warning** +> "hcs" mode is experimental + +| ⚡ Requirement | Lima >= 2.3 + (Windows >= Windows 11) | +| ----------------- | -------------------------------------------------------- | + +"hcs" option makes use of native virtualization support provided by Windows' Host Compute System (HCS) API ([more info](https://learn.microsoft.com/en-us/virtualization/api/hcs/overview)). + +An example configuration: +{{< tabpane text=true >}} +{{% tab header="CLI" %}} +```bash +limactl start --vm-type=hcs --plain --dns 8.8.8.8 +``` +{{% /tab %}} +{{% tab header="YAML" %}} +```yaml +# Example to run Ubuntu using vmType: hcs +vmType: hcs +images: +- location: "https://cloud-images.ubuntu.com/releases/resolute/release-20260720/ubuntu-26.04-server-cloudimg-amd64.img" + arch: "x86_64" + digest: "sha256:117816726abbdefc5ef3e38902e81a76f1c76c3610e709999d0885f9d5d9b477" +plain: true +dns: +- 8.8.8.8 +``` +{{% /tab %}} +{{< /tabpane >}} + +### Caveats +- We tested it works only on Windows 11 (x86_64). +- Only plain mode is supported (no file mount, no dynamic port-forwarding). +- `qemu-img` is required for the image conversion; see [Disk images](#disk-images). +- The user must be a member of the Hyper-V Administrators group; see [Setup for HCS driver](#setup-for-hcs-driver) +- `limactl` should be run from a shell opened with Administrator privileges. +- Currently, only one instance can run using hcs at a time, beucause the HCN network subnet is fixed and cannot be shared by multiple instances. +- A DNS server has to be specified explicitly. + + +### Disk images +- The `hcs` driver can only boot from a `.vhdx` disk image. +- If the image is not a `.vhdx` (for example `.qcow2`), Lima automatically converts it to `.vhdx` by running `qemu-img convert -O vhdx`. +- Therefore, if you would like to download the image or to pass a disk image which is not `.vhdx`, `qemu-img` is required. + +### Setup for HCS driver +1. **Enable virtualization on Windows:** + Follow the instrunctions in [Microsoft guide](https://support.microsoft.com/en-us/windows/experience/enable-virtualization-on-windows) to enable virtualization on your Windows host. + +2. **Add yourself to `Hyper-V Administrators` group:** + Run the following command on powershell or cmd.exe with Run as administrator: + ```bash + net localgroup "Hyper-V Administrators" /add + ``` + + Then, restart the terminal or reboot the computer for the changes to take effect. + + To verify that your user account has been added to `Hyper-V Administrators` group, run: + ```bash + net localgroup "Hyper-V Administrators" + ``` diff --git a/website/content/en/docs/dev/internals.md b/website/content/en/docs/dev/internals.md index 1c1b6bce..fc293f44 100644 --- a/website/content/en/docs/dev/internals.md +++ b/website/content/en/docs/dev/internals.md @@ -47,6 +47,7 @@ disk: - `image`: the downloaded VM image; renamed to `disk` or `iso` during setup - `image.ipsw`: hardlink to `image`, created for running `VZMacOSInstaller` that requires the image file to have the `.ipsw` suffix - `disk`: the VM disk (can be a symlink to legacy `diffdisk`) +- `disk.vhdx`: hardlink to `disk` to pass a `.vhdx` path to HCS driver - `iso`: optional CDROM image for ISO-based installations (can be a symlink to legacy `basedisk`) - `basedisk`: legacy name for the downloaded image (pre-v2.1 instances; may remain as a qcow2 backing file) - `diffdisk`: legacy name for `disk` (pre-v2.1 instances) @@ -72,7 +73,7 @@ VZ: - `vz-efi`: EFIVariable store file for a VM Serial: -- `serial.log`: default serial log (QEMU only), for debugging +- `serial.log`: default serial log (QEMU and HCS), for debugging - `serial.sock`: default serial socket (QEMU only), for debugging (Usage: `socat -,echo=0,icanon=0 unix-connect:serial.sock`) - `serialp.log`: PCI serial log (QEMU (ARM) only), for debugging - `serialp.sock`: PCI serial socket (QEMU (ARM) only), for debugging (Usage: `socat -,echo=0,icanon=0 unix-connect:serialp.sock`) diff --git a/website/content/en/docs/releases/experimental.md b/website/content/en/docs/releases/experimental.md index 0908b460..b0773529 100644 --- a/website/content/en/docs/releases/experimental.md +++ b/website/content/en/docs/releases/experimental.md @@ -16,6 +16,7 @@ The following features are experimental and subject to change: - `user.passwordlessSudo` (See [Sudo](../config/sudo)) - `External drivers`: building and using drivers as separate executables (see [Virtual Machine Drivers](../dev/drivers)) - [`vmType: krunkit`](../config/vmtype/krunkit.md) +- [`vmType:hcs`]((../config/vmtype/hcs.md)) - [`github` URL scheme](../templates/github.md): referencing templates on GitHub with `github:` URLs - [macOS guests](../usage/guests/macos.md) - [FreeBSD guests](../usage/guests/freebsd.md)