diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 08ee2e13..118211df 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ package v1alpha1 import ( "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -137,11 +138,49 @@ func (in *CozystackResourceDefinitionRelease) DeepCopy() *CozystackResourceDefin return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CozystackResourceDefinitionSecrets) DeepCopyInto(out *CozystackResourceDefinitionSecrets) { + *out = *in + if in.Exclude != nil { + in, out := &in.Exclude, &out.Exclude + *out = make([]*v1.LabelSelector, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + } + } + if in.Include != nil { + in, out := &in.Include, &out.Include + *out = make([]*v1.LabelSelector, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionSecrets. +func (in *CozystackResourceDefinitionSecrets) DeepCopy() *CozystackResourceDefinitionSecrets { + if in == nil { + return nil + } + out := new(CozystackResourceDefinitionSecrets) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CozystackResourceDefinitionSpec) DeepCopyInto(out *CozystackResourceDefinitionSpec) { *out = *in out.Application = in.Application in.Release.DeepCopyInto(&out.Release) + in.Secrets.DeepCopyInto(&out.Secrets) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionSpec. diff --git a/cmd/cozystack-controller/main.go b/cmd/cozystack-controller/main.go index 40220b85..82f6cc15 100644 --- a/cmd/cozystack-controller/main.go +++ b/cmd/cozystack-controller/main.go @@ -38,6 +38,7 @@ import ( cozystackiov1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" "github.com/cozystack/cozystack/internal/controller" + lcw "github.com/cozystack/cozystack/internal/lineagecontrollerwebhook" "github.com/cozystack/cozystack/internal/telemetry" helmv2 "github.com/fluxcd/helm-controller/api/v2" @@ -214,6 +215,20 @@ func main() { os.Exit(1) } + // special one that's both a webhook and a reconciler + lineageControllerWebhook := &lcw.LineageControllerWebhook{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + } + if err := lineageControllerWebhook.SetupWithManagerAsController(mgr); err != nil { + setupLog.Error(err, "unable to setup controller", "controller", "LineageController") + os.Exit(1) + } + if err := lineageControllerWebhook.SetupWithManagerAsWebhook(mgr); err != nil { + setupLog.Error(err, "unable to setup webhook", "webhook", "LineageWebhook") + os.Exit(1) + } + // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/internal/lineagecontrollerwebhook/config.go b/internal/lineagecontrollerwebhook/config.go new file mode 100644 index 00000000..57fc4ad9 --- /dev/null +++ b/internal/lineagecontrollerwebhook/config.go @@ -0,0 +1,44 @@ +package lineagecontrollerwebhook + +import ( + "fmt" + + cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" + helmv2 "github.com/fluxcd/helm-controller/api/v2" +) + +type chartRef struct { + repo string + chart string +} + +type appRef struct { + group string + kind string +} + +type runtimeConfig struct { + chartAppMap map[chartRef]*cozyv1alpha1.CozystackResourceDefinition + appCRDMap map[appRef]*cozyv1alpha1.CozystackResourceDefinition +} + +func (l *LineageControllerWebhook) initConfig() { + l.initOnce.Do(func() { + if l.config.Load() == nil { + l.config.Store(&runtimeConfig{chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition)}) + } + }) +} + +func (l *LineageControllerWebhook) Map(hr *helmv2.HelmRelease) (string, string, string, error) { + cfg, ok := l.config.Load().(*runtimeConfig) + if !ok { + return "", "", "", fmt.Errorf("failed to load chart-app mapping from config") + } + 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 +} diff --git a/internal/lineagecontrollerwebhook/controller.go b/internal/lineagecontrollerwebhook/controller.go new file mode 100644 index 00000000..7a1eb1d0 --- /dev/null +++ b/internal/lineagecontrollerwebhook/controller.go @@ -0,0 +1,55 @@ +package lineagecontrollerwebhook + +import ( + "context" + + cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// +kubebuilder:rbac:groups=cozystack.io,resources=cozystackresourcedefinitions,verbs=list;watch + +func (c *LineageControllerWebhook) SetupWithManagerAsController(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&cozyv1alpha1.CozystackResourceDefinition{}). + Complete(c) +} + +func (c *LineageControllerWebhook) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + l := log.FromContext(ctx) + crds := &cozyv1alpha1.CozystackResourceDefinitionList{} + if err := c.List(ctx, crds, &client.ListOptions{Namespace: "cozy-system"}); err != nil { + l.Error(err, "failed reading CozystackResourceDefinitions") + return ctrl.Result{}, err + } + cfg := &runtimeConfig{ + chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition), + appCRDMap: make(map[appRef]*cozyv1alpha1.CozystackResourceDefinition), + } + for _, crd := range crds.Items { + chRef := chartRef{ + crd.Spec.Release.Chart.SourceRef.Name, + crd.Spec.Release.Chart.Name, + } + appRef := appRef{ + "apps.cozystack.io", + crd.Spec.Application.Kind, + } + + newRef := crd + if _, exists := cfg.chartAppMap[chRef]; exists { + l.Info("duplicate chart mapping detected; ignoring subsequent entry", "key", chRef) + } else { + cfg.chartAppMap[chRef] = &newRef + } + if _, exists := cfg.appCRDMap[appRef]; exists { + l.Info("duplicate app mapping detected; ignoring subsequent entry", "key", appRef) + } else { + cfg.appCRDMap[appRef] = &newRef + } + } + c.config.Store(cfg) + return ctrl.Result{}, nil +} diff --git a/internal/lineagecontrollerwebhook/matcher.go b/internal/lineagecontrollerwebhook/matcher.go new file mode 100644 index 00000000..ff0da8b4 --- /dev/null +++ b/internal/lineagecontrollerwebhook/matcher.go @@ -0,0 +1,34 @@ +package lineagecontrollerwebhook + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" +) + +func matchLabelsToSelector(l map[string]string, s *metav1.LabelSelector) bool { + // TODO: emit warning if error + sel, err := metav1.LabelSelectorAsSelector(s) + if err != nil { + return false + } + return sel.Matches(labels.Set(l)) +} + +func matchLabelsToSelectorArray(l map[string]string, ss []*metav1.LabelSelector) bool { + for _, s := range ss { + if matchLabelsToSelector(l, s) { + return true + } + } + return false +} + +func matchLabelsToExcludeInclude(l map[string]string, ex, in []*metav1.LabelSelector) bool { + if matchLabelsToSelectorArray(l, ex) { + return false + } + if matchLabelsToSelectorArray(l, in) { + return true + } + return false +} diff --git a/internal/lineagecontrollerwebhook/types.go b/internal/lineagecontrollerwebhook/types.go new file mode 100644 index 00000000..f423ae95 --- /dev/null +++ b/internal/lineagecontrollerwebhook/types.go @@ -0,0 +1,23 @@ +package lineagecontrollerwebhook + +import ( + "sync" + "sync/atomic" + + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/dynamic" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// +kubebuilder:webhook:path=/mutate-lineage,mutating=true,failurePolicy=Fail,sideEffects=None,groups="",resources=pods,secrets,services,persistentvolumeclaims,verbs=create;update,versions=v1,name=mlineage.cozystack.io,admissionReviewVersions={v1} +type LineageControllerWebhook struct { + client.Client + Scheme *runtime.Scheme + decoder admission.Decoder + dynClient dynamic.Interface + mapper meta.RESTMapper + config atomic.Value + initOnce sync.Once +} diff --git a/internal/lineagecontrollerwebhook/webhook.go b/internal/lineagecontrollerwebhook/webhook.go new file mode 100644 index 00000000..ae4d1ea1 --- /dev/null +++ b/internal/lineagecontrollerwebhook/webhook.go @@ -0,0 +1,180 @@ +package lineagecontrollerwebhook + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/cozystack/cozystack/pkg/lineage" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/memory" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +var ( + NoAncestors = fmt.Errorf("no managed apps found in lineage") + AncestryAmbiguous = fmt.Errorf("object ancestry is ambiguous") +) + +// SetupWithManager registers the handler with the webhook server. +func (h *LineageControllerWebhook) SetupWithManagerAsWebhook(mgr ctrl.Manager) error { + cfg := rest.CopyConfig(mgr.GetConfig()) + + var err error + h.dynClient, err = dynamic.NewForConfig(cfg) + if err != nil { + return err + } + + discoClient, err := discovery.NewDiscoveryClientForConfig(cfg) + if err != nil { + return err + } + + cachedDisco := memory.NewMemCacheClient(discoClient) + h.mapper = restmapper.NewDeferredDiscoveryRESTMapper(cachedDisco) + + h.initConfig() + // Register HTTP path -> handler. + mgr.GetWebhookServer().Register("/mutate-lineage", &admission.Webhook{Handler: h}) + + return nil +} + +// InjectDecoder lets controller-runtime give us a decoder for AdmissionReview requests. +func (h *LineageControllerWebhook) InjectDecoder(d admission.Decoder) error { + h.decoder = d + return nil +} + +// Handle is called for each AdmissionReview that matches the webhook config. +func (h *LineageControllerWebhook) Handle(ctx context.Context, req admission.Request) admission.Response { + logger := log.FromContext(ctx).WithValues( + "gvk", req.Kind.String(), + "namespace", req.Namespace, + "name", req.Name, + "operation", req.Operation, + ) + warn := make(admission.Warnings, 0) + + obj := &unstructured.Unstructured{} + if err := h.decodeUnstructured(req, obj); err != nil { + return admission.Errored(400, fmt.Errorf("decode object: %w", err)) + } + + labels, err := h.computeLabels(ctx, obj) + for { + if err != nil && errors.Is(err, NoAncestors) { + return admission.Allowed("object not managed by app") + } + if err != nil && errors.Is(err, AncestryAmbiguous) { + warn = append(warn, "object ancestry ambiguous, using first ancestor found") + break + } + if err != nil { + logger.Error(err, "error computing lineage labels") + return admission.Errored(500, fmt.Errorf("error computing lineage labels: %w", err)) + } + if err == nil { + break + } + } + + h.applyLabels(obj, labels) + + mutated, err := json.Marshal(obj) + if err != nil { + return admission.Errored(500, fmt.Errorf("marshal mutated pod: %w", err)) + } + logger.V(1).Info("mutated pod", "namespace", obj.GetNamespace(), "name", obj.GetName()) + return admission.PatchResponseFromRaw(req.Object.Raw, mutated).WithWarnings(warn...) +} + +func (h *LineageControllerWebhook) computeLabels(ctx context.Context, o *unstructured.Unstructured) (map[string]string, error) { + owners := lineage.WalkOwnershipGraph(ctx, h.dynClient, h.mapper, h, o) + if len(owners) == 0 { + return nil, NoAncestors + } + obj, err := owners[0].GetUnstructured(ctx, h.dynClient, h.mapper) + if err != nil { + return nil, err + } + gv, err := schema.ParseGroupVersion(obj.GetAPIVersion()) + if err != nil { + // should never happen, we got an APIVersion right from the API + return nil, fmt.Errorf("could not parse APIVersion %s to a group and version: %w", obj.GetAPIVersion(), err) + } + if len(owners) > 1 { + err = AncestryAmbiguous + } + labels := map[string]string{ + // truncate apigroup to first 63 chars + "apps.cozystack.io/application.group": func(s string) string { + if len(s) < 63 { + return s + } + s = s[:63] + for b := s[62]; !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')); s = s[:len(s)-1] { + b = s[len(s)-1] + } + return s + }(gv.Group), + "apps.cozystack.io/application.kind": obj.GetKind(), + "apps.cozystack.io/application.name": obj.GetName(), + } + if o.GetAPIVersion() != "v1" || o.GetKind() != "Secret" { + return labels, err + } + cfg := h.config.Load().(*runtimeConfig) + crd := cfg.appCRDMap[appRef{gv.Group, obj.GetKind()}] + if matchLabelsToExcludeInclude(o.GetLabels(), crd.Spec.Secrets.Exclude, crd.Spec.Secrets.Include) { + labels["internal.cozystack.io/tenantsecret"] = "" + } + return labels, err +} + +func (h *LineageControllerWebhook) applyLabels(o *unstructured.Unstructured, labels map[string]string) { + if o.GetAPIVersion() == "operator.victoriametrics.com/v1beta1" && o.GetKind() == "VMCluster" { + unstructured.SetNestedStringMap(o.Object, labels, "spec", "managedMetadata", "labels") + return + } + + existing := o.GetLabels() + if existing == nil { + existing = make(map[string]string) + } + for k, v := range labels { + existing[k] = v + } + o.SetLabels(existing) +} + +func (h *LineageControllerWebhook) decodeUnstructured(req admission.Request, out *unstructured.Unstructured) error { + if h.decoder != nil { + if err := h.decoder.Decode(req, out); err == nil { + return nil + } + if req.Kind.Group != "" || req.Kind.Kind != "" || req.Kind.Version != "" { + out.SetGroupVersionKind(schema.GroupVersionKind{ + Group: req.Kind.Group, + Version: req.Kind.Version, + Kind: req.Kind.Kind, + }) + if err := h.decoder.Decode(req, out); err == nil { + return nil + } + } + } + if len(req.Object.Raw) == 0 { + return errors.New("empty admission object") + } + return json.Unmarshal(req.Object.Raw, &out.Object) +} diff --git a/packages/apps/clickhouse/images/clickhouse-backup.tag b/packages/apps/clickhouse/images/clickhouse-backup.tag index be1dc65f..7a6ada58 100644 --- a/packages/apps/clickhouse/images/clickhouse-backup.tag +++ b/packages/apps/clickhouse/images/clickhouse-backup.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/clickhouse-backup:0.13.0@sha256:3faf7a4cebf390b9053763107482de175aa0fdb88c1e77424fd81100b1c3a205 +ghcr.io/cozystack/cozystack/clickhouse-backup:latest@sha256:0f8707d348e03bfa7589159a61d781d4eca238bdfff5dc09f4d3a01b31285a55 diff --git a/packages/apps/http-cache/images/nginx-cache.tag b/packages/apps/http-cache/images/nginx-cache.tag index 81b33d31..9ded14c2 100644 --- a/packages/apps/http-cache/images/nginx-cache.tag +++ b/packages/apps/http-cache/images/nginx-cache.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/nginx-cache:0.7.0@sha256:50ac1581e3100bd6c477a71161cb455a341ffaf9e5e2f6086802e4e25271e8af +ghcr.io/cozystack/cozystack/nginx-cache:latest@sha256:a4ab68c6930263b80f7d0c75f9c87ff9725a835db3f23c6a655993ec4feed49c diff --git a/packages/apps/kubernetes/images/cluster-autoscaler.tag b/packages/apps/kubernetes/images/cluster-autoscaler.tag index 10aa5fd7..8117b889 100644 --- a/packages/apps/kubernetes/images/cluster-autoscaler.tag +++ b/packages/apps/kubernetes/images/cluster-autoscaler.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/cluster-autoscaler:0.29.1@sha256:2d39989846c3579dd020b9f6c77e6e314cc81aa344eaac0f6d633e723c17196d +ghcr.io/cozystack/cozystack/cluster-autoscaler:latest@sha256:89f822343654ea66efb3ac50bf72b483a52c1a11d33497fdfac5bbd0f3715c2b diff --git a/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag b/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag index c848c0a2..06b8300e 100644 --- a/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag +++ b/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:0.29.1@sha256:5335c044313b69ee13b30ca4941687e509005e55f4ae25723861edbf2fbd6dd2 +ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:latest@sha256:190b7d231da1dfbded3af77777cc99b93a29a36ea69186170460f09d533fe041 diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index ab468898..fd813a86 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag @@ -1 +1 @@ -kklinch0/kubevirt-csi-driver:0.37.0@sha256:d334ba727f0974b085f6e44ee52a61fa0c507063875b52006665dbacfa332cbd +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:latest@sha256:35684db82ef7d5ad0cb39fa2568aa09cf60c50f3307099dbb89521aefad5baf6 diff --git a/packages/apps/kubernetes/images/ubuntu-container-disk.tag b/packages/apps/kubernetes/images/ubuntu-container-disk.tag index 50416e0a..2c9d99ea 100644 --- a/packages/apps/kubernetes/images/ubuntu-container-disk.tag +++ b/packages/apps/kubernetes/images/ubuntu-container-disk.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.32@sha256:e53f2394c7aa76ad10818ffb945e40006cd77406999e47e036d41b8b0bf094cc +ghcr.io/cozystack/cozystack/ubuntu-container-disk:latest@sha256:ac91fb70d6a898c8a305522020dbbd1bfa6d073a76e5d696a74307487de47dc5 diff --git a/packages/apps/mysql/images/mariadb-backup.tag b/packages/apps/mysql/images/mariadb-backup.tag index 1a2e7dce..aa279946 100644 --- a/packages/apps/mysql/images/mariadb-backup.tag +++ b/packages/apps/mysql/images/mariadb-backup.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/mariadb-backup:0.10.0@sha256:a3789db9e9e065ff60cbac70771b4a8aa1460db3194307cf5ca5d4fe1b412b6b +ghcr.io/cozystack/cozystack/mariadb-backup:latest@sha256:abfc43aed08fbbeed8f090e90158108fe59ed6279db93d169c3b1b1656af0064 diff --git a/packages/apps/postgres/templates/db.yaml b/packages/apps/postgres/templates/db.yaml index 516077bd..de4f51a3 100644 --- a/packages/apps/postgres/templates/db.yaml +++ b/packages/apps/postgres/templates/db.yaml @@ -78,7 +78,7 @@ spec: labels: policy.cozystack.io/allow-to-apiserver: "true" app.kubernetes.io/name: postgres.apps.cozystack.io - app.kubernets.io/instance: {{ $.Release.Name }} + app.kubernetes.io/instance: {{ $.Release.Name }} --- apiVersion: cozystack.io/v1alpha1 kind: WorkloadMonitor @@ -91,5 +91,5 @@ spec: type: postgres selector: app.kubernetes.io/name: postgres.apps.cozystack.io - app.kubernets.io/instance: {{ $.Release.Name }} + app.kubernetes.io/instance: {{ $.Release.Name }} version: {{ $.Chart.Version }} diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 62eb3a5a..671a1523 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,2 +1,2 @@ cozystack: - image: ghcr.io/cozystack/cozystack/installer:v0.36.1@sha256:1579855349bef729209e8668b96dbb03e0fc80b74bcae2c25f3ed5f3ae6d2f7f + image: ghcr.io/cozystack/cozystack/installer:latest@sha256:d11c9b065535ef8ca9513d0181419ecb8934f36c92ce254f963fc301c7a3d30c diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index f0465bae..6228c44c 100755 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.36.1@sha256:150efd626321c9389415da5779504be4f10e70beafaeb1b7c162b08b3d50b51f + image: ghcr.io/cozystack/cozystack/e2e-sandbox:latest@sha256:1524ca94acb2ff0fa500ca6125eb980e6ac3f4105ae11deb16513ca8519bc7c6 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index ef5e743d..340850ce 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v0.36.1@sha256:ecf30f70d9a4b708f68fab52ba3a2ecc0787bb2e79906d76b770bb51f8d6ad6c +ghcr.io/cozystack/cozystack/matchbox:latest@sha256:1923a1c9db94f5dd867e36f6de1db2e55512491dbd9c95ad4671e01eaef4dcf5 diff --git a/packages/extra/monitoring/images/grafana.tag b/packages/extra/monitoring/images/grafana.tag index c411a05c..fff84feb 100644 --- a/packages/extra/monitoring/images/grafana.tag +++ b/packages/extra/monitoring/images/grafana.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/grafana:1.13.1@sha256:c63978e1ed0304e8518b31ddee56c4e8115541b997d8efbe1c0a74da57140399 +ghcr.io/cozystack/cozystack/grafana:latest@sha256:cc1843297f2dadb1740ebc4dfdc3dbe5b4ac840d1da37ed6fe93549d3000196c diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index dabec87c..3cfcc303 100644 --- a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag +++ b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.36.1@sha256:890c6f38d22fa8cba423d086686bd55c20b3d0c27881cf4b7a7801f1b0685112 +ghcr.io/cozystack/cozystack/objectstorage-sidecar:latest@sha256:325ab962acb6b546e1fc0f597c86af25100c7c2cb570affaad04954eb6dee449 diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index 896d33dc..5c20a503 100644 --- a/packages/system/bucket/images/s3manager.tag +++ b/packages/system/bucket/images/s3manager.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:899ea667b3e575244d512cade23f30cc93d768b070f9c2bebcb440e443444bdb +ghcr.io/cozystack/cozystack/s3manager:latest@sha256:377bc82c8404ae1ea201d8b9a64015952edfa06d88fb2678b7bf7c45b96fb968 diff --git a/packages/system/cilium/values.yaml b/packages/system/cilium/values.yaml index 25ed972f..db845e6f 100644 --- a/packages/system/cilium/values.yaml +++ b/packages/system/cilium/values.yaml @@ -14,7 +14,7 @@ cilium: mode: "kubernetes" image: repository: ghcr.io/cozystack/cozystack/cilium - tag: 1.17.5 + tag: latest digest: "sha256:2def2dccfc17870be6e1d63584c25b32e812f21c9cdcfa06deadd2787606654d" envoy: enabled: false diff --git a/packages/system/cozystack-api/templates/cozystack-resource-definitions.yaml b/packages/system/cozystack-api/templates/cozystack-resource-definitions.yaml index 90d3f629..4526532f 100644 --- a/packages/system/cozystack-api/templates/cozystack-resource-definitions.yaml +++ b/packages/system/cozystack-api/templates/cozystack-resource-definitions.yaml @@ -19,6 +19,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -41,6 +46,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -63,6 +73,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -85,6 +100,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -107,6 +127,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -129,6 +154,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -151,6 +181,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -173,6 +208,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -195,6 +235,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -217,6 +262,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -239,6 +289,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -261,6 +316,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -283,6 +343,13 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + - matchLabels: + cnpg.io/userType: superuser + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -305,6 +372,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -327,6 +399,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -349,6 +426,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -371,6 +453,11 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -393,6 +480,11 @@ spec: kind: HelmRepository name: cozystack-extra namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -415,6 +507,11 @@ spec: kind: HelmRepository name: cozystack-extra namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -437,6 +534,11 @@ spec: kind: HelmRepository name: cozystack-extra namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -459,6 +561,11 @@ spec: kind: HelmRepository name: cozystack-extra namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -481,6 +588,11 @@ spec: kind: HelmRepository name: cozystack-extra namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] --- apiVersion: cozystack.io/v1alpha1 kind: CozystackResourceDefinition @@ -503,3 +615,8 @@ spec: kind: HelmRepository name: cozystack-extra namespace: cozy-public + secrets: + exclude: + - matchLabels: + apps.cozystack.io/tenantresource: "false" + include: [{}] diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index f38bafb2..a7f9b23d 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,2 +1,2 @@ cozystackAPI: - image: ghcr.io/cozystack/cozystack/cozystack-api:v0.36.1@sha256:a9ce8848b0a46e52ce47ad10fcccc4e848740f5cf1d4e6cb78fc4a196e167e1b + image: ghcr.io/cozystack/cozystack/cozystack-api:latest@sha256:be6aca4df4b769538454d6c65b7045b2bef81b2a15d70eeddbfdf55d6bd17d6c diff --git a/packages/system/cozystack-controller/templates/certmanager.yaml b/packages/system/cozystack-controller/templates/certmanager.yaml new file mode 100644 index 00000000..6d9bce7d --- /dev/null +++ b/packages/system/cozystack-controller/templates/certmanager.yaml @@ -0,0 +1,45 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: cozystack-controller-webhook-selfsigned + namespace: {{ .Release.Namespace }} +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: cozystack-controller-webhook-ca + namespace: {{ .Release.Namespace }} +spec: + secretName: cozystack-controller-webhook-ca + duration: 43800h # 5 years + commonName: cozystack-controller-webhook-ca + issuerRef: + name: cozystack-controller-webhook-selfsigned + isCA: true +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: cozystack-controller-webhook-ca + namespace: {{ .Release.Namespace }} +spec: + ca: + secretName: cozystack-controller-webhook-ca +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: cozystack-controller-webhook + namespace: {{ .Release.Namespace }} +spec: + secretName: cozystack-controller-webhook-cert + duration: 8760h + renewBefore: 720h + issuerRef: + name: cozystack-controller-webhook-ca + commonName: cozystack-controller + dnsNames: + - cozystack-controller + - cozystack-controller.{{ .Release.Namespace }}.svc diff --git a/packages/system/cozystack-controller/templates/crds/cozystack.io_cozystackresourcedefinitions.yaml b/packages/system/cozystack-controller/templates/crds/cozystack.io_cozystackresourcedefinitions.yaml index 4fb0ff61..977a0f6d 100644 --- a/packages/system/cozystack-controller/templates/crds/cozystack.io_cozystackresourcedefinitions.yaml +++ b/packages/system/cozystack-controller/templates/crds/cozystack.io_cozystackresourcedefinitions.yaml @@ -106,6 +106,121 @@ spec: - chart - prefix type: object + secrets: + description: Secret selectors + properties: + exclude: + description: |- + Exclude contains an array of label selectors that target secrets. + If a secret matches the selector in any of the elements in the array, it is + hidden from the user, regardless of the matches in the include array. + items: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: array + include: + description: |- + Include contains an array of label selectors that target secrets. + If a secret matches the selector in any of the elements in the array, and + matches none of the selectors in the exclude array that secret is marked + as a tenant secret and is visible to users. + items: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: array + type: object required: - application - release diff --git a/packages/system/cozystack-controller/templates/deployment.yaml b/packages/system/cozystack-controller/templates/deployment.yaml index 33b309df..318ec3b2 100644 --- a/packages/system/cozystack-controller/templates/deployment.yaml +++ b/packages/system/cozystack-controller/templates/deployment.yaml @@ -2,7 +2,6 @@ apiVersion: apps/v1 kind: Deployment metadata: name: cozystack-controller - namespace: cozy-system labels: app: cozystack-controller spec: @@ -29,3 +28,15 @@ spec: {{- if .Values.cozystackController.disableTelemetry }} - --disable-telemetry {{- end }} + ports: + - name: webhook + containerPort: 9443 + volumeMounts: + - name: webhook-certs + mountPath: /tmp/k8s-webhook-server/serving-certs + readOnly: true + volumes: + - name: webhook-certs + secret: + secretName: cozystack-controller-webhook-cert + defaultMode: 0400 diff --git a/packages/system/cozystack-controller/templates/mutatingwebhookconfiguration.yaml b/packages/system/cozystack-controller/templates/mutatingwebhookconfiguration.yaml new file mode 100644 index 00000000..aebf8938 --- /dev/null +++ b/packages/system/cozystack-controller/templates/mutatingwebhookconfiguration.yaml @@ -0,0 +1,37 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: lineage + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/cozystack-controller-webhook + labels: + app: cozystack-controller +webhooks: + - name: lineage.cozystack.io + admissionReviewVersions: ["v1"] + sideEffects: None + clientConfig: + service: + name: cozystack-controller + namespace: {{ .Release.Namespace }} + path: /mutate-lineage + rules: + - operations: ["CREATE", "UPDATE"] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods","secrets", "services", "persistentvolumeclaims"] + - operations: ["CREATE", "UPDATE"] + apiGroups: ["operator.victoriametrics.com"] + apiVersions: ["v1beta1"] + resources: ["vmclusters"] + failurePolicy: Fail + namespaceSelector: + matchExpressions: + - key: cozystack.io/system + operator: NotIn + values: + - "true" + - key: kubernetes.io/metadata.name + operator: NotIn + values: + - kube-system diff --git a/packages/system/cozystack-controller/templates/service.yaml b/packages/system/cozystack-controller/templates/service.yaml new file mode 100644 index 00000000..8cc0eb61 --- /dev/null +++ b/packages/system/cozystack-controller/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: cozystack-controller + labels: + app: cozystack-controller +spec: + type: ClusterIP + ports: + - port: 443 + targetPort: 9443 + protocol: TCP + name: webhook + selector: + app: cozystack-controller diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index 8c9f3595..c5e7c85f 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,5 +1,5 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.36.1@sha256:6a6144430bdec901b4046840a484f0d9effc570f4f7cef2e5740332b98e3077a + image: ghcr.io/cozystack/cozystack/cozystack-controller:latest@sha256:20483582c50ead02d76719d3acfe2bb5e0d5a62de7dce65ffebe4039f8f3c400 debug: false disableTelemetry: false - cozystackVersion: "v0.36.1" + cozystackVersion: "latest" diff --git a/packages/system/dashboard/charts/kubeapps/templates/dashboard/configmap.yaml b/packages/system/dashboard/charts/kubeapps/templates/dashboard/configmap.yaml index a17cfecf..b1f1891b 100644 --- a/packages/system/dashboard/charts/kubeapps/templates/dashboard/configmap.yaml +++ b/packages/system/dashboard/charts/kubeapps/templates/dashboard/configmap.yaml @@ -76,7 +76,7 @@ data: "kubeappsNamespace": {{ .Release.Namespace | quote }}, "helmGlobalNamespace": {{ include "kubeapps.helmGlobalPackagingNamespace" . | quote }}, "carvelGlobalNamespace": {{ .Values.kubeappsapis.pluginConfig.kappController.packages.v1alpha1.globalPackagingNamespace | quote }}, - "appVersion": "v0.36.1", + "appVersion": "latest", "authProxyEnabled": {{ .Values.authProxy.enabled }}, "oauthLoginURI": {{ .Values.authProxy.oauthLoginURI | quote }}, "oauthLogoutURI": {{ .Values.authProxy.oauthLogoutURI | quote }}, diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index bfd0327e..300cf5a1 100644 --- a/packages/system/dashboard/values.yaml +++ b/packages/system/dashboard/values.yaml @@ -19,8 +19,8 @@ kubeapps: image: registry: ghcr.io/cozystack/cozystack repository: dashboard - tag: v0.36.1 - digest: "sha256:54906b3d2492c8603a347a5938b6db36e5ed5c4149111cae1804ac9110361947" + tag: latest + digest: "sha256:187d4e4964c57b318a4cf538d7e6d03c2f9a73d2ea3126b9be3d5ad7d48fa7f1" frontend: image: repository: bitnamilegacy/nginx @@ -48,8 +48,8 @@ kubeapps: image: registry: ghcr.io/cozystack/cozystack repository: kubeapps-apis - tag: v0.36.1 - digest: "sha256:95cec1059acc1e307f9346bdf570cfc1ca5962bdb803ff08ccd17712d1d8f485" + tag: latest + digest: "sha256:7f6a822c80dd84ee77276183b59ffe073ececff056bddd71e686b2d542f6fde8" pluginConfig: flux: packages: diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index 1a231935..6ed1713a 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v0.36.1@sha256:61bd1a046d0ad2bc90bf4507aa41073989001d13606e9b8cf8f318edcfadf2c7 + tag: latest@sha256:3ac3ed8f4b77d59745c89b215bf498b430a338c0ce39652ae0c389e36551fac9 repository: ghcr.io/cozystack/cozystack/kamaji resources: limits: @@ -13,4 +13,4 @@ kamaji: cpu: 100m memory: 100Mi extraArgs: - - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.36.1@sha256:61bd1a046d0ad2bc90bf4507aa41073989001d13606e9b8cf8f318edcfadf2c7 + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:latest@sha256:3ac3ed8f4b77d59745c89b215bf498b430a338c0ce39652ae0c389e36551fac9 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index 22647b56..74ef2ec8 100644 --- a/packages/system/kubeovn-plunger/values.yaml +++ b/packages/system/kubeovn-plunger/values.yaml @@ -1,4 +1,4 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.36.1@sha256:b38c0610e9648d21326be30a773173757897d2ba9ec438272c8ae3d738956b66 +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:latest@sha256:96950f57d68ca2a911a4fa4ec0b36bfb95c87ed8dc1a27845ae4a9dc1cb959ad ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 5a8f4a0a..410abd49 100644 --- a/packages/system/kubeovn-webhook/values.yaml +++ b/packages/system/kubeovn-webhook/values.yaml @@ -1,3 +1,3 @@ portSecurity: true routes: "" -image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.36.1@sha256:8fa755cdb024c7bdeff390d01d6b8569d60a4b244a7371209be4c4851df5fad4 +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:latest@sha256:ee87101fdcaf86c339463390c6f12ef107726746da38566bd936c5dfe5b70047 diff --git a/packages/system/kubeovn/values.yaml b/packages/system/kubeovn/values.yaml index 92319a51..145f86bc 100644 --- a/packages/system/kubeovn/values.yaml +++ b/packages/system/kubeovn/values.yaml @@ -64,4 +64,4 @@ global: images: kubeovn: repository: kubeovn - tag: v1.14.5@sha256:7035b06406493c3385645319a50f2198c6bfb343d2e8c3f0707e769db1e960f7 + tag: latest@sha256:0891335d3938dc604b47ae310f9bea815c352c15bea34dee111526c6aa6a48de diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index a979202b..cbf96787 100644 --- a/packages/system/kubevirt-csi-node/values.yaml +++ b/packages/system/kubevirt-csi-node/values.yaml @@ -1,3 +1,3 @@ storageClass: replicated csiDriver: - image: kklinch0/kubevirt-csi-driver:0.37.0@sha256:d334ba727f0974b085f6e44ee52a61fa0c507063875b52006665dbacfa332cbd + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:latest@sha256:35684db82ef7d5ad0cb39fa2568aa09cf60c50f3307099dbb89521aefad5baf6 diff --git a/packages/system/metallb/values.yaml b/packages/system/metallb/values.yaml index d392f4c4..d7d8c136 100644 --- a/packages/system/metallb/values.yaml +++ b/packages/system/metallb/values.yaml @@ -4,8 +4,8 @@ metallb: controller: image: repository: ghcr.io/cozystack/cozystack/metallb-controller - tag: v0.15.2@sha256:0e9080234fc8eedab78ad2831fb38df375c383e901a752d72b353c8d13b9605f + tag: v0.15.2@sha256:5106869c470fcce9e1ef1e07efe5224190ad19e8006d5889230a75df4988b68d speaker: image: repository: ghcr.io/cozystack/cozystack/metallb-speaker - tag: v0.15.2@sha256:e14d4c328c3ab91a6eadfeea90da96388503492d165e7e8582f291b1872e53b2 + tag: v0.15.2@sha256:2b837031e3c693c0fa0de0ad5a9036e2aabb5e90a7a235e19b089be73af11160 diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index 14427c67..c5836ea3 100644 --- a/packages/system/objectstorage-controller/values.yaml +++ b/packages/system/objectstorage-controller/values.yaml @@ -1,3 +1,3 @@ objectstorage: controller: - image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.36.1@sha256:aa0000265ae58155aebefedac72d0a6acc45437b8668bb9739bf11edefec067a" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:latest@sha256:02c1ff850922afd264659532465b079502c04e9e9f54686a26a26debad7880ef" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index fc081a90..59dabf84 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -118,7 +118,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.36.1@sha256:890c6f38d22fa8cba423d086686bd55c20b3d0c27881cf4b7a7801f1b0685112" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:latest@sha256:325ab962acb6b546e1fc0f597c86af25100c7c2cb570affaad04954eb6dee449" certificates: commonName: "SeaweedFS CA" ipAddresses: [] diff --git a/pkg/lineage/lineage.go b/pkg/lineage/lineage.go new file mode 100644 index 00000000..867e1aab --- /dev/null +++ b/pkg/lineage/lineage.go @@ -0,0 +1,195 @@ +package lineage + +import ( + "context" + "fmt" + "os" + "strings" + + helmv2 "github.com/fluxcd/helm-controller/api/v2" + "k8s.io/apimachinery/pkg/api/meta" + 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/schema" + "k8s.io/client-go/dynamic" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +const ( + HRAPIVersion = "helm.toolkit.fluxcd.io/v2" + HRKind = "HelmRelease" + HRLabel = "helm.toolkit.fluxcd.io/name" +) + +type ObjectID struct { + APIVersion string + Kind string + Namespace string + Name string +} + +func (o ObjectID) GetUnstructured(ctx context.Context, client dynamic.Interface, mapper meta.RESTMapper) (*unstructured.Unstructured, error) { + u, err := getUnstructuredObject(ctx, client, mapper, o.APIVersion, o.Kind, o.Namespace, o.Name) + if err != nil { + return nil, err + } + return u, nil +} + +func WalkOwnershipGraph( + ctx context.Context, + client dynamic.Interface, + mapper meta.RESTMapper, + appMapper AppMapper, + obj *unstructured.Unstructured, + memory ...interface{}, +) (out []ObjectID) { + + id := ObjectID{APIVersion: obj.GetAPIVersion(), Kind: obj.GetKind(), Namespace: obj.GetNamespace(), Name: obj.GetName()} + out = []ObjectID{} + l := log.FromContext(ctx) + + l.Info("processing object", "apiVersion", obj.GetAPIVersion(), "kind", obj.GetKind(), "name", obj.GetName()) + var visited map[ObjectID]bool + var ok bool + if len(memory) == 1 { + visited, ok = memory[0].(map[ObjectID]bool) + if !ok { + l.Error( + fmt.Errorf("invalid argument"), "could not parse visited map in WalkOwnershipGraph call", + "received", memory[0], "expected", "map[ObjectID]bool", + ) + return out + } + } + + if len(memory) == 0 { + visited = make(map[ObjectID]bool) + } + + if len(memory) != 0 && len(memory) != 1 { + l.Error( + fmt.Errorf("invalid argument count"), "could not parse variadic arguments to WalkOwnershipGraph", + "args passed", len(memory)+5, "expected args", "4|5", + ) + return out + } + + if visited[id] { + return out + } + + visited[id] = true + + ownerRefs := obj.GetOwnerReferences() + for _, owner := range ownerRefs { + ownerObj, err := getUnstructuredObject(ctx, client, mapper, owner.APIVersion, owner.Kind, obj.GetNamespace(), owner.Name) + if err != nil { + fmt.Fprintf(os.Stderr, "Could not fetch owner %s/%s (%s): %v\n", obj.GetNamespace(), owner.Name, owner.Kind, err) + continue + } + + out = append(out, WalkOwnershipGraph(ctx, client, mapper, appMapper, ownerObj, visited)...) + } + + // if object has owners, it couldn't be owned directly by the custom app + if len(ownerRefs) > 0 { + return + } + + // I want "if err1 != nil go to next block, if err2 != nil, go to next block, etc semantics", + // like an early return from a function, but if all checks succeed, I don't want to do the rest + // of the function, so it's a `for { if err { break } if othererr { break } if allgood { return } + for { + if obj.GetAPIVersion() != HRAPIVersion || obj.GetKind() != HRKind { + break + } + hr := helmReleaseFromUnstructured(obj) + if hr == nil { + break + } + a, k, p, err := appMapper.Map(hr) + if err != nil { + l.Error(err, "failed to map HelmRelease to app") + break + } + ownerObj, err := getUnstructuredObject(ctx, client, mapper, a, k, obj.GetNamespace(), strings.TrimPrefix(obj.GetName(), p)) + if err != nil { + l.Error(err, "couldn't get unstructured object", "APIVersion", a, "Kind", k, "Name", strings.TrimPrefix(obj.GetName(), p)) + break + } + // successfully mapped a HelmRelease to a custom app, no need to continue + out = append(out, + ObjectID{ + APIVersion: ownerObj.GetAPIVersion(), + Kind: ownerObj.GetKind(), + Namespace: ownerObj.GetNamespace(), + Name: ownerObj.GetName(), + }, + ) + return + } + + labels := obj.GetLabels() + name, ok := labels[HRLabel] + if !ok { + return + } + ownerObj, err := getUnstructuredObject(ctx, client, mapper, HRAPIVersion, HRKind, obj.GetNamespace(), name) + if err != nil { + return + } + out = append(out, WalkOwnershipGraph(ctx, client, mapper, appMapper, ownerObj, visited)...) + + return +} + +func getUnstructuredObject( + ctx context.Context, + client dynamic.Interface, + mapper meta.RESTMapper, + apiVersion, kind, namespace, name string, +) (*unstructured.Unstructured, error) { + l := log.FromContext(ctx) + gv, err := schema.ParseGroupVersion(apiVersion) + if err != nil { + l.Error( + err, "failed to parse groupversion", + "apiVersion", apiVersion, + ) + return nil, err + } + gvk := schema.GroupVersionKind{ + Group: gv.Group, + Version: gv.Version, + Kind: kind, + } + + mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) + if err != nil { + l.Error(err, "Could not map GVK "+gvk.String()) + return nil, err + } + + ns := namespace + if mapping.Scope.Name() != meta.RESTScopeNameNamespace { + ns = "" + } + + ownerObj, err := client.Resource(mapping.Resource).Namespace(ns).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return ownerObj, nil +} + +func helmReleaseFromUnstructured(obj *unstructured.Unstructured) *helmv2.HelmRelease { + if obj.GetAPIVersion() == HRAPIVersion && obj.GetKind() == HRKind { + hr := &helmv2.HelmRelease{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, hr); err == nil { + return hr + } + } + return nil +} diff --git a/pkg/lineage/lineage_test.go b/pkg/lineage/lineage_test.go new file mode 100644 index 00000000..7ae2a00c --- /dev/null +++ b/pkg/lineage/lineage_test.go @@ -0,0 +1,53 @@ +package lineage + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/go-logr/logr" + "github.com/go-logr/zapr" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/memory" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/restmapper" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +var ( + dynClient dynamic.Interface + mapper meta.RESTMapper + l logr.Logger + ctx context.Context +) + +func init() { + cfg := config.GetConfigOrDie() + + dynClient, _ = dynamic.NewForConfig(cfg) + + discoClient, _ := discovery.NewDiscoveryClientForConfig(cfg) + + cachedDisco := memory.NewMemCacheClient(discoClient) + mapper = restmapper.NewDeferredDiscoveryRESTMapper(cachedDisco) + + zapLogger, _ := zap.NewDevelopment() + l = zapr.NewLogger(zapLogger) + ctx = logr.NewContext(context.Background(), l) +} + +func TestWalkingOwnershipGraph(t *testing.T) { + obj, err := dynClient.Resource(schema.GroupVersionResource{"", "v1", "pods"}).Namespace(os.Args[1]).Get(ctx, os.Args[2], metav1.GetOptions{}) + if err != nil { + t.Fatal(err) + } + nodes := WalkOwnershipGraph(ctx, dynClient, mapper, obj) + for _, node := range nodes { + fmt.Printf("%#v\n", node) + } +} diff --git a/pkg/lineage/mapper.go b/pkg/lineage/mapper.go new file mode 100644 index 00000000..c424b288 --- /dev/null +++ b/pkg/lineage/mapper.go @@ -0,0 +1,49 @@ +package lineage + +import ( + "fmt" + "strings" + + helmv2 "github.com/fluxcd/helm-controller/api/v2" +) + +type AppMapper interface { + Map(*helmv2.HelmRelease) (apiVersion, kind, prefix string, err error) +} + +type stubMapper struct{} + +var stubMapperMap = map[string]string{ + "cozystack-extra/bootbox": "apps.cozystack.io/v1alpha1/BootBox/", + "cozystack-apps/bucket": "apps.cozystack.io/v1alpha1/Bucket/bucket-", + "cozystack-apps/clickhouse": "apps.cozystack.io/v1alpha1/ClickHouse/clickhouse-", + "cozystack-extra/etcd": "apps.cozystack.io/v1alpha1/Etcd/", + "cozystack-apps/ferretdb": "apps.cozystack.io/v1alpha1/FerretDB/ferretdb-", + "cozystack-apps/http-cache": "apps.cozystack.io/v1alpha1/HTTPCache/http-cache-", + "cozystack-extra/info": "apps.cozystack.io/v1alpha1/Info/", + "cozystack-extra/ingress": "apps.cozystack.io/v1alpha1/Ingress/", + "cozystack-apps/kafka": "apps.cozystack.io/v1alpha1/Kafka/kafka-", + "cozystack-apps/kubernetes": "apps.cozystack.io/v1alpha1/Kubernetes/kubernetes-", + "cozystack-extra/monitoring": "apps.cozystack.io/v1alpha1/Monitoring/", + "cozystack-apps/mysql": "apps.cozystack.io/v1alpha1/MySQL/mysql-", + "cozystack-apps/nats": "apps.cozystack.io/v1alpha1/NATS/nats-", + "cozystack-apps/postgres": "apps.cozystack.io/v1alpha1/Postgres/postgres-", + "cozystack-apps/rabbitmq": "apps.cozystack.io/v1alpha1/RabbitMQ/rabbitmq-", + "cozystack-apps/redis": "apps.cozystack.io/v1alpha1/Redis/redis-", + "cozystack-extra/seaweedfs": "apps.cozystack.io/v1alpha1/SeaweedFS/", + "cozystack-apps/tcp-balancer": "apps.cozystack.io/v1alpha1/TCPBalancer/tcp-balancer-", + "cozystack-apps/tenant": "apps.cozystack.io/v1alpha1/Tenant/tenant-", + "cozystack-apps/virtual-machine": "apps.cozystack.io/v1alpha1/VirtualMachine/virtual-machine-", + "cozystack-apps/vm-disk": "apps.cozystack.io/v1alpha1/VMDisk/vm-disk-", + "cozystack-apps/vm-instance": "apps.cozystack.io/v1alpha1/VMInstance/vm-instance-", + "cozystack-apps/vpn": "apps.cozystack.io/v1alpha1/VPN/vpn-", +} + +func (s *stubMapper) Map(hr *helmv2.HelmRelease) (string, string, string, error) { + val, ok := stubMapperMap[hr.Spec.Chart.Spec.SourceRef.Name+"/"+hr.Spec.Chart.Spec.Chart] + if !ok { + return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name) + } + split := strings.Split(val, "/") + return strings.Join(split[:2], "/"), split[2], split[3], nil +}