diff --git a/Makefile b/Makefile index 2fbc186f1..3bcb040c4 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ IMG ?= fluxcd/notification-controller:latest # Produce CRDs that work back to Kubernetes 1.16 CRD_OPTIONS ?= crd:crdVersions=v1 -SOURCE_VER ?= v0.35.0 +SOURCE_VER ?= v1.0.0-rc.1 # Repository root based on Git metadata REPOSITORY_ROOT := $(shell git rev-parse --show-toplevel) @@ -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/go.mod b/api/go.mod index 45663772e..1a2741fda 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,8 +3,8 @@ module github.com/fluxcd/notification-controller/api go 1.18 require ( - github.com/fluxcd/pkg/apis/meta v0.19.1 - k8s.io/apimachinery v0.26.2 + github.com/fluxcd/pkg/apis/meta v1.0.0 + k8s.io/apimachinery v0.26.3 sigs.k8s.io/controller-runtime v0.14.5 ) diff --git a/api/go.sum b/api/go.sum index 3de03b3ea..48d75b40f 100644 --- a/api/go.sum +++ b/api/go.sum @@ -1,8 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fluxcd/pkg/apis/meta v0.19.1 h1:fCI5CnTXpAqr67UlaI9q0H+OztMKB5kDTr6xV6vlAo0= -github.com/fluxcd/pkg/apis/meta v0.19.1/go.mod h1:ZPPMYrPnWwPQYNEGM/Uc0N4SurUPS3xNI3IIpCQEfuM= +github.com/fluxcd/pkg/apis/meta v1.0.0 h1:i9IGHd/VNEZELX7mepkiYFbJxs2J5znaB4cN9z2nPm8= +github.com/fluxcd/pkg/apis/meta v1.0.0/go.mod h1:04ZdpZYm1x+aL93K4daNHW1UX6E8K7Gyf5za9OhrE+U= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -72,8 +72,8 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= -k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ= -k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/apimachinery v0.26.3 h1:dQx6PNETJ7nODU3XPtrwkfuubs6w7sX0M8n61zHIV/k= +k8s.io/apimachinery v0.26.3/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= 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..4b54347b1 --- /dev/null +++ b/api/v1/reference_types.go @@ -0,0 +1,51 @@ +/* +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 + // If multiple resources are targeted `*` may be set. + // +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. + // MatchLabels requires the name to be set to `*`. + // +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..569313f4d 100644 --- a/config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml +++ b/config/crd/bases/notification.toolkit.fluxcd.io_receivers.yaml @@ -25,6 +25,232 @@ 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. MatchLabels requires the name to be set to `*`. + type: object + name: + 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 + 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 +453,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 +493,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 +516,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 +664,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 cfe813c4c..89e369758 100644 --- a/controllers/alert_controller_test.go +++ b/controllers/alert_controller_test.go @@ -43,36 +43,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, }, @@ -164,7 +165,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") @@ -196,19 +197,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()) @@ -234,12 +235,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, }, @@ -278,7 +279,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..0b57bc8f3 --- /dev/null +++ b/docs/api/v1/notification.md @@ -0,0 +1,444 @@ +
Packages:
+ +Package v1 contains API Schema definitions for the notification v1 API group.
+Resource Types: +Receiver is the Schema for the receivers API.
+| Field | +Description | +||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
+apiVersion+string |
+
+notification.toolkit.fluxcd.io/v1
+ |
+||||||||||||
+kind+string + |
+
+Receiver
+ |
+||||||||||||
+metadata+ + +Kubernetes meta/v1.ObjectMeta + + + |
+
+Refer to the Kubernetes API documentation for the fields of the
+metadata field.
+ |
+||||||||||||
+spec+ + +ReceiverSpec + + + |
+
+ + +
|
+||||||||||||
+status+ + +ReceiverStatus + + + |
++ | +
+(Appears on: +ReceiverSpec) +
+CrossNamespaceObjectReference contains enough information to let you locate the +typed referenced object at cluster level
+| Field | +Description | +
|---|---|
+apiVersion+ +string + + |
+
+(Optional)
+ API version of the referent + |
+
+kind+ +string + + |
+
+ Kind of the referent + |
+
+name+ +string + + |
+
+ Name of the referent
+If multiple resources are targeted |
+
+namespace+ +string + + |
+
+(Optional)
+ Namespace of the referent + |
+
+matchLabels+ +map[string]string + + |
+
+(Optional)
+ 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.
+MatchLabels requires the name to be set to |
+
+(Appears on: +Receiver) +
+ReceiverSpec defines the desired state of the Receiver.
+| Field | +Description | +
|---|---|
+type+ +string + + |
+
+ Type of webhook sender, used to determine +the validation procedure and payload deserialization. + |
+
+interval+ + +Kubernetes meta/v1.Duration + + + |
+
+(Optional)
+ Interval at which to reconcile the Receiver with its Secret references. + |
+
+events+ +[]string + + |
+
+(Optional)
+ Events specifies the list of event types to handle, +e.g. ‘push’ for GitHub or ‘Push Hook’ for GitLab. + |
+
+resources+ + +[]CrossNamespaceObjectReference + + + |
+
+ A list of resources to be notified about changes. + |
+
+secretRef+ + +github.com/fluxcd/pkg/apis/meta.LocalObjectReference + + + |
+
+ SecretRef specifies the Secret containing the token used +to validate the payload authenticity. + |
+
+suspend+ +bool + + |
+
+(Optional)
+ Suspend tells the controller to suspend subsequent +events handling for this receiver. + |
+
+(Appears on: +Receiver) +
+ReceiverStatus defines the observed state of the Receiver.
+| Field | +Description | +
|---|---|
+ReconcileRequestStatus+ + +github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus + + + |
+
+
+(Members of |
+
+conditions+ + +[]Kubernetes meta/v1.Condition + + + |
+
+(Optional)
+ Conditions holds the conditions for the Receiver. + |
+
+url+ +string + + |
+
+(Optional)
+ URL is the generated incoming webhook address in the format +of ‘/hook/sha256sum(token+name+namespace)’. +Deprecated: Replaced by WebhookPath. + |
+
+webhookPath+ +string + + |
+
+(Optional)
+ WebhookPath is the generated incoming webhook address in the format +of ‘/hook/sha256sum(token+name+namespace)’. + |
+
+observedGeneration+ +int64 + + |
+
+(Optional)
+ ObservedGeneration is the last observed generation of the Receiver object. + |
+
This page was automatically generated with gen-crd-api-reference-docs
Packages:
providerRefeventSourcesintervaltimeoutsecretRefcertSecretRefintervalresourcessecretRefproviderRefeventSourcesReconcileRequestStatus-(Appears on: -AlertSpec, -ReceiverSpec) -
CrossNamespaceObjectReference contains enough information to let you locate the typed referenced object at cluster level
intervaltimeoutsecretRefcertSecretRefReconcileRequestStatusintervalresourcessecretRefReconcileRequestStatusPackages:
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 3cca28d9f..51ae967e0 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..7632fbde9 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. @@ -48,7 +48,7 @@ var defaultFluxAPIVersions = map[string]string{ "Bucket": "source.toolkit.fluxcd.io/v1beta2", "HelmChart": "source.toolkit.fluxcd.io/v1beta2", "HelmRepository": "source.toolkit.fluxcd.io/v1beta2", - "GitRepository": "source.toolkit.fluxcd.io/v1beta2", + "GitRepository": "source.toolkit.fluxcd.io/v1", "OCIRepository": "source.toolkit.fluxcd.io/v1beta2", "ImageRepository": "image.toolkit.fluxcd.io/v1beta2", } 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{