Compare commits

...

48 Commits

Author SHA1 Message Date
Andrei Kvapil
4f0e042eac Merge remote-tracking branch 'upstream/main' into refactor-engine 2025-12-01 22:38:51 +01:00
Andrei Kvapil
66ab048612 fix ci pipeline
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-27 19:34:48 +01:00
Andrei Kvapil
cc52c69922 rebuild images
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:43:45 +01:00
Andrei Kvapil
4270d66376 Merge remote-tracking branch 'upstream/main' into refactor-engine 2025-11-25 18:35:43 +01:00
Andrei Kvapil
2ca68eda69 rebuild images
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:26:35 +01:00
Andrei Kvapil
9db99f7233 upd cozyreport
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:23:49 +01:00
Andrei Kvapil
a89dd819ff Return cozystack.io/system-app=true label
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:23:46 +01:00
Andrei Kvapil
657bddaeb9 Update e2e
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:23:41 +01:00
Andrei Kvapil
51d0001589 move Makefiles to hack
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:23:30 +01:00
Andrei Kvapil
e0ec967120 Rename resources
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 17:43:33 +01:00
Andrei Kvapil
b77791a5fe return removed files
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 17:17:03 +01:00
Andrei Kvapil
3d9cfee401 update migration
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 17:03:56 +01:00
Andrei Kvapil
e046206d2b refactor a bit
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 16:24:08 +01:00
Andrei Kvapil
c69756de51 Update SA for tenants
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 15:35:22 +01:00
Andrei Kvapil
15a9180b67 do not require apiServerEndpoint
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 15:34:40 +01:00
Andrei Kvapil
451ef73172 refactor escaping
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 15:01:06 +01:00
Andrei Kvapil
2077b0e515 fix namespace
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 02:17:33 +01:00
Andrei Kvapil
aaf2d1326a Move telemetry from cozystack-controller to cozystack-operator
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 02:17:05 +01:00
Andrei Kvapil
ea1d0363d1 [platform] Get rid of lookups for apps
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 01:15:08 +01:00
Andrei Kvapil
45bd323c6e [platform] Get rid of lookups
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-24 20:09:03 +01:00
Andrei Kvapil
b328124be7 1
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-24 17:12:11 +01:00
Andrei Kvapil
35086bc362 bug fixes and cleanup
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-24 15:12:37 +01:00
Andrei Kvapil
7b28139ad9 [platform] Add OCIRegistry with artefacts
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-24 13:44:16 +01:00
Andrei Kvapil
5883fbf7ea [cozystack-controller] Add reconciliation for CozystackResourceDefinitions
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-24 10:24:01 +01:00
Andrei Kvapil
167e85004c rename packages
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-23 00:37:27 +01:00
Andrei Kvapil
7fc458d136 [cozystack-api] Show revision
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-22 23:32:15 +01:00
Andrei Kvapil
bb220647ad Instruct Cozystack API server and lineage webhook logic to use labels
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-22 23:20:17 +01:00
Andrei Kvapil
a4cb9ae30b Move migrations to platform package
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-22 23:10:40 +01:00
Andrei Kvapil
982727ac91 refactor and install flux-aio
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-22 01:35:59 +01:00
Andrei Kvapil
6c3a7b7efb remove flux from bundles
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-21 16:17:10 +01:00
Andrei Kvapil
923dbd209d Fix artifact sources processing
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-20 23:39:16 +01:00
Andrei Kvapil
c23826efac separate artifactgeenrators
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-20 04:42:41 +01:00
Andrei Kvapil
36119cec45 Introduce CozystackPlatformConfiguration
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-20 04:28:44 +01:00
Andrei Kvapil
f98b429ad2 Implement business logic
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-20 03:33:54 +01:00
Andrei Kvapil
8a0935fb37 Design CozystackBundle API
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-20 02:22:45 +01:00
Andrei Kvapil
5dc9f590cf WIP WIP WIP WIP WIP WIP WIP WIP WIP
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 20:38:21 +01:00
Andrei Kvapil
17286ad213 fix merging values.yaml
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 17:59:06 +01:00
Andrei Kvapil
ea9d44b4af Refactor bundles (WIP)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 15:49:29 +01:00
Andrei Kvapil
7c2bec197b [flux] disable network policies
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 15:48:58 +01:00
Andrei Kvapil
4b1525a5f8 Cleanup
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 15:32:40 +01:00
Andrei Kvapil
2113d17a54 fix dependencies
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 15:22:51 +01:00
Andrei Kvapil
4f97aef04c Update apps to use ExternalArtifacts
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 14:31:54 +01:00
Andrei Kvapil
4b5d777b81 [fluxcd] fix advertisning url for source-watcher
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 09:47:27 +01:00
Andrei Kvapil
75197c6d25 Update apps to use ExternalArtifacts
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 00:41:09 +01:00
Andrei Kvapil
c808ed6f24 Add ArtifactGenerators reconciler
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 00:07:20 +01:00
Andrei Kvapil
222b582b68 Introduce cozystack-operator
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-18 23:14:36 +01:00
Andrei Kvapil
2a87c83043 Update Fluxcd 2.7.x, enable source-watcher
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-18 19:56:27 +01:00
Andrei Kvapil
e5b65e8e77 Remove cozystack-assets-server and move grafana-dashboards into separate pod
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-18 18:13:52 +01:00
356 changed files with 20895 additions and 4007 deletions

View File

@@ -58,7 +58,7 @@ jobs:
DOCKER_CONFIG: ${{ runner.temp }}/.docker
- name: Build Talos image
run: make -C packages/core/installer talos-nocloud
run: make -C packages/core/talos talos-nocloud
- name: Save git diff as patch
if: "!contains(github.event.pull_request.labels.*.name, 'release')"

View File

@@ -26,21 +26,18 @@ build: build-deps
make -C packages/system/bucket image
make -C packages/system/objectstorage-controller image
make -C packages/core/testing image
make -C packages/core/installer image
make -C packages/core/installer image-operator
make -C packages/core/talos image
make -C packages/core/platform image
make -C packages/core/installer image-packages
make manifests
repos:
rm -rf _out
make -C packages/system repo
make -C packages/apps repo
make -C packages/extra repo
manifests:
mkdir -p _out/assets
(cd packages/core/installer/; helm template -n cozy-installer installer .) > _out/assets/cozystack-installer.yaml
(cd packages/core/installer/; helm template -n cozy-system cozystack-operator . | sed '/^WARNING/d') > _out/assets/cozystack-installer.yaml
assets:
make -C packages/core/installer assets
make -C packages/core/talos assets
test:
make -C packages/core/testing apply

View File

@@ -18,50 +18,51 @@ package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:resource:scope=Cluster,shortName=appdef
// CozystackResourceDefinition is the Schema for the cozystackresourcedefinitions API
type CozystackResourceDefinition struct {
// ApplicationDefinition is the Schema for the applicationdefinitions API
type ApplicationDefinition struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec CozystackResourceDefinitionSpec `json:"spec,omitempty"`
Spec ApplicationDefinitionSpec `json:"spec,omitempty"`
}
// +kubebuilder:object:root=true
// CozystackResourceDefinitionList contains a list of CozystackResourceDefinitions
type CozystackResourceDefinitionList struct {
// ApplicationDefinitionList contains a list of ApplicationDefinitions
type ApplicationDefinitionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CozystackResourceDefinition `json:"items"`
Items []ApplicationDefinition `json:"items"`
}
func init() {
SchemeBuilder.Register(&CozystackResourceDefinition{}, &CozystackResourceDefinitionList{})
SchemeBuilder.Register(&ApplicationDefinition{}, &ApplicationDefinitionList{})
}
type CozystackResourceDefinitionSpec struct {
type ApplicationDefinitionSpec struct {
// Application configuration
Application CozystackResourceDefinitionApplication `json:"application"`
Application ApplicationDefinitionApplication `json:"application"`
// Release configuration
Release CozystackResourceDefinitionRelease `json:"release"`
Release ApplicationDefinitionRelease `json:"release"`
// Secret selectors
Secrets CozystackResourceDefinitionResources `json:"secrets,omitempty"`
Secrets ApplicationDefinitionResources `json:"secrets,omitempty"`
// Service selectors
Services CozystackResourceDefinitionResources `json:"services,omitempty"`
Services ApplicationDefinitionResources `json:"services,omitempty"`
// Ingress selectors
Ingresses CozystackResourceDefinitionResources `json:"ingresses,omitempty"`
Ingresses ApplicationDefinitionResources `json:"ingresses,omitempty"`
// Dashboard configuration for this resource
Dashboard *CozystackResourceDefinitionDashboard `json:"dashboard,omitempty"`
Dashboard *ApplicationDefinitionDashboard `json:"dashboard,omitempty"`
}
type CozystackResourceDefinitionChart struct {
type ApplicationDefinitionChart struct {
// Name of the Helm chart
Name string `json:"name"`
// Source reference for the Helm chart
@@ -79,7 +80,7 @@ type SourceRef struct {
Namespace string `json:"namespace"`
}
type CozystackResourceDefinitionApplication struct {
type ApplicationDefinitionApplication struct {
// Kind of the application, used for UI and API
Kind string `json:"kind"`
// OpenAPI schema for the application, used for API validation
@@ -90,16 +91,30 @@ type CozystackResourceDefinitionApplication struct {
Singular string `json:"singular"`
}
type CozystackResourceDefinitionRelease struct {
// Helm chart configuration
Chart CozystackResourceDefinitionChart `json:"chart"`
// +kubebuilder:validation:XValidation:rule="(has(self.chart) && !has(self.chartRef)) || (!has(self.chart) && has(self.chartRef))",message="either chart or chartRef must be set, but not both"
type ApplicationDefinitionRelease struct {
// Helm chart configuration (for HelmRepository source)
// +optional
Chart *ApplicationDefinitionChart `json:"chart,omitempty"`
// Chart reference configuration (for ExternalArtifact source)
// +optional
ChartRef *ApplicationDefinitionChartRef `json:"chartRef,omitempty"`
// Labels for the release
Labels map[string]string `json:"labels,omitempty"`
// Prefix for the release name
Prefix string `json:"prefix"`
// Default values to be merged into every HelmRelease created from this resource definition
// User-specified values in Application spec will override these default values
// +optional
Values *apiextensionsv1.JSON `json:"values,omitempty"`
}
// CozystackResourceDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support.
type ApplicationDefinitionChartRef struct {
// Source reference for the chart (ExternalArtifact)
SourceRef SourceRef `json:"sourceRef"`
}
// ApplicationDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support.
// A resource matches this selector only if it satisfies ALL criteria:
// - Label selector conditions (matchExpressions and matchLabels)
// - AND has a name that matches one of the names in resourceNames (if specified)
@@ -121,7 +136,7 @@ type CozystackResourceDefinitionRelease struct {
// - "{{ .name }}-secret"
// - "{{ .kind }}-{{ .name }}-tls"
// - "specificname"
type CozystackResourceDefinitionResourceSelector struct {
type ApplicationDefinitionResourceSelector struct {
metav1.LabelSelector `json:",inline"`
// ResourceNames is a list of resource names to match
// If specified, the resource must have one of these exact names to match the selector
@@ -129,16 +144,16 @@ type CozystackResourceDefinitionResourceSelector struct {
ResourceNames []string `json:"resourceNames,omitempty"`
}
type CozystackResourceDefinitionResources struct {
type ApplicationDefinitionResources struct {
// Exclude contains an array of resource selectors that target resources.
// If a resource matches the selector in any of the elements in the array, it is
// hidden from the user, regardless of the matches in the include array.
Exclude []*CozystackResourceDefinitionResourceSelector `json:"exclude,omitempty"`
Exclude []*ApplicationDefinitionResourceSelector `json:"exclude,omitempty"`
// Include contains an array of resource selectors that target resources.
// If a resource matches the selector in any of the elements in the array, and
// matches none of the selectors in the exclude array that resource is marked
// as a tenant resource and is visible to users.
Include []*CozystackResourceDefinitionResourceSelector `json:"include,omitempty"`
Include []*ApplicationDefinitionResourceSelector `json:"include,omitempty"`
}
// ---- Dashboard types ----
@@ -155,8 +170,8 @@ const (
DashboardTabYAML DashboardTab = "yaml"
)
// CozystackResourceDefinitionDashboard describes how this resource appears in the UI.
type CozystackResourceDefinitionDashboard struct {
// ApplicationDefinitionDashboard describes how this resource appears in the UI.
type ApplicationDefinitionDashboard struct {
// Human-readable name shown in the UI (e.g., "Bucket")
Singular string `json:"singular"`
// Plural human-readable name (e.g., "Buckets")

View File

@@ -0,0 +1,230 @@
/*
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 (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,shortName=bundle
// Bundle is the Schema for the bundles API
type Bundle struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec BundleSpec `json:"spec,omitempty"`
}
// +kubebuilder:object:root=true
// BundleList contains a list of Bundles
type BundleList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Bundle `json:"items"`
}
func init() {
SchemeBuilder.Register(&Bundle{}, &BundleList{})
}
// BundleSpec defines the desired state of Bundle
type BundleSpec struct {
// SourceRef is the source reference for the bundle charts
// +required
SourceRef BundleSourceRef `json:"sourceRef"`
// DependsOn is a list of bundle dependencies in the format "bundleName/target"
// For example: "cozystack-system/network"
// If specified, the dependencies listed in the target's packages will be taken
// from the specified bundle and added to all packages in this bundle
// +optional
DependsOn []string `json:"dependsOn,omitempty"`
// DependencyTargets defines named groups of packages that can be referenced
// by other bundles via dependsOn. Each target has a name and a list of packages.
// +optional
DependencyTargets []BundleDependencyTarget `json:"dependencyTargets,omitempty"`
// Libraries is a list of Helm library charts used by packages
// +optional
Libraries []BundleLibrary `json:"libraries,omitempty"`
// Artifacts is a list of Helm charts that will be built as ExternalArtifacts
// These artifacts can be referenced by ApplicationDefinitions
// +optional
Artifacts []BundleArtifact `json:"artifacts,omitempty"`
// Packages is a list of Helm releases to be installed as part of this bundle
// +required
Packages []BundleRelease `json:"packages"`
// DeletionPolicy defines how child resources should be handled when the bundle is deleted.
// - "Delete" (default): Child resources will be deleted when the bundle is deleted (via ownerReference).
// - "Orphan": Child resources will be orphaned (ownerReferences will be removed).
// +kubebuilder:validation:Enum=Delete;Orphan
// +kubebuilder:default=Delete
// +optional
DeletionPolicy DeletionPolicy `json:"deletionPolicy,omitempty"`
// Labels are labels that will be applied to all resources created by this bundle
// (ArtifactGenerators and HelmReleases). These labels are merged with the default
// cozystack.io/bundle label.
// +optional
Labels map[string]string `json:"labels,omitempty"`
// BasePath is the base path where packages are located in the source.
// For GitRepository, defaults to "packages" if not specified.
// For OCIRepository, defaults to empty string (root) if not specified.
// +optional
BasePath string `json:"basePath,omitempty"`
}
// DeletionPolicy defines how child resources should be handled when the parent is deleted.
// +kubebuilder:validation:Enum=Delete;Orphan
type DeletionPolicy string
const (
// DeletionPolicyDelete means child resources will be deleted when the parent is deleted.
DeletionPolicyDelete DeletionPolicy = "Delete"
// DeletionPolicyOrphan means child resources will be orphaned (ownerReferences removed).
DeletionPolicyOrphan DeletionPolicy = "Orphan"
)
// BundleDependencyTarget defines a named group of packages that can be referenced
// by other bundles via dependsOn
type BundleDependencyTarget struct {
// Name is the unique identifier for this dependency target
// +required
Name string `json:"name"`
// Packages is a list of package names that belong to this target
// These packages will be added as dependencies when this target is referenced
// +required
Packages []string `json:"packages"`
}
// BundleLibrary defines a Helm library chart
type BundleLibrary struct {
// Name is the unique identifier for this library
// +required
Name string `json:"name"`
// Path is the path to the library chart directory
// +required
Path string `json:"path"`
}
// BundleArtifact defines a Helm chart artifact that will be built as ExternalArtifact
type BundleArtifact struct {
// Name is the unique identifier for this artifact (used as ExternalArtifact name)
// +required
Name string `json:"name"`
// Path is the path to the Helm chart directory
// +required
Path string `json:"path"`
// Libraries is a list of library names that this artifact depends on
// +optional
Libraries []string `json:"libraries,omitempty"`
}
// BundleSourceRef defines the source reference for bundle charts
type BundleSourceRef struct {
// Kind of the source reference
// +kubebuilder:validation:Enum=GitRepository;OCIRepository
// +required
Kind string `json:"kind"`
// Name of the source reference
// +required
Name string `json:"name"`
// Namespace of the source reference
// +required
Namespace string `json:"namespace"`
}
// +kubebuilder:validation:XValidation:rule="(has(self.path) && !has(self.artifact)) || (!has(self.path) && has(self.artifact))",message="either path or artifact must be set, but not both"
// BundleRelease defines a single Helm release within a bundle
type BundleRelease struct {
// Name is the unique identifier for this release within the bundle
// +required
Name string `json:"name"`
// ReleaseName is the name of the HelmRelease resource that will be created
// +required
ReleaseName string `json:"releaseName"`
// Path is the path to the Helm chart directory
// Either Path or Artifact must be specified, but not both
// +optional
Path string `json:"path,omitempty"`
// Artifact is the name of an artifact from the bundle's artifacts list
// The artifact must exist in the bundle's artifacts section
// Either Path or Artifact must be specified, but not both
// +optional
Artifact string `json:"artifact,omitempty"`
// Namespace is the Kubernetes namespace where the release will be installed
// +required
Namespace string `json:"namespace"`
// Privileged indicates whether this release requires privileged access
// +optional
Privileged bool `json:"privileged,omitempty"`
// Disabled indicates whether this release is disabled (should not be installed)
// +optional
Disabled bool `json:"disabled,omitempty"`
// DependsOn is a list of release names that must be installed before this release
// +optional
DependsOn []string `json:"dependsOn,omitempty"`
// Libraries is a list of library names that this package depends on
// +optional
Libraries []string `json:"libraries,omitempty"`
// Values contains Helm chart values as a JSON object
// +optional
Values *apiextensionsv1.JSON `json:"values,omitempty"`
// ValuesFiles is a list of values file names to use
// +optional
ValuesFiles []string `json:"valuesFiles,omitempty"`
// Labels are labels that will be applied to the HelmRelease created for this package
// These labels are merged with bundle-level labels and the default cozystack.io/bundle label
// +optional
Labels map[string]string `json:"labels,omitempty"`
// NamespaceLabels are labels that will be applied to the namespace for this package
// These labels are merged with labels from other packages in the same namespace
// +optional
NamespaceLabels map[string]string `json:"namespaceLabels,omitempty"`
// NamespaceAnnotations are annotations that will be applied to the namespace for this package
// These annotations are merged with annotations from other packages in the same namespace
// +optional
NamespaceAnnotations map[string]string `json:"namespaceAnnotations,omitempty"`
}

View File

@@ -0,0 +1,71 @@
/*
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 (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,shortName=platform
// Platform is the Schema for the platforms API
type Platform struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PlatformSpec `json:"spec,omitempty"`
}
// +kubebuilder:object:root=true
// PlatformList contains a list of Platform
type PlatformList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Platform `json:"items"`
}
func init() {
SchemeBuilder.Register(&Platform{}, &PlatformList{})
}
// PlatformSpec defines the desired state of Platform
type PlatformSpec struct {
// SourceRef is the source reference for the platform chart
// This is used to generate the ArtifactGenerator
// +required
SourceRef SourceRef `json:"sourceRef"`
// Values contains Helm chart values as a JSON object
// These values are passed directly to HelmRelease.values
// +optional
Values *apiextensionsv1.JSON `json:"values,omitempty"`
// Interval is the interval at which to reconcile the HelmRelease
// +kubebuilder:default="5m"
// +optional
Interval *metav1.Duration `json:"interval,omitempty"`
// BasePath is the base path where the platform chart is located in the source.
// For GitRepository, defaults to "packages/core/platform" if not specified.
// For OCIRepository, defaults to "core/platform" if not specified.
// +optional
BasePath string `json:"basePath,omitempty"`
}

View File

@@ -21,30 +21,32 @@ limitations under the License.
package v1alpha1
import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinition) DeepCopyInto(out *CozystackResourceDefinition) {
func (in *ApplicationDefinition) DeepCopyInto(out *ApplicationDefinition) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinition.
func (in *CozystackResourceDefinition) DeepCopy() *CozystackResourceDefinition {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinition.
func (in *ApplicationDefinition) DeepCopy() *ApplicationDefinition {
if in == nil {
return nil
}
out := new(CozystackResourceDefinition)
out := new(ApplicationDefinition)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CozystackResourceDefinition) DeepCopyObject() runtime.Object {
func (in *ApplicationDefinition) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
@@ -52,38 +54,54 @@ func (in *CozystackResourceDefinition) DeepCopyObject() runtime.Object {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionApplication) DeepCopyInto(out *CozystackResourceDefinitionApplication) {
func (in *ApplicationDefinitionApplication) DeepCopyInto(out *ApplicationDefinitionApplication) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionApplication.
func (in *CozystackResourceDefinitionApplication) DeepCopy() *CozystackResourceDefinitionApplication {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionApplication.
func (in *ApplicationDefinitionApplication) DeepCopy() *ApplicationDefinitionApplication {
if in == nil {
return nil
}
out := new(CozystackResourceDefinitionApplication)
out := new(ApplicationDefinitionApplication)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionChart) DeepCopyInto(out *CozystackResourceDefinitionChart) {
func (in *ApplicationDefinitionChart) DeepCopyInto(out *ApplicationDefinitionChart) {
*out = *in
out.SourceRef = in.SourceRef
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionChart.
func (in *CozystackResourceDefinitionChart) DeepCopy() *CozystackResourceDefinitionChart {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionChart.
func (in *ApplicationDefinitionChart) DeepCopy() *ApplicationDefinitionChart {
if in == nil {
return nil
}
out := new(CozystackResourceDefinitionChart)
out := new(ApplicationDefinitionChart)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionDashboard) DeepCopyInto(out *CozystackResourceDefinitionDashboard) {
func (in *ApplicationDefinitionChartRef) DeepCopyInto(out *ApplicationDefinitionChartRef) {
*out = *in
out.SourceRef = in.SourceRef
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionChartRef.
func (in *ApplicationDefinitionChartRef) DeepCopy() *ApplicationDefinitionChartRef {
if in == nil {
return nil
}
out := new(ApplicationDefinitionChartRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDefinitionDashboard) DeepCopyInto(out *ApplicationDefinitionDashboard) {
*out = *in
if in.Tags != nil {
in, out := &in.Tags, &out.Tags
@@ -108,42 +126,42 @@ func (in *CozystackResourceDefinitionDashboard) DeepCopyInto(out *CozystackResou
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionDashboard.
func (in *CozystackResourceDefinitionDashboard) DeepCopy() *CozystackResourceDefinitionDashboard {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionDashboard.
func (in *ApplicationDefinitionDashboard) DeepCopy() *ApplicationDefinitionDashboard {
if in == nil {
return nil
}
out := new(CozystackResourceDefinitionDashboard)
out := new(ApplicationDefinitionDashboard)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionList) DeepCopyInto(out *CozystackResourceDefinitionList) {
func (in *ApplicationDefinitionList) DeepCopyInto(out *ApplicationDefinitionList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]CozystackResourceDefinition, len(*in))
*out = make([]ApplicationDefinition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionList.
func (in *CozystackResourceDefinitionList) DeepCopy() *CozystackResourceDefinitionList {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionList.
func (in *ApplicationDefinitionList) DeepCopy() *ApplicationDefinitionList {
if in == nil {
return nil
}
out := new(CozystackResourceDefinitionList)
out := new(ApplicationDefinitionList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CozystackResourceDefinitionList) DeepCopyObject() runtime.Object {
func (in *ApplicationDefinitionList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
@@ -151,9 +169,18 @@ func (in *CozystackResourceDefinitionList) DeepCopyObject() runtime.Object {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionRelease) DeepCopyInto(out *CozystackResourceDefinitionRelease) {
func (in *ApplicationDefinitionRelease) DeepCopyInto(out *ApplicationDefinitionRelease) {
*out = *in
out.Chart = in.Chart
if in.Chart != nil {
in, out := &in.Chart, &out.Chart
*out = new(ApplicationDefinitionChart)
**out = **in
}
if in.ChartRef != nil {
in, out := &in.ChartRef, &out.ChartRef
*out = new(ApplicationDefinitionChartRef)
**out = **in
}
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
@@ -161,20 +188,25 @@ func (in *CozystackResourceDefinitionRelease) DeepCopyInto(out *CozystackResourc
(*out)[key] = val
}
}
if in.Values != nil {
in, out := &in.Values, &out.Values
*out = new(v1.JSON)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionRelease.
func (in *CozystackResourceDefinitionRelease) DeepCopy() *CozystackResourceDefinitionRelease {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionRelease.
func (in *ApplicationDefinitionRelease) DeepCopy() *ApplicationDefinitionRelease {
if in == nil {
return nil
}
out := new(CozystackResourceDefinitionRelease)
out := new(ApplicationDefinitionRelease)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionResourceSelector) DeepCopyInto(out *CozystackResourceDefinitionResourceSelector) {
func (in *ApplicationDefinitionResourceSelector) DeepCopyInto(out *ApplicationDefinitionResourceSelector) {
*out = *in
in.LabelSelector.DeepCopyInto(&out.LabelSelector)
if in.ResourceNames != nil {
@@ -184,55 +216,55 @@ func (in *CozystackResourceDefinitionResourceSelector) DeepCopyInto(out *Cozysta
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionResourceSelector.
func (in *CozystackResourceDefinitionResourceSelector) DeepCopy() *CozystackResourceDefinitionResourceSelector {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionResourceSelector.
func (in *ApplicationDefinitionResourceSelector) DeepCopy() *ApplicationDefinitionResourceSelector {
if in == nil {
return nil
}
out := new(CozystackResourceDefinitionResourceSelector)
out := new(ApplicationDefinitionResourceSelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionResources) DeepCopyInto(out *CozystackResourceDefinitionResources) {
func (in *ApplicationDefinitionResources) DeepCopyInto(out *ApplicationDefinitionResources) {
*out = *in
if in.Exclude != nil {
in, out := &in.Exclude, &out.Exclude
*out = make([]*CozystackResourceDefinitionResourceSelector, len(*in))
*out = make([]*ApplicationDefinitionResourceSelector, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(CozystackResourceDefinitionResourceSelector)
*out = new(ApplicationDefinitionResourceSelector)
(*in).DeepCopyInto(*out)
}
}
}
if in.Include != nil {
in, out := &in.Include, &out.Include
*out = make([]*CozystackResourceDefinitionResourceSelector, len(*in))
*out = make([]*ApplicationDefinitionResourceSelector, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(CozystackResourceDefinitionResourceSelector)
*out = new(ApplicationDefinitionResourceSelector)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionResources.
func (in *CozystackResourceDefinitionResources) DeepCopy() *CozystackResourceDefinitionResources {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionResources.
func (in *ApplicationDefinitionResources) DeepCopy() *ApplicationDefinitionResources {
if in == nil {
return nil
}
out := new(CozystackResourceDefinitionResources)
out := new(ApplicationDefinitionResources)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CozystackResourceDefinitionSpec) DeepCopyInto(out *CozystackResourceDefinitionSpec) {
func (in *ApplicationDefinitionSpec) DeepCopyInto(out *ApplicationDefinitionSpec) {
*out = *in
out.Application = in.Application
in.Release.DeepCopyInto(&out.Release)
@@ -241,17 +273,339 @@ func (in *CozystackResourceDefinitionSpec) DeepCopyInto(out *CozystackResourceDe
in.Ingresses.DeepCopyInto(&out.Ingresses)
if in.Dashboard != nil {
in, out := &in.Dashboard, &out.Dashboard
*out = new(CozystackResourceDefinitionDashboard)
*out = new(ApplicationDefinitionDashboard)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionSpec.
func (in *CozystackResourceDefinitionSpec) DeepCopy() *CozystackResourceDefinitionSpec {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionSpec.
func (in *ApplicationDefinitionSpec) DeepCopy() *ApplicationDefinitionSpec {
if in == nil {
return nil
}
out := new(CozystackResourceDefinitionSpec)
out := new(ApplicationDefinitionSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Bundle) DeepCopyInto(out *Bundle) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Bundle.
func (in *Bundle) DeepCopy() *Bundle {
if in == nil {
return nil
}
out := new(Bundle)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Bundle) 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 *BundleArtifact) DeepCopyInto(out *BundleArtifact) {
*out = *in
if in.Libraries != nil {
in, out := &in.Libraries, &out.Libraries
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleArtifact.
func (in *BundleArtifact) DeepCopy() *BundleArtifact {
if in == nil {
return nil
}
out := new(BundleArtifact)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BundleDependencyTarget) DeepCopyInto(out *BundleDependencyTarget) {
*out = *in
if in.Packages != nil {
in, out := &in.Packages, &out.Packages
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleDependencyTarget.
func (in *BundleDependencyTarget) DeepCopy() *BundleDependencyTarget {
if in == nil {
return nil
}
out := new(BundleDependencyTarget)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BundleLibrary) DeepCopyInto(out *BundleLibrary) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleLibrary.
func (in *BundleLibrary) DeepCopy() *BundleLibrary {
if in == nil {
return nil
}
out := new(BundleLibrary)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BundleList) DeepCopyInto(out *BundleList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Bundle, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleList.
func (in *BundleList) DeepCopy() *BundleList {
if in == nil {
return nil
}
out := new(BundleList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *BundleList) 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 *BundleRelease) DeepCopyInto(out *BundleRelease) {
*out = *in
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Libraries != nil {
in, out := &in.Libraries, &out.Libraries
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Values != nil {
in, out := &in.Values, &out.Values
*out = new(v1.JSON)
(*in).DeepCopyInto(*out)
}
if in.ValuesFiles != nil {
in, out := &in.ValuesFiles, &out.ValuesFiles
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.NamespaceLabels != nil {
in, out := &in.NamespaceLabels, &out.NamespaceLabels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.NamespaceAnnotations != nil {
in, out := &in.NamespaceAnnotations, &out.NamespaceAnnotations
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleRelease.
func (in *BundleRelease) DeepCopy() *BundleRelease {
if in == nil {
return nil
}
out := new(BundleRelease)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BundleSourceRef) DeepCopyInto(out *BundleSourceRef) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleSourceRef.
func (in *BundleSourceRef) DeepCopy() *BundleSourceRef {
if in == nil {
return nil
}
out := new(BundleSourceRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BundleSpec) DeepCopyInto(out *BundleSpec) {
*out = *in
out.SourceRef = in.SourceRef
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.DependencyTargets != nil {
in, out := &in.DependencyTargets, &out.DependencyTargets
*out = make([]BundleDependencyTarget, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Libraries != nil {
in, out := &in.Libraries, &out.Libraries
*out = make([]BundleLibrary, len(*in))
copy(*out, *in)
}
if in.Artifacts != nil {
in, out := &in.Artifacts, &out.Artifacts
*out = make([]BundleArtifact, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Packages != nil {
in, out := &in.Packages, &out.Packages
*out = make([]BundleRelease, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleSpec.
func (in *BundleSpec) DeepCopy() *BundleSpec {
if in == nil {
return nil
}
out := new(BundleSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Platform) DeepCopyInto(out *Platform) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Platform.
func (in *Platform) DeepCopy() *Platform {
if in == nil {
return nil
}
out := new(Platform)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Platform) 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 *PlatformList) DeepCopyInto(out *PlatformList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Platform, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlatformList.
func (in *PlatformList) DeepCopy() *PlatformList {
if in == nil {
return nil
}
out := new(PlatformList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PlatformList) 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 *PlatformSpec) DeepCopyInto(out *PlatformSpec) {
*out = *in
out.SourceRef = in.SourceRef
if in.Values != nil {
in, out := &in.Values, &out.Values
*out = new(v1.JSON)
(*in).DeepCopyInto(*out)
}
if in.Interval != nil {
in, out := &in.Interval, &out.Interval
*out = new(metav1.Duration)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlatformSpec.
func (in *PlatformSpec) DeepCopy() *PlatformSpec {
if in == nil {
return nil
}
out := new(PlatformSpec)
in.DeepCopyInto(out)
return out
}

View File

@@ -1,29 +0,0 @@
package main
import (
"flag"
"log"
"net/http"
"path/filepath"
)
func main() {
addr := flag.String("address", ":8123", "Address to listen on")
dir := flag.String("dir", "/cozystack/assets", "Directory to serve files from")
flag.Parse()
absDir, err := filepath.Abs(*dir)
if err != nil {
log.Fatalf("Error getting absolute path for %s: %v", *dir, err)
}
fs := http.FileServer(http.Dir(absDir))
http.Handle("/", fs)
log.Printf("Server starting on %s, serving directory %s", *addr, absDir)
err = http.ListenAndServe(*addr, nil)
if err != nil {
log.Fatalf("Server failed to start: %v", err)
}
}

View File

@@ -20,7 +20,6 @@ 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.
@@ -39,7 +38,6 @@ import (
cozystackiov1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/cozystack/cozystack/internal/controller"
"github.com/cozystack/cozystack/internal/controller/dashboard"
"github.com/cozystack/cozystack/internal/telemetry"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
// +kubebuilder:scaffold:imports
@@ -65,10 +63,6 @@ func main() {
var probeAddr string
var secureMetrics bool
var enableHTTP2 bool
var disableTelemetry bool
var telemetryEndpoint string
var telemetryInterval string
var cozystackVersion string
var reconcileDeployment bool
var tlsOpts []func(*tls.Config)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
@@ -81,14 +75,6 @@ func main() {
"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")
flag.BoolVar(&reconcileDeployment, "reconcile-deployment", false,
"If set, the Cozystack API server is assumed to run as a Deployment, else as a DaemonSet.")
opts := zap.Options{
@@ -97,21 +83,6 @@ func main() {
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
@@ -200,32 +171,20 @@ func main() {
os.Exit(1)
}
if err = (&controller.TenantHelmReconciler{
if err = (&controller.NamespaceHelmReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "TenantHelmReconciler")
setupLog.Error(err, "unable to create controller", "controller", "NamespaceHelmReconciler")
os.Exit(1)
}
if err = (&controller.CozystackConfigReconciler{
if err = (&controller.ApplicationDefinitionReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
CozystackAPIKind: "Deployment",
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CozystackConfigReconciler")
os.Exit(1)
}
cozyAPIKind := "DaemonSet"
if reconcileDeployment {
cozyAPIKind = "Deployment"
}
if err = (&controller.CozystackResourceDefinitionReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
CozystackAPIKind: cozyAPIKind,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CozystackResourceDefinitionReconciler")
setupLog.Error(err, "unable to create controller", "controller", "ApplicationDefinitionReconciler")
os.Exit(1)
}
@@ -249,19 +208,6 @@ func main() {
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")
ctx := ctrl.SetupSignalHandler()
dashboardManager.InitializeStaticResources(ctx)

View File

@@ -0,0 +1,312 @@
/*
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.
*/
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
"strings"
"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"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcewatcherv1beta1 "github.com/fluxcd/source-watcher/api/v2/v1beta1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"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/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"github.com/cozystack/cozystack/internal/fluxinstall"
"github.com/cozystack/cozystack/internal/operator"
"github.com/cozystack/cozystack/internal/telemetry"
// +kubebuilder:scaffold:imports
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
// stringSliceFlag is a custom flag type that allows multiple values
type stringSliceFlag []string
func (f *stringSliceFlag) String() string {
return strings.Join(*f, ",")
}
func (f *stringSliceFlag) Set(value string) error {
*f = append(*f, value)
return nil
}
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
utilruntime.Must(cozyv1alpha1.AddToScheme(scheme))
utilruntime.Must(helmv2.AddToScheme(scheme))
utilruntime.Must(sourcev1.AddToScheme(scheme))
utilruntime.Must(sourcewatcherv1beta1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}
func main() {
var metricsAddr string
var enableLeaderElection bool
var probeAddr string
var secureMetrics bool
var enableHTTP2 bool
var installFlux bool
var disableTelemetry bool
var telemetryEndpoint string
var telemetryInterval string
var cozystackVersion string
var installFluxResources stringSliceFlag
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
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", false,
"If set the metrics endpoint is served securely")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.BoolVar(&installFlux, "install-flux", false, "Install Flux components before starting reconcile loop")
flag.Var(&installFluxResources, "install-flux-resource", "Install Flux resource (JSON format). Can be specified multiple times. Applied after Flux installation.")
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: true,
}
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)))
config := ctrl.GetConfigOrDie()
// Start the controller manager
setupLog.Info("Starting controller manager")
mgr, err := ctrl.NewManager(config, ctrl.Options{
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: metricsAddr,
SecureServing: secureMetrics,
},
WebhookServer: webhook.NewServer(webhook.Options{
Port: 9443,
}),
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "platform-operator.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, 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)
}
// Install Flux before starting reconcile loop
if installFlux {
setupLog.Info("Installing Flux components before starting reconcile loop")
installCtx, installCancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer installCancel()
// The namespace will be automatically extracted from the embedded manifests
if err := fluxinstall.Install(installCtx, mgr.GetClient(), fluxinstall.WriteEmbeddedManifests); err != nil {
setupLog.Error(err, "failed to install Flux, continuing anyway")
// Don't exit - allow operator to start even if Flux install fails
// This allows the operator to work in environments where Flux is already installed
} else {
setupLog.Info("Flux installation completed successfully")
}
}
// Install Flux resources after Flux installation
if len(installFluxResources) > 0 {
setupLog.Info("Installing Flux resources", "count", len(installFluxResources))
installCtx, installCancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer installCancel()
if err := installFluxResourcesFunc(installCtx, mgr.GetClient(), installFluxResources); err != nil {
setupLog.Error(err, "failed to install Flux resources, continuing anyway")
// Don't exit - allow operator to start even if resource installation fails
} else {
setupLog.Info("Flux resources installation completed successfully")
}
}
bundleReconciler := &operator.BundleReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}
if err = bundleReconciler.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Bundle")
os.Exit(1)
}
platformReconciler := &operator.PlatformReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}
if err = platformReconciler.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Platform")
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 controller manager")
mgrCtx := ctrl.SetupSignalHandler()
if err := mgr.Start(mgrCtx); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
// installFluxResourcesFunc installs Flux resources from JSON strings
func installFluxResourcesFunc(ctx context.Context, k8sClient client.Client, resources []string) error {
logger := log.FromContext(ctx)
for i, resourceJSON := range resources {
logger.Info("Installing Flux resource", "index", i+1, "total", len(resources))
// Parse JSON into unstructured object
var obj unstructured.Unstructured
if err := json.Unmarshal([]byte(resourceJSON), &obj.Object); err != nil {
return fmt.Errorf("failed to parse resource JSON at index %d: %w", i, err)
}
// Validate that it has required fields
if obj.GetAPIVersion() == "" {
return fmt.Errorf("resource at index %d missing apiVersion", i)
}
if obj.GetKind() == "" {
return fmt.Errorf("resource at index %d missing kind", i)
}
if obj.GetName() == "" {
return fmt.Errorf("resource at index %d missing metadata.name", i)
}
// Apply the resource (create or update)
logger.Info("Applying Flux resource",
"apiVersion", obj.GetAPIVersion(),
"kind", obj.GetKind(),
"name", obj.GetName(),
"namespace", obj.GetNamespace(),
)
// Use server-side apply or create/update
existing := &unstructured.Unstructured{}
existing.SetGroupVersionKind(obj.GroupVersionKind())
key := client.ObjectKey{
Name: obj.GetName(),
Namespace: obj.GetNamespace(),
}
err := k8sClient.Get(ctx, key, existing)
if err != nil {
if client.IgnoreNotFound(err) == nil {
// Resource doesn't exist, create it
if err := k8sClient.Create(ctx, &obj); err != nil {
return fmt.Errorf("failed to create resource %s/%s: %w", obj.GetKind(), obj.GetName(), err)
}
logger.Info("Created Flux resource", "kind", obj.GetKind(), "name", obj.GetName())
} else {
return fmt.Errorf("failed to check if resource exists: %w", err)
}
} else {
// Resource exists, update it
obj.SetResourceVersion(existing.GetResourceVersion())
if err := k8sClient.Update(ctx, &obj); err != nil {
return fmt.Errorf("failed to update resource %s/%s: %w", obj.GetKind(), obj.GetName(), err)
}
logger.Info("Updated Flux resource", "kind", obj.GetKind(), "name", obj.GetName())
}
}
return nil
}

View File

@@ -0,0 +1,31 @@
apiVersion: cozystack.io/v1alpha1
kind: Platform
metadata:
name: cozystack-platform
# Cluster-scoped resource, no namespace needed
spec:
# SourceRef is required - reference to the OCIRepository or GitRepository
sourceRef:
kind: OCIRepository
name: cozystack-packages
namespace: cozy-system
# Optional: Interval for HelmRelease reconciliation (default: 5m)
interval: 5m
# Optional: BasePath is the base path where the platform chart is located in the source.
# For GitRepository, defaults to "packages/core/platform" if not specified.
# For OCIRepository, defaults to "core/platform" if not specified.
# basePath: core/platform
# Optional: Values to pass to HelmRelease
# These values will be merged with sourceRef (which is automatically added)
values:
# Any custom values can be added here
# sourceRef will be automatically added by the controller
# Example custom values:
# customKey: customValue
# nested:
# config:
# enabled: true

148
go.mod
View File

@@ -2,33 +2,39 @@
module github.com/cozystack/cozystack
go 1.23.0
go 1.25.0
require (
github.com/fluxcd/helm-controller/api v1.1.0
github.com/fluxcd/helm-controller/api v1.4.3
github.com/fluxcd/source-controller/api v1.6.2
github.com/fluxcd/source-watcher/api/v2 v2.0.2
github.com/go-logr/logr v1.4.3
github.com/go-logr/zapr v1.3.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
github.com/onsi/ginkgo/v2 v2.23.3
github.com/onsi/gomega v1.37.0
github.com/prometheus/client_golang v1.22.0
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.11.1
go.uber.org/zap v1.27.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/component-base v0.31.2
k8s.io/api v0.34.1
k8s.io/apiextensions-apiserver v0.34.1
k8s.io/apimachinery v0.34.1
k8s.io/apiserver v0.34.1
k8s.io/client-go v0.34.1
k8s.io/component-base v0.34.1
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
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d
sigs.k8s.io/controller-runtime v0.22.2
sigs.k8s.io/structured-merge-diff/v4 v4.7.0
)
require (
cel.dev/expr v0.24.0 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
@@ -36,86 +42,88 @@ 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/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/felixge/httpsnoop v1.0.4 // 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/fluxcd/pkg/apis/acl v0.9.0 // indirect
github.com/fluxcd/pkg/apis/kustomize v1.13.0 // indirect
github.com/fluxcd/pkg/apis/meta v1.22.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // 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/jsonreference v0.21.0 // 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/btree v1.1.3 // indirect
github.com/google/cel-go v0.26.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/moby/spdystream v0.4.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/pflag v1.0.7 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.etcd.io/etcd/api/v3 v3.5.16 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect
go.etcd.io/etcd/client/v3 v3.5.16 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.etcd.io/etcd/api/v3 v3.6.4 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect
go.etcd.io/etcd/client/v3 v3.6.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.26.0 // indirect
golang.org/x/net v0.45.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.37.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
google.golang.org/protobuf v1.34.2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.5 // indirect
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.v3 v3.0.1 // indirect
k8s.io/kms v0.31.2 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
k8s.io/kms v0.34.1 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)

345
go.sum
View File

@@ -1,11 +1,11 @@
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
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/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=
@@ -18,45 +18,48 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr
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/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
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/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
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/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM=
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=
github.com/fluxcd/helm-controller/api v1.1.0/go.mod h1:BgHMgMY6CWynzl4KIbHpd6Wpn3FN9BqgkwmvoKCp6iE=
github.com/fluxcd/pkg/apis/kustomize v1.6.1 h1:22FJc69Mq4i8aCxnKPlddHhSMyI4UPkQkqiAdWFcqe0=
github.com/fluxcd/pkg/apis/kustomize v1.6.1/go.mod h1:5dvQ4IZwz0hMGmuj8tTWGtarsuxW0rWsxJOwC6i+0V8=
github.com/fluxcd/pkg/apis/meta v1.6.1 h1:maLhcRJ3P/70ArLCY/LF/YovkxXbX+6sTWZwZQBeNq0=
github.com/fluxcd/pkg/apis/meta v1.6.1/go.mod h1:YndB/gxgGZmKfqpAfFxyCDNFJFP0ikpeJzs66jwq280=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/fluxcd/helm-controller/api v1.4.3 h1:CdZwjL1liXmYCWyk2jscmFEB59tICIlnWB9PfDDW5q4=
github.com/fluxcd/helm-controller/api v1.4.3/go.mod h1:0XrBhKEaqvxyDj/FziG1Q8Fmx2UATdaqLgYqmZh6wW4=
github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA=
github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4=
github.com/fluxcd/pkg/apis/kustomize v1.13.0 h1:GGf0UBVRIku+gebY944icVeEIhyg1P/KE3IrhOyJJnE=
github.com/fluxcd/pkg/apis/kustomize v1.13.0/go.mod h1:TLKVqbtnzkhDuhWnAsN35977HvRfIjs+lgMuNro/LEc=
github.com/fluxcd/pkg/apis/meta v1.22.0 h1:EHWQH5ZWml7i8eZ/AMjm1jxid3j/PQ31p+hIwCt6crM=
github.com/fluxcd/pkg/apis/meta v1.22.0/go.mod h1:Kc1+bWe5p0doROzuV9XiTfV/oL3ddsemYXt8ZYWdVVg=
github.com/fluxcd/source-controller/api v1.6.2 h1:UmodAeqLIeF29HdTqf2GiacZyO+hJydJlepDaYsMvhc=
github.com/fluxcd/source-controller/api v1.6.2/go.mod h1:ZJcAi0nemsnBxjVgmJl0WQzNvB0rMETxQMTdoFosmMw=
github.com/fluxcd/source-watcher/api/v2 v2.0.2 h1:fWSxsDqYN7My2AEpQwbP7O6Qjix8nGBX+UE/qWHtZfM=
github.com/fluxcd/source-watcher/api/v2 v2.0.2/go.mod h1:Hs6ueayPt23jlkIr/d1pGPZ+OHiibQwWjxvU6xqljzg=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
@@ -64,162 +67,169 @@ github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZ
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI=
github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc=
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/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
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/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
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-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
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 v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
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=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8=
github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0=
github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
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/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
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=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk=
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0=
go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28=
go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q=
go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E=
go.etcd.io/etcd/client/v2 v2.305.13 h1:RWfV1SX5jTU0lbCvpVQe3iPQeAHETWdOTb6pxhd77C8=
go.etcd.io/etcd/client/v2 v2.305.13/go.mod h1:iQnL7fepbiomdXMb3om1rHq96htNNGv2sJkEcZGDRRg=
go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE=
go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50=
go.etcd.io/etcd/pkg/v3 v3.5.13 h1:st9bDWNsKkBNpP4PR1MvM/9NqUPfvYZx/YXegsYEH8M=
go.etcd.io/etcd/pkg/v3 v3.5.13/go.mod h1:N+4PLrp7agI/Viy+dUYpX7iRtSPvKq+w8Y14d1vX+m0=
go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA=
go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw=
go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok=
go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo=
go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk=
go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0=
go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI=
go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A=
go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo=
go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA=
go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE=
go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU=
go.etcd.io/etcd/server/v3 v3.6.4/go.mod h1:aYCL/h43yiONOv0QIR82kH/2xZ7m+IWYjzRmyQfnCAg=
go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
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.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
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.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
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=
@@ -228,50 +238,48 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
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=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
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.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
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=
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=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -281,39 +289,44 @@ 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.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0=
k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk=
k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0=
k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM=
k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw=
k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
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/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA=
k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ=
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI=
k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc=
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA=
k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0=
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A=
k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0=
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=
k8s.io/kms v0.31.2/go.mod h1:OZKwl1fan3n3N5FFxnW5C4V3ygrah/3YXeJWS3O6+94=
k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2 h1:GKE9U8BH16uynoxQii0auTjmmmuZ3O0LFMN6S0lPPhI=
k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q=
sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
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.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
k8s.io/kms v0.34.1 h1:iCFOvewDPzWM9fMTfyIPO+4MeuZ0tcZbugxLNSHFG4w=
k8s.io/kms v0.34.1/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0=
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.22.2 h1:cK2l8BGWsSWkXz09tcS4rJh95iOLney5eawcK5A33r4=
sigs.k8s.io/controller-runtime v0.22.2/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -12,13 +12,19 @@ command -V tar >/dev/null || exit $?
echo "Collecting Cozystack information..."
mkdir -p $REPORT_DIR/cozystack
kubectl get deploy -n cozy-system cozystack -o jsonpath='{.spec.template.spec.containers[0].image}' > $REPORT_DIR/cozystack/image.txt 2>&1
kubectl get cm -n cozy-system --no-headers | awk '$1 ~ /^cozystack/' |
while read NAME _; do
DIR=$REPORT_DIR/cozystack/configs
mkdir -p $DIR
kubectl get cm -n cozy-system $NAME -o yaml > $DIR/$NAME.yaml 2>&1
done
kubectl get deploy -n cozy-system cozystack-operator cozystack-controller -o yaml > $REPORT_DIR/cozystack/deployments.yaml 2>&1
echo "Collecting platforms..."
kubectl get platforms.cozystack.io -A > $REPORT_DIR/cozystack/platforms.txt 2>&1
kubectl get platforms.cozystack.io -A -o yaml > $REPORT_DIR/cozystack/platforms.yaml 2>&1
echo "Collecting bundles..."
kubectl get bundles.cozystack.io -A > $REPORT_DIR/cozystack/bundles.txt 2>&1
kubectl get bundles.cozystack.io -A -o yaml > $REPORT_DIR/cozystack/bundles.yaml 2>&1
echo "Collecting applicationdefinitions..."
kubectl get applicationdefinitions.cozystack.io -A > $REPORT_DIR/cozystack/applicationdefinitions.txt 2>&1
kubectl get applicationdefinitions.cozystack.io -A -o yaml > $REPORT_DIR/cozystack/applicationdefinitions.yaml 2>&1
# -- kubernetes module
@@ -56,6 +62,36 @@ kubectl get hr -A --no-headers | awk '$4 != "True"' | \
kubectl describe hr -n $NAMESPACE $NAME > $DIR/describe.txt 2>&1
done
echo "Collecting artifactgenerators..."
kubectl get artifactgenerators.source.extensions.fluxcd.io -A > $REPORT_DIR/kubernetes/artifactgenerators.txt 2>&1
kubectl get artifactgenerators.source.extensions.fluxcd.io -A --no-headers | awk '$4 != "True"' | \
while read NAMESPACE NAME _; do
DIR=$REPORT_DIR/kubernetes/artifactgenerators/$NAMESPACE/$NAME
mkdir -p $DIR
kubectl get artifactgenerators.source.extensions.fluxcd.io -n $NAMESPACE $NAME -o yaml > $DIR/artifactgenerator.yaml 2>&1
kubectl describe artifactgenerators.source.extensions.fluxcd.io -n $NAMESPACE $NAME > $DIR/describe.txt 2>&1
done
echo "Collecting ocirepositories..."
kubectl get ocirepositories.source.toolkit.fluxcd.io -A > $REPORT_DIR/kubernetes/ocirepositories.txt 2>&1
kubectl get ocirepositories.source.toolkit.fluxcd.io -A --no-headers | awk '$4 != "True"' | \
while read NAMESPACE NAME _; do
DIR=$REPORT_DIR/kubernetes/ocirepositories/$NAMESPACE/$NAME
mkdir -p $DIR
kubectl get ocirepositories.source.toolkit.fluxcd.io -n $NAMESPACE $NAME -o yaml > $DIR/ocirepository.yaml 2>&1
kubectl describe ocirepositories.source.toolkit.fluxcd.io -n $NAMESPACE $NAME > $DIR/describe.txt 2>&1
done
echo "Collecting gitrepositories..."
kubectl get gitrepositories.source.toolkit.fluxcd.io -A > $REPORT_DIR/kubernetes/gitrepositories.txt 2>&1
kubectl get gitrepositories.source.toolkit.fluxcd.io -A --no-headers | awk '$4 != "True"' | \
while read NAMESPACE NAME _; do
DIR=$REPORT_DIR/kubernetes/gitrepositories/$NAMESPACE/$NAME
mkdir -p $DIR
kubectl get gitrepositories.source.toolkit.fluxcd.io -n $NAMESPACE $NAME -o yaml > $DIR/gitrepository.yaml 2>&1
kubectl describe gitrepositories.source.toolkit.fluxcd.io -n $NAMESPACE $NAME > $DIR/describe.txt 2>&1
done
echo "Collecting pods..."
kubectl get pod -A -o wide > $REPORT_DIR/kubernetes/pods.txt 2>&1
kubectl get pod -A --no-headers | awk '$4 !~ /Running|Succeeded|Completed/' |

View File

@@ -8,23 +8,51 @@
}
@test "Install Cozystack" {
# Create namespace & configmap required by installer
kubectl create namespace cozy-system --dry-run=client -o yaml | kubectl apply -f -
kubectl create configmap cozystack -n cozy-system \
--from-literal=bundle-name=paas-full \
--from-literal=ipv4-pod-cidr=10.244.0.0/16 \
--from-literal=ipv4-pod-gateway=10.244.0.1 \
--from-literal=ipv4-svc-cidr=10.96.0.0/16 \
--from-literal=ipv4-join-cidr=100.64.0.0/16 \
--from-literal=root-host=example.org \
--from-literal=api-server-endpoint=https://192.168.123.10:6443 \
--dry-run=client -o yaml | kubectl apply -f -
# Apply installer manifests from file
kubectl apply -f _out/assets/cozystack-installer.yaml
# Wait for the installer deployment to become available
kubectl wait deployment/cozystack -n cozy-system --timeout=1m --for=condition=Available
kubectl wait deployment/cozystack-operator -n cozy-system --timeout=1m --for=condition=Available
# Wait for cozy-fluxcd namespace to be created
timeout 30 sh -ec 'until kubectl get namespace cozy-fluxcd >/dev/null 2>&1; do sleep 1; done'
# Wait for Flux deployment
timeout 30 sh -ec 'until kubectl get deployment/flux -n cozy-fluxcd >/dev/null 2>&1; do sleep 1; done'
kubectl wait deployment/flux -n cozy-fluxcd --timeout=1m --for=condition=Available
# Create Platform resource instead of configmap
kubectl apply -f - <<'EOF'
apiVersion: cozystack.io/v1alpha1
kind: Platform
metadata:
name: cozystack-platform
spec:
sourceRef:
kind: OCIRepository
name: cozystack-packages
namespace: cozy-system
values:
bundles:
system:
type: "full"
networking:
podCIDR: "10.244.0.0/16"
podGateway: "10.244.0.1"
serviceCIDR: "10.96.0.0/16"
joinCIDR: "100.64.0.0/16"
publishing:
host: "example.org"
apiServerEndpoint: "https://192.168.123.10:6443"
EOF
# Wait for ArtifactGenerator for cozystack-packages
timeout 60 sh -ec 'until kubectl get artifactgenerators.source.extensions.fluxcd.io cozystack-packages -n cozy-system >/dev/null 2>&1; do sleep 1; done'
kubectl wait artifactgenerators.source.extensions.fluxcd.io/cozystack-packages -n cozy-system --for=condition=ready --timeout=5m
# Wait for bundle ArtifactGenerators
timeout 60 sh -ec 'until kubectl get artifactgenerators.source.extensions.fluxcd.io cozystack-system cozystack-iaas cozystack-paas cozystack-naas -n cozy-system >/dev/null 2>&1; do sleep 1; done'
kubectl wait artifactgenerators.source.extensions.fluxcd.io -n cozy-system --for=condition=ready --timeout=5m cozystack-system cozystack-iaas cozystack-paas cozystack-naas
# Wait until HelmReleases appear & reconcile them
timeout 60 sh -ec 'until kubectl get hr -A -l cozystack.io/system-app=true | grep -q cozys; do sleep 1; done'
@@ -140,9 +168,8 @@ EOF
kubectl wait hr/seaweedfs-system -n tenant-root --timeout=2m --for=condition=ready
fi
# Expose Cozystack services through ingress
kubectl patch configmap/cozystack -n cozy-system --type merge -p '{"data":{"expose-services":"api,dashboard,cdi-uploadproxy,vm-exportproxy,keycloak"}}'
kubectl patch platform/cozystack-platform --type merge -p '{"spec":{"values":{"publishing":{"exposedServices":["api","dashboard","cdi-uploadproxy","vm-exportproxy","keycloak"]}}}}'
# NGINX ingress controller
timeout 60 sh -ec 'until kubectl get deploy root-ingress-controller -n tenant-root >/dev/null 2>&1; do sleep 1; done'
@@ -169,7 +196,7 @@ EOF
}
@test "Keycloak OIDC stack is healthy" {
kubectl patch configmap/cozystack -n cozy-system --type merge -p '{"data":{"oidc-enabled":"true"}}'
kubectl patch platform/cozystack-platform --type merge -p '{"spec":{"values":{"authentication":{"oidc":{"enabled":true}}}}}'
timeout 120 sh -ec 'until kubectl get hr -n cozy-keycloak keycloak keycloak-configure keycloak-operator >/dev/null 2>&1; do sleep 1; done'
kubectl wait hr/keycloak hr/keycloak-configure hr/keycloak-operator -n cozy-keycloak --timeout=10m --for=condition=ready

View File

@@ -54,5 +54,9 @@ kube::codegen::gen_openapi \
$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/crds
mv packages/system/cozystack-controller/crds/cozystack.io_cozystackresourcedefinitions.yaml \
packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml
mv packages/system/cozystack-controller/crds/cozystack.io_applicationdefinitions.yaml \
packages/core/installer/crds/cozystack.io_applicationdefinitions.yaml
mv packages/system/cozystack-controller/crds/cozystack.io_bundles.yaml \
packages/core/installer/crds/cozystack.io_bundles.yaml
mv packages/system/cozystack-controller/crds/cozystack.io_platforms.yaml \
packages/core/installer/crds/cozystack.io_platforms.yaml

View File

@@ -8,7 +8,7 @@ need yq; need jq; need base64
CHART_YAML="${CHART_YAML:-Chart.yaml}"
VALUES_YAML="${VALUES_YAML:-values.yaml}"
SCHEMA_JSON="${SCHEMA_JSON:-values.schema.json}"
CRD_DIR="../../system/cozystack-resource-definitions/cozyrds"
CRD_DIR="../../core/platform/bundles/*/applicationdefinitions"
[[ -f "$CHART_YAML" ]] || { echo "No $CHART_YAML found"; exit 1; }
[[ -f "$SCHEMA_JSON" ]] || { echo "No $SCHEMA_JSON found"; exit 1; }
@@ -54,37 +54,71 @@ fi
# Base64 (portable: no -w / -b options)
ICON_B64="$(base64 < "$ICON_PATH" | tr -d '\n' | tr -d '\r')"
# Decide which HelmRepository name to use based on path
# .../apps/... -> cozystack-apps
# .../extra/... -> cozystack-extra
# default: cozystack-apps
SOURCE_NAME="cozystack-apps"
case "$PWD" in
*"/apps/"*) SOURCE_NAME="cozystack-apps" ;;
*"/extra/"*) SOURCE_NAME="cozystack-extra" ;;
esac
# Find path to output CRD YAML
OUT="$(find $CRD_DIR -type f -name "${NAME}.yaml" | head -n 1)"
if [[ -z "$OUT" ]]; then
echo "Error: ApplicationDefinition file for '${NAME}' not found in ${CRD_DIR}"
echo "Please create the file first in one of the following directories:"
# Auto-detect existing directories
BASE_DIR="../../core/platform/bundles"
if [[ -d "$BASE_DIR" ]]; then
for bundle_dir in "$BASE_DIR"/*/applicationdefinitions; do
if [[ -d "$bundle_dir" ]]; then
bundle_name="$(basename "$(dirname "$bundle_dir")")"
echo " touch ${bundle_dir}/${NAME}.yaml # ${bundle_name}"
fi
done
else
# Fallback if base directory doesn't exist
echo " touch ../../core/platform/bundles/iaas/applicationdefinitions/${NAME}.yaml"
echo " touch ../../core/platform/bundles/paas/applicationdefinitions/${NAME}.yaml"
echo " touch ../../core/platform/bundles/naas/applicationdefinitions/${NAME}.yaml"
echo " touch ../../core/platform/bundles/system/applicationdefinitions/${NAME}.yaml"
fi
exit 1
fi
# If file doesn't exist, create a minimal skeleton
OUT="${OUT:-$CRD_DIR/$NAME.yaml}"
if [[ ! -f "$OUT" ]]; then
if [[ ! -s "$OUT" ]]; then
cat >"$OUT" <<EOF
apiVersion: cozystack.io/v1alpha1
kind: CozystackResourceDefinition
kind: ApplicationDefinition
metadata:
name: ${NAME}
spec: {}
spec:
release:
values:
_cozystack:
EOF
fi
# Determine package type (apps or extra) from current directory
CURRENT_DIR="$(pwd)"
PACKAGE_TYPE="apps" # default
if [[ "$CURRENT_DIR" == *"/packages/extra/"* ]]; then
PACKAGE_TYPE="extra"
elif [[ "$CURRENT_DIR" == *"/packages/apps/"* ]]; then
PACKAGE_TYPE="apps"
fi
# Extract bundle type (iaas, paas, naas, system) from OUT path
OUT_DIR="$(dirname "$OUT")"
BUNDLE_DIR="$(dirname "$OUT_DIR")"
BUNDLE_TYPE="$(basename "$BUNDLE_DIR")"
ARTIFACT_PREFIX="cozystack-${BUNDLE_TYPE}"
ARTIFACT_NAME="${ARTIFACT_PREFIX}-${NAME}"
# Export vars for yq env()
export RES_NAME="$NAME"
export PREFIX="$NAME-"
if [ "$SOURCE_NAME" == "cozystack-extra" ]; then
# For packages/extra, prefix should be empty; for packages/apps, prefix is "${NAME}-"
if [[ "$PACKAGE_TYPE" == "extra" ]]; then
export PREFIX=""
else
export PREFIX="${NAME}-"
fi
export DESCRIPTION="$DESC"
export ICON_B64="$ICON_B64"
export SOURCE_NAME="$SOURCE_NAME"
export ARTIFACT_NAME="$ARTIFACT_NAME"
export SCHEMA_JSON_MIN="$(jq -c . "$SCHEMA_JSON")"
# Generate keysOrder from values.yaml
@@ -114,6 +148,12 @@ export KEYS_ORDER="$(
'
)"
# Remove lines with cozystack.build-values before updating (Helm template syntax breaks yq parsing)
if [[ -f "$OUT" && -n "$OUT" ]]; then
# Use grep to filter out the line, more reliable than sed
grep -v 'cozystack\.build-values' "$OUT" > "${OUT}.tmp" && mv "${OUT}.tmp" "$OUT"
fi
# Update only necessary fields in-place
# - openAPISchema is loaded from file as a multi-line string (block scalar)
# - labels ensure cozystack.io/ui: "true"
@@ -121,19 +161,26 @@ export KEYS_ORDER="$(
# - sourceRef derived from directory (apps|extra)
yq -i '
.apiVersion = (.apiVersion // "cozystack.io/v1alpha1") |
.kind = (.kind // "CozystackResourceDefinition") |
.kind = (.kind // "ApplicationDefinition") |
.metadata.name = strenv(RES_NAME) |
.spec.application.openAPISchema = strenv(SCHEMA_JSON_MIN) |
(.spec.application.openAPISchema style="literal") |
.spec.release.prefix = (strenv(PREFIX)) |
.spec.release.labels."cozystack.io/ui" = "true" |
.spec.release.chart.name = strenv(RES_NAME) |
.spec.release.chart.sourceRef.kind = "HelmRepository" |
.spec.release.chart.sourceRef.name = strenv(SOURCE_NAME) |
.spec.release.chart.sourceRef.namespace = "cozy-public" |
del(.spec.release.chart) |
.spec.release.chartRef.sourceRef.kind = "ExternalArtifact" |
.spec.release.chartRef.sourceRef.name = strenv(ARTIFACT_NAME) |
.spec.release.chartRef.sourceRef.namespace = "cozy-system" |
.spec.dashboard.description = strenv(DESCRIPTION) |
.spec.dashboard.icon = strenv(ICON_B64) |
.spec.dashboard.keysOrder = env(KEYS_ORDER)
' "$OUT"
# Add back the Helm template line after _cozystack
if [[ -f "$OUT" && -n "$OUT" ]]; then
HELM_TEMPLATE=' {{- include "cozystack.build-values" . | nindent 8 }}'
# Use awk for more reliable insertion
awk -v template="$HELM_TEMPLATE" '/_cozystack:/ {print; print template; next} {print}' "$OUT" > "${OUT}.tmp" && mv "${OUT}.tmp" "$OUT"
fi
echo "Updated $OUT"

View File

@@ -0,0 +1,502 @@
package controller
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"slices"
"sync"
"time"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
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"
"github.com/cozystack/cozystack/pkg/cozylib"
)
// +kubebuilder:rbac:groups=cozystack.io,resources=applicationdefinitions,verbs=get;list;watch
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch;update;patch
// +kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch
type ApplicationDefinitionReconciler struct {
client.Client
Scheme *runtime.Scheme
Debounce time.Duration
mu sync.Mutex
lastEvent time.Time
lastHandled time.Time
CozystackAPIKind string
}
func (r *ApplicationDefinitionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
logger.Info("Reconciling ApplicationDefinitions", "request", req.NamespacedName)
// Get all ApplicationDefinitions
crdList := &cozyv1alpha1.ApplicationDefinitionList{}
if err := r.List(ctx, crdList); err != nil {
logger.Error(err, "failed to list ApplicationDefinitions")
return ctrl.Result{}, err
}
logger.Info("Found ApplicationDefinitions", "count", len(crdList.Items))
// Update HelmReleases for each CRD
for i := range crdList.Items {
crd := &crdList.Items[i]
logger.V(4).Info("Processing CRD", "crd", crd.Name, "hasValues", crd.Spec.Release.Values != nil)
if err := r.updateHelmReleasesForCRD(ctx, crd); err != nil {
logger.Error(err, "failed to update HelmReleases for CRD", "crd", crd.Name)
// Continue with other CRDs even if one fails
}
}
// Continue with debounced restart logic
return r.debouncedRestart(ctx)
}
func (r *ApplicationDefinitionReconciler) SetupWithManager(mgr ctrl.Manager) error {
if r.Debounce == 0 {
r.Debounce = 5 * time.Second
}
return ctrl.NewControllerManagedBy(mgr).
Named("applicationdefinition-controller").
Watches(
&cozyv1alpha1.ApplicationDefinition{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
r.mu.Lock()
r.lastEvent = time.Now()
r.mu.Unlock()
return []reconcile.Request{{
NamespacedName: types.NamespacedName{
Namespace: "cozy-system",
Name: "cozystack-api",
},
}}
}),
).
Watches(
&helmv2.HelmRelease{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
hr, ok := obj.(*helmv2.HelmRelease)
if !ok {
return nil
}
// Only watch HelmReleases with cozystack.io/ui=true label
if hr.Labels == nil || hr.Labels["cozystack.io/ui"] != "true" {
return nil
}
// Trigger reconciliation of all CRDs when a HelmRelease with the label is created/updated
r.mu.Lock()
r.lastEvent = time.Now()
r.mu.Unlock()
return []reconcile.Request{{
NamespacedName: types.NamespacedName{
Namespace: "cozy-system",
Name: "cozystack-api",
},
}}
}),
).
Complete(r)
}
type crdHashView struct {
Name string `json:"name"`
Spec cozyv1alpha1.ApplicationDefinitionSpec `json:"spec"`
}
func (r *ApplicationDefinitionReconciler) computeConfigHash(ctx context.Context) (string, error) {
list := &cozyv1alpha1.ApplicationDefinitionList{}
if err := r.List(ctx, list); err != nil {
return "", err
}
slices.SortFunc(list.Items, sortApplicationDefinitions)
views := make([]crdHashView, 0, len(list.Items))
for i := range list.Items {
views = append(views, crdHashView{
Name: list.Items[i].Name,
Spec: list.Items[i].Spec,
})
}
b, err := json.Marshal(views)
if err != nil {
return "", err
}
sum := sha256.Sum256(b)
return hex.EncodeToString(sum[:]), nil
}
func (r *ApplicationDefinitionReconciler) debouncedRestart(ctx context.Context) (ctrl.Result, error) {
logger := log.FromContext(ctx)
r.mu.Lock()
le := r.lastEvent
lh := r.lastHandled
debounce := r.Debounce
r.mu.Unlock()
if debounce <= 0 {
debounce = 5 * time.Second
}
if le.IsZero() {
return ctrl.Result{}, nil
}
if d := time.Since(le); d < debounce {
return ctrl.Result{RequeueAfter: debounce - d}, nil
}
if !lh.Before(le) {
return ctrl.Result{}, nil
}
newHash, err := r.computeConfigHash(ctx)
if err != nil {
return ctrl.Result{}, err
}
tpl, obj, patch, err := r.getWorkload(ctx, types.NamespacedName{Namespace: "cozy-system", Name: "cozystack-api"})
if err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
oldHash := tpl.Annotations["cozystack.io/config-hash"]
if oldHash == newHash && oldHash != "" {
r.mu.Lock()
r.lastHandled = le
r.mu.Unlock()
logger.Info("No changes in CRD config; skipping restart", "hash", newHash)
return ctrl.Result{}, nil
}
tpl.Annotations["cozystack.io/config-hash"] = newHash
if err := r.Patch(ctx, obj, patch); err != nil {
return ctrl.Result{}, err
}
r.mu.Lock()
r.lastHandled = le
r.mu.Unlock()
logger.Info("Updated cozystack-api podTemplate config-hash; rollout triggered",
"old", oldHash, "new", newHash)
return ctrl.Result{}, nil
}
func (r *ApplicationDefinitionReconciler) getWorkload(
ctx context.Context,
key types.NamespacedName,
) (tpl *corev1.PodTemplateSpec, obj client.Object, patch client.Patch, err error) {
if r.CozystackAPIKind == "Deployment" {
dep := &appsv1.Deployment{}
if err := r.Get(ctx, key, dep); err != nil {
return nil, nil, nil, err
}
obj = dep
tpl = &dep.Spec.Template
patch = client.MergeFrom(dep.DeepCopy())
} else {
ds := &appsv1.DaemonSet{}
if err := r.Get(ctx, key, ds); err != nil {
return nil, nil, nil, err
}
obj = ds
tpl = &ds.Spec.Template
patch = client.MergeFrom(ds.DeepCopy())
}
if tpl.Annotations == nil {
tpl.Annotations = make(map[string]string)
}
return tpl, obj, patch, nil
}
func sortApplicationDefinitions(a, b cozyv1alpha1.ApplicationDefinition) int {
if a.Name == b.Name {
return 0
}
if a.Name < b.Name {
return -1
}
return 1
}
// updateHelmReleasesForCRD updates all HelmReleases that match the application labels from ApplicationDefinition
func (r *ApplicationDefinitionReconciler) updateHelmReleasesForCRD(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error {
logger := log.FromContext(ctx)
// Use application labels to find HelmReleases
// Labels: apps.cozystack.io/application.kind and apps.cozystack.io/application.group
applicationKind := crd.Spec.Application.Kind
applicationGroup := "apps.cozystack.io" // All applications use this group
// Build label selector for HelmReleases
// Only reconcile HelmReleases with cozystack.io/ui=true label
labelSelector := client.MatchingLabels{
"apps.cozystack.io/application.kind": applicationKind,
"apps.cozystack.io/application.group": applicationGroup,
"cozystack.io/ui": "true",
}
// List all HelmReleases with matching labels
hrList := &helmv2.HelmReleaseList{}
if err := r.List(ctx, hrList, labelSelector); err != nil {
logger.Error(err, "failed to list HelmReleases", "kind", applicationKind, "group", applicationGroup)
return err
}
logger.Info("Found HelmReleases to update", "crd", crd.Name, "kind", applicationKind, "count", len(hrList.Items), "hasValues", crd.Spec.Release.Values != nil)
if crd.Spec.Release.Values != nil {
logger.V(4).Info("CRD has values", "crd", crd.Name, "valuesSize", len(crd.Spec.Release.Values.Raw))
}
// Log each HelmRelease that will be updated
for i := range hrList.Items {
hr := &hrList.Items[i]
logger.V(4).Info("Processing HelmRelease", "name", hr.Name, "namespace", hr.Namespace, "kind", applicationKind)
}
// Update each HelmRelease
for i := range hrList.Items {
hr := &hrList.Items[i]
if err := r.updateHelmReleaseChart(ctx, hr, crd); err != nil {
logger.Error(err, "failed to update HelmRelease", "name", hr.Name, "namespace", hr.Namespace)
continue
}
}
return nil
}
// updateHelmReleaseChart updates the chart/chartRef and values in HelmRelease based on ApplicationDefinition
func (r *ApplicationDefinitionReconciler) updateHelmReleaseChart(ctx context.Context, hr *helmv2.HelmRelease, crd *cozyv1alpha1.ApplicationDefinition) error {
logger := log.FromContext(ctx)
updated := false
hrCopy := hr.DeepCopy()
// Update based on Chart or ChartRef configuration
if crd.Spec.Release.Chart != nil {
// Using Chart (HelmRepository)
if hrCopy.Spec.Chart == nil {
// Need to create Chart spec
hrCopy.Spec.Chart = &helmv2.HelmChartTemplate{
Spec: helmv2.HelmChartTemplateSpec{
Chart: crd.Spec.Release.Chart.Name,
SourceRef: helmv2.CrossNamespaceObjectReference{
Kind: crd.Spec.Release.Chart.SourceRef.Kind,
Name: crd.Spec.Release.Chart.SourceRef.Name,
Namespace: crd.Spec.Release.Chart.SourceRef.Namespace,
},
},
}
// Clear ChartRef if it exists
hrCopy.Spec.ChartRef = nil
updated = true
} else {
// Update existing Chart spec
if hrCopy.Spec.Chart.Spec.Chart != crd.Spec.Release.Chart.Name ||
hrCopy.Spec.Chart.Spec.SourceRef.Kind != crd.Spec.Release.Chart.SourceRef.Kind ||
hrCopy.Spec.Chart.Spec.SourceRef.Name != crd.Spec.Release.Chart.SourceRef.Name ||
hrCopy.Spec.Chart.Spec.SourceRef.Namespace != crd.Spec.Release.Chart.SourceRef.Namespace {
hrCopy.Spec.Chart.Spec.Chart = crd.Spec.Release.Chart.Name
hrCopy.Spec.Chart.Spec.SourceRef = helmv2.CrossNamespaceObjectReference{
Kind: crd.Spec.Release.Chart.SourceRef.Kind,
Name: crd.Spec.Release.Chart.SourceRef.Name,
Namespace: crd.Spec.Release.Chart.SourceRef.Namespace,
}
// Clear ChartRef if it exists
hrCopy.Spec.ChartRef = nil
updated = true
}
}
} else if crd.Spec.Release.ChartRef != nil {
// Using ChartRef (ExternalArtifact)
expectedChartRef := &helmv2.CrossNamespaceSourceReference{
Kind: "ExternalArtifact",
Name: crd.Spec.Release.ChartRef.SourceRef.Name,
Namespace: crd.Spec.Release.ChartRef.SourceRef.Namespace,
}
if hrCopy.Spec.ChartRef == nil {
// Need to create ChartRef
hrCopy.Spec.ChartRef = expectedChartRef
// Clear Chart if it exists
hrCopy.Spec.Chart = nil
updated = true
} else {
// Update existing ChartRef
if hrCopy.Spec.ChartRef.Kind != expectedChartRef.Kind ||
hrCopy.Spec.ChartRef.Name != expectedChartRef.Name ||
hrCopy.Spec.ChartRef.Namespace != expectedChartRef.Namespace {
hrCopy.Spec.ChartRef = expectedChartRef
// Clear Chart if it exists
hrCopy.Spec.Chart = nil
updated = true
}
}
}
// Update Values from CRD if specified
var mergedValues *apiextensionsv1.JSON
var err error
if crd.Spec.Release.Values != nil {
logger.V(4).Info("Merging values from CRD", "name", hr.Name, "namespace", hr.Namespace, "crd", crd.Name)
mergedValues, err = cozylib.MergeValuesWithCRDPriority(crd.Spec.Release.Values, hrCopy.Spec.Values)
if err != nil {
logger.Error(err, "failed to merge values", "name", hr.Name, "namespace", hr.Namespace)
return fmt.Errorf("failed to merge values: %w", err)
}
} else {
// Even if CRD has no values, we still need to ensure _namespace is set
mergedValues = hrCopy.Spec.Values
}
// Always inject namespace annotations (top-level _namespace field)
// This matches the behavior in cozystack-api and NamespaceHelmReconciler
namespace := &corev1.Namespace{}
if err := r.Get(ctx, client.ObjectKey{Name: hrCopy.Namespace}, namespace); err == nil {
mergedValues, err = cozylib.InjectNamespaceAnnotationsIntoValues(mergedValues, namespace)
if err != nil {
logger.Error(err, "failed to inject namespace annotations", "name", hr.Name, "namespace", hr.Namespace)
// Continue even if namespace annotations injection fails
}
}
// Always update values to ensure _cozystack and _namespace are applied
// This ensures that CRD values (especially _cozystack and _namespace) are always applied
// We always update to ensure CRD values are propagated, even if they appear equal
// This is important because JSON comparison might not catch all differences (e.g., field order)
if crd.Spec.Release.Values != nil || mergedValues != hrCopy.Spec.Values {
hrCopy.Spec.Values = mergedValues
updated = true
if crd.Spec.Release.Values != nil {
logger.Info("Updated values from CRD", "name", hr.Name, "namespace", hr.Namespace, "crd", crd.Name)
} else {
logger.V(4).Info("Updated values with namespace labels", "name", hr.Name, "namespace", hr.Namespace, "crd", crd.Name)
}
} else {
logger.V(4).Info("No values update needed", "name", hr.Name, "namespace", hr.Namespace, "crd", crd.Name)
}
if !updated {
return nil
}
// Update the HelmRelease
patch := client.MergeFrom(hr.DeepCopy())
if err := r.Patch(ctx, hrCopy, patch); err != nil {
return err
}
logger.Info("Updated HelmRelease", "name", hr.Name, "namespace", hr.Namespace, "crd", crd.Name)
return nil
}
// mergeHelmReleaseValues merges CRD default values with existing HelmRelease values
// All fields are merged except "_cozystack" and "_namespace" which are fully overwritten from CRD values
// Existing HelmRelease values (outside of _cozystack and _namespace) take precedence (user values override defaults)
func (r *ApplicationDefinitionReconciler) mergeHelmReleaseValues(crdValues, existingValues *apiextensionsv1.JSON) (*apiextensionsv1.JSON, error) {
// If CRD has no values, preserve existing
if crdValues == nil || len(crdValues.Raw) == 0 {
return existingValues, nil
}
// If existing has no values, use CRD values
if existingValues == nil || len(existingValues.Raw) == 0 {
return crdValues, nil
}
var crdMap, existingMap map[string]interface{}
// Parse CRD values (defaults)
if err := json.Unmarshal(crdValues.Raw, &crdMap); err != nil {
return nil, fmt.Errorf("failed to unmarshal CRD values: %w", err)
}
// Parse existing HelmRelease values
if err := json.Unmarshal(existingValues.Raw, &existingMap); err != nil {
return nil, fmt.Errorf("failed to unmarshal existing values: %w", err)
}
// Start with existing values as base (user values take priority)
// Then merge CRD values on top, but _cozystack and _namespace from CRD completely overwrite
merged := deepMergeMaps(existingMap, crdMap)
// Explicitly handle "_cozystack" field: CRD values completely overwrite existing
// This ensures _cozystack field from CRD is always used, even if user modified it
if crdCozystack, exists := crdMap["_cozystack"]; exists {
merged["_cozystack"] = crdCozystack
}
// Explicitly handle "_namespace" field: CRD values completely overwrite existing
// This ensures _namespace field from CRD is always used, even if user modified it
if crdNamespace, exists := crdMap["_namespace"]; exists {
merged["_namespace"] = crdNamespace
}
mergedJSON, err := json.Marshal(merged)
if err != nil {
return nil, fmt.Errorf("failed to marshal merged values: %w", err)
}
return &apiextensionsv1.JSON{Raw: mergedJSON}, nil
}
// deepMergeMaps performs a deep merge of two maps
func deepMergeMaps(base, override map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
// Copy base map
for k, v := range base {
result[k] = v
}
// Merge override map
for k, v := range override {
if baseVal, exists := result[k]; exists {
// If both are maps, recursively merge
if baseMap, ok := baseVal.(map[string]interface{}); ok {
if overrideMap, ok := v.(map[string]interface{}); ok {
result[k] = deepMergeMaps(baseMap, overrideMap)
continue
}
}
}
// Override takes precedence
result[k] = v
}
return result
}
// valuesEqual compares two JSON values for equality
func valuesEqual(a, b *apiextensionsv1.JSON) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
// Simple byte comparison (could be improved with canonical JSON)
return string(a.Raw) == string(b.Raw)
}

View File

@@ -1,187 +0,0 @@
package controller
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"slices"
"sync"
"time"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
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"
)
type CozystackResourceDefinitionReconciler struct {
client.Client
Scheme *runtime.Scheme
Debounce time.Duration
mu sync.Mutex
lastEvent time.Time
lastHandled time.Time
CozystackAPIKind string
}
func (r *CozystackResourceDefinitionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
return r.debouncedRestart(ctx)
}
func (r *CozystackResourceDefinitionReconciler) SetupWithManager(mgr ctrl.Manager) error {
if r.Debounce == 0 {
r.Debounce = 5 * time.Second
}
return ctrl.NewControllerManagedBy(mgr).
Named("cozystackresource-controller").
Watches(
&cozyv1alpha1.CozystackResourceDefinition{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
r.mu.Lock()
r.lastEvent = time.Now()
r.mu.Unlock()
return []reconcile.Request{{
NamespacedName: types.NamespacedName{
Namespace: "cozy-system",
Name: "cozystack-api",
},
}}
}),
).
Complete(r)
}
type crdHashView struct {
Name string `json:"name"`
Spec cozyv1alpha1.CozystackResourceDefinitionSpec `json:"spec"`
}
func (r *CozystackResourceDefinitionReconciler) computeConfigHash(ctx context.Context) (string, error) {
list := &cozyv1alpha1.CozystackResourceDefinitionList{}
if err := r.List(ctx, list); err != nil {
return "", err
}
slices.SortFunc(list.Items, sortCozyRDs)
views := make([]crdHashView, 0, len(list.Items))
for i := range list.Items {
views = append(views, crdHashView{
Name: list.Items[i].Name,
Spec: list.Items[i].Spec,
})
}
b, err := json.Marshal(views)
if err != nil {
return "", err
}
sum := sha256.Sum256(b)
return hex.EncodeToString(sum[:]), nil
}
func (r *CozystackResourceDefinitionReconciler) debouncedRestart(ctx context.Context) (ctrl.Result, error) {
logger := log.FromContext(ctx)
r.mu.Lock()
le := r.lastEvent
lh := r.lastHandled
debounce := r.Debounce
r.mu.Unlock()
if debounce <= 0 {
debounce = 5 * time.Second
}
if le.IsZero() {
return ctrl.Result{}, nil
}
if d := time.Since(le); d < debounce {
return ctrl.Result{RequeueAfter: debounce - d}, nil
}
if !lh.Before(le) {
return ctrl.Result{}, nil
}
newHash, err := r.computeConfigHash(ctx)
if err != nil {
return ctrl.Result{}, err
}
tpl, obj, patch, err := r.getWorkload(ctx, types.NamespacedName{Namespace: "cozy-system", Name: "cozystack-api"})
if err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
oldHash := tpl.Annotations["cozystack.io/config-hash"]
if oldHash == newHash && oldHash != "" {
r.mu.Lock()
r.lastHandled = le
r.mu.Unlock()
logger.Info("No changes in CRD config; skipping restart", "hash", newHash)
return ctrl.Result{}, nil
}
tpl.Annotations["cozystack.io/config-hash"] = newHash
if err := r.Patch(ctx, obj, patch); err != nil {
return ctrl.Result{}, err
}
r.mu.Lock()
r.lastHandled = le
r.mu.Unlock()
logger.Info("Updated cozystack-api podTemplate config-hash; rollout triggered",
"old", oldHash, "new", newHash)
return ctrl.Result{}, nil
}
func (r *CozystackResourceDefinitionReconciler) getWorkload(
ctx context.Context,
key types.NamespacedName,
) (tpl *corev1.PodTemplateSpec, obj client.Object, patch client.Patch, err error) {
if r.CozystackAPIKind == "Deployment" {
dep := &appsv1.Deployment{}
if err := r.Get(ctx, key, dep); err != nil {
return nil, nil, nil, err
}
obj = dep
tpl = &dep.Spec.Template
patch = client.MergeFrom(dep.DeepCopy())
} else {
ds := &appsv1.DaemonSet{}
if err := r.Get(ctx, key, ds); err != nil {
return nil, nil, nil, err
}
obj = ds
tpl = &ds.Spec.Template
patch = client.MergeFrom(ds.DeepCopy())
}
if tpl.Annotations == nil {
tpl.Annotations = make(map[string]string)
}
return tpl, obj, patch, nil
}
func sortCozyRDs(a, b cozyv1alpha1.CozystackResourceDefinition) int {
if a.Name == b.Name {
return 0
}
if a.Name < b.Name {
return -1
}
return 1
}

View File

@@ -14,7 +14,7 @@ import (
)
// ensureBreadcrumb creates or updates a Breadcrumb resource for the given CRD
func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error {
func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error {
group, version, kind := pickGVK(crd)
lowerKind := strings.ToLower(kind)

View File

@@ -21,7 +21,7 @@ import (
//
// metadata.name: stock-namespace-<group>.<version>.<plural>
// spec.id: stock-namespace-/<group>/<version>/<plural>
func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (controllerutil.OperationResult, error) {
func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) (controllerutil.OperationResult, error) {
g, v, kind := pickGVK(crd)
plural := pickPlural(kind, crd)
// Details page segment uses lowercase kind, mirroring your example

View File

@@ -15,7 +15,7 @@ import (
)
// ensureCustomFormsOverride creates or updates a CustomFormsOverride resource for the given CRD
func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error {
func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error {
g, v, kind := pickGVK(crd)
plural := pickPlural(kind, crd)

View File

@@ -16,7 +16,7 @@ import (
)
// ensureCustomFormsPrefill creates or updates a CustomFormsPrefill resource for the given CRD
func (m *Manager) ensureCustomFormsPrefill(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) {
func (m *Manager) ensureCustomFormsPrefill(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) (reconcile.Result, error) {
logger := log.FromContext(ctx)
app := crd.Spec.Application

View File

@@ -15,7 +15,7 @@ import (
)
// ensureFactory creates or updates a Factory resource for the given CRD
func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error {
func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error {
g, v, kind := pickGVK(crd)
plural := pickPlural(kind, crd)
@@ -557,7 +557,7 @@ type factoryFlags struct {
// factoryFeatureFlags tries several conventional locations so you can evolve the API
// without breaking the controller. Defaults are false (hidden).
func factoryFeatureFlags(crd *cozyv1alpha1.CozystackResourceDefinition) factoryFlags {
func factoryFeatureFlags(crd *cozyv1alpha1.ApplicationDefinition) factoryFlags {
var f factoryFlags
f.Workloads = true

View File

@@ -23,7 +23,7 @@ type fieldInfo struct {
// pickGVK tries to read group/version/kind from the CRD. We prefer the "application" section,
// falling back to other likely fields if your schema differs.
func pickGVK(crd *cozyv1alpha1.CozystackResourceDefinition) (group, version, kind string) {
func pickGVK(crd *cozyv1alpha1.ApplicationDefinition) (group, version, kind string) {
// Best guess based on your examples:
if crd.Spec.Application.Kind != "" {
kind = crd.Spec.Application.Kind
@@ -41,7 +41,7 @@ func pickGVK(crd *cozyv1alpha1.CozystackResourceDefinition) (group, version, kin
}
// pickPlural prefers a field on the CRD if you have it; otherwise do a simple lowercase + "s".
func pickPlural(kind string, crd *cozyv1alpha1.CozystackResourceDefinition) string {
func pickPlural(kind string, crd *cozyv1alpha1.ApplicationDefinition) string {
// If you have crd.Spec.Application.Plural, prefer it. Example:
if crd.Spec.Application.Plural != "" {
return crd.Spec.Application.Plural

View File

@@ -56,7 +56,7 @@ func NewManager(c client.Client, scheme *runtime.Scheme) *Manager {
func (m *Manager) SetupWithManager(mgr ctrl.Manager) error {
if err := ctrl.NewControllerManagedBy(mgr).
Named("dashboard-reconciler").
For(&cozyv1alpha1.CozystackResourceDefinition{}).
For(&cozyv1alpha1.ApplicationDefinition{}).
Complete(m); err != nil {
return err
}
@@ -72,7 +72,7 @@ func (m *Manager) SetupWithManager(mgr ctrl.Manager) error {
func (m *Manager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
crd := &cozyv1alpha1.CozystackResourceDefinition{}
crd := &cozyv1alpha1.ApplicationDefinition{}
err := m.Get(ctx, types.NamespacedName{Name: req.Name}, crd)
if err != nil {
@@ -99,7 +99,7 @@ func (m *Manager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result,
// - ensureMarketplacePanel (implemented)
// - ensureSidebar (implemented)
// - ensureTableUriMapping (implemented)
func (m *Manager) EnsureForCRD(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) {
func (m *Manager) EnsureForCRD(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) (reconcile.Result, error) {
// Early return if crd.Spec.Dashboard is nil to prevent oscillation
if crd.Spec.Dashboard == nil {
return reconcile.Result{}, nil
@@ -148,7 +148,7 @@ func (m *Manager) InitializeStaticResources(ctx context.Context) error {
}
// addDashboardLabels adds standard dashboard management labels to a resource
func (m *Manager) addDashboardLabels(obj client.Object, crd *cozyv1alpha1.CozystackResourceDefinition, resourceType string) {
func (m *Manager) addDashboardLabels(obj client.Object, crd *cozyv1alpha1.ApplicationDefinition, resourceType string) {
labels := obj.GetLabels()
if labels == nil {
labels = make(map[string]string)
@@ -197,7 +197,7 @@ func (m *Manager) getStaticResourceSelector() client.MatchingLabels {
// CleanupOrphanedResources removes dashboard resources that are no longer needed
// This should be called after cache warming to ensure all current resources are known
func (m *Manager) CleanupOrphanedResources(ctx context.Context) error {
var crdList cozyv1alpha1.CozystackResourceDefinitionList
var crdList cozyv1alpha1.ApplicationDefinitionList
if err := m.List(ctx, &crdList, &client.ListOptions{}); err != nil {
return err
}
@@ -228,7 +228,7 @@ func (m *Manager) CleanupOrphanedResources(ctx context.Context) error {
}
// buildExpectedResourceSet creates a map of expected resource names by type
func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.CozystackResourceDefinition) map[string]map[string]bool {
func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.ApplicationDefinition) map[string]map[string]bool {
expected := make(map[string]map[string]bool)
// Initialize maps for each resource type

View File

@@ -16,7 +16,7 @@ import (
)
// ensureMarketplacePanel creates or updates a MarketplacePanel resource for the given CRD
func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) {
func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) (reconcile.Result, error) {
logger := log.FromContext(ctx)
mp := &dashv1alpha1.MarketplacePanel{}

View File

@@ -28,12 +28,12 @@ import (
// - Categories are ordered strictly as:
// Marketplace, IaaS, PaaS, NaaS, <others A→Z>, Resources, Administration
// - Items within each category: sort by Weight (desc), then Label (A→Z).
func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error {
func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error {
// Build the full menu once.
// 1) Fetch all CRDs
var all []cozyv1alpha1.CozystackResourceDefinition
var crdList cozyv1alpha1.CozystackResourceDefinitionList
var all []cozyv1alpha1.ApplicationDefinition
var crdList cozyv1alpha1.ApplicationDefinitionList
if err := m.List(ctx, &crdList, &client.ListOptions{}); err != nil {
return err
}
@@ -228,7 +228,7 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
// upsertMultipleSidebars creates/updates several Sidebar resources with the same menu spec.
func (m *Manager) upsertMultipleSidebars(
ctx context.Context,
crd *cozyv1alpha1.CozystackResourceDefinition,
crd *cozyv1alpha1.ApplicationDefinition,
ids []string,
keysAndTags map[string]any,
menuItems []any,
@@ -335,7 +335,7 @@ func orderCategoryLabels[T any](cats map[string][]T) []string {
}
// safeCategory returns spec.dashboard.category or "Resources" if not set.
func safeCategory(def *cozyv1alpha1.CozystackResourceDefinition) string {
func safeCategory(def *cozyv1alpha1.ApplicationDefinition) string {
if def == nil || def.Spec.Dashboard == nil {
return "Resources"
}

View File

@@ -7,7 +7,7 @@ import (
)
// ensureTableUriMapping creates or updates a TableUriMapping resource for the given CRD
func (m *Manager) ensureTableUriMapping(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error {
func (m *Manager) ensureTableUriMapping(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error {
// Links are fully managed by the CustomColumnsOverride.
return nil
}

View File

@@ -0,0 +1,170 @@
/*
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"
"encoding/json"
"fmt"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
"github.com/cozystack/cozystack/pkg/cozylib"
)
// +kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch;update;patch
type NamespaceHelmReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// Reconcile processes namespace changes and updates HelmReleases with namespace labels
func (r *NamespaceHelmReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// Get the namespace
namespace := &corev1.Namespace{}
if err := r.Get(ctx, req.NamespacedName, namespace); err != nil {
logger.Error(err, "unable to fetch Namespace")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Extract namespace.cozystack.io/* annotations
namespaceLabels := cozylib.ExtractNamespaceAnnotations(namespace)
if len(namespaceLabels) == 0 {
// No namespace labels to process, skip
return ctrl.Result{}, nil
}
logger.Info("processing namespace labels", "namespace", namespace.Name, "labels", namespaceLabels)
// List all HelmReleases in this namespace
helmReleaseList := &helmv2.HelmReleaseList{}
if err := r.List(ctx, helmReleaseList, client.InNamespace(namespace.Name)); err != nil {
logger.Error(err, "unable to list HelmReleases in namespace", "namespace", namespace.Name)
return ctrl.Result{}, err
}
// Update each HelmRelease with namespace labels
updated := 0
for i := range helmReleaseList.Items {
hr := &helmReleaseList.Items[i]
if err := r.updateHelmReleaseWithNamespaceLabels(ctx, hr, namespaceLabels); err != nil {
logger.Error(err, "failed to update HelmRelease", "name", hr.Name, "namespace", hr.Namespace)
continue
}
updated++
}
if updated > 0 {
logger.Info("updated HelmReleases with namespace labels", "namespace", namespace.Name, "count", updated)
}
return ctrl.Result{}, nil
}
// updateHelmReleaseWithNamespaceLabels updates HelmRelease values with namespace labels
func (r *NamespaceHelmReconciler) updateHelmReleaseWithNamespaceLabels(ctx context.Context, hr *helmv2.HelmRelease, namespaceLabels map[string]string) error {
logger := log.FromContext(ctx)
// Parse current values
var valuesMap map[string]interface{}
if hr.Spec.Values != nil && len(hr.Spec.Values.Raw) > 0 {
if err := json.Unmarshal(hr.Spec.Values.Raw, &valuesMap); err != nil {
return fmt.Errorf("failed to unmarshal HelmRelease values: %w", err)
}
} else {
valuesMap = make(map[string]interface{})
}
// Convert namespaceLabels from map[string]string to map[string]interface{}
namespaceLabelsMap := make(map[string]interface{})
for k, v := range namespaceLabels {
namespaceLabelsMap[k] = v
}
// Check if namespace labels need to be updated (top-level _namespace field)
needsUpdate := false
currentNamespace, exists := valuesMap["_namespace"]
if !exists {
needsUpdate = true
valuesMap["_namespace"] = namespaceLabelsMap
} else {
currentNamespaceMap, ok := currentNamespace.(map[string]interface{})
if !ok {
needsUpdate = true
valuesMap["_namespace"] = namespaceLabelsMap
} else {
// Compare and update if different
for k, v := range namespaceLabelsMap {
if currentVal, exists := currentNamespaceMap[k]; !exists || currentVal != v {
needsUpdate = true
currentNamespaceMap[k] = v
}
}
// Remove keys that are no longer in namespace labels
for k := range currentNamespaceMap {
if _, exists := namespaceLabelsMap[k]; !exists {
needsUpdate = true
delete(currentNamespaceMap, k)
}
}
if needsUpdate {
valuesMap["_namespace"] = currentNamespaceMap
}
}
}
if !needsUpdate {
// No changes needed
return nil
}
// Marshal back to JSON
mergedJSON, err := json.Marshal(valuesMap)
if err != nil {
return fmt.Errorf("failed to marshal values with namespace labels: %w", err)
}
// Update HelmRelease
patchTarget := hr.DeepCopy()
patchTarget.Spec.Values = &apiextensionsv1.JSON{Raw: mergedJSON}
patch := client.MergeFrom(hr)
if err := r.Patch(ctx, patchTarget, patch); err != nil {
return fmt.Errorf("failed to patch HelmRelease: %w", err)
}
logger.Info("updated HelmRelease with namespace labels", "name", hr.Name, "namespace", hr.Namespace)
return nil
}
// SetupWithManager sets up the controller with the Manager
func (r *NamespaceHelmReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Namespace{}).
Complete(r)
}

View File

@@ -1,140 +0,0 @@
package controller
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"time"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
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 CozystackConfigReconciler struct {
client.Client
Scheme *runtime.Scheme
}
var configMapNames = []string{"cozystack", "cozystack-branding", "cozystack-scheduling"}
const configMapNamespace = "cozy-system"
const digestAnnotation = "cozystack.io/cozy-config-digest"
const forceReconcileKey = "reconcile.fluxcd.io/forceAt"
const requestedAt = "reconcile.fluxcd.io/requestedAt"
func (r *CozystackConfigReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
time.Sleep(2 * time.Second)
digest, err := r.computeDigest(ctx)
if err != nil {
log.Error(err, "failed to compute config digest")
return ctrl.Result{}, nil
}
var helmList helmv2.HelmReleaseList
if err := r.List(ctx, &helmList); err != nil {
return ctrl.Result{}, fmt.Errorf("failed to list HelmReleases: %w", err)
}
now := time.Now().Format(time.RFC3339Nano)
updated := 0
for _, hr := range helmList.Items {
isSystemApp := hr.Labels["cozystack.io/system-app"] == "true"
isTenantRoot := hr.Namespace == "tenant-root" && hr.Name == "tenant-root"
if !isSystemApp && !isTenantRoot {
continue
}
patchTarget := hr.DeepCopy()
if hr.Annotations == nil {
hr.Annotations = map[string]string{}
}
if hr.Annotations[digestAnnotation] == digest {
continue
}
patchTarget.Annotations[digestAnnotation] = digest
patchTarget.Annotations[forceReconcileKey] = now
patchTarget.Annotations[requestedAt] = now
patch := client.MergeFrom(hr.DeepCopy())
if err := r.Patch(ctx, patchTarget, patch); err != nil {
log.Error(err, "failed to patch HelmRelease", "name", hr.Name, "namespace", hr.Namespace)
continue
}
updated++
log.Info("patched HelmRelease with new config digest", "name", hr.Name, "namespace", hr.Namespace)
}
log.Info("finished reconciliation", "updatedHelmReleases", updated)
return ctrl.Result{}, nil
}
func (r *CozystackConfigReconciler) computeDigest(ctx context.Context) (string, error) {
hash := sha256.New()
for _, name := range configMapNames {
var cm corev1.ConfigMap
err := r.Get(ctx, client.ObjectKey{Namespace: configMapNamespace, Name: name}, &cm)
if err != nil {
if kerrors.IsNotFound(err) {
continue // ignore missing
}
return "", err
}
// Sort keys for consistent hashing
var keys []string
for k := range cm.Data {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := cm.Data[k]
fmt.Fprintf(hash, "%s:%s=%s\n", name, k, v)
}
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
func (r *CozystackConfigReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
WithEventFilter(predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
cm, ok := e.ObjectNew.(*corev1.ConfigMap)
return ok && cm.Namespace == configMapNamespace && contains(configMapNames, cm.Name)
},
CreateFunc: func(e event.CreateEvent) bool {
cm, ok := e.Object.(*corev1.ConfigMap)
return ok && cm.Namespace == configMapNamespace && contains(configMapNames, cm.Name)
},
DeleteFunc: func(e event.DeleteEvent) bool {
cm, ok := e.Object.(*corev1.ConfigMap)
return ok && cm.Namespace == configMapNamespace && contains(configMapNames, cm.Name)
},
}).
For(&corev1.ConfigMap{}).
Complete(r)
}
func contains(slice []string, val string) bool {
for _, s := range slice {
if s == val {
return true
}
}
return false
}

View File

@@ -1,159 +0,0 @@
package controller
import (
"context"
"fmt"
"strings"
"time"
e "errors"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
"gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
)
type TenantHelmReconciler struct {
client.Client
Scheme *runtime.Scheme
}
func (r *TenantHelmReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
time.Sleep(2 * time.Second)
hr := &helmv2.HelmRelease{}
if err := r.Get(ctx, req.NamespacedName, hr); err != nil {
if errors.IsNotFound(err) {
return ctrl.Result{}, nil
}
logger.Error(err, "unable to fetch HelmRelease")
return ctrl.Result{}, err
}
if !strings.HasPrefix(hr.Name, "tenant-") {
return ctrl.Result{}, nil
}
if len(hr.Status.Conditions) == 0 || hr.Status.Conditions[0].Type != "Ready" {
return ctrl.Result{}, nil
}
if len(hr.Status.History) == 0 {
logger.Info("no history in HelmRelease status", "name", hr.Name)
return ctrl.Result{}, nil
}
if hr.Status.History[0].Status != "deployed" {
return ctrl.Result{}, nil
}
newDigest := hr.Status.History[0].Digest
var hrList helmv2.HelmReleaseList
childNamespace := getChildNamespace(hr.Namespace, hr.Name)
if childNamespace == "tenant-root" && hr.Name == "tenant-root" {
if hr.Spec.Values == nil {
logger.Error(e.New("hr.Spec.Values is nil"), "cant annotate tenant-root ns")
return ctrl.Result{}, nil
}
err := annotateTenantRootNs(*hr.Spec.Values, r.Client)
if err != nil {
logger.Error(err, "cant annotate tenant-root ns")
return ctrl.Result{}, nil
}
logger.Info("namespace 'tenant-root' annotated")
}
if err := r.List(ctx, &hrList, client.InNamespace(childNamespace)); err != nil {
logger.Error(err, "unable to list HelmReleases in namespace", "namespace", hr.Name)
return ctrl.Result{}, err
}
for _, item := range hrList.Items {
if item.Name == hr.Name {
continue
}
oldDigest := item.GetAnnotations()["cozystack.io/tenant-config-digest"]
if oldDigest == newDigest {
continue
}
patchTarget := item.DeepCopy()
if patchTarget.Annotations == nil {
patchTarget.Annotations = map[string]string{}
}
ts := time.Now().Format(time.RFC3339Nano)
patchTarget.Annotations["cozystack.io/tenant-config-digest"] = newDigest
patchTarget.Annotations["reconcile.fluxcd.io/forceAt"] = ts
patchTarget.Annotations["reconcile.fluxcd.io/requestedAt"] = ts
patch := client.MergeFrom(item.DeepCopy())
if err := r.Patch(ctx, patchTarget, patch); err != nil {
logger.Error(err, "failed to patch HelmRelease", "name", patchTarget.Name)
continue
}
logger.Info("patched HelmRelease with new digest", "name", patchTarget.Name, "digest", newDigest, "version", hr.Status.History[0].Version)
}
return ctrl.Result{}, nil
}
func (r *TenantHelmReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&helmv2.HelmRelease{}).
Complete(r)
}
func getChildNamespace(currentNamespace, hrName string) string {
tenantName := strings.TrimPrefix(hrName, "tenant-")
switch {
case currentNamespace == "tenant-root" && hrName == "tenant-root":
// 1) root tenant inside root namespace
return "tenant-root"
case currentNamespace == "tenant-root":
// 2) any other tenant in root namespace
return fmt.Sprintf("tenant-%s", tenantName)
default:
// 3) tenant in a dedicated namespace
return fmt.Sprintf("%s-%s", currentNamespace, tenantName)
}
}
func annotateTenantRootNs(values apiextensionsv1.JSON, c client.Client) error {
var data map[string]interface{}
if err := yaml.Unmarshal(values.Raw, &data); err != nil {
return fmt.Errorf("failed to parse HelmRelease values: %w", err)
}
host, ok := data["host"].(string)
if !ok || host == "" {
return fmt.Errorf("host field not found or not a string")
}
var ns corev1.Namespace
if err := c.Get(context.TODO(), client.ObjectKey{Name: "tenant-root"}, &ns); err != nil {
return fmt.Errorf("failed to get namespace tenant-root: %w", err)
}
if ns.Annotations == nil {
ns.Annotations = map[string]string{}
}
ns.Annotations["namespace.cozystack.io/host"] = host
if err := c.Update(context.TODO(), &ns); err != nil {
return fmt.Errorf("failed to update namespace: %w", err)
}
return nil
}

View File

@@ -0,0 +1,352 @@
/*
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.
*/
package fluxinstall
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
)
// Install installs Flux components using embedded manifests.
// It extracts the manifests and applies them to the cluster.
// The namespace is automatically determined from the Namespace object in the manifests.
func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifests func(string) error) error {
logger := log.FromContext(ctx)
// Create temporary directory for manifests
tmpDir, err := os.MkdirTemp("", "flux-install-*")
if err != nil {
return fmt.Errorf("failed to create temp directory: %w", err)
}
defer os.RemoveAll(tmpDir)
// Extract embedded manifests (generated by cozypkg)
manifestsDir := filepath.Join(tmpDir, "manifests")
if err := os.MkdirAll(manifestsDir, 0755); err != nil {
return fmt.Errorf("failed to create manifests directory: %w", err)
}
if err := writeEmbeddedManifests(manifestsDir); err != nil {
return fmt.Errorf("failed to extract embedded manifests: %w", err)
}
// Find the manifest file (should be fluxcd.yaml from cozypkg)
manifestPath := filepath.Join(manifestsDir, "fluxcd.yaml")
if _, err := os.Stat(manifestPath); err != nil {
// Try to find any YAML file if fluxcd.yaml doesn't exist
entries, err := os.ReadDir(manifestsDir)
if err != nil {
return fmt.Errorf("failed to read manifests directory: %w", err)
}
for _, entry := range entries {
if strings.HasSuffix(entry.Name(), ".yaml") {
manifestPath = filepath.Join(manifestsDir, entry.Name())
break
}
}
}
// Parse and apply manifests
objects, err := parseManifests(manifestPath)
if err != nil {
return fmt.Errorf("failed to parse manifests: %w", err)
}
if len(objects) == 0 {
return fmt.Errorf("no objects found in manifests")
}
// Inject KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT if set in operator environment
if err := injectKubernetesServiceEnv(objects); err != nil {
logger.Info("Failed to inject KUBERNETES_SERVICE_* env vars, continuing anyway", "error", err)
}
// Extract namespace from Namespace object in manifests
namespace, err := extractNamespace(objects)
if err != nil {
return fmt.Errorf("failed to extract namespace from manifests: %w", err)
}
logger.Info("Installing Flux components", "namespace", namespace)
// Apply manifests using server-side apply
logger.Info("Applying Flux manifests", "count", len(objects), "manifest", manifestPath, "namespace", namespace)
if err := applyManifests(ctx, k8sClient, objects); err != nil {
return fmt.Errorf("failed to apply manifests: %w", err)
}
logger.Info("Flux installation completed successfully")
return nil
}
// parseManifests parses YAML manifests into unstructured objects.
func parseManifests(manifestPath string) ([]*unstructured.Unstructured, error) {
data, err := os.ReadFile(manifestPath)
if err != nil {
return nil, fmt.Errorf("failed to read manifest file: %w", err)
}
return readYAMLObjects(bytes.NewReader(data))
}
// readYAMLObjects parses multi-document YAML into unstructured objects.
func readYAMLObjects(reader io.Reader) ([]*unstructured.Unstructured, error) {
var objects []*unstructured.Unstructured
yamlReader := k8syaml.NewYAMLReader(bufio.NewReader(reader))
for {
doc, err := yamlReader.Read()
if err != nil {
if err == io.EOF {
break
}
return nil, fmt.Errorf("failed to read YAML document: %w", err)
}
// Skip empty documents
if len(bytes.TrimSpace(doc)) == 0 {
continue
}
obj := &unstructured.Unstructured{}
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(doc), len(doc))
if err := decoder.Decode(obj); err != nil {
// Skip documents that can't be decoded (might be comments or empty)
if err == io.EOF {
continue
}
return nil, fmt.Errorf("failed to decode YAML document: %w", err)
}
// Skip empty objects (no kind)
if obj.GetKind() == "" {
continue
}
objects = append(objects, obj)
}
return objects, nil
}
// applyManifests applies Kubernetes objects using server-side apply.
func applyManifests(ctx context.Context, k8sClient client.Client, objects []*unstructured.Unstructured) error {
logger := log.FromContext(ctx)
decoder := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
// Separate CRDs and namespaces from other resources
var stageOne []*unstructured.Unstructured // CRDs and Namespaces
var stageTwo []*unstructured.Unstructured // Everything else
for _, obj := range objects {
if isClusterDefinition(obj) {
stageOne = append(stageOne, obj)
} else {
stageTwo = append(stageTwo, obj)
}
}
// Apply stage one (CRDs and Namespaces) first
if len(stageOne) > 0 {
logger.Info("Applying cluster definitions", "count", len(stageOne))
if err := applyObjects(ctx, k8sClient, decoder, stageOne); err != nil {
return fmt.Errorf("failed to apply cluster definitions: %w", err)
}
// Wait a bit for CRDs to be registered
time.Sleep(2 * time.Second)
}
// Apply stage two (everything else)
if len(stageTwo) > 0 {
logger.Info("Applying resources", "count", len(stageTwo))
if err := applyObjects(ctx, k8sClient, decoder, stageTwo); err != nil {
return fmt.Errorf("failed to apply resources: %w", err)
}
}
return nil
}
// applyObjects applies a list of objects using server-side apply.
func applyObjects(ctx context.Context, k8sClient client.Client, decoder runtime.Decoder, objects []*unstructured.Unstructured) error {
for _, obj := range objects {
// Use server-side apply with force ownership and field manager
// FieldManager is required for apply patch operations
patchOptions := &client.PatchOptions{
FieldManager: "cozystack-operator",
Force: func() *bool { b := true; return &b }(),
}
if err := k8sClient.Patch(ctx, obj, client.Apply, patchOptions); err != nil {
return fmt.Errorf("failed to apply object %s/%s: %w", obj.GetKind(), obj.GetName(), err)
}
}
return nil
}
// extractNamespace extracts the namespace name from the Namespace object in the manifests.
func extractNamespace(objects []*unstructured.Unstructured) (string, error) {
for _, obj := range objects {
if obj.GetKind() == "Namespace" {
namespace := obj.GetName()
if namespace == "" {
return "", fmt.Errorf("Namespace object has no name")
}
return namespace, nil
}
}
return "", fmt.Errorf("no Namespace object found in manifests")
}
// isClusterDefinition checks if an object is a CRD or Namespace.
func isClusterDefinition(obj *unstructured.Unstructured) bool {
kind := obj.GetKind()
return kind == "CustomResourceDefinition" || kind == "Namespace"
}
// injectKubernetesServiceEnv injects KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT
// environment variables into all containers of Deployment, StatefulSet, and DaemonSet objects
// if these variables are set in the operator's environment.
func injectKubernetesServiceEnv(objects []*unstructured.Unstructured) error {
kubernetesHost := os.Getenv("KUBERNETES_SERVICE_HOST")
kubernetesPort := os.Getenv("KUBERNETES_SERVICE_PORT")
// If neither variable is set, nothing to do
if kubernetesHost == "" && kubernetesPort == "" {
return nil
}
for _, obj := range objects {
kind := obj.GetKind()
if kind != "Deployment" && kind != "StatefulSet" && kind != "DaemonSet" {
continue
}
// Navigate to spec.template.spec.containers
spec, found, err := unstructured.NestedMap(obj.Object, "spec", "template", "spec")
if !found || err != nil {
continue
}
// Update containers
containers, found, err := unstructured.NestedSlice(spec, "containers")
if found && err == nil {
containers = updateContainersEnv(containers, kubernetesHost, kubernetesPort)
if err := unstructured.SetNestedSlice(spec, containers, "containers"); err != nil {
continue
}
}
// Update initContainers
initContainers, found, err := unstructured.NestedSlice(spec, "initContainers")
if found && err == nil {
initContainers = updateContainersEnv(initContainers, kubernetesHost, kubernetesPort)
if err := unstructured.SetNestedSlice(spec, initContainers, "initContainers"); err != nil {
continue
}
}
// Update spec in the object
if err := unstructured.SetNestedMap(obj.Object, spec, "spec", "template", "spec"); err != nil {
continue
}
}
return nil
}
// updateContainersEnv updates environment variables for a slice of containers.
func updateContainersEnv(containers []interface{}, kubernetesHost, kubernetesPort string) []interface{} {
for i, container := range containers {
containerMap, ok := container.(map[string]interface{})
if !ok {
continue
}
env, found, err := unstructured.NestedSlice(containerMap, "env")
if err != nil {
continue
}
if !found {
env = []interface{}{}
}
// Update or add KUBERNETES_SERVICE_HOST
if kubernetesHost != "" {
env = setEnvVar(env, "KUBERNETES_SERVICE_HOST", kubernetesHost)
}
// Update or add KUBERNETES_SERVICE_PORT
if kubernetesPort != "" {
env = setEnvVar(env, "KUBERNETES_SERVICE_PORT", kubernetesPort)
}
// Update the container's env
if err := unstructured.SetNestedSlice(containerMap, env, "env"); err != nil {
continue
}
// Update the container in the slice
containers[i] = containerMap
}
return containers
}
// setEnvVar updates or adds an environment variable in the env slice.
func setEnvVar(env []interface{}, name, value string) []interface{} {
// Check if variable already exists
for i, envVar := range env {
envVarMap, ok := envVar.(map[string]interface{})
if !ok {
continue
}
if envVarMap["name"] == name {
// Update existing variable
envVarMap["value"] = value
env[i] = envVarMap
return env
}
}
// Add new variable
env = append(env, map[string]interface{}{
"name": name,
"value": value,
})
return env
}

View File

@@ -0,0 +1,51 @@
/*
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.
*/
package fluxinstall
import (
"embed"
"fmt"
"io/fs"
"os"
"path"
)
//go:embed manifests/*.yaml
var embeddedFluxManifests embed.FS
// WriteEmbeddedManifests extracts embedded Flux manifests to a temporary directory.
func WriteEmbeddedManifests(dir string) error {
manifests, err := fs.ReadDir(embeddedFluxManifests, "manifests")
if err != nil {
return fmt.Errorf("failed to read embedded manifests: %w", err)
}
for _, manifest := range manifests {
data, err := fs.ReadFile(embeddedFluxManifests, path.Join("manifests", manifest.Name()))
if err != nil {
return fmt.Errorf("failed to read file %s: %w", manifest.Name(), err)
}
outputPath := path.Join(dir, manifest.Name())
if err := os.WriteFile(outputPath, data, 0666); err != nil {
return fmt.Errorf("failed to write file %s: %w", outputPath, err)
}
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,49 +2,38 @@ package lineagecontrollerwebhook
import (
"fmt"
"strings"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
)
type chartRef struct {
repo string
chart string
}
type appRef struct {
group string
kind string
}
type runtimeConfig struct {
chartAppMap map[chartRef]*cozyv1alpha1.CozystackResourceDefinition
appCRDMap map[appRef]*cozyv1alpha1.CozystackResourceDefinition
}
func (l *LineageControllerWebhook) initConfig() {
l.initOnce.Do(func() {
if l.config.Load() == nil {
l.config.Store(&runtimeConfig{
chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition),
appCRDMap: make(map[appRef]*cozyv1alpha1.CozystackResourceDefinition),
})
}
})
// No longer needed - we use labels directly from HelmRelease
}
func (l *LineageControllerWebhook) Map(hr *helmv2.HelmRelease) (string, string, string, error) {
cfg, ok := l.config.Load().(*runtimeConfig)
// Extract application metadata from labels
appKind, ok := hr.Labels["apps.cozystack.io/application.kind"]
if !ok {
return "", "", "", fmt.Errorf("failed to load chart-app mapping from config")
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app: missing apps.cozystack.io/application.kind label", hr.Namespace, hr.Name)
}
if hr.Spec.Chart == nil {
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name)
}
s := hr.Spec.Chart.Spec
val, ok := cfg.chartAppMap[chartRef{s.SourceRef.Name, s.Chart}]
appGroup, ok := hr.Labels["apps.cozystack.io/application.group"]
if !ok {
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name)
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app: missing apps.cozystack.io/application.group label", hr.Namespace, hr.Name)
}
return "apps.cozystack.io/v1alpha1", val.Spec.Application.Kind, val.Spec.Release.Prefix, nil
appName, ok := hr.Labels["apps.cozystack.io/application.name"]
if !ok {
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app: missing apps.cozystack.io/application.name label", hr.Namespace, hr.Name)
}
// Construct API version from group
apiVersion := fmt.Sprintf("%s/v1alpha1", appGroup)
// Extract prefix from HelmRelease name by removing the application name
// HelmRelease name format: <prefix><application-name>
prefix := strings.TrimSuffix(hr.Name, appName)
return apiVersion, appKind, prefix, nil
}

View File

@@ -1,54 +1,11 @@
package lineagecontrollerwebhook
import (
"context"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log"
)
// +kubebuilder:rbac:groups=cozystack.io,resources=cozystackresourcedefinitions,verbs=list;watch;get
// SetupWithManagerAsController is no longer needed since we don't watch ApplicationDefinitions
func (c *LineageControllerWebhook) SetupWithManagerAsController(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&cozyv1alpha1.CozystackResourceDefinition{}).
Complete(c)
}
func (c *LineageControllerWebhook) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
crds := &cozyv1alpha1.CozystackResourceDefinitionList{}
if err := c.List(ctx, crds); err != nil {
l.Error(err, "failed reading CozystackResourceDefinitions")
return ctrl.Result{}, err
}
cfg := &runtimeConfig{
chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition),
appCRDMap: make(map[appRef]*cozyv1alpha1.CozystackResourceDefinition),
}
for _, crd := range crds.Items {
chRef := chartRef{
crd.Spec.Release.Chart.SourceRef.Name,
crd.Spec.Release.Chart.Name,
}
appRef := appRef{
"apps.cozystack.io",
crd.Spec.Application.Kind,
}
newRef := crd
if _, exists := cfg.chartAppMap[chRef]; exists {
l.Info("duplicate chart mapping detected; ignoring subsequent entry", "key", chRef)
} else {
cfg.chartAppMap[chRef] = &newRef
}
if _, exists := cfg.appCRDMap[appRef]; exists {
l.Info("duplicate app mapping detected; ignoring subsequent entry", "key", appRef)
} else {
cfg.appCRDMap[appRef] = &newRef
}
}
c.config.Store(cfg)
return ctrl.Result{}, nil
// No controller needed - we use labels directly from HelmRelease
return nil
}

View File

@@ -42,7 +42,7 @@ func matchName(ctx context.Context, name string, templateContext map[string]stri
return false
}
func matchResourceToSelector(ctx context.Context, name string, templateContext, l map[string]string, s *cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool {
func matchResourceToSelector(ctx context.Context, name string, templateContext, l map[string]string, s *cozyv1alpha1.ApplicationDefinitionResourceSelector) bool {
sel, err := metav1.LabelSelectorAsSelector(&s.LabelSelector)
if err != nil {
log.FromContext(ctx).Error(err, "failed to convert label selector to selector")
@@ -53,7 +53,7 @@ func matchResourceToSelector(ctx context.Context, name string, templateContext,
return labelMatches && nameMatches
}
func matchResourceToSelectorArray(ctx context.Context, name string, templateContext, l map[string]string, ss []*cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool {
func matchResourceToSelectorArray(ctx context.Context, name string, templateContext, l map[string]string, ss []*cozyv1alpha1.ApplicationDefinitionResourceSelector) bool {
for _, s := range ss {
if matchResourceToSelector(ctx, name, templateContext, l, s) {
return true
@@ -62,7 +62,7 @@ func matchResourceToSelectorArray(ctx context.Context, name string, templateCont
return false
}
func matchResourceToExcludeInclude(ctx context.Context, name string, templateContext, l map[string]string, resources *cozyv1alpha1.CozystackResourceDefinitionResources) bool {
func matchResourceToExcludeInclude(ctx context.Context, name string, templateContext, l map[string]string, resources *cozyv1alpha1.ApplicationDefinitionResources) bool {
if resources == nil {
return false
}

View File

@@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/cozystack/cozystack/pkg/lineage"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -33,8 +32,8 @@ const (
ManagerNameKey = "apps.cozystack.io/application.name"
)
// getResourceSelectors returns the appropriate CozystackResourceDefinitionResources for a given GroupKind
func (h *LineageControllerWebhook) getResourceSelectors(gk schema.GroupKind, crd *cozyv1alpha1.CozystackResourceDefinition) *cozyv1alpha1.CozystackResourceDefinitionResources {
// getResourceSelectors returns the appropriate ApplicationDefinitionResources for a given GroupKind
func (h *LineageControllerWebhook) getResourceSelectors(gk schema.GroupKind, crd *cozyv1alpha1.ApplicationDefinition) *cozyv1alpha1.ApplicationDefinitionResources {
switch {
case gk.Group == "" && gk.Kind == "Secret":
return &crd.Spec.Secrets
@@ -88,13 +87,16 @@ func (h *LineageControllerWebhook) Handle(ctx context.Context, req admission.Req
"name", req.Name,
"operation", req.Operation,
)
logger.Info("webhook called", "gvk", req.Kind.String(), "namespace", req.Namespace, "name", req.Name, "operation", req.Operation)
warn := make(admission.Warnings, 0)
obj := &unstructured.Unstructured{}
if err := h.decodeUnstructured(req, obj); err != nil {
logger.Error(err, "failed to decode object")
return admission.Errored(400, fmt.Errorf("decode object: %w", err))
}
logger.V(1).Info("decoded object", "labels", obj.GetLabels(), "ownerReferences", obj.GetOwnerReferences())
labels, err := h.computeLabels(ctx, obj)
for {
if err != nil && errors.Is(err, NoAncestors) {
@@ -117,9 +119,10 @@ func (h *LineageControllerWebhook) Handle(ctx context.Context, req admission.Req
mutated, err := json.Marshal(obj)
if err != nil {
return admission.Errored(500, fmt.Errorf("marshal mutated pod: %w", err))
logger.Error(err, "failed to marshal mutated object")
return admission.Errored(500, fmt.Errorf("marshal mutated object: %w", err))
}
logger.V(1).Info("mutated pod", "namespace", obj.GetNamespace(), "name", obj.GetName())
logger.Info("mutated object", "namespace", obj.GetNamespace(), "name", obj.GetName(), "labels", labels)
return admission.PatchResponseFromRaw(req.Object.Raw, mutated).WithWarnings(warn...)
}
@@ -156,21 +159,9 @@ func (h *LineageControllerWebhook) computeLabels(ctx context.Context, o *unstruc
ManagerKindKey: obj.GetKind(),
ManagerNameKey: obj.GetName(),
}
templateLabels := map[string]string{
"kind": strings.ToLower(obj.GetKind()),
"name": obj.GetName(),
"namespace": o.GetNamespace(),
}
cfg := h.config.Load().(*runtimeConfig)
crd := cfg.appCRDMap[appRef{gv.Group, obj.GetKind()}]
resourceSelectors := h.getResourceSelectors(o.GroupVersionKind().GroupKind(), crd)
labels[corev1alpha1.TenantResourceLabelKey] = func(b bool) string {
if b {
return corev1alpha1.TenantResourceLabelValue
}
return "false"
}(matchResourceToExcludeInclude(ctx, o.GetName(), templateLabels, o.GetLabels(), resourceSelectors))
// Resource selectors are no longer needed since we don't use ApplicationDefinitions
// Set tenant resource label to false by default (can be overridden by other logic if needed)
labels[corev1alpha1.TenantResourceLabelKey] = "false"
return labels, err
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,541 @@
/*
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.
*/
package operator
import (
"context"
"encoding/json"
"fmt"
"strings"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
sourcewatcherv1beta1 "github.com/fluxcd/source-watcher/api/v2/v1beta1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"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/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// PlatformReconciler reconciles Platform resources
type PlatformReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=cozystack.io,resources=platforms,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=cozystack.io,resources=platforms/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=source.extensions.fluxcd.io,resources=artifactgenerators,verbs=get;list;watch;create;update;patch;delete
// Reconcile is part of the main kubernetes reconciliation loop
func (r *PlatformReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
platform := &cozyv1alpha1.Platform{}
if err := r.Get(ctx, req.NamespacedName, platform); err != nil {
if apierrors.IsNotFound(err) {
// Cleanup orphaned resources
return r.cleanupOrphanedResources(ctx, req.NamespacedName)
}
return ctrl.Result{}, err
}
// Set defaults
if platform.Spec.Interval == nil {
platform.Spec.Interval = &metav1.Duration{Duration: 5 * 60 * 1000000000} // 5m
}
// Reconcile ArtifactGenerator
if err := r.reconcileArtifactGenerator(ctx, platform); err != nil {
logger.Error(err, "failed to reconcile ArtifactGenerator")
return ctrl.Result{}, err
}
// Reconcile HelmRelease
if err := r.reconcileHelmRelease(ctx, platform); err != nil {
logger.Error(err, "failed to reconcile HelmRelease")
return ctrl.Result{}, err
}
// Cleanup orphaned resources with platform label
if err := r.cleanupOrphanedPlatformResources(ctx, platform); err != nil {
logger.Error(err, "failed to cleanup orphaned platform resources")
// Don't return error, just log it - cleanup is best effort
}
return ctrl.Result{}, nil
}
// reconcileArtifactGenerator creates or updates the ArtifactGenerator for the platform
func (r *PlatformReconciler) reconcileArtifactGenerator(ctx context.Context, platform *cozyv1alpha1.Platform) error {
logger := log.FromContext(ctx)
// Use fixed namespace for cluster-scoped resource
namespace := "cozy-system"
// Get basePath with default values (already includes full path to platform)
basePath := r.getBasePath(platform)
// Build full path from basePath (basePath already contains the full path)
fullPath := r.buildSourcePath(platform.Spec.SourceRef.Name, basePath, "")
// Extract the last component for the artifact name
artifactPathParts := strings.Split(strings.Trim(basePath, "/"), "/")
artifactName := artifactPathParts[len(artifactPathParts)-1]
copyOps := []sourcewatcherv1beta1.CopyOperation{
{
From: fullPath + "/**",
To: fmt.Sprintf("@artifact/%s/", artifactName),
},
}
// Create ArtifactGenerator
ag := &sourcewatcherv1beta1.ArtifactGenerator{
ObjectMeta: metav1.ObjectMeta{
Name: platform.Name,
Namespace: namespace,
Labels: map[string]string{
"cozystack.io/platform": platform.Name,
},
},
Spec: sourcewatcherv1beta1.ArtifactGeneratorSpec{
Sources: []sourcewatcherv1beta1.SourceReference{
{
Alias: platform.Spec.SourceRef.Name,
Kind: platform.Spec.SourceRef.Kind,
Name: platform.Spec.SourceRef.Name,
Namespace: platform.Spec.SourceRef.Namespace,
},
},
OutputArtifacts: []sourcewatcherv1beta1.OutputArtifact{
{
Name: artifactName,
Copy: copyOps,
},
},
},
}
// Set ownerReference
ag.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: platform.APIVersion,
Kind: platform.Kind,
Name: platform.Name,
UID: platform.UID,
Controller: func() *bool { b := true; return &b }(),
},
}
logger.Info("reconciling ArtifactGenerator", "name", platform.Name, "namespace", namespace)
if err := r.createOrUpdate(ctx, ag); err != nil {
return fmt.Errorf("failed to reconcile ArtifactGenerator %s: %w", platform.Name, err)
}
logger.Info("reconciled ArtifactGenerator", "name", platform.Name, "namespace", namespace)
return nil
}
// reconcileHelmRelease creates or updates the HelmRelease for the platform
func (r *PlatformReconciler) reconcileHelmRelease(ctx context.Context, platform *cozyv1alpha1.Platform) error {
logger := log.FromContext(ctx)
// HelmRelease name is fixed: cozystack-platform
// Use fixed namespace for cluster-scoped resource
namespace := "cozy-system"
// Get artifact name (last component of basePath)
basePath := r.getBasePath(platform)
artifactPathParts := strings.Split(strings.Trim(basePath, "/"), "/")
artifactName := artifactPathParts[len(artifactPathParts)-1]
// Merge values with sourceRef
values := r.mergeValuesWithSourceRef(platform.Spec.Values, platform.Spec.SourceRef)
// Create HelmRelease
hr := &helmv2.HelmRelease{
ObjectMeta: metav1.ObjectMeta{
Name: platform.Name,
Namespace: namespace,
Labels: map[string]string{
"cozystack.io/platform": platform.Name,
},
},
Spec: helmv2.HelmReleaseSpec{
Interval: *platform.Spec.Interval,
TargetNamespace: "cozy-system",
ReleaseName: "cozystack-platform",
ChartRef: &helmv2.CrossNamespaceSourceReference{
Kind: "ExternalArtifact",
Name: artifactName,
Namespace: namespace,
},
Values: values,
Install: &helmv2.Install{
Remediation: &helmv2.InstallRemediation{
Retries: -1,
},
},
Upgrade: &helmv2.Upgrade{
Remediation: &helmv2.UpgradeRemediation{
Retries: -1,
},
},
},
}
// Set ownerReference
hr.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: platform.APIVersion,
Kind: platform.Kind,
Name: platform.Name,
UID: platform.UID,
Controller: func() *bool { b := true; return &b }(),
},
}
logger.Info("reconciling HelmRelease", "name", platform.Name, "namespace", namespace)
if err := r.createOrUpdate(ctx, hr); err != nil {
return fmt.Errorf("failed to reconcile HelmRelease %s: %w", platform.Name, err)
}
logger.Info("reconciled HelmRelease", "name", platform.Name, "namespace", namespace)
return nil
}
// mergeValuesWithSourceRef merges platform values with sourceRef
func (r *PlatformReconciler) mergeValuesWithSourceRef(values *apiextensionsv1.JSON, sourceRef cozyv1alpha1.SourceRef) *apiextensionsv1.JSON {
// Build sourceRef map
sourceRefMap := map[string]interface{}{
"kind": sourceRef.Kind,
"name": sourceRef.Name,
"namespace": sourceRef.Namespace,
}
// If values is nil or empty, create new values with sourceRef
if values == nil || len(values.Raw) == 0 {
valuesMap := map[string]interface{}{
"sourceRef": sourceRefMap,
}
raw, _ := json.Marshal(valuesMap)
return &apiextensionsv1.JSON{Raw: raw}
}
// Parse existing values
var valuesMap map[string]interface{}
if err := json.Unmarshal(values.Raw, &valuesMap); err != nil {
// If unmarshal fails, create new values with sourceRef
valuesMap = map[string]interface{}{
"sourceRef": sourceRefMap,
}
raw, _ := json.Marshal(valuesMap)
return &apiextensionsv1.JSON{Raw: raw}
}
// Merge sourceRef into values (overwrite if exists)
valuesMap["sourceRef"] = sourceRefMap
// Marshal back to JSON
raw, err := json.Marshal(valuesMap)
if err != nil {
// If marshal fails, return original values
return values
}
return &apiextensionsv1.JSON{Raw: raw}
}
// getBasePath returns the basePath with default values based on source kind
func (r *PlatformReconciler) getBasePath(platform *cozyv1alpha1.Platform) string {
if platform.Spec.BasePath != "" {
return platform.Spec.BasePath
}
// Default values based on kind
if platform.Spec.SourceRef.Kind == "OCIRepository" {
return "core/platform" // Full path for OCI
}
// Default for GitRepository
return "packages/core/platform" // Full path for Git
}
// buildSourcePath builds the full source path from basePath and chart path
func (r *PlatformReconciler) buildSourcePath(sourceName, basePath, chartPath string) string {
// Remove leading/trailing slashes and combine
parts := []string{}
if basePath != "" {
parts = append(parts, strings.Trim(basePath, "/"))
}
if chartPath != "" {
parts = append(parts, strings.Trim(chartPath, "/"))
}
fullPath := strings.Join(parts, "/")
if fullPath == "" {
return fmt.Sprintf("@%s", sourceName)
}
return fmt.Sprintf("@%s/%s", sourceName, fullPath)
}
// cleanupOrphanedResources removes ArtifactGenerator and HelmRelease when Platform is deleted
func (r *PlatformReconciler) cleanupOrphanedResources(ctx context.Context, name client.ObjectKey) (ctrl.Result, error) {
logger := log.FromContext(ctx)
namespace := "cozy-system"
// Cleanup HelmReleases with the platform label that don't match
hrList := &helmv2.HelmReleaseList{}
if err := r.List(ctx, hrList, client.InNamespace(namespace), client.MatchingLabels{
"cozystack.io/platform": name.Name,
}); err != nil {
logger.Error(err, "failed to list HelmReleases for cleanup")
return ctrl.Result{}, err
}
for i := range hrList.Items {
hr := &hrList.Items[i]
// Check if this HelmRelease should exist (matches current Platform name)
// Since Platform is being deleted, all matching HelmReleases should be deleted
// OwnerReferences should handle this, but we'll also delete explicitly
if err := r.Delete(ctx, hr); err != nil {
if !apierrors.IsNotFound(err) {
logger.Error(err, "failed to delete orphaned HelmRelease", "name", hr.Name)
}
} else {
logger.Info("deleted orphaned HelmRelease", "name", hr.Name)
}
}
// Cleanup ArtifactGenerators with the platform label
agList := &sourcewatcherv1beta1.ArtifactGeneratorList{}
if err := r.List(ctx, agList, client.InNamespace(namespace), client.MatchingLabels{
"cozystack.io/platform": name.Name,
}); err != nil {
logger.Error(err, "failed to list ArtifactGenerators for cleanup")
return ctrl.Result{}, err
}
for i := range agList.Items {
ag := &agList.Items[i]
if err := r.Delete(ctx, ag); err != nil {
if !apierrors.IsNotFound(err) {
logger.Error(err, "failed to delete orphaned ArtifactGenerator", "name", ag.Name)
}
} else {
logger.Info("deleted orphaned ArtifactGenerator", "name", ag.Name)
}
}
return ctrl.Result{}, nil
}
// cleanupOrphanedPlatformResources removes HelmRelease and ArtifactGenerator resources
// that have the platform label but don't match the current Platform
func (r *PlatformReconciler) cleanupOrphanedPlatformResources(ctx context.Context, platform *cozyv1alpha1.Platform) error {
logger := log.FromContext(ctx)
namespace := "cozy-system"
platformName := platform.Name
// Cleanup orphaned HelmReleases
hrList := &helmv2.HelmReleaseList{}
if err := r.List(ctx, hrList, client.InNamespace(namespace), client.MatchingLabels{
"cozystack.io/platform": platformName,
}); err != nil {
return fmt.Errorf("failed to list HelmReleases: %w", err)
}
for i := range hrList.Items {
hr := &hrList.Items[i]
// Only delete if it doesn't match the current Platform name
// (in case Platform name changed)
if hr.Name != platformName {
logger.Info("deleting orphaned HelmRelease", "name", hr.Name, "expected", platformName)
if err := r.Delete(ctx, hr); err != nil {
if !apierrors.IsNotFound(err) {
logger.Error(err, "failed to delete orphaned HelmRelease", "name", hr.Name)
// Continue with other resources
}
}
}
}
// Cleanup orphaned ArtifactGenerators
agList := &sourcewatcherv1beta1.ArtifactGeneratorList{}
if err := r.List(ctx, agList, client.InNamespace(namespace), client.MatchingLabels{
"cozystack.io/platform": platformName,
}); err != nil {
return fmt.Errorf("failed to list ArtifactGenerators: %w", err)
}
for i := range agList.Items {
ag := &agList.Items[i]
// Only delete if it doesn't match the current Platform name
if ag.Name != platformName {
logger.Info("deleting orphaned ArtifactGenerator", "name", ag.Name, "expected", platformName)
if err := r.Delete(ctx, ag); err != nil {
if !apierrors.IsNotFound(err) {
logger.Error(err, "failed to delete orphaned ArtifactGenerator", "name", ag.Name)
// Continue with other resources
}
}
}
}
return nil
}
// createOrUpdate creates or updates a resource
func (r *PlatformReconciler) createOrUpdate(ctx context.Context, obj client.Object) error {
existing := obj.DeepCopyObject().(client.Object)
key := client.ObjectKeyFromObject(obj)
err := r.Get(ctx, key, existing)
if apierrors.IsNotFound(err) {
return r.Create(ctx, obj)
} else if err != nil {
return err
}
// Preserve resource version
obj.SetResourceVersion(existing.GetResourceVersion())
// Merge labels and annotations
labels := obj.GetLabels()
if labels == nil {
labels = make(map[string]string)
}
for k, v := range existing.GetLabels() {
if _, ok := labels[k]; !ok {
labels[k] = v
}
}
obj.SetLabels(labels)
annotations := obj.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
for k, v := range existing.GetAnnotations() {
if _, ok := annotations[k]; !ok {
annotations[k] = v
}
}
obj.SetAnnotations(annotations)
// For ArtifactGenerator, explicitly update Spec and ownerReferences
if ag, ok := obj.(*sourcewatcherv1beta1.ArtifactGenerator); ok {
if existingAG, ok := existing.(*sourcewatcherv1beta1.ArtifactGenerator); ok {
logger := log.FromContext(ctx)
logger.V(1).Info("updating ArtifactGenerator Spec", "name", ag.Name, "namespace", ag.Namespace)
existingAG.Spec = ag.Spec
existingAG.SetLabels(ag.GetLabels())
existingAG.SetAnnotations(ag.GetAnnotations())
// Always use ownerReferences from the new object (set in reconcileArtifactGenerator)
existingAG.SetOwnerReferences(ag.GetOwnerReferences())
obj = existingAG
}
}
// For HelmRelease, explicitly update Spec and ownerReferences
if hr, ok := obj.(*helmv2.HelmRelease); ok {
if existingHR, ok := existing.(*helmv2.HelmRelease); ok {
logger := log.FromContext(ctx)
logger.V(1).Info("updating HelmRelease Spec", "name", hr.Name, "namespace", hr.Namespace)
existingHR.Spec = hr.Spec
existingHR.SetLabels(hr.GetLabels())
existingHR.SetAnnotations(hr.GetAnnotations())
// Always use ownerReferences from the new object (set in reconcileHelmRelease)
existingHR.SetOwnerReferences(hr.GetOwnerReferences())
obj = existingHR
}
}
return r.Update(ctx, obj)
}
// SetupWithManager sets up the controller with the Manager
func (r *PlatformReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Named("cozystack-platform").
For(&cozyv1alpha1.Platform{}).
Watches(
&helmv2.HelmRelease{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
hr, ok := obj.(*helmv2.HelmRelease)
if !ok {
return nil
}
// Only watch HelmReleases with cozystack.io/platform label
platformName := hr.Labels["cozystack.io/platform"]
if platformName == "" {
return nil
}
return []reconcile.Request{
{
NamespacedName: client.ObjectKey{
Name: platformName,
// Cluster-scoped resource has no namespace
},
},
}
}),
builder.WithPredicates(
predicate.NewPredicateFuncs(func(obj client.Object) bool {
// Only watch resources with cozystack.io/platform label
labels := obj.GetLabels()
return labels != nil && labels["cozystack.io/platform"] != ""
}),
),
).
Watches(
&sourcewatcherv1beta1.ArtifactGenerator{},
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
ag, ok := obj.(*sourcewatcherv1beta1.ArtifactGenerator)
if !ok {
return nil
}
// Only watch ArtifactGenerators with cozystack.io/platform label
platformName := ag.Labels["cozystack.io/platform"]
if platformName == "" {
return nil
}
return []reconcile.Request{
{
NamespacedName: client.ObjectKey{
Name: platformName,
// Cluster-scoped resource has no namespace
},
},
}
}),
builder.WithPredicates(
predicate.NewPredicateFuncs(func(obj client.Object) bool {
// Only watch resources with cozystack.io/platform label
labels := obj.GetLabels()
return labels != nil && labels["cozystack.io/platform"] != ""
}),
),
).
Complete(r)
}

View File

@@ -11,13 +11,13 @@ import (
type Memory struct {
mu sync.RWMutex
data map[string]cozyv1alpha1.CozystackResourceDefinition
data map[string]cozyv1alpha1.ApplicationDefinition
primed bool
primeOnce sync.Once
}
func New() *Memory {
return &Memory{data: make(map[string]cozyv1alpha1.CozystackResourceDefinition)}
return &Memory{data: make(map[string]cozyv1alpha1.ApplicationDefinition)}
}
var (
@@ -30,7 +30,7 @@ func Global() *Memory {
return global
}
func (m *Memory) Upsert(obj *cozyv1alpha1.CozystackResourceDefinition) {
func (m *Memory) Upsert(obj *cozyv1alpha1.ApplicationDefinition) {
if obj == nil {
return
}
@@ -45,10 +45,10 @@ func (m *Memory) Delete(name string) {
m.mu.Unlock()
}
func (m *Memory) Snapshot() []cozyv1alpha1.CozystackResourceDefinition {
func (m *Memory) Snapshot() []cozyv1alpha1.ApplicationDefinition {
m.mu.RLock()
defer m.mu.RUnlock()
out := make([]cozyv1alpha1.CozystackResourceDefinition, 0, len(m.data))
out := make([]cozyv1alpha1.ApplicationDefinition, 0, len(m.data))
for _, v := range m.data {
out = append(out, v)
}
@@ -72,7 +72,7 @@ func (m *Memory) EnsurePrimingWithManager(mgr ctrl.Manager) error {
if ok := mgr.GetCache().WaitForCacheSync(ctx); !ok {
return nil
}
var list cozyv1alpha1.CozystackResourceDefinitionList
var list cozyv1alpha1.ApplicationDefinitionList
if err := mgr.GetClient().List(ctx, &list); err == nil {
for i := range list.Items {
m.Upsert(&list.Items[i])
@@ -87,11 +87,11 @@ func (m *Memory) EnsurePrimingWithManager(mgr ctrl.Manager) error {
return errOut
}
func (m *Memory) ListFromCacheOrAPI(ctx context.Context, c client.Client) ([]cozyv1alpha1.CozystackResourceDefinition, error) {
func (m *Memory) ListFromCacheOrAPI(ctx context.Context, c client.Client) ([]cozyv1alpha1.ApplicationDefinition, error) {
if m.IsPrimed() {
return m.Snapshot(), nil
}
var list cozyv1alpha1.CozystackResourceDefinitionList
var list cozyv1alpha1.ApplicationDefinitionList
if err := c.List(ctx, &list); err != nil {
return nil, err
}

View File

@@ -5,6 +5,7 @@ import (
"context"
"fmt"
"net/http"
"sort"
"strings"
"time"
@@ -120,16 +121,50 @@ func (c *Collector) collect(ctx context.Context) {
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
}
// Get all Bundles
var bundleList cozyv1alpha1.BundleList
bundleNameStr := ""
bundleEnable := ""
bundleDisable := ""
oidcEnabled := "false"
oidcEnabled := cozystackCM.Data["oidc-enabled"]
bundle := cozystackCM.Data["bundle-name"]
bundleEnable := cozystackCM.Data["bundle-enable"]
bundleDisable := cozystackCM.Data["bundle-disable"]
if err := c.client.List(ctx, &bundleList); err != nil {
logger.Info(fmt.Sprintf("Failed to list Bundles: %v", err))
// Continue with empty bundle data instead of returning
} else {
// Collect bundle names (sorted alphabetically)
bundleNames := make([]string, 0, len(bundleList.Items))
for _, bundle := range bundleList.Items {
bundleNames = append(bundleNames, bundle.Name)
}
sort.Strings(bundleNames)
bundleNameStr = strings.Join(bundleNames, ",")
// Collect all packages from all bundles
var allEnabledPackages []string
var allDisabledPackages []string
for _, bundle := range bundleList.Items {
for _, pkg := range bundle.Spec.Packages {
if pkg.Disabled {
allDisabledPackages = append(allDisabledPackages, pkg.Name)
} else {
allEnabledPackages = append(allEnabledPackages, pkg.Name)
// Check if keycloak package is enabled
if pkg.Name == "keycloak" {
oidcEnabled = "true"
}
}
}
}
// Sort package lists alphabetically
sort.Strings(allEnabledPackages)
sort.Strings(allDisabledPackages)
bundleEnable = strings.Join(allEnabledPackages, ",")
bundleDisable = strings.Join(allDisabledPackages, ",")
}
// Get Kubernetes version from nodes
var nodeList corev1.NodeList
@@ -143,32 +178,41 @@ func (c *Collector) collect(ctx context.Context) {
// Add Cozystack info metric
if len(nodeList.Items) > 0 {
k8sVersion, _ := c.discoveryClient.ServerVersion()
k8sVersion := "unknown"
if version, err := c.discoveryClient.ServerVersion(); err == nil && version != nil {
k8sVersion = version.String()
}
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",
"cozy_cluster_info{cozystack_version=\"%s\",kubernetes_version=\"%s\",oidc_enabled=\"%s\",bundle_name=\"%s\",bundle_enable=\"%s\",bundle_disable=\"%s\"} 1\n",
c.config.CozystackVersion,
k8sVersion,
oidcEnabled,
bundle,
bundleNameStr,
bundleEnable,
bundleDisable,
))
}
// Collect node metrics
if len(nodeList.Items) > 0 {
nodeOSCount := make(map[string]int)
kernelVersion := "unknown"
for _, node := range nodeList.Items {
key := fmt.Sprintf("%s (%s)", node.Status.NodeInfo.OperatingSystem, node.Status.NodeInfo.OSImage)
nodeOSCount[key] = nodeOSCount[key] + 1
if kernelVersion == "unknown" && node.Status.NodeInfo.KernelVersion != "" {
kernelVersion = node.Status.NodeInfo.KernelVersion
}
}
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,
kernelVersion,
count,
))
}
}
// Collect LoadBalancer services metrics
@@ -248,9 +292,8 @@ func (c *Collector) collect(ctx context.Context) {
var monitorList cozyv1alpha1.WorkloadMonitorList
if err := c.client.List(ctx, &monitorList); err != nil {
logger.Info(fmt.Sprintf("Failed to list WorkloadMonitors: %v", err))
return
}
// Continue without workload metrics instead of returning
} else {
for _, monitor := range monitorList.Items {
metrics.WriteString(fmt.Sprintf(
"cozy_workloads_count{uid=\"%s\",kind=\"%s\",type=\"%s\",version=\"%s\"} %d\n",
@@ -260,6 +303,7 @@ func (c *Collector) collect(ctx context.Context) {
monitor.Spec.Version,
monitor.Status.ObservedReplicas,
))
}
}
// Send metrics

View File

@@ -1,12 +1,7 @@
OUT=../../_out/repos/apps
CHARTS := $(shell find . -maxdepth 2 -name Chart.yaml | awk -F/ '{print $$2}')
include ../../scripts/common-envs.mk
repo:
rm -rf "$(OUT)"
helm package -d "$(OUT)" $(CHARTS) --version $(COZYSTACK_VERSION)
helm repo index "$(OUT)"
include ../../hack/common-envs.mk
fix-charts:
find . -maxdepth 2 -name Chart.yaml | awk -F/ '{print $$2}' | while read i; do sed -i -e "s/^name: .*/name: $$i/" -e "s/^version: .*/version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process/g" "$$i/Chart.yaml"; done

View File

@@ -1,8 +0,0 @@
### How to test packages local
```bash
cd packages/core/installer
make image-cozystack REGISTRY=YOUR_CUSTOM_REGISTRY
make apply
kubectl delete po -l app=source-controller -n cozy-fluxcd
```

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,5 +1,6 @@
{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }}
{{- $seaweedfs := index $myNS.metadata.annotations "namespace.cozystack.io/seaweedfs" }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $namespace := .Values._namespace | default dict }}
{{- $seaweedfs := dig "seaweedfs" "" $namespace }}
apiVersion: objectstorage.k8s.io/v1alpha1
kind: BucketClaim
metadata:

View File

@@ -3,15 +3,10 @@ kind: HelmRelease
metadata:
name: {{ .Release.Name }}-system
spec:
chart:
spec:
chart: cozy-bucket
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-bucket
namespace: cozy-system
interval: 5m
timeout: 10m
install:

View File

@@ -1,7 +1,7 @@
CLICKHOUSE_BACKUP_TAG = $(shell awk '$$0 ~ /^version:/ {print $$2}' Chart.yaml)
include ../../../scripts/common-envs.mk
include ../../../scripts/package.mk
include ../../../hack/common-envs.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,5 +1,5 @@
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $clusterDomain := dig "networking" "clusterDomain" "cozy.local" $cozystack }}
{{- if .Values.clickhouseKeeper.enabled }}
apiVersion: "clickhouse-keeper.altinity.com/v1"

View File

@@ -1,5 +1,5 @@
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $clusterDomain := dig "networking" "clusterDomain" "cozy.local" $cozystack }}
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }}
{{- $passwords := dict }}
{{- $users := .Values.users }}

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -50,15 +50,13 @@ spec:
postgresUID: 999
postgresGID: 999
enableSuperuserAccess: true
{{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }}
{{- if $configMap }}
{{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }}
{{- if $rawConstraints }}
{{- $rawConstraints | fromYaml | toYaml | nindent 2 }}
labelSelector:
matchLabels:
cnpg.io/cluster: {{ .Release.Name }}-postgres
{{- end }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $topologySpreadConstraints := dig "scheduling" "topologySpreadConstraints" (list) $cozystack }}
{{- if $topologySpreadConstraints }}
topologySpreadConstraints:
{{- range $topologySpreadConstraints }}
- {{- mergeOverwrite (dict "labelSelector" (dict "matchLabels" (dict "cnpg.io/cluster" (printf "%s-postgres" $.Release.Name)))) . | toYaml | nindent 6 | trim }}
{{- end }}
{{- end }}
minSyncReplicas: {{ .Values.quorum.minSyncReplicas }}
maxSyncReplicas: {{ .Values.quorum.maxSyncReplicas }}

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,5 +1,5 @@
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" | default (dict "data" (dict)) }}
{{- $clusterDomain := index $cozyConfig.data "cluster-domain" | default "cozy.local" }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $clusterDomain := dig "networking" "clusterDomain" "cozy.local" $cozystack }}
---
apiVersion: apps.foundationdb.org/v1beta2
kind: FoundationDBCluster

View File

@@ -1,7 +1,7 @@
NGINX_CACHE_TAG = $(shell awk '$$1 == "version:" {print $$2}' Chart.yaml)
include ../../../scripts/common-envs.mk
include ../../../scripts/package.mk
include ../../../hack/common-envs.mk
include ../../../hack/package.mk
image: image-nginx

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
PRESET_ENUM := ["nano","micro","small","medium","large","xlarge","2xlarge"]
generate:

View File

@@ -1,8 +1,8 @@
KUBERNETES_VERSION = v1.33
KUBERNETES_PKG_TAG = $(shell awk '$$1 == "version:" {print $$2}' Chart.yaml)
include ../../../scripts/common-envs.mk
include ../../../scripts/package.mk
include ../../../hack/common-envs.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,7 +1,8 @@
{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }}
{{- $etcd := index $myNS.metadata.annotations "namespace.cozystack.io/etcd" }}
{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }}
{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $namespace := .Values._namespace | default dict }}
{{- $etcd := dig "etcd" "" $namespace }}
{{- $ingress := dig "ingress" "" $namespace }}
{{- $host := dig "host" "" $namespace }}
{{- $kubevirtmachinetemplateNames := list }}
{{- define "kubevirtmachinetemplate" -}}
spec:
@@ -31,14 +32,11 @@ spec:
{{- end }}
cluster.x-k8s.io/deployment-name: {{ $.Release.Name }}-{{ .groupName }}
spec:
{{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }}
{{- if $configMap }}
{{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }}
{{- if $rawConstraints }}
{{- $rawConstraints | fromYaml | toYaml | nindent 10 }}
labelSelector:
matchLabels:
cluster.x-k8s.io/cluster-name: {{ $.Release.Name }}
{{- $cozystack := $.Values._cozystack | default dict }}
{{- $topologySpreadConstraints := dig "scheduling" "topologySpreadConstraints" (list) $cozystack }}
{{- if $topologySpreadConstraints }}
{{- range $topologySpreadConstraints }}
- {{- mergeOverwrite (dict "labelSelector" (dict "matchLabels" (dict "cluster.x-k8s.io/cluster-name" $.Release.Name))) . | toYaml | nindent 12 | trim }}
{{- end }}
{{- end }}
domain:

View File

@@ -8,15 +8,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: cert-manager-crds
chart:
spec:
chart: cozy-cert-manager-crds
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-cert-manager-crds
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -8,15 +8,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: cert-manager
chart:
spec:
chart: cozy-cert-manager
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-cert-manager
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -22,15 +22,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: cilium
chart:
spec:
chart: cozy-cilium
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-cilium
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -13,15 +13,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: coredns
chart:
spec:
chart: cozy-coredns
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-coredns
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -8,15 +8,10 @@ metadata:
spec:
interval: 5m
releaseName: csi
chart:
spec:
chart: cozy-kubevirt-csi-node
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-kubevirt-csi-node
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -8,15 +8,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: fluxcd-operator
chart:
spec:
chart: cozy-fluxcd-operator
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-fluxcd-operator
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig
@@ -56,15 +51,10 @@ metadata:
spec:
interval: 5m
releaseName: fluxcd
chart:
spec:
chart: cozy-fluxcd
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-fluxcd
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-kubeconfig

View File

@@ -8,15 +8,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: gateway-api-crds
chart:
spec:
chart: cozy-gateway-api-crds
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-gateway-api-crds
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -8,15 +8,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: gpu-operator
chart:
spec:
chart: cozy-gpu-operator
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-gpu-operator
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -27,15 +27,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: ingress-nginx
chart:
spec:
chart: cozy-ingress-nginx
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-ingress-nginx
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -1,5 +1,6 @@
{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }}
{{- $targetTenant := index $myNS.metadata.annotations "namespace.cozystack.io/monitoring" }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $namespace := .Values._namespace | default dict }}
{{- $targetTenant := dig "monitoring" "" $namespace }}
{{- if .Values.addons.monitoringAgents.enabled }}
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
@@ -10,15 +11,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: cozy-monitoring-agents
chart:
spec:
chart: cozy-monitoring-agents
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-monitoring-agents
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -8,15 +8,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: velero
chart:
spec:
chart: cozy-velero
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-velero
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -9,15 +9,10 @@ metadata:
spec:
interval: 5m
releaseName: vertical-pod-autoscaler-crds
chart:
spec:
chart: cozy-vertical-pod-autoscaler-crds
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-vertical-pod-autoscaler-crds
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -1,8 +1,8 @@
{{- define "cozystack.defaultVPAValues" -}}
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }}
{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }}
{{- $targetTenant := index $myNS.metadata.annotations "namespace.cozystack.io/monitoring" }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $clusterDomain := dig "networking" "clusterDomain" "cozy.local" $cozystack }}
{{- $namespace := .Values._namespace | default dict }}
{{- $targetTenant := dig "monitoring" "" $namespace }}
vpaForVPA: false
vertical-pod-autoscaler:
recommender:
@@ -36,15 +36,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: vertical-pod-autoscaler
chart:
spec:
chart: cozy-vertical-pod-autoscaler
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-vertical-pod-autoscaler
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -8,15 +8,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: cozy-victoria-metrics-operator
chart:
spec:
chart: cozy-victoria-metrics-operator
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-victoria-metrics-operator
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -7,15 +7,10 @@ metadata:
cozystack.io/target-cluster-name: {{ .Release.Name }}
spec:
releaseName: vsnap-crd
chart:
spec:
chart: cozy-vsnap-crd
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-iaas-kubernetes-volumesnapshot-crd
namespace: cozy-system
kubeConfig:
secretRef:
name: {{ .Release.Name }}-admin-kubeconfig

View File

@@ -1,5 +1,6 @@
{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }}
{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $namespace := .Values._namespace | default dict }}
{{- $ingress := dig "ingress" "" $namespace }}
{{- if and (eq .Values.addons.ingressNginx.exposeMethod "Proxied") .Values.addons.ingressNginx.hosts }}
---
apiVersion: networking.k8s.io/v1

View File

@@ -1,7 +1,7 @@
MARIADB_BACKUP_TAG = $(shell awk '$$1 == "version:" {print $$2}' Chart.yaml)
include ../../../scripts/common-envs.mk
include ../../../scripts/package.mk
include ../../../hack/common-envs.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,5 +1,5 @@
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $clusterDomain := dig "networking" "clusterDomain" "cozy.local" $cozystack }}
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }}
{{- $passwords := dict }}
@@ -35,15 +35,10 @@ kind: HelmRelease
metadata:
name: {{ .Release.Name }}-system
spec:
chart:
spec:
chart: cozy-nats
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-system
namespace: cozy-system
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-paas-nats
namespace: cozy-system
interval: 5m
timeout: 10m
install:

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -45,15 +45,13 @@ spec:
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 4 }}
enableSuperuserAccess: true
{{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }}
{{- if $configMap }}
{{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }}
{{- if $rawConstraints }}
{{- $rawConstraints | fromYaml | toYaml | nindent 2 }}
labelSelector:
matchLabels:
cnpg.io/cluster: {{ .Release.Name }}
{{- end }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $topologySpreadConstraints := dig "scheduling" "topologySpreadConstraints" (list) $cozystack }}
{{- if $topologySpreadConstraints }}
topologySpreadConstraints:
{{- range $topologySpreadConstraints }}
- {{- mergeOverwrite (dict "labelSelector" (dict "matchLabels" (dict "cnpg.io/cluster" $.Release.Name))) . | toYaml | nindent 6 | trim }}
{{- end }}
{{- end }}
postgresql:
parameters:

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -9,16 +9,14 @@ metadata:
internal.cozystack.io/tenantmodule: "true"
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
apps.cozystack.io/application.kind: Etcd
apps.cozystack.io/application.group: apps.cozystack.io
apps.cozystack.io/application.name: etcd
spec:
chart:
spec:
chart: etcd
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-extra
namespace: cozy-public
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-system-etcd
namespace: cozy-system
interval: 5m
timeout: 10m
install:

View File

@@ -8,16 +8,14 @@ metadata:
internal.cozystack.io/tenantmodule: "true"
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
apps.cozystack.io/application.kind: Info
apps.cozystack.io/application.group: apps.cozystack.io
apps.cozystack.io/application.name: info
spec:
chart:
spec:
chart: info
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-extra
namespace: cozy-public
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-system-info
namespace: cozy-system
interval: 5m
timeout: 10m
install:

View File

@@ -9,16 +9,14 @@ metadata:
internal.cozystack.io/tenantmodule: "true"
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
apps.cozystack.io/application.kind: Ingress
apps.cozystack.io/application.group: apps.cozystack.io
apps.cozystack.io/application.name: ingress
spec:
chart:
spec:
chart: ingress
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-extra
namespace: cozy-public
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-system-ingress
namespace: cozy-system
interval: 5m
timeout: 10m
install:

View File

@@ -1,6 +1,8 @@
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
{{- $oidcEnabled := index $cozyConfig.data "oidc-enabled" }}
{{- if eq $oidcEnabled "true" }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $authentication := $cozystack.authentication | default dict }}
{{- $oidc := $authentication.oidc | default dict }}
{{- $oidcEnabled := $oidc.enabled | default false }}
{{- if $oidcEnabled }}
{{- if .Capabilities.APIVersions.Has "v1.edp.epam.com/v1" }}
apiVersion: v1.edp.epam.com/v1
kind: KeycloakRealmGroup

View File

@@ -9,16 +9,14 @@ metadata:
internal.cozystack.io/tenantmodule: "true"
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
apps.cozystack.io/application.kind: Monitoring
apps.cozystack.io/application.group: apps.cozystack.io
apps.cozystack.io/application.name: monitoring
spec:
chart:
spec:
chart: monitoring
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-extra
namespace: cozy-public
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-system-monitoring
namespace: cozy-system
interval: 5m
timeout: 10m
install:

View File

@@ -1,15 +1,21 @@
{{- define "cozystack.namespace-anotations" }}
{{- $context := index . 0 }}
{{- $existingNS := index . 1 }}
{{- $namespace := index . 1 }}
{{- range $x := list "etcd" "monitoring" "ingress" "seaweedfs" }}
{{- if (index $context.Values $x) }}
namespace.cozystack.io/{{ $x }}: "{{ include "tenant.name" $context }}"
{{- else }}
namespace.cozystack.io/{{ $x }}: "{{ index $existingNS.metadata.annotations (printf "namespace.cozystack.io/%s" $x) | required (printf "namespace %s has no namespace.cozystack.io/%s annotation" $context.Release.Namespace $x) }}"
{{- $value := dig $x "" $namespace }}
{{- if $value }}
namespace.cozystack.io/{{ $x }}: "{{ $value }}"
{{- else }}
{{- fail (printf "namespace %s has no namespace.cozystack.io/%s in values.namespace" $context.Release.Namespace $x) }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- $namespace := .Values._namespace | default dict }}
{{- $existingNS := lookup "v1" "Namespace" "" .Release.Namespace }}
{{- if not $existingNS }}
{{- fail (printf "error lookup existing namespace: %s" .Release.Namespace) }}
@@ -26,10 +32,13 @@ metadata:
{{- if .Values.host }}
namespace.cozystack.io/host: "{{ .Values.host }}"
{{- else }}
{{ $parentHost := index $existingNS.metadata.annotations "namespace.cozystack.io/host" | required (printf "namespace %s has no namespace.cozystack.io/host annotation" .Release.Namespace) }}
{{- $parentHost := dig "host" "" $namespace }}
{{- if not $parentHost }}
{{- fail (printf "namespace %s has no host in values.namespace" .Release.Namespace) }}
{{- end }}
namespace.cozystack.io/host: "{{ splitList "-" (include "tenant.name" .) | last }}.{{ $parentHost }}"
{{- end }}
{{- include "cozystack.namespace-anotations" (list . $existingNS) | nindent 4 }}
{{- include "cozystack.namespace-anotations" (list . $namespace) | nindent 4 }}
labels:
tenant.cozystack.io/{{ include "tenant.name" $ }}: ""
{{- if hasPrefix "tenant-" .Release.Namespace }}
@@ -40,7 +49,7 @@ metadata:
{{- end }}
{{- end }}
{{- end }}
{{- include "cozystack.namespace-anotations" (list $ $existingNS) | nindent 4 }}
{{- include "cozystack.namespace-anotations" (list $ $namespace) | nindent 4 }}
alpha.kubevirt.io/auto-memory-limits-ratio: "1.0"
ownerReferences:
- apiVersion: v1

View File

@@ -9,16 +9,14 @@ metadata:
internal.cozystack.io/tenantmodule: "true"
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
apps.cozystack.io/application.kind: SeaweedFS
apps.cozystack.io/application.group: apps.cozystack.io
apps.cozystack.io/application.name: seaweedfs
spec:
chart:
spec:
chart: seaweedfs
reconcileStrategy: Revision
sourceRef:
kind: HelmRepository
name: cozystack-extra
namespace: cozy-public
version: '>= 0.0.0-0'
chartRef:
kind: ExternalArtifact
name: cozystack-system-seaweedfs
namespace: cozy-system
interval: 5m
timeout: 10m
install:

View File

@@ -385,7 +385,7 @@ apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "tenant.name" . }}
namespace: cozy-public
namespace: cozy-system
rules:
- apiGroups: ["source.toolkit.fluxcd.io"]
resources: ["helmrepositories"]
@@ -398,7 +398,7 @@ apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "tenant.name" . }}
namespace: cozy-public
namespace: cozy-system
subjects:
- kind: Group
name: {{ include "tenant.name" . }}-super-admin

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -43,7 +43,7 @@ spec:
{{- if $dv }}
pvc:
name: vm-image-{{ .Values.systemDisk.image }}
namespace: cozy-public
namespace: cozy-system
{{- else }}
http:
{{- if eq .Values.systemDisk.image "cirros" }}

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json

View File

@@ -1,4 +1,4 @@
include ../../../scripts/package.mk
include ../../../hack/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md

View File

@@ -1,5 +1,6 @@
{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }}
{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }}
{{- $cozystack := .Values._cozystack | default dict }}
{{- $namespace := .Values._namespace | default dict }}
{{- $host := dig "host" "" $namespace }}
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-vpn" .Release.Name) }}
{{- $accessKeys := list }}
{{- $passwords := dict }}

Some files were not shown because too many files have changed in this diff Show More