Merge branch 'helm:main' into feature/enhance-dry-run-for-helm-4

This commit is contained in:
MrJack 2026-02-12 11:58:50 +01:00 committed by GitHub
commit 00a389711c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 1269 additions and 673 deletions

20
KEYS
View file

@ -1058,3 +1058,23 @@ K6V08VpFmniENmCDHshXYq0gGiTDAP9FsXl2UtmFU5xuYxH4fRKIxgmxJRAFMWI8
u3Rdu/s+DQ==
=smBO
-----END PGP PUBLIC KEY BLOCK-----
pub ed25519 2026-02-08 [SC]
BF888333D96A1C18E2682AAED79D67C9EC016739
uid [ultimate] George Jenkins <gvjenkins@gmail.com>
sig 3 D79D67C9EC016739 2026-02-08 [self-signature]
sub cv25519 2026-02-08 [E]
sig D79D67C9EC016739 2026-02-08 [self-signature]
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEaYgDUBYJKwYBBAHaRw8BAQdAWKYkFrwgmfaY/hUq5Z2YpEy8WACKclo2eV/n
1ausaEy0JEdlb3JnZSBKZW5raW5zIDxndmplbmtpbnNAZ21haWwuY29tPoiTBBMW
CgA7FiEEv4iDM9lqHBjiaCqu151nyewBZzkFAmmIA1ACGwMFCwkIBwICIgIGFQoJ
CAsCBBYCAwECHgcCF4AACgkQ151nyewBZzlP0gD/ZFhm9FikdlZO5pW7xWR4YnP4
yFAuY32G9dNdFn1x1p4BALR8Rtpp68eC9R8bq3/r1dK8gwig8DMWirdYaf2ePKoL
uDgEaYgDUBIKKwYBBAGXVQEFAQEHQJwM3R9CTypooHz/4w1waXAct8K2wA1bwi1r
yfb6uMMKAwEIB4h4BBgWCgAgFiEEv4iDM9lqHBjiaCqu151nyewBZzkFAmmIA1AC
GwwACgkQ151nyewBZzlgYAEAoVwYdoO6f3VwGukpv7RtKwF7PQC9AnBUx98TZZ6t
IaoA/RR14NXYYcd0fCwN6sFPq58/NbNkRHBrfw1CntxiJcYD
=duOC
-----END PGP PUBLIC KEY BLOCK-----

View file

@ -17,6 +17,7 @@ limitations under the License.
package main // import "helm.sh/helm/v4/cmd/helm"
import (
"errors"
"log/slog"
"os"
@ -41,7 +42,8 @@ func main() {
}
if err := cmd.Execute(); err != nil {
if cerr, ok := err.(helmcmd.CommandError); ok {
var cerr helmcmd.CommandError
if errors.As(err, &cerr) {
os.Exit(cerr.ExitCode)
}
os.Exit(1)

View file

@ -18,6 +18,7 @@ package main
import (
"bytes"
"errors"
"os"
"os/exec"
"runtime"
@ -60,7 +61,8 @@ func TestCliPluginExitCode(t *testing.T) {
cmd.Stderr = stderr
err := cmd.Run()
exiterr, ok := err.(*exec.ExitError)
exiterr := &exec.ExitError{}
ok := errors.As(err, &exiterr)
if !ok {
t.Fatalf("Unexpected error type returned by os.Exit: %T", err)
}

18
go.mod
View file

@ -23,7 +23,7 @@ require (
github.com/gofrs/flock v0.13.0
github.com/gosuri/uitable v0.0.4
github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.11.1
github.com/lib/pq v1.11.2
github.com/mattn/go-shellwords v1.0.12
github.com/moby/term v0.5.2
github.com/opencontainers/go-digest v1.0.0
@ -35,9 +35,9 @@ require (
github.com/stretchr/testify v1.11.1
github.com/tetratelabs/wazero v1.11.0
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/crypto v0.47.0
golang.org/x/term v0.39.0
golang.org/x/text v0.33.0
golang.org/x/crypto v0.48.0
golang.org/x/term v0.40.0
golang.org/x/text v0.34.0
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.35.0
k8s.io/apiextensions-apiserver v0.35.0
@ -49,7 +49,7 @@ require (
k8s.io/kubectl v0.35.0
oras.land/oras-go/v2 v2.6.0
sigs.k8s.io/controller-runtime v0.23.1
sigs.k8s.io/kustomize/kyaml v0.21.0
sigs.k8s.io/kustomize/kyaml v0.21.1
sigs.k8s.io/yaml v1.6.0
)
@ -157,13 +157,13 @@ require (
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/mod v0.31.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.40.0 // indirect
golang.org/x/tools v0.41.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/grpc v1.72.2 // indirect

36
go.sum
View file

@ -192,8 +192,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.11.1 h1:wuChtj2hfsGmmx3nf1m7xC2XpK6OtelS2shMY+bGMtI=
github.com/lib/pq v1.11.1/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
@ -377,14 +377,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
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.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -395,8 +395,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -426,8 +426,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -435,8 +435,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@ -444,8 +444,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -454,8 +454,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
@ -509,8 +509,8 @@ sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5E
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I=
sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM=
sigs.k8s.io/kustomize/kyaml v0.21.0 h1:7mQAf3dUwf0wBerWJd8rXhVcnkk5Tvn/q91cGkaP6HQ=
sigs.k8s.io/kustomize/kyaml v0.21.0/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI=
sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.2-0.20260122202528-d9cc6641c482 h1:2WOzJpHUBVrrkDjU4KBT8n5LDcj824eX0I5UKcgeRUs=

View file

@ -45,7 +45,7 @@ type Chart struct {
// Templates for this chart.
Templates []*common.File `json:"templates"`
// Values are default config for this chart.
Values map[string]interface{} `json:"values"`
Values map[string]any `json:"values"`
// Schema is an optional JSON schema for imposing structure on Values
Schema []byte `json:"schema"`
// SchemaModTime the schema was last modified

View file

@ -44,7 +44,7 @@ type Dependency struct {
Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`
// ImportValues holds the mapping of source values to parent key to be imported. Each item can be a
// string or pair of child/parent sublist items.
ImportValues []interface{} `json:"import-values,omitempty" yaml:"import-values,omitempty"`
ImportValues []any `json:"import-values,omitempty" yaml:"import-values,omitempty"`
// Alias usable alias to be used for the chart
Alias string `json:"alias,omitempty" yaml:"alias,omitempty"`
}

View file

@ -25,6 +25,6 @@ func (v ValidationError) Error() string {
}
// ValidationErrorf takes a message and formatting options and creates a ValidationError
func ValidationErrorf(msg string, args ...interface{}) ValidationError {
func ValidationErrorf(msg string, args ...any) ValidationError {
return ValidationError(fmt.Sprintf(msg, args...))
}

View file

@ -43,7 +43,7 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption {
}
}
func RunAll(baseDir string, values map[string]interface{}, namespace string, options ...LinterOption) support.Linter {
func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter {
chartDir, _ := filepath.Abs(baseDir)

View file

@ -69,15 +69,15 @@ func Chartfile(linter *support.Linter) {
linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartDependencies(chartFile))
}
func validateChartVersionType(data map[string]interface{}) error {
func validateChartVersionType(data map[string]any) error {
return isStringValue(data, "version")
}
func validateChartAppVersionType(data map[string]interface{}) error {
func validateChartAppVersionType(data map[string]any) error {
return isStringValue(data, "appVersion")
}
func isStringValue(data map[string]interface{}, key string) error {
func isStringValue(data map[string]any, key string) error {
value, ok := data[key]
if !ok {
return nil
@ -214,12 +214,12 @@ func validateChartType(cf *chart.Metadata) error {
// loadChartFileForTypeCheck loads the Chart.yaml
// in a generic form of a map[string]interface{}, so that the type
// of the values can be checked
func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) {
func loadChartFileForTypeCheck(filename string) (map[string]any, error) {
b, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
y := make(map[string]interface{})
y := make(map[string]any)
err = yaml.Unmarshal(b, &y)
return y, err
}

View file

@ -42,17 +42,17 @@ import (
)
// Templates lints the templates in the Linter.
func Templates(linter *support.Linter, values map[string]interface{}, namespace string, _ bool) {
func Templates(linter *support.Linter, values map[string]any, namespace string, _ bool) {
TemplatesWithKubeVersion(linter, values, namespace, nil)
}
// TemplatesWithKubeVersion lints the templates in the Linter, allowing to specify the kubernetes version.
func TemplatesWithKubeVersion(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *common.KubeVersion) {
func TemplatesWithKubeVersion(linter *support.Linter, values map[string]any, namespace string, kubeVersion *common.KubeVersion) {
TemplatesWithSkipSchemaValidation(linter, values, namespace, kubeVersion, false)
}
// TemplatesWithSkipSchemaValidation lints the templates in the Linter, allowing to specify the kubernetes version and if schema validation is enabled or not.
func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *common.KubeVersion, skipSchemaValidation bool) {
func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string]any, namespace string, kubeVersion *common.KubeVersion, skipSchemaValidation bool) {
fpath := "templates/"
templatesPath := filepath.Join(linter.ChartDir, fpath)

View file

@ -49,7 +49,7 @@ func TestValidateAllowedExtension(t *testing.T) {
}
}
var values = map[string]interface{}{"nameOverride": "", "httpPort": 80}
var values = map[string]any{"nameOverride": "", "httpPort": 80}
const namespace = "testNamespace"
const strict = false
@ -249,7 +249,7 @@ func TestStrictTemplateParsingMapError(t *testing.T) {
APIVersion: "v2",
Version: "0.1.0",
},
Values: map[string]interface{}{
Values: map[string]any{
"mymap": map[string]string{
"key1": "val1",
},

View file

@ -32,7 +32,7 @@ import (
// they are only tested for well-formedness.
//
// If additional values are supplied, they are coalesced into the values in values.yaml.
func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}, skipSchemaValidation bool) {
func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]any, skipSchemaValidation bool) {
file := "values.yaml"
vf := filepath.Join(linter.ChartDir, file)
fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf))
@ -52,7 +52,7 @@ func validateValuesFileExistence(valuesPath string) error {
return nil
}
func validateValuesFile(valuesPath string, overrides map[string]interface{}, skipSchemaValidation bool) error {
func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaValidation bool) error {
values, err := common.ReadValuesFile(valuesPath)
if err != nil {
return fmt.Errorf("unable to parse YAML: %w", err)
@ -63,7 +63,7 @@ func validateValuesFile(valuesPath string, overrides map[string]interface{}, ski
// We could change that. For now, though, we retain that strategy, and thus can
// coalesce tables (like reuse-values does) instead of doing the full chart
// CoalesceValues
coalescedValues := util.CoalesceTables(make(map[string]interface{}, len(overrides)), overrides)
coalescedValues := util.CoalesceTables(make(map[string]any, len(overrides)), overrides)
coalescedValues = util.CoalesceTables(coalescedValues, values)
ext := filepath.Ext(valuesPath)

View file

@ -67,7 +67,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) {
`
tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml))
valfile := filepath.Join(tmpdir, "values.yaml")
if err := validateValuesFile(valfile, map[string]interface{}{}, false); err == nil {
if err := validateValuesFile(valfile, map[string]any{}, false); err == nil {
t.Fatal("expected values file to fail parsing")
}
}
@ -78,7 +78,7 @@ func TestValidateValuesFileSchema(t *testing.T) {
createTestingSchema(t, tmpdir)
valfile := filepath.Join(tmpdir, "values.yaml")
if err := validateValuesFile(valfile, map[string]interface{}{}, false); err != nil {
if err := validateValuesFile(valfile, map[string]any{}, false); err != nil {
t.Fatalf("Failed validation with %s", err)
}
}
@ -91,7 +91,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) {
valfile := filepath.Join(tmpdir, "values.yaml")
err := validateValuesFile(valfile, map[string]interface{}{}, false)
err := validateValuesFile(valfile, map[string]any{}, false)
if err == nil {
t.Fatal("expected values file to fail parsing")
}
@ -107,7 +107,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T
valfile := filepath.Join(tmpdir, "values.yaml")
err := validateValuesFile(valfile, map[string]interface{}{}, true)
err := validateValuesFile(valfile, map[string]any{}, true)
if err != nil {
t.Fatal("expected values file to pass parsing because of skipSchemaValidation")
}
@ -115,7 +115,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T
func TestValidateValuesFileSchemaOverrides(t *testing.T) {
yaml := "username: admin"
overrides := map[string]interface{}{
overrides := map[string]any{
"password": "swordfish",
}
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
@ -131,24 +131,24 @@ func TestValidateValuesFile(t *testing.T) {
tests := []struct {
name string
yaml string
overrides map[string]interface{}
overrides map[string]any
errorMessage string
}{
{
name: "value added",
yaml: "username: admin",
overrides: map[string]interface{}{"password": "swordfish"},
overrides: map[string]any{"password": "swordfish"},
},
{
name: "value not overridden",
yaml: "username: admin\npassword:",
overrides: map[string]interface{}{"username": "anotherUser"},
overrides: map[string]any{"username": "anotherUser"},
errorMessage: "- at '/password': got null, want string",
},
{
name: "value overridden",
yaml: "username: admin\npassword:",
overrides: map[string]interface{}{"username": "anotherUser", "password": "swordfish"},
overrides: map[string]any{"username": "anotherUser", "password": "swordfish"},
},
}

View file

@ -182,11 +182,11 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) {
//
// The reader is expected to contain one or more YAML documents, the values of which are merged.
// And the values can be either a chart's default values or user-supplied values.
func LoadValues(data io.Reader) (map[string]interface{}, error) {
values := map[string]interface{}{}
func LoadValues(data io.Reader) (map[string]any, error) {
values := map[string]any{}
reader := utilyaml.NewYAMLReader(bufio.NewReader(data))
for {
currentMap := map[string]interface{}{}
currentMap := map[string]any{}
raw, err := reader.Read()
if err != nil {
if errors.Is(err, io.EOF) {
@ -204,13 +204,13 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) {
// MergeMaps merges two maps. If a key exists in both maps, the value from b will be used.
// If the value is a map, the maps will be merged recursively.
func MergeMaps(a, b map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{}, len(a))
func MergeMaps(a, b map[string]any) map[string]any {
out := make(map[string]any, len(a))
maps.Copy(out, a)
for k, v := range b {
if v, ok := v.(map[string]interface{}); ok {
if v, ok := v.(map[string]any); ok {
if bv, ok := out[k]; ok {
if bv, ok := bv.(map[string]interface{}); ok {
if bv, ok := bv.(map[string]any); ok {
out[k] = MergeMaps(bv, v)
continue
}

View file

@ -455,7 +455,7 @@ func TestLoadInvalidArchive(t *testing.T) {
func TestLoadValues(t *testing.T) {
testCases := map[string]struct {
data []byte
expctedValues map[string]interface{}
expctedValues map[string]any
}{
"It should load values correctly": {
data: []byte(`
@ -464,11 +464,11 @@ foo:
bar:
version: v2
`),
expctedValues: map[string]interface{}{
"foo": map[string]interface{}{
expctedValues: map[string]any{
"foo": map[string]any{
"image": "foo:v1",
},
"bar": map[string]interface{}{
"bar": map[string]any{
"version": "v2",
},
},
@ -483,11 +483,11 @@ bar:
foo:
image: foo:v2
`),
expctedValues: map[string]interface{}{
"foo": map[string]interface{}{
expctedValues: map[string]any{
"foo": map[string]any{
"image": "foo:v2",
},
"bar": map[string]interface{}{
"bar": map[string]any{
"version": "v2",
},
},
@ -507,24 +507,24 @@ foo:
}
func TestMergeValuesV3(t *testing.T) {
nestedMap := map[string]interface{}{
nestedMap := map[string]any{
"foo": "bar",
"baz": map[string]string{
"cool": "stuff",
},
}
anotherNestedMap := map[string]interface{}{
anotherNestedMap := map[string]any{
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
flatMap := map[string]interface{}{
flatMap := map[string]any{
"foo": "bar",
"baz": "stuff",
}
anotherFlatMap := map[string]interface{}{
anotherFlatMap := map[string]any{
"testing": "fun",
}
@ -547,7 +547,7 @@ func TestMergeValuesV3(t *testing.T) {
}
testMap = MergeMaps(anotherFlatMap, anotherNestedMap)
expectedMap := map[string]interface{}{
expectedMap := map[string]any{
"testing": "fun",
"foo": "bar",
"baz": map[string]string{

View file

@ -670,7 +670,7 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
return fmt.Errorf("reading values file: %w", err)
}
var m map[string]interface{}
var m map[string]any
if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil {
return fmt.Errorf("transforming values file: %w", err)
}

View file

@ -16,6 +16,7 @@ limitations under the License.
package util
import (
"errors"
"fmt"
"log/slog"
"strings"
@ -44,6 +45,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values,
if len(c) > 0 {
// retrieve value
vv, err := cvals.PathValue(cpath + c)
var errNoValue common.ErrNoValue
if err == nil {
// if not bool, warn
if bv, ok := vv.(bool); ok {
@ -51,7 +53,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values,
break
}
slog.Warn("returned non-bool value", "path", c, "chart", r.Name)
} else if _, ok := err.(common.ErrNoValue); !ok {
} else if errors.As(err, &errNoValue) {
// this is a real error
slog.Warn("the method PathValue returned error", slog.Any("error", err))
}
@ -140,7 +142,7 @@ func copyMetadata(metadata *chart.Metadata) *chart.Metadata {
}
// processDependencyEnabled removes disabled charts from dependencies
func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error {
func processDependencyEnabled(c *chart.Chart, v map[string]any, path string) error {
if c.Metadata.Dependencies == nil {
return nil
}
@ -226,7 +228,7 @@ Loop:
}
// pathToMap creates a nested map given a YAML path in dot notation.
func pathToMap(path string, data map[string]interface{}) map[string]interface{} {
func pathToMap(path string, data map[string]any) map[string]any {
if path == "." {
return data
}
@ -235,13 +237,13 @@ func pathToMap(path string, data map[string]interface{}) map[string]interface{}
func parsePath(key string) []string { return strings.Split(key, ".") }
func set(path []string, data map[string]interface{}) map[string]interface{} {
func set(path []string, data map[string]any) map[string]any {
if len(path) == 0 {
return nil
}
cur := data
for i := len(path) - 1; i >= 0; i-- {
cur = map[string]interface{}{path[i]: cur}
cur = map[string]any{path[i]: cur}
}
return cur
}
@ -262,13 +264,13 @@ func processImportValues(c *chart.Chart, merge bool) error {
if err != nil {
return err
}
b := make(map[string]interface{})
b := make(map[string]any)
// import values from each dependency if specified in import-values
for _, r := range c.Metadata.Dependencies {
var outiv []interface{}
var outiv []any
for _, riv := range r.ImportValues {
switch iv := riv.(type) {
case map[string]interface{}:
case map[string]any:
child := fmt.Sprintf("%v", iv["child"])
parent := fmt.Sprintf("%v", iv["parent"])
@ -336,27 +338,27 @@ func processImportValues(c *chart.Chart, merge bool) error {
return nil
}
func deepCopyMap(vals map[string]interface{}) map[string]interface{} {
func deepCopyMap(vals map[string]any) map[string]any {
valsCopy, err := copystructure.Copy(vals)
if err != nil {
return vals
}
return valsCopy.(map[string]interface{})
return valsCopy.(map[string]any)
}
func trimNilValues(vals map[string]interface{}) map[string]interface{} {
func trimNilValues(vals map[string]any) map[string]any {
valsCopy, err := copystructure.Copy(vals)
if err != nil {
return vals
}
valsCopyMap := valsCopy.(map[string]interface{})
valsCopyMap := valsCopy.(map[string]any)
for key, val := range valsCopyMap {
if val == nil {
// Iterate over the values and remove nil keys
delete(valsCopyMap, key)
} else if istable(val) {
// Recursively call into ourselves to remove keys from inner tables
valsCopyMap[key] = trimNilValues(val.(map[string]interface{}))
valsCopyMap[key] = trimNilValues(val.(map[string]any))
}
}
@ -364,8 +366,8 @@ func trimNilValues(vals map[string]interface{}) map[string]interface{} {
}
// istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
func istable(v interface{}) bool {
_, ok := v.(map[string]interface{})
func istable(v any) bool {
_, ok := v.(map[string]any)
return ok
}

View file

@ -119,3 +119,29 @@ func TestAtomicWriteFile_LargeContent(t *testing.T) {
t.Fatalf("expected large content to match, got different length: %d vs %d", len(largeContent), len(got))
}
}
// TestPlatformAtomicWriteFile_OverwritesExisting verifies that the platform
// helper replaces existing files instead of silently skipping them.
func TestPlatformAtomicWriteFile_OverwritesExisting(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "overwrite_test")
first := bytes.NewReader([]byte("first"))
if err := PlatformAtomicWriteFile(path, first, 0644); err != nil {
t.Fatalf("first write failed: %v", err)
}
second := bytes.NewReader([]byte("second"))
if err := PlatformAtomicWriteFile(path, second, 0644); err != nil {
t.Fatalf("second write failed: %v", err)
}
contents, err := os.ReadFile(path)
if err != nil {
t.Fatalf("failed reading result: %v", err)
}
if string(contents) != "second" {
t.Fatalf("expected file to be overwritten, got %q", string(contents))
}
}

View file

@ -0,0 +1,32 @@
//go:build !windows
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fileutil
import (
"io"
"os"
)
// PlatformAtomicWriteFile atomically writes a file to disk.
//
// On non-Windows platforms we don't need extra coordination, so this simply
// delegates to AtomicWriteFile to preserve the existing overwrite behaviour.
func PlatformAtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error {
return AtomicWriteFile(filename, reader, mode)
}

View file

@ -0,0 +1,54 @@
//go:build windows
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fileutil
import (
"io"
"os"
"github.com/gofrs/flock"
)
// PlatformAtomicWriteFile atomically writes a file to disk with file locking to
// prevent concurrent writes. This is particularly useful on Windows where
// concurrent writes to the same file can cause "Access Denied" errors.
//
// The function acquires a lock on the target file and performs an atomic write,
// preserving the existing behaviour of overwriting any previous content once
// the lock is obtained.
func PlatformAtomicWriteFile(filename string, reader io.Reader, mode os.FileMode) error {
// Use a separate lock file to coordinate access between processes
// We cannot lock the target file directly as it would prevent the atomic rename
lockFileName := filename + ".lock"
fileLock := flock.New(lockFileName)
// Lock() ensures serialized access - if another process is writing, this will wait
if err := fileLock.Lock(); err != nil {
return err
}
defer func() {
fileLock.Unlock()
// Clean up the lock file
// Ignore errors as the file might not exist or be in use by another process
os.Remove(lockFileName)
}()
// Perform the atomic write while holding the lock
return AtomicWriteFile(filename, reader, mode)
}

19
internal/gates/doc.go Normal file
View file

@ -0,0 +1,19 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package gates contains internal feature gates that can be used to enable or disable experimental features.
// This is a separate internal package instead of using the pkg/gates package to avoid circular dependencies.
package gates

21
internal/gates/gates.go Normal file
View file

@ -0,0 +1,21 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gates
import "helm.sh/helm/v4/pkg/gates"
// ChartV3 is the feature gate for chart API version v3.
const ChartV3 gates.Gate = "HELM_EXPERIMENTAL_CHART_V3"

View file

@ -16,6 +16,7 @@ limitations under the License.
package plugin
import (
"errors"
"fmt"
"os"
"path/filepath"
@ -76,7 +77,8 @@ func TestSubprocessPluginRuntime(t *testing.T) {
})
require.Error(t, err)
ieerr, ok := err.(*InvokeExecError)
ieerr := &InvokeExecError{}
ok := errors.As(err, &ieerr)
require.True(t, ok, "expected InvokeExecError, got %T", err)
assert.Equal(t, 56, ieerr.ExitCode)

View file

@ -29,8 +29,8 @@ var updateGolden = flag.Bool("update", false, "update golden files")
// TestingT describes a testing object compatible with the critical functions from the testing.T type
type TestingT interface {
Fatal(...interface{})
Fatalf(string, ...interface{})
Fatal(...any)
Fatalf(string, ...any)
HelperT
}

View file

@ -164,7 +164,8 @@ func CopyFile(src, dst string) (err error) {
//
// ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522):
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) {
lerr := &os.LinkError{}
if errors.As(err, &lerr) && !errors.Is(lerr.Err, syscall.Errno(1314)) {
return err
}
} else {

View file

@ -32,6 +32,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs
import (
"errors"
"os"
"path/filepath"
"runtime"
@ -234,7 +235,7 @@ func TestCopyDirFail_SrcIsNotDir(t *testing.T) {
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
if err != errSrcNotDir {
if !errors.Is(err, errSrcNotDir) {
t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err)
}
@ -260,7 +261,7 @@ func TestCopyDirFail_DstExists(t *testing.T) {
t.Fatalf("expected error for CopyDir(%s, %s), got none", srcdir, dstdir)
}
if err != errDstExist {
if !errors.Is(err, errDstExist) {
t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errDstExist, srcdir, dstdir, err)
}
}

View file

@ -34,6 +34,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package fs
import (
"errors"
"fmt"
"os"
"syscall"
@ -46,10 +47,11 @@ func renameFallback(err error, src, dst string) error {
// copy if we detect that case. syscall.EXDEV is the common name for the
// cross device link error which has varying output text across different
// operating systems.
terr, ok := err.(*os.LinkError)
terr := &os.LinkError{}
ok := errors.As(err, &terr)
if !ok {
return err
} else if terr.Err != syscall.EXDEV {
} else if !errors.Is(terr.Err, syscall.EXDEV) {
return fmt.Errorf("link error: cannot rename %s to %s: %w", src, dst, terr)
}

View file

@ -181,12 +181,24 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
// We do these one file at a time in the order they were read.
totalItems := []*resource.Info{}
for _, obj := range crds {
if obj.File == nil {
return fmt.Errorf("failed to install CRD %s: file is empty", obj.Name)
}
if obj.File.Data == nil {
return fmt.Errorf("failed to install CRD %s: file data is empty", obj.Name)
}
// Read in the resources
res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false)
if err != nil {
return fmt.Errorf("failed to install CRD %s: %w", obj.Name, err)
}
if len(res) == 0 {
return fmt.Errorf("failed to install CRD %s: resources are empty", obj.Name)
}
// Send them to Kube
if _, err := i.cfg.KubeClient.Create(
res,
@ -222,27 +234,30 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
// the case when an action configuration is reused for multiple actions,
// as otherwise it is later loaded by ourselves when getCapabilities
// is called later on in the installation process.
if i.cfg.Capabilities != nil {
discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient()
if i.cfg.RESTClientGetter != nil {
if i.cfg.Capabilities != nil {
discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient()
if err != nil {
return err
}
if discoveryClient != nil {
i.cfg.Logger().Debug("clearing discovery cache")
discoveryClient.Invalidate()
_, _ = discoveryClient.ServerGroups()
}
}
// Invalidate the REST mapper, since it will not have the new CRDs
// present.
restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper()
if err != nil {
return err
}
i.cfg.Logger().Debug("clearing discovery cache")
discoveryClient.Invalidate()
_, _ = discoveryClient.ServerGroups()
}
// Invalidate the REST mapper, since it will not have the new CRDs
// present.
restMapper, err := i.cfg.RESTClientGetter.ToRESTMapper()
if err != nil {
return err
}
if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok {
i.cfg.Logger().Debug("clearing REST mapper cache")
resettable.Reset()
if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok {
i.cfg.Logger().Debug("clearing REST mapper cache")
resettable.Reset()
}
}
}
return nil
@ -252,7 +267,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error {
//
// If DryRun is set to true, this will prepare the release, but not install it
func (i *Install) Run(chrt ci.Charter, vals map[string]interface{}) (ri.Releaser, error) {
func (i *Install) Run(chrt ci.Charter, vals map[string]any) (ri.Releaser, error) {
ctx := context.Background()
return i.RunWithContext(ctx, chrt, vals)
}
@ -261,7 +276,7 @@ func (i *Install) Run(chrt ci.Charter, vals map[string]interface{}) (ri.Releaser
//
// When the task is cancelled through ctx, the function returns and the install
// proceeds in the background.
func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[string]interface{}) (ri.Releaser, error) {
func (i *Install) RunWithContext(ctx context.Context, ch ci.Charter, vals map[string]any) (ri.Releaser, error) {
var chrt *chart.Chart
switch c := ch.(type) {
case *chart.Chart:
@ -509,7 +524,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
// pre-install hooks
if !i.DisableHooks {
if err := i.cfg.execHook(rel, release.HookPreInstall, i.WaitStrategy, i.WaitOptions, i.Timeout, i.ServerSideApply); err != nil {
return rel, fmt.Errorf("failed pre-install: %s", err)
return rel, fmt.Errorf("failed pre-install: %w", err)
}
}
@ -555,7 +570,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource
if !i.DisableHooks {
if err := i.cfg.execHook(rel, release.HookPostInstall, i.WaitStrategy, i.WaitOptions, i.Timeout, i.ServerSideApply); err != nil {
return rel, fmt.Errorf("failed post-install: %s", err)
return rel, fmt.Errorf("failed post-install: %w", err)
}
}
@ -659,7 +674,7 @@ func releaseV1ListToReleaserList(ls []*release.Release) ([]ri.Releaser, error) {
}
// createRelease creates a new release object
func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}, labels map[string]string) *release.Release {
func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]any, labels map[string]string) *release.Release {
ts := i.cfg.Now()
r := &release.Release{

View file

@ -111,6 +111,54 @@ func createDummyResourceList(owned bool) kube.ResourceList {
return resourceList
}
func createDummyCRDList(owned bool) kube.ResourceList {
obj := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "dummyName",
Namespace: "spaced",
},
}
if owned {
obj.Labels = map[string]string{
"app.kubernetes.io/managed-by": "Helm",
}
obj.Annotations = map[string]string{
"meta.helm.sh/release-name": "test-install-release",
"meta.helm.sh/release-namespace": "spaced",
}
}
resInfo := resource.Info{
Name: "dummyName",
Namespace: "spaced",
Mapping: &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "test", Version: "v1", Resource: "crd"},
GroupVersionKind: schema.GroupVersionKind{Group: "test", Version: "v1", Kind: "crd"},
Scope: meta.RESTScopeNamespace,
},
Object: obj,
}
body := io.NopCloser(bytes.NewReader([]byte(kuberuntime.EncodeOrDie(appsv1Codec, obj))))
resInfo.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "test", Version: "v1"},
NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
Client: fake.CreateHTTPClient(func(_ *http.Request) (*http.Response, error) {
header := http.Header{}
header.Set("Content-Type", kuberuntime.ContentTypeJSON)
return &http.Response{
StatusCode: http.StatusOK,
Header: header,
Body: body,
}, nil
}),
}
var resourceList kube.ResourceList
resourceList.Append(&resInfo)
return resourceList
}
func installActionWithConfig(config *Configuration) *Install {
instAction := NewInstall(config)
instAction.Namespace = "spaced"
@ -1118,8 +1166,8 @@ func TestInstallSetRegistryClient(t *testing.T) {
assert.Equal(t, registryClient, instAction.GetRegistryClient())
}
func TestInstalLCRDs(t *testing.T) {
config := actionConfigFixture(t)
func TestInstallCRDs(t *testing.T) {
config := actionConfigFixtureWithDummyResources(t, createDummyCRDList(false))
instAction := NewInstall(config)
mockFile := common.File{
@ -1128,13 +1176,36 @@ func TestInstalLCRDs(t *testing.T) {
}
mockChart := buildChart(withFile(mockFile))
crdsToInstall := mockChart.CRDObjects()
assert.Len(t, crdsToInstall, 1)
assert.Equal(t, crdsToInstall[0].File.Data, mockFile.Data)
require.NoError(t, instAction.installCRDs(crdsToInstall))
}
func TestInstalLCRDs_KubeClient_BuildError(t *testing.T) {
func TestInstallCRDs_AlreadyExist(t *testing.T) {
dummyResources := createDummyCRDList(false)
failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: dummyResources}
mockError := &apierrors.StatusError{ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Reason: metav1.StatusReasonAlreadyExists,
}}
failingKubeClient.CreateError = mockError
config := actionConfigFixtureWithDummyResources(t, dummyResources)
config.KubeClient = &failingKubeClient
instAction := NewInstall(config)
mockFile := common.File{
Name: "crds/foo.yaml",
Data: []byte("hello"),
}
mockChart := buildChart(withFile(mockFile))
crdsToInstall := mockChart.CRDObjects()
assert.Nil(t, instAction.installCRDs(crdsToInstall))
}
func TestInstallCRDs_KubeClient_BuildError(t *testing.T) {
config := actionConfigFixture(t)
failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil}
failingKubeClient.BuildError = errors.New("build error")
@ -1151,7 +1222,7 @@ func TestInstalLCRDs_KubeClient_BuildError(t *testing.T) {
require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD")
}
func TestInstalLCRDs_KubeClient_CreateError(t *testing.T) {
func TestInstallCRDs_KubeClient_CreateError(t *testing.T) {
config := actionConfigFixture(t)
failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil}
failingKubeClient.CreateError = errors.New("create error")
@ -1168,28 +1239,7 @@ func TestInstalLCRDs_KubeClient_CreateError(t *testing.T) {
require.Error(t, instAction.installCRDs(crdsToInstall), "failed to install CRD")
}
func TestInstalLCRDs_AlreadyExist(t *testing.T) {
config := actionConfigFixture(t)
failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil}
mockError := &apierrors.StatusError{ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Reason: metav1.StatusReasonAlreadyExists,
}}
failingKubeClient.CreateError = mockError
config.KubeClient = &failingKubeClient
instAction := NewInstall(config)
mockFile := common.File{
Name: "crds/foo.yaml",
Data: []byte("hello"),
}
mockChart := buildChart(withFile(mockFile))
crdsToInstall := mockChart.CRDObjects()
assert.Nil(t, instAction.installCRDs(crdsToInstall))
}
func TestInstalLCRDs_WaiterError(t *testing.T) {
func TestInstallCRDs_WaiterError(t *testing.T) {
config := actionConfigFixture(t)
failingKubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: nil}
failingKubeClient.WaitError = errors.New("wait error")
@ -1221,6 +1271,45 @@ func TestCheckDependencies_MissingDependency(t *testing.T) {
assert.ErrorContains(t, CheckDependencies(mockChart, []ci.Dependency{&dependency}), "missing in charts")
}
func TestInstallCRDs_CheckNilErrors(t *testing.T) {
tests := []struct {
name string
input []chart.CRD
}{
{
name: "only one crd with file nil",
input: []chart.CRD{
{Name: "one", File: nil},
},
},
{
name: "only one crd with its file data nil",
input: []chart.CRD{
{Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: nil}},
},
},
{
name: "at least a crd with its file data nil",
input: []chart.CRD{
{Name: "one", File: &common.File{Name: "crds/foo.yaml", Data: []byte("data")}},
{Name: "two", File: &common.File{Name: "crds/foo2.yaml", Data: nil}},
{Name: "three", File: &common.File{Name: "crds/foo3.yaml", Data: []byte("data")}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
instAction := installAction(t)
err := instAction.installCRDs(tt.input)
if err == nil {
t.Errorf("got nil expected err")
}
})
}
}
func TestInstallRelease_WaitOptionsPassedDownstream(t *testing.T) {
is := assert.New(t)

View file

@ -17,6 +17,7 @@ limitations under the License.
package action
import (
"errors"
"os"
"path"
"testing"
@ -146,7 +147,7 @@ func TestValidateVersion(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := validateVersion(tt.args.ver); err != nil {
if err != tt.wantErr {
if !errors.Is(err, tt.wantErr) {
t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err)
}

View file

@ -156,13 +156,13 @@ func (u *Upgrade) SetRegistryClient(client *registry.Client) {
}
// Run executes the upgrade on the given release.
func (u *Upgrade) Run(name string, chart chart.Charter, vals map[string]interface{}) (ri.Releaser, error) {
func (u *Upgrade) Run(name string, chart chart.Charter, vals map[string]any) (ri.Releaser, error) {
ctx := context.Background()
return u.RunWithContext(ctx, name, chart, vals)
}
// RunWithContext executes the upgrade on the given release with context.
func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Charter, vals map[string]interface{}) (ri.Releaser, error) {
func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Charter, vals map[string]any) (ri.Releaser, error) {
if err := u.cfg.KubeClient.IsReachable(); err != nil {
return nil, err
}
@ -213,7 +213,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, ch chart.Char
}
// prepareUpgrade builds an upgraded release for an upgrade operation.
func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[string]interface{}) (*release.Release, *release.Release, bool, error) {
func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[string]any) (*release.Release, *release.Release, bool, error) {
if chart == nil {
return nil, nil, false, errMissingChart
}
@ -421,7 +421,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR
}
rChan := make(chan resultMessage)
ctxChan := make(chan resultMessage)
doneChan := make(chan interface{})
doneChan := make(chan any)
defer close(doneChan)
go u.releasingUpgrade(rChan, upgradedRelease, current, target, originalRelease, serverSideApply)
go u.handleContext(ctx, doneChan, ctxChan, upgradedRelease)
@ -447,7 +447,7 @@ func (u *Upgrade) reportToPerformUpgrade(c chan<- resultMessage, rel *release.Re
}
// Setup listener for SIGINT and SIGTERM
func (u *Upgrade) handleContext(ctx context.Context, done chan interface{}, c chan<- resultMessage, upgradedRelease *release.Release) {
func (u *Upgrade) handleContext(ctx context.Context, done chan any, c chan<- resultMessage, upgradedRelease *release.Release) {
select {
case <-ctx.Done():
err := ctx.Err()
@ -614,7 +614,7 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e
//
// This is skipped if the u.ResetValues flag is set, in which case the
// request values are not altered.
func (u *Upgrade) reuseValues(chart *chartv2.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) {
func (u *Upgrade) reuseValues(chart *chartv2.Chart, current *release.Release, newVals map[string]any) (map[string]any, error) {
if u.ResetValues {
// If ResetValues is set, we completely ignore current.Config.
u.cfg.Logger().Debug("resetting values to the chart's original version")

View file

@ -56,8 +56,8 @@ func (r *v2Accessor) IsRoot() bool {
return r.chrt.IsRoot()
}
func (r *v2Accessor) MetadataAsMap() map[string]interface{} {
var ret map[string]interface{}
func (r *v2Accessor) MetadataAsMap() map[string]any {
var ret map[string]any
if r.chrt.Metadata == nil {
return ret
}
@ -101,7 +101,7 @@ func (r *v2Accessor) MetaDependencies() []Dependency {
return deps
}
func (r *v2Accessor) Values() map[string]interface{} {
func (r *v2Accessor) Values() map[string]any {
return r.chrt.Values
}
@ -125,8 +125,8 @@ func (r *v3Accessor) IsRoot() bool {
return r.chrt.IsRoot()
}
func (r *v3Accessor) MetadataAsMap() map[string]interface{} {
var ret map[string]interface{}
func (r *v3Accessor) MetadataAsMap() map[string]any {
var ret map[string]any
if r.chrt.Metadata == nil {
return ret
}
@ -170,7 +170,7 @@ func (r *v3Accessor) MetaDependencies() []Dependency {
return deps
}
func (r *v3Accessor) Values() map[string]interface{} {
func (r *v3Accessor) Values() map[string]any {
return r.chrt.Values
}
@ -182,7 +182,7 @@ func (r *v3Accessor) Deprecated() bool {
return r.chrt.Metadata.Deprecated
}
func structToMap(obj interface{}) (map[string]interface{}, error) {
func structToMap(obj any) (map[string]any, error) {
objValue := reflect.ValueOf(obj)
// If the value is a pointer, dereference it
@ -195,7 +195,7 @@ func structToMap(obj interface{}) (map[string]interface{}, error) {
return nil, fmt.Errorf("input must be a struct or a pointer to a struct")
}
result := make(map[string]interface{})
result := make(map[string]any)
objType := objValue.Type()
for i := 0; i < objValue.NumField(); i++ {
@ -221,7 +221,7 @@ func structToMap(obj interface{}) (map[string]interface{}, error) {
result[field.Name] = nestedMap
}
case reflect.Slice:
sliceOfMaps := make([]interface{}, value.Len())
sliceOfMaps := make([]any, value.Len())
for j := 0; j < value.Len(); j++ {
sliceElement := value.Index(j)
if sliceElement.Kind() == reflect.Struct || sliceElement.Kind() == reflect.Pointer {

View file

@ -157,7 +157,7 @@ func makeDefaultCapabilities() (*Capabilities, error) {
v, err := semver.NewVersion(vstr)
if err != nil {
return nil, fmt.Errorf("unable to parse k8s.io/client-go version %q: %v", vstr, err)
return nil, fmt.Errorf("unable to parse k8s.io/client-go version %q: %w", vstr, err)
}
kubeVersionMajor := v.Major() + 1

View file

@ -42,7 +42,7 @@ func concatPrefix(a, b string) string {
// - Scalar values and arrays are replaced, maps are merged
// - A chart has access to all of the variables for it, as well as all of
// the values destined for its dependencies.
func CoalesceValues(chrt chart.Charter, vals map[string]interface{}) (common.Values, error) {
func CoalesceValues(chrt chart.Charter, vals map[string]any) (common.Values, error) {
valsCopy, err := copyValues(vals)
if err != nil {
return vals, err
@ -64,7 +64,7 @@ func CoalesceValues(chrt chart.Charter, vals map[string]interface{}) (common.Val
// Retaining Nils is useful when processes early in a Helm action or business
// logic need to retain them for when Coalescing will happen again later in the
// business logic.
func MergeValues(chrt chart.Charter, vals map[string]interface{}) (common.Values, error) {
func MergeValues(chrt chart.Charter, vals map[string]any) (common.Values, error) {
valsCopy, err := copyValues(vals)
if err != nil {
return vals, err
@ -72,22 +72,22 @@ func MergeValues(chrt chart.Charter, vals map[string]interface{}) (common.Values
return coalesce(log.Printf, chrt, valsCopy, "", true)
}
func copyValues(vals map[string]interface{}) (common.Values, error) {
func copyValues(vals map[string]any) (common.Values, error) {
v, err := copystructure.Copy(vals)
if err != nil {
return vals, err
}
valsCopy := v.(map[string]interface{})
valsCopy := v.(map[string]any)
// if we have an empty map, make sure it is initialized
if valsCopy == nil {
valsCopy = make(map[string]interface{})
valsCopy = make(map[string]any)
}
return valsCopy, nil
}
type printFn func(format string, v ...interface{})
type printFn func(format string, v ...any)
// coalesce coalesces the dest values and the chart values, giving priority to the dest values.
//
@ -96,13 +96,13 @@ type printFn func(format string, v ...interface{})
// Note, the merge argument specifies whether this is being used by MergeValues
// or CoalesceValues. Coalescing removes null values and their keys in some
// situations while merging keeps the null values.
func coalesce(printf printFn, ch chart.Charter, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) {
func coalesce(printf printFn, ch chart.Charter, dest map[string]any, prefix string, merge bool) (map[string]any, error) {
coalesceValues(printf, ch, dest, prefix, merge)
return coalesceDeps(printf, ch, dest, prefix, merge)
}
// coalesceDeps coalesces the dependencies of the given chart.
func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) {
func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]any, prefix string, merge bool) (map[string]any, error) {
ch, err := chart.NewAccessor(chrt)
if err != nil {
return dest, err
@ -114,12 +114,12 @@ func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]interface{
}
if c, ok := dest[sub.Name()]; !ok {
// If dest doesn't already have the key, create it.
dest[sub.Name()] = make(map[string]interface{})
dest[sub.Name()] = make(map[string]any)
} else if !istable(c) {
return dest, fmt.Errorf("type mismatch on %s: %t", sub.Name(), c)
}
if dv, ok := dest[sub.Name()]; ok {
dvmap := dv.(map[string]interface{})
dvmap := dv.(map[string]any)
subPrefix := concatPrefix(prefix, ch.Name())
// Get globals out of dest and merge them into dvmap.
coalesceGlobals(printf, dvmap, dest, subPrefix, merge)
@ -137,19 +137,19 @@ func coalesceDeps(printf printFn, chrt chart.Charter, dest map[string]interface{
// coalesceGlobals copies the globals out of src and merges them into dest.
//
// For convenience, returns dest.
func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix string, _ bool) {
var dg, sg map[string]interface{}
func coalesceGlobals(printf printFn, dest, src map[string]any, prefix string, _ bool) {
var dg, sg map[string]any
if destglob, ok := dest[common.GlobalKey]; !ok {
dg = make(map[string]interface{})
} else if dg, ok = destglob.(map[string]interface{}); !ok {
dg = make(map[string]any)
} else if dg, ok = destglob.(map[string]any); !ok {
printf("warning: skipping globals because destination %s is not a table.", common.GlobalKey)
return
}
if srcglob, ok := src[common.GlobalKey]; !ok {
sg = make(map[string]interface{})
} else if sg, ok = srcglob.(map[string]interface{}); !ok {
sg = make(map[string]any)
} else if sg, ok = srcglob.(map[string]any); !ok {
printf("warning: skipping globals because source %s is not a table.", common.GlobalKey)
return
}
@ -160,12 +160,12 @@ func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix st
// tables in globals.
for key, val := range sg {
if istable(val) {
vv := copyMap(val.(map[string]interface{}))
vv := copyMap(val.(map[string]any))
if destv, ok := dg[key]; !ok {
// Here there is no merge. We're just adding.
dg[key] = vv
} else {
if destvmap, ok := destv.(map[string]interface{}); !ok {
if destvmap, ok := destv.(map[string]any); !ok {
printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key)
} else {
// Basically, we reverse order of coalesce here to merge
@ -189,8 +189,8 @@ func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix st
dest[common.GlobalKey] = dg
}
func copyMap(src map[string]interface{}) map[string]interface{} {
m := make(map[string]interface{}, len(src))
func copyMap(src map[string]any) map[string]any {
m := make(map[string]any, len(src))
maps.Copy(m, src)
return m
}
@ -198,7 +198,7 @@ func copyMap(src map[string]interface{}) map[string]interface{} {
// coalesceValues builds up a values map for a particular chart.
//
// Values in v will override the values in the chart.
func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, prefix string, merge bool) {
func coalesceValues(printf printFn, c chart.Charter, v map[string]any, prefix string, merge bool) {
ch, err := chart.NewAccessor(c)
if err != nil {
return
@ -210,7 +210,7 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, p
// the original c.Values is altered. Creating a deep copy stops the problem.
// This section is fault-tolerant as there is no ability to return an error.
valuesCopy, err := copystructure.Copy(ch.Values())
var vc map[string]interface{}
var vc map[string]any
var ok bool
if err != nil {
// If there is an error something is wrong with copying c.Values it
@ -220,7 +220,7 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, p
printf("warning: unable to copy values, err: %s", err)
vc = ch.Values()
} else {
vc, ok = valuesCopy.(map[string]interface{})
vc, ok = valuesCopy.(map[string]any)
if !ok {
// c.Values has a map[string]interface{} structure. If the copy of
// it cannot be treated as map[string]interface{} there is something
@ -238,9 +238,9 @@ func coalesceValues(printf printFn, c chart.Charter, v map[string]interface{}, p
// This allows Helm's various sources of values (value files or --set) to
// remove incompatible keys from any previous chart, file, or set values.
delete(v, key)
} else if dest, ok := value.(map[string]interface{}); ok {
} else if dest, ok := value.(map[string]any); ok {
// if v[key] is a table, merge nv's val table into v[key].
src, ok := val.(map[string]interface{})
src, ok := val.(map[string]any)
if !ok {
// If the original value is nil, there is nothing to coalesce, so we don't print
// the warning
@ -283,18 +283,18 @@ func childChartMergeTrue(chrt chart.Charter, key string, merge bool) bool {
// CoalesceTables merges a source map into a destination map.
//
// dest is considered authoritative.
func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} {
func CoalesceTables(dst, src map[string]any) map[string]any {
return coalesceTablesFullKey(log.Printf, dst, src, "", false)
}
func MergeTables(dst, src map[string]interface{}) map[string]interface{} {
func MergeTables(dst, src map[string]any) map[string]any {
return coalesceTablesFullKey(log.Printf, dst, src, "", true)
}
// coalesceTablesFullKey merges a source map into a destination map.
//
// dest is considered authoritative.
func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, prefix string, merge bool) map[string]interface{} {
func coalesceTablesFullKey(printf printFn, dst, src map[string]any, prefix string, merge bool) map[string]any {
// When --reuse-values is set but there are no modifications yet, return new values
if src == nil {
return dst
@ -330,7 +330,7 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, pref
dst[key] = val
} else if istable(val) {
if istable(dv) {
coalesceTablesFullKey(printf, dv.(map[string]interface{}), val.(map[string]interface{}), fullkey, merge)
coalesceTablesFullKey(printf, dv.(map[string]any), val.(map[string]any), fullkey, merge)
} else {
printf("warning: cannot overwrite table with non table for %s (%v)", fullkey, val)
}
@ -342,7 +342,7 @@ func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, pref
}
// istable is a special-purpose function to see if the present thing matches the definition of a YAML table.
func istable(v interface{}) bool {
_, ok := v.(map[string]interface{})
func istable(v any) bool {
_, ok := v.(map[string]any)
return ok
}

View file

@ -75,65 +75,65 @@ func TestCoalesceValues(t *testing.T) {
c := withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "moby"},
Values: map[string]interface{}{
Values: map[string]any{
"back": "exists",
"bottom": "exists",
"front": "exists",
"left": "exists",
"name": "moby",
"nested": map[string]interface{}{"boat": true},
"nested": map[string]any{"boat": true},
"override": "bad",
"right": "exists",
"scope": "moby",
"top": "nope",
"global": map[string]interface{}{
"nested2": map[string]interface{}{"l0": "moby"},
"global": map[string]any{
"nested2": map[string]any{"l0": "moby"},
},
"pequod": map[string]interface{}{
"pequod": map[string]any{
"boat": "maybe",
"ahab": map[string]interface{}{
"ahab": map[string]any{
"boat": "maybe",
"nested": map[string]interface{}{"boat": "maybe"},
"nested": map[string]any{"boat": "maybe"},
},
},
},
},
withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "pequod"},
Values: map[string]interface{}{
Values: map[string]any{
"name": "pequod",
"scope": "pequod",
"global": map[string]interface{}{
"nested2": map[string]interface{}{"l1": "pequod"},
"global": map[string]any{
"nested2": map[string]any{"l1": "pequod"},
},
"boat": false,
"ahab": map[string]interface{}{
"ahab": map[string]any{
"boat": false,
"nested": map[string]interface{}{"boat": false},
"nested": map[string]any{"boat": false},
},
},
},
&chart.Chart{
Metadata: &chart.Metadata{Name: "ahab"},
Values: map[string]interface{}{
"global": map[string]interface{}{
"nested": map[string]interface{}{"foo": "bar", "foo2": "bar2"},
"nested2": map[string]interface{}{"l2": "ahab"},
Values: map[string]any{
"global": map[string]any{
"nested": map[string]any{"foo": "bar", "foo2": "bar2"},
"nested2": map[string]any{"l2": "ahab"},
},
"scope": "ahab",
"name": "ahab",
"boat": true,
"nested": map[string]interface{}{"foo": false, "boat": true},
"object": map[string]interface{}{"foo": "bar"},
"nested": map[string]any{"foo": false, "boat": true},
"object": map[string]any{"foo": "bar"},
},
},
),
&chart.Chart{
Metadata: &chart.Metadata{Name: "spouter"},
Values: map[string]interface{}{
Values: map[string]any{
"scope": "spouter",
"global": map[string]interface{}{
"nested2": map[string]interface{}{"l1": "spouter"},
"global": map[string]any{
"nested2": map[string]any{"l1": "spouter"},
},
},
},
@ -215,21 +215,21 @@ func TestCoalesceValues(t *testing.T) {
}
}
if _, ok := v["nested"].(map[string]interface{})["boat"]; ok {
if _, ok := v["nested"].(map[string]any)["boat"]; ok {
t.Error("Expected nested boat key to be removed, still present")
}
subchart := v["pequod"].(map[string]interface{})
subchart := v["pequod"].(map[string]any)
if _, ok := subchart["boat"]; ok {
t.Error("Expected subchart boat key to be removed, still present")
}
subsubchart := subchart["ahab"].(map[string]interface{})
subsubchart := subchart["ahab"].(map[string]any)
if _, ok := subsubchart["boat"]; ok {
t.Error("Expected sub-subchart ahab boat key to be removed, still present")
}
if _, ok := subsubchart["nested"].(map[string]interface{})["boat"]; ok {
if _, ok := subsubchart["nested"].(map[string]any)["boat"]; ok {
t.Error("Expected sub-subchart nested boat key to be removed, still present")
}
@ -241,7 +241,7 @@ func TestCoalesceValues(t *testing.T) {
is.Equal(valsCopy, vals)
}
func ttpl(tpl string, v map[string]interface{}) (string, error) {
func ttpl(tpl string, v map[string]any) (string, error) {
var b bytes.Buffer
tt := template.Must(template.New("t").Parse(tpl))
err := tt.Execute(&b, v)
@ -253,52 +253,52 @@ func TestMergeValues(t *testing.T) {
c := withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "moby"},
Values: map[string]interface{}{
Values: map[string]any{
"back": "exists",
"bottom": "exists",
"front": "exists",
"left": "exists",
"name": "moby",
"nested": map[string]interface{}{"boat": true},
"nested": map[string]any{"boat": true},
"override": "bad",
"right": "exists",
"scope": "moby",
"top": "nope",
"global": map[string]interface{}{
"nested2": map[string]interface{}{"l0": "moby"},
"global": map[string]any{
"nested2": map[string]any{"l0": "moby"},
},
},
},
withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "pequod"},
Values: map[string]interface{}{
Values: map[string]any{
"name": "pequod",
"scope": "pequod",
"global": map[string]interface{}{
"nested2": map[string]interface{}{"l1": "pequod"},
"global": map[string]any{
"nested2": map[string]any{"l1": "pequod"},
},
},
},
&chart.Chart{
Metadata: &chart.Metadata{Name: "ahab"},
Values: map[string]interface{}{
"global": map[string]interface{}{
"nested": map[string]interface{}{"foo": "bar"},
"nested2": map[string]interface{}{"l2": "ahab"},
Values: map[string]any{
"global": map[string]any{
"nested": map[string]any{"foo": "bar"},
"nested2": map[string]any{"l2": "ahab"},
},
"scope": "ahab",
"name": "ahab",
"boat": true,
"nested": map[string]interface{}{"foo": false, "bar": true},
"nested": map[string]any{"foo": false, "bar": true},
},
},
),
&chart.Chart{
Metadata: &chart.Metadata{Name: "spouter"},
Values: map[string]interface{}{
Values: map[string]any{
"scope": "spouter",
"global": map[string]interface{}{
"nested2": map[string]interface{}{"l1": "spouter"},
"global": map[string]any{
"nested2": map[string]any{"l1": "spouter"},
},
},
},
@ -383,16 +383,16 @@ func TestMergeValues(t *testing.T) {
}
}
if _, ok := v["nested"].(map[string]interface{})["boat"]; !ok {
if _, ok := v["nested"].(map[string]any)["boat"]; !ok {
t.Error("Expected nested boat key to be present but it was removed")
}
subchart := v["pequod"].(map[string]interface{})["ahab"].(map[string]interface{})
subchart := v["pequod"].(map[string]any)["ahab"].(map[string]any)
if _, ok := subchart["boat"]; !ok {
t.Error("Expected subchart boat key to be present but it was removed")
}
if _, ok := subchart["nested"].(map[string]interface{})["bar"]; !ok {
if _, ok := subchart["nested"].(map[string]any)["bar"]; !ok {
t.Error("Expected subchart nested bar key to be present but it was removed")
}
@ -401,28 +401,28 @@ func TestMergeValues(t *testing.T) {
}
func TestCoalesceTables(t *testing.T) {
dst := map[string]interface{}{
dst := map[string]any{
"name": "Ishmael",
"address": map[string]interface{}{
"address": map[string]any{
"street": "123 Spouter Inn Ct.",
"city": "Nantucket",
"country": nil,
},
"details": map[string]interface{}{
"details": map[string]any{
"friends": []string{"Tashtego"},
},
"boat": "pequod",
"hole": nil,
}
src := map[string]interface{}{
src := map[string]any{
"occupation": "whaler",
"address": map[string]interface{}{
"address": map[string]any{
"state": "MA",
"street": "234 Spouter Inn Ct.",
"country": "US",
},
"details": "empty",
"boat": map[string]interface{}{
"boat": map[string]any{
"mast": true,
},
"hole": "black",
@ -439,7 +439,7 @@ func TestCoalesceTables(t *testing.T) {
t.Errorf("Unexpected occupation: %s", dst["occupation"])
}
addr, ok := dst["address"].(map[string]interface{})
addr, ok := dst["address"].(map[string]any)
if !ok {
t.Fatal("Address went away.")
}
@ -460,7 +460,7 @@ func TestCoalesceTables(t *testing.T) {
t.Error("The country is not left out.")
}
if det, ok := dst["details"].(map[string]interface{}); !ok {
if det, ok := dst["details"].(map[string]any); !ok {
t.Fatalf("Details is the wrong type: %v", dst["details"])
} else if _, ok := det["friends"]; !ok {
t.Error("Could not find your friends. Maybe you don't have any. :-(")
@ -474,14 +474,14 @@ func TestCoalesceTables(t *testing.T) {
t.Error("The hole still exists.")
}
dst2 := map[string]interface{}{
dst2 := map[string]any{
"name": "Ishmael",
"address": map[string]interface{}{
"address": map[string]any{
"street": "123 Spouter Inn Ct.",
"city": "Nantucket",
"country": "US",
},
"details": map[string]interface{}{
"details": map[string]any{
"friends": []string{"Tashtego"},
},
"boat": "pequod",
@ -496,7 +496,7 @@ func TestCoalesceTables(t *testing.T) {
t.Errorf("Unexpected name: %s", dst2["name"])
}
addr2, ok := dst2["address"].(map[string]interface{})
addr2, ok := dst2["address"].(map[string]any)
if !ok {
t.Fatal("Address went away.")
}
@ -513,7 +513,7 @@ func TestCoalesceTables(t *testing.T) {
t.Errorf("Unexpected Country: %v", addr2["country"])
}
if det2, ok := dst2["details"].(map[string]interface{}); !ok {
if det2, ok := dst2["details"].(map[string]any); !ok {
t.Fatalf("Details is the wrong type: %v", dst2["details"])
} else if _, ok := det2["friends"]; !ok {
t.Error("Could not find your friends. Maybe you don't have any. :-(")
@ -529,28 +529,28 @@ func TestCoalesceTables(t *testing.T) {
}
func TestMergeTables(t *testing.T) {
dst := map[string]interface{}{
dst := map[string]any{
"name": "Ishmael",
"address": map[string]interface{}{
"address": map[string]any{
"street": "123 Spouter Inn Ct.",
"city": "Nantucket",
"country": nil,
},
"details": map[string]interface{}{
"details": map[string]any{
"friends": []string{"Tashtego"},
},
"boat": "pequod",
"hole": nil,
}
src := map[string]interface{}{
src := map[string]any{
"occupation": "whaler",
"address": map[string]interface{}{
"address": map[string]any{
"state": "MA",
"street": "234 Spouter Inn Ct.",
"country": "US",
},
"details": "empty",
"boat": map[string]interface{}{
"boat": map[string]any{
"mast": true,
},
"hole": "black",
@ -567,7 +567,7 @@ func TestMergeTables(t *testing.T) {
t.Errorf("Unexpected occupation: %s", dst["occupation"])
}
addr, ok := dst["address"].(map[string]interface{})
addr, ok := dst["address"].(map[string]any)
if !ok {
t.Fatal("Address went away.")
}
@ -590,7 +590,7 @@ func TestMergeTables(t *testing.T) {
t.Error("The country is left out.")
}
if det, ok := dst["details"].(map[string]interface{}); !ok {
if det, ok := dst["details"].(map[string]any); !ok {
t.Fatalf("Details is the wrong type: %v", dst["details"])
} else if _, ok := det["friends"]; !ok {
t.Error("Could not find your friends. Maybe you don't have any. :-(")
@ -606,14 +606,14 @@ func TestMergeTables(t *testing.T) {
t.Error("The hole no longer exists.")
}
dst2 := map[string]interface{}{
dst2 := map[string]any{
"name": "Ishmael",
"address": map[string]interface{}{
"address": map[string]any{
"street": "123 Spouter Inn Ct.",
"city": "Nantucket",
"country": "US",
},
"details": map[string]interface{}{
"details": map[string]any{
"friends": []string{"Tashtego"},
},
"boat": "pequod",
@ -629,7 +629,7 @@ func TestMergeTables(t *testing.T) {
t.Errorf("Unexpected name: %s", dst2["name"])
}
addr2, ok := dst2["address"].(map[string]interface{})
addr2, ok := dst2["address"].(map[string]any)
if !ok {
t.Fatal("Address went away.")
}
@ -646,7 +646,7 @@ func TestMergeTables(t *testing.T) {
t.Errorf("Unexpected Country: %v", addr2["country"])
}
if det2, ok := dst2["details"].(map[string]interface{}); !ok {
if det2, ok := dst2["details"].(map[string]any); !ok {
t.Fatalf("Details is the wrong type: %v", dst2["details"])
} else if _, ok := det2["friends"]; !ok {
t.Error("Could not find your friends. Maybe you don't have any. :-(")
@ -669,24 +669,24 @@ func TestCoalesceValuesWarnings(t *testing.T) {
c := withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "level1"},
Values: map[string]interface{}{
Values: map[string]any{
"name": "moby",
},
},
withDeps(&chart.Chart{
Metadata: &chart.Metadata{Name: "level2"},
Values: map[string]interface{}{
Values: map[string]any{
"name": "pequod",
},
},
&chart.Chart{
Metadata: &chart.Metadata{Name: "level3"},
Values: map[string]interface{}{
Values: map[string]any{
"name": "ahab",
"boat": true,
"spear": map[string]interface{}{
"spear": map[string]any{
"tip": true,
"sail": map[string]interface{}{
"sail": map[string]any{
"cotton": true,
},
},
@ -695,12 +695,12 @@ func TestCoalesceValuesWarnings(t *testing.T) {
),
)
vals := map[string]interface{}{
"level2": map[string]interface{}{
"level3": map[string]interface{}{
"boat": map[string]interface{}{"mast": true},
"spear": map[string]interface{}{
"tip": map[string]interface{}{
vals := map[string]any{
"level2": map[string]any{
"level3": map[string]any{
"boat": map[string]any{"mast": true},
"spear": map[string]any{
"tip": map[string]any{
"sharp": true,
},
"sail": true,
@ -710,7 +710,7 @@ func TestCoalesceValuesWarnings(t *testing.T) {
}
warnings := make([]string, 0)
printf := func(format string, v ...interface{}) {
printf := func(format string, v ...any) {
t.Logf(format, v...)
warnings = append(warnings, fmt.Sprintf(format, v...))
}

View file

@ -138,9 +138,9 @@ func TestValidateAgainstSchema(t *testing.T) {
}
chrt.AddDependency(subchart)
vals := map[string]interface{}{
vals := map[string]any{
"name": "John",
"subchart": map[string]interface{}{
"subchart": map[string]any{
"age": 25,
},
}
@ -165,9 +165,9 @@ func TestValidateAgainstSchemaNegative(t *testing.T) {
}
chrt.AddDependency(subchart)
vals := map[string]interface{}{
vals := map[string]any{
"name": "John",
"subchart": map[string]interface{}{},
"subchart": map[string]any{},
}
var errString string
@ -200,9 +200,9 @@ func TestValidateAgainstSchema2020(t *testing.T) {
}
chrt.AddDependency(subchart)
vals := map[string]interface{}{
vals := map[string]any{
"name": "John",
"subchart": map[string]interface{}{
"subchart": map[string]any{
"data": []any{"hello", 12},
},
}
@ -227,9 +227,9 @@ func TestValidateAgainstSchema2020Negative(t *testing.T) {
}
chrt.AddDependency(subchart)
vals := map[string]interface{}{
vals := map[string]any{
"name": "John",
"subchart": map[string]interface{}{
"subchart": map[string]any{
"data": []any{12},
},
}
@ -294,7 +294,7 @@ func TestValidateAgainstSingleSchema_UnresolvedURN_Ignored(t *testing.T) {
"$schema": "https://json-schema.org/draft-07/schema#",
"$ref": "urn:example:helm:schemas:v1:helm-schema-validation-conditions:v1/helmSchemaValidation-true"
}`)
vals := map[string]interface{}{"any": "value"}
vals := map[string]any{"any": "value"}
if err := ValidateAgainstSingleSchema(vals, schema); err != nil {
t.Fatalf("expected no error when URN unresolved is ignored, got: %v", err)
}

View file

@ -26,14 +26,14 @@ import (
// ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files
//
// This takes both ReleaseOptions and Capabilities to merge into the render values.
func ToRenderValues(chrt chart.Charter, chrtVals map[string]interface{}, options common.ReleaseOptions, caps *common.Capabilities) (common.Values, error) {
func ToRenderValues(chrt chart.Charter, chrtVals map[string]any, options common.ReleaseOptions, caps *common.Capabilities) (common.Values, error) {
return ToRenderValuesWithSchemaValidation(chrt, chrtVals, options, caps, false)
}
// ToRenderValuesWithSchemaValidation composes the struct from the data coming from the Releases, Charts and Values files
//
// This takes both ReleaseOptions and Capabilities to merge into the render values.
func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string]interface{}, options common.ReleaseOptions, caps *common.Capabilities, skipSchemaValidation bool) (common.Values, error) {
func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string]any, options common.ReleaseOptions, caps *common.Capabilities, skipSchemaValidation bool) (common.Values, error) {
if caps == nil {
caps = common.DefaultCapabilities
}
@ -41,10 +41,10 @@ func ToRenderValuesWithSchemaValidation(chrt chart.Charter, chrtVals map[string]
if err != nil {
return nil, err
}
top := map[string]interface{}{
top := map[string]any{
"Chart": accessor.MetadataAsMap(),
"Capabilities": caps,
"Release": map[string]interface{}{
"Release": map[string]any{
"Name": options.Name,
"Namespace": options.Namespace,
"IsUpgrade": options.IsUpgrade,

View file

@ -26,17 +26,17 @@ import (
func TestToRenderValues(t *testing.T) {
chartValues := map[string]interface{}{
chartValues := map[string]any{
"name": "al Rashid",
"where": map[string]interface{}{
"where": map[string]any{
"city": "Basrah",
"title": "caliph",
},
}
overrideValues := map[string]interface{}{
overrideValues := map[string]any{
"name": "Haroun",
"where": map[string]interface{}{
"where": map[string]any{
"city": "Baghdad",
"date": "809 CE",
},
@ -67,11 +67,11 @@ func TestToRenderValues(t *testing.T) {
}
// Ensure that the top-level values are all set.
metamap := res["Chart"].(map[string]interface{})
metamap := res["Chart"].(map[string]any)
if name := metamap["Name"]; name.(string) != "test" {
t.Errorf("Expected chart name 'test', got %q", name)
}
relmap := res["Release"].(map[string]interface{})
relmap := res["Release"].(map[string]any)
if name := relmap["Name"]; name.(string) != "Seven Voyages" {
t.Errorf("Expected release name 'Seven Voyages', got %q", name)
}
@ -98,7 +98,7 @@ func TestToRenderValues(t *testing.T) {
if vals["name"] != "Haroun" {
t.Errorf("Expected 'Haroun', got %q (%v)", vals["name"], vals)
}
where := vals["where"].(map[string]interface{})
where := vals["where"].(map[string]any)
expects := map[string]string{
"city": "Baghdad",
"date": "809 CE",

View file

@ -19,21 +19,21 @@ import (
common "helm.sh/helm/v4/pkg/chart/common"
)
type Charter interface{}
type Charter any
type Dependency interface{}
type Dependency any
type Accessor interface {
Name() string
IsRoot() bool
MetadataAsMap() map[string]interface{}
MetadataAsMap() map[string]any
Files() []*common.File
Templates() []*common.File
ChartFullPath() string
IsLibraryChart() bool
Dependencies() []Charter
MetaDependencies() []Dependency
Values() map[string]interface{}
Values() map[string]any
Schema() []byte
Deprecated() bool
}

View file

@ -70,15 +70,15 @@ func Chartfile(linter *support.Linter) {
linter.RunLinterRule(support.WarningSev, chartFileName, validateChartVersionStrictSemVerV2(chartFile))
}
func validateChartVersionType(data map[string]interface{}) error {
func validateChartVersionType(data map[string]any) error {
return isStringValue(data, "version")
}
func validateChartAppVersionType(data map[string]interface{}) error {
func validateChartAppVersionType(data map[string]any) error {
return isStringValue(data, "appVersion")
}
func isStringValue(data map[string]interface{}, key string) error {
func isStringValue(data map[string]any, key string) error {
value, ok := data[key]
if !ok {
return nil
@ -225,12 +225,12 @@ func validateChartType(cf *chart.Metadata) error {
// loadChartFileForTypeCheck loads the Chart.yaml
// in a generic form of a map[string]interface{}, so that the type
// of the values can be checked
func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) {
func loadChartFileForTypeCheck(filename string) (map[string]any, error) {
b, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
y := make(map[string]interface{})
y := make(map[string]any)
err = yaml.Unmarshal(b, &y)
return y, err
}

View file

@ -32,7 +32,7 @@ import (
// they are only tested for well-formedness.
//
// If additional values are supplied, they are coalesced into the values in values.yaml.
func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]interface{}, skipSchemaValidation bool) {
func ValuesWithOverrides(linter *support.Linter, valueOverrides map[string]any, skipSchemaValidation bool) {
file := "values.yaml"
vf := filepath.Join(linter.ChartDir, file)
fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf))
@ -52,7 +52,7 @@ func validateValuesFileExistence(valuesPath string) error {
return nil
}
func validateValuesFile(valuesPath string, overrides map[string]interface{}, skipSchemaValidation bool) error {
func validateValuesFile(valuesPath string, overrides map[string]any, skipSchemaValidation bool) error {
values, err := common.ReadValuesFile(valuesPath)
if err != nil {
return fmt.Errorf("unable to parse YAML: %w", err)
@ -63,7 +63,7 @@ func validateValuesFile(valuesPath string, overrides map[string]interface{}, ski
// We could change that. For now, though, we retain that strategy, and thus can
// coalesce tables (like reuse-values does) instead of doing the full chart
// CoalesceValues
coalescedValues := util.CoalesceTables(make(map[string]interface{}, len(overrides)), overrides)
coalescedValues := util.CoalesceTables(make(map[string]any, len(overrides)), overrides)
coalescedValues = util.CoalesceTables(coalescedValues, values)
ext := filepath.Ext(valuesPath)

View file

@ -67,7 +67,7 @@ func TestValidateValuesFileWellFormed(t *testing.T) {
`
tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml))
valfile := filepath.Join(tmpdir, "values.yaml")
if err := validateValuesFile(valfile, map[string]interface{}{}, false); err == nil {
if err := validateValuesFile(valfile, map[string]any{}, false); err == nil {
t.Fatal("expected values file to fail parsing")
}
}
@ -78,7 +78,7 @@ func TestValidateValuesFileSchema(t *testing.T) {
createTestingSchema(t, tmpdir)
valfile := filepath.Join(tmpdir, "values.yaml")
if err := validateValuesFile(valfile, map[string]interface{}{}, false); err != nil {
if err := validateValuesFile(valfile, map[string]any{}, false); err != nil {
t.Fatalf("Failed validation with %s", err)
}
}
@ -91,7 +91,7 @@ func TestValidateValuesFileSchemaFailure(t *testing.T) {
valfile := filepath.Join(tmpdir, "values.yaml")
err := validateValuesFile(valfile, map[string]interface{}{}, false)
err := validateValuesFile(valfile, map[string]any{}, false)
if err == nil {
t.Fatal("expected values file to fail parsing")
}
@ -107,7 +107,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T
valfile := filepath.Join(tmpdir, "values.yaml")
err := validateValuesFile(valfile, map[string]interface{}{}, true)
err := validateValuesFile(valfile, map[string]any{}, true)
if err != nil {
t.Fatal("expected values file to pass parsing because of skipSchemaValidation")
}
@ -115,7 +115,7 @@ func TestValidateValuesFileSchemaFailureButWithSkipSchemaValidation(t *testing.T
func TestValidateValuesFileSchemaOverrides(t *testing.T) {
yaml := "username: admin"
overrides := map[string]interface{}{
overrides := map[string]any{
"password": "swordfish",
}
tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml))
@ -131,24 +131,24 @@ func TestValidateValuesFile(t *testing.T) {
tests := []struct {
name string
yaml string
overrides map[string]interface{}
overrides map[string]any
errorMessage string
}{
{
name: "value added",
yaml: "username: admin",
overrides: map[string]interface{}{"password": "swordfish"},
overrides: map[string]any{"password": "swordfish"},
},
{
name: "value not overridden",
yaml: "username: admin\npassword:",
overrides: map[string]interface{}{"username": "anotherUser"},
overrides: map[string]any{"username": "anotherUser"},
errorMessage: "- at '/password': got null, want string",
},
{
name: "value overridden",
yaml: "username: admin\npassword:",
overrides: map[string]interface{}{"username": "anotherUser", "password": "swordfish"},
overrides: map[string]any{"username": "anotherUser", "password": "swordfish"},
},
}

View file

@ -209,11 +209,11 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) {
//
// The reader is expected to contain one or more YAML documents, the values of which are merged.
// And the values can be either a chart's default values or user-supplied values.
func LoadValues(data io.Reader) (map[string]interface{}, error) {
values := map[string]interface{}{}
func LoadValues(data io.Reader) (map[string]any, error) {
values := map[string]any{}
reader := utilyaml.NewYAMLReader(bufio.NewReader(data))
for {
currentMap := map[string]interface{}{}
currentMap := map[string]any{}
raw, err := reader.Read()
if err != nil {
if errors.Is(err, io.EOF) {
@ -231,13 +231,13 @@ func LoadValues(data io.Reader) (map[string]interface{}, error) {
// MergeMaps merges two maps. If a key exists in both maps, the value from b will be used.
// If the value is a map, the maps will be merged recursively.
func MergeMaps(a, b map[string]interface{}) map[string]interface{} {
out := make(map[string]interface{}, len(a))
func MergeMaps(a, b map[string]any) map[string]any {
out := make(map[string]any, len(a))
maps.Copy(out, a)
for k, v := range b {
if v, ok := v.(map[string]interface{}); ok {
if v, ok := v.(map[string]any); ok {
if bv, ok := out[k]; ok {
if bv, ok := bv.(map[string]interface{}); ok {
if bv, ok := bv.(map[string]any); ok {
out[k] = MergeMaps(bv, v)
continue
}

View file

@ -669,7 +669,7 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error {
return fmt.Errorf("reading values file: %w", err)
}
var m map[string]interface{}
var m map[string]any
if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil {
return fmt.Errorf("transforming values file: %w", err)
}

View file

@ -16,6 +16,7 @@ limitations under the License.
package util
import (
"errors"
"fmt"
"log/slog"
"strings"
@ -44,6 +45,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values,
if len(c) > 0 {
// retrieve value
vv, err := cvals.PathValue(cpath + c)
var errNoValue common.ErrNoValue
if err == nil {
// if not bool, warn
if bv, ok := vv.(bool); ok {
@ -51,7 +53,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals common.Values,
break
}
slog.Warn("returned non-bool value", "path", c, "chart", r.Name)
} else if _, ok := err.(common.ErrNoValue); !ok {
} else if !errors.As(err, &errNoValue) {
// this is a real error
slog.Warn("the method PathValue returned error", slog.Any("error", err))
}

View file

@ -42,8 +42,8 @@ type Options struct {
// MergeValues merges values from files specified via -f/--values and directly
// via --set-json, --set, --set-string, or --set-file, marshaling them to YAML
func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) {
base := map[string]interface{}{}
func (opts *Options) MergeValues(p getter.Providers) (map[string]any, error) {
base := map[string]any{}
// User specified a values files via -f/--values
for _, filePath := range opts.ValueFiles {
@ -64,7 +64,7 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er
trimmedValue := strings.TrimSpace(value)
if len(trimmedValue) > 0 && trimmedValue[0] == '{' {
// If value is JSON object format, parse it as map
var jsonMap map[string]interface{}
var jsonMap map[string]any
if err := json.Unmarshal([]byte(trimmedValue), &jsonMap); err != nil {
return nil, fmt.Errorf("failed parsing --set-json data JSON: %s", value)
}
@ -93,7 +93,7 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er
// User specified a value via --set-file
for _, value := range opts.FileValues {
reader := func(rs []rune) (interface{}, error) {
reader := func(rs []rune) (any, error) {
bytes, err := readFile(string(rs), p)
if err != nil {
return nil, err

View file

@ -23,6 +23,9 @@ import (
"github.com/spf13/cobra"
chartv3 "helm.sh/helm/v4/internal/chart/v3"
chartutilv3 "helm.sh/helm/v4/internal/chart/v3/util"
"helm.sh/helm/v4/internal/gates"
chart "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/cmd/require"
@ -51,9 +54,10 @@ will be overwritten, but other files will be left alone.
`
type createOptions struct {
starter string // --starter
name string
starterDir string
starter string // --starter
name string
starterDir string
chartAPIVersion string // --chart-api-version
}
func newCreateCmd(out io.Writer) *cobra.Command {
@ -81,12 +85,32 @@ func newCreateCmd(out io.Writer) *cobra.Command {
}
cmd.Flags().StringVarP(&o.starter, "starter", "p", "", "the name or absolute path to Helm starter scaffold")
cmd.Flags().StringVar(&o.chartAPIVersion, "chart-api-version", chart.APIVersionV2, "chart API version to use (v2 or v3)")
if !gates.ChartV3.IsEnabled() {
cmd.Flags().MarkHidden("chart-api-version")
}
return cmd
}
func (o *createOptions) run(out io.Writer) error {
fmt.Fprintf(out, "Creating %s\n", o.name)
switch o.chartAPIVersion {
case chart.APIVersionV2, "":
return o.createV2Chart(out)
case chartv3.APIVersionV3:
if !gates.ChartV3.IsEnabled() {
return gates.ChartV3.Error()
}
return o.createV3Chart(out)
default:
return fmt.Errorf("unsupported chart API version: %s (supported: v2, v3)", o.chartAPIVersion)
}
}
func (o *createOptions) createV2Chart(out io.Writer) error {
chartname := filepath.Base(o.name)
cfile := &chart.Metadata{
Name: chartname,
@ -111,3 +135,29 @@ func (o *createOptions) run(out io.Writer) error {
_, err := chartutil.Create(chartname, filepath.Dir(o.name))
return err
}
func (o *createOptions) createV3Chart(out io.Writer) error {
chartname := filepath.Base(o.name)
cfile := &chartv3.Metadata{
Name: chartname,
Description: "A Helm chart for Kubernetes",
Type: "application",
Version: "0.1.0",
AppVersion: "0.1.0",
APIVersion: chartv3.APIVersionV3,
}
if o.starter != "" {
// Create from the starter
lstarter := filepath.Join(o.starterDir, o.starter)
// If path is absolute, we don't want to prefix it with helm starters folder
if filepath.IsAbs(o.starter) {
lstarter = o.starter
}
return chartutilv3.CreateFrom(cfile, filepath.Dir(o.name), lstarter)
}
chartutilv3.Stderr = out
_, err := chartutilv3.Create(chartname, filepath.Dir(o.name))
return err
}

View file

@ -22,9 +22,13 @@ import (
"path/filepath"
"testing"
chartv3 "helm.sh/helm/v4/internal/chart/v3"
chartutilv3 "helm.sh/helm/v4/internal/chart/v3/util"
"helm.sh/helm/v4/internal/gates"
"helm.sh/helm/v4/internal/test/ensure"
chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/chart/v2/loader"
chart "helm.sh/helm/v4/pkg/chart"
chartloader "helm.sh/helm/v4/pkg/chart/loader"
chartv2 "helm.sh/helm/v4/pkg/chart/v2"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
"helm.sh/helm/v4/pkg/helmpath"
)
@ -46,143 +50,155 @@ func TestCreateCmd(t *testing.T) {
t.Fatalf("chart is not directory")
}
c, err := loader.LoadDir(cname)
c, err := chartloader.LoadDir(cname)
if err != nil {
t.Fatal(err)
}
if c.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, c.Name())
acc, err := chart.NewAccessor(c)
if err != nil {
t.Fatal(err)
}
if c.Metadata.APIVersion != chart.APIVersionV2 {
t.Errorf("Wrong API version: %q", c.Metadata.APIVersion)
if acc.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, acc.Name())
}
metadata := acc.MetadataAsMap()
apiVersion, ok := metadata["APIVersion"].(string)
if !ok {
t.Fatal("APIVersion not found in metadata")
}
if apiVersion != chartv2.APIVersionV2 {
t.Errorf("Wrong API version: %q", apiVersion)
}
}
func TestCreateStarterCmd(t *testing.T) {
t.Chdir(t.TempDir())
ensure.HelmHome(t)
cname := "testchart"
defer resetEnv()()
// Create a starter.
starterchart := helmpath.DataPath("starters")
os.MkdirAll(starterchart, 0o755)
if dest, err := chartutil.Create("starterchart", starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err)
} else {
t.Logf("Created %s", dest)
}
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil {
t.Fatalf("Could not write template: %s", err)
tests := []struct {
name string
chartAPIVersion string
useAbsolutePath bool
expectedVersion string
}{
{
name: "v2 with relative starter path",
chartAPIVersion: "",
useAbsolutePath: false,
expectedVersion: chartv2.APIVersionV2,
},
{
name: "v2 with absolute starter path",
chartAPIVersion: "",
useAbsolutePath: true,
expectedVersion: chartv2.APIVersionV2,
},
{
name: "v3 with relative starter path",
chartAPIVersion: "v3",
useAbsolutePath: false,
expectedVersion: chartv3.APIVersionV3,
},
}
// Run a create
if _, _, err := executeActionCommand(fmt.Sprintf("create --starter=starterchart %s", cname)); err != nil {
t.Errorf("Failed to run create: %s", err)
return
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Chdir(t.TempDir())
ensure.HelmHome(t)
defer resetEnv()()
// Test that the chart is there
if fi, err := os.Stat(cname); err != nil {
t.Fatalf("no chart directory: %s", err)
} else if !fi.IsDir() {
t.Fatalf("chart is not directory")
}
c, err := loader.LoadDir(cname)
if err != nil {
t.Fatal(err)
}
if c.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, c.Name())
}
if c.Metadata.APIVersion != chart.APIVersionV2 {
t.Errorf("Wrong API version: %q", c.Metadata.APIVersion)
}
expectedNumberOfTemplates := 10
if l := len(c.Templates); l != expectedNumberOfTemplates {
t.Errorf("Expected %d templates, got %d", expectedNumberOfTemplates, l)
}
found := false
for _, tpl := range c.Templates {
if tpl.Name == "templates/foo.tpl" {
found = true
if data := string(tpl.Data); data != "test" {
t.Errorf("Expected template 'test', got %q", data)
// Enable feature gate for v3 charts
if tt.chartAPIVersion == "v3" {
t.Setenv(string(gates.ChartV3), "1")
}
}
}
if !found {
t.Error("Did not find foo.tpl")
}
}
func TestCreateStarterAbsoluteCmd(t *testing.T) {
t.Chdir(t.TempDir())
defer resetEnv()()
ensure.HelmHome(t)
cname := "testchart"
cname := "testchart"
// Create a starter.
starterchart := helmpath.DataPath("starters")
os.MkdirAll(starterchart, 0o755)
if dest, err := chartutil.Create("starterchart", starterchart); err != nil {
t.Fatalf("Could not create chart: %s", err)
} else {
t.Logf("Created %s", dest)
}
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil {
t.Fatalf("Could not write template: %s", err)
}
starterChartPath := filepath.Join(starterchart, "starterchart")
// Run a create
if _, _, err := executeActionCommand(fmt.Sprintf("create --starter=%s %s", starterChartPath, cname)); err != nil {
t.Errorf("Failed to run create: %s", err)
return
}
// Test that the chart is there
if fi, err := os.Stat(cname); err != nil {
t.Fatalf("no chart directory: %s", err)
} else if !fi.IsDir() {
t.Fatalf("chart is not directory")
}
c, err := loader.LoadDir(cname)
if err != nil {
t.Fatal(err)
}
if c.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, c.Name())
}
if c.Metadata.APIVersion != chart.APIVersionV2 {
t.Errorf("Wrong API version: %q", c.Metadata.APIVersion)
}
expectedNumberOfTemplates := 10
if l := len(c.Templates); l != expectedNumberOfTemplates {
t.Errorf("Expected %d templates, got %d", expectedNumberOfTemplates, l)
}
found := false
for _, tpl := range c.Templates {
if tpl.Name == "templates/foo.tpl" {
found = true
if data := string(tpl.Data); data != "test" {
t.Errorf("Expected template 'test', got %q", data)
// Create a starter using the appropriate chartutil
starterchart := helmpath.DataPath("starters")
os.MkdirAll(starterchart, 0o755)
var err error
var dest string
if tt.chartAPIVersion == "v3" {
dest, err = chartutilv3.Create("starterchart", starterchart)
} else {
dest, err = chartutil.Create("starterchart", starterchart)
}
}
}
if !found {
t.Error("Did not find foo.tpl")
if err != nil {
t.Fatalf("Could not create chart: %s", err)
}
t.Logf("Created %s", dest)
tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl")
if err := os.WriteFile(tplpath, []byte("test"), 0o644); err != nil {
t.Fatalf("Could not write template: %s", err)
}
// Build the command
starterArg := "starterchart"
if tt.useAbsolutePath {
starterArg = filepath.Join(starterchart, "starterchart")
}
cmd := fmt.Sprintf("create --starter=%s", starterArg)
if tt.chartAPIVersion == "v3" {
cmd += fmt.Sprintf(" --chart-api-version=%s", chartv3.APIVersionV3)
} else {
cmd += fmt.Sprintf(" --chart-api-version=%s", chartv2.APIVersionV2)
}
cmd += " " + cname
// Run create
if _, _, err := executeActionCommand(cmd); err != nil {
t.Fatalf("Failed to run create: %s", err)
}
// Test that the chart is there
if fi, err := os.Stat(cname); err != nil {
t.Fatalf("no chart directory: %s", err)
} else if !fi.IsDir() {
t.Fatalf("chart is not directory")
}
// Load and verify the chart
c, err := chartloader.LoadDir(cname)
if err != nil {
t.Fatal(err)
}
acc, err := chart.NewAccessor(c)
if err != nil {
t.Fatal(err)
}
chartName := acc.Name()
metadata := acc.MetadataAsMap()
apiVersion, ok := metadata["APIVersion"].(string)
if !ok {
t.Fatal("APIVersion not found in metadata")
}
var templates []string
for _, tpl := range acc.Templates() {
templates = append(templates, tpl.Name)
}
if chartName != cname {
t.Errorf("Expected %q name, got %q", cname, chartName)
}
if apiVersion != tt.expectedVersion {
t.Errorf("Wrong API version: expected %q, got %q", tt.expectedVersion, apiVersion)
}
// Verify custom template exists
found := false
for _, name := range templates {
if name == "templates/foo.tpl" {
found = true
break
}
}
if !found {
t.Error("Did not find foo.tpl")
}
})
}
}
@ -190,3 +206,101 @@ func TestCreateFileCompletion(t *testing.T) {
checkFileCompletion(t, "create", true)
checkFileCompletion(t, "create myname", false)
}
func TestCreateCmdChartAPIVersionV2(t *testing.T) {
t.Chdir(t.TempDir())
ensure.HelmHome(t)
cname := "testchart"
// Run a create with explicit v2
if _, _, err := executeActionCommand("create --chart-api-version=v2 " + cname); err != nil {
t.Fatalf("Failed to run create: %s", err)
}
// Test that the chart is there
if fi, err := os.Stat(cname); err != nil {
t.Fatalf("no chart directory: %s", err)
} else if !fi.IsDir() {
t.Fatalf("chart is not directory")
}
c, err := chartloader.LoadDir(cname)
if err != nil {
t.Fatal(err)
}
acc, err := chart.NewAccessor(c)
if err != nil {
t.Fatal(err)
}
if acc.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, acc.Name())
}
metadata := acc.MetadataAsMap()
apiVersion, ok := metadata["APIVersion"].(string)
if !ok {
t.Fatal("APIVersion not found in metadata")
}
if apiVersion != chartv2.APIVersionV2 {
t.Errorf("Wrong API version: expected %q, got %q", chartv2.APIVersionV2, apiVersion)
}
}
func TestCreateCmdChartAPIVersionV3(t *testing.T) {
t.Chdir(t.TempDir())
ensure.HelmHome(t)
t.Setenv(string(gates.ChartV3), "1")
cname := "testchart"
// Run a create with v3
if _, _, err := executeActionCommand("create --chart-api-version=v3 " + cname); err != nil {
t.Fatalf("Failed to run create: %s", err)
}
// Test that the chart is there
if fi, err := os.Stat(cname); err != nil {
t.Fatalf("no chart directory: %s", err)
} else if !fi.IsDir() {
t.Fatalf("chart is not directory")
}
c, err := chartloader.LoadDir(cname)
if err != nil {
t.Fatal(err)
}
acc, err := chart.NewAccessor(c)
if err != nil {
t.Fatal(err)
}
if acc.Name() != cname {
t.Errorf("Expected %q name, got %q", cname, acc.Name())
}
metadata := acc.MetadataAsMap()
apiVersion, ok := metadata["APIVersion"].(string)
if !ok {
t.Fatal("APIVersion not found in metadata")
}
if apiVersion != chartv3.APIVersionV3 {
t.Errorf("Wrong API version: expected %q, got %q", chartv3.APIVersionV3, apiVersion)
}
}
func TestCreateCmdInvalidChartAPIVersion(t *testing.T) {
t.Chdir(t.TempDir())
ensure.HelmHome(t)
cname := "testchart"
// Run a create with invalid version
_, _, err := executeActionCommand("create --chart-api-version=v1 " + cname)
if err == nil {
t.Fatal("Expected error for invalid API version, got nil")
}
expectedErr := "unsupported chart API version: v1 (supported: v2, v3)"
if err.Error() != expectedErr {
t.Errorf("Expected error %q, got %q", expectedErr, err.Error())
}
}

View file

@ -16,6 +16,7 @@ limitations under the License.
package cmd
import (
"errors"
"fmt"
"io"
"os"
@ -76,7 +77,8 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
man.Verify = downloader.VerifyIfPossible
}
err = man.Build()
if e, ok := err.(downloader.ErrRepoNotFound); ok {
var e downloader.ErrRepoNotFound
if errors.As(err, &e) {
return fmt.Errorf("%s. Please add the missing repos via 'helm repo add'", e.Error())
}
return err

View file

@ -240,7 +240,7 @@ func TestInstall(t *testing.T) {
// Install chart with only crds
{
name: "install chart with only crds",
cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default",
cmd: "install crd-test testdata/testcharts/chart-with-only-crds --namespace default --dry-run",
},
// Verify the user/pass works
{

View file

@ -18,6 +18,7 @@ package cmd
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"log/slog"
@ -120,7 +121,8 @@ func loadCLIPlugins(baseCmd *cobra.Command, out io.Writer) {
Stderr: os.Stderr,
}
_, err = plug.Invoke(context.Background(), input)
if execErr, ok := err.(*plugin.InvokeExecError); ok {
execErr := &plugin.InvokeExecError{}
if errors.As(err, &execErr) {
return CommandError{
error: execErr.Err,
ExitCode: execErr.ExitCode,

View file

@ -80,7 +80,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
if kubeVersion != "" {
parsedKubeVersion, err := common.ParseKubeVersion(kubeVersion)
if err != nil {
return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err)
return fmt.Errorf("invalid kube version '%s': %w", kubeVersion, err)
}
client.KubeVersion = parsedKubeVersion
}

View file

@ -18,6 +18,7 @@ package cmd
import (
"context"
"errors"
"fmt"
"io"
"log"
@ -124,7 +125,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
histClient := action.NewHistory(cfg)
histClient.Max = 1
versions, err := histClient.Run(args[0])
if err == driver.ErrReleaseNotFound || isReleaseUninstalled(versions) {
if errors.Is(err, driver.ErrReleaseNotFound) || isReleaseUninstalled(versions) {
// Only print this to stdout for table output
if outfmt == output.Table {
fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0])

View file

@ -156,7 +156,11 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
}
destfile := filepath.Join(dest, name)
if err := fileutil.AtomicWriteFile(destfile, data, 0644); err != nil {
// Use PlatformAtomicWriteFile to handle platform-specific concurrency concerns
// (Windows requires locking to avoid "Access Denied" errors when multiple
// processes write the same file)
if err := fileutil.PlatformAtomicWriteFile(destfile, data, 0644); err != nil {
return destfile, nil, err
}
@ -186,7 +190,9 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
}
}
provfile := destfile + ".prov"
if err := fileutil.AtomicWriteFile(provfile, body, 0644); err != nil {
// Use PlatformAtomicWriteFile for the provenance file as well
if err := fileutil.PlatformAtomicWriteFile(provfile, body, 0644); err != nil {
return destfile, nil, err
}
@ -380,7 +386,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (string, *url
if err != nil {
// If there is no special config, return the default HTTP client and
// swallow the error.
if err == ErrNoOwnerRepo {
if errors.Is(err, ErrNoOwnerRepo) {
// Make sure to add the ref URL as the URL for the getter
c.Options = append(c.Options, getter.WithURL(ref))
return "", u, nil

View file

@ -18,6 +18,7 @@ package downloader
import (
"crypto/sha256"
"encoding/hex"
"errors"
"os"
"path/filepath"
"testing"
@ -376,7 +377,7 @@ func TestScanReposForURL(t *testing.T) {
// A lookup failure should produce an ErrNoOwnerRepo
u = "https://no.such.repo/foo/bar-1.23.4.tgz"
if _, err = c.scanReposForURL(u, rf); err != ErrNoOwnerRepo {
if _, err = c.scanReposForURL(u, rf); !errors.Is(err, ErrNoOwnerRepo) {
t.Fatalf("expected ErrNoOwnerRepo, got %v", err)
}
}

View file

@ -0,0 +1,131 @@
//go:build windows
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package downloader
import (
"os"
"path/filepath"
"sync"
"testing"
"helm.sh/helm/v4/pkg/cli"
"helm.sh/helm/v4/pkg/getter"
"helm.sh/helm/v4/pkg/repo/v1/repotest"
)
// TestParallelDownloadTo tests that parallel downloads to the same file
// don't cause "Access Denied" errors on Windows. This test is Windows-specific
// because the file locking behavior is only needed on Windows.
func TestParallelDownloadTo(t *testing.T) {
// Set up a simple test server with a chart
srv := repotest.NewTempServer(t, repotest.WithChartSourceGlob("testdata/*.tgz"))
defer srv.Stop()
if err := srv.CreateIndex(); err != nil {
t.Fatal(err)
}
dest := t.TempDir()
cacheDir := t.TempDir()
c := ChartDownloader{
Out: os.Stderr,
RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
ContentCache: cacheDir,
Cache: &DiskCache{Root: cacheDir},
Getters: getter.All(&cli.EnvSettings{
RepositoryConfig: repoConfig,
RepositoryCache: repoCache,
ContentCache: cacheDir,
}),
}
// Use a direct URL to bypass repository lookup
chartURL := srv.URL() + "/local-subchart-0.1.0.tgz"
// Number of parallel downloads to attempt
numDownloads := 10
var wg sync.WaitGroup
errors := make([]error, numDownloads)
// Launch multiple goroutines to download the same chart simultaneously
for i := 0; i < numDownloads; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
_, _, err := c.DownloadTo(chartURL, "", dest)
errors[index] = err
}(i)
}
wg.Wait()
// Check if any download failed
failedCount := 0
for i, err := range errors {
if err != nil {
t.Logf("Download %d failed: %v", i, err)
failedCount++
}
}
// With the file locking fix, all parallel downloads should succeed
if failedCount > 0 {
t.Errorf("Parallel downloads failed: %d out of %d downloads failed due to concurrent file access", failedCount, numDownloads)
}
// Verify the file exists and is valid
expectedFile := filepath.Join(dest, "local-subchart-0.1.0.tgz")
info, err := os.Stat(expectedFile)
if err != nil {
t.Errorf("Expected file %s does not exist: %v", expectedFile, err)
} else {
// Verify the file is not empty
if info.Size() == 0 {
t.Errorf("Downloaded file %s is empty (0 bytes)", expectedFile)
}
// Verify the file has the expected size (should match the source file)
sourceFile := "testdata/local-subchart-0.1.0.tgz"
sourceInfo, err := os.Stat(sourceFile)
if err == nil && info.Size() != sourceInfo.Size() {
t.Errorf("Downloaded file size (%d bytes) doesn't match source file size (%d bytes)",
info.Size(), sourceInfo.Size())
}
// Verify it's a valid tar.gz file by checking the magic bytes
file, err := os.Open(expectedFile)
if err == nil {
defer file.Close()
// gzip magic bytes are 0x1f 0x8b
magic := make([]byte, 2)
if n, err := file.Read(magic); err == nil && n == 2 {
if magic[0] != 0x1f || magic[1] != 0x8b {
t.Errorf("Downloaded file is not a valid gzip file (magic bytes: %x)", magic)
}
}
}
// Verify no lock file was left behind
lockFile := expectedFile + ".lock"
if _, err := os.Stat(lockFile); err == nil {
t.Errorf("Lock file %s was not cleaned up", lockFile)
}
}
}

View file

@ -30,7 +30,7 @@ import (
"k8s.io/client-go/rest"
)
type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error)
type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]any, error)
// NewLookupFunction returns a function for looking up objects in the cluster.
//
@ -55,11 +55,11 @@ func (c clientProviderFromConfig) GetClientFor(apiVersion, kind string) (dynamic
}
func newLookupFunction(clientProvider ClientProvider) lookupFunc {
return func(apiversion string, kind string, namespace string, name string) (map[string]interface{}, error) {
return func(apiversion string, kind string, namespace string, name string) (map[string]any, error) {
var client dynamic.ResourceInterface
c, namespaced, err := clientProvider.GetClientFor(apiversion, kind)
if err != nil {
return map[string]interface{}{}, err
return map[string]any{}, err
}
if namespaced && namespace != "" {
client = c.Namespace(namespace)
@ -73,9 +73,9 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc {
if apierrors.IsNotFound(err) {
// Just return an empty interface when the object was not found.
// That way, users can use `if not (lookup ...)` in their templates.
return map[string]interface{}{}, nil
return map[string]any{}, nil
}
return map[string]interface{}{}, err
return map[string]any{}, err
}
return obj.UnstructuredContent(), nil
}
@ -85,9 +85,9 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc {
if apierrors.IsNotFound(err) {
// Just return an empty interface when the object was not found.
// That way, users can use `if not (lookup ...)` in their templates.
return map[string]interface{}{}, nil
return map[string]any{}, nil
}
return map[string]interface{}{}, err
return map[string]any{}, err
}
return obj.UnstructuredContent(), nil
}

View file

@ -126,24 +126,20 @@ func TestConcurrencyDownloadIndex(t *testing.T) {
// 2) read index.yaml via LoadIndexFile (read operation).
// This checks for race conditions and ensures correct behavior under concurrent read/write access.
for range 150 {
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
idx, err := repo.DownloadIndexFile()
if err != nil {
t.Errorf("Failed to download index file to %s: %v", idx, err)
}
}()
})
wg.Add(1)
go func() {
defer wg.Done()
wg.Go(func() {
_, err := LoadIndexFile(indexFName)
if err != nil {
t.Errorf("Failed to load index file: %v", err)
}
}()
})
}
wg.Wait()
}

View file

@ -20,6 +20,7 @@ import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
@ -639,7 +640,7 @@ func TestIgnoreSkippableChartValidationError(t *testing.T) {
return
}
if tc.Input != result {
if !errors.Is(tc.Input, result) {
t.Error("expected the result equal to input")
}

View file

@ -180,7 +180,7 @@ func TestConfigMapQuery(t *testing.T) {
}
_, err = cfgmaps.Query(map[string]string{"name": "notExist"})
if err != ErrReleaseNotFound {
if !errors.Is(err, ErrReleaseNotFound) {
t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err)
}
}
@ -252,7 +252,7 @@ func TestConfigMapDelete(t *testing.T) {
// perform the delete on a non-existent release
_, err := cfgmaps.Delete("nonexistent")
if err != ErrReleaseNotFound {
if !errors.Is(err, ErrReleaseNotFound) {
t.Fatalf("Expected ErrReleaseNotFound: got {%v}", err)
}

View file

@ -15,6 +15,7 @@ package driver
import (
"database/sql/driver"
"errors"
"fmt"
"reflect"
"regexp"
@ -447,7 +448,7 @@ func TestSqlQuery(t *testing.T) {
_, err := sqlDriver.Query(labelSetUnknown)
if err == nil {
t.Errorf("Expected error {%v}, got nil", ErrReleaseNotFound)
} else if err != ErrReleaseNotFound {
} else if !errors.Is(err, ErrReleaseNotFound) {
t.Fatalf("failed to query for unknown smug-pigeon release: %v", err)
}

View file

@ -106,7 +106,7 @@ func (t *literalParser) key(data map[string]interface{}, nestedNameLevel int) (r
case lastRune == '=':
// found end of key: swallow the '=' and get the value
value, err := t.val()
if err == nil && err != io.EOF {
if err == nil && !errors.Is(err, io.EOF) {
return err
}
set(data, string(key), string(value))

View file

@ -26,7 +26,7 @@ import (
func TestParseLiteral(t *testing.T) {
cases := []struct {
str string
expect map[string]interface{}
expect map[string]any
err bool
}{
{
@ -35,61 +35,61 @@ func TestParseLiteral(t *testing.T) {
},
{
str: "name=",
expect: map[string]interface{}{"name": ""},
expect: map[string]any{"name": ""},
},
{
str: "name=value",
expect: map[string]interface{}{"name": "value"},
expect: map[string]any{"name": "value"},
err: false,
},
{
str: "long_int_string=1234567890",
expect: map[string]interface{}{"long_int_string": "1234567890"},
expect: map[string]any{"long_int_string": "1234567890"},
err: false,
},
{
str: "boolean=true",
expect: map[string]interface{}{"boolean": "true"},
expect: map[string]any{"boolean": "true"},
err: false,
},
{
str: "is_null=null",
expect: map[string]interface{}{"is_null": "null"},
expect: map[string]any{"is_null": "null"},
err: false,
},
{
str: "zero=0",
expect: map[string]interface{}{"zero": "0"},
expect: map[string]any{"zero": "0"},
err: false,
},
{
str: "name1=null,name2=value2",
expect: map[string]interface{}{"name1": "null,name2=value2"},
expect: map[string]any{"name1": "null,name2=value2"},
err: false,
},
{
str: "name1=value,,,tail",
expect: map[string]interface{}{"name1": "value,,,tail"},
expect: map[string]any{"name1": "value,,,tail"},
err: false,
},
{
str: "leading_zeros=00009",
expect: map[string]interface{}{"leading_zeros": "00009"},
expect: map[string]any{"leading_zeros": "00009"},
err: false,
},
{
str: "name=one two three",
expect: map[string]interface{}{"name": "one two three"},
expect: map[string]any{"name": "one two three"},
err: false,
},
{
str: "outer.inner=value",
expect: map[string]interface{}{"outer": map[string]interface{}{"inner": "value"}},
expect: map[string]any{"outer": map[string]any{"inner": "value"}},
err: false,
},
{
str: "outer.middle.inner=value",
expect: map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}},
expect: map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}},
err: false,
},
{
@ -98,7 +98,7 @@ func TestParseLiteral(t *testing.T) {
},
{
str: "name1.name2=",
expect: map[string]interface{}{"name1": map[string]interface{}{"name2": ""}},
expect: map[string]any{"name1": map[string]any{"name2": ""}},
err: false,
},
{
@ -111,20 +111,20 @@ func TestParseLiteral(t *testing.T) {
},
{
str: "name1={value1,value2}",
expect: map[string]interface{}{"name1": "{value1,value2}"},
expect: map[string]any{"name1": "{value1,value2}"},
},
// List support
{
str: "list[0]=foo",
expect: map[string]interface{}{"list": []string{"foo"}},
expect: map[string]any{"list": []string{"foo"}},
err: false,
},
{
str: "list[0].foo=bar",
expect: map[string]interface{}{
"list": []interface{}{
map[string]interface{}{"foo": "bar"},
expect: map[string]any{
"list": []any{
map[string]any{"foo": "bar"},
},
},
err: false,
@ -135,7 +135,7 @@ func TestParseLiteral(t *testing.T) {
},
{
str: "list[3]=bar",
expect: map[string]interface{}{"list": []interface{}{nil, nil, nil, "bar"}},
expect: map[string]any{"list": []any{nil, nil, nil, "bar"}},
err: false,
},
{
@ -144,133 +144,133 @@ func TestParseLiteral(t *testing.T) {
},
{
str: "noval[0]",
expect: map[string]interface{}{"noval": []interface{}{}},
expect: map[string]any{"noval": []any{}},
err: false,
},
{
str: "noval[0]=",
expect: map[string]interface{}{"noval": []interface{}{""}},
expect: map[string]any{"noval": []any{""}},
err: false,
},
{
str: "nested[0][0]=1",
expect: map[string]interface{}{"nested": []interface{}{[]interface{}{"1"}}},
expect: map[string]any{"nested": []any{[]any{"1"}}},
err: false,
},
{
str: "nested[1][1]=1",
expect: map[string]interface{}{"nested": []interface{}{nil, []interface{}{nil, "1"}}},
expect: map[string]any{"nested": []any{nil, []any{nil, "1"}}},
err: false,
},
{
str: "name1.name2[0].foo=bar",
expect: map[string]interface{}{
"name1": map[string]interface{}{
"name2": []map[string]interface{}{{"foo": "bar"}},
expect: map[string]any{
"name1": map[string]any{
"name2": []map[string]any{{"foo": "bar"}},
},
},
},
{
str: "name1.name2[1].foo=bar",
expect: map[string]interface{}{
"name1": map[string]interface{}{
"name2": []map[string]interface{}{nil, {"foo": "bar"}},
expect: map[string]any{
"name1": map[string]any{
"name2": []map[string]any{nil, {"foo": "bar"}},
},
},
},
{
str: "name1.name2[1].foo=bar",
expect: map[string]interface{}{
"name1": map[string]interface{}{
"name2": []map[string]interface{}{nil, {"foo": "bar"}},
expect: map[string]any{
"name1": map[string]any{
"name2": []map[string]any{nil, {"foo": "bar"}},
},
},
},
{
str: "]={}].",
expect: map[string]interface{}{"]": "{}]."},
expect: map[string]any{"]": "{}]."},
err: false,
},
// issue test cases: , = $ ( ) { } . \ \\
{
str: "name=val,val",
expect: map[string]interface{}{"name": "val,val"},
expect: map[string]any{"name": "val,val"},
err: false,
},
{
str: "name=val.val",
expect: map[string]interface{}{"name": "val.val"},
expect: map[string]any{"name": "val.val"},
err: false,
},
{
str: "name=val=val",
expect: map[string]interface{}{"name": "val=val"},
expect: map[string]any{"name": "val=val"},
err: false,
},
{
str: "name=val$val",
expect: map[string]interface{}{"name": "val$val"},
expect: map[string]any{"name": "val$val"},
err: false,
},
{
str: "name=(value",
expect: map[string]interface{}{"name": "(value"},
expect: map[string]any{"name": "(value"},
err: false,
},
{
str: "name=value)",
expect: map[string]interface{}{"name": "value)"},
expect: map[string]any{"name": "value)"},
err: false,
},
{
str: "name=(value)",
expect: map[string]interface{}{"name": "(value)"},
expect: map[string]any{"name": "(value)"},
err: false,
},
{
str: "name={value",
expect: map[string]interface{}{"name": "{value"},
expect: map[string]any{"name": "{value"},
err: false,
},
{
str: "name=value}",
expect: map[string]interface{}{"name": "value}"},
expect: map[string]any{"name": "value}"},
err: false,
},
{
str: "name={value}",
expect: map[string]interface{}{"name": "{value}"},
expect: map[string]any{"name": "{value}"},
err: false,
},
{
str: "name={value1,value2}",
expect: map[string]interface{}{"name": "{value1,value2}"},
expect: map[string]any{"name": "{value1,value2}"},
err: false,
},
{
str: `name=val\val`,
expect: map[string]interface{}{"name": `val\val`},
expect: map[string]any{"name": `val\val`},
err: false,
},
{
str: `name=val\\val`,
expect: map[string]interface{}{"name": `val\\val`},
expect: map[string]any{"name": `val\\val`},
err: false,
},
{
str: `name=val\\\val`,
expect: map[string]interface{}{"name": `val\\\val`},
expect: map[string]any{"name": `val\\\val`},
err: false,
},
{
str: `name={val,.?*v\0a!l)some`,
expect: map[string]interface{}{"name": `{val,.?*v\0a!l)some`},
expect: map[string]any{"name": `{val,.?*v\0a!l)some`},
err: false,
},
{
str: `name=em%GT)tqUDqz,i-\h+Mbqs-!:.m\\rE=mkbM#rR}@{-k@`,
expect: map[string]interface{}{"name": `em%GT)tqUDqz,i-\h+Mbqs-!:.m\\rE=mkbM#rR}@{-k@`},
expect: map[string]any{"name": `em%GT)tqUDqz,i-\h+Mbqs-!:.m\\rE=mkbM#rR}@{-k@`},
},
}
@ -307,20 +307,20 @@ func TestParseLiteralInto(t *testing.T) {
tests := []struct {
input string
input2 string
got map[string]interface{}
expect map[string]interface{}
got map[string]any
expect map[string]any
err bool
}{
{
input: "outer.inner1=value1,outer.inner3=value3,outer.inner4=4",
got: map[string]interface{}{
"outer": map[string]interface{}{
got: map[string]any{
"outer": map[string]any{
"inner1": "overwrite",
"inner2": "value2",
},
},
expect: map[string]interface{}{
"outer": map[string]interface{}{
expect: map[string]any{
"outer": map[string]any{
"inner1": "value1,outer.inner3=value3,outer.inner4=4",
"inner2": "value2",
}},
@ -329,9 +329,9 @@ func TestParseLiteralInto(t *testing.T) {
{
input: "listOuter[0][0].type=listValue",
input2: "listOuter[0][0].status=alive",
got: map[string]interface{}{},
expect: map[string]interface{}{
"listOuter": [][]interface{}{{map[string]string{
got: map[string]any{},
expect: map[string]any{
"listOuter": [][]any{{map[string]string{
"type": "listValue",
"status": "alive",
}}},
@ -341,9 +341,9 @@ func TestParseLiteralInto(t *testing.T) {
{
input: "listOuter[0][0].type=listValue",
input2: "listOuter[1][0].status=alive",
got: map[string]interface{}{},
expect: map[string]interface{}{
"listOuter": [][]interface{}{
got: map[string]any{},
expect: map[string]any{
"listOuter": [][]any{
{
map[string]string{"type": "listValue"},
},
@ -357,17 +357,17 @@ func TestParseLiteralInto(t *testing.T) {
{
input: "listOuter[0][1][0].type=listValue",
input2: "listOuter[0][0][1].status=alive",
got: map[string]interface{}{
"listOuter": []interface{}{
[]interface{}{
[]interface{}{
got: map[string]any{
"listOuter": []any{
[]any{
[]any{
map[string]string{"exited": "old"},
},
},
},
},
expect: map[string]interface{}{
"listOuter": [][][]interface{}{
expect: map[string]any{
"listOuter": [][][]any{
{
{
map[string]string{"exited": "old"},
@ -429,13 +429,13 @@ func TestParseLiteralNestedLevels(t *testing.T) {
tests := []struct {
str string
expect map[string]interface{}
expect map[string]any
err bool
errStr string
}{
{
"outer.middle.inner=value",
map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}},
map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}},
false,
"",
},

View file

@ -26,48 +26,48 @@ import (
func TestSetIndex(t *testing.T) {
tests := []struct {
name string
initial []interface{}
expect []interface{}
initial []any
expect []any
add int
val int
err bool
}{
{
name: "short",
initial: []interface{}{0, 1},
expect: []interface{}{0, 1, 2},
initial: []any{0, 1},
expect: []any{0, 1, 2},
add: 2,
val: 2,
err: false,
},
{
name: "equal",
initial: []interface{}{0, 1},
expect: []interface{}{0, 2},
initial: []any{0, 1},
expect: []any{0, 2},
add: 1,
val: 2,
err: false,
},
{
name: "long",
initial: []interface{}{0, 1, 2, 3, 4, 5},
expect: []interface{}{0, 1, 2, 4, 4, 5},
initial: []any{0, 1, 2, 3, 4, 5},
expect: []any{0, 1, 2, 4, 4, 5},
add: 3,
val: 4,
err: false,
},
{
name: "negative",
initial: []interface{}{0, 1, 2, 3, 4, 5},
expect: []interface{}{0, 1, 2, 3, 4, 5},
initial: []any{0, 1, 2, 3, 4, 5},
expect: []any{0, 1, 2, 3, 4, 5},
add: -1,
val: 4,
err: true,
},
{
name: "large",
initial: []interface{}{0, 1, 2, 3, 4, 5},
expect: []interface{}{0, 1, 2, 3, 4, 5},
initial: []any{0, 1, 2, 3, 4, 5},
expect: []any{0, 1, 2, 3, 4, 5},
add: MaxIndex + 1,
val: 4,
err: true,
@ -104,53 +104,53 @@ func TestSetIndex(t *testing.T) {
func TestParseSet(t *testing.T) {
testsString := []struct {
str string
expect map[string]interface{}
expect map[string]any
err bool
}{
{
str: "long_int_string=1234567890",
expect: map[string]interface{}{"long_int_string": "1234567890"},
expect: map[string]any{"long_int_string": "1234567890"},
err: false,
},
{
str: "boolean=true",
expect: map[string]interface{}{"boolean": "true"},
expect: map[string]any{"boolean": "true"},
err: false,
},
{
str: "is_null=null",
expect: map[string]interface{}{"is_null": "null"},
expect: map[string]any{"is_null": "null"},
err: false,
},
{
str: "zero=0",
expect: map[string]interface{}{"zero": "0"},
expect: map[string]any{"zero": "0"},
err: false,
},
}
tests := []struct {
str string
expect map[string]interface{}
expect map[string]any
err bool
}{
{
"name1=null,f=false,t=true",
map[string]interface{}{"name1": nil, "f": false, "t": true},
map[string]any{"name1": nil, "f": false, "t": true},
false,
},
{
"name1=value1",
map[string]interface{}{"name1": "value1"},
map[string]any{"name1": "value1"},
false,
},
{
"name1=value1,name2=value2",
map[string]interface{}{"name1": "value1", "name2": "value2"},
map[string]any{"name1": "value1", "name2": "value2"},
false,
},
{
"name1=value1,name2=value2,",
map[string]interface{}{"name1": "value1", "name2": "value2"},
map[string]any{"name1": "value1", "name2": "value2"},
false,
},
{
@ -159,27 +159,27 @@ func TestParseSet(t *testing.T) {
},
{
str: "name1=,name2=value2",
expect: map[string]interface{}{"name1": "", "name2": "value2"},
expect: map[string]any{"name1": "", "name2": "value2"},
},
{
str: "leading_zeros=00009",
expect: map[string]interface{}{"leading_zeros": "00009"},
expect: map[string]any{"leading_zeros": "00009"},
},
{
str: "zero_int=0",
expect: map[string]interface{}{"zero_int": 0},
expect: map[string]any{"zero_int": 0},
},
{
str: "long_int=1234567890",
expect: map[string]interface{}{"long_int": 1234567890},
expect: map[string]any{"long_int": 1234567890},
},
{
str: "boolean=true",
expect: map[string]interface{}{"boolean": true},
expect: map[string]any{"boolean": true},
},
{
str: "is_null=null",
expect: map[string]interface{}{"is_null": nil},
expect: map[string]any{"is_null": nil},
err: false,
},
{
@ -200,40 +200,40 @@ func TestParseSet(t *testing.T) {
},
{
"name1=one\\,two,name2=three\\,four",
map[string]interface{}{"name1": "one,two", "name2": "three,four"},
map[string]any{"name1": "one,two", "name2": "three,four"},
false,
},
{
"name1=one\\=two,name2=three\\=four",
map[string]interface{}{"name1": "one=two", "name2": "three=four"},
map[string]any{"name1": "one=two", "name2": "three=four"},
false,
},
{
"name1=one two three,name2=three two one",
map[string]interface{}{"name1": "one two three", "name2": "three two one"},
map[string]any{"name1": "one two three", "name2": "three two one"},
false,
},
{
"outer.inner=value",
map[string]interface{}{"outer": map[string]interface{}{"inner": "value"}},
map[string]any{"outer": map[string]any{"inner": "value"}},
false,
},
{
"outer.middle.inner=value",
map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}},
map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}},
false,
},
{
"outer.inner1=value,outer.inner2=value2",
map[string]interface{}{"outer": map[string]interface{}{"inner1": "value", "inner2": "value2"}},
map[string]any{"outer": map[string]any{"inner1": "value", "inner2": "value2"}},
false,
},
{
"outer.inner1=value,outer.middle.inner=value",
map[string]interface{}{
"outer": map[string]interface{}{
map[string]any{
"outer": map[string]any{
"inner1": "value",
"middle": map[string]interface{}{
"middle": map[string]any{
"inner": "value",
},
},
@ -250,7 +250,7 @@ func TestParseSet(t *testing.T) {
},
{
str: "name1.name2=",
expect: map[string]interface{}{"name1": map[string]interface{}{"name2": ""}},
expect: map[string]any{"name1": map[string]any{"name2": ""}},
},
{
str: "name1.=name2",
@ -262,12 +262,12 @@ func TestParseSet(t *testing.T) {
},
{
"name1={value1,value2}",
map[string]interface{}{"name1": []string{"value1", "value2"}},
map[string]any{"name1": []string{"value1", "value2"}},
false,
},
{
"name1={value1,value2},name2={value1,value2}",
map[string]interface{}{
map[string]any{
"name1": []string{"value1", "value2"},
"name2": []string{"value1", "value2"},
},
@ -275,12 +275,12 @@ func TestParseSet(t *testing.T) {
},
{
"name1={1021,902}",
map[string]interface{}{"name1": []int{1021, 902}},
map[string]any{"name1": []int{1021, 902}},
false,
},
{
"name1.name2={value1,value2}",
map[string]interface{}{"name1": map[string]interface{}{"name2": []string{"value1", "value2"}}},
map[string]any{"name1": map[string]any{"name2": []string{"value1", "value2"}}},
false,
},
{
@ -290,21 +290,21 @@ func TestParseSet(t *testing.T) {
// List support
{
str: "list[0]=foo",
expect: map[string]interface{}{"list": []string{"foo"}},
expect: map[string]any{"list": []string{"foo"}},
},
{
str: "list[0].foo=bar",
expect: map[string]interface{}{
"list": []interface{}{
map[string]interface{}{"foo": "bar"},
expect: map[string]any{
"list": []any{
map[string]any{"foo": "bar"},
},
},
},
{
str: "list[0].foo=bar,list[0].hello=world",
expect: map[string]interface{}{
"list": []interface{}{
map[string]interface{}{"foo": "bar", "hello": "world"},
expect: map[string]any{
"list": []any{
map[string]any{"foo": "bar", "hello": "world"},
},
},
},
@ -314,15 +314,15 @@ func TestParseSet(t *testing.T) {
},
{
str: "list[0]=foo,list[1]=bar",
expect: map[string]interface{}{"list": []string{"foo", "bar"}},
expect: map[string]any{"list": []string{"foo", "bar"}},
},
{
str: "list[0]=foo,list[1]=bar,",
expect: map[string]interface{}{"list": []string{"foo", "bar"}},
expect: map[string]any{"list": []string{"foo", "bar"}},
},
{
str: "list[0]=foo,list[3]=bar",
expect: map[string]interface{}{"list": []interface{}{"foo", nil, nil, "bar"}},
expect: map[string]any{"list": []any{"foo", nil, nil, "bar"}},
},
{
str: "list[0]=foo,list[-20]=bar",
@ -334,41 +334,41 @@ func TestParseSet(t *testing.T) {
},
{
str: "noval[0]",
expect: map[string]interface{}{"noval": []interface{}{}},
expect: map[string]any{"noval": []any{}},
},
{
str: "noval[0]=",
expect: map[string]interface{}{"noval": []interface{}{""}},
expect: map[string]any{"noval": []any{""}},
},
{
str: "nested[0][0]=1",
expect: map[string]interface{}{"nested": []interface{}{[]interface{}{1}}},
expect: map[string]any{"nested": []any{[]any{1}}},
},
{
str: "nested[1][1]=1",
expect: map[string]interface{}{"nested": []interface{}{nil, []interface{}{nil, 1}}},
expect: map[string]any{"nested": []any{nil, []any{nil, 1}}},
},
{
str: "name1.name2[0].foo=bar,name1.name2[1].foo=bar",
expect: map[string]interface{}{
"name1": map[string]interface{}{
"name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}},
expect: map[string]any{
"name1": map[string]any{
"name2": []map[string]any{{"foo": "bar"}, {"foo": "bar"}},
},
},
},
{
str: "name1.name2[1].foo=bar,name1.name2[0].foo=bar",
expect: map[string]interface{}{
"name1": map[string]interface{}{
"name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}},
expect: map[string]any{
"name1": map[string]any{
"name2": []map[string]any{{"foo": "bar"}, {"foo": "bar"}},
},
},
},
{
str: "name1.name2[1].foo=bar",
expect: map[string]interface{}{
"name1": map[string]interface{}{
"name2": []map[string]interface{}{nil, {"foo": "bar"}},
expect: map[string]any{
"name1": map[string]any{
"name2": []map[string]any{nil, {"foo": "bar"}},
},
},
},
@ -434,20 +434,20 @@ func TestParseInto(t *testing.T) {
tests := []struct {
input string
input2 string
got map[string]interface{}
expect map[string]interface{}
got map[string]any
expect map[string]any
err bool
}{
{
input: "outer.inner1=value1,outer.inner3=value3,outer.inner4=4",
got: map[string]interface{}{
"outer": map[string]interface{}{
got: map[string]any{
"outer": map[string]any{
"inner1": "overwrite",
"inner2": "value2",
},
},
expect: map[string]interface{}{
"outer": map[string]interface{}{
expect: map[string]any{
"outer": map[string]any{
"inner1": "value1",
"inner2": "value2",
"inner3": "value3",
@ -458,9 +458,9 @@ func TestParseInto(t *testing.T) {
{
input: "listOuter[0][0].type=listValue",
input2: "listOuter[0][0].status=alive",
got: map[string]interface{}{},
expect: map[string]interface{}{
"listOuter": [][]interface{}{{map[string]string{
got: map[string]any{},
expect: map[string]any{
"listOuter": [][]any{{map[string]string{
"type": "listValue",
"status": "alive",
}}},
@ -470,9 +470,9 @@ func TestParseInto(t *testing.T) {
{
input: "listOuter[0][0].type=listValue",
input2: "listOuter[1][0].status=alive",
got: map[string]interface{}{},
expect: map[string]interface{}{
"listOuter": [][]interface{}{
got: map[string]any{},
expect: map[string]any{
"listOuter": [][]any{
{
map[string]string{"type": "listValue"},
},
@ -486,17 +486,17 @@ func TestParseInto(t *testing.T) {
{
input: "listOuter[0][1][0].type=listValue",
input2: "listOuter[0][0][1].status=alive",
got: map[string]interface{}{
"listOuter": []interface{}{
[]interface{}{
[]interface{}{
got: map[string]any{
"listOuter": []any{
[]any{
[]any{
map[string]string{"exited": "old"},
},
},
},
},
expect: map[string]interface{}{
"listOuter": [][][]interface{}{
expect: map[string]any{
"listOuter": [][][]any{
{
{
map[string]string{"exited": "old"},
@ -544,15 +544,15 @@ func TestParseInto(t *testing.T) {
}
func TestParseIntoString(t *testing.T) {
got := map[string]interface{}{
"outer": map[string]interface{}{
got := map[string]any{
"outer": map[string]any{
"inner1": "overwrite",
"inner2": "value2",
},
}
input := "outer.inner1=1,outer.inner3=3"
expect := map[string]interface{}{
"outer": map[string]interface{}{
expect := map[string]any{
"outer": map[string]any{
"inner1": "1",
"inner2": "value2",
"inner3": "3",
@ -580,20 +580,20 @@ func TestParseIntoString(t *testing.T) {
func TestParseJSON(t *testing.T) {
tests := []struct {
input string
got map[string]interface{}
expect map[string]interface{}
got map[string]any
expect map[string]any
err bool
}{
{ // set json scalars values, and replace one existing key
input: "outer.inner1=\"1\",outer.inner3=3,outer.inner4=true,outer.inner5=\"true\"",
got: map[string]interface{}{
"outer": map[string]interface{}{
got: map[string]any{
"outer": map[string]any{
"inner1": "overwrite",
"inner2": "value2",
},
},
expect: map[string]interface{}{
"outer": map[string]interface{}{
expect: map[string]any{
"outer": map[string]any{
"inner1": "1",
"inner2": "value2",
"inner3": 3,
@ -605,43 +605,43 @@ func TestParseJSON(t *testing.T) {
},
{ // set json objects and arrays, and replace one existing key
input: "outer.inner1={\"a\":\"1\",\"b\":2,\"c\":[1,2,3]},outer.inner3=[\"new value 1\",\"new value 2\"],outer.inner4={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner5=[{\"A\":\"1\",\"B\":2,\"C\":[1,2,3]}]",
got: map[string]interface{}{
"outer": map[string]interface{}{
"inner1": map[string]interface{}{
got: map[string]any{
"outer": map[string]any{
"inner1": map[string]any{
"x": "overwrite",
},
"inner2": "value2",
"inner3": []interface{}{
"inner3": []any{
"overwrite",
},
},
},
expect: map[string]interface{}{
"outer": map[string]interface{}{
"inner1": map[string]interface{}{"a": "1", "b": 2, "c": []interface{}{1, 2, 3}},
expect: map[string]any{
"outer": map[string]any{
"inner1": map[string]any{"a": "1", "b": 2, "c": []any{1, 2, 3}},
"inner2": "value2",
"inner3": []interface{}{"new value 1", "new value 2"},
"inner4": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, 2, 3}},
"inner5": []interface{}{map[string]interface{}{"A": "1", "B": 2, "C": []interface{}{1, 2, 3}}},
"inner3": []any{"new value 1", "new value 2"},
"inner4": map[string]any{"aa": "1", "bb": 2, "cc": []any{1, 2, 3}},
"inner5": []any{map[string]any{"A": "1", "B": 2, "C": []any{1, 2, 3}}},
},
},
err: false,
},
{ // null assignment, and no value assigned (equivalent to null)
input: "outer.inner1=,outer.inner3={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner3.cc[1]=null",
got: map[string]interface{}{
"outer": map[string]interface{}{
"inner1": map[string]interface{}{
got: map[string]any{
"outer": map[string]any{
"inner1": map[string]any{
"x": "overwrite",
},
"inner2": "value2",
},
},
expect: map[string]interface{}{
"outer": map[string]interface{}{
expect: map[string]any{
"outer": map[string]any{
"inner1": nil,
"inner2": "value2",
"inner3": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, nil, 3}},
"inner3": map[string]any{"aa": "1", "bb": 2, "cc": []any{1, nil, 3}},
},
},
err: false,
@ -680,10 +680,10 @@ func TestParseJSON(t *testing.T) {
func TestParseFile(t *testing.T) {
input := "name1=path1"
expect := map[string]interface{}{
expect := map[string]any{
"name1": "value1",
}
rs2v := func(rs []rune) (interface{}, error) {
rs2v := func(rs []rune) (any, error) {
v := string(rs)
if v != "path1" {
t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v)
@ -712,12 +712,12 @@ func TestParseFile(t *testing.T) {
}
func TestParseIntoFile(t *testing.T) {
got := map[string]interface{}{}
got := map[string]any{}
input := "name1=path1"
expect := map[string]interface{}{
expect := map[string]any{
"name1": "value1",
}
rs2v := func(rs []rune) (interface{}, error) {
rs2v := func(rs []rune) (any, error) {
v := string(rs)
if v != "path1" {
t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v)
@ -768,13 +768,13 @@ func TestParseSetNestedLevels(t *testing.T) {
}
tests := []struct {
str string
expect map[string]interface{}
expect map[string]any
err bool
errStr string
}{
{
"outer.middle.inner=value",
map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}},
map[string]any{"outer": map[string]any{"middle": map[string]any{"inner": "value"}}},
false,
"",
},