Webhook Validation Rules

Detailed validation rules for Porch webhooks

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.type
  • spec.git.branch
  • spec.git.createBranch
  • spec.git.directory
  • spec.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.


See Also