Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions api/cloud/common/resource_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ import (
//
// Ref: https://github.com/temporalio/cloud-api/blob/main/temporal/api/cloud/resource/v1/message.proto
type ResourceStatus struct {
// ID is the identifier Temporal Cloud uses for the resource. Not set for
// resources that Temporal Cloud identifies by name.
// Maps to Temporal Cloud proto `id`, or `Namespace.namespace` for
// Namespaces.
// Cloud describes the resource as reported by Temporal Cloud.
// +optional
ID string `json:"id,omitempty"`
Cloud CloudStatus `json:"cloud,omitzero"`
// ResourceVersion is the version of the resource in Temporal Cloud. It is
// sent with each update and delete request.
// Maps to Temporal Cloud proto `resource_version`.
Expand All @@ -42,6 +39,17 @@ type ResourceStatus struct {
LastModifiedTime *metav1.Time `json:"lastModifiedTime,omitempty"`
}

// CloudStatus describes the resource as reported by Temporal Cloud.
type CloudStatus struct {
// ID is the identifier Temporal Cloud uses for the resource. Not set for
// resources that Temporal Cloud identifies by name.
// Maps to Temporal Cloud proto `id`, or `Namespace.namespace` for
// Namespaces.
// +optional
ID string `json:"id,omitempty"`
// TODO: Add identifier fields for referenced CRs
}

// ResourceState is the state of a resource in Temporal Cloud.
// Maps to Temporal Cloud proto `ResourceState`.
// +kubebuilder:validation:Enum=Activating;ActivationFailed;Active;Updating;UpdateFailed;Deleting;DeleteFailed;Deleted;Suspended;Expired
Expand Down
17 changes: 17 additions & 0 deletions api/cloud/common/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions api/cloud/v1alpha1/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="ID",type=string,JSONPath=`.status.id`
// +kubebuilder:printcolumn:name="ID",type=string,JSONPath=`.status.cloud.id`
// +kubebuilder:printcolumn:name="State",type=string,JSONPath=`.status.state`
// +kubebuilder:printcolumn:name="Region",type=string,JSONPath=`.spec.replicas[0].region`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
Expand All @@ -43,7 +43,7 @@ func (n *Namespace) FromProto(in *tcloudnamespacev1.Namespace) {
n.Spec.Project = in.GetProjectId()
n.Spec.Tags = maps.Clone(in.GetTags())

n.Status.ID = in.GetNamespace()
n.Status.Cloud.ID = in.GetNamespace()
n.Status.ResourceVersion = in.GetResourceVersion()
n.Status.State.FromProto(in.GetState())
n.Status.AsyncOperationID = in.GetAsyncOperationId()
Expand Down
2 changes: 1 addition & 1 deletion api/cloud/v1alpha1/namespace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func TestNamespaceFromProto_Status(t *testing.T) {
LastModifiedTime: timestamppb.New(modified),
},
exp: cloudcommon.ResourceStatus{
ID: "my-ns.acct1",
Cloud: cloudcommon.CloudStatus{ID: "my-ns.acct1"},
ResourceVersion: "rv-1",
State: cloudcommon.ResourceStateActive,
AsyncOperationID: "op-1",
Expand Down
22 changes: 11 additions & 11 deletions controller/namespace/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (r *Reconciler) ResourceDelete(

latest := original.DeepCopy()
cr := latest.CR().(*tcloudv1alpha1.Namespace)
if cr.Status.ID == "" {
if cr.Status.Cloud.ID == "" {
// The user deleted the CR before the operator created a Namespace in
// Temporal Cloud, so there's nothing to delete.
return latest, nil
Expand All @@ -70,7 +70,7 @@ func (r *Reconciler) ResourceDelete(
if policy == tcloudcommon.DeletionPolicyRetain {
log.Info(
"retaining Temporal Cloud Namespace because of the retain deletion policy",
slog.String("namespace_id", cr.Status.ID),
slog.String("namespace_id", cr.Status.Cloud.ID),
slog.String("deletion_policy", string(policy)),
)
return latest, nil
Expand All @@ -91,7 +91,7 @@ func (r *Reconciler) ResourceDelete(
// A previous delete failed. Don't send another one automatically.
err = ctrlerrors.NewTerminalError(fmt.Errorf(
"deleting Temporal Cloud namespace %s previously failed (async operation %s); not retrying",
cr.Status.ID, cr.Status.AsyncOperationID,
cr.Status.Cloud.ID, cr.Status.AsyncOperationID,
))
return latest, err
case cr.Status.State == tcloudcommon.ResourceStateDeleting &&
Expand All @@ -105,7 +105,7 @@ func (r *Reconciler) ResourceDelete(
err = requeue.NeededAfter(
fmt.Errorf(
"waiting for tcloud namespace %s to finish %s before deleting",
cr.Status.ID, cr.Status.State,
cr.Status.Cloud.ID, cr.Status.State,
),
requeue.DefaultRequeueAfterDuration,
)
Expand All @@ -120,7 +120,7 @@ func (r *Reconciler) ResourceDelete(
"tcloud namespace %s has deletion protection enabled; set "+
"spec.lifecycle.enableDeleteProtection to false to delete it, "+
"or set the %s annotation to %q to keep it",
cr.Status.ID, tcloudcommon.AnnotationKeyDeletionPolicy,
cr.Status.Cloud.ID, tcloudcommon.AnnotationKeyDeletionPolicy,
tcloudcommon.DeletionPolicyRetain,
)
condition.SetStalled(
Expand All @@ -133,15 +133,15 @@ func (r *Reconciler) ResourceDelete(
// Read the namespace so deletion protection in Temporal Cloud can be made
// to match the CR, and so the delete uses the current resource version.
getResp, err := tcc.GetNamespace(ctx, &tcloudservicev1.GetNamespaceRequest{
Namespace: cr.Status.ID,
Namespace: cr.Status.Cloud.ID,
})
if status.Code(err) == codes.NotFound {
// Already gone.
err = nil
return latest, nil
}
if err != nil {
err = fmt.Errorf("failed getting tcloud namespace %s: %w", cr.Status.ID, err)
err = fmt.Errorf("failed getting tcloud namespace %s: %w", cr.Status.Cloud.ID, err)
return latest, err
}
tns := getResp.GetNamespace()
Expand All @@ -154,7 +154,7 @@ func (r *Reconciler) ResourceDelete(
}

resp, err := tcc.DeleteNamespace(ctx, &tcloudservicev1.DeleteNamespaceRequest{
Namespace: cr.Status.ID,
Namespace: cr.Status.Cloud.ID,
ResourceVersion: tns.GetResourceVersion(),
})
if status.Code(err) == codes.NotFound {
Expand All @@ -165,7 +165,7 @@ func (r *Reconciler) ResourceDelete(
if err != nil {
// The gRPC error includes Temporal Cloud's message, for example that
// deletion protection is enabled.
err = fmt.Errorf("failed deleting tcloud namespace %s: %w", cr.Status.ID, err)
err = fmt.Errorf("failed deleting tcloud namespace %s: %w", cr.Status.Cloud.ID, err)
condition.SetStalled(
latest, metav1.ConditionTrue, err.Error(), tcloudcommon.ReasonResourceDeleteFailed,
)
Expand All @@ -177,7 +177,7 @@ func (r *Reconciler) ResourceDelete(
err = requeue.NeededAfter(
fmt.Errorf(
"deleting tcloud namespace %s (async operation %s)",
cr.Status.ID, cr.Status.AsyncOperationID,
cr.Status.Cloud.ID, cr.Status.AsyncOperationID,
),
requeue.DefaultRequeueAfterDuration,
)
Expand Down Expand Up @@ -242,7 +242,7 @@ func checkDelete(
operationv1.AsyncOperation_STATE_CANCELLED:
msg := fmt.Sprintf(
"deleting tcloud namespace %s failed: async operation %s is %s: %s",
cr.Status.ID, op.GetId(), op.GetState(), op.GetFailureReason(),
cr.Status.Cloud.ID, op.GetId(), op.GetState(), op.GetFailureReason(),
)
condition.SetStalled(
latest, metav1.ConditionTrue, msg, tcloudcommon.ReasonResourceDeleteFailed,
Expand Down
6 changes: 3 additions & 3 deletions controller/namespace/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,20 @@ const (

var (
statusActive = tcloudcommon.ResourceStatus{
ID: deleteTestID,
Cloud: tcloudcommon.CloudStatus{ID: deleteTestID},
ResourceVersion: deleteTestRV,
State: tcloudcommon.ResourceStateActive,
}

statusDeleting = tcloudcommon.ResourceStatus{
ID: deleteTestID,
Cloud: tcloudcommon.CloudStatus{ID: deleteTestID},
ResourceVersion: deleteTestRV,
State: tcloudcommon.ResourceStateDeleting,
AsyncOperationID: deleteTestAsyncOpID,
}

statusDeleteFailed = tcloudcommon.ResourceStatus{
ID: deleteTestID,
Cloud: tcloudcommon.CloudStatus{ID: deleteTestID},
ResourceVersion: deleteTestRV,
State: tcloudcommon.ResourceStateDeleteFailed,
AsyncOperationID: deleteTestAsyncOpID,
Expand Down
8 changes: 4 additions & 4 deletions controller/namespace/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (r *Reconciler) ResourceSetLatest(
cr := latest.(*res).cr

var tns *tcloudnamespacev1.Namespace
if cr.Status.ID == "" {
if cr.Status.Cloud.ID == "" {
// The qualified namespace name is only known once a reconcile after
// the create has saved it to status, and status can be lost (e.g. the
// CR was recreated). Look the namespace up by name so that a duplicate
Expand All @@ -128,7 +128,7 @@ func (r *Reconciler) ResourceSetLatest(
} else {
var resp *tcloudservicev1.GetNamespaceResponse
resp, err = tcc.GetNamespace(ctx, &tcloudservicev1.GetNamespaceRequest{
Namespace: cr.Status.ID,
Namespace: cr.Status.Cloud.ID,
})
if status.Code(err) == codes.NotFound {
// The namespace was deleted outside of the operator. Treat it
Expand Down Expand Up @@ -213,7 +213,7 @@ func syncNamespace(
desired *tcloudv1alpha1.Namespace,
latest *tcloudv1alpha1.Namespace,
) error {
if latest.Status.ID == "" {
if latest.Status.Cloud.ID == "" {
tns := desired.ToProto()
resp, err := tcc.CreateNamespace(ctx, &tcloudservicev1.CreateNamespaceRequest{
Spec: tns.GetSpec(),
Expand All @@ -232,7 +232,7 @@ func syncNamespace(
// Only an Active namespace accepts new operations. Every other state
// either resolves on its own, so we wait, or needs someone to step in, so
// we stop. Unknown states also wait rather than fall through to an update.
id := latest.Status.ID
id := latest.Status.Cloud.ID
switch state := latest.Status.State; state {
case cloudcommon.ResourceStateActive:
case cloudcommon.ResourceStateActivating, cloudcommon.ResourceStateUpdating:
Expand Down
8 changes: 4 additions & 4 deletions controller/namespace/reconcile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func TestReconciler_ResourceSetLatest(t *testing.T) {
project := "tmprl-project"
projectOther := "tmprl-project-other"

// tns has never been synced, so it has no Status.ID and
// tns has never been synced, so it has no Status.Cloud.ID and
// ResourceSetLatest looks the namespace up by name.
tns := &tcloudv1alpha1.Namespace{}
tns.Name = tnsName
Expand All @@ -105,9 +105,9 @@ func TestReconciler_ResourceSetLatest(t *testing.T) {
tns.Spec.RetentionDays = 7

// tnsSynced has been synced before, so ResourceSetLatest gets the
// namespace by its Status.ID.
// namespace by its Status.Cloud.ID.
tnsSynced := tns.DeepCopy()
tnsSynced.Status.ID = tnsID
tnsSynced.Status.Cloud.ID = tnsID
tnsSynced.Status.ResourceVersion = "rv-0"

// cloudNS is the namespace as Temporal Cloud reports it. Its project
Expand Down Expand Up @@ -146,7 +146,7 @@ func TestReconciler_ResourceSetLatest(t *testing.T) {
RetentionDays: 30,
}
ns.Status.ResourceStatus = cloudcommon.ResourceStatus{
ID: tnsID,
Cloud: cloudcommon.CloudStatus{ID: tnsID},
ResourceVersion: "rv-1",
State: cloudcommon.ResourceStateActive,
AsyncOperationID: "op-1",
Expand Down
21 changes: 13 additions & 8 deletions deploy/kustomize/crd/bases/cloud.temporal.io_namespaces.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.id
- jsonPath: .status.cloud.id
name: ID
type: string
- jsonPath: .status.state
Expand Down Expand Up @@ -366,6 +366,18 @@ spec:
started on the resource.
Maps to Temporal Cloud proto `async_operation_id`.
type: string
cloud:
description: Cloud describes the resource as reported by Temporal
Cloud.
properties:
id:
description: |-
ID is the identifier Temporal Cloud uses for the resource. Not set for
resources that Temporal Cloud identifies by name.
Maps to Temporal Cloud proto `id`, or `Namespace.namespace` for
Namespaces.
type: string
type: object
conditions:
description: Conditions is the collection of Conditions associated
with the resource.
Expand Down Expand Up @@ -433,13 +445,6 @@ spec:
Maps to Temporal Cloud proto `created_time`.
format: date-time
type: string
id:
description: |-
ID is the identifier Temporal Cloud uses for the resource. Not set for
resources that Temporal Cloud identifies by name.
Maps to Temporal Cloud proto `id`, or `Namespace.namespace` for
Namespaces.
type: string
lastModifiedTime:
description: |-
LastModifiedTime is when the resource was last modified in Temporal
Expand Down
7 changes: 4 additions & 3 deletions docs/crd-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ For the fields a CR manages, its `spec` is the source of truth. When the control

## Example Namespace CR

This manifest illustrates the proposed CR shape. The `status` field names `cloudID` and `cloudResourceVersion` are placeholders for the Cloud reference fields, whose names are not yet decided. All values are illustrative.
This manifest illustrates the proposed CR shape. The Cloud reference fields are grouped under `status.cloud`. All values are illustrative.

```yaml
apiVersion: cloud.temporal.io/v1alpha1
Expand All @@ -70,8 +70,9 @@ spec:
onDemand: {}
status:
# Cloud reference
cloudID: payments-prod.a1b2c
cloudResourceVersion: "4"
cloud:
id: payments-prod.a1b2c
resourceVersion: "4"
# Progress
observedGeneration: 1
conditions:
Expand Down
Loading