diff --git a/api/v1/helmrepository_types.go b/api/v1/helmrepository_types.go index 1c19064a5..e7282ebda 100644 --- a/api/v1/helmrepository_types.go +++ b/api/v1/helmrepository_types.go @@ -28,9 +28,6 @@ import ( const ( // HelmRepositoryKind is the string representation of a HelmRepository. HelmRepositoryKind = "HelmRepository" - // HelmRepositoryURLIndexKey is the key used for indexing HelmRepository - // objects by their HelmRepositorySpec.URL. - HelmRepositoryURLIndexKey = ".metadata.helmRepositoryURL" // HelmRepositoryTypeDefault is the default HelmRepository type. // It is used when no type is specified and corresponds to a Helm repository. HelmRepositoryTypeDefault = "default" diff --git a/api/v1/source.go b/api/v1/source.go index d879f6034..790eab7ee 100644 --- a/api/v1/source.go +++ b/api/v1/source.go @@ -24,12 +24,6 @@ import ( "github.com/fluxcd/pkg/apis/meta" ) -const ( - // SourceIndexKey is the key used for indexing objects based on their - // referenced Source. - SourceIndexKey string = ".metadata.source" -) - // Source interface must be supported by all API types. // Source is the interface that provides generic access to the Artifact and // interval. It must be supported by all kinds of the source.toolkit.fluxcd.io diff --git a/go.mod b/go.mod index 3affaa268..f961bb637 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3 github.com/Masterminds/semver/v3 v3.4.0 - github.com/cyphar/filepath-securejoin v0.6.0 + github.com/cyphar/filepath-securejoin v0.6.1 github.com/distribution/distribution/v3 v3.0.0 github.com/docker/cli v28.5.0+incompatible github.com/docker/go-units v0.5.0 @@ -27,10 +27,9 @@ require ( github.com/fluxcd/pkg/artifact v0.5.0 github.com/fluxcd/pkg/auth v0.33.0 github.com/fluxcd/pkg/cache v0.12.0 - github.com/fluxcd/pkg/git v0.38.0 - github.com/fluxcd/pkg/git/gogit v0.42.0 - github.com/fluxcd/pkg/gittestserver v0.22.0 - github.com/fluxcd/pkg/helmtestserver v0.33.0 + github.com/fluxcd/pkg/git v0.39.0 + github.com/fluxcd/pkg/gittestserver v0.23.0 + github.com/fluxcd/pkg/helmtestserver v0.34.0 github.com/fluxcd/pkg/http/transport v0.7.0 github.com/fluxcd/pkg/masktoken v0.8.0 github.com/fluxcd/pkg/oci v0.58.0 @@ -42,8 +41,8 @@ require ( github.com/fluxcd/pkg/version v0.11.0 github.com/fluxcd/source-controller/api v1.7.0 github.com/foxcpp/go-mockdns v1.1.0 - github.com/go-git/go-billy/v5 v5.6.2 - github.com/go-git/go-git/v5 v5.16.3 + github.com/go-git/go-billy/v5 v5.7.0 + github.com/go-git/go-git/v5 v5.16.4 github.com/go-logr/logr v1.4.3 github.com/google/go-containerregistry v0.20.6 github.com/google/go-containerregistry/pkg/authn/k8schain v0.0.0-20250613215107-59a4b8593039 @@ -51,7 +50,7 @@ require ( github.com/minio/minio-go/v7 v7.0.95 github.com/notaryproject/notation-core-go v1.3.0 github.com/notaryproject/notation-go v1.3.2 - github.com/onsi/gomega v1.38.2 + github.com/onsi/gomega v1.38.3 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 github.com/ory/dockertest/v3 v3.12.0 @@ -62,11 +61,11 @@ require ( github.com/sigstore/sigstore v1.9.5 github.com/sirupsen/logrus v1.9.3 github.com/spf13/pflag v1.0.10 - golang.org/x/crypto v0.44.0 + golang.org/x/crypto v0.46.0 golang.org/x/oauth2 v0.33.0 - golang.org/x/sync v0.18.0 + golang.org/x/sync v0.19.0 google.golang.org/api v0.256.0 - helm.sh/helm/v3 v3.19.2 + helm.sh/helm/v4 v4.0.4 k8s.io/api v0.34.2 k8s.io/apimachinery v0.34.2 k8s.io/client-go v0.34.2 @@ -144,7 +143,6 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 // indirect github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect github.com/buildkite/agent/v3 v3.98.2 // indirect github.com/buildkite/go-pipeline v0.13.3 // indirect @@ -159,11 +157,7 @@ require ( github.com/cloudflare/circl v1.6.1 // indirect github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect - github.com/containerd/containerd v1.7.29 // indirect github.com/containerd/continuity v0.4.5 // indirect - github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect github.com/coreos/go-oidc/v3 v3.16.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect @@ -181,6 +175,7 @@ require ( github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect @@ -188,6 +183,7 @@ require ( github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/extism/go-sdk v1.7.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/gitkit v0.6.0 // indirect @@ -232,7 +228,7 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20250225234217-098045d5e61f // indirect github.com/google/go-github/v72 v72.0.0 // indirect - github.com/google/go-github/v75 v75.0.0 // indirect + github.com/google/go-github/v81 v81.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect @@ -245,13 +241,12 @@ require ( github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/huandu/xstrings v1.5.0 // indirect + github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect github.com/in-toto/attestation v1.1.1 // indirect github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -344,6 +339,8 @@ require ( github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect + github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect + github.com/tetratelabs/wazero v1.9.0 // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/theupdateframework/go-tuf v0.7.0 // indirect github.com/theupdateframework/go-tuf/v2 v2.1.1 // indirect @@ -393,13 +390,13 @@ require ( go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/mod v0.30.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.38.0 // indirect + golang.org/x/tools v0.39.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect diff --git a/go.sum b/go.sum index 9997f7756..289a84508 100644 --- a/go.sum +++ b/go.sum @@ -227,8 +227,6 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bradleyfalzon/ghinstallation/v2 v2.17.0 h1:SmbUK/GxpAspRjSQbB6ARvH+ArzlNzTtHydNyXUQ6zg= -github.com/bradleyfalzon/ghinstallation/v2 v2.17.0/go.mod h1:vuD/xvJT9Y+ZVZRv4HQ42cMyPFIYqpc7AbB4Gvt/DlY= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= @@ -275,16 +273,8 @@ github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUo github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be h1:J5BL2kskAlV9ckgEsNQXscjIaLiOYiZ75d4e94E6dcQ= github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be/go.mod h1:mk5IQ+Y0ZeO87b858TlA645sVcEcbiX6YqP98kt+7+w= -github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE= -github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow= @@ -296,8 +286,8 @@ github.com/creack/pty v1.1.19 h1:tUN6H7LWqNx4hQVxomd0CVsDwaDr9gaRQaI4GpSmrsA= github.com/creack/pty v1.1.19/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= -github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= -github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= +github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -339,6 +329,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE= +github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= @@ -364,6 +356,8 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/extism/go-sdk v1.7.1 h1:lWJos6uY+tRFdlIHR+SJjwFDApY7OypS/2nMhiVQ9Sw= +github.com/extism/go-sdk v1.7.1/go.mod h1:IT+Xdg5AZM9hVtpFUA+uZCJMge/hbvshl8bwzLtFyKA= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -384,14 +378,12 @@ github.com/fluxcd/pkg/auth v0.33.0 h1:3ccwqpBr8uWEQgl15b7S0PwJ9EgtcKObg4J1jnaof2 github.com/fluxcd/pkg/auth v0.33.0/go.mod h1:ZAFC8pNZxhe+7RV2cQO1K9X62HM8BbRBnCE118oY/0A= github.com/fluxcd/pkg/cache v0.12.0 h1:mabABT3jIfuo84VbIW+qvfqMZ7PbM5tXQgQvA2uo2rc= github.com/fluxcd/pkg/cache v0.12.0/go.mod h1:HL/9cgBmwCdKIr3JH57rxrGdb7rOgX5Z1eJlHsaV1vE= -github.com/fluxcd/pkg/git v0.38.0 h1:fFH2PkL+VCtQ1aJec/6l3Wq5fQG1w02HHKfVY+gz1S4= -github.com/fluxcd/pkg/git v0.38.0/go.mod h1:PHilCGIM2t10CJ++yK4SFHIcBAXqMk14XcwZ/Rqw23I= -github.com/fluxcd/pkg/git/gogit v0.42.0 h1:AaaMNbuzO0lARhI2SoqLKkQhEN6QYE0fT5VG9oyMUTc= -github.com/fluxcd/pkg/git/gogit v0.42.0/go.mod h1:DxH7DalONwiZ29odi7TjmLlhO9xsO7svy9GPGgHsHsc= -github.com/fluxcd/pkg/gittestserver v0.22.0 h1:LkOmXAoYB/OoVDMhneeyqUIGvSCb9fJtcFIAFkNGpzc= -github.com/fluxcd/pkg/gittestserver v0.22.0/go.mod h1:kFBmc9akpmdY5EU5d0MuSj2eHgq6ebkmEKf6MEUyTIg= -github.com/fluxcd/pkg/helmtestserver v0.33.0 h1:3X7V5OCxdgJZ9rpefXj0nxLNE+dK05Fst2wSm+Q1HAw= -github.com/fluxcd/pkg/helmtestserver v0.33.0/go.mod h1:rv45BF8VicrC1RGMV8VhB3K19I+xyYYMPM23n/B5GNA= +github.com/fluxcd/pkg/git v0.39.0 h1:QydLWcsOso1BkO/ctE6ELlCFkhnGwpF2dUVa+R4aLp0= +github.com/fluxcd/pkg/git v0.39.0/go.mod h1:MPhYH/ir7jr7cgQd75kWPHCGuJBu+sg7jzi0JPTSkKA= +github.com/fluxcd/pkg/gittestserver v0.23.0 h1:o4tb4ic2GHf2xWHTf/07w/wVLFSvbybQ9bdhtWgDnS8= +github.com/fluxcd/pkg/gittestserver v0.23.0/go.mod h1:b+rbFRu8HTtTFJ7mr437nHIO12pEodYqvQ3QUDHgFIQ= +github.com/fluxcd/pkg/helmtestserver v0.34.0 h1:a6toTMPO6WfdohrkYCrs6vwOJK4VXpODLKc5311GawY= +github.com/fluxcd/pkg/helmtestserver v0.34.0/go.mod h1:w9xs7BzjopAV1+wEFi/V/rCDFIs1uvwfobUXWqX7poA= github.com/fluxcd/pkg/http/transport v0.7.0 h1:LbA0qzh1lT6GncWLkN/BjbSMrN8bdFtaa2TqxiIdyzs= github.com/fluxcd/pkg/http/transport v0.7.0/go.mod h1:G3ptGZKlY0PJZsvWCwzV9vKQ90yfP/mKT2/ZdAud9LE= github.com/fluxcd/pkg/lockedfile v0.7.0 h1:tmzW2GeMGuJMiCcVloXVd1vKZ92anm9WGkRgOBpWfRk= @@ -433,12 +425,12 @@ github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8b github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM= +github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8= -github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= +github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y= +github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= @@ -558,8 +550,8 @@ github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-2025022523421 github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20250225234217-098045d5e61f/go.mod h1:ZT74/OE6eosKneM9/LQItNxIMBV6CI5S46EXAnvkTBI= github.com/google/go-github/v72 v72.0.0 h1:FcIO37BLoVPBO9igQQ6tStsv2asG4IPcYFi655PPvBM= github.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg= -github.com/google/go-github/v75 v75.0.0 h1:k7q8Bvg+W5KxRl9Tjq16a9XEgVY1pwuiG5sIL7435Ic= -github.com/google/go-github/v75 v75.0.0/go.mod h1:H3LUJEA1TCrzuUqtdAQniBNwuKiQIqdGKgBo1/M/uqI= +github.com/google/go-github/v81 v81.0.0 h1:hTLugQRxSLD1Yei18fk4A5eYjOGLUBKAl/VCqOfFkZc= +github.com/google/go-github/v81 v81.0.0/go.mod h1:upyjaybucIbBIuxgJS7YLOZGziyvvJ92WX6WEBNE3sM= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -601,7 +593,6 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJr github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= @@ -637,6 +628,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca h1:T54Ema1DU8ngI+aef9ZhAhNGQhcRTrWxVeG07F+c/Rw= +github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/in-toto/attestation v1.1.1 h1:QD3d+oATQ0dFsWoNh5oT0udQ3tUrOsZZ0Fc3tSgWbzI= github.com/in-toto/attestation v1.1.1/go.mod h1:Dcq1zVwA2V7Qin8I7rgOi+i837wEf/mOZwRm047Sjys= github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= @@ -801,14 +794,14 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.25.2 h1:hepmgwx1D+llZleKQDMEvy8vIlCxMGt7W5ZxDjIEhsw= -github.com/onsi/ginkgo/v2 v2.25.2/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= +github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= +github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= -github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM= +github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/open-policy-agent/opa v1.5.1 h1:LTxxBJusMVjfs67W4FoRcnMfXADIGFMzpqnfk6D08Cg= github.com/open-policy-agent/opa v1.5.1/go.mod h1:bYbS7u+uhTI+cxHQIpzvr5hxX0hV7urWtY+38ZtjMgk= github.com/opencontainers/go-digest v1.0.1-0.20220411205349-bde1400a84be h1:f2PlhC9pm5sqpBZFvnAoKj+KzXRzbjFMA+TqXfJdgho= @@ -989,6 +982,10 @@ github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDd github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM= github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= +github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 h1:ZF+QBjOI+tILZjBaFj3HgFonKXUcwgJ4djLb6i42S3Q= +github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834/go.mod h1:m9ymHTgNSEjuxvw8E7WWe4Pl4hZQHXONY8wE6dMLaRk= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg= github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU= github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= @@ -1146,8 +1143,8 @@ golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= @@ -1162,8 +1159,8 @@ 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.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1195,8 +1192,8 @@ golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= @@ -1215,8 +1212,8 @@ golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1255,8 +1252,8 @@ golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1270,8 +1267,8 @@ golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -1284,8 +1281,8 @@ 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.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1304,8 +1301,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1373,8 +1370,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -helm.sh/helm/v3 v3.19.2 h1:psQjaM8aIWrSVEly6PgYtLu/y6MRSmok4ERiGhZmtUY= -helm.sh/helm/v3 v3.19.2/go.mod h1:gX10tB5ErM+8fr7bglUUS/UfTOO8UUTYWIBH1IYNnpE= +helm.sh/helm/v4 v4.0.4 h1:5Lokr7XxCe6IW/NMtdECuAFW/0bTs/2831deUrlKqP8= +helm.sh/helm/v4 v4.0.4/go.mod h1:fMyG9onvVK6HOBjjkzhhHORAsgEWlRMqDY84lvX7GvY= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= diff --git a/internal/controller/bucket_controller.go b/internal/controller/bucket_controller.go index 7fe881be6..dbd163dcb 100644 --- a/internal/controller/bucket_controller.go +++ b/internal/controller/bucket_controller.go @@ -175,11 +175,7 @@ type bucketCredentials struct { // executed serially to perform the complete reconcile of the object. type bucketReconcileFunc func(ctx context.Context, sp *patch.SerialPatcher, obj *sourcev1.Bucket, index *index.Digester, dir string) (sreconcile.Result, error) -func (r *BucketReconciler) SetupWithManager(mgr ctrl.Manager) error { - return r.SetupWithManagerAndOptions(mgr, BucketReconcilerOptions{}) -} - -func (r *BucketReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts BucketReconcilerOptions) error { +func (r *BucketReconciler) SetupWithManager(mgr ctrl.Manager, opts BucketReconcilerOptions) error { r.patchOptions = getPatchOptions(bucketReadyCondition.Owned, r.ControllerName) return ctrl.NewControllerManagedBy(mgr). diff --git a/internal/controller/common_test.go b/internal/controller/common_test.go index d9dcf88c1..717ba2c5d 100644 --- a/internal/controller/common_test.go +++ b/internal/controller/common_test.go @@ -69,13 +69,6 @@ func waitForSourceReadyWithArtifact(ctx context.Context, g *WithT, obj condition waitForSourceReady(ctx, g, obj, true) } -// waitForSourceReadyWithoutArtifact is a generic test helper to wait for an object -// to be ready of any source kind that don't have artifact in status when ready. -func waitForSourceReadyWithoutArtifact(ctx context.Context, g *WithT, obj conditions.Setter) { - g.THelper() - waitForSourceReady(ctx, g, obj, false) -} - // waitForSourceReady is a generic test helper to wait for an object to be // ready of any source kind. func waitForSourceReady(ctx context.Context, g *WithT, obj conditions.Setter, withArtifact bool) { @@ -116,14 +109,6 @@ func testSuspendedObjectDeleteWithArtifact(ctx context.Context, g *WithT, obj co testSuspendedObjectDelete(ctx, g, obj, true) } -// testSuspendedObjectDeleteWithoutArtifact is a generic test helper to test if -// a suspended object can be deleted for objects that don't have artifact in -// status when ready. -func testSuspendedObjectDeleteWithoutArtifact(ctx context.Context, g *WithT, obj conditions.Setter) { - g.THelper() - testSuspendedObjectDelete(ctx, g, obj, false) -} - // testSuspendedObjectDelete is a generic test helper to test if a suspended // object can be deleted. func testSuspendedObjectDelete(ctx context.Context, g *WithT, obj conditions.Setter, withArtifact bool) { diff --git a/internal/controller/gitrepository_controller.go b/internal/controller/gitrepository_controller.go index 1208c8ae0..30c406e71 100644 --- a/internal/controller/gitrepository_controller.go +++ b/internal/controller/gitrepository_controller.go @@ -151,11 +151,7 @@ type GitRepositoryReconcilerOptions struct { // v1.GitRepository (sub)reconcile functions. type gitRepositoryReconcileFunc func(ctx context.Context, sp *patch.SerialPatcher, obj *sourcev1.GitRepository, commit *git.Commit, includes *artifactSet, dir string) (sreconcile.Result, error) -func (r *GitRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error { - return r.SetupWithManagerAndOptions(mgr, GitRepositoryReconcilerOptions{}) -} - -func (r *GitRepositoryReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts GitRepositoryReconcilerOptions) error { +func (r *GitRepositoryReconciler) SetupWithManager(mgr ctrl.Manager, opts GitRepositoryReconcilerOptions) error { r.patchOptions = getPatchOptions(gitRepositoryReadyCondition.Owned, r.ControllerName) r.requeueDependency = opts.DependencyRequeueInterval @@ -328,7 +324,7 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, sp *patch.Seria func (r *GitRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *sourcev1.GitRepository, commit git.Commit, res sreconcile.Result, resErr error) { // Notify successful reconciliation for new artifact, no-op reconciliation // and recovery from any failure. - if r.shouldNotify(oldObj, newObj, res, resErr) { + if r.shouldNotify(newObj, res, resErr) { annotations := map[string]string{ fmt.Sprintf("%s/%s", sourcev1.GroupVersion.Group, eventv1.MetaRevisionKey): newObj.Status.Artifact.Revision, fmt.Sprintf("%s/%s", sourcev1.GroupVersion.Group, eventv1.MetaDigestKey): newObj.Status.Artifact.Digest, @@ -362,7 +358,7 @@ func (r *GitRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *so // notification should be sent. It decides about the final informational // notifications after the reconciliation. Failure notification and in-line // notifications are not handled here. -func (r *GitRepositoryReconciler) shouldNotify(oldObj, newObj *sourcev1.GitRepository, res sreconcile.Result, resErr error) bool { +func (r *GitRepositoryReconciler) shouldNotify(newObj *sourcev1.GitRepository, res sreconcile.Result, resErr error) bool { // Notify for successful reconciliation. if resErr == nil && res == sreconcile.ResultSuccess && newObj.Status.Artifact != nil { return true @@ -595,7 +591,7 @@ func (r *GitRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch conditions.Delete(obj, sourcev1.FetchFailedCondition) // Validate sparse checkout paths after successful checkout. - if err := r.validateSparseCheckoutPaths(ctx, obj, dir); err != nil { + if err := r.validateSparseCheckoutPaths(obj, dir); err != nil { e := serror.NewGeneric( fmt.Errorf("failed to sparse checkout directories : %w", err), sourcev1.GitOperationFailedReason, @@ -1302,7 +1298,7 @@ func gitContentConfigChanged(obj *sourcev1.GitRepository, includes *artifactSet) } // validateSparseCheckoutPaths checks if the sparse checkout paths exist in the cloned repository. -func (r *GitRepositoryReconciler) validateSparseCheckoutPaths(ctx context.Context, obj *sourcev1.GitRepository, dir string) error { +func (r *GitRepositoryReconciler) validateSparseCheckoutPaths(obj *sourcev1.GitRepository, dir string) error { if obj.Spec.SparseCheckout != nil { for _, path := range obj.Spec.SparseCheckout { fullPath := filepath.Join(dir, path) diff --git a/internal/controller/helmchart_controller.go b/internal/controller/helmchart_controller.go index e969bf67a..75a11cb6a 100644 --- a/internal/controller/helmchart_controller.go +++ b/internal/controller/helmchart_controller.go @@ -18,7 +18,6 @@ package controller import ( "context" - "crypto/tls" "encoding/json" "errors" "fmt" @@ -33,15 +32,14 @@ import ( "github.com/notaryproject/notation-go/verifier/trustpolicy" "github.com/opencontainers/go-digest" "github.com/sigstore/cosign/v2/pkg/cosign" - helmgetter "helm.sh/helm/v3/pkg/getter" - helmreg "helm.sh/helm/v3/pkg/registry" - helmrepo "helm.sh/helm/v3/pkg/repo" + helmgetter "helm.sh/helm/v4/pkg/getter" + helmreg "helm.sh/helm/v4/pkg/registry" + helmrepo "helm.sh/helm/v4/pkg/repo/v1" corev1 "k8s.io/api/core/v1" apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - kerrors "k8s.io/apimachinery/pkg/util/errors" kuberecorder "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" ctrl "sigs.k8s.io/controller-runtime" @@ -70,6 +68,7 @@ import ( serror "github.com/fluxcd/source-controller/internal/error" "github.com/fluxcd/source-controller/internal/helm/chart" "github.com/fluxcd/source-controller/internal/helm/getter" + "github.com/fluxcd/source-controller/internal/helm/registry" "github.com/fluxcd/source-controller/internal/helm/repository" soci "github.com/fluxcd/source-controller/internal/oci" scosign "github.com/fluxcd/source-controller/internal/oci/cosign" @@ -132,10 +131,9 @@ type HelmChartReconciler struct { kuberecorder.EventRecorder helper.Metrics - RegistryClientGenerator RegistryClientGeneratorFunc - Storage *storage.Storage - Getters helmgetter.Providers - ControllerName string + Storage *storage.Storage + Getters helmgetter.Providers + ControllerName string Cache *cache.Cache TTL time.Duration @@ -144,16 +142,6 @@ type HelmChartReconciler struct { patchOptions []patch.Option } -// RegistryClientGeneratorFunc is a function that returns a registry client -// and an optional file name. -// The file is used to store the registry client credentials. -// The caller is responsible for deleting the file. -type RegistryClientGeneratorFunc func(tlsConfig *tls.Config, isLogin, insecure bool) (*helmreg.Client, string, error) - -func (r *HelmChartReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { - return r.SetupWithManagerAndOptions(ctx, mgr, HelmChartReconcilerOptions{}) -} - type HelmChartReconcilerOptions struct { RateLimiter workqueue.TypedRateLimiter[reconcile.Request] } @@ -163,14 +151,32 @@ type HelmChartReconcilerOptions struct { // executed serially to perform the complete reconcile of the object. type helmChartReconcileFunc func(ctx context.Context, sp *patch.SerialPatcher, obj *sourcev1.HelmChart, build *chart.Build) (sreconcile.Result, error) -func (r *HelmChartReconciler) SetupWithManagerAndOptions(ctx context.Context, mgr ctrl.Manager, opts HelmChartReconcilerOptions) error { +const ( + // The following index keys were moved from the api/v1 package here + // because they are not really APIs (there's nothing users or SDK + // users can do with them, it's entirely an implementation detail). + + // indexKeyHelmRepositoryURL is used for allowing umbrella HelmChart + // objects to reference remote dependencies that can be resolved to + // HelmRepository objects in the same namespace (so the HelmChart + // controller can use their configuration to access the remote chart, + // e.g. authentication configuration). + indexKeyHelmRepositoryURL = ".metadata.helmRepositoryURL" + + // indexKeyHelmChartSource is used for watching the sources a HelmChart + // can refer to and trigger their reconciliations according to relevant + // events on the watched sources. + indexKeyHelmChartSource = ".metadata.helmChartSource" +) + +func (r *HelmChartReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, opts HelmChartReconcilerOptions) error { r.patchOptions = getPatchOptions(helmChartReadyCondition.Owned, r.ControllerName) - if err := mgr.GetCache().IndexField(ctx, &sourcev1.HelmRepository{}, sourcev1.HelmRepositoryURLIndexKey, + if err := mgr.GetCache().IndexField(ctx, &sourcev1.HelmRepository{}, indexKeyHelmRepositoryURL, r.indexHelmRepositoryByURL); err != nil { return fmt.Errorf("failed setting index fields: %w", err) } - if err := mgr.GetCache().IndexField(ctx, &sourcev1.HelmChart{}, sourcev1.SourceIndexKey, + if err := mgr.GetCache().IndexField(ctx, &sourcev1.HelmChart{}, indexKeyHelmChartSource, r.indexHelmChartBySource); err != nil { return fmt.Errorf("failed setting index fields: %w", err) } @@ -515,7 +521,7 @@ func (r *HelmChartReconciler) reconcileSource(ctx context.Context, sp *patch.Ser // object, and returns early. func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *sourcev1.HelmChart, repo *sourcev1.HelmRepository, b *chart.Build) (sreconcile.Result, error) { - // Used to login with the repository declared provider + // Used to get the client options for the repository ctxTimeout, cancel := context.WithTimeout(ctx, repo.GetTimeout()) defer cancel() @@ -524,7 +530,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * return chartRepoConfigErrorReturn(err, obj) } - clientOpts, certsTmpDir, err := getter.GetClientOpts(ctxTimeout, r.Client, repo, normalizedURL) + clientOpts, err := getter.GetClientOpts(ctxTimeout, r.Client, repo, normalizedURL) if err != nil && !errors.Is(err, getter.ErrDeprecatedTLSConfig) { e := serror.NewGeneric( err, @@ -533,14 +539,6 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) return sreconcile.ResultEmpty, e } - if certsTmpDir != "" { - defer func() { - if err := os.RemoveAll(certsTmpDir); err != nil { - r.eventLogf(ctx, obj, corev1.EventTypeWarning, meta.FailedReason, - "failed to delete temporary certificates directory: %s", err) - } - }() - } getterOpts := clientOpts.GetterOpts @@ -553,11 +551,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * return chartRepoConfigErrorReturn(err, obj) } - // with this function call, we create a temporary file to store the credentials if needed. - // this is needed because otherwise the credentials are stored in ~/.docker/config.json. - // TODO@souleb: remove this once the registry move to Oras v2 - // or rework to enable reusing credentials to avoid the unneccessary handshake operations - registryClient, credentialsFile, err := r.RegistryClientGenerator(clientOpts.TlsConfig, clientOpts.MustLoginToRegistry(), repo.Spec.Insecure) + registryClient, err := registry.NewClient(clientOpts.OCIAuth, clientOpts.TLSConfig, repo.Spec.Insecure) if err != nil { e := serror.NewGeneric( fmt.Errorf("failed to construct Helm client: %w", err), @@ -567,15 +561,6 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * return sreconcile.ResultEmpty, e } - if credentialsFile != "" { - defer func() { - if err := os.Remove(credentialsFile); err != nil { - r.eventLogf(ctx, obj, corev1.EventTypeWarning, meta.FailedReason, - "failed to delete temporary credentials file: %s", err) - } - }() - } - var verifiers []soci.Verifier if obj.Spec.Verify != nil { provider := obj.Spec.Verify.Provider @@ -610,22 +595,9 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * return chartRepoConfigErrorReturn(err, obj) } - // If login options are configured, use them to login to the registry - // The OCIGetter will later retrieve the stored credentials to pull the chart - if clientOpts.MustLoginToRegistry() { - err = ociChartRepo.Login(clientOpts.RegLoginOpts...) - if err != nil { - e := serror.NewGeneric( - fmt.Errorf("failed to login to OCI registry: %w", err), - sourcev1.AuthenticationFailedReason, - ) - conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) - return sreconcile.ResultEmpty, e - } - } chartRepo = ociChartRepo default: - httpChartRepo, err := repository.NewChartRepository(normalizedURL, r.Storage.LocalPath(*repo.GetArtifact()), r.Getters, clientOpts.TlsConfig, getterOpts...) + httpChartRepo, err := repository.NewChartRepository(normalizedURL, r.Storage.LocalPath(*repo.GetArtifact()), r.Getters, clientOpts.TLSConfig, getterOpts...) if err != nil { return chartRepoConfigErrorReturn(err, obj) } @@ -685,6 +657,33 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * ref := chart.RemoteReference{Name: obj.Spec.Chart, Version: obj.Spec.Version} build, err := cb.Build(ctx, ref, util.TempPathForObj("", ".tgz", obj), opts) if err != nil { + var containsAuthError bool + for _, reason := range []string{ + // basic auth + "401", "unauthorized", "authentication required", + // TLS + "tls", "unknown certificate authority", + } { + if strings.Contains(err.Error(), reason) { + containsAuthError = true + break + } + } + if containsAuthError { + e := serror.NewGeneric( + // Here we use %s instead of %w to avoid wrapping the error. + // We do this because the error here is a BuildError. But we + // just detected that it is actually an authentication error. + // We do this to avoid confusion in the higher levels of the + // reconciliation, which would see a BuildError instead of + // an authentication error and mark the FetchFailedCondition + // incorrectly. + fmt.Errorf("failed to authenticate with the Helm repository: %s", err.Error()), + sourcev1.AuthenticationFailedReason, + ) + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, e.Reason, "%s", e) + return sreconcile.ResultEmpty, e + } return sreconcile.ResultEmpty, err } @@ -1015,11 +1014,11 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont } } - // Used to login with the repository declared provider + // Used to get the client options for the repository ctxTimeout, cancel := context.WithTimeout(ctx, obj.GetTimeout()) defer cancel() - clientOpts, certsTmpDir, err := getter.GetClientOpts(ctxTimeout, r.Client, obj, normalizedURL) + clientOpts, err := getter.GetClientOpts(ctxTimeout, r.Client, obj, normalizedURL) if err != nil && !errors.Is(err, getter.ErrDeprecatedTLSConfig) { return nil, err } @@ -1027,45 +1026,23 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont var chartRepo repository.Downloader if helmreg.IsOCI(normalizedURL) { - registryClient, credentialsFile, err := r.RegistryClientGenerator(clientOpts.TlsConfig, clientOpts.MustLoginToRegistry(), obj.Spec.Insecure) + registryClient, err := registry.NewClient(clientOpts.OCIAuth, clientOpts.TLSConfig, obj.Spec.Insecure) if err != nil { return nil, fmt.Errorf("failed to create registry client: %w", err) } - var errs []error // Tell the chart repository to use the OCI client with the configured getter getterOpts = append(getterOpts, helmgetter.WithRegistryClient(registryClient)) ociChartRepo, err := repository.NewOCIChartRepository(normalizedURL, repository.WithOCIGetter(r.Getters), repository.WithOCIGetterOptions(getterOpts), - repository.WithOCIRegistryClient(registryClient), - repository.WithCertificatesStore(certsTmpDir), - repository.WithCredentialsFile(credentialsFile)) + repository.WithOCIRegistryClient(registryClient)) if err != nil { - errs = append(errs, fmt.Errorf("failed to create OCI chart repository: %w", err)) - // clean up the credentialsFile - if credentialsFile != "" { - if err := os.Remove(credentialsFile); err != nil { - errs = append(errs, err) - } - } - return nil, kerrors.NewAggregate(errs) - } - - // If login options are configured, use them to login to the registry - // The OCIGetter will later retrieve the stored credentials to pull the chart - if clientOpts.MustLoginToRegistry() { - err = ociChartRepo.Login(clientOpts.RegLoginOpts...) - if err != nil { - errs = append(errs, fmt.Errorf("failed to login to OCI chart repository: %w", err)) - // clean up the credentialsFile - errs = append(errs, ociChartRepo.Clear()) - return nil, kerrors.NewAggregate(errs) - } + return nil, fmt.Errorf("failed to create OCI chart repository: %w", err) } chartRepo = ociChartRepo } else { - httpChartRepo, err := repository.NewChartRepository(normalizedURL, "", r.Getters, clientOpts.TlsConfig, getterOpts...) + httpChartRepo, err := repository.NewChartRepository(normalizedURL, "", r.Getters, clientOpts.TLSConfig, getterOpts...) if err != nil { return nil, err } @@ -1096,10 +1073,12 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont } } +// resolveDependencyRepository attempts to find a HelmRepository in the given namespace matching the given URL. +// It allows umbrella charts referencing remote dependencies to be inflated for packaging. func (r *HelmChartReconciler) resolveDependencyRepository(ctx context.Context, url string, namespace string) (*sourcev1.HelmRepository, error) { listOpts := []client.ListOption{ client.InNamespace(namespace), - client.MatchingFields{sourcev1.HelmRepositoryURLIndexKey: url}, + client.MatchingFields{indexKeyHelmRepositoryURL: url}, client.Limit(1), } var list sourcev1.HelmRepositoryList @@ -1147,7 +1126,7 @@ func (r *HelmChartReconciler) requestsForHelmRepositoryChange(ctx context.Contex var list sourcev1.HelmChartList if err := r.List(ctx, &list, client.MatchingFields{ - sourcev1.SourceIndexKey: fmt.Sprintf("%s/%s", sourcev1.HelmRepositoryKind, repo.Name), + indexKeyHelmChartSource: fmt.Sprintf("%s/%s", sourcev1.HelmRepositoryKind, repo.Name), }); err != nil { ctrl.LoggerFrom(ctx).Error(err, "failed to list HelmCharts for HelmRepository change") return nil @@ -1177,7 +1156,7 @@ func (r *HelmChartReconciler) requestsForGitRepositoryChange(ctx context.Context var list sourcev1.HelmChartList if err := r.List(ctx, &list, client.MatchingFields{ - sourcev1.SourceIndexKey: fmt.Sprintf("%s/%s", sourcev1.GitRepositoryKind, repo.Name), + indexKeyHelmChartSource: fmt.Sprintf("%s/%s", sourcev1.GitRepositoryKind, repo.Name), }); err != nil { ctrl.LoggerFrom(ctx).Error(err, "failed to list HelmCharts for GitRepository change") return nil @@ -1207,7 +1186,7 @@ func (r *HelmChartReconciler) requestsForBucketChange(ctx context.Context, o cli var list sourcev1.HelmChartList if err := r.List(ctx, &list, client.MatchingFields{ - sourcev1.SourceIndexKey: fmt.Sprintf("%s/%s", sourcev1.BucketKind, bucket.Name), + indexKeyHelmChartSource: fmt.Sprintf("%s/%s", sourcev1.BucketKind, bucket.Name), }); err != nil { ctrl.LoggerFrom(ctx).Error(err, "failed to list HelmCharts for Bucket change") return nil diff --git a/internal/controller/helmchart_controller_test.go b/internal/controller/helmchart_controller_test.go index 190a9f8b5..0c4f516fd 100644 --- a/internal/controller/helmchart_controller_test.go +++ b/internal/controller/helmchart_controller_test.go @@ -46,9 +46,9 @@ import ( coptions "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign" "github.com/sigstore/cosign/v2/pkg/cosign" - hchart "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - helmreg "helm.sh/helm/v3/pkg/registry" + hchart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" + helmreg "helm.sh/helm/v4/pkg/registry" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -75,7 +75,6 @@ import ( serror "github.com/fluxcd/source-controller/internal/error" "github.com/fluxcd/source-controller/internal/helm/chart" "github.com/fluxcd/source-controller/internal/helm/chart/secureloader" - "github.com/fluxcd/source-controller/internal/helm/registry" "github.com/fluxcd/source-controller/internal/oci" snotation "github.com/fluxcd/source-controller/internal/oci/notation" sreconcile "github.com/fluxcd/source-controller/internal/reconcile" @@ -1298,10 +1297,20 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) { }, { name: "Forces build on generation change", + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "auth", + }, + Data: map[string][]byte{ + "username": []byte(testRegistryUsername), + "password": []byte(testRegistryPassword), + }, + }, beforeFunc: func(obj *sourcev1.HelmChart, repository *sourcev1.HelmRepository) { obj.Generation = 3 obj.Spec.Chart = metadata.Name obj.Spec.Version = metadata.Version + repository.Spec.SecretRef = &meta.LocalObjectReference{Name: "auth"} obj.Status.ObservedGeneration = 2 obj.Status.Artifact = &meta.Artifact{Path: metadata.Name + "-" + metadata.Version + ".tgz"} @@ -1371,12 +1380,11 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) { } r := &HelmChartReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Getters: testGetters, - Storage: st, - RegistryClientGenerator: registry.ClientGenerator, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Getters: testGetters, + Storage: st, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } repository := &sourcev1.HelmRepository{ @@ -1615,11 +1623,10 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) { WithScheme(testEnv.Scheme()). WithStatusSubresource(&sourcev1.HelmChart{}). Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: st, - Getters: testGetters, - RegistryClientGenerator: registry.ClientGenerator, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + EventRecorder: record.NewFakeRecorder(32), + Storage: st, + Getters: testGetters, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } obj := &sourcev1.HelmChart{ @@ -2437,6 +2444,7 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) { providerImg string want sreconcile.Result wantErr bool + wantErrMsg string assertConditions []metav1.Condition }{ { @@ -2491,7 +2499,7 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) { Data: map[string][]byte{}, }, assertConditions: []metav1.Condition{ - *conditions.TrueCondition(sourcev1.FetchFailedCondition, "Unknown", "unknown build error: failed to login to OCI registry"), + *conditions.TrueCondition(sourcev1.FetchFailedCondition, "Unknown", "unknown build error: failed to authenticate with the Helm repository"), }, }, { @@ -2600,6 +2608,29 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) { *conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: pulled 'helmchart' chart with version '0.1.0'"), }, }, + { + name: "HTTPS With CA cert and client cert auth, invalid key", + want: sreconcile.ResultEmpty, + wantErr: true, + wantErrMsg: "tls: unknown certificate authority", + registryOpts: registryOptions{ + withTLS: true, + withClientCertAuth: true, + }, + certSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "certs-secretref", + }, + Data: map[string][]byte{ + "ca.crt": tlsCA, + "tls.crt": clientInvalidPublicKey, + "tls.key": clientInvalidPrivateKey, + }, + }, + assertConditions: []metav1.Condition{ + *conditions.TrueCondition(sourcev1.FetchFailedCondition, "Unknown", "unknown build error: failed to authenticate with the Helm repository"), + }, + }, } for _, tt := range tests { @@ -2645,7 +2676,7 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) { } // If a provider specific image is provided, overwrite existing URL // set earlier. It'll fail, but it's necessary to set them because - // the login check expects the URLs to be of certain pattern. + // the authentication check expects the URLs to be of certain pattern. if tt.providerImg != "" { repo.Spec.URL = tt.providerImg } @@ -2687,11 +2718,10 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) { } r := &HelmChartReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Getters: testGetters, - RegistryClientGenerator: registry.ClientGenerator, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Getters: testGetters, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } var b chart.Build @@ -2718,6 +2748,9 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) { got, err := r.reconcileSource(ctx, sp, obj, &b) if tt.wantErr { g.Expect(err).To(HaveOccurred()) + if tt.wantErrMsg != "" { + g.Expect(err.Error()).To(ContainSubstring(tt.wantErrMsg)) + } } else { g.Expect(err).ToNot(HaveOccurred()) g.Expect(got).To(Equal(tt.want)) @@ -2844,12 +2877,11 @@ func TestHelmChartRepository_reconcileSource_verifyOCISourceSignature_keyless(t clientBuilder.WithObjects(repository) r := &HelmChartReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Getters: testGetters, - Storage: testStorage, - RegistryClientGenerator: registry.ClientGenerator, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Getters: testGetters, + Storage: testStorage, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } obj := &sourcev1.HelmChart{ @@ -3150,12 +3182,11 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignatureNotation(t *t clientBuilder.WithObjects(repository, secret, caSecret) r := &HelmChartReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Getters: testGetters, - Storage: st, - RegistryClientGenerator: registry.ClientGenerator, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Getters: testGetters, + Storage: st, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } obj := &sourcev1.HelmChart{ @@ -3402,12 +3433,11 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignatureCosign(t *tes clientBuilder.WithObjects(repository, secret) r := &HelmChartReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Getters: testGetters, - Storage: st, - RegistryClientGenerator: registry.ClientGenerator, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + EventRecorder: record.NewFakeRecorder(32), + Getters: testGetters, + Storage: st, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } obj := &sourcev1.HelmChart{ diff --git a/internal/controller/helmrepository_controller.go b/internal/controller/helmrepository_controller.go index 06c4494cf..0fd7eedc2 100644 --- a/internal/controller/helmrepository_controller.go +++ b/internal/controller/helmrepository_controller.go @@ -22,13 +22,12 @@ import ( "errors" "fmt" "net/url" - "strings" "time" "github.com/docker/go-units" "github.com/opencontainers/go-digest" - helmgetter "helm.sh/helm/v3/pkg/getter" - helmreg "helm.sh/helm/v3/pkg/registry" + helmgetter "helm.sh/helm/v4/pkg/getter" + helmreg "helm.sh/helm/v4/pkg/registry" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" kuberecorder "k8s.io/client-go/tools/record" @@ -130,11 +129,7 @@ type HelmRepositoryReconcilerOptions struct { // object. type helmRepositoryReconcileFunc func(ctx context.Context, sp *patch.SerialPatcher, obj *sourcev1.HelmRepository, artifact *meta.Artifact, repo *repository.ChartRepository) (sreconcile.Result, error) -func (r *HelmRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error { - return r.SetupWithManagerAndOptions(mgr, HelmRepositoryReconcilerOptions{}) -} - -func (r *HelmRepositoryReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts HelmRepositoryReconcilerOptions) error { +func (r *HelmRepositoryReconciler) SetupWithManager(mgr ctrl.Manager, opts HelmRepositoryReconcilerOptions) error { r.patchOptions = getPatchOptions(helmRepositoryReadyCondition.Owned, r.ControllerName) return ctrl.NewControllerManagedBy(mgr). @@ -396,7 +391,7 @@ func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, sp *patc obj *sourcev1.HelmRepository, artifact *meta.Artifact, chartRepo *repository.ChartRepository) (sreconcile.Result, error) { // Ensure it's not an OCI URL. API validation ensures that only // http/https/oci scheme are allowed. - if strings.HasPrefix(obj.Spec.URL, helmreg.OCIScheme) { + if helmreg.IsOCI(obj.Spec.URL) { err := fmt.Errorf("'oci' URL scheme cannot be used with 'default' HelmRepository type") e := serror.NewStalling( fmt.Errorf("invalid Helm repository URL: %w", err), @@ -416,7 +411,7 @@ func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, sp *patc return sreconcile.ResultEmpty, e } - clientOpts, _, err := getter.GetClientOpts(ctx, r.Client, obj, normalizedURL) + clientOpts, err := getter.GetClientOpts(ctx, r.Client, obj, normalizedURL) if err != nil { if errors.Is(err, getter.ErrDeprecatedTLSConfig) { ctrl.LoggerFrom(ctx). @@ -432,7 +427,7 @@ func (r *HelmRepositoryReconciler) reconcileSource(ctx context.Context, sp *patc } // Construct Helm chart repository with options and download index - newChartRepo, err := repository.NewChartRepository(obj.Spec.URL, "", r.Getters, clientOpts.TlsConfig, clientOpts.GetterOpts...) + newChartRepo, err := repository.NewChartRepository(obj.Spec.URL, "", r.Getters, clientOpts.TLSConfig, clientOpts.GetterOpts...) if err != nil { switch err.(type) { case *url.Error: diff --git a/internal/controller/helmrepository_controller_test.go b/internal/controller/helmrepository_controller_test.go index d76c58a42..f76d4f221 100644 --- a/internal/controller/helmrepository_controller_test.go +++ b/internal/controller/helmrepository_controller_test.go @@ -30,8 +30,8 @@ import ( . "github.com/onsi/gomega" "github.com/opencontainers/go-digest" - helmgetter "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo" + helmgetter "helm.sh/helm/v4/pkg/getter" + repo "helm.sh/helm/v4/pkg/repo/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/internal/controller/ocirepository_controller.go b/internal/controller/ocirepository_controller.go index a91c8a51b..003d4e24d 100644 --- a/internal/controller/ocirepository_controller.go +++ b/internal/controller/ocirepository_controller.go @@ -153,12 +153,7 @@ type OCIRepositoryReconcilerOptions struct { RateLimiter workqueue.TypedRateLimiter[reconcile.Request] } -// SetupWithManager sets up the controller with the Manager. -func (r *OCIRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error { - return r.SetupWithManagerAndOptions(mgr, OCIRepositoryReconcilerOptions{}) -} - -func (r *OCIRepositoryReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts OCIRepositoryReconcilerOptions) error { +func (r *OCIRepositoryReconciler) SetupWithManager(mgr ctrl.Manager, opts OCIRepositoryReconcilerOptions) error { r.patchOptions = getPatchOptions(ociRepositoryReadyCondition.Owned, r.ControllerName) r.requeueDependency = opts.DependencyRequeueInterval diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index ad0365616..b09df04b1 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -40,8 +40,8 @@ import ( "github.com/phayes/freeport" "github.com/sirupsen/logrus" "golang.org/x/crypto/bcrypt" - "helm.sh/helm/v3/pkg/getter" - helmreg "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v4/pkg/getter" + helmreg "helm.sh/helm/v4/pkg/registry" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/record" @@ -104,11 +104,13 @@ var ( ) var ( - tlsPublicKey []byte - tlsPrivateKey []byte - tlsCA []byte - clientPublicKey []byte - clientPrivateKey []byte + tlsPublicKey []byte + tlsPrivateKey []byte + tlsCA []byte + clientPublicKey []byte + clientPrivateKey []byte + clientInvalidPublicKey []byte + clientInvalidPrivateKey []byte ) var ( @@ -319,7 +321,7 @@ func TestMain(m *testing.M) { EventRecorder: record.NewFakeRecorder(32), Metrics: testMetricsH, Storage: testStorage, - }).SetupWithManagerAndOptions(testEnv, GitRepositoryReconcilerOptions{ + }).SetupWithManager(testEnv, GitRepositoryReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { panic(fmt.Sprintf("Failed to start GitRepositoryReconciler: %v", err)) @@ -330,7 +332,7 @@ func TestMain(m *testing.M) { EventRecorder: record.NewFakeRecorder(32), Metrics: testMetricsH, Storage: testStorage, - }).SetupWithManagerAndOptions(testEnv, BucketReconcilerOptions{ + }).SetupWithManager(testEnv, BucketReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { panic(fmt.Sprintf("Failed to start BucketReconciler: %v", err)) @@ -344,7 +346,7 @@ func TestMain(m *testing.M) { EventRecorder: record.NewFakeRecorder(32), Metrics: testMetricsH, Storage: testStorage, - }).SetupWithManagerAndOptions(testEnv, OCIRepositoryReconcilerOptions{ + }).SetupWithManager(testEnv, OCIRepositoryReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { panic(fmt.Sprintf("Failed to start OCIRepositoryReconciler: %v", err)) @@ -359,7 +361,7 @@ func TestMain(m *testing.M) { Cache: testCache, TTL: 1 * time.Second, CacheRecorder: cacheRecorder, - }).SetupWithManagerAndOptions(testEnv, HelmRepositoryReconcilerOptions{ + }).SetupWithManager(testEnv, HelmRepositoryReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { panic(fmt.Sprintf("Failed to start HelmRepositoryReconciler: %v", err)) @@ -374,7 +376,7 @@ func TestMain(m *testing.M) { Cache: testCache, TTL: 1 * time.Second, CacheRecorder: cacheRecorder, - }).SetupWithManagerAndOptions(ctx, testEnv, HelmChartReconcilerOptions{ + }).SetupWithManager(ctx, testEnv, HelmChartReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { panic(fmt.Sprintf("Failed to start HelmChartReconciler: %v", err)) @@ -430,6 +432,14 @@ func initTestTLS() { if err != nil { panic(err) } + clientInvalidPrivateKey, err = os.ReadFile("testdata/certs/client-key-invalid.pem") + if err != nil { + panic(err) + } + clientInvalidPublicKey, err = os.ReadFile("testdata/certs/client-invalid.pem") + if err != nil { + panic(err) + } } func newTestStorage(s *testserver.HTTPServer) (*storage.Storage, error) { diff --git a/internal/controller/testdata/certs/client-invalid.pem b/internal/controller/testdata/certs/client-invalid.pem new file mode 100644 index 000000000..1aac029c7 --- /dev/null +++ b/internal/controller/testdata/certs/client-invalid.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB7DCCAZKgAwIBAgIUU5MPoggXeOyumWsjh4Q/n/BpTBswCgYIKoZIzj0EAwIw +GTEXMBUGA1UEAxMOZXhhbXBsZS5jb20gQ0EwHhcNMjYwMTEyMTIwODAwWhcNMzYw +MTEwMTIwODAwWjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTBZMBMGByqGSM49AgEG +CCqGSM49AwEHA0IABIP7zXaME+OjAgpu5IIa44bZXNn9xr3fRY3j8Ye31rUx6jui +aJzMvCdsPe4N43rX9kiKMmY0fkECzGWIEuQnLTqjgbowgbcwDgYDVR0PAQH/BAQD +AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAA +MB0GA1UdDgQWBBS0ZJAL2KJiQE7GFEPq72ckTNlgFTAfBgNVHSMEGDAWgBS6LJ4I +SuRcw0gDTANfhZbHi+frPDA4BgNVHREEMTAvgglsb2NhbGhvc3SCC2V4YW1wbGUu +Y29tgg93d3cuZXhhbXBsZS5jb22HBH8AAAEwCgYIKoZIzj0EAwIDSAAwRQIgPIGt +gIUj0SL5+Sy78mb4enB9Wwp4aH15aSRiIp6FtS4CIQCWVdlu82pGZ6f5pCUnwQPE +qUH4LRLuvHORzB3QzBn9YQ== +-----END CERTIFICATE----- diff --git a/internal/controller/testdata/certs/client-key-invalid.pem b/internal/controller/testdata/certs/client-key-invalid.pem new file mode 100644 index 000000000..96bef50eb --- /dev/null +++ b/internal/controller/testdata/certs/client-key-invalid.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIEseX3noyD6XBtObhCxyBMqZbpW6N3SOkqt8Vf2IBMI9oAoGCCqGSM49 +AwEHoUQDQgAEg/vNdowT46MCCm7kghrjhtlc2f3Gvd9FjePxh7fWtTHqO6JonMy8 +J2w97g3jetf2SIoyZjR+QQLMZYgS5CctOg== +-----END EC PRIVATE KEY----- diff --git a/internal/helm/chart/builder.go b/internal/helm/chart/builder.go index 6ac896e78..4f15aeff4 100644 --- a/internal/helm/chart/builder.go +++ b/internal/helm/chart/builder.go @@ -25,8 +25,8 @@ import ( "strings" sourcefs "github.com/fluxcd/pkg/oci" - helmchart "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" + helmchart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "github.com/fluxcd/source-controller/internal/oci" ) diff --git a/internal/helm/chart/builder_local_test.go b/internal/helm/chart/builder_local_test.go index 4b26e1419..cf9d6a742 100644 --- a/internal/helm/chart/builder_local_test.go +++ b/internal/helm/chart/builder_local_test.go @@ -25,9 +25,9 @@ import ( . "github.com/onsi/gomega" "github.com/otiai10/copy" - helmchart "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/repo" + "helm.sh/helm/v4/pkg/chart/common" + helmchart "helm.sh/helm/v4/pkg/chart/v2" + repo "helm.sh/helm/v4/pkg/repo/v1" "github.com/fluxcd/source-controller/internal/helm/chart/secureloader" "github.com/fluxcd/source-controller/internal/helm/repository" @@ -66,10 +66,10 @@ func TestLocalBuilder_Build(t *testing.T) { name string reference Reference buildOpts BuildOptions - valuesFiles []helmchart.File + valuesFiles []common.File repositories map[string]repository.Downloader dependentChartPaths []string - wantValues chartutil.Values + wantValues common.Values wantVersion string wantPackaged bool wantErr string @@ -111,7 +111,7 @@ func TestLocalBuilder_Build(t *testing.T) { { name: "default values", reference: LocalReference{Path: "../testdata/charts/helmchart"}, - wantValues: chartutil.Values{ + wantValues: common.Values{ "replicaCount": float64(1), }, wantVersion: "0.1.0", @@ -123,7 +123,7 @@ func TestLocalBuilder_Build(t *testing.T) { buildOpts: BuildOptions{ ValuesFiles: []string{"custom-values1.yaml", "custom-values2.yaml"}, }, - valuesFiles: []helmchart.File{ + valuesFiles: []common.File{ { Name: "custom-values1.yaml", Data: []byte(`replicaCount: 11 @@ -135,7 +135,7 @@ nameOverride: "foo-name-override"`), fullnameOverride: "full-foo-name-override"`), }, }, - wantValues: chartutil.Values{ + wantValues: common.Values{ "replicaCount": float64(20), "nameOverride": "foo-name-override", "fullnameOverride": "full-foo-name-override", @@ -156,7 +156,7 @@ fullnameOverride: "full-foo-name-override"`), { name: "v1 chart", reference: LocalReference{Path: "./../testdata/charts/helmchart-v1"}, - wantValues: chartutil.Values{ + wantValues: common.Values{ "replicaCount": float64(1), }, wantVersion: "0.2.0", @@ -282,7 +282,7 @@ func TestLocalBuilder_Build_CachedChart(t *testing.T) { func Test_mergeFileValues(t *testing.T) { tests := []struct { name string - files []*helmchart.File + files []*common.File paths []string ignoreMissing bool wantValues map[string]interface{} @@ -291,7 +291,7 @@ func Test_mergeFileValues(t *testing.T) { }{ { name: "merges values from files", - files: []*helmchart.File{ + files: []*common.File{ {Name: "a.yaml", Data: []byte("a: b")}, {Name: "b.yaml", Data: []byte("b: c")}, {Name: "c.yaml", Data: []byte("b: d")}, @@ -310,7 +310,7 @@ func Test_mergeFileValues(t *testing.T) { }, { name: "unmarshal error", - files: []*helmchart.File{ + files: []*common.File{ {Name: "invalid", Data: []byte("abcd")}, }, paths: []string{"invalid"}, @@ -323,7 +323,7 @@ func Test_mergeFileValues(t *testing.T) { }, { name: "ignore missing files", - files: []*helmchart.File{ + files: []*common.File{ {Name: "a.yaml", Data: []byte("a: b")}, }, paths: []string{"a.yaml", "b.yaml"}, diff --git a/internal/helm/chart/builder_remote.go b/internal/helm/chart/builder_remote.go index 2cfdf81b4..dbe3addca 100644 --- a/internal/helm/chart/builder_remote.go +++ b/internal/helm/chart/builder_remote.go @@ -25,9 +25,9 @@ import ( "path/filepath" "github.com/Masterminds/semver/v3" - helmchart "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/repo" + helmchart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + repo "helm.sh/helm/v4/pkg/repo/v1" "sigs.k8s.io/yaml" sourcefs "github.com/fluxcd/pkg/oci" diff --git a/internal/helm/chart/builder_remote_test.go b/internal/helm/chart/builder_remote_test.go index 7994fa5ee..72adb20bc 100644 --- a/internal/helm/chart/builder_remote_test.go +++ b/internal/helm/chart/builder_remote_test.go @@ -28,10 +28,11 @@ import ( "testing" . "github.com/onsi/gomega" - helmchart "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - helmgetter "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v4/pkg/chart/common" + helmchart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + helmgetter "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/registry" "github.com/fluxcd/source-controller/internal/helm/chart/secureloader" "github.com/fluxcd/source-controller/internal/helm/repository" @@ -120,7 +121,7 @@ entries: reference Reference buildOpts BuildOptions repository *repository.ChartRepository - wantValues chartutil.Values + wantValues common.Values wantVersion string wantPackaged bool wantErr string @@ -167,7 +168,7 @@ entries: reference: RemoteReference{Name: "grafana"}, repository: mockRepo(), wantVersion: "0.1.0", - wantValues: chartutil.Values{ + wantValues: common.Values{ "replicaCount": float64(1), }, }, @@ -179,7 +180,7 @@ entries: }, repository: mockRepo(), wantVersion: "6.17.4", - wantValues: chartutil.Values{ + wantValues: common.Values{ "a": "b", "b": "d", }, @@ -268,7 +269,7 @@ func TestRemoteBuilder_BuildFromOCIChartRepository(t *testing.T) { reference Reference buildOpts BuildOptions repository *repository.OCIChartRepository - wantValues chartutil.Values + wantValues common.Values wantVersion string wantPackaged bool wantErr string @@ -315,7 +316,7 @@ func TestRemoteBuilder_BuildFromOCIChartRepository(t *testing.T) { reference: RemoteReference{Name: "grafana"}, repository: mockRepo(), wantVersion: "0.1.0", - wantValues: chartutil.Values{ + wantValues: common.Values{ "replicaCount": float64(1), }, }, @@ -324,7 +325,7 @@ func TestRemoteBuilder_BuildFromOCIChartRepository(t *testing.T) { reference: RemoteReference{Name: "another/grafana"}, repository: mockRepo(), wantVersion: "0.1.0", - wantValues: chartutil.Values{ + wantValues: common.Values{ "replicaCount": float64(1), }, }, @@ -336,7 +337,7 @@ func TestRemoteBuilder_BuildFromOCIChartRepository(t *testing.T) { }, repository: mockRepo(), wantVersion: "6.17.4", - wantValues: chartutil.Values{ + wantValues: common.Values{ "a": "b", "b": "d", }, @@ -455,7 +456,7 @@ func Test_mergeChartValues(t *testing.T) { { name: "merges values", chart: &helmchart.Chart{ - Files: []*helmchart.File{ + Files: []*common.File{ {Name: "a.yaml", Data: []byte("a: b")}, {Name: "b.yaml", Data: []byte("b: c")}, {Name: "c.yaml", Data: []byte("b: d")}, @@ -471,7 +472,7 @@ func Test_mergeChartValues(t *testing.T) { { name: "uses chart values", chart: &helmchart.Chart{ - Files: []*helmchart.File{ + Files: []*common.File{ {Name: "c.yaml", Data: []byte("b: d")}, }, Values: map[string]interface{}{ @@ -488,7 +489,7 @@ func Test_mergeChartValues(t *testing.T) { { name: "unmarshal error", chart: &helmchart.Chart{ - Files: []*helmchart.File{ + Files: []*common.File{ {Name: "invalid", Data: []byte("abcd")}, }, }, @@ -504,7 +505,7 @@ func Test_mergeChartValues(t *testing.T) { { name: "merges values ignoring file missing", chart: &helmchart.Chart{ - Files: []*helmchart.File{ + Files: []*common.File{ {Name: "a.yaml", Data: []byte("a: b")}, }, }, diff --git a/internal/helm/chart/builder_test.go b/internal/helm/chart/builder_test.go index d3fa55e38..c7b8e35aa 100644 --- a/internal/helm/chart/builder_test.go +++ b/internal/helm/chart/builder_test.go @@ -24,7 +24,7 @@ import ( "testing" . "github.com/onsi/gomega" - "helm.sh/helm/v3/pkg/chartutil" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "github.com/fluxcd/source-controller/internal/helm/chart/secureloader" ) diff --git a/internal/helm/chart/dependency_manager.go b/internal/helm/chart/dependency_manager.go index 8a3f0ccfb..93745123b 100644 --- a/internal/helm/chart/dependency_manager.go +++ b/internal/helm/chart/dependency_manager.go @@ -29,7 +29,7 @@ import ( securejoin "github.com/cyphar/filepath-securejoin" "golang.org/x/sync/errgroup" "golang.org/x/sync/semaphore" - helmchart "helm.sh/helm/v3/pkg/chart" + helmchart "helm.sh/helm/v4/pkg/chart/v2" "k8s.io/apimachinery/pkg/util/errors" "github.com/fluxcd/source-controller/internal/helm/chart/secureloader" diff --git a/internal/helm/chart/dependency_manager_test.go b/internal/helm/chart/dependency_manager_test.go index 241959fbe..11b78e422 100644 --- a/internal/helm/chart/dependency_manager_test.go +++ b/internal/helm/chart/dependency_manager_test.go @@ -28,10 +28,10 @@ import ( "testing" . "github.com/onsi/gomega" - helmchart "helm.sh/helm/v3/pkg/chart" - helmgetter "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" + helmchart "helm.sh/helm/v4/pkg/chart/v2" + helmgetter "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/registry" + repo "helm.sh/helm/v4/pkg/repo/v1" "github.com/fluxcd/source-controller/internal/helm/chart/secureloader" "github.com/fluxcd/source-controller/internal/helm/repository" @@ -76,9 +76,7 @@ func (g *mockGetter) Get(_ string, _ ...helmgetter.Option) (*bytes.Buffer, error func TestDependencyManager_Clear(t *testing.T) { g := NewWithT(t) - file, err := os.CreateTemp("", "") - g.Expect(err).ToNot(HaveOccurred()) - ociRepoWithCreds, err := repository.NewOCIChartRepository("oci://example.com", repository.WithCredentialsFile(file.Name())) + ociRepoWithCreds, err := repository.NewOCIChartRepository("oci://example.com") g.Expect(err).ToNot(HaveOccurred()) downloaders := map[string]repository.Downloader{ @@ -99,14 +97,9 @@ func TestDependencyManager_Clear(t *testing.T) { case *repository.ChartRepository: g.Expect(v.Index).To(BeNil()) case *repository.OCIChartRepository: - g.Expect(v.HasCredentials()).To(BeFalse()) + // nothing to check } } - - if _, err := os.Stat(file.Name()); !errors.Is(err, os.ErrNotExist) { - err = os.Remove(file.Name()) - g.Expect(err).ToNot(HaveOccurred()) - } } func TestDependencyManager_Build(t *testing.T) { diff --git a/internal/helm/chart/metadata.go b/internal/helm/chart/metadata.go index e3c91ac6b..51309e209 100644 --- a/internal/helm/chart/metadata.go +++ b/internal/helm/chart/metadata.go @@ -31,8 +31,9 @@ import ( "regexp" "strings" - helmchart "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v4/pkg/chart/common" + helmchart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "sigs.k8s.io/yaml" "github.com/fluxcd/source-controller/internal/helm" @@ -41,7 +42,7 @@ import ( var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) // OverwriteChartDefaultValues overwrites the chart default values file with the given data. -func OverwriteChartDefaultValues(chart *helmchart.Chart, vals chartutil.Values) (bool, error) { +func OverwriteChartDefaultValues(chart *helmchart.Chart, vals common.Values) (bool, error) { if vals == nil { return false, nil } diff --git a/internal/helm/chart/metadata_test.go b/internal/helm/chart/metadata_test.go index 1c002a1df..2574805cc 100644 --- a/internal/helm/chart/metadata_test.go +++ b/internal/helm/chart/metadata_test.go @@ -23,8 +23,9 @@ import ( . "github.com/onsi/gomega" "github.com/otiai10/copy" - helmchart "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v4/pkg/chart/common" + helmchart "helm.sh/helm/v4/pkg/chart/v2" + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" "github.com/fluxcd/source-controller/internal/helm" ) @@ -45,7 +46,7 @@ var ( originalValuesFixture = []byte(`override: original `) - chartFilesFixture = []*helmchart.File{ + chartFilesFixture = []*common.File{ { Name: "values.yaml", Data: originalValuesFixture, @@ -63,8 +64,8 @@ var ( func TestOverwriteChartDefaultValues(t *testing.T) { invalidChartFixture := chartFixture - invalidChartFixture.Raw = []*helmchart.File{} - invalidChartFixture.Files = []*helmchart.File{} + invalidChartFixture.Raw = []*common.File{} + invalidChartFixture.Files = []*common.File{} testCases := []struct { desc string @@ -103,7 +104,7 @@ func TestOverwriteChartDefaultValues(t *testing.T) { g := NewWithT(t) fixture := tt.chart - vals, err := chartutil.ReadValues(tt.data) + vals, err := common.ReadValues(tt.data) g.Expect(err).ToNot(HaveOccurred()) ok, err := OverwriteChartDefaultValues(&fixture, vals) g.Expect(ok).To(Equal(tt.ok)) diff --git a/internal/helm/chart/secureloader/directory.go b/internal/helm/chart/secureloader/directory.go index 90285758b..711f3da99 100644 --- a/internal/helm/chart/secureloader/directory.go +++ b/internal/helm/chart/secureloader/directory.go @@ -34,8 +34,9 @@ import ( "strings" securejoin "github.com/cyphar/filepath-securejoin" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v4/pkg/chart/loader/archive" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/chart/v2/loader" "github.com/fluxcd/source-controller/internal/helm" "github.com/fluxcd/source-controller/internal/helm/chart/secureloader/ignore" @@ -151,7 +152,7 @@ type secureFileWalker struct { absChartPath string maxSize int64 rules *ignore.Rules - files []*loader.BufferedFile + files []*archive.BufferedFile } func newSecureFileWalker(root, absChartPath string, maxSize int64, rules *ignore.Rules) *secureFileWalker { @@ -161,7 +162,7 @@ func newSecureFileWalker(root, absChartPath string, maxSize int64, rules *ignore absChartPath: absChartPath, maxSize: maxSize, rules: rules, - files: make([]*loader.BufferedFile, 0), + files: make([]*archive.BufferedFile, 0), } } @@ -226,7 +227,7 @@ func (w *secureFileWalker) walk(name, absName string, fi os.FileInfo, err error) } data = bytes.TrimPrefix(data, utf8bom) - w.files = append(w.files, &loader.BufferedFile{Name: n, Data: data}) + w.files = append(w.files, &archive.BufferedFile{Name: n, Data: data}) return nil } diff --git a/internal/helm/chart/secureloader/directory_test.go b/internal/helm/chart/secureloader/directory_test.go index 5dacfc7d8..d649c5ea6 100644 --- a/internal/helm/chart/secureloader/directory_test.go +++ b/internal/helm/chart/secureloader/directory_test.go @@ -27,7 +27,7 @@ import ( "testing/fstest" . "github.com/onsi/gomega" - "helm.sh/helm/v3/pkg/chart" + chart "helm.sh/helm/v4/pkg/chart/v2" "sigs.k8s.io/yaml" "github.com/fluxcd/source-controller/internal/helm" diff --git a/internal/helm/chart/secureloader/file.go b/internal/helm/chart/secureloader/file.go index ce42e4ed2..5b6253636 100644 --- a/internal/helm/chart/secureloader/file.go +++ b/internal/helm/chart/secureloader/file.go @@ -20,28 +20,38 @@ package secureloader import ( "io" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v4/pkg/chart/loader" + "helm.sh/helm/v4/pkg/chart/loader/archive" + chart "helm.sh/helm/v4/pkg/chart/v2" + loaderv2 "helm.sh/helm/v4/pkg/chart/v2/loader" ) -// FileLoader is equal to Helm's. -// Redeclared to avoid having to deal with multiple package imports, -// possibly resulting in using the non-secure directory loader. -type FileLoader = loader.FileLoader +// FileLoader wraps Helm's loader.FileLoader to implement the +// secureloader interface. +type FileLoader string + +func (f FileLoader) Load() (*chart.Chart, error) { + l := loader.FileLoader(f) + c, err := l.Load() + if err != nil { + return nil, err + } + return c.(*chart.Chart), nil +} // LoadFile loads from an archive file. func LoadFile(name string) (*chart.Chart, error) { - return loader.LoadFile(name) + return loaderv2.LoadFile(name) } // LoadArchiveFiles reads in files out of an archive into memory. This function // performs important path security checks and should always be used before // expanding a tarball -func LoadArchiveFiles(in io.Reader) ([]*loader.BufferedFile, error) { - return loader.LoadArchiveFiles(in) +func LoadArchiveFiles(in io.Reader) ([]*archive.BufferedFile, error) { + return archive.LoadArchiveFiles(in) } // LoadArchive loads from a reader containing a compressed tar archive. func LoadArchive(in io.Reader) (*chart.Chart, error) { - return loader.LoadArchive(in) + return loaderv2.LoadArchive(in) } diff --git a/internal/helm/chart/secureloader/loader.go b/internal/helm/chart/secureloader/loader.go index e17adc314..2f77e6e33 100644 --- a/internal/helm/chart/secureloader/loader.go +++ b/internal/helm/chart/secureloader/loader.go @@ -25,18 +25,22 @@ import ( "strings" securejoin "github.com/cyphar/filepath-securejoin" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + chart "helm.sh/helm/v4/pkg/chart/v2" "github.com/fluxcd/source-controller/internal/helm" ) +// FileLoaderV2 is the interface implemented by chart v2 loaders. +type FileLoaderV2 interface { + Load() (*chart.Chart, error) +} + // Loader returns a new loader.ChartLoader appropriate for the given chart // name. That being, SecureDirLoader when name is a directory, and // FileLoader when it's a file. // Name can be an absolute or relative path, but always has to be inside // root. -func Loader(root, name string) (loader.ChartLoader, error) { +func Loader(root, name string) (FileLoaderV2, error) { root, err := filepath.Abs(root) if err != nil { return nil, err diff --git a/internal/helm/chart/secureloader/loader_test.go b/internal/helm/chart/secureloader/loader_test.go index af7de550e..8cbf7e733 100644 --- a/internal/helm/chart/secureloader/loader_test.go +++ b/internal/helm/chart/secureloader/loader_test.go @@ -23,8 +23,7 @@ import ( "testing" . "github.com/onsi/gomega" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" + chart "helm.sh/helm/v4/pkg/chart/v2" "sigs.k8s.io/yaml" "github.com/fluxcd/source-controller/internal/helm" @@ -42,7 +41,7 @@ func TestLoader(t *testing.T) { got, err := Loader(tmpDir, fakeChart) g.Expect(err).ToNot(HaveOccurred()) - g.Expect(got).To(Equal(loader.FileLoader(fakeChart))) + g.Expect(got).To(Equal(FileLoader(fakeChart))) }) t.Run("dir loader", func(t *testing.T) { diff --git a/internal/helm/getter/client_opts.go b/internal/helm/getter/client_opts.go index 2dba9a00a..4804c45a8 100644 --- a/internal/helm/getter/client_opts.go +++ b/internal/helm/getter/client_opts.go @@ -21,14 +21,12 @@ import ( "crypto/tls" "errors" "fmt" - "os" - "path" "github.com/google/go-containerregistry/pkg/authn" - helmgetter "helm.sh/helm/v3/pkg/getter" - helmreg "helm.sh/helm/v3/pkg/registry" + helmgetter "helm.sh/helm/v4/pkg/getter" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" + "oras.land/oras-go/v2/registry/remote/auth" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/fluxcd/pkg/runtime/secrets" @@ -38,12 +36,6 @@ import ( soci "github.com/fluxcd/source-controller/internal/oci" ) -const ( - certFileName = "cert.pem" - keyFileName = "key.pem" - caFileName = "ca.pem" -) - var ErrDeprecatedTLSConfig = errors.New("TLS configured in a deprecated manner") // ClientOpts contains the various options to use while constructing @@ -51,25 +43,17 @@ var ErrDeprecatedTLSConfig = errors.New("TLS configured in a deprecated manner") type ClientOpts struct { Authenticator authn.Authenticator Keychain authn.Keychain - RegLoginOpts []helmreg.LoginOption - TlsConfig *tls.Config + TLSConfig *tls.Config GetterOpts []helmgetter.Option Insecure bool -} - -// MustLoginToRegistry returns true if the client options contain at least -// one registry login option. -func (o ClientOpts) MustLoginToRegistry() bool { - return len(o.RegLoginOpts) > 0 && o.RegLoginOpts[0] != nil + OCIAuth auth.CredentialFunc } // GetClientOpts uses the provided HelmRepository object and a normalized // URL to construct a HelmClientOpts object. If obj is an OCI HelmRepository, // then the returned options object will also contain the required registry // auth mechanisms. -// A temporary directory is created to store the certs files if needed and its path is returned along with the options object. It is the -// caller's responsibility to clean up the directory. -func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepository, url string) (*ClientOpts, string, error) { +func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepository, url string) (*ClientOpts, error) { // This function configures authentication for Helm repositories based on the provided secrets: // - CertSecretRef: TLS client certificates (always takes priority) // - SecretRef: Can contain Basic Auth or TLS certificates (deprecated) @@ -84,17 +68,15 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepos } // Process secrets and configure authentication - deprecatedTLS, certSecret, authSecret, err := configureAuthentication(ctx, c, obj, opts, url) + deprecatedTLS, authSecret, err := configureAuthentication(ctx, c, obj, opts) if err != nil { - return nil, "", err + return nil, err } // Setup OCI registry specific configurations if needed - var tempCertDir string if obj.Spec.Type == sourcev1.HelmRepositoryTypeOCI { - tempCertDir, err = configureOCIRegistryWithSecrets(ctx, obj, opts, url, certSecret, authSecret) - if err != nil { - return nil, "", err + if err := configureOCIRegistryWithSecrets(ctx, obj, opts, url, authSecret); err != nil { + return nil, err } } @@ -103,25 +85,23 @@ func GetClientOpts(ctx context.Context, c client.Client, obj *sourcev1.HelmRepos deprecatedErr = ErrDeprecatedTLSConfig } - return opts, tempCertDir, deprecatedErr + return opts, deprecatedErr } // configureAuthentication processes all secret references and sets up authentication. -// Returns (deprecatedTLS, certSecret, authSecret, error) where: +// Returns (deprecatedTLS, authSecret, error) where: // - deprecatedTLS: true if TLS config comes from SecretRef (deprecated pattern) -// - certSecret: the secret from CertSecretRef (nil if not specified) // - authSecret: the secret from SecretRef (nil if not specified) -func configureAuthentication(ctx context.Context, c client.Client, obj *sourcev1.HelmRepository, opts *ClientOpts, url string) (bool, *corev1.Secret, *corev1.Secret, error) { +func configureAuthentication(ctx context.Context, c client.Client, obj *sourcev1.HelmRepository, opts *ClientOpts) (bool, *corev1.Secret, error) { var deprecatedTLS bool - var certSecret, authSecret *corev1.Secret + var authSecret *corev1.Secret if obj.Spec.CertSecretRef != nil { secret, err := fetchSecret(ctx, c, obj.Spec.CertSecretRef.Name, obj.GetNamespace()) if err != nil { secretRef := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.Spec.CertSecretRef.Name} - return false, nil, nil, fmt.Errorf("failed to get TLS authentication secret '%s': %w", secretRef, err) + return false, nil, fmt.Errorf("failed to get TLS authentication secret '%s': %w", secretRef, err) } - certSecret = secret // NOTE: Use WithSystemCertPool to maintain backward compatibility with the existing // extend approach (system CAs + user CA) rather than the default replace approach (user CA only). @@ -129,9 +109,9 @@ func configureAuthentication(ctx context.Context, c client.Client, obj *sourcev1 var tlsOpts = []secrets.TLSConfigOption{secrets.WithSystemCertPool()} tlsConfig, err := secrets.TLSConfigFromSecret(ctx, secret, tlsOpts...) if err != nil { - return false, nil, nil, fmt.Errorf("failed to construct Helm client's TLS config: %w", err) + return false, nil, fmt.Errorf("failed to construct Helm client's TLS config: %w", err) } - opts.TlsConfig = tlsConfig + opts.TLSConfig = tlsConfig } // Extract all authentication methods from SecretRef. @@ -140,7 +120,7 @@ func configureAuthentication(ctx context.Context, c client.Client, obj *sourcev1 secret, err := fetchSecret(ctx, c, obj.Spec.SecretRef.Name, obj.GetNamespace()) if err != nil { secretRef := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.Spec.SecretRef.Name} - return false, nil, nil, fmt.Errorf("failed to get authentication secret '%s': %w", secretRef, err) + return false, nil, fmt.Errorf("failed to get authentication secret '%s': %w", secretRef, err) } authSecret = secret @@ -152,7 +132,7 @@ func configureAuthentication(ctx context.Context, c client.Client, obj *sourcev1 } methods, err := secrets.AuthMethodsFromSecret(ctx, secret, authOpts...) if err != nil { - return false, nil, nil, fmt.Errorf("failed to detect authentication methods: %w", err) + return false, nil, fmt.Errorf("failed to detect authentication methods: %w", err) } if methods.HasBasicAuth() { @@ -161,22 +141,22 @@ func configureAuthentication(ctx context.Context, c client.Client, obj *sourcev1 } // Use TLS from SecretRef only if CertSecretRef is not specified (CertSecretRef takes priority) - if opts.TlsConfig == nil && methods.HasTLS() { - opts.TlsConfig = methods.TLS + if opts.TLSConfig == nil && methods.HasTLS() { + opts.TLSConfig = methods.TLS deprecatedTLS = true } } - return deprecatedTLS, certSecret, authSecret, nil + return deprecatedTLS, authSecret, nil } // configureOCIRegistryWithSecrets sets up OCI-specific configurations using pre-fetched secrets -func configureOCIRegistryWithSecrets(ctx context.Context, obj *sourcev1.HelmRepository, opts *ClientOpts, url string, certSecret, authSecret *corev1.Secret) (string, error) { +func configureOCIRegistryWithSecrets(ctx context.Context, obj *sourcev1.HelmRepository, opts *ClientOpts, url string, authSecret *corev1.Secret) error { // Configure OCI authentication from authSecret if available if authSecret != nil { - keychain, err := registry.LoginOptionFromSecret(url, *authSecret) + keychain, err := registry.KeychainFromSecret(url, *authSecret) if err != nil { - return "", fmt.Errorf("failed to configure login options: %w", err) + return fmt.Errorf("failed to configure OCI registry authentication: %w", err) } opts.Keychain = keychain } @@ -185,53 +165,19 @@ func configureOCIRegistryWithSecrets(ctx context.Context, obj *sourcev1.HelmRepo if obj.Spec.SecretRef == nil && obj.Spec.Provider != "" && obj.Spec.Provider != sourcev1.GenericOCIProvider { authenticator, err := soci.OIDCAuth(ctx, url, obj.Spec.Provider) if err != nil { - return "", fmt.Errorf("failed to get credential from '%s': %w", obj.Spec.Provider, err) + return fmt.Errorf("failed to get credential from '%s': %w", obj.Spec.Provider, err) } opts.Authenticator = authenticator } - // Setup registry login options - loginOpt, err := registry.NewLoginOption(opts.Authenticator, opts.Keychain, url) + // Build registry authentication + creds, err := registry.NewCredentials(opts.Authenticator, opts.Keychain, url) if err != nil { - return "", err - } - if loginOpt == nil { - return "", nil + return err } - opts.RegLoginOpts = []helmreg.LoginOption{loginOpt, helmreg.LoginOptInsecure(obj.Spec.Insecure)} + opts.OCIAuth = creds - // Handle TLS for login options - var tempCertDir string - if opts.TlsConfig != nil { - // Until Helm 3.19 only a file-based login option for TLS is supported. - // In Helm 4 (or in Helm 3.20+ if it ever gets released), a simpler - // in-memory login option for TLS will be available: - // https://github.com/helm/helm/pull/31076 - - tempCertDir, err = os.MkdirTemp("", "helm-repo-oci-certs") - if err != nil { - return "", fmt.Errorf("cannot create temporary directory: %w", err) - } - - var tlsSecret *corev1.Secret - if certSecret != nil { - tlsSecret = certSecret - } else if authSecret != nil { - tlsSecret = authSecret - } - - certFile, keyFile, caFile, err := storeTLSCertificateFilesForOCI(ctx, tlsSecret, nil, tempCertDir) - if err != nil { - return "", fmt.Errorf("cannot write certs files to path: %w", err) - } - - tlsLoginOpt := registry.TLSLoginOption(certFile, keyFile, caFile) - if tlsLoginOpt != nil { - opts.RegLoginOpts = append(opts.RegLoginOpts, tlsLoginOpt) - } - } - - return tempCertDir, nil + return nil } func fetchSecret(ctx context.Context, c client.Client, name, namespace string) (*corev1.Secret, error) { @@ -245,57 +191,3 @@ func fetchSecret(ctx context.Context, c client.Client, name, namespace string) ( } return &secret, nil } - -// storeTLSCertificateFilesForOCI writes TLS certificate data from secrets to files for OCI registry authentication. -// Helm OCI registry client requires certificate file paths rather than in-memory data, -// so we need to temporarily write the certificate data to disk. -// Returns paths to the written cert, key, and CA files (any of which may be empty if not present). -func storeTLSCertificateFilesForOCI(ctx context.Context, certSecret, authSecret *corev1.Secret, path string) (string, string, string, error) { - var ( - certFile string - keyFile string - caFile string - err error - ) - - // Try to get TLS data from certSecret first, then authSecret - var tlsSecret *corev1.Secret - if certSecret != nil { - tlsSecret = certSecret - } else if authSecret != nil { - tlsSecret = authSecret - } - - if tlsSecret != nil { - if certData, exists := tlsSecret.Data[secrets.KeyTLSCert]; exists { - if keyData, keyExists := tlsSecret.Data[secrets.KeyTLSPrivateKey]; keyExists { - certFile, err = writeToFile(certData, certFileName, path) - if err != nil { - return "", "", "", err - } - keyFile, err = writeToFile(keyData, keyFileName, path) - if err != nil { - return "", "", "", err - } - } - } - - if caData, exists := tlsSecret.Data[secrets.KeyCACert]; exists { - caFile, err = writeToFile(caData, caFileName, path) - if err != nil { - return "", "", "", err - } - } - } - - return certFile, keyFile, caFile, nil -} - -func writeToFile(data []byte, filename, tmpDir string) (string, error) { - file := path.Join(tmpDir, filename) - err := os.WriteFile(file, data, 0o600) - if err != nil { - return "", err - } - return file, nil -} diff --git a/internal/helm/getter/client_opts_test.go b/internal/helm/getter/client_opts_test.go index bf40e7f86..87247b6a8 100644 --- a/internal/helm/getter/client_opts_test.go +++ b/internal/helm/getter/client_opts_test.go @@ -68,7 +68,7 @@ func TestGetClientOpts(t *testing.T) { }, }, afterFunc: func(t *WithT, hcOpts *ClientOpts) { - t.Expect(hcOpts.TlsConfig).ToNot(BeNil()) + t.Expect(hcOpts.TLSConfig).ToNot(BeNil()) t.Expect(len(hcOpts.GetterOpts)).To(Equal(4)) }, }, @@ -85,7 +85,7 @@ func TestGetClientOpts(t *testing.T) { }, }, afterFunc: func(t *WithT, hcOpts *ClientOpts) { - t.Expect(hcOpts.TlsConfig).ToNot(BeNil()) + t.Expect(hcOpts.TLSConfig).ToNot(BeNil()) t.Expect(len(hcOpts.GetterOpts)).To(Equal(4)) }, err: ErrDeprecatedTLSConfig, @@ -164,7 +164,7 @@ func TestGetClientOpts(t *testing.T) { } c := clientBuilder.Build() - clientOpts, _, err := GetClientOpts(context.TODO(), c, helmRepo, "https://ghcr.io/dummy") + clientOpts, err := GetClientOpts(context.TODO(), c, helmRepo, "https://ghcr.io/dummy") if tt.err != nil { g.Expect(err).To(Equal(tt.err)) } else { @@ -185,7 +185,8 @@ func TestGetClientOpts_registryTLSLoginOption(t *testing.T) { name string certSecret *corev1.Secret authSecret *corev1.Secret - loginOptsN int + expectAuth bool + expectTLS bool wantErrMsg string }{ { @@ -207,7 +208,8 @@ func TestGetClientOpts_registryTLSLoginOption(t *testing.T) { "password": []byte("pass"), }, }, - loginOptsN: 3, + expectAuth: true, + expectTLS: true, }, { name: "without caFile", @@ -240,7 +242,8 @@ func TestGetClientOpts_registryTLSLoginOption(t *testing.T) { "password": []byte("pass"), }, }, - loginOptsN: 2, + expectAuth: true, + expectTLS: false, }, } for _, tt := range tests { @@ -271,7 +274,7 @@ func TestGetClientOpts_registryTLSLoginOption(t *testing.T) { } c := clientBuilder.Build() - clientOpts, tmpDir, err := GetClientOpts(context.TODO(), c, helmRepo, "https://ghcr.io/dummy") + clientOpts, err := GetClientOpts(context.TODO(), c, helmRepo, "https://ghcr.io/dummy") if tt.wantErrMsg != "" { if err == nil { t.Errorf("GetClientOpts() expected error but got none") @@ -287,13 +290,23 @@ func TestGetClientOpts_registryTLSLoginOption(t *testing.T) { t.Errorf("GetClientOpts() error = %v", err) return } - if tmpDir != "" { - defer os.RemoveAll(tmpDir) + if tt.expectAuth { + if clientOpts.OCIAuth == nil { + t.Errorf("GetClientOpts() expected OCIAuth to be set but was nil") + } + } else { + if clientOpts.OCIAuth != nil { + t.Errorf("GetClientOpts() expected OCIAuth to be nil but was set") + } } - if tt.loginOptsN != len(clientOpts.RegLoginOpts) { - // we should have a login option but no TLS option - t.Errorf("expected length of %d for clientOpts.RegLoginOpts but got %d", tt.loginOptsN, len(clientOpts.RegLoginOpts)) - return + if tt.expectTLS { + if clientOpts.TLSConfig == nil { + t.Errorf("GetClientOpts() expected TLSConfig to be set but was nil") + } + } else { + if clientOpts.TLSConfig != nil { + t.Errorf("GetClientOpts() expected TLSConfig to be nil but was set") + } } }) } diff --git a/internal/helm/registry/auth.go b/internal/helm/registry/auth.go index c8b3ca6ae..33f03e86b 100644 --- a/internal/helm/registry/auth.go +++ b/internal/helm/registry/auth.go @@ -18,37 +18,38 @@ package registry import ( "bytes" + "context" "fmt" "net/url" "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config/credentials" - "github.com/fluxcd/source-controller/internal/helm/common" - "github.com/fluxcd/source-controller/internal/oci" "github.com/google/go-containerregistry/pkg/authn" - "helm.sh/helm/v3/pkg/registry" corev1 "k8s.io/api/core/v1" + "oras.land/oras-go/v2/registry/remote/auth" + + "github.com/fluxcd/source-controller/internal/helm/common" + "github.com/fluxcd/source-controller/internal/oci" ) // helper is a subset of the Docker credential helper credentials.Helper interface used by NewKeychainFromHelper. type helper struct { registry string username, password string - err error } func (h helper) Get(serverURL string) (string, string, error) { if serverURL != h.registry { return "", "", fmt.Errorf("unexpected serverURL: %s", serverURL) } - return h.username, h.password, h.err + return h.username, h.password, nil } -// LoginOptionFromSecret derives authentication data from a Secret to login to an OCI registry. This Secret +// KeychainFromSecret derives authentication data from a Secret to login to an OCI registry. This Secret // may either hold "username" and "password" fields or be of the corev1.SecretTypeDockerConfigJson type and hold // a corev1.DockerConfigJsonKey field with a complete Docker configuration. If both, "username" and "password" are // empty, a nil LoginOption and a nil error will be returned. -func LoginOptionFromSecret(registryURL string, secret corev1.Secret) (authn.Keychain, error) { +func KeychainFromSecret(registryURL string, secret corev1.Secret) (authn.Keychain, error) { var username, password string parsedURL, err := url.Parse(registryURL) if err != nil { @@ -86,31 +87,22 @@ func LoginOptionFromSecret(registryURL string, secret corev1.Secret) (authn.Keyc return authn.NewKeychainFromHelper(helper{registry: parsedURL.Host, username: username, password: password}), nil } -// KeyChainAdaptHelper returns an ORAS credentials callback configured with the authorization data -// from the given authn keychain. This allows for example to make use of credential helpers from -// cloud providers. -// Ref: https://github.com/google/go-containerregistry/tree/main/pkg/authn -func KeychainAdaptHelper(keyChain authn.Keychain) func(string) (registry.LoginOption, error) { - return func(registryURL string) (registry.LoginOption, error) { - parsedURL, err := url.Parse(registryURL) - if err != nil { - return nil, fmt.Errorf("unable to parse registry URL '%s'", registryURL) - } - authenticator, err := keyChain.Resolve(common.StringResource{Registry: parsedURL.Host}) - if err != nil { - return nil, fmt.Errorf("unable to resolve credentials for registry '%s': %w", registryURL, err) - } - - return AuthAdaptHelper(authenticator) +// credsFromKeychain returns oras v2 credentials from a go-containerregistry Keychain. +func credsFromKeychain(registryURL string, keyChain authn.Keychain) (auth.CredentialFunc, error) { + parsedURL, err := url.Parse(registryURL) + if err != nil { + return nil, fmt.Errorf("unable to parse registry URL '%s'", registryURL) } + authenticator, err := keyChain.Resolve(common.StringResource{Registry: parsedURL.Host}) + if err != nil { + return nil, fmt.Errorf("unable to resolve credentials for registry '%s': %w", registryURL, err) + } + return credsFromAuthenticator(authenticator) } -// AuthAdaptHelper returns an ORAS credentials callback configured with the authorization data -// from the given authn authenticator. This allows for example to make use of credential helpers from -// cloud providers. -// Ref: https://github.com/google/go-containerregistry/tree/main/pkg/authn -func AuthAdaptHelper(auth authn.Authenticator) (registry.LoginOption, error) { - authConfig, err := auth.Authorization() +// credsFromAuthenticator returns oras v2 credentials from a go-containerregistry Authenticator. +func credsFromAuthenticator(authenticator authn.Authenticator) (auth.CredentialFunc, error) { + authConfig, err := authenticator.Authorization() if err != nil { return nil, fmt.Errorf("unable to get authentication data from OIDC: %w", err) } @@ -124,29 +116,20 @@ func AuthAdaptHelper(auth authn.Authenticator) (registry.LoginOption, error) { case username == "" || password == "": return nil, fmt.Errorf("invalid auth data: required fields 'username' and 'password'") } - return registry.LoginOptBasicAuth(username, password), nil + + return func(ctx context.Context, hostport string) (auth.Credential, error) { + return auth.Credential{Username: username, Password: password}, nil + }, nil } -// NewLoginOption returns a registry login option for the given HelmRepository. -// If the HelmRepository does not specify a secretRef, a nil login option is returned. -func NewLoginOption(auth authn.Authenticator, keychain authn.Keychain, registryURL string) (registry.LoginOption, error) { +func NewCredentials(auth authn.Authenticator, keychain authn.Keychain, registryURL string) (auth.CredentialFunc, error) { if auth != nil { - return AuthAdaptHelper(auth) + return credsFromAuthenticator(auth) } if keychain != nil { - return KeychainAdaptHelper(keychain)(registryURL) + return credsFromKeychain(registryURL, keychain) } return nil, nil } - -// TLSLoginOption returns a LoginOption that can be used to configure the TLS client. -// It requires either the caFile or both certFile and keyFile to be not blank. -func TLSLoginOption(certFile, keyFile, caFile string) registry.LoginOption { - if (certFile != "" && keyFile != "") || caFile != "" { - return registry.LoginOptTLSClientConfig(certFile, keyFile, caFile) - } - - return nil -} diff --git a/internal/helm/registry/auth_test.go b/internal/helm/registry/auth_test.go index 14942a5bb..c0e3ffde5 100644 --- a/internal/helm/registry/auth_test.go +++ b/internal/helm/registry/auth_test.go @@ -128,7 +128,7 @@ func TestLoginOptionFromSecret(t *testing.T) { secret.Data = tt.secretData secret.Type = tt.secretType - _, err := LoginOptionFromSecret(tt.url, secret) + _, err := KeychainFromSecret(tt.url, secret) g.Expect(err != nil).To(Equal(tt.wantErr)) }) } @@ -176,7 +176,7 @@ func TestKeychainAdaptHelper(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - loginOpt, err := KeychainAdaptHelper(tt.auth)(repoURL) + loginOpt, err := credsFromKeychain(repoURL, tt.auth) if tt.wantErr { g.Expect(err).To(HaveOccurred()) return diff --git a/internal/helm/registry/client.go b/internal/helm/registry/client.go index 5b89ea12e..4ffe25ca0 100644 --- a/internal/helm/registry/client.go +++ b/internal/helm/registry/client.go @@ -18,66 +18,46 @@ package registry import ( "crypto/tls" + "fmt" "io" "net/http" - "os" - "helm.sh/helm/v3/pkg/registry" - "k8s.io/apimachinery/pkg/util/errors" + "helm.sh/helm/v4/pkg/registry" + "oras.land/oras-go/v2/registry/remote/auth" + "oras.land/oras-go/v2/registry/remote/retry" + + "github.com/fluxcd/pkg/oci" ) -// ClientGenerator generates a registry client and a temporary credential file. -// The client is meant to be used for a single reconciliation. -// The file is meant to be used for a single reconciliation and deleted after. -func ClientGenerator(tlsConfig *tls.Config, isLogin, insecureHTTP bool) (*registry.Client, string, error) { - if isLogin { - // create a temporary file to store the credentials - // this is needed because otherwise the credentials are stored in ~/.docker/config.json. - credentialsFile, err := os.CreateTemp("", "credentials") - if err != nil { - return nil, "", err - } +var ( + // userAgent is the User-Agent header value sent with each request to an OCI registry + // through the Helm/ORAS client. It extends the pkg/oci.UserAgent ("flux/v2") following + // its format "/". + userAgent = fmt.Sprintf("%s/helm/v4/oras/v2", oci.UserAgent) +) - var errs []error - rClient, err := newClient(credentialsFile.Name(), tlsConfig, insecureHTTP) - if err != nil { - errs = append(errs, err) - // attempt to delete the temporary file - if credentialsFile != nil { - err := os.Remove(credentialsFile.Name()) - if err != nil { - errs = append(errs, err) - } - } - return nil, "", errors.NewAggregate(errs) - } - return rClient, credentialsFile.Name(), nil +// NewClient creates a new OCI registry client with the provided options. +func NewClient(creds auth.CredentialFunc, tlsConfig *tls.Config, insecureHTTP bool) (*registry.Client, error) { + baseTransport := http.DefaultTransport.(*http.Transport).Clone() + if tlsConfig != nil { + baseTransport.TLSClientConfig = tlsConfig } - - rClient, err := newClient("", tlsConfig, insecureHTTP) - if err != nil { - return nil, "", err + client := auth.Client{ + Client: &http.Client{ + // We use the oras retry transport here to keep consistent with oras behavior. + Transport: retry.NewTransport(baseTransport), + }, + Header: http.Header{ + "User-Agent": {userAgent}, + }, + Credential: creds, } - return rClient, "", nil -} - -func newClient(credentialsFile string, tlsConfig *tls.Config, insecureHTTP bool) (*registry.Client, error) { opts := []registry.ClientOption{ registry.ClientOptWriter(io.Discard), + registry.ClientOptAuthorizer(client), } if insecureHTTP { opts = append(opts, registry.ClientOptPlainHTTP()) } - if tlsConfig != nil { - t := http.DefaultTransport.(*http.Transport).Clone() - t.TLSClientConfig = tlsConfig - opts = append(opts, registry.ClientOptHTTPClient(&http.Client{ - Transport: t, - })) - } - if credentialsFile != "" { - opts = append(opts, registry.ClientOptCredentialsFile(credentialsFile)) - } - return registry.NewClient(opts...) } diff --git a/internal/helm/repository/chart_repository.go b/internal/helm/repository/chart_repository.go index e8030ec7b..1459ca1e4 100644 --- a/internal/helm/repository/chart_repository.go +++ b/internal/helm/repository/chart_repository.go @@ -33,9 +33,9 @@ import ( "github.com/Masterminds/semver/v3" "github.com/opencontainers/go-digest" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/getter" + repo "helm.sh/helm/v4/pkg/repo/v1" "sigs.k8s.io/yaml" "github.com/fluxcd/pkg/version" diff --git a/internal/helm/repository/chart_repository_test.go b/internal/helm/repository/chart_repository_test.go index 1b2f1c0fb..53301cdb6 100644 --- a/internal/helm/repository/chart_repository_test.go +++ b/internal/helm/repository/chart_repository_test.go @@ -29,9 +29,9 @@ import ( . "github.com/onsi/gomega" "github.com/opencontainers/go-digest" - "helm.sh/helm/v3/pkg/chart" - helmgetter "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo" + chart "helm.sh/helm/v4/pkg/chart/v2" + helmgetter "helm.sh/helm/v4/pkg/getter" + repo "helm.sh/helm/v4/pkg/repo/v1" "github.com/fluxcd/source-controller/internal/helm" ) diff --git a/internal/helm/repository/oci_chart_repository.go b/internal/helm/repository/oci_chart_repository.go index 2bed964a2..82aa602f6 100644 --- a/internal/helm/repository/oci_chart_repository.go +++ b/internal/helm/repository/oci_chart_repository.go @@ -20,24 +20,23 @@ import ( "bytes" "context" "crypto/tls" - "errors" "fmt" "net/url" - "os" "path" "sort" "strings" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" + chart "helm.sh/helm/v4/pkg/chart/v2" + "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/registry" + repo "helm.sh/helm/v4/pkg/repo/v1" "github.com/Masterminds/semver/v3" "github.com/google/go-containerregistry/pkg/name" "github.com/fluxcd/pkg/http/transport" "github.com/fluxcd/pkg/version" + "github.com/fluxcd/source-controller/internal/oci" ) @@ -45,8 +44,6 @@ import ( // It is used by the OCIChartRepository to retrieve chart versions // from OCI registries type RegistryClient interface { - Login(host string, opts ...registry.LoginOption) error - Logout(host string, opts ...registry.LogoutOption) error Tags(url string) ([]string, error) } @@ -67,12 +64,6 @@ type OCIChartRepository struct { // RegistryClient is a client to use while downloading tags or charts from a registry. RegistryClient RegistryClient - // credentialsFile is a temporary credentials file to use while downloading tags or charts from a registry. - credentialsFile string - - // certificatesStore is a temporary store to use while downloading tags or charts from a registry. - certificatesStore string - // verifiers is a list of verifiers to use when verifying a chart. verifiers []oci.Verifier @@ -127,22 +118,6 @@ func WithOCIGetterOptions(getterOpts []getter.Option) OCIChartRepositoryOption { } } -// WithCredentialsFile returns a ChartRepositoryOption that will set the credentials file -func WithCredentialsFile(credentialsFile string) OCIChartRepositoryOption { - return func(r *OCIChartRepository) error { - r.credentialsFile = credentialsFile - return nil - } -} - -// WithCertificatesStore returns a ChartRepositoryOption that will set the certificates store -func WithCertificatesStore(store string) OCIChartRepositoryOption { - return func(r *OCIChartRepository) error { - r.certificatesStore = store - return nil - } -} - // NewOCIChartRepository constructs and returns a new ChartRepository with // the ChartRepository.Client configured to the getter.Getter for the // repository URL scheme. It returns an error on URL parsing failures. @@ -261,51 +236,9 @@ func (r *OCIChartRepository) DownloadChart(chart *repo.ChartVersion) (*bytes.Buf return b, nil } -// Login attempts to login to the OCI registry. -// It returns an error on failure. -func (r *OCIChartRepository) Login(opts ...registry.LoginOption) error { - err := r.RegistryClient.Login(r.URL.Host, opts...) - if err != nil { - return err - } - return nil -} - -// Logout attempts to logout from the OCI registry. -// It returns an error on failure. -func (r *OCIChartRepository) Logout() error { - err := r.RegistryClient.Logout(r.URL.Host) - if err != nil { - return err - } - return nil -} - -// HasCredentials returns true if the OCIChartRepository has credentials. -func (r *OCIChartRepository) HasCredentials() bool { - return r.credentialsFile != "" -} - -// Clear deletes the OCI registry credentials file. +// Clear deletes the OCI registry certificates store. func (r *OCIChartRepository) Clear() error { - var errs error - // clean the credentials file if it exists - if r.credentialsFile != "" { - if err := os.Remove(r.credentialsFile); err != nil { - errs = errors.Join(errs, err) - } - } - r.credentialsFile = "" - - // clean the certificates store if it exists - if r.certificatesStore != "" { - if err := os.RemoveAll(r.certificatesStore); err != nil { - errs = errors.Join(errs, err) - } - } - r.certificatesStore = "" - - return errs + return nil } // getLastMatchingVersionOrConstraint returns the last version that matches the given version string. diff --git a/internal/helm/repository/oci_chart_repository_test.go b/internal/helm/repository/oci_chart_repository_test.go index 504d44e3e..7f281c62d 100644 --- a/internal/helm/repository/oci_chart_repository_test.go +++ b/internal/helm/repository/oci_chart_repository_test.go @@ -25,10 +25,10 @@ import ( "testing" . "github.com/onsi/gomega" - "helm.sh/helm/v3/pkg/chart" - helmgetter "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" + chart "helm.sh/helm/v4/pkg/chart/v2" + helmgetter "helm.sh/helm/v4/pkg/getter" + "helm.sh/helm/v4/pkg/registry" + repo "helm.sh/helm/v4/pkg/repo/v1" ) type OCIMockGetter struct { diff --git a/internal/helm/repository/repository.go b/internal/helm/repository/repository.go index 6cee5f658..1f079fbe6 100644 --- a/internal/helm/repository/repository.go +++ b/internal/helm/repository/repository.go @@ -20,7 +20,7 @@ import ( "bytes" "context" - "helm.sh/helm/v3/pkg/repo" + repo "helm.sh/helm/v4/pkg/repo/v1" "github.com/fluxcd/source-controller/internal/oci" ) diff --git a/internal/helm/repository/utils.go b/internal/helm/repository/utils.go index b784dec0d..1768bc08b 100644 --- a/internal/helm/repository/utils.go +++ b/internal/helm/repository/utils.go @@ -21,7 +21,7 @@ import ( "net/url" "strings" - helmreg "helm.sh/helm/v3/pkg/registry" + helmreg "helm.sh/helm/v4/pkg/registry" ) const ( diff --git a/main.go b/main.go index cb019e6e4..7316cc2e8 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( "time" flag "github.com/spf13/pflag" - "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v4/pkg/getter" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -63,7 +63,6 @@ import ( "github.com/fluxcd/source-controller/internal/controller" "github.com/fluxcd/source-controller/internal/features" "github.com/fluxcd/source-controller/internal/helm" - "github.com/fluxcd/source-controller/internal/helm/registry" ) const controllerName = "source-controller" @@ -233,7 +232,7 @@ func main() { Storage: storage, ControllerName: controllerName, TokenCache: tokenCache, - }).SetupWithManagerAndOptions(mgr, controller.GitRepositoryReconcilerOptions{ + }).SetupWithManager(mgr, controller.GitRepositoryReconcilerOptions{ DependencyRequeueInterval: requeueDependency, RateLimiter: helper.GetRateLimiter(rateLimiterOptions), }); err != nil { @@ -251,7 +250,7 @@ func main() { Cache: helmIndexCache, TTL: helmIndexCacheItemTTL, CacheRecorder: cacheRecorder, - }).SetupWithManagerAndOptions(mgr, controller.HelmRepositoryReconcilerOptions{ + }).SetupWithManager(mgr, controller.HelmRepositoryReconcilerOptions{ RateLimiter: helper.GetRateLimiter(rateLimiterOptions), }); err != nil { setupLog.Error(err, "unable to create controller", "controller", sourcev1.HelmRepositoryKind) @@ -259,17 +258,16 @@ func main() { } if err := (&controller.HelmChartReconciler{ - Client: mgr.GetClient(), - RegistryClientGenerator: registry.ClientGenerator, - Storage: storage, - Getters: getters, - EventRecorder: eventRecorder, - Metrics: metrics, - ControllerName: controllerName, - Cache: helmIndexCache, - TTL: helmIndexCacheItemTTL, - CacheRecorder: cacheRecorder, - }).SetupWithManagerAndOptions(ctx, mgr, controller.HelmChartReconcilerOptions{ + Client: mgr.GetClient(), + Storage: storage, + Getters: getters, + EventRecorder: eventRecorder, + Metrics: metrics, + ControllerName: controllerName, + Cache: helmIndexCache, + TTL: helmIndexCacheItemTTL, + CacheRecorder: cacheRecorder, + }).SetupWithManager(ctx, mgr, controller.HelmChartReconcilerOptions{ RateLimiter: helper.GetRateLimiter(rateLimiterOptions), }); err != nil { setupLog.Error(err, "unable to create controller", "controller", sourcev1.HelmChartKind) @@ -283,7 +281,7 @@ func main() { Storage: storage, ControllerName: controllerName, TokenCache: tokenCache, - }).SetupWithManagerAndOptions(mgr, controller.BucketReconcilerOptions{ + }).SetupWithManager(mgr, controller.BucketReconcilerOptions{ RateLimiter: helper.GetRateLimiter(rateLimiterOptions), }); err != nil { setupLog.Error(err, "unable to create controller", "controller", sourcev1.BucketKind) @@ -297,7 +295,7 @@ func main() { ControllerName: controllerName, TokenCache: tokenCache, Metrics: metrics, - }).SetupWithManagerAndOptions(mgr, controller.OCIRepositoryReconcilerOptions{ + }).SetupWithManager(mgr, controller.OCIRepositoryReconcilerOptions{ RateLimiter: helper.GetRateLimiter(rateLimiterOptions), }); err != nil { setupLog.Error(err, "unable to create controller", "controller", sourcev1.OCIRepositoryKind)