Merge branch 'main' into fix/gnupg-keybox-keyring

pull/32281/head
George Jenkins 1 month ago committed by GitHub
commit 6455b38c9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -48,7 +48,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3 uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # pinv4.37.7
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # 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). # 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) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3 uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # pinv4.37.7
# ℹ️ Command-line programs to run using the OS shell. # ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -73,4 +73,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # pinv4.37.3 uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # pinv4.37.7

@ -64,6 +64,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard (optional). # Upload the results to GitHub's code scanning dashboard (optional).
# Commenting out will disable upload of results to your repo's Code Scanning dashboard # Commenting out will disable upload of results to your repo's Code Scanning dashboard
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3 uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with: with:
sarif_file: results.sarif sarif_file: results.sarif

@ -12,7 +12,7 @@ jobs:
issues: write issues: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0 - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.' stale-issue-message: 'This issue has been marked as stale because it has been open for 90 days with no activity. This thread will be automatically closed in 30 days if no further activity occurs.'

@ -1,6 +1,7 @@
formatters: formatters:
enable: enable:
- gofmt - gofmt
- gofumpt
- goimports - goimports
exclusions: exclusions:
@ -10,6 +11,10 @@ formatters:
gofmt: gofmt:
simplify: true simplify: true
gofumpt:
module-path: helm.sh/helm/v4
extra-rules: false
goimports: goimports:
local-prefixes: local-prefixes:
- helm.sh/helm/v4 - helm.sh/helm/v4

@ -29,15 +29,15 @@ require (
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.1
github.com/rubenv/sql-migrate v1.8.1 github.com/rubenv/sql-migrate v1.8.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/santhosh-tekuri/jsonschema/v6 v6.0.3
github.com/spf13/cobra v1.10.2 github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10 github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.12.1
github.com/tetratelabs/wazero v1.12.0 github.com/tetratelabs/wazero v1.12.0
go.yaml.in/yaml/v3 v3.0.4 go.yaml.in/yaml/v3 v3.0.5
golang.org/x/crypto v0.54.0 golang.org/x/crypto v0.55.0
golang.org/x/term v0.45.0 golang.org/x/term v0.45.0
golang.org/x/text v0.40.0 golang.org/x/text v0.41.0
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.36.3 k8s.io/api v0.36.3
k8s.io/apiextensions-apiserver v0.36.3 k8s.io/apiextensions-apiserver v0.36.3
@ -138,7 +138,7 @@ require (
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect
@ -151,20 +151,20 @@ require (
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect
go.opentelemetry.io/otel/log v0.19.0 // indirect go.opentelemetry.io/otel/log v0.19.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/sdk v1.44.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/mod v0.37.0 // indirect golang.org/x/mod v0.38.0 // indirect
golang.org/x/net v0.56.0 // indirect golang.org/x/net v0.57.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.22.0 // indirect golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect golang.org/x/sys v0.47.0 // indirect
golang.org/x/time v0.15.0 // indirect golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.47.0 // indirect golang.org/x/tools v0.48.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/grpc v1.82.1 // indirect google.golang.org/grpc v1.82.1 // indirect

@ -282,8 +282,8 @@ github.com/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy
github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8= github.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
@ -301,14 +301,14 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q= 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/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 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
@ -328,8 +328,8 @@ go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9a
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag=
@ -354,18 +354,20 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1J
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs=
go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4=
go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko=
go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg=
go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk=
go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -376,22 +378,23 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 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.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.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.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-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= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -402,8 +405,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.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 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.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= 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/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -450,8 +453,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.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.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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -460,8 +463,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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=

@ -30,14 +30,16 @@ import (
const namespace = "testNamespace" const namespace = "testNamespace"
const badChartDir = "rules/testdata/badchartfile" const (
const badValuesFileDir = "rules/testdata/badvaluesfile" badChartDir = "rules/testdata/badchartfile"
const badYamlFileDir = "rules/testdata/albatross" badValuesFileDir = "rules/testdata/badvaluesfile"
const badCrdFileDir = "rules/testdata/badcrdfile" badYamlFileDir = "rules/testdata/albatross"
const goodChartDir = "rules/testdata/goodone" badCrdFileDir = "rules/testdata/badcrdfile"
const subChartValuesDir = "rules/testdata/withsubchart" goodChartDir = "rules/testdata/goodone"
const malformedTemplate = "rules/testdata/malformed-template" subChartValuesDir = "rules/testdata/withsubchart"
const invalidChartFileDir = "rules/testdata/invalidchartfile" malformedTemplate = "rules/testdata/malformed-template"
invalidChartFileDir = "rules/testdata/invalidchartfile"
)
func TestBadChartV3(t *testing.T) { func TestBadChartV3(t *testing.T) {
var values map[string]any var values map[string]any

@ -44,8 +44,10 @@ var (
nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml")
) )
var badChart, _ = chartutil.LoadChartfile(badChartFilePath) var (
var badChartName, _ = chartutil.LoadChartfile(badChartNamePath) badChart, _ = chartutil.LoadChartfile(badChartFilePath)
badChartName, _ = chartutil.LoadChartfile(badChartNamePath)
)
// Validation functions Test // Validation functions Test
func TestValidateChartYamlNotDirectory(t *testing.T) { func TestValidateChartYamlNotDirectory(t *testing.T) {
@ -68,7 +70,7 @@ func TestValidateChartName(t *testing.T) {
} }
func TestValidateChartVersion(t *testing.T) { func TestValidateChartVersion(t *testing.T) {
var failTest = []struct { failTest := []struct {
Version string Version string
ErrorMsg string ErrorMsg string
}{ }{
@ -80,7 +82,7 @@ func TestValidateChartVersion(t *testing.T) {
{"1", "'1' is not a valid SemVerV2"}, {"1", "'1' is not a valid SemVerV2"},
} }
var successTest = []string{"0.0.1", "0.0.1+build", "0.0.1-beta"} successTest := []string{"0.0.1", "0.0.1+build", "0.0.1-beta"}
for i, test := range failTest { for i, test := range failTest {
t.Run(strconv.Itoa(i), func(t *testing.T) { t.Run(strconv.Itoa(i), func(t *testing.T) {
@ -96,7 +98,7 @@ func TestValidateChartVersion(t *testing.T) {
} }
func TestValidateChartMaintainer(t *testing.T) { func TestValidateChartMaintainer(t *testing.T) {
var failTest = []struct { failTest := []struct {
Name string Name string
Email string Email string
ErrorMsg string ErrorMsg string
@ -106,7 +108,7 @@ func TestValidateChartMaintainer(t *testing.T) {
{"John Snow", "wrongFormatEmail.com", "invalid email"}, {"John Snow", "wrongFormatEmail.com", "invalid email"},
} }
var successTest = []struct { successTest := []struct {
Name string Name string
Email string Email string
}{ }{
@ -134,8 +136,8 @@ func TestValidateChartMaintainer(t *testing.T) {
} }
func TestValidateChartSources(t *testing.T) { func TestValidateChartSources(t *testing.T) {
var failTest = []string{"", "RiverRun", "john@winterfell", "riverrun.io"} failTest := []string{"", "RiverRun", "john@winterfell", "riverrun.io"}
var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"} successTest := []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"}
for _, test := range failTest { for _, test := range failTest {
t.Run(test, func(t *testing.T) { t.Run(test, func(t *testing.T) {
badChart.Sources = []string{test} badChart.Sources = []string{test}
@ -166,8 +168,8 @@ func TestValidateChartIconPresence(t *testing.T) {
} }
func TestValidateChartIconURL(t *testing.T) { func TestValidateChartIconURL(t *testing.T) {
var failTest = []string{"RiverRun", "john@winterfell", "riverrun.io"} failTest := []string{"RiverRun", "john@winterfell", "riverrun.io"}
var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"} successTest := []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"}
for _, test := range failTest { for _, test := range failTest {
t.Run(test, func(t *testing.T) { t.Run(test, func(t *testing.T) {
badChart.Icon = test badChart.Icon = test

@ -35,11 +35,11 @@ import (
const templateTestBasedir = "./testdata/albatross" const templateTestBasedir = "./testdata/albatross"
func TestValidateAllowedExtension(t *testing.T) { func TestValidateAllowedExtension(t *testing.T) {
var failTest = []string{"/foo", "/test.toml"} failTest := []string{"/foo", "/test.toml"}
for _, test := range failTest { for _, test := range failTest {
require.ErrorContains(t, validateAllowedExtension(test), "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) require.ErrorContains(t, validateAllowedExtension(test), "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test)
} }
var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} successTest := []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"}
for _, test := range successTest { for _, test := range successTest {
assert.NoError(t, validateAllowedExtension(test), "validateAllowedExtension('%s') to return no error", test) assert.NoError(t, validateAllowedExtension(test), "validateAllowedExtension('%s') to return no error", test)
} }
@ -47,8 +47,10 @@ func TestValidateAllowedExtension(t *testing.T) {
var values = map[string]any{"nameOverride": "", "httpPort": 80} var values = map[string]any{"nameOverride": "", "httpPort": 80}
const namespace = "testNamespace" const (
const strict = false namespace = "testNamespace"
strict = false
)
func TestTemplateParsing(t *testing.T) { func TestTemplateParsing(t *testing.T) {
linter := support.Linter{ChartDir: templateTestBasedir} linter := support.Linter{ChartDir: templateTestBasedir}
@ -59,8 +61,10 @@ func TestTemplateParsing(t *testing.T) {
assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError") assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError")
} }
var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") var (
var ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored") wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml")
ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored")
)
// Test a template with all the existing features: // Test a template with all the existing features:
// namespaces, partial templates // namespaces, partial templates
@ -382,6 +386,7 @@ func TestEmptyWithCommentsManifests(t *testing.T) {
} }
require.Empty(t, linter.Messages, "Expected 0 lint errors") require.Empty(t, linter.Messages, "Expected 0 lint errors")
} }
func TestValidateListAnnotations(t *testing.T) { func TestValidateListAnnotations(t *testing.T) {
md := &k8sYamlStruct{ md := &k8sYamlStruct{
APIVersion: "v1", APIVersion: "v1",

@ -27,7 +27,7 @@ import (
var errLint = errors.New("lint failed") var errLint = errors.New("lint failed")
func TestRunLinterRule(t *testing.T) { func TestRunLinterRule(t *testing.T) {
var tests = []struct { tests := []struct {
Severity int Severity int
LintError error LintError error
ExpectedMessages int ExpectedMessages int

@ -183,8 +183,20 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) {
// The reader is expected to contain one or more YAML documents, the values of which are merged. // The reader is expected to contain one or more YAML documents, the values of which are merged.
// And the values can be either a chart's default values or user-supplied values. // And the values can be either a chart's default values or user-supplied values.
func LoadValues(data io.Reader) (map[string]any, error) { func LoadValues(data io.Reader) (map[string]any, error) {
// Read fully first. YAMLReader/LineReader can drop a final unterminated
// line when its length is an exact multiple of bufio.Reader's default
// buffer (4096). Appending a trailing newline avoids that case.
// See https://github.com/helm/helm/issues/32506
b, err := io.ReadAll(data)
if err != nil {
return nil, err
}
if len(b) > 0 && b[len(b)-1] != '\n' {
b = append(b, '\n')
}
values := map[string]any{} values := map[string]any{}
reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) reader := utilyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(b)))
for { for {
currentMap := map[string]any{} currentMap := map[string]any{}
raw, err := reader.Read() raw, err := reader.Read()

@ -21,6 +21,7 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"errors" "errors"
"fmt"
"io" "io"
"log" "log"
"os" "os"
@ -419,6 +420,33 @@ foo:
} }
} }
func TestLoadValuesEOFBoundary(t *testing.T) {
// Reproduces #32506: a single logical line whose length is a multiple of
// bufio's default buffer (4096) and has no trailing newline used to be
// dropped entirely by YAMLReader, yielding empty values.
// Also cover 8192 (2x buffer) so we do not only hit the single-buffer case.
for _, size := range []int{4096, 8192} {
t.Run(fmt.Sprintf("size_%d", size), func(t *testing.T) {
prefix := []byte(`{"foo":"`)
suffix := []byte(`"}`)
pad := size - len(prefix) - len(suffix)
data := make([]byte, 0, size)
data = append(data, prefix...)
data = append(data, bytes.Repeat([]byte("x"), pad)...)
data = append(data, suffix...)
if len(data) != size {
t.Fatalf("test setup: want data length %d, got %d", size, len(data))
}
values, err := LoadValues(bytes.NewReader(data))
require.NoError(t, err)
assert.Equal(t, map[string]any{
"foo": string(bytes.Repeat([]byte("x"), pad)),
}, values)
})
}
}
func TestMergeValuesV3(t *testing.T) { func TestMergeValuesV3(t *testing.T) {
nestedMap := map[string]any{ nestedMap := map[string]any{
"foo": "bar", "foo": "bar",

@ -26,7 +26,6 @@ var ErrHostnameNotProvided = errors.New("no hostname provided")
// Client represents a client capable of communicating with the Monocular API. // Client represents a client capable of communicating with the Monocular API.
type Client struct { type Client struct {
// The base URL for requests // The base URL for requests
BaseURL string BaseURL string
} }

@ -173,7 +173,7 @@ func TestExtract(t *testing.T) {
// Write a tarball to a buffer for us to extract // Write a tarball to a buffer for us to extract
var tarbuf bytes.Buffer var tarbuf bytes.Buffer
tw := tar.NewWriter(&tarbuf) tw := tar.NewWriter(&tarbuf)
var files = []struct { files := []struct {
Name, Body string Name, Body string
Mode int64 Mode int64
}{ }{
@ -293,7 +293,7 @@ func TestExtractWithNestedDirectories(t *testing.T) {
// Write a tarball with nested directory structure // Write a tarball with nested directory structure
var tarbuf bytes.Buffer var tarbuf bytes.Buffer
tw := tar.NewWriter(&tarbuf) tw := tar.NewWriter(&tarbuf)
var files = []struct { files := []struct {
Name string Name string
Body string Body string
Mode int64 Mode int64

@ -105,6 +105,7 @@ func TestVCSInstallerNonExistentVersion(t *testing.T) {
} }
require.EqualErrorf(t, err, fmt.Sprintf("requested version %q does not exist for plugin %q", version, source), "expected error for version does not exists") require.EqualErrorf(t, err, fmt.Sprintf("requested version %q does not exist for plugin %q", version, source), "expected error for version does not exists")
} }
func TestVCSInstallerUpdate(t *testing.T) { func TestVCSInstallerUpdate(t *testing.T) {
ensure.HelmHome(t) ensure.HelmHome(t)

@ -216,7 +216,6 @@ func findPlugins(pluginsDirs []string, findFn findFunc, filterFn filterFunc) ([]
var found []Plugin var found []Plugin
for _, pluginsDir := range pluginsDirs { for _, pluginsDir := range pluginsDirs {
ps, err := findFn(pluginsDir) ps, err := findFn(pluginsDir)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -154,8 +154,7 @@ func buildLegacyConfig(m MetadataLegacy, pluginType string) Config {
func buildLegacyRuntimeConfig(m MetadataLegacy) RuntimeConfig { func buildLegacyRuntimeConfig(m MetadataLegacy) RuntimeConfig {
var protocolCommands []SubprocessProtocolCommand var protocolCommands []SubprocessProtocolCommand
if len(m.Downloaders) > 0 { if len(m.Downloaders) > 0 {
protocolCommands = protocolCommands = make([]SubprocessProtocolCommand, 0, len(m.Downloaders))
make([]SubprocessProtocolCommand, 0, len(m.Downloaders))
for _, d := range m.Downloaders { for _, d := range m.Downloaders {
protocolCommands = append(protocolCommands, SubprocessProtocolCommand{ protocolCommands = append(protocolCommands, SubprocessProtocolCommand{
Protocols: d.Protocols, Protocols: d.Protocols,

@ -26,8 +26,10 @@ import (
"helm.sh/helm/v4/pkg/provenance" "helm.sh/helm/v4/pkg/provenance"
) )
const testKeyFile = "../../pkg/cmd/testdata/helm-test-key.secret" const (
const testPubFile = "../../pkg/cmd/testdata/helm-test-key.pub" testKeyFile = "../../pkg/cmd/testdata/helm-test-key.secret"
testPubFile = "../../pkg/cmd/testdata/helm-test-key.pub"
)
const testPluginYAML = `apiVersion: v1 const testPluginYAML = `apiVersion: v1
name: test-plugin name: test-plugin

@ -22,8 +22,10 @@ import (
type ApplyMethod string type ApplyMethod string
const ApplyMethodClientSideApply ApplyMethod = "csa" const (
const ApplyMethodServerSideApply ApplyMethod = "ssa" ApplyMethodClientSideApply ApplyMethod = "csa"
ApplyMethodServerSideApply ApplyMethod = "ssa"
)
// Release describes a deployment of a chart, together with the chart // Release describes a deployment of a chart, together with the chart
// and the variables used to deploy that chart. // and the variables used to deploy that chart.

@ -61,7 +61,8 @@ metadata:
annotations: annotations:
"helm.sh/hook": post-install "helm.sh/hook": post-install
`, `,
}, { },
{
name: []string{"third"}, name: []string{"third"},
path: "three", path: "three",
kind: []string{"ReplicaSet"}, kind: []string{"ReplicaSet"},
@ -73,7 +74,8 @@ metadata:
annotations: annotations:
"helm.sh/hook": no-such-hook "helm.sh/hook": no-such-hook
`, `,
}, { },
{
name: []string{"fourth"}, name: []string{"fourth"},
path: "four", path: "four",
kind: []string{"Pod"}, kind: []string{"Pod"},
@ -84,7 +86,8 @@ metadata:
name: fourth name: fourth
annotations: annotations:
nothing: here`, nothing: here`,
}, { },
{
name: []string{"fifth"}, name: []string{"fifth"},
path: "five", path: "five",
kind: []string{"ReplicaSet"}, kind: []string{"ReplicaSet"},
@ -96,14 +99,16 @@ metadata:
annotations: annotations:
"helm.sh/hook": post-delete, post-install "helm.sh/hook": post-delete, post-install
`, `,
}, { },
{
// Regression test: files with an underscore in the base name should be skipped. // Regression test: files with an underscore in the base name should be skipped.
name: []string{"sixth"}, name: []string{"sixth"},
path: "six/_six", path: "six/_six",
kind: []string{"ReplicaSet"}, kind: []string{"ReplicaSet"},
hooks: map[string][]release.HookEvent{"sixth": nil}, hooks: map[string][]release.HookEvent{"sixth": nil},
manifest: `invalid manifest`, // This will fail if partial is not skipped. manifest: `invalid manifest`, // This will fail if partial is not skipped.
}, { },
{
// Regression test: files with no content should be skipped. // Regression test: files with no content should be skipped.
name: []string{"seventh"}, name: []string{"seventh"},
path: "seven", path: "seven",

@ -126,7 +126,7 @@ func TestCopyDirFail_SrcInaccessible(t *testing.T) {
t.Skip("skipping on windows") t.Skip("skipping on windows")
} }
var currentUID = os.Getuid() currentUID := os.Getuid()
if currentUID == 0 { if currentUID == 0 {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible
@ -155,7 +155,7 @@ func TestCopyDirFail_DstInaccessible(t *testing.T) {
t.Skip("skipping on windows") t.Skip("skipping on windows")
} }
var currentUID = os.Getuid() currentUID := os.Getuid()
if currentUID == 0 { if currentUID == 0 {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible
@ -217,7 +217,7 @@ func TestCopyDirFailOpen(t *testing.T) {
t.Skip("skipping on windows") t.Skip("skipping on windows")
} }
var currentUID = os.Getuid() currentUID := os.Getuid()
if currentUID == 0 { if currentUID == 0 {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible
@ -319,7 +319,7 @@ func TestCopyFileFail(t *testing.T) {
t.Skip("skipping on windows") t.Skip("skipping on windows")
} }
var currentUID = os.Getuid() currentUID := os.Getuid()
if currentUID == 0 { if currentUID == 0 {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible
@ -389,7 +389,7 @@ func setupInaccessibleDir(t *testing.T, op func(dir string) error) func() {
} }
func TestIsDir(t *testing.T) { func TestIsDir(t *testing.T) {
var currentUID = os.Getuid() currentUID := os.Getuid()
if currentUID == 0 { if currentUID == 0 {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible
@ -441,7 +441,7 @@ func TestIsDir(t *testing.T) {
} }
func TestIsSymlink(t *testing.T) { func TestIsSymlink(t *testing.T) {
var currentUID = os.Getuid() currentUID := os.Getuid()
if currentUID == 0 { if currentUID == 0 {
// Skipping if root, because all files are accessible // Skipping if root, because all files are accessible

@ -19,10 +19,9 @@ package tlsutil
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"errors"
"fmt" "fmt"
"os" "os"
"errors"
) )
type TLSConfigOptions struct { type TLSConfigOptions struct {

@ -546,7 +546,7 @@ func TestMetadata_FormattedDepNames(t *testing.T) {
} }
func convertDeps(deps []*chart.Dependency) []ci.Dependency { func convertDeps(deps []*chart.Dependency) []ci.Dependency {
var newDeps = make([]ci.Dependency, len(deps)) newDeps := make([]ci.Dependency, len(deps))
for i, c := range deps { for i, c := range deps {
newDeps[i] = c newDeps[i] = c
} }

@ -34,7 +34,8 @@ import (
// execHook executes all of the hooks for the given hook event. // execHook executes all of the hooks for the given hook event.
func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption,
timeout time.Duration, serverSideApply bool) error { timeout time.Duration, serverSideApply bool,
) error {
shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, waitOptions, timeout, serverSideApply) shutdown, err := cfg.execHookWithDelayedShutdown(rl, hook, waitStrategy, waitOptions, timeout, serverSideApply)
if shutdown == nil { if shutdown == nil {
return err return err
@ -57,7 +58,8 @@ func shutdownNoOp() error {
// execHookWithDelayedShutdown executes all of the hooks for the given hook event and returns a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs. // execHookWithDelayedShutdown executes all of the hooks for the given hook event and returns a shutdownHook function to trigger deletions after doing other things like e.g. retrieving logs.
func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent, func (cfg *Configuration) execHookWithDelayedShutdown(rl *release.Release, hook release.HookEvent,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration, waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration,
serverSideApply bool) (ExecuteShutdownFunc, error) { serverSideApply bool,
) (ExecuteShutdownFunc, error) {
executingHooks := []*release.Hook{} executingHooks := []*release.Hook{}
for _, h := range rl.Hooks { for _, h := range rl.Hooks {
@ -176,7 +178,8 @@ func (x hookByWeight) Less(i, j int) bool {
// deleteHookByPolicy deletes a hook if the hook policy instructs it to // deleteHookByPolicy deletes a hook if the hook policy instructs it to
func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy, func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration,
) error {
// Never delete CustomResourceDefinitions; this could cause lots of // Never delete CustomResourceDefinitions; this could cause lots of
// cascading garbage collection. // cascading garbage collection.
if h.Kind == "CustomResourceDefinition" { if h.Kind == "CustomResourceDefinition" {
@ -210,7 +213,8 @@ func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.Hoo
// deleteHooksByPolicy deletes all hooks if the hook policy instructs it to // deleteHooksByPolicy deletes all hooks if the hook policy instructs it to
func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy, func (cfg *Configuration) deleteHooksByPolicy(hooks []*release.Hook, policy release.HookDeletePolicy,
waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration) error { waitStrategy kube.WaitStrategy, waitOptions []kube.WaitOption, timeout time.Duration,
) error {
for _, h := range hooks { for _, h := range hooks {
if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, waitOptions, timeout); err != nil { if err := cfg.deleteHookByPolicy(h, policy, waitStrategy, waitOptions, timeout); err != nil {
return err return err

@ -252,7 +252,6 @@ func (*HookFailingKubeClient) Build(reader io.Reader, _ bool) (kube.ResourceList
configMap := &v1.ConfigMap{} configMap := &v1.ConfigMap{}
err := yaml.NewYAMLOrJSONDecoder(reader, 1000).Decode(configMap) err := yaml.NewYAMLOrJSONDecoder(reader, 1000).Decode(configMap)
if err != nil { if err != nil {
return kube.ResourceList{}, err return kube.ResourceList{}, err
} }
@ -358,10 +357,12 @@ data:
}, },
}, },
}, },
}, resource.Info{ },
resource.Info{
Name: "build-config-2", Name: "build-config-2",
Namespace: "test", Namespace: "test",
}, []resource.Info{ },
[]resource.Info{
{ {
// This should be in the record for `before-hook-creation` // This should be in the record for `before-hook-creation`
Name: "build-config-1", Name: "build-config-1",
@ -382,7 +383,8 @@ data:
Name: "build-config-1", Name: "build-config-1",
Namespace: "test", Namespace: "test",
}, },
}, true, },
true,
}, },
} }

@ -733,7 +733,6 @@ func writeToFile(outputDir string, name string, data string, appendData bool) er
defer f.Close() defer f.Close()
_, err = fmt.Fprintf(f, "---\n# Source: %s\n%s\n", name, data) _, err = fmt.Fprintf(f, "---\n# Source: %s\n%s\n", name, data)
if err != nil { if err != nil {
return err return err
} }

@ -631,6 +631,7 @@ func TestInstallRelease_Wait(t *testing.T) {
is.Equal(goroutines, instAction.getGoroutineCount()) is.Equal(goroutines, instAction.getGoroutineCount())
} }
func TestInstallRelease_Wait_Interrupted(t *testing.T) { func TestInstallRelease_Wait_Interrupted(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t) req := require.New(t)
@ -652,9 +653,12 @@ func TestInstallRelease_Wait_Interrupted(t *testing.T) {
req.ErrorContains(err, "context canceled") req.ErrorContains(err, "context canceled")
is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background
time.Sleep(10 * time.Second) // wait for goroutine to finish // Poll until the background installation goroutine has finished.
is.Equal(goroutines, instAction.getGoroutineCount()) is.Eventually(func() bool {
return instAction.getGoroutineCount() == goroutines
}, 30*time.Second, 10*time.Millisecond)
} }
func TestInstallRelease_WaitForJobs(t *testing.T) { func TestInstallRelease_WaitForJobs(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t) req := require.New(t)
@ -722,6 +726,7 @@ func TestInstallRelease_RollbackOnFailure(t *testing.T) {
is.ErrorContains(err, "an error occurred while uninstalling the release") is.ErrorContains(err, "an error occurred while uninstalling the release")
}) })
} }
func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) { func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t) req := require.New(t)
@ -751,9 +756,12 @@ func TestInstallRelease_RollbackOnFailure_Interrupted(t *testing.T) {
req.Error(err) req.Error(err)
is.Equal(err, driver.ErrReleaseNotFound) is.Equal(err, driver.ErrReleaseNotFound)
is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background is.Equal(goroutines+1, instAction.getGoroutineCount()) // installation goroutine still is in background
time.Sleep(10 * time.Second) // wait for goroutine to finish // Poll until the background installation goroutine has finished.
is.Equal(goroutines, instAction.getGoroutineCount()) is.Eventually(func() bool {
return instAction.getGoroutineCount() == goroutines
}, 30*time.Second, 10*time.Millisecond)
} }
func TestNameTemplate(t *testing.T) { func TestNameTemplate(t *testing.T) {
testCases := []nameTemplateTestCase{ testCases := []nameTemplateTestCase{
// Just a straight up nop please // Just a straight up nop please

@ -172,7 +172,6 @@ func (l *List) Run() ([]ri.Releaser, error) {
return true return true
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -264,7 +263,7 @@ func filterLatestReleases(releases []*release.Release) []*release.Release {
latestReleases[key] = rls latestReleases[key] = rls
} }
var list = make([]*release.Release, 0, len(latestReleases)) list := make([]*release.Release, 0, len(latestReleases))
for _, rls := range latestReleases { for _, rls := range latestReleases {
list = append(list, rls) list = append(list, rls)
} }

@ -17,9 +17,14 @@ limitations under the License.
package action package action
import ( import (
"archive/tar"
"compress/gzip"
"errors"
"io"
"os" "os"
"path" "path"
"testing" "testing"
"time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -144,3 +149,60 @@ func TestRun(t *testing.T) {
require.Equal(t, "empty-0.1.0.tgz", filename) require.Equal(t, "empty-0.1.0.tgz", filename)
require.NoError(t, os.Remove(filename)) require.NoError(t, os.Remove(filename))
} }
// TestRunWithSourceDateEpochStampsLockGenerated verifies that packaging a chart
// that has a Chart.lock stamps both the tar entry modtime and the marshaled
// generated: field in Chart.lock to the given epoch.
//
// This guards against the normalization regression where a caller supplying a
// local-timezone or sub-second time.Time would produce a non-reproducible
// generated: value even when the same SOURCE_DATE_EPOCH is used on different
// machines.
func TestRunWithSourceDateEpochStampsLockGenerated(t *testing.T) {
// Use a non-local, non-UTC timezone and sub-second precision to confirm
// normalization: without UTC().Truncate(time.Second) the generated: field
// would contain a timezone offset or fractional seconds.
loc := time.FixedZone("UTC+3", 3*60*60)
rawEpoch := time.Unix(1700000000, 123456789).In(loc)
epoch := rawEpoch.UTC().Truncate(time.Second)
client := NewPackage()
client.SourceDateEpoch = &rawEpoch
filename, err := client.Run("testdata/charts/chart-with-lock", nil)
require.NoError(t, err)
t.Cleanup(func() { os.Remove(filename) })
f, err := os.Open(filename)
require.NoError(t, err)
defer f.Close()
gr, err := gzip.NewReader(f)
require.NoError(t, err)
defer gr.Close()
const wantPath = "chart-with-lock/Chart.lock"
found := false
tr := tar.NewReader(gr)
for {
hdr, err := tr.Next()
if errors.Is(err, io.EOF) {
break
}
require.NoError(t, err)
if hdr.Name != wantPath {
continue
}
found = true
require.True(t, epoch.Equal(hdr.ModTime),
"Chart.lock tar modtime: got %v, want %v", hdr.ModTime, epoch)
raw, err := io.ReadAll(tr)
require.NoError(t, err)
wantGenerated := epoch.Format(time.RFC3339)
require.Contains(t, string(raw), wantGenerated,
"Chart.lock generated: field should contain normalized UTC timestamp")
break
}
require.True(t, found, "expected archive to contain %q entry", wantPath)
}

@ -107,7 +107,6 @@ func (r *ReleaseTesting) Run(name string) (ri.Releaser, ExecuteShutdownFunc, err
serverSideApply := rel.ApplyMethod == string(release.ApplyMethodServerSideApply) serverSideApply := rel.ApplyMethod == string(release.ApplyMethodServerSideApply)
shutdown, err := r.cfg.execHookWithDelayedShutdown(rel, release.HookTest, kube.StatusWatcherStrategy, r.WaitOptions, r.Timeout, serverSideApply) shutdown, err := r.cfg.execHookWithDelayedShutdown(rel, release.HookTest, kube.StatusWatcherStrategy, r.WaitOptions, r.Timeout, serverSideApply)
if err != nil { if err != nil {
rel.Hooks = append(skippedHooks, rel.Hooks...) rel.Hooks = append(skippedHooks, rel.Hooks...)
r.cfg.Releases.Update(reli) r.cfg.Releases.Update(reli)

@ -21,6 +21,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"time" "time"
"unicode/utf8"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -31,6 +32,10 @@ import (
"helm.sh/helm/v4/pkg/storage/driver" "helm.sh/helm/v4/pkg/storage/driver"
) )
// MaxDescriptionLength is the maximum length allowed for a rollback description,
// including values provided via the --description flag and Rollback.Description.
const MaxDescriptionLength = 256
// Rollback is the action for rolling back to a given release. // Rollback is the action for rolling back to a given release.
// //
// It provides the implementation of 'helm rollback'. // It provides the implementation of 'helm rollback'.
@ -59,6 +64,8 @@ type Rollback struct {
ServerSideApply string ServerSideApply string
CleanupOnFail bool CleanupOnFail bool
MaxHistory int // MaxHistory limits the maximum number of revisions saved per release MaxHistory int // MaxHistory limits the maximum number of revisions saved per release
// Description is the description of this rollback operation
Description string
} }
// NewRollback creates a new Rollback object with the given configuration. // NewRollback creates a new Rollback object with the given configuration.
@ -72,6 +79,10 @@ func NewRollback(cfg *Configuration) *Rollback {
// Run executes 'helm rollback' against the given release. // Run executes 'helm rollback' against the given release.
func (r *Rollback) Run(name string) error { func (r *Rollback) Run(name string) error {
if descLen := utf8.RuneCountInString(r.Description); descLen > MaxDescriptionLength {
return fmt.Errorf("description must be %d characters or less, got %d", MaxDescriptionLength, descLen)
}
if err := r.cfg.KubeClient.IsReachable(); err != nil { if err := r.cfg.KubeClient.IsReachable(); err != nil {
return err return err
} }
@ -169,6 +180,12 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
return nil, nil, false, err return nil, nil, false, err
} }
// Determine the description for this rollback
description := r.Description
if description == "" {
description = fmt.Sprintf("Rollback to %d", previousVersion)
}
// Store a new release object with previous release's configuration // Store a new release object with previous release's configuration
targetRelease := &release.Release{ targetRelease := &release.Release{
Name: name, Name: name,
@ -183,7 +200,7 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
RollbackRevision: previousVersion, RollbackRevision: previousVersion,
// Because we lose the reference to previous version elsewhere, we set the // Because we lose the reference to previous version elsewhere, we set the
// message here, and only override it later if we experience failure. // message here, and only override it later if we experience failure.
Description: fmt.Sprintf("Rollback to %d", previousVersion), Description: description,
}, },
Version: currentRelease.Version + 1, Version: currentRelease.Version + 1,
Labels: previousRelease.Labels, Labels: previousRelease.Labels,
@ -232,7 +249,6 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas
kube.ClientUpdateOptionServerSideApply(serverSideApply, r.ForceConflicts), kube.ClientUpdateOptionServerSideApply(serverSideApply, r.ForceConflicts),
kube.ClientUpdateOptionThreeWayMergeForUnstructured(false), kube.ClientUpdateOptionThreeWayMergeForUnstructured(false),
kube.ClientUpdateOptionUpgradeClientSideFieldManager(true)) kube.ClientUpdateOptionUpgradeClientSideFieldManager(true))
if err != nil { if err != nil {
msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err)
r.cfg.Logger().Warn(msg) r.cfg.Logger().Warn(msg)

@ -20,6 +20,7 @@ import (
"context" "context"
"errors" "errors"
"io" "io"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -27,14 +28,26 @@ import (
"helm.sh/helm/v4/pkg/kube" "helm.sh/helm/v4/pkg/kube"
kubefake "helm.sh/helm/v4/pkg/kube/fake" kubefake "helm.sh/helm/v4/pkg/kube/fake"
"helm.sh/helm/v4/pkg/release/common"
) )
func rollbackAction(t *testing.T) *Rollback {
t.Helper()
config := actionConfigFixture(t)
rollAction := NewRollback(config)
return rollAction
}
func TestNewRollback(t *testing.T) { func TestNewRollback(t *testing.T) {
is := assert.New(t)
config := actionConfigFixture(t) config := actionConfigFixture(t)
client := NewRollback(config)
assert.NotNil(t, client) rollback := NewRollback(config)
assert.Equal(t, config, client.cfg)
is.NotNil(rollback)
is.Equal(config, rollback.cfg)
is.Equal(DryRunNone, rollback.DryRunStrategy)
is.Empty(rollback.Description)
} }
func TestRollbackRun_UnreachableKubeClient(t *testing.T) { func TestRollbackRun_UnreachableKubeClient(t *testing.T) {
@ -131,3 +144,219 @@ func TestRollbackRevisionZeroForNonRollback(t *testing.T) {
assert.Equal(t, 0, r.Info.RollbackRevision) assert.Equal(t, 0, r.Info.RollbackRevision)
} }
func TestRollback_WithDescription(t *testing.T) {
is := assert.New(t)
req := require.New(t)
rollAction := rollbackAction(t)
// Create two releases - version 1 (superseded) and version 2 (deployed)
rel1 := releaseStub()
rel1.Name = "test-release"
rel1.Version = 1
rel1.Info.Status = common.StatusSuperseded
rel1.ApplyMethod = "csa" // client-side apply
req.NoError(rollAction.cfg.Releases.Create(rel1))
rel2 := releaseStub()
rel2.Name = "test-release"
rel2.Version = 2
rel2.Info.Status = common.StatusDeployed
rel2.ApplyMethod = "csa" // client-side apply
req.NoError(rollAction.cfg.Releases.Create(rel2))
// Set custom description
customDescription := "Rollback due to critical bug in version 2"
rollAction.Description = customDescription
rollAction.Version = 1
rollAction.ServerSideApply = "false" // Disable server-side apply for testing
err := rollAction.Run("test-release")
req.NoError(err)
// Get the new release (version 3)
newReleasei, err := rollAction.cfg.Releases.Get("test-release", 3)
req.NoError(err)
newRelease, err := releaserToV1Release(newReleasei)
req.NoError(err)
// Verify the custom description was set
is.Equal(customDescription, newRelease.Info.Description)
}
func TestRollback_DefaultDescription(t *testing.T) {
is := assert.New(t)
req := require.New(t)
rollAction := rollbackAction(t)
// Create two releases - version 1 (superseded) and version 2 (deployed)
rel1 := releaseStub()
rel1.Name = "test-release-default"
rel1.Version = 1
rel1.Info.Status = common.StatusSuperseded
rel1.ApplyMethod = "csa" // client-side apply
req.NoError(rollAction.cfg.Releases.Create(rel1))
rel2 := releaseStub()
rel2.Name = "test-release-default"
rel2.Version = 2
rel2.Info.Status = common.StatusDeployed
rel2.ApplyMethod = "csa" // client-side apply
req.NoError(rollAction.cfg.Releases.Create(rel2))
// Don't set a description, rely on default
rollAction.Version = 1
rollAction.ServerSideApply = "false" // Disable server-side apply for testing
err := rollAction.Run("test-release-default")
req.NoError(err)
// Get the new release (version 3)
newReleasei, err := rollAction.cfg.Releases.Get("test-release-default", 3)
req.NoError(err)
newRelease, err := releaserToV1Release(newReleasei)
req.NoError(err)
// Verify the default description was set
is.Equal("Rollback to 1", newRelease.Info.Description)
}
func TestRollback_EmptyDescription(t *testing.T) {
is := assert.New(t)
req := require.New(t)
rollAction := rollbackAction(t)
// Create two releases - version 1 (superseded) and version 2 (deployed)
rel1 := releaseStub()
rel1.Name = "test-release-empty"
rel1.Version = 1
rel1.Info.Status = common.StatusSuperseded
rel1.ApplyMethod = "csa" // client-side apply
req.NoError(rollAction.cfg.Releases.Create(rel1))
rel2 := releaseStub()
rel2.Name = "test-release-empty"
rel2.Version = 2
rel2.Info.Status = common.StatusDeployed
rel2.ApplyMethod = "csa" // client-side apply
req.NoError(rollAction.cfg.Releases.Create(rel2))
// Set empty description (should use default)
rollAction.Description = ""
rollAction.Version = 1
rollAction.ServerSideApply = "false" // Disable server-side apply for testing
err := rollAction.Run("test-release-empty")
req.NoError(err)
// Get the new release (version 3)
newReleasei, err := rollAction.cfg.Releases.Get("test-release-empty", 3)
req.NoError(err)
newRelease, err := releaserToV1Release(newReleasei)
req.NoError(err)
// Verify the default description was used for empty string
is.Equal("Rollback to 1", newRelease.Info.Description)
}
func TestRollback_DescriptionTooLong(t *testing.T) {
req := require.New(t)
rollAction := rollbackAction(t)
rel1 := releaseStub()
rel1.Name = "test-release-desc-long"
rel1.Version = 1
rel1.Info.Status = common.StatusSuperseded
rel1.ApplyMethod = "csa"
req.NoError(rollAction.cfg.Releases.Create(rel1))
rel2 := releaseStub()
rel2.Name = "test-release-desc-long"
rel2.Version = 2
rel2.Info.Status = common.StatusDeployed
rel2.ApplyMethod = "csa"
req.NoError(rollAction.cfg.Releases.Create(rel2))
rollAction.Description = strings.Repeat("a", MaxDescriptionLength+1)
rollAction.Version = 1
rollAction.ServerSideApply = "false"
err := rollAction.Run("test-release-desc-long")
req.Error(err)
req.Contains(err.Error(), "description must be")
}
func TestRollback_DescriptionAtMaxLength(t *testing.T) {
is := assert.New(t)
req := require.New(t)
rollAction := rollbackAction(t)
rel1 := releaseStub()
rel1.Name = "test-release-desc-max"
rel1.Version = 1
rel1.Info.Status = common.StatusSuperseded
rel1.ApplyMethod = "csa"
req.NoError(rollAction.cfg.Releases.Create(rel1))
rel2 := releaseStub()
rel2.Name = "test-release-desc-max"
rel2.Version = 2
rel2.Info.Status = common.StatusDeployed
rel2.ApplyMethod = "csa"
req.NoError(rollAction.cfg.Releases.Create(rel2))
rollAction.Description = strings.Repeat("a", MaxDescriptionLength)
rollAction.Version = 1
rollAction.ServerSideApply = "false"
err := rollAction.Run("test-release-desc-max")
req.NoError(err)
newReleasei, err := rollAction.cfg.Releases.Get("test-release-desc-max", 3)
req.NoError(err)
newRelease, err := releaserToV1Release(newReleasei)
req.NoError(err)
is.Equal(strings.Repeat("a", MaxDescriptionLength), newRelease.Info.Description)
}
func TestRollback_DescriptionMultiByteCharacters(t *testing.T) {
is := assert.New(t)
req := require.New(t)
rollAction := rollbackAction(t)
rel1 := releaseStub()
rel1.Name = "test-release-desc-utf8"
rel1.Version = 1
rel1.Info.Status = common.StatusSuperseded
rel1.ApplyMethod = "csa"
req.NoError(rollAction.cfg.Releases.Create(rel1))
rel2 := releaseStub()
rel2.Name = "test-release-desc-utf8"
rel2.Version = 2
rel2.Info.Status = common.StatusDeployed
rel2.ApplyMethod = "csa"
req.NoError(rollAction.cfg.Releases.Create(rel2))
// "é" is 2 bytes in UTF-8 but 1 rune
rollAction.Description = strings.Repeat("é", MaxDescriptionLength)
rollAction.Version = 1
rollAction.ServerSideApply = "false"
err := rollAction.Run("test-release-desc-utf8")
req.NoError(err)
newReleasei, err := rollAction.cfg.Releases.Get("test-release-desc-utf8", 3)
req.NoError(err)
newRelease, err := releaserToV1Release(newReleasei)
req.NoError(err)
is.Equal(strings.Repeat("é", MaxDescriptionLength), newRelease.Info.Description)
}

@ -0,0 +1,3 @@
dependencies: []
digest: sha256:0000000000000000000000000000000000000000000000000000000000000
generated: "2016-01-01T00:00:00Z"

@ -0,0 +1,4 @@
apiVersion: v2
name: chart-with-lock
version: 0.1.0
description: Test chart with a Chart.lock, used to verify SourceDateEpoch stamping

@ -77,7 +77,6 @@ func (u *Uninstall) Run(name string) (*releasei.UninstallReleaseResponse, error)
if u.DryRun { if u.DryRun {
ri, err := u.cfg.releaseContent(name, 0) ri, err := u.cfg.releaseContent(name, 0)
if err != nil { if err != nil {
if u.IgnoreNotFound && errors.Is(err, driver.ErrReleaseNotFound) { if u.IgnoreNotFound && errors.Is(err, driver.ErrReleaseNotFound) {
return nil, nil return nil, nil

@ -59,6 +59,7 @@ func TestUninstallRelease_ignoreNotFound(t *testing.T) {
is.Nil(res) is.Nil(res)
is.NoError(err) is.NoError(err)
} }
func TestUninstallRelease_deleteRelease(t *testing.T) { func TestUninstallRelease_deleteRelease(t *testing.T) {
is := assert.New(t) is := assert.New(t)
req := require.New(t) req := require.New(t)

@ -85,7 +85,7 @@ func (r *v2Accessor) IsLibraryChart() bool {
} }
func (r *v2Accessor) Dependencies() []Charter { func (r *v2Accessor) Dependencies() []Charter {
var deps = make([]Charter, len(r.chrt.Dependencies())) deps := make([]Charter, len(r.chrt.Dependencies()))
for i, c := range r.chrt.Dependencies() { for i, c := range r.chrt.Dependencies() {
deps[i] = c deps[i] = c
} }
@ -93,7 +93,7 @@ func (r *v2Accessor) Dependencies() []Charter {
} }
func (r *v2Accessor) MetaDependencies() []Dependency { func (r *v2Accessor) MetaDependencies() []Dependency {
var deps = make([]Dependency, len(r.chrt.Metadata.Dependencies)) deps := make([]Dependency, len(r.chrt.Metadata.Dependencies))
for i, c := range r.chrt.Metadata.Dependencies { for i, c := range r.chrt.Metadata.Dependencies {
deps[i] = c deps[i] = c
} }
@ -154,7 +154,7 @@ func (r *v3Accessor) IsLibraryChart() bool {
} }
func (r *v3Accessor) Dependencies() []Charter { func (r *v3Accessor) Dependencies() []Charter {
var deps = make([]Charter, len(r.chrt.Dependencies())) deps := make([]Charter, len(r.chrt.Dependencies()))
for i, c := range r.chrt.Dependencies() { for i, c := range r.chrt.Dependencies() {
deps[i] = c deps[i] = c
} }
@ -162,7 +162,7 @@ func (r *v3Accessor) Dependencies() []Charter {
} }
func (r *v3Accessor) MetaDependencies() []Dependency { func (r *v3Accessor) MetaDependencies() []Dependency {
var deps = make([]Dependency, len(r.chrt.Dependencies())) deps := make([]Dependency, len(r.chrt.Dependencies()))
for i, c := range r.chrt.Metadata.Dependencies { for i, c := range r.chrt.Metadata.Dependencies {
deps[i] = c deps[i] = c
} }

@ -47,7 +47,6 @@ var (
panic(fmt.Sprintf("failed to create default capabilities: %v", err)) panic(fmt.Sprintf("failed to create default capabilities: %v", err))
} }
return caps return caps
}() }()
) )

@ -177,34 +177,41 @@ func (ch *Chart) CRDObjects() []CRD {
return crds return crds
} }
// StampModTimes sets timestamps on the chart (and dependencies) to epoch. // StampModTimes sets timestamps on the chart (and dependencies) to t,
// This is used for reproducible builds via SOURCE_DATE_EPOCH. // normalized to UTC and truncated to whole seconds.
func (ch *Chart) StampModTimes(epoch time.Time) { //
ch.ModTime = epoch // Normalization is required because Chart.lock's generated: field is written
// by yaml.Marshal from Lock.Generated. Without UTC/truncate, a caller
// supplying a local-zone or sub-second time.Time produces a generated: value
// with a timezone offset or fractional seconds, making the lock file content
// non-reproducible across machines even when the same SOURCE_DATE_EPOCH is used.
func (ch *Chart) StampModTimes(t time.Time) {
t = t.UTC().Truncate(time.Second)
ch.ModTime = t
if len(ch.Schema) > 0 { if len(ch.Schema) > 0 {
ch.SchemaModTime = epoch ch.SchemaModTime = t
} }
if ch.Lock != nil { if ch.Lock != nil {
ch.Lock.Generated = epoch ch.Lock.Generated = t
} }
for _, f := range ch.Raw { for _, f := range ch.Raw {
if f != nil { if f != nil {
f.ModTime = epoch f.ModTime = t
} }
} }
for _, f := range ch.Templates { for _, f := range ch.Templates {
if f != nil { if f != nil {
f.ModTime = epoch f.ModTime = t
} }
} }
for _, f := range ch.Files { for _, f := range ch.Files {
if f != nil { if f != nil {
f.ModTime = epoch f.ModTime = t
} }
} }
for _, dep := range ch.Dependencies() { for _, dep := range ch.Dependencies() {
dep.StampModTimes(epoch) dep.StampModTimes(t)
} }
} }

@ -30,14 +30,16 @@ import (
const namespace = "testNamespace" const namespace = "testNamespace"
const badChartDir = "rules/testdata/badchartfile" const (
const badValuesFileDir = "rules/testdata/badvaluesfile" badChartDir = "rules/testdata/badchartfile"
const badYamlFileDir = "rules/testdata/albatross" badValuesFileDir = "rules/testdata/badvaluesfile"
const badCrdFileDir = "rules/testdata/badcrdfile" badYamlFileDir = "rules/testdata/albatross"
const goodChartDir = "rules/testdata/goodone" badCrdFileDir = "rules/testdata/badcrdfile"
const subChartValuesDir = "rules/testdata/withsubchart" goodChartDir = "rules/testdata/goodone"
const malformedTemplate = "rules/testdata/malformed-template" subChartValuesDir = "rules/testdata/withsubchart"
const invalidChartFileDir = "rules/testdata/invalidchartfile" malformedTemplate = "rules/testdata/malformed-template"
invalidChartFileDir = "rules/testdata/invalidchartfile"
)
func TestBadChart(t *testing.T) { func TestBadChart(t *testing.T) {
var values map[string]any var values map[string]any

@ -161,7 +161,6 @@ func validateChartVersion(cf *chart.Metadata) error {
func validateChartVersionStrictSemVerV2(cf *chart.Metadata) error { func validateChartVersionStrictSemVerV2(cf *chart.Metadata) error {
_, err := semver.StrictNewVersion(cf.Version) _, err := semver.StrictNewVersion(cf.Version)
if err != nil { if err != nil {
return fmt.Errorf("version '%s' is not a valid SemVerV2", cf.Version) return fmt.Errorf("version '%s' is not a valid SemVerV2", cf.Version)
} }

@ -42,8 +42,10 @@ var (
nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml")
) )
var badChart, _ = chartutil.LoadChartfile(badChartFilePath) var (
var badChartName, _ = chartutil.LoadChartfile(badChartNamePath) badChart, _ = chartutil.LoadChartfile(badChartFilePath)
badChartName, _ = chartutil.LoadChartfile(badChartNamePath)
)
// Validation functions Test // Validation functions Test
func TestValidateChartYamlNotDirectory(t *testing.T) { func TestValidateChartYamlNotDirectory(t *testing.T) {
@ -63,7 +65,7 @@ func TestValidateChartName(t *testing.T) {
} }
func TestValidateChartVersion(t *testing.T) { func TestValidateChartVersion(t *testing.T) {
var failTest = []struct { failTest := []struct {
Version string Version string
ErrorMsg string ErrorMsg string
}{ }{
@ -73,7 +75,7 @@ func TestValidateChartVersion(t *testing.T) {
{"-3", "'-3' is not a valid SemVer"}, {"-3", "'-3' is not a valid SemVer"},
} }
var successTest = []string{"0.0.1", "0.0.1+build", "0.0.1-beta"} successTest := []string{"0.0.1", "0.0.1+build", "0.0.1-beta"}
for _, test := range failTest { for _, test := range failTest {
badChart.Version = test.Version badChart.Version = test.Version
@ -87,7 +89,7 @@ func TestValidateChartVersion(t *testing.T) {
} }
func TestValidateChartVersionStrictSemVerV2(t *testing.T) { func TestValidateChartVersionStrictSemVerV2(t *testing.T) {
var failTest = []struct { failTest := []struct {
Version string Version string
ErrorMsg string ErrorMsg string
}{ }{
@ -96,7 +98,7 @@ func TestValidateChartVersionStrictSemVerV2(t *testing.T) {
{"1.1", "version '1.1' is not a valid SemVerV2"}, {"1.1", "version '1.1' is not a valid SemVerV2"},
} }
var successTest = []string{"1.1.1", "0.0.1+build", "0.0.1-beta"} successTest := []string{"1.1.1", "0.0.1+build", "0.0.1-beta"}
for _, test := range failTest { for _, test := range failTest {
badChart.Version = test.Version badChart.Version = test.Version
@ -110,7 +112,7 @@ func TestValidateChartVersionStrictSemVerV2(t *testing.T) {
} }
func TestValidateChartMaintainer(t *testing.T) { func TestValidateChartMaintainer(t *testing.T) {
var failTest = []struct { failTest := []struct {
Name string Name string
Email string Email string
ErrorMsg string ErrorMsg string
@ -120,7 +122,7 @@ func TestValidateChartMaintainer(t *testing.T) {
{"John Snow", "wrongFormatEmail.com", "invalid email"}, {"John Snow", "wrongFormatEmail.com", "invalid email"},
} }
var successTest = []struct { successTest := []struct {
Name string Name string
Email string Email string
}{ }{
@ -144,8 +146,8 @@ func TestValidateChartMaintainer(t *testing.T) {
} }
func TestValidateChartSources(t *testing.T) { func TestValidateChartSources(t *testing.T) {
var failTest = []string{"", "RiverRun", "john@winterfell", "riverrun.io"} failTest := []string{"", "RiverRun", "john@winterfell", "riverrun.io"}
var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"} successTest := []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"}
for _, test := range failTest { for _, test := range failTest {
badChart.Sources = []string{test} badChart.Sources = []string{test}
require.ErrorContainsf(t, validateChartSources(badChart), "invalid source URL", "validateChartSources(%s) to return \"invalid source URL\", got no error", test) require.ErrorContainsf(t, validateChartSources(badChart), "invalid source URL", "validateChartSources(%s) to return \"invalid source URL\", got no error", test)
@ -174,8 +176,8 @@ func TestValidateChartIconPresence(t *testing.T) {
} }
func TestValidateChartIconURL(t *testing.T) { func TestValidateChartIconURL(t *testing.T) {
var failTest = []string{"RiverRun", "john@winterfell", "riverrun.io"} failTest := []string{"RiverRun", "john@winterfell", "riverrun.io"}
var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"} successTest := []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"}
for _, test := range failTest { for _, test := range failTest {
badChart.Icon = test badChart.Icon = test
require.ErrorContainsf(t, validateChartIconURL(badChart), "invalid icon URL", "validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test) require.ErrorContainsf(t, validateChartIconURL(badChart), "invalid icon URL", "validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test)

@ -35,11 +35,11 @@ import (
const templateTestBasedir = "./testdata/albatross" const templateTestBasedir = "./testdata/albatross"
func TestValidateAllowedExtension(t *testing.T) { func TestValidateAllowedExtension(t *testing.T) {
var failTest = []string{"/foo", "/test.toml"} failTest := []string{"/foo", "/test.toml"}
for _, test := range failTest { for _, test := range failTest {
require.ErrorContainsf(t, validateAllowedExtension(test), "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) require.ErrorContainsf(t, validateAllowedExtension(test), "Valid extensions are .yaml, .yml, .tpl, or .txt", "validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test)
} }
var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} successTest := []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"}
for _, test := range successTest { for _, test := range successTest {
assert.NoError(t, validateAllowedExtension(test), "validateAllowedExtension('%s') to return no error", test) assert.NoError(t, validateAllowedExtension(test), "validateAllowedExtension('%s') to return no error", test)
} }
@ -62,8 +62,10 @@ func TestTemplateParsing(t *testing.T) {
assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError") assert.ErrorContains(t, res[0].Err, "deliberateSyntaxError")
} }
var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") var (
var ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored") wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml")
ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored")
)
// Test a template with all the existing features: // Test a template with all the existing features:
// namespaces, partial templates // namespaces, partial templates
@ -406,6 +408,7 @@ func TestEmptyWithCommentsManifests(t *testing.T) {
} }
require.Empty(t, linter.Messages, "Expected 0 lint errors") require.Empty(t, linter.Messages, "Expected 0 lint errors")
} }
func TestValidateListAnnotations(t *testing.T) { func TestValidateListAnnotations(t *testing.T) {
md := &k8sYamlStruct{ md := &k8sYamlStruct{
APIVersion: "v1", APIVersion: "v1",

@ -27,7 +27,7 @@ import (
var errLint = errors.New("lint failed") var errLint = errors.New("lint failed")
func TestRunLinterRule(t *testing.T) { func TestRunLinterRule(t *testing.T) {
var tests = []struct { tests := []struct {
Severity int Severity int
LintError error LintError error
ExpectedMessages int ExpectedMessages int

@ -210,8 +210,20 @@ func LoadFiles(files []*archive.BufferedFile) (*chart.Chart, error) {
// The reader is expected to contain one or more YAML documents, the values of which are merged. // The reader is expected to contain one or more YAML documents, the values of which are merged.
// And the values can be either a chart's default values or user-supplied values. // And the values can be either a chart's default values or user-supplied values.
func LoadValues(data io.Reader) (map[string]any, error) { func LoadValues(data io.Reader) (map[string]any, error) {
// Read fully first. YAMLReader/LineReader can drop a final unterminated
// line when its length is an exact multiple of bufio.Reader's default
// buffer (4096). Appending a trailing newline avoids that case.
// See https://github.com/helm/helm/issues/32506
b, err := io.ReadAll(data)
if err != nil {
return nil, err
}
if len(b) > 0 && b[len(b)-1] != '\n' {
b = append(b, '\n')
}
values := map[string]any{} values := map[string]any{}
reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) reader := utilyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(b)))
for { for {
currentMap := map[string]any{} currentMap := map[string]any{}
raw, err := reader.Read() raw, err := reader.Read()

@ -21,6 +21,7 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"errors" "errors"
"fmt"
"io" "io"
"log" "log"
"os" "os"
@ -182,7 +183,8 @@ func TestLoadFiles_BadCases(t *testing.T) {
Data: []byte(""), Data: []byte(""),
}, },
}, },
expectError: "validation: chart.metadata.apiVersion is required"}, expectError: "validation: chart.metadata.apiVersion is required",
},
} { } {
_, err := LoadFiles(tt.bufferedFiles) _, err := LoadFiles(tt.bufferedFiles)
require.Error(t, err, "expected error when load illegal files") require.Error(t, err, "expected error when load illegal files")
@ -462,6 +464,33 @@ foo:
} }
} }
func TestLoadValuesEOFBoundary(t *testing.T) {
// Reproduces #32506: a single logical line whose length is a multiple of
// bufio's default buffer (4096) and has no trailing newline used to be
// dropped entirely by YAMLReader, yielding empty values.
// Also cover 8192 (2x buffer) so we do not only hit the single-buffer case.
for _, size := range []int{4096, 8192} {
t.Run(fmt.Sprintf("size_%d", size), func(t *testing.T) {
prefix := []byte(`{"foo":"`)
suffix := []byte(`"}`)
pad := size - len(prefix) - len(suffix)
data := make([]byte, 0, size)
data = append(data, prefix...)
data = append(data, bytes.Repeat([]byte("x"), pad)...)
data = append(data, suffix...)
if len(data) != size {
t.Fatalf("test setup: want data length %d, got %d", size, len(data))
}
values, err := LoadValues(bytes.NewReader(data))
require.NoError(t, err)
assert.Equal(t, map[string]any{
"foo": string(bytes.Repeat([]byte("x"), pad)),
}, values)
})
}
}
func TestMergeValuesV2(t *testing.T) { func TestMergeValuesV2(t *testing.T) {
nestedMap := map[string]any{ nestedMap := map[string]any{
"foo": "bar", "foo": "bar",

@ -29,6 +29,7 @@ import (
const completionDesc = ` const completionDesc = `
Generate autocompletion scripts for Helm for the specified shell. Generate autocompletion scripts for Helm for the specified shell.
` `
const bashCompDesc = ` const bashCompDesc = `
Generate the autocompletion script for Helm for the bash shell. Generate the autocompletion script for Helm for the bash shell.

@ -60,7 +60,6 @@ func TestDependencyBuildCmd(t *testing.T) {
cmd := fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir) cmd := fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir)
_, out, err := executeActionCommand(cmd) _, out, err := executeActionCommand(cmd)
// In the first pass, we basically want the same results as an update. // In the first pass, we basically want the same results as an update.
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
@ -106,7 +105,6 @@ func TestDependencyBuildCmd(t *testing.T) {
skipRefreshCmd := fmt.Sprintf("dependency build '%s' --skip-refresh --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir) skipRefreshCmd := fmt.Sprintf("dependency build '%s' --skip-refresh --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir)
_, out, err = executeActionCommand(skipRefreshCmd) _, out, err = executeActionCommand(skipRefreshCmd)
// In this pass, we check --skip-refresh option becomes effective. // In this pass, we check --skip-refresh option becomes effective.
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
@ -137,7 +135,6 @@ func TestDependencyBuildCmdWithHelmV2Hash(t *testing.T) {
cmd := fmt.Sprintf("dependency build '%s'", chartName) cmd := fmt.Sprintf("dependency build '%s'", chartName)
_, out, err := executeActionCommand(cmd) _, out, err := executeActionCommand(cmd)
// Want to make sure the build can verify Helm v2 hash // Want to make sure the build can verify Helm v2 hash
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)

@ -39,16 +39,20 @@ func TestDependencyListCmd(t *testing.T) {
noDependencies.golden = "output/dependency-list-no-requirements-windows.txt" noDependencies.golden = "output/dependency-list-no-requirements-windows.txt"
} }
tests := []cmdTestCase{noSuchChart, tests := []cmdTestCase{
noDependencies, { noSuchChart,
noDependencies,
{
name: "Dependencies in chart dir", name: "Dependencies in chart dir",
cmd: "dependency list testdata/testcharts/reqtest", cmd: "dependency list testdata/testcharts/reqtest",
golden: "output/dependency-list.txt", golden: "output/dependency-list.txt",
}, { },
{
name: "Dependencies in chart archive", name: "Dependencies in chart archive",
cmd: "dependency list testdata/testcharts/reqtest-0.1.0.tgz", cmd: "dependency list testdata/testcharts/reqtest-0.1.0.tgz",
golden: "output/dependency-list-archive.txt", golden: "output/dependency-list-archive.txt",
}} },
}
runTestCmd(t, tests) runTestCmd(t, tests)
} }

@ -209,7 +209,6 @@ func TestDependencyUpdateCmd_WithRepoThatWasNotAdded(t *testing.T) {
fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --content-cache %s", dir(chartname), fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --content-cache %s", dir(chartname),
dir("repositories.yaml"), dir(), contentCache), dir("repositories.yaml"), dir(), contentCache),
) )
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
t.Fatal(err) t.Fatal(err)

@ -152,7 +152,6 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) {
sort.Strings(formatNames) sort.Strings(formatNames)
return formatNames, cobra.ShellCompDirectiveNoFileComp return formatNames, cobra.ShellCompDirectiveNoFileComp
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

@ -75,7 +75,6 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

@ -71,7 +71,6 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

@ -66,7 +66,6 @@ func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

@ -70,7 +70,6 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

@ -69,7 +69,6 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

@ -221,7 +221,6 @@ type pluginCommand struct {
func loadCompletionForPlugin(pluginCmd *cobra.Command, plug plugin.Plugin) { func loadCompletionForPlugin(pluginCmd *cobra.Command, plug plugin.Plugin) {
// Parse the yaml file providing the plugin's sub-commands and flags // Parse the yaml file providing the plugin's sub-commands and flags
cmds, err := loadFile(plug.Dir() + string(filepath.Separator) + pluginStaticCompletionFile) cmds, err := loadFile(plug.Dir() + string(filepath.Separator) + pluginStaticCompletionFile)
if err != nil { if err != nil {
// The file could be missing or invalid. No static completion for this plugin. // The file could be missing or invalid. No static completion for this plugin.
slog.Debug("plugin completion file loading", slog.String("error", err.Error())) slog.Debug("plugin completion file loading", slog.String("error", err.Error()))

@ -97,7 +97,6 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
return compVersionFlag(args[0], toComplete) return compVersionFlag(args[0], toComplete)
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

@ -268,7 +268,8 @@ func runPullTests(t *testing.T, tests []struct {
wantErrorMsg string wantErrorMsg string
expectFile string expectFile string
expectDir bool expectDir bool
}, outdir string, additionalFlags string) { }, outdir string, additionalFlags string,
) {
t.Helper() t.Helper()
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

@ -73,7 +73,6 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
registryClient, err := newRegistryClient( registryClient, err := newRegistryClient(
out, o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSVerify, o.plainHTTP, o.username, o.password, out, o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSVerify, o.plainHTTP, o.username, o.password,
) )
if err != nil { if err != nil {
return fmt.Errorf("missing registry client: %w", err) return fmt.Errorf("missing registry client: %w", err)
} }

@ -76,7 +76,7 @@ func TestRepoRemove(t *testing.T) {
assert.Falsef(t, f.Has(testRepoName), "%s was not successfully removed from repositories list", testRepoName) assert.Falsef(t, f.Has(testRepoName), "%s was not successfully removed from repositories list", testRepoName)
// Test removal of multiple repos in one go // Test removal of multiple repos in one go
var testRepoNames = []string{"foo", "bar", "baz"} testRepoNames := []string{"foo", "bar", "baz"}
cacheFiles := make(map[string][]string, len(testRepoNames)) cacheFiles := make(map[string][]string, len(testRepoNames))
// Add test repos // Add test repos
@ -151,7 +151,7 @@ func TestRepoRemoveCompletion(t *testing.T) {
repoFile := filepath.Join(rootDir, "repositories.yaml") repoFile := filepath.Join(rootDir, "repositories.yaml")
repoCache := filepath.Join(rootDir, "cache") repoCache := filepath.Join(rootDir, "cache")
var testRepoNames = []string{"foo", "bar", "baz"} testRepoNames := []string{"foo", "bar", "baz"}
// Add test repos // Add test repos
for _, repoName := range testRepoNames { for _, repoName := range testRepoNames {

@ -159,7 +159,7 @@ func TestUpdateChartsFailWithError(t *testing.T) {
) )
defer ts.Stop() defer ts.Stop()
var invalidURL = ts.URL() + "55" invalidURL := ts.URL() + "55"
r1, err := repo.NewChartRepository(&repo.Entry{ r1, err := repo.NewChartRepository(&repo.Entry{
Name: "charts", Name: "charts",
URL: invalidURL, URL: invalidURL,

@ -21,6 +21,7 @@ import (
"io" "io"
"strconv" "strconv"
"time" "time"
"unicode/utf8"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -67,6 +68,11 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
client.Version = ver client.Version = ver
} }
// Validate description length
if descLen := utf8.RuneCountInString(client.Description); descLen > action.MaxDescriptionLength {
return fmt.Errorf("description must be %d characters or less, got %d", action.MaxDescriptionLength, descLen)
}
dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false) dryRunStrategy, err := cmdGetDryRunFlagStrategy(cmd, false)
if err != nil { if err != nil {
return err return err
@ -83,6 +89,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
} }
f := cmd.Flags() f := cmd.Flags()
f.StringVar(&client.Description, "description", "", fmt.Sprintf("add a custom description for the rollback (max %d characters)", action.MaxDescriptionLength))
f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement") f.BoolVar(&client.ForceReplace, "force-replace", false, "force resource updates by replacement")
f.BoolVar(&client.ForceReplace, "force", false, "deprecated") f.BoolVar(&client.ForceReplace, "force", false, "deprecated")
f.MarkDeprecated("force", "use --force-replace instead") f.MarkDeprecated("force", "use --force-replace instead")

@ -18,11 +18,13 @@ package cmd
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"helm.sh/helm/v4/pkg/action"
chart "helm.sh/helm/v4/pkg/chart/v2" chart "helm.sh/helm/v4/pkg/chart/v2"
"helm.sh/helm/v4/pkg/release/common" "helm.sh/helm/v4/pkg/release/common"
release "helm.sh/helm/v4/pkg/release/v1" release "helm.sh/helm/v4/pkg/release/v1"
@ -81,6 +83,11 @@ func TestRollbackCmd(t *testing.T) {
golden: "output/rollback-no-args.txt", golden: "output/rollback-no-args.txt",
rels: rels, rels: rels,
wantError: true, wantError: true,
}, {
name: "rollback a release with description",
cmd: "rollback funny-honey 1 --description 'Reverting due to bug in version 2'",
golden: "output/rollback.txt",
rels: rels,
}} }}
runTestCmd(t, tests) runTestCmd(t, tests)
} }
@ -127,6 +134,83 @@ func TestRollbackFileCompletion(t *testing.T) {
checkFileCompletion(t, "rollback myrelease 1", false) checkFileCompletion(t, "rollback myrelease 1", false)
} }
func TestRollbackWithDescription(t *testing.T) {
releaseName := "funny-bunny-desc"
rels := []*release.Release{
{
Name: releaseName,
Info: &release.Info{Status: common.StatusSuperseded},
Chart: &chart.Chart{},
Version: 1,
},
{
Name: releaseName,
Info: &release.Info{Status: common.StatusDeployed},
Chart: &chart.Chart{},
Version: 2,
},
}
storage := storageFixture()
for _, rel := range rels {
if err := storage.Create(rel); err != nil {
t.Fatal(err)
}
}
customDescription := "Rollback due to critical bug in version 2"
_, _, err := executeActionCommandC(storage, fmt.Sprintf("rollback %s 1 --description '%s'", releaseName, customDescription))
if err != nil {
t.Fatalf("unexpected error, got '%v'", err)
}
// Verify the description was stored correctly
updatedReli, err := storage.Get(releaseName, 3)
if err != nil {
t.Fatalf("unexpected error getting release, got '%v'", err)
}
updatedRel, err := releaserToV1Release(updatedReli)
if err != nil {
t.Fatalf("unexpected error converting release, got '%v'", err)
}
if updatedRel.Info.Description != customDescription {
t.Errorf("Expected description '%s', got '%s'", customDescription, updatedRel.Info.Description)
}
}
func TestRollbackDescriptionTooLong(t *testing.T) {
releaseName := "funny-bunny-long-desc"
rels := []*release.Release{
{
Name: releaseName,
Info: &release.Info{Status: common.StatusSuperseded},
Chart: &chart.Chart{},
Version: 1,
},
{
Name: releaseName,
Info: &release.Info{Status: common.StatusDeployed},
Chart: &chart.Chart{},
Version: 2,
},
}
storage := storageFixture()
for _, rel := range rels {
if err := storage.Create(rel); err != nil {
t.Fatal(err)
}
}
longDescription := strings.Repeat("a", action.MaxDescriptionLength+1)
_, _, err := executeActionCommandC(storage, fmt.Sprintf("rollback %s 1 --description '%s'", releaseName, longDescription))
if err == nil {
t.Error("expected error for description exceeding max length, got success")
}
if err != nil && !strings.Contains(err.Error(), fmt.Sprintf("description must be %d characters or less", action.MaxDescriptionLength)) {
t.Errorf("expected error about description length, got: %v", err)
}
}
func TestRollbackWithLabels(t *testing.T) { func TestRollbackWithLabels(t *testing.T) {
labels1 := map[string]string{"operation": "install", "firstLabel": "firstValue"} labels1 := map[string]string{"operation": "install", "firstLabel": "firstValue"}
labels2 := map[string]string{"operation": "upgrade", "secondLabel": "secondValue"} labels2 := map[string]string{"operation": "upgrade", "secondLabel": "secondValue"}

@ -229,7 +229,6 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg
} }
return nil, cobra.ShellCompDirectiveDefault return nil, cobra.ShellCompDirectiveDefault
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -253,7 +252,6 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg
} }
return nil, cobra.ShellCompDirectiveNoFileComp return nil, cobra.ShellCompDirectiveNoFileComp
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -446,7 +444,6 @@ func newRegistryClientWithTLS(
tlsutil.WithCertKeyPairFiles(certFile, keyFile), tlsutil.WithCertKeyPairFiles(certFile, keyFile),
tlsutil.WithCAFile(caFile), tlsutil.WithCAFile(caFile),
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("can't create TLS config for client: %w", err) return nil, fmt.Errorf("can't create TLS config for client: %w", err)
} }

@ -28,7 +28,7 @@ import (
func TestSearchHubCmd(t *testing.T) { func TestSearchHubCmd(t *testing.T) {
// Setup a mock search service // 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"}}}}]}` 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) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, searchResult) fmt.Fprintln(w, searchResult)
})) }))
@ -37,7 +37,7 @@ func TestSearchHubCmd(t *testing.T) {
// The expected output has the URL to the mocked search service in it // The expected output has the URL to the mocked search service in it
// Trailing spaces are necessary to preserve in "expected" as the uitable package adds // Trailing spaces are necessary to preserve in "expected" as the uitable package adds
// them during printing. // them during printing.
var expected = fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION expected := fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION
%s/charts/stable/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend %s/charts/stable/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend
%s/charts/bitnami/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend %s/charts/bitnami/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend
`, ts.URL, ts.URL) `, ts.URL, ts.URL)
@ -51,7 +51,7 @@ func TestSearchHubCmd(t *testing.T) {
func TestSearchHubListRepoCmd(t *testing.T) { func TestSearchHubListRepoCmd(t *testing.T) {
// Setup a mock search service // 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"}}}}]}` 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) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, searchResult) fmt.Fprintln(w, searchResult)
})) }))
@ -60,7 +60,7 @@ func TestSearchHubListRepoCmd(t *testing.T) {
// The expected output has the URL to the mocked search service in it // The expected output has the URL to the mocked search service in it
// Trailing spaces are necessary to preserve in "expected" as the uitable package adds // Trailing spaces are necessary to preserve in "expected" as the uitable package adds
// them during printing. // them during printing.
var expected = fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION REPO URL expected := fmt.Sprintf(`URL CHART VERSION APP VERSION DESCRIPTION REPO URL
%s/charts/stable/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend https://charts.helm.sh/stable %s/charts/stable/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend https://charts.helm.sh/stable
%s/charts/bitnami/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend https://charts.bitnami.com %s/charts/bitnami/phpmyadmin 3.0.0 4.9.0-1 phpMyAdmin is an mysql administration frontend https://charts.bitnami.com
`, ts.URL, ts.URL) `, ts.URL, ts.URL)
@ -96,7 +96,7 @@ func TestSearchHubCmd_FailOnNoResponseTests(t *testing.T) {
wantErr bool wantErr bool
} }
var tests = []testCase{ tests := []testCase{
{ {
name: "Search hub with no results in response", name: "Search hub with no results in response",
cmd: `search hub maria`, cmd: `search hub maria`,

@ -61,7 +61,8 @@ func TestSearchRepositoriesCmd(t *testing.T) {
cmd: "search repo syzygy --fail-on-no-result", cmd: "search repo syzygy --fail-on-no-result",
golden: "output/search-not-found-error.txt", golden: "output/search-not-found-error.txt",
wantError: true, wantError: true,
}, {name: "search for 'syzygy' with json output and --fail-on-no-result, expect failure for no results", }, {
name: "search for 'syzygy' with json output and --fail-on-no-result, expect failure for no results",
cmd: "search repo syzygy --output json --fail-on-no-result", cmd: "search repo syzygy --output json --fail-on-no-result",
golden: "output/search-not-found-error.txt", golden: "output/search-not-found-error.txt",
wantError: true, wantError: true,

@ -205,7 +205,6 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) {
} }
return compVersionFlag(args[0], toComplete) return compVersionFlag(args[0], toComplete)
}) })
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

@ -185,7 +185,8 @@ func TestStatusCompletion(t *testing.T) {
Version: "1.2.3-prerelease", Version: "1.2.3-prerelease",
}, },
}, },
}} },
}
tests := []cmdTestCase{{ tests := []cmdTestCase{{
name: "completion for status", name: "completion for status",

@ -254,7 +254,6 @@ func writeToFile(outputDir string, name string, data string, appendData bool) er
defer f.Close() defer f.Close()
_, err = fmt.Fprintf(f, "---\n# Source: %s\n%s\n", name, data) _, err = fmt.Fprintf(f, "---\n# Source: %s\n%s\n", name, data)
if err != nil { if err != nil {
return err return err
} }

@ -143,9 +143,11 @@ type renderable struct {
basePath string basePath string
} }
const warnStartDelim = "HELM_ERR_START" const (
const warnEndDelim = "HELM_ERR_END" warnStartDelim = "HELM_ERR_START"
const recursionMaxNums = 1000 warnEndDelim = "HELM_ERR_END"
recursionMaxNums = 1000
)
var warnRegex = regexp.MustCompile(warnStartDelim + `((?s).*)` + warnEndDelim) var warnRegex = regexp.MustCompile(warnStartDelim + `((?s).*)` + warnEndDelim)

@ -1278,6 +1278,7 @@ func TestTraceableError_SimpleForm(t *testing.T) {
assert.Equal(t, "error calling include", trace.message, "Expected %q, got %q", errString, trace.message) assert.Equal(t, "error calling include", trace.message, "Expected %q, got %q", errString, trace.message)
} }
} }
func TestTraceableError_ExecutingForm(t *testing.T) { func TestTraceableError_ExecutingForm(t *testing.T) {
testStrings := [][]string{ testStrings := [][]string{
{"function_not_found/templates/secret.yaml:6:11: executing \"function_not_found/templates/secret.yaml\" at <include \"name\" .>: ", "function_not_found/templates/secret.yaml:6:11"}, {"function_not_found/templates/secret.yaml:6:11: executing \"function_not_found/templates/secret.yaml\" at <include \"name\" .>: ", "function_not_found/templates/secret.yaml:6:11"},

@ -130,7 +130,6 @@ func toYAMLPretty(v any) string {
encoder := goYaml.NewEncoder(&data) encoder := goYaml.NewEncoder(&data)
encoder.SetIndent(2) encoder.SetIndent(2)
err := encoder.Encode(v) err := encoder.Encode(v)
if err != nil { if err != nil {
// Swallow errors inside of a template. // Swallow errors inside of a template.
return "" return ""

@ -147,39 +147,40 @@ keyInElement1 = "valueInElement1"`,
tpl string tpl string
expect any expect any
vars any vars any
}{{ }{
tpl: `{{ mustToYaml . }}`, {
vars: loopMap, tpl: `{{ mustToYaml . }}`,
}, { vars: loopMap,
tpl: `{{ mustToJson . }}`, }, {
vars: loopMap, tpl: `{{ mustToJson . }}`,
}, { vars: loopMap,
tpl: `{{ mustToDuration 30 }}`, }, {
expect: `30s`, tpl: `{{ mustToDuration 30 }}`,
vars: nil, expect: `30s`,
}, { vars: nil,
tpl: `{{ mustToDuration "1m30s" }}`, }, {
expect: `1m30s`, tpl: `{{ mustToDuration "1m30s" }}`,
vars: nil, expect: `1m30s`,
}, { vars: nil,
tpl: `{{ mustToDuration "foo" }}`, }, {
vars: nil, tpl: `{{ mustToDuration "foo" }}`,
}, { vars: nil,
tpl: `{{ toYaml . }}`, }, {
expect: "", // should return empty string and swallow error tpl: `{{ toYaml . }}`,
vars: loopMap, expect: "", // should return empty string and swallow error
}, { vars: loopMap,
tpl: `{{ toJson . }}`, }, {
expect: "", // should return empty string and swallow error tpl: `{{ toJson . }}`,
vars: loopMap, expect: "", // should return empty string and swallow error
}, { vars: loopMap,
tpl: `{{ mustToToml . }}`, }, {
vars: map[int]string{1: "one"}, // non-string key is invalid in TOML tpl: `{{ mustToToml . }}`,
}, { vars: map[int]string{1: "one"}, // non-string key is invalid in TOML
tpl: `{{ mustToToml . }}`, }, {
expect: "foo = \"bar\"\n", // should succeed and return TOML string tpl: `{{ mustToToml . }}`,
vars: map[string]string{"foo": "bar"}, expect: "foo = \"bar\"\n", // should succeed and return TOML string
}, vars: map[string]string{"foo": "bar"},
},
} }
for _, tt := range mustFuncsTests { for _, tt := range mustFuncsTests {
@ -368,48 +369,49 @@ func TestDurationHelpers(t *testing.T) {
name string name string
tpl string tpl string
vars any vars any
}{{ }{
name: "mustToDuration invalid string", {
tpl: `{{ mustToDuration "nope" }}`, name: "mustToDuration invalid string",
}, { tpl: `{{ mustToDuration "nope" }}`,
name: "mustToDuration empty string", }, {
tpl: `{{ mustToDuration "" }}`, name: "mustToDuration empty string",
}, { tpl: `{{ mustToDuration "" }}`,
name: "mustToDuration whitespace string", }, {
tpl: `{{ mustToDuration " " }}`, name: "mustToDuration whitespace string",
}, { tpl: `{{ mustToDuration " " }}`,
name: "mustToDuration unsupported type", }, {
tpl: `{{ mustToDuration . }}`, name: "mustToDuration unsupported type",
vars: []int{1, 2, 3}, tpl: `{{ mustToDuration . }}`,
}, { vars: []int{1, 2, 3},
name: "mustToDuration uint overflow", }, {
tpl: `{{ mustToDuration . }}`, name: "mustToDuration uint overflow",
vars: uint64(math.MaxInt64) + 1, tpl: `{{ mustToDuration . }}`,
}, { vars: uint64(math.MaxInt64) + 1,
name: "mustToDuration int overflow", }, {
tpl: `{{ mustToDuration . }}`, name: "mustToDuration int overflow",
vars: maxDurationSeconds + 1, tpl: `{{ mustToDuration . }}`,
}, { vars: maxDurationSeconds + 1,
name: "mustToDuration int underflow", }, {
tpl: `{{ mustToDuration . }}`, name: "mustToDuration int underflow",
vars: minDurationSeconds - 1, tpl: `{{ mustToDuration . }}`,
}, { vars: minDurationSeconds - 1,
name: "mustToDuration float overflow", }, {
tpl: `{{ mustToDuration . }}`, name: "mustToDuration float overflow",
vars: maxDurationSecondsFloat + 0.5, tpl: `{{ mustToDuration . }}`,
}, { vars: maxDurationSecondsFloat + 0.5,
name: "mustToDuration float underflow", }, {
tpl: `{{ mustToDuration . }}`, name: "mustToDuration float underflow",
vars: minDurationSecondsFloat - 0.5, tpl: `{{ mustToDuration . }}`,
}, { vars: minDurationSecondsFloat - 0.5,
name: "mustToDuration NaN", }, {
tpl: `{{ mustToDuration . }}`, name: "mustToDuration NaN",
vars: math.NaN(), tpl: `{{ mustToDuration . }}`,
}, { vars: math.NaN(),
name: "mustToDuration Inf", }, {
tpl: `{{ mustToDuration . }}`, name: "mustToDuration Inf",
vars: math.Inf(-1), tpl: `{{ mustToDuration . }}`,
}, vars: math.Inf(-1),
},
} }
for _, tt := range mustErrTests { for _, tt := range mustErrTests {

@ -165,7 +165,6 @@ func (g *OCIGetter) newRegistryClient() (*registry.Client, error) {
} }
client, err := registry.NewClient(opts...) client, err := registry.NewClient(opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -19,7 +19,6 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"net/url" "net/url"
"helm.sh/helm/v4/internal/plugin" "helm.sh/helm/v4/internal/plugin"

@ -17,7 +17,6 @@ package getter
import ( import (
"context" "context"
"testing" "testing"
"time" "time"

@ -127,8 +127,10 @@ const (
FieldValidationDirectiveStrict FieldValidationDirective = "Strict" FieldValidationDirectiveStrict FieldValidationDirective = "Strict"
) )
type CreateApplyFunc func(target *resource.Info) error type (
type UpdateApplyFunc func(original, target *resource.Info) error CreateApplyFunc func(target *resource.Info) error
UpdateApplyFunc func(original, target *resource.Info) error
)
func init() { func init() {
// Add CRDs to the scheme. They are missing by default. // Add CRDs to the scheme. They are missing by default.

@ -2260,7 +2260,8 @@ metadata:
} }
func createManifest(t *testing.T, manifest string, func createManifest(t *testing.T, manifest string,
fakeMapper meta.RESTMapper, fakeClient *dynamicfake.FakeDynamicClient) { fakeMapper meta.RESTMapper, fakeClient *dynamicfake.FakeDynamicClient,
) {
t.Helper() t.Helper()
m := make(map[string]any) m := make(map[string]any)

@ -28,8 +28,10 @@ import (
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
) )
var k8sNativeScheme *runtime.Scheme var (
var k8sNativeSchemeOnce sync.Once k8sNativeScheme *runtime.Scheme
k8sNativeSchemeOnce sync.Once
)
// AsVersioned converts the given info into a runtime.Object with the correct // AsVersioned converts the given info into a runtime.Object with the correct
// group and version set // group and version set
@ -41,7 +43,7 @@ func AsVersioned(info *resource.Info) runtime.Object {
// RESTMapping. If no mapping is provided, the default schema versioner is used // RESTMapping. If no mapping is provided, the default schema versioner is used
func convertWithMapper(obj runtime.Object, mapping *meta.RESTMapping) runtime.Object { func convertWithMapper(obj runtime.Object, mapping *meta.RESTMapping) runtime.Object {
s := kubernetesNativeScheme() s := kubernetesNativeScheme()
var gv = runtime.GroupVersioner(schema.GroupVersions(s.PrioritizedVersionsAllGroups())) gv := runtime.GroupVersioner(schema.GroupVersions(s.PrioritizedVersionsAllGroups()))
if mapping != nil { if mapping != nil {
gv = mapping.GroupVersionKind.GroupVersion() gv = mapping.GroupVersionKind.GroupVersion()
} }

@ -219,7 +219,6 @@ func (s *Signatory) ClearSign(archiveData []byte, filename string, metadataBytes
} }
_, err = io.Copy(w, b) _, err = io.Copy(w, b)
if err != nil { if err != nil {
// NB: We intentionally don't call `w.Close()` here! `w.Close()` is the method which // NB: We intentionally don't call `w.Close()` here! `w.Close()` is the method which
// actually does the PGP signing, and therefore is the part which uses the private key. // actually does the PGP signing, and therefore is the part which uses the private key.

@ -329,7 +329,6 @@ func ensureTLSConfig(client *auth.Client, setConfig *tls.Config) (*tls.Config, e
func LoginOptInsecure(insecure bool) LoginOption { func LoginOptInsecure(insecure bool) LoginOption {
return func(o *loginOperation) { return func(o *loginOperation) {
tlsConfig, err := ensureTLSConfig(o.client.authorizer, nil) tlsConfig, err := ensureTLSConfig(o.client.authorizer, nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -909,7 +908,8 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (string, *ur
// tagManifest prepares and tags a manifest in memory storage // tagManifest prepares and tags a manifest in memory storage
func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store, func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store,
configDescriptor ocispec.Descriptor, layers []ocispec.Descriptor, configDescriptor ocispec.Descriptor, layers []ocispec.Descriptor,
ociAnnotations map[string]string, parsedRef reference) (ocispec.Descriptor, error) { ociAnnotations map[string]string, parsedRef reference,
) (ocispec.Descriptor, error) {
manifest := ocispec.Manifest{ manifest := ocispec.Manifest{
Versioned: specs.Versioned{SchemaVersion: 2}, Versioned: specs.Versioned{SchemaVersion: 2},
Config: configDescriptor, Config: configDescriptor,

@ -40,6 +40,7 @@ func (suite *RegistryScopeTestSuite) SetupSuite() {
// when the registry itself was reached over https. // when the registry itself was reached over https.
setup(&suite.TestRegistry, false, false, "token") setup(&suite.TestRegistry, false, false, "token")
} }
func (suite *RegistryScopeTestSuite) TearDownSuite() { func (suite *RegistryScopeTestSuite) TearDownSuite() {
teardown(&suite.TestRegistry) teardown(&suite.TestRegistry)
os.RemoveAll(suite.WorkspaceDir) os.RemoveAll(suite.WorkspaceDir)

@ -77,7 +77,7 @@ func (a *v1Accessor) Version() int {
} }
func (a *v1Accessor) Hooks() []Hook { func (a *v1Accessor) Hooks() []Hook {
var hooks = make([]Hook, len(a.rel.Hooks)) hooks := make([]Hook, len(a.rel.Hooks))
for i, h := range a.rel.Hooks { for i, h := range a.rel.Hooks {
hooks[i] = h hooks[i] = h
} }
@ -141,7 +141,7 @@ func (a *v2Accessor) Version() int {
} }
func (a *v2Accessor) Hooks() []Hook { func (a *v2Accessor) Hooks() []Hook {
var hooks = make([]Hook, len(a.rel.Hooks)) hooks := make([]Hook, len(a.rel.Hooks))
for i, h := range a.rel.Hooks { for i, h := range a.rel.Hooks {
hooks[i] = h hooks[i] = h
} }

@ -22,8 +22,10 @@ import (
type ApplyMethod string type ApplyMethod string
const ApplyMethodClientSideApply ApplyMethod = "csa" const (
const ApplyMethodServerSideApply ApplyMethod = "ssa" ApplyMethodClientSideApply ApplyMethod = "csa"
ApplyMethodServerSideApply ApplyMethod = "ssa"
)
// Release describes a deployment of a chart, together with the chart // Release describes a deployment of a chart, together with the chart
// and the variables used to deploy that chart. // and the variables used to deploy that chart.

@ -61,7 +61,8 @@ metadata:
annotations: annotations:
"helm.sh/hook": post-install "helm.sh/hook": post-install
`, `,
}, { },
{
name: []string{"third"}, name: []string{"third"},
path: "three", path: "three",
kind: []string{"ReplicaSet"}, kind: []string{"ReplicaSet"},
@ -73,7 +74,8 @@ metadata:
annotations: annotations:
"helm.sh/hook": no-such-hook "helm.sh/hook": no-such-hook
`, `,
}, { },
{
name: []string{"fourth"}, name: []string{"fourth"},
path: "four", path: "four",
kind: []string{"Pod"}, kind: []string{"Pod"},
@ -84,7 +86,8 @@ metadata:
name: fourth name: fourth
annotations: annotations:
nothing: here`, nothing: here`,
}, { },
{
name: []string{"fifth"}, name: []string{"fifth"},
path: "five", path: "five",
kind: []string{"ReplicaSet"}, kind: []string{"ReplicaSet"},
@ -96,14 +99,16 @@ metadata:
annotations: annotations:
"helm.sh/hook": post-delete, post-install "helm.sh/hook": post-delete, post-install
`, `,
}, { },
{
// Regression test: files with an underscore in the base name should be skipped. // Regression test: files with an underscore in the base name should be skipped.
name: []string{"sixth"}, name: []string{"sixth"},
path: "six/_six", path: "six/_six",
kind: []string{"ReplicaSet"}, kind: []string{"ReplicaSet"},
hooks: map[string][]release.HookEvent{"sixth": nil}, hooks: map[string][]release.HookEvent{"sixth": nil},
manifest: `invalid manifest`, // This will fail if partial is not skipped. manifest: `invalid manifest`, // This will fail if partial is not skipped.
}, { },
{
// Regression test: files with no content should be skipped. // Regression test: files with no content should be skipped.
name: []string{"seventh"}, name: []string{"seventh"},
path: "seven", path: "seven",

@ -424,7 +424,6 @@ func TestIndexAdd(t *testing.T) {
baseURL string baseURL string
digest string digest string
}{ }{
{&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"}, {&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"},
{&chart.Metadata{APIVersion: "v2", Name: "alpine", Version: "0.1.0"}, "/home/charts/alpine-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"}, {&chart.Metadata{APIVersion: "v2", Name: "alpine", Version: "0.1.0"}, "/home/charts/alpine-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"},
{&chart.Metadata{APIVersion: "v2", Name: "deis", Version: "0.1.0"}, "/home/charts/deis-0.1.0.tgz", "http://example.com/charts/", "sha256:1234567890"}, {&chart.Metadata{APIVersion: "v2", Name: "deis", Version: "0.1.0"}, "/home/charts/deis-0.1.0.tgz", "http://example.com/charts/", "sha256:1234567890"},
@ -536,6 +535,7 @@ entries:
home: https://github.com/something/else home: https://github.com/something/else
digest: "sha256:1234567890abcdef" digest: "sha256:1234567890abcdef"
` `
var indexWithDuplicatesInLastChartDeps = ` var indexWithDuplicatesInLastChartDeps = `
apiVersion: v1 apiVersion: v1
entries: entries:

@ -137,15 +137,17 @@ func TestUpdateRepository(t *testing.T) {
}, },
) )
newRepoName := "sample" newRepoName := "sample"
sampleRepository.Update(&Entry{Name: newRepoName, sampleRepository.Update(&Entry{
URL: "https://example.com/sample", Name: newRepoName,
URL: "https://example.com/sample",
}) })
assert.Truef(t, sampleRepository.Has(newRepoName), "expected repository %s not found", newRepoName) assert.Truef(t, sampleRepository.Has(newRepoName), "expected repository %s not found", newRepoName)
repoCount := len(sampleRepository.Repositories) repoCount := len(sampleRepository.Repositories)
sampleRepository.Update(&Entry{Name: newRepoName, sampleRepository.Update(&Entry{
URL: "https://example.com/sample", Name: newRepoName,
URL: "https://example.com/sample",
}) })
assert.Lenf(t, sampleRepository.Repositories, repoCount, "invalid number of repositories found %d, expected number of repositories %d", len(sampleRepository.Repositories), repoCount) assert.Lenf(t, sampleRepository.Repositories, repoCount, "invalid number of repositories found %d, expected number of repositories %d", len(sampleRepository.Repositories), repoCount)

@ -23,7 +23,7 @@ import (
) )
func TestLabelsMatch(t *testing.T) { func TestLabelsMatch(t *testing.T) {
var tests = []struct { tests := []struct {
desc string desc string
set1 labels set1 labels
set2 labels set2 labels

@ -34,7 +34,7 @@ func TestMemoryName(t *testing.T) {
} }
func TestMemoryCreate(t *testing.T) { func TestMemoryCreate(t *testing.T) {
var tests = []struct { tests := []struct {
desc string desc string
rls *rspb.Release rls *rspb.Release
err bool err bool
@ -76,7 +76,7 @@ func TestMemoryCreate(t *testing.T) {
} }
func TestMemoryGet(t *testing.T) { func TestMemoryGet(t *testing.T) {
var tests = []struct { tests := []struct {
desc string desc string
key string key string
namespace string namespace string
@ -133,7 +133,7 @@ func TestMemoryList(t *testing.T) {
} }
func TestMemoryQuery(t *testing.T) { func TestMemoryQuery(t *testing.T) {
var tests = []struct { tests := []struct {
desc string desc string
xlen int xlen int
namespace string namespace string
@ -164,7 +164,7 @@ func TestMemoryQuery(t *testing.T) {
} }
func TestMemoryUpdate(t *testing.T) { func TestMemoryUpdate(t *testing.T) {
var tests = []struct { tests := []struct {
desc string desc string
key string key string
rls *rspb.Release rls *rspb.Release
@ -215,7 +215,7 @@ func TestMemoryUpdate(t *testing.T) {
} }
func TestMemoryDelete(t *testing.T) { func TestMemoryDelete(t *testing.T) {
var tests = []struct { tests := []struct {
desc string desc string
key string key string
namespace string namespace string

@ -31,7 +31,7 @@ func TestRecordsAdd(t *testing.T) {
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
}) })
var tests = []struct { tests := []struct {
desc string desc string
key string key string
ok bool ok bool
@ -62,7 +62,7 @@ func TestRecordsAdd(t *testing.T) {
} }
func TestRecordsRemove(t *testing.T) { func TestRecordsRemove(t *testing.T) {
var tests = []struct { tests := []struct {
desc string desc string
key string key string
ok bool ok bool
@ -112,7 +112,7 @@ func TestRecordsGet(t *testing.T) {
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
}) })
var tests = []struct { tests := []struct {
desc string desc string
key string key string
rec *record rec *record
@ -141,7 +141,7 @@ func TestRecordsIndex(t *testing.T) {
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
}) })
var tests = []struct { tests := []struct {
desc string desc string
key string key string
sort int sort int
@ -170,7 +170,7 @@ func TestRecordsExists(t *testing.T) {
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
}) })
var tests = []struct { tests := []struct {
desc string desc string
key string key string
ok bool ok bool
@ -199,7 +199,7 @@ func TestRecordsReplace(t *testing.T) {
newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)), newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", common.StatusDeployed)),
}) })
var tests = []struct { tests := []struct {
desc string desc string
key string key string
rec *record rec *record

@ -53,8 +53,10 @@ const postgreSQLDialect = "postgres"
// SQLDriverName is the string name of this driver. // SQLDriverName is the string name of this driver.
const SQLDriverName = "SQL" const SQLDriverName = "SQL"
const sqlReleaseTableName = "releases_v1" const (
const sqlCustomLabelsTableName = "custom_labels_v1" sqlReleaseTableName = "releases_v1"
sqlCustomLabelsTableName = "custom_labels_v1"
)
const ( const (
sqlReleaseTableKeyColumn = "key" sqlReleaseTableKeyColumn = "key"
@ -359,7 +361,7 @@ func (s *SQL) List(filter func(release.Releaser) bool) ([]release.Releaser, erro
return nil, err return nil, err
} }
var records = []SQLReleaseWrapper{} records := []SQLReleaseWrapper{}
if err := s.db.Select(&records, query, args...); err != nil { if err := s.db.Select(&records, query, args...); err != nil {
s.Logger().Debug("failed to list", slog.Any("error", err)) s.Logger().Debug("failed to list", slog.Any("error", err))
return nil, err return nil, err
@ -424,7 +426,7 @@ func (s *SQL) Query(labels map[string]string) ([]release.Releaser, error) {
return nil, err return nil, err
} }
var records = []SQLReleaseWrapper{} records := []SQLReleaseWrapper{}
if err := s.db.Select(&records, query, args...); err != nil { if err := s.db.Select(&records, query, args...); err != nil {
s.Logger().Debug("failed to query with labels", slog.Any("error", err)) s.Logger().Debug("failed to query with labels", slog.Any("error", err))
return nil, err return nil, err
@ -556,7 +558,6 @@ func (s *SQL) Create(key string, rel release.Releaser) error {
k, k,
v, v,
).ToSql() ).ToSql()
if err != nil { if err != nil {
defer transaction.Rollback() defer transaction.Rollback()
s.Logger().Debug("failed to build insert query", slog.Any("error", err)) s.Logger().Debug("failed to build insert query", slog.Any("error", err))
@ -603,7 +604,6 @@ func (s *SQL) Update(key string, rel release.Releaser) error {
Where(sq.Eq{sqlReleaseTableKeyColumn: key}). Where(sq.Eq{sqlReleaseTableKeyColumn: key}).
Where(sq.Eq{sqlReleaseTableNamespaceColumn: namespace}). Where(sq.Eq{sqlReleaseTableNamespaceColumn: namespace}).
ToSql() ToSql()
if err != nil { if err != nil {
s.Logger().Debug("failed to build update query", slog.Any("error", err)) s.Logger().Debug("failed to build update query", slog.Any("error", err))
return err return err
@ -681,7 +681,6 @@ func (s *SQL) Delete(key string) (release.Releaser, error) {
Where(sq.Eq{sqlCustomLabelsTableReleaseKeyColumn: key}). Where(sq.Eq{sqlCustomLabelsTableReleaseKeyColumn: key}).
Where(sq.Eq{sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace}). Where(sq.Eq{sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace}).
ToSql() ToSql()
if err != nil { if err != nil {
s.Logger().Debug("failed to build delete Labels query", slog.Any("error", err)) s.Logger().Debug("failed to build delete Labels query", slog.Any("error", err))
return nil, err return nil, err
@ -695,14 +694,16 @@ func (s *SQL) getReleaseCustomLabels(key string, _ string) (map[string]string, e
query, args, err := s.statementBuilder. query, args, err := s.statementBuilder.
Select(sqlCustomLabelsTableKeyColumn, sqlCustomLabelsTableValueColumn). Select(sqlCustomLabelsTableKeyColumn, sqlCustomLabelsTableValueColumn).
From(sqlCustomLabelsTableName). From(sqlCustomLabelsTableName).
Where(sq.Eq{sqlCustomLabelsTableReleaseKeyColumn: key, Where(sq.Eq{
sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace}). sqlCustomLabelsTableReleaseKeyColumn: key,
sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace,
}).
ToSql() ToSql()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var labelsList = []SQLReleaseCustomLabelWrapper{} labelsList := []SQLReleaseCustomLabelWrapper{}
if err := s.db.Select(&labelsList, query, args...); err != nil { if err := s.db.Select(&labelsList, query, args...); err != nil {
return nil, err return nil, err
} }

@ -38,7 +38,7 @@ func TestIsSystemLabel(t *testing.T) {
} }
func TestFilterSystemLabels(t *testing.T) { func TestFilterSystemLabels(t *testing.T) {
var tests = [][2]map[string]string{ tests := [][2]map[string]string{
{nil, map[string]string{}}, {nil, map[string]string{}},
{map[string]string{}, map[string]string{}}, {map[string]string{}, map[string]string{}},
{map[string]string{ {map[string]string{
@ -73,7 +73,7 @@ func TestFilterSystemLabels(t *testing.T) {
} }
func TestContainsSystemLabels(t *testing.T) { func TestContainsSystemLabels(t *testing.T) {
var tests = []struct { tests := []struct {
input map[string]string input map[string]string
output bool output bool
}{ }{

@ -137,7 +137,7 @@ func TestStorageList(t *testing.T) {
require.NoError(t, storage.Create(rls6), "Storing release 'rls6'") require.NoError(t, storage.Create(rls6), "Storing release 'rls6'")
} }
var listTests = []struct { listTests := []struct {
Description string Description string
NumExpected int NumExpected int
ListFunc func() ([]release.Releaser, error) ListFunc func() ([]release.Releaser, error)
@ -263,24 +263,31 @@ type MaxHistoryMockDriver struct {
func NewMaxHistoryMockDriver(d driver.Driver) *MaxHistoryMockDriver { func NewMaxHistoryMockDriver(d driver.Driver) *MaxHistoryMockDriver {
return &MaxHistoryMockDriver{Driver: d} return &MaxHistoryMockDriver{Driver: d}
} }
func (d *MaxHistoryMockDriver) Create(key string, rls release.Releaser) error { func (d *MaxHistoryMockDriver) Create(key string, rls release.Releaser) error {
return d.Driver.Create(key, rls) return d.Driver.Create(key, rls)
} }
func (d *MaxHistoryMockDriver) Update(key string, rls release.Releaser) error { func (d *MaxHistoryMockDriver) Update(key string, rls release.Releaser) error {
return d.Driver.Update(key, rls) return d.Driver.Update(key, rls)
} }
func (d *MaxHistoryMockDriver) Delete(_ string) (release.Releaser, error) { func (d *MaxHistoryMockDriver) Delete(_ string) (release.Releaser, error) {
return nil, errMaxHistoryMockDriverSomethingHappened return nil, errMaxHistoryMockDriverSomethingHappened
} }
func (d *MaxHistoryMockDriver) Get(key string) (release.Releaser, error) { func (d *MaxHistoryMockDriver) Get(key string) (release.Releaser, error) {
return d.Driver.Get(key) return d.Driver.Get(key)
} }
func (d *MaxHistoryMockDriver) List(filter func(release.Releaser) bool) ([]release.Releaser, error) { func (d *MaxHistoryMockDriver) List(filter func(release.Releaser) bool) ([]release.Releaser, error) {
return d.Driver.List(filter) return d.Driver.List(filter)
} }
func (d *MaxHistoryMockDriver) Query(labels map[string]string) ([]release.Releaser, error) { func (d *MaxHistoryMockDriver) Query(labels map[string]string) ([]release.Releaser, error) {
return d.Driver.Query(labels) return d.Driver.Query(labels)
} }
func (d *MaxHistoryMockDriver) Name() string { func (d *MaxHistoryMockDriver) Name() string {
return d.Driver.Name() return d.Driver.Name()
} }

@ -316,7 +316,8 @@ func TestParseLiteralInto(t *testing.T) {
"outer": map[string]any{ "outer": map[string]any{
"inner1": "value1,outer.inner3=value3,outer.inner4=4", "inner1": "value1,outer.inner3=value3,outer.inner4=4",
"inner2": "value2", "inner2": "value2",
}}, },
},
err: false, err: false,
}, },
{ {

@ -439,7 +439,8 @@ func TestParseInto(t *testing.T) {
"inner2": "value2", "inner2": "value2",
"inner3": "value3", "inner3": "value3",
"inner4": 4, "inner4": 4,
}}, },
},
err: false, err: false,
}, },
{ {

@ -114,12 +114,15 @@ verifySupported() {
checkDesiredVersion() { checkDesiredVersion() {
if [ "x$DESIRED_VERSION" == "x" ]; then if [ "x$DESIRED_VERSION" == "x" ]; then
# Get tag from release URL # Get tag from release URL
local latest_release_url="https://get.helm.sh/helm3-latest-version" # Cache behavior is provider-specific, so this mitigation is best effort.
# The current CDN does not revalidate on the request no-cache directive,
# so use a unique query while retaining no-cache for compliant intermediaries.
local latest_release_url="https://get.helm.sh/helm3-latest-version?ts=$(date +%s)"
local latest_release_response="" local latest_release_response=""
if [ "${HAS_CURL}" == "true" ]; then if [ "${HAS_CURL}" == "true" ]; then
latest_release_response=$( curl -L --silent --show-error --fail "$latest_release_url" 2>&1 || true ) latest_release_response=$( curl -L --silent --show-error --fail --header "Cache-Control: no-cache" "$latest_release_url" 2>&1 || true )
elif [ "${HAS_WGET}" == "true" ]; then elif [ "${HAS_WGET}" == "true" ]; then
latest_release_response=$( wget "$latest_release_url" -q -O - 2>&1 || true ) latest_release_response=$( wget "$latest_release_url" --header="Cache-Control: no-cache" -q -O - 2>&1 || true )
fi fi
TAG=$( echo "$latest_release_response" | grep '^v[0-9]' ) TAG=$( echo "$latest_release_response" | grep '^v[0-9]' )
if [ "x$TAG" == "x" ]; then if [ "x$TAG" == "x" ]; then

@ -114,12 +114,15 @@ verifySupported() {
checkDesiredVersion() { checkDesiredVersion() {
if [ "x$DESIRED_VERSION" == "x" ]; then if [ "x$DESIRED_VERSION" == "x" ]; then
# Get tag from release URL # Get tag from release URL
local latest_release_url="https://get.helm.sh/helm4-latest-version" # Cache behavior is provider-specific, so this mitigation is best effort.
# The current CDN does not revalidate on the request no-cache directive,
# so use a unique query while retaining no-cache for compliant intermediaries.
local latest_release_url="https://get.helm.sh/helm4-latest-version?ts=$(date +%s)"
local latest_release_response="" local latest_release_response=""
if [ "${HAS_CURL}" == "true" ]; then if [ "${HAS_CURL}" == "true" ]; then
latest_release_response=$( curl -L --silent --show-error --fail "$latest_release_url" 2>&1 || true ) latest_release_response=$( curl -L --silent --show-error --fail --header "Cache-Control: no-cache" "$latest_release_url" 2>&1 || true )
elif [ "${HAS_WGET}" == "true" ]; then elif [ "${HAS_WGET}" == "true" ]; then
latest_release_response=$( wget "$latest_release_url" -q -O - 2>&1 || true ) latest_release_response=$( wget "$latest_release_url" --header="Cache-Control: no-cache" -q -O - 2>&1 || true )
fi fi
TAG=$( echo "$latest_release_response" | grep '^v[0-9]' ) TAG=$( echo "$latest_release_response" | grep '^v[0-9]' )
if [ "x$TAG" == "x" ]; then if [ "x$TAG" == "x" ]; then

Loading…
Cancel
Save