From 0e3f717dd19b36a246b7a8281211797edcfbd3a6 Mon Sep 17 00:00:00 2001
From: Max Jonas Werner
Date: Wed, 22 Mar 2023 17:40:59 +0100
Subject: [PATCH] Introduce v1 API and bump Receiver version to v1
This commit bumps the Receiver API version to v1 in preparation of
the Flux GitOps GA milestone
(https://fluxcd.io/roadmap/#flux-gitops-ga-q1-2023).
We are now actively maintaining two versions of the notification API
group in parallel: v1 which currently only holds the Receiver kind and
v1beta2 for all other kinds.
Since we haven't run into this situation before, I had to change the
way we expose the API docs in ./docs/api: The directory now has
sub-directories for each active API version. Therefore we need to
change our scripts in the website repository to take this change into
account so that we expose both API group version at
https://fluxcd.io/flux/components/notification/api/. This change is
implemented in https://github.com/fluxcd/website/pull/1427.
refs #436
Signed-off-by: Max Jonas Werner
---
Makefile | 3 +-
PROJECT | 3 +
api/v1/condition_types.go | 31 +
api/v1/doc.go | 20 +
api/v1/groupversion_info.go | 33 +
api/v1/receiver_types.go | 160 +++
api/v1/reference_types.go | 49 +
api/v1/zz_generated.deepcopy.go | 164 ++++
api/v1beta1/receiver_types.go | 1 +
api/v1beta1/zz_generated.deepcopy.go | 2 +-
api/v1beta2/alert_types.go | 4 +-
api/v1beta2/receiver_types.go | 6 +-
api/v1beta2/zz_generated.deepcopy.go | 21 +-
...notification.toolkit.fluxcd.io_alerts.yaml | 12 +-
...ification.toolkit.fluxcd.io_receivers.yaml | 241 ++++-
...ver.yaml => notification_v1_receiver.yaml} | 2 +-
config/testdata/status-defaults/receiver.yaml | 2 +-
controllers/alert_controller.go | 25 +-
controllers/alert_controller_test.go | 27 +-
controllers/provider_controller.go | 15 +-
controllers/provider_controller_test.go | 9 +-
controllers/receiver_controller.go | 2 +-
controllers/receiver_controller_test.go | 3 +-
controllers/suite_test.go | 4 +-
docs/api/v1/notification.md | 442 +++++++++
docs/api/{ => v1beta2}/notification.md | 57 +-
docs/spec/README.md | 68 +-
docs/spec/v1/README.md | 7 +
docs/spec/v1/receivers.md | 913 ++++++++++++++++++
docs/spec/v1beta2/README.md | 2 +-
docs/spec/v1beta2/alerts.md | 2 +
docs/spec/v1beta2/events.md | 2 +
docs/spec/v1beta2/providers.md | 2 +
hack/api-docs/config.json | 12 +-
hack/api-docs/template/pkg.tpl | 7 +-
hack/boilerplate.go.txt | 2 +-
internal/server/event_handlers.go | 25 +-
internal/server/receiver_handler_test.go | 2 +-
internal/server/receiver_handlers.go | 2 +-
internal/server/receiver_server.go | 2 +-
main.go | 8 +-
41 files changed, 2210 insertions(+), 184 deletions(-)
create mode 100644 api/v1/condition_types.go
create mode 100644 api/v1/doc.go
create mode 100644 api/v1/groupversion_info.go
create mode 100644 api/v1/receiver_types.go
create mode 100644 api/v1/reference_types.go
create mode 100644 api/v1/zz_generated.deepcopy.go
rename config/samples/{notification_v1beta2_receiver.yaml => notification_v1_receiver.yaml} (86%)
create mode 100644 docs/api/v1/notification.md
rename docs/api/{ => v1beta2}/notification.md (89%)
create mode 100644 docs/spec/v1/README.md
create mode 100644 docs/spec/v1/receivers.md
diff --git a/Makefile b/Makefile
index 2fbc186f1..2502071f8 100644
--- a/Makefile
+++ b/Makefile
@@ -87,7 +87,8 @@ manifests: controller-gen
# Generate API reference documentation
api-docs: gen-crd-api-reference-docs
- $(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v1beta2 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/notification.md
+ $(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v1beta2 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/v1beta2/notification.md
+ $(GEN_CRD_API_REFERENCE_DOCS) -api-dir=./api/v1 -config=./hack/api-docs/config.json -template-dir=./hack/api-docs/template -out-file=./docs/api/v1/notification.md
# Run go mod tidy
tidy:
diff --git a/PROJECT b/PROJECT
index c53381c21..fa7a1babe 100644
--- a/PROJECT
+++ b/PROJECT
@@ -1,6 +1,9 @@
domain: toolkit.fluxcd.io
repo: github.com/fluxcd/notification-controller
resources:
+- group: notification
+ kind: Receiver
+ version: v1
- group: notification
kind: Provider
version: v1beta1
diff --git a/api/v1/condition_types.go b/api/v1/condition_types.go
new file mode 100644
index 000000000..66f9a3375
--- /dev/null
+++ b/api/v1/condition_types.go
@@ -0,0 +1,31 @@
+/*
+Copyright 2023 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
+
+const NotificationFinalizer = "finalizers.fluxcd.io"
+
+const (
+ // InitializedReason represents the fact that a given resource has been initialized.
+ InitializedReason string = "Initialized"
+
+ // ValidationFailedReason represents the fact that some part of the spec of a given resource
+ // couldn't be validated.
+ ValidationFailedReason string = "ValidationFailed"
+
+ // TokenNotFoundReason represents the fact that receiver token can't be found.
+ TokenNotFoundReason string = "TokenNotFound"
+)
diff --git a/api/v1/doc.go b/api/v1/doc.go
new file mode 100644
index 000000000..3123630d9
--- /dev/null
+++ b/api/v1/doc.go
@@ -0,0 +1,20 @@
+/*
+Copyright 2023 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 contains API Schema definitions for the notification v1 API group.
+// +kubebuilder:object:generate=true
+// +groupName=notification.toolkit.fluxcd.io
+package v1
diff --git a/api/v1/groupversion_info.go b/api/v1/groupversion_info.go
new file mode 100644
index 000000000..7a1fff8c7
--- /dev/null
+++ b/api/v1/groupversion_info.go
@@ -0,0 +1,33 @@
+/*
+Copyright 2023 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
+
+import (
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+var (
+ // GroupVersion is group version used to register these objects.
+ GroupVersion = schema.GroupVersion{Group: "notification.toolkit.fluxcd.io", Version: "v1"}
+
+ // SchemeBuilder is used to add go types to the GroupVersionKind scheme.
+ SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
+
+ // AddToScheme adds the types in this group-version to the given scheme.
+ AddToScheme = SchemeBuilder.AddToScheme
+)
diff --git a/api/v1/receiver_types.go b/api/v1/receiver_types.go
new file mode 100644
index 000000000..f7d73cba4
--- /dev/null
+++ b/api/v1/receiver_types.go
@@ -0,0 +1,160 @@
+/*
+Copyright 2023 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
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "time"
+
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/fluxcd/pkg/apis/meta"
+)
+
+const (
+ ReceiverKind string = "Receiver"
+ ReceiverWebhookPath string = "/hook/"
+ GenericReceiver string = "generic"
+ GenericHMACReceiver string = "generic-hmac"
+ GitHubReceiver string = "github"
+ GitLabReceiver string = "gitlab"
+ BitbucketReceiver string = "bitbucket"
+ HarborReceiver string = "harbor"
+ DockerHubReceiver string = "dockerhub"
+ QuayReceiver string = "quay"
+ GCRReceiver string = "gcr"
+ NexusReceiver string = "nexus"
+ ACRReceiver string = "acr"
+)
+
+// ReceiverSpec defines the desired state of the Receiver.
+type ReceiverSpec struct {
+ // Type of webhook sender, used to determine
+ // the validation procedure and payload deserialization.
+ // +kubebuilder:validation:Enum=generic;generic-hmac;github;gitlab;bitbucket;harbor;dockerhub;quay;gcr;nexus;acr
+ // +required
+ Type string `json:"type"`
+
+ // Interval at which to reconcile the Receiver with its Secret references.
+ // +kubebuilder:validation:Type=string
+ // +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$"
+ // +kubebuilder:default:="10m"
+ // +optional
+ Interval *metav1.Duration `json:"interval,omitempty"`
+
+ // Events specifies the list of event types to handle,
+ // e.g. 'push' for GitHub or 'Push Hook' for GitLab.
+ // +optional
+ Events []string `json:"events,omitempty"`
+
+ // A list of resources to be notified about changes.
+ // +required
+ Resources []CrossNamespaceObjectReference `json:"resources"`
+
+ // SecretRef specifies the Secret containing the token used
+ // to validate the payload authenticity.
+ // +required
+ SecretRef meta.LocalObjectReference `json:"secretRef"`
+
+ // Suspend tells the controller to suspend subsequent
+ // events handling for this receiver.
+ // +optional
+ Suspend bool `json:"suspend,omitempty"`
+}
+
+// ReceiverStatus defines the observed state of the Receiver.
+type ReceiverStatus struct {
+ meta.ReconcileRequestStatus `json:",inline"`
+
+ // Conditions holds the conditions for the Receiver.
+ // +optional
+ Conditions []metav1.Condition `json:"conditions,omitempty"`
+
+ // URL is the generated incoming webhook address in the format
+ // of '/hook/sha256sum(token+name+namespace)'.
+ // Deprecated: Replaced by WebhookPath.
+ // +optional
+ URL string `json:"url,omitempty"`
+
+ // WebhookPath is the generated incoming webhook address in the format
+ // of '/hook/sha256sum(token+name+namespace)'.
+ // +optional
+ WebhookPath string `json:"webhookPath,omitempty"`
+
+ // ObservedGeneration is the last observed generation of the Receiver object.
+ // +optional
+ ObservedGeneration int64 `json:"observedGeneration,omitempty"`
+}
+
+// GetConditions returns the status conditions of the object.
+func (in *Receiver) GetConditions() []metav1.Condition {
+ return in.Status.Conditions
+}
+
+// SetConditions sets the status conditions on the object.
+func (in *Receiver) SetConditions(conditions []metav1.Condition) {
+ in.Status.Conditions = conditions
+}
+
+// GetWebhookPath returns the incoming webhook path for the given token.
+func (in *Receiver) GetWebhookPath(token string) string {
+ digest := sha256.Sum256([]byte(token + in.GetName() + in.GetNamespace()))
+ return fmt.Sprintf("%s%x", ReceiverWebhookPath, digest)
+}
+
+// GetInterval returns the interval value with a default of 10m for this Receiver.
+func (in *Receiver) GetInterval() time.Duration {
+ duration := 10 * time.Minute
+ if in.Spec.Interval != nil {
+ duration = in.Spec.Interval.Duration
+ }
+
+ return duration
+}
+
+// +genclient
+// +genclient:Namespaced
+// +kubebuilder:storageversion
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
+// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
+// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description=""
+
+// Receiver is the Schema for the receivers API.
+type Receiver struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec ReceiverSpec `json:"spec,omitempty"`
+ // +kubebuilder:default:={"observedGeneration":-1}
+ Status ReceiverStatus `json:"status,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+
+// ReceiverList contains a list of Receivers.
+type ReceiverList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []Receiver `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&Receiver{}, &ReceiverList{})
+}
diff --git a/api/v1/reference_types.go b/api/v1/reference_types.go
new file mode 100644
index 000000000..be3573d9e
--- /dev/null
+++ b/api/v1/reference_types.go
@@ -0,0 +1,49 @@
+/*
+Copyright 2023 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
+
+// CrossNamespaceObjectReference contains enough information to let you locate the
+// typed referenced object at cluster level
+type CrossNamespaceObjectReference struct {
+ // API version of the referent
+ // +optional
+ APIVersion string `json:"apiVersion,omitempty"`
+
+ // Kind of the referent
+ // +kubebuilder:validation:Enum=Bucket;GitRepository;Kustomization;HelmRelease;HelmChart;HelmRepository;ImageRepository;ImagePolicy;ImageUpdateAutomation;OCIRepository
+ // +required
+ Kind string `json:"kind"`
+
+ // Name of the referent
+ // +kubebuilder:validation:MinLength=1
+ // +kubebuilder:validation:MaxLength=53
+ // +required
+ Name string `json:"name"`
+
+ // Namespace of the referent
+ // +kubebuilder:validation:MinLength=1
+ // +kubebuilder:validation:MaxLength=53
+ // +kubebuilder:validation:Optional
+ // +optional
+ Namespace string `json:"namespace,omitempty"`
+
+ // MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
+ // map is equivalent to an element of matchExpressions, whose key field is "key", the
+ // operator is "In", and the values array contains only "value". The requirements are ANDed.
+ // +optional
+ MatchLabels map[string]string `json:"matchLabels,omitempty"`
+}
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
new file mode 100644
index 000000000..04d33b5a9
--- /dev/null
+++ b/api/v1/zz_generated.deepcopy.go
@@ -0,0 +1,164 @@
+//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
+
+/*
+Copyright 2023 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.
+*/
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CrossNamespaceObjectReference) DeepCopyInto(out *CrossNamespaceObjectReference) {
+ *out = *in
+ if in.MatchLabels != nil {
+ in, out := &in.MatchLabels, &out.MatchLabels
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CrossNamespaceObjectReference.
+func (in *CrossNamespaceObjectReference) DeepCopy() *CrossNamespaceObjectReference {
+ if in == nil {
+ return nil
+ }
+ out := new(CrossNamespaceObjectReference)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Receiver) DeepCopyInto(out *Receiver) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Receiver.
+func (in *Receiver) DeepCopy() *Receiver {
+ if in == nil {
+ return nil
+ }
+ out := new(Receiver)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Receiver) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ReceiverList) DeepCopyInto(out *ReceiverList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]Receiver, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiverList.
+func (in *ReceiverList) DeepCopy() *ReceiverList {
+ if in == nil {
+ return nil
+ }
+ out := new(ReceiverList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ReceiverList) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ReceiverSpec) DeepCopyInto(out *ReceiverSpec) {
+ *out = *in
+ if in.Interval != nil {
+ in, out := &in.Interval, &out.Interval
+ *out = new(metav1.Duration)
+ **out = **in
+ }
+ if in.Events != nil {
+ in, out := &in.Events, &out.Events
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ if in.Resources != nil {
+ in, out := &in.Resources, &out.Resources
+ *out = make([]CrossNamespaceObjectReference, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ out.SecretRef = in.SecretRef
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiverSpec.
+func (in *ReceiverSpec) DeepCopy() *ReceiverSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(ReceiverSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ReceiverStatus) DeepCopyInto(out *ReceiverStatus) {
+ *out = *in
+ out.ReconcileRequestStatus = in.ReconcileRequestStatus
+ if in.Conditions != nil {
+ in, out := &in.Conditions, &out.Conditions
+ *out = make([]metav1.Condition, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiverStatus.
+func (in *ReceiverStatus) DeepCopy() *ReceiverStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(ReceiverStatus)
+ in.DeepCopyInto(out)
+ return out
+}
diff --git a/api/v1beta1/receiver_types.go b/api/v1beta1/receiver_types.go
index c96b4c271..50fe28040 100644
--- a/api/v1beta1/receiver_types.go
+++ b/api/v1beta1/receiver_types.go
@@ -100,6 +100,7 @@ func (in *Receiver) SetConditions(conditions []metav1.Condition) {
// +genclient:Namespaced
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
+// +kubebuilder:deprecatedversion:warning="v1beta1 Receiver is deprecated, upgrade to v1"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description=""
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index a7f1bece8..a0ce355a1 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -2,7 +2,7 @@
// +build !ignore_autogenerated
/*
-Copyright 2022 The Flux authors
+Copyright 2023 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.
diff --git a/api/v1beta2/alert_types.go b/api/v1beta2/alert_types.go
index 88a008389..b940442e1 100644
--- a/api/v1beta2/alert_types.go
+++ b/api/v1beta2/alert_types.go
@@ -19,6 +19,8 @@ package v1beta2
import (
"github.com/fluxcd/pkg/apis/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/fluxcd/notification-controller/api/v1"
)
const (
@@ -41,7 +43,7 @@ type AlertSpec struct {
// EventSources specifies how to filter events based
// on the involved object kind, name and namespace.
// +required
- EventSources []CrossNamespaceObjectReference `json:"eventSources"`
+ EventSources []v1.CrossNamespaceObjectReference `json:"eventSources"`
// ExclusionList specifies a list of Golang regular expressions
// to be used for excluding messages.
diff --git a/api/v1beta2/receiver_types.go b/api/v1beta2/receiver_types.go
index d52a49969..ad801ae5e 100644
--- a/api/v1beta2/receiver_types.go
+++ b/api/v1beta2/receiver_types.go
@@ -24,6 +24,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/fluxcd/pkg/apis/meta"
+
+ v1 "github.com/fluxcd/notification-controller/api/v1"
)
const (
@@ -63,7 +65,7 @@ type ReceiverSpec struct {
// A list of resources to be notified about changes.
// +required
- Resources []CrossNamespaceObjectReference `json:"resources"`
+ Resources []v1.CrossNamespaceObjectReference `json:"resources"`
// SecretRef specifies the Secret containing the token used
// to validate the payload authenticity.
@@ -128,9 +130,9 @@ func (in *Receiver) GetInterval() time.Duration {
// +genclient
// +genclient:Namespaced
-// +kubebuilder:storageversion
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
+// +kubebuilder:deprecatedversion:warning="v1beta2 Receiver is deprecated, upgrade to v1"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description=""
diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go
index 431aac8ba..20e779047 100644
--- a/api/v1beta2/zz_generated.deepcopy.go
+++ b/api/v1beta2/zz_generated.deepcopy.go
@@ -2,7 +2,7 @@
// +build !ignore_autogenerated
/*
-Copyright 2022 The Flux authors
+Copyright 2023 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.
@@ -22,8 +22,9 @@ limitations under the License.
package v1beta2
import (
+ "github.com/fluxcd/notification-controller/api/v1"
"github.com/fluxcd/pkg/apis/meta"
- "k8s.io/apimachinery/pkg/apis/meta/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@@ -92,7 +93,7 @@ func (in *AlertSpec) DeepCopyInto(out *AlertSpec) {
out.ProviderRef = in.ProviderRef
if in.EventSources != nil {
in, out := &in.EventSources, &out.EventSources
- *out = make([]CrossNamespaceObjectReference, len(*in))
+ *out = make([]v1.CrossNamespaceObjectReference, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -120,7 +121,7 @@ func (in *AlertStatus) DeepCopyInto(out *AlertStatus) {
out.ReconcileRequestStatus = in.ReconcileRequestStatus
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
- *out = make([]v1.Condition, len(*in))
+ *out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -223,12 +224,12 @@ func (in *ProviderSpec) DeepCopyInto(out *ProviderSpec) {
*out = *in
if in.Interval != nil {
in, out := &in.Interval, &out.Interval
- *out = new(v1.Duration)
+ *out = new(metav1.Duration)
**out = **in
}
if in.Timeout != nil {
in, out := &in.Timeout, &out.Timeout
- *out = new(v1.Duration)
+ *out = new(metav1.Duration)
**out = **in
}
if in.SecretRef != nil {
@@ -259,7 +260,7 @@ func (in *ProviderStatus) DeepCopyInto(out *ProviderStatus) {
out.ReconcileRequestStatus = in.ReconcileRequestStatus
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
- *out = make([]v1.Condition, len(*in))
+ *out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -340,7 +341,7 @@ func (in *ReceiverSpec) DeepCopyInto(out *ReceiverSpec) {
*out = *in
if in.Interval != nil {
in, out := &in.Interval, &out.Interval
- *out = new(v1.Duration)
+ *out = new(metav1.Duration)
**out = **in
}
if in.Events != nil {
@@ -350,7 +351,7 @@ func (in *ReceiverSpec) DeepCopyInto(out *ReceiverSpec) {
}
if in.Resources != nil {
in, out := &in.Resources, &out.Resources
- *out = make([]CrossNamespaceObjectReference, len(*in))
+ *out = make([]v1.CrossNamespaceObjectReference, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@@ -374,7 +375,7 @@ func (in *ReceiverStatus) DeepCopyInto(out *ReceiverStatus) {
out.ReconcileRequestStatus = in.ReconcileRequestStatus
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
- *out = make([]v1.Condition, len(*in))
+ *out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
diff --git a/config/crd/bases/notification.toolkit.fluxcd.io_alerts.yaml b/config/crd/bases/notification.toolkit.fluxcd.io_alerts.yaml
index 1502aaddb..6ffc4a2aa 100644
--- a/config/crd/bases/notification.toolkit.fluxcd.io_alerts.yaml
+++ b/config/crd/bases/notification.toolkit.fluxcd.io_alerts.yaml
@@ -256,10 +256,10 @@ spec:
to let you locate the typed referenced object at cluster level
properties:
apiVersion:
- description: API version of the referent.
+ description: API version of the referent
type: string
kind:
- description: Kind of the referent.
+ description: Kind of the referent
enum:
- Bucket
- GitRepository
@@ -279,19 +279,21 @@ spec:
{key,value} in the matchLabels map is equivalent to an element
of matchExpressions, whose key field is "key", the operator
is "In", and the values array contains only "value". The requirements
- are ANDed.
+ are ANDed. MatchLabels requires the name to be set to `*`.
type: object
name:
- description: Name of the referent.
+ description: Name of the referent If multiple resources are
+ targeted `*` may be set.
maxLength: 53
minLength: 1
type: string
namespace:
- description: Namespace of the referent.
+ description: Namespace of the referent
maxLength: 53
minLength: 1
type: string
required:
+ - kind
- name
type: object
type: array
diff --git a/config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml b/config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml
index 4291168e0..3f5b5d48e 100644
--- a/config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml
+++ b/config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml
@@ -25,6 +25,231 @@ spec:
- jsonPath: .status.conditions[?(@.type=="Ready")].message
name: Status
type: string
+ name: v1
+ schema:
+ openAPIV3Schema:
+ description: Receiver is the Schema for the receivers API.
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation
+ of an object. Servers should convert recognized schemas to the latest
+ internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this
+ object represents. Servers may infer this from the endpoint the client
+ submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: ReceiverSpec defines the desired state of the Receiver.
+ properties:
+ events:
+ description: Events specifies the list of event types to handle, e.g.
+ 'push' for GitHub or 'Push Hook' for GitLab.
+ items:
+ type: string
+ type: array
+ interval:
+ default: 10m
+ description: Interval at which to reconcile the Receiver with its
+ Secret references.
+ pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$
+ type: string
+ resources:
+ description: A list of resources to be notified about changes.
+ items:
+ description: CrossNamespaceObjectReference contains enough information
+ to let you locate the typed referenced object at cluster level
+ properties:
+ apiVersion:
+ description: API version of the referent
+ type: string
+ kind:
+ description: Kind of the referent
+ enum:
+ - Bucket
+ - GitRepository
+ - Kustomization
+ - HelmRelease
+ - HelmChart
+ - HelmRepository
+ - ImageRepository
+ - ImagePolicy
+ - ImageUpdateAutomation
+ - OCIRepository
+ type: string
+ matchLabels:
+ additionalProperties:
+ type: string
+ description: MatchLabels is a map of {key,value} pairs. A single
+ {key,value} in the matchLabels map is equivalent to an element
+ of matchExpressions, whose key field is "key", the operator
+ is "In", and the values array contains only "value". The requirements
+ are ANDed.
+ type: object
+ name:
+ description: Name of the referent
+ maxLength: 53
+ minLength: 1
+ type: string
+ namespace:
+ description: Namespace of the referent
+ maxLength: 53
+ minLength: 1
+ type: string
+ required:
+ - kind
+ - name
+ type: object
+ type: array
+ secretRef:
+ description: SecretRef specifies the Secret containing the token used
+ to validate the payload authenticity.
+ properties:
+ name:
+ description: Name of the referent.
+ type: string
+ required:
+ - name
+ type: object
+ suspend:
+ description: Suspend tells the controller to suspend subsequent events
+ handling for this receiver.
+ type: boolean
+ type:
+ description: Type of webhook sender, used to determine the validation
+ procedure and payload deserialization.
+ enum:
+ - generic
+ - generic-hmac
+ - github
+ - gitlab
+ - bitbucket
+ - harbor
+ - dockerhub
+ - quay
+ - gcr
+ - nexus
+ - acr
+ type: string
+ required:
+ - resources
+ - secretRef
+ - type
+ type: object
+ status:
+ default:
+ observedGeneration: -1
+ description: ReceiverStatus defines the observed state of the Receiver.
+ properties:
+ conditions:
+ description: Conditions holds the conditions for the Receiver.
+ items:
+ description: "Condition contains details for one aspect of the current
+ state of this API Resource. --- This struct is intended for direct
+ use as an array at the field path .status.conditions. For example,
+ \n type FooStatus struct{ // Represents the observations of a
+ foo's current state. // Known .status.conditions.type are: \"Available\",
+ \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
+ // +listType=map // +listMapKey=type Conditions []metav1.Condition
+ `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
+ protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
+ properties:
+ lastTransitionTime:
+ description: lastTransitionTime is the last time the condition
+ transitioned from one status to another. This should be when
+ the underlying condition changed. If that is not known, then
+ using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: message is a human readable message indicating
+ details about the transition. This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: observedGeneration represents the .metadata.generation
+ that the condition was set based upon. For instance, if .metadata.generation
+ is currently 12, but the .status.conditions[x].observedGeneration
+ is 9, the condition is out of date with respect to the current
+ state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: reason contains a programmatic identifier indicating
+ the reason for the condition's last transition. Producers
+ of specific condition types may define expected values and
+ meanings for this field, and whether the values are considered
+ a guaranteed API. The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ --- Many .condition.type values are consistent across resources
+ like Available, but because arbitrary conditions can be useful
+ (see .node.status.conditions), the ability to deconflict is
+ important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ lastHandledReconcileAt:
+ description: LastHandledReconcileAt holds the value of the most recent
+ reconcile request value, so a change of the annotation value can
+ be detected.
+ type: string
+ observedGeneration:
+ description: ObservedGeneration is the last observed generation of
+ the Receiver object.
+ format: int64
+ type: integer
+ url:
+ description: 'URL is the generated incoming webhook address in the
+ format of ''/hook/sha256sum(token+name+namespace)''. Deprecated:
+ Replaced by WebhookPath.'
+ type: string
+ webhookPath:
+ description: WebhookPath is the generated incoming webhook address
+ in the format of '/hook/sha256sum(token+name+namespace)'.
+ type: string
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+ - additionalPrinterColumns:
+ - jsonPath: .metadata.creationTimestamp
+ name: Age
+ type: date
+ - jsonPath: .status.conditions[?(@.type=="Ready")].status
+ name: Ready
+ type: string
+ - jsonPath: .status.conditions[?(@.type=="Ready")].message
+ name: Status
+ type: string
+ deprecated: true
+ deprecationWarning: v1beta1 Receiver is deprecated, upgrade to v1
name: v1beta1
schema:
openAPIV3Schema:
@@ -227,6 +452,8 @@ spec:
- jsonPath: .status.conditions[?(@.type=="Ready")].message
name: Status
type: string
+ deprecated: true
+ deprecationWarning: v1beta2 Receiver is deprecated, upgrade to v1
name: v1beta2
schema:
openAPIV3Schema:
@@ -265,10 +492,10 @@ spec:
to let you locate the typed referenced object at cluster level
properties:
apiVersion:
- description: API version of the referent.
+ description: API version of the referent
type: string
kind:
- description: Kind of the referent.
+ description: Kind of the referent
enum:
- Bucket
- GitRepository
@@ -288,19 +515,21 @@ spec:
{key,value} in the matchLabels map is equivalent to an element
of matchExpressions, whose key field is "key", the operator
is "In", and the values array contains only "value". The requirements
- are ANDed.
+ are ANDed. MatchLabels requires the name to be set to `*`.
type: object
name:
- description: Name of the referent.
+ description: Name of the referent If multiple resources are
+ targeted `*` may be set.
maxLength: 53
minLength: 1
type: string
namespace:
- description: Namespace of the referent.
+ description: Namespace of the referent
maxLength: 53
minLength: 1
type: string
required:
+ - kind
- name
type: object
type: array
@@ -434,6 +663,6 @@ spec:
type: object
type: object
served: true
- storage: true
+ storage: false
subresources:
status: {}
diff --git a/config/samples/notification_v1beta2_receiver.yaml b/config/samples/notification_v1_receiver.yaml
similarity index 86%
rename from config/samples/notification_v1beta2_receiver.yaml
rename to config/samples/notification_v1_receiver.yaml
index 893644d2c..f3c4c2870 100644
--- a/config/samples/notification_v1beta2_receiver.yaml
+++ b/config/samples/notification_v1_receiver.yaml
@@ -1,4 +1,4 @@
-apiVersion: notification.toolkit.fluxcd.io/v1beta2
+apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: receiver-sample
diff --git a/config/testdata/status-defaults/receiver.yaml b/config/testdata/status-defaults/receiver.yaml
index 254f51afb..fbba61a78 100644
--- a/config/testdata/status-defaults/receiver.yaml
+++ b/config/testdata/status-defaults/receiver.yaml
@@ -1,4 +1,4 @@
-apiVersion: notification.toolkit.fluxcd.io/v1beta1
+apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: status-defaults
diff --git a/controllers/alert_controller.go b/controllers/alert_controller.go
index 5f3916011..c76476112 100644
--- a/controllers/alert_controller.go
+++ b/controllers/alert_controller.go
@@ -43,7 +43,8 @@ import (
"github.com/fluxcd/pkg/runtime/predicates"
kuberecorder "k8s.io/client-go/tools/record"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
+ apiv1beta2 "github.com/fluxcd/notification-controller/api/v1beta2"
)
var (
@@ -69,9 +70,9 @@ func (r *AlertReconciler) SetupWithManager(mgr ctrl.Manager) error {
}
func (r *AlertReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts AlertReconcilerOptions) error {
- if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &apiv1.Alert{}, ProviderIndexKey,
+ if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &apiv1beta2.Alert{}, ProviderIndexKey,
func(o client.Object) []string {
- alert := o.(*apiv1.Alert)
+ alert := o.(*apiv1beta2.Alert)
return []string{
fmt.Sprintf("%s/%s", alert.GetNamespace(), alert.Spec.ProviderRef.Name),
}
@@ -81,11 +82,11 @@ func (r *AlertReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts Aler
recoverPanic := true
return ctrl.NewControllerManagedBy(mgr).
- For(&apiv1.Alert{}, builder.WithPredicates(
+ For(&apiv1beta2.Alert{}, builder.WithPredicates(
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
)).
Watches(
- &source.Kind{Type: &apiv1.Provider{}},
+ &source.Kind{Type: &apiv1beta2.Provider{}},
handler.EnqueueRequestsFromMapFunc(r.requestsForProviderChange),
builder.WithPredicates(predicate.GenerationChangedPredicate{}),
).
@@ -105,7 +106,7 @@ func (r *AlertReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resu
reconcileStart := time.Now()
log := ctrl.LoggerFrom(ctx)
- obj := &apiv1.Alert{}
+ obj := &apiv1beta2.Alert{}
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
@@ -158,7 +159,7 @@ func (r *AlertReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resu
return r.reconcile(ctx, obj)
}
-func (r *AlertReconciler) reconcile(ctx context.Context, alert *apiv1.Alert) (ctrl.Result, error) {
+func (r *AlertReconciler) reconcile(ctx context.Context, alert *apiv1beta2.Alert) (ctrl.Result, error) {
// Mark the resource as under reconciliation.
conditions.MarkReconciling(alert, meta.ProgressingReason, "Reconciliation in progress")
@@ -173,8 +174,8 @@ func (r *AlertReconciler) reconcile(ctx context.Context, alert *apiv1.Alert) (ct
return ctrl.Result{}, nil
}
-func (r *AlertReconciler) isProviderReady(ctx context.Context, alert *apiv1.Alert) error {
- provider := &apiv1.Provider{}
+func (r *AlertReconciler) isProviderReady(ctx context.Context, alert *apiv1beta2.Alert) error {
+ provider := &apiv1beta2.Provider{}
providerName := types.NamespacedName{Namespace: alert.Namespace, Name: alert.Spec.ProviderRef.Name}
if err := r.Get(ctx, providerName, provider); err != nil {
// log not found errors since they get filtered out
@@ -190,13 +191,13 @@ func (r *AlertReconciler) isProviderReady(ctx context.Context, alert *apiv1.Aler
}
func (r *AlertReconciler) requestsForProviderChange(o client.Object) []reconcile.Request {
- provider, ok := o.(*apiv1.Provider)
+ provider, ok := o.(*apiv1beta2.Provider)
if !ok {
panic(fmt.Errorf("expected a provider, got %T", o))
}
ctx := context.Background()
- var list apiv1.AlertList
+ var list apiv1beta2.AlertList
if err := r.List(ctx, &list, client.MatchingFields{
ProviderIndexKey: client.ObjectKeyFromObject(provider).String(),
}); err != nil {
@@ -212,7 +213,7 @@ func (r *AlertReconciler) requestsForProviderChange(o client.Object) []reconcile
}
// patch updates the object status, conditions and finalizers.
-func (r *AlertReconciler) patch(ctx context.Context, obj *apiv1.Alert, patcher *patch.SerialPatcher) (retErr error) {
+func (r *AlertReconciler) patch(ctx context.Context, obj *apiv1beta2.Alert, patcher *patch.SerialPatcher) (retErr error) {
// Configure the runtime patcher.
patchOpts := []patch.Option{}
ownedConditions := []string{
diff --git a/controllers/alert_controller_test.go b/controllers/alert_controller_test.go
index de975f249..1ac059db7 100644
--- a/controllers/alert_controller_test.go
+++ b/controllers/alert_controller_test.go
@@ -44,36 +44,37 @@ import (
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
+ apiv1beta2 "github.com/fluxcd/notification-controller/api/v1beta2"
"github.com/fluxcd/notification-controller/internal/server"
)
func TestAlertReconciler_Reconcile(t *testing.T) {
g := NewWithT(t)
timeout := 5 * time.Second
- resultA := &apiv1.Alert{}
+ resultA := &apiv1beta2.Alert{}
namespaceName := "alert-" + randStringRunes(5)
providerName := "provider-" + randStringRunes(5)
g.Expect(createNamespace(namespaceName)).NotTo(HaveOccurred(), "failed to create test namespace")
- provider := &apiv1.Provider{
+ provider := &apiv1beta2.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: providerName,
Namespace: namespaceName,
},
- Spec: apiv1.ProviderSpec{
+ Spec: apiv1beta2.ProviderSpec{
Type: "generic",
Address: "https://webhook.internal",
},
}
- alert := &apiv1.Alert{
+ alert := &apiv1beta2.Alert{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("alert-%s", randStringRunes(5)),
Namespace: namespaceName,
},
- Spec: apiv1.AlertSpec{
+ Spec: apiv1beta2.AlertSpec{
ProviderRef: meta.LocalObjectReference{
Name: providerName,
},
@@ -165,7 +166,7 @@ func TestAlertReconciler_EventHandler(t *testing.T) {
var (
namespace = "events-" + randStringRunes(5)
req *http.Request
- provider *apiv1.Provider
+ provider *apiv1beta2.Provider
)
g.Expect(createNamespace(namespace)).NotTo(HaveOccurred(), "failed to create test namespace")
@@ -200,19 +201,19 @@ func TestAlertReconciler_EventHandler(t *testing.T) {
Name: fmt.Sprintf("provider-%s", randStringRunes(5)),
Namespace: namespace,
}
- provider = &apiv1.Provider{
+ provider = &apiv1beta2.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: providerKey.Name,
Namespace: providerKey.Namespace,
},
- Spec: apiv1.ProviderSpec{
+ Spec: apiv1beta2.ProviderSpec{
Type: "generic",
Address: rcvServer.URL,
},
}
g.Expect(k8sClient.Create(context.Background(), provider)).To(Succeed())
g.Eventually(func() bool {
- var obj apiv1.Provider
+ var obj apiv1beta2.Provider
g.Expect(k8sClient.Get(context.Background(), client.ObjectKeyFromObject(provider), &obj))
return conditions.IsReady(&obj)
}, 30*time.Second, time.Second).Should(BeTrue())
@@ -238,12 +239,12 @@ func TestAlertReconciler_EventHandler(t *testing.T) {
Namespace: namespace,
}
- alert := &apiv1.Alert{
+ alert := &apiv1beta2.Alert{
ObjectMeta: metav1.ObjectMeta{
Name: alertKey.Name,
Namespace: alertKey.Namespace,
},
- Spec: apiv1.AlertSpec{
+ Spec: apiv1beta2.AlertSpec{
ProviderRef: meta.LocalObjectReference{
Name: providerKey.Name,
},
@@ -282,7 +283,7 @@ func TestAlertReconciler_EventHandler(t *testing.T) {
// wait for controller to mark the alert as ready
g.Eventually(func() bool {
- var obj apiv1.Alert
+ var obj apiv1beta2.Alert
g.Expect(k8sClient.Get(context.Background(), client.ObjectKeyFromObject(alert), &obj))
return conditions.IsReady(&obj)
}, 30*time.Second, time.Second).Should(BeTrue())
diff --git a/controllers/provider_controller.go b/controllers/provider_controller.go
index 5dc23eb76..1568f6889 100644
--- a/controllers/provider_controller.go
+++ b/controllers/provider_controller.go
@@ -43,7 +43,8 @@ import (
"github.com/fluxcd/pkg/runtime/patch"
"github.com/fluxcd/pkg/runtime/predicates"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
+ apiv1beta2 "github.com/fluxcd/notification-controller/api/v1beta2"
"github.com/fluxcd/notification-controller/internal/notifier"
)
@@ -68,7 +69,7 @@ func (r *ProviderReconciler) SetupWithManager(mgr ctrl.Manager) error {
func (r *ProviderReconciler) SetupWithManagerAndOptions(mgr ctrl.Manager, opts ProviderReconcilerOptions) error {
recoverPanic := true
return ctrl.NewControllerManagedBy(mgr).
- For(&apiv1.Provider{}, builder.WithPredicates(
+ For(&apiv1beta2.Provider{}, builder.WithPredicates(
predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}),
)).
WithOptions(controller.Options{
@@ -88,7 +89,7 @@ func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
reconcileStart := time.Now()
log := ctrl.LoggerFrom(ctx)
- obj := &apiv1.Provider{}
+ obj := &apiv1beta2.Provider{}
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
@@ -150,7 +151,7 @@ func (r *ProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (r
return r.reconcile(ctx, obj)
}
-func (r *ProviderReconciler) reconcile(ctx context.Context, obj *apiv1.Provider) (ctrl.Result, error) {
+func (r *ProviderReconciler) reconcile(ctx context.Context, obj *apiv1beta2.Provider) (ctrl.Result, error) {
// Mark the resource as under reconciliation.
conditions.MarkReconciling(obj, meta.ProgressingReason, "Reconciliation in progress")
conditions.Delete(obj, meta.StalledCondition)
@@ -173,7 +174,7 @@ func (r *ProviderReconciler) reconcile(ctx context.Context, obj *apiv1.Provider)
return ctrl.Result{RequeueAfter: obj.GetInterval()}, nil
}
-func (r *ProviderReconciler) validateURLs(provider *apiv1.Provider) error {
+func (r *ProviderReconciler) validateURLs(provider *apiv1beta2.Provider) error {
address := provider.Spec.Address
proxy := provider.Spec.Proxy
@@ -188,7 +189,7 @@ func (r *ProviderReconciler) validateURLs(provider *apiv1.Provider) error {
return nil
}
-func (r *ProviderReconciler) validateCredentials(ctx context.Context, provider *apiv1.Provider) error {
+func (r *ProviderReconciler) validateCredentials(ctx context.Context, provider *apiv1beta2.Provider) error {
address := provider.Spec.Address
proxy := provider.Spec.Proxy
username := provider.Spec.Username
@@ -265,7 +266,7 @@ func (r *ProviderReconciler) validateCredentials(ctx context.Context, provider *
}
// patch updates the object status, conditions and finalizers.
-func (r *ProviderReconciler) patch(ctx context.Context, obj *apiv1.Provider, patcher *patch.SerialPatcher) (retErr error) {
+func (r *ProviderReconciler) patch(ctx context.Context, obj *apiv1beta2.Provider, patcher *patch.SerialPatcher) (retErr error) {
// Configure the runtime patcher.
patchOpts := []patch.Option{}
ownedConditions := []string{
diff --git a/controllers/provider_controller_test.go b/controllers/provider_controller_test.go
index 689d21df2..ea531e677 100644
--- a/controllers/provider_controller_test.go
+++ b/controllers/provider_controller_test.go
@@ -33,13 +33,14 @@ import (
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/conditions"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
+ apiv1beta2 "github.com/fluxcd/notification-controller/api/v1beta2"
)
func TestProviderReconciler_Reconcile(t *testing.T) {
g := NewWithT(t)
timeout := 5 * time.Second
- resultP := &apiv1.Provider{}
+ resultP := &apiv1beta2.Provider{}
namespaceName := "provider-" + randStringRunes(5)
secretName := "secret-" + randStringRunes(5)
@@ -49,12 +50,12 @@ func TestProviderReconciler_Reconcile(t *testing.T) {
Name: fmt.Sprintf("provider-%s", randStringRunes(5)),
Namespace: namespaceName,
}
- provider := &apiv1.Provider{
+ provider := &apiv1beta2.Provider{
ObjectMeta: metav1.ObjectMeta{
Name: providerKey.Name,
Namespace: providerKey.Namespace,
},
- Spec: apiv1.ProviderSpec{
+ Spec: apiv1beta2.ProviderSpec{
Type: "generic",
Address: "https://webhook.internal",
},
diff --git a/controllers/receiver_controller.go b/controllers/receiver_controller.go
index f441fc1cc..3d658781f 100644
--- a/controllers/receiver_controller.go
+++ b/controllers/receiver_controller.go
@@ -40,7 +40,7 @@ import (
"github.com/fluxcd/pkg/runtime/patch"
"github.com/fluxcd/pkg/runtime/predicates"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
)
// ReceiverReconciler reconciles a Receiver object
diff --git a/controllers/receiver_controller_test.go b/controllers/receiver_controller_test.go
index 0f123469f..60738f0d6 100644
--- a/controllers/receiver_controller_test.go
+++ b/controllers/receiver_controller_test.go
@@ -39,7 +39,7 @@ import (
"github.com/fluxcd/pkg/runtime/conditions"
"github.com/fluxcd/pkg/ssa"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
"github.com/fluxcd/notification-controller/internal/server"
)
@@ -102,6 +102,7 @@ func TestReceiverReconciler_Reconcile(t *testing.T) {
g.Expect(conditions.Has(resultR, meta.ReconcilingCondition)).To(BeFalse())
g.Expect(controllerutil.ContainsFinalizer(resultR, apiv1.NotificationFinalizer)).To(BeTrue())
+ g.Expect(resultR.Spec.Interval.Duration).To(BeIdenticalTo(10 * time.Minute))
})
t.Run("fails with secret not found error", func(t *testing.T) {
diff --git a/controllers/suite_test.go b/controllers/suite_test.go
index 4ab956a5c..9c2302d3a 100644
--- a/controllers/suite_test.go
+++ b/controllers/suite_test.go
@@ -40,7 +40,8 @@ import (
"github.com/fluxcd/pkg/runtime/testenv"
"github.com/fluxcd/pkg/ssa"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
+ apiv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
// +kubebuilder:scaffold:imports
)
@@ -54,6 +55,7 @@ var (
func TestMain(m *testing.M) {
var err error
utilruntime.Must(apiv1.AddToScheme(scheme.Scheme))
+ utilruntime.Must(apiv1b2.AddToScheme(scheme.Scheme))
testEnv = testenv.New(testenv.WithCRDPath(
filepath.Join("..", "config", "crd", "bases"),
diff --git a/docs/api/v1/notification.md b/docs/api/v1/notification.md
new file mode 100644
index 000000000..8bfad07ad
--- /dev/null
+++ b/docs/api/v1/notification.md
@@ -0,0 +1,442 @@
+Notification API reference v1
+Packages:
+
+
+Package v1 contains API Schema definitions for the notification v1 API group.
+Resource Types:
+
+
+Receiver is the Schema for the receivers API.
+
+
+
+(Appears on:
+ReceiverSpec)
+
+CrossNamespaceObjectReference contains enough information to let you locate the
+typed referenced object at cluster level
+
+
+
+(Appears on:
+Receiver)
+
+ReceiverSpec defines the desired state of the Receiver.
+
+
+
+(Appears on:
+Receiver)
+
+ReceiverStatus defines the observed state of the Receiver.
+
+
+
This page was automatically generated with gen-crd-api-reference-docs
+
diff --git a/docs/api/notification.md b/docs/api/v1beta2/notification.md
similarity index 89%
rename from docs/api/notification.md
rename to docs/api/v1beta2/notification.md
index cc2837b5e..c2d7a56de 100644
--- a/docs/api/notification.md
+++ b/docs/api/v1beta2/notification.md
@@ -1,4 +1,4 @@
-Notification API reference
+Notification API reference v1beta2
Packages:
-
@@ -76,7 +76,7 @@ AlertSpec
providerRef
-
+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
@@ -102,8 +102,8 @@ If set to ‘info’ no events will be filtered.
|
eventSources
-
-[]CrossNamespaceObjectReference
+
+[]github.com/fluxcd/notification-controller/api/v1.CrossNamespaceObjectReference
|
@@ -241,7 +241,7 @@ string
interval
-
+
Kubernetes meta/v1.Duration
@@ -291,7 +291,7 @@ string
|
timeout
-
+
Kubernetes meta/v1.Duration
@@ -317,7 +317,7 @@ string
|
secretRef
-
+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
@@ -332,7 +332,7 @@ credentials for this Provider.
|
certSecretRef
-
+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
@@ -448,7 +448,7 @@ the validation procedure and payload deserialization.
|
interval
-
+
Kubernetes meta/v1.Duration
@@ -475,8 +475,8 @@ e.g. ‘push’ for GitHub or ‘Push Hook’ for GitLab.
|
resources
-
-[]CrossNamespaceObjectReference
+
+[]github.com/fluxcd/notification-controller/api/v1.CrossNamespaceObjectReference
|
@@ -488,7 +488,7 @@ e.g. ‘push’ for GitHub or ‘Push Hook’ for GitLab.
secretRef
-
+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
@@ -551,7 +551,7 @@ ReceiverStatus
|
providerRef
-
+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
@@ -577,8 +577,8 @@ If set to ‘info’ no events will be filtered.
|
eventSources
-
-[]CrossNamespaceObjectReference
+
+[]github.com/fluxcd/notification-controller/api/v1.CrossNamespaceObjectReference
|
@@ -650,7 +650,7 @@ events handling for this Alert.
ReconcileRequestStatus
-
+
github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
@@ -693,11 +693,6 @@ int64
-
-(Appears on:
-AlertSpec,
-ReceiverSpec)
-
CrossNamespaceObjectReference contains enough information to let you locate the
typed referenced object at cluster level
@@ -806,7 +801,7 @@ string
interval
-
+
Kubernetes meta/v1.Duration
@@ -856,7 +851,7 @@ string
|
timeout
-
+
Kubernetes meta/v1.Duration
@@ -882,7 +877,7 @@ string
|
secretRef
-
+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
@@ -897,7 +892,7 @@ credentials for this Provider.
|
certSecretRef
-
+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
@@ -946,7 +941,7 @@ events handling for this Provider.
|
ReconcileRequestStatus
-
+
github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
@@ -1020,7 +1015,7 @@ the validation procedure and payload deserialization.
|
interval
-
+
Kubernetes meta/v1.Duration
@@ -1047,8 +1042,8 @@ e.g. ‘push’ for GitHub or ‘Push Hook’ for GitLab.
|
resources
-
-[]CrossNamespaceObjectReference
+
+[]github.com/fluxcd/notification-controller/api/v1.CrossNamespaceObjectReference
|
@@ -1060,7 +1055,7 @@ e.g. ‘push’ for GitHub or ‘Push Hook’ for GitLab.
secretRef
-
+
github.com/fluxcd/pkg/apis/meta.LocalObjectReference
@@ -1108,7 +1103,7 @@ events handling for this receiver.
|
ReconcileRequestStatus
-
+
github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
diff --git a/docs/spec/README.md b/docs/spec/README.md
index 0b8c0cb98..43eb5d9f0 100644
--- a/docs/spec/README.md
+++ b/docs/spec/README.md
@@ -1,66 +1,8 @@
# Notification Controller
-The Notification Controller is a Kubernetes operator, specialized in handling inbound and outbound events.
+## API Specification
-## Motivation
-
-### Events dispatching
-
-The main goal is to provide a notification service that can
-receive events via HTTP and dispatch them to external systems
-based on event severity and involved objects.
-
-When operating a cluster, different teams may wish to receive notification about the status
-of their CD pipelines. For example, the on-call team would receive alerts about all
-failures in the cluster, while the dev team may wish to be alerted when a new version
-of an app was deployed and if the deployment is healthy.
-
-### Webhook receivers
-
-GitOps controllers are by nature pull based, in order to notify the controllers about
-changes in Git or Helm repositories, one may wish to setup webhooks and trigger
-a cluster reconciliation every time a source changes.
-
-## Design
-
-### Events dispatching
-
-The controller exposes an HTTP endpoint for receiving events from other controllers.
-An event must contain information about the involved object such as kind, name, namespace,
-a human-readable description of the event and the severity type e.g. info or error.
-
-The controller can be configured with Kubernetes custom resources that define how
-events are processed and where to dispatch them.
-
-Notification API:
-
-* [Alerts](v1beta2/alerts.md)
-* [Providers](v1beta2/providers.md)
-* [Events](v1beta2/events.md)
-
-The alert delivery method is **at-most once** with a timeout of 15 seconds.
-The controller performs automatic retries for connection errors and 500-range response code.
-If the webhook receiver returns an error, the controller will retry sending an alert for four times
-with an exponential backoff of maximum 30 seconds.
-
-### Webhook receivers
-
-The notification controller handles webhook requests on a dedicated port.
-This port can be used to create a Kubernetes LoadBalancer Service or
-Ingress to expose the receiver endpoint outside the cluster
-to be accessed by GitHub, GitLab, Bitbucket, Harbor, DockerHub, Jenkins, Quay, etc.
-
-Receiver API:
-
-* [Receivers](v1beta2/receivers.md)
-
-When a `Receiver` is created, the controller sets the `Receiver`
-status to Ready and generates a URL in the format `/hook/sha256sum(token+name+namespace)`.
-
-When the controller receives a POST request:
-* extract the SHA265 digest from the URL
-* loads the `Receiver` using the digest field selector
-* extracts the signature from HTTP headers based on `spec.type`
-* validates the signature using the `token` secret
-* extract the event type from the payload
-* triggers a reconciliation for `spec.resources` if the event type matches one of the `spec.events` items
+* [v1](v1/README.md)
+* [v1beta2](v1beta2/README.md)
+* [v1beta1](v1beta1/README.md)
+* [v1alpha1](v1alpha1/README.md)
diff --git a/docs/spec/v1/README.md b/docs/spec/v1/README.md
new file mode 100644
index 000000000..18960c538
--- /dev/null
+++ b/docs/spec/v1/README.md
@@ -0,0 +1,7 @@
+# notification.toolkit.fluxcd.io/v1
+
+This is the v1 API specification for defining events handling and dispatching.
+
+## Specification
+
+* [Receivers](receivers.md)
\ No newline at end of file
diff --git a/docs/spec/v1/receivers.md b/docs/spec/v1/receivers.md
new file mode 100644
index 000000000..8887d0573
--- /dev/null
+++ b/docs/spec/v1/receivers.md
@@ -0,0 +1,913 @@
+# Receivers
+
+
+
+The `Receiver` API defines an incoming webhook receiver that triggers the
+reconciliation for a group of Flux Custom Resources.
+
+## Example
+
+The following is an example of how to configure an incoming webhook for the
+GitHub repository where Flux was bootstrapped with `flux bootstrap github`.
+After a Git push, GitHub will send a push event to notification-controller,
+which in turn tells Flux to pull and apply the latest changes from upstream.
+
+**Note:** The following assumes an Ingress exposes the controller's
+`webhook-receiver` Kubernetes Service. How to configure the Ingress is out of
+scope for this example.
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: github-receiver
+ namespace: flux-system
+spec:
+ type: github
+ events:
+ - "ping"
+ - "push"
+ secretRef:
+ name: receiver-token
+ resources:
+ - apiVersion: source.toolkit.fluxcd.io/v1
+ kind: GitRepository
+ name: flux-system
+```
+
+In the above example:
+
+- A Receiver named `github-receiver` is created, indicated by the
+ `.metadata.name` field.
+- The notification-controller generates a unique webhook path using the
+ Receiver name, namespace and the token from the referenced
+ `.spec.secretRef.name` secret.
+- The incoming webhook path is reported in the `.status.webhookPath` field.
+- When a GitHub push event is received, the controller verifies the payload's
+ integrity and authenticity, using [HMAC][] and the `X-Hub-Signature` HTTP
+ header.
+- If the event type matches `.spec.events` and the payload is verified, then
+ the controller triggers a reconciliation for the `flux-system` GitRepository
+ which is listed under `.spec.resources`.
+
+You can run this example by saving the manifest into `github-receiver.yaml`.
+
+1. Generate a random string and create a Secret with a `token` field:
+
+ ```sh
+ TOKEN=$(head -c 12 /dev/urandom | shasum | cut -d ' ' -f1)
+
+ kubectl -n flux-system create secret generic receiver-token \
+ --from-literal=token=$TOKEN
+ ```
+
+2. Apply the resource on the cluster:
+
+ ```sh
+ kubectl -n flux-system apply -f github-receiver.yaml
+ ```
+
+3. Run `kubectl -n flux-system describe receiver github-receiver` to see its status:
+
+ ```console
+ ...
+ Status:
+ Conditions:
+ Last Transition Time: 2022-11-16T23:43:38Z
+ Message: Receiver initialised for path: /hook/bed6d00b5555b1603e1f59b94d7fdbca58089cb5663633fb83f2815dc626d92b
+ Observed Generation: 1
+ Reason: Succeeded
+ Status: True
+ Type: Ready
+ Observed Generation: 1
+ Webhook Path: /hook/bed6d00b5555b1603e1f59b94d7fdbca58089cb5663633fb83f2815dc626d92b
+ Events:
+ Type Reason Age From Message
+ ---- ------ ---- ---- -------
+ Normal Succeeded 82s notification-controller Reconciliation finished, next run in 10m
+ ```
+
+4. Run `kubectl -n flux-system get receivers` to see the generated webhook path:
+
+ ```console
+ NAME READY STATUS
+ github-receiver True Receiver initialised for path: /hook/bed6d00b5555b1603e1f59b94d7fdbca58089cb5663633fb83f2815dc626d92b
+ ```
+
+5. On GitHub, navigate to your repository and click on the "Add webhook" button
+ under "Settings/Webhooks". Fill the form with:
+
+ - **Payload URL**: The composed address, consisting of the Ingress' hostname
+ exposing the controller's `webhook-receiver` Kubernetes Service, and the
+ generated path for the Receiver. For this example:
+ `https:///hook/bed6d00b5555b1603e1f59b94d7fdbca58089cb5663633fb83f2815dc626d92b`
+ - **Secret**: The `token` string generated in step 1.
+
+## Writing a Receiver spec
+
+As with all other Kubernetes config, a Receiver needs `apiVersion`,
+`kind`, and `metadata` fields. The name of a Receiver object must be a
+valid [DNS subdomain name](https://kubernetes.io/docs/concepts/overview/working-with-objects/names#dns-subdomain-names).
+
+A Receiver also needs a
+[`.spec` section](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status).
+
+### Type
+
+`.spec.type` is a required field that specifies how the controller should
+handle the incoming webhook request.
+
+#### Supported Receiver types
+
+| Receiver | Type | Supports filtering using [Events](#events) |
+| ------------------------------------------ | -------------- | ------------------------------------------ |
+| [Generic webhook](#generic) | `generic` | ❌ |
+| [Generic webhook with HMAC](#generic-hmac) | `generic-hmac` | ❌ |
+| [GitHub](#github) | `github` | ✅ |
+| [Gitea](#github) | `github` | ✅ |
+| [GitLab](#gitlab) | `gitlab` | ✅ |
+| [Bitbucket server](#bitbucket-server) | `bitbucket` | ✅ |
+| [Harbor](#harbor) | `harbor` | ❌ |
+| [DockerHub](#dockerhub) | `dockerhub` | ❌ |
+| [Quay](#quay) | `quay` | ❌ |
+| [Nexus](#nexus) | `nexus` | ❌ |
+| [Azure Container Registry](#acr) | `acr` | ❌ |
+| [Google Container Registry](#gcr) | `gcr` | ❌ |
+
+#### Generic
+
+When a Receiver's `.spec.type` is set to `generic`, the controller will respond
+to any HTTP request to the generated [`.status.webhookPath` path](#webhook-path),
+and request a reconciliation for all listed [Resources](#resources).
+
+**Note:** This type of Receiver does not perform any validation on the incoming
+request, and it does not support filtering using [Events](#events).
+
+##### Generic example
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: generic-receiver
+ namespace: default
+spec:
+ type: generic
+ secretRef:
+ name: webhook-token
+ resources:
+ - apiVersion: source.toolkit.fluxcd.io/v1
+ kind: GitRepository
+ name: webapp
+ namespace: default
+```
+
+#### Generic HMAC
+
+When a Receiver's `.spec.type` is set to `generic-hmac`, the controller will
+respond to any HTTP request to the generated [`.status.webhookPath` path](#webhook-path),
+while verifying the request's payload integrity and authenticity using [HMAC][].
+
+The controller uses the `X-Signature` header to get the hash signature. This
+signature should be prefixed with the hash function (`sha1`, `sha256` or
+`sha512`) used to generate the signature, in the following format:
+`=`.
+
+To validate the HMAC signature, the controller will use the `token` string
+from the [Secret reference](#secret-reference) to generate a hash signature
+using the same hash function as the one specified in the `X-Signature` header.
+
+If the generated hash signature matches the one specified in the `X-Signature`
+header, the controller will request a reconciliation for all listed
+[Resources](#resources).
+
+**Note:** This type of Receiver does not support filtering using
+[Events](#events).
+
+##### Generic HMAC example
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: generic-hmac-receiver
+ namespace: default
+spec:
+ type: generic-hmac
+ secretRef:
+ name: webhook-token
+ resources:
+ - apiVersion: source.toolkit.fluxcd.io/v1
+ kind: GitRepository
+ name: webapp
+ namespace: default
+```
+
+##### HMAC signature generation example
+
+1. Generate the HMAC hash for the request body using OpenSSL:
+
+ ```sh
+ printf '' | openssl dgst -sha1 -r -hmac "" | awk '{print $1}'
+ ```
+
+ You can replace the `-sha1` flag with `-sha256` or `-sha512` to use a
+ different hash function.
+
+2. Send an HTTP POST request with the body and the HMAC hash to the webhook URL:
+
+ ```sh
+ curl -X POST -H "X-Signature: =" -d ''
+ ```
+
+#### GitHub
+
+When a Receiver's `.spec.type` is set to `github`, the controller will respond
+to an [HTTP webhook event payload](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads)
+from GitHub to the generated [`.status.webhookPath` path](#webhook-path),
+while verifying the payload using [HMAC][].
+
+The controller uses the [`X-Hub-Signature` header](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#delivery-headers)
+from the request made by GitHub to get the hash signature. To enable the
+inclusion of this header, the `token` string from the [Secret reference](#secret-reference)
+must be configured as the [secret token for the
+webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks#setting-your-secret-token).
+
+The controller will calculate the HMAC hash signature for the received request
+payload using the same `token` string, and compare it with the one specified in
+the header. If the two signatures match, the controller will request a
+reconciliation for all listed [Resources](#resources).
+
+This type of Receiver offers the ability to filter incoming events by comparing
+the `X-GitHub-Event` header to the list of [Events](#events).
+For a list of available events, see the [GitHub
+documentation](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads).
+
+##### GitHub example
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: github-receiver
+ namespace: default
+spec:
+ type: github
+ events:
+ - "ping"
+ - "push"
+ secretRef:
+ name: webhook-token
+ resources:
+ - apiVersion: source.toolkit.fluxcd.io/v1
+ kind: GitRepository
+ name: webapp
+```
+
+The above example makes use of the [`.spec.events` field](#events) to filter
+incoming events from GitHub, instructing the controller to only respond to
+[`ping`](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#ping)
+and [`push`](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#push)
+events.
+
+#### Gitea
+
+For Gitea, the `.spec.type` field can be set to `github` as it produces [GitHub
+type](#github) compatible [webhook event payloads](https://docs.gitea.io/en-us/webhooks/).
+
+**Note:** While the payloads are compatible with the GitHub type, the number of
+available events may be limited and/or different from the ones available in
+GitHub. Refer to the [Gitea source code](https://github.com/go-gitea/gitea/blob/main/models/webhook/hooktask.go#L28)
+to see the list of available [events](#events).
+
+#### GitLab
+
+When a Receiver's `.spec.type` is set to `gitlab`, the controller will respond
+to an [HTTP webhook event payload](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#events)
+from GitLab to the generated [`.status.webhookPath` path](#webhook-path).
+
+The controller validates the payload's authenticity by comparing the
+[`X-Gitlab-Token` header](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#validate-payloads-by-using-a-secret-token)
+from the request made by GitLab to the `token` string from the [Secret
+reference](#secret-reference). To enable the inclusion of this header, the
+`token` string must be configured as the "Secret token" while [configuring a
+webhook in GitLab](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#configure-a-webhook-in-gitlab).
+
+If the two tokens match, the controller will request a reconciliation for all
+listed [Resources](#resources).
+
+This type of Receiver offers the ability to filter incoming events by comparing
+the `X-Gitlab-Event` header to the list of [Events](#events). For a list of
+available webhook types, refer to the [GitLab
+documentation](https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html).
+
+##### GitLab example
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: gitlab-receiver
+ namespace: default
+spec:
+ type: gitlab
+ events:
+ - "Push Hook"
+ - "Tag Push Hook"
+ secretRef:
+ name: webhook-token
+ resources:
+ - apiVersion: source.toolkit.fluxcd.io/v1
+ kind: GitRepository
+ name: webapp-frontend
+ - apiVersion: source.toolkit.fluxcd.io/v1
+ kind: GitRepository
+ name: webapp-backend
+```
+
+The above example makes use of the [`.spec.events` field](#events) to filter
+incoming events from GitLab, instructing the controller to only respond to
+[`Push Hook`](https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#push-events)
+and [`Tag Push Hook`](https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#tag-events)
+events.
+
+#### Bitbucket Server
+
+When a Receiver's `.spec.type` is set to `bitbucket`, the controller will
+respond to an [HTTP webhook event payload](https://confluence.atlassian.com/bitbucketserver/event-payload-938025882.html)
+from Bitbucket Server to the generated [`.status.webhookPath` path](#webhook-path),
+while verifying the payload's integrity and authenticity using [HMAC][].
+
+The controller uses the [`X-Hub-Signature` header](https://confluence.atlassian.com/bitbucketserver/manage-webhooks-938025878.html#Managewebhooks-webhooksecrets)
+from the request made by BitBucket Server to get the hash signature. To enable
+the inclusion of this header, the `token` string from the [Secret
+reference](#secret-reference) must be configured as the "Secret" while creating
+a webhook in Bitbucket Server.
+
+The controller will calculate the HMAC hash signature for the received request
+payload using the same `token` string, and compare it with the one specified in
+the header. If the two signatures match, the controller will request a
+reconciliation for all listed [Resources](#resources).
+
+This type of Receiver offers the ability to filter incoming events by comparing
+the `X-Event-Key` header to the list of [Events](#events). For a list of
+available event keys, refer to the [Bitbucket Server
+documentation](https://confluence.atlassian.com/bitbucketserver/event-payload-938025882.html#Eventpayload-Repositoryevents).
+
+**Note:** Bitbucket Cloud does not support signing webhook requests
+([BCLOUD-14683](https://jira.atlassian.com/browse/BCLOUD-14683),
+[BCLOUD-12195](https://jira.atlassian.com/browse/BCLOUD-12195)). If your
+repositories are on Bitbucket Cloud, you will need to use a [Generic
+Receiver](#generic) instead.
+
+##### Bitbucket Server example
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: bitbucket-receiver
+ namespace: default
+spec:
+ type: bitbucket
+ events:
+ - "repo:refs_changed"
+ secretRef:
+ name: webhook-token
+ resources:
+ - apiVersion: source.toolkit.fluxcd.io/v1
+ kind: GitRepository
+ name: webapp
+```
+
+The above example makes use of the [`.spec.events` field](#events) to filter
+incoming events from Bitbucket Server, instructing the controller to only
+respond to [`repo:refs_changed` (Push)](https://confluence.atlassian.com/bitbucketserver/event-payload-938025882.html#Eventpayload-Push)
+events.
+
+#### Harbor
+
+When a Receiver's `.spec.type` is set to `harbor`, the controller will respond
+to an [HTTP webhook event payload](https://goharbor.io/docs/latest/working-with-projects/project-configuration/configure-webhooks/#payload-format)
+from Harbor to the generated [`.status.webhookPath` path](#webhook-path).
+
+The controller validates the payload's authenticity by comparing the
+`Authorization` header from the request made by Harbor to the `token` string
+from the [Secret reference](#secret-reference). To enable the inclusion of this
+header, the `token` string must be configured as the "Auth Header" while
+[configuring a webhook in
+Harbor](https://goharbor.io/docs/latest/working-with-projects/project-configuration/configure-webhooks/#configure-webhooks).
+
+If the two tokens match, the controller will request a reconciliation for all
+listed [Resources](#resources).
+
+**Note:** This type of Receiver does not support filtering using
+[Events](#events). However, Harbor does support configuring event types for
+which a webhook will be triggered.
+
+##### Harbor example
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: harbor-receiver
+ namespace: default
+spec:
+ type: harbor
+ secretRef:
+ name: webhook-token
+ resources:
+ - apiVersion: image.toolkit.fluxcd.io/v1beta2
+ kind: ImageRepository
+ name: webapp
+```
+
+#### DockerHub
+
+When a Receiver's `.spec.type` is set to `dockerhub`, the controller will
+respond to an [HTTP webhook event payload](https://docs.docker.com/docker-hub/webhooks/)
+from DockerHub to the generated [`.status.webhookPath` path](#webhook-path).
+
+The controller performs minimal validation of the payload by attempting to
+unmarshal the [JSON request body](https://docs.docker.com/docker-hub/webhooks/#example-webhook-payload).
+If the unmarshalling is successful, the controller will request a reconciliation
+for all listed [Resources](#resources).
+
+**Note:** This type of Receiver does not support filtering using
+[Events](#events).
+
+##### DockerHub example
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: dockerhub-receiver
+ namespace: default
+spec:
+ type: dockerhub
+ secretRef:
+ name: webhook-token
+ resources:
+ - apiVersion: image.toolkit.fluxcd.io/v1beta2
+ kind: ImageRepository
+ name: webapp
+```
+
+#### Quay
+
+When a Receiver's `.spec.type` is set to `quay`, the controller will respond to
+an HTTP [Repository Push Notification payload](https://docs.quay.io/guides/notifications.html#repository-push)
+from Quay to the generated [`.status.webhookPath` path](#webhook-path).
+
+The controller performs minimal validation of the payload by attempting to
+unmarshal the JSON request body to the expected format. If the unmarshalling is
+successful, the controller will request a reconciliation for all listed
+[Resources](#resources).
+
+**Note:** This type of Receiver does not support filtering using
+[Events](#events). In addition, it does not support any "Repository
+Notification" other than "Repository Push".
+
+##### Quay example
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: quay-receiver
+ namespace: default
+spec:
+ type: quay
+ secretRef:
+ name: webhook-token
+ resources:
+ - apiVersion: image.toolkit.fluxcd.io/v1beta2
+ kind: ImageRepository
+ name: webapp
+```
+
+#### Nexus
+
+When a Receiver's `.spec.type` is set to `nexus`, the controller will respond
+to an [HTTP webhook event payload](https://help.sonatype.com/repomanager3/integrations/webhooks/example-headers-and-payloads)
+from Nexus Repository Manager 3 to the generated [`.status.webhookPath`
+path](#webhook-path), while verifying the payload's integrity and
+authenticity using [HMAC][].
+
+The controller validates the payload by comparing the
+[`X-Nexus-Webhook-Signature` header](https://help.sonatype.com/repomanager3/integrations/webhooks/working-with-hmac-payloads)
+from the request made by Nexus to the `token` string from the [Secret
+reference](#secret-reference). To enable the inclusion of this header, the
+`token` string must be configured as the "Secret Key" while [enabling a
+repository webhook capability](https://help.sonatype.com/repomanager3/integrations/webhooks/enabling-a-repository-webhook-capability).
+
+The controller will calculate the HMAC hash signature for the received request
+payload using the same `token` string, and compare it with the one specified in
+the header. If the two signatures match, the controller will attempt to
+unmarshal the request body to the expected format. If the unmarshalling is
+successful, the controller will request a reconciliation for all listed
+[Resources](#resources).
+
+**Note:** This type of Receiver does not support filtering using
+[Events](#events).
+
+##### Nexus example
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: nexus-receiver
+ namespace: default
+spec:
+ type: nexus
+ secretRef:
+ name: webhook-token
+ resources:
+ - apiVersion: image.toolkit.fluxcd.io/v1beta2
+ kind: ImageRepository
+ name: webapp
+```
+
+#### GCR
+
+When a Receiver's `.spec.type` is set to `gcr`, the controller will respond to
+an [HTTP webhook event payload](https://cloud.google.com/container-registry/docs/configuring-notifications#notification_examples)
+from Google Cloud Registry to the generated [`.status.webhookPath`](#webhook-path),
+while verifying the payload is legitimate using [JWT](https://cloud.google.com/pubsub/docs/push#authentication).
+
+The controller verifies the request originates from Google by validating the
+token from the [`Authorization` header](https://cloud.google.com/pubsub/docs/push#validate_tokens).
+For this to work, authentication must be enabled for the Pub/Sub subscription,
+refer to the [Google Cloud documentation](https://cloud.google.com/pubsub/docs/push#configure_for_push_authentication)
+for more information.
+
+When the verification succeeds, the request payload is unmarshalled to the
+expected format. If this is successful, the controller will request a
+reconciliation for all listed [Resources](#resources).
+
+**Note:** This type of Receiver does not support filtering using
+[Events](#events).
+
+##### GCR example
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: gcr-receiver
+ namespace: default
+spec:
+ type: gcr
+ secretRef:
+ name: webhook-token
+ resources:
+ - apiVersion: image.toolkit.fluxcd.io/v1beta2
+ kind: ImageRepository
+ name: webapp
+ namespace: default
+```
+
+#### ACR
+
+When a Receiver's `.spec.type` is set to `acr`, the controller will respond to
+an [HTTP webhook event payload](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-webhook-reference),
+from Azure Container Registry to the generated [`.status.webhookPath`](#webhook-path).
+
+The controller performs minimal validation of the payload by attempting to
+unmarshal the JSON request body. If the unmarshalling is successful, the
+controller will request a reconciliation for all listed [Resources](#resources).
+
+**Note:** This type of Receiver does not support filtering using
+[Events](#events). However, Azure Container Registry does [support configuring
+webhooks to only send events for specific actions](https://learn.microsoft.com/en-us/azure/container-registry/container-registry-webhook#create-webhook---azure-portal).
+
+##### ACR example
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name: acr-receiver
+ namespace: default
+spec:
+ type: acr
+ secretRef:
+ name: webhook-token
+ resources:
+ - kind: ImageRepository
+ name: webapp
+```
+
+### Events
+
+`.spec.events` is an optional field to specify a list of webhook payload event
+types this Receiver should act on. If left empty, no filtering is applied and
+any (valid) payload is handled.
+
+**Note:** Support for this field, and the entries in it, is dependent on the
+Receiver type. See the [supported Receiver types](#supported-receiver-types)
+section for more information.
+
+### Resources
+
+`.spec.resources` is a required field to specify which Flux Custom Resources
+should be reconciled when the Receiver's [webhook path](#webhook-path) is
+called.
+
+A resource entry contains the following fields:
+
+- `apiVersion` (Optional): The Flux Custom Resource API group and version, such as
+ `source.toolkit.fluxcd.io/v1beta2`.
+- `kind`: The Flux Custom Resource kind, supported values are `Bucket`,
+ `GitRepository`, `Kustomization`, `HelmRelease`, `HelmChart`,
+ `HelmRepository`, `ImageRepository`, `ImagePolicy`, `ImageUpdateAutomation`
+ and `OCIRepository`.
+- `name`: The Flux Custom Resource `.metadata.name`.
+- `namespace` (Optional): The Flux Custom Resource `.metadata.namespace`.
+ When not specified, the Receiver's `.metadata.namespace` is used instead.
+
+**Note:** Cross-namespace references [can be disabled for security
+reasons](#disabling-cross-namespace-selectors).
+
+### Secret reference
+
+`.spec.secretRef.name` is a required field to specify a name reference to a
+Secret in the same namespace as the Receiver. The Secret must contain a `token`
+key, whose value is a string containing a (random) secret token.
+
+This token is used to salt the generated [webhook path](#webhook-path), and
+depending on the Receiver [type](#supported-receiver-types), to verify the
+authenticity of a request.
+
+#### Secret example
+
+```yaml
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: webhook-token
+ namespace: default
+type: Opaque
+stringData:
+ token:
+```
+
+### Interval
+
+`.spec.interval` is an optional field with a default of ten minutes that specifies
+the time interval at which the controller reconciles the provider with its Secret
+reference.
+
+### Suspend
+
+`.spec.suspend` is an optional field to suspend the Receiver.
+When set to `true`, the controller will stop processing events for this Receiver.
+When the field is set to `false` or removed, it will resume.
+
+## Working with Receivers
+
+### Disabling cross-namespace selectors
+
+On multi-tenant clusters, platform admins can disable cross-namespace
+references with the `--no-cross-namespace-refs=true` flag. When this flag is
+set, Receivers can only refer to [Resources](#resources) in the same namespace
+as the [Alert](alerts.md) object, preventing tenants from triggering
+reconciliations to another tenant's resources.
+
+### Public Ingress considerations
+
+Considerations should be made when exposing the controller's `webhook-receiver`
+Kubernetes Service to the public internet. Each request to a Receiver [webhook
+path](#webhook-path) will result in request to the Kubernetes API, as the
+controller needs to fetch information about the resource. This endpoint may be
+protected with a token, but this does not defend against a situation where a
+legitimate webhook caller starts sending large amounts of requests, or the
+token is somehow leaked. This may result in the controller, as it may get rate
+limited by the Kubernetes API, degrading its functionality.
+
+It is therefore a good idea to set rate limits on the Ingress which exposes
+the Kubernetes Service. If you are using ingress-nginx, this can be done by
+[adding annotations](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#rate-limiting).
+
+### Triggering a reconcile
+
+To manually tell the notification-controller to reconcile a Receiver outside
+of the [specified interval window](#interval), a Receiver can be annotated with
+`reconcile.fluxcd.io/requestedAt: `. Annotating the resource
+queues the Receiver for reconciliation if the `` differs from
+the last value the controller acted on, as reported in
+[`.status.lastHandledReconcileAt`](#last-handled-reconcile-at).
+
+Using `kubectl`:
+
+```sh
+kubectl annotate --field-manager=flux-client-side-apply --overwrite receiver/ reconcile.fluxcd.io/requestedAt="$(date +%s)"
+```
+
+Using `flux`:
+
+```sh
+flux reconcile source receiver
+```
+
+### Waiting for `Ready`
+
+When a change is applied, it is possible to wait for the Receiver to reach a
+[ready state](#ready-receiver) using `kubectl`:
+
+```sh
+kubectl wait receiver/ --for=condition=ready --timeout=1m
+```
+
+### Suspending and resuming
+
+When you find yourself in a situation where you temporarily want to pause the
+reconciliation of a Receiver and the handling of requests, you can suspend it
+using the [`.spec.suspend` field](#suspend).
+
+#### Suspend a Receiver
+
+In your YAML declaration:
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name:
+spec:
+ suspend: true
+```
+
+Using `kubectl`:
+
+```sh
+kubectl patch receiver --field-manager=flux-client-side-apply -p '{\"spec\": {\"suspend\" : true }}'
+```
+
+Using `flux`:
+
+```sh
+flux suspend receiver
+```
+
+#### Resume a Receiver
+
+In your YAML declaration, comment out (or remove) the field:
+
+```yaml
+---
+apiVersion: notification.toolkit.fluxcd.io/v1
+kind: Receiver
+metadata:
+ name:
+spec:
+ # suspend: true
+```
+
+**Note:** Setting the field value to `false` has the same effect as removing
+it, but does not allow for "hot patching" using e.g. `kubectl` while practicing
+GitOps; as the manually applied patch would be overwritten by the declared
+state in Git.
+
+Using `kubectl`:
+
+```sh
+kubectl patch receiver --field-manager=flux-client-side-apply -p '{\"spec\" : {\"suspend\" : false }}'
+```
+
+Using `flux`:
+
+```sh
+flux resume receiver
+```
+
+### Debugging a Receiver
+
+There are several ways to gather information about a Receiver for debugging
+purposes.
+
+#### Describe the Receiver
+
+Describing a Receiver using `kubectl describe receiver ` displays
+the latest recorded information for the resource in the Status and Events
+sections:
+
+```console
+...
+Status:
+...
+Status:
+ Conditions:
+ Last Transition Time: 2022-11-21T12:41:48Z
+ Message: Reconciliation in progress
+ Observed Generation: 1
+ Reason: ProgressingWithRetry
+ Status: True
+ Type: Reconciling
+ Last Transition Time: 2022-11-21T12:41:48Z
+ Message: unable to read token from secret 'default/webhook-token' error: Secret "webhook-token" not found
+ Observed Generation: 1
+ Reason: TokenNotFound
+ Status: False
+ Type: Ready
+ Observed Generation: -1
+Events:
+ Type Reason Age From Message
+ ---- ------ ---- ---- -------
+ Warning Failed 5s (x4 over 16s) notification-controller unable to read token from secret 'default/webhook-token' error: Secret "webhook-token" not found
+```
+
+#### Trace emitted Events
+
+To view events for specific Receiver(s), `kubectl events` can be used in
+combination with `--for` to list the Events for specific objects.
+For example, running
+
+```sh
+kubectl events --for=Receiver/
+```
+
+lists
+
+```console
+LAST SEEN TYPE REASON OBJECT MESSAGE
+3m44s Warning Failed receiver/ unable to read token from secret 'default/webhook-token' error: Secret "webhook-token" not found
+```
+
+## Receiver Status
+
+### Conditions
+
+A Receiver enters various states during its lifecycle, reflected as
+[Kubernetes Conditions][typical-status-properties].
+It can be [ready](#ready-receiver), or it can [fail during
+reconciliation](#failed-receiver).
+
+The Receiver API is compatible with the [kstatus specification][kstatus-spec],
+and reports the `Reconciling` condition where applicable.
+
+#### Ready Receiver
+
+The notification-controller marks a Receiver as _ready_ when it has the following
+characteristics:
+
+- The Receiver's Secret referenced in `.spec.secretRef.name` is found on the cluster.
+- The Receiver's Secret contains a `token` key.
+
+When the Receiver is "ready", the controller sets a Condition with the following
+attributes in the Alert's `.status.conditions`:
+
+- `type: Ready`
+- `status: "True"`
+- `reason: Succeeded`
+
+#### Failed Receiver
+
+The notification-controller may get stuck trying to reconcile a Receiver if its
+secret token can not be found.
+
+When this happens, the controller sets the `Ready` Condition status to `False`,
+and adds a Condition with the following attributes:
+
+- `type: Reconciling`
+- `status: "True"`
+- `reason: ProgressingWithRetry`
+
+### Observed Generation
+
+The notification-controller reports an
+[observed generation][typical-status-properties]
+in the Receiver's `.status.observedGeneration`. The observed generation is the
+latest `.metadata.generation` which resulted in a [ready state](#ready-receiver).
+
+### Last Handled Reconcile At
+
+The notification-controller reports the last `reconcile.fluxcd.io/requestedAt`
+annotation value it acted on in the `.status.lastHandledReconcileAt` field.
+
+### Webhook Path
+
+When a Receiver becomes [ready](#ready-receiver), the controller reports the
+generated incoming webhook path under `.status.webhookPath`. The path format is
+`/hook/sha256sum(token+name+namespace)`.
+
+[typical-status-properties]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties
+[kstatus-spec]: https://github.com/kubernetes-sigs/cli-utils/tree/master/pkg/kstatus
+[HMAC]: https://en.wikipedia.org/wiki/HMAC
diff --git a/docs/spec/v1beta2/README.md b/docs/spec/v1beta2/README.md
index f853575ef..e3ee66dba 100644
--- a/docs/spec/v1beta2/README.md
+++ b/docs/spec/v1beta2/README.md
@@ -11,4 +11,4 @@ This is the v1beta2 API specification for defining events handling and dispatchi
## Go Client
-* [github.com/fluxcd/pkg/recorder](https://github.com/fluxcd/pkg/tree/main/recorder)
+* [github.com/fluxcd/pkg/runtime/events](https://pkg.go.dev/github.com/fluxcd/pkg/runtime/events)
diff --git a/docs/spec/v1beta2/alerts.md b/docs/spec/v1beta2/alerts.md
index 83b9709dd..4bdd8d89a 100644
--- a/docs/spec/v1beta2/alerts.md
+++ b/docs/spec/v1beta2/alerts.md
@@ -1,5 +1,7 @@
# Alerts
+
+
The `Alert` API defines how events are filtered by severity and involved object, and what provider to use for dispatching.
## Example
diff --git a/docs/spec/v1beta2/events.md b/docs/spec/v1beta2/events.md
index 286fe3a98..b9d9bb3ad 100644
--- a/docs/spec/v1beta2/events.md
+++ b/docs/spec/v1beta2/events.md
@@ -1,5 +1,7 @@
# Events
+
+
The `Event` API defines the structure of the events issued by Flux controllers.
Flux controllers use the [fluxcd/pkg/runtime/events](https://github.com/fluxcd/pkg/tree/main/runtime/events)
diff --git a/docs/spec/v1beta2/providers.md b/docs/spec/v1beta2/providers.md
index 3dcb66d9e..0b0f052ab 100644
--- a/docs/spec/v1beta2/providers.md
+++ b/docs/spec/v1beta2/providers.md
@@ -1,5 +1,7 @@
# Providers
+
+
The `Provider` API defines how events are encoded and where to send them.
## Example
diff --git a/hack/api-docs/config.json b/hack/api-docs/config.json
index b0a2cb74d..7f9e03347 100644
--- a/hack/api-docs/config.json
+++ b/hack/api-docs/config.json
@@ -9,7 +9,7 @@
"externalPackages": [
{
"typeMatchPrefix": "^k8s\\.io/apimachinery/pkg/apis/meta/v1\\.Duration$",
- "docsURLTemplate": "https://godoc.org/k8s.io/apimachinery/pkg/apis/meta/v1#Duration"
+ "docsURLTemplate": "https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration"
},
{
"typeMatchPrefix": "^k8s\\.io/apiextensions-apiserver/pkg/apis/apiextensions/v1\\.JSON$",
@@ -21,15 +21,19 @@
},
{
"typeMatchPrefix": "^github.com/fluxcd/pkg/runtime/dependency\\.CrossNamespaceDependencyReference$",
- "docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/runtime/dependency#CrossNamespaceDependencyReference"
+ "docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/pkg/runtime/dependency#CrossNamespaceDependencyReference"
},
{
"typeMatchPrefix": "^github.com/fluxcd/pkg/apis/kustomize",
- "docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/apis/kustomize#{{ .TypeIdentifier }}"
+ "docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/pkg/apis/kustomize#{{ .TypeIdentifier }}"
},
{
"typeMatchPrefix": "^github.com/fluxcd/pkg/apis/meta",
- "docsURLTemplate": "https://godoc.org/github.com/fluxcd/pkg/apis/meta#{{ .TypeIdentifier }}"
+ "docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/pkg/apis/meta#{{ .TypeIdentifier }}"
+ },
+ {
+ "typeMatchPrefix": "^github.com/fluxcd/notification-controller/api/v1",
+ "docsURLTemplate": "https://pkg.go.dev/github.com/fluxcd/notification-controller/api/v1#{{ .TypeIdentifier }}"
}
],
"typeDisplayNamePrefixOverrides": {
diff --git a/hack/api-docs/template/pkg.tpl b/hack/api-docs/template/pkg.tpl
index 380a56dc1..cfcca1a92 100644
--- a/hack/api-docs/template/pkg.tpl
+++ b/hack/api-docs/template/pkg.tpl
@@ -1,5 +1,10 @@
{{ define "packages" }}
- Notification API reference
+ Notification API reference
+ {{- with (index .packages 0) -}}
+ {{ with (index .GoPackages 0 ) -}}
+ {{ printf " %s" .Name -}}
+ {{ end -}}
+ {{ end }}
{{ with .packages}}
Packages:
diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt
index 74dbebc30..e4b53a5f0 100644
--- a/hack/boilerplate.go.txt
+++ b/hack/boilerplate.go.txt
@@ -1,5 +1,5 @@
/*
-Copyright 2022 The Flux authors
+Copyright 2023 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.
diff --git a/internal/server/event_handlers.go b/internal/server/event_handlers.go
index 30a876db1..532cfc901 100644
--- a/internal/server/event_handlers.go
+++ b/internal/server/event_handlers.go
@@ -38,7 +38,8 @@ import (
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
"github.com/fluxcd/pkg/masktoken"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
+ apiv1beta2 "github.com/fluxcd/notification-controller/api/v1beta2"
"github.com/fluxcd/notification-controller/internal/notifier"
)
@@ -66,7 +67,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
defer cancel()
- var allAlerts apiv1.AlertList
+ var allAlerts apiv1beta2.AlertList
err = s.kubeClient.List(ctx, &allAlerts)
if err != nil {
s.logger.Error(err, "listing alerts failed")
@@ -75,7 +76,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
}
// find matching alerts
- alerts := make([]apiv1.Alert, 0)
+ alerts := make([]apiv1beta2.Alert, 0)
each_alert:
for _, alert := range allAlerts.Items {
// skip suspended and not ready alerts
@@ -134,13 +135,13 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
continue
}
- var provider apiv1.Provider
+ var provider apiv1beta2.Provider
providerName := types.NamespacedName{Namespace: alert.Namespace, Name: alert.Spec.ProviderRef.Name}
err = s.kubeClient.Get(ctx, providerName, &provider)
if err != nil {
s.logger.Error(err, "failed to read provider",
- "reconciler kind", apiv1.ProviderKind,
+ "reconciler kind", apiv1beta2.ProviderKind,
"name", providerName.Name,
"namespace", providerName.Namespace)
continue
@@ -163,7 +164,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
err = s.kubeClient.Get(ctx, secretName, &secret)
if err != nil {
s.logger.Error(err, "failed to read secret",
- "reconciler kind", apiv1.ProviderKind,
+ "reconciler kind", apiv1beta2.ProviderKind,
"name", providerName.Name,
"namespace", providerName.Namespace)
continue
@@ -193,7 +194,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
err := yaml.Unmarshal(h, &headers)
if err != nil {
s.logger.Error(err, "failed to read headers from secret",
- "reconciler kind", apiv1.ProviderKind,
+ "reconciler kind", apiv1beta2.ProviderKind,
"name", providerName.Name,
"namespace", providerName.Namespace)
continue
@@ -209,7 +210,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
err = s.kubeClient.Get(ctx, secretName, &secret)
if err != nil {
s.logger.Error(err, "failed to read secret",
- "reconciler kind", apiv1.ProviderKind,
+ "reconciler kind", apiv1beta2.ProviderKind,
"name", providerName.Name,
"namespace", providerName.Namespace)
continue
@@ -218,7 +219,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
caFile, ok := secret.Data["caFile"]
if !ok {
s.logger.Error(err, "failed to read secret key caFile",
- "reconciler kind", apiv1.ProviderKind,
+ "reconciler kind", apiv1beta2.ProviderKind,
"name", providerName.Name,
"namespace", providerName.Namespace)
continue
@@ -228,7 +229,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
ok = certPool.AppendCertsFromPEM(caFile)
if !ok {
s.logger.Error(err, "could not append to cert pool",
- "reconciler kind", apiv1.ProviderKind,
+ "reconciler kind", apiv1beta2.ProviderKind,
"name", providerName.Name,
"namespace", providerName.Namespace)
continue
@@ -237,7 +238,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
if webhook == "" {
s.logger.Error(nil, "provider has no address",
- "reconciler kind", apiv1.ProviderKind,
+ "reconciler kind", apiv1beta2.ProviderKind,
"name", providerName.Name,
"namespace", providerName.Namespace)
continue
@@ -247,7 +248,7 @@ func (s *EventServer) handleEvent() func(w http.ResponseWriter, r *http.Request)
sender, err := factory.Notifier(provider.Spec.Type)
if err != nil {
s.logger.Error(err, "failed to initialize provider",
- "reconciler kind", apiv1.ProviderKind,
+ "reconciler kind", apiv1beta2.ProviderKind,
"name", providerName.Name,
"namespace", providerName.Namespace)
continue
diff --git a/internal/server/receiver_handler_test.go b/internal/server/receiver_handler_test.go
index c134b04d6..de3d00381 100644
--- a/internal/server/receiver_handler_test.go
+++ b/internal/server/receiver_handler_test.go
@@ -38,7 +38,7 @@ import (
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/logger"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
)
func Test_handlePayload(t *testing.T) {
diff --git a/internal/server/receiver_handlers.go b/internal/server/receiver_handlers.go
index 7ebc2cc50..f6059cf91 100644
--- a/internal/server/receiver_handlers.go
+++ b/internal/server/receiver_handlers.go
@@ -40,7 +40,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
)
// defaultFluxAPIVersions is a map of Flux API kinds to their API versions.
diff --git a/internal/server/receiver_server.go b/internal/server/receiver_server.go
index 0ea4ccbd9..00f6a70f6 100644
--- a/internal/server/receiver_server.go
+++ b/internal/server/receiver_server.go
@@ -27,7 +27,7 @@ import (
"github.com/slok/go-http-metrics/middleware/std"
"sigs.k8s.io/controller-runtime/pkg/client"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
)
// ReceiverServer handles webhook POST requests
diff --git a/main.go b/main.go
index 4f68abfb3..017d91b82 100644
--- a/main.go
+++ b/main.go
@@ -42,7 +42,8 @@ import (
"github.com/fluxcd/pkg/runtime/pprof"
"github.com/fluxcd/pkg/runtime/probes"
- apiv1 "github.com/fluxcd/notification-controller/api/v1beta2"
+ apiv1 "github.com/fluxcd/notification-controller/api/v1"
+ apiv1b2 "github.com/fluxcd/notification-controller/api/v1beta2"
"github.com/fluxcd/notification-controller/controllers"
"github.com/fluxcd/notification-controller/internal/features"
"github.com/fluxcd/notification-controller/internal/server"
@@ -60,6 +61,7 @@ func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = apiv1.AddToScheme(scheme)
+ _ = apiv1b2.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
@@ -188,6 +190,10 @@ func main() {
store, err := memorystore.New(&memorystore.Config{
Interval: rateLimitInterval,
})
+ if err != nil {
+ setupLog.Error(err, "unable to create middleware store")
+ os.Exit(1)
+ }
setupLog.Info("starting event server", "addr", eventsAddr)
eventMdlw := middleware.New(middleware.Config{
| |