From 227848a59dacf1a575c8863ba0e0f5fff2f1ac38 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 9 Jan 2025 12:24:51 +0100 Subject: [PATCH] Introduce cozystack-controller (#560) ## Summary by CodeRabbit Based on the comprehensive summary of changes, here are the release notes: - **New Features** - Added a new Kubernetes controller for managing workload monitoring - Introduced telemetry collection capabilities with configurable options - Added new Custom Resource Definitions (CRDs) for Workload and WorkloadMonitor - **Improvements** - Enhanced API infrastructure with new API group and version - Improved deployment configurations for various system components - Added development container and workflow configurations - **Bug Fixes** - Updated import paths to correct domain naming - **Chores** - Updated copyright years - Refined module dependencies - Standardized code linting and testing configurations - **Infrastructure** - Increased `cozystack-api` deployment replicas from 1 to 2 for improved availability --------- Signed-off-by: Andrei Kvapil --- Makefile | 3 + .../cozystack_api_violation_exceptions.list | 2 +- api/v1alpha1/groupversion_info.go | 36 +++ api/v1alpha1/workload_types.go | 70 +++++ api/v1alpha1/workloadmonitor_types.go | 91 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 238 ++++++++++++++ cmd/cozystack-api/main.go | 2 +- cmd/cozystack-controller/main.go | 210 +++++++++++++ go.mod | 24 +- go.sum | 12 +- hack/boilerplate.go.txt | 2 +- hack/update-codegen.sh | 4 + internal/controller/suite_test.go | 96 ++++++ .../controller/workloadmonitor_controller.go | 273 ++++++++++++++++ internal/telemetry/collector.go | 292 ++++++++++++++++++ internal/telemetry/config.go | 27 ++ .../core/platform/bundles/distro-full.yaml | 11 + .../core/platform/bundles/distro-hosted.yaml | 11 + packages/core/platform/bundles/paas-full.yaml | 11 + .../core/platform/bundles/paas-hosted.yaml | 11 + .../images/cozystack-api/Dockerfile | 3 +- .../cozystack-api/templates/deployment.yaml | 2 +- .../system/cozystack-controller/.gitignore | 27 ++ .../system/cozystack-controller/Chart.yaml | 3 + packages/system/cozystack-controller/Makefile | 24 ++ .../images/cozystack-controller/Dockerfile | 20 ++ .../crds/cozystack.io_workloadmonitors.yaml | 117 +++++++ .../crds/cozystack.io_workloads.yaml | 88 ++++++ .../templates/deployment.yaml | 31 ++ .../templates/rbac-bind.yaml | 12 + .../cozystack-controller/templates/rbac.yaml | 11 + .../cozystack-controller/templates/sa.yaml | 5 + .../system/cozystack-controller/values.yaml | 5 + pkg/apis/apps/fuzzer/fuzzer.go | 2 +- pkg/apis/apps/install/install.go | 2 +- pkg/apis/apps/install/roundtrip_test.go | 2 +- pkg/apis/apps/v1alpha1/doc.go | 4 +- pkg/apis/apps/v1alpha1/register.go | 2 +- .../apps/v1alpha1/zz_generated.conversion.go | 2 +- .../apps/v1alpha1/zz_generated.deepcopy.go | 2 +- .../apps/v1alpha1/zz_generated.defaults.go | 2 +- pkg/apis/apps/validation/validation.go | 2 +- pkg/apiserver/apiserver.go | 10 +- pkg/apiserver/scheme_test.go | 2 +- pkg/cmd/server/start.go | 8 +- .../apps/v1alpha1/application.go | 2 +- pkg/generated/applyconfiguration/utils.go | 6 +- .../listers/apps/v1alpha1/application.go | 2 +- pkg/generated/openapi/zz_generated.openapi.go | 16 +- pkg/registry/apps/application/rest.go | 4 +- pkg/registry/registry.go | 2 +- 51 files changed, 1789 insertions(+), 57 deletions(-) create mode 100644 api/v1alpha1/groupversion_info.go create mode 100644 api/v1alpha1/workload_types.go create mode 100644 api/v1alpha1/workloadmonitor_types.go create mode 100644 api/v1alpha1/zz_generated.deepcopy.go create mode 100644 cmd/cozystack-controller/main.go create mode 100644 internal/controller/suite_test.go create mode 100644 internal/controller/workloadmonitor_controller.go create mode 100644 internal/telemetry/collector.go create mode 100644 internal/telemetry/config.go create mode 100644 packages/system/cozystack-controller/.gitignore create mode 100644 packages/system/cozystack-controller/Chart.yaml create mode 100644 packages/system/cozystack-controller/Makefile create mode 100644 packages/system/cozystack-controller/images/cozystack-controller/Dockerfile create mode 100644 packages/system/cozystack-controller/templates/crds/cozystack.io_workloadmonitors.yaml create mode 100644 packages/system/cozystack-controller/templates/crds/cozystack.io_workloads.yaml create mode 100644 packages/system/cozystack-controller/templates/deployment.yaml create mode 100644 packages/system/cozystack-controller/templates/rbac-bind.yaml create mode 100644 packages/system/cozystack-controller/templates/rbac.yaml create mode 100644 packages/system/cozystack-controller/templates/sa.yaml create mode 100644 packages/system/cozystack-controller/values.yaml diff --git a/Makefile b/Makefile index 89c3b638..ee4a29e2 100644 --- a/Makefile +++ b/Makefile @@ -37,3 +37,6 @@ test: make -C packages/core/testing apply make -C packages/core/testing test make -C packages/core/testing test-applications + +generate: + hack/update-codegen.sh diff --git a/api/api-rules/cozystack_api_violation_exceptions.list b/api/api-rules/cozystack_api_violation_exceptions.list index fc2d804c..5c65e849 100644 --- a/api/api-rules/cozystack_api_violation_exceptions.list +++ b/api/api-rules/cozystack_api_violation_exceptions.list @@ -1,4 +1,4 @@ -API rule violation: list_type_missing,github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1,ApplicationStatus,Conditions +API rule violation: list_type_missing,github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1,ApplicationStatus,Conditions API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Ref API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Schema API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XEmbeddedResource diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go new file mode 100644 index 00000000..1dd84ee1 --- /dev/null +++ b/api/v1alpha1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2025. + +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 v1alpha1 contains API Schema definitions for the v1alpha1 API group. +// +kubebuilder:object:generate=true +// +groupName=cozystack.io +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: "cozystack.io", 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/api/v1alpha1/workload_types.go b/api/v1alpha1/workload_types.go new file mode 100644 index 00000000..41bd9eda --- /dev/null +++ b/api/v1alpha1/workload_types.go @@ -0,0 +1,70 @@ +/* +Copyright 2025. + +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 v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// WorkloadStatus defines the observed state of Workload +type WorkloadStatus struct { + // Kind represents the type of workload (redis, postgres, etc.) + // +required + Kind string `json:"kind"` + + // Type represents the specific role of the workload (redis, sentinel, etc.) + // If not specified, defaults to Kind + // +optional + Type string `json:"type,omitempty"` + + // Resources specifies the compute resources allocated to this workload + // +required + Resources map[string]resource.Quantity `json:"resources"` + + // Operational indicates if all pods of the workload are ready + // +optional + Operational bool `json:"operational"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:printcolumn:name="Kind",type="string",JSONPath=".status.kind" +// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".status.type" +// +kubebuilder:printcolumn:name="CPU",type="string",JSONPath=".status.resources.cpu" +// +kubebuilder:printcolumn:name="Memory",type="string",JSONPath=".status.resources.memory" +// +kubebuilder:printcolumn:name="Operational",type="boolean",JSONPath=`.status.operational` + +// Workload is the Schema for the workloads API +type Workload struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Status WorkloadStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// WorkloadList contains a list of Workload +type WorkloadList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Workload `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Workload{}, &WorkloadList{}) +} diff --git a/api/v1alpha1/workloadmonitor_types.go b/api/v1alpha1/workloadmonitor_types.go new file mode 100644 index 00000000..e7639edb --- /dev/null +++ b/api/v1alpha1/workloadmonitor_types.go @@ -0,0 +1,91 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// WorkloadMonitorSpec defines the desired state of WorkloadMonitor +type WorkloadMonitorSpec struct { + // Selector is a label selector to find workloads to monitor + // +required + Selector map[string]string `json:"selector"` + + // Kind specifies the kind of the workload + // +optional + Kind string `json:"kind,omitempty"` + + // Type specifies the type of the workload + // +optional + Type string `json:"type,omitempty"` + + // Version specifies the version of the workload + // +optional + Version string `json:"version,omitempty"` + + // MinReplicas specifies the minimum number of replicas that should be available + // +kubebuilder:validation:Minimum=0 + // +optional + MinReplicas *int32 `json:"minReplicas,omitempty"` + + // Replicas is the desired number of replicas + // If not specified, will use observedReplicas as the target + // +kubebuilder:validation:Minimum=0 + // +optional + Replicas *int32 `json:"replicas,omitempty"` +} + +// WorkloadMonitorStatus defines the observed state of WorkloadMonitor +type WorkloadMonitorStatus struct { + // Operational indicates if the workload meets all operational requirements + // +optional + Operational *bool `json:"operational,omitempty"` + + // AvailableReplicas is the number of ready replicas + // +optional + AvailableReplicas int32 `json:"availableReplicas"` + + // ObservedReplicas is the total number of pods observed + // +optional + ObservedReplicas int32 `json:"observedReplicas"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="Kind",type="string",JSONPath=".spec.kind" +// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.type" +// +kubebuilder:printcolumn:name="Version",type="string",JSONPath=".spec.version" +// +kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".spec.replicas" +// +kubebuilder:printcolumn:name="MinReplicas",type="integer",JSONPath=".spec.minReplicas" +// +kubebuilder:printcolumn:name="Available",type="integer",JSONPath=".status.availableReplicas" +// +kubebuilder:printcolumn:name="Observed",type="integer",JSONPath=".status.observedReplicas" +// +kubebuilder:printcolumn:name="Operational",type="boolean",JSONPath=".status.operational" + +// WorkloadMonitor is the Schema for the workloadmonitors API +type WorkloadMonitor struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec WorkloadMonitorSpec `json:"spec,omitempty"` + Status WorkloadMonitorStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// WorkloadMonitorList contains a list of WorkloadMonitor +type WorkloadMonitorList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []WorkloadMonitor `json:"items"` +} + +func init() { + SchemeBuilder.Register(&WorkloadMonitor{}, &WorkloadMonitorList{}) +} + +// GetSelector returns the label selector from metadata +func (w *WorkloadMonitor) GetSelector() map[string]string { + return w.Spec.Selector +} + +// Selector specifies the label selector for workloads +type Selector map[string]string diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000..f510e197 --- /dev/null +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,238 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2025 The Cozystack Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/api/resource" + 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 Selector) DeepCopyInto(out *Selector) { + { + in := &in + *out = make(Selector, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Selector. +func (in Selector) DeepCopy() Selector { + if in == nil { + return nil + } + out := new(Selector) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Workload) DeepCopyInto(out *Workload) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Workload. +func (in *Workload) DeepCopy() *Workload { + if in == nil { + return nil + } + out := new(Workload) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Workload) 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 *WorkloadList) DeepCopyInto(out *WorkloadList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Workload, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadList. +func (in *WorkloadList) DeepCopy() *WorkloadList { + if in == nil { + return nil + } + out := new(WorkloadList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WorkloadList) 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 *WorkloadMonitor) DeepCopyInto(out *WorkloadMonitor) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadMonitor. +func (in *WorkloadMonitor) DeepCopy() *WorkloadMonitor { + if in == nil { + return nil + } + out := new(WorkloadMonitor) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WorkloadMonitor) 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 *WorkloadMonitorList) DeepCopyInto(out *WorkloadMonitorList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]WorkloadMonitor, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadMonitorList. +func (in *WorkloadMonitorList) DeepCopy() *WorkloadMonitorList { + if in == nil { + return nil + } + out := new(WorkloadMonitorList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *WorkloadMonitorList) 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 *WorkloadMonitorSpec) DeepCopyInto(out *WorkloadMonitorSpec) { + *out = *in + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.MinReplicas != nil { + in, out := &in.MinReplicas, &out.MinReplicas + *out = new(int32) + **out = **in + } + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadMonitorSpec. +func (in *WorkloadMonitorSpec) DeepCopy() *WorkloadMonitorSpec { + if in == nil { + return nil + } + out := new(WorkloadMonitorSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkloadMonitorStatus) DeepCopyInto(out *WorkloadMonitorStatus) { + *out = *in + if in.Operational != nil { + in, out := &in.Operational, &out.Operational + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadMonitorStatus. +func (in *WorkloadMonitorStatus) DeepCopy() *WorkloadMonitorStatus { + if in == nil { + return nil + } + out := new(WorkloadMonitorStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkloadStatus) DeepCopyInto(out *WorkloadStatus) { + *out = *in + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make(map[string]resource.Quantity, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkloadStatus. +func (in *WorkloadStatus) DeepCopy() *WorkloadStatus { + if in == nil { + return nil + } + out := new(WorkloadStatus) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/cozystack-api/main.go b/cmd/cozystack-api/main.go index dfd2eeb5..5c743da1 100644 --- a/cmd/cozystack-api/main.go +++ b/cmd/cozystack-api/main.go @@ -19,7 +19,7 @@ package main import ( "os" - "github.com/aenix.io/cozystack/pkg/cmd/server" + "github.com/aenix-io/cozystack/pkg/cmd/server" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/component-base/cli" ) diff --git a/cmd/cozystack-controller/main.go b/cmd/cozystack-controller/main.go new file mode 100644 index 00000000..04befbfa --- /dev/null +++ b/cmd/cozystack-controller/main.go @@ -0,0 +1,210 @@ +/* +Copyright 2025. + +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 main + +import ( + "crypto/tls" + "flag" + "os" + "time" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + cozystackiov1alpha1 "github.com/aenix-io/cozystack/api/v1alpha1" + "github.com/aenix-io/cozystack/internal/controller" + "github.com/aenix-io/cozystack/internal/telemetry" + // +kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(cozystackiov1alpha1.AddToScheme(scheme)) + // +kubebuilder:scaffold:scheme +} + +func main() { + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + var secureMetrics bool + var enableHTTP2 bool + var disableTelemetry bool + var telemetryEndpoint string + var telemetryInterval string + var cozystackVersion string + var tlsOpts []func(*tls.Config) + flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ + "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + flag.BoolVar(&secureMetrics, "metrics-secure", true, + "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") + flag.BoolVar(&enableHTTP2, "enable-http2", false, + "If set, HTTP/2 will be enabled for the metrics and webhook servers") + flag.BoolVar(&disableTelemetry, "disable-telemetry", false, + "Disable telemetry collection") + flag.StringVar(&telemetryEndpoint, "telemetry-endpoint", "https://telemetry.cozystack.io", + "Endpoint for sending telemetry data") + flag.StringVar(&telemetryInterval, "telemetry-interval", "15m", + "Interval between telemetry data collection (e.g. 15m, 1h)") + flag.StringVar(&cozystackVersion, "cozystack-version", "unknown", + "Version of Cozystack") + opts := zap.Options{ + Development: false, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + // Parse telemetry interval + interval, err := time.ParseDuration(telemetryInterval) + if err != nil { + setupLog.Error(err, "invalid telemetry interval") + os.Exit(1) + } + + // Configure telemetry + telemetryConfig := telemetry.Config{ + Disabled: disableTelemetry, + Endpoint: telemetryEndpoint, + Interval: interval, + CozystackVersion: cozystackVersion, + } + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + // if the enable-http2 flag is false (the default), http/2 should be disabled + // due to its vulnerabilities. More specifically, disabling http/2 will + // prevent from being vulnerable to the HTTP/2 Stream Cancellation and + // Rapid Reset CVEs. For more information see: + // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 + // - https://github.com/advisories/GHSA-4374-p667-p6c8 + disableHTTP2 := func(c *tls.Config) { + setupLog.Info("disabling http/2") + c.NextProtos = []string{"http/1.1"} + } + + if !enableHTTP2 { + tlsOpts = append(tlsOpts, disableHTTP2) + } + + webhookServer := webhook.NewServer(webhook.Options{ + TLSOpts: tlsOpts, + }) + + // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. + // More info: + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/server + // - https://book.kubebuilder.io/reference/metrics.html + metricsServerOptions := metricsserver.Options{ + BindAddress: metricsAddr, + SecureServing: secureMetrics, + TLSOpts: tlsOpts, + } + + if secureMetrics { + // FilterProvider is used to protect the metrics endpoint with authn/authz. + // These configurations ensure that only authorized users and service accounts + // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/filters#WithAuthenticationAndAuthorization + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization + + // TODO(user): If CertDir, CertName, and KeyName are not specified, controller-runtime will automatically + // generate self-signed certificates for the metrics server. While convenient for development and testing, + // this setup is not recommended for production. + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + Metrics: metricsServerOptions, + WebhookServer: webhookServer, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "19a0338c.cozystack.io", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + if err = (&controller.WorkloadMonitorReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "WorkloadMonitor") + os.Exit(1) + } + // +kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + // Initialize telemetry collector + collector, err := telemetry.NewCollector(mgr.GetClient(), &telemetryConfig, mgr.GetConfig()) + if err != nil { + setupLog.V(1).Error(err, "unable to create telemetry collector, telemetry will be disabled") + } + + if collector != nil { + if err := mgr.Add(collector); err != nil { + setupLog.Error(err, "unable to set up telemetry collector") + setupLog.V(1).Error(err, "unable to set up telemetry collector, continuing without telemetry") + } + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} diff --git a/go.mod b/go.mod index 103f0929..a9bbce89 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,27 @@ // This is a generated file. Do not edit directly. -module github.com/aenix.io/cozystack +module github.com/aenix-io/cozystack go 1.23.0 require ( - github.com/emicklei/go-restful/v3 v3.11.0 + github.com/fluxcd/helm-controller/api v1.1.0 github.com/google/gofuzz v1.2.0 + github.com/onsi/ginkgo/v2 v2.19.0 + github.com/onsi/gomega v1.33.1 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 + gopkg.in/yaml.v2 v2.4.0 + k8s.io/api v0.31.2 k8s.io/apiextensions-apiserver v0.31.2 k8s.io/apimachinery v0.31.2 k8s.io/apiserver v0.31.2 k8s.io/client-go v0.31.2 - k8s.io/code-generator v0.31.2 k8s.io/component-base v0.31.2 + k8s.io/klog/v2 v2.130.1 k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 + sigs.k8s.io/controller-runtime v0.19.0 sigs.k8s.io/structured-merge-diff/v4 v4.4.1 ) @@ -31,23 +36,27 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // 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/evanphx/json-patch/v5 v5.9.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fluxcd/helm-controller/api v1.1.0 // indirect github.com/fluxcd/pkg/apis/kustomize v1.6.1 // indirect github.com/fluxcd/pkg/apis/meta v1.6.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/cel-go v0.21.0 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect @@ -84,7 +93,6 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect @@ -93,6 +101,7 @@ require ( golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/grpc v1.65.0 // indirect @@ -100,14 +109,9 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.31.2 // indirect - k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect - k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kms v0.31.2 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect - sigs.k8s.io/controller-runtime v0.19.0 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index a75e23ba..382420fe 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,10 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 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 v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluxcd/helm-controller/api v1.1.0 h1:NS5Wm3U6Kv4w7Cw2sDOV++vf2ecGfFV00x1+2Y3QcOY= @@ -214,8 +218,6 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -252,6 +254,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= @@ -287,12 +291,8 @@ k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= -k8s.io/code-generator v0.31.2 h1:xLWxG0HEpMSHfcM//3u3Ro2Hmc6AyyLINQS//Z2GEOI= -k8s.io/code-generator v0.31.2/go.mod h1:eEQHXgBU/m7LDaToDoiz3t97dUUVyOblQdwOr8rivqc= k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= -k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4= -k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kms v0.31.2 h1:pyx7l2qVOkClzFMIWMVF/FxsSkgd+OIGH7DecpbscJI= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index b10e59a5..4b746998 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2024 The Cozystack Authors. +Copyright 2025 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index b56d0799..a2a3b581 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -22,6 +22,7 @@ SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)} API_KNOWN_VIOLATIONS_DIR="${API_KNOWN_VIOLATIONS_DIR:-"${SCRIPT_ROOT}/api/api-rules"}" UPDATE_API_KNOWN_VIOLATIONS="${UPDATE_API_KNOWN_VIOLATIONS:-true}" +CONTROLLER_GEN="go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.4" source "${CODEGEN_PKG}/kube_codegen.sh" @@ -46,3 +47,6 @@ kube::codegen::gen_openapi \ ${update_report:+"${update_report}"} \ --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ "${SCRIPT_ROOT}/pkg/apis" + +$CONTROLLER_GEN object:headerFile="hack/boilerplate.go.txt" paths="./api/..." +$CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config=packages/system/cozystack-controller/templates/crds diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go new file mode 100644 index 00000000..b5db051b --- /dev/null +++ b/internal/controller/suite_test.go @@ -0,0 +1,96 @@ +/* +Copyright 2025. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "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" + + cozystackiov1alpha1 "github.com/aenix-io/cozystack/api/v1alpha1" + // +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 +var ctx context.Context +var cancel context.CancelFunc + +func TestControllers(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + 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.31.0-%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 = cozystackiov1alpha1.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") + cancel() + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/internal/controller/workloadmonitor_controller.go b/internal/controller/workloadmonitor_controller.go new file mode 100644 index 00000000..a6096c1a --- /dev/null +++ b/internal/controller/workloadmonitor_controller.go @@ -0,0 +1,273 @@ +package controller + +import ( + "context" + "encoding/json" + "sort" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/pointer" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + cozyv1alpha1 "github.com/aenix-io/cozystack/api/v1alpha1" +) + +// WorkloadMonitorReconciler reconciles a WorkloadMonitor object +type WorkloadMonitorReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=cozystack.io,resources=workloadmonitors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=cozystack.io,resources=workloadmonitors/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=cozystack.io,resources=workloads,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=cozystack.io,resources=workloads/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch + +// isPodReady checks if the Pod is in the Ready condition. +func (r *WorkloadMonitorReconciler) isPodReady(pod *corev1.Pod) bool { + for _, c := range pod.Status.Conditions { + if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue { + return true + } + } + return false +} + +// updateOwnerReferences adds the given monitor as a new owner reference to the object if not already present. +// It then sorts the owner references to enforce a consistent order. +func updateOwnerReferences(obj metav1.Object, monitor client.Object) { + // Retrieve current owner references + owners := obj.GetOwnerReferences() + + // Check if current monitor is already in owner references + var alreadyOwned bool + for _, ownerRef := range owners { + if ownerRef.UID == monitor.GetUID() { + alreadyOwned = true + break + } + } + + runtimeObj, ok := monitor.(runtime.Object) + if !ok { + return + } + gvk := runtimeObj.GetObjectKind().GroupVersionKind() + + // If not already present, add new owner reference without controller flag + if !alreadyOwned { + newOwnerRef := metav1.OwnerReference{ + APIVersion: gvk.GroupVersion().String(), + Kind: gvk.Kind, + Name: monitor.GetName(), + UID: monitor.GetUID(), + // Set Controller to false to avoid conflict as multiple controllers are not allowed + Controller: pointer.BoolPtr(false), + BlockOwnerDeletion: pointer.BoolPtr(true), + } + owners = append(owners, newOwnerRef) + } + + // Sort owner references to enforce a consistent order by UID + sort.SliceStable(owners, func(i, j int) bool { + return owners[i].UID < owners[j].UID + }) + + // Update the owner references of the object + obj.SetOwnerReferences(owners) +} + +// reconcilePodForMonitor creates or updates a Workload object for the given Pod and WorkloadMonitor. +func (r *WorkloadMonitorReconciler) reconcilePodForMonitor( + ctx context.Context, + monitor *cozyv1alpha1.WorkloadMonitor, + pod corev1.Pod, +) error { + logger := log.FromContext(ctx) + + // Combine both init containers and normal containers to sum resources properly + combinedContainers := append(pod.Spec.InitContainers, pod.Spec.Containers...) + + // totalResources will store the sum of all container resource limits + totalResources := make(map[string]resource.Quantity) + + // Iterate over all containers to aggregate their Limits + for _, container := range combinedContainers { + for name, qty := range container.Resources.Limits { + if existing, exists := totalResources[name.String()]; exists { + existing.Add(qty) + totalResources[name.String()] = existing + } else { + totalResources[name.String()] = qty.DeepCopy() + } + } + } + + // If annotation "workload.cozystack.io/resources" is present, parse and merge + if resourcesStr, ok := pod.Annotations["workload.cozystack.io/resources"]; ok { + annRes := map[string]string{} + if err := json.Unmarshal([]byte(resourcesStr), &annRes); err != nil { + logger.Error(err, "Failed to parse resources annotation", "pod", pod.Name) + } else { + for k, v := range annRes { + parsed, err := resource.ParseQuantity(v) + if err != nil { + logger.Error(err, "Failed to parse resource quantity from annotation", "key", k, "value", v) + continue + } + totalResources[k] = parsed + } + } + } + + workload := &cozyv1alpha1.Workload{ + ObjectMeta: metav1.ObjectMeta{ + Name: pod.Name, + Namespace: pod.Namespace, + }, + } + + _, err := ctrl.CreateOrUpdate(ctx, r.Client, workload, func() error { + // Update owner references with the new monitor + updateOwnerReferences(workload.GetObjectMeta(), monitor) + + // Copy labels from the Pod if needed + workload.Labels = pod.Labels + + // Fill Workload status fields: + workload.Status.Kind = monitor.Spec.Kind + workload.Status.Type = monitor.Spec.Type + workload.Status.Resources = totalResources + workload.Status.Operational = r.isPodReady(&pod) + + return nil + }) + if err != nil { + logger.Error(err, "Failed to CreateOrUpdate Workload", "workload", workload.Name) + return err + } + + return nil +} + +// Reconcile is the main reconcile loop. +// 1. It reconciles WorkloadMonitor objects themselves (create/update/delete). +// 2. It also reconciles Pod events mapped to WorkloadMonitor via label selector. +func (r *WorkloadMonitorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx) + + // Fetch the WorkloadMonitor object if it exists + monitor := &cozyv1alpha1.WorkloadMonitor{} + err := r.Get(ctx, req.NamespacedName, monitor) + if err != nil { + // If the resource is not found, it may be a Pod event (mapFunc). + if apierrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + logger.Error(err, "Unable to fetch WorkloadMonitor") + return ctrl.Result{}, err + } + + // List Pods that match the WorkloadMonitor's selector + podList := &corev1.PodList{} + if err := r.List( + ctx, + podList, + client.InNamespace(monitor.Namespace), + client.MatchingLabels(monitor.Spec.Selector), + ); err != nil { + logger.Error(err, "Unable to list Pods for WorkloadMonitor", "monitor", monitor.Name) + return ctrl.Result{}, err + } + + var observedReplicas, availableReplicas int32 + + // For each matching Pod, reconcile the corresponding Workload + for _, pod := range podList.Items { + observedReplicas++ + if err := r.reconcilePodForMonitor(ctx, monitor, pod); err != nil { + logger.Error(err, "Failed to reconcile Workload for Pod", "pod", pod.Name) + continue + } + if r.isPodReady(&pod) { + availableReplicas++ + } + } + + // Update WorkloadMonitor status based on observed pods + monitor.Status.ObservedReplicas = observedReplicas + monitor.Status.AvailableReplicas = availableReplicas + + // Default to operational = true, but check MinReplicas if set + monitor.Status.Operational = pointer.Bool(true) + if monitor.Spec.MinReplicas != nil && availableReplicas < *monitor.Spec.MinReplicas { + monitor.Status.Operational = pointer.Bool(false) + } + + // Update the WorkloadMonitor status in the cluster + if err := r.Status().Update(ctx, monitor); err != nil { + logger.Error(err, "Unable to update WorkloadMonitor status", "monitor", monitor.Name) + return ctrl.Result{}, err + } + + // Return without requeue if we want purely event-driven reconciliations + return ctrl.Result{}, nil +} + +// SetupWithManager registers our controller with the Manager and sets up watches. +func (r *WorkloadMonitorReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + // Watch WorkloadMonitor objects + For(&cozyv1alpha1.WorkloadMonitor{}). + // Also watch Pod objects and map them back to WorkloadMonitor if labels match + Watches( + &corev1.Pod{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + pod, ok := obj.(*corev1.Pod) + if !ok { + return nil + } + + var monitorList cozyv1alpha1.WorkloadMonitorList + // List all WorkloadMonitors in the same namespace + if err := r.List(ctx, &monitorList, client.InNamespace(pod.Namespace)); err != nil { + return nil + } + + // Match each monitor's selector with the Pod's labels + var requests []reconcile.Request + for _, m := range monitorList.Items { + matches := true + for k, v := range m.Spec.Selector { + if podVal, exists := pod.Labels[k]; !exists || podVal != v { + matches = false + break + } + } + if matches { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: m.Namespace, + Name: m.Name, + }, + }) + } + } + return requests + }), + ). + // Watch for changes to Workload objects we create (owned by WorkloadMonitor) + Owns(&cozyv1alpha1.Workload{}). + Complete(r) +} diff --git a/internal/telemetry/collector.go b/internal/telemetry/collector.go new file mode 100644 index 00000000..7c542961 --- /dev/null +++ b/internal/telemetry/collector.go @@ -0,0 +1,292 @@ +package telemetry + +import ( + "bytes" + "context" + "fmt" + "net/http" + "strings" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/discovery" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + cozyv1alpha1 "github.com/aenix-io/cozystack/api/v1alpha1" +) + +// Collector handles telemetry data collection and sending +type Collector struct { + client client.Client + discoveryClient discovery.DiscoveryInterface + config *Config + ticker *time.Ticker + stopCh chan struct{} +} + +// NewCollector creates a new telemetry collector +func NewCollector(client client.Client, config *Config, kubeConfig *rest.Config) (*Collector, error) { + discoveryClient, err := discovery.NewDiscoveryClientForConfig(kubeConfig) + if err != nil { + return nil, fmt.Errorf("failed to create discovery client: %w", err) + } + return &Collector{ + client: client, + discoveryClient: discoveryClient, + config: config, + }, nil +} + +// Start implements manager.Runnable +func (c *Collector) Start(ctx context.Context) error { + if c.config.Disabled { + return nil + } + + c.ticker = time.NewTicker(c.config.Interval) + c.stopCh = make(chan struct{}) + + // Initial collection + c.collect(ctx) + + for { + select { + case <-ctx.Done(): + c.ticker.Stop() + close(c.stopCh) + return nil + case <-c.ticker.C: + c.collect(ctx) + } + } +} + +// NeedLeaderElection implements manager.LeaderElectionRunnable +func (c *Collector) NeedLeaderElection() bool { + // Only run telemetry collector on the leader + return true +} + +// Stop halts telemetry collection +func (c *Collector) Stop() { + close(c.stopCh) +} + +// getSizeGroup returns the exponential size group for PVC +func getSizeGroup(size resource.Quantity) string { + gb := size.Value() / (1024 * 1024 * 1024) + switch { + case gb <= 1: + return "1Gi" + case gb <= 5: + return "5Gi" + case gb <= 10: + return "10Gi" + case gb <= 25: + return "25Gi" + case gb <= 50: + return "50Gi" + case gb <= 100: + return "100Gi" + case gb <= 250: + return "250Gi" + case gb <= 500: + return "500Gi" + case gb <= 1024: + return "1Ti" + case gb <= 2048: + return "2Ti" + case gb <= 5120: + return "5Ti" + default: + return "10Ti" + } +} + +// collect gathers and sends telemetry data +func (c *Collector) collect(ctx context.Context) { + logger := log.FromContext(ctx).V(1) + + // Get cluster ID from kube-system namespace + var kubeSystemNS corev1.Namespace + if err := c.client.Get(ctx, types.NamespacedName{Name: "kube-system"}, &kubeSystemNS); err != nil { + logger.Info(fmt.Sprintf("Failed to get kube-system namespace: %v", err)) + return + } + + clusterID := string(kubeSystemNS.UID) + + var cozystackCM corev1.ConfigMap + if err := c.client.Get(ctx, types.NamespacedName{Namespace: "cozy-system", Name: "cozystack"}, &cozystackCM); err != nil { + logger.Info(fmt.Sprintf("Failed to get cozystack configmap in cozy-system namespace: %v", err)) + return + } + + oidcEnabled := cozystackCM.Data["oidc-enabled"] + bundle := cozystackCM.Data["bundle-name"] + bundleEnable := cozystackCM.Data["bundle-enable"] + bundleDisable := cozystackCM.Data["bundle-disable"] + + // Get Kubernetes version from nodes + var nodeList corev1.NodeList + if err := c.client.List(ctx, &nodeList); err != nil { + logger.Info(fmt.Sprintf("Failed to list nodes: %v", err)) + return + } + + // Create metrics buffer + var metrics strings.Builder + + // Add Cozystack info metric + if len(nodeList.Items) > 0 { + k8sVersion, _ := c.discoveryClient.ServerVersion() + metrics.WriteString(fmt.Sprintf( + "cozy_cluster_info{cozystack_version=\"%s\",kubernetes_version=\"%s\",oidc_enabled=\"%s\",bundle_name=\"%s\",bunde_enable=\"%s\",bunde_disable=\"%s\"} 1\n", + c.config.CozystackVersion, + k8sVersion, + oidcEnabled, + bundle, + bundleEnable, + bundleDisable, + )) + } + + // Collect node metrics + nodeOSCount := make(map[string]int) + for _, node := range nodeList.Items { + key := fmt.Sprintf("%s (%s)", node.Status.NodeInfo.OperatingSystem, node.Status.NodeInfo.OSImage) + nodeOSCount[key] = nodeOSCount[key] + 1 + } + + for osKey, count := range nodeOSCount { + metrics.WriteString(fmt.Sprintf( + "cozy_nodes_count{os=\"%s\",kernel=\"%s\"} %d\n", + osKey, + nodeList.Items[0].Status.NodeInfo.KernelVersion, + count, + )) + } + + // Collect LoadBalancer services metrics + var serviceList corev1.ServiceList + if err := c.client.List(ctx, &serviceList); err != nil { + logger.Info(fmt.Sprintf("Failed to list Services: %v", err)) + } else { + lbCount := 0 + for _, svc := range serviceList.Items { + if svc.Spec.Type == corev1.ServiceTypeLoadBalancer { + lbCount++ + } + } + metrics.WriteString(fmt.Sprintf("cozy_loadbalancers_count %d\n", lbCount)) + } + + // Count tenant namespaces + var nsList corev1.NamespaceList + if err := c.client.List(ctx, &nsList); err != nil { + logger.Info(fmt.Sprintf("Failed to list Namespaces: %v", err)) + } else { + tenantCount := 0 + for _, ns := range nsList.Items { + if strings.HasPrefix(ns.Name, "tenant-") { + tenantCount++ + } + } + metrics.WriteString(fmt.Sprintf("cozy_tenants_count %d\n", tenantCount)) + } + + // Collect PV metrics grouped by driver and size + var pvList corev1.PersistentVolumeList + if err := c.client.List(ctx, &pvList); err != nil { + logger.Info(fmt.Sprintf("Failed to list PVs: %v", err)) + } else { + // Map to store counts by size and driver + pvMetrics := make(map[string]map[string]int) + + for _, pv := range pvList.Items { + if capacity, ok := pv.Spec.Capacity[corev1.ResourceStorage]; ok { + sizeGroup := getSizeGroup(capacity) + + // Get the CSI driver name + driver := "unknown" + if pv.Spec.CSI != nil { + driver = pv.Spec.CSI.Driver + } else if pv.Spec.HostPath != nil { + driver = "hostpath" + } else if pv.Spec.NFS != nil { + driver = "nfs" + } + + // Initialize nested map if needed + if _, exists := pvMetrics[sizeGroup]; !exists { + pvMetrics[sizeGroup] = make(map[string]int) + } + + // Increment count for this size/driver combination + pvMetrics[sizeGroup][driver]++ + } + } + + // Write metrics + for size, drivers := range pvMetrics { + for driver, count := range drivers { + metrics.WriteString(fmt.Sprintf( + "cozy_pvs_count{driver=\"%s\",size=\"%s\"} %d\n", + driver, + size, + count, + )) + } + } + } + + // Collect workload metrics + var monitorList cozyv1alpha1.WorkloadMonitorList + if err := c.client.List(ctx, &monitorList); err != nil { + logger.Info(fmt.Sprintf("Failed to list WorkloadMonitors: %v", err)) + return + } + + for _, monitor := range monitorList.Items { + metrics.WriteString(fmt.Sprintf( + "cozy_workloads_count{uid=\"%s\",kind=\"%s\",type=\"%s\",version=\"%s\"} %d\n", + monitor.UID, + monitor.Spec.Kind, + monitor.Spec.Type, + monitor.Spec.Version, + monitor.Status.ObservedReplicas, + )) + } + + // Send metrics + if err := c.sendMetrics(clusterID, metrics.String()); err != nil { + logger.Info(fmt.Sprintf("Failed to send metrics: %v", err)) + } +} + +// sendMetrics sends collected metrics to the configured endpoint +func (c *Collector) sendMetrics(clusterID, metrics string) error { + req, err := http.NewRequest("POST", c.config.Endpoint, bytes.NewBufferString(metrics)) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "text/plain") + req.Header.Set("X-Cluster-ID", clusterID) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return fmt.Errorf("failed to send request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + return nil +} diff --git a/internal/telemetry/config.go b/internal/telemetry/config.go new file mode 100644 index 00000000..b4c9b4d1 --- /dev/null +++ b/internal/telemetry/config.go @@ -0,0 +1,27 @@ +package telemetry + +import ( + "time" +) + +// Config holds telemetry configuration +type Config struct { + // Disable telemetry collection if set to true + Disabled bool + // Endpoint to send telemetry data to + Endpoint string + // Interval between telemetry data collection + Interval time.Duration + // CozystackVersion represents the current version of Cozystack + CozystackVersion string +} + +// DefaultConfig returns default telemetry configuration +func DefaultConfig() *Config { + return &Config{ + Disabled: false, + Endpoint: "https://telemetry.cozystack.io", + Interval: 15 * time.Minute, + CozystackVersion: "unknown", + } +} diff --git a/packages/core/platform/bundles/distro-full.yaml b/packages/core/platform/bundles/distro-full.yaml index 26ef8aac..c232cc3f 100644 --- a/packages/core/platform/bundles/distro-full.yaml +++ b/packages/core/platform/bundles/distro-full.yaml @@ -37,6 +37,17 @@ releases: namespace: cozy-cert-manager dependsOn: [cilium] +- name: cozystack-controller + releaseName: cozystack-controller + chart: cozy-cozystack-controller + namespace: cozy-system + dependsOn: [cilium] + {{- if eq (index $cozyConfig.data "telemetry-enabled") "false" }} + values: + cozystackController: + disableTelemetry: true + {{- end }} + - name: cert-manager releaseName: cert-manager chart: cozy-cert-manager diff --git a/packages/core/platform/bundles/distro-hosted.yaml b/packages/core/platform/bundles/distro-hosted.yaml index 7b138a8d..eb826b16 100644 --- a/packages/core/platform/bundles/distro-hosted.yaml +++ b/packages/core/platform/bundles/distro-hosted.yaml @@ -20,6 +20,17 @@ releases: namespace: cozy-cert-manager dependsOn: [] +- name: cozystack-controller + releaseName: cozystack-controller + chart: cozy-cozystack-controller + namespace: cozy-system + dependsOn: [cilium] + {{- if eq (index $cozyConfig.data "telemetry-enabled") "false" }} + values: + cozystackController: + disableTelemetry: true + {{- end }} + - name: cert-manager releaseName: cert-manager chart: cozy-cert-manager diff --git a/packages/core/platform/bundles/paas-full.yaml b/packages/core/platform/bundles/paas-full.yaml index ff8cbef4..8563824f 100644 --- a/packages/core/platform/bundles/paas-full.yaml +++ b/packages/core/platform/bundles/paas-full.yaml @@ -62,6 +62,17 @@ releases: namespace: cozy-system dependsOn: [cilium,kubeovn] +- name: cozystack-controller + releaseName: cozystack-controller + chart: cozy-cozystack-controller + namespace: cozy-system + dependsOn: [cilium,kubeovn] + {{- if eq (index $cozyConfig.data "telemetry-enabled") "false" }} + values: + cozystackController: + disableTelemetry: true + {{- end }} + - name: cert-manager releaseName: cert-manager chart: cozy-cert-manager diff --git a/packages/core/platform/bundles/paas-hosted.yaml b/packages/core/platform/bundles/paas-hosted.yaml index 6b218497..c986a06b 100644 --- a/packages/core/platform/bundles/paas-hosted.yaml +++ b/packages/core/platform/bundles/paas-hosted.yaml @@ -35,6 +35,17 @@ releases: namespace: cozy-system dependsOn: [] +- name: cozystack-controller + releaseName: cozystack-controller + chart: cozy-cozystack-controller + namespace: cozy-system + dependsOn: [] + {{- if eq (index $cozyConfig.data "telemetry-enabled") "false" }} + values: + cozystackController: + disableTelemetry: true + {{- end }} + - name: cert-manager releaseName: cert-manager chart: cozy-cert-manager diff --git a/packages/system/cozystack-api/images/cozystack-api/Dockerfile b/packages/system/cozystack-api/images/cozystack-api/Dockerfile index 176420fb..6b8e5cc1 100644 --- a/packages/system/cozystack-api/images/cozystack-api/Dockerfile +++ b/packages/system/cozystack-api/images/cozystack-api/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:alpine3.19 as builder +FROM golang:1.23-alpine as builder WORKDIR /workspace @@ -8,6 +8,7 @@ RUN go mod download COPY api api/ COPY pkg pkg/ COPY cmd cmd/ +COPY internal internal/ RUN CGO_ENABLED=0 go build -ldflags="-extldflags=-static" -o /cozystack-api cmd/cozystack-api/main.go diff --git a/packages/system/cozystack-api/templates/deployment.yaml b/packages/system/cozystack-api/templates/deployment.yaml index fbcb2f5f..3dd93bd8 100644 --- a/packages/system/cozystack-api/templates/deployment.yaml +++ b/packages/system/cozystack-api/templates/deployment.yaml @@ -6,7 +6,7 @@ metadata: labels: app: cozystack-api spec: - replicas: 1 + replicas: 2 selector: matchLabels: app: cozystack-api diff --git a/packages/system/cozystack-controller/.gitignore b/packages/system/cozystack-controller/.gitignore new file mode 100644 index 00000000..ada68ff0 --- /dev/null +++ b/packages/system/cozystack-controller/.gitignore @@ -0,0 +1,27 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin/* +Dockerfile.cross + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Go workspace file +go.work + +# Kubernetes Generated files - skip generated files, except for vendored files +!vendor/**/zz_generated.* + +# editor and IDE paraphernalia +.idea +.vscode +*.swp +*.swo +*~ diff --git a/packages/system/cozystack-controller/Chart.yaml b/packages/system/cozystack-controller/Chart.yaml new file mode 100644 index 00000000..66080059 --- /dev/null +++ b/packages/system/cozystack-controller/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-cozystack-controller +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/cozystack-controller/Makefile b/packages/system/cozystack-controller/Makefile new file mode 100644 index 00000000..22bc1fef --- /dev/null +++ b/packages/system/cozystack-controller/Makefile @@ -0,0 +1,24 @@ +NAME=cozystack-controller +NAMESPACE=cozy-system + +include ../../../scripts/common-envs.mk +include ../../../scripts/package.mk + +image: image-cozystack-controller update-version + +image-cozystack-controller: + docker buildx build -f images/cozystack-controller/Dockerfile ../../.. \ + --provenance false \ + --tag $(REGISTRY)/cozystack-controller:$(call settag,$(TAG)) \ + --cache-from type=registry,ref=$(REGISTRY)/cozystack-controller:latest \ + --cache-to type=inline \ + --metadata-file images/cozystack-controller.json \ + --push=$(PUSH) \ + --load=$(LOAD) + IMAGE="$(REGISTRY)/cozystack-controller:$(call settag,$(TAG))@$$(yq e '."containerimage.digest"' images/cozystack-controller.json -o json -r)" \ + yq -i '.cozystackController.image = strenv(IMAGE)' values.yaml + rm -f images/cozystack-controller.json + +update-version: + TAG="$(call settag,$(TAG))" \ + yq -i '.cozystackController.cozystackVersion = strenv(TAG)' values.yaml diff --git a/packages/system/cozystack-controller/images/cozystack-controller/Dockerfile b/packages/system/cozystack-controller/images/cozystack-controller/Dockerfile new file mode 100644 index 00000000..c289c8d2 --- /dev/null +++ b/packages/system/cozystack-controller/images/cozystack-controller/Dockerfile @@ -0,0 +1,20 @@ +FROM golang:1.23-alpine as builder + +WORKDIR /workspace + +COPY go.mod go.sum ./ +RUN go mod download + +COPY api api/ +COPY pkg pkg/ +COPY cmd cmd/ +COPY internal internal/ + +RUN CGO_ENABLED=0 go build -ldflags="-extldflags=-static" -o /cozystack-controller cmd/cozystack-controller/main.go + +FROM scratch + +COPY --from=builder /cozystack-controller /cozystack-controller +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt + +ENTRYPOINT ["/cozystack-controller"] diff --git a/packages/system/cozystack-controller/templates/crds/cozystack.io_workloadmonitors.yaml b/packages/system/cozystack-controller/templates/crds/cozystack.io_workloadmonitors.yaml new file mode 100644 index 00000000..5c1318c0 --- /dev/null +++ b/packages/system/cozystack-controller/templates/crds/cozystack.io_workloadmonitors.yaml @@ -0,0 +1,117 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: workloadmonitors.cozystack.io +spec: + group: cozystack.io + names: + kind: WorkloadMonitor + listKind: WorkloadMonitorList + plural: workloadmonitors + singular: workloadmonitor + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.kind + name: Kind + type: string + - jsonPath: .spec.type + name: Type + type: string + - jsonPath: .spec.version + name: Version + type: string + - jsonPath: .spec.replicas + name: Replicas + type: integer + - jsonPath: .spec.minReplicas + name: MinReplicas + type: integer + - jsonPath: .status.availableReplicas + name: Available + type: integer + - jsonPath: .status.observedReplicas + name: Observed + type: integer + - jsonPath: .status.operational + name: Operational + type: boolean + name: v1alpha1 + schema: + openAPIV3Schema: + description: WorkloadMonitor is the Schema for the workloadmonitors 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: WorkloadMonitorSpec defines the desired state of WorkloadMonitor + properties: + kind: + description: Kind specifies the kind of the workload + type: string + minReplicas: + description: MinReplicas specifies the minimum number of replicas + that should be available + format: int32 + minimum: 0 + type: integer + replicas: + description: |- + Replicas is the desired number of replicas + If not specified, will use observedReplicas as the target + format: int32 + minimum: 0 + type: integer + selector: + additionalProperties: + type: string + description: Selector is a label selector to find workloads to monitor + type: object + type: + description: Type specifies the type of the workload + type: string + version: + description: Version specifies the version of the workload + type: string + required: + - selector + type: object + status: + description: WorkloadMonitorStatus defines the observed state of WorkloadMonitor + properties: + availableReplicas: + description: AvailableReplicas is the number of ready replicas + format: int32 + type: integer + observedReplicas: + description: ObservedReplicas is the total number of pods observed + format: int32 + type: integer + operational: + description: Operational indicates if the workload meets all operational + requirements + type: boolean + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/cozystack-controller/templates/crds/cozystack.io_workloads.yaml b/packages/system/cozystack-controller/templates/crds/cozystack.io_workloads.yaml new file mode 100644 index 00000000..d09d4902 --- /dev/null +++ b/packages/system/cozystack-controller/templates/crds/cozystack.io_workloads.yaml @@ -0,0 +1,88 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: workloads.cozystack.io +spec: + group: cozystack.io + names: + kind: Workload + listKind: WorkloadList + plural: workloads + singular: workload + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.kind + name: Kind + type: string + - jsonPath: .status.type + name: Type + type: string + - jsonPath: .status.resources.cpu + name: CPU + type: string + - jsonPath: .status.resources.memory + name: Memory + type: string + - jsonPath: .status.operational + name: Operational + type: boolean + name: v1alpha1 + schema: + openAPIV3Schema: + description: Workload is the Schema for the workloads 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 + status: + description: WorkloadStatus defines the observed state of Workload + properties: + kind: + description: Kind represents the type of workload (redis, postgres, + etc.) + type: string + operational: + description: Operational indicates if all pods of the workload are + ready + type: boolean + resources: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: Resources specifies the compute resources allocated to + this workload + type: object + type: + description: |- + Type represents the specific role of the workload (redis, sentinel, etc.) + If not specified, defaults to Kind + type: string + required: + - kind + - resources + type: object + type: object + served: true + storage: true + subresources: {} diff --git a/packages/system/cozystack-controller/templates/deployment.yaml b/packages/system/cozystack-controller/templates/deployment.yaml new file mode 100644 index 00000000..33b309df --- /dev/null +++ b/packages/system/cozystack-controller/templates/deployment.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cozystack-controller + namespace: cozy-system + labels: + app: cozystack-controller +spec: + replicas: 1 + selector: + matchLabels: + app: cozystack-controller + template: + metadata: + labels: + app: cozystack-controller + spec: + serviceAccountName: cozystack-controller + containers: + - name: cozystack-controller + image: "{{ .Values.cozystackController.image }}" + args: + - --cozystack-version={{ .Values.cozystackController.cozystackVersion }} + {{- if .Values.cozystackController.debug }} + - --zap-log-level=debug + {{- else }} + - --zap-log-level=info + {{- end }} + {{- if .Values.cozystackController.disableTelemetry }} + - --disable-telemetry + {{- end }} diff --git a/packages/system/cozystack-controller/templates/rbac-bind.yaml b/packages/system/cozystack-controller/templates/rbac-bind.yaml new file mode 100644 index 00000000..67661e62 --- /dev/null +++ b/packages/system/cozystack-controller/templates/rbac-bind.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cozystack-controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cozystack-controller +subjects: +- kind: ServiceAccount + name: cozystack-controller + namespace: cozy-system diff --git a/packages/system/cozystack-controller/templates/rbac.yaml b/packages/system/cozystack-controller/templates/rbac.yaml new file mode 100644 index 00000000..be3f28f8 --- /dev/null +++ b/packages/system/cozystack-controller/templates/rbac.yaml @@ -0,0 +1,11 @@ +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: cozystack-controller +rules: +- apiGroups: [""] + resources: ["configmaps", "pods", "namespaces", "nodes", "services", "persistentvolumes"] + verbs: ["get", "watch", "list"] +- apiGroups: ['cozystack.io'] + resources: ['*'] + verbs: ['*'] diff --git a/packages/system/cozystack-controller/templates/sa.yaml b/packages/system/cozystack-controller/templates/sa.yaml new file mode 100644 index 00000000..55ef1c4a --- /dev/null +++ b/packages/system/cozystack-controller/templates/sa.yaml @@ -0,0 +1,5 @@ +kind: ServiceAccount +apiVersion: v1 +metadata: + name: cozystack-controller + namespace: cozy-system diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml new file mode 100644 index 00000000..a9a7b543 --- /dev/null +++ b/packages/system/cozystack-controller/values.yaml @@ -0,0 +1,5 @@ +cozystackController: + image: ghcr.io/aenix-io/cozystack/cozystack-controller:latest@sha256:380bb9d4402b2f2025dec29e0dccca2a6a456cf9b66b3553b328fb4498f3fea3 + debug: false + disableTelemetry: false + cozystackVersion: "latest" diff --git a/pkg/apis/apps/fuzzer/fuzzer.go b/pkg/apis/apps/fuzzer/fuzzer.go index be35c0e1..428e5244 100644 --- a/pkg/apis/apps/fuzzer/fuzzer.go +++ b/pkg/apis/apps/fuzzer/fuzzer.go @@ -17,7 +17,7 @@ limitations under the License. package fuzzer import ( - "github.com/aenix.io/cozystack/pkg/apis/apps" + "github.com/aenix-io/cozystack/pkg/apis/apps" fuzz "github.com/google/gofuzz" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" diff --git a/pkg/apis/apps/install/install.go b/pkg/apis/apps/install/install.go index 2b6ae886..f51b3685 100644 --- a/pkg/apis/apps/install/install.go +++ b/pkg/apis/apps/install/install.go @@ -17,7 +17,7 @@ limitations under the License. package install import ( - appsv1alpha1 "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1" + appsv1alpha1 "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) diff --git a/pkg/apis/apps/install/roundtrip_test.go b/pkg/apis/apps/install/roundtrip_test.go index 48f6fad0..2393d6fe 100644 --- a/pkg/apis/apps/install/roundtrip_test.go +++ b/pkg/apis/apps/install/roundtrip_test.go @@ -19,7 +19,7 @@ package install import ( "testing" - appsfuzzer "github.com/aenix.io/cozystack/pkg/apis/apps/fuzzer" + appsfuzzer "github.com/aenix-io/cozystack/pkg/apis/apps/fuzzer" "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" ) diff --git a/pkg/apis/apps/v1alpha1/doc.go b/pkg/apis/apps/v1alpha1/doc.go index a696dc63..b4652358 100644 --- a/pkg/apis/apps/v1alpha1/doc.go +++ b/pkg/apis/apps/v1alpha1/doc.go @@ -16,10 +16,10 @@ limitations under the License. // +k8s:openapi-gen=true // +k8s:deepcopy-gen=package -// +k8s:conversion-gen=github.com/aenix.io/cozystack/pkg/apis/apps +// +k8s:conversion-gen=github.com/aenix-io/cozystack/pkg/apis/apps // +k8s:conversion-gen=k8s.io/apiextensions-apiserver/pkg/apis/apiextensions // +k8s:defaulter-gen=TypeMeta // +groupName=apps.cozystack.io // Package v1alpha1 is the v1alpha1 version of the API. -package v1alpha1 // import "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1" +package v1alpha1 // import "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1" diff --git a/pkg/apis/apps/v1alpha1/register.go b/pkg/apis/apps/v1alpha1/register.go index 2849b078..362b6026 100644 --- a/pkg/apis/apps/v1alpha1/register.go +++ b/pkg/apis/apps/v1alpha1/register.go @@ -17,7 +17,7 @@ limitations under the License. package v1alpha1 import ( - "github.com/aenix.io/cozystack/pkg/config" + "github.com/aenix-io/cozystack/pkg/config" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" diff --git a/pkg/apis/apps/v1alpha1/zz_generated.conversion.go b/pkg/apis/apps/v1alpha1/zz_generated.conversion.go index a379c96d..3b3b068a 100644 --- a/pkg/apis/apps/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/apps/v1alpha1/zz_generated.conversion.go @@ -2,7 +2,7 @@ // +build !ignore_autogenerated /* -Copyright 2024 The Cozystack Authors. +Copyright 2025 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go index 1d6082ef..5ea7f311 100644 --- a/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go @@ -2,7 +2,7 @@ // +build !ignore_autogenerated /* -Copyright 2024 The Cozystack Authors. +Copyright 2025 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/apis/apps/v1alpha1/zz_generated.defaults.go b/pkg/apis/apps/v1alpha1/zz_generated.defaults.go index dc03a5bb..4e70d879 100644 --- a/pkg/apis/apps/v1alpha1/zz_generated.defaults.go +++ b/pkg/apis/apps/v1alpha1/zz_generated.defaults.go @@ -2,7 +2,7 @@ // +build !ignore_autogenerated /* -Copyright 2024 The Cozystack Authors. +Copyright 2025 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/apis/apps/validation/validation.go b/pkg/apis/apps/validation/validation.go index bac817c1..01d53689 100644 --- a/pkg/apis/apps/validation/validation.go +++ b/pkg/apis/apps/validation/validation.go @@ -17,7 +17,7 @@ limitations under the License. package validation import ( - "github.com/aenix.io/cozystack/pkg/apis/apps" + "github.com/aenix-io/cozystack/pkg/apis/apps" "k8s.io/apimachinery/pkg/util/validation/field" ) diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index aa8ec349..de23e4cc 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -29,11 +29,11 @@ import ( "k8s.io/client-go/dynamic" restclient "k8s.io/client-go/rest" - "github.com/aenix.io/cozystack/pkg/apis/apps" - "github.com/aenix.io/cozystack/pkg/apis/apps/install" - "github.com/aenix.io/cozystack/pkg/config" - appsregistry "github.com/aenix.io/cozystack/pkg/registry" - applicationstorage "github.com/aenix.io/cozystack/pkg/registry/apps/application" + "github.com/aenix-io/cozystack/pkg/apis/apps" + "github.com/aenix-io/cozystack/pkg/apis/apps/install" + "github.com/aenix-io/cozystack/pkg/config" + appsregistry "github.com/aenix-io/cozystack/pkg/registry" + applicationstorage "github.com/aenix-io/cozystack/pkg/registry/apps/application" ) var ( diff --git a/pkg/apiserver/scheme_test.go b/pkg/apiserver/scheme_test.go index e51f29ae..98dd1386 100644 --- a/pkg/apiserver/scheme_test.go +++ b/pkg/apiserver/scheme_test.go @@ -19,7 +19,7 @@ package apiserver import ( "testing" - appsfuzzer "github.com/aenix.io/cozystack/pkg/apis/apps/fuzzer" + appsfuzzer "github.com/aenix-io/cozystack/pkg/apis/apps/fuzzer" "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" ) diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index f8ca25c9..04e4f4dc 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -23,10 +23,10 @@ import ( "io" "net" - "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1" - "github.com/aenix.io/cozystack/pkg/apiserver" - "github.com/aenix.io/cozystack/pkg/config" - sampleopenapi "github.com/aenix.io/cozystack/pkg/generated/openapi" + "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1" + "github.com/aenix-io/cozystack/pkg/apiserver" + "github.com/aenix-io/cozystack/pkg/config" + sampleopenapi "github.com/aenix-io/cozystack/pkg/generated/openapi" "github.com/spf13/cobra" utilerrors "k8s.io/apimachinery/pkg/util/errors" utilruntime "k8s.io/apimachinery/pkg/util/runtime" diff --git a/pkg/generated/applyconfiguration/apps/v1alpha1/application.go b/pkg/generated/applyconfiguration/apps/v1alpha1/application.go index 65de594e..90eb7cd8 100644 --- a/pkg/generated/applyconfiguration/apps/v1alpha1/application.go +++ b/pkg/generated/applyconfiguration/apps/v1alpha1/application.go @@ -22,7 +22,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" v1 "k8s.io/client-go/applyconfigurations/meta/v1" - appsv1alpha1 "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1" + appsv1alpha1 "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1" ) // ApplicationApplyConfiguration represents a declarative configuration of the Application type for use diff --git a/pkg/generated/applyconfiguration/utils.go b/pkg/generated/applyconfiguration/utils.go index df2a5db6..356c5854 100644 --- a/pkg/generated/applyconfiguration/utils.go +++ b/pkg/generated/applyconfiguration/utils.go @@ -22,9 +22,9 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" testing "k8s.io/client-go/testing" - v1alpha1 "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1" - internal "github.com/aenix.io/cozystack/pkg/generated/applyconfiguration/internal" - appsv1alpha1 "github.com/aenix.io/cozystack/pkg/generated/applyconfiguration/apps/v1alpha1" + v1alpha1 "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1" + internal "github.com/aenix-io/cozystack/pkg/generated/applyconfiguration/internal" + appsv1alpha1 "github.com/aenix-io/cozystack/pkg/generated/applyconfiguration/apps/v1alpha1" ) // ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no diff --git a/pkg/generated/listers/apps/v1alpha1/application.go b/pkg/generated/listers/apps/v1alpha1/application.go index bf35b20b..2ab3c8e6 100644 --- a/pkg/generated/listers/apps/v1alpha1/application.go +++ b/pkg/generated/listers/apps/v1alpha1/application.go @@ -22,7 +22,7 @@ import ( labels "k8s.io/apimachinery/pkg/labels" listers "k8s.io/client-go/listers" cache "k8s.io/client-go/tools/cache" - appsv1alpha1 "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1" + appsv1alpha1 "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1" ) // ApplicationLister helps list Applications. diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 1b53cf7e..da8f8d15 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -2,7 +2,7 @@ // +build !ignore_autogenerated /* -Copyright 2024 The Cozystack Authors. +Copyright 2025 The Cozystack Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -30,9 +30,9 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1.Application": schema_pkg_apis_apps_v1alpha1_Application(ref), - "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1.ApplicationList": schema_pkg_apis_apps_v1alpha1_ApplicationList(ref), - "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1.ApplicationStatus": schema_pkg_apis_apps_v1alpha1_ApplicationStatus(ref), + "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1.Application": schema_pkg_apis_apps_v1alpha1_Application(ref), + "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1.ApplicationList": schema_pkg_apis_apps_v1alpha1_ApplicationList(ref), + "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1.ApplicationStatus": schema_pkg_apis_apps_v1alpha1_ApplicationStatus(ref), "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionRequest": schema_pkg_apis_apiextensions_v1_ConversionRequest(ref), "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionResponse": schema_pkg_apis_apiextensions_v1_ConversionResponse(ref), "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionReview": schema_pkg_apis_apiextensions_v1_ConversionReview(ref), @@ -157,14 +157,14 @@ func schema_pkg_apis_apps_v1alpha1_Application(ref common.ReferenceCallback) com "status": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1.ApplicationStatus"), + Ref: ref("github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1.ApplicationStatus"), }, }, }, }, }, Dependencies: []string{ - "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1.ApplicationStatus", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1.ApplicationStatus", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -202,7 +202,7 @@ func schema_pkg_apis_apps_v1alpha1_ApplicationList(ref common.ReferenceCallback) Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1.Application"), + Ref: ref("github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1.Application"), }, }, }, @@ -213,7 +213,7 @@ func schema_pkg_apis_apps_v1alpha1_ApplicationList(ref common.ReferenceCallback) }, }, Dependencies: []string{ - "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1.Application", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1.Application", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index 94facf36..4b0028fd 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -39,8 +39,8 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/klog/v2" - appsv1alpha1 "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1" - "github.com/aenix.io/cozystack/pkg/config" + appsv1alpha1 "github.com/aenix-io/cozystack/pkg/apis/apps/v1alpha1" + "github.com/aenix-io/cozystack/pkg/config" // Importing API errors package to construct appropriate error responses apierrors "k8s.io/apimachinery/pkg/api/errors" diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 6e198356..3aa163ab 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -17,7 +17,7 @@ limitations under the License. package registry import ( - "github.com/aenix.io/cozystack/pkg/registry/apps/application" + "github.com/aenix-io/cozystack/pkg/registry/apps/application" "k8s.io/apimachinery/pkg/runtime/schema" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest"