diff --git a/charts/templates/events/decommission/sensor.yaml b/charts/templates/events/decommission/sensor.yaml index 4337278ad3e439e475ea397c35189f3774156988..2b9bbcc83ee6939fb4fab92e14481fa5022315d1 100644 --- a/charts/templates/events/decommission/sensor.yaml +++ b/charts/templates/events/decommission/sensor.yaml @@ -12,7 +12,10 @@ spec: eventSourceName: decommission eventName: decommissionRequest triggers: - - template: + - rateLimit: + unit: Second + requestsPerUnit: 1 + template: name: argo-workflow-trigger argoWorkflow: operation: submit @@ -103,6 +106,3 @@ spec: dataKey: headers.scriptTriggerId value: "" dest: spec.arguments.parameters.0.value - retryStrategy: - steps: 2 - duration: 30s \ No newline at end of file diff --git a/charts/templates/events/provision/sensor.yaml b/charts/templates/events/provision/sensor.yaml index f2ed9fe0231d5111ecabab1e46a615f4caae860d..7712a33e213f966ea5236d201eadb51b0ed5b78a 100644 --- a/charts/templates/events/provision/sensor.yaml +++ b/charts/templates/events/provision/sensor.yaml @@ -12,7 +12,10 @@ spec: eventSourceName: provision eventName: provisionRequest triggers: - - template: + - rateLimit: + unit: Second + requestsPerUnit: 1 + template: name: argo-workflow-trigger argoWorkflow: operation: submit @@ -88,7 +91,6 @@ spec: ls; echo ---ls ../repos---; ls ../repos; - python doesntexist.py; python main.py {{`{{inputs.parameters.scriptTriggerId}}`}} {{`{{inputs.parameters.body}}`}} || exit $?; git config --global user.email "workflow@argo.kube"; git config --global user.name "${GIT_USER}"; @@ -118,7 +120,4 @@ spec: - src: dependencyName: message dataKey: body - dest: spec.arguments.parameters.2.value - retryStrategy: - steps: 2 - duration: 30s \ No newline at end of file + dest: spec.arguments.parameters.2.value \ No newline at end of file diff --git a/charts/templates/events/provision/status-sensor.yaml b/charts/templates/events/provision/status-sensor.yaml index ca327a63145d7faf2d887b148b85c52082ee98f8..1b80b82f5a5c3bffb9750233911d18b2b975ab0c 100644 --- a/charts/templates/events/provision/status-sensor.yaml +++ b/charts/templates/events/provision/status-sensor.yaml @@ -28,185 +28,224 @@ spec: namespace: {{ .Release.Namespace }} spec: podGC: - strategy: OnPodCompletion - deleteDelayDuration: 120s + strategy: OnWorkflowSuccess + deleteDelayDuration: 180s serviceAccountName: provisioner-workflows entrypoint: main - workflowMetadata: arguments: parameters: - name: body - value: default-is-overriden + value: default-body templates: - name: main + retryStrategy: + limit: "5" + backoff: + duration: "3m" steps: - - - name: validate - template: validate-status + - - name: get-tracking-metadata + template: get-tracking-metadata arguments: parameters: - name: message value: "{{`{{workflow.parameters.body}}`}}" - - - name: label - template: done-label + - - name: get-application-resource + template: get-application-resource arguments: parameters: - - name: UUID - value: "{{`{{steps.validate.outputs.result}}`}}" - - - name: parse-application - template: parse-application + - name: uuid + value: "{{`{{steps.get-tracking-metadata.outputs.parameters.uuid}}`}}" + - - name: validate-application-status + template: validate-application-status arguments: parameters: - - name: resource - value: "{{`{{steps.label.outputs.parameters.application-resource}}`}}" - - - name: parse-claim - template: parse-claim + - name: application + value: "{{`{{steps.get-application-resource.outputs.parameters.application}}`}}" + - name: uuid + value: "{{`{{steps.get-tracking-metadata.outputs.parameters.uuid}}`}}" + - - name: get-claim-resource + template: get-claim-resource arguments: parameters: - - name: claim-reference - value: "{{`{{steps.parse-application.outputs.result}}`}}" - - - name: payload - template: payload + - name: kind + value: "{{`{{steps.get-tracking-metadata.outputs.parameters.kind}}`}}" + - name: uuid + value: "{{`{{steps.get-tracking-metadata.outputs.parameters.uuid}}`}}" + - - name: validate-claim-status + template: validate-claim-status arguments: parameters: - - name: resource - value: "{{`{{steps.parse-claim.outputs.parameters.claim-resource}}`}}" - - - name: message - template: message + - name: claim + value: "{{`{{steps.get-claim-resource.outputs.parameters.claim}}`}}" + - name: uuid + value: "{{`{{steps.get-tracking-metadata.outputs.parameters.uuid}}`}}" + - - name: send-message + template: send-message arguments: parameters: - - name: payload - value: "{{`{{steps.payload.outputs.result}}`}}" - - name: validate-status + - name: message-body + value: "{{`{{steps.validate-claim-status.outputs.result}}`}}" + - name: get-tracking-metadata inputs: parameters: - name: message + outputs: + parameters: + - name: kind + valueFrom: + path: /tmp/claim-kind + - name: uuid + valueFrom: + path: /tmp/claim-uuid script: image: python:alpine3.10 command: [python] source: | import json + LOGFILE = '/dev/termination-log' + def write_termination_log(msg, echo_stdout=True): + if echo_stdout: + print(msg) + with open(LOGFILE, mode="a", encoding="utf-8") as logfile: + print(msg, file=logfile) + SAVE_KIND = '/tmp/claim-kind' + SAVE_UUID = '/tmp/claim-uuid' rawstr = r'''{{`{{inputs.parameters.message}}`}}''' d = json.loads(rawstr) - # structures - app_status = None - app_meta = None - app_resources = None - # values - app_health = '' - app_sync = '' - claim_health = '' - claim_sync = '' - claim_id = '' - # log - keys_log = '' + uuid = None + kind = None try: - app_status = d['status'] - app_meta = d['metadata'] - app_health = app_status['health']['status'] - app_sync = app_status['sync']['status'] - # Application only tracks the Composite Claim, so one resource should be present - app_resources = app_status['resources'] - claim = app_resources[0] - claim_health = claim['health']['status'] - claim_sync = claim['status'] - claim_id = app_meta['labels']['claim-uuid'] - # save fields before assertion - keys_log = f"app_health:{app_health}\napp_sync:{app_sync}\nclaim_health:{claim_health}\nclaim_sync:{claim_sync}\nclaim_id:{claim_id}\n" - assert app_health == "Healthy" and claim_health == "Healthy" and app_sync == "Synced" and claim_sync == "Synced" - print(claim_id) - except IndexError: - print(keys_log) - print("--app_resources:") - print(app_resources) - msg = "Composite Claim resource not embedded in the Application status" - raise ValueError(msg) + uuid = d['metadata']['labels']['claim-uuid'] + kind = d['metadata']['labels']['claim-kind'] + print(f"Kind:{kind}") + print(f"UUID:{uuid}") except KeyError: - print(keys_log) - if not app_status: - msg = "Application status not available" - raise ValueError(msg) - msg = "Required status fields not available" - if all((app_health, app_sync)) and not all((claim_health, claim_sync, claim_id)): - msg = "Required Composite Claim status fields are not available." - print("--embedded claim:") - print(claim) - raise ValueError(msg) - except AssertionError: - print(keys_log) - msg = "Resources are not ready yet." - raise ValueError(msg) - - - name: done-label + write_termination_log('Missing required Application metadata labels: claim-uuid, claim-kind. Verify the deployment script.') + raise ValueError('Missing required Application metadata labels.') + with open(SAVE_KIND, mode="w", encoding="utf-8") as output: + output.write(kind) + with open(SAVE_UUID, mode="w", encoding="utf-8") as output: + output.write(uuid) + - name: get-application-resource inputs: parameters: - - name: UUID + - name: uuid script: image: bitnami/kubectl:latest command: [sh] source: | - echo "patching application with label claim-uuid:{{`{{inputs.parameters.UUID}}`}}" - kubectl label -n {{ .Release.Namespace }} applications crossplane-claim-{{`{{inputs.parameters.UUID}}`}} provisioning-status="finalized" - kubectl get -n {{ .Release.Namespace }} applications --selector=claim-uuid={{`{{inputs.parameters.UUID}}`}} -o json > /tmp/resource.json + # Get the Application after the initial burst of events. + sleep 5 + echo "Retrieving Application with label claim-uuid: {{`{{inputs.parameters.uuid}}`}}" + kubectl get -n {{ .Release.Namespace }} applications --selector=claim-uuid={{`{{inputs.parameters.uuid}}`}} -o json > /tmp/application.json outputs: parameters: - - name: application-resource + - name: application valueFrom: - path: /tmp/resource.json - - name: parse-application + path: /tmp/application.json + - name: validate-application-status inputs: parameters: - - name: resource + - name: application + - name: uuid script: image: python:alpine3.10 command: [python] source: | import json - rawstr = r'''{{`{{inputs.parameters.resource}}`}}''' - + LOGFILE = '/dev/termination-log' + def write_termination_log(msg, echo_stdout=True): + if echo_stdout: + print(msg) + with open(LOGFILE, mode="a", encoding="utf-8") as logfile: + print(msg, file=logfile) + rawstr = r'''{{`{{inputs.parameters.application}}`}}''' + uuid = r'''{{`{{inputs.parameters.uuid}}`}}''' d = json.loads(rawstr) - claim_kind = d['items'][0]['metadata']['labels']['claim-kind'] - claim_selector = d['items'][0]['metadata']['labels']['claim-uuid'] - print(f"{claim_kind} --selector=uuid={claim_selector}") - - name: parse-claim + if not len(d["items"]): + raise ValueError(f"Application (UUID: {uuid}) not found") + resource = d["items"][0] + app_sync = None + app_health = None + try: + app_sync = resource['status']['sync']['status'] + app_health = resource['status']['health']['status'] + except KeyError: + missing = "sync" if not app_sync else "sync, health" + write_termination_log(f"Missing required Application (UUID:{uuid}) status fields: {missing}") + raise ValueError("missing status fields") + try: + assert app_health == "Healthy" and app_sync == "Synced" + except AssertionError: + write_termination_log(f"Application (UUID:{uuid}) not ready yet. Sync:{app_sync} Health:{app_health}") + raise ValueError("application not ready") + - name: get-claim-resource inputs: parameters: - - name: claim-reference + - name: kind + - name: uuid script: image: bitnami/kubectl:latest command: [sh] source: | - kubectl get -n {{ .Release.Namespace }} {{`{{inputs.parameters.claim-reference}}`}} - kubectl get -n {{ .Release.Namespace }} -o json {{`{{inputs.parameters.claim-reference}}`}} > /tmp/resource.json + echo "retrieving Crossplane Claim of kind: {{`{{inputs.parameters.kind}}`}} with label claim-uuid: {{`{{inputs.parameters.uuid}}`}}" + kubectl get -n {{ .Release.Namespace }} {{`{{inputs.parameters.kind}}`}} --selector=uuid={{`{{inputs.parameters.uuid}}`}} -o json > /tmp/claim.json outputs: parameters: - - name: claim-resource + - name: claim valueFrom: - path: /tmp/resource.json - - name: payload + path: /tmp/claim.json + - name: validate-claim-status inputs: parameters: - - name: resource + - name: claim + - name: uuid script: image: python:alpine3.10 command: [python] source: | import json - rawstr = r'''{{`{{inputs.parameters.resource}}`}}''' - rawstr = rawstr.replace('\n', '') - d = {} + LOGFILE = '/dev/termination-log' + def write_termination_log(msg, echo_stdout=True): + if echo_stdout: + print(msg) + with open(LOGFILE, mode="a", encoding="utf-8") as logfile: + print(msg, file=logfile) + + rawstr = r'''{{`{{inputs.parameters.claim}}`}}''' + uuid = r'''{{`{{inputs.parameters.uuid}}`}}''' + d = json.loads(rawstr) + if not len(d["items"]): + raise ValueError(f"Claim (UUID: {uuid}) not found") + resource = d["items"][0] + ready = False + synced = False + reason = None + message = None + for c in resource["status"]["conditions"]: + if c["type"] == "Synced" and c["status"] == "True": + synced = True + if c["type"] == "Ready": + if c["status"] == "True": + ready = True + else: + reason = c["reason"] if "reason" in c else "" + message = c["message"] if "message" in c else "" try: - d = json.loads(rawstr) - except: - raise ValueError("Claim status message is not valid JSON") - payload = {} - payload["vmIps"] = d["items"][0]["status"]["vmIps"] - payload["status"] = "Provisioning finalized successfully" - payload["scriptTriggerId"] = d["items"][0]["metadata"]["labels"]["uuid"] - print(json.dumps(payload).replace('"', '\\"')) - - name: message + assert synced and ready + except AssertionError: + msg = f"Claim (UUID: {uuid}) not ready yet, reason: {reason} - {message}" if reason and message else "Claim not ready yet" + write_termination_log(msg) + raise ValueError(f"Claim (UUID: {uuid}) not ready yet") + message_body = {} + message_body["vmIps"] = resource["status"]["vmIps"] + message_body["status"] = "Provisioning finalized successfully" + message_body["scriptTriggerId"] = uuid + print(json.dumps(message_body).replace('"', '\\"')) + - name: send-message inputs: parameters: - - name: payload + - name: message-body script: {{- if .Values.kafkaAuthEnable }} env: @@ -227,10 +266,10 @@ spec: command: [sh] {{- if .Values.kafkaAuthEnable }} source: | - echo {{`{{inputs.parameters.payload}}`}} | kafkacat -P -b {{ .Values.kafkaEndpoint }} -X security.protocol=SASL_PLAINTEXT -X sasl.username="$USERNAME" -X sasl.password="$PASSWORD" -X sasl.mechanism="$MECHANISM" -t {{ .Values.kafkaProvisioningResponsesTopicName }} -J + echo {{`{{inputs.parameters.message-body}}`}} | kafkacat -P -b {{ .Values.kafkaEndpoint }} -X security.protocol=SASL_PLAINTEXT -X sasl.username="$USERNAME" -X sasl.password="$PASSWORD" -X sasl.mechanism="$MECHANISM" -t {{ .Values.kafkaProvisioningResponsesTopicName }} -J {{- else }} source: | - echo {{`{{inputs.parameters.payload}}`}} | kafkacat -P -b {{ .Values.kafkaEndpoint }} -t {{ .Values.kafkaProvisioningResponsesTopicName }} -J + echo {{`{{inputs.parameters.message-body}}`}} | kafkacat -P -b {{ .Values.kafkaEndpoint }} -t {{ .Values.kafkaProvisioningResponsesTopicName }} -J {{- end }} parameters: diff --git a/charts/templates/events/provision/status-source.yaml b/charts/templates/events/provision/status-source.yaml index d5eceaccd0209866a3c6ee2d3302570c264a67e8..c0fc36a58c0139516bcd283322d822fea8775aa2 100644 --- a/charts/templates/events/provision/status-source.yaml +++ b/charts/templates/events/provision/status-source.yaml @@ -14,14 +14,10 @@ spec: version: v1alpha1 resource: applications eventTypes: - - UPDATE + - ADD filter: afterStart: true labels: - key: track-events operation: "==" value: "claim-application" - - key: provisioning-status - operation: "!=" - value: "finalized" - diff --git a/charts/templates/rbac/rbac.yaml b/charts/templates/rbac/rbac.yaml index 4983929c365c929858f6c8fea7b78f12f8b9ca88..237dbe54d530a93cde8e557005a6c1f6a3ddc603 100644 --- a/charts/templates/rbac/rbac.yaml +++ b/charts/templates/rbac/rbac.yaml @@ -97,7 +97,7 @@ metadata: roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: patch-workflow + name: view-workflow subjects: - kind: ServiceAccount name: provisioner-events diff --git a/charts/values.yaml b/charts/values.yaml index 3f651f5576e20982b6573ec34e0b33fb0a9790d7..45aed983ad40e1de605df1f5a3815cd149798e78 100644 --- a/charts/values.yaml +++ b/charts/values.yaml @@ -2,7 +2,7 @@ dependenciesReleaseName: provisioner-dependencies dependenciesNamespace: infrastructure cliEnabled: false -provisionWorkflowImage: code.europa.eu:4567/simpl/simpl-open/development/infrastructure/infrastructure-crossplane/to-provision-workflow:v0.3.0 +provisionWorkflowImage: code.europa.eu:4567/simpl/simpl-open/development/infrastructure/infrastructure-crossplane/to-provision-workflow:v0.5.0 kafkaAuthEnable: true kafkaAuthMechanism: PLAIN @@ -10,3 +10,7 @@ kafkaProvisioningRequestsTopicName: to-provision kafkaProvisioningResponsesTopicName: provisioned kafkaDecommissioningRequestsTopicName: to-decommission kafkaDecommissioningResponsesTopicName: decommissioned + +configuration: + packages: + - code.europa.eu:4567/simpl/simpl-open/development/infrastructure/infrastructure-crossplane/configuration:v0.3.10 \ No newline at end of file diff --git a/workflow-images/to-provision/Dockerfile b/workflow-images/to-provision/Dockerfile index 81777c5ec72c2eab5c69025ce85271488f767a37..8834be148008588d7f70c0c9ba05e46d1453781d 100644 --- a/workflow-images/to-provision/Dockerfile +++ b/workflow-images/to-provision/Dockerfile @@ -1,4 +1,5 @@ -FROM python:3 +FROM python:3-alpine +RUN apk add --no-cache git WORKDIR /work