diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 008d6a594..1c3ea0bf4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # pinv4.36.2 + uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -59,7 +59,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # pinv4.36.2 + uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -73,4 +73,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # pinv4.36.2 + uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # pinv4.36.0 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 55a9aec82..c4a0cb196 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0 with: sarif_file: results.sarif diff --git a/.golangci.yml b/.golangci.yml index 48c8c5ab2..1ed3353b4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -19,12 +19,9 @@ linters: # Keep sorted alphabetically enable: - - bidichk - depguard - dupl - - errorlint - exhaustive - - gocritic - gomodguard - govet - ineffassign @@ -41,7 +38,6 @@ linters: - unused - usestdlibvars - usetesting - - whitespace exclusions: @@ -69,57 +65,9 @@ linters: dupl: threshold: 400 - errorlint: - # Check for plain type assertions and type switches. - asserts: false - # Check for plain error comparisons. - comparison: false - exhaustive: default-signifies-exhaustive: true - gocritic: - disabled-checks: - - appendAssign - - appendCombine - - assignOp - - badCall - - commentedOutCode - - commentFormatting - - deferInLoop - - elseif - - emptyStringTest - - exposedSyncMutex - - filepathJoin - - httpNoBody - - hugeParam - - ifElseChain - - importShadow - - nestingReduce - - nilValReturn - - octalLiteral - - paramTypeCombine - - preferStringWriter - - ptrToRefParam - - rangeValCopy - - regexpSimplify - - singleCaseSwitch - - sloppyReassign - - sloppyLen - - sprintfQuotedString - - stringConcatSimplify - - stringsCompare - - stringXbytes - - todoCommentWithoutDetail - - tooManyResultsChecker - - typeAssertChain - - uncheckedInlineErr - - unnamedResult - - unnecessaryDefer - - unslice - - whyNoLint - enable-all: true - gomodguard: blocked: modules: @@ -153,6 +101,8 @@ linters: - empty - encoded-compare - equal-values + - error-is-as + - error-nil - expected-actual - float-compare - go-require diff --git a/go.mod b/go.mod index e04604af0..8220a5f1c 100644 --- a/go.mod +++ b/go.mod @@ -33,11 +33,11 @@ require ( github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 - github.com/tetratelabs/wazero v1.12.0 + github.com/tetratelabs/wazero v1.11.0 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/crypto v0.53.0 - golang.org/x/term v0.44.0 - golang.org/x/text v0.38.0 + golang.org/x/crypto v0.51.0 + golang.org/x/term v0.43.0 + golang.org/x/text v0.37.0 gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/api v0.36.1 k8s.io/apiextensions-apiserver v0.36.1 @@ -47,7 +47,7 @@ require ( k8s.io/client-go v0.36.1 k8s.io/klog/v2 v2.140.0 k8s.io/kubectl v0.36.1 - oras.land/oras-go/v2 v2.6.1 + oras.land/oras-go/v2 v2.6.0 sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/kustomize/kyaml v0.21.1 sigs.k8s.io/yaml v1.6.0 @@ -157,13 +157,13 @@ require ( go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect - golang.org/x/mod v0.36.0 // indirect - golang.org/x/net v0.55.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sync v0.21.0 // indirect - golang.org/x/sys v0.46.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.44.0 // indirect golang.org/x/time v0.15.0 // indirect - golang.org/x/tools v0.45.0 // indirect + golang.org/x/tools v0.44.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/grpc v1.80.0 // indirect diff --git a/go.sum b/go.sum index c0da36a37..544e4205e 100644 --- a/go.sum +++ b/go.sum @@ -309,8 +309,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q= github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk= -github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU= -github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0= +github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA= +github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= @@ -382,14 +382,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= -golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -400,8 +400,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -412,8 +412,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.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 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= @@ -430,8 +430,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.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= 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= @@ -439,8 +439,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.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= 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= @@ -448,8 +448,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.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -458,8 +458,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= @@ -507,8 +507,8 @@ k8s.io/kubectl v0.36.1 h1:96HqS9twIdHM0MlJLTwbo14b9kUKPkOzZ4tlRDLv4qI= k8s.io/kubectl v0.36.1/go.mod h1:/DGPAIewKsFWF9VFgGvkPhao2Ev4SNuE3BioZo8yPbk= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= -oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= +oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= +oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= diff --git a/internal/chart/v3/chart.go b/internal/chart/v3/chart.go index 71a90c855..d76a5688c 100644 --- a/internal/chart/v3/chart.go +++ b/internal/chart/v3/chart.go @@ -140,7 +140,6 @@ func (ch *Chart) AppVersion() string { } // CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. -// // Deprecated: use CRDObjects() func (ch *Chart) CRDs() []*common.File { files := []*common.File{} diff --git a/internal/chart/v3/chart_test.go b/internal/chart/v3/chart_test.go index f89030095..5f6ca548d 100644 --- a/internal/chart/v3/chart_test.go +++ b/internal/chart/v3/chart_test.go @@ -104,7 +104,7 @@ func TestMetadata(t *testing.T) { is.Equal("foo.yaml", chrt.Name()) is.Equal("1.0.0", chrt.AppVersion()) - is.NoError(chrt.Validate()) + is.Equal(nil, chrt.Validate()) } func TestIsRoot(t *testing.T) { diff --git a/internal/chart/v3/lint/lint.go b/internal/chart/v3/lint/lint.go index ea8ca3d60..e98edfabe 100644 --- a/internal/chart/v3/lint/lint.go +++ b/internal/chart/v3/lint/lint.go @@ -44,6 +44,7 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption { } func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter { + chartDir, _ := filepath.Abs(baseDir) lo := linterOptions{} diff --git a/internal/chart/v3/lint/rules/template_test.go b/internal/chart/v3/lint/rules/template_test.go index 88343b330..b1371659f 100644 --- a/internal/chart/v3/lint/rules/template_test.go +++ b/internal/chart/v3/lint/rules/template_test.go @@ -242,6 +242,7 @@ data: // // See https://github.com/helm/helm/issues/7483 func TestStrictTemplateParsingMapError(t *testing.T) { + ch := chart.Chart{ Metadata: &chart.Metadata{ Name: "regression7483", @@ -370,6 +371,7 @@ func TestValidateTopIndentLevel(t *testing.T) { t.Errorf("Expected %t for %q", shouldFail, doc) } } + } // TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments @@ -461,4 +463,5 @@ func TestIsYamlFileExtension(t *testing.T) { t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected) } } + } diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go index de9219a1b..c4c252407 100644 --- a/internal/chart/v3/loader/load_test.go +++ b/internal/chart/v3/loader/load_test.go @@ -337,6 +337,7 @@ icon: https://example.com/64x64.png if text.String() != "" { t.Errorf("Expected no message to Stderr, got %s", text.String()) } + } // Packaging the chart on a Windows machine will produce an @@ -606,6 +607,7 @@ func verifyChart(t *testing.T, c *chart.Chart) { t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) } } + } func verifyDependencies(t *testing.T, c *chart.Chart) { diff --git a/internal/chart/v3/util/create.go b/internal/chart/v3/util/create.go index b3d75ac2d..48d2120e5 100644 --- a/internal/chart/v3/util/create.go +++ b/internal/chart/v3/util/create.go @@ -702,6 +702,7 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error { // error. In such a case, this will attempt to clean up by removing the // new chart directory. func Create(name, dir string) (string, error) { + // Sanity-check the name of a chart so user doesn't create one that causes problems. if err := validateChartName(name); err != nil { return "", err diff --git a/internal/chart/v3/util/dependencies_test.go b/internal/chart/v3/util/dependencies_test.go index 0a0937e4a..c8a176725 100644 --- a/internal/chart/v3/util/dependencies_test.go +++ b/internal/chart/v3/util/dependencies_test.go @@ -459,6 +459,7 @@ func TestDependentChartAliases(t *testing.T) { if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil { t.Fatalf("expected no chart but got %s", aliasChart.Name()) } + } func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) { diff --git a/internal/chart/v3/util/validate_name.go b/internal/chart/v3/util/validate_name.go index 7d85a5b88..6595e085d 100644 --- a/internal/chart/v3/util/validate_name.go +++ b/internal/chart/v3/util/validate_name.go @@ -79,6 +79,7 @@ func ValidateReleaseName(name string) error { // This case is preserved for backwards compatibility if name == "" { return errMissingName + } if len(name) > maxReleaseNameLen || !validName.MatchString(name) { return errInvalidName diff --git a/internal/cli/output/color_test.go b/internal/cli/output/color_test.go index f0484fc3a..3b8de39e8 100644 --- a/internal/cli/output/color_test.go +++ b/internal/cli/output/color_test.go @@ -24,6 +24,7 @@ import ( ) func TestColorizeStatus(t *testing.T) { + tests := []struct { name string status common.Status @@ -106,6 +107,7 @@ func TestColorizeStatus(t *testing.T) { } func TestColorizeHeader(t *testing.T) { + tests := []struct { name string header string @@ -147,6 +149,7 @@ func TestColorizeHeader(t *testing.T) { } func TestColorizeNamespace(t *testing.T) { + tests := []struct { name string namespace string diff --git a/internal/monocular/client.go b/internal/monocular/client.go index cd1a0d666..f4ef5d647 100644 --- a/internal/monocular/client.go +++ b/internal/monocular/client.go @@ -33,6 +33,7 @@ type Client struct { // New creates a new client func New(u string) (*Client, error) { + // Validate we have a URL if err := validate(u); err != nil { return nil, err @@ -45,6 +46,7 @@ func New(u string) (*Client, error) { // Validate if the base URL for monocular is valid. func validate(u string) error { + // Check if it is parsable p, err := url.Parse(u) if err != nil { diff --git a/internal/monocular/search.go b/internal/monocular/search.go index cfae87ded..fcf04b7a4 100644 --- a/internal/monocular/search.go +++ b/internal/monocular/search.go @@ -99,6 +99,7 @@ type ChartVersion struct { // Search performs a search against the monocular search API func (c *Client) Search(term string) ([]SearchResult, error) { + // Create the URL to the search endpoint // Note, this is currently an internal API for the Hub. This should be // formatted without showing how monocular operates. diff --git a/internal/monocular/search_test.go b/internal/monocular/search_test.go index cdf67f69e..fc82ef4b4 100644 --- a/internal/monocular/search_test.go +++ b/internal/monocular/search_test.go @@ -27,6 +27,7 @@ import ( var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` func TestSearch(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, searchResult) })) diff --git a/internal/plugin/installer/base_test.go b/internal/plugin/installer/base_test.go index 6df8ec8a1..62b77bde5 100644 --- a/internal/plugin/installer/base_test.go +++ b/internal/plugin/installer/base_test.go @@ -35,6 +35,7 @@ func TestPath(t *testing.T) { } for _, tt := range tests { + t.Setenv("HELM_PLUGINS", tt.helmPluginsDir) baseIns := newBase(tt.source) baseInsPath := baseIns.Path() diff --git a/internal/plugin/installer/extractor.go b/internal/plugin/installer/extractor.go index 3af56ab56..b753dfbca 100644 --- a/internal/plugin/installer/extractor.go +++ b/internal/plugin/installer/extractor.go @@ -90,6 +90,7 @@ func NewExtractor(source string) (Extractor, error) { // - Beginning a path with a path separator is illegal // - Rudimentary symlink protections are offered by SecureJoin. func cleanJoin(root, dest string) (string, error) { + // On Windows, this is a drive separator. On UNIX-like, this is the path list separator. // In neither case do we want to trust a TAR that contains these. if strings.Contains(dest, ":") { diff --git a/internal/plugin/installer/http_installer_test.go b/internal/plugin/installer/http_installer_test.go index efbca90c9..006b7a7b3 100644 --- a/internal/plugin/installer/http_installer_test.go +++ b/internal/plugin/installer/http_installer_test.go @@ -124,6 +124,7 @@ func TestHTTPInstaller(t *testing.T) { } else if err.Error() != "plugin already exists" { t.Fatalf("expected error for plugin exists, got (%v)", err) } + } func TestHTTPInstallerNonExistentVersion(t *testing.T) { @@ -156,6 +157,7 @@ func TestHTTPInstallerNonExistentVersion(t *testing.T) { if err := Install(i); err == nil { t.Fatal("expected error from http client") } + } func TestHTTPInstallerUpdate(t *testing.T) { @@ -295,6 +297,7 @@ func TestExtract(t *testing.T) { t.Fatalf("Expected %s to have %o mode but has %o (umask: %o)", readmeFullPath, expectedReadmePerm, info.Mode().Perm(), currentUmask) } + } func TestCleanJoin(t *testing.T) { @@ -324,9 +327,11 @@ func TestCleanJoin(t *testing.T) { t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out) } } + } func TestMediaTypeToExtension(t *testing.T) { + for mt, shouldPass := range map[string]bool{ "": false, "application/gzip": true, diff --git a/internal/plugin/installer/installer.go b/internal/plugin/installer/installer.go index f73197629..69a797ad9 100644 --- a/internal/plugin/installer/installer.go +++ b/internal/plugin/installer/installer.go @@ -72,6 +72,7 @@ type VerificationResult struct { // InstallWithOptions installs a plugin with options. func InstallWithOptions(i Installer, opts Options) (*VerificationResult, error) { + if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil { return nil, err } diff --git a/internal/plugin/installer/vcs_installer_test.go b/internal/plugin/installer/vcs_installer_test.go index 54f94c724..7fe627b59 100644 --- a/internal/plugin/installer/vcs_installer_test.go +++ b/internal/plugin/installer/vcs_installer_test.go @@ -185,4 +185,5 @@ func TestVCSInstallerUpdate(t *testing.T) { } else if err.Error() != "plugin repo was modified" { t.Fatalf("expected error for plugin modified, got (%v)", err) } + } diff --git a/internal/plugin/loader.go b/internal/plugin/loader.go index cfa618f39..d99395641 100644 --- a/internal/plugin/loader.go +++ b/internal/plugin/loader.go @@ -45,6 +45,7 @@ func peekAPIVersion(r io.Reader) (string, error) { } func loadMetadataLegacy(metadataData []byte) (*Metadata, error) { + var ml MetadataLegacy d := yaml.NewDecoder(bytes.NewReader(metadataData)) // NOTE: No strict unmarshalling for legacy plugins - maintain backwards compatibility @@ -64,6 +65,7 @@ func loadMetadataLegacy(metadataData []byte) (*Metadata, error) { } func loadMetadataV1(metadataData []byte) (*Metadata, error) { + var mv1 MetadataV1 d := yaml.NewDecoder(bytes.NewReader(metadataData)) d.KnownFields(true) @@ -107,6 +109,7 @@ type prototypePluginManager struct { } func newPrototypePluginManager() (*prototypePluginManager, error) { + cc, err := wazero.NewCompilationCacheWithDir(helmpath.CachePath("wazero-build")) if err != nil { return nil, fmt.Errorf("failed to create wazero compilation cache: %w", err) @@ -226,6 +229,7 @@ func findPlugins(pluginsDirs []string, findFn findFunc, filterFn filterFunc) ([] found = append(found, p) } } + } return found, nil @@ -238,6 +242,7 @@ func makeDescriptorFilter(descriptor Descriptor) filterFunc { // If name is specified, it must match if descriptor.Name != "" && p.Metadata().Name != descriptor.Name { return false + } // If type is specified, it must match if descriptor.Type != "" && p.Metadata().Type != descriptor.Type { diff --git a/internal/plugin/loader_test.go b/internal/plugin/loader_test.go index 8eb443de6..b98f87a07 100644 --- a/internal/plugin/loader_test.go +++ b/internal/plugin/loader_test.go @@ -62,6 +62,7 @@ name: "test-plugin" } func TestLoadDir(t *testing.T) { + makeMetadata := func(apiVersion string) Metadata { usage := "hello [params]..." if apiVersion == "legacy" { diff --git a/internal/plugin/metadata_legacy.go b/internal/plugin/metadata_legacy.go index 26132a783..cdde475bb 100644 --- a/internal/plugin/metadata_legacy.go +++ b/internal/plugin/metadata_legacy.go @@ -50,8 +50,7 @@ type MetadataLegacy struct { PlatformCommand []PlatformCommand `yaml:"platformCommand"` // Command is the plugin command, as a single string. - // - // Deprecated: Use PlatformCommand instead. Removed in subprocess/v1 plugins. + // DEPRECATED: Use PlatformCommand instead. Removed in subprocess/v1 plugins. Command string `yaml:"command"` // IgnoreFlags ignores any flags passed in from Helm @@ -61,8 +60,7 @@ type MetadataLegacy struct { PlatformHooks PlatformHooks `yaml:"platformHooks"` // Hooks are commands that will run on plugin events, as a single string. - // - // Deprecated: Use PlatformHooks instead. Removed in subprocess/v1 plugins. + // DEPRECATED: Use PlatformHooks instead. Removed in subprocess/v1 plugins. Hooks Hooks `yaml:"hooks"` // Downloaders field is used if the plugin supply downloader mechanism diff --git a/internal/plugin/metadata_test.go b/internal/plugin/metadata_test.go index 6113f30c7..a2acd7925 100644 --- a/internal/plugin/metadata_test.go +++ b/internal/plugin/metadata_test.go @@ -23,6 +23,7 @@ import ( ) func TestValidatePluginData(t *testing.T) { + // A mock plugin with no commands mockNoCommand := mockSubprocessCLIPlugin(t, "foo") mockNoCommand.metadata.RuntimeConfig = &RuntimeConfigSubprocess{ diff --git a/internal/plugin/plugin_type_registry_test.go b/internal/plugin/plugin_type_registry_test.go index 157c2f1d7..22f26262d 100644 --- a/internal/plugin/plugin_type_registry_test.go +++ b/internal/plugin/plugin_type_registry_test.go @@ -28,6 +28,7 @@ func TestMakeOutputMessage(t *testing.T) { ptm := pluginTypesIndex["getter/v1"] outputType := reflect.Zero(ptm.outputType).Interface() assert.IsType(t, schema.OutputMessageGetterV1{}, outputType) + } func TestMakeConfig(t *testing.T) { diff --git a/internal/plugin/runtime_extismv1.go b/internal/plugin/runtime_extismv1.go index ffa108a08..cd9a02535 100644 --- a/internal/plugin/runtime_extismv1.go +++ b/internal/plugin/runtime_extismv1.go @@ -99,6 +99,7 @@ type RuntimeExtismV1 struct { var _ Runtime = (*RuntimeExtismV1)(nil) func (r *RuntimeExtismV1) CreatePlugin(pluginDir string, metadata *Metadata) (Plugin, error) { + rc, ok := metadata.RuntimeConfig.(*RuntimeConfigExtismV1) if !ok { return nil, fmt.Errorf("invalid extism/v1 plugin runtime config type: %T", metadata.RuntimeConfig) @@ -138,6 +139,7 @@ func (p *ExtismV1PluginRuntime) Dir() string { } func (p *ExtismV1PluginRuntime) Invoke(ctx context.Context, input *Input) (*Output, error) { + var tmpDir string if p.rc.FileSystem.CreateTempDir { tmpDirInner, err := os.MkdirTemp(os.TempDir(), "helm-plugin-*") diff --git a/internal/plugin/runtime_extismv1_test.go b/internal/plugin/runtime_extismv1_test.go index bcd791905..8d9c55195 100644 --- a/internal/plugin/runtime_extismv1_test.go +++ b/internal/plugin/runtime_extismv1_test.go @@ -79,7 +79,7 @@ func TestRuntimeExtismV1InvokePlugin(t *testing.T) { Name: "Phippy", }, }) - require.NoError(t, err) + require.Nil(t, err) msg := output.Message.(schema.OutputMessageTestV1) assert.Equal(t, "Hello, Phippy! (6)", msg.Greeting) diff --git a/internal/plugin/subprocess_commands_test.go b/internal/plugin/subprocess_commands_test.go index dbce42f6a..df854b4ca 100644 --- a/internal/plugin/subprocess_commands_test.go +++ b/internal/plugin/subprocess_commands_test.go @@ -48,6 +48,7 @@ func TestPrepareCommand(t *testing.T) { } func TestPrepareCommandExtraArgs(t *testing.T) { + cmdMain := "sh" cmdArgs := []string{"-c", "echo \"test\""} platformCommand := []PlatformCommand{ diff --git a/internal/release/v2/util/manifest_sorter_test.go b/internal/release/v2/util/manifest_sorter_test.go index c8851d678..28f0b34cc 100644 --- a/internal/release/v2/util/manifest_sorter_test.go +++ b/internal/release/v2/util/manifest_sorter_test.go @@ -26,6 +26,7 @@ import ( ) func TestSortManifests(t *testing.T) { + data := []struct { name []string path string @@ -182,6 +183,7 @@ metadata: if !reflect.DeepEqual(expectedHooks, out.Events) { t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events) } + } } if !found { diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 5f0c5b148..184c8404b 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -54,6 +54,7 @@ func New(chartpath, cachepath string, registryClient *registry.Client) *Resolver // Resolve resolves dependencies and returns a lock file with the resolution. func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { + // Now we clone the dependencies, locking as we go. locked := make([]*chart.Dependency, len(reqs)) missing := []string{} @@ -145,6 +146,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string Version: version, }, }} + } else { // Retrieve list of tags for repository ref := fmt.Sprintf("%s/%s", strings.TrimPrefix(d.Repository, registry.OCIScheme+"://"), d.Name) diff --git a/internal/third_party/dep/fs/fs_test.go b/internal/third_party/dep/fs/fs_test.go index 806e33c28..8f28c3af7 100644 --- a/internal/third_party/dep/fs/fs_test.go +++ b/internal/third_party/dep/fs/fs_test.go @@ -238,6 +238,7 @@ func TestCopyDirFail_SrcIsNotDir(t *testing.T) { if !errors.Is(err, errSrcNotDir) { t.Fatalf("expected %v error for CopyDir(%s, %s), got %s", errSrcNotDir, srcdir, dstdir, err) } + } func TestCopyDirFail_DstExists(t *testing.T) { @@ -490,6 +491,7 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() { } func TestIsDir(t *testing.T) { + var currentUID = os.Getuid() if currentUID == 0 { @@ -542,6 +544,7 @@ func TestIsDir(t *testing.T) { } func TestIsSymlink(t *testing.T) { + var currentUID = os.Getuid() if currentUID == 0 { diff --git a/internal/version/clientgo.go b/internal/version/clientgo.go index 50a4fd5cb..1e98a8292 100644 --- a/internal/version/clientgo.go +++ b/internal/version/clientgo.go @@ -18,13 +18,28 @@ package version import ( "errors" + "fmt" "runtime/debug" "slices" _ "k8s.io/client-go/kubernetes" // Force k8s.io/client-go to be included in the build ) +var ( + // kubeClientVersionMajorOverride and kubeClientVersionMinorOverride can be + // set via -X ldflags to specify the k8s.io/client-go version in non-standard + // builds (e.g., Bazel) where debug.ReadBuildInfo() is not available. + // Example: -X helm.sh/helm/v4/internal/version.kubeClientVersionMajorOverride=0 + // -X helm.sh/helm/v4/internal/version.kubeClientVersionMinorOverride=30 + kubeClientVersionMajorOverride = "" + kubeClientVersionMinorOverride = "" +) + func K8sIOClientGoModVersion() (string, error) { + if kubeClientVersionMajorOverride != "" && kubeClientVersionMinorOverride != "" { + return fmt.Sprintf("v%s.%s.0", kubeClientVersionMajorOverride, kubeClientVersionMinorOverride), nil + } + info, ok := debug.ReadBuildInfo() if !ok { return "", errors.New("failed to read build info") diff --git a/internal/version/version.go b/internal/version/version.go index 572938c96..007f79f16 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -77,6 +77,7 @@ func GetUserAgent() string { // Get returns build info func Get() BuildInfo { + makeKubeClientVersionString := func() string { // Test builds don't include debug info / module info // (And even if they did, we probably want a stable version during tests anyway) diff --git a/pkg/action/action.go b/pkg/action/action.go index 4ed718371..8c1888144 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -630,6 +630,7 @@ func GetVersionSet(client discovery.ServerResourcesInterface) (common.VersionSet var ok bool for _, r := range resources { for _, rl := range r.APIResources { + // A Kind at a GroupVersion can show up more than once. We only want // it displayed once in the final output. id = path.Join(r.GroupVersion, rl.Kind) diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go index 8ceca1433..5c87f7cba 100644 --- a/pkg/action/dependency.go +++ b/pkg/action/dependency.go @@ -120,6 +120,7 @@ func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, p if r := statArchiveForStatus(archive, dep); r != "" { return r } + } // End unnecessary code. diff --git a/pkg/action/get_metadata.go b/pkg/action/get_metadata.go index 7d5a3f5f9..5312dac7f 100644 --- a/pkg/action/get_metadata.go +++ b/pkg/action/get_metadata.go @@ -119,6 +119,7 @@ func (m *Metadata) FormattedDepNames() string { continue } depsNames = append(depsNames, ac.Name()) + } sort.StringSlice(depsNames).Sort() diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go index d7824d69f..e7be37bd8 100644 --- a/pkg/action/hooks.go +++ b/pkg/action/hooks.go @@ -35,6 +35,7 @@ import ( func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration, serverSideApply bool) error { + shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, waitOptions, timeout, serverSideApply) if shutdown == nil { return err @@ -58,6 +59,7 @@ func shutdownNoOp() error { func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration, serverSideApply bool) (ExecuteShutdownFunc, error) { + executingHooks := []*release.Hook{} for _, h := range rl.Hooks { @@ -177,6 +179,7 @@ func (x hookByWeight) Less(i, j int) bool { // deleteHookByPolicy deletes a hook if the hook policy instructs it to func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { + // Never delete CustomResourceDefinitions; this could cause lots of // cascading garbage collection. if h.Kind == "CustomResourceDefinition" { @@ -211,6 +214,7 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo // deleteHooksByPolicy deletes all hooks if the hook policy instructs it to func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { + for _, h := range hooks { if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, waitOptions, timeout); err != nil { return err diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index ede39e552..05ca9a75e 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -737,6 +737,7 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) { }) } func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { + is := assert.New(t) instAction := installAction(t) instAction.ReleaseName = "interrupted-release" @@ -766,6 +767,7 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background time.Sleep(10 * time.Second) // wait for goroutine to finish is.Equal(goroutines, instAction.getGoroutineCount()) + } func TestNameTemplate(t *testing.T) { testCases := []nameTemplateTestCase{ @@ -802,6 +804,7 @@ func TestNameTemplate(t *testing.T) { } for _, tc := range testCases { + n, err := TemplateName(tc.tpl) if err != nil { if tc.expectedErrorStr == "" { @@ -864,7 +867,7 @@ func TestInstallReleaseOutputDir(t *testing.T) { test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt") _, err = os.Stat(filepath.Join(dir, "hello/templates/empty")) - is.ErrorIs(err, fs.ErrNotExist) + is.True(errors.Is(err, fs.ErrNotExist)) } func TestInstallOutputDirWithReleaseName(t *testing.T) { @@ -900,7 +903,7 @@ func TestInstallOutputDirWithReleaseName(t *testing.T) { test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt") _, err = os.Stat(filepath.Join(newDir, "hello/templates/empty")) - is.ErrorIs(err, fs.ErrNotExist) + is.True(errors.Is(err, fs.ErrNotExist)) } func TestNameAndChart(t *testing.T) { @@ -1165,7 +1168,7 @@ func TestInstallCRDs_AlreadyExist(t *testing.T) { mockChart := buildChart(withFile(mockFile)) crdsToInstall := mockChart.CRDObjects() - assert.NoError(t, instAction.installCRDs(crdsToInstall)) + assert.Nil(t, instAction.installCRDs(crdsToInstall)) } func TestInstallCRDs_KubeClient_BuildError(t *testing.T) { @@ -1224,7 +1227,7 @@ func TestCheckDependencies(t *testing.T) { dependency := chart.Dependency{Name: "hello"} mockChart := buildChart(withDependency()) - assert.NoError(t, CheckDependencies(mockChart, []ci.Dependency{&dependency})) + assert.Nil(t, CheckDependencies(mockChart, []ci.Dependency{&dependency})) } func TestCheckDependencies_MissingDependency(t *testing.T) { diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go index f8a55c0e0..2e1d4ff07 100644 --- a/pkg/action/package_test.go +++ b/pkg/action/package_test.go @@ -150,6 +150,7 @@ func TestValidateVersion(t *testing.T) { if !errors.Is(err, tt.wantErr) { t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err) } + } }) } diff --git a/pkg/action/registry_login_test.go b/pkg/action/registry_login_test.go index 474118c9f..590673b3a 100644 --- a/pkg/action/registry_login_test.go +++ b/pkg/action/registry_login_test.go @@ -37,7 +37,7 @@ func TestWithCertFile(t *testing.T) { certFile := "testdata/cert.pem" opt := WithCertFile(certFile) - assert.NoError(t, opt(client)) + assert.Nil(t, opt(client)) assert.Equal(t, certFile, client.certFile) } @@ -47,7 +47,7 @@ func TestWithInsecure(t *testing.T) { opt := WithInsecure(true) - assert.NoError(t, opt(client)) + assert.Nil(t, opt(client)) assert.True(t, client.insecure) } @@ -58,7 +58,7 @@ func TestWithKeyFile(t *testing.T) { keyFile := "testdata/key.pem" opt := WithKeyFile(keyFile) - assert.NoError(t, opt(client)) + assert.Nil(t, opt(client)) assert.Equal(t, keyFile, client.keyFile) } @@ -69,7 +69,7 @@ func TestWithCAFile(t *testing.T) { caFile := "testdata/ca.pem" opt := WithCAFile(caFile) - assert.NoError(t, opt(client)) + assert.Nil(t, opt(client)) assert.Equal(t, caFile, client.caFile) } @@ -79,6 +79,6 @@ func TestWithPlainHTTPLogin(t *testing.T) { opt := WithPlainHTTPLogin(true) - assert.NoError(t, opt(client)) + assert.Nil(t, opt(client)) assert.True(t, client.plainHTTP) } diff --git a/pkg/action/resource_policy.go b/pkg/action/resource_policy.go index 4f4e2ff55..fcea98ad6 100644 --- a/pkg/action/resource_policy.go +++ b/pkg/action/resource_policy.go @@ -40,6 +40,7 @@ func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining [] if resourcePolicyType == kube.KeepPolicy { keep = append(keep, m) } + } return keep, remaining } diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index b14305112..79156991c 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -88,73 +88,6 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) if err != nil { return nil, err } - - // Verify ownership in dry-run mode to show what would actually be deleted - manifests := releaseutil.SplitManifests(r.Manifest) - _, files, err := releaseutil.SortManifests(manifests, nil, releaseutil.UninstallOrder) - if err == nil { - filesToKeep, filesToDelete := filterManifestsToKeep(files) - - var builder strings.Builder - for _, file := range filesToDelete { - builder.WriteString("\n---\n" + file.Content) - } - - resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false) - if err == nil && len(resources) > 0 { - ownedResources, unownedResources, unverifiableResources, err := verifyOwnershipBeforeDelete(resources, r.Name, r.Namespace) - if err == nil { - if len(unownedResources) > 0 { - u.cfg.Logger().Warn("dry-run: resources would be skipped because they are not owned by this release", - "release", r.Name, - "count", len(unownedResources)) - for _, info := range unownedResources { - u.cfg.Logger().Warn("dry-run: would skip resource", - "kind", info.Mapping.GroupVersionKind.Kind, - "name", info.Name, - "namespace", info.Namespace) - } - } - - if len(unverifiableResources) > 0 { - u.cfg.Logger().Warn("dry-run: resources would be skipped because their ownership could not be verified", - "release", r.Name, - "count", len(unverifiableResources)) - for _, ur := range unverifiableResources { - u.cfg.Logger().Warn("dry-run: would skip resource (ownership could not be verified)", - "kind", ur.Info.Mapping.GroupVersionKind.Kind, - "name", ur.Info.Name, - "namespace", ur.Info.Namespace, - "error", ur.Err) - } - } - - if len(ownedResources) > 0 { - u.cfg.Logger().Debug("dry-run: resources would be deleted", - "release", r.Name, - "count", len(ownedResources)) - for _, info := range ownedResources { - u.cfg.Logger().Debug("dry-run: would delete resource", - "kind", info.Mapping.GroupVersionKind.Kind, - "name", info.Name, - "namespace", info.Namespace) - } - } - } - } - - // Include kept resources in dry-run info - if len(filesToKeep) > 0 { - var kept strings.Builder - kept.WriteString("These resources were kept due to the resource policy:\n") - for _, f := range filesToKeep { - fmt.Fprintf(&kept, "[%s] %s\n", f.Head.Kind, f.Head.Metadata.Name) - } - res := &releasei.UninstallReleaseResponse{Release: r, Info: kept.String()} - return res, nil - } - } - return &releasei.UninstallReleaseResponse{Release: r}, nil } @@ -220,6 +153,9 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error) return nil, fmt.Errorf("failed to delete release: %s", name) } + if kept != "" { + kept = "These resources were kept due to the resource policy:\n" + kept + } res.Info = kept if err := waiter.WaitForDelete(deletedResources, u.Timeout); err != nil { @@ -333,11 +269,8 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri filesToKeep, filesToDelete := filterManifestsToKeep(files) var kept strings.Builder - if len(filesToKeep) > 0 { - kept.WriteString("These resources were kept due to the resource policy:\n") - for _, f := range filesToKeep { - fmt.Fprintf(&kept, "[%s] %s\n", f.Head.Kind, f.Head.Metadata.Name) - } + for _, f := range filesToKeep { + fmt.Fprintf(&kept, "[%s] %s\n", f.Head.Kind, f.Head.Metadata.Name) } var builder strings.Builder @@ -349,69 +282,13 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri if err != nil { return nil, "", []error{fmt.Errorf("unable to build kubernetes objects for delete: %w", err)} } - - // Verify ownership before deleting resources - var ownedResources, unownedResources kube.ResourceList - var unverifiableResources []unverifiableResource if len(resources) > 0 { - ownedResources, unownedResources, unverifiableResources, err = verifyOwnershipBeforeDelete(resources, rel.Name, rel.Namespace) - if err != nil { - return nil, "", []error{fmt.Errorf("unable to verify resource ownership: %w", err)} - } - - // Log warnings for unowned resources - if len(unownedResources) > 0 { - for _, info := range unownedResources { - u.cfg.Logger().Warn("skipping delete of resource not owned by this release", - "kind", info.Mapping.GroupVersionKind.Kind, - "name", info.Name, - "namespace", info.Namespace, - "release", rel.Name) - } - if kept.Len() > 0 { - kept.WriteString("\n") - } - fmt.Fprintf(&kept, "%d resource(s) were not deleted because they are not owned by this release:\n", len(unownedResources)) - for _, info := range unownedResources { - fmt.Fprintf(&kept, "[%s] %s\n", info.Mapping.GroupVersionKind.Kind, info.Name) - } - } - - // Log warnings for resources whose ownership could not be verified - if len(unverifiableResources) > 0 { - for _, ur := range unverifiableResources { - u.cfg.Logger().Warn("skipping delete of resource because ownership could not be verified", - "kind", ur.Info.Mapping.GroupVersionKind.Kind, - "name", ur.Info.Name, - "namespace", ur.Info.Namespace, - "release", rel.Name, - "error", ur.Err) - } - if kept.Len() > 0 { - kept.WriteString("\n") - } - fmt.Fprintf(&kept, "%d resource(s) were not deleted because their ownership could not be verified:\n", len(unverifiableResources)) - for _, ur := range unverifiableResources { - fmt.Fprintf(&kept, "[%s] %s: %s\n", ur.Info.Mapping.GroupVersionKind.Kind, ur.Info.Name, ur.Err) - } - } - - // Delete only owned resources - if len(ownedResources) > 0 { - for _, info := range ownedResources { - u.cfg.Logger().Debug("deleting resource owned by this release", - "kind", info.Mapping.GroupVersionKind.Kind, - "name", info.Name, - "namespace", info.Namespace, - "release", rel.Name) - } - _, errs = u.cfg.KubeClient.Delete(ownedResources, parseCascadingFlag(u.DeletionPropagation, u.cfg.Logger())) - } + _, errs = u.cfg.KubeClient.Delete(resources, parseCascadingFlag(u.DeletionPropagation)) } - return ownedResources, kept.String(), errs + return resources, kept.String(), errs } -func parseCascadingFlag(cascadingFlag string, logger *slog.Logger) v1.DeletionPropagation { +func parseCascadingFlag(cascadingFlag string) v1.DeletionPropagation { switch cascadingFlag { case "orphan": return v1.DeletePropagationOrphan @@ -420,7 +297,7 @@ func parseCascadingFlag(cascadingFlag string, logger *slog.Logger) v1.DeletionPr case "background": return v1.DeletePropagationBackground default: - logger.Debug("uninstall: given cascade value, defaulting to delete propagation background", "value", cascadingFlag) + slog.Debug("uninstall: given cascade value, defaulting to delete propagation background", "value", cascadingFlag) return v1.DeletePropagationBackground } } diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go index 90f69d639..aeac98142 100644 --- a/pkg/action/uninstall_test.go +++ b/pkg/action/uninstall_test.go @@ -17,10 +17,9 @@ limitations under the License. package action import ( - "bytes" + "context" "errors" "io" - "log/slog" "testing" "github.com/stretchr/testify/assert" @@ -148,16 +147,9 @@ func TestUninstallRelease_Cascade(t *testing.T) { } }` require.NoError(t, unAction.cfg.Releases.Create(rel)) - - // Create dummy resources with Mapping but no Client - this skips ownership verification - // (nil Client is treated as owned) and goes directly to delete - dummyResources := kube.ResourceList{ - newDeploymentResource("secret", "", ""), - } - failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) failer.DeleteError = errors.New("Uninstall with cascade failed") - failer.DummyResources = dummyResources + failer.BuildDummy = true unAction.cfg.KubeClient = failer _, err := unAction.Run(rel.Name) require.Error(t, err) @@ -178,177 +170,39 @@ func TestUninstallRun_UnreachableKubeClient(t *testing.T) { assert.ErrorContains(t, err, "connection refused") } -func TestUninstallRelease_OwnershipVerification(t *testing.T) { +func TestUninstall_WaitOptionsPassedDownstream(t *testing.T) { is := assert.New(t) - // Create a buffer to capture log output - logBuffer := &bytes.Buffer{} - handler := slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}) - - config := actionConfigFixture(t) - config.SetLogger(handler) - - unAction := NewUninstall(config) - unAction.DisableHooks = true - unAction.DryRun = false - unAction.KeepHistory = true - - rel := releaseStub() - rel.Name = "ownership-test" - rel.Namespace = "default" - rel.Manifest = `apiVersion: v1 -kind: ConfigMap -metadata: - name: test-configmap - labels: - app.kubernetes.io/managed-by: Helm - annotations: - meta.helm.sh/release-name: ownership-test - meta.helm.sh/release-namespace: default -data: - key: value` - require.NoError(t, config.Releases.Create(rel)) - - // Create dummy resources with proper ownership metadata - labels := map[string]string{ - "app.kubernetes.io/managed-by": "Helm", - } - annotations := map[string]string{ - "meta.helm.sh/release-name": "ownership-test", - "meta.helm.sh/release-namespace": "default", - } - dummyResources := kube.ResourceList{ - newDeploymentWithOwner("owned-deploy", "default", labels, annotations), - } - failer := config.KubeClient.(*kubefake.FailingKubeClient) - failer.DummyResources = dummyResources - - resi, err := unAction.Run(rel.Name) - is.NoError(err) - is.NotNil(resi) - res, err := releaserToV1Release(resi.Release) - is.NoError(err) - is.Equal(common.StatusUninstalled, res.Info.Status) - - // Verify log contains debug message about deleting owned resource - logOutput := logBuffer.String() - is.Contains(logOutput, "deleting resource owned by this release") - is.Contains(logOutput, "owned-deploy") - is.Contains(logOutput, "Deployment") -} - -func TestUninstallRelease_OwnershipVerification_WithKeepPolicy(t *testing.T) { - is := assert.New(t) - - // Create a buffer to capture log output - logBuffer := &bytes.Buffer{} - handler := slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelWarn}) - - config := actionConfigFixture(t) - config.SetLogger(handler) - - unAction := NewUninstall(config) + unAction := uninstallAction(t) unAction.DisableHooks = true unAction.DryRun = false - unAction.KeepHistory = true - - rel := releaseStub() - rel.Name = "keep-and-ownership" - rel.Namespace = "default" - rel.Manifest = `apiVersion: v1 -kind: Secret -metadata: - name: kept-secret - annotations: - helm.sh/resource-policy: keep - meta.helm.sh/release-name: keep-and-ownership - meta.helm.sh/release-namespace: default - labels: - app.kubernetes.io/managed-by: Helm -type: Opaque -data: - password: cGFzc3dvcmQ= ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: deleted-configmap - labels: - app.kubernetes.io/managed-by: Helm - annotations: - meta.helm.sh/release-name: keep-and-ownership - meta.helm.sh/release-namespace: default -data: - key: value` - require.NoError(t, config.Releases.Create(rel)) - - // Create dummy resources - one unowned to test logging - dummyResources := kube.ResourceList{ - newDeploymentWithOwner("unowned-deploy", "default", nil, nil), - } - failer := config.KubeClient.(*kubefake.FailingKubeClient) - failer.DummyResources = dummyResources - - res, err := unAction.Run(rel.Name) - is.NoError(err) - is.NotNil(res) - // Should contain info about kept resources - is.Contains(res.Info, "kept due to the resource policy") - - // Verify log contains warning about skipped unowned resource - logOutput := logBuffer.String() - is.Contains(logOutput, "skipping delete of resource not owned by this release") - is.Contains(logOutput, "unowned-deploy") -} - -func TestUninstallRelease_DryRun_OwnershipVerification(t *testing.T) { - is := assert.New(t) - - // Create a buffer to capture log output - logBuffer := &bytes.Buffer{} - handler := slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelWarn}) - - config := actionConfigFixture(t) - config.SetLogger(handler) + unAction.WaitStrategy = kube.StatusWatcherStrategy - unAction := NewUninstall(config) - unAction.DisableHooks = true - unAction.DryRun = true + // Use WithWaitContext as a marker WaitOption that we can track + ctx := context.Background() + unAction.WaitOptions = []kube.WaitOption{kube.WithWaitContext(ctx)} rel := releaseStub() - rel.Name = "dryrun-ownership" - rel.Namespace = "default" - rel.Manifest = `apiVersion: v1 -kind: ConfigMap -metadata: - name: test-configmap - labels: - app.kubernetes.io/managed-by: Helm - annotations: - meta.helm.sh/release-name: dryrun-ownership - meta.helm.sh/release-namespace: default -data: - key: value` - require.NoError(t, config.Releases.Create(rel)) + rel.Name = "wait-options-uninstall" + rel.Manifest = `{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "secret" + }, + "type": "Opaque", + "data": { + "password": "password" + } + }` + require.NoError(t, unAction.cfg.Releases.Create(rel)) - // Create dummy resources - one unowned to test dry-run logging - dummyResources := kube.ResourceList{ - newDeploymentWithOwner("dryrun-unowned-deploy", "default", nil, nil), - } - failer := config.KubeClient.(*kubefake.FailingKubeClient) - failer.DummyResources = dummyResources + // Access the underlying FailingKubeClient to check recorded options + failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - resi, err := unAction.Run(rel.Name) - is.NoError(err) - is.NotNil(resi) - is.NotNil(resi.Release) - res, err := releaserToV1Release(resi.Release) + _, err := unAction.Run(rel.Name) is.NoError(err) - is.Equal("dryrun-ownership", res.Name) - // Verify log contains dry-run warning about resources that would be skipped - logOutput := logBuffer.String() - is.Contains(logOutput, "dry-run: would skip resource") - is.Contains(logOutput, "dryrun-unowned-deploy") - is.Contains(logOutput, "Deployment") + // Verify that WaitOptions were passed to GetWaiter + is.NotEmpty(failer.RecordedWaitOptions, "WaitOptions should be passed to GetWaiter") } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 0d7b1b148..00939ffa6 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -268,6 +268,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chartv2.Chart, vals map[str return nil, nil, false, err } } + } // determine if values will be reused diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index ab19d82b7..393692976 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -446,6 +446,7 @@ func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { } func TestUpgradeRelease_Interrupted_RollbackOnFailure(t *testing.T) { + is := assert.New(t) req := require.New(t) @@ -715,7 +716,7 @@ func TestGetUpgradeServerSideValue(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { serverSideApply, err := getUpgradeServerSideValue(tt.actionServerSideOption, tt.releaseApplyMethod) - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, tt.expectedServerSideApply, serverSideApply) }) } @@ -740,6 +741,7 @@ func TestGetUpgradeServerSideValue(t *testing.T) { assert.ErrorContains(t, err, tt.expectedErrorMsg) }) } + } func TestUpgradeRun_UnreachableKubeClient(t *testing.T) { diff --git a/pkg/action/validate.go b/pkg/action/validate.go index 948005521..94bf4906b 100644 --- a/pkg/action/validate.go +++ b/pkg/action/validate.go @@ -93,9 +93,9 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN // Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace. if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil { - return fmt.Errorf("%s exists and cannot be imported into the current release: %w", resourceString(info), err) + return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err) } - // Resources that are not found are skipped because they are already deleted and do not need deletion. + infoCopy := *info requireUpdate.Append(&infoCopy) return nil @@ -104,69 +104,6 @@ func existingResourceConflict(resources kube.ResourceList, releaseName, releaseN return requireUpdate, err } -// unverifiableResource pairs a resource with the error encountered while attempting -// to verify its ownership (for example, RBAC or network failures). -type unverifiableResource struct { - Info *resource.Info - Err error -} - -// verifyOwnershipBeforeDelete checks that resources in the list are owned by the specified release. -// It returns three lists: -// - owned: resources confirmed to be owned by the release (safe to delete). -// - unowned: resources that exist but are not owned by the release (should be skipped). -// - unverifiable: resources whose ownership could not be determined due to a fetch -// error (e.g. RBAC or network issues), paired with the underlying error. -// -// Resources that are not found on the server are excluded from all returned lists, -// since they have already been deleted and require no further action. -func verifyOwnershipBeforeDelete(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, kube.ResourceList, []unverifiableResource, error) { - var owned kube.ResourceList - var unowned kube.ResourceList - var unverifiable []unverifiableResource - - err := resources.Visit(func(info *resource.Info, err error) error { - if err != nil { - return err - } - - // If client is not available, skip verification (test scenario or build failure) - if info.Client == nil { - infoCopy := *info - owned.Append(&infoCopy) - return nil - } - - helper := resource.NewHelper(info.Client, info.Mapping) - existing, err := helper.Get(info.Namespace, info.Name) - if err != nil { - if apierrors.IsNotFound(err) { - // Resource already deleted; nothing to do. - return nil - } - // Cannot fetch resource (network/permission issue); ownership unverifiable. - infoCopy := *info - unverifiable = append(unverifiable, unverifiableResource{Info: &infoCopy, Err: err}) - return nil - } - - // Verify ownership of the existing resource - if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil { - // Resource not owned by this release, cannot delete - infoCopy := *info - unowned.Append(&infoCopy) - return nil - } - - // Resource is owned by this release, can delete - infoCopy := *info - owned.Append(&infoCopy) - return nil - }) - - return owned, unowned, unverifiable, err -} - func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) error { lbls, err := accessor.Labels(obj) if err != nil { @@ -179,13 +116,13 @@ func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) er var errs []error if err := requireValue(lbls, appManagedByLabel, appManagedByHelm); err != nil { - errs = append(errs, fmt.Errorf("label validation error: %w", err)) + errs = append(errs, fmt.Errorf("label validation error: %s", err)) } if err := requireValue(annos, helmReleaseNameAnnotation, releaseName); err != nil { - errs = append(errs, fmt.Errorf("annotation validation error: %w", err)) + errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) } if err := requireValue(annos, helmReleaseNamespaceAnnotation, releaseNamespace); err != nil { - errs = append(errs, fmt.Errorf("annotation validation error: %w", err)) + errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) } if len(errs) > 0 { @@ -217,7 +154,7 @@ func setMetadataVisitor(releaseName, releaseNamespace string, forceOwnership boo if !forceOwnership { if err := checkOwnership(info.Object, releaseName, releaseNamespace); err != nil { - return fmt.Errorf("%s cannot be owned: %w", resourceString(info), err) + return fmt.Errorf("%s cannot be owned: %s", resourceString(info), err) } } @@ -225,7 +162,7 @@ func setMetadataVisitor(releaseName, releaseNamespace string, forceOwnership boo appManagedByLabel: appManagedByHelm, }); err != nil { return fmt.Errorf( - "%s labels could not be updated: %w", + "%s labels could not be updated: %s", resourceString(info), err, ) } @@ -235,7 +172,7 @@ func setMetadataVisitor(releaseName, releaseNamespace string, forceOwnership boo helmReleaseNamespaceAnnotation: releaseNamespace, }); err != nil { return fmt.Errorf( - "%s annotations could not be updated: %w", + "%s annotations could not be updated: %s", resourceString(info), err, ) } diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go index 8e983e9ff..fd59abcb1 100644 --- a/pkg/action/validate_test.go +++ b/pkg/action/validate_test.go @@ -211,98 +211,6 @@ func TestCheckOwnership(t *testing.T) { assert.EqualError(t, err, `invalid ownership metadata; label validation error: key "app.kubernetes.io/managed-by" must equal "Helm": current value is "helm"`) } -func TestVerifyOwnershipBeforeDelete(t *testing.T) { - var ( - releaseName = "rel-a" - releaseNamespace = "ns-a" - labels = map[string]string{ - appManagedByLabel: appManagedByHelm, - } - annotations = map[string]string{ - helmReleaseNameAnnotation: releaseName, - helmReleaseNamespaceAnnotation: releaseNamespace, - } - wrongAnnotations = map[string]string{ - helmReleaseNameAnnotation: "rel-b", - helmReleaseNamespaceAnnotation: releaseNamespace, - } - ) - - // Test all resources properly owned - t.Run("all resources owned", func(t *testing.T) { - owned1 := newDeploymentWithOwner("owned1", "ns-a", labels, annotations) - owned2 := newDeploymentWithOwner("owned2", "ns-a", labels, annotations) - resources := kube.ResourceList{owned1, owned2} - - ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) - assert.Len(t, ownedList, 2) - assert.Len(t, unownedList, 0) - }) - - // Test mix of owned and unowned resources - t.Run("mixed ownership", func(t *testing.T) { - owned := newDeploymentWithOwner("owned", "ns-a", labels, annotations) - unowned := newDeploymentWithOwner("unowned", "ns-a", labels, wrongAnnotations) - resources := kube.ResourceList{owned, unowned} - - ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) - assert.Len(t, ownedList, 1) - assert.Len(t, unownedList, 1) - assert.Equal(t, "owned", ownedList[0].Name) - assert.Equal(t, "unowned", unownedList[0].Name) - }) - - // Test resource not found (should be skipped - not in either list) - t.Run("resource not found", func(t *testing.T) { - missing := newMissingDeployment("missing", "ns-a") - resources := kube.ResourceList{missing} - - ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) - assert.Len(t, ownedList, 0) - assert.Len(t, unownedList, 0) - }) - - // Test resource with no ownership metadata - t.Run("no ownership metadata", func(t *testing.T) { - noMeta := newDeploymentWithOwner("no-meta", "ns-a", nil, nil) - resources := kube.ResourceList{noMeta} - - ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) - assert.Len(t, ownedList, 0) - assert.Len(t, unownedList, 1) - }) - - // Test resource owned by different release - t.Run("owned by different release", func(t *testing.T) { - otherRelease := newDeploymentWithOwner("other", "ns-a", labels, wrongAnnotations) - resources := kube.ResourceList{otherRelease} - - ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) - assert.Len(t, ownedList, 0) - assert.Len(t, unownedList, 1) - }) - - // Test mixed scenario: owned, unowned, and missing resources - t.Run("mixed with missing resources", func(t *testing.T) { - owned := newDeploymentWithOwner("owned", "ns-a", labels, annotations) - unowned := newDeploymentWithOwner("unowned", "ns-a", labels, wrongAnnotations) - missing := newMissingDeployment("missing", "ns-a") - resources := kube.ResourceList{owned, unowned, missing} - - ownedList, unownedList, _, err := verifyOwnershipBeforeDelete(resources, releaseName, releaseNamespace) - assert.NoError(t, err) - assert.Len(t, ownedList, 1) - assert.Len(t, unownedList, 1) - assert.Equal(t, "owned", ownedList[0].Name) - assert.Equal(t, "unowned", unownedList[0].Name) - }) -} - func TestSetMetadataVisitor(t *testing.T) { var ( err error @@ -365,6 +273,7 @@ func TestValidateNameAndGenerateName(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { + skip, err := validateNameAndGenerateName(tc.info) if tc.wantErr { diff --git a/pkg/chart/common/capabilities.go b/pkg/chart/common/capabilities.go index 16910acaa..9afaf9e4d 100644 --- a/pkg/chart/common/capabilities.go +++ b/pkg/chart/common/capabilities.go @@ -17,6 +17,7 @@ package common import ( "fmt" + "log/slog" "slices" "strconv" "strings" @@ -152,7 +153,10 @@ func makeDefaultCapabilities() (*Capabilities, error) { vstr, err := helmversion.K8sIOClientGoModVersion() if err != nil { - return nil, fmt.Errorf("failed to retrieve k8s.io/client-go version: %w", err) + // Build info may be unavailable when compiled with toolchains other + // than "go build" (e.g. Bazel). Fall back to a safe default. + slog.Warn("failed to retrieve k8s.io/client-go version, falling back to default Kubernetes version", slog.Any("error", err)) + return newCapabilities(kubeVersionMajorTesting, kubeVersionMinorTesting) } v, err := semver.NewVersion(vstr) @@ -167,6 +171,7 @@ func makeDefaultCapabilities() (*Capabilities, error) { } func newCapabilities(kubeVersionMajor, kubeVersionMinor uint64) (*Capabilities, error) { + version := fmt.Sprintf("v%d.%d.0", kubeVersionMajor, kubeVersionMinor) return &Capabilities{ KubeVersion: KubeVersion{ diff --git a/pkg/chart/common/util/coalesce_test.go b/pkg/chart/common/util/coalesce_test.go index 33274920c..252ef11ec 100644 --- a/pkg/chart/common/util/coalesce_test.go +++ b/pkg/chart/common/util/coalesce_test.go @@ -666,6 +666,7 @@ func TestMergeTables(t *testing.T) { } func TestCoalesceValuesWarnings(t *testing.T) { + c := withDeps(&chart.Chart{ Metadata: &chart.Metadata{Name: "level1"}, Values: map[string]any{ @@ -723,6 +724,7 @@ func TestCoalesceValuesWarnings(t *testing.T) { assert.Contains(t, warnings, "warning: skipped value for level1.level2.level3.boat: Not a table.") assert.Contains(t, warnings, "warning: destination for level1.level2.level3.spear.tip is a table. Ignoring non-table value (true)") assert.Contains(t, warnings, "warning: cannot overwrite table with non table for level1.level2.level3.spear.sail (map[cotton:true])") + } func TestConcatPrefix(t *testing.T) { diff --git a/pkg/chart/common/util/values_test.go b/pkg/chart/common/util/values_test.go index 1882fd0c4..23b8a3de2 100644 --- a/pkg/chart/common/util/values_test.go +++ b/pkg/chart/common/util/values_test.go @@ -25,6 +25,7 @@ import ( ) func TestToRenderValues(t *testing.T) { + chartValues := map[string]any{ "name": "al Rashid", "where": map[string]any{ diff --git a/pkg/chart/loader/load.go b/pkg/chart/loader/load.go index 6fe246474..3fd381825 100644 --- a/pkg/chart/loader/load.go +++ b/pkg/chart/loader/load.go @@ -100,6 +100,7 @@ func LoadDir(dir string) (chart.Charter, error) { default: return nil, errors.New("unsupported chart version") } + } // FileLoader loads a chart from a file diff --git a/pkg/chart/v2/chart.go b/pkg/chart/v2/chart.go index 9d798b4de..4cfc2b890 100644 --- a/pkg/chart/v2/chart.go +++ b/pkg/chart/v2/chart.go @@ -143,7 +143,6 @@ func (ch *Chart) AppVersion() string { } // CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. -// // Deprecated: use CRDObjects() func (ch *Chart) CRDs() []*common.File { files := []*common.File{} diff --git a/pkg/chart/v2/chart_test.go b/pkg/chart/v2/chart_test.go index 52e9c6b43..d44e7251b 100644 --- a/pkg/chart/v2/chart_test.go +++ b/pkg/chart/v2/chart_test.go @@ -104,7 +104,7 @@ func TestMetadata(t *testing.T) { is.Equal("foo.yaml", chrt.Name()) is.Equal("1.0.0", chrt.AppVersion()) - is.NoError(chrt.Validate()) + is.Equal(nil, chrt.Validate()) } func TestIsRoot(t *testing.T) { diff --git a/pkg/chart/v2/lint/lint.go b/pkg/chart/v2/lint/lint.go index 23a93c551..7f6f26320 100644 --- a/pkg/chart/v2/lint/lint.go +++ b/pkg/chart/v2/lint/lint.go @@ -44,6 +44,7 @@ func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption { } func RunAll(baseDir string, values map[string]any, namespace string, options ...LinterOption) support.Linter { + chartDir, _ := filepath.Abs(baseDir) lo := linterOptions{} diff --git a/pkg/chart/v2/lint/rules/template.go b/pkg/chart/v2/lint/rules/template.go index 25ed31900..94210dec8 100644 --- a/pkg/chart/v2/lint/rules/template.go +++ b/pkg/chart/v2/lint/rules/template.go @@ -62,6 +62,7 @@ func TemplateLinterSkipSchemaValidation(skipSchemaValidation bool) TemplateLinte } func newTemplateLinter(linter *support.Linter, namespace string, values map[string]any, options ...TemplateLinterOption) templateLinter { + result := templateLinter{ linter: linter, values: values, diff --git a/pkg/chart/v2/lint/rules/template_test.go b/pkg/chart/v2/lint/rules/template_test.go index f0eb008b3..b6a62e8fd 100644 --- a/pkg/chart/v2/lint/rules/template_test.go +++ b/pkg/chart/v2/lint/rules/template_test.go @@ -257,6 +257,7 @@ data: // // See https://github.com/helm/helm/issues/7483 func TestStrictTemplateParsingMapError(t *testing.T) { + ch := chart.Chart{ Metadata: &chart.Metadata{ Name: "regression7483", @@ -389,6 +390,7 @@ func TestValidateTopIndentLevel(t *testing.T) { t.Errorf("Expected %t for %q", shouldFail, doc) } } + } // TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments @@ -484,4 +486,5 @@ func TestIsYamlFileExtension(t *testing.T) { t.Errorf("isYamlFileExtension(%s) = %v; want %v", test.filename, result, test.expected) } } + } diff --git a/pkg/chart/v2/loader/load_test.go b/pkg/chart/v2/loader/load_test.go index be7041386..aed071b2f 100644 --- a/pkg/chart/v2/loader/load_test.go +++ b/pkg/chart/v2/loader/load_test.go @@ -390,6 +390,7 @@ icon: https://example.com/64x64.png if text.String() != "" { t.Errorf("Expected no message to Stderr, got %s", text.String()) } + } // Packaging the chart on a Windows machine will produce an @@ -659,6 +660,7 @@ func verifyChart(t *testing.T, c *chart.Chart) { t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) } } + } func verifyDependencies(t *testing.T, c *chart.Chart) { diff --git a/pkg/chart/v2/util/create.go b/pkg/chart/v2/util/create.go index 36e3bfe72..0d7ae8d5c 100644 --- a/pkg/chart/v2/util/create.go +++ b/pkg/chart/v2/util/create.go @@ -701,6 +701,7 @@ func CreateFrom(chartfile *chart.Metadata, dest, src string) error { // error. In such a case, this will attempt to clean up by removing the // new chart directory. func Create(name, dir string) (string, error) { + // Sanity-check the name of a chart so user doesn't create one that causes problems. if err := validateChartName(name); err != nil { return "", err diff --git a/pkg/chart/v2/util/dependencies_test.go b/pkg/chart/v2/util/dependencies_test.go index 90a8806ec..0e4df8528 100644 --- a/pkg/chart/v2/util/dependencies_test.go +++ b/pkg/chart/v2/util/dependencies_test.go @@ -459,6 +459,7 @@ func TestDependentChartAliases(t *testing.T) { if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil { t.Fatalf("expected no chart but got %s", aliasChart.Name()) } + } func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) { diff --git a/pkg/chart/v2/util/validate_name.go b/pkg/chart/v2/util/validate_name.go index 7d85a5b88..6595e085d 100644 --- a/pkg/chart/v2/util/validate_name.go +++ b/pkg/chart/v2/util/validate_name.go @@ -79,6 +79,7 @@ func ValidateReleaseName(name string) error { // This case is preserved for backwards compatibility if name == "" { return errMissingName + } if len(name) > maxReleaseNameLen || !validName.MatchString(name) { return errInvalidName diff --git a/pkg/cmd/completion_test.go b/pkg/cmd/completion_test.go index 399ff1f0c..81c1ee2ad 100644 --- a/pkg/cmd/completion_test.go +++ b/pkg/cmd/completion_test.go @@ -51,6 +51,7 @@ func checkFileCompletion(t *testing.T, cmdName string, shouldBePerformed bool) { if shouldBePerformed { t.Errorf("Unexpected directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName) } else { + t.Errorf("Did not receive directive ShellCompDirectiveNoFileComp when completing '%s'", cmdName) } t.Log(out) diff --git a/pkg/cmd/dependency_build.go b/pkg/cmd/dependency_build.go index f572fbae1..b8ac16e60 100644 --- a/pkg/cmd/dependency_build.go +++ b/pkg/cmd/dependency_build.go @@ -55,7 +55,7 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command { if len(args) > 0 { chartpath = filepath.Clean(args[0]) } - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/dependency_update.go b/pkg/cmd/dependency_update.go index a7131e587..7f805c37b 100644 --- a/pkg/cmd/dependency_update.go +++ b/pkg/cmd/dependency_update.go @@ -58,7 +58,7 @@ func newDependencyUpdateCmd(_ *action.Configuration, out io.Writer) *cobra.Comma if len(args) > 0 { chartpath = filepath.Clean(args[0]) } - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/flags.go b/pkg/cmd/flags.go index e7db3dde2..5a220d1ce 100644 --- a/pkg/cmd/flags.go +++ b/pkg/cmd/flags.go @@ -220,6 +220,7 @@ func (p *postRendererArgsSlice) Type() string { } func (p *postRendererArgsSlice) Set(val string) error { + // a post-renderer defined by a user may accept empty arguments p.options.args = append(p.options.args, val) diff --git a/pkg/cmd/get_metadata.go b/pkg/cmd/get_metadata.go index c26af646f..eb90b6e44 100644 --- a/pkg/cmd/get_metadata.go +++ b/pkg/cmd/get_metadata.go @@ -77,6 +77,7 @@ func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command } func (w metadataWriter) WriteTable(out io.Writer) error { + formatApplyMethod := func(applyMethod string) string { switch applyMethod { case "": diff --git a/pkg/cmd/helpers.go b/pkg/cmd/helpers.go index 8866b8650..e555dd18b 100644 --- a/pkg/cmd/helpers.go +++ b/pkg/cmd/helpers.go @@ -43,6 +43,7 @@ func addDryRunFlag(cmd *cobra.Command) { // Determine the `action.DryRunStrategy` given -dry-run=` flag (or absence of) // Legacy usage of the flag: boolean values, and `--dry-run` (without value) are supported, and log warnings emitted func cmdGetDryRunFlagStrategy(cmd *cobra.Command, isTemplate bool) (action.DryRunStrategy, error) { + f := cmd.Flag("dry-run") v := f.Value.String() diff --git a/pkg/cmd/helpers_test.go b/pkg/cmd/helpers_test.go index 37ceb6a85..08065499e 100644 --- a/pkg/cmd/helpers_test.go +++ b/pkg/cmd/helpers_test.go @@ -157,6 +157,7 @@ func resetEnv() func() { } func TestCmdGetDryRunFlagStrategy(t *testing.T) { + type testCaseExpectedLog struct { Level string Msg string @@ -273,6 +274,7 @@ func TestCmdGetDryRunFlagStrategy(t *testing.T) { } for name, tc := range testCases { + logBuf := new(bytes.Buffer) logger := slog.New(slog.NewJSONHandler(logBuf, nil)) slog.SetDefault(logger) @@ -288,14 +290,14 @@ func TestCmdGetDryRunFlagStrategy(t *testing.T) { if tc.ExpectedError { assert.Error(t, err) } else { - assert.NoError(t, err) + assert.Nil(t, err) assert.Equal(t, tc.ExpectedStrategy, dryRunStrategy) } if tc.ExpectedLog != nil { logResult := map[string]string{} err = json.Unmarshal(logBuf.Bytes(), &logResult) - require.NoError(t, err) + require.Nil(t, err) assert.Equal(t, tc.ExpectedLog.Level, logResult["level"]) assert.Equal(t, tc.ExpectedLog.Msg, logResult["msg"]) diff --git a/pkg/cmd/history.go b/pkg/cmd/history.go index 33b2f11cb..7c0f3ecde 100644 --- a/pkg/cmd/history.go +++ b/pkg/cmd/history.go @@ -225,6 +225,7 @@ func getReleaseHistory(rls []*release.Release) (history releaseHistory) { } if !r.Info.LastDeployed.IsZero() { rInfo.Updated = r.Info.LastDeployed + } history = append(history, rInfo) } diff --git a/pkg/cmd/install.go b/pkg/cmd/install.go index 832968ff0..67e2a9fab 100644 --- a/pkg/cmd/install.go +++ b/pkg/cmd/install.go @@ -143,7 +143,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return compInstall(args, toComplete, client) }, RunE: func(cmd *cobra.Command, args []string) error { - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/lint.go b/pkg/cmd/lint.go index 1b5c3b212..ccc53ddd0 100644 --- a/pkg/cmd/lint.go +++ b/pkg/cmd/lint.go @@ -59,7 +59,7 @@ func newLintCmd(out io.Writer) *cobra.Command { if kubeVersion != "" { parsedKubeVersion, err := common.ParseKubeVersion(kubeVersion) if err != nil { - return fmt.Errorf("invalid kube version '%s': %w", kubeVersion, err) + return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err) } client.KubeVersion = parsedKubeVersion } diff --git a/pkg/cmd/lint_test.go b/pkg/cmd/lint_test.go index 0670e9f4d..82fe249f6 100644 --- a/pkg/cmd/lint_test.go +++ b/pkg/cmd/lint_test.go @@ -60,6 +60,7 @@ func TestLintCmdWithQuietFlag(t *testing.T) { wantError: true, }} runTestCmd(t, tests) + } func TestLintCmdWithKubeVersionFlag(t *testing.T) { diff --git a/pkg/cmd/load_plugins.go b/pkg/cmd/load_plugins.go index b6cc38ce6..029dd04f5 100644 --- a/pkg/cmd/load_plugins.go +++ b/pkg/cmd/load_plugins.go @@ -333,6 +333,7 @@ func loadFile(path string) (*pluginCommand, error) { // to obtain the dynamic completion choices. It must pass all the flags and sub-commands // specified in the command-line to the plugin.complete executable (except helm's global flags) func pluginDynamicComp(plug plugin.Plugin, cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + subprocessPlug, ok := plug.(*plugin.SubprocessPluginRuntime) if !ok { // Completion only supported for subprocess plugins (TODO: fix this) diff --git a/pkg/cmd/package.go b/pkg/cmd/package.go index 46133d971..14f9c8425 100644 --- a/pkg/cmd/package.go +++ b/pkg/cmd/package.go @@ -75,7 +75,7 @@ func newPackageCmd(out io.Writer) *cobra.Command { return err } - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/plugin_uninstall.go b/pkg/cmd/plugin_uninstall.go index 81d9b31ee..c75cf6264 100644 --- a/pkg/cmd/plugin_uninstall.go +++ b/pkg/cmd/plugin_uninstall.go @@ -70,7 +70,7 @@ func (o *pluginUninstallOptions) run(out io.Writer) error { for _, name := range o.names { if found := findPlugin(plugins, name); found != nil { if err := uninstallPlugin(found); err != nil { - errorPlugins = append(errorPlugins, fmt.Errorf("failed to uninstall plugin %s, got error (%w)", name, err)) + errorPlugins = append(errorPlugins, fmt.Errorf("failed to uninstall plugin %s, got error (%v)", name, err)) } else { fmt.Fprintf(out, "Uninstalled plugin: %s\n", name) } diff --git a/pkg/cmd/plugin_update.go b/pkg/cmd/plugin_update.go index 1cba7ffa9..83ef35107 100644 --- a/pkg/cmd/plugin_update.go +++ b/pkg/cmd/plugin_update.go @@ -71,7 +71,7 @@ func (o *pluginUpdateOptions) run(out io.Writer) error { for _, name := range o.names { if found := findPlugin(plugins, name); found != nil { if err := updatePlugin(found); err != nil { - errorPlugins = append(errorPlugins, fmt.Errorf("failed to update plugin %s, got error (%w)", name, err)) + errorPlugins = append(errorPlugins, fmt.Errorf("failed to update plugin %s, got error (%v)", name, err)) } else { fmt.Fprintf(out, "Updated plugin: %s\n", name) } diff --git a/pkg/cmd/pull.go b/pkg/cmd/pull.go index d371db4ad..bb7a8d1c0 100644 --- a/pkg/cmd/pull.go +++ b/pkg/cmd/pull.go @@ -65,7 +65,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.Version = ">0.0.0-0" } - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index 511061dcd..f749c218c 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -260,6 +260,7 @@ func TestPullCmd(t *testing.T) { if out != outString { t.Errorf("%q: expected verification output %q, got %q", tt.name, outString, out) } + } ef := filepath.Join(outdir, tt.expectFile) diff --git a/pkg/cmd/push.go b/pkg/cmd/push.go index f32ce92be..94c5732ff 100644 --- a/pkg/cmd/push.go +++ b/pkg/cmd/push.go @@ -71,7 +71,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { }, RunE: func(_ *cobra.Command, args []string) error { registryClient, err := newRegistryClient( - out, o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSVerify, o.plainHTTP, o.username, o.password, + o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSVerify, o.plainHTTP, o.username, o.password, ) if err != nil { diff --git a/pkg/cmd/repo_index_test.go b/pkg/cmd/repo_index_test.go index 68f105d6d..c8959f21e 100644 --- a/pkg/cmd/repo_index_test.go +++ b/pkg/cmd/repo_index_test.go @@ -28,6 +28,7 @@ import ( ) func TestRepoIndexCmd(t *testing.T) { + dir := t.TempDir() comp := filepath.Join(dir, "compressedchart-0.1.0.tgz") diff --git a/pkg/cmd/repo_remove_test.go b/pkg/cmd/repo_remove_test.go index 79778e75e..f2641ccf0 100644 --- a/pkg/cmd/repo_remove_test.go +++ b/pkg/cmd/repo_remove_test.go @@ -103,6 +103,7 @@ func TestRepoRemove(t *testing.T) { cacheIndex, cacheChart := createCacheFiles(rootDir, repoName) cacheFiles[repoName] = []string{cacheIndex, cacheChart} + } // Create repo remove command diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 3203ced25..04ba91c1f 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -257,7 +257,7 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg log.Fatal(err) } - registryClient, err := newDefaultRegistryClient(out, false, "", "") + registryClient, err := newDefaultRegistryClient(false, "", "") if err != nil { return nil, err } @@ -355,6 +355,7 @@ func hookOutputWriter(_, _, _ string) io.Writer { } func checkForExpiredRepos(repofile string) { + expiredRepos := []struct { name string old string @@ -398,30 +399,31 @@ func checkForExpiredRepos(repofile string) { ) } } + } func newRegistryClient( - out io.Writer, certFile, keyFile, caFile string, insecureSkipTLSVerify, plainHTTP bool, username, password string, + certFile, keyFile, caFile string, insecureSkipTLSVerify, plainHTTP bool, username, password string, ) (*registry.Client, error) { if certFile != "" && keyFile != "" || caFile != "" || insecureSkipTLSVerify { - registryClient, err := newRegistryClientWithTLS(out, certFile, keyFile, caFile, insecureSkipTLSVerify, username, password) + registryClient, err := newRegistryClientWithTLS(certFile, keyFile, caFile, insecureSkipTLSVerify, username, password) if err != nil { return nil, err } return registryClient, nil } - registryClient, err := newDefaultRegistryClient(out, plainHTTP, username, password) + registryClient, err := newDefaultRegistryClient(plainHTTP, username, password) if err != nil { return nil, err } return registryClient, nil } -func newDefaultRegistryClient(out io.Writer, plainHTTP bool, username, password string) (*registry.Client, error) { +func newDefaultRegistryClient(plainHTTP bool, username, password string) (*registry.Client, error) { opts := []registry.ClientOption{ registry.ClientOptDebug(settings.Debug), registry.ClientOptEnableCache(true), - registry.ClientOptWriter(out), + registry.ClientOptWriter(os.Stderr), registry.ClientOptCredentialsFile(settings.RegistryConfig), registry.ClientOptBasicAuth(username, password), } @@ -438,7 +440,7 @@ func newDefaultRegistryClient(out io.Writer, plainHTTP bool, username, password } func newRegistryClientWithTLS( - out io.Writer, certFile, keyFile, caFile string, insecureSkipTLSVerify bool, username, password string, + certFile, keyFile, caFile string, insecureSkipTLSVerify bool, username, password string, ) (*registry.Client, error) { tlsConf, err := tlsutil.NewTLSConfig( tlsutil.WithInsecureSkipVerify(insecureSkipTLSVerify), @@ -454,7 +456,7 @@ func newRegistryClientWithTLS( registryClient, err := registry.NewClient( registry.ClientOptDebug(settings.Debug), registry.ClientOptEnableCache(true), - registry.ClientOptWriter(out), + registry.ClientOptWriter(os.Stderr), registry.ClientOptCredentialsFile(settings.RegistryConfig), registry.ClientOptHTTPClient(&http.Client{ Transport: &http.Transport{ diff --git a/pkg/cmd/search.go b/pkg/cmd/search.go index eb6c6e0b5..4d110286d 100644 --- a/pkg/cmd/search.go +++ b/pkg/cmd/search.go @@ -29,6 +29,7 @@ Use search subcommands to search different locations for charts. ` func newSearchCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ Use: "search [keyword]", Short: "search for a keyword in charts", diff --git a/pkg/cmd/search/search.go b/pkg/cmd/search/search.go index cb630709f..1c7bb1d06 100644 --- a/pkg/cmd/search/search.go +++ b/pkg/cmd/search/search.go @@ -123,6 +123,7 @@ func (i *Index) Search(term string, threshold int, regexp bool) ([]*Result, erro // calcScore calculates a score for a match. func (i *Index) calcScore(index int, matchline string) int { + // This is currently tied to the fact that sep is a single char. splits := []int{} s := rune(sep[0]) diff --git a/pkg/cmd/search/search_test.go b/pkg/cmd/search/search_test.go index 46394e77f..b3220394f 100644 --- a/pkg/cmd/search/search_test.go +++ b/pkg/cmd/search/search_test.go @@ -149,6 +149,7 @@ func TestAddRepo_Sort(t *testing.T) { } func TestSearchByName(t *testing.T) { + tests := []struct { name string query string @@ -244,6 +245,7 @@ func TestSearchByName(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + charts, err := i.Search(tt.query, 100, tt.regexp) if err != nil { if tt.fail { @@ -272,6 +274,7 @@ func TestSearchByName(t *testing.T) { t.Errorf("[%d]: Expected name %q, got %q", i, ex.Name, got.Name) } } + }) } } diff --git a/pkg/cmd/search_hub.go b/pkg/cmd/search_hub.go index 3081961be..f9adb73f4 100644 --- a/pkg/cmd/search_hub.go +++ b/pkg/cmd/search_hub.go @@ -196,4 +196,5 @@ func (h *hubSearchWriter) encodeByFormat(out io.Writer, format output.Format) er // WriteJSON and WriteYAML, we shouldn't get invalid types return nil } + } diff --git a/pkg/cmd/search_hub_test.go b/pkg/cmd/search_hub_test.go index 98bf052f2..62881cbdd 100644 --- a/pkg/cmd/search_hub_test.go +++ b/pkg/cmd/search_hub_test.go @@ -24,6 +24,7 @@ import ( ) func TestSearchHubCmd(t *testing.T) { + // Setup a mock search service var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { @@ -53,6 +54,7 @@ func TestSearchHubCmd(t *testing.T) { } func TestSearchHubListRepoCmd(t *testing.T) { + // Setup a mock search service var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { diff --git a/pkg/cmd/show.go b/pkg/cmd/show.go index a92ca75b4..d7249c3fe 100644 --- a/pkg/cmd/show.go +++ b/pkg/cmd/show.go @@ -84,7 +84,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: validArgsFunc, RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowAll - err := addRegistryClient(out, client) + err := addRegistryClient(client) if err != nil { return err } @@ -105,7 +105,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: validArgsFunc, RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowValues - err := addRegistryClient(out, client) + err := addRegistryClient(client) if err != nil { return err } @@ -126,7 +126,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: validArgsFunc, RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowChart - err := addRegistryClient(out, client) + err := addRegistryClient(client) if err != nil { return err } @@ -147,7 +147,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: validArgsFunc, RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowReadme - err := addRegistryClient(out, client) + err := addRegistryClient(client) if err != nil { return err } @@ -168,7 +168,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ValidArgsFunction: validArgsFunc, RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowCRDs - err := addRegistryClient(out, client) + err := addRegistryClient(client) if err != nil { return err } @@ -225,8 +225,8 @@ func runShow(args []string, client *action.Show) (string, error) { return client.Run(cp) } -func addRegistryClient(out io.Writer, client *action.Show) error { - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, +func addRegistryClient(client *action.Show) error { + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/template.go b/pkg/cmd/template.go index bb364231b..047fd60df 100644 --- a/pkg/cmd/template.go +++ b/pkg/cmd/template.go @@ -85,7 +85,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.KubeVersion = parsedKubeVersion } - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) @@ -113,7 +113,6 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return err } - installErr := err // We ignore a potential error here because, when the --debug flag was specified, // we always want to print the YAML, even if it is not valid. The error is still returned afterwards. @@ -143,6 +142,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return err } } + } } @@ -187,10 +187,6 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { missing = false } if missing { - if installErr != nil && settings.Debug { - // assume the manifest itself is too malformed to be rendered - return installErr - } return fmt.Errorf("could not find template %s in chart", f) } } @@ -202,7 +198,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } } - return installErr + return err }, } diff --git a/pkg/cmd/template_test.go b/pkg/cmd/template_test.go index b43151413..7391781f6 100644 --- a/pkg/cmd/template_test.go +++ b/pkg/cmd/template_test.go @@ -133,12 +133,6 @@ func TestTemplateCmd(t *testing.T) { wantError: true, golden: "output/template-with-invalid-yaml-debug.txt", }, - { - name: "chart with template with invalid template expression (--debug, --show-only)", - cmd: fmt.Sprintf("template '%s' --debug --show-only %s", "testdata/testcharts/chart-with-template-with-invalid-template-expr", "templates/alpine-pod.yaml"), - wantError: true, - golden: "output/template-with-invalid-template-expr-debug-show-only.txt", - }, { name: "template skip-tests", cmd: fmt.Sprintf(`template '%s' --skip-tests`, chartPath), diff --git a/pkg/cmd/testdata/output/template-with-invalid-template-expr-debug-show-only.txt b/pkg/cmd/testdata/output/template-with-invalid-template-expr-debug-show-only.txt deleted file mode 100644 index 89d518bbb..000000000 --- a/pkg/cmd/testdata/output/template-with-invalid-template-expr-debug-show-only.txt +++ /dev/null @@ -1,3 +0,0 @@ -Error: chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml:7:38 - executing "chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml" at : - invalid value; expected string diff --git a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/Chart.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/Chart.yaml deleted file mode 100644 index 83541d4dd..000000000 --- a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/Chart.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -description: Deploy a basic Alpine Linux pod -home: https://helm.sh/helm -name: chart-with-template-with-invalid-template-expr -sources: - - https://github.com/helm/helm -version: 0.1.0 -type: application diff --git a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/README.md b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/README.md deleted file mode 100644 index fcf7ee017..000000000 --- a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/README.md +++ /dev/null @@ -1,13 +0,0 @@ -#Alpine: A simple Helm chart - -Run a single pod of Alpine Linux. - -This example was generated using the command `helm create alpine`. - -The `templates/` directory contains a very simple pod resource with a -couple of parameters. - -The `values.yaml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml deleted file mode 100644 index bdc0fe39b..000000000 --- a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/templates/alpine-pod.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{.Release.Name}}-{{.Values.Name}}" -spec: - containers: - - name: {{ .Values.nonExistentKey | b64enc }} - image: "alpine:3.9" - command: ["/bin/sleep","9000"] diff --git a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/values.yaml b/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/values.yaml deleted file mode 100644 index 807e12aea..000000000 --- a/pkg/cmd/testdata/testcharts/chart-with-template-with-invalid-template-expr/values.yaml +++ /dev/null @@ -1 +0,0 @@ -Name: my-alpine diff --git a/pkg/cmd/uninstall.go b/pkg/cmd/uninstall.go index bfecd4741..49f7bd19d 100644 --- a/pkg/cmd/uninstall.go +++ b/pkg/cmd/uninstall.go @@ -59,6 +59,7 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return validationErr } for i := range args { + res, err := client.Run(args[i]) if err != nil { return err diff --git a/pkg/cmd/upgrade.go b/pkg/cmd/upgrade.go index 9ec4f4ef2..43e19ab22 100644 --- a/pkg/cmd/upgrade.go +++ b/pkg/cmd/upgrade.go @@ -105,7 +105,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { client.Namespace = settings.Namespace() - registryClient, err := newRegistryClient(out, client.CertFile, client.KeyFile, client.CaFile, + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, client.InsecureSkipTLSVerify, client.PlainHTTP, client.Username, client.Password) if err != nil { return fmt.Errorf("missing registry client: %w", err) diff --git a/pkg/cmd/upgrade_test.go b/pkg/cmd/upgrade_test.go index 30188d3d9..f96f6ec0d 100644 --- a/pkg/cmd/upgrade_test.go +++ b/pkg/cmd/upgrade_test.go @@ -34,6 +34,7 @@ import ( ) func TestUpgradeCmd(t *testing.T) { + tmpChart := t.TempDir() cfile := &chart.Chart{ Metadata: &chart.Metadata{ @@ -221,6 +222,7 @@ func TestUpgradeWithValue(t *testing.T) { if !strings.Contains(updatedRel.Manifest, "drink: tea") { t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) } + } func TestUpgradeWithStringValue(t *testing.T) { @@ -251,9 +253,11 @@ func TestUpgradeWithStringValue(t *testing.T) { if !strings.Contains(updatedRel.Manifest, "drink: coffee") { t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) } + } func TestUpgradeInstallWithSubchartNotes(t *testing.T) { + releaseName := "wacky-bunny-v1" relMock, ch, _ := prepareMockRelease(t, releaseName) @@ -285,9 +289,11 @@ func TestUpgradeInstallWithSubchartNotes(t *testing.T) { if !strings.Contains(upgradedRel.Info.Notes, "SUBCHART NOTES") { t.Errorf("The subchart notes are not set correctly. NOTES: %s", upgradedRel.Info.Notes) } + } func TestUpgradeWithValuesFile(t *testing.T) { + releaseName := "funny-bunny-v4" relMock, ch, chartPath := prepareMockRelease(t, releaseName) @@ -315,9 +321,11 @@ func TestUpgradeWithValuesFile(t *testing.T) { if !strings.Contains(updatedRel.Manifest, "drink: beer") { t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) } + } func TestUpgradeWithValuesFromStdin(t *testing.T) { + releaseName := "funny-bunny-v5" relMock, ch, chartPath := prepareMockRelease(t, releaseName) @@ -353,6 +361,7 @@ func TestUpgradeWithValuesFromStdin(t *testing.T) { } func TestUpgradeInstallWithValuesFromStdin(t *testing.T) { + releaseName := "funny-bunny-v6" _, _, chartPath := prepareMockRelease(t, releaseName) @@ -383,6 +392,7 @@ func TestUpgradeInstallWithValuesFromStdin(t *testing.T) { if !strings.Contains(updatedRel.Manifest, "drink: beer") { t.Errorf("The value is not set correctly. manifest: %s", updatedRel.Manifest) } + } func prepareMockRelease(t *testing.T, releaseName string) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) { diff --git a/pkg/cmd/verify_test.go b/pkg/cmd/verify_test.go index 050d799c9..ae373afd2 100644 --- a/pkg/cmd/verify_test.go +++ b/pkg/cmd/verify_test.go @@ -22,6 +22,7 @@ import ( ) func TestVerifyCmd(t *testing.T) { + statExe := "stat" statPathMsg := "no such file or directory" statFileMsg := statPathMsg diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 22c6c71a3..9c26f925e 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -282,6 +282,7 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena // If provenance is requested, verify it. ver := &provenance.Verification{} if c.Verify > VerifyNever { + ppth, err := c.Cache.Get(digest32, CacheProv) if err == nil { slog.Debug("found provenance in cache", "id", digestString) @@ -307,6 +308,7 @@ func (c *ChartDownloader) DownloadToCache(ref, version string) (string, *provena } if c.Verify != VerifyLater { + // provenance files pin to a specific name so this needs to be accounted for // when verifying. // Note, this does make an assumption that the name/version is unique to a diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index ac4f2207e..16459229d 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -261,7 +261,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { return err } } else { - return fmt.Errorf("unable to retrieve file info for '%s': %w", destPath, err) + return fmt.Errorf("unable to retrieve file info for '%s': %v", destPath, err) } // Prepare tmpPath @@ -281,17 +281,17 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error { chartPath := filepath.Join(destPath, dep.Name) ch, err := loader.LoadDir(chartPath) if err != nil { - return fmt.Errorf("unable to load chart '%s': %w", chartPath, err) + return fmt.Errorf("unable to load chart '%s': %v", chartPath, err) } constraint, err := semver.NewConstraint(dep.Version) if err != nil { - return fmt.Errorf("dependency %s has an invalid version/constraint format: %w", dep.Name, err) + return fmt.Errorf("dependency %s has an invalid version/constraint format: %s", dep.Name, err) } v, err := semver.NewVersion(ch.Metadata.Version) if err != nil { - return fmt.Errorf("invalid version %s for dependency %s: %w", dep.Version, dep.Name, err) + return fmt.Errorf("invalid version %s for dependency %s: %s", dep.Version, dep.Name, err) } if !constraint.Check(v) { @@ -502,9 +502,11 @@ Loop: // in a known repo and attempt to ensure the data is present for steps like // version resolution. func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart.Dependency) (map[string]string, error) { + var ru []*repo.Entry for _, dd := range deps { + // If the chart is in the local charts directory no repository needs // to be specified. if dd.Repository == "" { @@ -677,6 +679,7 @@ func dedupeRepos(repos []*repo.Entry) []*repo.Entry { } func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { + var wg sync.WaitGroup localRepos := dedupeRepos(repos) diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 612ab85f0..869b5d202 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -27,7 +27,6 @@ import ( "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -658,6 +657,7 @@ func TestRenderDependency(t *testing.T) { if out["outerchart/templates/outer"] != expect { t.Errorf("Expected %q, got %q", expect, out["outer"]) } + } func TestRenderNestedValues(t *testing.T) { @@ -813,6 +813,7 @@ func TestRenderBuiltinValues(t *testing.T) { t.Errorf("Expected %q, got %q", expect, out[file]) } } + } func TestAlterFuncMap_include(t *testing.T) { @@ -1008,6 +1009,7 @@ func TestAlterFuncMap_tplinclude(t *testing.T) { if got := out["TplFunction/templates/base"]; got != expect { t.Errorf("Expected %q, got %q (%v)", expect, got, out) } + } func TestRenderRecursionLimit(t *testing.T) { @@ -1066,6 +1068,7 @@ func TestRenderRecursionLimit(t *testing.T) { if got := out["overlook/templates/quote"]; got != expect { t.Errorf("Expected %q, got %q (%v)", expect, got, out) } + } func TestRenderLoadTemplateForTplFromFile(t *testing.T) { @@ -1316,6 +1319,7 @@ func TestRenderTplMissingKeyString(t *testing.T) { if !strings.Contains(errTxt, "noSuchKey") { t.Errorf("Expected error to contain 'noSuchKey', got %s", errTxt) } + } func TestNestedHelpersProducesMultilineStacktrace(t *testing.T) { @@ -1353,7 +1357,7 @@ NestedHelperFunctions/charts/common/templates/_helpers_2.tpl:1:49 } _, err := Render(c, vals) - require.Error(t, err) + assert.NotNil(t, err) assert.Equal(t, expectedErrorMessage, err.Error()) } @@ -1387,7 +1391,7 @@ template: no template "nested_helper.name" associated with template "gotpl"` } _, err := Render(c, vals) - require.Error(t, err) + assert.NotNil(t, err) assert.Equal(t, expectedErrorMessage, err.Error()) } diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go index c5c441c14..52b6ffdaf 100644 --- a/pkg/engine/lookup_func.go +++ b/pkg/engine/lookup_func.go @@ -73,12 +73,6 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc { if apierrors.IsNotFound(err) { // Just return an empty interface when the object was not found. // That way, users can use `if not (lookup ...)` in their templates. - slog.Debug("lookup: resource not found", - slog.String("apiVersion", apiversion), - slog.String("kind", kind), - slog.String("namespace", namespace), - slog.String("name", name), - ) return map[string]any{}, nil } return map[string]any{}, err @@ -91,11 +85,6 @@ func newLookupFunction(clientProvider ClientProvider) lookupFunc { if apierrors.IsNotFound(err) { // Just return an empty interface when the object was not found. // That way, users can use `if not (lookup ...)` in their templates. - slog.Debug("lookup: resource list not found", - slog.String("apiVersion", apiversion), - slog.String("kind", kind), - slog.String("namespace", namespace), - ) return map[string]any{}, nil } return map[string]any{}, err diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index f3116d626..7d4581233 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -510,6 +510,7 @@ func TestDownloadInsecureSkipTLSVerify(t *testing.T) { if _, err = g.Get(u.String()); err != nil { t.Error(err) } + } func TestHTTPGetterTarDownload(t *testing.T) { diff --git a/pkg/getter/plugingetter.go b/pkg/getter/plugingetter.go index c683a1090..ef8b87503 100644 --- a/pkg/getter/plugingetter.go +++ b/pkg/getter/plugingetter.go @@ -41,6 +41,7 @@ func collectGetterPlugins(settings *cli.EnvSettings) (Providers, error) { env := plugin.FormatEnv(settings.EnvVars()) pluginConstructorBuilder := func(plg plugin.Plugin) Constructor { return func(option ...Option) (Getter, error) { + return &getterPlugin{ options: append([]Option{}, option...), plg: plg, diff --git a/pkg/helmpath/lazypath.go b/pkg/helmpath/lazypath.go index bf6d71aa7..c1f868754 100644 --- a/pkg/helmpath/lazypath.go +++ b/pkg/helmpath/lazypath.go @@ -38,6 +38,7 @@ const ( type lazypath string func (l lazypath) path(helmEnvVar, xdgEnvVar string, defaultFn func() string, elem ...string) string { + // There is an order to checking for a path. // 1. See if a Helm specific environment variable has been set. // 2. Check if an XDG environment variable is set diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 17c8718b0..c955e8875 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -521,6 +521,7 @@ func determineFieldValidationDirective(validate bool) FieldValidationDirective { } func buildResourceList(f Factory, namespace string, validationDirective FieldValidationDirective, reader io.Reader, transformRequest resource.RequestTransform) (ResourceList, error) { + schema, err := f.Validator(string(validationDirective)) if err != nil { return nil, err @@ -858,6 +859,7 @@ func (c *Client) Update(originals, targets ResourceList, options ...ClientUpdate slog.String("fieldValidationDirective", string(updateOptions.fieldValidationDirective)), slog.Bool("upgradeClientSideFieldManager", updateOptions.upgradeClientSideFieldManager)) return func(original, target *resource.Info) error { + logger := c.Logger().With( slog.String("namespace", target.Namespace), slog.String("name", target.Name), @@ -952,6 +954,7 @@ func isIncompatibleServerError(err error) bool { // getManagedFieldsManager returns the manager string. If one was set it will be returned. // Otherwise, one is calculated based on the name of the binary. func getManagedFieldsManager() string { + // When a manager is explicitly set use it if ManagedFieldsManager != "" { return ManagedFieldsManager @@ -1099,6 +1102,7 @@ func createPatch(original runtime.Object, target *resource.Info, threeWayMergeFo } func replaceResource(target *resource.Info, fieldValidationDirective FieldValidationDirective) error { + helper := resource.NewHelper(target.Client, target.Mapping). WithFieldValidation(string(fieldValidationDirective)). WithFieldManager(getManagedFieldsManager()) @@ -1113,9 +1117,11 @@ func replaceResource(target *resource.Info, fieldValidationDirective FieldValida } return nil + } func patchResourceClientSide(original runtime.Object, target *resource.Info, threeWayMergeForUnstructured bool) error { + patch, patchType, err := createPatch(original, target, threeWayMergeForUnstructured) if err != nil { return fmt.Errorf("failed to create patch: %w", err) @@ -1149,12 +1155,14 @@ func patchResourceClientSide(original runtime.Object, target *resource.Info, thr // that upgrade CSA managed fields to SSA apply // see: https://github.com/kubernetes/kubernetes/pull/112905 func upgradeClientSideFieldManager(info *resource.Info, dryRun bool, fieldValidationDirective FieldValidationDirective) (bool, error) { + fieldManagerName := getManagedFieldsManager() patched := false err := retry.RetryOnConflict( retry.DefaultRetry, func() error { + if err := info.Get(); err != nil { return fmt.Errorf("failed to get object %s/%s %s: %w", info.Namespace, info.Name, info.Mapping.GroupVersionKind.String(), err) } @@ -1226,7 +1234,7 @@ func patchResourceServerSide(target *resource.Info, dryRun bool, forceConflicts ) if err != nil { if isIncompatibleServerError(err) { - return fmt.Errorf("server-side apply not available on the server: %w", err) + return fmt.Errorf("server-side apply not available on the server: %v", err) } if apierrors.IsConflict(err) { @@ -1243,7 +1251,7 @@ func patchResourceServerSide(target *resource.Info, dryRun bool, forceConflicts func (c *Client) GetPodList(namespace string, listOptions metav1.ListOptions) (*v1.PodList, error) { podList, err := c.kubeClient.CoreV1().Pods(namespace).List(context.Background(), listOptions) if err != nil { - return nil, fmt.Errorf("failed to get pod list with options: %+v with error: %w", listOptions, err) + return nil, fmt.Errorf("failed to get pod list with options: %+v with error: %v", listOptions, err) } return podList, nil } diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index cd00baa94..ed871c05a 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -282,6 +282,7 @@ func TestCreate(t *testing.T) { c := newTestClient(t) for name, tc := range testCases { t.Run(name, func(t *testing.T) { + client := NewRequestResponseLogClient(t, func(previous []RequestResponseAction, req *http.Request) (*http.Response, error) { return tc.Callback(t, tc, previous, req) }) @@ -316,6 +317,7 @@ func TestCreate(t *testing.T) { } assert.Equal(t, tc.ExpectedActions, actions) + }) } } @@ -442,6 +444,7 @@ func TestUpdate(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { + listOriginal := tc.OriginalPods listTarget := tc.TargetPods @@ -1407,6 +1410,7 @@ func TestIsReachable(t *testing.T) { if !strings.Contains(err.Error(), tt.errorContains) { t.Errorf("expected error message to contain '%s', got: %v", tt.errorContains, err) } + } else { if err != nil { t.Errorf("expected no error but got: %v", err) @@ -1484,6 +1488,7 @@ func TestReplaceResource(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { + testFactory := cmdtesting.NewTestFactory() t.Cleanup(testFactory.Cleanup) @@ -1606,6 +1611,7 @@ func TestPatchResourceClientSide(t *testing.T) { t.Fail() return nil, nil + }, ExpectedErrorContains: "cannot patch \"whale\" with kind Pod: the server reported a conflict", }, @@ -1627,12 +1633,14 @@ func TestPatchResourceClientSide(t *testing.T) { t.Fail() return nil, nil // newResponse(http.StatusOK, &tc.TargetPods.Items[0]) + }, }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { + testFactory := cmdtesting.NewTestFactory() t.Cleanup(testFactory.Cleanup) @@ -1816,6 +1824,7 @@ func TestPatchResourceServerSide(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { + testFactory := cmdtesting.NewTestFactory() t.Cleanup(testFactory.Cleanup) @@ -1846,6 +1855,7 @@ func TestPatchResourceServerSide(t *testing.T) { } func TestDetermineFieldValidationDirective(t *testing.T) { + assert.Equal(t, FieldValidationDirectiveIgnore, determineFieldValidationDirective(false)) assert.Equal(t, FieldValidationDirectiveStrict, determineFieldValidationDirective(true)) } diff --git a/pkg/kube/fake/failing_kube_client.go b/pkg/kube/fake/failing_kube_client.go index 75d0c8de1..0f7787f79 100644 --- a/pkg/kube/fake/failing_kube_client.go +++ b/pkg/kube/fake/failing_kube_client.go @@ -19,7 +19,6 @@ package fake import ( "io" - "sync" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -50,7 +49,6 @@ type FailingKubeClient struct { WaitDuration time.Duration // RecordedWaitOptions stores the WaitOptions passed to GetWaiter for testing RecordedWaitOptions []kube.WaitOption - mu sync.Mutex } var _ kube.Interface = &FailingKubeClient{} @@ -160,14 +158,9 @@ func (f *FailingKubeClient) GetWaiter(ws kube.WaitStrategy) (kube.Waiter, error) return f.GetWaiterWithOptions(ws) } -func (f *FailingKubeClient) appendRecordedWaitOptionsLocked(opts ...kube.WaitOption) { - f.mu.Lock() - defer f.mu.Unlock() - f.RecordedWaitOptions = append(f.RecordedWaitOptions, opts...) -} - func (f *FailingKubeClient) GetWaiterWithOptions(ws kube.WaitStrategy, opts ...kube.WaitOption) (kube.Waiter, error) { - f.appendRecordedWaitOptionsLocked(opts...) + // Record the WaitOptions for testing + f.RecordedWaitOptions = append(f.RecordedWaitOptions, opts...) waiter, _ := f.PrintingKubeClient.GetWaiterWithOptions(ws, opts...) printingKubeWaiter, _ := waiter.(*PrintingKubeWaiter) return &FailingKubeWaiter{ diff --git a/pkg/kube/statuswait.go b/pkg/kube/statuswait.go index adc7a425a..59c1218ff 100644 --- a/pkg/kube/statuswait.go +++ b/pkg/kube/statuswait.go @@ -68,12 +68,6 @@ func alwaysReady(_ *unstructured.Unstructured) (*status.Result, error) { }, nil } -func getStatusWatcher(dynamicClient dynamic.Interface, mapper meta.RESTMapper) *watcher.DefaultStatusWatcher { - sw := watcher.NewDefaultStatusWatcher(dynamicClient, mapper) - sw.ResyncPeriod = 3 * time.Minute - return sw -} - func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.Duration) error { if timeout == 0 { timeout = DefaultStatusWatcherTimeout @@ -81,7 +75,7 @@ func (w *statusWaiter) WatchUntilReady(resourceList ResourceList, timeout time.D ctx, cancel := w.contextWithTimeout(w.watchUntilReadyCtx, timeout) defer cancel() w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) - sw := getStatusWatcher(w.client, w.restMapper) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) jobSR := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) podSR := helmStatusReaders.NewCustomPodStatusReader(w.restMapper) // We don't want to wait on any other resources as watchUntilReady is only for Helm hooks. @@ -103,7 +97,7 @@ func (w *statusWaiter) Wait(resourceList ResourceList, timeout time.Duration) er ctx, cancel := w.contextWithTimeout(w.waitCtx, timeout) defer cancel() w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) - sw := getStatusWatcher(w.client, w.restMapper) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) sw.StatusReader = statusreaders.NewStatusReader(w.restMapper, w.readers...) return w.wait(ctx, resourceList, sw) } @@ -115,7 +109,7 @@ func (w *statusWaiter) WaitWithJobs(resourceList ResourceList, timeout time.Dura ctx, cancel := w.contextWithTimeout(w.waitWithJobsCtx, timeout) defer cancel() w.Logger().Debug("waiting for resources", "count", len(resourceList), "timeout", timeout) - sw := getStatusWatcher(w.client, w.restMapper) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) newCustomJobStatusReader := helmStatusReaders.NewCustomJobStatusReader(w.restMapper) readers := append([]engine.StatusReader(nil), w.readers...) readers = append(readers, newCustomJobStatusReader) @@ -131,7 +125,7 @@ func (w *statusWaiter) WaitForDelete(resourceList ResourceList, timeout time.Dur ctx, cancel := w.contextWithTimeout(w.waitForDeleteCtx, timeout) defer cancel() w.Logger().Debug("waiting for resources to be deleted", "count", len(resourceList), "timeout", timeout) - sw := getStatusWatcher(w.client, w.restMapper) + sw := watcher.NewDefaultStatusWatcher(w.client, w.restMapper) return w.waitForDelete(ctx, resourceList, sw) } @@ -167,15 +161,7 @@ func (w *statusWaiter) waitForDelete(ctx context.Context, resourceList ResourceL rs.Identifier.GroupKind.Kind, rs.Identifier.Namespace, rs.Identifier.Name, rs.Status, rs.Message)) } if err := ctx.Err(); err != nil { - // context.Canceled and other non-deadline errors always propagate: they signal an - // external interruption regardless of resource state. - // context.DeadlineExceeded is only added when there are resource-specific errors; - // if all resources are Unknown or NotFound the timeout is not itself a failure for - // a delete wait (e.g. resources deleted before the watch started stay Unknown in - // the fake client but are effectively gone). - if !errors.Is(err, context.DeadlineExceeded) || len(errs) > 0 { - errs = append(errs, err) - } + errs = append(errs, err) } if len(errs) > 0 { return errors.Join(errs...) @@ -248,7 +234,6 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl return func(statusCollector *collector.ResourceStatusCollector, _ event.Event) { var rss []*event.ResourceStatus var nonDesiredResources []*event.ResourceStatus - var unknownSkipped int for _, rs := range statusCollector.ResourceStatuses { if rs == nil { continue @@ -256,7 +241,6 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl // If a resource is already deleted before waiting has started, it will show as unknown. // This check ensures we don't wait forever for a resource that is already deleted. if rs.Status == status.UnknownStatus && desired == status.NotFoundStatus { - unknownSkipped++ continue } // Failed is a terminal state. This check ensures we don't wait forever for a resource @@ -270,14 +254,6 @@ func statusObserver(cancel context.CancelFunc, desired status.Status, logger *sl } } - // During informer initialization there is a brief window where existing resources - // appear as Unknown before their real status is delivered. If every resource was - // skipped as Unknown, we cannot yet distinguish "all deleted" from "not yet synced", - // so hold off on the early-cancel to avoid a spurious success or premature exit. - if unknownSkipped > 0 && len(rss) == 0 { - return - } - if aggregator.AggregateStatus(rss, desired) == desired { logger.Debug("all resources achieved desired status", "desiredStatus", desired, "resourceCount", len(rss)) cancel() diff --git a/pkg/registry/chart.go b/pkg/registry/chart.go index d70b7e912..b00fc616d 100644 --- a/pkg/registry/chart.go +++ b/pkg/registry/chart.go @@ -43,12 +43,14 @@ func extractChartMeta(chartData []byte) (*chart.Metadata, error) { // generateOCIAnnotations will generate OCI annotations to include within the OCI manifest func generateOCIAnnotations(meta *chart.Metadata, creationTime string) map[string]string { + // Get annotations from Chart attributes ociAnnotations := generateChartOCIAnnotations(meta, creationTime) // Copy Chart annotations annotations: for chartAnnotationKey, chartAnnotationValue := range meta.Annotations { + // Avoid overriding key properties for _, immutableOciKey := range immutableOciAnnotations { if immutableOciKey == chartAnnotationKey { @@ -86,6 +88,7 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ var maintainerSb strings.Builder for maintainerIdx, maintainer := range meta.Maintainers { + if len(maintainer.Name) > 0 { maintainerSb.WriteString(maintainer.Name) } @@ -99,9 +102,11 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ if maintainerIdx < len(meta.Maintainers)-1 { maintainerSb.WriteString(", ") } + } chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationAuthors, maintainerSb.String()) + } return chartOCIAnnotations @@ -109,6 +114,7 @@ func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[ // addToMap takes an existing map and adds an item if the value is not empty func addToMap(inputMap map[string]string, newKey string, newValue string) map[string]string { + // Add item to map if its if len(strings.TrimSpace(newValue)) > 0 { inputMap[newKey] = newValue diff --git a/pkg/registry/chart_test.go b/pkg/registry/chart_test.go index 0b247601b..77ccdaab7 100644 --- a/pkg/registry/chart_test.go +++ b/pkg/registry/chart_test.go @@ -27,6 +27,7 @@ import ( ) func TestGenerateOCIChartAnnotations(t *testing.T) { + nowString := time.Now().Format(time.RFC3339) tests := []struct { @@ -146,15 +147,18 @@ func TestGenerateOCIChartAnnotations(t *testing.T) { } for _, tt := range tests { + result := generateChartOCIAnnotations(tt.chart, nowString) if !reflect.DeepEqual(tt.expect, result) { t.Errorf("%s: expected map %v, got %v", tt.name, tt.expect, result) } + } } func TestGenerateOCIAnnotations(t *testing.T) { + nowString := time.Now().Format(time.RFC3339) tests := []struct { @@ -217,15 +221,18 @@ func TestGenerateOCIAnnotations(t *testing.T) { } for _, tt := range tests { + result := generateOCIAnnotations(tt.chart, nowString) if !reflect.DeepEqual(tt.expect, result) { t.Errorf("%s: expected map %v, got %v", tt.name, tt.expect, result) } + } } func TestGenerateOCICreatedAnnotations(t *testing.T) { + nowTime := time.Now() nowTimeString := nowTime.Format(time.RFC3339) @@ -261,5 +268,7 @@ func TestGenerateOCICreatedAnnotations(t *testing.T) { if !nowTime.Before(createdTimeAnnotation) { t.Errorf("%s annotation with value '%s' not configured properly. Annotation value is not after %s", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated], nowTimeString) } + } + } diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 77f3d3efd..f2bfd13b4 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -819,6 +819,7 @@ func (c *Client) Tags(ref string) ([]string, error) { } return tags, nil + } // Resolve a reference to a descriptor. @@ -912,6 +913,7 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (string, *ur func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store, configDescriptor ocispec.Descriptor, layers []ocispec.Descriptor, ociAnnotations map[string]string, parsedRef reference) (ocispec.Descriptor, error) { + manifest := ocispec.Manifest{ Versioned: specs.Versioned{SchemaVersion: 2}, Config: configDescriptor, diff --git a/pkg/registry/client_http_test.go b/pkg/registry/client_http_test.go index 3eb74c541..1c6751559 100644 --- a/pkg/registry/client_http_test.go +++ b/pkg/registry/client_http_test.go @@ -17,6 +17,7 @@ limitations under the License. package registry import ( + "errors" "os" "testing" @@ -42,12 +43,12 @@ func (suite *HTTPRegistryClientTestSuite) Test_0_Login() { err := suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), LoginOptPlainText(true)) - suite.Require().Error(err, "error logging into registry with bad credentials") + suite.NotNil(err, "error logging into registry with bad credentials") err = suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), LoginOptPlainText(true)) - suite.Require().NoError(err, "no error logging into registry with good credentials") + suite.Nil(err, "no error logging into registry with good credentials") } func (suite *HTTPRegistryClientTestSuite) Test_1_Push() { @@ -67,15 +68,15 @@ func (suite *HTTPRegistryClientTestSuite) Test_4_ManInTheMiddle() { // returns content that does not match the expected digest _, err := suite.RegistryClient.Pull(ref) - suite.Require().Error(err) - suite.ErrorIs(err, content.ErrMismatchedDigest) + suite.NotNil(err) + suite.True(errors.Is(err, content.ErrMismatchedDigest)) } func (suite *HTTPRegistryClientTestSuite) Test_5_ImageIndex() { ref := suite.FakeRegistryHost + "/testrepo/image-index:0.1.0" _, err := suite.RegistryClient.Pull(ref) - suite.Require().NoError(err) + suite.Nil(err) } func TestHTTPRegistryClientTestSuite(t *testing.T) { diff --git a/pkg/registry/client_insecure_tls_test.go b/pkg/registry/client_insecure_tls_test.go index 8c4e928e4..2774f5e6f 100644 --- a/pkg/registry/client_insecure_tls_test.go +++ b/pkg/registry/client_insecure_tls_test.go @@ -41,12 +41,12 @@ func (suite *InsecureTLSRegistryClientTestSuite) Test_0_Login() { err := suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), LoginOptInsecure(true)) - suite.Require().Error(err, "error logging into registry with bad credentials") + suite.NotNil(err, "error logging into registry with bad credentials") err = suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), LoginOptInsecure(true)) - suite.Require().NoError(err, "no error logging into registry with good credentials") + suite.Nil(err, "no error logging into registry with good credentials") } func (suite *InsecureTLSRegistryClientTestSuite) Test_1_Push() { @@ -65,11 +65,11 @@ func (suite *InsecureTLSRegistryClientTestSuite) Test_4_Logout() { err := suite.RegistryClient.Logout("this-host-aint-real:5000") if err != nil { // credential backend for mac generates an error - suite.Require().Error(err, "failed to delete the credential for this-host-aint-real:5000") + suite.NotNil(err, "failed to delete the credential for this-host-aint-real:5000") } err = suite.RegistryClient.Logout(suite.DockerRegistryHost) - suite.Require().NoError(err, "no error logging out of registry") + suite.Nil(err, "no error logging out of registry") } func TestInsecureTLSRegistryClientTestSuite(t *testing.T) { diff --git a/pkg/registry/client_tls_test.go b/pkg/registry/client_tls_test.go index 2bea0377f..ddeeb3b66 100644 --- a/pkg/registry/client_tls_test.go +++ b/pkg/registry/client_tls_test.go @@ -43,26 +43,26 @@ func (suite *TLSRegistryClientTestSuite) Test_0_Login() { err := suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), LoginOptTLSClientConfig(tlsCert, tlsKey, tlsCA)) - suite.Require().Error(err, "error logging into registry with bad credentials") + suite.NotNil(err, "error logging into registry with bad credentials") err = suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), LoginOptTLSClientConfig(tlsCert, tlsKey, tlsCA)) - suite.Require().NoError(err, "no error logging into registry with good credentials") + suite.Nil(err, "no error logging into registry with good credentials") } func (suite *TLSRegistryClientTestSuite) Test_1_Login() { err := suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth("badverybad", "ohsobad"), LoginOptTLSClientConfigFromConfig(&tls.Config{})) - suite.Require().Error(err, "error logging into registry with bad credentials") + suite.NotNil(err, "error logging into registry with bad credentials") // Create a *tls.Config from tlsCert, tlsKey, and tlsCA. cert, err := tls.LoadX509KeyPair(tlsCert, tlsKey) - suite.Require().NoError(err, "error loading x509 key pair") + suite.Nil(err, "error loading x509 key pair") rootCAs := x509.NewCertPool() caCert, err := os.ReadFile(tlsCA) - suite.Require().NoError(err, "error reading CA certificate") + suite.Nil(err, "error reading CA certificate") rootCAs.AppendCertsFromPEM(caCert) conf := &tls.Config{ Certificates: []tls.Certificate{cert}, @@ -72,7 +72,7 @@ func (suite *TLSRegistryClientTestSuite) Test_1_Login() { err = suite.RegistryClient.Login(suite.DockerRegistryHost, LoginOptBasicAuth(testUsername, testPassword), LoginOptTLSClientConfigFromConfig(conf)) - suite.Require().NoError(err, "no error logging into registry with good credentials") + suite.Nil(err, "no error logging into registry with good credentials") } func (suite *TLSRegistryClientTestSuite) Test_1_Push() { @@ -91,11 +91,11 @@ func (suite *TLSRegistryClientTestSuite) Test_4_Logout() { err := suite.RegistryClient.Logout("this-host-aint-real:5000") if err != nil { // credential backend for mac generates an error - suite.Require().Error(err, "failed to delete the credential for this-host-aint-real:5000") + suite.NotNil(err, "failed to delete the credential for this-host-aint-real:5000") } err = suite.RegistryClient.Logout(suite.DockerRegistryHost) - suite.Require().NoError(err, "no error logging out of registry") + suite.Nil(err, "no error logging out of registry") } func TestTLSRegistryClientTestSuite(t *testing.T) { diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index 770da2467..b0c08fa3c 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -107,26 +107,26 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool) { TLSClientConfig: tlsConf, }, } - suite.Require().NoError(err, "no error loading tls config") + suite.Nil(err, "no error loading tls config") opts = append(opts, ClientOptHTTPClient(httpClient)) } else { opts = append(opts, ClientOptPlainHTTP()) } suite.RegistryClient, err = NewClient(opts...) - suite.Require().NoError(err, "no error creating registry client") + suite.Nil(err, "no error creating registry client") // create htpasswd file (w BCrypt, which is required) pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) - suite.Require().NoError(err, "no error generating bcrypt password for test htpasswd file") + suite.Nil(err, "no error generating bcrypt password for test htpasswd file") htpasswdPath := filepath.Join(suite.WorkspaceDir, testHtpasswdFileBasename) err = os.WriteFile(htpasswdPath, fmt.Appendf(nil, "%s:%s\n", testUsername, string(pwBytes)), 0644) - suite.Require().NoError(err, "no error creating test htpasswd file") + suite.Nil(err, "no error creating test htpasswd file") // Registry config config := &configuration.Configuration{} ln, err := net.Listen("tcp", "127.0.0.1:0") - suite.Require().NoError(err, "no error finding free port for test registry") + suite.Nil(err, "no error finding free port for test registry") defer func() { _ = ln.Close() }() // Change the registry host to another host which is not localhost. @@ -159,7 +159,7 @@ func setup(suite *TestRegistry, tlsEnabled, insecure bool) { } } suite.dockerRegistry, err = registry.NewRegistry(context.Background(), config) - suite.Require().NoError(err, "no error creating test registry") + suite.Nil(err, "no error creating test registry") suite.FakeRegistryHost = initFakeRegistryTestServer() suite.CompromisedRegistryHost = initCompromisedRegistryTestServer() @@ -380,66 +380,67 @@ func initFakeRegistryTestServer() string { } func testPush(suite *TestRegistry) { + testingChartCreationTime := "1977-09-02T22:04:05Z" // Bad bytes ref := suite.DockerRegistryHost + "/testrepo/testchart:1.2.3" _, err := suite.RegistryClient.Push([]byte("hello"), ref, PushOptCreationTime(testingChartCreationTime)) - suite.Require().Error(err, "error pushing non-chart bytes") + suite.NotNil(err, "error pushing non-chart bytes") // Load a test chart chartData, err := os.ReadFile("../repo/v1/repotest/testdata/examplechart-0.1.0.tgz") - suite.Require().NoError(err, "no error loading test chart") + suite.Nil(err, "no error loading test chart") meta, err := extractChartMeta(chartData) - suite.Require().NoError(err, "no error extracting chart meta") + suite.Nil(err, "no error extracting chart meta") // non-strict ref (chart name) ref = fmt.Sprintf("%s/testrepo/boop:%s", suite.DockerRegistryHost, meta.Version) _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) - suite.Require().Error(err, "error pushing non-strict ref (bad basename)") + suite.NotNil(err, "error pushing non-strict ref (bad basename)") // non-strict ref (chart name), with strict mode disabled _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false), PushOptCreationTime(testingChartCreationTime)) - suite.Require().NoError(err, "no error pushing non-strict ref (bad basename), with strict mode disabled") + suite.Nil(err, "no error pushing non-strict ref (bad basename), with strict mode disabled") // non-strict ref (chart version) ref = fmt.Sprintf("%s/testrepo/%s:latest", suite.DockerRegistryHost, meta.Name) _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) - suite.Require().Error(err, "error pushing non-strict ref (bad tag)") + suite.NotNil(err, "error pushing non-strict ref (bad tag)") // non-strict ref (chart version), with strict mode disabled _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false), PushOptCreationTime(testingChartCreationTime)) - suite.Require().NoError(err, "no error pushing non-strict ref (bad tag), with strict mode disabled") + suite.Nil(err, "no error pushing non-strict ref (bad tag), with strict mode disabled") // basic push, good ref chartData, err = os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Require().NoError(err, "no error loading test chart") + suite.Nil(err, "no error loading test chart") meta, err = extractChartMeta(chartData) - suite.Require().NoError(err, "no error extracting chart meta") + suite.Nil(err, "no error extracting chart meta") ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) - suite.Require().NoError(err, "no error pushing good ref") + suite.Nil(err, "no error pushing good ref") _, err = suite.RegistryClient.Pull(ref) - suite.Require().NoError(err, "no error pulling a simple chart") + suite.Nil(err, "no error pulling a simple chart") // Load another test chart chartData, err = os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz") - suite.Require().NoError(err, "no error loading test chart") + suite.Nil(err, "no error loading test chart") meta, err = extractChartMeta(chartData) - suite.Require().NoError(err, "no error extracting chart meta") + suite.Nil(err, "no error extracting chart meta") // Load prov file provData, err := os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov") - suite.Require().NoError(err, "no error loading test prov") + suite.Nil(err, "no error loading test prov") // push with prov ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData), PushOptCreationTime(testingChartCreationTime)) - suite.Require().NoError(err, "no error pushing good ref with prov") + suite.Nil(err, "no error pushing good ref with prov") _, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true)) - suite.Require().NoError(err, "no error pulling a simple chart") + suite.Nil(err, "no error pulling a simple chart") // Validate the output // Note: these digests/sizes etc may change if the test chart/prov files are modified, @@ -469,50 +470,50 @@ func testPull(suite *TestRegistry) { // bad/missing ref ref := suite.DockerRegistryHost + "/testrepo/no-existy:1.2.3" _, err := suite.RegistryClient.Pull(ref) - suite.Require().Error(err, "error on bad/missing ref") + suite.NotNil(err, "error on bad/missing ref") // Load test chart (to build ref pushed in previous test) chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Require().NoError(err, "no error loading test chart") + suite.Nil(err, "no error loading test chart") meta, err := extractChartMeta(chartData) - suite.Require().NoError(err, "no error extracting chart meta") + suite.Nil(err, "no error extracting chart meta") ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) // Simple pull, chart only _, err = suite.RegistryClient.Pull(ref) - suite.Require().NoError(err, "no error pulling a simple chart") + suite.Nil(err, "no error pulling a simple chart") // Simple pull with prov (no prov uploaded) _, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true)) - suite.Require().Error(err, "error pulling a chart with prov when no prov exists") + suite.NotNil(err, "error pulling a chart with prov when no prov exists") // Simple pull with prov, ignoring missing prov _, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true), PullOptIgnoreMissingProv(true)) - suite.Require().NoError(err, + suite.Nil(err, "no error pulling a chart with prov when no prov exists, ignoring missing") // Load test chart (to build ref pushed in previous test) chartData, err = os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz") - suite.Require().NoError(err, "no error loading test chart") + suite.Nil(err, "no error loading test chart") meta, err = extractChartMeta(chartData) - suite.Require().NoError(err, "no error extracting chart meta") + suite.Nil(err, "no error extracting chart meta") ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) // Load prov file provData, err := os.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov") - suite.Require().NoError(err, "no error loading test prov") + suite.Nil(err, "no error loading test prov") // no chart and no prov causes error _, err = suite.RegistryClient.Pull(ref, PullOptWithChart(false), PullOptWithProv(false)) - suite.Require().Error(err, "error on both no chart and no prov") + suite.NotNil(err, "error on both no chart and no prov") // full pull with chart and prov result, err := suite.RegistryClient.Pull(ref, PullOptWithProv(true)) - suite.Require().NoError(err, "no error pulling a chart with prov") + suite.Require().Nil(err, "no error pulling a chart with prov") // Validate the output // Note: these digests/sizes etc may change if the test chart/prov files are modified, @@ -547,13 +548,13 @@ func testPull(suite *TestRegistry) { func testTags(suite *TestRegistry) { // Load test chart (to build ref pushed in previous test) chartData, err := os.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Require().NoError(err, "no error loading test chart") + suite.Nil(err, "no error loading test chart") meta, err := extractChartMeta(chartData) - suite.Require().NoError(err, "no error extracting chart meta") + suite.Nil(err, "no error extracting chart meta") ref := fmt.Sprintf("%s/testrepo/%s", suite.DockerRegistryHost, meta.Name) // Query for tags and validate length tags, err := suite.RegistryClient.Tags(ref) - suite.Require().NoError(err, "no error retrieving tags") + suite.Nil(err, "no error retrieving tags") suite.Equal(1, len(tags)) } diff --git a/pkg/release/v1/util/manifest_sorter_test.go b/pkg/release/v1/util/manifest_sorter_test.go index baa320be0..4360013e5 100644 --- a/pkg/release/v1/util/manifest_sorter_test.go +++ b/pkg/release/v1/util/manifest_sorter_test.go @@ -26,6 +26,7 @@ import ( ) func TestSortManifests(t *testing.T) { + data := []struct { name []string path string @@ -182,6 +183,7 @@ metadata: if !reflect.DeepEqual(expectedHooks, out.Events) { t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events) } + } } if !found { diff --git a/pkg/repo/v1/chartrepo.go b/pkg/repo/v1/chartrepo.go index 09b74d602..deef7474e 100644 --- a/pkg/repo/v1/chartrepo.go +++ b/pkg/repo/v1/chartrepo.go @@ -173,6 +173,7 @@ func WithInsecureSkipTLSVerify(insecureSkipTLSVerify bool) FindChartInRepoURLOpt // FindChartInRepoURL finds chart in chart repository pointed by repoURL // without adding repo to repositories func FindChartInRepoURL(repoURL string, chartName string, getters getter.Providers, options ...FindChartInRepoURLOption) (string, error) { + opts := findChartInRepoURLOptions{} for _, option := range options { option(&opts) diff --git a/pkg/repo/v1/chartrepo_test.go b/pkg/repo/v1/chartrepo_test.go index 9c992474b..a0a8dcfc4 100644 --- a/pkg/repo/v1/chartrepo_test.go +++ b/pkg/repo/v1/chartrepo_test.go @@ -126,6 +126,7 @@ func TestConcurrencyDownloadIndex(t *testing.T) { // 2) read index.yaml via LoadIndexFile (read operation). // This checks for race conditions and ensures correct behavior under concurrent read/write access. for range 150 { + wg.Go(func() { idx, err := repo.DownloadIndexFile() if err != nil { @@ -233,6 +234,7 @@ func TestFindChartInRepoURL(t *testing.T) { } func TestErrorFindChartInRepoURL(t *testing.T) { + g := getter.All(&cli.EnvSettings{ RepositoryCache: t.TempDir(), }) diff --git a/pkg/repo/v1/index.go b/pkg/repo/v1/index.go index 57e4c081f..ba747d702 100644 --- a/pkg/repo/v1/index.go +++ b/pkg/repo/v1/index.go @@ -176,19 +176,6 @@ func (i IndexFile) SortEntries() { } } -// isVersionRange checks if the version string is a range constraint (e.g., "^1", "~1.10") -// rather than an exact version (e.g., "1.10.0"). -func isVersionRange(version string) bool { - if strings.ContainsAny(version, "^~<>=!*") || strings.Contains(version, "||") || strings.Contains(version, " - ") { - return true - } - core := version - if idx := strings.IndexAny(version, "-+"); idx != -1 { - core = version[:idx] - } - return strings.ContainsAny(core, "xX") -} - // Get returns the ChartVersion for the given name. // // If version is empty, this will return the chart with the latest stable version, @@ -229,10 +216,8 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) { } if constraint.Check(test) { - if len(version) != 0 && !isVersionRange(version) { + if len(version) != 0 { slog.Warn("unable to find exact version requested; falling back to closest available version", "chart", name, "requested", version, "selected", ver.Version) - } else if len(version) != 0 && isVersionRange(version) { - slog.Debug("selected version matching constraint", "chart", name, "constraint", version, "selected", ver.Version) } return ver, nil } diff --git a/pkg/repo/v1/index_test.go b/pkg/repo/v1/index_test.go index 9259e5a4d..550c8e82c 100644 --- a/pkg/repo/v1/index_test.go +++ b/pkg/repo/v1/index_test.go @@ -140,6 +140,7 @@ func TestIndexFile(t *testing.T) { } func TestLoadIndex(t *testing.T) { + tests := []struct { Name string Filename string @@ -249,6 +250,7 @@ func TestMerge(t *testing.T) { if v := vs[1]; v.Version != "0.2.0" { t.Errorf("Expected %q version to be 0.2.0, got %s", v.Name, v.Version) } + } func TestDownloadIndexFile(t *testing.T) { @@ -640,6 +642,7 @@ func TestIgnoreSkippableChartValidationError(t *testing.T) { if !errors.Is(tc.Input, result) { t.Error("expected the result equal to input") } + }) } } @@ -715,45 +718,3 @@ func TestLoadIndex_DuplicateChartDeps(t *testing.T) { }) } } - -func TestIsVersionRange(t *testing.T) { - tests := []struct { - version string - expected bool - }{ - {"1.0.0", false}, - {"1.0.0+metadata", false}, - {"v1.19.2", false}, - {"v1", false}, - {"^1", true}, - {"^1.2.3", true}, - {"~1.10", true}, - {"~1.10.0", true}, - {">= 1.0.0", true}, - {"> 1.0.0", true}, - {"< 2.0.0", true}, - {"<= 2.0.0", true}, - {"!= 1.0.0", true}, - {"1.*", true}, - {"1.x", true}, - {"1.X", true}, - {"v1.x", true}, - {"v1.X", true}, - {"1.0.0 - 2.0.0", true}, - {"^1.0.0 || ^2.0.0", true}, - {">=1.0.0 <2.0.0", true}, - // Exact versions with 'x'/'X' in prerelease or build metadata - {"1.0.0-fix", false}, - {"2.0.0-next", false}, - {"1.0.0+exp", false}, - } - - for _, tt := range tests { - t.Run(tt.version, func(t *testing.T) { - got := isVersionRange(tt.version) - if got != tt.expected { - t.Errorf("isVersionRange(%q) = %v, want %v", tt.version, got, tt.expected) - } - }) - } -} diff --git a/pkg/repo/v1/repotest/server_test.go b/pkg/repo/v1/repotest/server_test.go index 499091a57..f0e374fc0 100644 --- a/pkg/repo/v1/repotest/server_test.go +++ b/pkg/repo/v1/repotest/server_test.go @@ -143,6 +143,7 @@ func TestNewTempServer(t *testing.T) { if res.StatusCode != http.StatusOK { t.Errorf("Expected 200, got %d", res.StatusCode) } + } { @@ -202,6 +203,7 @@ func TestNewTempServer(t *testing.T) { } }) } + } func TestNewTempServer_TLS(t *testing.T) { diff --git a/pkg/repo/v1/repotest/tlsconfig.go b/pkg/repo/v1/repotest/tlsconfig.go index d579f8054..3ea7338ff 100644 --- a/pkg/repo/v1/repotest/tlsconfig.go +++ b/pkg/repo/v1/repotest/tlsconfig.go @@ -35,7 +35,8 @@ func MakeTestTLSConfig(t *testing.T, path string) *tls.Config { tlsutil.WithCertKeyPairFiles(pub, priv), tlsutil.WithCAFile(ca), ) - require.NoError(t, err) + //require.Nil(t, err, err.Error()) + require.Nil(t, err) tlsConf.ServerName = "helm.sh" diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go index 95f85faa6..c6401b425 100644 --- a/pkg/storage/driver/memory_test.go +++ b/pkg/storage/driver/memory_test.go @@ -300,4 +300,5 @@ func TestMemoryDelete(t *testing.T) { t.Logf("Name: %s, Version: %d", rac.Name(), rac.Version()) } } + } diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index f3dd053a2..21d9f6679 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -134,6 +134,7 @@ func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool { } func (s *SQL) ensureDBSetup() error { + migrations := &migrate.MemoryMigrationSource{ Migrations: []*migrate.Migration{ { @@ -484,7 +485,7 @@ func (s *SQL) Create(key string, rel release.Releaser) error { transaction, err := s.db.Beginx() if err != nil { s.Logger().Debug("failed to start SQL transaction", slog.Any("error", err)) - return fmt.Errorf("error beginning transaction: %w", err) + return fmt.Errorf("error beginning transaction: %v", err) } insertQuery, args, err := s.statementBuilder. @@ -622,7 +623,7 @@ func (s *SQL) Delete(key string) (release.Releaser, error) { transaction, err := s.db.Beginx() if err != nil { s.Logger().Debug("failed to start SQL transaction", slog.Any("error", err)) - return nil, fmt.Errorf("error beginning transaction: %w", err) + return nil, fmt.Errorf("error beginning transaction: %v", err) } selectQuery, args, err := s.statementBuilder. diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go index 1dbbd0e1b..cecaa2453 100644 --- a/pkg/strvals/parser.go +++ b/pkg/strvals/parser.go @@ -330,6 +330,7 @@ func (t *parser) keyIndex() (int, error) { } // v should be the index return strconv.Atoi(string(v)) + } func (t *parser) listItem(list []any, i, nestedNameLevel int) ([]any, error) {