mirror of
https://github.com/helm/helm.git
synced 2026-02-20 00:13:02 -05:00
Merge branch 'helm:main' into feature/enhance-dry-run-for-helm-4
This commit is contained in:
commit
00a389711c
65 changed files with 1269 additions and 673 deletions
20
KEYS
20
KEYS
|
|
@ -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-----
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
18
go.mod
|
|
@ -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
36
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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...))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
internal/fileutil/fileutil_unix.go
Normal file
32
internal/fileutil/fileutil_unix.go
Normal 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)
|
||||
}
|
||||
54
internal/fileutil/fileutil_windows.go
Normal file
54
internal/fileutil/fileutil_windows.go
Normal 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
19
internal/gates/doc.go
Normal 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
21
internal/gates/gates.go
Normal 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"
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
3
internal/third_party/dep/fs/fs.go
vendored
3
internal/third_party/dep/fs/fs.go
vendored
|
|
@ -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 {
|
||||
|
|
|
|||
5
internal/third_party/dep/fs/fs_test.go
vendored
5
internal/third_party/dep/fs/fs_test.go
vendored
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
internal/third_party/dep/fs/rename.go
vendored
6
internal/third_party/dep/fs/rename.go
vendored
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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...))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
131
pkg/downloader/chart_downloader_windows_test.go
Normal file
131
pkg/downloader/chart_downloader_windows_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
"",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
"",
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue