Repository navigation
Feat/webhook workload monitors - #1448
Conversation
|
Caution Review failedThe pull request is closed. Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughIntroduces a new LineageControllerWebhook with controller and mutating webhook setup, lineage traversal utilities, and secret classification via CRD spec.secrets. Adds webhook manifests (service, certificates, deployment changes, configuration). Extends deepcopy for secrets. Updates multiple image tags to latest and fixes a label typo. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant K8s API Server as API Server
participant Webhook as LineageControllerWebhook
participant Dyn as Dynamic Client
participant Mapper as RESTMapper
participant Lineage as pkg/lineage
User->>API Server: CREATE/UPDATE (Pod/Secret/Service/PVC/VMCluster)
API Server->>Webhook: AdmissionReview (/mutate-lineage)
Webhook->>Webhook: decodeUnstructured(req)
Webhook->>Dyn: Fetch object/owners via computeLabels
Webhook->>Mapper: Resolve GVKs
Webhook->>Lineage: WalkOwnershipGraph(obj, appMapper)
alt No ancestors
Webhook-->>API Server: allow (no-op patch), warning
else Ambiguous ancestry
Webhook-->>API Server: allow (no-op patch), warning
else Resolved
Webhook->>Webhook: applyLabels (incl. VMCluster special path)
Webhook-->>API Server: PatchResponse (labels added)
end
sequenceDiagram
autonumber
participant Manager
participant Controller as LineageControllerWebhook (Controller)
participant API as K8s API
participant Store as runtimeConfig (atomic.Value)
Manager->>Controller: SetupWithManagerAsController()
loop On CRD changes
Controller->>API: List CozystackResourceDefinitions (cozy-system)
Controller->>Controller: Build chartAppMap, appCRDMap (dedupe)
Controller->>Store: Store config (atomic.Value)
end
Manager->>Controller: SetupWithManagerAsWebhook()
Controller->>Manager: Register /mutate-lineage
note right of Controller: Uses stored config for HelmRelease→App mapping
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (43)
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello Timofei Larkin (@lllamnyp), I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a significant new feature to the Cozystack controller: a mutating admission webhook designed to ensure proper lineage tracking for Kubernetes resources. Many resources created by managed applications often lack clear labels linking them back to their parent app, especially when intermediate controllers are involved. This webhook addresses this by inspecting the ownership chain of newly created or updated resources (like pods, secrets, and PVCs) and injecting labels that identify the ultimate Cozystack managed application. Furthermore, it extends the CozystackResourceDefinition API to allow for flexible classification of secrets as "user-facing" or not, using label selectors, providing finer-grained control over secret visibility. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a valuable feature: a mutating webhook to label resources with their owning Cozystack application, which will greatly improve resource tracking and visibility. The implementation, which includes a new controller for CozystackResourceDefinition and the webhook logic, is generally well-structured. However, I've identified a critical issue that could lead to a panic in the webhook, potentially blocking resource creations and updates. I've also found a few high-severity issues, including a broken test and some brittle code that could also panic under certain conditions. Additionally, there are several medium-severity suggestions aimed at improving code quality, robustness, and maintainability. Please review the detailed comments for each point.
| 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"] = "" | ||
| } |
There was a problem hiding this comment.
There's a potential for a nil pointer dereference here. If cfg.appCRDMap does not contain the appRef key, crd will be nil. The subsequent access to crd.Spec on the next line will cause the webhook to panic. This is a critical issue as it could block the creation or update of any resource the webhook is configured to watch. You should add a check to ensure crd is not nil before attempting to access its fields.
if crd, ok := cfg.appCRDMap[appRef{gv.Group, obj.GetKind()}]; ok {
if matchLabelsToExcludeInclude(o.GetLabels(), crd.Spec.Secrets.Exclude, crd.Spec.Secrets.Include) {
labels["internal.cozystack.io/tenantsecret"] = ""
}
}| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| nodes := WalkOwnershipGraph(ctx, dynClient, mapper, obj) |
There was a problem hiding this comment.
This test will not compile because the call to WalkOwnershipGraph is missing the required appMapper argument. Additionally, this test relies on command-line arguments (os.Args) to specify the resource to test against, which makes it a manual test rather than an automated unit test. It should be updated to be a proper, self-contained unit test using mock objects or a test environment.
| nodes := WalkOwnershipGraph(ctx, dynClient, mapper, obj) | |
| nodes := WalkOwnershipGraph(ctx, dynClient, mapper, &stubMapper{}, obj) |
| split := strings.Split(val, "/") | ||
| return strings.Join(split[:2], "/"), split[2], split[3], nil |
There was a problem hiding this comment.
The string splitting logic here is brittle. If a value in stubMapperMap does not contain at least three / characters, strings.Split will produce a slice with fewer than four elements, and accessing split[2] or split[3] will cause a panic. You should add a check to validate the length of the split slice before accessing its elements to prevent this.
split := strings.Split(val, "/")
if len(split) < 4 {
return "", "", "", fmt.Errorf("invalid format in stubMapperMap for key %s: %s", hr.Spec.Chart.Spec.SourceRef.Name+"/"+hr.Spec.Chart.Spec.Chart, val)
}
return strings.Join(split[:2], "/"), split[2], split[3], nil| if err != nil { | ||
| return false | ||
| } |
There was a problem hiding this comment.
The error from metav1.LabelSelectorAsSelector is silently ignored, which can make debugging invalid LabelSelectors in CozystackResourceDefinition resources difficult. As the TODO on line 9 suggests, this error should be handled, preferably by logging it. This would likely require passing a logger (or a context from which a logger can be retrieved) into this function and its callers (matchLabelsToSelectorArray and matchLabelsToExcludeInclude).
| 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 |
There was a problem hiding this comment.
The logic in this for loop to truncate the string and ensure it ends with an alphanumeric character is a bit convoluted and hard to follow. A simpler and more idiomatic Go implementation would improve readability and maintainability.
s = s[:63]
for len(s) > 0 {
b := s[len(s)-1]
if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') {
break
}
s = s[:len(s)-1]
}
return s| cozystackController: | ||
| image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.36.1@sha256:6a6144430bdec901b4046840a484f0d9effc570f4f7cef2e5740332b98e3077a | ||
| image: ghcr.io/cozystack/cozystack/cozystack-controller:latest@sha256:d01d35592a95ca2425a43fd1d2b64143838722b8d10c882d651dbe5602b68ce1 | ||
| debug: false | ||
| disableTelemetry: false | ||
| cozystackVersion: "v0.36.1" | ||
| cozystackVersion: "latest" |
There was a problem hiding this comment.
Using the latest tag for both the container image and the cozystackVersion is not recommended for production or even development environments, as it makes deployments non-deterministic and harder to reproduce or roll back. It's a best practice to pin to a specific, immutable version tag (e.g., v0.37.0) and its corresponding SHA256 digest.
cozystackController:
image: ghcr.io/cozystack/cozystack/cozystack-controller:vX.Y.Z@sha256:d01d35592a95ca2425a43fd1d2b64143838722b8d10c882d651dbe5602b68ce1
debug: false
disableTelemetry: false
cozystackVersion: "vX.Y.Z"85de5db to
00dd909
Compare
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (34)
packages/extra/monitoring/images/grafana.tag (1)
1-1: Avoid using thelatesttag; prefer explicit version tag or digest-onlyReplace the reference in packages/extra/monitoring/images/grafana.tag (uses
:latestwith a pinned digest) with a digest-only or explicit version reference:-ghcr.io/cozystack/cozystack/grafana:latest@sha256:cc1843297f2dadb1740ebc4dfdc3dbe5b4ac840d1da37ed6fe93549d3000196c +ghcr.io/cozystack/cozystack/grafana@sha256:cc1843297f2dadb1740ebc4dfdc3dbe5b4ac840d1da37ed6fe93549d3000196cpackages/system/cozystack-api/values.yaml (1)
2-2: Avoid:latesttag; keep digest-only to prevent AlwaysPull and ambiguous rollouts.Using
:latestforcesimagePullPolicy: Alwaysby default and can surprise rollouts/rate‑limit pulls. You already pin by digest—drop the tag for reproducible, predictable behavior.- image: ghcr.io/cozystack/cozystack/cozystack-api:latest@sha256:be6aca4df4b769538454d6c65b7045b2bef81b2a15d70eeddbfdf55d6bd17d6c + image: ghcr.io/cozystack/cozystack/cozystack-api@sha256:be6aca4df4b769538454d6c65b7045b2bef81b2a15d70eeddbfdf55d6bd17d6cpackages/system/objectstorage-controller/values.yaml (1)
3-3: Avoid ":latest" tags in production charts; keep semver tag + digest for traceability."latest" obscures the running version in labels/telemetry. Prefer a specific version tag pinned by digest.
Confirm the digest corresponds to the intended controller release, and consider restoring a versioned tag.
packages/apps/clickhouse/images/clickhouse-backup.tag (1)
1-1: Use versioned tag rather than "latest" for backup tooling images.Backups/restores are sensitive to version drift. Keep a semver tag with the digest for deterministic rollbacks and audits.
packages/system/seaweedfs/values.yaml (1)
121-121: Sidecar image should use explicit versioned tag + digest, not "latest".Prevents ambiguous versioning in labels and improves SBOM/compliance tracking.
packages/system/cilium/values.yaml (1)
17-18: Cilium image tag set to "latest" while digest is pinned — prefer semver tag for clarity and compatibility checks.Many components/readiness checks and labels surface the tag; "latest" can mask real version, hindering debugging and drift detection. Keep a concrete version tag with the digest.
Make sure chart/cluster features configured here are compatible with the actual digest’s Cilium version.
packages/core/installer/values.yaml (1)
2-2: Installer image should remain on a versioned tag for reproducibility.Switching to "latest" reduces ability to bisect/rollback installs reliably. Recommend semver tag + digest.
packages/system/dashboard/charts/kubeapps/templates/dashboard/configmap.yaml (1)
79-79: Don’t hardcode appVersion to "latest" in user‑facing config; derive from chart/image.This degrades UX and debug value. Use the chart/image version templating already available in this chart.
Apply this change to keep it consistent with labels:
- "appVersion": "latest", + "appVersion": {{ include "common.images.version" ( dict "imageRoot" .Values.dashboard.image "chart" .Chart ) | toJson }},packages/system/kubevirt-csi-node/values.yaml (1)
3-3: Pin kubevirt-csi-driver to a specific version tag with digest; avoid "latest".CSI components are critical; explicit versions help with compatibility and incident response.
packages/system/kubeovn/values.yaml (1)
67-67: Replace 'latest' tags (even with digest) with semver tags + digestUsing
latest@sha256:...pins content but keeps the "latest" tag in labels/tooling — use a versioned tag (e.g., vX.Y.Z@sha256:0891335d...) for provenance, rollbacks and SBOMs.
Change packages/system/kubeovn/values.yaml (line 67)
tag: latest@sha256:0891335d3938dc604b47ae310f9bea815c352c15bea34dee111526c6aa6a48de
to a semver tag with the same digest; repo-wide grep returned many otherlatestoccurrences — update them or gatelatestbehind a dev overlay.packages/apps/http-cache/images/nginx-cache.tag (1)
1-1: Avoid using the "latest" tag even with a pinned digest for traceability and pull behavior.
- "latest" defaults imagePullPolicy to Always, which can cause unnecessary pulls even though the digest is immutable.
- Prefer an explicit version tag matching the digest, or use repo@sha256 without a tag if supported by your tooling.
Can you confirm the digest corresponds to the intended release (and that you want Always pulls here)?
packages/core/testing/values.yaml (1)
2-2: e2e image set to "latest": confirm intent and stability.Pinned digest ensures reproducibility, but "latest" will still default pullPolicy to Always. For CI stability and speed, consider an explicit version tag or explicitly setting pullPolicy in the chart.
Is Always pull desired in your CI runners for this image?
packages/extra/seaweedfs/images/objectstorage-sidecar.tag (1)
1-1: Prefer versioned tag or digest-only to avoid "latest".
- Keeps provenance clear in audits and release notes.
- Avoids implicit Always pulls from using "latest".
Please confirm the digest maps to the target release.
packages/extra/bootbox/images/matchbox.tag (1)
1-1: Use an explicit version tag (or digest-only) rather than "latest".Improves auditability and avoids implicit Always pulls.
Confirm the digest is the intended version.
packages/apps/kubernetes/images/ubuntu-container-disk.tag (1)
1-1: For VM base images, avoid "latest" to prevent unexpected pull behavior and ease provenance.
- Prefer a semver tag aligned with the digest or digest-only.
- If consumers run this with "latest", Kubernetes will default to Always pulling unless overridden.
Is the consuming chart setting pullPolicy explicitly for this image?
packages/system/dashboard/values.yaml (1)
51-52: kubeapps-apis: avoidlatest— set pullPolicy or use a versioned tag.packages/system/dashboard/values.yaml sets kubeappsapis.image.tag: latest (digest present). Prefer a fixed version tag or add an explicit pullPolicy so imagePullPolicy is deterministic (templates read .Values.kubeappsapis.image.pullPolicy).
Suggested change:
image:
registry: ghcr.io/cozystack/cozystack
repository: kubeapps-apis
tag: latest
digest: "sha256:7f6a822c80dd84ee77276183b59ffe073ececff056bddd71e686b2d542f6fde8"
pullPolicy: IfNotPresentcmd/cozystack-controller/main.go (1)
218-231: Registering the webhook + controller: LGTM; add explicit webhook server settings and a success log.
- Minor: log successful setup to ease troubleshooting.
- Recommended: set explicit Port and CertDir for the webhook server (defaults work but being explicit avoids chart/template drift).
Apply this within the selected block to add a success log:
if err := lineageControllerWebhook.SetupWithManagerAsWebhook(mgr); err != nil { setupLog.Error(err, "unable to setup webhook", "webhook", "LineageWebhook") os.Exit(1) } +setupLog.Info("Lineage webhook and controller registered")Additionally, set explicit webhook server options (outside this block, around Lines 128-131):
webhookServer := webhook.NewServer(webhook.Options{ TLSOpts: tlsOpts, Port: 9443, CertDir: "/tmp/k8s-webhook-server/serving-certs", })Ensure your Helm chart mounts certs to the same path or update CertDir accordingly.
Can you confirm the chart/template mounts serving certs at
/tmp/k8s-webhook-server/serving-certs(or update CertDir to match)?packages/apps/kubernetes/images/kubevirt-csi-driver.tag (1)
1-1: Avoid:latesttag; use digest-only or a versioned tag + digest.Using
latest@sha256:...is confusing and can mislead humans/tools. Prefer either digest-only or a specific version tag plus the digest.Apply:
-ghcr.io/cozystack/cozystack/kubevirt-csi-driver:latest@sha256:35684db82ef7d5ad0cb39fa2568aa09cf60c50f3307099dbb89521aefad5baf6 +ghcr.io/cozystack/cozystack/kubevirt-csi-driver@sha256:35684db82ef7d5ad0cb39fa2568aa09cf60c50f3307099dbb89521aefad5baf6pkg/lineage/lineage_test.go (1)
29-42: Avoid init-time real cluster dependencies in unit tests.init() with GetConfigOrDie() and real discovery makes this a manual/integration test. Consider moving setup into the test and using envtest or the dynamic fake client with a static RESTMapper.
packages/system/cozystack-controller/templates/deployment.yaml (1)
31-42: Add basic probes and hardening for the webhook container.Readiness/liveness on 9443 (tcp) improves rollout safety; minimal securityContext/resources help harden defaults.
Apply:
- name: webhook containerPort: 9443 + readinessProbe: + tcpSocket: + port: 9443 + initialDelaySeconds: 2 + periodSeconds: 10 + livenessProbe: + tcpSocket: + port: 9443 + initialDelaySeconds: 10 + periodSeconds: 20 volumeMounts: - name: webhook-certs mountPath: /tmp/k8s-webhook-server/serving-certs readOnly: true + securityContext: + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 500m + memory: 256Mi volumes: - name: webhook-certs secret: secretName: cozystack-controller-webhook-cert defaultMode: 0400internal/lineagecontrollerwebhook/controller.go (2)
12-12: RBAC: includegetto be safe.Controller cache typically needs list/watch; adding get is a low-cost hedge if future code uses direct Get.
Apply:
-// +kubebuilder:rbac:groups=cozystack.io,resources=cozystackresourcedefinitions,verbs=list;watch +// +kubebuilder:rbac:groups=cozystack.io,resources=cozystackresourcedefinitions,verbs=get;list;watch
23-23: Make the namespace configurable with a default.Hard-coding "cozy-system" limits portability.
Apply:
- if err := c.List(ctx, crds, &client.ListOptions{Namespace: "cozy-system"}); err != nil { + ns := "cozy-system" + if v := ctrl.GetConfigOrDie(); v != nil { + // optionally: pick from env var if provided + if n := os.Getenv("COZYSTACK_SYSTEM_NAMESPACE"); n != "" { + ns = n + } + } + if err := c.List(ctx, crds, &client.ListOptions{Namespace: ns}); err != nil {Add
osto imports.internal/lineagecontrollerwebhook/matcher.go (1)
8-15: Handle nil selectors and log invalid LabelSelectors.Avoid treating nil as match-all and emit a warning on parse errors to aid CRD debugging.
Apply:
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)) + if s == nil { + return false + } + sel, err := metav1.LabelSelectorAsSelector(s) + if err != nil { + log.Log.Error(err, "invalid LabelSelector in CozystackResourceDefinition", "selector", s) + return false + } + return sel.Matches(labels.Set(l)) }And add import:
import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/log" )packages/system/cozystack-controller/templates/mutatingwebhookconfiguration.yaml (2)
22-22: Style: add spaces after commas in the list.Resolves yamllint warning; improves readability.
- resources: ["pods","secrets", "services", "persistentvolumeclaims"] + resources: ["pods", "secrets", "services", "persistentvolumeclaims"]
18-27: Consider adding timeoutSeconds and objectSelector guardrails.
- Set timeoutSeconds (e.g., 10) explicitly.
- If your webhook skips SA token secrets internally, fine; otherwise consider an objectSelector to reduce blast radius (labels only; SA token detection likely needs in-handler checks).
Can you confirm the webhook handler explicitly ignores service account token secrets (type kubernetes.io/service-account-token) and other control-plane managed objects?
internal/lineagecontrollerwebhook/config.go (1)
39-43: Key may collide across SourceRef namespaces.chartRef uses only SourceRef.Name and Chart. If multiple HelmRepository/OCIRepository objects share the same Name in different namespaces, mappings will collide.
Consider including SourceRef.Namespace in chartRef (and the controller that populates the map) to avoid collisions.
packages/system/cozystack-controller/templates/certmanager.yaml (1)
43-45: Add FQDN to DNS names for webhook TLS.Including the cluster-local FQDN improves compatibility with resolvers that use FQDNs.
dnsNames: - cozystack-controller - cozystack-controller.{{ .Release.Namespace }}.svc + - cozystack-controller.{{ .Release.Namespace }}.svc.cluster.localapi/v1alpha1/cozystackresourcedefinitions_types.go (1)
93-103: Validation hints for label selectors (optional).If you want to enforce non-empty items: add kubebuilder validation (MinItems) to Include/Exclude arrays. Optional.
internal/lineagecontrollerwebhook/types.go (1)
14-14: Align webhook name with deployed MWC (optional).Marker uses
name=mlineage.cozystack.iowhile Helm MWC useslineage.cozystack.io. If you rely on generated manifests elsewhere, consider aligning to avoid duplicate MWCs.-// +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} +// +kubebuilder:webhook:path=/mutate-lineage,mutating=true,failurePolicy=Fail,sideEffects=None,groups="",resources=pods,secrets,services,persistentvolumeclaims,verbs=create;update,versions=v1,name=lineage.cozystack.io,admissionReviewVersions={v1}pkg/lineage/lineage.go (2)
88-91: Use structured logger instead of stderr.Keeps logs consistent with controller-runtime and avoids noisy stderr writes.
- fmt.Fprintf(os.Stderr, "Could not fetch owner %s/%s (%s): %v\n", obj.GetNamespace(), owner.Name, owner.Kind, err) + l.Error(err, "could not fetch owner", + "namespace", obj.GetNamespace(), "name", owner.Name, "kind", owner.Kind)Also remove the unused os import after this change.
117-121: Optional: log when prefix trimming is ineffective.HelmRelease name not starting with prefix results in a likely miss; a debug log helps troubleshooting.
- ownerObj, err := getUnstructuredObject(ctx, client, mapper, a, k, obj.GetNamespace(), strings.TrimPrefix(obj.GetName(), p)) + targetName := strings.TrimPrefix(obj.GetName(), p) + if targetName == obj.GetName() { + l.V(1).Info("helmrelease name did not have expected prefix", "name", obj.GetName(), "prefix", p) + } + ownerObj, err := getUnstructuredObject(ctx, client, mapper, a, k, obj.GetNamespace(), targetName)internal/lineagecontrollerwebhook/webhook.go (3)
73-89: Simplify error handling; remove redundant for-loop.Logic is single-pass; a straight if/switch is clearer.
- 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 - } - } + labels, err := h.computeLabels(ctx, obj) + if err != nil { + if errors.Is(err, NoAncestors) { + return admission.Allowed("object not managed by app") + } + if errors.Is(err, AncestryAmbiguous) { + warn = append(warn, "object ancestry ambiguous, using first ancestor found") + } else { + logger.Error(err, "error computing lineage labels") + return admission.Errored(500, fmt.Errorf("error computing lineage labels: %w", err)) + } + }
145-148: Handle potential error from SetNestedStringMap (best-effort).Consider capturing/logging the error to aid debugging; current code silently ignores failures.
Example:
if err := unstructured.SetNestedStringMap(o.Object, labels, "spec", "managedMetadata", "labels"); err != nil { // TODO: add logging if a logger is available here }
27-27: Comment/name mismatch.Update the comment to match the function name.
-// SetupWithManager registers the handler with the webhook server. +// SetupWithManagerAsWebhook registers the handler with the webhook server.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (42)
api/v1alpha1/cozystackresourcedefinitions_types.go(3 hunks)api/v1alpha1/zz_generated.deepcopy.go(2 hunks)cmd/cozystack-controller/main.go(2 hunks)internal/lineagecontrollerwebhook/config.go(1 hunks)internal/lineagecontrollerwebhook/controller.go(1 hunks)internal/lineagecontrollerwebhook/matcher.go(1 hunks)internal/lineagecontrollerwebhook/types.go(1 hunks)internal/lineagecontrollerwebhook/webhook.go(1 hunks)packages/apps/clickhouse/images/clickhouse-backup.tag(1 hunks)packages/apps/http-cache/images/nginx-cache.tag(1 hunks)packages/apps/kubernetes/images/cluster-autoscaler.tag(1 hunks)packages/apps/kubernetes/images/kubevirt-cloud-provider.tag(1 hunks)packages/apps/kubernetes/images/kubevirt-csi-driver.tag(1 hunks)packages/apps/kubernetes/images/ubuntu-container-disk.tag(1 hunks)packages/apps/mysql/images/mariadb-backup.tag(1 hunks)packages/core/installer/values.yaml(1 hunks)packages/core/testing/values.yaml(1 hunks)packages/extra/bootbox/images/matchbox.tag(1 hunks)packages/extra/monitoring/images/grafana.tag(1 hunks)packages/extra/seaweedfs/images/objectstorage-sidecar.tag(1 hunks)packages/system/bucket/images/s3manager.tag(1 hunks)packages/system/cilium/values.yaml(1 hunks)packages/system/cozystack-api/values.yaml(1 hunks)packages/system/cozystack-controller/templates/certmanager.yaml(1 hunks)packages/system/cozystack-controller/templates/crds/cozystack.io_cozystackresourcedefinitions.yaml(1 hunks)packages/system/cozystack-controller/templates/deployment.yaml(1 hunks)packages/system/cozystack-controller/templates/mutatingwebhookconfiguration.yaml(1 hunks)packages/system/cozystack-controller/templates/service.yaml(1 hunks)packages/system/cozystack-controller/values.yaml(1 hunks)packages/system/dashboard/charts/kubeapps/templates/dashboard/configmap.yaml(1 hunks)packages/system/dashboard/values.yaml(2 hunks)packages/system/kamaji/values.yaml(2 hunks)packages/system/kubeovn-plunger/values.yaml(1 hunks)packages/system/kubeovn-webhook/values.yaml(1 hunks)packages/system/kubeovn/values.yaml(1 hunks)packages/system/kubevirt-csi-node/values.yaml(1 hunks)packages/system/metallb/values.yaml(1 hunks)packages/system/objectstorage-controller/values.yaml(1 hunks)packages/system/seaweedfs/values.yaml(1 hunks)pkg/lineage/lineage.go(1 hunks)pkg/lineage/lineage_test.go(1 hunks)pkg/lineage/mapper.go(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-11T16:05:52.112Z
Learnt from: lllamnyp
PR: cozystack/cozystack#1400
File: packages/system/cozystack-controller/templates/certmanager.yaml:39-45
Timestamp: 2025-09-11T16:05:52.112Z
Learning: In Cozystack, `cluster.local` is not the cluster domain, and the Kubernetes API server doesn't use the cluster domain when contacting webhooks. DNS names for webhook certificates should match the actual service names without assuming `cluster.local` suffix.
Applied to files:
packages/system/cozystack-controller/templates/service.yaml
🧬 Code graph analysis (9)
api/v1alpha1/zz_generated.deepcopy.go (1)
api/v1alpha1/cozystackresourcedefinitions_types.go (2)
CozystackResourceDefinitionSecrets(93-103)CozystackResourceDefinitionSpec(46-53)
internal/lineagecontrollerwebhook/controller.go (2)
internal/lineagecontrollerwebhook/types.go (1)
LineageControllerWebhook(15-23)api/v1alpha1/cozystackresourcedefinitions_types.go (3)
CozystackResourceDefinition(26-31)CozystackResourceDefinitionList(36-40)SourceRef(62-71)
internal/lineagecontrollerwebhook/config.go (2)
api/v1alpha1/cozystackresourcedefinitions_types.go (2)
CozystackResourceDefinition(26-31)SourceRef(62-71)internal/lineagecontrollerwebhook/types.go (1)
LineageControllerWebhook(15-23)
pkg/lineage/mapper.go (1)
api/v1alpha1/cozystackresourcedefinitions_types.go (1)
SourceRef(62-71)
pkg/lineage/lineage.go (1)
pkg/lineage/mapper.go (1)
AppMapper(10-12)
pkg/lineage/lineage_test.go (1)
pkg/lineage/lineage.go (1)
WalkOwnershipGraph(40-146)
internal/lineagecontrollerwebhook/types.go (1)
pkg/apiserver/apiserver.go (1)
Scheme(41-41)
internal/lineagecontrollerwebhook/webhook.go (2)
internal/lineagecontrollerwebhook/types.go (1)
LineageControllerWebhook(15-23)pkg/lineage/lineage.go (1)
WalkOwnershipGraph(40-146)
cmd/cozystack-controller/main.go (1)
internal/lineagecontrollerwebhook/types.go (1)
LineageControllerWebhook(15-23)
🪛 YAMLlint (1.37.1)
packages/system/cozystack-controller/templates/mutatingwebhookconfiguration.yaml
[warning] 22-22: too few spaces after comma
(commas)
[error] 6-6: syntax error: expected , but found ''
(syntax)
🔇 Additional comments (16)
packages/system/metallb/values.yaml (2)
7-7: Digest‑pinned tag looks good; verify chart handles tag@sha256.Many charts render image as ":". Using "v0.15.2@sha256:…" as the tag is valid for K8s/CRI, but confirm the Metallb chart/template doesn’t also append a separate digest field (to avoid double-specifying) and that pulls succeed.
11-11: Consistent pin for speaker; same verification applies.Good to pin to digest. Please confirm the chart renders "repository:tag" only and that "v0.15.2@sha256:…" pulls successfully for the speaker as well. Keep controller/speaker in lockstep (which you do here).
packages/system/kamaji/values.yaml (2)
6-6: Don't use ':latest' even when pinned by digestReplace in packages/system/kamaji/values.yaml (line 6):
tag: latest@sha256:3ac3ed8f4b77d59745c89b215bf498b430a338c0ce39652ae0c389e36551fac9
with an immutable semver tag that matches the digest (e.g., vX.Y.Z@sha256:...).Ensure Helm templates render the full image string "repository:tag@sha256:…" and do not split/normalize the digest.
Verify there are no other ':latest' tags repo-wide (policy scanners often flag ':latest').
16-16: Validate--migrate-imagecompatibility and pin image (avoid:latest)
- Kamaji supports
--migrate-imageand repo[:tag]Shamim Hasnath (@sha256): is a valid image reference; replace:latest@sha256:...with a pinned release tag or name@sha256 and ensure the digest matches the controller/migrator image to avoid upgrade breakage.- Ensure CRDs installed (kamaji-crds) match that controller image and test upgrades — edge/latest artifacts can change and migrator/controller skew can brick upgrades.
packages/system/kamaji/values.yaml (line 16): - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:latest@sha256:3ac3ed8f4b77d59745c89b215bf498b430a338c0ce39652ae0c389e36551fac9
packages/system/dashboard/values.yaml (1)
22-23: No change required — kubeapps subchart already sets dashboard.image.pullPolicy to IfNotPresent.packages/system/dashboard/charts/kubeapps/values.yaml defines dashboard.image.pullPolicy: IfNotPresent and the chart templates read .Values.dashboard.image.pullPolicy, so adding pullPolicy in the top-level values.yaml is unnecessary.
cmd/cozystack-controller/main.go (1)
41-41: Import of lineage webhook/controller: LGTM.Alias and package selection look correct.
packages/system/cozystack-controller/values.yaml (1)
2-5: Do not ship controller aslatest; pin version and setcozystackVersionto the actual release.Controller images and version identifiers should be immutable for traceability and safer rollbacks. This mirrors a prior review note and still applies.
Run this to inventory all
latest@sha256usages to assess blast radius:#!/bin/bash set -euo pipefail rg -nP ':[Ll]atest@sha256:[0-9a-f]{64}\b' -S -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' -C2packages/apps/mysql/images/mariadb-backup.tag (1)
1-1: Avoidlatest— pin a concrete version tag or use digest-onlypackages/apps/mysql/images/mariadb-backup.tag references
ghcr.io/cozystack/cozystack/mariadb-backup:latest@sha256:abfc43aed08fbbeed8f090e90158108fe59ed6279db93d169c3b1b1656af0064; replace with an explicit version + digest (e.g., vX.Y.Z@sha256:...) or digest-only (ghcr.io/...Shamim Hasnath (@sha256):...) for reproducible deployments and safer rollbacks.If this
latestusage is intentional, confirm — I can run a repo-wide search for other occurrences.api/v1alpha1/zz_generated.deepcopy.go (1)
141-176: Deepcopy implementations for Secrets are correct and nil-safe.Slices are deep-copied with per-element allocation, and Spec.DeepCopyInto delegates to Secrets.DeepCopyInto as expected.
Also applies to: 183-184
packages/system/cozystack-controller/templates/crds/cozystack.io_cozystackresourcedefinitions.yaml (1)
109-223: CRD schema forspec.secretslooks consistent with k8s LabelSelector semantics.Include/exclude arrays, matchExpressions/matchLabels, and list/map hints are correctly defined.
pkg/lineage/lineage_test.go (1)
44-53: Test won’t compile (missingappMapperarg); also relies on os.Args making it non-automated.Pass an
AppMapper(e.g.,&stubMapper{}) and guard manual invocation to avoid breaking CI.Apply:
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 len(os.Args) < 3 { + t.Skip("manual test: usage `go test -args <namespace> <name>`") + } + obj, err := dynClient.Resource(schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}). + Namespace(os.Args[1]). + Get(ctx, os.Args[2], metav1.GetOptions{}) if err != nil { t.Fatal(err) } - nodes := WalkOwnershipGraph(ctx, dynClient, mapper, obj) + nodes := WalkOwnershipGraph(ctx, dynClient, mapper, &stubMapper{}, obj) for _, node := range nodes { fmt.Printf("%#v\n", node) } }Longer-term: convert this into a proper unit/integration test using envtest or the dynamic fake client instead of real cluster access.
packages/system/cozystack-controller/templates/service.yaml (1)
9-15: Approve — webhook SANs and service refs match (no cluster.local).cert-manager Certificate for the webhook includes dnsNames "cozystack-controller" and "cozystack-controller.{{ .Release.Namespace }}.svc"; MutatingWebhookConfiguration clientConfig.service entries reference name: cozystack-controller and namespace: {{ .Release.Namespace }}.
pkg/lineage/mapper.go (1)
42-49: Guard against malformed mapping strings to avoid panic.Accessing split[2]/split[3] without length check can panic. This was raised previously.
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] + key := hr.Spec.Chart.Spec.SourceRef.Name + "/" + hr.Spec.Chart.Spec.Chart + val, ok := stubMapperMap[key] 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 + if len(split) < 4 { + return "", "", "", fmt.Errorf("invalid format in stubMapperMap for key %s: %s", key, val) + } + return strings.Join(split[:2], "/"), split[2], split[3], nil }internal/lineagecontrollerwebhook/types.go (1)
18-18: Use a pointer for the admission decoder.
Controller-runtime injects *admission.Decoder; use a pointer to avoid copies and match InjectDecoder signature.
internal/lineagecontrollerwebhook/types.go:18- decoder admission.Decoder + decoder *admission.DecoderConfirm your InjectDecoder implementation assigns to this field.
internal/lineagecontrollerwebhook/webhook.go (2)
136-140: Fix nil-deref when CRD mapping is absent (gate before dereferencing).Guard
crdaccess; currently panics if the key is missing.- 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"] = "" - } + cfg := h.config.Load().(*runtimeConfig) + if crd, ok := cfg.appCRDMap[appRef{gv.Group, obj.GetKind()}]; ok && crd != nil { + if matchLabelsToExcludeInclude(o.GetLabels(), crd.Spec.Secrets.Exclude, crd.Spec.Secrets.Include) { + labels["internal.cozystack.io/tenantsecret"] = "" + } + }
121-129: Make truncation safe; current loop can panic when trimming.The loop indexes
s[len(s)-1]without guarding empty string; simplify and guard length.- "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.group": func(s string) string { + if len(s) > 63 { + s = s[:63] + } + for len(s) > 0 { + b := s[len(s)-1] + if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') { + break + } + s = s[:len(s)-1] + } + return s + }(gv.Group),
| // Secret selectors | ||
| Secrets CozystackResourceDefinitionSecrets `json:"secrets,omitempty"` | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Make Secrets optional (pointer) to honor omitempty and keep API clean.
As a non-pointer struct, omitempty won’t omit the field, producing secrets: {}. Use a pointer so absent values are omitted.
- // Secret selectors
- Secrets CozystackResourceDefinitionSecrets `json:"secrets,omitempty"`
+ // Secret selectors
+ Secrets *CozystackResourceDefinitionSecrets `json:"secrets,omitempty"`Follow-up: regenerate deepcopy and CRDs (controller-gen) after this change.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Secret selectors | |
| Secrets CozystackResourceDefinitionSecrets `json:"secrets,omitempty"` | |
| } | |
| // Secret selectors | |
| Secrets *CozystackResourceDefinitionSecrets `json:"secrets,omitempty"` | |
| } |
🤖 Prompt for AI Agents
In api/v1alpha1/cozystackresourcedefinitions_types.go around lines 51 to 53, the
Secrets field is declared as a non-pointer struct which prevents
json:"omitempty" from omitting it (resulting in empty objects like secrets:{});
change the field type to a pointer (e.g., *CozystackResourceDefinitionSecrets)
so nil values are omitted, update any constructors/initializers that set or
check this field to handle nil, then regenerate deepcopy functions and CRDs by
running controller-gen (and go generate / make target as used in the repo) to
update the generated code and manifests.
| func (l *LineageControllerWebhook) initConfig() { | ||
| l.initOnce.Do(func() { | ||
| if l.config.Load() == nil { | ||
| l.config.Store(&runtimeConfig{chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition)}) | ||
| } | ||
| }) | ||
| } |
There was a problem hiding this comment.
Initialize all maps in runtimeConfig.
appCRDMap remains nil; future reads will NPE. Initialize both.
func (l *LineageControllerWebhook) initConfig() {
l.initOnce.Do(func() {
if l.config.Load() == nil {
- l.config.Store(&runtimeConfig{chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition)})
+ l.config.Store(&runtimeConfig{
+ chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition),
+ appCRDMap: make(map[appRef]*cozyv1alpha1.CozystackResourceDefinition),
+ })
}
})
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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) initConfig() { | |
| l.initOnce.Do(func() { | |
| if l.config.Load() == nil { | |
| l.config.Store(&runtimeConfig{ | |
| chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition), | |
| appCRDMap: make(map[appRef]*cozyv1alpha1.CozystackResourceDefinition), | |
| }) | |
| } | |
| }) | |
| } |
🤖 Prompt for AI Agents
In internal/lineagecontrollerwebhook/config.go around lines 25-31, the
runtimeConfig is only initializing chartAppMap and leaves appCRDMap nil which
can cause NPEs later; update the stored runtimeConfig to initialize both maps
(e.g. chartAppMap: make(map[chartRef]*cozyv1alpha1.CozystackResourceDefinition)
and appCRDMap: make(map[string]*cozyv1alpha1.CozystackResourceDefinition) or the
correct key/type used elsewhere) so both maps are non-nil when stored.
| 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 | ||
| } |
There was a problem hiding this comment.
Guard against uninitialized atomic.Value (call initConfig) and improve error.
Load on zero-value atomic.Value can be problematic; ensure initialization, then check nil before type assertion.
func (l *LineageControllerWebhook) Map(hr *helmv2.HelmRelease) (string, string, string, error) {
- cfg, ok := l.config.Load().(*runtimeConfig)
- if !ok {
+ l.initConfig()
+ raw := l.config.Load()
+ if raw == nil {
+ return "", "", "", fmt.Errorf("runtime config not initialized")
+ }
+ cfg, ok := raw.(*runtimeConfig)
+ if !ok || cfg == nil {
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
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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 | |
| } | |
| func (l *LineageControllerWebhook) Map(hr *helmv2.HelmRelease) (string, string, string, error) { | |
| l.initConfig() | |
| raw := l.config.Load() | |
| if raw == nil { | |
| return "", "", "", fmt.Errorf("runtime config not initialized") | |
| } | |
| cfg, ok := raw.(*runtimeConfig) | |
| if !ok || cfg == nil { | |
| 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 | |
| } |
🤖 Prompt for AI Agents
internal/lineagecontrollerwebhook/config.go around lines 33 to 44: the code
calls l.config.Load() without guaranteeing the atomic.Value has been
initialized, then asserts to *runtimeConfig which can panic on nil/zero-value;
ensure initConfig has been called (or lazily initialize) before using Load,
check that loaded value is non-nil before the type assertion, and return a
clearer error that includes the helm release namespace/name when config is
missing; i.e., call or verify initConfig earlier, after Load verify v :=
l.config.Load(); if v == nil return an error like "config not initialized for
helm release <ns>/<name>", then safely type-assert to *runtimeConfig and proceed
as before.
| 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...) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick
Fix log/error strings: not always a Pod.
Use “object” to avoid misleading logs.
mutated, err := json.Marshal(obj)
if err != nil {
- return admission.Errored(500, fmt.Errorf("marshal mutated pod: %w", err))
+ return admission.Errored(500, fmt.Errorf("marshal mutated object: %w", err))
}
- logger.V(1).Info("mutated pod", "namespace", obj.GetNamespace(), "name", obj.GetName())
+ logger.V(1).Info("mutated object", "namespace", obj.GetNamespace(), "name", obj.GetName())📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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...) | |
| } | |
| mutated, err := json.Marshal(obj) | |
| if err != nil { | |
| return admission.Errored(500, fmt.Errorf("marshal mutated object: %w", err)) | |
| } | |
| logger.V(1).Info("mutated object", "namespace", obj.GetNamespace(), "name", obj.GetName()) | |
| return admission.PatchResponseFromRaw(req.Object.Raw, mutated).WithWarnings(warn...) | |
| } |
🤖 Prompt for AI Agents
In internal/lineagecontrollerwebhook/webhook.go around lines 93 to 99, the log
and error messages refer to "pod" but the code operates on a generic object;
change the wording to "object" (e.g., update the error from "marshal mutated
pod: %w" to "marshal mutated object: %w" and the logger message from "mutated
pod" to "mutated object") so logs/errors are accurate; update both strings
accordingly and run a quick build to ensure no formatting issues.
| @@ -1 +1 @@ | |||
| ghcr.io/cozystack/cozystack/cluster-autoscaler:0.29.1@sha256:2d39989846c3579dd020b9f6c77e6e314cc81aa344eaac0f6d633e723c17196d | |||
| ghcr.io/cozystack/cozystack/cluster-autoscaler:latest@sha256:89f822343654ea66efb3ac50bf72b483a52c1a11d33497fdfac5bbd0f3715c2b | |||
There was a problem hiding this comment.
🧩 Analysis chain
Cluster Autoscaler must match the cluster minor version; don’t use latest.
Pin to the autoscaler version recommended for your Kubernetes minor (e.g., CA 1.29.x for K8s 1.29). Using latest can break scale operations.
🌐 Web query:
What is the version skew/matrix between Kubernetes and Cluster Autoscaler (which CA version should be used for each K8s minor)?
💡 Result:
Short answer: use the Cluster Autoscaler release whose minor version matches your Kubernetes control‑plane minor (i.e. for Kubernetes 1.X run cluster‑autoscaler v1.X). This is the recommended / tested pairing; running other combinations is possible in some cases but not guaranteed. [1][2][3]
Sources:
- Cluster Autoscaler README / recommendations. [1]
- Historic compatibility table / guidance (tested-on combinations; CA minor == K8s minor). [2]
- Issues/discussions where maintainers and users state CA releases should follow the Kubernetes minor release. [3]
If you want, I can produce a concrete per‑minor table for specific Kubernetes versions you care about (e.g. 1.24 → CA v1.24, 1.25 → CA v1.25, etc.).
Pin Cluster Autoscaler to the Kubernetes minor version — do not use latest
Cluster Autoscaler releases are tested per Kubernetes minor; use CA v1.X for K8s 1.X (e.g., K8s 1.29 → cluster-autoscaler v1.29.x). Replace the image in packages/apps/kubernetes/images/cluster-autoscaler.tag (line 1) with the CA release tag/digest that matches your control‑plane minor.
🤖 Prompt for AI Agents
In packages/apps/kubernetes/images/cluster-autoscaler.tag around line 1, the
image references "latest" which must be pinned to a Cluster Autoscaler release
that matches your Kubernetes control-plane minor; determine the control-plane
minor (e.g., 1.29), pick the corresponding Cluster Autoscaler v1.<minor>.x
release (from the official autoscaler GitHub releases or GHCR), get its exact
image tag or digest
(ghcr.io/cozystack/cozystack/cluster-autoscaler:v1.<minor>.<patch>@sha256:<digest>),
and replace the current line with that exact tag@sha256 value so the CA version
is tied to your K8s minor.
| @@ -1 +1 @@ | |||
| ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:899ea667b3e575244d512cade23f30cc93d768b070f9c2bebcb440e443444bdb | |||
| ghcr.io/cozystack/cozystack/s3manager:latest@sha256:377bc82c8404ae1ea201d8b9a64015952edfa06d88fb2678b7bf7c45b96fb968 | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion
🧩 Analysis chain
Avoid using the 'latest' tag; pin by digest-only or version+digest for reproducibility
Using :latest undermines clarity and can confuse tooling, even though the digest ensures immutability. Prefer either digest-only or a semantic version tag plus the digest.
Apply this diff to remove the latest tag while preserving the exact image:
-ghcr.io/cozystack/cozystack/s3manager:latest@sha256:377bc82c8404ae1ea201d8b9a64015952edfa06d88fb2678b7bf7c45b96fb968
+ghcr.io/cozystack/cozystack/s3manager@sha256:377bc82c8404ae1ea201d8b9a64015952edfa06d88fb2678b7bf7c45b96fb968If you prefer human-readable versioning, replace latest with the actual release tag that corresponds to this digest (and keep the digest).
Run this to find any other latest usages to correct:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find image refs using ':latest' (with or without digest)
rg -nP ':[^@\s]*latest(@sha256:[0-9a-f]{64})?' -C0Length of output: 54271
Remove the :latest tag — pin by digest-only or version+digest
Apply this diff in packages/system/bucket/images/s3manager.tag to preserve the exact image:
-ghcr.io/cozystack/cozystack/s3manager:latest@sha256:377bc82c8404ae1ea201d8b9a64015952edfa06d88fb2678b7bf7c45b96fb968
+ghcr.io/cozystack/cozystack/s3manager@sha256:377bc82c8404ae1ea201d8b9a64015952edfa06d88fb2678b7bf7c45b96fb968Repository search shows multiple other :latest usages — run rg -nP ':[^@\s]*latest(@sha256:[0-9a-f]{64})?' -C0 and update other image refs similarly (use digest-only or version+digest).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ghcr.io/cozystack/cozystack/s3manager:latest@sha256:377bc82c8404ae1ea201d8b9a64015952edfa06d88fb2678b7bf7c45b96fb968 | |
| ghcr.io/cozystack/cozystack/s3manager@sha256:377bc82c8404ae1ea201d8b9a64015952edfa06d88fb2678b7bf7c45b96fb968 |
🤖 Prompt for AI Agents
In packages/system/bucket/images/s3manager.tag lines 1-1, the image reference
includes the :latest tag alongside a digest; remove the :latest tag so the image
is pinned by digest-only (e.g.,
ghcr.io/cozystack/cozystack/s3manager@sha256:377bc82c8404ae1ea201d8b9a64015952edfa06d88fb2678b7bf7c45b96fb968)
or replace with an explicit version+digest if you want a version label. Also
search the repo for other occurrences with rg -nP
':[^@\s]*latest(@sha256:[0-9a-f]{64})?' and update those image refs similarly to
use digest-only or version+digest, then run tests/build to verify no regressions
and commit the changes.
| @@ -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 | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion
🧩 Analysis chain
Verify chart defaults: imagePullPolicy and appVersion alignment.
Ensure the chart does not implicitly set imagePullPolicy: Always and that Chart.yaml appVersion matches the intended API version corresponding to this digest.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check imagePullPolicy in cozystack-api chart and templates =="
rg -n -C2 -i '\bimagePullPolicy\b' packages/system/cozystack-api || echo "No explicit imagePullPolicy found."
echo
echo "== Show Chart.yaml version and appVersion for cozystack-api =="
fd -a Chart.yaml packages/system/cozystack-api | while read -r f; do
echo "--- $f"
rg -n '^(version|appVersion):' -n "$f" || true
doneLength of output: 329
Ensure imagePullPolicy is explicit and Chart.appVersion is set
- No explicit imagePullPolicy found; values image contains ":latest" (with a digest) so Kubernetes will default pullPolicy to Always. Add image.pullPolicy: IfNotPresent (and make templates use {{ .Values.image.pullPolicy | default "IfNotPresent" }}) or remove the ":latest" when pinning by digest — packages/system/cozystack-api/values.yaml and packages/system/cozystack-api/templates/.
- Chart.yaml lacks appVersion (only has version: 0.0.0 placeholder). Add appVersion matching the API version for this pinned digest — packages/system/cozystack-api/Chart.yaml.
🤖 Prompt for AI Agents
In packages/system/cozystack-api/values.yaml around line 2, add an explicit
image.pullPolicy key (set to IfNotPresent) or remove the ":latest" tag when
using a pinned digest so Kubernetes doesn't implicitly use Always; also update
the chart templates under packages/system/cozystack-api/templates to reference
the pullPolicy via {{ .Values.image.pullPolicy | default "IfNotPresent" }}. In
packages/system/cozystack-api/Chart.yaml add an appVersion field matching the
API version for the pinned digest (set appVersion to the semantic version or tag
corresponding to that digest) so Chart.appVersion is populated. Ensure both
changes are committed together.
| metadata: | ||
| name: lineage | ||
| annotations: | ||
| cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/cozystack-controller-webhook |
There was a problem hiding this comment.
🧹 Nitpick
Quote the CA injection annotation to avoid YAML/template parse issues.
Unquoted Go templates often trip YAML linters and some tooling. Quote the value.
Apply:
- cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/cozystack-controller-webhook
+ cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/cozystack-controller-webhook"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/cozystack-controller-webhook | |
| cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/cozystack-controller-webhook" |
🧰 Tools
🪛 YAMLlint (1.37.1)
[error] 6-6: syntax error: expected , but found ''
(syntax)
🤖 Prompt for AI Agents
In
packages/system/cozystack-controller/templates/mutatingwebhookconfiguration.yaml
around line 6, the cert-manager.io/inject-ca-from annotation value is unquoted
which can cause YAML/template parse or linter errors; wrap the templated value
in quotes (e.g., "..." ) so the entire annotation value is a quoted string,
preserving the template expression and avoiding parsing issues.
| 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 |
There was a problem hiding this comment.
Pin kube-ovn-plunger image; avoid latest.
Keep this aligned with a tested kube-ovn version to avoid drift between helpers and controllers.
🤖 Prompt for AI Agents
In packages/system/kubeovn-plunger/values.yaml around line 3, the image is
pinned to "latest" which can drift; replace the "latest" tag with the specific
kube-ovn-plunger version that matches your tested kube-ovn release and include
the corresponding sha256 digest (e.g.,
ghcr.io/.../kubeovn-plunger:vX.Y.Z@sha256:...) so the helper image is immutable
and aligned with controller versions; update the tag+digest, commit the new
values.yaml, and add a short comment noting the matched kube-ovn version for
future maintenance.
| 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 |
There was a problem hiding this comment.
Pin kube-ovn-webhook image; latest on networking webhooks is high risk.
Networking control-plane components should be pinned to a known-good version + digest. latest can lead to non-deterministic behavior and hard-to-debug outages.
🤖 Prompt for AI Agents
In packages/system/kubeovn-webhook/values.yaml around line 3, the image
reference uses the unstable "latest" tag with a digest which is risky for
networking control-plane components; replace the image value with a specific
released semantic version tag plus its corresponding digest (e.g.
ghcr.io/cozystack/cozystack/kubeovn-webhook:vX.Y.Z@sha256:<digest>) so the
deployment is pinned to a known-good immutable artifact, update any
documentation or Chart defaults that reference this value, and add a note or
automation (dependabot/cron) to track and propose controlled updates of the
tag+digest.
This patch expands the CozystackResourceDefinitions with new label selector fields to include and exclude secrets by their labelsets. This will enable application developers to selectively show or hide application secrets to and from end-users. ```release-note [platform] Add selectors for application secrets, offering developers an API to control secret visibility for end users. ``` Signed-off-by: Timofei Larkin <[email protected]>
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. ```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. ``` Signed-off-by: Timofei Larkin <[email protected]>
This patch populates existing CozystackResourceDefinitions with minimal working examples of secret selectors to take advantage of the newest revision of the ancestor tracking webhook. ```release-note [platform] Specify secret selectors for existing managed apps in their respective CozystackResourceDefinitions, which provides the last bit of information necessary for the lineage webhook to correctly mark secrets as user-facing or not. ``` Signed-off-by: Timofei Larkin <[email protected]>
95fd92a to
4620f7d
Compare
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
Summary by CodeRabbit
New Features
Bug Fixes
Chores