K8s manifests; view and edit
View any K8s resource as YAML and edit live with optimistic concurrency, validation, and dry-run support.
ZopDay exposes a live K8s manifest view and editor for every resource discovered on a registered cluster. View the running YAML, see diffs against your edited copy, and apply changes; all gated by Kubernetes RBAC and ZopDay’s own permission system.
View modes
The manifest drawer in K8sDetailDrawer supports three modes, switched via the toolbar:
| Mode | What it shows | Editable? |
|---|---|---|
| View | The full live manifest as YAML, rendered read-only | No |
| Edit | The manifest in an editable Monaco editor | Yes (gated by RBAC) |
| Diff | Side-by-side: original (live) vs modified (your changes) | View only |
Monaco is lazy-loaded the first time you open the editor; first-open takes ~500ms, subsequent opens are instant.
Reading a manifest
GET /resources/{resID}/live/manifestReturns the K8s object as JSON. The frontend converts to YAML via js-yaml for display. The discoverer owns the read path because read is its core job.
Secret data is stripped before display. A Secret object’s data field shows as [REDACTED] in the view. You can confirm a Secret exists and what keys it has, but the values never leave the cluster.
Server-managed fields are visible but greyed out in the editor; status, metadata.managedFields, metadata.resourceVersion, etc. Editing them is allowed but ignored on apply (they’re stripped server-side).
Editing a manifest
PUT /resources/{clusterResID}/children/{kind}/{namespace}/{name}/manifestLives in the provisioner’s k8sedit package (write path = provisioner; read path = discoverer; clean service split).
The full request body is your edited manifest as JSON. The endpoint:
- Validates identity:
name,namespace,kindin the body must match the path. Mismatch returns 400. - Strips server-managed fields:
status,metadata.managedFields, etc. removed before apply. - Blocks dangerous kinds: node, CustomResourceDefinition, Namespace, Secret, ClusterRole, ClusterRoleBinding, Role, RoleBinding. These are not editable through this surface; use the cloud console or
kubectl. - Supports server-side dry-run: pass
?dryRun=Allto validate without applying. Returns the mutated object as the cluster would have written it. - Optimistic concurrency: uses
resourceVersionfor conflict detection. If the live resource changed since you read it, returns 409 Conflict with the currentresourceVersion. Refresh and re-apply.
Why server-side apply
ZopDay uses dynamic client server-side apply for two reasons:
- Concurrent edits are detected: if your colleague edited the same Deployment 30 seconds before you, you get 409 with their changes visible in the diff, not a silent overwrite.
- CRD-managed fields are preserved: fields owned by other controllers (Argo, FluxCD, GitOps controllers) survive a ZopDay edit because server-side apply respects field ownership.
Dangerous-kind blocklist
These K8s kinds cannot be edited through ZopDay’s manifest editor:
| Kind | Why |
|---|---|
| Node | Editing a node spec can break the cluster autoscaler in non-obvious ways. Use the cloud console. |
| CustomResourceDefinition | Editing a CRD can break every CR in the cluster. Use kubectl deliberately. |
| Namespace | Namespace edits are rarely surgical and often a sign of something wrong elsewhere. |
| Secret | Edit secrets through your cloud secret manager + External Secrets Operator. Direct edits are too easy to bungle. |
| ClusterRole / ClusterRoleBinding | RBAC changes are sensitive. Use a GitOps workflow or kubectl with peer review. |
| Role / RoleBinding | Same. |
For dangerous kinds, the View mode still works; the Edit button is disabled with a tooltip pointing at the reason.
This blocklist governs the Edit (PUT) surface. The Create (POST children) and Delete (DELETE children) routes below are not gated by the same static kind list; they are governed by ZopDay RBAC and Kubernetes RBAC, so creating or deleting a sensitive kind (Secret, Node, CRD, and the rest) through those routes succeeds only if both permission layers allow it. Treat those operations with the same caution, or use kubectl / a GitOps workflow.
Create a new K8s resource
POST /resources/{clusterResID}/childrenCreates a new K8s resource via dynamic client. Returns 409 if the resource already exists at that (kind, namespace, name). Useful for one-off resource creation outside the Helm / ZopDay deploy flow; e.g. creating a CR for a controller that ZopDay’s catalog doesn’t know about yet.
Delete a K8s resource
DELETE /resources/{clusterResID}/children/{kind}/{namespace}/{name}Idempotent; success if the resource was deleted, success if it was already gone, error only if delete itself fails (e.g. RBAC denial, finalizer hang).
For resources with finalizers that are stuck, the delete request times out after 30 seconds and returns 504. You then need to remove the finalizer manually or via kubectl.
RBAC gating
Two layers of permission:
- ZopDay RBAC: the user must have
resource:updatepolicy in their role. The frontend gates the Edit button on this. - Kubernetes RBAC: when the manifest is applied, it goes through Kubernetes API server authorisation. If ZopDay’s kubeauth principal doesn’t have permission to update this kind in this namespace, the server returns 403.
Both must succeed. ZopDay’s policy is checked first (fast, in-process); the cluster’s RBAC is checked on apply (slower, network round-trip).
Dry-run from the UI
Before applying a manifest change, click Dry Run in the editor toolbar. This calls the PUT endpoint with ?dryRun=All. The result is shown in the right pane:
- ✅ Green: the cluster accepts the change. Mutations show in the diff.
- ⚠️ Yellow: validation warnings (e.g. PSP / PSS admission complaints). Apply will succeed but pods may not schedule.
- ❌ Red: the cluster rejects the change. The error message tells you why (RBAC, validation, conflict).
Dry-run is free; no actual mutation happens. Use it liberally before applying real changes.
Audit trail
Every successful Create / Update / Delete via the manifest endpoints:
- Writes an audit log row (actor, action, resource, body diff, timestamp)
- Surfaces on the resource’s history tab in the UI
- Forwardable to SIEM via the Enterprise tier audit feed
The audit row includes the full request body and the full response body: useful for compliance review of “who changed what.” Bodies over the audit size cap are truncated with a marker (consistent with Audit logs), so very large manifests may not be captured in full. For Secret kinds (which can’t be edited through this UI anyway), the audit log would not contain the secret value because the data is stripped server-side before storage.
CLI access
For automation, the same endpoints are callable via curl / SDK:
curl -X PUT https://api.zop.dev/v1/resources/$RES_ID/children/Deployment/payments-prod/api-server/manifest \ -H "Authorization: Bearer $ZOPDAY_PAT" \ -H "Content-Type: application/json" \ -d @manifest.jsonThe PAT carries your role’s policies. RBAC is identical to UI access.
Limitations
A few things the manifest editor cannot do today:
- Edit across clusters in one operation: every edit is scoped to one resource on one cluster. For multi-cluster operations, use a GitOps tool.
- Edit by selector: you can’t say “edit every Deployment with label
app=payments”. One resource at a time. - Diff against an arbitrary historical version: diff is live vs. your edits, not vs. previous revisions. For deploy revisions, use the deployment history.