diff --git a/.gitignore b/.gitignore index 75698e993..7ea0717ed 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ bin/ vendor/ # Ignores charts pulled for dependency build tests cmd/helm/testdata/testcharts/issue-7233/charts/* +pkg/cmd/testdata/testcharts/issue-7233/charts/* .pre-commit-config.yaml diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index da6a5c54e..eefce5158 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -17,7 +17,7 @@ limitations under the License. package main // import "helm.sh/helm/v4/cmd/helm" import ( - "log" + "log/slog" "os" // Import to initialize client auth plugins. @@ -27,10 +27,6 @@ import ( "helm.sh/helm/v4/pkg/kube" ) -func init() { - log.SetFlags(log.Lshortfile) -} - func main() { // Setting the name of the app for managedFields in the Kubernetes client. // It is set here to the full name of "helm" so that renaming of helm to @@ -40,12 +36,12 @@ func main() { cmd, err := helmcmd.NewRootCmd(os.Stdout, os.Args[1:]) if err != nil { - helmcmd.Warning("%+v", err) + slog.Warn("command failed", slog.Any("error", err)) os.Exit(1) } if err := cmd.Execute(); err != nil { - helmcmd.Debug("%+v", err) + slog.Debug("error", slog.Any("error", err)) switch e := err.(type) { case helmcmd.PluginError: os.Exit(e.Code) diff --git a/go.mod b/go.mod index bfc55057a..ad119b6b8 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module helm.sh/helm/v4 -go 1.23.0 +go 1.23.7 require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 @@ -12,7 +12,7 @@ require ( github.com/Masterminds/vcs v1.13.3 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/cyphar/filepath-securejoin v0.4.1 - github.com/distribution/distribution/v3 v3.0.0-rc.3 + github.com/distribution/distribution/v3 v3.0.0 github.com/evanphx/json-patch v5.9.11+incompatible github.com/fluxcd/cli-utils v0.36.0-flux.12 github.com/foxcpp/go-mockdns v1.1.0 @@ -29,13 +29,14 @@ require ( github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/pkg/errors v0.9.1 github.com/rubenv/sql-migrate v1.7.1 + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 github.com/stretchr/testify v1.10.0 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/crypto v0.36.0 - golang.org/x/term v0.30.0 - golang.org/x/text v0.23.0 + golang.org/x/crypto v0.37.0 + golang.org/x/term v0.31.0 + golang.org/x/text v0.24.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.32.3 k8s.io/apiextensions-apiserver v0.32.3 @@ -129,7 +130,7 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 // indirect github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect - github.com/redis/go-redis/v9 v9.1.0 // indirect + github.com/redis/go-redis/v9 v9.7.3 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -163,9 +164,9 @@ require ( go.opentelemetry.io/proto/otlp v1.3.1 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.37.0 // indirect - golang.org/x/oauth2 v0.25.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.29.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect diff --git a/go.sum b/go.sum index 1153931d8..4fcd483a4 100644 --- a/go.sum +++ b/go.sum @@ -37,10 +37,11 @@ github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2y github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= -github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= -github.com/bsm/ginkgo/v2 v2.9.5/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= -github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -62,10 +63,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/distribution/distribution/v3 v3.0.0-rc.3 h1:JRJso9IVLoooKX76oWR+DWCCdZlK5m4nRtDWvzB1ITg= -github.com/distribution/distribution/v3 v3.0.0-rc.3/go.mod h1:offoOgrnYs+CFwis8nE0hyzYZqRCZj5EFc5kgfszwiE= +github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= +github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= @@ -288,14 +291,16 @@ github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJu github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ= github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= -github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY= -github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= +github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= +github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rubenv/sql-migrate v1.7.1 h1:f/o0WgfO/GqNuVg+6801K/KW3WdDSupzSjDYODmiUq4= github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2AS+ezLDFb4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= @@ -393,8 +398,8 @@ 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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -418,8 +423,8 @@ 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.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -430,8 +435,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -453,8 +458,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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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= @@ -462,8 +467,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.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= +golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= 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= @@ -471,8 +476,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.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/logging/logging.go b/internal/logging/logging.go new file mode 100644 index 000000000..946a211ef --- /dev/null +++ b/internal/logging/logging.go @@ -0,0 +1,87 @@ +/* +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 logging + +import ( + "context" + "log/slog" + "os" +) + +// DebugEnabledFunc is a function type that determines if debug logging is enabled +// We use a function because we want to check the setting at log time, not when the logger is created +type DebugEnabledFunc func() bool + +// DebugCheckHandler checks settings.Debug at log time +type DebugCheckHandler struct { + handler slog.Handler + debugEnabled DebugEnabledFunc +} + +// Enabled implements slog.Handler.Enabled +func (h *DebugCheckHandler) Enabled(_ context.Context, level slog.Level) bool { + if level == slog.LevelDebug { + return h.debugEnabled() + } + return true // Always log other levels +} + +// Handle implements slog.Handler.Handle +func (h *DebugCheckHandler) Handle(ctx context.Context, r slog.Record) error { + return h.handler.Handle(ctx, r) +} + +// WithAttrs implements slog.Handler.WithAttrs +func (h *DebugCheckHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &DebugCheckHandler{ + handler: h.handler.WithAttrs(attrs), + debugEnabled: h.debugEnabled, + } +} + +// WithGroup implements slog.Handler.WithGroup +func (h *DebugCheckHandler) WithGroup(name string) slog.Handler { + return &DebugCheckHandler{ + handler: h.handler.WithGroup(name), + debugEnabled: h.debugEnabled, + } +} + +// NewLogger creates a new logger with dynamic debug checking +func NewLogger(debugEnabled DebugEnabledFunc) *slog.Logger { + // Create base handler that removes timestamps + baseHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ + // Always use LevelDebug here to allow all messages through + // Our custom handler will do the filtering + Level: slog.LevelDebug, + ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr { + // Remove the time attribute + if a.Key == slog.TimeKey { + return slog.Attr{} + } + return a + }, + }) + + // Wrap with our dynamic debug-checking handler + dynamicHandler := &DebugCheckHandler{ + handler: baseHandler, + debugEnabled: debugEnabled, + } + + return slog.New(dynamicHandler) +} diff --git a/internal/monocular/client.go b/internal/monocular/client.go index 88a2564b9..f4ef5d647 100644 --- a/internal/monocular/client.go +++ b/internal/monocular/client.go @@ -29,9 +29,6 @@ type Client struct { // The base URL for requests BaseURL string - - // The internal logger to use - Log func(string, ...interface{}) } // New creates a new client @@ -44,12 +41,9 @@ func New(u string) (*Client, error) { return &Client{ BaseURL: u, - Log: nopLogger, }, nil } -var nopLogger = func(_ string, _ ...interface{}) {} - // Validate if the base URL for monocular is valid. func validate(u string) error { diff --git a/pkg/action/action.go b/pkg/action/action.go index ea2dc0dd7..937b42537 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -20,10 +20,10 @@ import ( "bytes" "fmt" "io" + "log/slog" "os" "path" "path/filepath" - "regexp" "strings" "github.com/pkg/errors" @@ -63,21 +63,6 @@ var ( errPending = errors.New("another operation (install/upgrade/rollback) is in progress") ) -// ValidName is a regular expression for resource names. -// -// DEPRECATED: This will be removed in Helm 4, and is no longer used here. See -// pkg/lint/rules.validateMetadataNameFunc for the replacement. -// -// According to the Kubernetes help text, the regular expression it uses is: -// -// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* -// -// This follows the above regular expression (but requires a full string match, not partial). -// -// The Kubernetes documentation is here, though it is not entirely correct: -// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -var ValidName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) - // Configuration injects the dependencies that all actions share. type Configuration struct { // RESTClientGetter is an interface that loads Kubernetes clients. @@ -95,8 +80,6 @@ type Configuration struct { // Capabilities describes the capabilities of the Kubernetes cluster. Capabilities *chartutil.Capabilities - Log func(string, ...interface{}) - // HookOutputFunc called with container name and returns and expects writer that will receive the log output. HookOutputFunc func(namespace, pod, container string) io.Writer } @@ -243,9 +226,6 @@ type RESTClientGetter interface { ToRESTMapper() (meta.RESTMapper, error) } -// DebugLog sets the logger that writes debug strings -type DebugLog func(format string, v ...interface{}) - // capabilities builds a Capabilities from discovery information. func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) { if cfg.Capabilities != nil { @@ -269,8 +249,8 @@ func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) { apiVersions, err := GetVersionSet(dc) if err != nil { if discovery.IsGroupDiscoveryFailedError(err) { - cfg.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err) - cfg.Log("WARNING: To fix this, kubectl delete apiservice ") + slog.Warn("the kubernetes server has an orphaned API service", slog.Any("error", err)) + slog.Warn("to fix this, kubectl delete apiservice ") } else { return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes") } @@ -369,14 +349,13 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.Version // recordRelease with an update operation in case reuse has been set. func (cfg *Configuration) recordRelease(r *release.Release) { if err := cfg.Releases.Update(r); err != nil { - cfg.Log("warning: Failed to update release %s: %s", r.Name, err) + slog.Warn("failed to update release", "name", r.Name, "revision", r.Version, slog.Any("error", err)) } } // Init initializes the action configuration -func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error { +func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string) error { kc := kube.New(getter) - kc.Log = log lazyClient := &lazyClient{ namespace: namespace, @@ -387,11 +366,9 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp switch helmDriver { case "secret", "secrets", "": d := driver.NewSecrets(newSecretClient(lazyClient)) - d.Log = log store = storage.Init(d) case "configmap", "configmaps": d := driver.NewConfigMaps(newConfigMapClient(lazyClient)) - d.Log = log store = storage.Init(d) case "memory": var d *driver.Memory @@ -411,7 +388,6 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp case "sql": d, err := driver.NewSQL( os.Getenv("HELM_DRIVER_SQL_CONNECTION_STRING"), - log, namespace, ) if err != nil { @@ -425,7 +401,6 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp cfg.RESTClientGetter = getter cfg.KubeClient = kc cfg.Releases = store - cfg.Log = log cfg.HookOutputFunc = func(_, _, _ string) io.Writer { return io.Discard } return nil diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index fd5825990..f808163fb 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -19,11 +19,13 @@ import ( "flag" "fmt" "io" + "log/slog" "testing" "github.com/stretchr/testify/assert" fakeclientset "k8s.io/client-go/kubernetes/fake" + "helm.sh/helm/v4/internal/logging" chart "helm.sh/helm/v4/pkg/chart/v2" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "helm.sh/helm/v4/pkg/kube" @@ -35,7 +37,7 @@ import ( "helm.sh/helm/v4/pkg/time" ) -var verbose = flag.Bool("test.log", false, "enable test logging") +var verbose = flag.Bool("test.log", false, "enable test logging (debug by default)") func actionConfigFixture(t *testing.T) *Configuration { return actionConfigFixtureWithDummyResources(t, nil) @@ -44,6 +46,11 @@ func actionConfigFixture(t *testing.T) *Configuration { func actionConfigFixtureWithDummyResources(t *testing.T, dummyResources kube.ResourceList) *Configuration { t.Helper() + logger := logging.NewLogger(func() bool { + return *verbose + }) + slog.SetDefault(logger) + registryClient, err := registry.NewClient() if err != nil { t.Fatal(err) @@ -54,12 +61,6 @@ func actionConfigFixtureWithDummyResources(t *testing.T, dummyResources kube.Res KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: io.Discard}, DummyResources: dummyResources}, Capabilities: chartutil.DefaultCapabilities, RegistryClient: registryClient, - Log: func(format string, v ...interface{}) { - t.Helper() - if *verbose { - t.Logf(format, v...) - } - }, } } @@ -339,7 +340,7 @@ func TestConfiguration_Init(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cfg := &Configuration{} - actualErr := cfg.Init(nil, "default", tt.helmDriver, nil) + actualErr := cfg.Init(nil, "default", tt.helmDriver) if tt.expectErr { assert.Error(t, actualErr) assert.Contains(t, actualErr.Error(), tt.errMsg) diff --git a/pkg/action/history.go b/pkg/action/history.go index 04743f4cd..b8e472195 100644 --- a/pkg/action/history.go +++ b/pkg/action/history.go @@ -17,6 +17,8 @@ limitations under the License. package action import ( + "log/slog" + "github.com/pkg/errors" chartutil "helm.sh/helm/v4/pkg/chart/v2/util" @@ -53,6 +55,6 @@ func (h *History) Run(name string) ([]*release.Release, error) { return nil, errors.Errorf("release name is invalid: %s", name) } - h.cfg.Log("getting history for release %s", name) + slog.Debug("getting history for release", "release", name) return h.cfg.Releases.History(name) } diff --git a/pkg/action/install.go b/pkg/action/install.go index 6a5e9589d..b7d6a81d0 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -21,6 +21,7 @@ import ( "context" "fmt" "io" + "log/slog" "net/url" "os" "path" @@ -172,7 +173,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error { // If the error is CRD already exists, continue. if apierrors.IsAlreadyExists(err) { crdName := res[0].Name - i.cfg.Log("CRD %s is already present. Skipping.", crdName) + slog.Debug("CRD is already present. Skipping", "crd", crdName) continue } return errors.Wrapf(err, "failed to install CRD %s", obj.Name) @@ -200,7 +201,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return err } - i.cfg.Log("Clearing discovery cache") + slog.Debug("clearing discovery cache") discoveryClient.Invalidate() _, _ = discoveryClient.ServerGroups() @@ -213,7 +214,7 @@ func (i *Install) installCRDs(crds []chart.CRD) error { return err } if resettable, ok := restMapper.(meta.ResettableRESTMapper); ok { - i.cfg.Log("Clearing REST mapper cache") + slog.Debug("clearing REST mapper cache") resettable.Reset() } } @@ -237,24 +238,24 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma // Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) if !i.ClientOnly { if err := i.cfg.KubeClient.IsReachable(); err != nil { - i.cfg.Log(fmt.Sprintf("ERROR: Cluster reachability check failed: %v", err)) + slog.Error(fmt.Sprintf("cluster reachability check failed: %v", err)) return nil, errors.Wrap(err, "cluster reachability check failed") } } // HideSecret must be used with dry run. Otherwise, return an error. if !i.isDryRun() && i.HideSecret { - i.cfg.Log("ERROR: Hiding Kubernetes secrets requires a dry-run mode") + slog.Error("hiding Kubernetes secrets requires a dry-run mode") return nil, errors.New("Hiding Kubernetes secrets requires a dry-run mode") } if err := i.availableName(); err != nil { - i.cfg.Log(fmt.Sprintf("ERROR: Release name check failed: %v", err)) + slog.Error("release name check failed", slog.Any("error", err)) return nil, errors.Wrap(err, "release name check failed") } if err := chartutil.ProcessDependencies(chrt, vals); err != nil { - i.cfg.Log(fmt.Sprintf("ERROR: Processing chart dependencies failed: %v", err)) + slog.Error("chart dependencies processing failed", slog.Any("error", err)) return nil, errors.Wrap(err, "chart dependencies processing failed") } @@ -268,7 +269,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 { // On dry run, bail here if i.isDryRun() { - i.cfg.Log("WARNING: This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") + slog.Warn("This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") } else if err := i.installCRDs(crds); err != nil { return nil, err } @@ -288,7 +289,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma mem.SetNamespace(i.Namespace) i.cfg.Releases = storage.Init(mem) } else if !i.ClientOnly && len(i.APIVersions) > 0 { - i.cfg.Log("API Version list given outside of client only mode, this list will be ignored") + slog.Debug("API Version list given outside of client only mode, this list will be ignored") } // Make sure if Atomic is set, that wait is set as well. This makes it so @@ -509,7 +510,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource // One possible strategy would be to do a timed retry to see if we can get // this stored in the future. if err := i.recordRelease(rel); err != nil { - i.cfg.Log("failed to record the release: %s", err) + slog.Error("failed to record the release", slog.Any("error", err)) } return rel, nil @@ -518,7 +519,7 @@ func (i *Install) performInstall(rel *release.Release, toBeAdopted kube.Resource func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) { rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error())) if i.Atomic { - i.cfg.Log("Install failed and atomic is set, uninstalling release") + slog.Debug("install failed, uninstalling release", "release", i.ReleaseName) uninstall := NewUninstall(i.cfg) uninstall.DisableHooks = i.DisableHooks uninstall.KeepHistory = false diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index b7d12db3f..56204651f 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -641,6 +641,8 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) time.AfterFunc(time.Second, cancel) + goroutines := runtime.NumGoroutine() + res, err := instAction.RunWithContext(ctx, buildChart(), vals) is.Error(err) is.Contains(err.Error(), "context canceled") @@ -651,6 +653,9 @@ func TestInstallRelease_Atomic_Interrupted(t *testing.T) { _, err = instAction.cfg.Releases.Get(res.Name, res.Version) is.Error(err) is.Equal(err, driver.ErrReleaseNotFound) + is.Equal(goroutines+1, runtime.NumGoroutine()) // installation goroutine still is in background + time.Sleep(10 * time.Second) // wait for goroutine to finish + is.Equal(goroutines, runtime.NumGoroutine()) } func TestNameTemplate(t *testing.T) { diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index 870f1e635..34bd0ac52 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -19,6 +19,7 @@ package action import ( "bytes" "fmt" + "log/slog" "strings" "time" @@ -63,26 +64,26 @@ func (r *Rollback) Run(name string) error { r.cfg.Releases.MaxHistory = r.MaxHistory - r.cfg.Log("preparing rollback of %s", name) + slog.Debug("preparing rollback", "name", name) currentRelease, targetRelease, err := r.prepareRollback(name) if err != nil { return err } if !r.DryRun { - r.cfg.Log("creating rolled back release for %s", name) + slog.Debug("creating rolled back release", "name", name) if err := r.cfg.Releases.Create(targetRelease); err != nil { return err } } - r.cfg.Log("performing rollback of %s", name) + slog.Debug("performing rollback", "name", name) if _, err := r.performRollback(currentRelease, targetRelease); err != nil { return err } if !r.DryRun { - r.cfg.Log("updating status for rolled back release for %s", name) + slog.Debug("updating status for rolled back release", "name", name) if err := r.cfg.Releases.Update(targetRelease); err != nil { return err } @@ -129,7 +130,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele return nil, nil, errors.Errorf("release has no %d version", previousVersion) } - r.cfg.Log("rolling back %s (current: v%d, target: v%d)", name, currentRelease.Version, previousVersion) + slog.Debug("rolling back", "name", name, "currentVersion", currentRelease.Version, "targetVersion", previousVersion) previousRelease, err := r.cfg.Releases.Get(name, previousVersion) if err != nil { @@ -162,7 +163,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release) (*release.Release, error) { if r.DryRun { - r.cfg.Log("dry run for %s", targetRelease.Name) + slog.Debug("dry run", "name", targetRelease.Name) return targetRelease, nil } @@ -181,7 +182,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas return targetRelease, err } } else { - r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) + slog.Debug("rollback hooks disabled", "name", targetRelease.Name) } // It is safe to use "force" here because these are resources currently rendered by the chart. @@ -193,14 +194,14 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas if err != nil { msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) - r.cfg.Log("warning: %s", msg) + slog.Warn(msg) currentRelease.Info.Status = release.StatusSuperseded targetRelease.Info.Status = release.StatusFailed targetRelease.Info.Description = msg r.cfg.recordRelease(currentRelease) r.cfg.recordRelease(targetRelease) if r.CleanupOnFail { - r.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(results.Created)) + slog.Debug("cleanup on fail set, cleaning up resources", "count", len(results.Created)) _, errs := r.cfg.KubeClient.Delete(results.Created) if errs != nil { var errorList []string @@ -209,7 +210,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas } return targetRelease, errors.Wrapf(fmt.Errorf("unable to cleanup resources: %s", strings.Join(errorList, ", ")), "an error occurred while cleaning up resources. original rollback error: %s", err) } - r.cfg.Log("Resource cleanup complete") + slog.Debug("resource cleanup complete") } return targetRelease, err } @@ -220,7 +221,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // levels, we should make these error level logs so users are notified // that they'll need to go do the cleanup on their own if err := recreate(r.cfg, results.Updated); err != nil { - r.cfg.Log(err.Error()) + slog.Error(err.Error()) } } waiter, err := r.cfg.KubeClient.GetWaiter(r.WaitStrategy) @@ -256,7 +257,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas } // Supersede all previous deployments, see issue #2941. for _, rel := range deployed { - r.cfg.Log("superseding previous deployment %d", rel.Version) + slog.Debug("superseding previous deployment", "version", rel.Version) rel.Info.Status = release.StatusSuperseded r.cfg.recordRelease(rel) } diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index eeff997d3..fa69d2a48 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -17,6 +17,7 @@ limitations under the License. package action import ( + "log/slog" "strings" "time" @@ -104,7 +105,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) return nil, errors.Errorf("the release named %q is already deleted", name) } - u.cfg.Log("uninstall: Deleting %s", name) + slog.Debug("uninstall: deleting release", "name", name) rel.Info.Status = release.StatusUninstalling rel.Info.Deleted = helmtime.Now() rel.Info.Description = "Deletion in progress (or silently failed)" @@ -115,18 +116,18 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) return res, err } } else { - u.cfg.Log("delete hooks disabled for %s", name) + slog.Debug("delete hooks disabled", "release", name) } // From here on out, the release is currently considered to be in StatusUninstalling // state. if err := u.cfg.Releases.Update(rel); err != nil { - u.cfg.Log("uninstall: Failed to store updated release: %s", err) + slog.Debug("uninstall: Failed to store updated release", slog.Any("error", err)) } deletedResources, kept, errs := u.deleteRelease(rel) if errs != nil { - u.cfg.Log("uninstall: Failed to delete release: %s", errs) + slog.Debug("uninstall: Failed to delete release", slog.Any("error", errs)) return nil, errors.Errorf("failed to delete release: %s", name) } @@ -153,7 +154,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) } if !u.KeepHistory { - u.cfg.Log("purge requested for %s", name) + slog.Debug("purge requested", "release", name) err := u.purgeReleases(rels...) if err != nil { errs = append(errs, errors.Wrap(err, "uninstall: Failed to purge the release")) @@ -168,7 +169,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) } if err := u.cfg.Releases.Update(rel); err != nil { - u.cfg.Log("uninstall: Failed to store updated release: %s", err) + slog.Debug("uninstall: Failed to store updated release", slog.Any("error", err)) } if len(errs) > 0 { @@ -225,7 +226,7 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri } if len(resources) > 0 { if kubeClient, ok := u.cfg.KubeClient.(kube.InterfaceDeletionPropagation); ok { - _, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.cfg, u.DeletionPropagation)) + _, errs = kubeClient.DeleteWithPropagationPolicy(resources, parseCascadingFlag(u.DeletionPropagation)) return resources, kept, errs } _, errs = u.cfg.KubeClient.Delete(resources) @@ -233,7 +234,7 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri return resources, kept, errs } -func parseCascadingFlag(cfg *Configuration, cascadingFlag string) v1.DeletionPropagation { +func parseCascadingFlag(cascadingFlag string) v1.DeletionPropagation { switch cascadingFlag { case "orphan": return v1.DeletePropagationOrphan @@ -242,7 +243,7 @@ func parseCascadingFlag(cfg *Configuration, cascadingFlag string) v1.DeletionPro case "background": return v1.DeletePropagationBackground default: - cfg.Log("uninstall: given cascade value: %s, defaulting to delete propagation background", cascadingFlag) + slog.Debug("uninstall: given cascade value, defaulting to delete propagation background", "value", cascadingFlag) return v1.DeletePropagationBackground } } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index e3b775a25..ea09c8ed0 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "fmt" + "log/slog" "strings" "sync" "time" @@ -163,7 +164,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart. return nil, errors.Errorf("release name is invalid: %s", name) } - u.cfg.Log("preparing upgrade for %s", name) + slog.Debug("preparing upgrade", "name", name) currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals) if err != nil { return nil, err @@ -171,7 +172,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart. u.cfg.Releases.MaxHistory = u.MaxHistory - u.cfg.Log("performing update for %s", name) + slog.Debug("performing update", "name", name) res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease) if err != nil { return res, err @@ -179,7 +180,7 @@ func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart. // Do not update for dry runs if !u.isDryRun() { - u.cfg.Log("updating status for upgraded release for %s", name) + slog.Debug("updating status for upgraded release", "name", name) if err := u.cfg.Releases.Update(upgradedRelease); err != nil { return res, err } @@ -365,7 +366,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR // Run if it is a dry run if u.isDryRun() { - u.cfg.Log("dry run for %s", upgradedRelease.Name) + slog.Debug("dry run for release", "name", upgradedRelease.Name) if len(u.Description) > 0 { upgradedRelease.Info.Description = u.Description } else { @@ -374,7 +375,7 @@ func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedR return upgradedRelease, nil } - u.cfg.Log("creating upgraded release for %s", upgradedRelease.Name) + slog.Debug("creating upgraded release", "name", upgradedRelease.Name) if err := u.cfg.Releases.Create(upgradedRelease); err != nil { return nil, err } @@ -425,7 +426,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele return } } else { - u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name) + slog.Debug("upgrade hooks disabled", "name", upgradedRelease.Name) } results, err := u.cfg.KubeClient.Update(current, target, u.Force) @@ -441,7 +442,7 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele // levels, we should make these error level logs so users are notified // that they'll need to go do the cleanup on their own if err := recreate(u.cfg, results.Updated); err != nil { - u.cfg.Log(err.Error()) + slog.Error(err.Error()) } } waiter, err := u.cfg.KubeClient.GetWaiter(u.WaitStrategy) @@ -486,13 +487,13 @@ func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *rele func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, err error) (*release.Release, error) { msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err) - u.cfg.Log("warning: %s", msg) + slog.Warn("upgrade failed", "name", rel.Name, slog.Any("error", err)) rel.Info.Status = release.StatusFailed rel.Info.Description = msg u.cfg.recordRelease(rel) if u.CleanupOnFail && len(created) > 0 { - u.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(created)) + slog.Debug("cleanup on fail set", "cleaning_resources", len(created)) _, errs := u.cfg.KubeClient.Delete(created) if errs != nil { var errorList []string @@ -501,10 +502,10 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e } return rel, errors.Wrapf(fmt.Errorf("unable to cleanup resources: %s", strings.Join(errorList, ", ")), "an error occurred while cleaning up resources. original upgrade error: %s", err) } - u.cfg.Log("Resource cleanup complete") + slog.Debug("resource cleanup complete") } if u.Atomic { - u.cfg.Log("Upgrade failed and atomic is set, rolling back to last successful release") + slog.Debug("upgrade failed and atomic is set, rolling back to last successful release") // As a protection, get the last successful release before rollback. // If there are no successful releases, bail out @@ -556,13 +557,13 @@ func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, e func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) { if u.ResetValues { // If ResetValues is set, we completely ignore current.Config. - u.cfg.Log("resetting values to the chart's original version") + slog.Debug("resetting values to the chart's original version") return newVals, nil } // If the ReuseValues flag is set, we always copy the old values over the new config's values. if u.ReuseValues { - u.cfg.Log("reusing the old release's values") + slog.Debug("reusing the old release's values") // We have to regenerate the old coalesced values: oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) @@ -579,7 +580,7 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newV // If the ResetThenReuseValues flag is set, we use the new chart's values, but we copy the old config's values over the new config's values. if u.ResetThenReuseValues { - u.cfg.Log("merging values from old release to new values") + slog.Debug("merging values from old release to new values") newVals = chartutil.CoalesceTables(newVals, current.Config) @@ -587,7 +588,7 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newV } if len(newVals) == 0 && len(current.Config) > 0 { - u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version) + slog.Debug("copying values from old release", "name", current.Name, "version", current.Version) newVals = current.Config } return newVals, nil diff --git a/pkg/chart/v2/loader/archive.go b/pkg/chart/v2/loader/archive.go index cb6d3bfe8..655fe87fa 100644 --- a/pkg/chart/v2/loader/archive.go +++ b/pkg/chart/v2/loader/archive.go @@ -33,6 +33,15 @@ import ( chart "helm.sh/helm/v4/pkg/chart/v2" ) +// MaxDecompressedChartSize is the maximum size of a chart archive that will be +// decompressed. This is the decompressed size of all the files. +// The default value is 100 MiB. +var MaxDecompressedChartSize int64 = 100 * 1024 * 1024 // Default 100 MiB + +// MaxDecompressedFileSize is the size of the largest file that Helm will attempt to load. +// The size of the file is the decompressed version of it when it is stored in an archive. +var MaxDecompressedFileSize int64 = 5 * 1024 * 1024 // Default 5 MiB + var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) // FileLoader loads a chart from a file @@ -119,6 +128,7 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { files := []*BufferedFile{} tr := tar.NewReader(unzipped) + remainingSize := MaxDecompressedChartSize for { b := bytes.NewBuffer(nil) hd, err := tr.Next() @@ -178,10 +188,30 @@ func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { return nil, errors.New("chart yaml not in base directory") } - if _, err := io.Copy(b, tr); err != nil { + if hd.Size > remainingSize { + return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) + } + + if hd.Size > MaxDecompressedFileSize { + return nil, fmt.Errorf("decompressed chart file %q is larger than the maximum file size %d", hd.Name, MaxDecompressedFileSize) + } + + limitedReader := io.LimitReader(tr, remainingSize) + + bytesWritten, err := io.Copy(b, limitedReader) + if err != nil { return nil, err } + remainingSize -= bytesWritten + // When the bytesWritten are less than the file size it means the limit reader ended + // copying early. Here we report that error. This is important if the last file extracted + // is the one that goes over the limit. It assumes the Size stored in the tar header + // is correct, something many applications do. + if bytesWritten < hd.Size || remainingSize <= 0 { + return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) + } + data := bytes.TrimPrefix(b.Bytes(), utf8bom) files = append(files, &BufferedFile{Name: n, Data: data}) diff --git a/pkg/chart/v2/loader/directory.go b/pkg/chart/v2/loader/directory.go index 37b24d3f9..dbf3eb882 100644 --- a/pkg/chart/v2/loader/directory.go +++ b/pkg/chart/v2/loader/directory.go @@ -101,6 +101,10 @@ func LoadDir(dir string) (*chart.Chart, error) { return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) } + if fi.Size() > MaxDecompressedFileSize { + return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), MaxDecompressedFileSize) + } + data, err := os.ReadFile(name) if err != nil { return errors.Wrapf(err, "error reading %s", n) diff --git a/pkg/chart/v2/util/dependencies.go b/pkg/chart/v2/util/dependencies.go index 72a08b2a9..b7f78010b 100644 --- a/pkg/chart/v2/util/dependencies.go +++ b/pkg/chart/v2/util/dependencies.go @@ -254,7 +254,7 @@ func processImportValues(c *chart.Chart, merge bool) error { // get child table vv, err := cvals.Table(r.Name + "." + child) if err != nil { - slog.Warn("ImportValues missing table from chart", "chart", r.Name, "error", err) + slog.Warn("ImportValues missing table from chart", "chart", r.Name, slog.Any("error", err)) continue } // create value map from child to be merged into parent diff --git a/pkg/chart/v2/util/jsonschema.go b/pkg/chart/v2/util/jsonschema.go index 615dc5320..66ab42542 100644 --- a/pkg/chart/v2/util/jsonschema.go +++ b/pkg/chart/v2/util/jsonschema.go @@ -18,10 +18,12 @@ package util import ( "bytes" + "encoding/json" "fmt" "strings" "github.com/pkg/errors" + "github.com/santhosh-tekuri/jsonschema/v6" "github.com/xeipuuv/gojsonschema" "sigs.k8s.io/yaml" @@ -73,6 +75,11 @@ func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) (reterr error if bytes.Equal(valuesJSON, []byte("null")) { valuesJSON = []byte("{}") } + + if schemaIs2020(schemaJSON) { + return validateUsingNewValidator(valuesJSON, schemaJSON) + } + schemaLoader := gojsonschema.NewBytesLoader(schemaJSON) valuesLoader := gojsonschema.NewBytesLoader(valuesJSON) @@ -91,3 +98,35 @@ func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) (reterr error return nil } + +func validateUsingNewValidator(valuesJSON, schemaJSON []byte) error { + schema, err := jsonschema.UnmarshalJSON(bytes.NewReader(schemaJSON)) + if err != nil { + return err + } + values, err := jsonschema.UnmarshalJSON(bytes.NewReader(valuesJSON)) + if err != nil { + return err + } + + compiler := jsonschema.NewCompiler() + err = compiler.AddResource("file:///values.schema.json", schema) + if err != nil { + return err + } + + validator, err := compiler.Compile("file:///values.schema.json") + if err != nil { + return err + } + + return validator.Validate(values) +} + +func schemaIs2020(schemaJSON []byte) bool { + var partialSchema struct { + Schema string `json:"$schema"` + } + _ = json.Unmarshal(schemaJSON, &partialSchema) + return partialSchema.Schema == "https://json-schema.org/draft/2020-12/schema" +} diff --git a/pkg/chart/v2/util/jsonschema_test.go b/pkg/chart/v2/util/jsonschema_test.go index 3e3315732..6337ab259 100644 --- a/pkg/chart/v2/util/jsonschema_test.go +++ b/pkg/chart/v2/util/jsonschema_test.go @@ -104,6 +104,21 @@ const subchartSchema = `{ } ` +const subchartSchema2020 = `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Values", + "type": "object", + "properties": { + "data": { + "type": "array", + "contains": { "type": "string" }, + "unevaluatedItems": { "type": "number" } + } + }, + "required": ["data"] +} +` + func TestValidateAgainstSchema(t *testing.T) { subchartJSON := []byte(subchartSchema) subchart := &chart.Chart{ @@ -165,3 +180,68 @@ func TestValidateAgainstSchemaNegative(t *testing.T) { t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) } } + +func TestValidateAgainstSchema2020(t *testing.T) { + subchartJSON := []byte(subchartSchema2020) + subchart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "subchart", + }, + Schema: subchartJSON, + } + chrt := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "chrt", + }, + } + chrt.AddDependency(subchart) + + vals := map[string]interface{}{ + "name": "John", + "subchart": map[string]interface{}{ + "data": []any{"hello", 12}, + }, + } + + if err := ValidateAgainstSchema(chrt, vals); err != nil { + t.Errorf("Error validating Values against Schema: %s", err) + } +} + +func TestValidateAgainstSchema2020Negative(t *testing.T) { + subchartJSON := []byte(subchartSchema2020) + subchart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "subchart", + }, + Schema: subchartJSON, + } + chrt := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "chrt", + }, + } + chrt.AddDependency(subchart) + + vals := map[string]interface{}{ + "name": "John", + "subchart": map[string]interface{}{ + "data": []any{12}, + }, + } + + var errString string + if err := ValidateAgainstSchema(chrt, vals); err == nil { + t.Fatalf("Expected an error, but got nil") + } else { + errString = err.Error() + } + + expectedErrString := `subchart: +jsonschema validation failed with 'file:///values.schema.json#' +- at '/data': no items match contains schema + - at '/data/0': got number, want string` + if errString != expectedErrString { + t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) + } +} diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index ed3b83a55..eb829c21e 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -20,6 +20,7 @@ import ( "flag" "fmt" "log" + "log/slog" "path/filepath" "sort" "strings" @@ -82,11 +83,11 @@ func (ws *waitValue) Set(s string) error { *ws = waitValue(s) return nil case "true": - Warning("--wait=true is deprecated (boolean value) and can be replaced with --wait=watcher") + slog.Warn("--wait=true is deprecated (boolean value) and can be replaced with --wait=watcher") *ws = waitValue(kube.StatusWatcherStrategy) return nil case "false": - Warning("--wait=false is deprecated (boolean value) and can be replaced by omitting the --wait flag") + slog.Warn("--wait=false is deprecated (boolean value) and can be replaced by omitting the --wait flag") *ws = waitValue(kube.HookOnlyStrategy) return nil default: diff --git a/pkg/cmd/helpers_test.go b/pkg/cmd/helpers_test.go index effbc1673..b48f802b5 100644 --- a/pkg/cmd/helpers_test.go +++ b/pkg/cmd/helpers_test.go @@ -92,7 +92,6 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string) Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, Capabilities: chartutil.DefaultCapabilities, - Log: func(_ string, _ ...interface{}) {}, } root, err := newRootCmdWithConfig(actionConfig, buf, args) diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 051612bb8..ee018c88a 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "log" + "log/slog" "os" "os/signal" "syscall" @@ -229,9 +230,9 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal } func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) { - Debug("Original chart version: %q", client.Version) + slog.Debug("Original chart version", "version", client.Version) if client.Version == "" && client.Devel { - Debug("setting version to >0.0.0-0") + slog.Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } @@ -246,7 +247,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options return nil, err } - Debug("CHART PATH: %s\n", cp) + slog.Debug("Chart path", "path", cp) p := getter.All(settings) vals, err := valueOpts.MergeValues(p) @@ -265,7 +266,7 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options } if chartRequested.Metadata.Deprecated { - Warning("This chart is deprecated") + slog.Warn("this chart is deprecated") } if req := chartRequested.Metadata.Dependencies; req != nil { diff --git a/pkg/cmd/list.go b/pkg/cmd/list.go index 85acbc97f..69a4ff36d 100644 --- a/pkg/cmd/list.go +++ b/pkg/cmd/list.go @@ -71,7 +71,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: noMoreArgsCompFunc, RunE: func(cmd *cobra.Command, _ []string) error { if client.AllNamespaces { - if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), Debug); err != nil { + if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER")); err != nil { return err } } diff --git a/pkg/cmd/plugin.go b/pkg/cmd/plugin.go index 3340e76e6..355ed4349 100644 --- a/pkg/cmd/plugin.go +++ b/pkg/cmd/plugin.go @@ -17,6 +17,7 @@ package cmd import ( "io" + "log/slog" "os" "os/exec" @@ -66,7 +67,7 @@ func runHook(p *plugin.Plugin, event string) error { prog := exec.Command(main, argv...) - Debug("running %s hook: %s", event, prog) + slog.Debug("running hook", "event", event, "program", prog) prog.Stdout, prog.Stderr = os.Stdout, os.Stderr if err := prog.Run(); err != nil { diff --git a/pkg/cmd/plugin_install.go b/pkg/cmd/plugin_install.go index e17744cbb..14469f5b4 100644 --- a/pkg/cmd/plugin_install.go +++ b/pkg/cmd/plugin_install.go @@ -18,6 +18,7 @@ package cmd import ( "fmt" "io" + "log/slog" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -79,7 +80,7 @@ func (o *pluginInstallOptions) run(out io.Writer) error { return err } - Debug("loading plugin from %s", i.Path()) + slog.Debug("loading plugin", "path", i.Path()) p, err := plugin.LoadDir(i.Path()) if err != nil { return errors.Wrap(err, "plugin is installed but unusable") diff --git a/pkg/cmd/plugin_list.go b/pkg/cmd/plugin_list.go index 9cca790ae..fdd66ec0a 100644 --- a/pkg/cmd/plugin_list.go +++ b/pkg/cmd/plugin_list.go @@ -18,6 +18,7 @@ package cmd import ( "fmt" "io" + "log/slog" "github.com/gosuri/uitable" "github.com/spf13/cobra" @@ -32,7 +33,7 @@ func newPluginListCmd(out io.Writer) *cobra.Command { Short: "list installed Helm plugins", ValidArgsFunction: noMoreArgsCompFunc, RunE: func(_ *cobra.Command, _ []string) error { - Debug("pluginDirs: %s", settings.PluginsDirectory) + slog.Debug("pluginDirs", "directory", settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { return err diff --git a/pkg/cmd/plugin_uninstall.go b/pkg/cmd/plugin_uninstall.go index c1f90ca49..61bc3d724 100644 --- a/pkg/cmd/plugin_uninstall.go +++ b/pkg/cmd/plugin_uninstall.go @@ -18,6 +18,7 @@ package cmd import ( "fmt" "io" + "log/slog" "os" "strings" @@ -60,7 +61,7 @@ func (o *pluginUninstallOptions) complete(args []string) error { } func (o *pluginUninstallOptions) run(out io.Writer) error { - Debug("loading installed plugins from %s", settings.PluginsDirectory) + slog.Debug("loading installer plugins", "dir", settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { return err diff --git a/pkg/cmd/plugin_update.go b/pkg/cmd/plugin_update.go index cbbd8994c..c9a8ca238 100644 --- a/pkg/cmd/plugin_update.go +++ b/pkg/cmd/plugin_update.go @@ -18,6 +18,7 @@ package cmd import ( "fmt" "io" + "log/slog" "path/filepath" "strings" @@ -62,7 +63,7 @@ func (o *pluginUpdateOptions) complete(args []string) error { func (o *pluginUpdateOptions) run(out io.Writer) error { installer.Debug = settings.Debug - Debug("loading installed plugins from %s", settings.PluginsDirectory) + slog.Debug("loading installed plugins", "path", settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { return err @@ -104,7 +105,7 @@ func updatePlugin(p *plugin.Plugin) error { return err } - Debug("loading plugin from %s", i.Path()) + slog.Debug("loading plugin", "path", i.Path()) updatedPlugin, err := plugin.LoadDir(i.Path()) if err != nil { return err diff --git a/pkg/cmd/pull.go b/pkg/cmd/pull.go index 5d188ee4f..e3d93c049 100644 --- a/pkg/cmd/pull.go +++ b/pkg/cmd/pull.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "log" + "log/slog" "github.com/spf13/cobra" @@ -60,7 +61,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { RunE: func(_ *cobra.Command, args []string) error { client.Settings = settings if client.Version == "" && client.Devel { - Debug("setting version to >0.0.0-0") + slog.Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } diff --git a/pkg/cmd/registry_login.go b/pkg/cmd/registry_login.go index 1dfb3c798..3719c1c17 100644 --- a/pkg/cmd/registry_login.go +++ b/pkg/cmd/registry_login.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "io" + "log/slog" "os" "strings" @@ -122,7 +123,7 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd } } } else { - Warning("Using --password via the CLI is insecure. Use --password-stdin.") + slog.Warn("using --password via the CLI is insecure. Use --password-stdin") } return username, password, nil diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index ea686be7c..ee22533f0 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "log" + "log/slog" "net/http" "os" "strings" @@ -31,6 +32,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/clientcmd" + "helm.sh/helm/v4/internal/logging" "helm.sh/helm/v4/internal/tlsutil" "helm.sh/helm/v4/pkg/action" "helm.sh/helm/v4/pkg/cli" @@ -96,16 +98,6 @@ By default, the default directories depend on the Operating System. The defaults var settings = cli.New() -func Debug(format string, v ...interface{}) { - if settings.Debug { - log.Output(2, fmt.Sprintf("[debug] "+format+"\n", v...)) - } -} - -func Warning(format string, v ...interface{}) { - fmt.Fprintf(os.Stderr, "WARNING: "+format+"\n", v...) -} - func NewRootCmd(out io.Writer, args []string) (*cobra.Command, error) { actionConfig := new(action.Configuration) cmd, err := newRootCmdWithConfig(actionConfig, out, args) @@ -114,7 +106,7 @@ func NewRootCmd(out io.Writer, args []string) (*cobra.Command, error) { } cobra.OnInitialize(func() { helmDriver := os.Getenv("HELM_DRIVER") - if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver, Debug); err != nil { + if err := actionConfig.Init(settings.RESTClientGetter(), settings.Namespace(), helmDriver); err != nil { log.Fatal(err) } if helmDriver == "memory" { @@ -148,6 +140,9 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg settings.AddFlags(flags) addKlogFlags(flags) + logger := logging.NewLogger(func() bool { return settings.Debug }) + slog.SetDefault(logger) + // Setup shell completion for the namespace flag err := cmd.RegisterFlagCompletionFunc("namespace", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { if client, err := actionConfig.KubernetesClientSet(); err == nil { diff --git a/pkg/cmd/search_hub.go b/pkg/cmd/search_hub.go index b7f25444e..380d1e394 100644 --- a/pkg/cmd/search_hub.go +++ b/pkg/cmd/search_hub.go @@ -19,6 +19,7 @@ package cmd import ( "fmt" "io" + "log/slog" "strings" "github.com/gosuri/uitable" @@ -89,7 +90,7 @@ func (o *searchHubOptions) run(out io.Writer, args []string) error { q := strings.Join(args, " ") results, err := c.Search(q) if err != nil { - Debug("%s", err) + slog.Debug("search failed", slog.Any("error", err)) return fmt.Errorf("unable to perform search against %q", o.searchEndpoint) } diff --git a/pkg/cmd/search_repo.go b/pkg/cmd/search_repo.go index bc73e52b2..b93b871b1 100644 --- a/pkg/cmd/search_repo.go +++ b/pkg/cmd/search_repo.go @@ -21,6 +21,7 @@ import ( "bytes" "fmt" "io" + "log/slog" "os" "path/filepath" "strings" @@ -130,17 +131,17 @@ func (o *searchRepoOptions) run(out io.Writer, args []string) error { } func (o *searchRepoOptions) setupSearchedVersion() { - Debug("Original chart version: %q", o.version) + slog.Debug("original chart version", "version", o.version) if o.version != "" { return } if o.devel { // search for releases and prereleases (alpha, beta, and release candidate releases). - Debug("setting version to >0.0.0-0") + slog.Debug("setting version to >0.0.0-0") o.version = ">0.0.0-0" } else { // search only for stable releases, prerelease versions will be skipped - Debug("setting version to >0.0.0") + slog.Debug("setting version to >0.0.0") o.version = ">0.0.0" } } @@ -189,8 +190,7 @@ func (o *searchRepoOptions) buildIndex() (*search.Index, error) { f := filepath.Join(o.repoCacheDir, helmpath.CacheIndexFile(n)) ind, err := repo.LoadIndexFile(f) if err != nil { - Warning("Repo %q is corrupt or missing. Try 'helm repo update'.", n) - Warning("%s", err) + slog.Warn("repo is corrupt or missing", "repo", n, slog.Any("error", err)) continue } diff --git a/pkg/cmd/show.go b/pkg/cmd/show.go index a02af6f18..22d8bee49 100644 --- a/pkg/cmd/show.go +++ b/pkg/cmd/show.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "log" + "log/slog" "github.com/spf13/cobra" @@ -211,9 +212,9 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) { } func runShow(args []string, client *action.Show) (string, error) { - Debug("Original chart version: %q", client.Version) + slog.Debug("original chart version", "version", client.Version) if client.Version == "" && client.Devel { - Debug("setting version to >0.0.0-0") + slog.Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index afbbde435..2e0f16212 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "log" + "log/slog" "os" "os/signal" "syscall" @@ -173,7 +174,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } if client.Version == "" && client.Devel { - Debug("setting version to >0.0.0-0") + slog.Debug("setting version to >0.0.0-0") client.Version = ">0.0.0-0" } @@ -225,7 +226,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } if ch.Metadata.Deprecated { - Warning("This chart is deprecated") + slog.Warn("this chart is deprecated") } // Create context and prepare the handle of SIGTERM diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index b7460850a..6c415bfb5 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -35,9 +35,6 @@ type lookupFunc = func(apiversion string, resource string, namespace string, nam // NewLookupFunction returns a function for looking up objects in the cluster. // // If the resource does not exist, no error is raised. -// -// This function is considered deprecated, and will be renamed in Helm 4. It will no -// longer be a public function. func NewLookupFunction(config *rest.Config) lookupFunc { return newLookupFunction(clientProviderFromConfig{config: config}) } @@ -101,7 +98,7 @@ func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config) gvk := schema.FromAPIVersionAndKind(apiversion, kind) apiRes, err := getAPIResourceForGVK(gvk, config) if err != nil { - slog.Error("unable to get apiresource", "groupVersionKind", gvk.String(), "error", err) + slog.Error("unable to get apiresource", "groupVersionKind", gvk.String(), slog.Any("error", err)) return nil, false, errors.Wrapf(err, "unable to get apiresource from unstructured: %s", gvk.String()) } gvr := schema.GroupVersionResource{ @@ -127,7 +124,7 @@ func getAPIResourceForGVK(gvk schema.GroupVersionKind, config *rest.Config) (met } resList, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) if err != nil { - slog.Error("unable to retrieve resource list", "GroupVersion", gvk.GroupVersion().String(), "error", err) + slog.Error("unable to retrieve resource list", "GroupVersion", gvk.GroupVersion().String(), slog.Any("error", err)) return res, err } for _, resource := range resList.APIResources { diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index 37d80cda7..a945dec2b 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -26,7 +26,6 @@ import ( "github.com/pkg/errors" "helm.sh/helm/v4/internal/tlsutil" - "helm.sh/helm/v4/internal/urlutil" "helm.sh/helm/v4/internal/version" ) @@ -137,12 +136,6 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) { return nil, errors.Wrap(err, "can't create TLS config for client") } - sni, err := urlutil.ExtractHostname(g.opts.url) - if err != nil { - return nil, err - } - tlsConf.ServerName = sni - g.transport.TLSClientConfig = tlsConf } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index 24e670f6e..27752a257 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -358,6 +358,131 @@ func TestDownloadTLS(t *testing.T) { } } +func TestDownloadTLSWithRedirect(t *testing.T) { + cd := "../../testdata" + srv2Resp := "hello" + insecureSkipTLSverify := false + + // Server 2 that will actually fulfil the request. + ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "localhost-crt.pem"), filepath.Join(cd, "key.pem") + tlsConf, err := tlsutil.NewTLSConfig( + tlsutil.WithCAFile(ca), + tlsutil.WithCertKeyPairFiles(pub, priv), + tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify), + ) + + if err != nil { + t.Fatal(errors.Wrap(err, "can't create TLS config for client")) + } + + tlsSrv2 := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) { + rw.Header().Set("Content-Type", "text/plain") + rw.Write([]byte(srv2Resp)) + })) + + tlsSrv2.TLS = tlsConf + tlsSrv2.StartTLS() + defer tlsSrv2.Close() + + // Server 1 responds with a redirect to Server 2. + ca, pub, priv = filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem") + tlsConf, err = tlsutil.NewTLSConfig( + tlsutil.WithCAFile(ca), + tlsutil.WithCertKeyPairFiles(pub, priv), + tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify), + ) + + if err != nil { + t.Fatal(errors.Wrap(err, "can't create TLS config for client")) + } + + tlsSrv1 := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + u, _ := url.ParseRequestURI(tlsSrv2.URL) + + // Make the request using the hostname 'localhost' (to which 'localhost-crt.pem' is issued) + // to verify that a successful TLS connection is made even if the client doesn't specify + // the hostname (SNI) in `tls.Config.ServerName`. By default the hostname is derived from the + // request URL for every request (including redirects). Setting `tls.Config.ServerName` on the + // client just overrides the remote endpoint's hostname. + // See https://github.com/golang/go/blob/3979fb9/src/net/http/transport.go#L1505-L1513. + u.Host = fmt.Sprintf("localhost:%s", u.Port()) + + http.Redirect(rw, r, u.String(), http.StatusTemporaryRedirect) + })) + + tlsSrv1.TLS = tlsConf + tlsSrv1.StartTLS() + defer tlsSrv1.Close() + + u, _ := url.ParseRequestURI(tlsSrv1.URL) + + t.Run("Test with TLS", func(t *testing.T) { + g, err := NewHTTPGetter( + WithURL(u.String()), + WithTLSClientConfig(pub, priv, ca), + ) + if err != nil { + t.Fatal(err) + } + + buf, err := g.Get(u.String()) + if err != nil { + t.Error(err) + } + + b, err := io.ReadAll(buf) + if err != nil { + t.Error(err) + } + + if string(b) != srv2Resp { + t.Errorf("expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) + } + }) + + t.Run("Test with TLS config being passed along in .Get (see #6635)", func(t *testing.T) { + g, err := NewHTTPGetter() + if err != nil { + t.Fatal(err) + } + + buf, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)) + if err != nil { + t.Error(err) + } + + b, err := io.ReadAll(buf) + if err != nil { + t.Error(err) + } + + if string(b) != srv2Resp { + t.Errorf("expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) + } + }) + + t.Run("Test with only the CA file (see also #6635)", func(t *testing.T) { + g, err := NewHTTPGetter() + if err != nil { + t.Fatal(err) + } + + buf, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig("", "", ca)) + if err != nil { + t.Error(err) + } + + b, err := io.ReadAll(buf) + if err != nil { + t.Error(err) + } + + if string(b) != srv2Resp { + t.Errorf("expected response from Server2 to be '%s', instead got: %s", srv2Resp, string(b)) + } + }) +} + func TestDownloadInsecureSkipTLSVerify(t *testing.T) { ts := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) defer ts.Close() @@ -450,9 +575,6 @@ func TestHttpClientInsecureSkipVerify(t *testing.T) { if len(transport.TLSClientConfig.Certificates) <= 0 { t.Fatal("transport.TLSClientConfig.Certificates is not present") } - if transport.TLSClientConfig.ServerName == "" { - t.Fatal("TLSClientConfig.ServerName is blank") - } } func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expectedValue bool) *http.Transport { diff --git a/pkg/ignore/rules.go b/pkg/ignore/rules.go index 3f672873c..02a3777ff 100644 --- a/pkg/ignore/rules.go +++ b/pkg/ignore/rules.go @@ -177,7 +177,7 @@ func (r *Rules) parseRule(rule string) error { rule = strings.TrimPrefix(rule, "/") ok, err := filepath.Match(rule, n) if err != nil { - slog.Error("failed to compile", "rule", rule, "error", err) + slog.Error("failed to compile", "rule", rule, slog.Any("error", err)) return false } return ok @@ -187,7 +187,7 @@ func (r *Rules) parseRule(rule string) error { p.match = func(n string, _ os.FileInfo) bool { ok, err := filepath.Match(rule, n) if err != nil { - slog.Error("failed to compile", "rule", rule, "error", err) + slog.Error("failed to compile", "rule", rule, slog.Any("error", err)) return false } return ok @@ -199,7 +199,7 @@ func (r *Rules) parseRule(rule string) error { n = filepath.Base(n) ok, err := filepath.Match(rule, n) if err != nil { - slog.Error("failed to compile", "rule", rule, "error", err) + slog.Error("failed to compile", "rule", rule, slog.Any("error", err)) return false } return ok diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 695bda3ef..583c24a3e 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -22,6 +22,7 @@ import ( "encoding/json" "fmt" "io" + "log/slog" "os" "path/filepath" "reflect" @@ -75,7 +76,6 @@ type Client struct { // needs. The smaller surface area of the interface means there is a lower // chance of it changing. Factory Factory - Log func(string, ...interface{}) // Namespace allows to bypass the kubeconfig file for the choice of the namespace Namespace string @@ -122,7 +122,6 @@ func (c *Client) newStatusWatcher() (*statusWaiter, error) { return &statusWaiter{ restMapper: restMapper, client: dynamicClient, - log: c.Log, }, nil } @@ -133,7 +132,7 @@ func (c *Client) GetWaiter(strategy WaitStrategy) (Waiter, error) { if err != nil { return nil, err } - return &legacyWaiter{kubeClient: kc, log: c.Log}, nil + return &legacyWaiter{kubeClient: kc}, nil case StatusWatcherStrategy: return c.newStatusWatcher() case HookOnlyStrategy: @@ -164,13 +163,10 @@ func New(getter genericclioptions.RESTClientGetter) *Client { factory := cmdutil.NewFactory(getter) c := &Client{ Factory: factory, - Log: nopLogger, } return c } -var nopLogger = func(_ string, _ ...interface{}) {} - // getKubeClient get or create a new KubernetesClientSet func (c *Client) getKubeClient() (kubernetes.Interface, error) { var err error @@ -200,7 +196,7 @@ func (c *Client) IsReachable() error { // Create creates Kubernetes resources specified in the resource list. func (c *Client) Create(resources ResourceList) (*Result, error) { - c.Log("creating %d resource(s)", len(resources)) + slog.Debug("creating resource(s)", "resources", len(resources)) if err := perform(resources, createResource); err != nil { return nil, err } @@ -252,7 +248,7 @@ func (c *Client) Get(resources ResourceList, related bool) (map[string][]runtime objs, err = c.getSelectRelationPod(info, objs, isTable, &podSelectors) if err != nil { - c.Log("Warning: get the relation pod is failed, err:%s", err.Error()) + slog.Warn("get the relation pod is failed", slog.Any("error", err)) } } } @@ -270,7 +266,7 @@ func (c *Client) getSelectRelationPod(info *resource.Info, objs map[string][]run if info == nil { return objs, nil } - c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name) + slog.Debug("get relation pod of object", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) selector, ok, _ := getSelectorFromObject(info.Object) if !ok { return objs, nil @@ -405,7 +401,7 @@ func (c *Client) update(original, target ResourceList, force, threeWayMerge bool updateErrors := []string{} res := &Result{} - c.Log("checking %d resources for changes", len(target)) + slog.Debug("checking resources for changes", "resources", len(target)) err := target.Visit(func(info *resource.Info, err error) error { if err != nil { return err @@ -426,7 +422,7 @@ func (c *Client) update(original, target ResourceList, force, threeWayMerge bool } kind := info.Mapping.GroupVersionKind.Kind - c.Log("Created a new %s called %q in %s\n", kind, info.Name, info.Namespace) + slog.Debug("created a new resource", "namespace", info.Namespace, "name", info.Name, "kind", kind) return nil } @@ -437,7 +433,7 @@ func (c *Client) update(original, target ResourceList, force, threeWayMerge bool } if err := updateResource(c, info, originalInfo.Object, force, threeWayMerge); err != nil { - c.Log("error updating the resource %q:\n\t %v", info.Name, err) + slog.Debug("error updating the resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) updateErrors = append(updateErrors, err.Error()) } // Because we check for errors later, append the info regardless @@ -454,22 +450,22 @@ func (c *Client) update(original, target ResourceList, force, threeWayMerge bool } for _, info := range original.Difference(target) { - c.Log("Deleting %s %q in namespace %s...", info.Mapping.GroupVersionKind.Kind, info.Name, info.Namespace) + slog.Debug("deleting resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) if err := info.Get(); err != nil { - c.Log("Unable to get obj %q, err: %s", info.Name, err) + slog.Debug("unable to get object", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) continue } annotations, err := metadataAccessor.Annotations(info.Object) if err != nil { - c.Log("Unable to get annotations on %q, err: %s", info.Name, err) + slog.Debug("unable to get annotations", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) } if annotations != nil && annotations[ResourcePolicyAnno] == KeepPolicy { - c.Log("Skipping delete of %q due to annotation [%s=%s]", info.Name, ResourcePolicyAnno, KeepPolicy) + slog.Debug("skipping delete due to annotation", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, "annotation", ResourcePolicyAnno, "value", KeepPolicy) continue } if err := deleteResource(info, metav1.DeletePropagationBackground); err != nil { - c.Log("Failed to delete %q, err: %s", info.ObjectName(), err) + slog.Debug("failed to delete resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) continue } res.Deleted = append(res.Deleted, info) @@ -518,16 +514,16 @@ func (c *Client) DeleteWithPropagationPolicy(resources ResourceList, policy meta return rdelete(c, resources, policy) } -func rdelete(c *Client, resources ResourceList, propagation metav1.DeletionPropagation) (*Result, []error) { +func rdelete(_ *Client, resources ResourceList, propagation metav1.DeletionPropagation) (*Result, []error) { var errs []error res := &Result{} mtx := sync.Mutex{} err := perform(resources, func(info *resource.Info) error { - c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind) + slog.Debug("starting delete resource", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind) err := deleteResource(info, propagation) if err == nil || apierrors.IsNotFound(err) { if err != nil { - c.Log("Ignoring delete failure for %q %s: %v", info.Name, info.Mapping.GroupVersionKind, err) + slog.Debug("ignoring delete failure", "namespace", info.Namespace, "name", info.Name, "kind", info.Mapping.GroupVersionKind.Kind, slog.Any("error", err)) } mtx.Lock() defer mtx.Unlock() @@ -674,7 +670,7 @@ func createPatch(target *resource.Info, current runtime.Object, threeWayMergeFor return patch, types.StrategicMergePatchType, err } -func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force, threeWayMergeForUnstructured bool) error { +func updateResource(_ *Client, target *resource.Info, currentObj runtime.Object, force, threeWayMergeForUnstructured bool) error { var ( obj runtime.Object helper = resource.NewHelper(target.Client, target.Mapping).WithFieldManager(getManagedFieldsManager()) @@ -688,7 +684,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, if err != nil { return errors.Wrap(err, "failed to replace object") } - c.Log("Replaced %q with kind %s for kind %s", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind) + slog.Debug("replace succeeded", "name", target.Name, "initialKind", currentObj.GetObjectKind().GroupVersionKind().Kind, "kind", kind) } else { patch, patchType, err := createPatch(target, currentObj, threeWayMergeForUnstructured) if err != nil { @@ -696,7 +692,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, } if patch == nil || string(patch) == "{}" { - c.Log("Looks like there are no changes for %s %q", kind, target.Name) + slog.Debug("no changes detected", "kind", kind, "name", target.Name) // This needs to happen to make sure that Helm has the latest info from the API // Otherwise there will be no labels and other functions that use labels will panic if err := target.Get(); err != nil { @@ -705,7 +701,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, return nil } // send patch to server - c.Log("Patch %s %q in namespace %s", kind, target.Name, target.Namespace) + slog.Debug("patching resource", "kind", kind, "name", target.Name, "namespace", target.Namespace) obj, err = helper.Patch(target.Namespace, target.Name, patchType, patch, nil) if err != nil { return errors.Wrapf(err, "cannot patch %q with kind %s", target.Name, kind) @@ -752,9 +748,6 @@ func copyRequestStreamToWriter(request *rest.Request, podName, containerName str if err != nil { return errors.Errorf("Failed to copy IO from logs for pod: %s, container: %s", podName, containerName) } - if err != nil { - return errors.Errorf("Failed to close reader for pod: %s, container: %s", podName, containerName) - } return nil } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index 2238b34c0..56c7eebc9 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -112,7 +112,6 @@ func newTestClient(t *testing.T) *Client { return &Client{ Factory: testFactory.WithNamespace("default"), - Log: nopLogger, } } diff --git a/pkg/kube/ready.go b/pkg/kube/ready.go index dd5869e6a..feda44d63 100644 --- a/pkg/kube/ready.go +++ b/pkg/kube/ready.go @@ -19,6 +19,7 @@ package kube // import "helm.sh/helm/v4/pkg/kube" import ( "context" "fmt" + "log/slog" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" @@ -57,13 +58,9 @@ func CheckJobs(checkJobs bool) ReadyCheckerOption { // NewReadyChecker creates a new checker. Passed ReadyCheckerOptions can // be used to override defaults. -func NewReadyChecker(cl kubernetes.Interface, log func(string, ...interface{}), opts ...ReadyCheckerOption) ReadyChecker { +func NewReadyChecker(cl kubernetes.Interface, opts ...ReadyCheckerOption) ReadyChecker { c := ReadyChecker{ client: cl, - log: log, - } - if c.log == nil { - c.log = nopLogger } for _, opt := range opts { opt(&c) @@ -74,7 +71,6 @@ func NewReadyChecker(cl kubernetes.Interface, log func(string, ...interface{}), // ReadyChecker is a type that can check core Kubernetes types for readiness. type ReadyChecker struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -230,20 +226,21 @@ func (c *ReadyChecker) isPodReady(pod *corev1.Pod) bool { return true } } - c.log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName()) + slog.Debug("Pod is not ready", "namespace", pod.GetNamespace(), "name", pod.GetName()) return false } func (c *ReadyChecker) jobReady(job *batchv1.Job) (bool, error) { if job.Status.Failed > *job.Spec.BackoffLimit { - c.log("Job is failed: %s/%s", job.GetNamespace(), job.GetName()) + slog.Debug("Job is failed", "namespace", job.GetNamespace(), "name", job.GetName()) // If a job is failed, it can't recover, so throw an error return false, fmt.Errorf("job is failed: %s/%s", job.GetNamespace(), job.GetName()) } if job.Spec.Completions != nil && job.Status.Succeeded < *job.Spec.Completions { - c.log("Job is not completed: %s/%s", job.GetNamespace(), job.GetName()) + slog.Debug("Job is not completed", "namespace", job.GetNamespace(), "name", job.GetName()) return false, nil } + slog.Debug("Job is completed", "namespace", job.GetNamespace(), "name", job.GetName()) return true, nil } @@ -255,7 +252,7 @@ func (c *ReadyChecker) serviceReady(s *corev1.Service) bool { // Ensure that the service cluster IP is not empty if s.Spec.ClusterIP == "" { - c.log("Service does not have cluster IP address: %s/%s", s.GetNamespace(), s.GetName()) + slog.Debug("Service does not have cluster IP address", "namespace", s.GetNamespace(), "name", s.GetName()) return false } @@ -263,24 +260,25 @@ func (c *ReadyChecker) serviceReady(s *corev1.Service) bool { if s.Spec.Type == corev1.ServiceTypeLoadBalancer { // do not wait when at least 1 external IP is set if len(s.Spec.ExternalIPs) > 0 { - c.log("Service %s/%s has external IP addresses (%v), marking as ready", s.GetNamespace(), s.GetName(), s.Spec.ExternalIPs) + slog.Debug("Service has external IP addresses", "namespace", s.GetNamespace(), "name", s.GetName(), "externalIPs", s.Spec.ExternalIPs) return true } if s.Status.LoadBalancer.Ingress == nil { - c.log("Service does not have load balancer ingress IP address: %s/%s", s.GetNamespace(), s.GetName()) + slog.Debug("Service does not have load balancer ingress IP address", "namespace", s.GetNamespace(), "name", s.GetName()) return false } } - + slog.Debug("Service is ready", "namespace", s.GetNamespace(), "name", s.GetName(), "clusterIP", s.Spec.ClusterIP, "externalIPs", s.Spec.ExternalIPs) return true } func (c *ReadyChecker) volumeReady(v *corev1.PersistentVolumeClaim) bool { if v.Status.Phase != corev1.ClaimBound { - c.log("PersistentVolumeClaim is not bound: %s/%s", v.GetNamespace(), v.GetName()) + slog.Debug("PersistentVolumeClaim is not bound", "namespace", v.GetNamespace(), "name", v.GetName()) return false } + slog.Debug("PersistentVolumeClaim is bound", "namespace", v.GetNamespace(), "name", v.GetName(), "phase", v.Status.Phase) return true } @@ -291,22 +289,23 @@ func (c *ReadyChecker) deploymentReady(rs *appsv1.ReplicaSet, dep *appsv1.Deploy } // Verify the generation observed by the deployment controller matches the spec generation if dep.Status.ObservedGeneration != dep.ObjectMeta.Generation { - c.log("Deployment is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", dep.Namespace, dep.Name, dep.Status.ObservedGeneration, dep.ObjectMeta.Generation) + slog.Debug("Deployment is not ready, observedGeneration does not match spec generation", "namespace", dep.GetNamespace(), "name", dep.GetName(), "actualGeneration", dep.Status.ObservedGeneration, "expectedGeneration", dep.ObjectMeta.Generation) return false } expectedReady := *dep.Spec.Replicas - deploymentutil.MaxUnavailable(*dep) if !(rs.Status.ReadyReplicas >= expectedReady) { - c.log("Deployment is not ready: %s/%s. %d out of %d expected pods are ready", dep.Namespace, dep.Name, rs.Status.ReadyReplicas, expectedReady) + slog.Debug("Deployment does not have enough pods ready", "namespace", dep.GetNamespace(), "name", dep.GetName(), "readyPods", rs.Status.ReadyReplicas, "totalPods", expectedReady) return false } + slog.Debug("Deployment is ready", "namespace", dep.GetNamespace(), "name", dep.GetName(), "readyPods", rs.Status.ReadyReplicas, "totalPods", expectedReady) return true } func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool { // Verify the generation observed by the daemonSet controller matches the spec generation if ds.Status.ObservedGeneration != ds.ObjectMeta.Generation { - c.log("DaemonSet is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", ds.Namespace, ds.Name, ds.Status.ObservedGeneration, ds.ObjectMeta.Generation) + slog.Debug("DaemonSet is not ready, observedGeneration does not match spec generation", "namespace", ds.GetNamespace(), "name", ds.GetName(), "observedGeneration", ds.Status.ObservedGeneration, "expectedGeneration", ds.ObjectMeta.Generation) return false } @@ -317,7 +316,7 @@ func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool { // Make sure all the updated pods have been scheduled if ds.Status.UpdatedNumberScheduled != ds.Status.DesiredNumberScheduled { - c.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", ds.Namespace, ds.Name, ds.Status.UpdatedNumberScheduled, ds.Status.DesiredNumberScheduled) + slog.Debug("DaemonSet does not have enough Pods scheduled", "namespace", ds.GetNamespace(), "name", ds.GetName(), "scheduledPods", ds.Status.UpdatedNumberScheduled, "totalPods", ds.Status.DesiredNumberScheduled) return false } maxUnavailable, err := intstr.GetScaledValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, int(ds.Status.DesiredNumberScheduled), true) @@ -330,9 +329,10 @@ func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool { expectedReady := int(ds.Status.DesiredNumberScheduled) - maxUnavailable if !(int(ds.Status.NumberReady) >= expectedReady) { - c.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods are ready", ds.Namespace, ds.Name, ds.Status.NumberReady, expectedReady) + slog.Debug("DaemonSet does not have enough Pods ready", "namespace", ds.GetNamespace(), "name", ds.GetName(), "readyPods", ds.Status.NumberReady, "totalPods", expectedReady) return false } + slog.Debug("DaemonSet is ready", "namespace", ds.GetNamespace(), "name", ds.GetName(), "readyPods", ds.Status.NumberReady, "totalPods", expectedReady) return true } @@ -382,13 +382,13 @@ func (c *ReadyChecker) crdReady(crd apiextv1.CustomResourceDefinition) bool { func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { // Verify the generation observed by the statefulSet controller matches the spec generation if sts.Status.ObservedGeneration != sts.ObjectMeta.Generation { - c.log("StatefulSet is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", sts.Namespace, sts.Name, sts.Status.ObservedGeneration, sts.ObjectMeta.Generation) + slog.Debug("StatefulSet is not ready, observedGeneration doest not match spec generation", "namespace", sts.GetNamespace(), "name", sts.GetName(), "actualGeneration", sts.Status.ObservedGeneration, "expectedGeneration", sts.ObjectMeta.Generation) return false } // If the update strategy is not a rolling update, there will be nothing to wait for if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType { - c.log("StatefulSet skipped ready check: %s/%s. updateStrategy is %v", sts.Namespace, sts.Name, sts.Spec.UpdateStrategy.Type) + slog.Debug("StatefulSet skipped ready check", "namespace", sts.GetNamespace(), "name", sts.GetName(), "updateStrategy", sts.Spec.UpdateStrategy.Type) return true } @@ -414,30 +414,29 @@ func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { // Make sure all the updated pods have been scheduled if int(sts.Status.UpdatedReplicas) < expectedReplicas { - c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", sts.Namespace, sts.Name, sts.Status.UpdatedReplicas, expectedReplicas) + slog.Debug("StatefulSet does not have enough Pods scheduled", "namespace", sts.GetNamespace(), "name", sts.GetName(), "readyPods", sts.Status.UpdatedReplicas, "totalPods", expectedReplicas) return false } if int(sts.Status.ReadyReplicas) != replicas { - c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas) + slog.Debug("StatefulSet does not have enough Pods ready", "namespace", sts.GetNamespace(), "name", sts.GetName(), "readyPods", sts.Status.ReadyReplicas, "totalPods", replicas) return false } // This check only makes sense when all partitions are being upgraded otherwise during a // partitioned rolling upgrade, this condition will never evaluate to true, leading to // error. if partition == 0 && sts.Status.CurrentRevision != sts.Status.UpdateRevision { - c.log("StatefulSet is not ready: %s/%s. currentRevision %s does not yet match updateRevision %s", sts.Namespace, sts.Name, sts.Status.CurrentRevision, sts.Status.UpdateRevision) + slog.Debug("StatefulSet is not ready, currentRevision does not match updateRevision", "namespace", sts.GetNamespace(), "name", sts.GetName(), "currentRevision", sts.Status.CurrentRevision, "updateRevision", sts.Status.UpdateRevision) return false } - - c.log("StatefulSet is ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas) + slog.Debug("StatefulSet is ready", "namespace", sts.GetNamespace(), "name", sts.GetName(), "readyPods", sts.Status.ReadyReplicas, "totalPods", replicas) return true } func (c *ReadyChecker) replicationControllerReady(rc *corev1.ReplicationController) bool { // Verify the generation observed by the replicationController controller matches the spec generation if rc.Status.ObservedGeneration != rc.ObjectMeta.Generation { - c.log("ReplicationController is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", rc.Namespace, rc.Name, rc.Status.ObservedGeneration, rc.ObjectMeta.Generation) + slog.Debug("ReplicationController is not ready, observedGeneration doest not match spec generation", "namespace", rc.GetNamespace(), "name", rc.GetName(), "actualGeneration", rc.Status.ObservedGeneration, "expectedGeneration", rc.ObjectMeta.Generation) return false } return true @@ -446,7 +445,7 @@ func (c *ReadyChecker) replicationControllerReady(rc *corev1.ReplicationControll func (c *ReadyChecker) replicaSetReady(rs *appsv1.ReplicaSet) bool { // Verify the generation observed by the replicaSet controller matches the spec generation if rs.Status.ObservedGeneration != rs.ObjectMeta.Generation { - c.log("ReplicaSet is not ready: %s/%s. observedGeneration (%d) does not match spec generation (%d).", rs.Namespace, rs.Name, rs.Status.ObservedGeneration, rs.ObjectMeta.Generation) + slog.Debug("ReplicaSet is not ready, observedGeneration doest not match spec generation", "namespace", rs.GetNamespace(), "name", rs.GetName(), "actualGeneration", rs.Status.ObservedGeneration, "expectedGeneration", rs.ObjectMeta.Generation) return false } return true diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go index 879fa4c76..9d1dfd272 100644 --- a/pkg/kube/ready_test.go +++ b/pkg/kube/ready_test.go @@ -37,7 +37,6 @@ const defaultNamespace = metav1.NamespaceDefault func Test_ReadyChecker_IsReady_Pod(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -57,7 +56,6 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) { name: "IsReady Pod", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -73,7 +71,6 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) { name: "IsReady Pod returns error", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -90,7 +87,6 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -113,7 +109,6 @@ func Test_ReadyChecker_IsReady_Pod(t *testing.T) { func Test_ReadyChecker_IsReady_Job(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -133,7 +128,6 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) { name: "IsReady Job error while getting job", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -149,7 +143,6 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) { name: "IsReady Job", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -166,7 +159,6 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -188,7 +180,6 @@ func Test_ReadyChecker_IsReady_Job(t *testing.T) { func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -209,7 +200,6 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { name: "IsReady Deployments error while getting current Deployment", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -226,7 +216,6 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { name: "IsReady Deployments", //TODO fix this one fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -244,7 +233,6 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -270,7 +258,6 @@ func Test_ReadyChecker_IsReady_Deployment(t *testing.T) { func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -290,7 +277,6 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { name: "IsReady PersistentVolumeClaim", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -306,7 +292,6 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { name: "IsReady PersistentVolumeClaim with error", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -323,7 +308,6 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -345,7 +329,6 @@ func Test_ReadyChecker_IsReady_PersistentVolumeClaim(t *testing.T) { func Test_ReadyChecker_IsReady_Service(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -365,7 +348,6 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) { name: "IsReady Service", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -381,7 +363,6 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) { name: "IsReady Service with error", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -398,7 +379,6 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -420,7 +400,6 @@ func Test_ReadyChecker_IsReady_Service(t *testing.T) { func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -440,7 +419,6 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { name: "IsReady DaemonSet", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -456,7 +434,6 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { name: "IsReady DaemonSet with error", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -473,7 +450,6 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -495,7 +471,6 @@ func Test_ReadyChecker_IsReady_DaemonSet(t *testing.T) { func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -515,7 +490,6 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { name: "IsReady StatefulSet", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -531,7 +505,6 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { name: "IsReady StatefulSet with error", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -548,7 +521,6 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -570,7 +542,6 @@ func Test_ReadyChecker_IsReady_StatefulSet(t *testing.T) { func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -590,7 +561,6 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { name: "IsReady ReplicationController", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -606,7 +576,6 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { name: "IsReady ReplicationController with error", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -622,7 +591,6 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { name: "IsReady ReplicationController and pods not ready for object", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -639,7 +607,6 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -661,7 +628,6 @@ func Test_ReadyChecker_IsReady_ReplicationController(t *testing.T) { func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) { type fields struct { client kubernetes.Interface - log func(string, ...interface{}) checkJobs bool pausedAsReady bool } @@ -681,7 +647,6 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) { name: "IsReady ReplicaSet", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -697,7 +662,6 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) { name: "IsReady ReplicaSet not ready", fields: fields{ client: fake.NewClientset(), - log: func(string, ...interface{}) {}, checkJobs: true, pausedAsReady: false, }, @@ -714,7 +678,6 @@ func Test_ReadyChecker_IsReady_ReplicaSet(t *testing.T) { t.Run(tt.name, func(t *testing.T) { c := &ReadyChecker{ client: tt.fields.client, - log: tt.fields.log, checkJobs: tt.fields.checkJobs, pausedAsReady: tt.fields.pausedAsReady, } @@ -791,7 +754,7 @@ func Test_ReadyChecker_deploymentReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.deploymentReady(tt.args.rs, tt.args.dep); got != tt.want { t.Errorf("deploymentReady() = %v, want %v", got, tt.want) } @@ -825,7 +788,7 @@ func Test_ReadyChecker_replicaSetReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.replicaSetReady(tt.args.rs); got != tt.want { t.Errorf("replicaSetReady() = %v, want %v", got, tt.want) } @@ -859,7 +822,7 @@ func Test_ReadyChecker_replicationControllerReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.replicationControllerReady(tt.args.rc); got != tt.want { t.Errorf("replicationControllerReady() = %v, want %v", got, tt.want) } @@ -914,7 +877,7 @@ func Test_ReadyChecker_daemonSetReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.daemonSetReady(tt.args.ds); got != tt.want { t.Errorf("daemonSetReady() = %v, want %v", got, tt.want) } @@ -990,7 +953,7 @@ func Test_ReadyChecker_statefulSetReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.statefulSetReady(tt.args.sts); got != tt.want { t.Errorf("statefulSetReady() = %v, want %v", got, tt.want) } @@ -1049,7 +1012,7 @@ func Test_ReadyChecker_podsReadyForObject(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) for _, pod := range tt.existPods { if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(context.TODO(), &pod, metav1.CreateOptions{}); err != nil { t.Errorf("Failed to create Pod error: %v", err) @@ -1128,7 +1091,7 @@ func Test_ReadyChecker_jobReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) got, err := c.jobReady(tt.args.job) if (err != nil) != tt.wantErr { t.Errorf("jobReady() error = %v, wantErr %v", err, tt.wantErr) @@ -1167,7 +1130,7 @@ func Test_ReadyChecker_volumeReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) if got := c.volumeReady(tt.args.v); got != tt.want { t.Errorf("volumeReady() = %v, want %v", got, tt.want) } @@ -1212,7 +1175,7 @@ func Test_ReadyChecker_serviceReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) got := c.serviceReady(tt.args.service) if got != tt.want { t.Errorf("serviceReady() = %v, want %v", got, tt.want) @@ -1281,7 +1244,7 @@ func Test_ReadyChecker_crdBetaReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) got := c.crdBetaReady(tt.args.crdBeta) if got != tt.want { t.Errorf("crdBetaReady() = %v, want %v", got, tt.want) @@ -1350,7 +1313,7 @@ func Test_ReadyChecker_crdReady(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewClientset(), nil) + c := NewReadyChecker(fake.NewClientset()) got := c.crdReady(tt.args.crdBeta) if got != tt.want { t.Errorf("crdBetaReady() = %v, want %v", got, tt.want) diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index 22242b40f..2d7cfe971 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "log/slog" "sort" "time" @@ -42,7 +43,6 @@ import ( type statusWaiter struct { client dynamic.Interface restMapper meta.RESTMapper - log func(string, ...interface{}) } func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) { @@ -55,7 +55,7 @@ func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) { func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - w.log("waiting for %d pods and jobs to complete with a timeout of %s", len(resourceList), timeout) + slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper) @@ -76,7 +76,7 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) error { ctx, cancel := context.WithTimeout(context.TODO(), timeout) defer cancel() - w.log("beginning wait for %d resources with timeout of %s", len(resourceList), timeout) + slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) return w.wait(ctx, resourceList, sw) } @@ -84,7 +84,7 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Duration) error { ctx, cancel := context.WithTimeout(context.TODO(), timeout) defer cancel() - w.log("beginning wait for %d resources with timeout of %s", len(resourceList), timeout) + slog.Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) customSR := statusreaders.NewStatusReader(w.restMapper, newCustomJobStatusReader) @@ -95,7 +95,7 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Duration) error { ctx, cancel := context.WithTimeout(context.TODO(), timeout) defer cancel() - w.log("beginning wait for %d resources to be deleted with timeout of %s", len(resourceList), timeout) + slog.Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) return w.waitForDelete(ctx, resourceList, sw) } @@ -113,7 +113,7 @@ func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceL } eventCh := sw.Watch(cancelCtx, resources, watcher.Options{}) statusCollector := collector.NewResourceStatusCollector(resources) - done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.NotFoundStatus, w.log)) + done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.NotFoundStatus)) <-done if statusCollector.Error != nil { @@ -156,7 +156,7 @@ func (w *statusWaiter) wait(ctx context.Context, resourceList ResourceList, sw w eventCh := sw.Watch(cancelCtx, resources, watcher.Options{}) statusCollector := collector.NewResourceStatusCollector(resources) - done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.CurrentStatus, w.log)) + done := statusCollector.ListenWithObserver(eventCh, statusObserver(cancel, status.CurrentStatus)) <-done if statusCollector.Error != nil { @@ -179,7 +179,7 @@ func (w *statusWaiter) wait(ctx context.Context, resourceList ResourceList, sw w return nil } -func statusObserver(cancel context.CancelFunc, desired status.Status, logFn func(string, ...interface{})) collector.ObserverFunc { +func statusObserver(cancel context.CancelFunc, desired status.Status) collector.ObserverFunc { return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) { var rss []*event.ResourceStatus var nonDesiredResources []*event.ResourceStatus @@ -209,8 +209,7 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logFn func return nonDesiredResources[i].Identifier.Name < nonDesiredResources[j].Identifier.Name }) first := nonDesiredResources[0] - logFn("waiting for resource: name: %s, kind: %s, desired status: %s, actual status: %s \n", - first.Identifier.Name, first.Identifier.GroupKind.Kind, desired, first.Status) + slog.Debug("waiting for resource", "name", first.Identifier.Name, "kind", first.Identifier.GroupKind.Kind, "expectedStatus", desired, "actualStatus", first.Status) } } } diff --git a/pkg/kube/statuswait_test.go b/pkg/kube/statuswait_test.go index fee325ddc..0b309b22d 100644 --- a/pkg/kube/statuswait_test.go +++ b/pkg/kube/statuswait_test.go @@ -217,7 +217,6 @@ func TestStatusWaitForDelete(t *testing.T) { statusWaiter := statusWaiter{ restMapper: fakeMapper, client: fakeClient, - log: t.Logf, } objsToCreate := getRuntimeObjFromManifests(t, tt.manifestsToCreate) for _, objToCreate := range objsToCreate { @@ -258,7 +257,6 @@ func TestStatusWaitForDeleteNonExistentObject(t *testing.T) { statusWaiter := statusWaiter{ restMapper: fakeMapper, client: fakeClient, - log: t.Logf, } // Don't create the object to test that the wait for delete works when the object doesn't exist objManifest := getRuntimeObjFromManifests(t, []string{podCurrentManifest}) @@ -317,7 +315,6 @@ func TestStatusWait(t *testing.T) { statusWaiter := statusWaiter{ client: fakeClient, restMapper: fakeMapper, - log: t.Logf, } objs := getRuntimeObjFromManifests(t, tt.objManifests) for _, obj := range objs { @@ -371,7 +368,6 @@ func TestWaitForJobComplete(t *testing.T) { statusWaiter := statusWaiter{ client: fakeClient, restMapper: fakeMapper, - log: t.Logf, } objs := getRuntimeObjFromManifests(t, tt.objManifests) for _, obj := range objs { @@ -431,7 +427,6 @@ func TestWatchForReady(t *testing.T) { statusWaiter := statusWaiter{ client: fakeClient, restMapper: fakeMapper, - log: t.Logf, } objs := getRuntimeObjFromManifests(t, tt.objManifests) for _, obj := range objs { diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index 79a2df8cc..f384193e6 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -50,24 +50,23 @@ import ( // Helm 4 now uses the StatusWaiter implementation instead type legacyWaiter struct { c ReadyChecker - log func(string, ...interface{}) kubeClient *kubernetes.Clientset } func (hw *legacyWaiter) Wait(resources ResourceList, timeout time.Duration) error { - hw.c = NewReadyChecker(hw.kubeClient, hw.log, PausedAsReady(true)) + hw.c = NewReadyChecker(hw.kubeClient, PausedAsReady(true)) return hw.waitForResources(resources, timeout) } func (hw *legacyWaiter) WaitWithJobs(resources ResourceList, timeout time.Duration) error { - hw.c = NewReadyChecker(hw.kubeClient, hw.log, PausedAsReady(true), CheckJobs(true)) + hw.c = NewReadyChecker(hw.kubeClient, PausedAsReady(true), CheckJobs(true)) return hw.waitForResources(resources, timeout) } // waitForResources polls to get the current status of all pods, PVCs, Services and // Jobs(optional) until all are ready or a timeout is reached func (hw *legacyWaiter) waitForResources(created ResourceList, timeout time.Duration) error { - hw.log("beginning wait for %d resources with timeout of %v", len(created), timeout) + slog.Debug("beginning wait for resources", "count", len(created), "timeout", timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -85,10 +84,10 @@ func (hw *legacyWaiter) waitForResources(created ResourceList, timeout time.Dura if waitRetries > 0 && hw.isRetryableError(err, v) { numberOfErrors[i]++ if numberOfErrors[i] > waitRetries { - hw.log("Max number of retries reached") + slog.Debug("max number of retries reached", "resource", v.Name, "retries", numberOfErrors[i]) return false, err } - hw.log("Retrying as current number of retries %d less than max number of retries %d", numberOfErrors[i]-1, waitRetries) + slog.Debug("retrying resource readiness", "resource", v.Name, "currentRetries", numberOfErrors[i]-1, "maxRetries", waitRetries) return false, nil } numberOfErrors[i] = 0 @@ -104,14 +103,14 @@ func (hw *legacyWaiter) isRetryableError(err error, resource *resource.Info) boo if err == nil { return false } - hw.log("Error received when checking status of resource %s. Error: '%s', Resource details: '%s'", resource.Name, err, resource) + slog.Debug("error received when checking resource status", "resource", resource.Name, slog.Any("error", err)) if ev, ok := err.(*apierrors.StatusError); ok { statusCode := ev.Status().Code retryable := hw.isRetryableHTTPStatusCode(statusCode) - hw.log("Status code received: %d. Retryable error? %t", statusCode, retryable) + slog.Debug("status code received", "resource", resource.Name, "statusCode", statusCode, "retryable", retryable) return retryable } - hw.log("Retryable error? %t", true) + slog.Debug("retryable error assumed", "resource", resource.Name) return true } @@ -249,7 +248,7 @@ func (hw *legacyWaiter) watchUntilReady(timeout time.Duration, info *resource.In return nil } - hw.log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout) + slog.Debug("watching for resource changes", "kind", kind, "resource", info.Name, "timeout", timeout) // Use a selector on the name of the resource. This should be unique for the // given version and kind @@ -277,7 +276,8 @@ func (hw *legacyWaiter) watchUntilReady(timeout time.Duration, info *resource.In // we get. We care mostly about jobs, where what we want to see is // the status go into a good state. For other types, like ReplicaSet // we don't really do anything to support these as hooks. - hw.log("Add/Modify event for %s: %v", info.Name, e.Type) + slog.Debug("add/modify event received", "resource", info.Name, "eventType", e.Type) + switch kind { case "Job": return hw.waitForJob(obj, info.Name) @@ -286,11 +286,11 @@ func (hw *legacyWaiter) watchUntilReady(timeout time.Duration, info *resource.In } return true, nil case watch.Deleted: - hw.log("Deleted event for %s", info.Name) + slog.Debug("deleted event received", "resource", info.Name) return true, nil case watch.Error: // Handle error and return with an error. - hw.log("Error event for %s", info.Name) + slog.Error("error event received", "resource", info.Name) return true, errors.Errorf("failed to deploy %s", info.Name) default: return false, nil @@ -312,11 +312,12 @@ func (hw *legacyWaiter) waitForJob(obj runtime.Object, name string) (bool, error if c.Type == batchv1.JobComplete && c.Status == "True" { return true, nil } else if c.Type == batchv1.JobFailed && c.Status == "True" { + slog.Error("job failed", "job", name, "reason", c.Reason) return true, errors.Errorf("job %s failed: %s", name, c.Reason) } } - hw.log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded) + slog.Debug("job status update", "job", name, "active", o.Status.Active, "failed", o.Status.Failed, "succeeded", o.Status.Succeeded) return false, nil } @@ -331,14 +332,15 @@ func (hw *legacyWaiter) waitForPodSuccess(obj runtime.Object, name string) (bool switch o.Status.Phase { case corev1.PodSucceeded: - hw.log("Pod %s succeeded", o.Name) + slog.Debug("pod succeeded", "pod", o.Name) return true, nil case corev1.PodFailed: + slog.Error("pod failed", "pod", o.Name) return true, errors.Errorf("pod %s failed", o.Name) case corev1.PodPending: - hw.log("Pod %s pending", o.Name) + slog.Debug("pod pending", "pod", o.Name) case corev1.PodRunning: - hw.log("Pod %s running", o.Name) + slog.Debug("pod running", "pod", o.Name) } return false, nil diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 287968340..4d421f5bf 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -24,7 +24,6 @@ import ( "os" "path" "path/filepath" - "regexp" "strings" "github.com/pkg/errors" @@ -39,11 +38,6 @@ import ( "helm.sh/helm/v4/pkg/lint/support" ) -var ( - crdHookSearch = regexp.MustCompile(`"?helm\.sh/hook"?:\s+crd-install`) - releaseTimeSearch = regexp.MustCompile(`\.Release\.Time`) -) - // Templates lints the templates in the Linter. func Templates(linter *support.Linter, values map[string]interface{}, namespace string, _ bool) { TemplatesWithKubeVersion(linter, values, namespace, nil) @@ -119,14 +113,10 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string - Metadata.Namespace is not set */ for _, template := range chart.Templates { - fileName, data := template.Name, template.Data + fileName := template.Name fpath = fileName linter.RunLinterRule(support.ErrorSev, fpath, validateAllowedExtension(fileName)) - // These are v3 specific checks to make sure and warn people if their - // chart is not compatible with v3 - linter.RunLinterRule(support.WarningSev, fpath, validateNoCRDHooks(data)) - linter.RunLinterRule(support.ErrorSev, fpath, validateNoReleaseTime(data)) // We only apply the following lint rules to yaml files if filepath.Ext(fileName) != ".yaml" || filepath.Ext(fileName) == ".yml" { @@ -291,20 +281,6 @@ func validateMetadataNameFunc(obj *K8sYamlStruct) validation.ValidateNameFunc { } } -func validateNoCRDHooks(manifest []byte) error { - if crdHookSearch.Match(manifest) { - return errors.New("manifest is a crd-install hook. This hook is no longer supported in v3 and all CRDs should also exist the crds/ directory at the top level of the chart") - } - return nil -} - -func validateNoReleaseTime(manifest []byte) error { - if releaseTimeSearch.Match(manifest) { - return errors.New(".Release.Time has been removed in v3, please replace with the `now` function in your templates") - } - return nil -} - // validateMatchSelector ensures that template specs have a selector declared. // See https://github.com/helm/helm/issues/1990 func validateMatchSelector(yamlStruct *K8sYamlStruct, manifest string) error { diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go index 7205ace6d..bd503368d 100644 --- a/pkg/lint/rules/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -85,26 +85,6 @@ func TestTemplateIntegrationHappyPath(t *testing.T) { } } -func TestV3Fail(t *testing.T) { - linter := support.Linter{ChartDir: "./testdata/v3-fail"} - Templates(&linter, values, namespace, strict) - res := linter.Messages - - if len(res) != 3 { - t.Fatalf("Expected 3 errors, got %d, %v", len(res), res) - } - - if !strings.Contains(res[0].Err.Error(), ".Release.Time has been removed in v3") { - t.Errorf("Unexpected error: %s", res[0].Err) - } - if !strings.Contains(res[1].Err.Error(), "manifest is a crd-install hook") { - t.Errorf("Unexpected error: %s", res[1].Err) - } - if !strings.Contains(res[2].Err.Error(), "manifest is a crd-install hook") { - t.Errorf("Unexpected error: %s", res[2].Err) - } -} - func TestMultiTemplateFail(t *testing.T) { linter := support.Linter{ChartDir: "./testdata/multi-template-fail"} Templates(&linter, values, namespace, strict) diff --git a/pkg/release/util/kind_sorter.go b/pkg/release/util/kind_sorter.go index 22795733c..bc074340f 100644 --- a/pkg/release/util/kind_sorter.go +++ b/pkg/release/util/kind_sorter.go @@ -65,12 +65,17 @@ var InstallOrder KindSortOrder = []string{ "IngressClass", "Ingress", "APIService", + "MutatingWebhookConfiguration", + "ValidatingWebhookConfiguration", } // UninstallOrder is the order in which manifests should be uninstalled (by Kind). // // Those occurring earlier in the list get uninstalled before those occurring later in the list. var UninstallOrder KindSortOrder = []string{ + // For uninstall, we remove validation before mutation to ensure webhooks don't block removal + "ValidatingWebhookConfiguration", + "MutatingWebhookConfiguration", "APIService", "Ingress", "IngressClass", diff --git a/pkg/release/util/kind_sorter_test.go b/pkg/release/util/kind_sorter_test.go index 00d80ecf2..919de24e5 100644 --- a/pkg/release/util/kind_sorter_test.go +++ b/pkg/release/util/kind_sorter_test.go @@ -173,6 +173,14 @@ func TestKindSorter(t *testing.T) { Name: "F", Head: &SimpleHead{Kind: "PriorityClass"}, }, + { + Name: "M", + Head: &SimpleHead{Kind: "MutatingWebhookConfiguration"}, + }, + { + Name: "V", + Head: &SimpleHead{Kind: "ValidatingWebhookConfiguration"}, + }, } for _, test := range []struct { @@ -180,8 +188,8 @@ func TestKindSorter(t *testing.T) { order KindSortOrder expected string }{ - {"install", InstallOrder, "FaAbcC3deEf1gh2iIjJkKlLmnopqrxstuUvw!"}, - {"uninstall", UninstallOrder, "wvUmutsxrqponLlKkJjIi2hg1fEed3CcbAaF!"}, + {"install", InstallOrder, "FaAbcC3deEf1gh2iIjJkKlLmnopqrxstuUvwMV!"}, + {"uninstall", UninstallOrder, "VMwvUmutsxrqponLlKkJjIi2hg1fEed3CcbAaF!"}, } { var buf bytes.Buffer t.Run(test.description, func(t *testing.T) { diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go index 2b84b7f82..3e4acfd81 100644 --- a/pkg/storage/driver/cfgmaps.go +++ b/pkg/storage/driver/cfgmaps.go @@ -19,6 +19,7 @@ package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "context" "fmt" + "log/slog" "strconv" "strings" "time" @@ -43,7 +44,6 @@ const ConfigMapsDriverName = "ConfigMap" // ConfigMapsInterface. type ConfigMaps struct { impl corev1.ConfigMapInterface - Log func(string, ...interface{}) } // NewConfigMaps initializes a new ConfigMaps wrapping an implementation of @@ -51,7 +51,6 @@ type ConfigMaps struct { func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps { return &ConfigMaps{ impl: impl, - Log: func(_ string, _ ...interface{}) {}, } } @@ -70,13 +69,13 @@ func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) { return nil, ErrReleaseNotFound } - cfgmaps.Log("get: failed to get %q: %s", key, err) + slog.Debug("failed to get release", "key", key, slog.Any("error", err)) return nil, err } // found the configmap, decode the base64 data string r, err := decodeRelease(obj.Data["release"]) if err != nil { - cfgmaps.Log("get: failed to decode data %q: %s", key, err) + slog.Debug("failed to decode data", "key", key, slog.Any("error", err)) return nil, err } r.Labels = filterSystemLabels(obj.ObjectMeta.Labels) @@ -93,7 +92,7 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas list, err := cfgmaps.impl.List(context.Background(), opts) if err != nil { - cfgmaps.Log("list: failed to list: %s", err) + slog.Debug("failed to list releases", slog.Any("error", err)) return nil, err } @@ -104,7 +103,7 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas for _, item := range list.Items { rls, err := decodeRelease(item.Data["release"]) if err != nil { - cfgmaps.Log("list: failed to decode release: %v: %s", item, err) + slog.Debug("failed to decode release", "item", item, slog.Any("error", err)) continue } @@ -132,7 +131,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err list, err := cfgmaps.impl.List(context.Background(), opts) if err != nil { - cfgmaps.Log("query: failed to query with labels: %s", err) + slog.Debug("failed to query with labels", slog.Any("error", err)) return nil, err } @@ -144,7 +143,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err for _, item := range list.Items { rls, err := decodeRelease(item.Data["release"]) if err != nil { - cfgmaps.Log("query: failed to decode release: %s", err) + slog.Debug("failed to decode release", slog.Any("error", err)) continue } rls.Labels = item.ObjectMeta.Labels @@ -166,7 +165,7 @@ func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error { // create a new configmap to hold the release obj, err := newConfigMapsObject(key, rls, lbs) if err != nil { - cfgmaps.Log("create: failed to encode release %q: %s", rls.Name, err) + slog.Debug("failed to encode release", "name", rls.Name, slog.Any("error", err)) return err } // push the configmap object out into the kubiverse @@ -175,7 +174,7 @@ func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error { return ErrReleaseExists } - cfgmaps.Log("create: failed to create: %s", err) + slog.Debug("failed to create release", slog.Any("error", err)) return err } return nil @@ -194,13 +193,13 @@ func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error { // create a new configmap object to hold the release obj, err := newConfigMapsObject(key, rls, lbs) if err != nil { - cfgmaps.Log("update: failed to encode release %q: %s", rls.Name, err) + slog.Debug("failed to encode release", "name", rls.Name, slog.Any("error", err)) return err } // push the configmap object out into the kubiverse _, err = cfgmaps.impl.Update(context.Background(), obj, metav1.UpdateOptions{}) if err != nil { - cfgmaps.Log("update: failed to update: %s", err) + slog.Debug("failed to update release", slog.Any("error", err)) return err } return nil diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index 53919b45d..54fda0542 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -262,7 +262,6 @@ func newTestFixtureSQL(t *testing.T, _ ...*rspb.Release) (*SQL, sqlmock.Sqlmock) sqlxDB := sqlx.NewDb(sqlDB, "sqlmock") return &SQL{ db: sqlxDB, - Log: func(_ string, _ ...interface{}) {}, namespace: "default", statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), }, mock diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go index 2ab128c6b..a69f1ed65 100644 --- a/pkg/storage/driver/secrets.go +++ b/pkg/storage/driver/secrets.go @@ -19,6 +19,7 @@ package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "context" "fmt" + "log/slog" "strconv" "strings" "time" @@ -43,7 +44,6 @@ const SecretsDriverName = "Secret" // SecretsInterface. type Secrets struct { impl corev1.SecretInterface - Log func(string, ...interface{}) } // NewSecrets initializes a new Secrets wrapping an implementation of @@ -51,7 +51,6 @@ type Secrets struct { func NewSecrets(impl corev1.SecretInterface) *Secrets { return &Secrets{ impl: impl, - Log: func(_ string, _ ...interface{}) {}, } } @@ -96,7 +95,7 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, for _, item := range list.Items { rls, err := decodeRelease(string(item.Data["release"])) if err != nil { - secrets.Log("list: failed to decode release: %v: %s", item, err) + slog.Debug("list failed to decode release", "key", item.Name, slog.Any("error", err)) continue } @@ -135,7 +134,7 @@ func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) for _, item := range list.Items { rls, err := decodeRelease(string(item.Data["release"])) if err != nil { - secrets.Log("query: failed to decode release: %s", err) + slog.Debug("failed to decode release", "key", item.Name, slog.Any("error", err)) continue } rls.Labels = item.ObjectMeta.Labels diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index 12bdd3ff4..c3740b9a3 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -18,6 +18,7 @@ package driver // import "helm.sh/helm/v4/pkg/storage/driver" import ( "fmt" + "log/slog" "sort" "strconv" "time" @@ -86,8 +87,6 @@ type SQL struct { db *sqlx.DB namespace string statementBuilder sq.StatementBuilderType - - Log func(string, ...interface{}) } // Name returns the name of the driver. @@ -108,13 +107,13 @@ func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool { records, err := migrate.GetMigrationRecords(s.db.DB, postgreSQLDialect) migrate.SetDisableCreateTable(false) if err != nil { - s.Log("checkAlreadyApplied: failed to get migration records: %v", err) + slog.Debug("failed to get migration records", slog.Any("error", err)) return false } for _, record := range records { if _, ok := migrationsIDs[record.Id]; ok { - s.Log("checkAlreadyApplied: found previous migration (Id: %v) applied at %v", record.Id, record.AppliedAt) + slog.Debug("found previous migration", "id", record.Id, "appliedAt", record.AppliedAt) delete(migrationsIDs, record.Id) } } @@ -122,7 +121,7 @@ func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool { // check if all migrations applied if len(migrationsIDs) != 0 { for id := range migrationsIDs { - s.Log("checkAlreadyApplied: find unapplied migration (id: %v)", id) + slog.Debug("find unapplied migration", "id", id) } return false } @@ -276,7 +275,7 @@ type SQLReleaseCustomLabelWrapper struct { } // NewSQL initializes a new sql driver. -func NewSQL(connectionString string, logger func(string, ...interface{}), namespace string) (*SQL, error) { +func NewSQL(connectionString string, namespace string) (*SQL, error) { db, err := sqlx.Connect(postgreSQLDialect, connectionString) if err != nil { return nil, err @@ -284,7 +283,6 @@ func NewSQL(connectionString string, logger func(string, ...interface{}), namesp driver := &SQL{ db: db, - Log: logger, statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), } @@ -309,24 +307,24 @@ func (s *SQL) Get(key string) (*rspb.Release, error) { query, args, err := qb.ToSql() if err != nil { - s.Log("failed to build query: %v", err) + slog.Debug("failed to build query", slog.Any("error", err)) return nil, err } // Get will return an error if the result is empty if err := s.db.Get(&record, query, args...); err != nil { - s.Log("got SQL error when getting release %s: %v", key, err) + slog.Debug("got SQL error when getting release", "key", key, slog.Any("error", err)) return nil, ErrReleaseNotFound } release, err := decodeRelease(record.Body) if err != nil { - s.Log("get: failed to decode data %q: %v", key, err) + slog.Debug("failed to decode data", "key", key, slog.Any("error", err)) return nil, err } if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { - s.Log("failed to get release %s/%s custom labels: %v", s.namespace, key, err) + slog.Debug("failed to get release custom labels", "namespace", s.namespace, "key", key, slog.Any("error", err)) return nil, err } @@ -347,13 +345,13 @@ func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { query, args, err := sb.ToSql() if err != nil { - s.Log("failed to build query: %v", err) + slog.Debug("failed to build query", slog.Any("error", err)) return nil, err } var records = []SQLReleaseWrapper{} if err := s.db.Select(&records, query, args...); err != nil { - s.Log("list: failed to list: %v", err) + slog.Debug("failed to list", slog.Any("error", err)) return nil, err } @@ -361,12 +359,12 @@ func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { for _, record := range records { release, err := decodeRelease(record.Body) if err != nil { - s.Log("list: failed to decode release: %v: %v", record, err) + slog.Debug("failed to decode release", "record", record, slog.Any("error", err)) continue } if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { - s.Log("failed to get release %s/%s custom labels: %v", record.Namespace, record.Key, err) + slog.Debug("failed to get release custom labels", "namespace", record.Namespace, "key", record.Key, slog.Any("error", err)) return nil, err } for k, v := range getReleaseSystemLabels(release) { @@ -396,7 +394,7 @@ func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { if _, ok := labelMap[key]; ok { sb = sb.Where(sq.Eq{key: labels[key]}) } else { - s.Log("unknown label %s", key) + slog.Debug("unknown label", "key", key) return nil, fmt.Errorf("unknown label %s", key) } } @@ -409,13 +407,13 @@ func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { // Build our query query, args, err := sb.ToSql() if err != nil { - s.Log("failed to build query: %v", err) + slog.Debug("failed to build query", slog.Any("error", err)) return nil, err } var records = []SQLReleaseWrapper{} if err := s.db.Select(&records, query, args...); err != nil { - s.Log("list: failed to query with labels: %v", err) + slog.Debug("failed to query with labels", slog.Any("error", err)) return nil, err } @@ -427,12 +425,12 @@ func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { for _, record := range records { release, err := decodeRelease(record.Body) if err != nil { - s.Log("list: failed to decode release: %v: %v", record, err) + slog.Debug("failed to decode release", "record", record, slog.Any("error", err)) continue } if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil { - s.Log("failed to get release %s/%s custom labels: %v", record.Namespace, record.Key, err) + slog.Debug("failed to get release custom labels", "namespace", record.Namespace, "key", record.Key, slog.Any("error", err)) return nil, err } @@ -456,13 +454,13 @@ func (s *SQL) Create(key string, rls *rspb.Release) error { body, err := encodeRelease(rls) if err != nil { - s.Log("failed to encode release: %v", err) + slog.Debug("failed to encode release", slog.Any("error", err)) return err } transaction, err := s.db.Beginx() if err != nil { - s.Log("failed to start SQL transaction: %v", err) + slog.Debug("failed to start SQL transaction", slog.Any("error", err)) return fmt.Errorf("error beginning transaction: %v", err) } @@ -491,7 +489,7 @@ func (s *SQL) Create(key string, rls *rspb.Release) error { int(time.Now().Unix()), ).ToSql() if err != nil { - s.Log("failed to build insert query: %v", err) + slog.Debug("failed to build insert query", slog.Any("error", err)) return err } @@ -505,17 +503,17 @@ func (s *SQL) Create(key string, rls *rspb.Release) error { Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). ToSql() if buildErr != nil { - s.Log("failed to build select query: %v", buildErr) + slog.Debug("failed to build select query", "error", buildErr) return err } var record SQLReleaseWrapper if err := transaction.Get(&record, selectQuery, args...); err == nil { - s.Log("release %s already exists", key) + slog.Debug("release already exists", "key", key) return ErrReleaseExists } - s.Log("failed to store release %s in SQL database: %v", key, err) + slog.Debug("failed to store release in SQL database", "key", key, slog.Any("error", err)) return err } @@ -538,13 +536,13 @@ func (s *SQL) Create(key string, rls *rspb.Release) error { if err != nil { defer transaction.Rollback() - s.Log("failed to build insert query: %v", err) + slog.Debug("failed to build insert query", slog.Any("error", err)) return err } if _, err := transaction.Exec(insertLabelsQuery, args...); err != nil { defer transaction.Rollback() - s.Log("failed to write Labels: %v", err) + slog.Debug("failed to write Labels", slog.Any("error", err)) return err } } @@ -563,7 +561,7 @@ func (s *SQL) Update(key string, rls *rspb.Release) error { body, err := encodeRelease(rls) if err != nil { - s.Log("failed to encode release: %v", err) + slog.Debug("failed to encode release", slog.Any("error", err)) return err } @@ -580,12 +578,12 @@ func (s *SQL) Update(key string, rls *rspb.Release) error { ToSql() if err != nil { - s.Log("failed to build update query: %v", err) + slog.Debug("failed to build update query", slog.Any("error", err)) return err } if _, err := s.db.Exec(query, args...); err != nil { - s.Log("failed to update release %s in SQL database: %v", key, err) + slog.Debug("failed to update release in SQL database", "key", key, slog.Any("error", err)) return err } @@ -596,7 +594,7 @@ func (s *SQL) Update(key string, rls *rspb.Release) error { func (s *SQL) Delete(key string) (*rspb.Release, error) { transaction, err := s.db.Beginx() if err != nil { - s.Log("failed to start SQL transaction: %v", err) + slog.Debug("failed to start SQL transaction", slog.Any("error", err)) return nil, fmt.Errorf("error beginning transaction: %v", err) } @@ -607,20 +605,20 @@ func (s *SQL) Delete(key string) (*rspb.Release, error) { Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). ToSql() if err != nil { - s.Log("failed to build select query: %v", err) + slog.Debug("failed to build select query", slog.Any("error", err)) return nil, err } var record SQLReleaseWrapper err = transaction.Get(&record, selectQuery, args...) if err != nil { - s.Log("release %s not found: %v", key, err) + slog.Debug("release not found", "key", key, slog.Any("error", err)) return nil, ErrReleaseNotFound } release, err := decodeRelease(record.Body) if err != nil { - s.Log("failed to decode release %s: %v", key, err) + slog.Debug("failed to decode release", "key", key, slog.Any("error", err)) transaction.Rollback() return nil, err } @@ -632,18 +630,18 @@ func (s *SQL) Delete(key string) (*rspb.Release, error) { Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). ToSql() if err != nil { - s.Log("failed to build delete query: %v", err) + slog.Debug("failed to build delete query", slog.Any("error", err)) return nil, err } _, err = transaction.Exec(deleteQuery, args...) if err != nil { - s.Log("failed perform delete query: %v", err) + slog.Debug("failed perform delete query", slog.Any("error", err)) return release, err } if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil { - s.Log("failed to get release %s/%s custom labels: %v", s.namespace, key, err) + slog.Debug("failed to get release custom labels", "namespace", s.namespace, "key", key, slog.Any("error", err)) return nil, err } @@ -654,7 +652,7 @@ func (s *SQL) Delete(key string) (*rspb.Release, error) { ToSql() if err != nil { - s.Log("failed to build delete Labels query: %v", err) + slog.Debug("failed to build delete Labels query", slog.Any("error", err)) return nil, err } _, err = transaction.Exec(deleteCustomLabelsQuery, args...) diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 5e8718ea0..f98daeba6 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -18,6 +18,7 @@ package storage // import "helm.sh/helm/v4/pkg/storage" import ( "fmt" + "log/slog" "strings" "github.com/pkg/errors" @@ -42,15 +43,13 @@ type Storage struct { // be retained, including the most recent release. Values of 0 or less are // ignored (meaning no limits are imposed). MaxHistory int - - Log func(string, ...interface{}) } // Get retrieves the release from storage. An error is returned // if the storage driver failed to fetch the release, or the // release identified by the key, version pair does not exist. func (s *Storage) Get(name string, version int) (*rspb.Release, error) { - s.Log("getting release %q", makeKey(name, version)) + slog.Debug("getting release", "key", makeKey(name, version)) return s.Driver.Get(makeKey(name, version)) } @@ -58,7 +57,7 @@ func (s *Storage) Get(name string, version int) (*rspb.Release, error) { // error is returned if the storage driver fails to store the // release, or a release with an identical key already exists. func (s *Storage) Create(rls *rspb.Release) error { - s.Log("creating release %q", makeKey(rls.Name, rls.Version)) + slog.Debug("creating release", "key", makeKey(rls.Name, rls.Version)) if s.MaxHistory > 0 { // Want to make space for one more release. if err := s.removeLeastRecent(rls.Name, s.MaxHistory-1); err != nil && @@ -73,7 +72,7 @@ func (s *Storage) Create(rls *rspb.Release) error { // storage backend fails to update the release or if the release // does not exist. func (s *Storage) Update(rls *rspb.Release) error { - s.Log("updating release %q", makeKey(rls.Name, rls.Version)) + slog.Debug("updating release", "key", makeKey(rls.Name, rls.Version)) return s.Driver.Update(makeKey(rls.Name, rls.Version), rls) } @@ -81,21 +80,21 @@ func (s *Storage) Update(rls *rspb.Release) error { // the storage backend fails to delete the release or if the release // does not exist. func (s *Storage) Delete(name string, version int) (*rspb.Release, error) { - s.Log("deleting release %q", makeKey(name, version)) + slog.Debug("deleting release", "key", makeKey(name, version)) return s.Driver.Delete(makeKey(name, version)) } // ListReleases returns all releases from storage. An error is returned if the // storage backend fails to retrieve the releases. func (s *Storage) ListReleases() ([]*rspb.Release, error) { - s.Log("listing all releases in storage") + slog.Debug("listing all releases in storage") return s.Driver.List(func(_ *rspb.Release) bool { return true }) } // ListUninstalled returns all releases with Status == UNINSTALLED. An error is returned // if the storage backend fails to retrieve the releases. func (s *Storage) ListUninstalled() ([]*rspb.Release, error) { - s.Log("listing uninstalled releases in storage") + slog.Debug("listing uninstalled releases in storage") return s.Driver.List(func(rls *rspb.Release) bool { return relutil.StatusFilter(rspb.StatusUninstalled).Check(rls) }) @@ -104,7 +103,7 @@ func (s *Storage) ListUninstalled() ([]*rspb.Release, error) { // ListDeployed returns all releases with Status == DEPLOYED. An error is returned // if the storage backend fails to retrieve the releases. func (s *Storage) ListDeployed() ([]*rspb.Release, error) { - s.Log("listing all deployed releases in storage") + slog.Debug("listing all deployed releases in storage") return s.Driver.List(func(rls *rspb.Release) bool { return relutil.StatusFilter(rspb.StatusDeployed).Check(rls) }) @@ -132,7 +131,7 @@ func (s *Storage) Deployed(name string) (*rspb.Release, error) { // DeployedAll returns all deployed releases with the provided name, or // returns driver.NewErrNoDeployedReleases if not found. func (s *Storage) DeployedAll(name string) ([]*rspb.Release, error) { - s.Log("getting deployed releases from %q history", name) + slog.Debug("getting deployed releases", "name", name) ls, err := s.Driver.Query(map[string]string{ "name": name, @@ -151,7 +150,7 @@ func (s *Storage) DeployedAll(name string) ([]*rspb.Release, error) { // History returns the revision history for the release with the provided name, or // returns driver.ErrReleaseNotFound if no such release name exists. func (s *Storage) History(name string) ([]*rspb.Release, error) { - s.Log("getting release history for %q", name) + slog.Debug("getting release history", "name", name) return s.Driver.Query(map[string]string{"name": name, "owner": "helm"}) } @@ -206,7 +205,7 @@ func (s *Storage) removeLeastRecent(name string, maximum int) error { } } - s.Log("Pruned %d record(s) from %s with %d error(s)", len(toDelete), name, len(errs)) + slog.Debug("pruned records", "count", len(toDelete), "release", name, "errors", len(errs)) switch c := len(errs); c { case 0: return nil @@ -221,7 +220,7 @@ func (s *Storage) deleteReleaseVersion(name string, version int) error { key := makeKey(name, version) _, err := s.Delete(name, version) if err != nil { - s.Log("error pruning %s from release history: %s", key, err) + slog.Debug("error pruning release", "key", key, slog.Any("error", err)) return err } return nil @@ -229,7 +228,7 @@ func (s *Storage) deleteReleaseVersion(name string, version int) error { // Last fetches the last revision of the named release. func (s *Storage) Last(name string) (*rspb.Release, error) { - s.Log("getting last revision of %q", name) + slog.Debug("getting last revision", "name", name) h, err := s.History(name) if err != nil { return nil, err @@ -261,6 +260,5 @@ func Init(d driver.Driver) *Storage { } return &Storage{ Driver: d, - Log: func(_ string, _ ...interface{}) {}, } } diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go index 1dadc9c93..f99d10214 100644 --- a/pkg/storage/storage_test.go +++ b/pkg/storage/storage_test.go @@ -312,7 +312,6 @@ func (d *MaxHistoryMockDriver) Name() string { func TestMaxHistoryErrorHandling(t *testing.T) { //func TestStorageRemoveLeastRecentWithError(t *testing.T) { storage := Init(NewMaxHistoryMockDriver(driver.NewMemory())) - storage.Log = t.Logf storage.MaxHistory = 1 @@ -338,7 +337,6 @@ func TestMaxHistoryErrorHandling(t *testing.T) { func TestStorageRemoveLeastRecent(t *testing.T) { storage := Init(driver.NewMemory()) - storage.Log = t.Logf // Make sure that specifying this at the outset doesn't cause any bugs. storage.MaxHistory = 10 @@ -395,7 +393,6 @@ func TestStorageRemoveLeastRecent(t *testing.T) { func TestStorageDoNotDeleteDeployed(t *testing.T) { storage := Init(driver.NewMemory()) - storage.Log = t.Logf storage.MaxHistory = 3 const name = "angry-bird" diff --git a/testdata/localhost-crt.pem b/testdata/localhost-crt.pem new file mode 100644 index 000000000..70fa0a429 --- /dev/null +++ b/testdata/localhost-crt.pem @@ -0,0 +1,73 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 7f:5e:fa:21:fa:ee:e4:6a:be:9b:c2:80:bf:ed:42:f3:2d:47:f5:d2 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, ST=CO, L=Boulder, O=Helm, CN=helm.sh + Validity + Not Before: Nov 6 21:59:18 2023 GMT + Not After : Nov 3 21:59:18 2033 GMT + Subject: C=CA, ST=ON, L=Kitchener, O=Helm, CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:c8:89:55:0d:0b:f1:da:e6:c0:70:7d:d3:27:cd: + b8:a8:81:8b:7c:a4:89:e5:d1:b1:78:01:1d:df:44: + 88:0b:fc:d6:81:35:3d:d1:3b:5e:8f:bb:93:b3:7e: + 28:db:ed:ff:a0:13:3a:70:a3:fe:94:6b:0b:fe:fb: + 63:00:b0:cb:dc:81:cd:80:dc:d0:2f:bf:b2:4f:9a: + 81:d4:22:dc:97:c8:8f:27:86:59:91:fa:92:05:75: + c4:cc:6b:f5:a9:6b:74:1e:f5:db:a9:f8:bf:8c:a2: + 25:fd:a0:cc:79:f4:25:57:74:a9:23:9b:e2:b7:22: + 7a:14:7a:3d:ea:f1:7e:32:6b:57:6c:2e:c6:4f:75: + 54:f9:6b:54:d2:ca:eb:54:1c:af:39:15:9b:d0:7c: + 0f:f8:55:51:04:ea:da:fa:7b:8b:63:0f:ac:39:b1: + f6:4b:8e:4e:f6:ea:e9:7b:e6:ba:5e:5a:8e:91:ef: + dc:b1:7d:52:3f:73:83:52:46:83:48:49:ff:f2:2d: + ca:54:f2:36:bb:49:cc:59:99:c0:9e:cf:8e:78:55: + 6c:ed:7d:7e:83:b8:59:2c:7d:f8:1a:81:f0:7d:f5: + 27:f2:db:ae:d4:31:54:38:fe:47:b2:ee:16:20:0f: + f1:db:2d:28:bf:6f:38:eb:11:bb:9a:d4:b2:5a:3a: + 4a:7f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:localhost + Signature Algorithm: sha256WithRSAEncryption + 47:47:fe:29:ca:94:28:75:59:ba:ab:67:ab:c6:a6:0b:0a:f2: + 0f:26:d9:1d:35:db:68:a5:d8:f5:1f:d1:87:e7:a7:74:fd:c0: + 22:aa:c8:ec:6c:d3:ac:8a:0b:ed:59:3a:a0:12:77:7c:53:74: + fd:30:59:34:8f:a4:ef:5b:98:3f:ff:cf:89:87:ed:d3:7f:41: + 2f:b1:9a:12:71:bb:fe:3a:cf:77:16:32:bc:83:90:cc:52:2f: + 3b:f4:ae:db:b1:bb:f0:dd:30:d4:03:17:5e:47:b7:06:86:7a: + 16:b1:72:2f:80:5d:d4:c0:f9:6c:91:df:5a:c5:15:86:66:68: + c8:90:8e:f1:a2:bb:40:0f:ef:26:1b:02:c4:42:de:8c:69:ec: + ad:27:d0:bc:da:7c:76:33:86:de:b7:c4:04:64:e6:f6:dc:44: + 89:7b:b8:2f:c7:28:7a:4c:a6:01:ad:a5:17:64:3a:23:da:aa: + db:ce:3f:86:e9:92:dc:0d:c4:5a:b4:52:a8:8a:ee:3d:62:7d: + b1:c8:fa:ef:96:2b:ab:f1:e1:6d:6f:7d:1e:ce:bc:7a:d0:92: + 02:1b:c8:55:36:77:bf:d4:42:d3:fc:57:ca:b7:cc:95:be:ce: + f8:6e:b2:28:ca:4d:9a:00:7d:78:c8:56:04:2e:b3:ac:03:fa: + 05:d8:42:bd +-----BEGIN CERTIFICATE----- +MIIDRDCCAiygAwIBAgIUf176Ifru5Gq+m8KAv+1C8y1H9dIwDQYJKoZIhvcNAQEL +BQAwTTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMRAwDgYDVQQHDAdCb3VsZGVy +MQ0wCwYDVQQKDARIZWxtMRAwDgYDVQQDDAdoZWxtLnNoMB4XDTIzMTEwNjIxNTkx +OFoXDTMzMTEwMzIxNTkxOFowUTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMRIw +EAYDVQQHDAlLaXRjaGVuZXIxDTALBgNVBAoMBEhlbG0xEjAQBgNVBAMMCWxvY2Fs +aG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMiJVQ0L8drmwHB9 +0yfNuKiBi3ykieXRsXgBHd9EiAv81oE1PdE7Xo+7k7N+KNvt/6ATOnCj/pRrC/77 +YwCwy9yBzYDc0C+/sk+agdQi3JfIjyeGWZH6kgV1xMxr9alrdB7126n4v4yiJf2g +zHn0JVd0qSOb4rciehR6PerxfjJrV2wuxk91VPlrVNLK61QcrzkVm9B8D/hVUQTq +2vp7i2MPrDmx9kuOTvbq6Xvmul5ajpHv3LF9Uj9zg1JGg0hJ//ItylTyNrtJzFmZ +wJ7PjnhVbO19foO4WSx9+BqB8H31J/LbrtQxVDj+R7LuFiAP8dstKL9vOOsRu5rU +slo6Sn8CAwEAAaMYMBYwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEB +CwUAA4IBAQBHR/4pypQodVm6q2erxqYLCvIPJtkdNdtopdj1H9GH56d0/cAiqsjs +bNOsigvtWTqgEnd8U3T9MFk0j6TvW5g//8+Jh+3Tf0EvsZoScbv+Os93FjK8g5DM +Ui879K7bsbvw3TDUAxdeR7cGhnoWsXIvgF3UwPlskd9axRWGZmjIkI7xortAD+8m +GwLEQt6MaeytJ9C82nx2M4bet8QEZOb23ESJe7gvxyh6TKYBraUXZDoj2qrbzj+G +6ZLcDcRatFKoiu49Yn2xyPrvliur8eFtb30ezrx60JICG8hVNne/1ELT/FfKt8yV +vs74brIoyk2aAH14yFYELrOsA/oF2EK9 +-----END CERTIFICATE----- diff --git a/testdata/openssl.conf b/testdata/openssl.conf index 9b27e445b..be5ff04b7 100644 --- a/testdata/openssl.conf +++ b/testdata/openssl.conf @@ -40,3 +40,7 @@ subjectAltName = @alternate_names [alternate_names] DNS.1 = helm.sh IP.1 = 127.0.0.1 + +# # Used to generate localhost-crt.pem +# [alternate_names] +# DNS.1 = localhost