diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 571cc3c7..867efdf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ permissions: read-all jobs: build: - uses: miracum/.github/.github/workflows/standard-build.yaml@e86b05b914141334811d43d0973c95cec61bb729 # v1.4.6 + uses: miracum/.github/.github/workflows/standard-build.yaml@f9d64a7dbe928557fde9f96defa3e372bc0eaf21 # v1.6.1 permissions: contents: read id-token: write @@ -26,8 +26,79 @@ jobs: secrets: github-token: ${{ secrets.GITHUB_TOKEN }} + test: + name: run k8s smoke test + runs-on: ubuntu-22.04 + if: ${{ github.event_name == 'pull_request' }} + needs: + - build + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Create KinD cluster + uses: helm/kind-action@99576bfa6ddf9a8e612d83b513da5a75875caced # v1.9.0 + with: + cluster_name: kind + + - name: Download image + uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 + with: + name: ${{ needs.build.outputs.image-slug }} + path: /tmp + + - name: Load image into KinD + run: | + kind load image-archive /tmp/image.tar + + - name: List images in cluster + run: docker exec kind-control-plane crictl images + + - name: Install test chart + env: + IMAGE_TAG: ${{ needs.build.outputs.image-version }} + run: | + helm dep up tests/k8s + + # start by first installing the Strimzi and Prometheus operators + helm upgrade --install \ + --set "stream-processors.enabled=false" \ + --set "stream-processors.processors.obds-to-fhir.container.image.tag=${IMAGE_TAG}" \ + --wait \ + --timeout=10m \ + obds-to-fhir-test \ + tests/k8s + + # install the actual obds-to-fhir stream processor + helm upgrade --install \ + --set "stream-processors.enabled=true" \ + --set "stream-processors.processors.obds-to-fhir.container.image.tag=${IMAGE_TAG}" \ + --wait \ + --timeout=10m \ + obds-to-fhir-test \ + tests/k8s + + - name: Run Helm test to make sure everything started correctly + run: | + helm test obds-to-fhir-test + + - name: Print cluster logs + if: always() + run: | + kubectl cluster-info dump -o yaml | tee kind-cluster-dump.txt + + - name: Upload cluster dump + if: always() + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: kind-cluster-dump.txt + path: | + kind-cluster-dump.txt + lint: - uses: miracum/.github/.github/workflows/standard-lint.yaml@e86b05b914141334811d43d0973c95cec61bb729 # v1.4.6 + uses: miracum/.github/.github/workflows/standard-lint.yaml@f9d64a7dbe928557fde9f96defa3e372bc0eaf21 # v1.6.1 permissions: contents: read pull-requests: write @@ -41,7 +112,7 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} release: - uses: miracum/.github/.github/workflows/standard-release.yaml@e86b05b914141334811d43d0973c95cec61bb729 # v1.4.6 + uses: miracum/.github/.github/workflows/standard-release.yaml@f9d64a7dbe928557fde9f96defa3e372bc0eaf21 # v1.6.1 needs: - build permissions: diff --git a/.github/workflows/schedule.yaml b/.github/workflows/schedule.yaml index ca7aa657..aaf2b737 100644 --- a/.github/workflows/schedule.yaml +++ b/.github/workflows/schedule.yaml @@ -10,7 +10,7 @@ permissions: read-all jobs: schedule: - uses: miracum/.github/.github/workflows/standard-schedule.yaml@e86b05b914141334811d43d0973c95cec61bb729 # v1.4.6 + uses: miracum/.github/.github/workflows/standard-schedule.yaml@f9d64a7dbe928557fde9f96defa3e372bc0eaf21 # v1.6.1 permissions: contents: read issues: write diff --git a/.gitignore b/.gitignore index dc79945d..41a3c8b6 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,5 @@ bin fhirpkg.lock.json *.received.fhir.json + +tests/k8s/**/charts diff --git a/.prettierignore b/.prettierignore index 7a6ca384..0ffc19d5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ src/test/java/snapshots/** +tests/k8s/**/templates diff --git a/.yamllint b/.yamllint.yml similarity index 69% rename from .yamllint rename to .yamllint.yml index 77cd8111..dd80fcd2 100644 --- a/.yamllint +++ b/.yamllint.yml @@ -1,7 +1,9 @@ --- - extends: default rules: document-start: disable line-length: disable + +ignore: | + tests/k8s/**/templates diff --git a/build.gradle b/build.gradle index 2895d783..fab19187 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,8 @@ dependencies { implementation 'org.apache.kafka:kafka-streams' implementation 'org.springframework.cloud:spring-cloud-stream' implementation 'org.springframework.cloud:spring-cloud-stream-binder-kafka-streams' + implementation "org.springframework.boot:spring-boot-starter-actuator" + implementation "org.springframework.boot:spring-boot-starter-web" implementation "ca.uhn.hapi.fhir:hapi-fhir-structures-r4:${hapiVersion}" implementation 'io.micrometer:micrometer-registry-prometheus:1.12.2' implementation 'io.micrometer:micrometer-core:1.12.2' diff --git a/deploy/docker-compose.dev.yml b/deploy/docker-compose.dev.yml index 9d7fbfd3..5f5ac867 100644 --- a/deploy/docker-compose.dev.yml +++ b/deploy/docker-compose.dev.yml @@ -2,7 +2,7 @@ version: "3.7" services: oracle2: - image: docker.miracum.org/oracle/database:19.3.0-se2 + image: harbor.miracum.org/oracle/database:19.3.0-se2 hostname: oracle2 ports: - "1521:1521" @@ -39,21 +39,6 @@ services: depends_on: - zoo1 - kafka-rest-proxy: - image: confluentinc/cp-kafka-rest:7.5.3@sha256:a4558632cad6c4eedd8bc340a30277ab020b4fd97444844df1be0dbf9061782d - hostname: kafka-rest-proxy - ports: - - "8082:8082" - environment: - # KAFKA_REST_ZOOKEEPER_CONNECT: zoo1:2181 - KAFKA_REST_LISTENERS: http://0.0.0.0:8082/ - KAFKA_REST_SCHEMA_REGISTRY_URL: http://kafka-schema-registry:8081/ - KAFKA_REST_HOST_NAME: kafka-rest-proxy - KAFKA_REST_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092 - depends_on: - - zoo1 - - kafka1 - kafka-connect: image: confluentinc/cp-kafka-connect:7.5.3@sha256:e6bc1b88b43a23de290da574b0bbc06135b0005ecb9cac0c95155d3ff50f283a hostname: kafka-connect @@ -80,7 +65,6 @@ services: depends_on: - zoo1 - kafka1 - - kafka-rest-proxy akhq: image: tchiotludo/akhq:0.24.0 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 139f1d46..f00b641c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -165,3 +165,20 @@ spring: key-store-password: ${SSL_KEY_STORE_PASSWORD} producer: compression-type: gzip + +management: + endpoint: + health: + show-details: always + probes: + enabled: true + add-additional-paths: true + endpoints: + web: + exposure: + include: "health,prometheus" + health: + livenessstate: + enabled: true + readinessstate: + enabled: true diff --git a/tests/k8s/Chart.lock b/tests/k8s/Chart.lock new file mode 100644 index 00000000..564ecd36 --- /dev/null +++ b/tests/k8s/Chart.lock @@ -0,0 +1,12 @@ +dependencies: +- name: stream-processors + repository: oci://ghcr.io/miracum/charts + version: 1.6.1 +- name: strimzi-kafka-operator + repository: oci://quay.io/strimzi-helm + version: 0.40.0 +- name: kube-prometheus-stack + repository: oci://ghcr.io/prometheus-community/charts + version: 57.0.3 +digest: sha256:c3e7f5ed93a6ac74b481eacebb57b683a410808b4c2be3f41436914e3b0d1a82 +generated: "2024-03-18T15:26:37.617325079+01:00" diff --git a/tests/k8s/Chart.yaml b/tests/k8s/Chart.yaml new file mode 100644 index 00000000..e1c8a6a0 --- /dev/null +++ b/tests/k8s/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: obds-to-fhir-test +type: application +description: Simple smoke test for deploying obds-to-fhir on K8s. +deprecated: false +kubeVersion: ">= 1.21.0" +dependencies: + - name: kube-prometheus-stack + repository: oci://ghcr.io/prometheus-community/charts + version: 57.0.3 + - name: stream-processors + repository: oci://ghcr.io/miracum/charts + version: 1.6.1 + - name: strimzi-kafka-operator + repository: oci://quay.io/strimzi-helm + version: 0.40.0 +version: 0.0.0 +appVersion: 0.0.0 diff --git a/tests/k8s/templates/kafka-cluster.yaml b/tests/k8s/templates/kafka-cluster.yaml new file mode 100644 index 00000000..dff979f4 --- /dev/null +++ b/tests/k8s/templates/kafka-cluster.yaml @@ -0,0 +1,49 @@ +apiVersion: kafka.strimzi.io/v1beta2 +kind: KafkaNodePool +metadata: + name: dual-role + labels: + strimzi.io/cluster: obds-to-fhir-kafka +spec: + replicas: 1 + roles: + - controller + - broker + storage: + type: jbod + volumes: + - id: 0 + type: persistent-claim + size: 8Gi + deleteClaim: false +--- +apiVersion: kafka.strimzi.io/v1beta2 +kind: Kafka +metadata: + name: obds-to-fhir-kafka + annotations: + strimzi.io/node-pools: enabled + strimzi.io/kraft: enabled +spec: + kafka: + version: 3.7.0 + metadataVersion: 3.7-IV4 + listeners: + - name: tls + port: 9093 + type: internal + tls: true + config: + offsets.topic.replication.factor: 1 + transaction.state.log.replication.factor: 1 + transaction.state.log.min.isr: 1 + num.partitions: 1 + auto.create.topics.enable: true + default.replication.factor: 1 + log.cleanup.policy: compact + # 30 MiB + message.max.bytes: 31457280 + # https://kafka.apache.org/documentation/#brokerconfigs_compression.type + compression.type: gzip + entityOperator: + userOperator: {} diff --git a/tests/k8s/templates/tests/test-connection.yaml b/tests/k8s/templates/tests/test-connection.yaml new file mode 100644 index 00000000..1422c73a --- /dev/null +++ b/tests/k8s/templates/tests/test-connection.yaml @@ -0,0 +1,48 @@ +{{- $streamProcessorsFullName := (include "stream-processors.fullname" (index .Subcharts "stream-processors")) -}} +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-test-connection" + annotations: + helm.sh/hook: test-success +spec: + restartPolicy: Never + containers: + - name: probe-health-endpoint + image: "{{ .Values.curl.image.registry }}/{{ .Values.curl.image.repository }}:{{ .Values.curl.image.tag }}" + imagePullPolicy: IfNotPresent + command: ["curl", "--fail-with-body"] + args: ["http://{{ $streamProcessorsFullName }}-obds-to-fhir-metrics:8080/actuator/health"] + {{- with $.Values.restrictedContainerSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tests.resources }} + resources: + {{- toYaml . | nindent 8 }} + {{- end }} + livenessProbe: + exec: + command: ["true"] + readinessProbe: + exec: + command: ["true"] + - name: probe-metrics-endpoint + image: "{{ .Values.curl.image.registry }}/{{ .Values.curl.image.repository }}:{{ .Values.curl.image.tag }}" + imagePullPolicy: IfNotPresent + command: ["curl", "--fail-with-body"] + args: ["http://{{ $streamProcessorsFullName }}-obds-to-fhir-metrics:8080/actuator/prometheus"] + {{- with $.Values.restrictedContainerSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tests.resources }} + resources: + {{- toYaml . | nindent 8 }} + {{- end }} + livenessProbe: + exec: + command: ["true"] + readinessProbe: + exec: + command: ["true"] diff --git a/tests/k8s/values.yaml b/tests/k8s/values.yaml new file mode 100644 index 00000000..60c52134 --- /dev/null +++ b/tests/k8s/values.yaml @@ -0,0 +1,99 @@ +stream-processors: + enabled: true + strimziClusterName: obds-to-fhir-kafka + processors: + obds-to-fhir: + replicaCount: 1 + metrics: + enabled: true + serviceMonitor: + path: /actuator/prometheus + additionalLabels: + release: kube-prometheus-stack + container: + image: + registry: ghcr.io + repository: miracum/obds-to-fhir + tag: "" # set in CI + pullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + privileged: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + seccompProfile: + type: RuntimeDefault + livenessProbe: + httpGet: + path: /livez + port: 8080 + failureThreshold: 3 + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 15 + readinessProbe: + httpGet: + path: /readyz + port: 8080 + failureThreshold: 3 + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 15 + env: + - name: "FHIR_OUTPUT_TOPIC_PARTITION_COUNT" + value: "1" + - name: SPRING_CLOUD_STREAM_KAFKA_BINDER_MINPARTITIONCOUNT + value: "1" + - name: SPRING_CLOUD_STREAM_KAFKA_BINDER_AUTOADDPARTITIONS + value: "true" + - name: "NUM_STREAM_THREADS" + value: "1" + - name: JAVA_TOOL_OPTIONS + value: "-XX:MaxRAMPercentage=75" + - name: ACTIVE_PROFILE + value: "patient" + +kube-prometheus-stack: + kubeStateMetrics: + enabled: false + nodeExporter: + enabled: false + grafana: + enabled: false + alertmanager: + enabled: false + +curl: + image: + registry: docker.io + repository: curlimages/curl + tag: 8.6.0@sha256:c3b8bee303c6c6beed656cfc921218c529d65aa61114eb9e27c62047a1271b9b + +restrictedContainerSecurityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + privileged: false + capabilities: + drop: + - ALL + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + seccompProfile: + type: RuntimeDefault + +tests: + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi diff --git a/trivy.yaml b/trivy.yaml new file mode 100644 index 00000000..4449305f --- /dev/null +++ b/trivy.yaml @@ -0,0 +1,3 @@ +scan: + skip-dirs: + - tests