From afc6a2754f45774ad5a5e200cc7b50ba085eabb9 Mon Sep 17 00:00:00 2001 From: Adrian Fernandez De La Torre Date: Thu, 26 Mar 2026 02:22:16 +0100 Subject: [PATCH] Migrate event recorder to event/v1 Switch reconcilers to fluxcd/pkg/runtime/events with a shared reconcile.EventLogf. Add a typed api/v1 Action and named event reason constants. Signed-off-by: Adrian Fernandez De La Torre --- api/v1/action.go | 62 +++++++ api/v1/condition_types.go | 16 ++ docs/api/v1/source.md | 5 + go.mod | 75 +++++---- go.sum | 154 +++++++++--------- internal/controller/bucket_controller.go | 54 ++---- internal/controller/bucket_controller_test.go | 81 +++++---- .../controller/gitrepository_controller.go | 46 ++---- .../gitrepository_controller_test.go | 147 ++++++++++------- internal/controller/helmchart_controller.go | 58 +++---- .../controller/helmchart_controller_test.go | 94 ++++++----- .../controller/helmrepository_controller.go | 45 ++--- .../helmrepository_controller_test.go | 95 +++++++---- .../controller/ocirepository_controller.go | 46 ++---- .../ocirepository_controller_test.go | 131 +++++++++------ internal/controller/suite_test.go | 32 ++-- internal/reconcile/event.go | 50 ++++++ internal/reconcile/summarize/processor.go | 26 +-- .../reconcile/summarize/processor_test.go | 4 +- internal/reconcile/summarize/summary.go | 6 +- internal/reconcile/summarize/summary_test.go | 6 +- main.go | 27 ++- 22 files changed, 701 insertions(+), 559 deletions(-) create mode 100644 api/v1/action.go create mode 100644 internal/reconcile/event.go diff --git a/api/v1/action.go b/api/v1/action.go new file mode 100644 index 000000000..6214686e8 --- /dev/null +++ b/api/v1/action.go @@ -0,0 +1,62 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +// Action describes an observable stage of a source reconcile loop, from +// reconciling the local artifact storage through acquiring and verifying the +// upstream source, packaging the artifact and finalizing on deletion. +type Action string + +// String returns the string representation of the Action. +func (a Action) String() string { + return string(a) +} + +const ( + // ActionReconcile denotes the overall outcome of the reconcile loop, + // emitted once per run to report that reconciliation finished or failed. + ActionReconcile Action = "Reconcile" + + // ActionReconcileStorage verifies that the advertised artifact still + // exists in local storage, reconstructs the storage path, garbage + // collects stale artifacts and sets the artifact URL. + ActionReconcileStorage Action = "ReconcileStorage" + + // ActionReconcileSource acquires the upstream content — cloning the Git + // repository, pulling the OCI artifact, downloading the Helm index or + // chart, or fetching the Bucket objects — into a working directory and + // determines the revision. + ActionReconcileSource Action = "ReconcileSource" + + // ActionVerifySource validates the authenticity of the acquired source, + // using PGP or SSH for GitRepository and Cosign or Notation for + // OCIRepository and HelmChart. It runs as part of ReconcileSource. + ActionVerifySource Action = "VerifySource" + + // ActionReconcileArtifact archives the acquired content into an immutable + // Artifact, persists it under the per-object storage lock and publishes + // the artifact URL and revision. + ActionReconcileArtifact Action = "ReconcileArtifact" + + // ActionGarbageCollect prunes stale artifacts from storage, honoring the + // configured retention TTL and record count. + ActionGarbageCollect Action = "GarbageCollect" + + // ActionFinalize garbage collects the object's artifacts and removes the + // finalizer when the source is deleted. + ActionFinalize Action = "Finalize" +) diff --git a/api/v1/condition_types.go b/api/v1/condition_types.go index 9641db99c..619e36b6d 100644 --- a/api/v1/condition_types.go +++ b/api/v1/condition_types.go @@ -75,6 +75,10 @@ const ( // check failed. VerificationError string = "VerificationError" + // ArtifactVerificationFailedReason signals that the integrity verification + // of an Artifact in storage failed. + ArtifactVerificationFailedReason string = "ArtifactVerificationFailed" + // DirCreationFailedReason signals a failure caused by a directory creation // operation. DirCreationFailedReason string = "DirectoryCreationFailed" @@ -102,6 +106,18 @@ const ( // with the Source. ArtifactUpToDateReason string = "ArtifactUpToDate" + // NewArtifactReason signals that a new Artifact has been stored for the + // Source. + NewArtifactReason string = "NewArtifact" + + // NoSourceArtifactReason signals that the referenced Source does not yet + // have an Artifact available. + NoSourceArtifactReason string = "NoSourceArtifact" + + // GarbageCollectionSucceededReason signals that garbage collection of + // stale artifacts from storage succeeded. + GarbageCollectionSucceededReason string = "GarbageCollectionSucceeded" + // CacheOperationFailedReason signals a failure in cache operation. CacheOperationFailedReason string = "CacheOperationFailed" diff --git a/docs/api/v1/source.md b/docs/api/v1/source.md index a3ea61377..f7ecd4e34 100644 --- a/docs/api/v1/source.md +++ b/docs/api/v1/source.md @@ -1327,6 +1327,11 @@ OCIRepositoryStatus +

Action +(string alias)

+

Action describes an observable stage of a source reconcile loop, from +reconciling the local artifact storage through acquiring and verifying the +upstream source, packaging the artifact and finalizing on deletion.

BucketSTSSpec

diff --git a/go.mod b/go.mod index c96f84dbd..efd2d6b8f 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/docker/go-units v0.5.0 github.com/elazarl/goproxy v1.8.4 github.com/fluxcd/cli-utils v1.3.0 - github.com/fluxcd/pkg/apis/event v0.29.0 + github.com/fluxcd/pkg/apis/event v0.30.0 github.com/fluxcd/pkg/apis/meta v1.32.0 github.com/fluxcd/pkg/artifact v0.21.0 github.com/fluxcd/pkg/auth v0.57.0 @@ -34,7 +34,7 @@ require ( github.com/fluxcd/pkg/http/transport v0.8.0 github.com/fluxcd/pkg/masktoken v0.9.0 github.com/fluxcd/pkg/oci v0.70.0 - github.com/fluxcd/pkg/runtime v0.112.0 + github.com/fluxcd/pkg/runtime v0.115.0 github.com/fluxcd/pkg/sourceignore v0.19.0 github.com/fluxcd/pkg/ssh v0.26.0 github.com/fluxcd/pkg/tar v1.2.0 @@ -63,13 +63,13 @@ require ( github.com/sigstore/sigstore-go v1.2.0 github.com/sirupsen/logrus v1.9.4 github.com/spf13/pflag v1.0.10 - golang.org/x/crypto v0.56.0 - golang.org/x/sync v0.22.0 + golang.org/x/crypto v0.57.0 + golang.org/x/sync v0.23.0 google.golang.org/api v0.297.0 helm.sh/helm/v4 v4.2.4 - k8s.io/api v0.37.0 - k8s.io/apimachinery v0.37.0 - k8s.io/client-go v0.37.0 + k8s.io/api v0.37.1 + k8s.io/apimachinery v0.37.1 + k8s.io/client-go v0.37.1 k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 oras.land/oras-go/v2 v2.6.1 sigs.k8s.io/controller-runtime v0.25.1 @@ -182,12 +182,12 @@ require ( 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.19.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/felixge/httpsnoop v1.1.0 // indirect github.com/fluxcd/gitkit v0.6.0 // indirect github.com/fluxcd/pkg/apis/acl v0.11.0 // indirect github.com/fluxcd/pkg/lockedfile v0.8.0 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect - github.com/fxamacker/cbor/v2 v2.9.3 // indirect + github.com/fxamacker/cbor/v2 v2.9.4 // indirect github.com/gaganhr94/docker-credential-acr v1.0.2 // indirect github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect github.com/go-chi/chi/v5 v5.3.0 // indirect @@ -198,20 +198,19 @@ require ( github.com/go-ldap/ldap/v3 v3.4.10 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/analysis v0.25.2 // indirect - github.com/go-openapi/errors v0.22.7 // indirect + github.com/go-openapi/analysis v0.25.5 // indirect + github.com/go-openapi/errors v0.22.8 // indirect github.com/go-openapi/jsonpointer v1.0.0 // indirect github.com/go-openapi/jsonreference v1.0.1 // indirect - github.com/go-openapi/loads v0.23.3 // indirect - github.com/go-openapi/runtime v0.32.3 // indirect + github.com/go-openapi/loads v0.25.0 // indirect + github.com/go-openapi/runtime v0.33.0 // indirect github.com/go-openapi/runtime/server-middleware v0.30.0 // indirect - github.com/go-openapi/spec v0.22.5 // indirect - github.com/go-openapi/strfmt v0.26.3 // indirect + github.com/go-openapi/spec v0.22.9 // indirect + github.com/go-openapi/strfmt v0.27.0 // indirect github.com/go-openapi/swag v0.29.1 // indirect github.com/go-openapi/swag/cmdutils v0.29.1 // indirect github.com/go-openapi/swag/conv v0.29.1 // indirect github.com/go-openapi/swag/fileutils v0.29.1 // indirect - github.com/go-openapi/swag/jsonname v0.26.0 // indirect github.com/go-openapi/swag/jsonutils v0.29.1 // indirect github.com/go-openapi/swag/loading v0.29.1 // indirect github.com/go-openapi/swag/mangling v0.29.1 // indirect @@ -220,7 +219,7 @@ require ( github.com/go-openapi/swag/stringutils v0.29.1 // indirect github.com/go-openapi/swag/typeutils v0.29.1 // indirect github.com/go-openapi/swag/yamlutils v0.29.1 // indirect - github.com/go-openapi/validate v0.25.3 // indirect + github.com/go-openapi/validate v0.26.1 // indirect github.com/go-piv/piv-go/v2 v2.5.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gobwas/glob v0.2.3 // indirect @@ -244,7 +243,7 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gosuri/uitable v0.0.4 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect @@ -302,7 +301,7 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.3 // indirect - github.com/prometheus/common v0.71.0 // indirect + github.com/prometheus/common v0.72.0 // indirect github.com/prometheus/otlptranslator v1.0.0 // indirect github.com/prometheus/procfs v0.22.0 // indirect github.com/redis/go-redis/extra/rediscmd/v9 v9.5.3 // indirect @@ -353,9 +352,9 @@ require ( go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 // indirect + go.opentelemetry.io/otel v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 // indirect @@ -366,30 +365,30 @@ require ( go.opentelemetry.io/otel/exporters/prometheus v0.65.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0 // indirect go.opentelemetry.io/otel/log v0.19.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/sdk v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.45.0 // indirect + go.opentelemetry.io/otel/sdk v1.45.0 // indirect go.opentelemetry.io/otel/sdk/log v0.19.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.45.0 // indirect + go.opentelemetry.io/otel/trace v1.45.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/mod v0.39.0 // indirect - golang.org/x/net v0.58.0 // indirect - golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.47.0 // indirect - golang.org/x/term v0.45.0 // indirect - golang.org/x/text v0.41.0 // indirect + golang.org/x/mod v0.41.0 // indirect + golang.org/x/net v0.59.0 // indirect + golang.org/x/oauth2 v0.37.0 // indirect + golang.org/x/sys v0.48.0 // indirect + golang.org/x/term v0.46.0 // indirect + golang.org/x/text v0.42.0 // indirect golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.49.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect google.golang.org/grpc v1.83.2 // indirect google.golang.org/protobuf v1.36.12 // indirect @@ -399,14 +398,14 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.37.0 // indirect - k8s.io/apiserver v0.37.0 // indirect + k8s.io/apiextensions-apiserver v0.37.1 // indirect + k8s.io/apiserver v0.37.1 // indirect k8s.io/cli-runtime v0.37.0 // indirect - k8s.io/component-base v0.37.0 // indirect + k8s.io/component-base v0.37.1 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260821135717-be32def86098 // indirect k8s.io/kubectl v0.37.0 // indirect - sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/json v0.0.0-20260909141634-11ed52e25bc5 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/go.sum b/go.sum index f26b51b7d..60aeb2440 100644 --- a/go.sum +++ b/go.sum @@ -348,16 +348,16 @@ 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.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/fluxcd/cli-utils v1.3.0 h1:doEjNgeNb4klO/1mNgU9bkeou5zH/YpHBQqZ7HTBgkc= github.com/fluxcd/cli-utils v1.3.0/go.mod h1:qW5YGrml58qvAv+fBqp9Vp4xGkkIc6h6eJLd77eqFoM= github.com/fluxcd/gitkit v0.6.0 h1:iNg5LTx6ePo+Pl0ZwqHTAkhbUHxGVSY3YCxCdw7VIFg= github.com/fluxcd/gitkit v0.6.0/go.mod h1:svOHuKi0fO9HoawdK4HfHAJJseZDHHjk7I3ihnCIqNo= github.com/fluxcd/pkg/apis/acl v0.11.0 h1:BxThatpA6qd5Qzwim6/hRu0gdAX0JxNm3PledDuJqXo= github.com/fluxcd/pkg/apis/acl v0.11.0/go.mod h1:a87i2A7AlFO5N2J8CxtzaUCCDmuLLWOHwkKu3eJF5fY= -github.com/fluxcd/pkg/apis/event v0.29.0 h1:3yqa9SiHKjfPi7Tvm6PmdNrWNkQF6VSqVes3MkzTPi4= -github.com/fluxcd/pkg/apis/event v0.29.0/go.mod h1:zUZtsqopj9glf75szkL2anOFlHnhHuz9Mei9vnTusWE= +github.com/fluxcd/pkg/apis/event v0.30.0 h1:OX//v3zcR7MJjSGW01UQFiKPFJ80abjFddDYvAACdVM= +github.com/fluxcd/pkg/apis/event v0.30.0/go.mod h1:zUZtsqopj9glf75szkL2anOFlHnhHuz9Mei9vnTusWE= github.com/fluxcd/pkg/apis/meta v1.32.0 h1:jWuNuIziUM8NOrhZB7vovdFQ4wBYRNJoAn+XUoYlj1I= github.com/fluxcd/pkg/apis/meta v1.32.0/go.mod h1:bZmU0RbSwFzsCg9sgjhbSWxgSbUy+3Oh/s7qUCZjwPk= github.com/fluxcd/pkg/artifact v0.21.0 h1:hUb2JacxhTLMNmFy8G6IvJxJLFvifNpCr7dDuEQscmo= @@ -380,8 +380,8 @@ github.com/fluxcd/pkg/masktoken v0.9.0 h1:7KlzGsto6NEa4e4taP71M/3s+oRBgpwDQEYuWB github.com/fluxcd/pkg/masktoken v0.9.0/go.mod h1:4Gcu4E5jb65nhIC3/PeynOP0/o4bJ4n77nvlgENgdx8= github.com/fluxcd/pkg/oci v0.70.0 h1:rylPalOomTmMPR/SYSEcADxo6XM1AUr62FMwLoubd0Y= github.com/fluxcd/pkg/oci v0.70.0/go.mod h1:+mnv4AX8sGeCqFEF3xs9O04on8ByZNtq/ZQIHXJk0qU= -github.com/fluxcd/pkg/runtime v0.112.0 h1:FnE+98Fg2LJYRBxMYGOIuE4C7nwtLMCXxFFJpMizdsA= -github.com/fluxcd/pkg/runtime v0.112.0/go.mod h1:YUljTAXVaeWG+GLnXNZopFFMZstUoTxJs7+vl91OV58= +github.com/fluxcd/pkg/runtime v0.115.0 h1:jPt/YG/NXBjAqfc1M/APXClYuGx4dtDC7+kBm4xOus0= +github.com/fluxcd/pkg/runtime v0.115.0/go.mod h1:B8SgKzN1cctmfLImWbYM1lmlZdjx8IzqnQuLA1A/dKk= github.com/fluxcd/pkg/sourceignore v0.19.0 h1:s3/E03o9JTAvjIE7K8vI257AT2+0zy8cFOFaX+lx0z8= github.com/fluxcd/pkg/sourceignore v0.19.0/go.mod h1:MYwDEmDdj5GjHg1r1ljgtNkXNGkeV42G8BnCsggqExg= github.com/fluxcd/pkg/ssh v0.26.0 h1:fKw0gyZ2KgbngyI4zAzIfa0qnB4XjSpxBg1ZP6RySc8= @@ -401,8 +401,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= -github.com/fxamacker/cbor/v2 v2.9.3 h1:oQBnFATpNdY8gJHTndDDv5Xl4QqNaz51G5LLEPhng3Q= -github.com/fxamacker/cbor/v2 v2.9.3/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fxamacker/cbor/v2 v2.9.4 h1:xwjVlxEMR3S605oUlgBjKLTTeGFciYPGYCtF/35LKGo= +github.com/fxamacker/cbor/v2 v2.9.4/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gaganhr94/docker-credential-acr v1.0.2 h1:0eMFjVqRUmwINbhFxb5xLTWLJpdNta34TGuInVGtEGk= github.com/gaganhr94/docker-credential-acr v1.0.2/go.mod h1:8yd2V0GhCyd17MpMxfAJzcZqldu1ghFmrUV0GS7qcGc= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= @@ -437,24 +437,24 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/analysis v0.25.2 h1:I0vy4n3alz+DHTiN1PRhCb7QZxkK6g5YmswZKv2TKuw= -github.com/go-openapi/analysis v0.25.2/go.mod h1:Uhs1t/2XR10EnwONYILGEzw8gcfGIG5Xk5K2AxnhqDo= -github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA= -github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w= +github.com/go-openapi/analysis v0.25.5 h1:xPYEvTb90o1y0epuiOPAoG4QqahjP3cdp5xNlHeKJRI= +github.com/go-openapi/analysis v0.25.5/go.mod h1:d3UGtQC5uq5Kqqqis2VH09Km/v3vwsWrYkbp4gdm+Rc= +github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= +github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= github.com/go-openapi/jsonreference v1.0.1 h1:4zJ7AmYDKNmD3aSpfPnFNCFA5E80/xMHUNKgydaLh38= github.com/go-openapi/jsonreference v1.0.1/go.mod h1:dYplQXa6p5lXprLcJ8LE2iU7vNpXsAHDQ5ZAgL+Qx3A= -github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ= -github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA= -github.com/go-openapi/runtime v0.32.3 h1:J7Ycy5DJmhhP1By3NifhRUjnkXTrk21qbeqSULjwX8U= -github.com/go-openapi/runtime v0.32.3/go.mod h1:/WTQi0fa5DiGnnCXQKsTkSm15OzJp8Uz3H2t+67TBr4= +github.com/go-openapi/loads v0.25.0 h1:74Bc2snfaVlsHzwdQj/3gsA9XJz3daXTJVs+4ZaK7jI= +github.com/go-openapi/loads v0.25.0/go.mod h1:JFBw4SIB9+PTIFHDfcXuSSy5h6aWzjtUCrPYyx3qWU8= +github.com/go-openapi/runtime v0.33.0 h1:Dd3Oj2ig+WH8ckK95l0Wn2V8a4bH/UqWPRZVT0vc8yU= +github.com/go-openapi/runtime v0.33.0/go.mod h1:+rsupH3+TFKqmFysqkmgBOTxpVJV8eV+j9myvvea2Xw= github.com/go-openapi/runtime/server-middleware v0.30.0 h1:8rPoJ/xv7JL8BsovaqboKETlpWBArVh8n+0L/GyePog= github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs= -github.com/go-openapi/spec v0.22.5 h1:KhO7RBlKQfonUWX2WzQCoLIXVA6AcNqDGZ3a1Dutdlo= -github.com/go-openapi/spec v0.22.5/go.mod h1:vxpOtMya5TXtENXKE5bKqv5NjocVhyhxHrlZfvKnZ74= -github.com/go-openapi/strfmt v0.26.3 h1:rzmslHarJgBbf2qfGge+X3htclQfmXqBZMm0Too0HhU= -github.com/go-openapi/strfmt v0.26.3/go.mod h1:a5nsUw0oRpQzZeOwx8bi6cKbzFZslpbCKt1LEot+KnQ= +github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w= +github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0= +github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM= +github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc= github.com/go-openapi/swag v0.29.1 h1:C6EeWzUwQtcWEhE9eqBdUubGXxhWY4PlzHMLD7kLaiQ= github.com/go-openapi/swag v0.29.1/go.mod h1:BzxEXKiPlSXRsRTv1KSBF/BpGKHxA/YciCnr4tv9bvA= github.com/go-openapi/swag/cmdutils v0.29.1 h1:3DorPGfUdE80BogKY22EzoHBcHMrkVomZMoV7kS4ANY= @@ -463,8 +463,6 @@ github.com/go-openapi/swag/conv v0.29.1 h1:AC4Eh/5c/eUDOUCzzsRC9ghmFgOSBHeRMGIng github.com/go-openapi/swag/conv v0.29.1/go.mod h1:S1X7/ZrBEZOC0Wc8AGxjbcGS92l3WEjA7aPtpl+RaqM= github.com/go-openapi/swag/fileutils v0.29.1 h1:ZcPzMceVhU1WPbK6N1G6sNQKdd1CWJlf3cA08UHuoM0= github.com/go-openapi/swag/fileutils v0.29.1/go.mod h1:/wofKYckbtRl2p3+EwQsosie5CT1B38+dQ+PS579BzI= -github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= -github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= github.com/go-openapi/swag/jsonutils v0.29.1 h1:AFCxs0eQZ24/QyfhVHM2t49rMz7Vv3XCsZQI6yrNy+c= github.com/go-openapi/swag/jsonutils v0.29.1/go.mod h1:u3+sCfJpttDpcmS5kpm0yxL6GK0eWgODsx8Yw8fcqNM= github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.1 h1:BiiXE31Bx9SfpsMmOQj5KYpUhTZBpLVriVhJDuLuY2o= @@ -487,8 +485,8 @@ github.com/go-openapi/testify/enable/yaml/v2 v2.6.1 h1:Jm+/ze2rMtbD98yen92AhATGL github.com/go-openapi/testify/enable/yaml/v2 v2.6.1/go.mod h1:YDPnwCRDu38/oJBVMBVXOUDiJ9cIeBHWvfImHaXqnv4= github.com/go-openapi/testify/v2 v2.6.1 h1:6CNJhTjMzgaeaH8WhshcsZNPIvRemiOcFpU7seO/y7Q= github.com/go-openapi/testify/v2 v2.6.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= -github.com/go-openapi/validate v0.25.3 h1:4nzAIavcJ7WveHK2+V1UAkZK3kWcjzxZCzjfZAfavKs= -github.com/go-openapi/validate v0.25.3/go.mod h1:GemfuGMyYpIaBoKpX3z8sLywrmxpzWVOoJ7R0VeAVuk= +github.com/go-openapi/validate v0.26.1 h1:pZSbvtRO8G2R2FpWTYRn3w8LrsNwbtaVhP2dWiBa0Us= +github.com/go-openapi/validate v0.26.1/go.mod h1:B8UMgXiQiwwQWIbmuROlwJZDPGlikPuh7iHV1vPX9Oo= github.com/go-piv/piv-go/v2 v2.5.0 h1:w4KZ3GytEGZt8zm+S7olcIHZk0giL23xVqCa2HgwuqA= github.com/go-piv/piv-go/v2 v2.5.0/go.mod h1:ShZi74nnrWNQEdWzRUd/3cSig3uNOcEZp+EWl0oewnI= github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= @@ -600,8 +598,8 @@ github.com/graph-gophers/graphql-go v1.9.0 h1:yu0ucKHLc5qGpRwLYKIWtr9bOoxovkWasu github.com/graph-gophers/graphql-go v1.9.0/go.mod h1:23olKZ7duEvHlF/2ELEoSZaY1aNPfShjP782SOoNTyM= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY= 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= @@ -849,8 +847,8 @@ github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPT github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.71.0 h1:9KDAKb7Mj3HEVKyFCK6Dc/HIwlBzZIN2l7/lrHl3KK8= -github.com/prometheus/common v0.71.0/go.mod h1:CLJ5H8TEsGX8bl31BdMkfhIZ+QmZ9tBPPotUxUbfcmk= +github.com/prometheus/common v0.72.0 h1:tAYsE+sPJxIncDAobm4H5aQjmox9ZxEIIqPbiffa8G4= +github.com/prometheus/common v0.72.0/go.mod h1:77NWqAQ2tXT7BIK40qjJdw5Acrsrg1TlHAnsQi3i6mk= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1042,12 +1040,12 @@ go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9d go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8= go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0 h1:0Qx7VGBacMm9ZENQ7TnNObTYI4ShC+lHI16seduaxZo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.68.0/go.mod h1:Sje3i3MjSPKTSPvVWCaL8ugBzJwik3u4smCjUeuupqg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0 h1:oECp5f+hN7nkwjU/8BxQ/q23bGPb8FIrD839owX222E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0/go.mod h1:DqEFwLumhzMBDQv9PcWbyoDxHI/4lAk6CM4nJBH39sc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 h1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0/go.mod h1:085m8qbm4hgc8rZWGDEa4vmyyo2c3nPxUslYUKUIU04= +go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= +go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= @@ -1068,24 +1066,24 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0 h1:KJVjPD3rcPb98rIs3 go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.18.0/go.mod h1:K3kRa2ckmHWQaTWQdPRHc7qGXASuVuoEQXzrvlA98Ws= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0 h1:s/1iRkCKDfhlh1JF26knRneorus8aOwVIDhvYx9WoDw= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.42.0/go.mod h1:UI3wi0FXg1Pofb8ZBiBLhtMzgoTm1TYkMvn71fAqDzs= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0 h1:lsA/S1bxgdbyFGkTj+3meEdJ6ADVU7QoFstV6MXgE68= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0/go.mod h1:L7u+MirGoB1bjeLH66+xDykF4RC8C3RN7lIFpBiewUo= go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= -go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= +go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= +go.opentelemetry.io/otel/metric/x v0.67.0 h1:PcicCNZFkZ4bXfSooXdo3WN7RBOVOtjVdo1wD358Uns= +go.opentelemetry.io/otel/metric/x v0.67.0/go.mod h1:FBjCWZe6wgcqxcMtjdGiClDKXb2YxxXii0CXftE4QtI= +go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= +go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk= go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/otel/sdk/metric v1.45.0 h1:oVFszMfyj1Am6s24Vtc7wBb8BKLcwepJjNEYILuiE3o= +go.opentelemetry.io/otel/sdk/metric v1.45.0/go.mod h1:vUWUxDZvu1WVRj8JA8S0AdhsPrZoDpA2DdZauIh4mDA= +go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= +go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.step.sm/crypto v0.77.7 h1:6azC+pD678Vjju8yXnMDHCZJ+HzFaEmL3sCryiezTIA= @@ -1121,8 +1119,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.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= -golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= +golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= +golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -1134,8 +1132,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.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.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= -golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= +golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c= +golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o= 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= @@ -1166,11 +1164,11 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 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.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= -golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= +golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= +golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= -golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98= +golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1184,8 +1182,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 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.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= +golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= 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= @@ -1223,8 +1221,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.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= +golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= 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= @@ -1237,8 +1235,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.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= -golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= +golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= 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= @@ -1251,8 +1249,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.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= +golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1287,8 +1285,8 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms= google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= -google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d h1:QwnJwPte4XXAkhPu26LTDIahnsMSUV0kK8HkxbC+Pc4= -google.golang.org/genproto/googleapis/api v0.0.0-20260715232425-e75dac1f907d/go.mod h1:WRrQ7/7N19PypuT0fxLOL5Lq0waoiRri4FbtHDEKrGE= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc= +google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4= google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -1341,20 +1339,20 @@ helm.sh/helm/v4 v4.2.4 h1:qIysMI0JpTC4WXf3AQ99V6rZGT0+gO0Ww8IOnnUnaZk= helm.sh/helm/v4 v4.2.4/go.mod h1:ZP8nFdYe7jG1PTQelKzQXQ7m09/ruhMTrpDAf+OL5ms= 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.37.0 h1:Z//Vj9N7RA/yS2sDmxyeo7h+RR4zbUrd2vrd3Z0TbB4= -k8s.io/api v0.37.0/go.mod h1:LKXgcJWMc+f4OLbP5SFR8rulEg07zZhpi/zMULiBImk= -k8s.io/apiextensions-apiserver v0.37.0 h1:zRMQ3+/LIE5oZ0tVvXwYHC+dIkSP5cjNWju7AZU1LOI= -k8s.io/apiextensions-apiserver v0.37.0/go.mod h1:HU0PfSBwchHL5iDau6jjt9zU6ryWkDDlaVUiq91NK80= -k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0= -k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE= -k8s.io/apiserver v0.37.0 h1:TXg7OxsOWrAH8J4Zi/gBAZuMw1Dfdd+6cca2h4qjRqo= -k8s.io/apiserver v0.37.0/go.mod h1:OddHDF4gy9qyIb8o/3+qaeP6S0vEObWLgOygVqXksv0= +k8s.io/api v0.37.1 h1:l6N77U7tjwB5L056bgrBTJIEdevac/naBZ3iSvDNfpM= +k8s.io/api v0.37.1/go.mod h1:zSlbB1YpJ1YQlFVQy20UYll81UJSJJUMLhkhvg6Z78M= +k8s.io/apiextensions-apiserver v0.37.1 h1:7fIQG8eThDSTVYBWg/DOpI8v5wYfKCo0N8/TDMjj+zY= +k8s.io/apiextensions-apiserver v0.37.1/go.mod h1:1Q3ujWwHgnhBoGsdmGORflwK1sDFmuaqH1OsMDWk2Gs= +k8s.io/apimachinery v0.37.1 h1:hGCYyvKHCwtwMitj2vU4vYx0Z16N9GyZk9BBnz0wDAE= +k8s.io/apimachinery v0.37.1/go.mod h1:jF84AyUi/IRIXRot5f+lm6MpxoWI+F1XgjaMmwCdTFw= +k8s.io/apiserver v0.37.1 h1:dUsNwjsvItyVKNpC1N0AhQ0OBSiUJKfWz004vPifOe0= +k8s.io/apiserver v0.37.1/go.mod h1:EWXW6PntGfOC2uD9GGVobnOExgFoX1z0hZo7f7Rs7Ng= k8s.io/cli-runtime v0.37.0 h1:U3XakUeirBQJMz5688r04z74SIHSE7V5SIZ6Ho5JyBM= k8s.io/cli-runtime v0.37.0/go.mod h1:qiQMFkKwFFuPH6zy953On+nc3qfpEHAIDrJmAuRz5Vg= -k8s.io/client-go v0.37.0 h1:nsN31fy8wBySuZ+QRnKmrjRSQLOG2rvoGN0tKd12zhQ= -k8s.io/client-go v0.37.0/go.mod h1:FcGqw+Ll/gNQiq+nPGY1Oyt9y7SgDh1d3MW3RFDEbn0= -k8s.io/component-base v0.37.0 h1:3SdSa4+itMdFTDFTeR8CxKGmSTSMXFlKL4ky8OqjguM= -k8s.io/component-base v0.37.0/go.mod h1:LjOebp4R9y6LODWZQv102ZQxGheLcDO2ZJLAw6bbh4I= +k8s.io/client-go v0.37.1 h1:QTv/5ha4jAHtW9qxxVBkQVFBRDb4jHfFopQqqMdc+wM= +k8s.io/client-go v0.37.1/go.mod h1:dnAPtTnCNY38Ho04D2KdY1F4IKausa9UbqaAZKl60SY= +k8s.io/component-base v0.37.1 h1:93DMmlENnK7gNLkL4pMqLO5M/HVf3p3sM4q/GasLveY= +k8s.io/component-base v0.37.1/go.mod h1:bBrdziT4dreQG5lzTaPVxBwsZe1oxphG+ZVdD56dQWQ= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260821135717-be32def86098 h1:z5+pcu1jTyKK5mNTe2/+x+U6Uuv9jRVOJQLaBJJMpeI= @@ -1367,8 +1365,8 @@ oras.land/oras-go/v2 v2.6.1 h1:bonOEkjLfp8tt6qXWRRWP6p1F+9octchOf2EqnWB4Zs= oras.land/oras-go/v2 v2.6.1/go.mod h1:dhtFrFOuZuDtAVeZ9FUnaa5zfzplG3ZnFX9/uH1J/Yk= sigs.k8s.io/controller-runtime v0.25.1 h1:BKgU9OeE8xv8EbbM8cY0NVzTQs35rokkdq1jh12fMb4= sigs.k8s.io/controller-runtime v0.25.1/go.mod h1:4QqLdT6z/L6Olj8JJCtvztid4/fnIiYsfaTFScegctc= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/json v0.0.0-20260909141634-11ed52e25bc5 h1:k9jo7ED/fsfOcz3L1P20P5tjTNtsdQNwMOG2aKzHrl8= +sigs.k8s.io/json v0.0.0-20260909141634-11ed52e25bc5/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= sigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI= sigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= diff --git a/internal/controller/bucket_controller.go b/internal/controller/bucket_controller.go index 797b48709..8044875b8 100644 --- a/internal/controller/bucket_controller.go +++ b/internal/controller/bucket_controller.go @@ -32,9 +32,7 @@ import ( "golang.org/x/sync/errgroup" "golang.org/x/sync/semaphore" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - kuberecorder "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -43,7 +41,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" + eventv1 "github.com/fluxcd/pkg/apis/event/v1" "github.com/fluxcd/pkg/apis/meta" intdigest "github.com/fluxcd/pkg/artifact/digest" "github.com/fluxcd/pkg/artifact/storage" @@ -51,6 +49,7 @@ import ( "github.com/fluxcd/pkg/cache" "github.com/fluxcd/pkg/runtime/conditions" helper "github.com/fluxcd/pkg/runtime/controller" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/jitter" "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/runtime/predicates" @@ -126,7 +125,7 @@ var bucketFailConditions = []string{ // BucketReconciler reconciles a v1.Bucket object. type BucketReconciler struct { client.Client - kuberecorder.EventRecorder + events.Recorder helper.Metrics Storage *storage.Storage @@ -207,7 +206,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res // Always attempt to patch the object and status after each reconciliation // NOTE: The final runtime result and error are set in this block. defer func() { - summarizeHelper := summarize.NewHelper(r.EventRecorder, serialPatcher) + summarizeHelper := summarize.NewHelper(r.Recorder, serialPatcher) summarizeOpts := []summarize.Option{ summarize.WithConditions(bucketReadyCondition), summarize.WithReconcileResult(recResult), @@ -346,15 +345,16 @@ func (r *BucketReconciler) notify(ctx context.Context, oldObj, newObj *sourcev1. message := fmt.Sprintf("stored artifact with %d fetched files from '%s' bucket", index.Len(), newObj.Spec.BucketName) + action := sourcev1.ActionReconcile // Notify on new artifact and failure recovery. if !oldObj.GetArtifact().HasDigest(newObj.GetArtifact().Digest) { - r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal, - "NewArtifact", "%s", message) + r.AnnotatedEventf(newObj, nil, annotations, corev1.EventTypeNormal, + sourcev1.NewArtifactReason, action.String(), "%s", message) ctrl.LoggerFrom(ctx).Info(message) } else { if sreconcile.FailureRecovery(oldObj, newObj, bucketFailConditions) { - r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal, - meta.SucceededReason, "%s", message) + r.AnnotatedEventf(newObj, nil, annotations, corev1.EventTypeNormal, + meta.SucceededReason, action.String(), "%s", message) ctrl.LoggerFrom(ctx).Info(message) } } @@ -388,7 +388,7 @@ func (r *BucketReconciler) reconcileStorage(ctx context.Context, sp *patch.Seria // matches the actual artifact if !artifactMissing { if err := r.Storage.VerifyArtifact(*artifact); err != nil { - r.Eventf(obj, corev1.EventTypeWarning, "ArtifactVerificationFailed", "failed to verify integrity of artifact: %s", err.Error()) + r.Eventf(obj, nil, corev1.EventTypeWarning, sourcev1.ArtifactVerificationFailedReason, sourcev1.ActionReconcileStorage.String(), "failed to verify integrity of artifact: %s", err.Error()) if err = r.Storage.Remove(*artifact); err != nil { return sreconcile.ResultEmpty, fmt.Errorf("failed to remove artifact after digest mismatch: %w", err) @@ -525,7 +525,7 @@ func (r *BucketReconciler) reconcileArtifact(ctx context.Context, sp *patch.Seri if curArtifact := obj.GetArtifact(); curArtifact != nil && curArtifact.Revision != "" { curRev := digest.Digest(curArtifact.Revision) if curRev.Validate() == nil && index.Digest(curRev.Algorithm()) == curRev { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.ArtifactUpToDateReason, "artifact up-to-date with remote revision: '%s'", artifact.Revision) + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.ArtifactUpToDateReason, sourcev1.ActionReconcileArtifact, "artifact up-to-date with remote revision: '%s'", artifact.Revision) return sreconcile.ResultSuccess, nil } } @@ -582,7 +582,7 @@ func (r *BucketReconciler) reconcileArtifact(ctx context.Context, sp *patch.Seri // Update symlink on a "best effort" basis url, err := r.Storage.Symlink(artifact, "latest.tar.gz") if err != nil { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.SymlinkUpdateFailedReason, + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.SymlinkUpdateFailedReason, sourcev1.ActionReconcileArtifact, "failed to update status URL symlink: %s", err) } if url != "" { @@ -626,7 +626,7 @@ func (r *BucketReconciler) garbageCollect(ctx context.Context, obj *sourcev1.Buc "GarbageCollectionFailed", ) } else if deleted != "" { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "GarbageCollectionSucceeded", + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.GarbageCollectionSucceededReason, sourcev1.ActionFinalize, "garbage collected artifacts for deleted resource") } obj.Status.Artifact = nil @@ -641,7 +641,7 @@ func (r *BucketReconciler) garbageCollect(ctx context.Context, obj *sourcev1.Buc ) } if len(delFiles) > 0 { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "GarbageCollectionSucceeded", + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.GarbageCollectionSucceededReason, sourcev1.ActionGarbageCollect, "garbage collected %d artifacts", len(delFiles)) return nil } @@ -649,32 +649,6 @@ func (r *BucketReconciler) garbageCollect(ctx context.Context, obj *sourcev1.Buc return nil } -// eventLogf records events, and logs at the same time. -// -// This log is different from the debug log in the EventRecorder, in the sense -// that this is a simple log. While the debug log contains complete details -// about the event. -func (r *BucketReconciler) eventLogf(ctx context.Context, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) { - r.annotatedEventLogf(ctx, obj, nil, eventType, reason, messageFmt, args...) -} - -// annotatedEventLogf records annotated events, and logs at the same time. -// -// This log is different from the debug log in the EventRecorder, in the sense -// that this is a simple log. While the debug log contains complete details -// about the event. -func (r *BucketReconciler) annotatedEventLogf(ctx context.Context, - obj runtime.Object, annotations map[string]string, eventType string, reason string, messageFmt string, args ...interface{}) { - msg := fmt.Sprintf(messageFmt, args...) - // Log and emit event. - if eventType == corev1.EventTypeWarning { - ctrl.LoggerFrom(ctx).Error(errors.New(reason), msg) - } else { - ctrl.LoggerFrom(ctx).Info(msg) - } - r.AnnotatedEventf(obj, annotations, eventType, reason, "%s", msg) -} - // fetchEtagIndex fetches the current etagIndex for the in the obj specified // bucket using the given provider, while filtering them using .sourceignore // rules. After fetching an object, the etag value in the index is updated to diff --git a/internal/controller/bucket_controller_test.go b/internal/controller/bucket_controller_test.go index 2b7dd4743..494c53303 100644 --- a/internal/controller/bucket_controller_test.go +++ b/internal/controller/bucket_controller_test.go @@ -31,18 +31,19 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" kstatus "github.com/fluxcd/cli-utils/pkg/kstatus/status" + "github.com/fluxcd/pkg/apis/meta" intdigest "github.com/fluxcd/pkg/artifact/digest" "github.com/fluxcd/pkg/artifact/storage" "github.com/fluxcd/pkg/auth" "github.com/fluxcd/pkg/runtime/conditions" conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/jitter" "github.com/fluxcd/pkg/runtime/patch" @@ -84,9 +85,9 @@ func TestBucketReconciler_deleteBeforeFinalizer(t *testing.T) { g.Expect(k8sClient.Delete(ctx, bucket)).NotTo(HaveOccurred()) r := &BucketReconciler{ - Client: k8sClient, - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, + Client: k8sClient, + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, } // NOTE: Only a real API server responds with an error in this scenario. _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(bucket)}) @@ -383,9 +384,9 @@ func TestBucketReconciler_reconcileStorage(t *testing.T) { WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.Bucket{}). Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), } obj := &sourcev1.Bucket{ @@ -918,10 +919,10 @@ func TestBucketReconciler_reconcileSource_generic(t *testing.T) { } r := &BucketReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Client: clientBuilder.Build(), - Storage: testStorage, - patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Client: clientBuilder.Build(), + Storage: testStorage, + patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), } tmpDir := t.TempDir() @@ -1437,10 +1438,10 @@ func TestBucketReconciler_reconcileSource_gcs(t *testing.T) { } r := &BucketReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Client: clientBuilder.Build(), - Storage: testStorage, - patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Client: clientBuilder.Build(), + Storage: testStorage, + patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), } // Handle ObjectLevelWorkloadIdentity feature gate @@ -1640,10 +1641,10 @@ func TestBucketReconciler_reconcileArtifact(t *testing.T) { WithStatusSubresource(&sourcev1.Bucket{}) r := &BucketReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), } obj := &sourcev1.Bucket{ @@ -1764,7 +1765,7 @@ func TestBucketReconciler_statusConditions(t *testing.T) { } ctx := context.TODO() - summarizeHelper := summarize.NewHelper(record.NewFakeRecorder(32), serialPatcher) + summarizeHelper := summarize.NewHelper(events.NewFakeRecorder(32, false), serialPatcher) summarizeOpts := []summarize.Option{ summarize.WithConditions(bucketReadyCondition), summarize.WithReconcileResult(sreconcile.ResultSuccess), @@ -1791,7 +1792,7 @@ func TestBucketReconciler_notify(t *testing.T) { resErr error oldObjBeforeFunc func(obj *sourcev1.Bucket) newObjBeforeFunc func(obj *sourcev1.Bucket) - wantEvent string + wantEvent *corev1.Event }{ { name: "error - no event", @@ -1805,7 +1806,12 @@ func TestBucketReconciler_notify(t *testing.T) { newObjBeforeFunc: func(obj *sourcev1.Bucket) { obj.Status.Artifact = &meta.Artifact{Revision: "xxx", Digest: "yyy"} }, - wantEvent: "Normal NewArtifact stored artifact with 2 fetched files from", + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: sourcev1.NewArtifactReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored artifact with 2 fetched files from", + }, }, { name: "recovery from failure", @@ -1820,7 +1826,12 @@ func TestBucketReconciler_notify(t *testing.T) { obj.Status.Artifact = &meta.Artifact{Revision: "xxx", Digest: "yyy"} conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready") }, - wantEvent: "Normal Succeeded stored artifact with 2 fetched files from", + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: meta.SucceededReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored artifact with 2 fetched files from", + }, }, { name: "recovery and new artifact", @@ -1835,7 +1846,12 @@ func TestBucketReconciler_notify(t *testing.T) { obj.Status.Artifact = &meta.Artifact{Revision: "aaa", Digest: "bbb"} conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready") }, - wantEvent: "Normal NewArtifact stored artifact with 2 fetched files from", + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: sourcev1.NewArtifactReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored artifact with 2 fetched files from", + }, }, { name: "no updates", @@ -1856,7 +1872,7 @@ func TestBucketReconciler_notify(t *testing.T) { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - recorder := record.NewFakeRecorder(32) + recorder := events.NewFakeRecorder(32, false) oldObj := &sourcev1.Bucket{ Spec: sourcev1.BucketSpec{ @@ -1873,8 +1889,8 @@ func TestBucketReconciler_notify(t *testing.T) { } reconciler := &BucketReconciler{ - EventRecorder: recorder, - patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), + Recorder: recorder, + patchOptions: getPatchOptions(bucketReadyCondition.Owned, "sc"), } index := index.NewDigester(index.WithIndex(map[string]string{ "zzz": "qqq", @@ -1884,12 +1900,15 @@ func TestBucketReconciler_notify(t *testing.T) { select { case x, ok := <-recorder.Events: - g.Expect(ok).To(Equal(tt.wantEvent != ""), "unexpected event received") - if tt.wantEvent != "" { - g.Expect(x).To(ContainSubstring(tt.wantEvent)) + g.Expect(ok).To(Equal(tt.wantEvent != nil), "unexpected event received") + if tt.wantEvent != nil { + g.Expect(x.Type).To(Equal(tt.wantEvent.Type)) + g.Expect(x.Reason).To(Equal(tt.wantEvent.Reason)) + g.Expect(x.Action).To(Equal(tt.wantEvent.Action)) + g.Expect(x.Message).To(ContainSubstring(tt.wantEvent.Message)) } default: - if tt.wantEvent != "" { + if tt.wantEvent != nil { t.Errorf("expected some event to be emitted") } } diff --git a/internal/controller/gitrepository_controller.go b/internal/controller/gitrepository_controller.go index a3c27c9b6..e4628de7f 100644 --- a/internal/controller/gitrepository_controller.go +++ b/internal/controller/gitrepository_controller.go @@ -30,14 +30,13 @@ import ( "github.com/fluxcd/pkg/auth" "github.com/fluxcd/pkg/auth/githubapp" authutils "github.com/fluxcd/pkg/auth/utils" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/logger" "github.com/fluxcd/pkg/runtime/secrets" "github.com/go-git/go-git/v5/plumbing/transport" ssh "golang.org/x/crypto/ssh" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - kuberecorder "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" @@ -48,7 +47,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" + eventv1 "github.com/fluxcd/pkg/apis/event/v1" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/artifact/storage" "github.com/fluxcd/pkg/cache" @@ -165,7 +164,7 @@ func getPatchOptions(ownedConditions []string, controllerName string) []patch.Op // GitRepositoryReconciler reconciles a v1.GitRepository object. type GitRepositoryReconciler struct { client.Client - kuberecorder.EventRecorder + events.Recorder helper.Metrics Storage *storage.Storage @@ -225,7 +224,7 @@ func (r *GitRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reques // Always attempt to patch the object and status after each reconciliation // NOTE: The final runtime result and error are set in this block. defer func() { - summarizeHelper := summarize.NewHelper(r.EventRecorder, serialPatcher) + summarizeHelper := summarize.NewHelper(r.Recorder, serialPatcher) summarizeOpts := []summarize.Option{ summarize.WithConditions(gitRepositoryReadyCondition), summarize.WithBiPolarityConditionTypes(sourcev1.SourceVerifiedCondition), @@ -375,15 +374,16 @@ func (r *GitRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *so message = fmt.Sprintf("stored artifact for commit '%s'", commitReference(newObj, &commit)) } + action := sourcev1.ActionReconcile // Notify on new artifact and failure recovery. if !oldObj.GetArtifact().HasDigest(newObj.GetArtifact().Digest) { - r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal, - "NewArtifact", "%s", message) + r.AnnotatedEventf(newObj, nil, annotations, corev1.EventTypeNormal, + sourcev1.NewArtifactReason, action.String(), "%s", message) ctrl.LoggerFrom(ctx).Info(message) } else { if sreconcile.FailureRecovery(oldObj, newObj, gitRepositoryFailConditions) { - r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal, - meta.SucceededReason, "%s", message) + r.AnnotatedEventf(newObj, nil, annotations, corev1.EventTypeNormal, + meta.SucceededReason, action.String(), "%s", message) ctrl.LoggerFrom(ctx).Info(message) } } @@ -437,7 +437,7 @@ func (r *GitRepositoryReconciler) reconcileStorage(ctx context.Context, sp *patc // matches the actual artifact if !artifactMissing { if err := r.Storage.VerifyArtifact(*artifact); err != nil { - r.Eventf(obj, corev1.EventTypeWarning, "ArtifactVerificationFailed", "failed to verify integrity of artifact: %s", err.Error()) + r.Eventf(obj, nil, corev1.EventTypeWarning, sourcev1.ArtifactVerificationFailedReason, sourcev1.ActionReconcileStorage.String(), "failed to verify integrity of artifact: %s", err.Error()) if err = r.Storage.Remove(*artifact); err != nil { return sreconcile.ResultEmpty, fmt.Errorf("failed to remove artifact after digest mismatch: %w", err) @@ -870,7 +870,7 @@ func (r *GitRepositoryReconciler) reconcileArtifact(ctx context.Context, sp *pat if curArtifact := obj.GetArtifact(); curArtifact.HasRevision(artifact.Revision) && !includes.Diff(obj.Status.IncludedArtifacts) && !gitContentConfigChanged(obj, includes) { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.ArtifactUpToDateReason, "artifact up-to-date with remote revision: '%s'", curArtifact.Revision) + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.ArtifactUpToDateReason, sourcev1.ActionReconcileArtifact, "artifact up-to-date with remote revision: '%s'", curArtifact.Revision) return sreconcile.ResultSuccess, nil } @@ -947,7 +947,7 @@ func (r *GitRepositoryReconciler) reconcileArtifact(ctx context.Context, sp *pat if fi, err := os.Lstat(r.Storage.LocalPath(artifact)); err == nil { if fi.Mode()&os.ModeSymlink != 0 { if err := os.Remove(r.Storage.LocalPath(*symArtifact)); err != nil { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.SymlinkUpdateFailedReason, + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.SymlinkUpdateFailedReason, sourcev1.ActionReconcileArtifact, "failed to remove (deprecated) symlink: %s", err) } } @@ -1218,7 +1218,7 @@ func (r *GitRepositoryReconciler) verifySignature(ctx context.Context, obj *sour mode := obj.Spec.Verification.GetMode() obj.Status.SourceVerificationMode = &mode conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, reason, "%s", message.String()) - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, reason, "%s", message.String()) + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, reason, sourcev1.ActionVerifySource, "%s", message.String()) return sreconcile.ResultSuccess, nil } @@ -1256,7 +1256,7 @@ func (r *GitRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc "GarbageCollectionFailed", ) } else if deleted != "" { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "GarbageCollectionSucceeded", + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.GarbageCollectionSucceededReason, sourcev1.ActionFinalize, "garbage collected artifacts for deleted resource") } obj.Status.Artifact = nil @@ -1271,7 +1271,7 @@ func (r *GitRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc ) } if len(delFiles) > 0 { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "GarbageCollectionSucceeded", + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.GarbageCollectionSucceededReason, sourcev1.ActionGarbageCollect, "garbage collected %d artifacts", len(delFiles)) return nil } @@ -1279,22 +1279,6 @@ func (r *GitRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc return nil } -// eventLogf records events, and logs at the same time. -// -// This log is different from the debug log in the EventRecorder, in the sense -// that this is a simple log. While the debug log contains complete details -// about the event. -func (r *GitRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) { - msg := fmt.Sprintf(messageFmt, args...) - // Log and emit event. - if eventType == corev1.EventTypeWarning { - ctrl.LoggerFrom(ctx).Error(errors.New(reason), msg) - } else { - ctrl.LoggerFrom(ctx).Info(msg) - } - r.Eventf(obj, eventType, reason, "%s", msg) -} - // gitContentConfigChanged evaluates the current spec with the observations of // the artifact in the status to determine if artifact content configuration has // changed and requires rebuilding the artifact. Rebuilding the artifact is also diff --git a/internal/controller/gitrepository_controller_test.go b/internal/controller/gitrepository_controller_test.go index 84b2074a8..75e88507b 100644 --- a/internal/controller/gitrepository_controller_test.go +++ b/internal/controller/gitrepository_controller_test.go @@ -39,7 +39,6 @@ import ( sshtestdata "golang.org/x/crypto/ssh/testdata" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/record" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -55,6 +54,7 @@ import ( "github.com/fluxcd/pkg/gittestserver" "github.com/fluxcd/pkg/runtime/conditions" conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/jitter" "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/ssh" @@ -231,9 +231,9 @@ func TestGitRepositoryReconciler_deleteBeforeFinalizer(t *testing.T) { g.Expect(k8sClient.Delete(ctx, gitRepo)).NotTo(HaveOccurred()) r := &GitRepositoryReconciler{ - Client: k8sClient, - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, + Client: k8sClient, + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, } // NOTE: Only a real API server responds with an error in this scenario. _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(gitRepo)}) @@ -344,10 +344,10 @@ func TestGitRepositoryReconciler_reconcileSource_emptyRepository(t *testing.T) { WithStatusSubresource(&sourcev1.GitRepository{}) r := &GitRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } g.Expect(r.Client.Create(context.TODO(), obj)).ToNot(HaveOccurred()) @@ -901,10 +901,10 @@ func TestGitRepositoryReconciler_reconcileSource_authStrategy(t *testing.T) { } r := &GitRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } tmpDir := t.TempDir() @@ -1084,10 +1084,10 @@ func TestGitRepositoryReconciler_getAuthOpts_provider(t *testing.T) { obj := &sourcev1.GitRepository{} r := &GitRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Client: clientBuilder.Build(), - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Client: clientBuilder.Build(), + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } url, err := url.Parse(tt.url) @@ -1305,9 +1305,9 @@ func TestGitRepositoryReconciler_reconcileSource_checkoutStrategy(t *testing.T) WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.GitRepository{}). Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } for _, tt := range tests { @@ -1509,10 +1509,10 @@ func TestGitRepositoryReconciler_reconcileArtifact(t *testing.T) { resetChmod(tt.dir, 0o750, 0o600) r := &GitRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } obj := &sourcev1.GitRepository{ @@ -1660,7 +1660,7 @@ func TestGitRepositoryReconciler_reconcileInclude(t *testing.T) { r := &GitRepositoryReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: storage, requeueDependency: dependencyInterval, features: features.FeatureGates(), @@ -1917,10 +1917,10 @@ func TestGitRepositoryReconciler_reconcileStorage(t *testing.T) { WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.GitRepository{}). Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } obj := &sourcev1.GitRepository{ @@ -1971,10 +1971,10 @@ func TestGitRepositoryReconciler_reconcileDelete(t *testing.T) { g := NewWithT(t) r := &GitRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } obj := &sourcev1.GitRepository{ @@ -2762,10 +2762,10 @@ func TestGitRepositoryReconciler_verifySignature(t *testing.T) { } r := &GitRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Client: clientBuilder.Build(), - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Client: clientBuilder.Build(), + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } obj := &sourcev1.GitRepository{ @@ -2914,11 +2914,11 @@ func TestGitRepositoryReconciler_ConditionsUpdate(t *testing.T) { WithStatusSubresource(&sourcev1.GitRepository{}) r := &GitRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } key := client.ObjectKeyFromObject(obj) @@ -3165,7 +3165,7 @@ func TestGitRepositoryReconciler_statusConditions(t *testing.T) { } ctx := context.TODO() - summarizeHelper := summarize.NewHelper(record.NewFakeRecorder(32), serialPatcher) + summarizeHelper := summarize.NewHelper(events.NewFakeRecorder(32, false), serialPatcher) summarizeOpts := []summarize.Option{ summarize.WithConditions(gitRepositoryReadyCondition), summarize.WithBiPolarityConditionTypes(sourcev1.SourceVerifiedCondition), @@ -3206,7 +3206,7 @@ func TestGitRepositoryReconciler_notify(t *testing.T) { oldObjBeforeFunc func(obj *sourcev1.GitRepository) newObjBeforeFunc func(obj *sourcev1.GitRepository) commit git.Commit - wantEvent string + wantEvent *corev1.Event }{ { name: "error - no event", @@ -3220,8 +3220,13 @@ func TestGitRepositoryReconciler_notify(t *testing.T) { newObjBeforeFunc: func(obj *sourcev1.GitRepository) { obj.Status.Artifact = &meta.Artifact{Revision: "xxx", Digest: "yyy"} }, - commit: concreteCommit, - wantEvent: "Normal NewArtifact stored artifact for commit 'test commit'", + commit: concreteCommit, + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: sourcev1.NewArtifactReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored artifact for commit 'test commit'", + }, }, { name: "recovery from failure", @@ -3236,8 +3241,13 @@ func TestGitRepositoryReconciler_notify(t *testing.T) { obj.Status.Artifact = &meta.Artifact{Revision: "xxx", Digest: "yyy"} conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready") }, - commit: concreteCommit, - wantEvent: "Normal Succeeded stored artifact for commit 'test commit'", + commit: concreteCommit, + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: meta.SucceededReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored artifact for commit 'test commit'", + }, }, { name: "recovery and new artifact", @@ -3252,8 +3262,13 @@ func TestGitRepositoryReconciler_notify(t *testing.T) { obj.Status.Artifact = &meta.Artifact{Revision: "aaa", Digest: "bbb"} conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready") }, - commit: concreteCommit, - wantEvent: "Normal NewArtifact stored artifact for commit 'test commit'", + commit: concreteCommit, + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: sourcev1.NewArtifactReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored artifact for commit 'test commit'", + }, }, { name: "no updates", @@ -3281,15 +3296,20 @@ func TestGitRepositoryReconciler_notify(t *testing.T) { obj.Status.Artifact = &meta.Artifact{Revision: "xxx", Digest: "yyy"} conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready") }, - commit: partialCommit, // no-op will always result in partial commit. - wantEvent: "Normal Succeeded stored artifact for commit 'sha1:b9b3feadba509cb9b22e968a5d27e96c2bc2ff91'", + commit: partialCommit, // no-op will always result in partial commit. + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: meta.SucceededReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored artifact for commit 'sha1:b9b3feadba509cb9b22e968a5d27e96c2bc2ff91'", + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - recorder := record.NewFakeRecorder(32) + recorder := events.NewFakeRecorder(32, false) oldObj := &sourcev1.GitRepository{} newObj := oldObj.DeepCopy() @@ -3302,20 +3322,23 @@ func TestGitRepositoryReconciler_notify(t *testing.T) { } reconciler := &GitRepositoryReconciler{ - EventRecorder: recorder, - features: features.FeatureGates(), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + Recorder: recorder, + features: features.FeatureGates(), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } reconciler.notify(ctx, oldObj, newObj, tt.commit, tt.res, tt.resErr) select { case x, ok := <-recorder.Events: - g.Expect(ok).To(Equal(tt.wantEvent != ""), "unexpected event received") - if tt.wantEvent != "" { - g.Expect(x).To(ContainSubstring(tt.wantEvent)) + g.Expect(ok).To(Equal(tt.wantEvent != nil), "unexpected event received") + if tt.wantEvent != nil { + g.Expect(x.Type).To(Equal(tt.wantEvent.Type)) + g.Expect(x.Reason).To(Equal(tt.wantEvent.Reason)) + g.Expect(x.Action).To(Equal(tt.wantEvent.Action)) + g.Expect(x.Message).To(ContainSubstring(tt.wantEvent.Message)) } default: - if tt.wantEvent != "" { + if tt.wantEvent != nil { t.Errorf("expected some event to be emitted") } } @@ -3445,9 +3468,9 @@ func TestGitRepositoryReconciler_fetchIncludes(t *testing.T) { } r := &GitRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + Recorder: events.NewFakeRecorder(32, false), + patchOptions: getPatchOptions(gitRepositoryReadyCondition.Owned, "sc"), } obj := &sourcev1.GitRepository{ diff --git a/internal/controller/helmchart_controller.go b/internal/controller/helmchart_controller.go index 9197e11e7..ca80c4f25 100644 --- a/internal/controller/helmchart_controller.go +++ b/internal/controller/helmchart_controller.go @@ -38,9 +38,7 @@ import ( 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" - kuberecorder "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -51,12 +49,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" + eventv1 "github.com/fluxcd/pkg/apis/event/v1" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/artifact/storage" "github.com/fluxcd/pkg/git" "github.com/fluxcd/pkg/runtime/conditions" helper "github.com/fluxcd/pkg/runtime/controller" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/jitter" "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/runtime/predicates" @@ -128,7 +127,7 @@ var helmChartFailConditions = []string{ // HelmChartReconciler reconciles a HelmChart object type HelmChartReconciler struct { client.Client - kuberecorder.EventRecorder + events.Recorder helper.Metrics Storage *storage.Storage @@ -226,7 +225,7 @@ func (r *HelmChartReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // Always attempt to patch the object after each reconciliation. // NOTE: The final runtime result and error are set in this block. defer func() { - summarizeHelper := summarize.NewHelper(r.EventRecorder, serialPatcher) + summarizeHelper := summarize.NewHelper(r.Recorder, serialPatcher) summarizeOpts := []summarize.Option{ summarize.WithConditions(helmChartReadyCondition), summarize.WithBiPolarityConditionTypes(sourcev1.SourceVerifiedCondition), @@ -346,15 +345,16 @@ func (r *HelmChartReconciler) notify(ctx context.Context, oldObj, newObj *source fmt.Sprintf("%s/%s", sourcev1.GroupVersion.Group, eventv1.MetaDigestKey): newObj.Status.Artifact.Digest, } + action := sourcev1.ActionReconcile // Notify on new artifact and failure recovery. if !oldObj.GetArtifact().HasDigest(newObj.GetArtifact().Digest) { - r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal, - reasonForBuild(build), "%s", build.Summary()) + r.AnnotatedEventf(newObj, nil, annotations, corev1.EventTypeNormal, + reasonForBuild(build), action.String(), "%s", build.Summary()) ctrl.LoggerFrom(ctx).Info(build.Summary()) } else { if sreconcile.FailureRecovery(oldObj, newObj, helmChartFailConditions) { - r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal, - reasonForBuild(build), "%s", build.Summary()) + r.AnnotatedEventf(newObj, nil, annotations, corev1.EventTypeNormal, + reasonForBuild(build), action.String(), "%s", build.Summary()) ctrl.LoggerFrom(ctx).Info(build.Summary()) } } @@ -388,7 +388,7 @@ func (r *HelmChartReconciler) reconcileStorage(ctx context.Context, sp *patch.Se // matches the actual artifact if !artifactMissing { if err := r.Storage.VerifyArtifact(*artifact); err != nil { - r.Eventf(obj, corev1.EventTypeWarning, "ArtifactVerificationFailed", "failed to verify integrity of artifact: %s", err.Error()) + r.Eventf(obj, nil, corev1.EventTypeWarning, sourcev1.ArtifactVerificationFailedReason, sourcev1.ActionReconcileStorage.String(), "failed to verify integrity of artifact: %s", err.Error()) if err = r.Storage.Remove(*artifact); err != nil { return sreconcile.ResultEmpty, fmt.Errorf("failed to remove artifact after digest mismatch: %w", err) @@ -458,9 +458,9 @@ func (r *HelmChartReconciler) reconcileSource(ctx context.Context, sp *patch.Ser if s.GetArtifact() == nil || !r.Storage.ArtifactExist(*s.GetArtifact()) { // Set the condition to indicate that the source has no artifact for all types except OCI HelmRepository if helmRepo, ok := s.(*sourcev1.HelmRepository); !ok || helmRepo.Spec.Type != sourcev1.HelmRepositoryTypeOCI { - conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, "NoSourceArtifact", + conditions.MarkTrue(obj, sourcev1.FetchFailedCondition, sourcev1.NoSourceArtifactReason, "no artifact available for %s source '%s'", obj.Spec.SourceRef.Kind, obj.Spec.SourceRef.Name) - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "NoSourceArtifact", + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.NoSourceArtifactReason, sourcev1.ActionReconcileSource, "no artifact available for %s source '%s'", obj.Spec.SourceRef.Kind, obj.Spec.SourceRef.Name) return sreconcile.ResultRequeue, nil } @@ -482,7 +482,7 @@ func (r *HelmChartReconciler) reconcileSource(ctx context.Context, sp *patch.Ser // a sudden (partial) disappearance of observed state. // TODO(hidde): include specific name/version information? if depNum := build.ResolvedDependencies; build.Complete() && depNum > 0 { - r.Eventf(obj, eventv1.EventTypeTrace, "ResolvedDependencies", "resolved %d chart dependencies", depNum) + r.Eventf(obj, nil, eventv1.EventTypeTrace, "ResolvedDependencies", sourcev1.ActionReconcileSource.String(), "resolved %d chart dependencies", depNum) } // Handle any build error @@ -623,7 +623,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * // If we succeed in loading the index, cache it. if httpChartRepo.Index != nil { if err = r.Cache.Set(repo.GetArtifact().Path, httpChartRepo.Index, r.TTL); err != nil { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.CacheOperationFailedReason, "failed to cache index: %s", err) + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.CacheOperationFailedReason, sourcev1.ActionReconcileSource, "failed to cache index: %s", err) } } }() @@ -752,7 +752,7 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj defer func() { err := dm.Clear() if err != nil { - r.eventLogf(ctx, obj, corev1.EventTypeWarning, meta.FailedReason, + sreconcile.EventLogf(ctx, r, obj, corev1.EventTypeWarning, meta.FailedReason, sourcev1.ActionReconcileSource, "dependency manager cleanup error: %s", err) } }() @@ -845,7 +845,7 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, _ *patch.Se // Return early if the build path equals the current artifact path if curArtifact := obj.GetArtifact(); curArtifact != nil && r.Storage.LocalPath(*curArtifact) == b.Path { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.ArtifactUpToDateReason, "artifact up-to-date with remote revision: '%s'", artifact.Revision) + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.ArtifactUpToDateReason, sourcev1.ActionReconcileArtifact, "artifact up-to-date with remote revision: '%s'", artifact.Revision) return sreconcile.ResultSuccess, nil } @@ -894,7 +894,7 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, _ *patch.Se // Update symlink on a "best effort" basis symURL, err := r.Storage.Symlink(artifact, "latest.tar.gz") if err != nil { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.SymlinkUpdateFailedReason, + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.SymlinkUpdateFailedReason, sourcev1.ActionReconcileArtifact, "failed to update status URL symlink: %s", err) } if symURL != "" { @@ -904,7 +904,7 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, _ *patch.Se return sreconcile.ResultSuccess, nil } -// getSource returns the v1beta1.Source for the given object, or an error describing why the source could not be +// getSource returns the v1.Source for the given object, or an error describing why the source could not be // returned. func (r *HelmChartReconciler) getSource(ctx context.Context, obj *sourcev1.HelmChart) (sourcev1.Source, error) { namespacedName := types.NamespacedName{ @@ -968,7 +968,7 @@ func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1. "GarbageCollectionFailed", ) } else if deleted != "" { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "GarbageCollectionSucceeded", + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.GarbageCollectionSucceededReason, sourcev1.ActionFinalize, "garbage collected artifacts for deleted resource") } obj.Status.Artifact = nil @@ -983,7 +983,7 @@ func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1. ) } if len(delFiles) > 0 { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "GarbageCollectionSucceeded", + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.GarbageCollectionSucceededReason, sourcev1.ActionGarbageCollect, "garbage collected %d artifacts", len(delFiles)) return nil } @@ -992,7 +992,7 @@ func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1. } // namespacedChartRepositoryCallback returns a chart.GetChartDownloaderCallback scoped to the given namespace. -// The returned callback returns a repository.Downloader configured with the retrieved v1beta1.HelmRepository, +// The returned callback returns a repository.Downloader configured with the retrieved v1.HelmRepository, // or a shim with defaults if no object could be found. // The callback returns an object with a state, so the caller has to do the necessary cleanup. func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Context, name, namespace string) chart.GetChartDownloaderCallback { @@ -1202,22 +1202,6 @@ func (r *HelmChartReconciler) requestsForBucketChange(ctx context.Context, o cli return reqs } -// eventLogf records events, and logs at the same time. -// -// This log is different from the debug log in the EventRecorder, in the sense -// that this is a simple log. While the debug log contains complete details -// about the event. -func (r *HelmChartReconciler) eventLogf(ctx context.Context, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) { - msg := fmt.Sprintf(messageFmt, args...) - // Log and emit event. - if eventType == corev1.EventTypeWarning { - ctrl.LoggerFrom(ctx).Error(errors.New(reason), msg) - } else { - ctrl.LoggerFrom(ctx).Info(msg) - } - r.Eventf(obj, eventType, reason, "%s", msg) -} - // observeChartBuild records the observation on the given given build and error on the object. func observeChartBuild(ctx context.Context, sp *patch.SerialPatcher, pOpts []patch.Option, obj *sourcev1.HelmChart, build *chart.Build, err error) { if build.HasMetadata() { diff --git a/internal/controller/helmchart_controller_test.go b/internal/controller/helmchart_controller_test.go index 29e8ba525..762dce5c6 100644 --- a/internal/controller/helmchart_controller_test.go +++ b/internal/controller/helmchart_controller_test.go @@ -54,7 +54,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/tools/record" oras "oras.land/oras-go/v2/registry/remote" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -67,6 +66,7 @@ import ( "github.com/fluxcd/pkg/helmtestserver" "github.com/fluxcd/pkg/runtime/conditions" conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/jitter" "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/testserver" @@ -112,7 +112,7 @@ func TestHelmChartReconciler_deleteBeforeFinalizer(t *testing.T) { r := &HelmChartReconciler{ Client: k8sClient, - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, } @@ -520,9 +520,9 @@ func TestHelmChartReconciler_reconcileStorage(t *testing.T) { WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.HelmChart{}). Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } obj := &sourcev1.HelmChart{ @@ -773,7 +773,7 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) { g.Expect(obj.Status.ObservedSourceArtifactRevision).To(Equal("foo")) g.Expect(obj.Status.Conditions).To(conditions.MatchConditions([]metav1.Condition{ - *conditions.TrueCondition(sourcev1.FetchFailedCondition, "NoSourceArtifact", "no artifact available"), + *conditions.TrueCondition(sourcev1.FetchFailedCondition, sourcev1.NoSourceArtifactReason, "no artifact available"), *conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "foo"), *conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "foo"), })) @@ -794,7 +794,7 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) { r := &HelmChartReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: st, CosignVerifierFactory: testCosignVerifierFactory, patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), @@ -1131,7 +1131,7 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) { r := &HelmChartReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Getters: testGetters, Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, @@ -1384,7 +1384,7 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) { r := &HelmChartReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Getters: testGetters, Storage: st, CosignVerifierFactory: testCosignVerifierFactory, @@ -1627,10 +1627,10 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) { WithScheme(testEnv.Scheme()). WithStatusSubresource(&sourcev1.HelmChart{}). Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: st, - Getters: testGetters, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Storage: st, + Getters: testGetters, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } obj := &sourcev1.HelmChart{ @@ -1838,9 +1838,9 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) { WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.HelmChart{}). Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } obj := &sourcev1.HelmChart{ @@ -2028,7 +2028,7 @@ func TestHelmChartReconciler_reconcileDelete(t *testing.T) { g := NewWithT(t) r := &HelmChartReconciler{ - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), @@ -2298,7 +2298,7 @@ func TestHelmChartReconciler_statusConditions(t *testing.T) { } ctx := context.TODO() - summarizeHelper := summarize.NewHelper(record.NewFakeRecorder(32), serialPatcher) + summarizeHelper := summarize.NewHelper(events.NewFakeRecorder(32, false), serialPatcher) summarizeOpts := []summarize.Option{ summarize.WithConditions(helmChartReadyCondition), summarize.WithBiPolarityConditionTypes(sourcev1.SourceVerifiedCondition), @@ -2326,7 +2326,7 @@ func TestHelmChartReconciler_notify(t *testing.T) { resErr error oldObjBeforeFunc func(obj *sourcev1.HelmChart) newObjBeforeFunc func(obj *sourcev1.HelmChart) - wantEvent string + wantEvent *corev1.Event }{ { name: "error - no event", @@ -2340,7 +2340,12 @@ func TestHelmChartReconciler_notify(t *testing.T) { newObjBeforeFunc: func(obj *sourcev1.HelmChart) { obj.Status.Artifact = &meta.Artifact{Revision: "xxx", Digest: "yyy"} }, - wantEvent: "Normal ChartPackageSucceeded packaged", + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: sourcev1.ChartPackageSucceededReason, + Action: sourcev1.ActionReconcile.String(), + Message: "packaged", + }, }, { name: "recovery from failure", @@ -2355,7 +2360,12 @@ func TestHelmChartReconciler_notify(t *testing.T) { obj.Status.Artifact = &meta.Artifact{Revision: "xxx", Digest: "yyy"} conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready") }, - wantEvent: "Normal ChartPackageSucceeded packaged", + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: sourcev1.ChartPackageSucceededReason, + Action: sourcev1.ActionReconcile.String(), + Message: "packaged", + }, }, { name: "recovery and new artifact", @@ -2370,7 +2380,12 @@ func TestHelmChartReconciler_notify(t *testing.T) { obj.Status.Artifact = &meta.Artifact{Revision: "aaa", Digest: "bbb"} conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready") }, - wantEvent: "Normal ChartPackageSucceeded packaged", + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: sourcev1.ChartPackageSucceededReason, + Action: sourcev1.ActionReconcile.String(), + Message: "packaged", + }, }, { name: "no updates", @@ -2390,7 +2405,7 @@ func TestHelmChartReconciler_notify(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - recorder := record.NewFakeRecorder(32) + recorder := events.NewFakeRecorder(32, false) oldObj := &sourcev1.HelmChart{} newObj := oldObj.DeepCopy() @@ -2403,8 +2418,8 @@ func TestHelmChartReconciler_notify(t *testing.T) { } reconciler := &HelmChartReconciler{ - EventRecorder: recorder, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + Recorder: recorder, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } build := &chart.Build{ Name: "foo", @@ -2416,12 +2431,15 @@ func TestHelmChartReconciler_notify(t *testing.T) { select { case x, ok := <-recorder.Events: - g.Expect(ok).To(Equal(tt.wantEvent != ""), "unexpected event received") - if tt.wantEvent != "" { - g.Expect(x).To(ContainSubstring(tt.wantEvent)) + g.Expect(ok).To(Equal(tt.wantEvent != nil), "unexpected event received") + if tt.wantEvent != nil { + g.Expect(x.Type).To(Equal(tt.wantEvent.Type)) + g.Expect(x.Reason).To(Equal(tt.wantEvent.Reason)) + g.Expect(x.Action).To(Equal(tt.wantEvent.Action)) + g.Expect(x.Message).To(ContainSubstring(tt.wantEvent.Message)) } default: - if tt.wantEvent != "" { + if tt.wantEvent != nil { t.Errorf("expected some event to be emitted") } } @@ -2689,8 +2707,8 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) { } if tt.secretOpts.username != "" && tt.secretOpts.password != "" { - tt.secret.Data[".dockerconfigjson"] = []byte(fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, - server.registryHost, tt.secretOpts.username, tt.secretOpts.password)) + tt.secret.Data[".dockerconfigjson"] = fmt.Appendf(nil, `{"auths": {%q: {"username": %q, "password": %q}}}`, + server.registryHost, tt.secretOpts.username, tt.secretOpts.password) } if tt.secret != nil { @@ -2725,10 +2743,10 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) { } r := &HelmChartReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Getters: testGetters, - patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + Recorder: events.NewFakeRecorder(32, false), + Getters: testGetters, + patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"), } var b chart.Build @@ -2885,7 +2903,7 @@ func TestHelmChartRepository_reconcileSource_verifyOCISourceSignature_keyless(t r := &HelmChartReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Getters: testGetters, Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, @@ -3192,7 +3210,7 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignatureNotation(t *t r := &HelmChartReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Getters: testGetters, Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, @@ -3446,7 +3464,7 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignatureCosign(t *tes r := &HelmChartReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Getters: testGetters, Storage: st, CosignVerifierFactory: testCosignVerifierFactory, diff --git a/internal/controller/helmrepository_controller.go b/internal/controller/helmrepository_controller.go index 475fb0918..7266e6e89 100644 --- a/internal/controller/helmrepository_controller.go +++ b/internal/controller/helmrepository_controller.go @@ -29,8 +29,6 @@ import ( 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" "k8s.io/client-go/util/workqueue" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -39,12 +37,13 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" + eventv1 "github.com/fluxcd/pkg/apis/event/v1" "github.com/fluxcd/pkg/apis/meta" intdigest "github.com/fluxcd/pkg/artifact/digest" "github.com/fluxcd/pkg/artifact/storage" "github.com/fluxcd/pkg/runtime/conditions" helper "github.com/fluxcd/pkg/runtime/controller" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/jitter" "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/runtime/predicates" @@ -105,7 +104,7 @@ var helmRepositoryFailConditions = []string{ // HelmRepositoryReconciler reconciles a v1.HelmRepository object. type HelmRepositoryReconciler struct { client.Client - kuberecorder.EventRecorder + events.Recorder helper.Metrics Getters helmgetter.Providers @@ -170,7 +169,7 @@ func (r *HelmRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reque // Always attempt to patch the object after each reconciliation. // NOTE: The final runtime result and error are set in this block. defer func() { - summarizeHelper := summarize.NewHelper(r.EventRecorder, serialPatcher) + summarizeHelper := summarize.NewHelper(r.Recorder, serialPatcher) summarizeOpts := []summarize.Option{ summarize.WithConditions(helmRepositoryReadyCondition), summarize.WithReconcileResult(recResult), @@ -299,13 +298,13 @@ func (r *HelmRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *s // Notify on new artifact and failure recovery. if !oldObj.GetArtifact().HasDigest(newObj.GetArtifact().Digest) { - r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal, - "NewArtifact", "%s", message) + r.AnnotatedEventf(newObj, nil, annotations, corev1.EventTypeNormal, + sourcev1.NewArtifactReason, sourcev1.ActionReconcile.String(), "%s", message) ctrl.LoggerFrom(ctx).Info(message) } else { if sreconcile.FailureRecovery(oldObj, newObj, helmRepositoryFailConditions) { - r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal, - meta.SucceededReason, "%s", message) + r.AnnotatedEventf(newObj, nil, annotations, corev1.EventTypeNormal, + meta.SucceededReason, sourcev1.ActionReconcile.String(), "%s", message) ctrl.LoggerFrom(ctx).Info(message) } } @@ -340,7 +339,7 @@ func (r *HelmRepositoryReconciler) reconcileStorage(ctx context.Context, sp *pat // matches the actual artifact if !artifactMissing { if err := r.Storage.VerifyArtifact(*artifact); err != nil { - r.Eventf(obj, corev1.EventTypeWarning, "ArtifactVerificationFailed", "failed to verify integrity of artifact: %s", err.Error()) + r.Eventf(obj, nil, corev1.EventTypeWarning, sourcev1.ArtifactVerificationFailedReason, sourcev1.ActionReconcileStorage.String(), "failed to verify integrity of artifact: %s", err.Error()) if err = r.Storage.Remove(*artifact); err != nil { return sreconcile.ResultEmpty, fmt.Errorf("failed to remove artifact after digest mismatch: %w", err) @@ -547,7 +546,7 @@ func (r *HelmRepositoryReconciler) reconcileArtifact(ctx context.Context, sp *pa r.Cache.SetExpiration(artifact.Path, r.TTL) } - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.ArtifactUpToDateReason, "artifact up-to-date with remote revision: '%s'", artifact.Revision) + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.ArtifactUpToDateReason, sourcev1.ActionReconcileArtifact, "artifact up-to-date with remote revision: '%s'", artifact.Revision) return sreconcile.ResultSuccess, nil } @@ -609,14 +608,14 @@ func (r *HelmRepositoryReconciler) reconcileArtifact(ctx context.Context, sp *pa // authentication. Using the Artifact.Path is safe as the path is in // the format of: ///. if err := r.Cache.Set(artifact.Path, chartRepo.Index, r.TTL); err != nil { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.CacheOperationFailedReason, "failed to cache index: %s", err) + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.CacheOperationFailedReason, sourcev1.ActionReconcileArtifact, "failed to cache index: %s", err) } } // Update index symlink. indexURL, err := r.Storage.Symlink(*artifact, "index.yaml") if err != nil { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.SymlinkUpdateFailedReason, + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.SymlinkUpdateFailedReason, sourcev1.ActionReconcileArtifact, "failed to update status URL symlink: %s", err) } if indexURL != "" { @@ -665,7 +664,7 @@ func (r *HelmRepositoryReconciler) garbageCollect(ctx context.Context, obj *sour "GarbageCollectionFailed", ) } else if deleted != "" { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "GarbageCollectionSucceeded", + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.GarbageCollectionSucceededReason, sourcev1.ActionFinalize, "garbage collected artifacts for deleted resource") } // Evict the index from the cache. @@ -688,7 +687,7 @@ func (r *HelmRepositoryReconciler) garbageCollect(ctx context.Context, obj *sour ) } if len(delFiles) > 0 { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "GarbageCollectionSucceeded", + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.GarbageCollectionSucceededReason, sourcev1.ActionGarbageCollect, "garbage collected %d artifacts", len(delFiles)) return nil } @@ -696,22 +695,6 @@ func (r *HelmRepositoryReconciler) garbageCollect(ctx context.Context, obj *sour return nil } -// eventLogf records events, and logs at the same time. -// -// This log is different from the debug log in the EventRecorder, in the sense -// that this is a simple log. While the debug log contains complete details -// about the event. -func (r *HelmRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) { - msg := fmt.Sprintf(messageFmt, args...) - // Log and emit event. - if eventType == corev1.EventTypeWarning { - ctrl.LoggerFrom(ctx).Error(errors.New(reason), msg) - } else { - ctrl.LoggerFrom(ctx).Info(msg) - } - r.Eventf(obj, eventType, reason, "%s", msg) -} - // migrateToStatic is HelmRepository OCI migration to static object. func (r *HelmRepositoryReconciler) migrationToStatic(ctx context.Context, sp *patch.SerialPatcher, obj *sourcev1.HelmRepository) (result ctrl.Result, err error) { // Skip migration if suspended and not being deleted. diff --git a/internal/controller/helmrepository_controller_test.go b/internal/controller/helmrepository_controller_test.go index 6318592d8..c34c49d27 100644 --- a/internal/controller/helmrepository_controller_test.go +++ b/internal/controller/helmrepository_controller_test.go @@ -35,7 +35,6 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -48,6 +47,7 @@ import ( "github.com/fluxcd/pkg/helmtestserver" "github.com/fluxcd/pkg/runtime/conditions" conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/runtime/secrets" @@ -85,9 +85,9 @@ func TestHelmRepositoryReconciler_deleteBeforeFinalizer(t *testing.T) { g.Expect(k8sClient.Delete(ctx, helmrepo)).NotTo(HaveOccurred()) r := &HelmRepositoryReconciler{ - Client: k8sClient, - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, + Client: k8sClient, + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, } // NOTE: Only a real API server responds with an error in this scenario. _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(helmrepo)}) @@ -358,11 +358,11 @@ func TestHelmRepositoryReconciler_reconcileStorage(t *testing.T) { WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.HelmRepository{}). Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - Cache: cache.New(10, time.Minute), - TTL: time.Minute, - patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + Cache: cache.New(10, time.Minute), + TTL: time.Minute, + patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), } obj := &sourcev1.HelmRepository{ @@ -1045,11 +1045,11 @@ func TestHelmRepositoryReconciler_reconcileSource(t *testing.T) { } r := &HelmRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Client: clientBuilder.Build(), - Storage: testStorage, - Getters: testGetters, - patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Client: clientBuilder.Build(), + Storage: testStorage, + Getters: testGetters, + patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), } if tt.beforeFunc != nil { tt.beforeFunc(g, obj) @@ -1218,11 +1218,11 @@ func TestHelmRepositoryReconciler_reconcileArtifact(t *testing.T) { WithScheme(testEnv.GetScheme()). WithStatusSubresource(&sourcev1.HelmRepository{}). Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - Cache: tt.cache, - TTL: 1 * time.Minute, - patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + Cache: tt.cache, + TTL: 1 * time.Minute, + patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), } obj := &sourcev1.HelmRepository{ @@ -1296,10 +1296,10 @@ func TestHelmRepositoryReconciler_garbageCollectEvictsCache(t *testing.T) { c := cache.New(10, time.Minute) r := &HelmRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - Cache: c, - TTL: time.Minute, + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + Cache: c, + TTL: time.Minute, } obj := &sourcev1.HelmRepository{ @@ -1543,7 +1543,7 @@ func TestHelmRepositoryReconciler_statusConditions(t *testing.T) { } ctx := context.TODO() - summarizeHelper := summarize.NewHelper(record.NewFakeRecorder(32), serialPatcher) + summarizeHelper := summarize.NewHelper(events.NewFakeRecorder(32, false), serialPatcher) summarizeOpts := []summarize.Option{ summarize.WithConditions(helmRepositoryReadyCondition), summarize.WithReconcileResult(sreconcile.ResultSuccess), @@ -1569,7 +1569,7 @@ func TestHelmRepositoryReconciler_notify(t *testing.T) { resErr error oldObjBeforeFunc func(obj *sourcev1.HelmRepository) newObjBeforeFunc func(obj *sourcev1.HelmRepository) - wantEvent string + wantEvent *corev1.Event }{ { name: "error - no event", @@ -1583,7 +1583,12 @@ func TestHelmRepositoryReconciler_notify(t *testing.T) { newObjBeforeFunc: func(obj *sourcev1.HelmRepository) { obj.Status.Artifact = &meta.Artifact{Revision: "xxx", Digest: "yyy", Size: nil} }, - wantEvent: "Normal NewArtifact stored fetched index of unknown size", + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: sourcev1.NewArtifactReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored fetched index of unknown size", + }, }, { name: "new artifact", @@ -1592,7 +1597,12 @@ func TestHelmRepositoryReconciler_notify(t *testing.T) { newObjBeforeFunc: func(obj *sourcev1.HelmRepository) { obj.Status.Artifact = &meta.Artifact{Revision: "xxx", Digest: "yyy", Size: &aSize} }, - wantEvent: "Normal NewArtifact stored fetched index of size", + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: sourcev1.NewArtifactReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored fetched index of size", + }, }, { name: "recovery from failure", @@ -1607,7 +1617,12 @@ func TestHelmRepositoryReconciler_notify(t *testing.T) { obj.Status.Artifact = &meta.Artifact{Revision: "xxx", Digest: "yyy", Size: &aSize} conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready") }, - wantEvent: "Normal Succeeded stored fetched index of size", + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: meta.SucceededReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored fetched index of size", + }, }, { name: "recovery and new artifact", @@ -1622,7 +1637,12 @@ func TestHelmRepositoryReconciler_notify(t *testing.T) { obj.Status.Artifact = &meta.Artifact{Revision: "aaa", Digest: "bbb", Size: &aSize} conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready") }, - wantEvent: "Normal NewArtifact stored fetched index of size", + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: sourcev1.NewArtifactReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored fetched index of size", + }, }, { name: "no updates", @@ -1642,7 +1662,7 @@ func TestHelmRepositoryReconciler_notify(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - recorder := record.NewFakeRecorder(32) + recorder := events.NewFakeRecorder(32, false) oldObj := &sourcev1.HelmRepository{} newObj := oldObj.DeepCopy() @@ -1655,8 +1675,8 @@ func TestHelmRepositoryReconciler_notify(t *testing.T) { } reconciler := &HelmRepositoryReconciler{ - EventRecorder: recorder, - patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), + Recorder: recorder, + patchOptions: getPatchOptions(helmRepositoryReadyCondition.Owned, "sc"), } chartRepo := repository.ChartRepository{ URL: "some-address", @@ -1665,12 +1685,15 @@ func TestHelmRepositoryReconciler_notify(t *testing.T) { select { case x, ok := <-recorder.Events: - g.Expect(ok).To(Equal(tt.wantEvent != ""), "unexpected event received") - if tt.wantEvent != "" { - g.Expect(x).To(ContainSubstring(tt.wantEvent)) + g.Expect(ok).To(Equal(tt.wantEvent != nil), "unexpected event received") + if tt.wantEvent != nil { + g.Expect(x.Type).To(Equal(tt.wantEvent.Type)) + g.Expect(x.Reason).To(Equal(tt.wantEvent.Reason)) + g.Expect(x.Action).To(Equal(tt.wantEvent.Action)) + g.Expect(x.Message).To(ContainSubstring(tt.wantEvent.Message)) } default: - if tt.wantEvent != "" { + if tt.wantEvent != nil { t.Errorf("expected some event to be emitted") } } diff --git a/internal/controller/ocirepository_controller.go b/internal/controller/ocirepository_controller.go index 34a053a1c..96c57958a 100644 --- a/internal/controller/ocirepository_controller.go +++ b/internal/controller/ocirepository_controller.go @@ -20,7 +20,6 @@ import ( "context" cryptotls "crypto/tls" "encoding/json" - "errors" "fmt" "io" "net/http" @@ -42,14 +41,12 @@ import ( "github.com/sigstore/cosign/v3/pkg/cosign" "helm.sh/helm/v4/pkg/registry" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - kuberecorder "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/reconcile" - eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" + eventv1 "github.com/fluxcd/pkg/apis/event/v1" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/artifact/storage" "github.com/fluxcd/pkg/auth" @@ -57,6 +54,7 @@ import ( "github.com/fluxcd/pkg/oci" "github.com/fluxcd/pkg/runtime/conditions" helper "github.com/fluxcd/pkg/runtime/controller" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/jitter" "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/runtime/predicates" @@ -139,7 +137,7 @@ type ociRepositoryReconcileFunc func(ctx context.Context, sp *patch.SerialPatche type OCIRepositoryReconciler struct { client.Client helper.Metrics - kuberecorder.EventRecorder + events.Recorder Storage *storage.Storage ControllerName string @@ -195,7 +193,7 @@ func (r *OCIRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Reques // Always attempt to patch the object and status after each reconciliation // NOTE: The final runtime result and error are set in this block. defer func() { - summarizeHelper := summarize.NewHelper(r.EventRecorder, serialPatcher) + summarizeHelper := summarize.NewHelper(r.Recorder, serialPatcher) summarizeOpts := []summarize.Option{ summarize.WithConditions(ociRepositoryReadyCondition), summarize.WithBiPolarityConditionTypes(sourcev1.SourceVerifiedCondition), @@ -982,7 +980,7 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *sourcev1.OC secretRef := types.NamespacedName{Namespace: obj.Namespace, Name: obj.Spec.SecretRef.Name} err := r.Get(ctx, secretRef, &imagePullSecret) if err != nil { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.AuthenticationFailedReason, + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.AuthenticationFailedReason, sourcev1.ActionReconcileSource, "auth secret '%s' not found", obj.Spec.SecretRef.Name) return nil, fmt.Errorf("failed to get secret '%s': %w", secretRef, err) } @@ -1085,7 +1083,7 @@ func (r *OCIRepositoryReconciler) reconcileStorage(ctx context.Context, sp *patc // matches the actual artifact if !artifactMissing { if err := r.Storage.VerifyArtifact(*artifact); err != nil { - r.Eventf(obj, corev1.EventTypeWarning, "ArtifactVerificationFailed", "failed to verify integrity of artifact: %s", err.Error()) + r.Eventf(obj, nil, corev1.EventTypeWarning, sourcev1.ArtifactVerificationFailedReason, sourcev1.ActionReconcileStorage.String(), "failed to verify integrity of artifact: %s", err.Error()) if err = r.Storage.Remove(*artifact); err != nil { return sreconcile.ResultEmpty, fmt.Errorf("failed to remove artifact after digest mismatch: %w", err) @@ -1149,7 +1147,7 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, sp *pat // The artifact is up-to-date if obj.GetArtifact().HasRevision(artifact.Revision) && !ociContentConfigChanged(obj) { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.ArtifactUpToDateReason, + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.ArtifactUpToDateReason, sourcev1.ActionReconcileArtifact, "artifact up-to-date with remote revision: '%s'", artifact.Revision) return sreconcile.ResultSuccess, nil } @@ -1232,7 +1230,7 @@ func (r *OCIRepositoryReconciler) reconcileArtifact(ctx context.Context, sp *pat // Update symlink on a "best effort" basis url, err := r.Storage.Symlink(artifact, "latest.tar.gz") if err != nil { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, sourcev1.SymlinkUpdateFailedReason, + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.SymlinkUpdateFailedReason, sourcev1.ActionReconcileArtifact, "failed to update status URL symlink: %s", err) } if url != "" { @@ -1276,7 +1274,7 @@ func (r *OCIRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc "GarbageCollectionFailed", ) } else if deleted != "" { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "GarbageCollectionSucceeded", + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.GarbageCollectionSucceededReason, sourcev1.ActionFinalize, "garbage collected artifacts for deleted resource") } obj.Status.Artifact = nil @@ -1291,7 +1289,7 @@ func (r *OCIRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc ) } if len(delFiles) > 0 { - r.eventLogf(ctx, obj, eventv1.EventTypeTrace, "GarbageCollectionSucceeded", + sreconcile.EventLogf(ctx, r, obj, eventv1.EventTypeTrace, sourcev1.GarbageCollectionSucceededReason, sourcev1.ActionGarbageCollect, "garbage collected %d artifacts", len(delFiles)) return nil } @@ -1299,22 +1297,6 @@ func (r *OCIRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc return nil } -// eventLogf records events, and logs at the same time. -// -// This log is different from the debug log in the EventRecorder, in the sense -// that this is a simple log. While the debug log contains complete details -// about the event. -func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context, obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) { - msg := fmt.Sprintf(messageFmt, args...) - // Log and emit event. - if eventType == corev1.EventTypeWarning { - ctrl.LoggerFrom(ctx).Error(errors.New(reason), msg) - } else { - ctrl.LoggerFrom(ctx).Info(msg) - } - r.Eventf(obj, eventType, reason, "%s", msg) -} - // notify emits notification related to the reconciliation. func (r *OCIRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *sourcev1.OCIRepository, res sreconcile.Result, resErr error) { // Notify successful reconciliation for new artifact and recovery from any @@ -1346,13 +1328,13 @@ func (r *OCIRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *so // Notify on new artifact and failure recovery. if !oldObj.GetArtifact().HasDigest(newObj.GetArtifact().Digest) { - r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal, - "NewArtifact", "%s", message) + r.AnnotatedEventf(newObj, nil, annotations, corev1.EventTypeNormal, + sourcev1.NewArtifactReason, sourcev1.ActionReconcile.String(), "%s", message) ctrl.LoggerFrom(ctx).Info(message) } else { if sreconcile.FailureRecovery(oldObj, newObj, ociRepositoryFailConditions) { - r.AnnotatedEventf(newObj, annotations, corev1.EventTypeNormal, - meta.SucceededReason, "%s", message) + r.AnnotatedEventf(newObj, nil, annotations, corev1.EventTypeNormal, + meta.SucceededReason, sourcev1.ActionReconcile.String(), "%s", message) ctrl.LoggerFrom(ctx).Info(message) } } diff --git a/internal/controller/ocirepository_controller_test.go b/internal/controller/ocirepository_controller_test.go index fc4069e2c..7eb65e3df 100644 --- a/internal/controller/ocirepository_controller_test.go +++ b/internal/controller/ocirepository_controller_test.go @@ -51,7 +51,6 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/record" "k8s.io/utils/ptr" oras "oras.land/oras-go/v2/registry/remote" ctrl "sigs.k8s.io/controller-runtime" @@ -60,7 +59,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" kstatus "github.com/fluxcd/cli-utils/pkg/kstatus/status" - eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" + eventv1 "github.com/fluxcd/pkg/apis/event/v1" "github.com/fluxcd/pkg/apis/meta" intdigest "github.com/fluxcd/pkg/artifact/digest" "github.com/fluxcd/pkg/artifact/storage" @@ -69,6 +68,7 @@ import ( "github.com/fluxcd/pkg/oci" "github.com/fluxcd/pkg/runtime/conditions" conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/pkg/tar" @@ -111,7 +111,7 @@ func TestOCIRepositoryReconciler_deleteBeforeFinalizer(t *testing.T) { r := &OCIRepositoryReconciler{ Client: k8sClient, - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, } @@ -771,8 +771,8 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) { }, Type: corev1.SecretTypeDockerConfigJson, Data: map[string][]byte{ - ".dockerconfigjson": []byte(fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, - server.registryHost, tt.secretOpts.username, tt.secretOpts.password)), + ".dockerconfigjson": fmt.Appendf(nil, `{"auths": {%q: {"username": %q, "password": %q}}}`, + server.registryHost, tt.secretOpts.username, tt.secretOpts.password), }, } clientBuilder.WithObjects(secret) @@ -807,7 +807,7 @@ func TestOCIRepository_reconcileSource_authStrategy(t *testing.T) { r := &OCIRepositoryReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), @@ -1033,7 +1033,7 @@ func TestOCIRepository_ProxySecret(t *testing.T) { digest: pi.digest, proxySecret: &corev1.Secret{ Data: map[string][]byte{ - "address": []byte(fmt.Sprintf("http://%s", proxyAddr)), + "address": fmt.Appendf(nil, "http://%s", proxyAddr), }, }, expectreadyconition: true, @@ -1045,7 +1045,7 @@ func TestOCIRepository_ProxySecret(t *testing.T) { digest: pi.digest, proxySecret: &corev1.Secret{ Data: map[string][]byte{ - "address": []byte(fmt.Sprintf("http://localhost:%d", proxyPort+1)), + "address": fmt.Appendf(nil, "http://localhost:%d", proxyPort+1), }, }, expectreadyconition: false, @@ -1267,7 +1267,7 @@ func TestOCIRepository_reconcileSource_remoteReference(t *testing.T) { r := &OCIRepositoryReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), @@ -1470,7 +1470,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignatureNotation(t *testi r := &OCIRepositoryReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), @@ -1834,7 +1834,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceTrustPolicyNotation(t *tes r := &OCIRepositoryReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), @@ -2131,7 +2131,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignatureCosign(t *testing r := &OCIRepositoryReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), @@ -2401,7 +2401,7 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature_keyless(t *testi r := &OCIRepositoryReconciler{ Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), @@ -2586,10 +2586,10 @@ func TestOCIRepository_reconcileSource_noop(t *testing.T) { WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), } for _, tt := range tests { @@ -2818,10 +2818,10 @@ func TestOCIRepository_reconcileArtifact(t *testing.T) { WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), } for _, tt := range tests { @@ -2983,10 +2983,10 @@ func TestOCIRepository_getArtifactRef(t *testing.T) { WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), } for _, tt := range tests { @@ -3317,10 +3317,10 @@ func TestOCIRepository_reconcileStorage(t *testing.T) { WithStatusSubresource(&sourcev1.OCIRepository{}) r := &OCIRepositoryReconciler{ - Client: clientBuilder.Build(), - EventRecorder: record.NewFakeRecorder(32), - Storage: testStorage, - patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + Client: clientBuilder.Build(), + Recorder: events.NewFakeRecorder(32, false), + Storage: testStorage, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), } for _, tt := range tests { @@ -3381,7 +3381,7 @@ func TestOCIRepository_ReconcileDelete(t *testing.T) { g := NewWithT(t) r := &OCIRepositoryReconciler{ - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Storage: testStorage, CosignVerifierFactory: testCosignVerifierFactory, patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), @@ -3414,14 +3414,13 @@ func TestOCIRepositoryReconciler_notify(t *testing.T) { noopErr.Ignore = true tests := []struct { - name string - res sreconcile.Result - resErr error - oldObjBeforeFunc func(obj *sourcev1.OCIRepository) - newObjBeforeFunc func(obj *sourcev1.OCIRepository) - commit git.Commit - wantEvent string - wantOriginRevision string + name string + res sreconcile.Result + resErr error + oldObjBeforeFunc func(obj *sourcev1.OCIRepository) + newObjBeforeFunc func(obj *sourcev1.OCIRepository) + commit git.Commit + wantEvent *corev1.Event }{ { name: "error - no event", @@ -3443,8 +3442,17 @@ func TestOCIRepositoryReconciler_notify(t *testing.T) { }, } }, - wantEvent: "Normal NewArtifact stored artifact with revision 'xxx' from 'oci://newurl.io', origin source 'https://github.com/stefanprodan/podinfo', origin revision '6.1.8/b3b00fe35424a45d373bf4c7214178bc36fd7872'", - wantOriginRevision: "6.1.8/b3b00fe35424a45d373bf4c7214178bc36fd7872", + wantEvent: &corev1.Event{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + fmt.Sprintf("%s/%s", sourcev1.GroupVersion.Group, eventv1.MetaOriginRevisionKey): "6.1.8/b3b00fe35424a45d373bf4c7214178bc36fd7872", + }, + }, + Type: corev1.EventTypeNormal, + Reason: sourcev1.NewArtifactReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored artifact with revision 'xxx' from 'oci://newurl.io', origin source 'https://github.com/stefanprodan/podinfo', origin revision '6.1.8/b3b00fe35424a45d373bf4c7214178bc36fd7872'", + }, }, { name: "recovery from failure", @@ -3466,8 +3474,17 @@ func TestOCIRepositoryReconciler_notify(t *testing.T) { } conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready") }, - wantEvent: "Normal Succeeded stored artifact with revision 'xxx' from 'oci://newurl.io'", - wantOriginRevision: "6.1.8/b3b00fe35424a45d373bf4c7214178bc36fd7872", + wantEvent: &corev1.Event{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + fmt.Sprintf("%s/%s", sourcev1.GroupVersion.Group, eventv1.MetaOriginRevisionKey): "6.1.8/b3b00fe35424a45d373bf4c7214178bc36fd7872", + }, + }, + Type: corev1.EventTypeNormal, + Reason: meta.SucceededReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored artifact with revision 'xxx' from 'oci://newurl.io'", + }, }, { name: "recovery and new artifact", @@ -3483,7 +3500,12 @@ func TestOCIRepositoryReconciler_notify(t *testing.T) { obj.Status.Artifact = &meta.Artifact{Revision: "aaa", Digest: "bbb"} conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "ready") }, - wantEvent: "Normal NewArtifact stored artifact with revision 'aaa' from 'oci://newurl.io'", + wantEvent: &corev1.Event{ + Type: corev1.EventTypeNormal, + Reason: sourcev1.NewArtifactReason, + Action: sourcev1.ActionReconcile.String(), + Message: "stored artifact with revision 'aaa' from 'oci://newurl.io'", + }, }, { name: "no updates", @@ -3512,7 +3534,7 @@ func TestOCIRepositoryReconciler_notify(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := NewWithT(t) - recorder := record.NewFakeRecorder(32) + recorder := events.NewFakeRecorder(32, false) oldObj := &sourcev1.OCIRepository{} newObj := oldObj.DeepCopy() @@ -3525,25 +3547,28 @@ func TestOCIRepositoryReconciler_notify(t *testing.T) { } reconciler := &OCIRepositoryReconciler{ - EventRecorder: recorder, - patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), + Recorder: recorder, + patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"), } reconciler.notify(ctx, oldObj, newObj, tt.res, tt.resErr) select { case x, ok := <-recorder.Events: - g.Expect(ok).To(Equal(tt.wantEvent != ""), "unexpected event received") - if tt.wantEvent != "" { - g.Expect(x).To(ContainSubstring(tt.wantEvent)) + g.Expect(ok).To(Equal(tt.wantEvent != nil), "unexpected event received") + if tt.wantEvent != nil { + g.Expect(x.Type).To(Equal(tt.wantEvent.Type)) + g.Expect(x.Reason).To(Equal(tt.wantEvent.Reason)) + g.Expect(x.Action).To(Equal(tt.wantEvent.Action)) + g.Expect(x.Message).To(ContainSubstring(tt.wantEvent.Message)) originRevisionKey := fmt.Sprintf("%s/%s", sourcev1.GroupVersion.Group, eventv1.MetaOriginRevisionKey) - if tt.wantOriginRevision != "" { - g.Expect(x).To(ContainSubstring(fmt.Sprintf("%s:%s", originRevisionKey, tt.wantOriginRevision))) + if want, ok := tt.wantEvent.Annotations[originRevisionKey]; ok { + g.Expect(x.Annotations).To(HaveKeyWithValue(originRevisionKey, want)) } else { - g.Expect(x).NotTo(ContainSubstring(originRevisionKey)) + g.Expect(x.Annotations).NotTo(HaveKey(originRevisionKey)) } } default: - if tt.wantEvent != "" { + if tt.wantEvent != nil { t.Errorf("expected some event to be emitted") } } diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 53da2f74e..91d999c60 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -43,7 +43,6 @@ import ( 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" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" @@ -52,6 +51,7 @@ import ( "github.com/fluxcd/pkg/artifact/digest" "github.com/fluxcd/pkg/artifact/storage" "github.com/fluxcd/pkg/runtime/controller" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/metrics" "github.com/fluxcd/pkg/runtime/testenv" "github.com/fluxcd/pkg/testserver" @@ -177,7 +177,7 @@ func setupRegistryServer(ctx context.Context, workspaceDir string, opts registry } htpasswdPath := filepath.Join(workspaceDir, testRegistryHtpasswdFileBasename) - if err = os.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testRegistryUsername, string(pwBytes))), 0644); err != nil { + if err = os.WriteFile(htpasswdPath, fmt.Appendf(nil, "%s:%s\n", testRegistryUsername, string(pwBytes)), 0644); err != nil { return nil, fmt.Errorf("failed to create htpasswd file: %s", err) } @@ -321,10 +321,10 @@ func TestMain(m *testing.M) { defer testRegistryServer.Close() if err := (&GitRepositoryReconciler{ - Client: testEnv, - EventRecorder: record.NewFakeRecorder(32), - Metrics: testMetricsH, - Storage: testStorage, + Client: testEnv, + Recorder: events.NewFakeRecorder(32, false), + Metrics: testMetricsH, + Storage: testStorage, }).SetupWithManager(testEnv, GitRepositoryReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { @@ -332,10 +332,10 @@ func TestMain(m *testing.M) { } if err := (&BucketReconciler{ - Client: testEnv, - EventRecorder: record.NewFakeRecorder(32), - Metrics: testMetricsH, - Storage: testStorage, + Client: testEnv, + Recorder: events.NewFakeRecorder(32, false), + Metrics: testMetricsH, + Storage: testStorage, }).SetupWithManager(testEnv, BucketReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { @@ -346,10 +346,10 @@ func TestMain(m *testing.M) { cacheRecorder := cache.MustMakeMetrics() if err := (&OCIRepositoryReconciler{ - Client: testEnv, - EventRecorder: record.NewFakeRecorder(32), - Metrics: testMetricsH, - Storage: testStorage, + Client: testEnv, + Recorder: events.NewFakeRecorder(32, false), + Metrics: testMetricsH, + Storage: testStorage, }).SetupWithManager(testEnv, OCIRepositoryReconcilerOptions{ RateLimiter: controller.GetDefaultRateLimiter(), }); err != nil { @@ -358,7 +358,7 @@ func TestMain(m *testing.M) { if err := (&HelmRepositoryReconciler{ Client: testEnv, - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Metrics: testMetricsH, Getters: testGetters, Storage: testStorage, @@ -373,7 +373,7 @@ func TestMain(m *testing.M) { if err := (&HelmChartReconciler{ Client: testEnv, - EventRecorder: record.NewFakeRecorder(32), + Recorder: events.NewFakeRecorder(32, false), Metrics: testMetricsH, Getters: testGetters, Storage: testStorage, diff --git a/internal/reconcile/event.go b/internal/reconcile/event.go new file mode 100644 index 000000000..57ebf4eb4 --- /dev/null +++ b/internal/reconcile/event.go @@ -0,0 +1,50 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package reconcile + +import ( + "context" + "errors" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/fluxcd/pkg/runtime/events" + + sourcev1 "github.com/fluxcd/source-controller/api/v1" +) + +// EventLogf records an event and logs the message at the same time. +// +// This log is different from the debug log in the EventRecorder, in the sense +// that this is a simple log. While the debug log contains complete details +// about the event. +// +// The action identifies the reconcile stage that produced the event and is +// recorded in the event's action field. Callers pass the relevant +// sourcev1.Action for the stage. +func EventLogf(ctx context.Context, rec events.Recorder, obj runtime.Object, eventType string, reason string, action sourcev1.Action, messageFmt string, args ...interface{}) { + msg := fmt.Sprintf(messageFmt, args...) + if eventType == corev1.EventTypeWarning { + ctrl.LoggerFrom(ctx).Error(errors.New(reason), msg) + } else { + ctrl.LoggerFrom(ctx).Info(msg) + } + rec.Eventf(obj, nil, eventType, reason, action.String(), "%s", msg) +} diff --git a/internal/reconcile/summarize/processor.go b/internal/reconcile/summarize/processor.go index fb2e655c2..2b1fdddb6 100644 --- a/internal/reconcile/summarize/processor.go +++ b/internal/reconcile/summarize/processor.go @@ -20,12 +20,13 @@ import ( "context" corev1 "k8s.io/api/core/v1" - kuberecorder "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" + eventv1 "github.com/fluxcd/pkg/apis/event/v1" "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/events" + sourcev1 "github.com/fluxcd/source-controller/api/v1" serror "github.com/fluxcd/source-controller/internal/error" "github.com/fluxcd/source-controller/internal/object" "github.com/fluxcd/source-controller/internal/reconcile" @@ -34,12 +35,12 @@ import ( // ResultProcessor processes the results of reconciliation (the object, result // and error). Any errors during processing need not result in the // reconciliation failure. The errors can be recorded as logs and events. -type ResultProcessor func(context.Context, kuberecorder.EventRecorder, client.Object, reconcile.Result, error) +type ResultProcessor func(context.Context, events.Recorder, client.Object, reconcile.Result, error) // RecordReconcileReq is a ResultProcessor that checks the reconcile // annotation value and sets it in the object status as // status.lastHandledReconcileAt. -func RecordReconcileReq(ctx context.Context, recorder kuberecorder.EventRecorder, obj client.Object, _ reconcile.Result, _ error) { +func RecordReconcileReq(ctx context.Context, recorder events.Recorder, obj client.Object, _ reconcile.Result, _ error) { if v, ok := meta.ReconcileAnnotationValue(obj.GetAnnotations()); ok { object.SetStatusLastHandledReconcileAt(obj, v) } @@ -49,23 +50,24 @@ func RecordReconcileReq(ctx context.Context, recorder kuberecorder.EventRecorder // configured in the given error. Logging and event recording are the handled // actions at present. As more configurations are added to serror.Config, more // action handlers can be added here. -func ErrorActionHandler(ctx context.Context, recorder kuberecorder.EventRecorder, obj client.Object, _ reconcile.Result, err error) { +func ErrorActionHandler(ctx context.Context, recorder events.Recorder, obj client.Object, _ reconcile.Result, err error) { + action := sourcev1.ActionReconcile switch e := err.(type) { case *serror.Generic: if e.Log { logError(ctx, e.Config.Event, e, e.Error()) } - recordEvent(recorder, obj, e.Config.Event, e.Config.Notification, err, e.Reason) + recordEvent(recorder, obj, e.Config.Event, e.Config.Notification, err, e.Reason, action) case *serror.Waiting: if e.Log { logError(ctx, e.Config.Event, e, "reconciliation waiting", "reason", e.Err, "duration", e.RequeueAfter) } - recordEvent(recorder, obj, e.Config.Event, e.Config.Notification, err, e.Reason) + recordEvent(recorder, obj, e.Config.Event, e.Config.Notification, err, e.Reason, action) case *serror.Stalling: if e.Log { logError(ctx, e.Config.Event, e, "reconciliation stalled") } - recordEvent(recorder, obj, e.Config.Event, e.Config.Notification, err, e.Reason) + recordEvent(recorder, obj, e.Config.Event, e.Config.Notification, err, e.Reason, action) } } @@ -80,7 +82,7 @@ func logError(ctx context.Context, eventType string, err error, msg string, keys } // recordEvent records events based on the passed error configurations. -func recordEvent(recorder kuberecorder.EventRecorder, obj client.Object, eventType string, notification bool, err error, reason string) { +func recordEvent(recorder events.Recorder, obj client.Object, eventType string, notification bool, err error, reason string, action sourcev1.Action) { if eventType == serror.EventTypeNone { return } @@ -88,16 +90,16 @@ func recordEvent(recorder kuberecorder.EventRecorder, obj client.Object, eventTy case corev1.EventTypeNormal: if notification { // K8s native event and notification-controller event. - recorder.Eventf(obj, corev1.EventTypeNormal, reason, "%s", err.Error()) + recorder.Eventf(obj, nil, corev1.EventTypeNormal, reason, action.String(), "%s", err.Error()) } else { // K8s native event only. - recorder.Eventf(obj, eventv1.EventTypeTrace, reason, "%s", err.Error()) + recorder.Eventf(obj, nil, eventv1.EventTypeTrace, reason, action.String(), "%s", err.Error()) } case corev1.EventTypeWarning: // TODO: Due to the current implementation of the event recorder, all // the K8s warning events are also sent as notification controller // notifications. Once the recorder becomes capable of separating the // two, conditionally record events. - recorder.Eventf(obj, corev1.EventTypeWarning, reason, "%s", err.Error()) + recorder.Eventf(obj, nil, corev1.EventTypeWarning, reason, action.String(), "%s", err.Error()) } } diff --git a/internal/reconcile/summarize/processor_test.go b/internal/reconcile/summarize/processor_test.go index 44f68b5bf..d7af0a00f 100644 --- a/internal/reconcile/summarize/processor_test.go +++ b/internal/reconcile/summarize/processor_test.go @@ -22,10 +22,10 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/events" sourcev1 "github.com/fluxcd/source-controller/api/v1" "github.com/fluxcd/source-controller/internal/object" @@ -119,7 +119,7 @@ func TestRecordReconcileReq(t *testing.T) { } ctx := context.TODO() - RecordReconcileReq(ctx, record.NewFakeRecorder(32), obj, reconcile.ResultEmpty, nil) + RecordReconcileReq(ctx, events.NewFakeRecorder(32, false), obj, reconcile.ResultEmpty, nil) if tt.afterFunc != nil { tt.afterFunc(g, obj) diff --git a/internal/reconcile/summarize/summary.go b/internal/reconcile/summarize/summary.go index 8650a0907..53535b908 100644 --- a/internal/reconcile/summarize/summary.go +++ b/internal/reconcile/summarize/summary.go @@ -22,11 +22,11 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" kerrors "k8s.io/apimachinery/pkg/util/errors" - kuberecorder "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/patch" "github.com/fluxcd/source-controller/internal/reconcile" @@ -50,12 +50,12 @@ type Conditions struct { // Helper is SummarizeAndPatch helper. type Helper struct { - recorder kuberecorder.EventRecorder + recorder events.Recorder serialPatcher *patch.SerialPatcher } // NewHelper returns an initialized Helper. -func NewHelper(recorder kuberecorder.EventRecorder, serialPatcher *patch.SerialPatcher) *Helper { +func NewHelper(recorder events.Recorder, serialPatcher *patch.SerialPatcher) *Helper { return &Helper{ recorder: recorder, serialPatcher: serialPatcher, diff --git a/internal/reconcile/summarize/summary_test.go b/internal/reconcile/summarize/summary_test.go index c4c16e4eb..c9acbca38 100644 --- a/internal/reconcile/summarize/summary_test.go +++ b/internal/reconcile/summarize/summary_test.go @@ -26,7 +26,6 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -34,6 +33,7 @@ import ( "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" conditionscheck "github.com/fluxcd/pkg/runtime/conditions/check" + "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/patch" sourcev1 "github.com/fluxcd/source-controller/api/v1" @@ -351,7 +351,7 @@ func TestSummarizeAndPatch(t *testing.T) { serialPatcher := patch.NewSerialPatcher(obj, c) - summaryHelper := NewHelper(record.NewFakeRecorder(32), serialPatcher) + summaryHelper := NewHelper(events.NewFakeRecorder(32, false), serialPatcher) summaryOpts := []Option{ WithReconcileResult(tt.result), WithReconcileError(tt.reconcileErr), @@ -479,7 +479,7 @@ func TestSummarizeAndPatch_Intermediate(t *testing.T) { g.Expect(c.Create(ctx, obj)).To(Succeed()) serialPatcher := patch.NewSerialPatcher(obj, c) - summaryHelper := NewHelper(record.NewFakeRecorder(32), serialPatcher) + summaryHelper := NewHelper(events.NewFakeRecorder(32, false), serialPatcher) summaryOpts := []Option{ WithConditions(tt.conditions...), WithResultBuilder(reconcile.AlwaysRequeueResultBuilder{RequeueAfter: interval}), diff --git a/main.go b/main.go index 75d897bd8..3fbef9181 100644 --- a/main.go +++ b/main.go @@ -28,7 +28,6 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - "k8s.io/client-go/tools/record" "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache" @@ -195,7 +194,12 @@ func main() { metrics := helper.NewMetrics(mgr, metrics.MustMakeRecorder(), sourcev1.SourceFinalizer) cacheRecorder := cache.MustMakeMetrics() - eventRecorder := mustSetupEventRecorder(mgr, eventsAddr, controllerName) + + eventRecorder, err := events.NewRecorder(ctrl.Log, eventsAddr, controllerName, events.WithManager(mgr)) + if err != nil { + setupLog.Error(err, "unable to create event recorder") + os.Exit(1) + } algo, err := artdigest.AlgorithmForName(artifactOptions.ArtifactDigestAlgo) if err != nil { @@ -230,7 +234,7 @@ func main() { if err := (&controller.GitRepositoryReconciler{ Client: mgr.GetClient(), - EventRecorder: eventRecorder, + Recorder: eventRecorder, Metrics: metrics, Storage: storage, ControllerName: controllerName, @@ -245,7 +249,7 @@ func main() { if err := (&controller.HelmRepositoryReconciler{ Client: mgr.GetClient(), - EventRecorder: eventRecorder, + Recorder: eventRecorder, Metrics: metrics, Storage: storage, Getters: getters, @@ -264,7 +268,7 @@ func main() { Client: mgr.GetClient(), Storage: storage, Getters: getters, - EventRecorder: eventRecorder, + Recorder: eventRecorder, Metrics: metrics, ControllerName: controllerName, CosignVerifierFactory: CosignVerifierFactory, @@ -280,7 +284,7 @@ func main() { if err := (&controller.BucketReconciler{ Client: mgr.GetClient(), - EventRecorder: eventRecorder, + Recorder: eventRecorder, Metrics: metrics, Storage: storage, ControllerName: controllerName, @@ -295,7 +299,7 @@ func main() { if err := (&controller.OCIRepositoryReconciler{ Client: mgr.GetClient(), Storage: storage, - EventRecorder: eventRecorder, + Recorder: eventRecorder, ControllerName: controllerName, TokenCache: tokenCache, CosignVerifierFactory: CosignVerifierFactory, @@ -328,15 +332,6 @@ func main() { } } -func mustSetupEventRecorder(mgr ctrl.Manager, eventsAddr, controllerName string) record.EventRecorder { - eventRecorder, err := events.NewRecorder(mgr, ctrl.Log, eventsAddr, controllerName) - if err != nil { - setupLog.Error(err, "unable to create event recorder") - os.Exit(1) - } - return eventRecorder -} - func mustSetupManager(metricsAddr, healthAddr string, maxConcurrent int, watchOpts helper.WatchOptions, clientOpts client.Options, leaderOpts leaderelection.Options) ctrl.Manager {