diff --git a/.apidoc.yaml b/.apidoc.yaml new file mode 100644 index 0000000..68fe9ee --- /dev/null +++ b/.apidoc.yaml @@ -0,0 +1,6 @@ +processor: + ignoreTypes: + - "(.*)List$" + - "TypeMeta" +render: + kubernetesVersion: 1.25 diff --git a/.golangci.yaml b/.golangci.yaml index 1911340..46d7ace 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -9,6 +9,9 @@ linters: - gci # Disable gci import ordering checker since its buggy - depguard # Disallows to use non-listed packages - tagalign # Disallows to use non-aligned tags +linters-settings: + funlen: + lines: 90 issues: exclude-rules: - path: .*/metrics.go @@ -19,3 +22,9 @@ run: tests: false skip-dirs: - cmd/benchmark + skip-files: + - cmd/.*.go + - mocks/.*.go + - common/config.go + - .*/groupversion_info.go + - .*/zz_generated.deepcopy.go \ No newline at end of file diff --git a/README.md b/README.md index ef6fbeb..7fbd592 100644 --- a/README.md +++ b/README.md @@ -3,19 +3,20 @@ logo -> Mayfly is a Kubernetes operator that enables you to create temporary resources on the cluster that will expire after a certain period of time. +> Mayfly is a Kubernetes operator that enables you to have time-based resources. They creates or deletes on the specified time. ## 📖 General Information ### 📄 Summary -The Mayfly Operator allows you to have your resources on your cluster for a temporary time. -It deletes those resources from the cluster, according to the Mayfly expiration annotation that you set to specify how long the resource should remain active. This can be used to create temporary resources, temporary accesses, or simply to keep your cluster organized and tidy. +The Mayfly Operator allows you to have your resources on your cluster for a temporary time by the given expiration or mayfly create the resources at the time you specified. +It deletes those resources from the cluster, according to the Mayfly expiration annotation that you set to specify how long the resource should remain active. This can be used to create temporary resources, temporary accesses, or simply to keep your cluster organized and tidy. +Also, It creates the resources you specify at the given time by using `ScheduleResource` custom resource definition. You can also merge these two features together, just to have some resource created in the future and only for a specific amount of time. ### 🛠 Configuration Mayfly is an easy-to-use and configurable project that uses resource watches and schedulers to delete your resources at the appropriate time. It is simple to set up and customize. -To specify which resources should be monitored and cleaned up, you can set the `RESOURCES` environment variable to a comma-separated list of `{ApiVersion};{Kind}` as text. This allows you to customize which resources are targeted for cleanup. +To specify which resources should be monitored and cleaned up, you can set the `RESOURCES` environment variable to a comma-separated list of `{ApiVersion};{Kind}` as text. This allows you to customize which resources are targeted for cleanup with expiration annotations. Example: ``` @@ -23,6 +24,9 @@ export RESOURCES="v1;Secret,test.com/v1alpha;MyCRD" ``` ## 🚀 Usage + +### Resouce Expiration + Once you have determined which resources you want Mayfly to monitor, you can set the `mayfly.cloud.namecheap.com/expire` annotation on those resources with a duration value. This will cause Mayfly to delete the resources once the specified duration has passed, based on the time of their creation. Keep in mind that the expiration will be calculated based on the creation time of the resource. @@ -43,6 +47,35 @@ spec: - infinity ``` +### Scheduled Resource Creation + +The `ScheduledResource` CRD allows you to schedule the creation of an object in the future. This can be combined with the expire annotation, enabling Mayfly to create and remove certain objects for a temporary period in the future. + +Example: +``` +apiVersion: cloud.namecheap.com/v1alpha1 +kind: ScheduledResource +metadata: + annotations: + mayfly.cloud.namecheap.com/expire: 60m + name: example + namespace: default +spec: + in: 30m + content: | + apiVersion: v1 + kind: Secret + metadata: + name: example + namespace: default + annotations: + mayfly.cloud.namecheap.com/expire: 30m + data: + .secret-file: dmFsdWUtMg0KDQo= +status: + condition: Scheduled +``` +This feature is particularly useful for setting up temporary resources that are only needed for a short period, reducing clutter and improving the efficiency of resource management. ## 🛳️ Deployment diff --git a/cmd/benchmark/benchmark.go b/cmd/benchmark/benchmark.go index a35fd5f..39fb054 100644 --- a/cmd/benchmark/benchmark.go +++ b/cmd/benchmark/benchmark.go @@ -5,7 +5,7 @@ import ( "fmt" time "time" - "github.com/NCCloud/mayfly/pkg" + "github.com/NCCloud/mayfly/pkg/common" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -30,7 +30,7 @@ func (b *Benchmark) Listener() Result { kind := make(map[string]int) now := time.Now() for _, resourceKind := range b.config.Resources { - resourceList := pkg.NewResourceInstanceList(resourceKind) + resourceList := common.NewResourceInstanceList(resourceKind) resourcesListErr := b.mgrClient.List(context.Background(), resourceList) if resourcesListErr != nil { diff --git a/cmd/benchmark/main.go b/cmd/benchmark/main.go index bc5d1fd..8479645 100644 --- a/cmd/benchmark/main.go +++ b/cmd/benchmark/main.go @@ -5,7 +5,7 @@ import ( "os" "time" - "github.com/NCCloud/mayfly/pkg" + "github.com/NCCloud/mayfly/pkg/common" "github.com/go-echarts/go-echarts/v2/charts" "github.com/go-echarts/go-echarts/v2/opts" @@ -19,7 +19,7 @@ import ( ) var ( - config *pkg.Config + config *common.Config mgrClient client.Client pageTitle = "Mayfly Benchmark" benchmarkHtml = "mayfly_benchmark.html" @@ -29,7 +29,7 @@ func init() { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - config = pkg.NewConfig() + config = common.NewConfig() var mgrClientErr error mgrClient, mgrClientErr = client.New(ctrl.GetConfigOrDie(), client.Options{Scheme: scheme}) diff --git a/cmd/benchmark/types.go b/cmd/benchmark/types.go index bdd852e..dce2c7e 100644 --- a/cmd/benchmark/types.go +++ b/cmd/benchmark/types.go @@ -3,14 +3,14 @@ package main import ( "time" - "github.com/NCCloud/mayfly/pkg" + "github.com/NCCloud/mayfly/pkg/common" "sigs.k8s.io/controller-runtime/pkg/client" ) type Benchmark struct { granularity time.Duration - config *pkg.Config + config *common.Config mgrClient client.Client startedAt time.Time count int @@ -29,7 +29,7 @@ type Point struct { time time.Time } -func NewBenchmark(mgrClient client.Client, config *pkg.Config, count int) *Benchmark { +func NewBenchmark(mgrClient client.Client, config *common.Config, count int) *Benchmark { return &Benchmark{ granularity: 5 * time.Second, config: config, diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 9e1ba3b..afb416d 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -3,9 +3,14 @@ package main import ( "fmt" + "github.com/NCCloud/mayfly/pkg/apis/v1alpha1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + "github.com/NCCloud/mayfly/pkg/common" + "github.com/NCCloud/mayfly/pkg/controllers" + "sigs.k8s.io/controller-runtime/pkg/metrics/server" - "github.com/NCCloud/mayfly/pkg" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/cache" @@ -21,7 +26,10 @@ const ( func main() { logger := zap.New() scheme := runtime.NewScheme() - config := pkg.NewConfig() + config := common.NewConfig() + + utilruntime.Must(v1alpha1.AddToScheme(scheme)) + ctrl.SetLogger(logger) logger.Info("Configuration", "config", config) @@ -35,7 +43,7 @@ func main() { LeaderElection: config.EnableLeaderElection, LeaderElectionID: "mayfly-leader.cloud.namecheap.com", Cache: cache.Options{ - SyncPeriod: config.SyncPeriod, + SyncPeriod: &config.SyncPeriod, }, }) if managerErr != nil { @@ -43,14 +51,20 @@ func main() { } client := manager.GetClient() - scheduler := pkg.NewScheduler(config, client) + scheduler := common.NewScheduler(config, client) for _, resource := range config.Resources { - if err := pkg.NewController(config, client, resource, scheduler).SetupWithManager(manager); err != nil { - panic(err) + if expirationControllerErr := controllers.NewExpirationController(config, client, resource, scheduler). + SetupWithManager(manager); expirationControllerErr != nil { + panic(expirationControllerErr) } } + if scheduledResourceControllerErr := controllers.NewScheduledResourceController(config, client, scheduler). + SetupWithManager(manager); scheduledResourceControllerErr != nil { + panic(scheduledResourceControllerErr) + } + if addHealthCheckErr := manager.AddHealthzCheck("healthz", healthz.Ping); addHealthCheckErr != nil { panic(addHealthCheckErr) } diff --git a/deploy/crds/cloud.namecheap.com_scheduledresources.yaml b/deploy/crds/cloud.namecheap.com_scheduledresources.yaml new file mode 100644 index 0000000..e909dbb --- /dev/null +++ b/deploy/crds/cloud.namecheap.com_scheduledresources.yaml @@ -0,0 +1,64 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: scheduledresources.cloud.namecheap.com +spec: + group: cloud.namecheap.com + names: + kind: ScheduledResource + listKind: ScheduledResourceList + plural: scheduledresources + singular: scheduledresource + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.in + name: In + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .status.condition + name: Condition + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + 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: + properties: + content: + type: string + in: + type: string + required: + - content + - in + type: object + status: + properties: + condition: + type: string + required: + - condition + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/devops.sh b/devops.sh index 79fcf84..842fe1b 100755 --- a/devops.sh +++ b/devops.sh @@ -1,13 +1,29 @@ #!/usr/bin/env bash +export CONTROLLER_GEN_VERSION="v0.13.0" export GOLANGCI_LINT_VERSION="v1.54.2" +export MOCKERY_GEN_VERSION="v2.36.0" +export GOFUMPT_VERSION="v0.5.0" +export TESTENV_VERSION="1.25.x!" prerequisites() { - if ! command -v golangci-lint &>/dev/null; then + if [[ "$(controller-gen --version 2>&1)" != *"$CONTROLLER_GEN_VERSION"* ]]; then + go install sigs.k8s.io/controller-tools/cmd/controller-gen@"${CONTROLLER_GEN_VERSION}" + fi + if [[ "$(golangci-lint --version 2>&1)" != *"$GOLANGCI_LINT_VERSION"* ]]; then go install github.com/golangci/golangci-lint/cmd/golangci-lint@"${GOLANGCI_LINT_VERSION}" fi - if ! command -v gofumpt &>/dev/null; then - go install mvdan.cc/gofumpt@latest + if [[ "$(mockery --version 2>&1)" != *"$MOCKERY_GEN_VERSION"* ]]; then + go install github.com/vektra/mockery/v2@"${MOCKERY_GEN_VERSION}" + fi + if [[ "$(gofumpt --version 2>&1)" != *"$GOFUMPT_VERSION"* ]]; then + go install mvdan.cc/gofumpt@"${GOFUMPT_VERSION}" + fi + if ! command -v crd-ref-docs &>/dev/null; then + go install github.com/elastic/crd-ref-docs@latest + fi + if ! command -v setup-envtest &>/dev/null; then + go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest fi } @@ -16,8 +32,21 @@ lint() { golangci-lint run --timeout=10m } +generate() { + rm -rf deploy/crds + controller-gen object paths="./..." + controller-gen crd paths="./..." output:dir=deploy/crds + sed '/Compiled/d' pkg/apis/v1alpha1/zz_generated.deepcopy.go > pkg/apis/v1alpha1/zz_generated.deepcopy.gotmp + mv pkg/apis/v1alpha1/zz_generated.deepcopy.gotmp pkg/apis/v1alpha1/zz_generated.deepcopy.go + crd-ref-docs --source-path=./pkg/apis --config .apidoc.yaml --renderer markdown --output-path=./docs/api.md +} + +install() { + kubectl apply -f deploy/crds +} + test() { - go test -v ./... + go test -v -coverpkg=./... ./... } prerequisites diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..c3fb1c5 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,76 @@ +# API Reference + +## Packages +- [cloud.namecheap.com/v1alpha1](#cloudnamecheapcomv1alpha1) + + +## cloud.namecheap.com/v1alpha1 + +Package v1alpha1 contains API Schema definitions for the v1alpha1 API group + +### Resource Types +- [ScheduledResource](#scheduledresource) + + + +#### Condition + +_Underlying type:_ _string_ + + + +_Appears in:_ +- [ScheduledResourceStatus](#scheduledresourcestatus) + + + + + +#### ScheduledResource + + + + + + + +| Field | Description | +| --- | --- | +| `apiVersion` _string_ | `cloud.namecheap.com/v1alpha1` +| `kind` _string_ | `ScheduledResource` +| `kind` _string_ | 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 | +| `apiVersion` _string_ | 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 | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | +| `spec` _[ScheduledResourceSpec](#scheduledresourcespec)_ | | +| `status` _[ScheduledResourceStatus](#scheduledresourcestatus)_ | | + + +#### ScheduledResourceSpec + + + + + +_Appears in:_ +- [ScheduledResource](#scheduledresource) + +| Field | Description | +| --- | --- | +| `in` _string_ | | +| `content` _string_ | | + + +#### ScheduledResourceStatus + + + + + +_Appears in:_ +- [ScheduledResource](#scheduledresource) + +| Field | Description | +| --- | --- | +| `condition` _[Condition](#condition)_ | | + + diff --git a/examples/combination.yaml b/examples/combination.yaml new file mode 100644 index 0000000..da66c61 --- /dev/null +++ b/examples/combination.yaml @@ -0,0 +1,18 @@ +apiVersion: cloud.namecheap.com/v1alpha1 +kind: ScheduledResource +metadata: + name: combination-example + annotations: + mayfly.cloud.namecheap.com/expire: 10s +spec: + in: "5s" + content: | + apiVersion: v1 + kind: Secret + metadata: + name: combination-example + namespace: default + annotations: + mayfly.cloud.namecheap.com/expire: 10s + data: + .secret-file: dmFsdWUtMg0KDQo= \ No newline at end of file diff --git a/example.yaml b/examples/expiration.yaml similarity index 57% rename from example.yaml rename to examples/expiration.yaml index 7d21f86..b89a0fe 100644 --- a/example.yaml +++ b/examples/expiration.yaml @@ -1,10 +1,9 @@ apiVersion: v1 kind: Secret metadata: - name: website-1-temp + name: expiration-example namespace: default annotations: mayfly.cloud.namecheap.com/expire: 10s data: - mysql-user-password: cUIzN3Y5cmo4bmN5TmI2NVVWMDQxMktZ -type: Opaque + .secret-file: dmFsdWUtMg0KDQo= \ No newline at end of file diff --git a/examples/scheduled_resource.yaml b/examples/scheduled_resource.yaml new file mode 100644 index 0000000..ce5e72b --- /dev/null +++ b/examples/scheduled_resource.yaml @@ -0,0 +1,14 @@ +apiVersion: cloud.namecheap.com/v1alpha1 +kind: ScheduledResource +metadata: + name: scheduled-resource-example +spec: + in: "5s" + content: | + apiVersion: v1 + kind: Secret + metadata: + name: scheduled-resource-example + namespace: default + data: + .secret-file: dmFsdWUtMg0KDQo= \ No newline at end of file diff --git a/go.mod b/go.mod index 1f3d4d1..959c585 100644 --- a/go.mod +++ b/go.mod @@ -4,24 +4,24 @@ go 1.21 require ( github.com/caarlos0/env/v9 v9.0.0 - github.com/go-co-op/gocron v1.32.1 - github.com/go-echarts/go-echarts/v2 v2.2.7 + github.com/go-co-op/gocron v1.37.0 + github.com/go-echarts/go-echarts/v2 v2.3.2 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 - github.com/prometheus/client_golang v1.16.0 - k8s.io/api v0.28.0 - k8s.io/apimachinery v0.28.0 - k8s.io/client-go v0.28.0 - sigs.k8s.io/controller-runtime v0.16.0 + github.com/prometheus/client_golang v1.17.0 + k8s.io/api v0.29.0 + k8s.io/apimachinery v0.29.0 + k8s.io/client-go v0.29.0 + sigs.k8s.io/controller-runtime v0.16.3 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -30,9 +30,9 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -42,33 +42,34 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.10.1 // indirect + github.com/prometheus/procfs v0.11.1 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/spf13/pflag v1.0.5 // indirect go.uber.org/atomic v1.10.0 // indirect + go.uber.org/goleak v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.25.0 // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect - golang.org/x/net v0.13.0 // indirect - golang.org/x/oauth2 v0.8.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/term v0.10.0 // indirect - golang.org/x/text v0.11.0 // indirect + golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.10.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.28.0 // indirect - k8s.io/component-base v0.28.0 // indirect - k8s.io/klog/v2 v2.100.1 // indirect - k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect - k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + k8s.io/apiextensions-apiserver v0.28.3 // indirect + k8s.io/component-base v0.28.3 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index e4754d1..690b329 100644 --- a/go.sum +++ b/go.sum @@ -1,49 +1,32 @@ -github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= -github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/caarlos0/env/v9 v9.0.0 h1:SI6JNsOA+y5gj9njpgybykATIylrRMklbs5ch6wO6pc= github.com/caarlos0/env/v9 v9.0.0/go.mod h1:ye5mlCVMYh6tZ+vCgrs/B95sj88cg5Tlnc0XIzgZ020= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= -github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +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/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/go-co-op/gocron v1.32.1 h1:h+StA6Qzlv+ImlCaLfA26rLN9eS/l4sO7oWmPUbRVIY= -github.com/go-co-op/gocron v1.32.1/go.mod h1:UGz2oYvVS6PsqlwuOdo5L1Djsg/cQjxJ6T5ntkhp9Bg= -github.com/go-echarts/go-echarts/v2 v2.2.7 h1:mtFAuoqQ7McdlKrJ0gLexwxMPT7yoscDDhULNwPOxBk= -github.com/go-echarts/go-echarts/v2 v2.2.7/go.mod h1:VEeyPT5Odx/UHeuxtIAHGu2+87MWGA5OBaZ120NFi/w= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= +github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY= +github.com/go-co-op/gocron/v2 v2.1.0/go.mod h1:yuQ4a9rIMpkdBVU+Rd5EyuEKaFjl/c7ykupXHnXB6MU= +github.com/go-echarts/go-echarts/v2 v2.3.2 h1:imRxqF5sLtEPBsv5HGwz9KklNuwCo0fTITZ31mrgfzo= +github.com/go-echarts/go-echarts/v2 v2.3.2/go.mod h1:56YlvzhW/a+du15f3S2qUGNDfKnFOeJSThBIrVFHDtI= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= @@ -63,29 +46,22 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/cel-go v0.16.0 h1:DG9YQ8nFCFXAs/FDDwBxmL1tpKNrdlGUM9U3537bX/Y= -github.com/google/cel-go v0.16.0/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= @@ -113,10 +89,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= +github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -125,26 +101,22 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= +github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= -github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= +github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= 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/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 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= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -153,44 +125,19 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= -go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= -go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= -go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= -go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= -go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0 h1:xFSRQBbXF6VvYRf2lqMJXxoB72XI1K/azav8TekHHSw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.35.0/go.mod h1:h8TWwRAhQpOd0aM5nYsRD8+flnkj+526GEIVlarH7eY= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.1 h1:sxoY9kG1s1WpSYNyzm24rlwH4lnRYFXUVVBmKMBfRgw= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.35.1/go.mod h1:9NiG9I2aHTKkcxqCILhjtyNA1QEiCjdBACv4IvrFQ+c= -go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= -go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0 h1:TaB+1rQhddO1sF71MpZOZAuSPW1klK2M8XxfrBMfK7Y= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.10.0/go.mod h1:78XhIg8Ht9vR4tbLNUhXsiOnE2HOuSeKAiAcoVQEpOY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0 h1:pDDYmo0QadUPal5fwXoY1pmMpFcdyhXOmL5drCrI3vU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.10.0/go.mod h1:Krqnjl22jUJ0HgMzw5eveuCvFDXY4nSYb4F8t5gdrag= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0 h1:KtiUEhQmj/Pa874bVYKGNVdq8NPKiacPbaRRtgXi+t4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0/go.mod h1:OfUCyyIiDvNXHWpcWgbF+MWvqPZiNa3YDEnivcnYsV0= -go.opentelemetry.io/otel/metric v0.31.0 h1:6SiklT+gfWAwWUR0meEMxQBtihpiEs4c+vL9spDTqUs= -go.opentelemetry.io/otel/metric v0.31.0/go.mod h1:ohmwj9KTSIeBnDBm/ZwH2PSZxZzoOaG2xZeekTRzL5A= -go.opentelemetry.io/otel/sdk v1.10.0 h1:jZ6K7sVn04kk/3DNUdJ4mqRlGDiXAVuIG+MMENpTNdY= -go.opentelemetry.io/otel/sdk v1.10.0/go.mod h1:vO06iKzD5baltJz1zarxMCNHFpUlUiOy4s65ECtn6kE= -go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= -go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= -go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= -go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -200,10 +147,8 @@ go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -215,17 +160,15 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY= -golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= -golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= +golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -234,16 +177,16 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -252,8 +195,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= -golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -262,18 +205,10 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54 h1:9NWlQfY2ePejTmfwUH1OWwmznFa+0kKcHGPDvcPza9M= -google.golang.org/genproto v0.0.0-20230526161137-0005af68ea54/go.mod h1:zqTuNwFlFRsw5zIts5VnzLQxSRqh+CGOTVMlYbY0Eyk= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9 h1:m8v1xLLLzMe1m5P+gCTF8nJB9epwZQUBERm20Oy1poQ= -google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -281,41 +216,33 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.28.0 h1:3j3VPWmN9tTDI68NETBWlDiA9qOiGJ7sdKeufehBYsM= -k8s.io/api v0.28.0/go.mod h1:0l8NZJzB0i/etuWnIXcwfIv+xnDOhL3lLW919AWYDuY= -k8s.io/apiextensions-apiserver v0.28.0 h1:CszgmBL8CizEnj4sj7/PtLGey6Na3YgWyGCPONv7E9E= -k8s.io/apiextensions-apiserver v0.28.0/go.mod h1:uRdYiwIuu0SyqJKriKmqEN2jThIJPhVmOWETm8ud1VE= -k8s.io/apimachinery v0.28.0 h1:ScHS2AG16UlYWk63r46oU3D5y54T53cVI5mMJwwqFNA= -k8s.io/apimachinery v0.28.0/go.mod h1:X0xh/chESs2hP9koe+SdIAcXWcQ+RM5hy0ZynB+yEvw= -k8s.io/apiserver v0.28.0 h1:wVh7bK6Xj7hq+5ntInysTeQRAOqqFoKGUOW2yj8DXrY= -k8s.io/apiserver v0.28.0/go.mod h1:MvLmtxhQ0Tb1SZk4hfJBjs8iqr5nhYeaFSaoEcz7Lk4= -k8s.io/client-go v0.28.0 h1:ebcPRDZsCjpj62+cMk1eGNX1QkMdRmQ6lmz5BLoFWeM= -k8s.io/client-go v0.28.0/go.mod h1:0Asy9Xt3U98RypWJmU1ZrRAGKhP6NqDPmptlAzK2kMc= -k8s.io/component-base v0.28.0 h1:HQKy1enJrOeJlTlN4a6dU09wtmXaUvThC0irImfqyxI= -k8s.io/component-base v0.28.0/go.mod h1:Yyf3+ZypLfMydVzuLBqJ5V7Kx6WwDr/5cN+dFjw1FNk= -k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= -k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kms v0.28.0 h1:BwJhU9qPcJhHLUcQjtelOSjYti+1/caJLr+4jHbKzTA= -k8s.io/kms v0.28.0/go.mod h1:CNU792ls92v2Ye7Vn1jn+xLqYtUSezDZNVu6PLbJyrU= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= -k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= -k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 h1:trsWhjU5jZrx6UvFu4WzQDrN7Pga4a7Qg+zcfcj64PA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISXqCDVVcyO8hLn12AKVYYUjM7ftlqsqmrhMZE0= -sigs.k8s.io/controller-runtime v0.16.0 h1:5koYaaRVBHDr0LZAJjO5dWzUjMsh6cwa7q1Mmusrdvk= -sigs.k8s.io/controller-runtime v0.16.0/go.mod h1:77DnuwA8+J7AO0njzv3wbNlMOnGuLrwFr8JPNwx3J7g= +k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= +k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= +k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= +k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= +k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= +k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= +k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= +k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= +sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/pkg/apis/v1alpha1/groupversion_info.go b/pkg/apis/v1alpha1/groupversion_info.go new file mode 100644 index 0000000..d742ebb --- /dev/null +++ b/pkg/apis/v1alpha1/groupversion_info.go @@ -0,0 +1,20 @@ +// Package v1alpha1 contains API Schema definitions for the v1alpha1 API group +// +kubebuilder:object:generate=true +// +groupName=cloud.namecheap.com +package v1alpha1 + +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: "cloud.namecheap.com", Version: "v1alpha1"} + + // 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/pkg/apis/v1alpha1/scheduled_resource_types.go b/pkg/apis/v1alpha1/scheduled_resource_types.go new file mode 100644 index 0000000..9cc75b4 --- /dev/null +++ b/pkg/apis/v1alpha1/scheduled_resource_types.go @@ -0,0 +1,76 @@ +package v1alpha1 + +import ( + "errors" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/serializer/yaml" +) + +type ( + FailureReason string + Condition string +) + +const ( + ConditionCreated Condition = "Created" + ConditionScheduled Condition = "Scheduled" + ConditionFailed Condition = "Failed" +) + +var ErrObjectIsNotValid = errors.New("object is not valid") + +func init() { + SchemeBuilder.Register(&ScheduledResource{}, &ScheduledResourceList{}) +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="In",type=string,JSONPath=".spec.in" +//+kubebuilder:printcolumn:name="Age",type=date,JSONPath=".metadata.creationTimestamp" +//+kubebuilder:printcolumn:name="Condition",type=string,JSONPath=".status.condition" + +type ScheduledResource struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ScheduledResourceSpec `json:"spec,omitempty"` + Status ScheduledResourceStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +type ScheduledResourceList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ScheduledResource `json:"items"` +} + +type ScheduledResourceSpec struct { + In string `json:"in"` + Content string `json:"content"` +} + +type ScheduledResourceStatus struct { + Condition Condition `json:"condition"` +} + +func (in *ScheduledResource) IsBeingDeleted() bool { + return in.DeletionTimestamp != nil +} + +func (in *ScheduledResource) GetContent() (*unstructured.Unstructured, error) { + object, _, decodeErr := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme). + Decode([]byte(in.Spec.Content), nil, nil) + if decodeErr != nil { + return nil, decodeErr + } + + unstructuredObj, isUnstructuredObj := object.(*unstructured.Unstructured) + if !isUnstructuredObj { + return nil, ErrObjectIsNotValid + } + + return unstructuredObj, nil +} diff --git a/pkg/apis/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000..0e7fb1c --- /dev/null +++ b/pkg/apis/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,98 @@ +//go:build !ignore_autogenerated + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +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 *ScheduledResource) DeepCopyInto(out *ScheduledResource) { + *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 ScheduledResource. +func (in *ScheduledResource) DeepCopy() *ScheduledResource { + if in == nil { + return nil + } + out := new(ScheduledResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ScheduledResource) 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 *ScheduledResourceList) DeepCopyInto(out *ScheduledResourceList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ScheduledResource, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScheduledResourceList. +func (in *ScheduledResourceList) DeepCopy() *ScheduledResourceList { + if in == nil { + return nil + } + out := new(ScheduledResourceList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ScheduledResourceList) 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 *ScheduledResourceSpec) DeepCopyInto(out *ScheduledResourceSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScheduledResourceSpec. +func (in *ScheduledResourceSpec) DeepCopy() *ScheduledResourceSpec { + if in == nil { + return nil + } + out := new(ScheduledResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ScheduledResourceStatus) DeepCopyInto(out *ScheduledResourceStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScheduledResourceStatus. +func (in *ScheduledResourceStatus) DeepCopy() *ScheduledResourceStatus { + if in == nil { + return nil + } + out := new(ScheduledResourceStatus) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/common/config.go b/pkg/common/config.go new file mode 100644 index 0000000..fdb4068 --- /dev/null +++ b/pkg/common/config.go @@ -0,0 +1,24 @@ +package common + +import ( + "time" + + "github.com/caarlos0/env/v9" +) + +type Config struct { + EnableLeaderElection bool `env:"ENABLE_LEADER_ELECTION" envDefault:"false"` + SyncPeriod time.Duration `env:"SYNC_PERIOD" envDefault:"30m"` + MonitoringInterval time.Duration `env:"MONITORING_INTERVAL" envDefault:"5s"` + ExpirationLabel string `env:"EXPIRATION_LABEL" envDefault:"mayfly.cloud.namecheap.com/expire"` + Resources []string `env:"RESOURCES" envSeparator:"," envDefault:"v1;Secret,cloud.namecheap.com/v1alpha1;ScheduledResource"` +} + +func NewConfig() *Config { + operatorConfig := &Config{} + if err := env.Parse(operatorConfig); err != nil { + panic(err) + } + + return operatorConfig +} diff --git a/pkg/metrics.go b/pkg/common/metrics.go similarity index 97% rename from pkg/metrics.go rename to pkg/common/metrics.go index f657347..31ee926 100644 --- a/pkg/metrics.go +++ b/pkg/common/metrics.go @@ -1,4 +1,4 @@ -package pkg +package common import ( "github.com/prometheus/client_golang/prometheus" diff --git a/pkg/common/scheduler.go b/pkg/common/scheduler.go new file mode 100644 index 0000000..d64822d --- /dev/null +++ b/pkg/common/scheduler.go @@ -0,0 +1,65 @@ +package common + +import ( + "time" + + "github.com/go-co-op/gocron" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Scheduler struct { + config *Config + client client.Client + scheduler *gocron.Scheduler +} + +func NewScheduler(config *Config, client client.Client) *Scheduler { + scheduler := &Scheduler{ + config: config, + client: client, + scheduler: gocron.NewScheduler(time.UTC), + } + + scheduler.startMonitoring() + scheduler.scheduler.StartAsync() + + return scheduler +} + +func (s *Scheduler) CreateOrUpdateTask(tag string, date time.Time, task func() error) error { + if jobs, _ := s.scheduler.FindJobsByTag(tag); len(jobs) > 0 { + if jobs[0].NextRun().Equal(date) { + return nil + } + + if removeJobErr := s.scheduler.RemoveByTag(tag); removeJobErr != nil { + return removeJobErr + } + } + + _, jobErr := s.scheduler.StartAt(date).Every(1).LimitRunsTo(1).Tag(tag).Do(task) + + return jobErr +} + +func (s *Scheduler) DeleteTask(tag string) error { + return s.scheduler.RemoveByTag(tag) +} + +func (s *Scheduler) startMonitoring() { + if _, doErr := s.scheduler.SingletonMode().Every(s.config.MonitoringInterval).Do(func() { + exportMayflyTotalJobsMetrics(float64(len(s.scheduler.Jobs()))) + + pastJobs := 0 + for _, job := range s.scheduler.Jobs() { + if job.NextRun().Before(time.Now()) { + pastJobs++ + } + } + + exportMayflyPastJobsMetrics(float64(pastJobs)) + }); doErr != nil { + panic(doErr) + } +} diff --git a/pkg/functions.go b/pkg/common/utils.go similarity index 93% rename from pkg/functions.go rename to pkg/common/utils.go index 02a3f38..b7f2a5c 100644 --- a/pkg/functions.go +++ b/pkg/common/utils.go @@ -1,4 +1,4 @@ -package pkg +package common import ( "fmt" @@ -30,7 +30,7 @@ func NewResourceInstance(apiVersionKind string) *unstructured.Unstructured { apiVersionKindArr := strings.Split(apiVersionKind, ";") return &unstructured.Unstructured{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": apiVersionKindArr[0], "kind": apiVersionKindArr[1], }, @@ -41,7 +41,7 @@ func NewResourceInstanceList(apiVersionKind string) *unstructured.UnstructuredLi resourceInstance := NewResourceInstance(apiVersionKind) return &unstructured.UnstructuredList{ - Object: map[string]interface{}{ + Object: map[string]any{ "apiVersion": resourceInstance.GetAPIVersion(), "kind": fmt.Sprintf("%sList", resourceInstance.GetKind()), }, diff --git a/pkg/config.go b/pkg/config.go deleted file mode 100644 index 2f05666..0000000 --- a/pkg/config.go +++ /dev/null @@ -1,23 +0,0 @@ -package pkg - -import ( - "time" - - "github.com/caarlos0/env/v9" -) - -type Config struct { - EnableLeaderElection bool `env:"ENABLE_LEADER_ELECTION" envDefault:"false"` - SyncPeriod *time.Duration `env:"SYNC_PERIOD" envDefault:"30m"` - ExpirationLabel string `env:"EXPIRATION_LABEL" envDefault:"mayfly.cloud.namecheap.com/expire"` - Resources []string `env:"RESOURCES" envSeparator:"," envDefault:"v1;Secret"` -} - -func NewConfig() *Config { - operatorConfig := &Config{} - if err := env.Parse(operatorConfig); err != nil { - panic(err) - } - - return operatorConfig -} diff --git a/pkg/controller.go b/pkg/controller.go deleted file mode 100644 index 3901c96..0000000 --- a/pkg/controller.go +++ /dev/null @@ -1,102 +0,0 @@ -package pkg - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/api/errors" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/predicate" -) - -type Controller struct { - APIVersionKind string - Config *Config - MgrClient client.Client - Scheduler *Scheduler -} - -func NewController(config *Config, client client.Client, apiVersionKind string, scheduler *Scheduler) *Controller { - return &Controller{ - APIVersionKind: apiVersionKind, - Config: config, - MgrClient: client, - Scheduler: scheduler, - } -} - -func (r *Controller) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := log.FromContext(ctx) - logger.Info("Reconciliation started.") - - resource := NewResourceInstance(r.APIVersionKind) - - if getErr := r.MgrClient.Get(ctx, req.NamespacedName, resource); getErr != nil { - if errors.IsNotFound(getErr) { - _ = r.Scheduler.RemoveJob(fmt.Sprintf("%v", resource.GetUID())) - - return ctrl.Result{}, nil - } - - return ctrl.Result{}, getErr - } - - hasExpired, expirationDate, hasExpiredErr := IsExpired(resource, r.Config) - if hasExpiredErr != nil { - logger.Error(hasExpiredErr, "Error while checking if resource has expired.") - - return ctrl.Result{}, hasExpiredErr - } - - if hasExpired { - logger.Info("Resource already expired. Removing") - - _ = r.MgrClient.Delete(ctx, resource) - - _ = r.Scheduler.RemoveJob(fmt.Sprintf("%v", resource.GetUID())) - - return ctrl.Result{}, nil - } - - startJobErr := r.Scheduler.StartOrUpdateJob(ctx, expirationDate, func(ctx context.Context, client client.Client, - resource client.Object, - ) error { - return r.MgrClient.Delete(ctx, resource) - }, r.MgrClient, resource) - if startJobErr != nil { - logger.Error(startJobErr, "Error while starting job.") - - return ctrl.Result{}, startJobErr - } - - return ctrl.Result{}, nil -} - -func (r *Controller) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(NewResourceInstance(r.APIVersionKind)). - WithEventFilter(predicate.Funcs{ - CreateFunc: func(createEvent event.CreateEvent) bool { - mayFlyAnnotation := createEvent.Object.GetAnnotations()[r.Config.ExpirationLabel] - - return mayFlyAnnotation != "" - }, - DeleteFunc: func(deleteEvent event.DeleteEvent) bool { - mayFlyAnnotation := deleteEvent.Object.GetAnnotations()[r.Config.ExpirationLabel] - - return mayFlyAnnotation != "" - }, - UpdateFunc: func(updateEvent event.UpdateEvent) bool { - oldMayFlyAnnotation := updateEvent.ObjectOld.GetAnnotations()[r.Config.ExpirationLabel] - newMayFlyAnnotation := updateEvent.ObjectNew.GetAnnotations()[r.Config.ExpirationLabel] - if newMayFlyAnnotation != "" && oldMayFlyAnnotation != newMayFlyAnnotation { - return true - } - - return false - }, - }).Complete(r) -} diff --git a/pkg/controllers/expiration.go b/pkg/controllers/expiration.go new file mode 100644 index 0000000..4c41923 --- /dev/null +++ b/pkg/controllers/expiration.go @@ -0,0 +1,97 @@ +package controllers + +import ( + "context" + "fmt" + + "github.com/NCCloud/mayfly/pkg/common" + + "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +type ExpirationController struct { + config *common.Config + client client.Client + scheduler *common.Scheduler + apiVersionKind string +} + +func NewExpirationController(config *common.Config, client client.Client, + apiVersionKind string, scheduler *common.Scheduler, +) *ExpirationController { + return &ExpirationController{ + config: config, + client: client, + scheduler: scheduler, + apiVersionKind: apiVersionKind, + } +} + +func (r *ExpirationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var ( + logger = log.FromContext(ctx) + resource = common.NewResourceInstance(r.apiVersionKind) + apiVersion, kind = resource.GroupVersionKind().ToAPIVersionAndKind() + tag = fmt.Sprintf("%s/%s/%s/%s/delete", apiVersion, kind, req.Name, req.Namespace) + ) + + logger.Info("Reconciliation started.") + defer logger.Info("Reconciliation finished.") + + if getErr := r.client.Get(ctx, req.NamespacedName, resource); getErr != nil { + if errors.IsNotFound(getErr) { + _ = r.scheduler.DeleteTask(tag) + } + + return ctrl.Result{}, client.IgnoreNotFound(getErr) + } + + hasExpired, expirationDate, hasExpiredErr := common.IsExpired(resource, r.config) + if hasExpiredErr != nil { + logger.Error(hasExpiredErr, "Error while checking if resource has expired.") + + return ctrl.Result{}, hasExpiredErr + } + + if hasExpired { + logger.Info("Resource already expired will be removed.") + + _ = r.scheduler.DeleteTask(tag) + + return ctrl.Result{}, client.IgnoreNotFound(r.client.Delete(ctx, resource)) + } + + if createOrUpdateTaskErr := r.scheduler.CreateOrUpdateTask(tag, expirationDate, func() error { + return r.client.Delete(context.Background(), resource) + }); createOrUpdateTaskErr != nil { + logger.Error(createOrUpdateTaskErr, "Error while creating or updating task.") + + return ctrl.Result{}, createOrUpdateTaskErr + } + + return ctrl.Result{}, nil +} + +func (r *ExpirationController) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(common.NewResourceInstance(r.apiVersionKind)). + WithEventFilter(predicate.Funcs{ + CreateFunc: func(createEvent event.CreateEvent) bool { + return len(createEvent.Object.GetAnnotations()[r.config.ExpirationLabel]) != 0 + }, + DeleteFunc: func(deleteEvent event.DeleteEvent) bool { + return len(deleteEvent.Object.GetAnnotations()[r.config.ExpirationLabel]) != 0 + }, + UpdateFunc: func(updateEvent event.UpdateEvent) bool { + oldMayFlyAnnotation := updateEvent.ObjectOld.GetAnnotations()[r.config.ExpirationLabel] + newMayFlyAnnotation := updateEvent.ObjectNew.GetAnnotations()[r.config.ExpirationLabel] + + return len(newMayFlyAnnotation) != 0 && oldMayFlyAnnotation != newMayFlyAnnotation + }, + }).Complete(r) +} diff --git a/pkg/controllers/scheduled_resource.go b/pkg/controllers/scheduled_resource.go new file mode 100644 index 0000000..5b025f2 --- /dev/null +++ b/pkg/controllers/scheduled_resource.go @@ -0,0 +1,113 @@ +package controllers + +import ( + "context" + errors2 "errors" + "fmt" + "time" + + "k8s.io/apimachinery/pkg/api/errors" + + "github.com/NCCloud/mayfly/pkg/apis/v1alpha1" + "github.com/NCCloud/mayfly/pkg/common" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +type ScheduledResourceController struct { + config *common.Config + client client.Client + scheduler *common.Scheduler +} + +func NewScheduledResourceController(config *common.Config, client client.Client, + scheduler *common.Scheduler, +) *ScheduledResourceController { + return &ScheduledResourceController{ + config: config, + client: client, + scheduler: scheduler, + } +} + +func (r *ScheduledResourceController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var ( + logger = log.FromContext(ctx) + scheduledResource = &v1alpha1.ScheduledResource{} + tag = fmt.Sprintf("v1alpha1/ScheduledResource/%s/%s/create", req.Name, req.Namespace) + ) + + scheduledResource.GroupVersionKind().ToAPIVersionAndKind() + + logger.Info("Reconciliation started.") + defer logger.Info("Reconciliation finished.") + + if getErr := r.client.Get(ctx, req.NamespacedName, scheduledResource); getErr != nil { + if errors.IsNotFound(getErr) { + _ = r.scheduler.DeleteTask(tag) + } + + return ctrl.Result{}, client.IgnoreNotFound(getErr) + } + + if scheduledResource.Status.Condition == v1alpha1.ConditionCreated { + return ctrl.Result{}, nil + } + + duration, parseDurationErr := time.ParseDuration(scheduledResource.Spec.In) + if parseDurationErr != nil { + logger.Error(parseDurationErr, "Error while parsing duration.") + + return ctrl.Result{}, parseDurationErr + } + + if createOrUpdateTaskErr := r.scheduler.CreateOrUpdateTask(tag, scheduledResource.CreationTimestamp.Add(duration), + func() error { + content, contentErr := scheduledResource.GetContent() + if contentErr != nil { + logger.Error(contentErr, "Error while parsing content.") + + return contentErr + } + + if getErr := r.client.Get(context.Background(), client. + ObjectKeyFromObject(scheduledResource), scheduledResource); client.IgnoreNotFound(getErr) != nil { + logger.Error(contentErr, "Error while getting resource.") + + return getErr + } + + if createErr := r.client.Create(context.Background(), + content); client.IgnoreAlreadyExists(createErr) != nil { + logger.Error(contentErr, "An error occurred while creating resource.") + + scheduledResource.Status.Condition = v1alpha1.ConditionFailed + + return errors2.Join(createErr, r.client.Status().Update(context.Background(), scheduledResource)) + } + + logger.Info(fmt.Sprintf("%s created.", tag)) + + _ = r.scheduler.DeleteTask(tag) + + scheduledResource.Status.Condition = v1alpha1.ConditionCreated + + return r.client.Status().Update(context.Background(), scheduledResource) + }); createOrUpdateTaskErr != nil { + logger.Error(createOrUpdateTaskErr, "Error while creating or updating task.") + + return ctrl.Result{}, createOrUpdateTaskErr + } + + scheduledResource.Status.Condition = v1alpha1.ConditionScheduled + + return ctrl.Result{}, r.client.Status().Update(ctx, scheduledResource) +} + +func (r *ScheduledResourceController) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.ScheduledResource{}). + Complete(r) +} diff --git a/pkg/scheduler.go b/pkg/scheduler.go deleted file mode 100644 index 232697d..0000000 --- a/pkg/scheduler.go +++ /dev/null @@ -1,85 +0,0 @@ -package pkg - -import ( - "context" - "fmt" - "time" - - "github.com/go-co-op/gocron" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type Scheduler struct { - Config *Config - Client client.Client - CronScheduler *gocron.Scheduler -} - -func NewScheduler(config *Config, client client.Client) *Scheduler { - cronScheduler := gocron.NewScheduler(time.UTC) - cronScheduler.StartAsync() - - scheduler := &Scheduler{ - Config: config, - Client: client, - CronScheduler: cronScheduler, - } - - scheduler.startMonitoring() - - return scheduler -} - -func (s *Scheduler) startMonitoring() { - const granularity = 5 * time.Second - - go func() { - for { - exportMayflyTotalJobsMetrics(float64(len(s.CronScheduler.Jobs()))) - - pastJobs := 0 - - for _, job := range s.CronScheduler.Jobs() { - if job.NextRun().Before(time.Now()) { - pastJobs++ - } - } - - exportMayflyPastJobsMetrics(float64(pastJobs)) - time.Sleep(granularity) - } - }() -} - -func (s *Scheduler) StartOrUpdateJob(ctx context.Context, expirationDate time.Time, - task func(ctx context.Context, client client.Client, resource client.Object) error, - client client.Client, resource client.Object, -) error { - jobs, _ := s.CronScheduler.FindJobsByTag(fmt.Sprintf("%v", resource.GetUID())) - - if len(jobs) > 0 { - jobExpirationDate := jobs[0].NextRun() - if jobExpirationDate.Equal(expirationDate) { - return nil - } - - _ = s.RemoveJob(fmt.Sprintf("%v", resource.GetUID())) - } - - _, jobErr := s.CronScheduler. - Every(1). - LimitRunsTo(1). - StartAt(expirationDate). - Tag(fmt.Sprintf("%v", resource.GetUID())). - Do(task, ctx, client, resource) - - if jobErr != nil { - return jobErr - } - - return nil -} - -func (s *Scheduler) RemoveJob(id string) error { - return s.CronScheduler.RemoveByTag(id) -}