Webhook Validation Rules
Webhooks validate resources at admission time, before they’re written to Kubernetes etcd. This means invalid configurations are rejected immediately, preventing invalid state from entering the system. The validation rules below are enforced by two webhooks: one for PackageRevision v1alpha2 resources and one for Repository resources.
PackageRevision Validation
CREATE
When creating a PackageRevision, the webhook validates:
Repository must exist in the same namespace and have the porch.kpt.dev/v1alpha2-migration: "true" annotation. If missing, you’ll see the Repository {namespace}/{name} not found or not enabled for v1alpha2 error messages. Add the annotation to the Repository resource.
Lifecycle must be Draft or Proposed. Use Draft for work-in-progress or Proposed to request approval immediately. Creating directly as Published or DeletionProposed is rejected.
Source must specify exactly one of: init (new), cloneFrom (clone), copyFrom (copy), or upgrade (upgrade). You’ll get an error if none or multiple are set.
Workspace must be unique within the package. If you receive the workspace 'drafting' already exists for package 'example-package' error message, choose a different name or delete the existing draft.
UPDATE
Immutable fields (spec.source, spec.packageName, spec.repository, spec.workspaceName) cannot be changed after creation. If you need to change these, delete and recreate the PackageRevision.
Lifecycle transitions must follow the valid state machine: Draft → Proposed → Published → DeletionProposed, and Published ↔ DeletionProposed. By default, Draft cannot skip to Published; enable direct publication with --allow-direct-publication-from-draft on porch-server and --packagerevisions.allow-direct-publication-from-draft on porch-controllers (both must match). You cannot downgrade (e.g., Published back to Draft).
Render race prevention blocks lifecycle changes while render is in progress or before content has been rendered. Wait for rendering to complete or push new content before changing lifecycle.
DELETE
Upstream references prevent deletion if other PackageRevisions reference this one as upstream (via cloneFrom or upgrade). Delete referencing packages first, or carefully use cascade deletion.
Published packages require moving to DeletionProposed first via a lifecycle transition, then delete. Draft packages can be deleted immediately.
Repository Validation
CREATE/UPDATE
Git conflict detection prevents multiple repositories from claiming the same git location (URL + branch + directory). If you see the Repository conflict with existing repository: {namespace}/{name} error message, use a different branch, directory, or namespace.
Directory nesting is also prevented. A root directory conflicts with any subdirectory under the same URL+branch, and subdirectories cannot be nested under each other. For example, you cannot have both / and /foo or both /foo and /foo/bar for the same URL+branch combination.
OCI repositories don’t have these constraints since they’re endpoint-based rather than directory-based. Multiple OCI repositories can use the same URL if they reference different endpoints.
UPDATE
Immutable fields are enforced by CEL validation rules on the Repository CRD. The following fields cannot be changed after creation; attempting to modify them is rejected at admission time with a spec.<field> is immutable error:
spec.typespec.git.branchspec.git.createBranchspec.git.directoryspec.oci.registry
To change an immutable field, delete and recreate the Repository resource.
spec.git.repo is mutable. Changing the Git URL rehomes the repository to a different remote. The URL change is still subject to the webhook’s cross-resource conflict checks: RepositoryValidator.Handle runs on UPDATE as well as CREATE, so a new URL (together with the branch and directory) that duplicates or nests with another Repository’s Git location in the same namespace is rejected at admission time, exactly as on create. What is not checked at admission is history compatibility — whether the new remote is the same repository Porch was already tracking. That is validated asynchronously during the next repository sync: the commit recorded in status.gitCommitHash must still be present in the history of the configured branch on the new remote (it need not be the new HEAD — the branch may have moved forward). If the recorded commit is not in the new branch’s history, the sync fails and the repository reports Ready=False / Reason=Error. See
Rehoming a Repository for the full behavior and
Repository Rehoming Fails After URL Change for troubleshooting.
Common Issues
Repository not found or not enabled for v1alpha2: Verify the repository exists with kubectl get repositories -n namespace. If it exists, add the migration annotation: kubectl annotate repository repo -n namespace porch.kpt.dev/v1alpha2-migration=true
Workspace already exists: Choose a different workspace name or delete the existing draft. List with kubectl get packagerevisions -n namespace to see what workspaces are in use.
Invalid lifecycle transition: The default path is Draft → Proposed → Published (porchctl rpkg propose then porchctl rpkg approve). Draft → Published is rejected unless --allow-direct-publication-from-draft is true on porch-server and --packagerevisions.allow-direct-publication-from-draft is true on porch-controllers.
Cannot change lifecycle during render: Wait for rendering to complete by checking kubectl describe packagerevision <name> -n namespace, or push new content and retry.
Repository conflict: List existing repositories with kubectl get repositories -n namespace and use a different branch, directory, or namespace for your new repository.
Performance
Webhooks timeout after 30 seconds per operation, with expected latency <100ms p99. If you see timeouts, check pod resources (CPU/memory), etcd performance, network latency to the webhook endpoint, and webhook logs for errors.