mirror of
https://github.com/cozystack/cozystack.git
synced 2026-03-03 21:48:57 +00:00
Compare commits
15 Commits
replace-lo
...
cozystack-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43cef5d50e | ||
|
|
a80917ac1f | ||
|
|
224da109e2 | ||
|
|
e59784e01a | ||
|
|
6466d69b6b | ||
|
|
0f2fc0d991 | ||
|
|
84f2f83b03 | ||
|
|
38995ae1ab | ||
|
|
71a6dde579 | ||
|
|
0256075312 | ||
|
|
dc7dbe22f2 | ||
|
|
55da82dec2 | ||
|
|
e963e480a7 | ||
|
|
0377bd3d29 | ||
|
|
fdca498382 |
@@ -92,7 +92,8 @@ type CozystackResourceDefinitionApplication struct {
|
||||
|
||||
type CozystackResourceDefinitionRelease struct {
|
||||
// Helm chart configuration
|
||||
Chart CozystackResourceDefinitionChart `json:"chart"`
|
||||
// +optional
|
||||
Chart CozystackResourceDefinitionChart `json:"chart,omitempty"`
|
||||
// Labels for the release
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
// Prefix for the release name
|
||||
@@ -110,17 +111,18 @@ type CozystackResourceDefinitionRelease struct {
|
||||
// - {{ .namespace }}: The namespace of the resource being processed
|
||||
//
|
||||
// Example YAML:
|
||||
// secrets:
|
||||
// include:
|
||||
// - matchExpressions:
|
||||
// - key: badlabel
|
||||
// operator: DoesNotExist
|
||||
// matchLabels:
|
||||
// goodlabel: goodvalue
|
||||
// resourceNames:
|
||||
// - "{{ .name }}-secret"
|
||||
// - "{{ .kind }}-{{ .name }}-tls"
|
||||
// - "specificname"
|
||||
//
|
||||
// secrets:
|
||||
// include:
|
||||
// - matchExpressions:
|
||||
// - key: badlabel
|
||||
// operator: DoesNotExist
|
||||
// matchLabels:
|
||||
// goodlabel: goodvalue
|
||||
// resourceNames:
|
||||
// - "{{ .name }}-secret"
|
||||
// - "{{ .kind }}-{{ .name }}-tls"
|
||||
// - "specificname"
|
||||
type CozystackResourceDefinitionResourceSelector struct {
|
||||
metav1.LabelSelector `json:",inline"`
|
||||
// ResourceNames is a list of resource names to match
|
||||
|
||||
89
api/v1alpha1/package_types.go
Normal file
89
api/v1alpha1/package_types.go
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
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={pkg,pkgs}
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="Variant",type="string",JSONPath=".spec.variant",description="Selected variant"
|
||||
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status",description="Ready status"
|
||||
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message",description="Ready message"
|
||||
|
||||
// Package is the Schema for the packages API
|
||||
type Package struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec PackageSpec `json:"spec,omitempty"`
|
||||
Status PackageStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// PackageList contains a list of Packages
|
||||
type PackageList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Package `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&Package{}, &PackageList{})
|
||||
}
|
||||
|
||||
// PackageSpec defines the desired state of Package
|
||||
type PackageSpec struct {
|
||||
// Variant is the name of the variant to use from the PackageSource
|
||||
// If not specified, defaults to "default"
|
||||
// +optional
|
||||
Variant string `json:"variant,omitempty"`
|
||||
|
||||
// IgnoreDependencies is a list of package source dependencies to ignore
|
||||
// Dependencies listed here will not be installed even if they are specified in the PackageSource
|
||||
// +optional
|
||||
IgnoreDependencies []string `json:"ignoreDependencies,omitempty"`
|
||||
|
||||
// Components is a map of release name to component overrides
|
||||
// Allows overriding values and enabling/disabling specific components from the PackageSource
|
||||
// +optional
|
||||
Components map[string]PackageComponent `json:"components,omitempty"`
|
||||
}
|
||||
|
||||
// PackageRelease defines overrides for a specific component
|
||||
type PackageComponent struct {
|
||||
// Enabled indicates whether this component should be installed
|
||||
// If false, the component will be disabled even if it's defined in the PackageSource
|
||||
// +optional
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
|
||||
// Values contains Helm chart values as a JSON object
|
||||
// These values will be merged with the default values from the PackageSource
|
||||
// +optional
|
||||
Values *apiextensionsv1.JSON `json:"values,omitempty"`
|
||||
}
|
||||
|
||||
// PackageStatus defines the observed state of Package
|
||||
type PackageStatus struct {
|
||||
// Conditions represents the latest available observations of a Package's state
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
184
api/v1alpha1/packagesource_types.go
Normal file
184
api/v1alpha1/packagesource_types.go
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
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 (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:resource:scope=Cluster,shortName={pkgsrc,pkgsrcs}
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:printcolumn:name="Variants",type="string",JSONPath=".status.variants",description="Package variants (comma-separated)"
|
||||
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status",description="Ready status"
|
||||
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message",description="Ready message"
|
||||
|
||||
// PackageSource is the Schema for the packagesources API
|
||||
type PackageSource struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec PackageSourceSpec `json:"spec,omitempty"`
|
||||
Status PackageSourceStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// PackageSourceList contains a list of PackageSources
|
||||
type PackageSourceList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []PackageSource `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&PackageSource{}, &PackageSourceList{})
|
||||
}
|
||||
|
||||
// PackageSourceSpec defines the desired state of PackageSource
|
||||
type PackageSourceSpec struct {
|
||||
// SourceRef is the source reference for the package source charts
|
||||
// +optional
|
||||
SourceRef *PackageSourceRef `json:"sourceRef,omitempty"`
|
||||
|
||||
// Variants is a list of package source variants
|
||||
// Each variant defines components, applications, dependencies, and libraries for a specific configuration
|
||||
// +optional
|
||||
Variants []Variant `json:"variants,omitempty"`
|
||||
}
|
||||
|
||||
// Variant defines a single variant configuration
|
||||
type Variant struct {
|
||||
// Name is the unique identifier for this variant
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
|
||||
// DependsOn is a list of package source dependencies
|
||||
// For example: "cozystack.networking"
|
||||
// +optional
|
||||
DependsOn []string `json:"dependsOn,omitempty"`
|
||||
|
||||
// Libraries is a list of Helm library charts used by components in this variant
|
||||
// +optional
|
||||
Libraries []Library `json:"libraries,omitempty"`
|
||||
|
||||
// Components is a list of Helm releases to be installed as part of this variant
|
||||
// +optional
|
||||
Components []Component `json:"components,omitempty"`
|
||||
}
|
||||
|
||||
// DependencyTarget defines a named group of packages that can be referenced
|
||||
// by other package sources via dependsOn
|
||||
type DependencyTarget 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"`
|
||||
}
|
||||
|
||||
// Library defines a Helm library chart
|
||||
type Library struct {
|
||||
// Name is the optional name for library placed in charts
|
||||
// +optional
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Path is the path to the library chart directory
|
||||
// +required
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// PackageSourceRef defines the source reference for package source charts
|
||||
type PackageSourceRef 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"`
|
||||
|
||||
// Path 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
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// ComponentInstall defines installation parameters for a component
|
||||
type ComponentInstall struct {
|
||||
// ReleaseName is the name of the HelmRelease resource that will be created
|
||||
// If not specified, defaults to the component Name field
|
||||
// +optional
|
||||
ReleaseName string `json:"releaseName,omitempty"`
|
||||
|
||||
// Namespace is the Kubernetes namespace where the release will be installed
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
|
||||
// Privileged indicates whether this release requires privileged access
|
||||
// +optional
|
||||
Privileged bool `json:"privileged,omitempty"`
|
||||
|
||||
// DependsOn is a list of component names that must be installed before this component
|
||||
// +optional
|
||||
DependsOn []string `json:"dependsOn,omitempty"`
|
||||
}
|
||||
|
||||
// Component defines a single Helm release component within a package source
|
||||
type Component struct {
|
||||
// Name is the unique identifier for this component within the package source
|
||||
// +required
|
||||
Name string `json:"name"`
|
||||
|
||||
// Path is the path to the Helm chart directory
|
||||
// +required
|
||||
Path string `json:"path"`
|
||||
|
||||
// Install defines installation parameters for this component
|
||||
// +optional
|
||||
Install *ComponentInstall `json:"install,omitempty"`
|
||||
|
||||
// Libraries is a list of library names that this component depends on
|
||||
// These libraries must be defined at the variant level
|
||||
// +optional
|
||||
Libraries []string `json:"libraries,omitempty"`
|
||||
|
||||
// ValuesFiles is a list of values file names to use
|
||||
// +optional
|
||||
ValuesFiles []string `json:"valuesFiles,omitempty"`
|
||||
}
|
||||
|
||||
// PackageSourceStatus defines the observed state of PackageSource
|
||||
type PackageSourceStatus struct {
|
||||
// Variants is a comma-separated list of package variant names
|
||||
// This field is populated by the controller based on spec.variants keys
|
||||
// +optional
|
||||
Variants string `json:"variants,omitempty"`
|
||||
|
||||
// Conditions represents the latest available observations of a PackageSource's state
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
@@ -21,10 +21,62 @@ 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 *Component) DeepCopyInto(out *Component) {
|
||||
*out = *in
|
||||
if in.Install != nil {
|
||||
in, out := &in.Install, &out.Install
|
||||
*out = new(ComponentInstall)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Libraries != nil {
|
||||
in, out := &in.Libraries, &out.Libraries
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.ValuesFiles != nil {
|
||||
in, out := &in.ValuesFiles, &out.ValuesFiles
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Component.
|
||||
func (in *Component) DeepCopy() *Component {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Component)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ComponentInstall) DeepCopyInto(out *ComponentInstall) {
|
||||
*out = *in
|
||||
if in.DependsOn != nil {
|
||||
in, out := &in.DependsOn, &out.DependsOn
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentInstall.
|
||||
func (in *ComponentInstall) DeepCopy() *ComponentInstall {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ComponentInstall)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CozystackResourceDefinition) DeepCopyInto(out *CozystackResourceDefinition) {
|
||||
*out = *in
|
||||
@@ -256,6 +308,297 @@ func (in *CozystackResourceDefinitionSpec) DeepCopy() *CozystackResourceDefiniti
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DependencyTarget) DeepCopyInto(out *DependencyTarget) {
|
||||
*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 DependencyTarget.
|
||||
func (in *DependencyTarget) DeepCopy() *DependencyTarget {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DependencyTarget)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Library) DeepCopyInto(out *Library) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Library.
|
||||
func (in *Library) DeepCopy() *Library {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Library)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Package) DeepCopyInto(out *Package) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Package.
|
||||
func (in *Package) DeepCopy() *Package {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Package)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Package) 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 *PackageComponent) DeepCopyInto(out *PackageComponent) {
|
||||
*out = *in
|
||||
if in.Enabled != nil {
|
||||
in, out := &in.Enabled, &out.Enabled
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
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 PackageComponent.
|
||||
func (in *PackageComponent) DeepCopy() *PackageComponent {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PackageComponent)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PackageList) DeepCopyInto(out *PackageList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Package, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageList.
|
||||
func (in *PackageList) DeepCopy() *PackageList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PackageList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PackageList) 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 *PackageSource) DeepCopyInto(out *PackageSource) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageSource.
|
||||
func (in *PackageSource) DeepCopy() *PackageSource {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PackageSource)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PackageSource) 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 *PackageSourceList) DeepCopyInto(out *PackageSourceList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]PackageSource, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageSourceList.
|
||||
func (in *PackageSourceList) DeepCopy() *PackageSourceList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PackageSourceList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PackageSourceList) 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 *PackageSourceRef) DeepCopyInto(out *PackageSourceRef) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageSourceRef.
|
||||
func (in *PackageSourceRef) DeepCopy() *PackageSourceRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PackageSourceRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PackageSourceSpec) DeepCopyInto(out *PackageSourceSpec) {
|
||||
*out = *in
|
||||
if in.SourceRef != nil {
|
||||
in, out := &in.SourceRef, &out.SourceRef
|
||||
*out = new(PackageSourceRef)
|
||||
**out = **in
|
||||
}
|
||||
if in.Variants != nil {
|
||||
in, out := &in.Variants, &out.Variants
|
||||
*out = make([]Variant, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageSourceSpec.
|
||||
func (in *PackageSourceSpec) DeepCopy() *PackageSourceSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PackageSourceSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PackageSourceStatus) DeepCopyInto(out *PackageSourceStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]metav1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageSourceStatus.
|
||||
func (in *PackageSourceStatus) DeepCopy() *PackageSourceStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PackageSourceStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PackageSpec) DeepCopyInto(out *PackageSpec) {
|
||||
*out = *in
|
||||
if in.IgnoreDependencies != nil {
|
||||
in, out := &in.IgnoreDependencies, &out.IgnoreDependencies
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Components != nil {
|
||||
in, out := &in.Components, &out.Components
|
||||
*out = make(map[string]PackageComponent, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageSpec.
|
||||
func (in *PackageSpec) DeepCopy() *PackageSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PackageSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PackageStatus) DeepCopyInto(out *PackageStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]metav1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageStatus.
|
||||
func (in *PackageStatus) DeepCopy() *PackageStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PackageStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in Selector) DeepCopyInto(out *Selector) {
|
||||
{
|
||||
@@ -292,6 +635,38 @@ func (in *SourceRef) DeepCopy() *SourceRef {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Variant) DeepCopyInto(out *Variant) {
|
||||
*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([]Library, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Components != nil {
|
||||
in, out := &in.Components, &out.Components
|
||||
*out = make([]Component, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Variant.
|
||||
func (in *Variant) DeepCopy() *Variant {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Variant)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Workload) DeepCopyInto(out *Workload) {
|
||||
*out = *in
|
||||
|
||||
565
cmd/cozypkg/cmd/add.go
Normal file
565
cmd/cozypkg/cmd/add.go
Normal file
@@ -0,0 +1,565 @@
|
||||
/*
|
||||
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 cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
var addCmdFlags struct {
|
||||
files []string
|
||||
kubeconfig string
|
||||
}
|
||||
|
||||
var addCmd = &cobra.Command{
|
||||
Use: "add [package]...",
|
||||
Short: "Install PackageSource and its dependencies interactively",
|
||||
Long: `Install PackageSource and its dependencies interactively.
|
||||
|
||||
You can specify packages as arguments or use -f flag to read from files.
|
||||
Multiple -f flags can be specified, and they can point to files or directories.`,
|
||||
Args: cobra.ArbitraryArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Collect package names from arguments and files
|
||||
packageNames := make(map[string]bool)
|
||||
packagesFromFiles := make(map[string]string) // packageName -> filePath
|
||||
|
||||
for _, arg := range args {
|
||||
packageNames[arg] = true
|
||||
}
|
||||
|
||||
// Read packages from files
|
||||
for _, filePath := range addCmdFlags.files {
|
||||
packages, err := readPackagesFromFile(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read packages from %s: %w", filePath, err)
|
||||
}
|
||||
for _, pkg := range packages {
|
||||
packageNames[pkg] = true
|
||||
packagesFromFiles[pkg] = filePath
|
||||
}
|
||||
}
|
||||
|
||||
if len(packageNames) == 0 {
|
||||
return fmt.Errorf("no packages specified")
|
||||
}
|
||||
|
||||
// Create Kubernetes client config
|
||||
var config *rest.Config
|
||||
var err error
|
||||
|
||||
if addCmdFlags.kubeconfig != "" {
|
||||
config, err = clientcmd.BuildConfigFromFlags("", addCmdFlags.kubeconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load kubeconfig from %s: %w", addCmdFlags.kubeconfig, err)
|
||||
}
|
||||
} else {
|
||||
config, err = ctrl.GetConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get kubeconfig: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
utilruntime.Must(cozyv1alpha1.AddToScheme(scheme))
|
||||
|
||||
k8sClient, err := client.New(config, client.Options{Scheme: scheme})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create k8s client: %w", err)
|
||||
}
|
||||
|
||||
// Process each package
|
||||
for packageName := range packageNames {
|
||||
// Check if package comes from a file
|
||||
if filePath, fromFile := packagesFromFiles[packageName]; fromFile {
|
||||
// Try to create Package directly from file
|
||||
if err := createPackageFromFile(ctx, k8sClient, filePath, packageName); err == nil {
|
||||
fmt.Fprintf(os.Stderr, "✓ Added Package %s\n", packageName)
|
||||
continue
|
||||
}
|
||||
// If failed, fall back to interactive installation
|
||||
}
|
||||
|
||||
// Interactive installation from PackageSource
|
||||
if err := installPackage(ctx, k8sClient, packageName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func readPackagesFromFile(filePath string) ([]string, error) {
|
||||
info, err := os.Stat(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var packages []string
|
||||
|
||||
if info.IsDir() {
|
||||
// Read all YAML files from directory
|
||||
err := filepath.Walk(filePath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() || !strings.HasSuffix(path, ".yaml") && !strings.HasSuffix(path, ".yml") {
|
||||
return nil
|
||||
}
|
||||
|
||||
pkgs, err := readPackagesFromYAMLFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read %s: %w", path, err)
|
||||
}
|
||||
packages = append(packages, pkgs...)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
packages, err = readPackagesFromYAMLFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
}
|
||||
|
||||
func readPackagesFromYAMLFile(filePath string) ([]string, error) {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var packages []string
|
||||
|
||||
// Split YAML documents (in case of multiple resources)
|
||||
documents := strings.Split(string(data), "---")
|
||||
|
||||
for _, doc := range documents {
|
||||
doc = strings.TrimSpace(doc)
|
||||
if doc == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse using Kubernetes decoder
|
||||
decoder := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
obj := &unstructured.Unstructured{}
|
||||
_, _, err := decoder.Decode([]byte(doc), nil, obj)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it's a Package
|
||||
if obj.GetKind() == "Package" {
|
||||
name := obj.GetName()
|
||||
if name != "" {
|
||||
packages = append(packages, name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it's a PackageSource
|
||||
if obj.GetKind() == "PackageSource" {
|
||||
name := obj.GetName()
|
||||
if name != "" {
|
||||
packages = append(packages, name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to parse as PackageList
|
||||
if obj.GetKind() == "PackageList" {
|
||||
items, found, err := unstructured.NestedSlice(obj.Object, "items")
|
||||
if err == nil && found {
|
||||
for _, item := range items {
|
||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
if metadata, ok := itemMap["metadata"].(map[string]interface{}); ok {
|
||||
if name, ok := metadata["name"].(string); ok && name != "" {
|
||||
packages = append(packages, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to parse as PackageSourceList
|
||||
if obj.GetKind() == "PackageSourceList" {
|
||||
items, found, err := unstructured.NestedSlice(obj.Object, "items")
|
||||
if err == nil && found {
|
||||
for _, item := range items {
|
||||
if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
if metadata, ok := itemMap["metadata"].(map[string]interface{}); ok {
|
||||
if name, ok := metadata["name"].(string); ok && name != "" {
|
||||
packages = append(packages, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(packages) == 0 {
|
||||
return nil, fmt.Errorf("no valid packages found in file")
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
}
|
||||
|
||||
// buildDependencyTree builds a dependency tree starting from the root PackageSource
|
||||
// Returns both the dependency tree and a map of dependencies to their requesters
|
||||
func buildDependencyTree(ctx context.Context, k8sClient client.Client, rootName string) (map[string][]string, map[string]string, error) {
|
||||
tree := make(map[string][]string)
|
||||
dependencyRequesters := make(map[string]string) // dep -> requester
|
||||
visited := make(map[string]bool)
|
||||
|
||||
// Ensure root is in tree even if it has no dependencies
|
||||
tree[rootName] = []string{}
|
||||
|
||||
var buildTree func(string) error
|
||||
buildTree = func(pkgName string) error {
|
||||
if visited[pkgName] {
|
||||
return nil
|
||||
}
|
||||
visited[pkgName] = true
|
||||
|
||||
// Get PackageSource
|
||||
ps := &cozyv1alpha1.PackageSource{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Name: pkgName}, ps); err != nil {
|
||||
// If PackageSource doesn't exist, just skip it
|
||||
return nil
|
||||
}
|
||||
|
||||
// Collect all dependencies from all variants
|
||||
deps := make(map[string]bool)
|
||||
for _, variant := range ps.Spec.Variants {
|
||||
for _, dep := range variant.DependsOn {
|
||||
deps[dep] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Add dependencies to tree
|
||||
for dep := range deps {
|
||||
if _, exists := tree[pkgName]; !exists {
|
||||
tree[pkgName] = []string{}
|
||||
}
|
||||
tree[pkgName] = append(tree[pkgName], dep)
|
||||
// Track who requested this dependency
|
||||
dependencyRequesters[dep] = pkgName
|
||||
// Recursively build tree for dependencies
|
||||
if err := buildTree(dep); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := buildTree(rootName); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return tree, dependencyRequesters, nil
|
||||
}
|
||||
|
||||
// topologicalSort performs topological sort on the dependency tree
|
||||
// Returns order from root to leaves (dependencies first)
|
||||
func topologicalSort(tree map[string][]string) ([]string, error) {
|
||||
// Build reverse graph (dependencies -> dependents)
|
||||
reverseGraph := make(map[string][]string)
|
||||
allNodes := make(map[string]bool)
|
||||
|
||||
for node, deps := range tree {
|
||||
allNodes[node] = true
|
||||
for _, dep := range deps {
|
||||
allNodes[dep] = true
|
||||
reverseGraph[dep] = append(reverseGraph[dep], node)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate in-degrees (how many dependencies a node has)
|
||||
inDegree := make(map[string]int)
|
||||
for node := range allNodes {
|
||||
inDegree[node] = 0
|
||||
}
|
||||
for node, deps := range tree {
|
||||
inDegree[node] = len(deps)
|
||||
}
|
||||
|
||||
// Kahn's algorithm - start with nodes that have no dependencies
|
||||
var queue []string
|
||||
for node, degree := range inDegree {
|
||||
if degree == 0 {
|
||||
queue = append(queue, node)
|
||||
}
|
||||
}
|
||||
|
||||
var result []string
|
||||
for len(queue) > 0 {
|
||||
node := queue[0]
|
||||
queue = queue[1:]
|
||||
result = append(result, node)
|
||||
|
||||
// Process dependents (nodes that depend on this node)
|
||||
for _, dependent := range reverseGraph[node] {
|
||||
inDegree[dependent]--
|
||||
if inDegree[dependent] == 0 {
|
||||
queue = append(queue, dependent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for cycles
|
||||
if len(result) != len(allNodes) {
|
||||
return nil, fmt.Errorf("dependency cycle detected")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// createPackageFromFile creates a Package resource directly from a YAML file
|
||||
func createPackageFromFile(ctx context.Context, k8sClient client.Client, filePath string, packageName string) error {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Split YAML documents
|
||||
documents := strings.Split(string(data), "---")
|
||||
|
||||
for _, doc := range documents {
|
||||
doc = strings.TrimSpace(doc)
|
||||
if doc == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse using Kubernetes decoder
|
||||
decoder := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
obj := &unstructured.Unstructured{}
|
||||
_, _, err := decoder.Decode([]byte(doc), nil, obj)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if it's a Package with matching name
|
||||
if obj.GetKind() == "Package" && obj.GetName() == packageName {
|
||||
// Convert to Package
|
||||
var pkg cozyv1alpha1.Package
|
||||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &pkg); err != nil {
|
||||
return fmt.Errorf("failed to convert Package: %w", err)
|
||||
}
|
||||
|
||||
// Create Package
|
||||
if err := k8sClient.Create(ctx, &pkg); err != nil {
|
||||
return fmt.Errorf("failed to create Package: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Package %s not found in file", packageName)
|
||||
}
|
||||
|
||||
func installPackage(ctx context.Context, k8sClient client.Client, packageSourceName string) error {
|
||||
// Get PackageSource
|
||||
packageSource := &cozyv1alpha1.PackageSource{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Name: packageSourceName}, packageSource); err != nil {
|
||||
return fmt.Errorf("failed to get PackageSource %s: %w", packageSourceName, err)
|
||||
}
|
||||
|
||||
// Build dependency tree
|
||||
dependencyTree, dependencyRequesters, err := buildDependencyTree(ctx, k8sClient, packageSourceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build dependency tree: %w", err)
|
||||
}
|
||||
|
||||
// Topological sort (install from root to leaves)
|
||||
installOrder, err := topologicalSort(dependencyTree)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sort dependencies: %w", err)
|
||||
}
|
||||
|
||||
// Get all PackageSources for variant selection
|
||||
var allPackageSources cozyv1alpha1.PackageSourceList
|
||||
if err := k8sClient.List(ctx, &allPackageSources); err != nil {
|
||||
return fmt.Errorf("failed to list PackageSources: %w", err)
|
||||
}
|
||||
|
||||
packageSourceMap := make(map[string]*cozyv1alpha1.PackageSource)
|
||||
for i := range allPackageSources.Items {
|
||||
packageSourceMap[allPackageSources.Items[i].Name] = &allPackageSources.Items[i]
|
||||
}
|
||||
|
||||
// Get all installed Packages
|
||||
var installedPackages cozyv1alpha1.PackageList
|
||||
if err := k8sClient.List(ctx, &installedPackages); err != nil {
|
||||
return fmt.Errorf("failed to list Packages: %w", err)
|
||||
}
|
||||
|
||||
installedMap := make(map[string]*cozyv1alpha1.Package)
|
||||
for i := range installedPackages.Items {
|
||||
installedMap[installedPackages.Items[i].Name] = &installedPackages.Items[i]
|
||||
}
|
||||
|
||||
// First, collect all variant selections
|
||||
fmt.Fprintf(os.Stderr, "Installing %s and its dependencies...\n\n", packageSourceName)
|
||||
packageVariants := make(map[string]string) // packageName -> variant
|
||||
|
||||
for _, pkgName := range installOrder {
|
||||
// Check if already installed
|
||||
if installed, exists := installedMap[pkgName]; exists {
|
||||
variant := installed.Spec.Variant
|
||||
if variant == "" {
|
||||
variant = "default"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "✓ %s (already installed, variant: %s)\n", pkgName, variant)
|
||||
packageVariants[pkgName] = variant
|
||||
continue
|
||||
}
|
||||
|
||||
// Get PackageSource for this dependency
|
||||
ps, exists := packageSourceMap[pkgName]
|
||||
if !exists {
|
||||
requester := dependencyRequesters[pkgName]
|
||||
if requester != "" {
|
||||
return fmt.Errorf("PackageSource %s not found (required by %s)", pkgName, requester)
|
||||
}
|
||||
return fmt.Errorf("PackageSource %s not found", pkgName)
|
||||
}
|
||||
|
||||
// Select variant interactively
|
||||
variant, err := selectVariantInteractive(ps)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to select variant for %s: %w", pkgName, err)
|
||||
}
|
||||
|
||||
packageVariants[pkgName] = variant
|
||||
}
|
||||
|
||||
// Now create all Package resources
|
||||
for _, pkgName := range installOrder {
|
||||
// Skip if already installed
|
||||
if _, exists := installedMap[pkgName]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
variant := packageVariants[pkgName]
|
||||
|
||||
// Create Package
|
||||
pkg := &cozyv1alpha1.Package{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: pkgName,
|
||||
},
|
||||
Spec: cozyv1alpha1.PackageSpec{
|
||||
Variant: variant,
|
||||
},
|
||||
}
|
||||
|
||||
if err := k8sClient.Create(ctx, pkg); err != nil {
|
||||
return fmt.Errorf("failed to create Package %s: %w", pkgName, err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "✓ Added Package %s\n", pkgName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// selectVariantInteractive prompts user to select a variant
|
||||
func selectVariantInteractive(ps *cozyv1alpha1.PackageSource) (string, error) {
|
||||
if len(ps.Spec.Variants) == 0 {
|
||||
return "", fmt.Errorf("no variants available for PackageSource %s", ps.Name)
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
fmt.Fprintf(os.Stderr, "\nPackageSource: %s\n", ps.Name)
|
||||
fmt.Fprintf(os.Stderr, "Available variants:\n")
|
||||
for i, variant := range ps.Spec.Variants {
|
||||
fmt.Fprintf(os.Stderr, " %d. %s\n", i+1, variant.Name)
|
||||
}
|
||||
|
||||
// If only one variant, use it as default
|
||||
defaultVariant := ps.Spec.Variants[0].Name
|
||||
var prompt string
|
||||
if len(ps.Spec.Variants) == 1 {
|
||||
prompt = "Select variant [1]: "
|
||||
} else {
|
||||
prompt = fmt.Sprintf("Select variant (1-%d): ", len(ps.Spec.Variants))
|
||||
}
|
||||
|
||||
for {
|
||||
fmt.Fprintf(os.Stderr, prompt)
|
||||
input, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read input: %w", err)
|
||||
}
|
||||
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
// If input is empty and there's a default variant, use it
|
||||
if input == "" && len(ps.Spec.Variants) == 1 {
|
||||
return defaultVariant, nil
|
||||
}
|
||||
|
||||
choice, err := strconv.Atoi(input)
|
||||
if err != nil || choice < 1 || choice > len(ps.Spec.Variants) {
|
||||
fmt.Fprintf(os.Stderr, "Invalid choice. Please enter a number between 1 and %d.\n", len(ps.Spec.Variants))
|
||||
continue
|
||||
}
|
||||
|
||||
return ps.Spec.Variants[choice-1].Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(addCmd)
|
||||
addCmd.Flags().StringArrayVarP(&addCmdFlags.files, "file", "f", []string{}, "Read packages from file or directory (can be specified multiple times)")
|
||||
addCmd.Flags().StringVar(&addCmdFlags.kubeconfig, "kubeconfig", "", "Path to kubeconfig file (defaults to ~/.kube/config or KUBECONFIG env var)")
|
||||
}
|
||||
|
||||
121
cmd/cozypkg/cmd/del.go
Normal file
121
cmd/cozypkg/cmd/del.go
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
var delCmdFlags struct {
|
||||
files []string
|
||||
kubeconfig string
|
||||
}
|
||||
|
||||
var delCmd = &cobra.Command{
|
||||
Use: "del [package]...",
|
||||
Short: "Delete Package resources",
|
||||
Long: `Delete Package resources.
|
||||
|
||||
You can specify packages as arguments or use -f flag to read from files.
|
||||
Multiple -f flags can be specified, and they can point to files or directories.`,
|
||||
Args: cobra.ArbitraryArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Collect package names from arguments and files
|
||||
packageNames := make(map[string]bool)
|
||||
for _, arg := range args {
|
||||
packageNames[arg] = true
|
||||
}
|
||||
|
||||
// Read packages from files (reuse function from add.go)
|
||||
for _, filePath := range delCmdFlags.files {
|
||||
packages, err := readPackagesFromFile(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read packages from %s: %w", filePath, err)
|
||||
}
|
||||
for _, pkg := range packages {
|
||||
packageNames[pkg] = true
|
||||
}
|
||||
}
|
||||
|
||||
if len(packageNames) == 0 {
|
||||
return fmt.Errorf("no packages specified")
|
||||
}
|
||||
|
||||
// Create Kubernetes client config
|
||||
var config *rest.Config
|
||||
var err error
|
||||
|
||||
if delCmdFlags.kubeconfig != "" {
|
||||
config, err = clientcmd.BuildConfigFromFlags("", delCmdFlags.kubeconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load kubeconfig from %s: %w", delCmdFlags.kubeconfig, err)
|
||||
}
|
||||
} else {
|
||||
config, err = ctrl.GetConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get kubeconfig: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
utilruntime.Must(cozyv1alpha1.AddToScheme(scheme))
|
||||
|
||||
k8sClient, err := client.New(config, client.Options{Scheme: scheme})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create k8s client: %w", err)
|
||||
}
|
||||
|
||||
// Delete each package
|
||||
for packageName := range packageNames {
|
||||
pkg := &cozyv1alpha1.Package{}
|
||||
pkg.Name = packageName
|
||||
if err := k8sClient.Delete(ctx, pkg); err != nil {
|
||||
if client.IgnoreNotFound(err) == nil {
|
||||
fmt.Fprintf(os.Stderr, "⚠ Package %s not found, skipping\n", packageName)
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("failed to delete Package %s: %w", packageName, err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "✓ Deleted Package %s\n", packageName)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(delCmd)
|
||||
delCmd.Flags().StringArrayVarP(&delCmdFlags.files, "file", "f", []string{}, "Read packages from file or directory (can be specified multiple times)")
|
||||
delCmd.Flags().StringVar(&delCmdFlags.kubeconfig, "kubeconfig", "", "Path to kubeconfig file (defaults to ~/.kube/config or KUBECONFIG env var)")
|
||||
}
|
||||
|
||||
300
cmd/cozypkg/cmd/dependencies.go
Normal file
300
cmd/cozypkg/cmd/dependencies.go
Normal file
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/emicklei/dot"
|
||||
"github.com/spf13/cobra"
|
||||
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
var dotCmdFlags struct {
|
||||
installed bool
|
||||
components bool
|
||||
files []string
|
||||
kubeconfig string
|
||||
}
|
||||
|
||||
var dotCmd = &cobra.Command{
|
||||
Use: "dot [package]...",
|
||||
Short: "Generate dependency graph as graphviz DOT format",
|
||||
Long: `Generate dependency graph as graphviz DOT format.
|
||||
|
||||
Pipe the output through the "dot" program (part of graphviz package) to render the graph:
|
||||
|
||||
cozypkg dot | dot -Tpng > graph.png
|
||||
|
||||
By default, shows dependencies for all PackageSource resources.
|
||||
Use --installed to show only installed Package resources.
|
||||
Specify packages as arguments or use -f flag to read from files.`,
|
||||
Args: cobra.ArbitraryArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Collect package names from arguments and files
|
||||
packageNames := make(map[string]bool)
|
||||
for _, arg := range args {
|
||||
packageNames[arg] = true
|
||||
}
|
||||
|
||||
// Read packages from files (reuse function from add.go)
|
||||
for _, filePath := range dotCmdFlags.files {
|
||||
packages, err := readPackagesFromFile(filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read packages from %s: %w", filePath, err)
|
||||
}
|
||||
for _, pkg := range packages {
|
||||
packageNames[pkg] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to slice, empty means all packages
|
||||
var selectedPackages []string
|
||||
if len(packageNames) > 0 {
|
||||
for pkg := range packageNames {
|
||||
selectedPackages = append(selectedPackages, pkg)
|
||||
}
|
||||
}
|
||||
|
||||
// If multiple packages specified, show graph for all of them
|
||||
// If single package, use packageName for backward compatibility
|
||||
var packageName string
|
||||
if len(selectedPackages) == 1 {
|
||||
packageName = selectedPackages[0]
|
||||
} else if len(selectedPackages) > 1 {
|
||||
// Multiple packages - pass empty string to packageName, use selectedPackages
|
||||
packageName = ""
|
||||
}
|
||||
|
||||
// packagesOnly is inverse of components flag (if components=false, then packagesOnly=true)
|
||||
packagesOnly := !dotCmdFlags.components
|
||||
graph, allNodes, err := buildGraphFromCluster(ctx, dotCmdFlags.kubeconfig, packagesOnly, dotCmdFlags.installed, packageName, selectedPackages)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting PackageSource dependencies: %w", err)
|
||||
}
|
||||
|
||||
dotGraph := generateDOTGraph(graph, allNodes, packagesOnly)
|
||||
dotGraph.Write(os.Stdout)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(dotCmd)
|
||||
dotCmd.Flags().BoolVarP(&dotCmdFlags.installed, "installed", "i", false, "show dependencies only for installed Package resources")
|
||||
dotCmd.Flags().BoolVar(&dotCmdFlags.components, "components", true, "show component-level dependencies (default: true)")
|
||||
dotCmd.Flags().StringArrayVarP(&dotCmdFlags.files, "file", "f", []string{}, "Read packages from file or directory (can be specified multiple times)")
|
||||
dotCmd.Flags().StringVar(&dotCmdFlags.kubeconfig, "kubeconfig", "", "Path to kubeconfig file (defaults to ~/.kube/config or KUBECONFIG env var)")
|
||||
}
|
||||
|
||||
var (
|
||||
dependenciesScheme = runtime.NewScheme()
|
||||
)
|
||||
|
||||
func init() {
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(dependenciesScheme))
|
||||
utilruntime.Must(cozyv1alpha1.AddToScheme(dependenciesScheme))
|
||||
}
|
||||
|
||||
// buildGraphFromCluster builds a dependency graph from PackageSource resources in the cluster.
|
||||
func buildGraphFromCluster(ctx context.Context, kubeconfig string, packagesOnly bool, installedOnly bool, packageName string, selectedPackages []string) (map[string][]string, map[string]bool, error) {
|
||||
// Create Kubernetes client config
|
||||
var config *rest.Config
|
||||
var err error
|
||||
|
||||
if kubeconfig != "" {
|
||||
// Load kubeconfig from explicit path
|
||||
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to load kubeconfig from %s: %w", kubeconfig, err)
|
||||
}
|
||||
} else {
|
||||
// Use default kubeconfig loading (from env var or ~/.kube/config)
|
||||
config, err = ctrl.GetConfig()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get kubeconfig: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
k8sClient, err := client.New(config, client.Options{Scheme: dependenciesScheme})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create k8s client: %w", err)
|
||||
}
|
||||
|
||||
// Get installed Packages if needed
|
||||
installedPackages := make(map[string]bool)
|
||||
if installedOnly || packageName != "" {
|
||||
var packageList cozyv1alpha1.PackageList
|
||||
if err := k8sClient.List(ctx, &packageList); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to list Packages: %w", err)
|
||||
}
|
||||
for _, pkg := range packageList.Items {
|
||||
installedPackages[pkg.Name] = true
|
||||
}
|
||||
}
|
||||
|
||||
// List all PackageSource resources
|
||||
var packageSourceList cozyv1alpha1.PackageSourceList
|
||||
if err := k8sClient.List(ctx, &packageSourceList); err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to list PackageSources: %w", err)
|
||||
}
|
||||
|
||||
graph := make(map[string][]string)
|
||||
allNodes := make(map[string]bool)
|
||||
|
||||
// Process each PackageSource
|
||||
for _, ps := range packageSourceList.Items {
|
||||
psName := ps.Name
|
||||
if psName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter by package name if specified
|
||||
if packageName != "" && psName != packageName {
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter by selected packages if specified
|
||||
if len(selectedPackages) > 0 {
|
||||
found := false
|
||||
for _, selected := range selectedPackages {
|
||||
if psName == selected {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by installed packages if flag is set
|
||||
if installedOnly && !installedPackages[psName] {
|
||||
continue
|
||||
}
|
||||
|
||||
allNodes[psName] = true
|
||||
|
||||
// Extract dependencies from variants
|
||||
for _, variant := range ps.Spec.Variants {
|
||||
// Variant-level dependencies
|
||||
for _, dep := range variant.DependsOn {
|
||||
// If installedOnly is set, only include dependencies that are installed
|
||||
if installedOnly && !installedPackages[dep] {
|
||||
continue
|
||||
}
|
||||
graph[psName] = append(graph[psName], dep)
|
||||
allNodes[dep] = true
|
||||
}
|
||||
|
||||
// Component-level dependencies
|
||||
if !packagesOnly {
|
||||
for _, component := range variant.Components {
|
||||
componentName := fmt.Sprintf("%s.%s", psName, component.Name)
|
||||
allNodes[componentName] = true
|
||||
|
||||
if component.Install != nil {
|
||||
for _, dep := range component.Install.DependsOn {
|
||||
// Check if it's a local component dependency or external
|
||||
if strings.Contains(dep, ".") {
|
||||
graph[componentName] = append(graph[componentName], dep)
|
||||
allNodes[dep] = true
|
||||
} else {
|
||||
// Local component dependency
|
||||
localDep := fmt.Sprintf("%s.%s", psName, dep)
|
||||
graph[componentName] = append(graph[componentName], localDep)
|
||||
allNodes[localDep] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return graph, allNodes, nil
|
||||
}
|
||||
|
||||
// generateDOTGraph generates a DOT graph from the dependency graph.
|
||||
func generateDOTGraph(graph map[string][]string, allNodes map[string]bool, packagesOnly bool) *dot.Graph {
|
||||
g := dot.NewGraph(dot.Directed)
|
||||
g.Attr("rankdir", "LR")
|
||||
g.Attr("nodesep", "0.5")
|
||||
g.Attr("ranksep", "1.0")
|
||||
|
||||
// Add nodes
|
||||
for node := range allNodes {
|
||||
if packagesOnly && strings.Contains(node, ".") && !strings.HasPrefix(node, "cozystack.") {
|
||||
// Skip component nodes when packages-only is enabled
|
||||
continue
|
||||
}
|
||||
|
||||
n := g.Node(node)
|
||||
|
||||
// Style nodes based on type
|
||||
if strings.Contains(node, ".") && !strings.HasPrefix(node, "cozystack.") {
|
||||
// Component node
|
||||
n.Attr("shape", "box")
|
||||
n.Attr("style", "rounded,filled")
|
||||
n.Attr("fillcolor", "lightyellow")
|
||||
n.Attr("label", strings.Split(node, ".")[len(strings.Split(node, "."))-1])
|
||||
} else {
|
||||
// Package node
|
||||
n.Attr("shape", "box")
|
||||
n.Attr("style", "rounded,filled")
|
||||
n.Attr("fillcolor", "lightblue")
|
||||
n.Attr("label", node)
|
||||
}
|
||||
}
|
||||
|
||||
// Add edges
|
||||
for source, targets := range graph {
|
||||
if packagesOnly && strings.Contains(source, ".") && !strings.HasPrefix(source, "cozystack.") {
|
||||
// Skip component edges when packages-only is enabled
|
||||
continue
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
if packagesOnly && strings.Contains(target, ".") && !strings.HasPrefix(target, "cozystack.") {
|
||||
// Skip component edges when packages-only is enabled
|
||||
continue
|
||||
}
|
||||
|
||||
// Only add edge if both nodes exist
|
||||
if allNodes[source] && allNodes[target] {
|
||||
g.Edge(g.Node(source), g.Node(target))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
201
cmd/cozypkg/cmd/list.go
Normal file
201
cmd/cozypkg/cmd/list.go
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
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 cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
)
|
||||
|
||||
var listCmdFlags struct {
|
||||
installed bool
|
||||
components bool
|
||||
kubeconfig string
|
||||
}
|
||||
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List PackageSource or Package resources",
|
||||
Long: `List PackageSource or Package resources in table format.
|
||||
|
||||
By default, lists PackageSource resources. Use --installed flag to list installed Package resources.
|
||||
Use --components flag to show components on separate lines.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create Kubernetes client config
|
||||
var config *rest.Config
|
||||
var err error
|
||||
|
||||
if listCmdFlags.kubeconfig != "" {
|
||||
config, err = clientcmd.BuildConfigFromFlags("", listCmdFlags.kubeconfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load kubeconfig from %s: %w", listCmdFlags.kubeconfig, err)
|
||||
}
|
||||
} else {
|
||||
config, err = ctrl.GetConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get kubeconfig: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
utilruntime.Must(cozyv1alpha1.AddToScheme(scheme))
|
||||
|
||||
k8sClient, err := client.New(config, client.Options{Scheme: scheme})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create k8s client: %w", err)
|
||||
}
|
||||
|
||||
if listCmdFlags.installed {
|
||||
return listPackages(ctx, k8sClient, listCmdFlags.components)
|
||||
}
|
||||
return listPackageSources(ctx, k8sClient, listCmdFlags.components)
|
||||
},
|
||||
}
|
||||
|
||||
func listPackageSources(ctx context.Context, k8sClient client.Client, showComponents bool) error {
|
||||
var psList cozyv1alpha1.PackageSourceList
|
||||
if err := k8sClient.List(ctx, &psList); err != nil {
|
||||
return fmt.Errorf("failed to list PackageSources: %w", err)
|
||||
}
|
||||
|
||||
// Print header
|
||||
fmt.Fprintf(os.Stdout, "%-50s %-30s %-10s %s\n", "NAME", "VARIANTS", "READY", "STATUS")
|
||||
fmt.Fprintf(os.Stdout, "%-50s %-30s %-10s %s\n", strings.Repeat("-", 50), strings.Repeat("-", 30), strings.Repeat("-", 10), strings.Repeat("-", 50))
|
||||
|
||||
// Print rows
|
||||
for _, ps := range psList.Items {
|
||||
// Get variants
|
||||
var variants []string
|
||||
for _, variant := range ps.Spec.Variants {
|
||||
variants = append(variants, variant.Name)
|
||||
}
|
||||
variantsStr := strings.Join(variants, ",")
|
||||
if len(variantsStr) > 28 {
|
||||
variantsStr = variantsStr[:25] + "..."
|
||||
}
|
||||
|
||||
// Get Ready condition
|
||||
ready := "Unknown"
|
||||
status := ""
|
||||
for _, condition := range ps.Status.Conditions {
|
||||
if condition.Type == "Ready" {
|
||||
ready = string(condition.Status)
|
||||
status = condition.Message
|
||||
if len(status) > 48 {
|
||||
status = status[:45] + "..."
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "%-50s %-30s %-10s %s\n", ps.Name, variantsStr, ready, status)
|
||||
|
||||
// Show components if requested
|
||||
if showComponents {
|
||||
for _, variant := range ps.Spec.Variants {
|
||||
for _, component := range variant.Components {
|
||||
fmt.Fprintf(os.Stdout, " %-48s %-30s %-10s %s\n",
|
||||
fmt.Sprintf("%s.%s", ps.Name, component.Name),
|
||||
variant.Name, "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func listPackages(ctx context.Context, k8sClient client.Client, showComponents bool) error {
|
||||
var pkgList cozyv1alpha1.PackageList
|
||||
if err := k8sClient.List(ctx, &pkgList); err != nil {
|
||||
return fmt.Errorf("failed to list Packages: %w", err)
|
||||
}
|
||||
|
||||
// Print header
|
||||
fmt.Fprintf(os.Stdout, "%-50s %-20s %-10s %s\n", "NAME", "VARIANT", "READY", "STATUS")
|
||||
fmt.Fprintf(os.Stdout, "%-50s %-20s %-10s %s\n", strings.Repeat("-", 50), strings.Repeat("-", 20), strings.Repeat("-", 10), strings.Repeat("-", 50))
|
||||
|
||||
// Print rows
|
||||
for _, pkg := range pkgList.Items {
|
||||
variant := pkg.Spec.Variant
|
||||
if variant == "" {
|
||||
variant = "default"
|
||||
}
|
||||
|
||||
// Get Ready condition
|
||||
ready := "Unknown"
|
||||
status := ""
|
||||
for _, condition := range pkg.Status.Conditions {
|
||||
if condition.Type == "Ready" {
|
||||
ready = string(condition.Status)
|
||||
status = condition.Message
|
||||
if len(status) > 48 {
|
||||
status = status[:45] + "..."
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "%-50s %-20s %-10s %s\n", pkg.Name, variant, ready, status)
|
||||
|
||||
// Show components if requested
|
||||
if showComponents {
|
||||
// Get PackageSource to show components
|
||||
ps := &cozyv1alpha1.PackageSource{}
|
||||
if err := k8sClient.Get(ctx, client.ObjectKey{Name: pkg.Name}, ps); err == nil {
|
||||
// Find the variant
|
||||
for _, v := range ps.Spec.Variants {
|
||||
if v.Name == variant {
|
||||
for _, component := range v.Components {
|
||||
fmt.Fprintf(os.Stdout, " %-48s %-20s %-10s %s\n",
|
||||
fmt.Sprintf("%s.%s", pkg.Name, component.Name),
|
||||
variant, "", "")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(listCmd)
|
||||
listCmd.Flags().BoolVarP(&listCmdFlags.installed, "installed", "i", false, "list installed Package resources instead of PackageSource resources")
|
||||
listCmd.Flags().BoolVar(&listCmdFlags.components, "components", false, "show components on separate lines")
|
||||
listCmd.Flags().StringVar(&listCmdFlags.kubeconfig, "kubeconfig", "", "Path to kubeconfig file (defaults to ~/.kube/config or KUBECONFIG env var)")
|
||||
}
|
||||
|
||||
49
cmd/cozypkg/cmd/root.go
Normal file
49
cmd/cozypkg/cmd/root.go
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands.
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "cozypkg",
|
||||
Short: "A CLI for managing Cozystack packages",
|
||||
Long: ``,
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
DisableAutoGenTag: true,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() error {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Commands are registered in their respective init() functions
|
||||
}
|
||||
|
||||
30
cmd/cozypkg/main.go
Normal file
30
cmd/cozypkg/main.go
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
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 (
|
||||
"os"
|
||||
|
||||
"github.com/cozystack/cozystack/cmd/cozypkg/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := cmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
481
cmd/cozystack-operator/main.go
Normal file
481
cmd/cozystack-operator/main.go
Normal file
@@ -0,0 +1,481 @@
|
||||
/*
|
||||
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"
|
||||
"net/url"
|
||||
"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"
|
||||
// +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 cozystackVersion string
|
||||
var installFluxResources stringSliceFlag
|
||||
var platformSource string
|
||||
var platformSourceName string
|
||||
|
||||
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.StringVar(&cozystackVersion, "cozystack-version", "unknown",
|
||||
"Version of Cozystack")
|
||||
flag.StringVar(&platformSource, "platform-source", "", "Platform source URL (oci:// or git://). If specified, generates OCIRepository or GitRepository resource.")
|
||||
flag.StringVar(&platformSourceName, "platform-source-name", "cozystack-packages", "Name for the generated platform source resource (default: cozystack-packages)")
|
||||
|
||||
opts := zap.Options{
|
||||
Development: true,
|
||||
}
|
||||
opts.BindFlags(flag.CommandLine)
|
||||
flag.Parse()
|
||||
|
||||
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: "cozystack-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")
|
||||
}
|
||||
}
|
||||
|
||||
// Generate and install platform source resource if specified
|
||||
if platformSource != "" {
|
||||
setupLog.Info("Generating platform source resource", "source", platformSource, "name", platformSourceName)
|
||||
installCtx, installCancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer installCancel()
|
||||
|
||||
if err := installPlatformSourceResource(installCtx, mgr.GetClient(), platformSource, platformSourceName); err != nil {
|
||||
setupLog.Error(err, "failed to install platform source resource")
|
||||
os.Exit(1)
|
||||
} else {
|
||||
setupLog.Info("Platform source resource installation completed successfully")
|
||||
}
|
||||
}
|
||||
|
||||
// Setup PackageSource reconciler
|
||||
if err := (&operator.PackageSourceReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "PackageSource")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Setup Package reconciler
|
||||
if err := (&operator.PackageReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Package")
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// installPlatformSourceResource generates and installs a Flux source resource (OCIRepository or GitRepository)
|
||||
// based on the platform source URL
|
||||
func installPlatformSourceResource(ctx context.Context, k8sClient client.Client, sourceURL, resourceName string) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
// Parse the source URL to determine type
|
||||
sourceType, repoURL, ref, err := parsePlatformSource(sourceURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse platform source URL: %w", err)
|
||||
}
|
||||
|
||||
var obj *unstructured.Unstructured
|
||||
switch sourceType {
|
||||
case "oci":
|
||||
obj, err = generateOCIRepository(resourceName, repoURL, ref)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate OCIRepository: %w", err)
|
||||
}
|
||||
case "git":
|
||||
obj, err = generateGitRepository(resourceName, repoURL, ref)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate GitRepository: %w", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported source type: %s (expected oci:// or git://)", sourceType)
|
||||
}
|
||||
|
||||
// Apply the resource (create or update)
|
||||
logger.Info("Applying platform source resource",
|
||||
"apiVersion", obj.GetAPIVersion(),
|
||||
"kind", obj.GetKind(),
|
||||
"name", obj.GetName(),
|
||||
"namespace", obj.GetNamespace(),
|
||||
)
|
||||
|
||||
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 platform source 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 platform source resource", "kind", obj.GetKind(), "name", obj.GetName())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parsePlatformSource parses the source URL and returns the type, repository URL, and reference
|
||||
// Supports formats:
|
||||
// - oci://registry.example.com/repo@sha256:digest
|
||||
// - oci://registry.example.com/repo (ref will be empty)
|
||||
// - git://github.com/user/repo@branch
|
||||
// - git://github.com/user/repo (ref will default to "main")
|
||||
// - https://github.com/user/repo@branch (treated as git)
|
||||
func parsePlatformSource(sourceURL string) (sourceType, repoURL, ref string, err error) {
|
||||
// Normalize the URL by trimming whitespace
|
||||
sourceURL = strings.TrimSpace(sourceURL)
|
||||
|
||||
// Check for oci:// prefix
|
||||
if strings.HasPrefix(sourceURL, "oci://") {
|
||||
// Remove oci:// prefix
|
||||
rest := strings.TrimPrefix(sourceURL, "oci://")
|
||||
|
||||
// Check for @sha256: digest (look for @ followed by sha256:)
|
||||
// We need to find the last @ before sha256: to handle paths with @ symbols
|
||||
sha256Idx := strings.Index(rest, "@sha256:")
|
||||
if sha256Idx != -1 {
|
||||
repoURL = "oci://" + rest[:sha256Idx]
|
||||
ref = rest[sha256Idx+1:] // sha256:digest
|
||||
} else {
|
||||
// Check for @ without sha256: (might be a tag)
|
||||
if atIdx := strings.LastIndex(rest, "@"); atIdx != -1 {
|
||||
// Could be a tag, but for OCI we expect sha256: digest
|
||||
// For now, treat everything after @ as the ref
|
||||
repoURL = "oci://" + rest[:atIdx]
|
||||
ref = rest[atIdx+1:]
|
||||
} else {
|
||||
repoURL = "oci://" + rest
|
||||
ref = "" // No digest specified
|
||||
}
|
||||
}
|
||||
return "oci", repoURL, ref, nil
|
||||
}
|
||||
|
||||
// Check for git:// prefix or treat as git for http/https
|
||||
if strings.HasPrefix(sourceURL, "git://") || strings.HasPrefix(sourceURL, "http://") || strings.HasPrefix(sourceURL, "https://") || strings.HasPrefix(sourceURL, "ssh://") {
|
||||
// Parse URL to extract ref if present
|
||||
parsedURL, err := url.Parse(sourceURL)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("invalid URL: %w", err)
|
||||
}
|
||||
|
||||
// Check for @ref in the path (e.g., git://host/path@branch)
|
||||
path := parsedURL.Path
|
||||
if idx := strings.LastIndex(path, "@"); idx != -1 {
|
||||
repoURL = fmt.Sprintf("%s://%s%s", parsedURL.Scheme, parsedURL.Host, path[:idx])
|
||||
if parsedURL.RawQuery != "" {
|
||||
repoURL += "?" + parsedURL.RawQuery
|
||||
}
|
||||
ref = path[idx+1:]
|
||||
} else {
|
||||
// Default to main branch if no ref specified
|
||||
repoURL = sourceURL
|
||||
ref = "main"
|
||||
}
|
||||
|
||||
// Normalize git:// to https:// for GitRepository
|
||||
if strings.HasPrefix(repoURL, "git://") {
|
||||
repoURL = strings.Replace(repoURL, "git://", "https://", 1)
|
||||
}
|
||||
|
||||
return "git", repoURL, ref, nil
|
||||
}
|
||||
|
||||
return "", "", "", fmt.Errorf("unsupported source URL scheme (expected oci:// or git://): %s", sourceURL)
|
||||
}
|
||||
|
||||
// generateOCIRepository creates an OCIRepository resource
|
||||
func generateOCIRepository(name, repoURL, digest string) (*unstructured.Unstructured, error) {
|
||||
obj := &unstructured.Unstructured{}
|
||||
obj.SetAPIVersion("source.toolkit.fluxcd.io/v1")
|
||||
obj.SetKind("OCIRepository")
|
||||
obj.SetName(name)
|
||||
obj.SetNamespace("cozy-system")
|
||||
|
||||
spec := map[string]interface{}{
|
||||
"interval": "5m0s",
|
||||
"url": repoURL,
|
||||
}
|
||||
|
||||
if digest != "" {
|
||||
// Ensure digest starts with sha256:
|
||||
if !strings.HasPrefix(digest, "sha256:") {
|
||||
digest = "sha256:" + digest
|
||||
}
|
||||
spec["ref"] = map[string]interface{}{
|
||||
"digest": digest,
|
||||
}
|
||||
}
|
||||
|
||||
if err := unstructured.SetNestedField(obj.Object, spec, "spec"); err != nil {
|
||||
return nil, fmt.Errorf("failed to set spec: %w", err)
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// generateGitRepository creates a GitRepository resource
|
||||
func generateGitRepository(name, repoURL, ref string) (*unstructured.Unstructured, error) {
|
||||
obj := &unstructured.Unstructured{}
|
||||
obj.SetAPIVersion("source.toolkit.fluxcd.io/v1")
|
||||
obj.SetKind("GitRepository")
|
||||
obj.SetName(name)
|
||||
obj.SetNamespace("cozy-system")
|
||||
|
||||
spec := map[string]interface{}{
|
||||
"interval": "5m0s",
|
||||
"url": repoURL,
|
||||
"ref": map[string]interface{}{
|
||||
"branch": ref,
|
||||
},
|
||||
}
|
||||
|
||||
if err := unstructured.SetNestedField(obj.Object, spec, "spec"); err != nil {
|
||||
return nil, fmt.Errorf("failed to set spec: %w", err)
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
148
go.mod
148
go.mod
@@ -2,38 +2,41 @@
|
||||
|
||||
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/go-logr/logr v1.4.2
|
||||
github.com/emicklei/dot v1.10.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/prometheus/client_golang v1.19.1
|
||||
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/robfig/cron/v3 v3.0.1
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/spf13/cobra v1.9.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
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
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
|
||||
@@ -41,85 +44,90 @@ 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/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-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_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
|
||||
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
|
||||
)
|
||||
|
||||
// See: issues.k8s.io/135537
|
||||
replace k8s.io/apimachinery => github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614
|
||||
replace k8s.io/apimachinery => github.com/cozystack/apimachinery v0.0.0-20251219010959-1f91eabae46c
|
||||
|
||||
347
go.sum
347
go.sum
@@ -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,47 +18,52 @@ 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/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614 h1:jH9elECUvhiIs3IMv3oS5k1JgCLVsSK6oU4dmq5gyW8=
|
||||
github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
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/cozystack/apimachinery v0.0.0-20251219010959-1f91eabae46c h1:C2wIfH/OzhU9XOK/e6Ik9cg7nZ1z6fN4lf6a3yFdik8=
|
||||
github.com/cozystack/apimachinery v0.0.0-20251219010959-1f91eabae46c/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
|
||||
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/dot v1.10.0 h1:z17n0ce/FBMz3QbShSzVGhiW447Qhu7fljzvp3Gs6ig=
|
||||
github.com/emicklei/dot v1.10.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
|
||||
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=
|
||||
@@ -66,164 +71,171 @@ 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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.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=
|
||||
@@ -232,50 +244,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=
|
||||
@@ -285,37 +295,42 @@ 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/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/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=
|
||||
|
||||
@@ -24,6 +24,8 @@ API_KNOWN_VIOLATIONS_DIR="${API_KNOWN_VIOLATIONS_DIR:-"${SCRIPT_ROOT}/api/api-ru
|
||||
UPDATE_API_KNOWN_VIOLATIONS="${UPDATE_API_KNOWN_VIOLATIONS:-true}"
|
||||
CONTROLLER_GEN="go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.4"
|
||||
TMPDIR=$(mktemp -d)
|
||||
OPERATOR_CRDDIR=packages/core/installer/crds
|
||||
BACKUPSTRATEGY_CRDDIR=packages/system/backupstrategy-controller/definitions
|
||||
COZY_CONTROLLER_CRDDIR=packages/system/cozystack-controller/crds
|
||||
COZY_RD_CRDDIR=packages/system/cozystack-resource-definition-crd/definition
|
||||
BACKUPS_CORE_CRDDIR=packages/system/backup-controller/definitions
|
||||
@@ -62,6 +64,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=${TMPDIR}
|
||||
|
||||
mv ${TMPDIR}/cozystack.io_packages.yaml ${OPERATOR_CRDDIR}/cozystack.io_packages.yaml
|
||||
mv ${TMPDIR}/cozystack.io_packagesources.yaml ${OPERATOR_CRDDIR}/cozystack.io_packagesources.yaml
|
||||
|
||||
mv ${TMPDIR}/cozystack.io_cozystackresourcedefinitions.yaml \
|
||||
${COZY_RD_CRDDIR}/cozystack.io_cozystackresourcedefinitions.yaml
|
||||
|
||||
|
||||
@@ -5,11 +5,13 @@ import (
|
||||
"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"
|
||||
@@ -37,6 +39,11 @@ type CozystackResourceDefinitionReconciler struct {
|
||||
}
|
||||
|
||||
func (r *CozystackResourceDefinitionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
if err := r.reconcileCozyRDAndUpdateHelmReleases(ctx); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Continue with debounced restart logic
|
||||
return r.debouncedRestart(ctx)
|
||||
}
|
||||
|
||||
@@ -185,3 +192,138 @@ func sortCozyRDs(a, b cozyv1alpha1.CozystackResourceDefinition) int {
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// reconcileCozyRDAndUpdateHelmReleases reconciles all CozystackResourceDefinitions and updates HelmReleases from them
|
||||
func (r *CozystackResourceDefinitionReconciler) reconcileCozyRDAndUpdateHelmReleases(ctx context.Context) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
// List all CozystackResourceDefinitions
|
||||
crdList := &cozyv1alpha1.CozystackResourceDefinitionList{}
|
||||
if err := r.List(ctx, crdList); err != nil {
|
||||
logger.Error(err, "failed to list CozystackResourceDefinitions")
|
||||
return err
|
||||
}
|
||||
|
||||
// Update HelmReleases for each CRD
|
||||
for i := range crdList.Items {
|
||||
crd := &crdList.Items[i]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateHelmReleasesForCRD updates all HelmReleases that match the application labels from CozystackResourceDefinition
|
||||
func (r *CozystackResourceDefinitionReconciler) updateHelmReleasesForCRD(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) 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
|
||||
|
||||
// Validate that applicationKind is non-empty
|
||||
if applicationKind == "" {
|
||||
logger.Error(fmt.Errorf("Application.Kind is empty"), "Skipping HelmRelease update: invalid CozystackResourceDefinition", "crd", crd.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
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.V(4).Info("Found HelmReleases to update", "crd", crd.Name, "kind", applicationKind, "count", len(hrList.Items))
|
||||
|
||||
// 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 in HelmRelease based on CozystackResourceDefinition
|
||||
func (r *CozystackResourceDefinitionReconciler) updateHelmReleaseChart(ctx context.Context, hr *helmv2.HelmRelease, crd *cozyv1alpha1.CozystackResourceDefinition) error {
|
||||
logger := log.FromContext(ctx)
|
||||
hrCopy := hr.DeepCopy()
|
||||
updated := false
|
||||
|
||||
// Validate Chart configuration exists
|
||||
if crd.Spec.Release.Chart.Name == "" {
|
||||
logger.V(4).Info("Skipping HelmRelease chart update: Chart.Name is empty", "crd", crd.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate SourceRef fields
|
||||
if crd.Spec.Release.Chart.SourceRef.Kind == "" ||
|
||||
crd.Spec.Release.Chart.SourceRef.Name == "" ||
|
||||
crd.Spec.Release.Chart.SourceRef.Namespace == "" {
|
||||
logger.Error(fmt.Errorf("invalid SourceRef in CRD"), "Skipping HelmRelease chart update: SourceRef fields are incomplete",
|
||||
"crd", crd.Name,
|
||||
"kind", crd.Spec.Release.Chart.SourceRef.Kind,
|
||||
"name", crd.Spec.Release.Chart.SourceRef.Name,
|
||||
"namespace", crd.Spec.Release.Chart.SourceRef.Namespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get version and reconcileStrategy from CRD or use defaults
|
||||
version := ">= 0.0.0-0"
|
||||
reconcileStrategy := "Revision"
|
||||
// TODO: Add Version and ReconcileStrategy fields to CozystackResourceDefinitionChart if needed
|
||||
|
||||
// Build expected SourceRef
|
||||
expectedSourceRef := helmv2.CrossNamespaceObjectReference{
|
||||
Kind: crd.Spec.Release.Chart.SourceRef.Kind,
|
||||
Name: crd.Spec.Release.Chart.SourceRef.Name,
|
||||
Namespace: crd.Spec.Release.Chart.SourceRef.Namespace,
|
||||
}
|
||||
|
||||
if hrCopy.Spec.Chart == nil {
|
||||
// Need to create Chart spec
|
||||
hrCopy.Spec.Chart = &helmv2.HelmChartTemplate{
|
||||
Spec: helmv2.HelmChartTemplateSpec{
|
||||
Chart: crd.Spec.Release.Chart.Name,
|
||||
Version: version,
|
||||
ReconcileStrategy: reconcileStrategy,
|
||||
SourceRef: expectedSourceRef,
|
||||
},
|
||||
}
|
||||
updated = true
|
||||
} else {
|
||||
// Update existing Chart spec
|
||||
if hrCopy.Spec.Chart.Spec.Chart != crd.Spec.Release.Chart.Name ||
|
||||
hrCopy.Spec.Chart.Spec.SourceRef != expectedSourceRef {
|
||||
hrCopy.Spec.Chart.Spec.Chart = crd.Spec.Release.Chart.Name
|
||||
hrCopy.Spec.Chart.Spec.SourceRef = expectedSourceRef
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
|
||||
if updated {
|
||||
logger.V(4).Info("Updating HelmRelease chart", "name", hr.Name, "namespace", hr.Namespace)
|
||||
if err := r.Update(ctx, hrCopy); err != nil {
|
||||
return fmt.Errorf("failed to update HelmRelease: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
352
internal/fluxinstall/install.go
Normal file
352
internal/fluxinstall/install.go
Normal 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
|
||||
}
|
||||
|
||||
51
internal/fluxinstall/manifests.embed.go
Normal file
51
internal/fluxinstall/manifests.embed.go
Normal 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
|
||||
}
|
||||
|
||||
@@ -11623,7 +11623,6 @@ spec:
|
||||
value: /tmp/.sigstore
|
||||
- name: NO_PROXY
|
||||
value: .svc
|
||||
{{- include "cozy.kubernetes_envs" . | nindent 12 }}
|
||||
image: ghcr.io/fluxcd/source-controller:v1.7.3
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
@@ -11694,7 +11693,6 @@ spec:
|
||||
value: /tmp/.sigstore
|
||||
- name: NO_PROXY
|
||||
value: .svc
|
||||
{{- include "cozy.kubernetes_envs" . | nindent 12 }}
|
||||
image: ghcr.io/fluxcd/kustomize-controller:v1.7.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
@@ -11760,7 +11758,6 @@ spec:
|
||||
value: /tmp/.sigstore
|
||||
- name: NO_PROXY
|
||||
value: .svc
|
||||
{{- include "cozy.kubernetes_envs" . | nindent 12 }}
|
||||
image: ghcr.io/fluxcd/helm-controller:v1.4.3
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
@@ -11823,7 +11820,6 @@ spec:
|
||||
value: /tmp/.sigstore
|
||||
- name: NO_PROXY
|
||||
value: .svc
|
||||
{{- include "cozy.kubernetes_envs" . | nindent 12 }}
|
||||
image: ghcr.io/fluxcd/notification-controller:v1.7.4
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
@@ -11894,7 +11890,6 @@ spec:
|
||||
value: /tmp/.sigstore
|
||||
- name: NO_PROXY
|
||||
value: .svc
|
||||
{{- include "cozy.kubernetes_envs" . | nindent 12 }}
|
||||
image: ghcr.io/fluxcd/source-watcher:v2.0.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
@@ -11936,7 +11931,6 @@ spec:
|
||||
name: data
|
||||
- mountPath: /tmp
|
||||
name: tmp
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
hostNetwork: true
|
||||
priorityClassName: system-cluster-critical
|
||||
securityContext:
|
||||
@@ -2,49 +2,72 @@ 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
|
||||
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),
|
||||
appCRDMap: make(map[appRef]*cozyv1alpha1.CozystackResourceDefinition),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (l *LineageControllerWebhook) Map(hr *helmv2.HelmRelease) (string, string, string, error) {
|
||||
cfg, ok := l.config.Load().(*runtimeConfig)
|
||||
// getApplicationLabel safely extracts an application label from HelmRelease
|
||||
func getApplicationLabel(hr *helmv2.HelmRelease, key string) (string, error) {
|
||||
if hr.Labels == nil {
|
||||
return "", fmt.Errorf("cannot map helm release %s/%s to dynamic app: labels are nil", hr.Namespace, hr.Name)
|
||||
}
|
||||
val, ok := hr.Labels[key]
|
||||
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 %s label", hr.Namespace, hr.Name, key)
|
||||
}
|
||||
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}]
|
||||
if !ok {
|
||||
return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name)
|
||||
}
|
||||
return "apps.cozystack.io/v1alpha1", val.Spec.Application.Kind, val.Spec.Release.Prefix, nil
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (l *LineageControllerWebhook) Map(hr *helmv2.HelmRelease) (string, string, string, error) {
|
||||
// Extract application metadata from labels
|
||||
appKind, err := getApplicationLabel(hr, "apps.cozystack.io/application.kind")
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
appGroup, err := getApplicationLabel(hr, "apps.cozystack.io/application.group")
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
appName, err := getApplicationLabel(hr, "apps.cozystack.io/application.name")
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
// Validate the derived prefix
|
||||
// This ensures correctness when appName appears multiple times in hr.Name
|
||||
if prefix+appName != hr.Name {
|
||||
return "", "", "", fmt.Errorf("cannot derive prefix from helm release %s/%s: name does not end with application name %s", hr.Namespace, hr.Name, appName)
|
||||
}
|
||||
|
||||
return apiVersion, appKind, prefix, nil
|
||||
}
|
||||
|
||||
@@ -24,25 +24,15 @@ func (c *LineageControllerWebhook) Reconcile(ctx context.Context, req ctrl.Reque
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
cfg := &runtimeConfig{
|
||||
chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition),
|
||||
appCRDMap: make(map[appRef]*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 {
|
||||
|
||||
1235
internal/operator/bundle_reconciler.go.old
Normal file
1235
internal/operator/bundle_reconciler.go.old
Normal file
File diff suppressed because it is too large
Load Diff
889
internal/operator/package_reconciler.go
Normal file
889
internal/operator/package_reconciler.go
Normal file
@@ -0,0 +1,889 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
|
||||
helmv2 "github.com/fluxcd/helm-controller/api/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/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"
|
||||
)
|
||||
|
||||
// PackageReconciler reconciles Package resources
|
||||
type PackageReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=cozystack.io,resources=packages,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=cozystack.io,resources=packages/status,verbs=get;update;patch
|
||||
// +kubebuilder:rbac:groups=cozystack.io,resources=packagesources,verbs=get;list;watch
|
||||
// +kubebuilder:rbac:groups=helm.toolkit.fluxcd.io,resources=helmreleases,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch;create;update;patch
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop
|
||||
func (r *PackageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
pkg := &cozyv1alpha1.Package{}
|
||||
if err := r.Get(ctx, req.NamespacedName, pkg); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
// Resource not found, return (ownerReference will handle cleanup)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Get PackageSource with the same name
|
||||
packageSource := &cozyv1alpha1.PackageSource{}
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: pkg.Name}, packageSource); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
meta.SetStatusCondition(&pkg.Status.Conditions, metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "PackageSourceNotFound",
|
||||
Message: fmt.Sprintf("PackageSource %s not found", pkg.Name),
|
||||
})
|
||||
if err := r.Status().Update(ctx, pkg); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Determine variant (default to "default" if not specified)
|
||||
variantName := pkg.Spec.Variant
|
||||
if variantName == "" {
|
||||
variantName = "default"
|
||||
}
|
||||
|
||||
// Find the variant in PackageSource
|
||||
var variant *cozyv1alpha1.Variant
|
||||
for i := range packageSource.Spec.Variants {
|
||||
if packageSource.Spec.Variants[i].Name == variantName {
|
||||
variant = &packageSource.Spec.Variants[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if variant == nil {
|
||||
meta.SetStatusCondition(&pkg.Status.Conditions, metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "VariantNotFound",
|
||||
Message: fmt.Sprintf("Variant %s not found in PackageSource %s", variantName, pkg.Name),
|
||||
})
|
||||
if err := r.Status().Update(ctx, pkg); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// Reconcile namespaces from components
|
||||
if err := r.reconcileNamespaces(ctx, pkg, variant); err != nil {
|
||||
logger.Error(err, "failed to reconcile namespaces")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Validate variant dependencies before creating HelmReleases
|
||||
// If dependencies are missing, we don't create new HelmReleases but don't delete existing ones
|
||||
if err := r.validateVariantDependencies(ctx, pkg, variant); err != nil {
|
||||
logger.Info("variant dependencies not ready, skipping HelmRelease creation", "package", pkg.Name, "error", err)
|
||||
meta.SetStatusCondition(&pkg.Status.Conditions, metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "DependenciesNotReady",
|
||||
Message: fmt.Sprintf("Variant dependencies not ready: %v", err),
|
||||
})
|
||||
if err := r.Status().Update(ctx, pkg); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
// Return success to avoid requeue, but don't create HelmReleases
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// Create HelmReleases for components with Install section
|
||||
helmReleaseCount := 0
|
||||
for _, component := range variant.Components {
|
||||
// Skip components without Install section
|
||||
if component.Install == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if component is disabled via Package spec
|
||||
if pkgComponent, ok := pkg.Spec.Components[component.Name]; ok {
|
||||
if pkgComponent.Enabled != nil && !*pkgComponent.Enabled {
|
||||
logger.V(1).Info("skipping disabled component", "package", pkg.Name, "component", component.Name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Build artifact name: <packagesource>-<variant>-<componentname> (with dots replaced by dashes)
|
||||
artifactName := fmt.Sprintf("%s-%s-%s",
|
||||
strings.ReplaceAll(packageSource.Name, ".", "-"),
|
||||
strings.ReplaceAll(variantName, ".", "-"),
|
||||
strings.ReplaceAll(component.Name, ".", "-"))
|
||||
|
||||
// Determine namespace (from Install or default to cozy-system)
|
||||
namespace := component.Install.Namespace
|
||||
if namespace == "" {
|
||||
namespace = "cozy-system"
|
||||
}
|
||||
|
||||
// Determine release name (from Install or use component name)
|
||||
releaseName := component.Install.ReleaseName
|
||||
if releaseName == "" {
|
||||
releaseName = component.Name
|
||||
}
|
||||
|
||||
// Build labels
|
||||
labels := make(map[string]string)
|
||||
labels["cozystack.io/package"] = pkg.Name
|
||||
if component.Install.Privileged {
|
||||
labels["cozystack.io/privileged"] = "true"
|
||||
}
|
||||
|
||||
// Create HelmRelease
|
||||
hr := &helmv2.HelmRelease{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: releaseName,
|
||||
Namespace: namespace,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: helmv2.HelmReleaseSpec{
|
||||
Interval: metav1.Duration{Duration: 5 * 60 * 1000000000}, // 5m
|
||||
ChartRef: &helmv2.CrossNamespaceSourceReference{
|
||||
Kind: "ExternalArtifact",
|
||||
Name: artifactName,
|
||||
Namespace: "cozy-system",
|
||||
},
|
||||
Install: &helmv2.Install{
|
||||
Remediation: &helmv2.InstallRemediation{
|
||||
Retries: -1,
|
||||
},
|
||||
},
|
||||
Upgrade: &helmv2.Upgrade{
|
||||
Remediation: &helmv2.UpgradeRemediation{
|
||||
Retries: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Set ownerReference
|
||||
hr.OwnerReferences = []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: pkg.APIVersion,
|
||||
Kind: pkg.Kind,
|
||||
Name: pkg.Name,
|
||||
UID: pkg.UID,
|
||||
Controller: func() *bool { b := true; return &b }(),
|
||||
},
|
||||
}
|
||||
|
||||
// Merge values from Package spec if provided
|
||||
if pkgComponent, ok := pkg.Spec.Components[component.Name]; ok && pkgComponent.Values != nil {
|
||||
hr.Spec.Values = pkgComponent.Values
|
||||
}
|
||||
|
||||
// Build DependsOn from component Install and variant DependsOn
|
||||
dependsOn, err := r.buildDependsOn(ctx, pkg, packageSource, variant, &component)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to build DependsOn", "component", component.Name)
|
||||
meta.SetStatusCondition(&pkg.Status.Conditions, metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "DependsOnFailed",
|
||||
Message: fmt.Sprintf("Failed to build DependsOn for component %s: %v", component.Name, err),
|
||||
})
|
||||
if err := r.Status().Update(ctx, pkg); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
if len(dependsOn) > 0 {
|
||||
hr.Spec.DependsOn = dependsOn
|
||||
}
|
||||
|
||||
if err := r.createOrUpdateHelmRelease(ctx, hr); err != nil {
|
||||
logger.Error(err, "failed to reconcile HelmRelease", "name", releaseName, "namespace", namespace)
|
||||
meta.SetStatusCondition(&pkg.Status.Conditions, metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "HelmReleaseFailed",
|
||||
Message: fmt.Sprintf("Failed to create HelmRelease %s: %v", releaseName, err),
|
||||
})
|
||||
if err := r.Status().Update(ctx, pkg); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
helmReleaseCount++
|
||||
logger.Info("reconciled HelmRelease", "package", pkg.Name, "component", component.Name, "releaseName", releaseName, "namespace", namespace)
|
||||
}
|
||||
|
||||
// Cleanup orphaned HelmReleases
|
||||
if err := r.cleanupOrphanedHelmReleases(ctx, pkg, variant); err != nil {
|
||||
logger.Error(err, "failed to cleanup orphaned HelmReleases")
|
||||
// Don't return error, continue with status update
|
||||
}
|
||||
|
||||
// Update status with success message
|
||||
message := fmt.Sprintf("reconciliation succeeded, generated %d helmrelease(s)", helmReleaseCount)
|
||||
meta.SetStatusCondition(&pkg.Status.Conditions, metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "ReconciliationSucceeded",
|
||||
Message: message,
|
||||
})
|
||||
|
||||
if err := r.Status().Update(ctx, pkg); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
logger.Info("reconciled Package", "name", pkg.Name, "helmReleaseCount", helmReleaseCount)
|
||||
|
||||
// Trigger reconcile for Packages that depend on this Package
|
||||
if err := r.triggerDependentPackages(ctx, pkg.Name); err != nil {
|
||||
logger.Error(err, "failed to trigger dependent Packages", "package", pkg.Name)
|
||||
// Don't return error, this is best-effort
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// createOrUpdateHelmRelease creates or updates a HelmRelease
|
||||
func (r *PackageReconciler) createOrUpdateHelmRelease(ctx context.Context, hr *helmv2.HelmRelease) error {
|
||||
existing := &helmv2.HelmRelease{}
|
||||
key := types.NamespacedName{
|
||||
Name: hr.Name,
|
||||
Namespace: hr.Namespace,
|
||||
}
|
||||
|
||||
err := r.Get(ctx, key, existing)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return r.Create(ctx, hr)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Preserve resource version
|
||||
hr.SetResourceVersion(existing.GetResourceVersion())
|
||||
|
||||
// Merge labels
|
||||
labels := hr.GetLabels()
|
||||
if labels == nil {
|
||||
labels = make(map[string]string)
|
||||
}
|
||||
for k, v := range existing.GetLabels() {
|
||||
if _, ok := labels[k]; !ok {
|
||||
labels[k] = v
|
||||
}
|
||||
}
|
||||
hr.SetLabels(labels)
|
||||
|
||||
// Merge annotations
|
||||
annotations := hr.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
for k, v := range existing.GetAnnotations() {
|
||||
if _, ok := annotations[k]; !ok {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
hr.SetAnnotations(annotations)
|
||||
|
||||
// Update Spec
|
||||
existing.Spec = hr.Spec
|
||||
existing.SetLabels(hr.GetLabels())
|
||||
existing.SetAnnotations(hr.GetAnnotations())
|
||||
existing.SetOwnerReferences(hr.GetOwnerReferences())
|
||||
|
||||
return r.Update(ctx, existing)
|
||||
}
|
||||
|
||||
// buildDependsOn builds DependsOn list for a component
|
||||
// Includes:
|
||||
// 1. Dependencies from component.Install.DependsOn (with namespace from referenced component)
|
||||
// 2. Dependencies from variant.DependsOn (all components with Install from referenced Package)
|
||||
func (r *PackageReconciler) buildDependsOn(ctx context.Context, pkg *cozyv1alpha1.Package, packageSource *cozyv1alpha1.PackageSource, variant *cozyv1alpha1.Variant, component *cozyv1alpha1.Component) ([]helmv2.DependencyReference, error) {
|
||||
logger := log.FromContext(ctx)
|
||||
dependsOn := []helmv2.DependencyReference{}
|
||||
|
||||
// Build map of component names to their release names and namespaces in current variant
|
||||
componentMap := make(map[string]struct {
|
||||
releaseName string
|
||||
namespace string
|
||||
})
|
||||
for _, comp := range variant.Components {
|
||||
if comp.Install == nil {
|
||||
continue
|
||||
}
|
||||
compNamespace := comp.Install.Namespace
|
||||
if compNamespace == "" {
|
||||
compNamespace = "cozy-system"
|
||||
}
|
||||
compReleaseName := comp.Install.ReleaseName
|
||||
if compReleaseName == "" {
|
||||
compReleaseName = comp.Name
|
||||
}
|
||||
componentMap[comp.Name] = struct {
|
||||
releaseName string
|
||||
namespace string
|
||||
}{
|
||||
releaseName: compReleaseName,
|
||||
namespace: compNamespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Add dependencies from component.Install.DependsOn
|
||||
if len(component.Install.DependsOn) > 0 {
|
||||
for _, depName := range component.Install.DependsOn {
|
||||
depComp, ok := componentMap[depName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("component %s not found in variant for dependency %s", depName, component.Name)
|
||||
}
|
||||
dependsOn = append(dependsOn, helmv2.DependencyReference{
|
||||
Name: depComp.releaseName,
|
||||
Namespace: depComp.namespace,
|
||||
})
|
||||
logger.V(1).Info("added component dependency", "component", component.Name, "dependsOn", depName, "releaseName", depComp.releaseName, "namespace", depComp.namespace)
|
||||
}
|
||||
}
|
||||
|
||||
// Add dependencies from variant.DependsOn
|
||||
if len(variant.DependsOn) > 0 {
|
||||
for _, depPackageName := range variant.DependsOn {
|
||||
// Check if dependency is in IgnoreDependencies
|
||||
ignore := false
|
||||
for _, ignoreDep := range pkg.Spec.IgnoreDependencies {
|
||||
if ignoreDep == depPackageName {
|
||||
ignore = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if ignore {
|
||||
logger.V(1).Info("ignoring dependency", "package", pkg.Name, "dependency", depPackageName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the Package
|
||||
depPackage := &cozyv1alpha1.Package{}
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: depPackageName}, depPackage); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil, fmt.Errorf("dependent Package %s not found", depPackageName)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get dependent Package %s: %w", depPackageName, err)
|
||||
}
|
||||
|
||||
// Get the variant from dependent Package
|
||||
depVariantName := depPackage.Spec.Variant
|
||||
if depVariantName == "" {
|
||||
depVariantName = "default"
|
||||
}
|
||||
|
||||
// Get the PackageSource
|
||||
depPackageSource := &cozyv1alpha1.PackageSource{}
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: depPackageName}, depPackageSource); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil, fmt.Errorf("dependent PackageSource %s not found", depPackageName)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get dependent PackageSource %s: %w", depPackageName, err)
|
||||
}
|
||||
|
||||
// Find the variant in PackageSource
|
||||
var depVariant *cozyv1alpha1.Variant
|
||||
for i := range depPackageSource.Spec.Variants {
|
||||
if depPackageSource.Spec.Variants[i].Name == depVariantName {
|
||||
depVariant = &depPackageSource.Spec.Variants[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if depVariant == nil {
|
||||
return nil, fmt.Errorf("dependent variant %s not found in PackageSource %s", depVariantName, depPackageName)
|
||||
}
|
||||
|
||||
// Add all components with Install from dependent variant
|
||||
for _, depComp := range depVariant.Components {
|
||||
if depComp.Install == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if component is disabled in dependent Package
|
||||
if depPkgComponent, ok := depPackage.Spec.Components[depComp.Name]; ok {
|
||||
if depPkgComponent.Enabled != nil && !*depPkgComponent.Enabled {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
depCompNamespace := depComp.Install.Namespace
|
||||
if depCompNamespace == "" {
|
||||
depCompNamespace = "cozy-system"
|
||||
}
|
||||
depCompReleaseName := depComp.Install.ReleaseName
|
||||
if depCompReleaseName == "" {
|
||||
depCompReleaseName = depComp.Name
|
||||
}
|
||||
|
||||
dependsOn = append(dependsOn, helmv2.DependencyReference{
|
||||
Name: depCompReleaseName,
|
||||
Namespace: depCompNamespace,
|
||||
})
|
||||
logger.V(1).Info("added variant dependency", "package", pkg.Name, "dependency", depPackageName, "component", depComp.Name, "releaseName", depCompReleaseName, "namespace", depCompNamespace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dependsOn, nil
|
||||
}
|
||||
|
||||
// validateVariantDependencies validates that all variant dependencies exist
|
||||
// Returns error if any dependency is missing
|
||||
func (r *PackageReconciler) validateVariantDependencies(ctx context.Context, pkg *cozyv1alpha1.Package, variant *cozyv1alpha1.Variant) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
if len(variant.DependsOn) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, depPackageName := range variant.DependsOn {
|
||||
// Check if dependency is in IgnoreDependencies
|
||||
ignore := false
|
||||
for _, ignoreDep := range pkg.Spec.IgnoreDependencies {
|
||||
if ignoreDep == depPackageName {
|
||||
ignore = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if ignore {
|
||||
logger.V(1).Info("ignoring dependency", "package", pkg.Name, "dependency", depPackageName)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the Package
|
||||
depPackage := &cozyv1alpha1.Package{}
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: depPackageName}, depPackage); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return fmt.Errorf("dependent Package %s not found", depPackageName)
|
||||
}
|
||||
return fmt.Errorf("failed to get dependent Package %s: %w", depPackageName, err)
|
||||
}
|
||||
|
||||
// Get the PackageSource
|
||||
depPackageSource := &cozyv1alpha1.PackageSource{}
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: depPackageName}, depPackageSource); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return fmt.Errorf("dependent PackageSource %s not found", depPackageName)
|
||||
}
|
||||
return fmt.Errorf("failed to get dependent PackageSource %s: %w", depPackageName, err)
|
||||
}
|
||||
|
||||
// Get the variant from dependent Package
|
||||
depVariantName := depPackage.Spec.Variant
|
||||
if depVariantName == "" {
|
||||
depVariantName = "default"
|
||||
}
|
||||
|
||||
// Find the variant in PackageSource
|
||||
var depVariant *cozyv1alpha1.Variant
|
||||
for i := range depPackageSource.Spec.Variants {
|
||||
if depPackageSource.Spec.Variants[i].Name == depVariantName {
|
||||
depVariant = &depPackageSource.Spec.Variants[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if depVariant == nil {
|
||||
return fmt.Errorf("dependent variant %s not found in PackageSource %s", depVariantName, depPackageName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// reconcileNamespaces creates or updates namespaces based on components in the variant
|
||||
func (r *PackageReconciler) reconcileNamespaces(ctx context.Context, pkg *cozyv1alpha1.Package, variant *cozyv1alpha1.Variant) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
// Collect namespaces from components
|
||||
// Map: namespace -> {isPrivileged}
|
||||
type namespaceInfo struct {
|
||||
privileged bool
|
||||
}
|
||||
namespacesMap := make(map[string]namespaceInfo)
|
||||
|
||||
for _, component := range variant.Components {
|
||||
// Skip components without Install section
|
||||
if component.Install == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if component is disabled via Package spec
|
||||
if pkgComponent, ok := pkg.Spec.Components[component.Name]; ok {
|
||||
if pkgComponent.Enabled != nil && !*pkgComponent.Enabled {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace must be set
|
||||
namespace := component.Install.Namespace
|
||||
if namespace == "" {
|
||||
return fmt.Errorf("component %s has empty namespace in Install section", component.Name)
|
||||
}
|
||||
|
||||
info, exists := namespacesMap[namespace]
|
||||
if !exists {
|
||||
info = namespaceInfo{
|
||||
privileged: false,
|
||||
}
|
||||
}
|
||||
|
||||
// If component is privileged, mark namespace as privileged
|
||||
if component.Install.Privileged {
|
||||
info.privileged = true
|
||||
}
|
||||
|
||||
namespacesMap[namespace] = info
|
||||
}
|
||||
|
||||
// Create or update all namespaces
|
||||
for nsName, info := range namespacesMap {
|
||||
namespace := &corev1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: nsName,
|
||||
Labels: make(map[string]string),
|
||||
Annotations: map[string]string{
|
||||
"helm.sh/resource-policy": "keep",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Add system label only for non-tenant namespaces
|
||||
if !strings.HasPrefix(nsName, "tenant-") {
|
||||
namespace.Labels["cozystack.io/system"] = "true"
|
||||
}
|
||||
|
||||
// Add privileged label if needed
|
||||
if info.privileged {
|
||||
namespace.Labels["pod-security.kubernetes.io/enforce"] = "privileged"
|
||||
}
|
||||
|
||||
if err := r.createOrUpdateNamespace(ctx, namespace); err != nil {
|
||||
logger.Error(err, "failed to reconcile namespace", "name", nsName, "privileged", info.privileged)
|
||||
return fmt.Errorf("failed to reconcile namespace %s: %w", nsName, err)
|
||||
}
|
||||
logger.Info("reconciled namespace", "name", nsName, "privileged", info.privileged)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createOrUpdateNamespace creates or updates a namespace
|
||||
func (r *PackageReconciler) createOrUpdateNamespace(ctx context.Context, namespace *corev1.Namespace) error {
|
||||
existing := &corev1.Namespace{}
|
||||
key := types.NamespacedName{Name: namespace.Name}
|
||||
|
||||
err := r.Get(ctx, key, existing)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return r.Create(ctx, namespace)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Preserve resource version
|
||||
namespace.SetResourceVersion(existing.GetResourceVersion())
|
||||
|
||||
// Merge labels
|
||||
labels := namespace.GetLabels()
|
||||
if labels == nil {
|
||||
labels = make(map[string]string)
|
||||
}
|
||||
for k, v := range existing.GetLabels() {
|
||||
if _, ok := labels[k]; !ok {
|
||||
labels[k] = v
|
||||
}
|
||||
}
|
||||
namespace.SetLabels(labels)
|
||||
|
||||
// Merge annotations
|
||||
annotations := namespace.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
for k, v := range existing.GetAnnotations() {
|
||||
if _, ok := annotations[k]; !ok {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
namespace.SetAnnotations(annotations)
|
||||
|
||||
return r.Update(ctx, namespace)
|
||||
}
|
||||
|
||||
// cleanupOrphanedHelmReleases removes HelmReleases that are no longer needed
|
||||
func (r *PackageReconciler) cleanupOrphanedHelmReleases(ctx context.Context, pkg *cozyv1alpha1.Package, variant *cozyv1alpha1.Variant) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
// Build map of desired HelmRelease names (from components with Install)
|
||||
desiredReleases := make(map[types.NamespacedName]bool)
|
||||
for _, component := range variant.Components {
|
||||
if component.Install == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if component is disabled via Package spec
|
||||
if pkgComponent, ok := pkg.Spec.Components[component.Name]; ok {
|
||||
if pkgComponent.Enabled != nil && !*pkgComponent.Enabled {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
namespace := component.Install.Namespace
|
||||
if namespace == "" {
|
||||
namespace = "cozy-system"
|
||||
}
|
||||
|
||||
releaseName := component.Install.ReleaseName
|
||||
if releaseName == "" {
|
||||
releaseName = component.Name
|
||||
}
|
||||
|
||||
desiredReleases[types.NamespacedName{
|
||||
Name: releaseName,
|
||||
Namespace: namespace,
|
||||
}] = true
|
||||
}
|
||||
|
||||
// Find all HelmReleases owned by this Package
|
||||
hrList := &helmv2.HelmReleaseList{}
|
||||
if err := r.List(ctx, hrList, client.MatchingLabels{
|
||||
"cozystack.io/package": pkg.Name,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete HelmReleases that are not in desired list
|
||||
for _, hr := range hrList.Items {
|
||||
key := types.NamespacedName{
|
||||
Name: hr.Name,
|
||||
Namespace: hr.Namespace,
|
||||
}
|
||||
if !desiredReleases[key] {
|
||||
logger.Info("deleting orphaned HelmRelease", "name", hr.Name, "namespace", hr.Namespace, "package", pkg.Name)
|
||||
if err := r.Delete(ctx, &hr); err != nil && !apierrors.IsNotFound(err) {
|
||||
logger.Error(err, "failed to delete orphaned HelmRelease", "name", hr.Name, "namespace", hr.Namespace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// triggerDependentPackages triggers reconcile for all Packages that depend on the given Package
|
||||
func (r *PackageReconciler) triggerDependentPackages(ctx context.Context, packageName string) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
// Get all Packages
|
||||
packageList := &cozyv1alpha1.PackageList{}
|
||||
if err := r.List(ctx, packageList); err != nil {
|
||||
return fmt.Errorf("failed to list Packages: %w", err)
|
||||
}
|
||||
|
||||
// For each Package, check if it depends on the given Package
|
||||
for _, pkg := range packageList.Items {
|
||||
// Skip the Package itself
|
||||
if pkg.Name == packageName {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get PackageSource
|
||||
packageSource := &cozyv1alpha1.PackageSource{}
|
||||
if err := r.Get(ctx, types.NamespacedName{Name: pkg.Name}, packageSource); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
logger.V(1).Error(err, "failed to get PackageSource", "package", pkg.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// Determine variant
|
||||
variantName := pkg.Spec.Variant
|
||||
if variantName == "" {
|
||||
variantName = "default"
|
||||
}
|
||||
|
||||
// Find variant
|
||||
var variant *cozyv1alpha1.Variant
|
||||
for i := range packageSource.Spec.Variants {
|
||||
if packageSource.Spec.Variants[i].Name == variantName {
|
||||
variant = &packageSource.Spec.Variants[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if variant == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this Package depends on the given Package
|
||||
dependsOn := false
|
||||
for _, dep := range variant.DependsOn {
|
||||
// Check if dependency is in IgnoreDependencies
|
||||
ignore := false
|
||||
for _, ignoreDep := range pkg.Spec.IgnoreDependencies {
|
||||
if ignoreDep == dep {
|
||||
ignore = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if ignore {
|
||||
continue
|
||||
}
|
||||
|
||||
if dep == packageName {
|
||||
dependsOn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if dependsOn {
|
||||
logger.V(1).Info("triggering reconcile for dependent Package", "package", pkg.Name, "dependency", packageName)
|
||||
// Trigger reconcile by updating the Package (add annotation or just requeue)
|
||||
// We can't directly requeue from here, but we can update the Package to trigger reconcile
|
||||
// Actually, we can use the client to trigger an update, but that might cause infinite loop
|
||||
// Better approach: use event handler or just log and let the watch handle it
|
||||
// For now, we'll just log - the watch on PackageSource should handle it
|
||||
// But we need a way to trigger reconcile...
|
||||
// Let's add an annotation to trigger reconcile
|
||||
if pkg.Annotations == nil {
|
||||
pkg.Annotations = make(map[string]string)
|
||||
}
|
||||
pkg.Annotations["cozystack.io/trigger-reconcile"] = fmt.Sprintf("%d", metav1.Now().Unix())
|
||||
if err := r.Update(ctx, &pkg); err != nil {
|
||||
logger.V(1).Error(err, "failed to trigger reconcile for dependent Package", "package", pkg.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *PackageReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
Named("cozystack-package").
|
||||
For(&cozyv1alpha1.Package{}).
|
||||
Watches(
|
||||
&cozyv1alpha1.PackageSource{},
|
||||
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
ps, ok := obj.(*cozyv1alpha1.PackageSource)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
// Find Package with the same name as PackageSource
|
||||
// PackageSource and Package share the same name
|
||||
pkg := &cozyv1alpha1.Package{}
|
||||
if err := mgr.GetClient().Get(ctx, types.NamespacedName{Name: ps.Name}, pkg); err != nil {
|
||||
// Package not found, that's ok - it might not exist yet
|
||||
return nil
|
||||
}
|
||||
// Trigger reconcile for the corresponding Package
|
||||
return []reconcile.Request{{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: pkg.Name,
|
||||
},
|
||||
}}
|
||||
}),
|
||||
).
|
||||
Watches(
|
||||
&cozyv1alpha1.Package{},
|
||||
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
updatedPkg, ok := obj.(*cozyv1alpha1.Package)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
// Find all Packages that depend on this Package
|
||||
packageList := &cozyv1alpha1.PackageList{}
|
||||
if err := mgr.GetClient().List(ctx, packageList); err != nil {
|
||||
return nil
|
||||
}
|
||||
var requests []reconcile.Request
|
||||
for _, pkg := range packageList.Items {
|
||||
if pkg.Name == updatedPkg.Name {
|
||||
continue // Skip the Package itself
|
||||
}
|
||||
// Get PackageSource to check dependencies
|
||||
packageSource := &cozyv1alpha1.PackageSource{}
|
||||
if err := mgr.GetClient().Get(ctx, types.NamespacedName{Name: pkg.Name}, packageSource); err != nil {
|
||||
continue
|
||||
}
|
||||
// Determine variant
|
||||
variantName := pkg.Spec.Variant
|
||||
if variantName == "" {
|
||||
variantName = "default"
|
||||
}
|
||||
// Find variant
|
||||
for _, variant := range packageSource.Spec.Variants {
|
||||
if variant.Name == variantName {
|
||||
// Check if this variant depends on updatedPkg
|
||||
for _, dep := range variant.DependsOn {
|
||||
// Check if dependency is in IgnoreDependencies
|
||||
ignore := false
|
||||
for _, ignoreDep := range pkg.Spec.IgnoreDependencies {
|
||||
if ignoreDep == dep {
|
||||
ignore = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if ignore {
|
||||
continue
|
||||
}
|
||||
if dep == updatedPkg.Name {
|
||||
requests = append(requests, reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: pkg.Name,
|
||||
},
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return requests
|
||||
}),
|
||||
).
|
||||
Complete(r)
|
||||
}
|
||||
468
internal/operator/packagesource_reconciler.go
Normal file
468
internal/operator/packagesource_reconciler.go
Normal file
@@ -0,0 +1,468 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
|
||||
sourcewatcherv1beta1 "github.com/fluxcd/source-watcher/api/v2/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/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"
|
||||
)
|
||||
|
||||
// PackageSourceReconciler reconciles PackageSource resources
|
||||
type PackageSourceReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// +kubebuilder:rbac:groups=cozystack.io,resources=packagesources,verbs=get;list;watch;create;update;patch;delete
|
||||
// +kubebuilder:rbac:groups=cozystack.io,resources=packagesources/status,verbs=get;update;patch
|
||||
// +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 *PackageSourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
packageSource := &cozyv1alpha1.PackageSource{}
|
||||
if err := r.Get(ctx, req.NamespacedName, packageSource); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
// Resource not found, return (ownerReference will handle cleanup)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Generate ArtifactGenerator for package source
|
||||
if err := r.reconcileArtifactGenerators(ctx, packageSource); err != nil {
|
||||
logger.Error(err, "failed to reconcile ArtifactGenerator")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// Update PackageSource status (variants and conditions from ArtifactGenerator)
|
||||
if err := r.updateStatus(ctx, packageSource); err != nil {
|
||||
logger.Error(err, "failed to update status")
|
||||
// Don't return error, status update is not critical
|
||||
}
|
||||
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// reconcileArtifactGenerators generates a single ArtifactGenerator for the package source
|
||||
// Creates one ArtifactGenerator per package source with all OutputArtifacts from components
|
||||
func (r *PackageSourceReconciler) reconcileArtifactGenerators(ctx context.Context, packageSource *cozyv1alpha1.PackageSource) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
// Check if SourceRef is set
|
||||
if packageSource.Spec.SourceRef == nil {
|
||||
logger.Info("skipping ArtifactGenerator creation, SourceRef not set", "packageSource", packageSource.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build library map from all variants
|
||||
// Map key is the library name (from lib.Name or extracted from path)
|
||||
// This allows components to reference libraries by name
|
||||
libraryMap := make(map[string]cozyv1alpha1.Library)
|
||||
for _, variant := range packageSource.Spec.Variants {
|
||||
for _, lib := range variant.Libraries {
|
||||
libName := lib.Name
|
||||
if libName == "" {
|
||||
// If library name is not set, extract from path
|
||||
libName = r.getPackageNameFromPath(lib.Path)
|
||||
}
|
||||
if libName != "" {
|
||||
// Store library with the resolved name
|
||||
libraryMap[libName] = lib
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace is always cozy-system
|
||||
namespace := "cozy-system"
|
||||
// ArtifactGenerator name is the package source name
|
||||
agName := packageSource.Name
|
||||
|
||||
// Collect all OutputArtifacts
|
||||
outputArtifacts := []sourcewatcherv1beta1.OutputArtifact{}
|
||||
|
||||
// Process all variants and their components
|
||||
for _, variant := range packageSource.Spec.Variants {
|
||||
for _, component := range variant.Components {
|
||||
// Skip components without path
|
||||
if component.Path == "" {
|
||||
logger.V(1).Info("skipping component without path", "packageSource", packageSource.Name, "variant", variant.Name, "component", component.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.V(1).Info("processing component", "packageSource", packageSource.Name, "variant", variant.Name, "component", component.Name, "path", component.Path)
|
||||
|
||||
// Extract component name from path (last component)
|
||||
componentPathName := r.getPackageNameFromPath(component.Path)
|
||||
if componentPathName == "" {
|
||||
logger.Info("skipping component with invalid path", "packageSource", packageSource.Name, "variant", variant.Name, "component", component.Name, "path", component.Path)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get basePath with default values
|
||||
basePath := r.getBasePath(packageSource)
|
||||
|
||||
// Build copy operations
|
||||
copyOps := []sourcewatcherv1beta1.CopyOperation{
|
||||
{
|
||||
From: r.buildSourcePath(packageSource.Spec.SourceRef.Name, basePath, component.Path),
|
||||
To: fmt.Sprintf("@artifact/%s/", componentPathName),
|
||||
},
|
||||
}
|
||||
|
||||
// Add libraries if specified
|
||||
for _, libName := range component.Libraries {
|
||||
if lib, ok := libraryMap[libName]; ok {
|
||||
copyOps = append(copyOps, sourcewatcherv1beta1.CopyOperation{
|
||||
From: r.buildSourcePath(packageSource.Spec.SourceRef.Name, basePath, lib.Path),
|
||||
To: fmt.Sprintf("@artifact/%s/charts/%s/", componentPathName, libName),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add valuesFiles if specified
|
||||
for i, valuesFile := range component.ValuesFiles {
|
||||
strategy := "Merge"
|
||||
if i == 0 {
|
||||
strategy = "Overwrite"
|
||||
}
|
||||
copyOps = append(copyOps, sourcewatcherv1beta1.CopyOperation{
|
||||
From: r.buildSourceFilePath(packageSource.Spec.SourceRef.Name, basePath, fmt.Sprintf("%s/%s", component.Path, valuesFile)),
|
||||
To: fmt.Sprintf("@artifact/%s/values.yaml", componentPathName),
|
||||
Strategy: strategy,
|
||||
})
|
||||
}
|
||||
|
||||
// Artifact name: <packagesource>-<variant>-<componentname>
|
||||
// Replace dots with dashes to comply with Kubernetes naming requirements
|
||||
artifactName := fmt.Sprintf("%s-%s-%s",
|
||||
strings.ReplaceAll(packageSource.Name, ".", "-"),
|
||||
strings.ReplaceAll(variant.Name, ".", "-"),
|
||||
strings.ReplaceAll(component.Name, ".", "-"))
|
||||
|
||||
outputArtifacts = append(outputArtifacts, sourcewatcherv1beta1.OutputArtifact{
|
||||
Name: artifactName,
|
||||
Copy: copyOps,
|
||||
})
|
||||
|
||||
logger.Info("added OutputArtifact for component", "packageSource", packageSource.Name, "variant", variant.Name, "component", component.Name, "artifactName", artifactName)
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no OutputArtifacts, return (ownerReference will handle cleanup if needed)
|
||||
if len(outputArtifacts) == 0 {
|
||||
logger.Info("no OutputArtifacts to generate, skipping ArtifactGenerator creation", "packageSource", packageSource.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build labels
|
||||
labels := make(map[string]string)
|
||||
labels["cozystack.io/packagesource"] = packageSource.Name
|
||||
|
||||
// Create single ArtifactGenerator for the package source
|
||||
ag := &sourcewatcherv1beta1.ArtifactGenerator{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: agName,
|
||||
Namespace: namespace,
|
||||
Labels: labels,
|
||||
},
|
||||
Spec: sourcewatcherv1beta1.ArtifactGeneratorSpec{
|
||||
Sources: []sourcewatcherv1beta1.SourceReference{
|
||||
{
|
||||
Alias: packageSource.Spec.SourceRef.Name,
|
||||
Kind: packageSource.Spec.SourceRef.Kind,
|
||||
Name: packageSource.Spec.SourceRef.Name,
|
||||
Namespace: packageSource.Spec.SourceRef.Namespace,
|
||||
},
|
||||
},
|
||||
OutputArtifacts: outputArtifacts,
|
||||
},
|
||||
}
|
||||
|
||||
// Set ownerReference
|
||||
ag.OwnerReferences = []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: packageSource.APIVersion,
|
||||
Kind: packageSource.Kind,
|
||||
Name: packageSource.Name,
|
||||
UID: packageSource.UID,
|
||||
Controller: func() *bool { b := true; return &b }(),
|
||||
},
|
||||
}
|
||||
|
||||
logger.Info("creating ArtifactGenerator for package source", "packageSource", packageSource.Name, "agName", agName, "namespace", namespace, "outputArtifactCount", len(outputArtifacts))
|
||||
|
||||
if err := r.createOrUpdate(ctx, ag); err != nil {
|
||||
return fmt.Errorf("failed to reconcile ArtifactGenerator %s: %w", agName, err)
|
||||
}
|
||||
|
||||
logger.Info("reconciled ArtifactGenerator for package source", "name", agName, "namespace", namespace, "outputArtifactCount", len(outputArtifacts))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func (r *PackageSourceReconciler) getPackageNameFromPath(path string) string {
|
||||
parts := strings.Split(path, "/")
|
||||
if len(parts) > 0 {
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getBasePath returns the basePath with default values based on source kind
|
||||
func (r *PackageSourceReconciler) getBasePath(packageSource *cozyv1alpha1.PackageSource) string {
|
||||
// If path is explicitly set in SourceRef, use it (but normalize "/" to empty)
|
||||
if packageSource.Spec.SourceRef.Path != "" {
|
||||
path := strings.Trim(packageSource.Spec.SourceRef.Path, "/")
|
||||
// If path is "/" or empty after trim, return empty string
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
return path
|
||||
}
|
||||
// Default values based on kind
|
||||
if packageSource.Spec.SourceRef.Kind == "OCIRepository" {
|
||||
return "" // Root for OCI
|
||||
}
|
||||
// Default for GitRepository
|
||||
return "packages"
|
||||
}
|
||||
|
||||
// buildSourcePath builds the full source path using basePath with glob pattern
|
||||
func (r *PackageSourceReconciler) buildSourcePath(sourceName, basePath, path string) string {
|
||||
// Remove leading/trailing slashes and combine
|
||||
parts := []string{}
|
||||
if basePath != "" {
|
||||
trimmed := strings.Trim(basePath, "/")
|
||||
if trimmed != "" {
|
||||
parts = append(parts, trimmed)
|
||||
}
|
||||
}
|
||||
if path != "" {
|
||||
trimmed := strings.Trim(path, "/")
|
||||
if trimmed != "" {
|
||||
parts = append(parts, trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
fullPath := strings.Join(parts, "/")
|
||||
if fullPath == "" {
|
||||
return fmt.Sprintf("@%s/**", sourceName)
|
||||
}
|
||||
return fmt.Sprintf("@%s/%s/**", sourceName, fullPath)
|
||||
}
|
||||
|
||||
// buildSourceFilePath builds the full source path for a specific file (without glob pattern)
|
||||
func (r *PackageSourceReconciler) buildSourceFilePath(sourceName, basePath, path string) string {
|
||||
// Remove leading/trailing slashes and combine
|
||||
parts := []string{}
|
||||
if basePath != "" {
|
||||
trimmed := strings.Trim(basePath, "/")
|
||||
if trimmed != "" {
|
||||
parts = append(parts, trimmed)
|
||||
}
|
||||
}
|
||||
if path != "" {
|
||||
trimmed := strings.Trim(path, "/")
|
||||
if trimmed != "" {
|
||||
parts = append(parts, trimmed)
|
||||
}
|
||||
}
|
||||
|
||||
fullPath := strings.Join(parts, "/")
|
||||
if fullPath == "" {
|
||||
return fmt.Sprintf("@%s", sourceName)
|
||||
}
|
||||
return fmt.Sprintf("@%s/%s", sourceName, fullPath)
|
||||
}
|
||||
|
||||
// createOrUpdate creates or updates a resource
|
||||
func (r *PackageSourceReconciler) 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 (OutputArtifacts and Sources)
|
||||
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,
|
||||
"outputArtifactCount", len(ag.Spec.OutputArtifacts))
|
||||
// Update Spec from obj (which contains the desired state with all OutputArtifacts)
|
||||
existingAG.Spec = ag.Spec
|
||||
// Preserve metadata updates we made above
|
||||
existingAG.SetLabels(ag.GetLabels())
|
||||
existingAG.SetAnnotations(ag.GetAnnotations())
|
||||
existingAG.SetOwnerReferences(ag.GetOwnerReferences())
|
||||
// Use existingAG for Update
|
||||
obj = existingAG
|
||||
}
|
||||
}
|
||||
|
||||
return r.Update(ctx, obj)
|
||||
}
|
||||
|
||||
// updateStatus updates PackageSource status (variants and conditions from ArtifactGenerator)
|
||||
func (r *PackageSourceReconciler) updateStatus(ctx context.Context, packageSource *cozyv1alpha1.PackageSource) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
// Update variants in status from spec
|
||||
variantNames := make([]string, 0, len(packageSource.Spec.Variants))
|
||||
for _, variant := range packageSource.Spec.Variants {
|
||||
variantNames = append(variantNames, variant.Name)
|
||||
}
|
||||
packageSource.Status.Variants = strings.Join(variantNames, ",")
|
||||
|
||||
// Check if SourceRef is set
|
||||
if packageSource.Spec.SourceRef == nil {
|
||||
// Set status to unknown if SourceRef is not set
|
||||
meta.SetStatusCondition(&packageSource.Status.Conditions, metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: metav1.ConditionUnknown,
|
||||
Reason: "SourceRefNotSet",
|
||||
Message: "SourceRef is not configured",
|
||||
})
|
||||
return r.Status().Update(ctx, packageSource)
|
||||
}
|
||||
|
||||
// Get ArtifactGenerator
|
||||
ag := &sourcewatcherv1beta1.ArtifactGenerator{}
|
||||
agKey := types.NamespacedName{
|
||||
Name: packageSource.Name,
|
||||
Namespace: "cozy-system",
|
||||
}
|
||||
|
||||
if err := r.Get(ctx, agKey, ag); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
// ArtifactGenerator not found, set status to unknown
|
||||
meta.SetStatusCondition(&packageSource.Status.Conditions, metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: metav1.ConditionUnknown,
|
||||
Reason: "ArtifactGeneratorNotFound",
|
||||
Message: "ArtifactGenerator not found",
|
||||
})
|
||||
return r.Status().Update(ctx, packageSource)
|
||||
}
|
||||
return fmt.Errorf("failed to get ArtifactGenerator: %w", err)
|
||||
}
|
||||
|
||||
// Find Ready condition in ArtifactGenerator
|
||||
readyCondition := meta.FindStatusCondition(ag.Status.Conditions, "Ready")
|
||||
if readyCondition == nil {
|
||||
// No Ready condition in ArtifactGenerator, set status to unknown
|
||||
meta.SetStatusCondition(&packageSource.Status.Conditions, metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: metav1.ConditionUnknown,
|
||||
Reason: "ArtifactGeneratorNotReady",
|
||||
Message: "ArtifactGenerator Ready condition not found",
|
||||
})
|
||||
return r.Status().Update(ctx, packageSource)
|
||||
}
|
||||
|
||||
// Copy Ready condition from ArtifactGenerator to PackageSource
|
||||
meta.SetStatusCondition(&packageSource.Status.Conditions, metav1.Condition{
|
||||
Type: "Ready",
|
||||
Status: readyCondition.Status,
|
||||
Reason: readyCondition.Reason,
|
||||
Message: readyCondition.Message,
|
||||
ObservedGeneration: packageSource.Generation,
|
||||
LastTransitionTime: readyCondition.LastTransitionTime,
|
||||
})
|
||||
|
||||
logger.V(1).Info("updated PackageSource status from ArtifactGenerator",
|
||||
"packageSource", packageSource.Name,
|
||||
"status", readyCondition.Status,
|
||||
"reason", readyCondition.Reason)
|
||||
|
||||
return r.Status().Update(ctx, packageSource)
|
||||
}
|
||||
|
||||
// SetupWithManager sets up the controller with the Manager.
|
||||
func (r *PackageSourceReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
Named("cozystack-packagesource").
|
||||
For(&cozyv1alpha1.PackageSource{}).
|
||||
Watches(
|
||||
&sourcewatcherv1beta1.ArtifactGenerator{},
|
||||
handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
|
||||
ag, ok := obj.(*sourcewatcherv1beta1.ArtifactGenerator)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
// Find the PackageSource that owns this ArtifactGenerator by ownerReference
|
||||
for _, ownerRef := range ag.OwnerReferences {
|
||||
if ownerRef.Kind == "PackageSource" {
|
||||
return []reconcile.Request{{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: ownerRef.Name,
|
||||
},
|
||||
}}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
@@ -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-bucket-application-default-bucket-system
|
||||
namespace: cozy-system
|
||||
interval: 5m
|
||||
timeout: 10m
|
||||
install:
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-cert-manager-crds
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-cert-manager
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-cilium
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-coredns
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-kubevirt-csi-node
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-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-kubernetes-application-kubevirt-kubernetes-fluxcd
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-gateway-api-crds
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-gpu-operator
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-ingress-nginx
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -7,15 +7,10 @@ metadata:
|
||||
cozystack.io/target-cluster-name: {{ .Release.Name }}
|
||||
spec:
|
||||
releaseName: metrics-server
|
||||
chart:
|
||||
spec:
|
||||
chart: cozy-metrics-server
|
||||
reconcileStrategy: Revision
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: cozystack-system
|
||||
namespace: cozy-system
|
||||
version: '>= 0.0.0-0'
|
||||
chartRef:
|
||||
kind: ExternalArtifact
|
||||
name: cozystack-kubernetes-application-kubevirt-kubernetes-metrics-server
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -10,15 +10,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-kubernetes-application-kubevirt-kubernetes-monitoring-agents
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -7,15 +7,10 @@ metadata:
|
||||
cozystack.io/target-cluster-name: {{ .Release.Name }}
|
||||
spec:
|
||||
releaseName: prometheus-operator-crds
|
||||
chart:
|
||||
spec:
|
||||
chart: cozy-prometheus-operator-crds
|
||||
reconcileStrategy: Revision
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: cozystack-system
|
||||
namespace: cozy-system
|
||||
version: '>= 0.0.0-0'
|
||||
chartRef:
|
||||
kind: ExternalArtifact
|
||||
name: cozystack-kubernetes-application-kubevirt-kubernetes-prometheus-operator-crds
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-velero
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-vertical-pod-autoscaler-crds
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-vertical-pod-autoscaler
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-victoria-metrics-operator
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-kubernetes-application-kubevirt-kubernetes-volumesnapshot-crd
|
||||
namespace: cozy-system
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
|
||||
@@ -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-nats-application-default-nats-system
|
||||
namespace: cozy-system
|
||||
interval: 5m
|
||||
timeout: 10m
|
||||
install:
|
||||
|
||||
@@ -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-etcd-application-default-etcd
|
||||
namespace: cozy-system
|
||||
interval: 5m
|
||||
timeout: 10m
|
||||
install:
|
||||
|
||||
@@ -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-info-application-default-info
|
||||
namespace: cozy-system
|
||||
interval: 5m
|
||||
timeout: 10m
|
||||
install:
|
||||
|
||||
@@ -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-ingress-application-default-ingress
|
||||
namespace: cozy-system
|
||||
interval: 5m
|
||||
timeout: 10m
|
||||
install:
|
||||
|
||||
@@ -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-monitoring-application-default-monitoring
|
||||
namespace: cozy-system
|
||||
interval: 5m
|
||||
timeout: 10m
|
||||
install:
|
||||
|
||||
@@ -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-seaweedfs-application-default-seaweedfs
|
||||
namespace: cozy-system
|
||||
interval: 5m
|
||||
timeout: 10m
|
||||
install:
|
||||
|
||||
@@ -13,10 +13,10 @@ diff:
|
||||
cozypkg show -n $(NAMESPACE) $(NAME) --plain | kubectl diff -f-
|
||||
|
||||
update:
|
||||
timoni bundle build -f flux-aio.cue > templates/fluxcd.yaml
|
||||
yq eval '(select(.kind == "Namespace") | .metadata.labels."pod-security.kubernetes.io/enforce") = "privileged"' -i templates/fluxcd.yaml
|
||||
sed -i templates/fluxcd.yaml \
|
||||
timoni bundle build -f flux-aio.cue > manifests/fluxcd.yaml
|
||||
yq eval '(select(.kind == "Namespace") | .metadata.labels."pod-security.kubernetes.io/enforce") = "privileged"' -i manifests/fluxcd.yaml
|
||||
sed -i manifests/fluxcd.yaml \
|
||||
-e '/timoni/d' \
|
||||
-e 's|\.cluster\.local\.,||g' -e 's|\.cluster\.local\,||g' -e 's|\.cluster\.local\.||g' \
|
||||
-e '/value: .svc/a \ {{- include "cozy.kubernetes_envs" . | nindent 12 }}' \
|
||||
-e '/hostNetwork: true/i \ dnsPolicy: ClusterFirstWithHostNet'
|
||||
-e 's|\.cluster\.local\.,||g' -e 's|\.cluster\.local\,||g' -e 's|\.cluster\.local\.||g'
|
||||
# TODO: solve dns issue with hostNetwork for installing helmreleases in tenant k8s clusters
|
||||
#-e '/hostNetwork: true/i \ dnsPolicy: ClusterFirstWithHostNet'
|
||||
|
||||
1
packages/core/flux-aio/manifests
Symbolic link
1
packages/core/flux-aio/manifests
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../internal/fluxinstall/manifests
|
||||
@@ -1,13 +0,0 @@
|
||||
{{- define "cozy.kubernetes_envs" }}
|
||||
{{- $cozyDeployment := lookup "apps/v1" "Deployment" "cozy-system" "cozystack" }}
|
||||
{{- $cozyContainers := dig "spec" "template" "spec" "containers" dict $cozyDeployment }}
|
||||
{{- range $cozyContainers }}
|
||||
{{- if eq .name "cozystack" }}
|
||||
{{- range .env }}
|
||||
{{- if has .name (list "KUBERNETES_SERVICE_HOST" "KUBERNETES_SERVICE_PORT") }}
|
||||
- {{ toJson . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
1
packages/core/installer/1.yaml
Normal file
1
packages/core/installer/1.yaml
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -20,18 +20,7 @@ diff:
|
||||
update:
|
||||
hack/gen-profiles.sh
|
||||
|
||||
image: pre-checks image-matchbox image-cozystack image-talos
|
||||
|
||||
image-cozystack:
|
||||
docker buildx build -f images/cozystack/Dockerfile ../../.. \
|
||||
--tag $(REGISTRY)/installer:$(call settag,$(TAG)) \
|
||||
--cache-from type=registry,ref=$(REGISTRY)/installer:latest \
|
||||
--cache-to type=inline \
|
||||
--metadata-file images/installer.json \
|
||||
$(BUILDX_ARGS)
|
||||
IMAGE="$(REGISTRY)/installer:$(call settag,$(TAG))@$$(yq e '."containerimage.digest"' images/installer.json -o json -r)" \
|
||||
yq -i '.cozystack.image = strenv(IMAGE)' values.yaml
|
||||
rm -f images/installer.json
|
||||
image: pre-checks image-matchbox image-talos image-operator image-packages
|
||||
|
||||
image-talos:
|
||||
test -f ../../../_out/assets/installer-amd64.tar || make talos-installer
|
||||
@@ -58,3 +47,31 @@ talos-initramfs talos-kernel talos-installer talos-iso talos-nocloud talos-metal
|
||||
cat images/talos/profiles/$(subst talos-,,$@).yaml | \
|
||||
docker run --rm -i -v /dev:/dev --privileged "ghcr.io/siderolabs/imager:$(TALOS_VERSION)" --tar-to-stdout - | \
|
||||
tar -C ../../../_out/assets -xzf-
|
||||
|
||||
update-version:
|
||||
TAG="$(call settag,$(TAG))" \
|
||||
yq -i '.cozystackOperator.cozystackVersion = strenv(TAG)' values.yaml
|
||||
|
||||
image-operator:
|
||||
docker buildx build -f images/cozystack-operator/Dockerfile ../../.. \
|
||||
--tag $(REGISTRY)/cozystack-operator:$(call settag,$(TAG)) \
|
||||
--cache-from type=registry,ref=$(REGISTRY)/cozystack-operator:latest \
|
||||
--cache-to type=inline \
|
||||
--metadata-file images/cozystack-operator.json \
|
||||
$(BUILDX_ARGS)
|
||||
IMAGE="$(REGISTRY)/cozystack-operator:$(call settag,$(TAG))@$$(yq e '."containerimage.digest"' images/cozystack-operator.json -o json -r)" \
|
||||
yq -i '.cozystackOperator.image = strenv(IMAGE)' values.yaml
|
||||
rm -f images/cozystack-operator.json
|
||||
|
||||
|
||||
image-packages: update-version
|
||||
mkdir -p ../../../_out/assets images
|
||||
flux push artifact \
|
||||
oci://$(REGISTRY)/platform-packages:$(call settag,$(TAG)) \
|
||||
--path=../../../packages \
|
||||
--source=https://github.com/cozystack/cozystack \
|
||||
--revision="$$(git describe --tags):$$(git rev-parse HEAD)" \
|
||||
2>&1 | tee images/cozystack-packages.log
|
||||
export REPO="oci://$(REGISTRY)/platform-packages"; \
|
||||
export DIGEST=$$(awk -F@ '/artifact successfully pushed/ {print $$2}' images/cozystack-packages.log; rm -f images/cozystack-packages.log); \
|
||||
test -n "$$DIGEST" && yq -i '.cozystackOperator.platformSource = (strenv(REPO) + "@" + strenv(DIGEST))' values.yaml
|
||||
|
||||
156
packages/core/installer/crds/cozystack.io_packages.yaml
Normal file
156
packages/core/installer/crds/cozystack.io_packages.yaml
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.4
|
||||
name: packages.cozystack.io
|
||||
spec:
|
||||
group: cozystack.io
|
||||
names:
|
||||
kind: Package
|
||||
listKind: PackageList
|
||||
plural: packages
|
||||
shortNames:
|
||||
- pkg
|
||||
- pkgs
|
||||
singular: package
|
||||
scope: Cluster
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: Selected variant
|
||||
jsonPath: .spec.variant
|
||||
name: Variant
|
||||
type: string
|
||||
- description: Ready status
|
||||
jsonPath: .status.conditions[?(@.type=='Ready')].status
|
||||
name: Ready
|
||||
type: string
|
||||
- description: Ready message
|
||||
jsonPath: .status.conditions[?(@.type=='Ready')].message
|
||||
name: Status
|
||||
type: string
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: Package is the Schema for the packages API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: PackageSpec defines the desired state of Package
|
||||
properties:
|
||||
components:
|
||||
additionalProperties:
|
||||
description: PackageRelease defines overrides for a specific component
|
||||
properties:
|
||||
enabled:
|
||||
description: |-
|
||||
Enabled indicates whether this component should be installed
|
||||
If false, the component will be disabled even if it's defined in the PackageSource
|
||||
type: boolean
|
||||
values:
|
||||
description: |-
|
||||
Values contains Helm chart values as a JSON object
|
||||
These values will be merged with the default values from the PackageSource
|
||||
x-kubernetes-preserve-unknown-fields: true
|
||||
type: object
|
||||
description: |-
|
||||
Components is a map of release name to component overrides
|
||||
Allows overriding values and enabling/disabling specific components from the PackageSource
|
||||
type: object
|
||||
ignoreDependencies:
|
||||
description: |-
|
||||
IgnoreDependencies is a list of package source dependencies to ignore
|
||||
Dependencies listed here will not be installed even if they are specified in the PackageSource
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
variant:
|
||||
description: |-
|
||||
Variant is the name of the variant to use from the PackageSource
|
||||
If not specified, defaults to "default"
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
description: PackageStatus defines the observed state of Package
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions represents the latest available observations
|
||||
of a Package's state
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
251
packages/core/installer/crds/cozystack.io_packagesources.yaml
Normal file
251
packages/core/installer/crds/cozystack.io_packagesources.yaml
Normal file
@@ -0,0 +1,251 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.4
|
||||
name: packagesources.cozystack.io
|
||||
spec:
|
||||
group: cozystack.io
|
||||
names:
|
||||
kind: PackageSource
|
||||
listKind: PackageSourceList
|
||||
plural: packagesources
|
||||
shortNames:
|
||||
- pkgsrc
|
||||
- pkgsrcs
|
||||
singular: packagesource
|
||||
scope: Cluster
|
||||
versions:
|
||||
- additionalPrinterColumns:
|
||||
- description: Package variants (comma-separated)
|
||||
jsonPath: .status.variants
|
||||
name: Variants
|
||||
type: string
|
||||
- description: Ready status
|
||||
jsonPath: .status.conditions[?(@.type=='Ready')].status
|
||||
name: Ready
|
||||
type: string
|
||||
- description: Ready message
|
||||
jsonPath: .status.conditions[?(@.type=='Ready')].message
|
||||
name: Status
|
||||
type: string
|
||||
name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: PackageSource is the Schema for the packagesources API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: PackageSourceSpec defines the desired state of PackageSource
|
||||
properties:
|
||||
sourceRef:
|
||||
description: SourceRef is the source reference for the package source
|
||||
charts
|
||||
properties:
|
||||
kind:
|
||||
description: Kind of the source reference
|
||||
enum:
|
||||
- GitRepository
|
||||
- OCIRepository
|
||||
type: string
|
||||
name:
|
||||
description: Name of the source reference
|
||||
type: string
|
||||
namespace:
|
||||
description: Namespace of the source reference
|
||||
type: string
|
||||
path:
|
||||
description: |-
|
||||
Path 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.
|
||||
type: string
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
variants:
|
||||
description: |-
|
||||
Variants is a list of package source variants
|
||||
Each variant defines components, applications, dependencies, and libraries for a specific configuration
|
||||
items:
|
||||
description: Variant defines a single variant configuration
|
||||
properties:
|
||||
components:
|
||||
description: Components is a list of Helm releases to be installed
|
||||
as part of this variant
|
||||
items:
|
||||
description: Component defines a single Helm release component
|
||||
within a package source
|
||||
properties:
|
||||
install:
|
||||
description: Install defines installation parameters for
|
||||
this component
|
||||
properties:
|
||||
dependsOn:
|
||||
description: DependsOn is a list of component names
|
||||
that must be installed before this component
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
namespace:
|
||||
description: Namespace is the Kubernetes namespace
|
||||
where the release will be installed
|
||||
type: string
|
||||
privileged:
|
||||
description: Privileged indicates whether this release
|
||||
requires privileged access
|
||||
type: boolean
|
||||
releaseName:
|
||||
description: |-
|
||||
ReleaseName is the name of the HelmRelease resource that will be created
|
||||
If not specified, defaults to the component Name field
|
||||
type: string
|
||||
type: object
|
||||
libraries:
|
||||
description: |-
|
||||
Libraries is a list of library names that this component depends on
|
||||
These libraries must be defined at the variant level
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
description: Name is the unique identifier for this component
|
||||
within the package source
|
||||
type: string
|
||||
path:
|
||||
description: Path is the path to the Helm chart directory
|
||||
type: string
|
||||
valuesFiles:
|
||||
description: ValuesFiles is a list of values file names
|
||||
to use
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- name
|
||||
- path
|
||||
type: object
|
||||
type: array
|
||||
dependsOn:
|
||||
description: |-
|
||||
DependsOn is a list of package source dependencies
|
||||
For example: "cozystack.networking"
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
libraries:
|
||||
description: Libraries is a list of Helm library charts used
|
||||
by components in this variant
|
||||
items:
|
||||
description: Library defines a Helm library chart
|
||||
properties:
|
||||
name:
|
||||
description: Name is the optional name for library placed
|
||||
in charts
|
||||
type: string
|
||||
path:
|
||||
description: Path is the path to the library chart directory
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: Name is the unique identifier for this variant
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
type: object
|
||||
status:
|
||||
description: PackageSourceStatus defines the observed state of PackageSource
|
||||
properties:
|
||||
conditions:
|
||||
description: Conditions represents the latest available observations
|
||||
of a PackageSource's state
|
||||
items:
|
||||
description: Condition contains details for one aspect of the current
|
||||
state of this API Resource.
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: |-
|
||||
lastTransitionTime is the last time the condition transitioned from one status to another.
|
||||
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: |-
|
||||
message is a human readable message indicating details about the transition.
|
||||
This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: |-
|
||||
observedGeneration represents the .metadata.generation that the condition was set based upon.
|
||||
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
|
||||
with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: |-
|
||||
reason contains a programmatic identifier indicating the reason for the condition's last transition.
|
||||
Producers of specific condition types may define expected values and meanings for this field,
|
||||
and whether the values are considered a guaranteed API.
|
||||
The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
variants:
|
||||
description: |-
|
||||
Variants is a comma-separated list of package variant names
|
||||
This field is populated by the controller based on spec.variants keys
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
20
packages/core/installer/example/platform.yaml
Normal file
20
packages/core/installer/example/platform.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.cozystack-platform
|
||||
spec:
|
||||
variant: isp-full
|
||||
components:
|
||||
platform:
|
||||
values:
|
||||
publishing:
|
||||
host: "dev5.infra.aenix.org"
|
||||
apiServerEndpoint: "https://api.dev5.infra.aenix.org"
|
||||
externalIPs:
|
||||
- 10.4.0.94
|
||||
- 10.4.0.179
|
||||
- 10.4.0.26
|
||||
authentication:
|
||||
oidc:
|
||||
enabled: true
|
||||
23
packages/core/installer/images/cozystack-operator/Dockerfile
Normal file
23
packages/core/installer/images/cozystack-operator/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM golang:1.25-alpine as builder
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
RUN apk add --no-cache make git
|
||||
|
||||
COPY . /src/
|
||||
WORKDIR /src
|
||||
|
||||
RUN go mod download
|
||||
|
||||
# Build cozystack-operator
|
||||
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
|
||||
-ldflags="-w -s" \
|
||||
-o /cozystack-operator \
|
||||
./cmd/cozystack-operator
|
||||
|
||||
FROM alpine:3.22
|
||||
|
||||
COPY --from=builder /cozystack-operator /usr/bin/cozystack-operator
|
||||
|
||||
ENTRYPOINT ["/usr/bin/cozystack-operator"]
|
||||
@@ -0,0 +1,12 @@
|
||||
# Exclude everything except src directory
|
||||
*
|
||||
!src/**
|
||||
!api/**
|
||||
!cmd/**
|
||||
!hack/**
|
||||
!internal/**
|
||||
!packages/**
|
||||
!pkg/**
|
||||
!scripts/**
|
||||
!go.mod
|
||||
!go.sum
|
||||
@@ -1,41 +0,0 @@
|
||||
FROM golang:1.24-alpine AS k8s-await-election-builder
|
||||
|
||||
ARG K8S_AWAIT_ELECTION_GITREPO=https://github.com/LINBIT/k8s-await-election
|
||||
ARG K8S_AWAIT_ELECTION_VERSION=0.4.1
|
||||
|
||||
# TARGETARCH is a docker special variable: https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
||||
ARG TARGETARCH
|
||||
|
||||
RUN apk add --no-cache git make
|
||||
RUN git clone ${K8S_AWAIT_ELECTION_GITREPO} /usr/local/go/k8s-await-election/ \
|
||||
&& cd /usr/local/go/k8s-await-election \
|
||||
&& git reset --hard v${K8S_AWAIT_ELECTION_VERSION} \
|
||||
&& make \
|
||||
&& mv ./out/k8s-await-election-${TARGETARCH} /k8s-await-election
|
||||
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
RUN apk add --no-cache make git
|
||||
RUN apk add helm --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community
|
||||
|
||||
COPY . /src/
|
||||
WORKDIR /src
|
||||
|
||||
RUN go mod download
|
||||
|
||||
FROM alpine:3.22
|
||||
|
||||
RUN wget -O- https://github.com/cozystack/cozypkg/raw/refs/heads/main/hack/install.sh | sh -s -- -v 1.2.0
|
||||
|
||||
RUN apk add --no-cache make kubectl helm coreutils git jq openssl
|
||||
|
||||
COPY --from=builder /src/scripts /cozystack/scripts
|
||||
COPY --from=builder /src/packages/core /cozystack/packages/core
|
||||
COPY --from=builder /src/packages/system /cozystack/packages/system
|
||||
COPY --from=k8s-await-election-builder /k8s-await-election /usr/bin/k8s-await-election
|
||||
|
||||
WORKDIR /cozystack
|
||||
ENTRYPOINT ["/usr/bin/k8s-await-election", "/cozystack/scripts/installer.sh" ]
|
||||
@@ -1 +0,0 @@
|
||||
_out
|
||||
117
packages/core/installer/templates/cozystack-operator.yaml
Normal file
117
packages/core/installer/templates/cozystack-operator.yaml
Normal file
@@ -0,0 +1,117 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: cozy-system
|
||||
labels:
|
||||
cozystack.io/system: "true"
|
||||
pod-security.kubernetes.io/enforce: privileged
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: cozystack
|
||||
namespace: cozy-system
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: cozystack
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: cozystack
|
||||
namespace: cozy-system
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: cluster-admin
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: cozystack-operator
|
||||
namespace: cozy-system
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cozystack-operator
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 0
|
||||
maxUnavailable: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cozystack-operator
|
||||
spec:
|
||||
serviceAccountName: cozystack
|
||||
containers:
|
||||
- name: cozystack-operator
|
||||
image: "{{ .Values.cozystackOperator.image }}"
|
||||
args:
|
||||
- --leader-elect=true
|
||||
- --install-flux=true
|
||||
- --metrics-bind-address=0
|
||||
- --health-probe-bind-address=
|
||||
- --cozystack-version={{ .Values.cozystackOperator.cozystackVersion }}
|
||||
{{- if .Values.cozystackOperator.disableTelemetry }}
|
||||
- --disable-telemetry
|
||||
{{- end }}
|
||||
- --platform-source-name=cozystack-platform
|
||||
- --platform-source={{ .Values.cozystackOperator.platformSource }}
|
||||
env:
|
||||
- name: KUBERNETES_SERVICE_HOST
|
||||
value: localhost
|
||||
- name: KUBERNETES_SERVICE_PORT
|
||||
value: "7445"
|
||||
hostNetwork: true
|
||||
tolerations:
|
||||
- key: "node.kubernetes.io/not-ready"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
- key: "node.cilium.io/agent-not-ready"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: PackageSource
|
||||
metadata:
|
||||
name: cozystack.cozystack-platform
|
||||
spec:
|
||||
sourceRef:
|
||||
kind: OCIRepository
|
||||
name: cozystack-packages
|
||||
namespace: cozy-system
|
||||
path: /
|
||||
variants:
|
||||
- name: default
|
||||
components:
|
||||
- install:
|
||||
namespace: cozy-system
|
||||
releaseName: cozystack-platform
|
||||
name: cozystack-platform
|
||||
path: core/platform
|
||||
valuesFiles:
|
||||
- values.yaml
|
||||
- name: isp-full
|
||||
components:
|
||||
- install:
|
||||
namespace: cozy-system
|
||||
releaseName: cozystack-platform
|
||||
name: cozystack-platform
|
||||
path: core/platform
|
||||
valuesFiles:
|
||||
- values.yaml
|
||||
- values-isp-full.yaml
|
||||
- name: isp-hosted
|
||||
components:
|
||||
- install:
|
||||
namespace: cozy-system
|
||||
releaseName: cozystack-platform
|
||||
name: cozystack-platform
|
||||
path: core/platform
|
||||
valuesFiles:
|
||||
- values.yaml
|
||||
- values-isp-hosted.yaml
|
||||
@@ -1,79 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: cozy-system
|
||||
labels:
|
||||
cozystack.io/system: "true"
|
||||
pod-security.kubernetes.io/enforce: privileged
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: cozystack
|
||||
namespace: cozy-system
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: cozystack
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: cozystack
|
||||
namespace: cozy-system
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: cluster-admin
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: cozystack
|
||||
namespace: cozy-system
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cozystack
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 0
|
||||
maxUnavailable: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cozystack
|
||||
spec:
|
||||
hostNetwork: true
|
||||
serviceAccountName: cozystack
|
||||
containers:
|
||||
- name: cozystack
|
||||
image: "{{ .Values.cozystack.image }}"
|
||||
env:
|
||||
- name: KUBERNETES_SERVICE_HOST
|
||||
value: localhost
|
||||
- name: INSTALL_FLUX
|
||||
value: "true"
|
||||
- name: KUBERNETES_SERVICE_PORT
|
||||
value: "7445"
|
||||
- name: K8S_AWAIT_ELECTION_ENABLED
|
||||
value: "1"
|
||||
- name: K8S_AWAIT_ELECTION_NAME
|
||||
value: cozystack
|
||||
- name: K8S_AWAIT_ELECTION_LOCK_NAME
|
||||
value: cozystack
|
||||
- name: K8S_AWAIT_ELECTION_LOCK_NAMESPACE
|
||||
value: cozy-system
|
||||
- name: K8S_AWAIT_ELECTION_IDENTITY
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
tolerations:
|
||||
- key: "node.kubernetes.io/not-ready"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
- key: "node.cilium.io/agent-not-ready"
|
||||
operator: "Exists"
|
||||
effect: "NoSchedule"
|
||||
6
packages/core/installer/templates/crds.yaml
Normal file
6
packages/core/installer/templates/crds.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
{{/*
|
||||
{{- range $path, $_ := .Files.Glob "crds/*.yaml" }}
|
||||
---
|
||||
{{ $.Files.Get $path }}
|
||||
{{- end }}
|
||||
*/}}
|
||||
@@ -1,2 +1,6 @@
|
||||
cozystack:
|
||||
image: ghcr.io/cozystack/cozystack/installer:v0.38.2@sha256:9ff92b655de6f9bea3cba4cd42dcffabd9aace6966dcfb1cc02dda2420ea4a15
|
||||
cozystackOperator:
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-operator:latest@sha256:f7f6e0fd9e896b7bfa642d0bfa4378bc14e646bc5c2e86e2e09a82770ef33181
|
||||
platformSource: 'oci://ghcr.io/cozystack/cozystack/platform-packages@sha256:0576491291b33936cdf770a5c5b5692add97339c1505fc67a92df9d69dfbfdf6'
|
||||
cozystackVersion: latest
|
||||
|
||||
@@ -1,34 +1,30 @@
|
||||
NAME=platform
|
||||
NAME=cozystack-platform
|
||||
NAMESPACE=cozy-system
|
||||
|
||||
include ../../../scripts/common-envs.mk
|
||||
include ../../../scripts/package.mk
|
||||
|
||||
show:
|
||||
cozypkg show -n $(NAMESPACE) $(NAME) --plain
|
||||
image: image-migrations
|
||||
|
||||
apply:
|
||||
cozypkg show -n $(NAMESPACE) $(NAME) --plain | kubectl apply -f-
|
||||
kubectl delete helmreleases.helm.toolkit.fluxcd.io -l cozystack.io/marked-for-deletion=true -A
|
||||
|
||||
reconcile: apply
|
||||
|
||||
namespaces-show:
|
||||
cozypkg show -n $(NAMESPACE) $(NAME) --plain -s templates/namespaces.yaml
|
||||
|
||||
namespaces-apply:
|
||||
cozypkg show -n $(NAMESPACE) $(NAME) --plain -s templates/namespaces.yaml | kubectl apply -f-
|
||||
|
||||
diff:
|
||||
cozypkg show -n $(NAMESPACE) $(NAME) --plain | kubectl diff -f-
|
||||
|
||||
image: image-assets
|
||||
image-assets:
|
||||
docker buildx build -f images/cozystack-assets/Dockerfile ../../.. \
|
||||
--tag $(REGISTRY)/cozystack-assets:$(call settag,$(TAG)) \
|
||||
--cache-from type=registry,ref=$(REGISTRY)/cozystack-assets:latest \
|
||||
image-migrations:
|
||||
docker buildx build images/migrations \
|
||||
--tag $(REGISTRY)/platform-migrations:$(call settag,$(TAG)) \
|
||||
--cache-from type=registry,ref=$(REGISTRY)/platform-migrations:latest \
|
||||
--cache-to type=inline \
|
||||
--metadata-file images/cozystack-assets.json \
|
||||
--metadata-file images/migrations.json \
|
||||
$(BUILDX_ARGS)
|
||||
IMAGE="$(REGISTRY)/cozystack-assets:$(call settag,$(TAG))@$$(yq e '."containerimage.digest"' images/cozystack-assets.json -o json -r)" \
|
||||
yq -i '.assets.image = strenv(IMAGE)' values.yaml
|
||||
rm -f images/cozystack-assets.json
|
||||
IMAGE="$(REGISTRY)/platform-migrations:$(call settag,$(TAG))@$$(yq e '."containerimage.digest"' images/migrations.json -o json -r)" \
|
||||
yq -i '.migrations.image = strenv(IMAGE)' values.yaml
|
||||
rm -f images/migrations.json
|
||||
|
||||
generate:
|
||||
@echo "Generating target version from migrations..."
|
||||
@LAST_MIGRATION=$$(ls -1 images/migrations/migrations/ 2>/dev/null | grep -E '^[0-9]+$$' | sort -n | tail -1); \
|
||||
if [ -z "$$LAST_MIGRATION" ]; then \
|
||||
echo "Error: No migration files found" >&2; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
TARGET_VERSION=$$((LAST_MIGRATION + 1)); \
|
||||
echo "Last migration: $$LAST_MIGRATION, Target version: $$TARGET_VERSION"; \
|
||||
yq -i '.migrations.targetVersion = $$TARGET_VERSION' values.yaml; \
|
||||
echo "Updated targetVersion to $$TARGET_VERSION in values.yaml"
|
||||
|
||||
13
packages/core/platform/images/migrations/Dockerfile
Normal file
13
packages/core/platform/images/migrations/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM alpine:3.22
|
||||
|
||||
RUN wget -O- https://github.com/cozystack/cozypkg/raw/refs/heads/main/hack/install.sh | sh -s -- -v 1.4.0
|
||||
|
||||
RUN apk add --no-cache kubectl helm coreutils git jq ca-certificates bash curl
|
||||
|
||||
COPY migrations /migrations
|
||||
COPY run-migrations.sh /usr/bin/run-migrations.sh
|
||||
|
||||
WORKDIR /migrations
|
||||
|
||||
ENTRYPOINT ["/usr/bin/run-migrations.sh"]
|
||||
|
||||
25
packages/core/platform/images/migrations/migrations/1
Executable file
25
packages/core/platform/images/migrations/migrations/1
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
# Migration 1 --> 2
|
||||
|
||||
# Fix mariadb-operator secrets
|
||||
if kubectl get -n cozy-mariadb-operator secret/mariadb-operator-webhook-cert; then
|
||||
kubectl annotate -n cozy-mariadb-operator secret/mariadb-operator-webhook-cert meta.helm.sh/release-namespace=cozy-mariadb-operator meta.helm.sh/release-name=mariadb-operator
|
||||
kubectl label -n cozy-mariadb-operator secret/mariadb-operator-webhook-cert app.kubernetes.io/managed-by=Helm
|
||||
fi
|
||||
|
||||
# Gratefully remove fluxcd release and keep resources
|
||||
if kubectl get hr -n cozy-fluxcd cozy-fluxcd 2>/dev/null; then
|
||||
kubectl patch hr -n cozy-fluxcd cozy-fluxcd -p '{"spec": {"suspend": true}, "metadata": {"finalizers": null}}' --type=merge
|
||||
kubectl delete hr -n cozy-fluxcd cozy-fluxcd
|
||||
fi
|
||||
kubectl delete secret -n cozy-fluxcd -l name=fluxcd
|
||||
|
||||
# Fix kubeovn crds
|
||||
kubeovn_crds=$(kubectl get crd -o name | grep '\.kubeovn\.io$')
|
||||
if [ -n "$kubeovn_crds" ]; then
|
||||
kubectl annotate $kubeovn_crds meta.helm.sh/release-namespace=cozy-kubeovn meta.helm.sh/release-name=kubeovn
|
||||
kubectl label $kubeovn_crds app.kubernetes.io/managed-by=Helm
|
||||
fi
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=2 --dry-run=client -o yaml | kubectl apply -f-
|
||||
15
packages/core/platform/images/migrations/migrations/10
Executable file
15
packages/core/platform/images/migrations/migrations/10
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
# Migration 10 --> 11
|
||||
|
||||
# Force reconcile hr keycloak-configure
|
||||
if kubectl get helmrelease keycloak-configure -n cozy-keycloak; then
|
||||
kubectl delete po -l app=source-controller -n cozy-fluxcd
|
||||
timestamp=$(date --rfc-3339=ns)
|
||||
kubectl annotate helmrelease keycloak-configure -n cozy-keycloak \
|
||||
reconcile.fluxcd.io/forceAt="$timestamp" \
|
||||
reconcile.fluxcd.io/requestedAt="$timestamp" \
|
||||
--overwrite
|
||||
fi
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=11 --dry-run=client -o yaml | kubectl apply -f-
|
||||
21
packages/core/platform/images/migrations/migrations/11
Executable file
21
packages/core/platform/images/migrations/migrations/11
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# Migration 11 --> 12
|
||||
|
||||
# Recreate daemonset kube-rbac-proxy
|
||||
|
||||
if kubectl get daemonset kube-rbac-proxy -n cozy-monitoring; then
|
||||
kubectl delete daemonset kube-rbac-proxy --cascade=orphan -n cozy-monitoring
|
||||
fi
|
||||
|
||||
if kubectl get helmrelease monitoring-agents -n cozy-monitoring; then
|
||||
timestamp=$(date --rfc-3339=ns)
|
||||
kubectl annotate helmrelease monitoring-agents -n cozy-monitoring \
|
||||
reconcile.fluxcd.io/forceAt="$timestamp" \
|
||||
reconcile.fluxcd.io/requestedAt="$timestamp" \
|
||||
--overwrite
|
||||
fi
|
||||
|
||||
kubectl delete pods -l app.kubernetes.io/component=kube-rbac-proxy -n cozy-monitoring
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=12 --dry-run=client -o yaml | kubectl apply -f-
|
||||
35
packages/core/platform/images/migrations/migrations/12
Executable file
35
packages/core/platform/images/migrations/migrations/12
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
# Migration 12 --> 13
|
||||
|
||||
# Copy configuration from ingress to cozystack configmap
|
||||
if kubectl get hr -n tenant-root tenant-root > /dev/null; then
|
||||
expose_services=$(
|
||||
kubectl get hr -n tenant-root ingress -o go-template='{{ with .spec }}{{ with .values }}{{ if .dashboard }}dashboard,{{ end }}{{ if .cdiUploadProxy }}cdi-uploadproxy,{{ end }}{{ if .virtExportProxy }}vm-exportproxy,{{ end }}{{ end }}{{ end }}'
|
||||
)
|
||||
expose_services=$(echo "$expose_services" | awk '{sub(/,$/,""); print}')
|
||||
|
||||
expose_external_ips=$(
|
||||
kubectl get hr -n tenant-root ingress -o go-template='{{ with .spec }}{{ with .values }}{{ if .externalIPs }}{{ range .externalIPs }}{{ . }},{{ end }}{{ end }}{{ end }}{{ end }}'
|
||||
)
|
||||
expose_external_ips=$(echo "$expose_external_ips" | awk '{sub(/,$/,""); print}')
|
||||
|
||||
existing_expose_external_ips=$(kubectl get cm -n cozy-system cozystack -o go-template='{{ index .data "expose-external-ips" }}')
|
||||
existing_expose_services=$(kubectl get cm -n cozy-system cozystack -o go-template='{{ index .data "expose-services" }}')
|
||||
|
||||
if [ "$existing_expose_external_ips" == "<no value>" ]; then
|
||||
kubectl patch cm -n cozy-system cozystack --type merge -p="{\"data\":{\"expose-external-ips\":\"$expose_external_ips\"}}"
|
||||
fi
|
||||
|
||||
if [ "$existing_expose_services" == "<no value>" ]; then
|
||||
kubectl patch cm -n cozy-system cozystack --type merge -p="{\"data\":{\"expose-services\":\"$expose_services\"}}"
|
||||
fi
|
||||
|
||||
kubectl patch hr -n tenant-root ingress --type json -p='[{"op": "remove", "path": "/spec/values/dashboard"}]' || true
|
||||
kubectl patch hr -n tenant-root ingress --type json -p='[{"op": "remove", "path": "/spec/values/cdiUploadProxy"}]' || true
|
||||
kubectl patch hr -n tenant-root ingress --type json -p='[{"op": "remove", "path": "/spec/values/virtExportProxy"}]' || true
|
||||
kubectl patch hr -n tenant-root ingress --type json -p='[{"op": "remove", "path": "/spec/values/externalIPs"}]' || true
|
||||
kubectl patch hr -n tenant-root ingress --type merge -p='{"spec":{"chart":{"spec":{"version":"1.6.0"}}}}'
|
||||
fi
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=13 --dry-run=client -o yaml | kubectl apply -f-
|
||||
10
packages/core/platform/images/migrations/migrations/13
Executable file
10
packages/core/platform/images/migrations/migrations/13
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Migration 13 --> 14
|
||||
|
||||
# Upgrade tenants.apps to new chart version
|
||||
kubectl get tenants.apps.cozystack.io -A --no-headers --output=custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name' | while read NAMESPACE NAME; do
|
||||
kubectl patch tenants.apps.cozystack.io -n "$NAMESPACE" "$NAME" --type merge -p '{"appVersion":"1.10.0"}'
|
||||
done
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=14 --dry-run=client -o yaml | kubectl apply -f-
|
||||
10
packages/core/platform/images/migrations/migrations/14
Executable file
10
packages/core/platform/images/migrations/migrations/14
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Migration 14 --> 15
|
||||
|
||||
# Delete the `capi-providers` HelmRelease in the `cozy-cluster-api` namespace if present
|
||||
if kubectl get hr -n cozy-cluster-api capi-providers >/dev/null 2>&1; then
|
||||
kubectl delete hr -n cozy-cluster-api capi-providers
|
||||
fi
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=15 --dry-run=client -o yaml | kubectl apply -f-
|
||||
28
packages/core/platform/images/migrations/migrations/15
Executable file
28
packages/core/platform/images/migrations/migrations/15
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Migration 15 --> 16
|
||||
|
||||
if kubectl get validatingwebhookconfigurations.admissionregistration.k8s.io kamaji-validating-webhook-configuration; then
|
||||
kubectl delete validatingwebhookconfigurations.admissionregistration.k8s.io kamaji-validating-webhook-configuration
|
||||
fi
|
||||
kubectl get kamajicontrolplane -A -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,VERSION:.spec.version,VERSION:.status.version --no-headers |
|
||||
while read namespace name version status; do
|
||||
if [ "$status" = "v1.32.4" ]; then
|
||||
continue
|
||||
fi
|
||||
(set -x; kubectl patch kamajicontrolplane "$name" -n "$namespace" --type merge -p '{"spec":{"version":"1.32.4"}}')
|
||||
(set -x; kubectl patch kamajicontrolplane "$name" -n "$namespace" --type merge -p '{"status":{"version":"v1.32.4"}}' --subresource status)
|
||||
(set -x; kubectl patch tcp "$name" -n "$namespace" --type merge -p '{"spec":{"kubernetes":{"version":"1.32.4"}}}')
|
||||
(set -x; kubectl patch tcp "$name" -n "$namespace" --type merge -p '{"status":{"kubernetesResources":{"version":{"version":"v1.32.4"}}}} ' --subresource status)
|
||||
done
|
||||
|
||||
# Upgrade kubernetes.apps to new chart version
|
||||
kubectl get kuberneteses.apps.cozystack.io -A --no-headers --output=custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name' | while read NAMESPACE NAME; do
|
||||
kubectl patch kuberneteses.apps.cozystack.io -n "$NAMESPACE" "$NAME" --type merge -p '{"appVersion":"0.24.0"}'
|
||||
done
|
||||
|
||||
if kubectl get helmrelease kamaji -n cozy-kamaji; then
|
||||
cozypkg reconcile kamaji -n cozy-kamaji --force
|
||||
fi
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=16 --dry-run=client -o yaml | kubectl apply -f-
|
||||
95
packages/core/platform/images/migrations/migrations/16
Executable file
95
packages/core/platform/images/migrations/migrations/16
Executable file
@@ -0,0 +1,95 @@
|
||||
#!/bin/sh
|
||||
# Migration 16 --> 17
|
||||
#
|
||||
# fix-nested-resources-map.sh – prints kubectl patch commands.
|
||||
# * Replaces each resources section with {cpu,memory} merged from requests + limits
|
||||
# * Adds/Replaces .appVersion with "*"
|
||||
|
||||
set -e
|
||||
|
||||
CRDS='
|
||||
clickhouses.apps.cozystack.io
|
||||
etcds.apps.cozystack.io
|
||||
ferretdb.apps.cozystack.io
|
||||
httpcaches.apps.cozystack.io
|
||||
kafkas.apps.cozystack.io
|
||||
kuberneteses.apps.cozystack.io
|
||||
monitorings.apps.cozystack.io
|
||||
mysqls.apps.cozystack.io
|
||||
natses.apps.cozystack.io
|
||||
postgreses.apps.cozystack.io
|
||||
rabbitmqs.apps.cozystack.io
|
||||
redises.apps.cozystack.io
|
||||
seaweedfses.apps.cozystack.io
|
||||
tcpbalancers.apps.cozystack.io
|
||||
virtualmachines.apps.cozystack.io
|
||||
vminstances.apps.cozystack.io
|
||||
vpns.apps.cozystack.io
|
||||
'
|
||||
|
||||
for KIND in $CRDS; do
|
||||
kubectl get "$KIND" -A -o json | jq -r --arg kind "$KIND" '
|
||||
.items[]
|
||||
| . as $obj
|
||||
| ($obj.metadata.namespace // "") as $ns # namespace (empty string for cluster-scoped)
|
||||
| $obj.metadata.name as $name # object name
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Build an array with every JSON path ending with "resources"
|
||||
# -------------------------------------------------------------------------
|
||||
| [ $obj
|
||||
| paths
|
||||
| select(.[-1] == "resources")
|
||||
] as $rpaths
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Iterate through each resources path
|
||||
# -------------------------------------------------------------------------
|
||||
| foreach $rpaths[] as $rpath (null;
|
||||
# Current resources object
|
||||
($obj | getpath($rpath)) as $res
|
||||
|
||||
# requests + limits merged; requests override limits on key collision
|
||||
| ($res.requests? // {}) as $req
|
||||
| ($res.limits? // {}) as $lim
|
||||
| ($req + $lim) as $flat
|
||||
|
||||
# Keep cpu & memory only
|
||||
| ($flat
|
||||
| with_entries(select(.key|test("^(cpu|memory)$")))
|
||||
) as $value
|
||||
| select(($value|length) > 0) # skip if nothing to patch
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# RFC6901-encoded JSON Pointer to the resources section
|
||||
# ---------------------------------------------------------------------
|
||||
| ("/" + ($rpath
|
||||
| map(
|
||||
tostring
|
||||
| gsub("~";"~0")
|
||||
| gsub("/";"~1")
|
||||
)
|
||||
| join("/")
|
||||
)
|
||||
) as $pointer
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Compose JSON Patch: 1) add/replace appVersion, 2) replace resources
|
||||
# ---------------------------------------------------------------------
|
||||
| [
|
||||
{ op:"add", path:"/appVersion", value:"*" },
|
||||
{ op:"replace", path:$pointer, value:$value }
|
||||
] as $patch
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Print one ready-to-run kubectl patch command
|
||||
# ---------------------------------------------------------------------
|
||||
| "kubectl " +
|
||||
(if $ns != "" then "-n \($ns) " else "" end) +
|
||||
"patch \($kind) \($name) --type=json -p '\''\($patch|tojson)'\''"
|
||||
)
|
||||
' | sh -ex
|
||||
done
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=17 --dry-run=client -o yaml | kubectl apply -f-
|
||||
10
packages/core/platform/images/migrations/migrations/17
Executable file
10
packages/core/platform/images/migrations/migrations/17
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Migration 17 --> 18
|
||||
|
||||
# Upgrade kubernetes.apps to new chart version
|
||||
kubectl get kuberneteses.apps.cozystack.io -A --no-headers --output=custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name' | while read NAMESPACE NAME; do
|
||||
kubectl patch kuberneteses.apps.cozystack.io -n "$NAMESPACE" "$NAME" --type merge -p '{"appVersion":"0.26.1"}'
|
||||
done
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=18 --dry-run=client -o yaml | kubectl apply -f-
|
||||
18
packages/core/platform/images/migrations/migrations/18
Executable file
18
packages/core/platform/images/migrations/migrations/18
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
# Migration 18 --> 19
|
||||
|
||||
# Upgrade tenants.apps to new chart version
|
||||
kubectl get tenants.apps.cozystack.io -A --no-headers --output=custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name' | while read NAMESPACE NAME; do
|
||||
kubectl patch tenants.apps.cozystack.io -n "$NAMESPACE" "$NAME" --type merge -p '{"appVersion":"1.13.0"}'
|
||||
done
|
||||
|
||||
# Upgrade virtualmachines.apps to new chart version
|
||||
kubectl get virtualmachines.apps.cozystack.io -A --no-headers --output=custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name' | while read NAMESPACE NAME; do
|
||||
kubectl patch virtualmachines.apps.cozystack.io -n "$NAMESPACE" "$NAME" --type merge -p '{"appVersion":"0.14.0"}'
|
||||
done
|
||||
kubectl get vminstances.apps.cozystack.io -A --no-headers --output=custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name' | while read NAMESPACE NAME; do
|
||||
kubectl patch vminstances.apps.cozystack.io -n "$NAMESPACE" "$NAME" --type merge -p '{"appVersion":"0.12.0"}'
|
||||
done
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=19 --dry-run=client -o yaml | kubectl apply -f-
|
||||
53
packages/core/platform/images/migrations/migrations/19
Executable file
53
packages/core/platform/images/migrations/migrations/19
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/sh
|
||||
# Migration 19 --> 20
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
kubectl get helmreleases.helm.toolkit.fluxcd.io -A \
|
||||
--field-selector=metadata.name=seaweedfs -o json | jq -r '
|
||||
.items[] as $o
|
||||
| ($o.metadata.namespace // "") as $ns
|
||||
| $o.metadata.name as $name
|
||||
| $o.spec as $s
|
||||
| $o.spec.values as $v
|
||||
|
||||
| (
|
||||
($s.chart.spec.version? // "") != "0.7.0"
|
||||
or ($v.size? != null)
|
||||
or ($v.storageClass? != null)
|
||||
or ($v.replicas? != null)
|
||||
or ($v.zones? != null)
|
||||
) as $needsChange
|
||||
| select($needsChange)
|
||||
|
||||
# JSON Patch
|
||||
| [
|
||||
(if $s.chart.spec.version? then
|
||||
{op:"replace", path:"/spec/chart/spec/version", value:"0.7.0"}
|
||||
else
|
||||
{op:"add", path:"/spec/chart/spec/version", value:"0.7.0"}
|
||||
end),
|
||||
|
||||
(if ($v|type) != "object" then {op:"add", path:"/spec/values", value:{}} else empty end),
|
||||
|
||||
(if ($v.volume?|type) != "object" then {op:"add", path:"/spec/values/volume", value:{}} else empty end),
|
||||
|
||||
(if $v.size? then {op:"add", path:"/spec/values/volume/size", value:$v.size} else empty end),
|
||||
(if $v.storageClass? then {op:"add", path:"/spec/values/volume/storageClass", value:$v.storageClass} else empty end),
|
||||
(if $v.replicas? then {op:"add", path:"/spec/values/volume/replicas", value:$v.replicas} else empty end),
|
||||
(if $v.zones? then {op:"add", path:"/spec/values/volume/zones", value:$v.zones} else empty end),
|
||||
|
||||
(if $v.size? then {op:"remove", path:"/spec/values/size"} else empty end),
|
||||
(if $v.storageClass? then {op:"remove", path:"/spec/values/storageClass"} else empty end),
|
||||
(if $v.replicas? then {op:"remove", path:"/spec/values/replicas"} else empty end),
|
||||
(if $v.zones? then {op:"remove", path:"/spec/values/zones"} else empty end)
|
||||
] as $patch
|
||||
|
||||
| "kubectl " +
|
||||
(if $ns != "" then "-n \($ns) " else "" end) +
|
||||
"patch helmreleases.helm.toolkit.fluxcd.io \($name) --type=json -p '\''\($patch|tojson)'\''"
|
||||
' | sh -ex
|
||||
|
||||
# Stamp version
|
||||
kubectl create configmap -n cozy-system cozystack-version \
|
||||
--from-literal=version=20 --dry-run=client -o yaml | kubectl apply -f-
|
||||
16
packages/core/platform/images/migrations/migrations/2
Executable file
16
packages/core/platform/images/migrations/migrations/2
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# Migration 2 --> 3
|
||||
|
||||
if [ -d packages/system ]; then
|
||||
kubectl apply -f packages/system/mariadb-operator/charts/mariadb-operator/crds/crds.yaml --server-side --force-conflicts
|
||||
fi
|
||||
|
||||
# Fix mariadb-operator crds
|
||||
mariadb_crds=$(kubectl get crd -o name | grep '\.k8s\.mariadb\.com$')
|
||||
if [ -n "$mariadb_crds" ]; then
|
||||
kubectl annotate $mariadb_crds meta.helm.sh/release-namespace=cozy-mariadb-operator meta.helm.sh/release-name=mariadb-operator
|
||||
kubectl label $mariadb_crds app.kubernetes.io/managed-by=Helm
|
||||
fi
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=3 --dry-run=client -o yaml | kubectl apply -f-
|
||||
56
packages/core/platform/images/migrations/migrations/20
Executable file
56
packages/core/platform/images/migrations/migrations/20
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/bin/sh
|
||||
# Migration 20 --> 21
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
kubectl -n cozy-system delete helmrelease cozystack-api \
|
||||
|| kubectl -n cozy-system delete deployment cozystack-api --ignore-not-found # fallback to help migration progress if it failed first time
|
||||
while [ $( kubectl -n cozy-system get po -l app=cozystack-api --no-headers | wc -l ) != 0 ] ;
|
||||
do
|
||||
echo "Waiting for Cozystack API pods to be deleted";
|
||||
sleep 1
|
||||
done
|
||||
|
||||
kubectl -n cozy-system delete helmrelease cozystack-controller \
|
||||
|| kubectl -n cozy-system delete deployment cozystack-controller --ignore-not-found # same fallback
|
||||
kubectl delete customresourcedefinitions.apiextensions.k8s.io cozystackresourcedefinitions.cozystack.io --ignore-not-found
|
||||
while [ $( kubectl -n cozy-system get po -l app=cozystack-controller --no-headers | wc -l ) != 0 ] ;
|
||||
do
|
||||
echo "Waiting for Cozystack controller pods to be deleted";
|
||||
sleep 1
|
||||
done
|
||||
|
||||
kubectl delete helmrelease -n cozy-dashboard dashboard --ignore-not-found
|
||||
sleep 5
|
||||
if [ -d "packages/system" ]; then
|
||||
cozypkg -n cozy-system -C packages/system/cozystack-resource-definition-crd apply cozystack-resource-definition-crd --plain
|
||||
cozypkg -n cozy-system -C packages/system/cozystack-resource-definitions apply cozystack-resource-definitions --plain
|
||||
cozypkg -n cozy-system -C packages/system/cozystack-api apply cozystack-api --plain
|
||||
cozypkg -n cozy-system -C packages/system/cozystack-controller/ apply cozystack-api --plain --take-ownership
|
||||
cozypkg -n cozy-system -C packages/system/lineage-controller-webhook/ apply cozystack-api --plain --take-ownership
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
echo "Running lineage-webhook test"
|
||||
kubectl delete ns cozy-lineage-webhook-test --ignore-not-found && kubectl create ns cozy-lineage-webhook-test
|
||||
cleanup_test_ns() {
|
||||
kubectl delete ns cozy-lineage-webhook-test --ignore-not-found
|
||||
}
|
||||
trap cleanup_test_ns ERR
|
||||
timeout 60 sh -c 'until kubectl -n cozy-lineage-webhook-test create service clusterip lineage-webhook-test --clusterip="None" --dry-run=server; do sleep 1; done'
|
||||
cleanup_test_ns
|
||||
|
||||
timestamp=$(date --rfc-3339=ns || date)
|
||||
kubectl get namespace -o custom-columns=NAME:.metadata.name --no-headers |
|
||||
grep '^tenant-' |
|
||||
while read namespace ; do
|
||||
(set -x; \
|
||||
kubectl annotate \
|
||||
pods,services,pvc,secrets,ingresses.networking.k8s.io,workloadmonitors.cozystack.io \
|
||||
-n "$namespace" --all \
|
||||
migration.cozystack.io="$timestamp" --overwrite || true)
|
||||
done
|
||||
|
||||
# Stamp version
|
||||
kubectl create configmap -n cozy-system cozystack-version \
|
||||
--from-literal=version=21 --dry-run=client -o yaml | kubectl apply -f-
|
||||
21
packages/core/platform/images/migrations/migrations/21
Executable file
21
packages/core/platform/images/migrations/migrations/21
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# Migration 21 --> 22
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Delete old cozystack deployment
|
||||
kubectl -n cozy-system delete deployment cozystack --ignore-not-found
|
||||
while [ $( kubectl -n cozy-system get po -l app=cozystack --no-headers 2>/dev/null | wc -l ) != 0 ] ;
|
||||
do
|
||||
echo "Waiting for old cozystack deployment pods to be deleted";
|
||||
sleep 1
|
||||
done
|
||||
|
||||
kubectl delete hr -n cozy-system cozystack-resource-definitions --ignore-not-found
|
||||
kubectl delete hr -n cozy-system cozystack-resource-definition-crd --ignore-not-found
|
||||
kubectl delete crd cozystackresourcedefinitions.cozycloud.io --ignore-not-found
|
||||
kubectl delete hr -n cozy-fluxcd fluxcd --ignore-not-found
|
||||
|
||||
# Stamp version
|
||||
kubectl create configmap -n cozy-system cozystack-version \
|
||||
--from-literal=version=22 --dry-run=client -o yaml | kubectl apply -f-
|
||||
161
packages/core/platform/images/migrations/migrations/22
Executable file
161
packages/core/platform/images/migrations/migrations/22
Executable file
@@ -0,0 +1,161 @@
|
||||
#!/bin/sh
|
||||
# Migration 22 --> 23
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "Migrating HelmReleases: adding application labels for tenant-* namespaces"
|
||||
|
||||
# Function to determine application type from HelmRelease name
|
||||
determine_app_type() {
|
||||
local name="$1"
|
||||
local app_kind=""
|
||||
local app_name=""
|
||||
|
||||
# Try to match by prefix (longest match first)
|
||||
case "$name" in
|
||||
virtual-machine-*)
|
||||
app_kind="VirtualMachine"
|
||||
app_name="${name#virtual-machine-}"
|
||||
;;
|
||||
vm-instance-*)
|
||||
app_kind="VMInstance"
|
||||
app_name="${name#vm-instance-}"
|
||||
;;
|
||||
vm-disk-*)
|
||||
app_kind="VMDisk"
|
||||
app_name="${name#vm-disk-}"
|
||||
;;
|
||||
virtualprivatecloud-*)
|
||||
app_kind="VirtualPrivateCloud"
|
||||
app_name="${name#virtualprivatecloud-}"
|
||||
;;
|
||||
http-cache-*)
|
||||
app_kind="HTTPCache"
|
||||
app_name="${name#http-cache-}"
|
||||
;;
|
||||
tcp-balancer-*)
|
||||
app_kind="TCPBalancer"
|
||||
app_name="${name#tcp-balancer-}"
|
||||
;;
|
||||
clickhouse-*)
|
||||
app_kind="ClickHouse"
|
||||
app_name="${name#clickhouse-}"
|
||||
;;
|
||||
foundationdb-*)
|
||||
app_kind="FoundationDB"
|
||||
app_name="${name#foundationdb-}"
|
||||
;;
|
||||
ferretdb-*)
|
||||
app_kind="FerretDB"
|
||||
app_name="${name#ferretdb-}"
|
||||
;;
|
||||
rabbitmq-*)
|
||||
app_kind="RabbitMQ"
|
||||
app_name="${name#rabbitmq-}"
|
||||
;;
|
||||
kubernetes-*)
|
||||
app_kind="Kubernetes"
|
||||
app_name="${name#kubernetes-}"
|
||||
;;
|
||||
bucket-*)
|
||||
app_kind="Bucket"
|
||||
app_name="${name#bucket-}"
|
||||
;;
|
||||
kafka-*)
|
||||
app_kind="Kafka"
|
||||
app_name="${name#kafka-}"
|
||||
;;
|
||||
mysql-*)
|
||||
app_kind="MySQL"
|
||||
app_name="${name#mysql-}"
|
||||
;;
|
||||
nats-*)
|
||||
app_kind="NATS"
|
||||
app_name="${name#nats-}"
|
||||
;;
|
||||
postgres-*)
|
||||
app_kind="PostgreSQL"
|
||||
app_name="${name#postgres-}"
|
||||
;;
|
||||
redis-*)
|
||||
app_kind="Redis"
|
||||
app_name="${name#redis-}"
|
||||
;;
|
||||
tenant-*)
|
||||
app_kind="Tenant"
|
||||
app_name="${name#tenant-}"
|
||||
;;
|
||||
vpn-*)
|
||||
app_kind="VPN"
|
||||
app_name="${name#vpn-}"
|
||||
;;
|
||||
bootbox)
|
||||
app_kind="BootBox"
|
||||
app_name="bootbox"
|
||||
;;
|
||||
etcd)
|
||||
app_kind="Etcd"
|
||||
app_name="etcd"
|
||||
;;
|
||||
info)
|
||||
app_kind="Info"
|
||||
app_name="info"
|
||||
;;
|
||||
ingress|ingress-*)
|
||||
app_kind="Ingress"
|
||||
if [ "$name" = "ingress" ]; then
|
||||
app_name="ingress"
|
||||
else
|
||||
app_name="${name#ingress-}"
|
||||
fi
|
||||
;;
|
||||
monitoring)
|
||||
app_kind="Monitoring"
|
||||
app_name="monitoring"
|
||||
;;
|
||||
seaweedfs)
|
||||
app_kind="SeaweedFS"
|
||||
app_name="seaweedfs"
|
||||
;;
|
||||
*)
|
||||
# Unknown type
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "$app_kind|$app_name"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Process all HelmReleases in tenant-* namespaces with cozystack.io/ui=true label
|
||||
kubectl get helmreleases --all-namespaces -l cozystack.io/ui=true -o json | \
|
||||
jq -r '.items[] | select(.metadata.namespace | startswith("tenant-")) | "\(.metadata.namespace)|\(.metadata.name)"' | \
|
||||
while IFS='|' read -r namespace name; do
|
||||
echo "Processing HelmRelease $namespace/$name"
|
||||
|
||||
# Determine application type
|
||||
app_type=$(determine_app_type "$name")
|
||||
if [ $? -ne 0 ] || [ -z "$app_type" ]; then
|
||||
echo "Warning: Could not determine application type for $namespace/$name, skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
app_kind=$(echo "$app_type" | cut -d'|' -f1)
|
||||
app_name=$(echo "$app_type" | cut -d'|' -f2)
|
||||
app_group="apps.cozystack.io"
|
||||
|
||||
# Build labels string
|
||||
labels="apps.cozystack.io/application.kind=$app_kind"
|
||||
labels="$labels apps.cozystack.io/application.group=$app_group"
|
||||
labels="$labels apps.cozystack.io/application.name=$app_name"
|
||||
|
||||
# Apply labels using kubectl label --overwrite
|
||||
kubectl label helmrelease -n "$namespace" "$name" --overwrite $labels
|
||||
echo "Added application labels to $namespace/$name: $labels"
|
||||
done
|
||||
|
||||
echo "Migration completed"
|
||||
|
||||
# Stamp version
|
||||
kubectl create configmap -n cozy-system cozystack-version \
|
||||
--from-literal=version=23 --dry-run=client -o yaml | kubectl apply -f-
|
||||
12
packages/core/platform/images/migrations/migrations/3
Executable file
12
packages/core/platform/images/migrations/migrations/3
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
# Migration 3 --> 4
|
||||
|
||||
# Fix kubeovn crds
|
||||
kubeovn_crds=$(kubectl get crd -o name | grep '\.kubeovn\.io$')
|
||||
if [ -n "$kubeovn_crds" ]; then
|
||||
kubectl annotate $kubeovn_crds meta.helm.sh/release-namespace=cozy-kubeovn meta.helm.sh/release-name=kubeovn
|
||||
kubectl label $kubeovn_crds app.kubernetes.io/managed-by=Helm
|
||||
fi
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=4 --dry-run=client -o yaml | kubectl apply -f-
|
||||
15
packages/core/platform/images/migrations/migrations/4
Executable file
15
packages/core/platform/images/migrations/migrations/4
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
# Migration 4 --> 5
|
||||
|
||||
# Fix tenant-kubernetes PVCs
|
||||
kubectl get secret -l kamaji.clastix.io/project=kamaji,kamaji.clastix.io/component=admin-kubeconfig -A --output=go-template='{{ range .items }}{{ printf "%s %s %s %s\n" .metadata.namespace .metadata.name (index .metadata.labels "kamaji.clastix.io/name") (index .data "super-admin.conf") }}{{ end }}' | while read NAMESPACE NAME CLUSTER CONFIGB64; do
|
||||
config=$(mktemp)
|
||||
echo "$CONFIGB64" | base64 -d > "$config"
|
||||
kubectl get pv --kubeconfig="$config" --output=go-template='{{ range .items }}{{ printf "%s\n" .metadata.name }}{{ end }}' | while read PVC; do
|
||||
(set -x; kubectl label pvc --overwrite -n "$NAMESPACE" "$PVC" "cluster.x-k8s.io/cluster-name=$CLUSTER")
|
||||
done
|
||||
rm -f "$config"
|
||||
done
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=5 --dry-run=client -o yaml | kubectl apply -f-
|
||||
15
packages/core/platform/images/migrations/migrations/5
Executable file
15
packages/core/platform/images/migrations/migrations/5
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
# Migration 5 --> 6
|
||||
|
||||
# Fix tenant-kubernetes PVCs
|
||||
kubectl get secret -l kamaji.clastix.io/project=kamaji,kamaji.clastix.io/component=admin-kubeconfig -A --output=go-template='{{ range .items }}{{ printf "%s %s %s %s\n" .metadata.namespace .metadata.name (index .metadata.labels "kamaji.clastix.io/name") (index .data "super-admin.conf") }}{{ end }}' | while read NAMESPACE NAME CLUSTER CONFIGB64; do
|
||||
config=$(mktemp)
|
||||
echo "$CONFIGB64" | base64 -d > "$config"
|
||||
kubectl get pv --kubeconfig="$config" --output=go-template='{{ range .items }}{{ printf "%s\n" .metadata.name }}{{ end }}' | while read PVC; do
|
||||
(set -x; kubectl label pvc --overwrite -n "$NAMESPACE" "$PVC" "cluster.x-k8s.io/cluster-name=$CLUSTER")
|
||||
(set -x; kubectl label dv --overwrite -n "$NAMESPACE" "$PVC" "cluster.x-k8s.io/cluster-name=$CLUSTER")
|
||||
done
|
||||
rm -f "$config"
|
||||
done
|
||||
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=6 --dry-run=client -o yaml | kubectl apply -f-
|
||||
16
packages/core/platform/images/migrations/migrations/6
Executable file
16
packages/core/platform/images/migrations/migrations/6
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# Migration 6 --> 7
|
||||
|
||||
# Delete cert-manager crds labels and annotations
|
||||
kubectl patch hr -n cozy-cert-manager cert-manager -p '{"spec": {"suspend": true}}' --type=merge --field-manager=flux-client-side-apply
|
||||
certmanager_crds=$(kubectl get crd -o name | grep '\.cert-manager\.io$')
|
||||
if [ -n "$certmanager_crds" ]; then
|
||||
kubectl annotate $certmanager_crds meta.helm.sh/release-namespace=cozy-cert-manager meta.helm.sh/release-name=cert-manager-crds
|
||||
kubectl label $certmanager_crds app.kubernetes.io/managed-by=Helm
|
||||
fi
|
||||
|
||||
# Remove monitoring, because it is renamed to monitoring-agents
|
||||
kubectl get hr -n cozy-monitoring monitoring && kubectl delete hr -n cozy-monitoring monitoring --wait=0
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=7 --dry-run=client -o yaml | kubectl apply -f-
|
||||
8
packages/core/platform/images/migrations/migrations/7
Executable file
8
packages/core/platform/images/migrations/migrations/7
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
# Migration 7 --> 8
|
||||
|
||||
host=$(kubectl get hr tenant-root -n tenant-root -o yaml | grep 'host:' | awk '{print $2}')
|
||||
kubectl patch configmap -n cozy-system cozystack --type merge -p "{\"data\":{\"root-host\":\"$host\"}}"
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=8 --dry-run=client -o yaml | kubectl apply -f-
|
||||
9
packages/core/platform/images/migrations/migrations/8
Executable file
9
packages/core/platform/images/migrations/migrations/8
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
# Migration 8 --> 9
|
||||
|
||||
if kubectl get clusterrolebinding kubeapps-admin-group; then
|
||||
kubectl delete clusterrolebinding kubeapps-admin-group
|
||||
fi
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=9 --dry-run=client -o yaml | kubectl apply -f-
|
||||
10
packages/core/platform/images/migrations/migrations/9
Executable file
10
packages/core/platform/images/migrations/migrations/9
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# Migration 9 --> 10
|
||||
|
||||
# Upgrade kubernetes.apps to new chart version
|
||||
kubectl get kuberneteses.apps.cozystack.io -A --no-headers --output=custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name' | while read NAMESPACE NAME; do
|
||||
kubectl patch kuberneteses.apps.cozystack.io -n "$NAMESPACE" "$NAME" --type merge -p '{"appVersion":"0.15.1"}'
|
||||
done
|
||||
|
||||
# Write version to cozystack-version config
|
||||
kubectl create configmap -n cozy-system cozystack-version --from-literal=version=10 --dry-run=client -o yaml | kubectl apply -f-
|
||||
42
packages/core/platform/images/migrations/run-migrations.sh
Executable file
42
packages/core/platform/images/migrations/run-migrations.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
set -euo pipefail
|
||||
|
||||
NAMESPACE="${NAMESPACE:-cozy-system}"
|
||||
CURRENT_VERSION="${CURRENT_VERSION:-0}"
|
||||
TARGET_VERSION="${TARGET_VERSION:-0}"
|
||||
|
||||
echo "Starting migrations from version $CURRENT_VERSION to $TARGET_VERSION"
|
||||
|
||||
# Check if ConfigMap exists
|
||||
if ! kubectl get configmap -n "$NAMESPACE" cozystack-version >/dev/null 2>&1; then
|
||||
echo "ConfigMap cozystack-version does not exist, creating it with version $TARGET_VERSION"
|
||||
kubectl create configmap -n "$NAMESPACE" cozystack-version \
|
||||
--from-literal=version="$TARGET_VERSION" \
|
||||
--dry-run=client -o yaml | kubectl apply -f-
|
||||
echo "ConfigMap created with version $TARGET_VERSION"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# If current version is already at target, nothing to do
|
||||
if [ "$CURRENT_VERSION" -ge "$TARGET_VERSION" ]; then
|
||||
echo "Current version $CURRENT_VERSION is already at or above target version $TARGET_VERSION"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Run migrations sequentially from current version to target version
|
||||
for i in $(seq $((CURRENT_VERSION + 1)) $TARGET_VERSION); do
|
||||
if [ -f "/migrations/$i" ]; then
|
||||
echo "Running migration $i"
|
||||
chmod +x /migrations/$i
|
||||
/migrations/$i || {
|
||||
echo "Migration $i failed"
|
||||
exit 1
|
||||
}
|
||||
echo "Migration $i completed successfully"
|
||||
else
|
||||
echo "Migration $i not found, skipping"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All migrations completed successfully"
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
{{/*
|
||||
Get IP-addresses of master nodes
|
||||
*/}}
|
||||
{{- define "cozystack.master-node-ips" -}}
|
||||
{{- $nodes := lookup "v1" "Node" "" "" -}}
|
||||
{{- $ips := list -}}
|
||||
{{- range $node := $nodes.items -}}
|
||||
{{- if eq (index $node.metadata.labels "node-role.kubernetes.io/control-plane") "" -}}
|
||||
{{- range $address := $node.status.addresses -}}
|
||||
{{- if eq $address.type "InternalIP" -}}
|
||||
{{- $ips = append $ips $address.address -}}
|
||||
{{- break -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{ join "," $ips }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Get Kubernetes API Endpoint from cozystack deployment
|
||||
Returns host:port format
|
||||
*/}}
|
||||
{{- define "cozystack.kubernetesAPIEndpoint" -}}
|
||||
{{- $cozyDeployment := lookup "apps/v1" "Deployment" "cozy-system" "cozystack" }}
|
||||
{{- $cozyContainers := dig "spec" "template" "spec" "containers" list $cozyDeployment }}
|
||||
{{- $kubernetesServiceHost := "" }}
|
||||
{{- $kubernetesServicePort := "" }}
|
||||
{{- range $cozyContainers }}
|
||||
{{- if eq .name "cozystack" }}
|
||||
{{- range .env }}
|
||||
{{- if eq .name "KUBERNETES_SERVICE_HOST" }}
|
||||
{{- $kubernetesServiceHost = .value }}
|
||||
{{- end }}
|
||||
{{- if eq .name "KUBERNETES_SERVICE_PORT" }}
|
||||
{{- $kubernetesServicePort = .value }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if eq $kubernetesServiceHost "" }}
|
||||
{{- $kubernetesServiceHost = "kubernetes.default.svc" }}
|
||||
{{- end }}
|
||||
{{- if eq $kubernetesServicePort "" }}
|
||||
{{- $kubernetesServicePort = "443" }}
|
||||
{{- end }}
|
||||
{{- printf "%s:%s" $kubernetesServiceHost $kubernetesServicePort }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "cozystack.defaultDashboardValues" -}}
|
||||
kubeapps:
|
||||
{{- if .Capabilities.APIVersions.Has "source.toolkit.fluxcd.io/v1" }}
|
||||
{{- with (lookup "source.toolkit.fluxcd.io/v1" "HelmRepository" "cozy-public" "").items }}
|
||||
redis:
|
||||
master:
|
||||
podAnnotations:
|
||||
{{- range $index, $repo := . }}
|
||||
{{- with (($repo.status).artifact).revision }}
|
||||
repository.cozystack.io/{{ $repo.metadata.name }}: {{ quote . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
frontend:
|
||||
resourcesPreset: "none"
|
||||
dashboard:
|
||||
resourcesPreset: "none"
|
||||
{{- $cozystackBranding:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }}
|
||||
{{- $branding := dig "data" "branding" "" $cozystackBranding }}
|
||||
{{- if $branding }}
|
||||
customLocale:
|
||||
"Kubeapps": {{ $branding }}
|
||||
{{- end }}
|
||||
customStyle: |
|
||||
{{- $logoImage := dig "data" "logo" "" $cozystackBranding }}
|
||||
{{- if $logoImage }}
|
||||
.kubeapps-logo {
|
||||
background-image: {{ $logoImage }}
|
||||
}
|
||||
{{- end }}
|
||||
#serviceaccount-selector {
|
||||
display: none;
|
||||
}
|
||||
.login-moreinfo {
|
||||
display: none;
|
||||
}
|
||||
a[href="#/docs"] {
|
||||
display: none;
|
||||
}
|
||||
.login-group .clr-form-control .clr-control-label {
|
||||
display: none;
|
||||
}
|
||||
.appview-separator div.appview-first-row div.center {
|
||||
display: none;
|
||||
}
|
||||
.appview-separator div.appview-first-row section[aria-labelledby="app-secrets"] {
|
||||
display: none;
|
||||
}
|
||||
.appview-first-row section[aria-labelledby="access-urls-title"] {
|
||||
width: 100%;
|
||||
}
|
||||
.header-version {
|
||||
display: none;
|
||||
}
|
||||
.label.label-info-secondary {
|
||||
display: none;
|
||||
}
|
||||
{{- end }}
|
||||
@@ -1,64 +0,0 @@
|
||||
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
|
||||
{{- $bundleName := index $cozyConfig.data "bundle-name" }}
|
||||
{{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }}
|
||||
{{- $host := "example.org" }}
|
||||
{{- $host := "example.org" }}
|
||||
{{- if $cozyConfig.data }}
|
||||
{{- if hasKey $cozyConfig.data "root-host" }}
|
||||
{{- $host = index $cozyConfig.data "root-host" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- $tenantRoot := dict }}
|
||||
{{- if .Capabilities.APIVersions.Has "helm.toolkit.fluxcd.io/v2" }}
|
||||
{{- $tenantRoot = lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" "tenant-root" "tenant-root" }}
|
||||
{{- end }}
|
||||
{{- if and $tenantRoot $tenantRoot.spec $tenantRoot.spec.values $tenantRoot.spec.values.host }}
|
||||
{{- $host = $tenantRoot.spec.values.host }}
|
||||
{{- else }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
namespace.cozystack.io/etcd: tenant-root
|
||||
namespace.cozystack.io/monitoring: tenant-root
|
||||
namespace.cozystack.io/ingress: tenant-root
|
||||
namespace.cozystack.io/seaweedfs: tenant-root
|
||||
namespace.cozystack.io/host: "{{ $host }}"
|
||||
name: tenant-root
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: tenant-root
|
||||
namespace: tenant-root
|
||||
labels:
|
||||
cozystack.io/ui: "true"
|
||||
spec:
|
||||
interval: 0s
|
||||
releaseName: tenant-root
|
||||
install:
|
||||
remediation:
|
||||
retries: -1
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: -1
|
||||
chart:
|
||||
spec:
|
||||
chart: tenant
|
||||
version: '>= 0.0.0-0'
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: cozystack-apps
|
||||
namespace: cozy-public
|
||||
values:
|
||||
host: "{{ $host }}"
|
||||
dependsOn:
|
||||
{{- range $x := $bundle.releases }}
|
||||
{{- if has $x.name (list "cilium" "kubeovn") }}
|
||||
- name: {{ $x.name }}
|
||||
namespace: {{ $x.namespace }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
23
packages/core/platform/templates/config.yaml
Normal file
23
packages/core/platform/templates/config.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cozystack
|
||||
namespace: cozy-system
|
||||
data:
|
||||
root-host: {{ .Values.publishing.host | quote }}
|
||||
api-server-endpoint: {{ .Values.publishing.apiServerEndpoint | quote }}
|
||||
expose-services: {{ (join "," .Values.publishing.exposedServices) | quote }}
|
||||
expose-ingress: {{ .Values.publishing.ingressName | quote }}
|
||||
expose-external-ips: {{ (join "," .Values.publishing.externalIPs) | quote }}
|
||||
cluster-domain: {{ .Values.networking.clusterDomain | quote }}
|
||||
ipv4-pod-cidr: {{ .Values.networking.podCIDR | quote }}
|
||||
ipv4-pod-gateway: {{ .Values.networking.podGateway | quote }}
|
||||
ipv4-svc-cidr: {{ .Values.networking.serviceCIDR | quote }}
|
||||
ipv4-join-cidr: {{ .Values.networking.joinCIDR | quote }}
|
||||
clusterissuer: {{ .Values.publishing.certificates.issuerType | quote }}
|
||||
oidc-enabled: {{ .Values.authentication.oidc.enabled | toString | quote }}
|
||||
extra-keycloak-redirect-uri-for-dashboard: {{ (join "," .Values.authentication.oidc.dashboard.extraRedirectUris) | quote }}
|
||||
cpu-allocation-ratio: {{ .Values.resources.cpuAllocationRatio | toString | quote }}
|
||||
memory-allocation-ratio: {{ .Values.resources.memoryAllocationRatio | toString | quote }}
|
||||
ephemeral-storage-allocation-ratio: {{ .Values.resources.ephemeralStorageAllocationRatio | toString | quote }}
|
||||
management-kubeconfig-endpoint: {{ .Values.publishing.apiServerEndpoint | quote }}
|
||||
@@ -0,0 +1,35 @@
|
||||
{{- if .Values.registries.mirrors }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: patch-containerd
|
||||
namespace: cozy-system
|
||||
type: Opaque
|
||||
stringData:
|
||||
{{- range $registry, $mirror := .Values.registries.mirrors }}
|
||||
{{- if $mirror.endpoints }}
|
||||
{{ $registry }}.toml: |
|
||||
server = "https://{{ $registry }}"
|
||||
{{- range $endpoint := $mirror.endpoints }}
|
||||
[host."{{ $endpoint }}"]
|
||||
capabilities = ["pull", "resolve"]
|
||||
{{- $endpointConfig := index $.Values.registries.config $endpoint }}
|
||||
{{- if $endpointConfig }}
|
||||
{{- if $endpointConfig.tls }}
|
||||
{{- if $endpointConfig.tls.insecureSkipVerify }}
|
||||
skip_verify = true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if $endpointConfig.auth }}
|
||||
[host."{{ $endpoint }}".auth]
|
||||
username = "{{ $endpointConfig.auth.username }}"
|
||||
password = "{{ $endpointConfig.auth.password }}"
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
skip_verify = true
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: cozystack-assets
|
||||
namespace: cozy-system
|
||||
labels:
|
||||
app: cozystack-assets
|
||||
spec:
|
||||
serviceName: cozystack-assets
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: cozystack-assets
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: cozystack-assets
|
||||
spec:
|
||||
hostNetwork: true
|
||||
containers:
|
||||
- name: assets-server
|
||||
image: "{{ .Values.assets.image }}"
|
||||
args:
|
||||
- "-dir=/cozystack/assets"
|
||||
- "-address=:8123"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8123
|
||||
hostPort: 8123
|
||||
tolerations:
|
||||
- operator: Exists
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: cozystack-assets-reader
|
||||
namespace: cozy-system
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources:
|
||||
- pods/proxy
|
||||
resourceNames:
|
||||
- cozystack-assets-0
|
||||
verbs:
|
||||
- get
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: cozystack-assets-reader
|
||||
namespace: cozy-system
|
||||
subjects:
|
||||
- kind: User
|
||||
name: cozystack-assets-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: cozystack-assets-reader
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: cozystack-assets
|
||||
namespace: cozy-system
|
||||
spec:
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8123
|
||||
selector:
|
||||
app: cozystack-assets
|
||||
type: ClusterIP
|
||||
@@ -1,97 +0,0 @@
|
||||
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
|
||||
{{- $bundleName := index $cozyConfig.data "bundle-name" }}
|
||||
{{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }}
|
||||
{{- $dependencyNamespaces := dict }}
|
||||
{{- $disabledComponents := splitList "," ((index $cozyConfig.data "bundle-disable") | default "") }}
|
||||
{{- $enabledComponents := splitList "," ((index $cozyConfig.data "bundle-enable") | default "") }}
|
||||
{{- $oidcEnabled := (index (default dict $cozyConfig.data) "oidc-enabled") | default "false" | eq "true" }}
|
||||
|
||||
{{/* collect dependency namespaces from releases */}}
|
||||
{{- range $x := $bundle.releases }}
|
||||
{{- $_ := set $dependencyNamespaces $x.name $x.namespace }}
|
||||
{{- end }}
|
||||
|
||||
{{- range $x := $bundle.releases }}
|
||||
|
||||
{{- $shouldInstall := true }}
|
||||
{{- $shouldDelete := false }}
|
||||
{{- $notEnabledOptionalComponent := and ($x.optional) (not (has $x.name $enabledComponents)) }}
|
||||
{{- $disabledComponent := has $x.name $disabledComponents }}
|
||||
{{- $isKeycloakComponent := or (eq $x.name "keycloak") (eq $x.name "keycloak-operator") (eq $x.name "keycloak-configure") }}
|
||||
|
||||
{{- if and $isKeycloakComponent (not $oidcEnabled) }}
|
||||
{{- $shouldInstall = false }}
|
||||
{{- if $.Capabilities.APIVersions.Has "helm.toolkit.fluxcd.io/v2" }}
|
||||
{{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" $x.namespace $x.name }}
|
||||
{{- $shouldDelete = true }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if or $disabledComponent $notEnabledOptionalComponent }}
|
||||
{{- $shouldInstall = false }}
|
||||
{{- if $.Capabilities.APIVersions.Has "helm.toolkit.fluxcd.io/v2" }}
|
||||
{{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" $x.namespace $x.name }}
|
||||
{{- $shouldDelete = true }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- if or $shouldInstall $shouldDelete }}
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: {{ $x.name }}
|
||||
namespace: {{ $x.namespace }}
|
||||
labels:
|
||||
cozystack.io/repository: system
|
||||
cozystack.io/system-app: "true"
|
||||
{{- if $shouldDelete }}
|
||||
cozystack.io/marked-for-deletion: "true"
|
||||
{{- end }}
|
||||
spec:
|
||||
interval: 5m
|
||||
releaseName: {{ $x.releaseName | default $x.name }}
|
||||
install:
|
||||
crds: CreateReplace
|
||||
remediation:
|
||||
retries: -1
|
||||
upgrade:
|
||||
crds: CreateReplace
|
||||
remediation:
|
||||
retries: -1
|
||||
chart:
|
||||
spec:
|
||||
chart: {{ $x.chart }}
|
||||
reconcileStrategy: Revision
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: cozystack-system
|
||||
namespace: cozy-system
|
||||
version: '>= 0.0.0-0'
|
||||
{{- with $x.valuesFiles }}
|
||||
valuesFiles:
|
||||
{{- toYaml $x.valuesFiles | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- $values := dict }}
|
||||
{{- with $x.values }}
|
||||
{{- $values = merge . $values }}
|
||||
{{- end }}
|
||||
{{- with index $cozyConfig.data (printf "values-%s" $x.name) }}
|
||||
{{- $values = mergeOverwrite $values (fromYaml .) }}
|
||||
{{- end }}
|
||||
{{- with $values }}
|
||||
values:
|
||||
{{- toYaml . | nindent 4}}
|
||||
{{- end }}
|
||||
|
||||
{{- with $x.dependsOn }}
|
||||
dependsOn:
|
||||
{{- range $dep := . }}
|
||||
{{- if not (has $dep $disabledComponents) }}
|
||||
- name: {{ $dep }}
|
||||
namespace: {{ index $dependencyNamespaces $dep }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: cozystack-system
|
||||
namespace: cozy-system
|
||||
labels:
|
||||
cozystack.io/repository: system
|
||||
spec:
|
||||
interval: 5m0s
|
||||
url: https://{{ include "cozystack.kubernetesAPIEndpoint" . }}/api/v1/namespaces/cozy-system/pods/cozystack-assets-0/proxy/repos/system
|
||||
certSecretRef:
|
||||
name: cozystack-assets-tls
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: cozystack-apps
|
||||
namespace: cozy-public
|
||||
labels:
|
||||
cozystack.io/ui: "true"
|
||||
cozystack.io/repository: apps
|
||||
spec:
|
||||
interval: 5m0s
|
||||
url: https://{{ include "cozystack.kubernetesAPIEndpoint" . }}/api/v1/namespaces/cozy-system/pods/cozystack-assets-0/proxy/repos/apps
|
||||
certSecretRef:
|
||||
name: cozystack-assets-tls
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: cozystack-extra
|
||||
namespace: cozy-public
|
||||
labels:
|
||||
cozystack.io/repository: extra
|
||||
spec:
|
||||
interval: 5m0s
|
||||
url: https://{{ include "cozystack.kubernetesAPIEndpoint" . }}/api/v1/namespaces/cozy-system/pods/cozystack-assets-0/proxy/repos/extra
|
||||
certSecretRef:
|
||||
name: cozystack-assets-tls
|
||||
68
packages/core/platform/templates/migration-hook.yaml
Normal file
68
packages/core/platform/templates/migration-hook.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
{{- $shouldRunMigrationHook := false }}
|
||||
{{- $currentVersion := 0 }}
|
||||
{{- $targetVersion := .Values.migrations.targetVersion | int }}
|
||||
{{- $configMap := lookup "v1" "ConfigMap" .Release.Namespace "cozystack-version" }}
|
||||
{{- if $configMap }}
|
||||
{{- $currentVersion = dig "data" "version" "0" $configMap | int }}
|
||||
{{- if lt $currentVersion $targetVersion }}
|
||||
{{- $shouldRunMigrationHook = true }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- if $shouldRunMigrationHook }}
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: cozystack-migration-hook
|
||||
annotations:
|
||||
helm.sh/hook: pre-upgrade
|
||||
helm.sh/hook-weight: "1"
|
||||
helm.sh/hook-delete-policy: before-hook-creation
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
policy.cozystack.io/allow-to-apiserver: "true"
|
||||
spec:
|
||||
serviceAccountName: cozystack-migration-hook
|
||||
containers:
|
||||
- name: migration
|
||||
image: {{ .Values.migrations.image }}
|
||||
env:
|
||||
- name: NAMESPACE
|
||||
value: {{ .Release.Namespace | quote }}
|
||||
- name: CURRENT_VERSION
|
||||
value: {{ $currentVersion | quote }}
|
||||
- name: TARGET_VERSION
|
||||
value: {{ $targetVersion | quote }}
|
||||
restartPolicy: Never
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
helm.sh/hook: pre-upgrade
|
||||
helm.sh/hook-weight: "1"
|
||||
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation
|
||||
name: cozystack-migration-hook
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: cluster-admin
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: cozystack-migration-hook
|
||||
namespace: {{ .Release.Namespace | quote }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: cozystack-migration-hook
|
||||
annotations:
|
||||
helm.sh/hook: pre-upgrade
|
||||
helm.sh/hook-weight: "1"
|
||||
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation
|
||||
{{- end }}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
|
||||
{{- $bundleName := index $cozyConfig.data "bundle-name" }}
|
||||
{{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }}
|
||||
{{- $disabledComponents := splitList "," ((index $cozyConfig.data "bundle-disable") | default "") }}
|
||||
{{- $enabledComponents := splitList "," ((index $cozyConfig.data "bundle-enable") | default "") }}
|
||||
{{- $namespaces := dict }}
|
||||
|
||||
{{/* collect namespaces from releases */}}
|
||||
{{- range $x := $bundle.releases }}
|
||||
{{- if not (hasKey $namespaces $x.namespace) }}
|
||||
{{- if not (has $x.name $disabledComponents) }}
|
||||
{{- if or (not $x.optional) (and ($x.optional) (has $x.name $enabledComponents)) }}
|
||||
{{- $_ := set $namespaces $x.namespace false }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{/* if at least one release requires a privileged namespace, then it should be privileged */}}
|
||||
{{- if or $x.privileged (index $namespaces $x.namespace) }}
|
||||
{{- $_ := set $namespaces $x.namespace true }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/* Add extra namespaces */}}
|
||||
{{- $_ := set $namespaces "cozy-system" true }}
|
||||
{{- $_ := set $namespaces "cozy-public" false }}
|
||||
|
||||
{{- range $namespace, $privileged := $namespaces }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
annotations:
|
||||
"helm.sh/resource-policy": keep
|
||||
labels:
|
||||
cozystack.io/system: "true"
|
||||
{{- if $privileged }}
|
||||
pod-security.kubernetes.io/enforce: privileged
|
||||
{{- end }}
|
||||
name: {{ $namespace }}
|
||||
{{- end }}
|
||||
215
packages/core/platform/templates/packages/isp-full.yaml
Normal file
215
packages/core/platform/templates/packages/isp-full.yaml
Normal file
@@ -0,0 +1,215 @@
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.networking
|
||||
spec:
|
||||
variant: kubeovn-cilium
|
||||
components:
|
||||
kubeovn:
|
||||
values:
|
||||
kube-ovn:
|
||||
ipv4:
|
||||
POD_CIDR: "{{ .Values.networking.podCIDR }}"
|
||||
POD_GATEWAY: "{{ .Values.networking.podGateway }}"
|
||||
SVC_CIDR: "{{ .Values.networking.serviceCIDR }}"
|
||||
JOIN_CIDR: "{{ .Values.networking.joinCIDR }}"
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.cozystack-engine
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.cert-manager
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.kubeovn-webhook
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.kubeovn-plunger
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.victoria-metrics-operator
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.cozy-proxy
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.multus
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.tenant-application
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.ingress-application
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.seaweedfs-application
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.info-application
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.monitoring-application
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.etcd-application
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.cozystack-basics
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.backup-controller
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.velero
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.vertical-pod-autoscaler
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.metallb
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.gpu-operator
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.reloader
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.linstor
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.monitoring-agents
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.goldpinger
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.snapshot-controller
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
---
|
||||
apiVersion: cozystack.io/v1alpha1
|
||||
kind: Package
|
||||
metadata:
|
||||
name: cozystack.prometheus-operator-crds
|
||||
spec:
|
||||
variant: default
|
||||
components: {}
|
||||
17
packages/core/platform/templates/repository.yaml
Normal file
17
packages/core/platform/templates/repository.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
{{- $sourceRef := .Values.sourceRef }}
|
||||
{{- $apiVersion := "source.toolkit.fluxcd.io/v1" }}
|
||||
{{- $kind := $sourceRef.kind }}
|
||||
{{- $name := $sourceRef.name }}
|
||||
{{- $namespace := $sourceRef.namespace }}
|
||||
{{- $sourceRepo := lookup $apiVersion $kind $namespace $name }}
|
||||
{{- if not $sourceRepo }}
|
||||
{{- fail (printf "Source repository %s/%s of kind %s not found in namespace %s" $namespace $name $kind $namespace) }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: {{ $apiVersion }}
|
||||
kind: {{ $kind }}
|
||||
metadata:
|
||||
name: cozystack-packages
|
||||
namespace: cozy-system
|
||||
spec:
|
||||
{{- $sourceRepo.spec | toYaml | nindent 2 }}
|
||||
11
packages/core/platform/templates/scheduling.yaml
Normal file
11
packages/core/platform/templates/scheduling.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
{{- if .Values.scheduling.globalAppTopologySpreadConstraints }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cozystack-scheduling
|
||||
namespace: cozy-system
|
||||
data:
|
||||
globalAppTopologySpreadConstraints: |
|
||||
{{- toYaml .Values.scheduling.globalAppTopologySpreadConstraints | nindent 4 }}
|
||||
{{- end }}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user