diff --git a/src/domains/gps-app/04_apim_gpd_debezium.tf b/src/domains/gps-app/04_apim_gpd_debezium.tf new file mode 100644 index 0000000000..6134e34ad9 --- /dev/null +++ b/src/domains/gps-app/04_apim_gpd_debezium.tf @@ -0,0 +1,76 @@ +#################### +## Local variables # +#################### + +locals { + apim_gpd_debezium_api = { + published = false + subscription_required = true + approval_required = false + subscriptions_limit = 1000 + service_url = format("https://%s/debezium-gpd", local.gps_hostname) + } +} + +############## +## Products ## +############## + +module "apim_gpd_debezium_product" { + source = "./.terraform/modules/__v3__/api_management_product" + + product_id = "product-gpd-debezium" + display_name = "GPD Debezium API pagoPA" + description = "Prodotto GPD Debezium API" + + api_management_name = local.pagopa_apim_name + resource_group_name = local.pagopa_apim_rg + + published = local.apim_gpd_debezium_api.published + subscription_required = local.apim_gpd_debezium_api.subscription_required + approval_required = local.apim_gpd_debezium_api.approval_required + subscriptions_limit = local.apim_gpd_debezium_api.subscriptions_limit + + policy_xml = file("./api_product/debezium-api/_base_policy.xml") +} + +############## +## API ## +############## + +resource "azurerm_api_management_api_version_set" "api_gpd_debezium_api" { + + name = format("%s-api-gpd-debezium-api", var.env_short) + api_management_name = local.pagopa_apim_name + resource_group_name = local.pagopa_apim_rg + display_name = "GPD Debezium API" + versioning_scheme = "Segment" +} + + +module "apim_api_gpd_debezium_api" { + source = "./.terraform/modules/__v3__/api_management_api" + + name = format("%s-api-gpd-debezium-api", var.env_short) + api_management_name = local.pagopa_apim_name + resource_group_name = local.pagopa_apim_rg + product_ids = [module.apim_gpd_debezium_product.product_id, module.apim_gpd_debezium_product.product_id] + subscription_required = local.apim_gpd_debezium_api.subscription_required + api_version = "v1" + version_set_id = azurerm_api_management_api_version_set.api_gpd_debezium_api.id + service_url = format("https://%s", module.reporting_analysis_function.default_hostname) + + description = "Api GPD Debezium" + display_name = "GPDDebezium API pagoPA" + path = "gpd-debezium/api" + protocols = ["https"] + + content_format = "openapi" + content_value = templatefile("./api/debezium-api/v1/_openapi.json.tpl", { + host = local.apim_hostname + }) + + xml_content = templatefile("./api/debezium-api/v1/_base_policy.xml", { + origin = format("https://%s.%s.%s", var.cname_record_name, var.apim_dns_zone_prefix, var.external_domain) + }) +} diff --git a/src/domains/gps-app/05_debezium_connect.tf b/src/domains/gps-app/05_debezium_connect.tf index 9e5fc74e33..8a78465227 100644 --- a/src/domains/gps-app/05_debezium_connect.tf +++ b/src/domains/gps-app/05_debezium_connect.tf @@ -91,6 +91,24 @@ locals { max_threads = var.max_threads }) + healthchecker_config_yaml = templatefile("${path.module}/yaml/healthchecker-config-map.yaml", { + namespace = "gps" # kubernetes_namespace.namespace.metadata[0].name + }) + + debezium_health_checker_cron_yaml = templatefile("${path.module}/yaml/debezium-health-checker-cron.yaml", { + namespace = "gps" # kubernetes_namespace.namespace.metadata[0].name + }) + + debezium_network_policy_yaml = templatefile("${path.module}/yaml/debezium-network-policy.yaml", { + namespace = "gps" # kubernetes_namespace.namespace.metadata[0].name + }) + + debezium_ingress_yaml = templatefile("${path.module}/yaml/debezium-ingress.yaml", { + namespace = "gps" # kubernetes_namespace.namespace.metadata[0].name + host = "${var.location_short}${var.env_short}.gps.internal.${var.env_short}.platform.pagopa.it" + secret = "${var.location_short}${var.env_short}-gps-internal-${var.env_short}-platform-pagopa-it" + }) + } resource "kubectl_manifest" "debezium_role" { @@ -178,3 +196,36 @@ resource "null_resource" "wait_postgres_connector" { interpreter = ["/bin/bash", "-c"] } } + +resource "kubectl_manifest" "healthchecker-config-map" { + depends_on = [ + helm_release.strimzi-kafka-operator + ] + force_conflicts = true + yaml_body = local.healthchecker_config_yaml +} + +resource "kubectl_manifest" "healthchecker-cron" { + depends_on = [ + helm_release.strimzi-kafka-operator, kubectl_manifest.healthchecker-config-map + ] + force_conflicts = true + yaml_body = local.debezium_health_checker_cron_yaml +} + +resource "kubectl_manifest" "debezium-ingress" { + depends_on = [ + kubectl_manifest.kafka_connect + ] + force_conflicts = true + yaml_body = local.debezium_ingress_yaml +} + +resource "kubectl_manifest" "debezium-network-policy" { + depends_on = [ + kubectl_manifest.kafka_connect + ] + force_conflicts = true + yaml_body = local.debezium_network_policy_yaml +} + diff --git a/src/domains/gps-app/api/debezium-api/v1/_base_policy.xml b/src/domains/gps-app/api/debezium-api/v1/_base_policy.xml new file mode 100644 index 0000000000..f9edd06caf --- /dev/null +++ b/src/domains/gps-app/api/debezium-api/v1/_base_policy.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/src/domains/gps-app/api/debezium-api/v1/_openapi.json.tpl b/src/domains/gps-app/api/debezium-api/v1/_openapi.json.tpl new file mode 100644 index 0000000000..bd0a560a71 --- /dev/null +++ b/src/domains/gps-app/api/debezium-api/v1/_openapi.json.tpl @@ -0,0 +1,156 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Debezium API - GPD", + "version": "1.0.0" + }, + "servers": [ + { + "url": "${host}" + } + ], + "paths": { + "/connectors": { + "get": { + "tags": [ + "Get Connectors List" + ], + "summary": "getConnectors", + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + } + } + }, + "400": { + "description": "Error response", + "content": { + "application/json": {} + } + } + } + } + }, + "/connectors/{connectorId}/status": { + "get": { + "tags": [ + "Get Detail on Connector Status" + ], + "summary": "getConnectorStatus", + "parameters": [ + { + "name": "connectorId", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "debezium-connector-postgres" + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": {} + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": {} + } + } + } + } + }, + "/connectors/{connectorId}/restart": { + "post": { + "tags": [ + "Restart Connector" + ], + "summary": "restartConnector", + "parameters": [ + { + "name": "connectorId", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "debezium-connector-postgres" + } + ], + "responses": { + "204": { + "description": "Successful response", + "content": { + "application/json": {} + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": {} + } + } + } + } + }, + "/connectors/{connectorId}/tasks/{taskId}/restart": { + "post": { + "tags": [ + "Restart Task " + ], + "summary": "restartTask", + "parameters": [ + { + "name": "connectorId", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "debezium-connector-postgres" + }, + { + "name": "taskId", + "in": "path", + "schema": { + "type": "string" + }, + "required": true, + "example": "0" + } + ], + "responses": { + "204": { + "description": "Successful response", + "content": { + "application/json": {} + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": {} + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "ApiKey": { + "type": "apiKey", + "description": "The API key to access this function app.", + "name": "Ocp-Apim-Subscription-Key", + "in": "header" + } + } + } +} diff --git a/src/domains/gps-app/api_product/debezium-api/_base_policy.xml b/src/domains/gps-app/api_product/debezium-api/_base_policy.xml new file mode 100644 index 0000000000..ce1df461e7 --- /dev/null +++ b/src/domains/gps-app/api_product/debezium-api/_base_policy.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/src/domains/gps-app/yaml/debezium-health-checker-cron.yaml b/src/domains/gps-app/yaml/debezium-health-checker-cron.yaml new file mode 100644 index 0000000000..e4e6db5c9d --- /dev/null +++ b/src/domains/gps-app/yaml/debezium-health-checker-cron.yaml @@ -0,0 +1,26 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: debezium-health-checker-cron + namespace: ${namespace} +spec: + schedule: "*/5 * * * *" # Runs every 5 minutes + successfulJobsHistoryLimit: 0 + failedJobsHistoryLimit: 1 + jobTemplate: + spec: + ttlSecondsAfterFinished: 100 + template: + spec: + containers: + - name: debezium-health-checker + image: bitnami/kubectl:latest + command: ["/bin/bash", "/scripts/check_kafka_connect.sh"] + volumeMounts: + - name: script-volume + mountPath: /scripts + restartPolicy: OnFailure + volumes: + - name: script-volume + configMap: + name: health-checker-script diff --git a/src/domains/gps-app/yaml/debezium-ingress.yaml b/src/domains/gps-app/yaml/debezium-ingress.yaml new file mode 100644 index 0000000000..2ee911e76f --- /dev/null +++ b/src/domains/gps-app/yaml/debezium-ingress.yaml @@ -0,0 +1,29 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: 1m + nginx.ingress.kubernetes.io/rewrite-target: /$1 + nginx.ingress.kubernetes.io/use-regex: "true" + labels: + ingress: debezium + name: debezium-ingress + namespace: ${namespace} +spec: + ingressClassName: nginx + rules: + - host: ${host} + http: + paths: + - backend: + service: + name: debezium-connect-cluster-connect-api + port: + number: 8083 + path: /debezium-gpd/(.*) + pathType: ImplementationSpecific + tls: + - hosts: + - ${host} + secretName: ${secret} diff --git a/src/domains/gps-app/yaml/debezium-network-policy.yaml b/src/domains/gps-app/yaml/debezium-network-policy.yaml new file mode 100644 index 0000000000..3556d0678e --- /dev/null +++ b/src/domains/gps-app/yaml/debezium-network-policy.yaml @@ -0,0 +1,29 @@ +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: debezium-connect-cluster-network-policy + namespace: {namespace} +spec: + ingress: + - from: + - podSelector: + matchLabels: + ingress: debezium + - podSelector: + matchLabels: + strimzi.io/cluster: debezium-connect-cluster + strimzi.io/kind: KafkaConnect + strimzi.io/name: debezium-connect-cluster-connect + - podSelector: + matchLabels: + strimzi.io/kind: cluster-operator + ports: + - port: 8083 + protocol: TCP + podSelector: + matchLabels: + strimzi.io/cluster: debezium-connect-cluster + strimzi.io/kind: KafkaConnect + strimzi.io/name: debezium-connect-cluster-connect + policyTypes: + - Ingress diff --git a/src/domains/gps-app/yaml/healthchecker-config-map.yaml b/src/domains/gps-app/yaml/healthchecker-config-map.yaml new file mode 100644 index 0000000000..9add933d42 --- /dev/null +++ b/src/domains/gps-app/yaml/healthchecker-config-map.yaml @@ -0,0 +1,34 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: health-checker-script + namespace: ${namespace} +data: + check_kafka_connect.sh: | + #!/bin/bash + STATUS_URL="http://debezium-connect-cluster-connect-api:8083/connectors/debezium-connector-postgres/status" + CONNECTOR_RESTART_URL="http://debezium-connect-cluster-connect-api:8083/connectors/debezium-connector-postgres/restart" + TASK_RESTART_URL="http://debezium-connect-cluster-connect-api:8083/connectors/debezium-connector-postgres/tasks" + + STATUS_RESPONSE=$(curl -s -X GET "$STATUS_URL") + + CONNECTOR_STATUS=$(echo "$STATUS_RESPONSE" | grep -o '"connector":{"state":"[^"]*"' | sed 's/"connector":{"state":"//;s/"//') + + if [[ "$CONNECTOR_STATUS" != "RUNNING" ]]; then + echo "Connector is not running (state: $CONNECTOR_STATUS). Restarting..." + curl -s -X POST "$CONNECTOR_RESTART_URL" && echo "Connector restart command issued." + else + echo "Connector is running normally." + fi + + TASKS=$(echo "$STATUS_RESPONSE" | grep -o '"id":[0-9]*,"state":"[^"]*"' | sed 's/"id"://;s/"state":"//;s/,/ /') + + echo "$TASKS" | while read -r TASK_ID TASK_STATE; do + if [[ "$TASK_STATE" != "RUNNING" ]]; then + echo "Task $TASK_ID is not running (state: $TASK_STATE). Restarting..." + TASK_RESTART_API="${TASK_RESTART_URL}/${TASK_ID}/restart" + curl -s -X POST "$TASK_RESTART_API" && echo "Task $TASK_ID restart command issued." + else + echo "Task $TASK_ID is running normally." + fi + done