diff --git a/PROJECT b/PROJECT index 471052d..f3175d7 100644 --- a/PROJECT +++ b/PROJECT @@ -33,5 +33,10 @@ resources: group: uptime-robot kind: Account path: github.com/clevyr/uptime-robot-operator/api/v1 +- controller: true + domain: k8s.io + group: networking + kind: Ingress + path: k8s.io/api/networking/v1 version: v1 version: "3" diff --git a/api/v1/monitor_types.go b/api/v1/monitor_types.go index 7a3fdb4..741b45a 100644 --- a/api/v1/monitor_types.go +++ b/api/v1/monitor_types.go @@ -34,7 +34,7 @@ type MonitorContactRef struct { type MonitorSpec struct { // Interval defines the reconcile interval. //+kubebuilder:default:="24h" - Interval metav1.Duration `json:"interval,omitempty"` + Interval *metav1.Duration `json:"interval,omitempty"` // Prune enables garbage collection. //+kubebuilder:default:=true @@ -48,6 +48,9 @@ type MonitorSpec struct { // +kubebuilder:default:={{}} Contacts []MonitorContactRef `json:"contacts,omitempty"` + + // SourceRef optionally references the object that created this Monitor. + SourceRef *corev1.TypedLocalObjectReference `json:"sourceRef,omitempty"` } // MonitorStatus defines the observed state of Monitor diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index b543348..065da2e 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -21,6 +21,8 @@ limitations under the License. package v1 import ( + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -284,7 +286,11 @@ func (in *MonitorList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MonitorSpec) DeepCopyInto(out *MonitorSpec) { *out = *in - out.Interval = in.Interval + if in.Interval != nil { + in, out := &in.Interval, &out.Interval + *out = new(metav1.Duration) + **out = **in + } out.Account = in.Account in.Monitor.DeepCopyInto(&out.Monitor) if in.Contacts != nil { @@ -292,6 +298,11 @@ func (in *MonitorSpec) DeepCopyInto(out *MonitorSpec) { *out = make([]MonitorContactRef, len(*in)) copy(*out, *in) } + if in.SourceRef != nil { + in, out := &in.SourceRef, &out.SourceRef + *out = new(corev1.TypedLocalObjectReference) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MonitorSpec. diff --git a/cmd/main.go b/cmd/main.go index b5bf82e..1779d52 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -146,6 +146,12 @@ func main() { Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Account") + } + if err = (&controller.IngressReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Ingress") os.Exit(1) } //+kubebuilder:scaffold:builder diff --git a/config/crd/bases/uptime-robot.clevyr.com_monitors.yaml b/config/crd/bases/uptime-robot.clevyr.com_monitors.yaml index 72031a2..4f249e2 100644 --- a/config/crd/bases/uptime-robot.clevyr.com_monitors.yaml +++ b/config/crd/bases/uptime-robot.clevyr.com_monitors.yaml @@ -215,6 +215,27 @@ spec: default: true description: Prune enables garbage collection. type: boolean + sourceRef: + description: SourceRef optionally references the object that created + this Monitor. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic required: - monitor type: object diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index d0e36db..086874d 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -12,6 +12,32 @@ rules: - get - list - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses/finalizers + verbs: + - update +- apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - get + - patch + - update - apiGroups: - uptime-robot.clevyr.com resources: diff --git a/go.mod b/go.mod index f81ee96..32cef3f 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/clevyr/uptime-robot-operator go 1.22.2 require ( + github.com/knadh/koanf/maps v0.1.1 + github.com/mitchellh/mapstructure v1.5.0 github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/gomega v1.32.0 k8s.io/api v0.29.3 @@ -37,6 +39,8 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect diff --git a/go.sum b/go.sum index 0d37a3f..3e0c386 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -69,6 +71,12 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/internal/controller/ingress_controller.go b/internal/controller/ingress_controller.go new file mode 100644 index 0000000..5635d6d --- /dev/null +++ b/internal/controller/ingress_controller.go @@ -0,0 +1,230 @@ +/* +Copyright 2024. + +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 controller + +import ( + "context" + "net/url" + "strings" + + uptimerobotv1 "github.com/clevyr/uptime-robot-operator/api/v1" + "github.com/clevyr/uptime-robot-operator/internal/util" + "github.com/knadh/koanf/maps" + "github.com/mitchellh/mapstructure" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +const AnnotationPrefix = "uptime-robot.clevyr.com/" + +// IngressReconciler reconciles a Ingress object +type IngressReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.2/pkg/reconcile +func (r *IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + ingress := &networkingv1.Ingress{} + if err := r.Get(ctx, req.NamespacedName, ingress); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + list, err := r.findMonitors(ctx, ingress) + if err != nil { + return ctrl.Result{}, err + } + + const myFinalizerName = "uptime-robot.clevyr.com/finalizer" + if !ingress.DeletionTimestamp.IsZero() { + // Object is being deleted + if controllerutil.ContainsFinalizer(ingress, myFinalizerName) { + for _, monitor := range list.Items { + if err := r.Delete(ctx, &monitor); err != nil { + return ctrl.Result{}, err + } + } + + controllerutil.RemoveFinalizer(ingress, myFinalizerName) + if err := r.Update(ctx, ingress); err != nil { + return ctrl.Result{}, err + } + } + + return ctrl.Result{}, nil + } + + annotationCount := r.countMatchingAnnotations(ingress) + var create bool + if annotationCount == 0 { + if controllerutil.ContainsFinalizer(ingress, myFinalizerName) { + // Delete existing Monitor + for _, monitor := range list.Items { + if err := r.Delete(ctx, &monitor); err != nil { + return ctrl.Result{}, err + } + } + + controllerutil.RemoveFinalizer(ingress, myFinalizerName) + if err := r.Update(ctx, ingress); err != nil { + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil + } else if len(list.Items) == 0 { + // Create new Monitor + create = true + list.Items = append(list.Items, uptimerobotv1.Monitor{ + ObjectMeta: metav1.ObjectMeta{ + Name: ingress.Name, + Namespace: req.Namespace, + }, + Spec: uptimerobotv1.MonitorSpec{ + SourceRef: &corev1.TypedLocalObjectReference{ + Kind: ingress.Kind, + Name: ingress.Name, + }, + }, + }) + } + + annotations := r.getMatchingAnnotations(ingress) + for _, monitor := range list.Items { + if err := r.updateValues(ingress, &monitor, annotations); err != nil { + return ctrl.Result{}, err + } + + if create { + if err := r.Create(ctx, &monitor); err != nil { + return ctrl.Result{}, err + } + } else { + if err := r.Update(ctx, &monitor); err != nil { + return ctrl.Result{}, err + } + } + } + + if !controllerutil.ContainsFinalizer(ingress, myFinalizerName) { + controllerutil.AddFinalizer(ingress, myFinalizerName) + if err := r.Update(ctx, ingress); err != nil { + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&networkingv1.Ingress{}). + Complete(r) +} + +func (r *IngressReconciler) findMonitors(ctx context.Context, ingress *networkingv1.Ingress) (*uptimerobotv1.MonitorList, error) { + list := &uptimerobotv1.MonitorList{} + err := r.Client.List(ctx, list, &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector("spec.sourceRef", ingress.Kind+"/"+ingress.Name), + }) + if err != nil { + return list, err + } + return list, nil +} + +func (r *IngressReconciler) countMatchingAnnotations(ingress *networkingv1.Ingress) uint { + var count uint + for k := range ingress.Annotations { + if strings.HasPrefix(k, AnnotationPrefix) { + count++ + } + } + return count +} + +func (r *IngressReconciler) getMatchingAnnotations(ingress *networkingv1.Ingress) map[string]string { + annotations := make(map[string]string, r.countMatchingAnnotations(ingress)) + for k, v := range ingress.Annotations { + if strings.HasPrefix(k, AnnotationPrefix) { + annotations[strings.TrimPrefix(k, AnnotationPrefix)] = v + } + } + return annotations +} + +func (r *IngressReconciler) updateValues(ingress *networkingv1.Ingress, monitor *uptimerobotv1.Monitor, annotations map[string]string) error { + monitor.Spec.Monitor.FriendlyName = ingress.Name + if _, ok := annotations["monitor.url"]; !ok { + if len(ingress.Spec.Rules) != 0 { + var u url.URL + if u.Scheme, ok = annotations["monitor.scheme"]; !ok { + if len(ingress.Spec.TLS) == 0 { + u.Scheme = "http" + } else { + u.Scheme = "https" + } + } + rule := ingress.Spec.Rules[0] + if u.Host, ok = annotations["monitor.host"]; !ok { + u.Host = rule.Host + } + if u.Path, ok = annotations["monitor.path"]; !ok && len(rule.HTTP.Paths) != 0 { + if path := rule.HTTP.Paths[0].Path; path != "/" { + u.Path = path + } + } + monitor.Spec.Monitor.URL = u.String() + } + } + + dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + DecodeHook: mapstructure.ComposeDecodeHookFunc( + util.DecodeHookMetav1Duration, + mapstructure.TextUnmarshallerHookFunc(), + ), + TagName: "json", + WeaklyTypedInput: true, + Result: &monitor.Spec, + }) + if err != nil { + return err + } + + expanded := make(map[string]any, len(annotations)) + for k, v := range annotations { + expanded[k] = v + } + expanded = maps.Unflatten(expanded, ".") + return dec.Decode(expanded) +} diff --git a/internal/controller/ingress_controller_test.go b/internal/controller/ingress_controller_test.go new file mode 100644 index 0000000..7cd5dab --- /dev/null +++ b/internal/controller/ingress_controller_test.go @@ -0,0 +1,30 @@ +/* +Copyright 2024. + +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 controller + +import ( + . "github.com/onsi/ginkgo/v2" +) + +var _ = Describe("Ingress Controller", func() { + Context("When reconciling a resource", func() { + It("should successfully reconcile the resource", func() { + // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. + // Example: If you expect a certain status condition after reconciliation, verify it here. + }) + }) +}) diff --git a/internal/controller/monitor_controller.go b/internal/controller/monitor_controller.go index 41d5f56..ed7e62d 100644 --- a/internal/controller/monitor_controller.go +++ b/internal/controller/monitor_controller.go @@ -188,6 +188,16 @@ func (r *MonitorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct // SetupWithManager sets up the controller with the Manager. func (r *MonitorReconciler) SetupWithManager(mgr ctrl.Manager) error { + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &uptimerobotv1.Monitor{}, "spec.sourceRef", func(rawObj client.Object) []string { + monitor := rawObj.(*uptimerobotv1.Monitor) + if monitor.Spec.SourceRef == nil { + return nil + } + return []string{monitor.Spec.SourceRef.Kind + "/" + monitor.Spec.SourceRef.Name} + }); err != nil { + return err + } + return ctrl.NewControllerManagedBy(mgr). For(&uptimerobotv1.Monitor{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Complete(r) diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index a8d76d6..9b5cf0f 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -37,6 +37,8 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + networkingv1 "k8s.io/api/networking/v1" + uptimerobotv1 "github.com/clevyr/uptime-robot-operator/api/v1" //+kubebuilder:scaffold:imports ) @@ -83,6 +85,9 @@ var _ = BeforeSuite(func() { err = uptimerobotv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) + err = networkingv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/internal/uptimerobot/client.go b/internal/uptimerobot/client.go index c26eba3..be1f4bb 100644 --- a/internal/uptimerobot/client.go +++ b/internal/uptimerobot/client.go @@ -89,7 +89,7 @@ func (c Client) MonitorValues(monitor Monitor, form url.Values, contacts Monitor if monitor.Keyword != nil { form.Set("keyword_type", strconv.Itoa(int(monitor.Keyword.Type))) caseType := "1" - if monitor.Keyword.CaseSensitive { + if *monitor.Keyword.CaseSensitive { caseType = "0" } form.Set("keyword_case_type", caseType) diff --git a/internal/uptimerobot/monitor.go b/internal/uptimerobot/monitor.go index 4520925..635a08f 100644 --- a/internal/uptimerobot/monitor.go +++ b/internal/uptimerobot/monitor.go @@ -22,7 +22,7 @@ type Monitor struct { // Interval is the monitoring interval. //+kubebuilder:default:="60s" - Interval metav1.Duration `json:"interval,omitempty"` + Interval *metav1.Duration `json:"interval,omitempty"` // Status toggles pause status for the monitor. 0 is paused, 1 is running. //+kubebuilder:default:=1 @@ -30,7 +30,7 @@ type Monitor struct { // Timeout is the monitor timeout. //+kubebuilder:default:="30s" - Timeout metav1.Duration `json:"timeout,omitempty"` + Timeout *metav1.Duration `json:"timeout,omitempty"` // HTTPMethod defines the HTTP verb to use. //+kubebuilder:default:="HEAD" @@ -46,11 +46,13 @@ type Monitor struct { Auth *MonitorAuth `json:"auth,omitempty"` } +//+kubebuilder:object:generate=true + type MonitorKeyword struct { Type urtypes.KeywordType `json:"type"` //+kubebuilder:default:=false - CaseSensitive bool `json:"caseSensitive,omitempty"` + CaseSensitive *bool `json:"caseSensitive,omitempty"` Value string `json:"value"` } diff --git a/internal/uptimerobot/urtypes/httpmethod.go b/internal/uptimerobot/urtypes/httpmethod.go index c75ec40..f1a0d60 100644 --- a/internal/uptimerobot/urtypes/httpmethod.go +++ b/internal/uptimerobot/urtypes/httpmethod.go @@ -1,6 +1,6 @@ package urtypes -//go:generate enumer -type HTTPMethod -json -trimprefix HTTP +//go:generate enumer -type HTTPMethod -trimprefix HTTP -json -text //+kubebuilder:validation:Type:=string //+kubebuilder:validation:Enum:=HEAD;GET;POST;PUT;PATCH;DELETE;OPTIONS diff --git a/internal/uptimerobot/urtypes/httpmethod_enumer.go b/internal/uptimerobot/urtypes/httpmethod_enumer.go index fb2c4d6..704f74b 100644 --- a/internal/uptimerobot/urtypes/httpmethod_enumer.go +++ b/internal/uptimerobot/urtypes/httpmethod_enumer.go @@ -1,4 +1,4 @@ -// Code generated by "enumer -type HTTPMethod -json -trimprefix HTTP"; DO NOT EDIT. +// Code generated by "enumer -type HTTPMethod -trimprefix HTTP -json -text"; DO NOT EDIT. package urtypes @@ -115,3 +115,15 @@ func (i *HTTPMethod) UnmarshalJSON(data []byte) error { *i, err = HTTPMethodString(s) return err } + +// MarshalText implements the encoding.TextMarshaler interface for HTTPMethod +func (i HTTPMethod) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for HTTPMethod +func (i *HTTPMethod) UnmarshalText(text []byte) error { + var err error + *i, err = HTTPMethodString(string(text)) + return err +} diff --git a/internal/uptimerobot/urtypes/keywordtype.go b/internal/uptimerobot/urtypes/keywordtype.go index c1a9c45..a1bdae8 100644 --- a/internal/uptimerobot/urtypes/keywordtype.go +++ b/internal/uptimerobot/urtypes/keywordtype.go @@ -1,6 +1,6 @@ package urtypes -//go:generate enumer -type KeywordType -json -trimprefix Keyword +//go:generate enumer -type KeywordType -trimprefix Keyword -json -text //+kubebuilder:validation:Type:=string //+kubebuilder:validation:Enum:=Exists;NotExists diff --git a/internal/uptimerobot/urtypes/keywordtype_enumer.go b/internal/uptimerobot/urtypes/keywordtype_enumer.go index 59d4542..3319bc6 100644 --- a/internal/uptimerobot/urtypes/keywordtype_enumer.go +++ b/internal/uptimerobot/urtypes/keywordtype_enumer.go @@ -1,4 +1,4 @@ -// Code generated by "enumer -type KeywordType -json -trimprefix Keyword"; DO NOT EDIT. +// Code generated by "enumer -type KeywordType -trimprefix Keyword -json -text"; DO NOT EDIT. package urtypes @@ -95,3 +95,15 @@ func (i *KeywordType) UnmarshalJSON(data []byte) error { *i, err = KeywordTypeString(s) return err } + +// MarshalText implements the encoding.TextMarshaler interface for KeywordType +func (i KeywordType) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for KeywordType +func (i *KeywordType) UnmarshalText(text []byte) error { + var err error + *i, err = KeywordTypeString(string(text)) + return err +} diff --git a/internal/uptimerobot/urtypes/monitorauthtype.go b/internal/uptimerobot/urtypes/monitorauthtype.go index 9a7d34d..043cb97 100644 --- a/internal/uptimerobot/urtypes/monitorauthtype.go +++ b/internal/uptimerobot/urtypes/monitorauthtype.go @@ -1,6 +1,6 @@ package urtypes -//go:generate enumer -type MonitorAuthType -json -trimprefix Auth +//go:generate enumer -type MonitorAuthType -trimprefix Auth -json -text //+kubebuilder:validation:Type:=string //+kubebuilder:validation:Enum:=Basic;Digest diff --git a/internal/uptimerobot/urtypes/monitorauthtype_enumer.go b/internal/uptimerobot/urtypes/monitorauthtype_enumer.go index 9a70d1e..efeed78 100644 --- a/internal/uptimerobot/urtypes/monitorauthtype_enumer.go +++ b/internal/uptimerobot/urtypes/monitorauthtype_enumer.go @@ -1,4 +1,4 @@ -// Code generated by "enumer -type MonitorAuthType -json -trimprefix Auth"; DO NOT EDIT. +// Code generated by "enumer -type MonitorAuthType -trimprefix Auth -json -text"; DO NOT EDIT. package urtypes @@ -95,3 +95,15 @@ func (i *MonitorAuthType) UnmarshalJSON(data []byte) error { *i, err = MonitorAuthTypeString(s) return err } + +// MarshalText implements the encoding.TextMarshaler interface for MonitorAuthType +func (i MonitorAuthType) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for MonitorAuthType +func (i *MonitorAuthType) UnmarshalText(text []byte) error { + var err error + *i, err = MonitorAuthTypeString(string(text)) + return err +} diff --git a/internal/uptimerobot/urtypes/monitortype.go b/internal/uptimerobot/urtypes/monitortype.go index 0e2ea32..f0da4c1 100644 --- a/internal/uptimerobot/urtypes/monitortype.go +++ b/internal/uptimerobot/urtypes/monitortype.go @@ -1,6 +1,6 @@ package urtypes -//go:generate enumer -type MonitorType -json -trimprefix Type +//go:generate enumer -type MonitorType -trimprefix Type -json -text //+kubebuilder:validation:Type:=string //+kubebuilder:validation:Enum:=HTTPS;Keyword;Ping;Port;Heartbeat diff --git a/internal/uptimerobot/urtypes/monitortype_enumer.go b/internal/uptimerobot/urtypes/monitortype_enumer.go index 917c35d..198a63b 100644 --- a/internal/uptimerobot/urtypes/monitortype_enumer.go +++ b/internal/uptimerobot/urtypes/monitortype_enumer.go @@ -1,4 +1,4 @@ -// Code generated by "enumer -type MonitorType -json -trimprefix Type"; DO NOT EDIT. +// Code generated by "enumer -type MonitorType -trimprefix Type -json -text"; DO NOT EDIT. package urtypes @@ -107,3 +107,15 @@ func (i *MonitorType) UnmarshalJSON(data []byte) error { *i, err = MonitorTypeString(s) return err } + +// MarshalText implements the encoding.TextMarshaler interface for MonitorType +func (i MonitorType) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for MonitorType +func (i *MonitorType) UnmarshalText(text []byte) error { + var err error + *i, err = MonitorTypeString(string(text)) + return err +} diff --git a/internal/uptimerobot/urtypes/porttype.go b/internal/uptimerobot/urtypes/porttype.go index 70981f1..d13e5c3 100644 --- a/internal/uptimerobot/urtypes/porttype.go +++ b/internal/uptimerobot/urtypes/porttype.go @@ -1,6 +1,6 @@ package urtypes -//go:generate enumer -type PortType -json -trimprefix Port +//go:generate enumer -type PortType -trimprefix Port -json -text //+kubebuilder:validation:Type:=string //+kubebuilder:validation:Enum:=HTTP;FTP;SMTP;POP3;IMAP;Custom diff --git a/internal/uptimerobot/urtypes/porttype_enumer.go b/internal/uptimerobot/urtypes/porttype_enumer.go index 8257c69..4c904b3 100644 --- a/internal/uptimerobot/urtypes/porttype_enumer.go +++ b/internal/uptimerobot/urtypes/porttype_enumer.go @@ -1,4 +1,4 @@ -// Code generated by "enumer -type PortType -json -trimprefix Port"; DO NOT EDIT. +// Code generated by "enumer -type PortType -trimprefix Port -json -text"; DO NOT EDIT. package urtypes @@ -111,3 +111,15 @@ func (i *PortType) UnmarshalJSON(data []byte) error { *i, err = PortTypeString(s) return err } + +// MarshalText implements the encoding.TextMarshaler interface for PortType +func (i PortType) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for PortType +func (i *PortType) UnmarshalText(text []byte) error { + var err error + *i, err = PortTypeString(string(text)) + return err +} diff --git a/internal/uptimerobot/zz_generated.deepcopy.go b/internal/uptimerobot/zz_generated.deepcopy.go index 03677eb..1729227 100644 --- a/internal/uptimerobot/zz_generated.deepcopy.go +++ b/internal/uptimerobot/zz_generated.deepcopy.go @@ -20,17 +20,27 @@ limitations under the License. package uptimerobot -import () +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" +) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Monitor) DeepCopyInto(out *Monitor) { *out = *in - out.Interval = in.Interval - out.Timeout = in.Timeout + if in.Interval != nil { + in, out := &in.Interval, &out.Interval + *out = new(v1.Duration) + **out = **in + } + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } if in.Keyword != nil { in, out := &in.Keyword, &out.Keyword *out = new(MonitorKeyword) - **out = **in + (*in).DeepCopyInto(*out) } if in.Port != nil { in, out := &in.Port, &out.Port @@ -53,3 +63,23 @@ func (in *Monitor) DeepCopy() *Monitor { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MonitorKeyword) DeepCopyInto(out *MonitorKeyword) { + *out = *in + if in.CaseSensitive != nil { + in, out := &in.CaseSensitive, &out.CaseSensitive + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MonitorKeyword. +func (in *MonitorKeyword) DeepCopy() *MonitorKeyword { + if in == nil { + return nil + } + out := new(MonitorKeyword) + in.DeepCopyInto(out) + return out +} diff --git a/internal/util/decodehooks.go b/internal/util/decodehooks.go new file mode 100644 index 0000000..d059b95 --- /dev/null +++ b/internal/util/decodehooks.go @@ -0,0 +1,26 @@ +package util + +import ( + "reflect" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func DecodeHookMetav1Duration(from, to reflect.Type, data any) (any, error) { + if from.Kind() != reflect.String { + return data, nil + } + + var result metav1.Duration + if to != reflect.TypeOf(result) { + return data, nil + } + + if from.Kind() == reflect.String { + d, err := time.ParseDuration(data.(string)) + result.Duration = d + return result, err + } + return data, nil +}