diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..307975134 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +.git +.github +**/bin +**/obj +test +**/.vs +**/.idea +**/.vscode +*.user diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2477db934..139a6d770 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,17 +2,24 @@ name: deploy on: workflow_dispatch: + inputs: + environment: + type: choice + options: [dev, staging, production] + required: true concurrency: group: deploy-${{ github.ref }} cancel-in-progress: false jobs: - ecr: + deploy: runs-on: ubuntu-latest + environment: ${{ inputs.environment }} permissions: id-token: write # This is required for requesting the JWT contents: read # This is required for actions/checkout + steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd @@ -25,9 +32,6 @@ jobs: - name: Restore dependencies run: dotnet restore - - name: Install dotnet sql package - run: dotnet tool install --global microsoft.sqlpackage --version 170.3.93 - - name: Build run: dotnet build --configuration Release --no-restore @@ -39,40 +43,34 @@ jobs: with: role-to-assume: ${{ secrets.ECR_ROLE_TO_ASSUME }} aws-region: ${{ vars.ECR_REGION }} - + - name: Login to ECR uses: aws-actions/amazon-ecr-login@33f92af657bba1882ab79d8621debd2f6769a0c9 id: login-ecr - name: Build and Push Server.UI Container run: | - dotnet publish src/Server.UI/Server.UI.csproj \ - --configuration Release \ - --no-build \ - /t:PublishContainer \ - /p:ContainerRegistry=${{ steps.login-ecr.outputs.registry }} \ - /p:ContainerRepository=${{ vars.ECR_REPOSITORY }} \ - /p:ContainerImageTag=cats-${{ github.sha }} + docker build \ + -f src/Server.UI/Dockerfile \ + -t ${{ steps.login-ecr.outputs.registry }}/${{ vars.ECR_REPOSITORY }}:cats-${{ github.sha }} \ + . + docker push ${{ steps.login-ecr.outputs.registry }}/${{ vars.ECR_REPOSITORY }}:cats-${{ github.sha }} - name: Build and Push Worker Container run: | - dotnet publish src/Worker/Worker.csproj \ - --configuration Release \ - --no-build \ - /t:PublishContainer \ - /p:ContainerRegistry=${{ steps.login-ecr.outputs.registry }} \ - /p:ContainerRepository=${{ vars.ECR_REPOSITORY }} \ - /p:ContainerImageTag=worker-${{ github.sha }} - + docker build \ + -f src/Worker/Dockerfile \ + -t ${{ steps.login-ecr.outputs.registry }}/${{ vars.ECR_REPOSITORY }}:worker-${{ github.sha }} \ + . + docker push ${{ steps.login-ecr.outputs.registry }}/${{ vars.ECR_REPOSITORY }}:worker-${{ github.sha }} + - name: Build and Push DatabaseSeeding Container run: | - dotnet publish src/DatabaseSeeding/DatabaseSeeding.csproj \ - --configuration Release \ - --no-build \ - /t:PublishContainer \ - /p:ContainerRegistry=${{ steps.login-ecr.outputs.registry }} \ - /p:ContainerRepository=${{ vars.ECR_REPOSITORY }} \ - /p:ContainerImageTag=seeder-${{ github.sha }} + docker build \ + -f src/DatabaseSeeding/Dockerfile \ + -t ${{ steps.login-ecr.outputs.registry }}/${{ vars.ECR_REPOSITORY }}:seeder-${{ github.sha }} \ + . + docker push ${{ steps.login-ecr.outputs.registry }}/${{ vars.ECR_REPOSITORY }}:seeder-${{ github.sha }} - name: Build and Push DatabaseMigrator Container run: | @@ -81,25 +79,16 @@ jobs: -t ${{ steps.login-ecr.outputs.registry }}/${{ vars.ECR_REPOSITORY }}:migrator-${{ github.sha }} \ . docker push ${{ steps.login-ecr.outputs.registry }}/${{ vars.ECR_REPOSITORY }}:migrator-${{ github.sha }} - + - name: Generate app version id: version run: echo "app_version=$(date +'%Y.%m.%d').${{ github.run_number }}" >> $GITHUB_OUTPUT - - name: Generate Kubernetes Manifests - run: | - mkdir -p deploy - for file in infra/*.yml; do - envsubst < "$file" > "deploy/$(basename "$file")" - done - env: - IMAGE_TAG: ${{ github.sha }} - APP_VERSION: ${{ steps.version.outputs.app_version }} - REGISTRY: ${{ steps.login-ecr.outputs.registry }} - REPOSITORY: ${{ vars.ECR_REPOSITORY }} - NAMESPACE: ${{ secrets.KUBE_NAMESPACE }} - DOTNET_ENVIRONMENT: "Development" - + - name: Setup Helm + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 + with: + version: v3.21.2 + - name: Configure kubectl run: | echo "${{ secrets.KUBE_CERT }}" > ca.crt @@ -111,33 +100,72 @@ jobs: KUBE_NAMESPACE: ${{ secrets.KUBE_NAMESPACE }} KUBE_CLUSTER: ${{ secrets.KUBE_CLUSTER }} - - name: Run database migration + - name: Build Helm dependencies run: | - kubectl -n ${KUBE_NAMESPACE} delete pod -l app=migrator --wait=false || true - kubectl -n ${KUBE_NAMESPACE} apply -f deploy/migrator-pod.yml - if ! kubectl -n ${KUBE_NAMESPACE} wait --for=jsonpath='{.status.phase}'=Succeeded --timeout=300s pod/migrator-${{ github.sha }}; then - echo "Migration pod did not succeed within timeout." - kubectl -n ${KUBE_NAMESPACE} describe pod/migrator-${{ github.sha }} || true - exit 1 - fi + set -euo pipefail + helm repo add hmpps-helm-charts https://ministryofjustice.github.io/hmpps-helm-charts + helm dependency update ./helm_deploy/cats + + - name: Run database migrations + run: | + set -euo pipefail + helm upgrade --install cats-migrate ./helm_deploy/cats \ + --namespace "${KUBE_NAMESPACE}" \ + --values ./helm_deploy/cats/values-${{ inputs.environment }}.yaml \ + --set migrator.enabled=true \ + --set serviceAccountName="${KUBE_NAMESPACE}" \ + --set migrator.image.repository="${REGISTRY}/${REPOSITORY}" \ + --set migrator.image.tag="migrator-${{ github.sha }}" \ + --timeout 5m + kubectl -n "${KUBE_NAMESPACE}" wait --for=jsonpath='{.status.phase}'=Succeeded --timeout=300s pod -l app=migrator env: KUBE_NAMESPACE: ${{ secrets.KUBE_NAMESPACE }} + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + REPOSITORY: ${{ vars.ECR_REPOSITORY }} - - name: Run database seeding + - name: Seed the database run: | - kubectl -n ${KUBE_NAMESPACE} delete pod -l app=seeder --wait=false || true - kubectl -n ${KUBE_NAMESPACE} apply -f deploy/seeder-pod.yml - if ! kubectl -n ${KUBE_NAMESPACE} wait --for=jsonpath='{.status.phase}'=Succeeded --timeout=300s pod/seeder-${{ github.sha }}; then - echo "Seeder pod did not succeed within timeout." - kubectl -n ${KUBE_NAMESPACE} describe pod/seeder-${{ github.sha }} || true - exit 1 - fi + set -euo pipefail + helm upgrade --install cats-seed ./helm_deploy/cats \ + --namespace "${KUBE_NAMESPACE}" \ + --values ./helm_deploy/cats/values-${{ inputs.environment }}.yaml \ + --set seeder.enabled=true \ + --set serviceAccountName="${KUBE_NAMESPACE}" \ + --set seeder.image.repository="${REGISTRY}/${REPOSITORY}" \ + --set seeder.image.tag="seeder-${{ github.sha }}" \ + --timeout 5m + kubectl -n "${KUBE_NAMESPACE}" wait --for=jsonpath='{.status.phase}'=Succeeded --timeout=300s pod -l app=seeder env: KUBE_NAMESPACE: ${{ secrets.KUBE_NAMESPACE }} + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + REPOSITORY: ${{ vars.ECR_REPOSITORY }} - - name: Deploy to Kubernetes + - name: Deploy CATS and Worker run: | - rm -f deploy/migrator-pod.yml deploy/seeder-pod.yml - kubectl -n ${KUBE_NAMESPACE} apply -f deploy/ + set -euo pipefail + IMAGE_REPOSITORY="${REGISTRY}/${REPOSITORY}" + + helm upgrade --install cats ./helm_deploy/cats \ + --namespace "${KUBE_NAMESPACE}" \ + --values ./helm_deploy/cats/values-${{ inputs.environment }}.yaml \ + --set app.enabled=true \ + --set worker.enabled=true \ + --set rabbitmq.enabled=true \ + --set redis.enabled=true \ + --set serviceAccountName="${KUBE_NAMESPACE}" \ + --set app.serviceAccountName="${KUBE_NAMESPACE}" \ + --set app.image.repository="${IMAGE_REPOSITORY}" \ + --set app.image.tag="cats-${{ github.sha }}" \ + --set app.env.Sentry__Release="${APP_VERSION}" \ + --set app.env.AppConfigurationSettings__Version="${APP_VERSION}" \ + --set worker.serviceAccountName="${KUBE_NAMESPACE}" \ + --set worker.image.repository="${IMAGE_REPOSITORY}" \ + --set worker.image.tag="worker-${{ github.sha }}" \ + --set worker.env.Sentry__Release="${APP_VERSION}" \ + --set worker.env.AppConfigurationSettings__Version="${APP_VERSION}" \ + --atomic --wait --timeout 10m env: KUBE_NAMESPACE: ${{ secrets.KUBE_NAMESPACE }} + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + REPOSITORY: ${{ vars.ECR_REPOSITORY }} + APP_VERSION: ${{ steps.version.outputs.app_version }} diff --git a/.github/workflows/validate-helm.yml b/.github/workflows/validate-helm.yml new file mode 100644 index 000000000..0101f378f --- /dev/null +++ b/.github/workflows/validate-helm.yml @@ -0,0 +1,80 @@ +name: Validate Helm + +on: + pull_request: + branches: + - main + paths: + - helm_deploy/** + - .github/workflows/validate-helm.yml + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + + - name: Setup Helm + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 + with: + version: v3.21.2 + + - name: Build chart dependencies + run: | + helm repo add hmpps-helm-charts https://ministryofjustice.github.io/hmpps-helm-charts + helm dependency update ./helm_deploy/cats + + - name: Lint and template all environments + run: | + set -euo pipefail + for env in dev staging production; do + echo "::group::helm lint ($env)" + helm lint ./helm_deploy/cats --values ./helm_deploy/cats/values-$env.yaml + echo "::endgroup::" + + echo "::group::helm template app ($env)" + # Render with placeholder per-deploy values that CI normally supplies via --set, + # so templating exercises the same paths as a real deploy. + helm template cats ./helm_deploy/cats \ + --namespace "cfocats-$env" \ + --values ./helm_deploy/cats/values-$env.yaml \ + --set app.enabled=true \ + --set worker.enabled=true \ + --set rabbitmq.enabled=true \ + --set redis.enabled=true \ + --set serviceAccountName="cfocats-$env" \ + --set app.serviceAccountName="cfocats-$env" \ + --set app.image.repository="example/cfocats" \ + --set app.image.tag="cats-validate" \ + --set worker.serviceAccountName="cfocats-$env" \ + --set worker.image.repository="example/cfocats" \ + --set worker.image.tag="worker-validate" \ + > /dev/null + echo "::endgroup::" + + echo "::group::helm template migrate ($env)" + helm template cats-migrate ./helm_deploy/cats \ + --namespace "cfocats-$env" \ + --values ./helm_deploy/cats/values-$env.yaml \ + --set migrator.enabled=true \ + --set serviceAccountName="cfocats-$env" \ + --set migrator.image.repository="example/cfocats" \ + --set migrator.image.tag="migrator-validate" \ + > /dev/null + echo "::endgroup::" + + echo "::group::helm template seed ($env)" + helm template cats-seed ./helm_deploy/cats \ + --namespace "cfocats-$env" \ + --values ./helm_deploy/cats/values-$env.yaml \ + --set seeder.enabled=true \ + --set serviceAccountName="cfocats-$env" \ + --set seeder.image.repository="example/cfocats" \ + --set seeder.image.tag="seeder-validate" \ + > /dev/null + echo "::endgroup::" + done diff --git a/.gitignore b/.gitignore index 664db9536..800ed87f3 100644 --- a/.gitignore +++ b/.gitignore @@ -481,3 +481,7 @@ aspire-output/ # ls cache files for C# develop extension *csproj.lscache + +# Helm +helm_deploy/*/charts/ +helm_deploy/*/Chart.lock diff --git a/helm_deploy/cats/.helmignore b/helm_deploy/cats/.helmignore new file mode 100644 index 000000000..c33b73443 --- /dev/null +++ b/helm_deploy/cats/.helmignore @@ -0,0 +1,7 @@ +.git/ +.gitignore +*.tmproj +*.bak +*.orig +.vscode/ +.idea/ diff --git a/helm_deploy/cats/Chart.yaml b/helm_deploy/cats/Chart.yaml new file mode 100644 index 000000000..39ad19796 --- /dev/null +++ b/helm_deploy/cats/Chart.yaml @@ -0,0 +1,33 @@ +apiVersion: v2 +name: cats +description: | + HMPPS - Case Assessment and Tracking System (CATS) +type: application + +# Version of this chart. Bump on every change to the chart/values. +version: "0.1.0" + +# Mirrors the application version; the running image is selected via image tags at deploy time. +appVersion: "0.1.0" + +dependencies: + # Web tier (Blazor Server UI) — ingress, SignalR sticky sessions, multiple replicas. + - name: generic-service + alias: app + version: "3.17.2" + repository: https://ministryofjustice.github.io/hmpps-helm-charts + condition: app.enabled + + # Background worker (Quartz jobs) — single instance, no ingress. + - name: generic-service + alias: worker + version: "3.17.2" + repository: https://ministryofjustice.github.io/hmpps-helm-charts + condition: worker.enabled + + # todo: enable prometheus alerts + # https://user-guide.cloud-platform.service.justice.gov.uk/documentation/monitoring-an-app/how-to-create-alarms.html#creating-your-own-custom-alerts + # uncomment and re-run `helm dependency update ./helm_deploy/cats` to fetch it. + # - name: generic-prometheus-alerts + # version: "1.17.1" + # repository: https://ministryofjustice.github.io/hmpps-helm-charts diff --git a/helm_deploy/cats/templates/_helpers.tpl b/helm_deploy/cats/templates/_helpers.tpl new file mode 100644 index 000000000..147787657 --- /dev/null +++ b/helm_deploy/cats/templates/_helpers.tpl @@ -0,0 +1,24 @@ +{{/* +Environment variables that expose the MSSQL connection details from the +rds-mssql-instance-output namespace secret, plus the composed connection string. +Used by the migrator and seeder Pods. +*/}} +{{- define "cats.databaseEnv" -}} +- name: DATABASE_ADDRESS + valueFrom: + secretKeyRef: + name: rds-mssql-instance-output + key: rds_instance_address +- name: DATABASE_USERNAME + valueFrom: + secretKeyRef: + name: rds-mssql-instance-output + key: database_username +- name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: rds-mssql-instance-output + key: database_password +- name: ConnectionStrings__CatsDb + value: {{ .Values.connectionStrings.catsDb | quote }} +{{- end -}} diff --git a/helm_deploy/cats/templates/migrator-pod.yaml b/helm_deploy/cats/templates/migrator-pod.yaml new file mode 100644 index 000000000..d205e1db6 --- /dev/null +++ b/helm_deploy/cats/templates/migrator-pod.yaml @@ -0,0 +1,31 @@ +{{- if .Values.migrator.enabled }} +apiVersion: v1 +kind: Pod +metadata: + # Revision-suffixed so each upgrade of the cats-migrate release runs a fresh Pod + # (a Pod spec is immutable, so a stable name could not be re-applied). Helm prunes + # the previous revision's Pod on upgrade. + name: cats-migrator-{{ .Release.Revision }} + labels: + app: migrator +spec: + serviceAccountName: {{ .Values.serviceAccountName }} + restartPolicy: OnFailure + securityContext: + seccompProfile: + type: RuntimeDefault + runAsUser: 1001 + runAsGroup: 1001 + runAsNonRoot: true + containers: + - name: migrator + image: "{{ .Values.migrator.image.repository }}:{{ .Values.migrator.image.tag }}" + securityContext: + allowPrivilegeEscalation: false + privileged: false + capabilities: + drop: + - ALL + env: + {{- include "cats.databaseEnv" . | nindent 8 }} +{{- end }} diff --git a/helm_deploy/cats/templates/rabbitmq.yaml b/helm_deploy/cats/templates/rabbitmq.yaml new file mode 100644 index 000000000..1a97241e5 --- /dev/null +++ b/helm_deploy/cats/templates/rabbitmq.yaml @@ -0,0 +1,87 @@ +{{- if .Values.rabbitmq.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rabbitmq-deployment + labels: + app: rabbitmq +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + securityContext: + seccompProfile: + type: RuntimeDefault + runAsUser: 1001 + runAsGroup: 1001 + runAsNonRoot: true + serviceAccountName: {{ .Values.serviceAccountName }} + containers: + - name: rabbitmq + image: {{ .Values.rabbitmq.image | quote }} + imagePullPolicy: Always + ports: + - containerPort: 5672 + - containerPort: 15672 + securityContext: + allowPrivilegeEscalation: false + privileged: false + startupProbe: + tcpSocket: + port: 5672 + periodSeconds: 10 + failureThreshold: 30 + livenessProbe: + tcpSocket: + port: 5672 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + tcpSocket: + port: 5672 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + env: + - name: RABBITMQ_DEFAULT_USER + valueFrom: + secretKeyRef: + name: config + key: RABBIT_USER + - name: RABBITMQ_DEFAULT_PASS + valueFrom: + secretKeyRef: + name: config + key: RABBIT_PASS + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: rabbitmq-service + labels: + app: rabbitmq +spec: + selector: + app: rabbitmq + ports: + - name: amqp + port: 5672 + targetPort: 5672 + - name: management + port: 15672 + targetPort: 15672 +{{- end }} diff --git a/helm_deploy/cats/templates/rds-port-forward-deployment.yaml b/helm_deploy/cats/templates/rds-port-forward-deployment.yaml new file mode 100644 index 000000000..5bd03d83a --- /dev/null +++ b/helm_deploy/cats/templates/rds-port-forward-deployment.yaml @@ -0,0 +1,34 @@ +{{- if .Values.rdsPortForward.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rds-port-forward-deployment + labels: + app: rds-port-forward +spec: + replicas: 1 + selector: + matchLabels: + app: rds-port-forward + template: + metadata: + labels: + app: rds-port-forward + spec: + serviceAccountName: {{ .Values.serviceAccountName }} + containers: + - name: rds-port-forward + image: {{ .Values.rdsPortForward.image | quote }} + ports: + - containerPort: {{ .Values.rdsPortForward.localPort }} # this is your LOCAL_PORT inside the pod + env: + - name: REMOTE_HOST + valueFrom: + secretKeyRef: + name: rds-mssql-instance-output + key: rds_instance_address + - name: LOCAL_PORT + value: {{ .Values.rdsPortForward.localPort | quote }} # what the pod listens on + - name: REMOTE_PORT + value: {{ .Values.rdsPortForward.remotePort | quote }} # SQL Server in the cloud +{{- end }} diff --git a/helm_deploy/cats/templates/redis.yaml b/helm_deploy/cats/templates/redis.yaml new file mode 100644 index 000000000..168f606b9 --- /dev/null +++ b/helm_deploy/cats/templates/redis.yaml @@ -0,0 +1,80 @@ +{{- if .Values.redis.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-deployment + labels: + app: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + securityContext: + seccompProfile: + type: RuntimeDefault + runAsUser: 1001 + runAsGroup: 1001 + runAsNonRoot: true + serviceAccountName: {{ .Values.serviceAccountName }} + containers: + - name: redis + image: {{ .Values.redis.image | quote }} + imagePullPolicy: Always + # Used only as a SignalR backplane and Fusion cache, so all data is ephemeral + # and rebuildable. Disable RDB/AOF persistence to avoid the MISCONF + # "stop-writes-on-bgsave-error" failure when /data is not writable. + args: + - --save + - "" + - --appendonly + - "no" + ports: + - containerPort: 6379 + securityContext: + allowPrivilegeEscalation: false + privileged: false + startupProbe: + tcpSocket: + port: 6379 + periodSeconds: 5 + failureThreshold: 20 + livenessProbe: + tcpSocket: + port: 6379 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + tcpSocket: + port: 6379 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 256Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-service + labels: + app: redis +spec: + selector: + app: redis + ports: + - name: redis + port: 6379 + targetPort: 6379 +{{- end }} diff --git a/helm_deploy/cats/templates/seeder-pod.yaml b/helm_deploy/cats/templates/seeder-pod.yaml new file mode 100644 index 000000000..48027f6b0 --- /dev/null +++ b/helm_deploy/cats/templates/seeder-pod.yaml @@ -0,0 +1,31 @@ +{{- if .Values.seeder.enabled }} +apiVersion: v1 +kind: Pod +metadata: + # Revision-suffixed so each upgrade of the cats-seed release runs a fresh Pod + # (a Pod spec is immutable, so a stable name could not be re-applied). Helm prunes + # the previous revision's Pod on upgrade. + name: cats-seeder-{{ .Release.Revision }} + labels: + app: seeder +spec: + serviceAccountName: {{ .Values.serviceAccountName }} + restartPolicy: OnFailure + securityContext: + seccompProfile: + type: RuntimeDefault + runAsUser: 1001 + runAsGroup: 1001 + runAsNonRoot: true + containers: + - name: seeder + image: "{{ .Values.seeder.image.repository }}:{{ .Values.seeder.image.tag }}" + securityContext: + allowPrivilegeEscalation: false + privileged: false + capabilities: + drop: + - ALL + env: + {{- include "cats.databaseEnv" . | nindent 8 }} +{{- end }} diff --git a/helm_deploy/cats/values-dev.yaml b/helm_deploy/cats/values-dev.yaml new file mode 100644 index 000000000..8df5b263f --- /dev/null +++ b/helm_deploy/cats/values-dev.yaml @@ -0,0 +1,23 @@ +# Development overrides. Namespace: cfocats-dev + +app: + replicaCount: 3 + ingress: + host: cfocats-dev.live.cloud-platform.service.justice.gov.uk + env: + DOTNET_ENVIRONMENT: "Development" + Sentry__Environment: "Development-CloudPlatform" + +worker: + replicaCount: 1 + env: + DOTNET_ENVIRONMENT: "Development" + Sentry__Environment: "Development-CloudPlatform" + +# RDS bridge - disabled in non-development environments +rdsPortForward: + enabled: true + +# todo: enable prometheus alerts +# generic-prometheus-alerts: +# alertSeverity: cfo-alerts-nonprod diff --git a/helm_deploy/cats/values-production.yaml b/helm_deploy/cats/values-production.yaml new file mode 100644 index 000000000..88592e30d --- /dev/null +++ b/helm_deploy/cats/values-production.yaml @@ -0,0 +1,39 @@ +app: + replicaCount: 4 + ingress: + host: cfocats-production.live.cloud-platform.service.justice.gov.uk + className: modsec + # modsecurity_mode: On # Default to modsecurity_mode "DetectionOnly" until staging is tuned, then "On". + + resources: + requests: + cpu: 500m + memory: 1Gi + limits: + cpu: "2" + memory: 2Gi + env: + DOTNET_ENVIRONMENT: "Production" + Sentry__Environment: "Production-CloudPlatform" + +worker: + replicaCount: 1 + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + env: + DOTNET_ENVIRONMENT: "Production" + Sentry__Environment: "Production-CloudPlatform" + Features__PresenceHub__RelayUserPresenceNotifications: "false" + +# RDS bridge - disabled in non-development environments +rdsPortForward: + enabled: false + +# todo: enable prometheus alerts +# generic-prometheus-alerts: +# alertSeverity: cfo-alerts diff --git a/helm_deploy/cats/values-staging.yaml b/helm_deploy/cats/values-staging.yaml new file mode 100644 index 000000000..3f7554bb0 --- /dev/null +++ b/helm_deploy/cats/values-staging.yaml @@ -0,0 +1,22 @@ +app: + replicaCount: 3 + ingress: + host: cfocats-staging.live.cloud-platform.service.justice.gov.uk + env: + DOTNET_ENVIRONMENT: "Staging" + Sentry__Environment: "Staging-CloudPlatform" + +worker: + replicaCount: 1 + env: + DOTNET_ENVIRONMENT: "Staging" + Sentry__Environment: "Staging-CloudPlatform" + Features__PresenceHub__RelayUserPresenceNotifications: "false" + +# RDS bridge - disabled in non-development environments +rdsPortForward: + enabled: false + +# todo: enable prometheus alerts +# generic-prometheus-alerts: +# alertSeverity: cfo-alerts-nonprod diff --git a/helm_deploy/cats/values.yaml b/helm_deploy/cats/values.yaml new file mode 100644 index 000000000..a09120e33 --- /dev/null +++ b/helm_deploy/cats/values.yaml @@ -0,0 +1,223 @@ +serviceAccountName: "" + +# --------------------------------------------------------------------------- +# Component toggles — every workload is off by default and each release opts in +# via --set (see README). The shared values-.yaml files are used by all +# three releases, so enabling happens per release in CI, not in those files: +# app deploy : --set app.enabled=true worker.enabled=true rabbitmq.enabled=true redis.enabled=true +# migrate : --set migrator.enabled=true +# seed : --set seeder.enabled=true +# --------------------------------------------------------------------------- + +connectionStrings: + catsDb: &catsDb "Server=$(DATABASE_ADDRESS);Database=CatsDb;User Id=$(DATABASE_USERNAME);Password=$(DATABASE_PASSWORD);TrustServerCertificate=True;" + rabbit: &rabbit "amqp://$(RABBIT_USER):$(RABBIT_PASS)@rabbitmq-service:5672" + redis: &redis "redis-service:6379" + +app: + enabled: false + nameOverride: cats + fullnameOverride: cats + + replicaCount: 3 + + image: + repository: "" # set by CI via --set + tag: "" # set by CI via --set + pullPolicy: IfNotPresent + port: 8080 + + service: + enabled: true + type: ClusterIP + port: 8080 + + ingress: + enabled: true + path: / + healthPath: /health + tlsSecretName: "" + # ModSecurity WAF. Default class is the non-prod controller; production overrides to "modsec". + className: modsec-non-prod + modsecurity_enabled: true + # WAF engine: "DetectionOnly" (log only) or "On" (block). Flip per-env in overlays once tuned. + modsecurity_mode: DetectionOnly + modsecurity_snippet: | + SecAuditEngine On + SecRuleEngine {{ .Values.ingress.modsecurity_mode }} + SecDefaultAction "phase:2,pass,log,tag:github_team=hmpps-creating-future-opportunities-devs,tag:namespace={{ .Release.Namespace }}" + annotations: + nginx.ingress.kubernetes.io/affinity: "cookie" + nginx.ingress.kubernetes.io/session-cookie-name: "http-cookie" + nginx.ingress.kubernetes.io/session-cookie-expires: "172800" + nginx.ingress.kubernetes.io/session-cookie-max-age: "172800" + + startupProbe: + httpGet: + path: /alive + port: http + failureThreshold: 30 + periodSeconds: 5 + readinessProbe: + httpGet: + path: /health + port: http + periodSeconds: 10 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /alive + port: http + periodSeconds: 20 + failureThreshold: 3 + + resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: "1" + memory: 1Gi + + poddisruptionbudget: + enabled: true + minAvailable: 1 + + namespace_secrets: + rds-mssql-instance-output: + DATABASE_ADDRESS: "rds_instance_address" + DATABASE_USERNAME: "database_username" + DATABASE_PASSWORD: "database_password" + s3-bucket-output: + AWS__Bucket: "bucket_name" + config: + RABBIT_USER: "RABBIT_USER" + RABBIT_PASS: "RABBIT_PASS" + Sentry__Dsn: "SentryDsn" + + env: + ConnectionStrings__CatsDb: *catsDb + ConnectionStrings__rabbit: *rabbit + ConnectionStrings__redis: *redis + AWS__RootFolder: "Files" + Features__UseWorkerForJobs: "true" + Features__PresenceHub__Enabled: "true" + Features__PresenceHub__RelayUserPresenceNotifications: "true" + Features__UseSignalRBackplane: "true" + WorkerOptions__BaseUrl: "http://cats-worker:8080" + Sentry__Release: "0.0.0" # overridden by CI + AppConfigurationSettings__Version: "0.0.0" # overridden by CI + +# --------------------------------------------------------------------------- +# Background worker +# --------------------------------------------------------------------------- +worker: + enabled: false + nameOverride: cats-worker + fullnameOverride: cats-worker + + replicaCount: 1 + + autoscaling: + enabled: false + + strategy: + type: Recreate + rollingUpdate: null + + image: + repository: "" # set by CI via --set + tag: "" # set by CI via --set + pullPolicy: IfNotPresent + port: 8080 + + service: + enabled: true + type: ClusterIP + port: 8080 + + ingress: + enabled: false + + poddisruptionbudget: + enabled: false + + startupProbe: + httpGet: + path: /alive + port: http + failureThreshold: 30 + periodSeconds: 5 + readinessProbe: + httpGet: + path: /health + port: http + periodSeconds: 10 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /alive + port: http + periodSeconds: 20 + failureThreshold: 3 + + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi + + namespace_secrets: + rds-mssql-instance-output: + DATABASE_ADDRESS: "rds_instance_address" + DATABASE_USERNAME: "database_username" + DATABASE_PASSWORD: "database_password" + config: + RABBIT_USER: "RABBIT_USER" + RABBIT_PASS: "RABBIT_PASS" + Sentry__Dsn: "SentryDsn" + + env: + ConnectionStrings__CatsDb: *catsDb + ConnectionStrings__rabbit: *rabbit + Sentry__Release: "0.0.0" + AppConfigurationSettings__Version: "0.0.0" + +rabbitmq: + enabled: false + image: "rabbitmq:4.3-management-alpine@sha256:1a43764bdcf116542e7c8c794adc67c79461727da16d474e9e21483fe7f716d3" + +redis: + enabled: false + image: "redis:7.4-alpine@sha256:b1addbe72465a718643cff9e60a58e6df1841e29d6d7d60c9a85d8d72f08d1a7" + +migrator: + enabled: false + image: + repository: "" # set by CI via --set + tag: "" # set by CI via --set (migrator-) + +seeder: + enabled: false + image: + repository: "" # set by CI via --set + tag: "" # set by CI via --set (seeder-) + +# --------------------------------------------------------------------------- +# RDS port-forward — an ad-hoc helper Deployment that bridges to the cloud RDS +# SQL Server so it can be reached via `kubectl port-forward`. Off in +# non-development environments. +# --------------------------------------------------------------------------- +rdsPortForward: + enabled: false + image: "ministryofjustice/port-forward@sha256:eaed873978acf6ccf23f08315f56ef71f0a7ffcd6a0bea821459bfb363b65e76" + localPort: 11433 # port the pod listens on (your LOCAL_PORT) + remotePort: 1433 # SQL Server port in the cloud + +# todo: enable prometheus alerts +# generic-prometheus-alerts: +# targetApplication: cats +# businessUnit: hmpps +# alertSeverity: cfo-alerts diff --git a/infra/cats-deployment.yml b/infra/cats-deployment.yml deleted file mode 100644 index 2870dc6dc..000000000 --- a/infra/cats-deployment.yml +++ /dev/null @@ -1,113 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: cats-deployment - labels: - app: cats -spec: - replicas: 3 - selector: - matchLabels: - app: cats # this should match the selector in service.yml - template: - metadata: - labels: - app: cats # this should match the selector in service.yml - spec: - securityContext: - seccompProfile: - type: RuntimeDefault - runAsUser: 1001 - runAsGroup: 1001 - runAsNonRoot: true - serviceAccountName: ${NAMESPACE} - containers: - - name: cats - image: ${REGISTRY}/${REPOSITORY}:cats-${IMAGE_TAG} - ports: - - containerPort: 8080 - startupProbe: - httpGet: - path: /alive - port: 8080 - failureThreshold: 30 - periodSeconds: 5 - readinessProbe: - httpGet: - path: /health - port: 8080 - periodSeconds: 10 - failureThreshold: 3 - livenessProbe: - httpGet: - path: /alive - port: 8080 - periodSeconds: 20 - failureThreshold: 3 - securityContext: - allowPrivilegeEscalation: false - privileged: false - capabilities: - drop: ["ALL"] - env: - - name: DATABASE_ADDRESS - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: rds_instance_address - - name: DATABASE_USERNAME - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: database_username - - name: DATABASE_PASSWORD - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: database_password - - name: AWS__Bucket - valueFrom: - secretKeyRef: - name: s3-bucket-output - key: bucket_name - - name: RABBIT_USER - valueFrom: - secretKeyRef: - name: config - key: RABBIT_USER - - name: RABBIT_PASS - valueFrom: - secretKeyRef: - name: config - key: RABBIT_PASS - - name: DOTNET_ENVIRONMENT - value: "${DOTNET_ENVIRONMENT}" - - name: ConnectionStrings__CatsDb - value: "Server=$(DATABASE_ADDRESS);Database=CatsDb;User Id=$(DATABASE_USERNAME);Password=$(DATABASE_PASSWORD);TrustServerCertificate=True;" - - name: ConnectionStrings__rabbit - value: "amqp://$(RABBIT_USER):$(RABBIT_PASS)@rabbitmq-service:5672" - - name: ConnectionStrings__redis - value: "redis-service:6379" - - name: AWS__RootFolder - value: "Files" - - name: Sentry__Dsn - valueFrom: - secretKeyRef: - name: config - key: SentryDsn - - name: Sentry__Environment - value: "${DOTNET_ENVIRONMENT}-CloudPlatform" - - name: Sentry__Release - value: "${APP_VERSION}" - - name: AppConfigurationSettings__Version - value: "${APP_VERSION}" - - name: Features__UseWorkerForJobs - value: "true" - - name: Features__PresenceHub__Enabled - value: "true" - - name: Features__PresenceHub__RelayUserPresenceNotifications - value: "true" - - name: Features__UseSignalRBackplane - value: "true" - - name: WorkerOptions__BaseUrl - value: "http://cats-worker-service:8080" diff --git a/infra/cats-ingress.yml b/infra/cats-ingress.yml deleted file mode 100644 index 9ffde4ef4..000000000 --- a/infra/cats-ingress.yml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: cats-ingress - annotations: - external-dns.alpha.kubernetes.io/set-identifier: cats-ingress-${NAMESPACE}-green - external-dns.alpha.kubernetes.io/aws-weight: "100" - # Enable stickiness - nginx.ingress.kubernetes.io/affinity: "cookie" - nginx.ingress.kubernetes.io/session-cookie-name: "http-cookie" - nginx.ingress.kubernetes.io/session-cookie-expires: "172800" - nginx.ingress.kubernetes.io/session-cookie-max-age: "172800" -spec: - ingressClassName: default # modsec - tls: - - hosts: - - ${NAMESPACE}.live.cloud-platform.service.justice.gov.uk - rules: - - host: ${NAMESPACE}.live.cloud-platform.service.justice.gov.uk - http: - paths: - - path: / - pathType: ImplementationSpecific - backend: - service: - name: cats-service # this should match the metadata.name in service.yml - port: - number: 8080 diff --git a/infra/cats-service.yml b/infra/cats-service.yml deleted file mode 100644 index d4a74dc2a..000000000 --- a/infra/cats-service.yml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: cats-service -spec: - selector: - app: cats # this should match the pod label in deployment.yml - ports: - - name: http - port: 8080 - targetPort: 8080 diff --git a/infra/cats-worker-deployment.yml b/infra/cats-worker-deployment.yml deleted file mode 100644 index 1baed419e..000000000 --- a/infra/cats-worker-deployment.yml +++ /dev/null @@ -1,94 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: cats-worker-deployment - labels: - app: cats-worker -spec: - replicas: 1 # Quartz jobs must not run concurrently across multiple pods - selector: - matchLabels: - app: cats-worker - template: - metadata: - labels: - app: cats-worker - spec: - securityContext: - seccompProfile: - type: RuntimeDefault - runAsUser: 1001 - runAsGroup: 1001 - runAsNonRoot: true - serviceAccountName: ${NAMESPACE} - containers: - - name: cats-worker - image: ${REGISTRY}/${REPOSITORY}:worker-${IMAGE_TAG} - ports: - - containerPort: 8080 - startupProbe: - httpGet: - path: /alive - port: 8080 - failureThreshold: 30 - periodSeconds: 5 - readinessProbe: - httpGet: - path: /health - port: 8080 - periodSeconds: 10 - failureThreshold: 3 - livenessProbe: - httpGet: - path: /alive - port: 8080 - periodSeconds: 20 - failureThreshold: 3 - securityContext: - allowPrivilegeEscalation: false - privileged: false - capabilities: - drop: ["ALL"] - env: - - name: DATABASE_ADDRESS - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: rds_instance_address - - name: DATABASE_USERNAME - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: database_username - - name: DATABASE_PASSWORD - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: database_password - - name: RABBIT_USER - valueFrom: - secretKeyRef: - name: config - key: RABBIT_USER - - name: RABBIT_PASS - valueFrom: - secretKeyRef: - name: config - key: RABBIT_PASS - - name: DOTNET_ENVIRONMENT - value: "${DOTNET_ENVIRONMENT}" - - name: ConnectionStrings__CatsDb - value: "Server=$(DATABASE_ADDRESS);Database=CatsDb;User Id=$(DATABASE_USERNAME);Password=$(DATABASE_PASSWORD);TrustServerCertificate=True;" - - name: ConnectionStrings__rabbit - value: "amqp://$(RABBIT_USER):$(RABBIT_PASS)@rabbitmq-service:5672" - - name: Sentry__Dsn - valueFrom: - secretKeyRef: - name: config - key: SentryDsn - - name: Sentry__Environment - value: "${DOTNET_ENVIRONMENT}-CloudPlatform" - - name: Sentry__Release - value: "${APP_VERSION}" - - name: AppConfigurationSettings__Version - value: "${APP_VERSION}" diff --git a/infra/cats-worker-service.yml b/infra/cats-worker-service.yml deleted file mode 100644 index 6b52dc57e..000000000 --- a/infra/cats-worker-service.yml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: cats-worker-service -spec: - selector: - app: cats-worker - ports: - - name: http - port: 8080 - targetPort: 8080 diff --git a/infra/migrator-pod.yml b/infra/migrator-pod.yml deleted file mode 100644 index ae3fccfa0..000000000 --- a/infra/migrator-pod.yml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: migrator-${IMAGE_TAG} - labels: - app: migrator -spec: - serviceAccountName: ${NAMESPACE} - restartPolicy: OnFailure - securityContext: - seccompProfile: - type: RuntimeDefault - runAsUser: 1001 - runAsGroup: 1001 - runAsNonRoot: true - containers: - - name: migrator - image: ${REGISTRY}/${REPOSITORY}:migrator-${IMAGE_TAG} - securityContext: - allowPrivilegeEscalation: false - privileged: false - capabilities: - drop: ["ALL"] - env: - - name: DATABASE_ADDRESS - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: rds_instance_address - - name: DATABASE_USERNAME - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: database_username - - name: DATABASE_PASSWORD - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: database_password - - name: ConnectionStrings__CatsDb - value: "Server=$(DATABASE_ADDRESS);Database=CatsDb;User Id=$(DATABASE_USERNAME);Password=$(DATABASE_PASSWORD);TrustServerCertificate=True;" diff --git a/infra/port-forward-deployment.yml b/infra/port-forward-deployment.yml deleted file mode 100644 index a31abad30..000000000 --- a/infra/port-forward-deployment.yml +++ /dev/null @@ -1,32 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: port-forward-deployment - labels: - app: port-forward -spec: - replicas: 1 - selector: - matchLabels: - app: port-forward - template: - metadata: - labels: - app: port-forward - spec: - serviceAccountName: ${NAMESPACE} - containers: - - name: port-forward - image: ministryofjustice/port-forward@sha256:eaed873978acf6ccf23f08315f56ef71f0a7ffcd6a0bea821459bfb363b65e76 - ports: - - containerPort: 11433 # this is your LOCAL_PORT inside the pod - env: - - name: REMOTE_HOST - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: rds_instance_address - - name: LOCAL_PORT - value: "11433" # what the pod listens on - - name: REMOTE_PORT - value: "1433" # SQL Server in the cloud \ No newline at end of file diff --git a/infra/rabbitmq-deployment.yml b/infra/rabbitmq-deployment.yml deleted file mode 100644 index 4ea6ba7d3..000000000 --- a/infra/rabbitmq-deployment.yml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: rabbitmq-deployment - labels: - app: rabbitmq -spec: - replicas: 1 - selector: - matchLabels: - app: rabbitmq # this should match the selector in service.yml - template: - metadata: - labels: - app: rabbitmq # this should match the selector in service.yml - spec: - securityContext: - seccompProfile: - type: RuntimeDefault - runAsUser: 1001 - runAsGroup: 1001 - runAsNonRoot: true - serviceAccountName: ${NAMESPACE} - containers: - - name: rabbitmq - image: rabbitmq:4.3-management-alpine@sha256:1a43764bdcf116542e7c8c794adc67c79461727da16d474e9e21483fe7f716d3 - imagePullPolicy: Always - ports: - - containerPort: 5672 - - containerPort: 15672 - securityContext: - allowPrivilegeEscalation: false - privileged: false - env: - - name: RABBITMQ_DEFAULT_USER - valueFrom: - secretKeyRef: - name: config - key: RABBIT_USER - - name: RABBITMQ_DEFAULT_PASS - valueFrom: - secretKeyRef: - name: config - key: RABBIT_PASS \ No newline at end of file diff --git a/infra/rabbitmq-service.yml b/infra/rabbitmq-service.yml deleted file mode 100644 index dab2d7963..000000000 --- a/infra/rabbitmq-service.yml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: rabbitmq-service -spec: - selector: - app: rabbitmq # this should match the pod label in deployment.yml - ports: - - name: amqp - port: 5672 - targetPort: 5672 - - name: management - port: 15672 - targetPort: 15672 \ No newline at end of file diff --git a/infra/redis-deployment.yml b/infra/redis-deployment.yml deleted file mode 100644 index 6acf8e3b3..000000000 --- a/infra/redis-deployment.yml +++ /dev/null @@ -1,40 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: redis-deployment - labels: - app: redis -spec: - replicas: 1 - selector: - matchLabels: - app: redis # this should match the selector in service.yml - template: - metadata: - labels: - app: redis # this should match the selector in service.yml - spec: - securityContext: - seccompProfile: - type: RuntimeDefault - runAsUser: 1001 - runAsGroup: 1001 - runAsNonRoot: true - serviceAccountName: ${NAMESPACE} - containers: - - name: redis - image: redis:7.4-alpine@sha256:b1addbe72465a718643cff9e60a58e6df1841e29d6d7d60c9a85d8d72f08d1a7 - imagePullPolicy: Always - # Used only as a SignalR backplane and Fusion cache, so all data is - # ephemeral and rebuildable. Disable RDB/AOF persistence to avoid the - # MISCONF "stop-writes-on-bgsave-error" failure when /data is not writable. - args: - - --save - - "" - - --appendonly - - "no" - ports: - - containerPort: 6379 - securityContext: - allowPrivilegeEscalation: false - privileged: false diff --git a/infra/redis-service.yml b/infra/redis-service.yml deleted file mode 100644 index f7cd28ed2..000000000 --- a/infra/redis-service.yml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: redis-service -spec: - selector: - app: redis # this should match the pod label in deployment.yml - ports: - - name: redis - port: 6379 - targetPort: 6379 diff --git a/infra/seeder-pod.yml b/infra/seeder-pod.yml deleted file mode 100644 index ca8c18acb..000000000 --- a/infra/seeder-pod.yml +++ /dev/null @@ -1,43 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: seeder-${IMAGE_TAG} - labels: - app: seeder -spec: - serviceAccountName: ${NAMESPACE} - restartPolicy: OnFailure - securityContext: - seccompProfile: - type: RuntimeDefault - runAsUser: 1001 - runAsGroup: 1001 - runAsNonRoot: true - containers: - - name: seeder - image: ${REGISTRY}/${REPOSITORY}:seeder-${IMAGE_TAG} - securityContext: - allowPrivilegeEscalation: false - privileged: false - capabilities: - drop: ["ALL"] - env: - - name: DATABASE_ADDRESS - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: rds_instance_address - - name: DATABASE_USERNAME - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: database_username - - name: DATABASE_PASSWORD - valueFrom: - secretKeyRef: - name: rds-mssql-instance-output - key: database_password - - name: DOTNET_ENVIRONMENT - value: "${DOTNET_ENVIRONMENT}" - - name: ConnectionStrings__CatsDb - value: "Server=$(DATABASE_ADDRESS);Database=CatsDb;User Id=$(DATABASE_USERNAME);Password=$(DATABASE_PASSWORD);TrustServerCertificate=True;" diff --git a/src/Database/Dockerfile b/src/Database/Dockerfile index c90657531..732cac416 100644 --- a/src/Database/Dockerfile +++ b/src/Database/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0@sha256:548d93f8a18a1acbe6cc127bc4f47281430d34a9e35c18afa80a8d6741c2adc3 AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0.300@sha256:c0790639332692a0d56cdd81ed581cfd24d040d9839764c138994866df89a3b6 AS build WORKDIR /src # Install sqlpackage to a fixed, non-root path @@ -12,7 +12,7 @@ COPY src/Database/CatsDb/ src/Database/CatsDb/ RUN dotnet build src/Database/CatsDb/CatsDb.sqlproj --configuration Release -FROM mcr.microsoft.com/dotnet/runtime:10.0@sha256:58318ab0733b63d3ac0d7609c46f2718244e623a176f45991ee01fad46fbf880 AS final +FROM mcr.microsoft.com/dotnet/runtime:10.0.9@sha256:58318ab0733b63d3ac0d7609c46f2718244e623a176f45991ee01fad46fbf880 AS final WORKDIR /app # Copy sqlpackage and make it accessible to non-root users diff --git a/src/DatabaseSeeding/Dockerfile b/src/DatabaseSeeding/Dockerfile new file mode 100644 index 000000000..b05f8d6a9 --- /dev/null +++ b/src/DatabaseSeeding/Dockerfile @@ -0,0 +1,20 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0.300@sha256:c0790639332692a0d56cdd81ed581cfd24d040d9839764c138994866df89a3b6 AS build +WORKDIR /src + +# Copy solution-level config required to restore and build +COPY Directory.Build.props Directory.Packages.props NuGet.config global.json ./ +COPY src/ src/ + +RUN dotnet restore src/DatabaseSeeding/DatabaseSeeding.csproj +RUN dotnet publish src/DatabaseSeeding/DatabaseSeeding.csproj \ + --configuration Release \ + --no-restore \ + --output /app/publish + + +FROM mcr.microsoft.com/dotnet/runtime:10.0.9@sha256:58318ab0733b63d3ac0d7609c46f2718244e623a176f45991ee01fad46fbf880 AS final +WORKDIR /app + +COPY --from=build /app/publish ./ + +ENTRYPOINT ["dotnet", "DatabaseSeeding.dll"] diff --git a/src/Server.UI/Dockerfile b/src/Server.UI/Dockerfile new file mode 100644 index 000000000..1af0998e8 --- /dev/null +++ b/src/Server.UI/Dockerfile @@ -0,0 +1,22 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0.300@sha256:c0790639332692a0d56cdd81ed581cfd24d040d9839764c138994866df89a3b6 AS build +WORKDIR /src + +# Copy solution-level config required to restore and build +COPY Directory.Build.props Directory.Packages.props NuGet.config global.json ./ +COPY src/ src/ + +RUN dotnet restore src/Server.UI/Server.UI.csproj +RUN dotnet publish src/Server.UI/Server.UI.csproj \ + --configuration Release \ + --no-restore \ + --output /app/publish + + +FROM mcr.microsoft.com/dotnet/aspnet:10.0.9@sha256:ddcf70ad1ab963a4fcd41fbd722a6b660e404e87567cfbd46fd2809c21b02088 AS final +WORKDIR /app + +COPY --from=build /app/publish ./ +USER $APP_UID + +EXPOSE 8080 +ENTRYPOINT ["dotnet", "Cfo.Cats.Server.UI.dll"] diff --git a/src/Worker/Dockerfile b/src/Worker/Dockerfile new file mode 100644 index 000000000..3174c4084 --- /dev/null +++ b/src/Worker/Dockerfile @@ -0,0 +1,22 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0.300@sha256:c0790639332692a0d56cdd81ed581cfd24d040d9839764c138994866df89a3b6 AS build +WORKDIR /src + +# Copy solution-level config required to restore and build +COPY Directory.Build.props Directory.Packages.props NuGet.config global.json ./ +COPY src/ src/ + +RUN dotnet restore src/Worker/Worker.csproj +RUN dotnet publish src/Worker/Worker.csproj \ + --configuration Release \ + --no-restore \ + --output /app/publish + + +FROM mcr.microsoft.com/dotnet/aspnet:10.0.9@sha256:ddcf70ad1ab963a4fcd41fbd722a6b660e404e87567cfbd46fd2809c21b02088 AS final +WORKDIR /app + +COPY --from=build /app/publish ./ +USER $APP_UID + +EXPOSE 8080 +ENTRYPOINT ["dotnet", "Cfo.Cats.Worker.dll"]