Skip to content

feat(admission): validate adoption annotations with admission policies - #64

Open
tomba7 wants to merge 1 commit into
mainfrom
tomba/validate-adoption-annotations
Open

tomba7 wants to merge 1 commit into
mainfrom
tomba/validate-adoption-annotations

Conversation

@tomba7

@tomba7 tomba7 commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

Part of #56

Summary

  • Add two admission policies, installed by default.
    • The common resource CR VAP holds the adoption rules for every Temporal Cloud resource kind
      • The adoption policy must be adopt or adopt-or-create
      • Adoption and cloud.temporal.io/resource-id must be set together
      • Spec is required unless the policy is adopt.
    • The namespace CR VAP requires replicas and retentionDays whenever spec is set.
      • Make the namespace spec, replicas and retentionDays optional in the CRD so an adopted namespace can omit spec.
      • The admission policies enforce those requirements.
  • Added examples under examples/adoption for each allowed and rejected case.

Testing

Note: A follow up PR will add GDT tests for the following manual testing

$ kubectl apply -f examples/adoption/namespace-adopt-without-spec.yaml
namespace.cloud.temporal.io/adopt-without-spec created

$ kubectl apply -f examples/adoption/namespace-adopt-or-create.yaml
namespace.cloud.temporal.io/adopt-or-create created

$ kubectl apply -f examples/adoption/namespace-invalid-adoption-policy.yaml
The namespaces "invalid-adoption-policy" is invalid: : ValidatingAdmissionPolicy 'temporal-cloud-resource-validation' with binding 'temporal-cloud-resource-validation' denied request: metadata.annotations[cloud.temporal.io/adoption-policy]: Unsupported value: "create-new": supported values: "adopt", "adopt-or-create"

$ kubectl apply -f examples/adoption/namespace-adopt-without-resource-id.yaml
The namespaces "adopt-without-resource-id" is invalid: : ValidatingAdmissionPolicy 'temporal-cloud-resource-validation' with binding 'temporal-cloud-resource-validation' denied request: metadata.annotations[cloud.temporal.io/resource-id]: Required value when the cloud.temporal.io/adoption-policy annotation is "adopt" or "adopt-or-create"

$ kubectl apply -f examples/adoption/namespace-adopt-or-create-without-spec.yaml
The namespaces "adopt-or-create-without-spec" is invalid: : ValidatingAdmissionPolicy 'temporal-cloud-resource-validation' with binding 'temporal-cloud-resource-validation' denied request: spec: Required value unless the cloud.temporal.io/adoption-policy annotation is "adopt"

$ kubectl apply -f examples/adoption/namespace-adopt-with-spec.yaml
The namespaces "adopt-with-spec" is invalid: : ValidatingAdmissionPolicy 'temporal-cloud-namespace-validation' with binding 'temporal-cloud-namespace-validation' denied request: spec.replicas, spec.retentionDays: Required value; to adopt the Namespace as is, omit spec

@tomba7
tomba7 requested a review from jaypipes as a code owner November 4, 2026 22:10

@nmechack27 nmechack27 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this pr up so quickly. I left a couple of comments but overall looks good!

// Replicas are the regions where the Namespace is placed.
// Maps to Temporal Cloud proto `NamespaceSpec.replicas`.
// +required
// Required and is enforced by the temporal-cloud-namespace-validation admission policy.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Required and is enforced by the temporal-cloud-namespace-validation admission policy.
// Required unless the cloud.temporal.io/adoption-policy annotation is
// "adopt"; enforced by the temporal-cloud-namespace-validation admission
// policy, and by the operator before it creates or updates a Namespace.

// Maps to Temporal Cloud proto `NamespaceSpec.retention_days`.
// +required
RetentionDays int32 `json:"retentionDays"`
// Required and is enforced by the temporal-cloud-namespace-validation admission policy.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Required and is enforced by the temporal-cloud-namespace-validation admission policy.
// Required unless the cloud.temporal.io/adoption-policy annotation is
// "adopt"; enforced by the temporal-cloud-namespace-validation admission
// policy, and by the operator before it creates or updates a Namespace.

expression: >-
!has(object.spec) ? [] :
(has(object.spec.replicas) ? [] : ['spec.replicas']) +
(has(object.spec.retentionDays) ? [] : ['spec.retentionDays'])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would let retentionDays: 0 through. We can do object.spec.retentionDays > 0 here.

Comment on lines +18 to +19
- name: resourceID
expression: "object.?metadata.annotations['cloud.temporal.io/resource-id'].orValue('')"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping resource-id here makes sense for resources that need an ID to adopt. For namespaces, though, adoption path looks the Namespace up by name in ResourceSetLatest, so it shouldn't require resource-id. Since this policy currently matches namespaces, the "resource-id is required when adopting" rule would reject a namespace adopt CR without one.

I think this should either move the resource-id rules into the per-resource policies and leave them out of namespace_validation_policy.yaml, or skip them for namespaces here (e.g. `request.resource.resource == 'namespaces' || ...)?

Comment on lines +22 to +28
- expression: >-
!variables.adoptionPolicy.hasValue() ||
variables.adoptionPolicy.value() in ['adopt', 'adopt-or-create']
messageExpression: >-
'metadata.annotations[cloud.temporal.io/adoption-policy]: Unsupported value: "' +
variables.adoptionPolicy.value() + '": supported values: "adopt", "adopt-or-create"'
reason: Invalid

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My pr only supports adopt for now so adopt-or-create would be treated as an invalid value. We can add it when it lands.

Comment on lines +14 to +17
- name: adoptionPolicy
expression: "object.?metadata.annotations['cloud.temporal.io/adoption-policy']"
- name: adopt
expression: "variables.adoptionPolicy.orValue('') == 'adopt'"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The operator lowercases and trims the annotation before validating so Adopt or adopt work here but these comparisons are exact the policy would reject them.
There's probably a way to by case sensitivity here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nmechack27 we could lowercase the variable adoptionPolicy by changing its expression to:

    expression: "object.?metadata.annotations['cloud.temporal.io/adoption-policy'].lowerAscii()"

@@ -0,0 +1,3 @@
resources:
- cloud_resource_validation_policy.yaml
- namespace_validation_policy.yaml

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this admission/ folder included in the top-level kustomization that make install uses?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants