diff --git a/functions/oidcclient/010-state-status.yaml.gotmpl b/functions/oidcclient/010-state-status.yaml.gotmpl index 9daea77..e817538 100644 --- a/functions/oidcclient/010-state-status.yaml.gotmpl +++ b/functions/oidcclient/010-state-status.yaml.gotmpl @@ -1,9 +1,42 @@ # code: language=yaml {{- $observed := $.observed.resources | default dict }} + +# ProviderConfigs do not publish Ready conditions. Their templates opt into +# immediate composition readiness, so observed existence is their readiness +# signal for Usage creation. +{{- $kubernetesProviderConfigEntry := get $observed "kubernetes-providerconfig" | default dict }} +{{- $kubernetesProviderConfigResource := $kubernetesProviderConfigEntry.resource | default dict }} +{{- $kubernetesProviderConfigReady := not (empty (($kubernetesProviderConfigResource.metadata | default dict).name | default "")) }} + +{{- $credentialsEntry := get $observed "credentials" | default dict }} +{{- $credentialsResource := $credentialsEntry.resource | default dict }} +{{- $credentialsReady := false }} +{{- range (($credentialsResource.status | default dict).conditions | default list) }} + {{- if and (eq .type "Ready") (eq .status "True") }} + {{- $credentialsReady = true }} + {{- end }} +{{- end }} + +{{- $providerConfigEntry := get $observed "providerconfig" | default dict }} +{{- $providerConfigResource := $providerConfigEntry.resource | default dict }} +{{- $providerConfigReady := not (empty (($providerConfigResource.metadata | default dict).name | default "")) }} + {{- $oidcEntry := get $observed "oidc" | default dict }} {{- $oidcResource := $oidcEntry.resource | default dict }} {{- $atProvider := ($oidcResource.status | default dict).atProvider | default dict }} +{{- $oidcReady := false }} +{{- range (($oidcResource.status | default dict).conditions | default list) }} + {{- if and (eq .type "Ready") (eq .status "True") }} + {{- $oidcReady = true }} + {{- end }} +{{- end }} +{{- $_ := set $state "observed" (dict + "kubernetesProviderConfigReady" $kubernetesProviderConfigReady + "credentialsReady" $credentialsReady + "providerConfigReady" $providerConfigReady + "oidcReady" $oidcReady +) }} {{- $_ := set $state.status "applicationId" ($atProvider.id | default "") }} {{- $_ := set $state.status "clientId" ($atProvider.clientId | default "") }} diff --git a/functions/oidcclient/050-kubernetes-provider-config.yaml.gotmpl b/functions/oidcclient/050-kubernetes-provider-config.yaml.gotmpl index 19a023b..fd8e757 100644 --- a/functions/oidcclient/050-kubernetes-provider-config.yaml.gotmpl +++ b/functions/oidcclient/050-kubernetes-provider-config.yaml.gotmpl @@ -17,3 +17,29 @@ metadata: spec: credentials: source: InjectedIdentity + +{{- /* Keep provider-kubernetes available while it deletes the ExternalSecret + Object. ProviderConfigs are composition-ready once observed. */}} +{{- if and $state.observed.kubernetesProviderConfigReady $state.observed.credentialsReady }} +--- +apiVersion: protection.crossplane.io/v1beta1 +kind: Usage +metadata: + name: {{ printf "%s-delete-credentials-before-kubernetes-providerconfig" $state.name }} + namespace: {{ $state.namespace | quote }} + annotations: + {{ setResourceNameAnnotation "usage-credentials-kubernetes-providerconfig" }} + labels: {{ $state.labels | toJson }} +spec: + replayDeletion: true + of: + apiVersion: kubernetes.m.crossplane.io/v1alpha1 + kind: ProviderConfig + resourceRef: + name: {{ $state.kubernetesProviderConfigName | quote }} + by: + apiVersion: kubernetes.m.crossplane.io/v1alpha1 + kind: Object + resourceRef: + name: {{ $state.providerSecretName | quote }} +{{- end }} diff --git a/functions/oidcclient/100-bootstrap-credentials.yaml.gotmpl b/functions/oidcclient/100-bootstrap-credentials.yaml.gotmpl index 297d873..d33ab39 100644 --- a/functions/oidcclient/100-bootstrap-credentials.yaml.gotmpl +++ b/functions/oidcclient/100-bootstrap-credentials.yaml.gotmpl @@ -49,3 +49,29 @@ spec: providerConfigRef: name: {{ $state.kubernetesProviderConfigName | quote }} kind: ProviderConfig + +{{- /* The Zitadel ProviderConfig reads the Secret produced by this Object. + Keep that producer alive until the secret consumer is gone. */}} +{{- if and $state.observed.credentialsReady $state.observed.providerConfigReady }} +--- +apiVersion: protection.crossplane.io/v1beta1 +kind: Usage +metadata: + name: {{ printf "%s-delete-zitadel-providerconfig-before-credentials" $state.name }} + namespace: {{ $state.namespace | quote }} + annotations: + {{ setResourceNameAnnotation "usage-zitadel-providerconfig-credentials" }} + labels: {{ $state.labels | toJson }} +spec: + replayDeletion: true + of: + apiVersion: kubernetes.m.crossplane.io/v1alpha1 + kind: Object + resourceRef: + name: {{ $state.providerSecretName | quote }} + by: + apiVersion: zitadel.m.crossplane.io/v1beta1 + kind: ProviderConfig + resourceRef: + name: {{ $state.providerConfigName | quote }} +{{- end }} diff --git a/functions/oidcclient/200-provider-config.yaml.gotmpl b/functions/oidcclient/200-provider-config.yaml.gotmpl index 5475341..d6e0a66 100644 --- a/functions/oidcclient/200-provider-config.yaml.gotmpl +++ b/functions/oidcclient/200-provider-config.yaml.gotmpl @@ -17,3 +17,29 @@ spec: name: {{ $state.providerSecretName | quote }} namespace: {{ $state.namespace | quote }} key: credentials + +{{- /* Keep the Zitadel ProviderConfig and its credentials available while the + provider deletes the external OIDC application. */}} +{{- if and $state.observed.providerConfigReady $state.observed.oidcReady }} +--- +apiVersion: protection.crossplane.io/v1beta1 +kind: Usage +metadata: + name: {{ printf "%s-delete-oidc-before-zitadel-providerconfig" $state.name }} + namespace: {{ $state.namespace | quote }} + annotations: + {{ setResourceNameAnnotation "usage-oidc-zitadel-providerconfig" }} + labels: {{ $state.labels | toJson }} +spec: + replayDeletion: true + of: + apiVersion: zitadel.m.crossplane.io/v1beta1 + kind: ProviderConfig + resourceRef: + name: {{ $state.providerConfigName | quote }} + by: + apiVersion: application.zitadel.m.crossplane.io/v1alpha1 + kind: Oidc + resourceRef: + name: {{ $state.name | quote }} +{{- end }} diff --git a/tests/test-oidcclient/main.k b/tests/test-oidcclient/main.k index 3128585..7cacd8c 100644 --- a/tests/test-oidcclient/main.k +++ b/tests/test-oidcclient/main.k @@ -140,6 +140,69 @@ items = [ metadata.name = "storybook-pr-381" status = {applicationId = "app-123", clientId = "client-123"} } + { + apiVersion = "protection.crossplane.io/v1beta1" + kind = "Usage" + metadata = { + name = "storybook-pr-381-delete-credentials-before-kubernetes-providerconfig" + namespace = "storybook-pr-381" + } + spec = { + replayDeletion = True + of = { + apiVersion = "kubernetes.m.crossplane.io/v1alpha1" + kind = "ProviderConfig" + resourceRef.name = "storybook-pr-381-kubernetes" + } + by = { + apiVersion = "kubernetes.m.crossplane.io/v1alpha1" + kind = "Object" + resourceRef.name = "storybook-pr-381-zitadel-credentials" + } + } + } + { + apiVersion = "protection.crossplane.io/v1beta1" + kind = "Usage" + metadata = { + name = "storybook-pr-381-delete-zitadel-providerconfig-before-credentials" + namespace = "storybook-pr-381" + } + spec = { + replayDeletion = True + of = { + apiVersion = "kubernetes.m.crossplane.io/v1alpha1" + kind = "Object" + resourceRef.name = "storybook-pr-381-zitadel-credentials" + } + by = { + apiVersion = "zitadel.m.crossplane.io/v1beta1" + kind = "ProviderConfig" + resourceRef.name = "storybook-pr-381" + } + } + } + { + apiVersion = "protection.crossplane.io/v1beta1" + kind = "Usage" + metadata = { + name = "storybook-pr-381-delete-oidc-before-zitadel-providerconfig" + namespace = "storybook-pr-381" + } + spec = { + replayDeletion = True + of = { + apiVersion = "zitadel.m.crossplane.io/v1beta1" + kind = "ProviderConfig" + resourceRef.name = "storybook-pr-381" + } + by = { + apiVersion = "application.zitadel.m.crossplane.io/v1alpha1" + kind = "Oidc" + resourceRef.name = "storybook-pr-381" + } + } + } ] } }