mirror of
https://github.com/outbackdingo/cozystack.git
synced 2026-01-27 10:18:39 +00:00
Feat/webhook workload monitors (#1448)
## What this PR does Many resources created as part of managed apps in cozystack (pods, secrets, etc) do not carry predictable labels that unambiguously indicate which app originally triggered their creation. Some resources are managed by controllers and other custom resources and this indirection can lead to loss of information. Other controllers sometimes simply do not allow setting labels on controlled resources and the latter do not inherit labels from the owner. This patch implements a webhook that sidesteps this problem with a universal solution. On creation of a pod/secret/PVC etc it walks through the owner references until a HelmRelease is found that can be matched with a managed app dynamically registered in the Cozystack API server. The pod is mutated with labels identifying the managed app. This resubmission of the PR now includes semantics to compare secrets to label selectors in CozystackResourceDefinitions to determine, whether they should be marked as user-facing or not. ### Release note ```release-note [cozystack-controller] Add a mutating webhook to identify the Cozystack managed app that ultimately owns low-level resources created in the cluster and label these resources with a reference to said app. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Adds a lineage mutating webhook that auto-applies ancestry labels to core resources and VMCluster. - Introduces secret include/exclude selectors in resource definitions for fine-grained tenant secret visibility. - Deploys webhook service with TLS via cert-manager (issuers, certificates) and updates deployment to expose webhook port. - Chores - Updates numerous container images to latest tags and digests across system and app components (controller, dashboard, kubeovn, cilium, kamaji, storage, etc.). <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
44
internal/lineagecontrollerwebhook/config.go
Normal file
44
internal/lineagecontrollerwebhook/config.go
Normal file
@@ -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
|
||||
}
|
||||
55
internal/lineagecontrollerwebhook/controller.go
Normal file
55
internal/lineagecontrollerwebhook/controller.go
Normal file
@@ -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
|
||||
}
|
||||
34
internal/lineagecontrollerwebhook/matcher.go
Normal file
34
internal/lineagecontrollerwebhook/matcher.go
Normal file
@@ -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
|
||||
}
|
||||
23
internal/lineagecontrollerwebhook/types.go
Normal file
23
internal/lineagecontrollerwebhook/types.go
Normal file
@@ -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
|
||||
}
|
||||
180
internal/lineagecontrollerwebhook/webhook.go
Normal file
180
internal/lineagecontrollerwebhook/webhook.go
Normal file
@@ -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)
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/clickhouse-backup:0.13.0@sha256:3faf7a4cebf390b9053763107482de175aa0fdb88c1e77424fd81100b1c3a205
|
||||
ghcr.io/cozystack/cozystack/clickhouse-backup:latest@sha256:0f8707d348e03bfa7589159a61d781d4eca238bdfff5dc09f4d3a01b31285a55
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/nginx-cache:0.7.0@sha256:50ac1581e3100bd6c477a71161cb455a341ffaf9e5e2f6086802e4e25271e8af
|
||||
ghcr.io/cozystack/cozystack/nginx-cache:latest@sha256:a4ab68c6930263b80f7d0c75f9c87ff9725a835db3f23c6a655993ec4feed49c
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.29.1@sha256:2d39989846c3579dd020b9f6c77e6e314cc81aa344eaac0f6d633e723c17196d
|
||||
ghcr.io/cozystack/cozystack/cluster-autoscaler:latest@sha256:89f822343654ea66efb3ac50bf72b483a52c1a11d33497fdfac5bbd0f3715c2b
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:0.29.1@sha256:5335c044313b69ee13b30ca4941687e509005e55f4ae25723861edbf2fbd6dd2
|
||||
ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:latest@sha256:190b7d231da1dfbded3af77777cc99b93a29a36ea69186170460f09d533fe041
|
||||
|
||||
@@ -1 +1 @@
|
||||
kklinch0/kubevirt-csi-driver:0.37.0@sha256:d334ba727f0974b085f6e44ee52a61fa0c507063875b52006665dbacfa332cbd
|
||||
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:latest@sha256:35684db82ef7d5ad0cb39fa2568aa09cf60c50f3307099dbb89521aefad5baf6
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.32@sha256:e53f2394c7aa76ad10818ffb945e40006cd77406999e47e036d41b8b0bf094cc
|
||||
ghcr.io/cozystack/cozystack/ubuntu-container-disk:latest@sha256:ac91fb70d6a898c8a305522020dbbd1bfa6d073a76e5d696a74307487de47dc5
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/mariadb-backup:0.10.0@sha256:a3789db9e9e065ff60cbac70771b4a8aa1460db3194307cf5ca5d4fe1b412b6b
|
||||
ghcr.io/cozystack/cozystack/mariadb-backup:latest@sha256:abfc43aed08fbbeed8f090e90158108fe59ed6279db93d169c3b1b1656af0064
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/matchbox:v0.36.1@sha256:ecf30f70d9a4b708f68fab52ba3a2ecc0787bb2e79906d76b770bb51f8d6ad6c
|
||||
ghcr.io/cozystack/cozystack/matchbox:latest@sha256:1923a1c9db94f5dd867e36f6de1db2e55512491dbd9c95ad4671e01eaef4dcf5
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/grafana:1.13.1@sha256:c63978e1ed0304e8518b31ddee56c4e8115541b997d8efbe1c0a74da57140399
|
||||
ghcr.io/cozystack/cozystack/grafana:latest@sha256:cc1843297f2dadb1740ebc4dfdc3dbe5b4ac840d1da37ed6fe93549d3000196c
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.36.1@sha256:890c6f38d22fa8cba423d086686bd55c20b3d0c27881cf4b7a7801f1b0685112
|
||||
ghcr.io/cozystack/cozystack/objectstorage-sidecar:latest@sha256:325ab962acb6b546e1fc0f597c86af25100c7c2cb570affaad04954eb6dee449
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:899ea667b3e575244d512cade23f30cc93d768b070f9c2bebcb440e443444bdb
|
||||
ghcr.io/cozystack/cozystack/s3manager:latest@sha256:377bc82c8404ae1ea201d8b9a64015952edfa06d88fb2678b7bf7c45b96fb968
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: [{}]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
15
packages/system/cozystack-controller/templates/service.yaml
Normal file
15
packages/system/cozystack-controller/templates/service.yaml
Normal file
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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 }},
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -64,4 +64,4 @@ global:
|
||||
images:
|
||||
kubeovn:
|
||||
repository: kubeovn
|
||||
tag: v1.14.5@sha256:7035b06406493c3385645319a50f2198c6bfb343d2e8c3f0707e769db1e960f7
|
||||
tag: latest@sha256:0891335d3938dc604b47ae310f9bea815c352c15bea34dee111526c6aa6a48de
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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: []
|
||||
|
||||
195
pkg/lineage/lineage.go
Normal file
195
pkg/lineage/lineage.go
Normal file
@@ -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
|
||||
}
|
||||
53
pkg/lineage/lineage_test.go
Normal file
53
pkg/lineage/lineage_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
49
pkg/lineage/mapper.go
Normal file
49
pkg/lineage/mapper.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user