diff --git a/Dockerfile b/Dockerfile index e254e5c..71b47f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,7 @@ COPY cmd/main.go cmd/main.go COPY internal/gcp internal/gcp COPY internal/controller/ internal/controller/ COPY internal/util/ internal/util/ +COPY api api/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command diff --git a/PROJECT b/PROJECT index a254ba5..5591e91 100644 --- a/PROJECT +++ b/PROJECT @@ -5,6 +5,7 @@ domain: gdp.deliveryhero.io layout: - go.kubebuilder.io/v4 +multigroup: true projectName: gcp-config-connector-tagging-operator repo: github.com/deliveryhero/gcp-config-connector-tagging-operator resources: @@ -18,4 +19,13 @@ resources: group: kms kind: KMSKeyRing version: v1beta1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: gdp.deliveryhero.io + group: batch + kind: CronJob + path: github.com/deliveryhero/gcp-config-connector-tagging-operator/api/batch/v1 + version: v1 version: "3" diff --git a/api/batch/v1/cronjob_types.go b/api/batch/v1/cronjob_types.go new file mode 100644 index 0000000..f1a3514 --- /dev/null +++ b/api/batch/v1/cronjob_types.go @@ -0,0 +1,64 @@ +/* +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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// CronJobSpec defines the desired state of CronJob +type CronJobSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Foo is an example field of CronJob. Edit cronjob_types.go to remove/update + Foo string `json:"foo,omitempty"` +} + +// CronJobStatus defines the observed state of CronJob +type CronJobStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// CronJob is the Schema for the cronjobs API +type CronJob struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec CronJobSpec `json:"spec,omitempty"` + Status CronJobStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// CronJobList contains a list of CronJob +type CronJobList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CronJob `json:"items"` +} + +func init() { + SchemeBuilder.Register(&CronJob{}, &CronJobList{}) +} diff --git a/api/batch/v1/groupversion_info.go b/api/batch/v1/groupversion_info.go new file mode 100644 index 0000000..e74856f --- /dev/null +++ b/api/batch/v1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +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 v1 contains API Schema definitions for the batch v1 API group +// +kubebuilder:object:generate=true +// +groupName=batch.gdp.deliveryhero.io +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: "batch.gdp.deliveryhero.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/batch/v1/zz_generated.deepcopy.go b/api/batch/v1/zz_generated.deepcopy.go new file mode 100644 index 0000000..b1a156d --- /dev/null +++ b/api/batch/v1/zz_generated.deepcopy.go @@ -0,0 +1,114 @@ +//go:build !ignore_autogenerated + +/* +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + 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 *CronJob) DeepCopyInto(out *CronJob) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJob. +func (in *CronJob) DeepCopy() *CronJob { + if in == nil { + return nil + } + out := new(CronJob) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CronJob) 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 *CronJobList) DeepCopyInto(out *CronJobList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CronJob, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobList. +func (in *CronJobList) DeepCopy() *CronJobList { + if in == nil { + return nil + } + out := new(CronJobList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CronJobList) 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 *CronJobSpec) DeepCopyInto(out *CronJobSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobSpec. +func (in *CronJobSpec) DeepCopy() *CronJobSpec { + if in == nil { + return nil + } + out := new(CronJobSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CronJobStatus) DeepCopyInto(out *CronJobStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobStatus. +func (in *CronJobStatus) DeepCopy() *CronJobStatus { + if in == nil { + return nil + } + out := new(CronJobStatus) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/main.go b/cmd/main.go index 9d0d5ef..bc8db91 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -42,7 +42,9 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + batchv1 "github.com/deliveryhero/gcp-config-connector-tagging-operator/api/batch/v1" "github.com/deliveryhero/gcp-config-connector-tagging-operator/internal/controller" + batchcontroller "github.com/deliveryhero/gcp-config-connector-tagging-operator/internal/controller/batch" "github.com/deliveryhero/gcp-config-connector-tagging-operator/internal/controller/resources" "github.com/deliveryhero/gcp-config-connector-tagging-operator/internal/gcp" "github.com/deliveryhero/gcp-config-connector-tagging-operator/internal/util" @@ -62,6 +64,7 @@ func init() { utilruntime.Must(redisv1beta1.AddToScheme(scheme)) utilruntime.Must(kmsv1beta1.AddToScheme(scheme)) + utilruntime.Must(batchv1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -198,6 +201,13 @@ func main() { controller.CreateTaggableResourceController(mgr, tagsManager, &resources.SQLInstanceMetadataProvider{}, labelMatcher) controller.CreateTaggableResourceController(mgr, tagsManager, &resources.RedisInstanceMetadataProvider{}, labelMatcher) controller.CreateTaggableResourceController(mgr, tagsManager, &resources.KMSKeyRingMetadataProvider{}, labelMatcher) + if err = (&batchcontroller.CronJobReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "CronJob") + os.Exit(1) + } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/crd/bases/batch.gdp.deliveryhero.io_cronjobs.yaml b/config/crd/bases/batch.gdp.deliveryhero.io_cronjobs.yaml new file mode 100644 index 0000000..e34a254 --- /dev/null +++ b/config/crd/bases/batch.gdp.deliveryhero.io_cronjobs.yaml @@ -0,0 +1,54 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: cronjobs.batch.gdp.deliveryhero.io +spec: + group: batch.gdp.deliveryhero.io + names: + kind: CronJob + listKind: CronJobList + plural: cronjobs + singular: cronjob + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: CronJob is the Schema for the cronjobs 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: CronJobSpec defines the desired state of CronJob + properties: + foo: + description: Foo is an example field of CronJob. Edit cronjob_types.go + to remove/update + type: string + type: object + status: + description: CronJobStatus defines the observed state of CronJob + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml new file mode 100644 index 0000000..af237dc --- /dev/null +++ b/config/crd/kustomization.yaml @@ -0,0 +1,23 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/batch.gdp.deliveryhero.io_cronjobs.yaml +#+kubebuilder:scaffold:crdkustomizeresource + +patches: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +#- path: patches/webhook_in_batch_cronjobs.yaml +#+kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +#- path: patches/cainjection_in_batch_cronjobs.yaml +#+kubebuilder:scaffold:crdkustomizecainjectionpatch + +# [WEBHOOK] To enable webhook, uncomment the following section +# the following config is for teaching kustomize how to do kustomization for CRDs. + +#configurations: +#- kustomizeconfig.yaml diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml new file mode 100644 index 0000000..ec5c150 --- /dev/null +++ b/config/crd/kustomizeconfig.yaml @@ -0,0 +1,19 @@ +# This file is for teaching kustomize how to substitute name and namespace reference in CRD +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index eca8163..826262f 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -15,7 +15,7 @@ namePrefix: gcp-config-connector-tagging-operator- # someName: someValue resources: -#- ../crd +- ../crd - ../rbac - ../manager # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in diff --git a/config/rbac/batch_cronjob_editor_role.yaml b/config/rbac/batch_cronjob_editor_role.yaml new file mode 100644 index 0000000..785adb8 --- /dev/null +++ b/config/rbac/batch_cronjob_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit cronjobs. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: cronjob-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: gcp-config-connector-tagging-operator + app.kubernetes.io/part-of: gcp-config-connector-tagging-operator + app.kubernetes.io/managed-by: kustomize + name: cronjob-editor-role +rules: +- apiGroups: + - batch.gdp.deliveryhero.io + resources: + - cronjobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - batch.gdp.deliveryhero.io + resources: + - cronjobs/status + verbs: + - get diff --git a/config/rbac/batch_cronjob_viewer_role.yaml b/config/rbac/batch_cronjob_viewer_role.yaml new file mode 100644 index 0000000..c197780 --- /dev/null +++ b/config/rbac/batch_cronjob_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view cronjobs. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: cronjob-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: gcp-config-connector-tagging-operator + app.kubernetes.io/part-of: gcp-config-connector-tagging-operator + app.kubernetes.io/managed-by: kustomize + name: cronjob-viewer-role +rules: +- apiGroups: + - batch.gdp.deliveryhero.io + resources: + - cronjobs + verbs: + - get + - list + - watch +- apiGroups: + - batch.gdp.deliveryhero.io + resources: + - cronjobs/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 181289e..44576b8 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -4,6 +4,32 @@ kind: ClusterRole metadata: name: manager-role rules: +- apiGroups: + - batch.gdp.deliveryhero.io + resources: + - cronjobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - batch.gdp.deliveryhero.io + resources: + - cronjobs/finalizers + verbs: + - update +- apiGroups: + - batch.gdp.deliveryhero.io + resources: + - cronjobs/status + verbs: + - get + - patch + - update - apiGroups: - kms.cnrm.cloud.google.com resources: diff --git a/config/samples/batch_v1_cronjob.yaml b/config/samples/batch_v1_cronjob.yaml new file mode 100644 index 0000000..3585201 --- /dev/null +++ b/config/samples/batch_v1_cronjob.yaml @@ -0,0 +1,12 @@ +apiVersion: batch.gdp.deliveryhero.io/v1 +kind: CronJob +metadata: + labels: + app.kubernetes.io/name: cronjob + app.kubernetes.io/instance: cronjob-sample + app.kubernetes.io/part-of: gcp-config-connector-tagging-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: gcp-config-connector-tagging-operator + name: cronjob-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml new file mode 100644 index 0000000..9324acc --- /dev/null +++ b/config/samples/kustomization.yaml @@ -0,0 +1,4 @@ +## Append samples of your project ## +resources: +- batch_v1_cronjob.yaml +#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/go.mod b/go.mod index f958856..88ea264 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/envoyproxy/go-control-plane v0.13.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect @@ -75,7 +75,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.18.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect github.com/prometheus/common v0.45.0 // indirect @@ -119,6 +119,7 @@ require ( k8s.io/apiextensions-apiserver v0.30.1 // indirect k8s.io/apiserver v0.30.1 // indirect k8s.io/component-base v0.30.1 // indirect + k8s.io/cri-api v0.31.2 k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 // indirect diff --git a/go.sum b/go.sum index ab80b9c..1a0aecb 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,9 @@ github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8E github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -173,8 +174,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -184,8 +186,8 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= @@ -358,6 +360,8 @@ k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= k8s.io/component-base v0.30.1 h1:bvAtlPh1UrdaZL20D9+sWxsJljMi0QZ3Lmw+kmZAaxQ= k8s.io/component-base v0.30.1/go.mod h1:e/X9kDiOebwlI41AvBHuWdqFriSRrX50CdwA9TFaHLI= +k8s.io/cri-api v0.31.2 h1:O/weUnSHvM59nTio0unxIUFyRHMRKkYn96YDILSQKmo= +k8s.io/cri-api v0.31.2/go.mod h1:Po3TMAYH/+KrZabi7QiwQI4a692oZcUOUThd/rqwxrI= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= diff --git a/helm-chart/gcp-config-connector-tagging-operator/templates/cronjob-crd.yaml b/helm-chart/gcp-config-connector-tagging-operator/templates/cronjob-crd.yaml new file mode 100644 index 0000000..9edfb3e --- /dev/null +++ b/helm-chart/gcp-config-connector-tagging-operator/templates/cronjob-crd.yaml @@ -0,0 +1,61 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: cronjobs.batch.gdp.deliveryhero.io + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + labels: + {{- include "gcp-config-connector-tagging-operator.labels" . | nindent 4 }} +spec: + group: batch.gdp.deliveryhero.io + names: + kind: CronJob + listKind: CronJobList + plural: cronjobs + singular: cronjob + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: CronJob is the Schema for the cronjobs 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: CronJobSpec defines the desired state of CronJob + properties: + foo: + description: Foo is an example field of CronJob. Edit cronjob_types.go + to remove/update + type: string + type: object + status: + description: CronJobStatus defines the observed state of CronJob + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] \ No newline at end of file diff --git a/helm-chart/gcp-config-connector-tagging-operator/templates/manager-rbac.yaml b/helm-chart/gcp-config-connector-tagging-operator/templates/manager-rbac.yaml index 1ad4110..dbf999d 100644 --- a/helm-chart/gcp-config-connector-tagging-operator/templates/manager-rbac.yaml +++ b/helm-chart/gcp-config-connector-tagging-operator/templates/manager-rbac.yaml @@ -5,6 +5,32 @@ metadata: labels: {{- include "gcp-config-connector-tagging-operator.labels" . | nindent 4 }} rules: +- apiGroups: + - batch.gdp.deliveryhero.io + resources: + - cronjobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - batch.gdp.deliveryhero.io + resources: + - cronjobs/finalizers + verbs: + - update +- apiGroups: + - batch.gdp.deliveryhero.io + resources: + - cronjobs/status + verbs: + - get + - patch + - update - apiGroups: - kms.cnrm.cloud.google.com resources: diff --git a/internal/controller/batch/cronjob_controller.go b/internal/controller/batch/cronjob_controller.go new file mode 100644 index 0000000..f741b07 --- /dev/null +++ b/internal/controller/batch/cronjob_controller.go @@ -0,0 +1,112 @@ +/* +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 batch + +import ( + "context" + "fmt" + "time" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cri-api/pkg/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + tagsv1alpha1 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/tags/v1alpha1" + batchv1 "github.com/deliveryhero/gcp-config-connector-tagging-operator/api/batch/v1" + "github.com/deliveryhero/gcp-config-connector-tagging-operator/internal/gcp" +) + +// CronJobReconciler reconciles a CronJob object +type CronJobReconciler struct { + client.Client + Scheme *runtime.Scheme + TagsManager gcp.TagsManager +} + +//+kubebuilder:rbac:groups=batch.gdp.deliveryhero.io,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=batch.gdp.deliveryhero.io,resources=cronjobs/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=batch.gdp.deliveryhero.io,resources=cronjobs/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. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the CronJob object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.3/pkg/reconcile +func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + log.Info("Starting reconciliation to delete unused tags") + + unusedTags, err := r.identifyUnusedTags(ctx) + if err != nil { + log.Error(err, "Failed to identify unused tags") + return ctrl.Result{}, err + } + + if err := r.deleteUnusedTags(ctx, unusedTags); err != nil { + log.Error(err, "Failed to delete unused tags") + return ctrl.Result{}, err + } + + log.Info("Reconciliation complete") + return ctrl.Result{RequeueAfter: 24 * time.Hour}, nil // Requeue daily +} + +// finds tags that have no active bindings +func (r *CronJobReconciler) identifyUnusedTags(ctx context.Context) ([]tagsv1alpha1.TagsLocationTagBinding, error) { + var unusedTags []tagsv1alpha1.TagsLocationTagBinding + var allTagBindings tagsv1alpha1.TagsLocationTagBindingList + + // List all tag bindings + if err := r.List(ctx, &allTagBindings); err != nil { + return nil, fmt.Errorf("failed to list tag bindings: %w", err) + } + + // Identify bindings marked for deletion + for _, tagBinding := range allTagBindings.Items { + if tagBinding.ObjectMeta.DeletionTimestamp.IsZero() { + // Tag in-use + continue + } + unusedTags = append(unusedTags, tagBinding) + } + return unusedTags, nil +} + +// remove the specified unused tags from the system +func (r *CronJobReconciler) deleteUnusedTags(ctx context.Context, unusedTags []tagsv1alpha1.TagsLocationTagBinding) error { + for _, tag := range unusedTags { + if err := r.Delete(ctx, &tag); err != nil { + if !errors.IsNotFound(err) { + return fmt.Errorf("failed to delete unused tag %s: %w", tag.Name, err) + } + } + } + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *CronJobReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&batchv1.CronJob{}). + Complete(r) +} diff --git a/internal/controller/batch/suite_test.go b/internal/controller/batch/suite_test.go new file mode 100644 index 0000000..82dd6e0 --- /dev/null +++ b/internal/controller/batch/suite_test.go @@ -0,0 +1,90 @@ +/* +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 batch + +import ( + "fmt" + "path/filepath" + "runtime" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + batchv1 "github.com/deliveryhero/gcp-config-connector-tagging-operator/api/batch/v1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + + // The BinaryAssetsDirectory is only required if you want to run the tests directly + // without call the makefile target test. If not informed it will look for the + // default path defined in controller-runtime which is /usr/local/kubebuilder/. + // Note that you must have the required binaries setup under the bin directory to perform + // the tests directly. When we run make test it will be setup and used automatically. + BinaryAssetsDirectory: filepath.Join("..", "..", "..", "bin", "k8s", + fmt.Sprintf("1.28.3-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = batchv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +})