diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0f836ba..b104e50 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,6 @@ on: jobs: build-push-php: - environment: PROD runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -19,7 +18,6 @@ jobs: IMAGE_REPOSITORY: ${{ vars.IMAGE_REPO }} build-push-caddy: - environment: PROD # Same Dockerfile as php, with a build target which is after needs: [build-push-php] runs-on: ubuntu-latest diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 7ee093e..332d294 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -5,6 +5,9 @@ on: push: branches: - main + - staging + pull_request: + types: [ opened, reopened, synchronize, labeled ] workflow_dispatch: ~ permissions: @@ -14,11 +17,23 @@ permissions: packages: write jobs: + remove-deploy-label: + name: Remove deploy label + if: github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') + runs-on: ubuntu-latest + steps: + - uses: mondeja/remove-labels-gh-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + labels: | + deploy build: + if: github.event_name != 'pull_request' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy')) name: Build uses: ./.github/workflows/build.yml deploy: + if: github.event_name != 'pull_request' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy')) name: Deploy needs: [ build ] uses: ./.github/workflows/deploy.yml @@ -30,6 +45,6 @@ jobs: storage-secret-key: ${{ secrets.STORAGE_SECRET_KEY}} project-id: ${{ secrets.PROJECT_ID }} workload-identity-provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }} - database-url: ${{ secrets.DATABASE_URL }} + pg-password: ${{ secrets.PG_PASSWORD }} mailer-dsn: ${{ secrets.MAILER_DSN }} sms-dsn: ${{ secrets.SMS_DSN }} diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml new file mode 100644 index 0000000..6ac07d5 --- /dev/null +++ b/.github/workflows/cleanup.yml @@ -0,0 +1,75 @@ +name: Cleanup + +on: + pull_request: + types: [ closed ] + +permissions: + id-token: write + contents: read + pull-requests: write + +jobs: + meta: + name: Meta + runs-on: ubuntu-latest + outputs: + context: ${{ steps.meta.outputs.context }} + environment: ${{ steps.meta.outputs.environment }} + namespace: ${{ steps.meta.outputs.namespace }} + + steps: + - name: Generate metadata + id: meta + run: | + set -xo pipefail + PROJECT=plateforme-ebs + CONTEXT=nonprod + ENVIRONMENT=nonprod + + echo "context=${CONTEXT}" >> $GITHUB_OUTPUT + echo "environment=${ENVIRONMENT}" >> $GITHUB_OUTPUT + echo "namespace=${CONTEXT}-${PROJECT}" >> $GITHUB_OUTPUT + + + cleanup: + name: Cleanup + runs-on: ubuntu-latest + environment: + name: ${{ needs.meta.outputs.environment }} + needs: ["meta"] + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: GKE Auth + uses: 'google-github-actions/auth@v2' + with: + project_id: '${{ secrets.PROJECT_ID }}' + workload_identity_provider: '${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}' + + - name: Setup gcloud + uses: google-github-actions/setup-gcloud@v1 + with: + project_id: ${{ secrets.PROJECT_ID }} + + - name: Connect cluster + run: | + gcloud components install gke-gcloud-auth-plugin + gcloud auth login --cred-file=$CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE + gcloud container clusters get-credentials ${{ vars.CLUSTER_NAME }} --region europe-west1 --project ${{ secrets.PROJECT_ID }} + kubectl config view + + - name: Uninstall helm release + id: uninstall_helm_release + run: | + export RELEASE_NAME=pr-$(jq --raw-output .pull_request.number $GITHUB_EVENT_PATH) + echo "Uninstalling release $RELEASE_NAME in namespace ${{ needs.meta.outputs.namespace }}..." + if ! helm uninstall $RELEASE_NAME --namespace ${{ needs.meta.outputs.namespace }} --wait ; then + echo "HELM Uninstall has failed !" + echo "Please ask the SRE team to manually clean remaining objects" + exit 1 + fi + echo "HELM uninstall successfull" + echo "Cleaning remaining PVC..." + kubectl delete pvc -l app.kubernetes.io/instance=$RELEASE_NAME --namespace ${{ needs.meta.outputs.namespace }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ed5218f..82fb858 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -23,6 +23,9 @@ on: required: true database-url: description: Database URL + required: false + pg-password: + description: PostgreSQL password for CNPG cluster required: true mailer-dsn: description: Mailer DSN @@ -55,15 +58,32 @@ jobs: run: | set -xo pipefail PROJECT=${{ vars.PROJECT_NAME }} - # Tags are deployed in prod - CONTEXT=prod - ENVIRONMENT=prod + if [[ "${{ github.ref_name }}" == "main" ]]; then + # Tags are deployed in prod + CONTEXT=prod + ENVIRONMENT=prod + IMAGE_TAG=${{ github.ref_name }} + RELEASE_NAME=prod + TRUSTED_HOST=$(echo ${{ vars.DOMAIN }} | sed 's/\./\\\\\\\\./g') + URL=${{ vars.DOMAIN }} + STORAGE_NAME=${{ vars.PROD_STORAGE_BUCKET }} + else + CONTEXT=nonprod + ENVIRONMENT=nonprod + IMAGE_TAG=sha-${GITHUB_SHA::7} + if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then + RELEASE_NAME=pr-$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") + STORAGE_NAME=${{ vars.STORAGE_BUCKET }}-pr + else + RELEASE_NAME=${{ github.ref_name }} + STORAGE_NAME=${{ vars.NONPROD_STORAGE_BUCKET }}-main + fi + URL=${RELEASE_NAME}.${{ vars.NONPROD_DOMAIN }} + TRUSTED_HOST=$(echo ${URL} | sed 's/\./\\\\\\\\./g') + fi PHP_IMAGE_REPO=${{ vars.IMAGE_REPO }}/${{ vars.IMAGE_NAME_PHP }} CADDY_IMAGE_REPO=${{ vars.IMAGE_REPO }}/${{ vars.IMAGE_NAME_CADDY }} - IMAGE_TAG=sha-${GITHUB_SHA::7} - RELEASE_NAME=prod - TRUSTED_HOST=$(echo ${{ vars.DOMAIN }} | sed 's/\./\\\\\\\\./g') - + echo "url=${URL}" >> $GITHUB_OUTPUT echo "trusted_host=${TRUSTED_HOST}" >> $GITHUB_OUTPUT echo "context=${CONTEXT}" >> $GITHUB_OUTPUT echo "environment=${ENVIRONMENT}" >> $GITHUB_OUTPUT @@ -72,6 +92,7 @@ jobs: echo "caddy_image_repo=${CADDY_IMAGE_REPO}" >> $GITHUB_OUTPUT echo "release_name=${RELEASE_NAME}" >> $GITHUB_OUTPUT echo "namespace=${CONTEXT}-${PROJECT}" >> $GITHUB_OUTPUT + echo "storage_name=${STORAGE_NAME}" >> $GITHUB_OUTPUT deploy: name: Deploy @@ -129,15 +150,14 @@ jobs: --set=php.image.tag=${{ needs.meta.outputs.image_tag }} \ --set=caddy.image.repository=${{ needs.meta.outputs.caddy_image_repo }} \ --set=caddy.image.tag=${{ needs.meta.outputs.image_tag }} \ - --set=ingress.hosts[0].host=${{ vars.DOMAIN }} \ + --set=ingress.hosts[0].host=${{ needs.meta.outputs.url }} \ --set=ingress.tls[0].secretName=${{ needs.meta.outputs.release_name }}-tls \ - --set=ingress.tls[0].hosts[0]=${{ vars.DOMAIN }} \ - --set=postgresql.url="${{ secrets.database-url }}" \ - --set=postgresql.enabled='${{ github.event_name == 'pull_request' }}' \ + --set=ingress.tls[0].hosts[0]=${{ needs.meta.outputs.url }} \ --set=payum.apikey="${{ secrets.payum-apikey }}" \ + --set=cnpg.credentials.password="${{ secrets.pg-password }}" \ --set=mailer.dsn="${{ secrets.mailer-dsn }}" \ --set=sms.dsn="${{ secrets.sms-dsn }}" \ - --set=php.storage.bucket="${{ vars.STORAGE_BUCKET }}" \ + --set=php.storage.bucket="${{ needs.meta.outputs.storage_name }}" \ --set=php.storage.endpoint="https://storage.googleapis.com" \ --set=php.storage.region="eu-west-1" \ --set=php.storage.usePathStyleEndpoint=true \ diff --git a/Dockerfile b/Dockerfile index 0ec384a..75c94bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ RUN yarn build FROM php:${PHP_VERSION}-fpm-alpine AS app_php # needed for security update until base image is updated -#RUN apk upgrade libcurl curl openssl openssl-dev libressl libcrypto3 libssl3 +RUN apk upgrade --no-cache libcurl curl openssl openssl-dev libressl libcrypto3 libssl3 nghttp2-libs # Allow to use development versions of Symfony ARG STABILITY="stable" @@ -196,6 +196,6 @@ COPY --from=app_php /srv/app/public public/ COPY docker/caddy/Caddyfile /etc/caddy/Caddyfile # needed for security update until base image is updated -#RUN apk upgrade libcurl curl openssl openssl-dev libressl libcrypto1.1 libssl1.1 libcrypto3 libssl3 +RUN apk upgrade --no-cache libcurl curl openssl openssl-dev libcrypto3 libssl3 nghttp2-libs WORKDIR /srv/app diff --git a/helm/chart/Chart.yaml b/helm/chart/Chart.yaml index 570186a..f288cfc 100644 --- a/helm/chart/Chart.yaml +++ b/helm/chart/Chart.yaml @@ -25,11 +25,6 @@ version: 0.0.1 appVersion: 0.0.1 dependencies: - # bitnami chart are using the workaround from https://github.com/bitnami/charts/issues/10539 - - name: postgresql - version: ~11.9.13 - repository: https://charts.bitnami.com/bitnami/ - condition: postgresql.enabled - name: external-dns version: ~5.4.15 repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami diff --git a/helm/chart/templates/NOTES.txt b/helm/chart/templates/NOTES.txt index 059053a..2dcdef5 100644 --- a/helm/chart/templates/NOTES.txt +++ b/helm/chart/templates/NOTES.txt @@ -1,3 +1,8 @@ +{{- if .Values.cnpg.enabled }} +IMPORTANT: This chart requires the CloudNativePG operator to be installed in your cluster. + helm upgrade --install cnpg --namespace cnpg-system --create-namespace cloudnative-pg/cloudnative-pg + See: https://cloudnative-pg.io/documentation/current/installation_upgrade/ +{{ end }} 1. Get the application URL by running these commands: {{- if .Values.ingress.enabled }} {{- range $host := .Values.ingress.hosts }} diff --git a/helm/chart/templates/_helpers.tpl b/helm/chart/templates/_helpers.tpl index b6a5d3b..889ec12 100644 --- a/helm/chart/templates/_helpers.tpl +++ b/helm/chart/templates/_helpers.tpl @@ -80,6 +80,13 @@ app.kubernetes.io/name: {{ include "plateforme-ebs.name" . }}-pwa app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} +{{/* +CNPG cluster name +*/}} +{{- define "plateforme-ebs.cnpgClusterName" -}} +{{- printf "%s-postgresql" (include "plateforme-ebs" .) }} +{{- end }} + {{/* Create the name of the service account to use */}} diff --git a/helm/chart/templates/cnpg-cluster.yaml b/helm/chart/templates/cnpg-cluster.yaml new file mode 100644 index 0000000..80cee44 --- /dev/null +++ b/helm/chart/templates/cnpg-cluster.yaml @@ -0,0 +1,38 @@ +{{- if .Values.cnpg.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "plateforme-ebs.cnpgClusterName" . }}-credentials + labels: + {{- include "plateforme-ebs.labels" . | nindent 4 }} +type: kubernetes.io/basic-auth +stringData: + username: {{ .Values.cnpg.owner | quote }} + password: {{ .Values.cnpg.credentials.password | quote }} +--- +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: {{ include "plateforme-ebs.cnpgClusterName" . }} + labels: + {{- include "plateforme-ebs.labels" . | nindent 4 }} +spec: + instances: {{ .Values.cnpg.instances }} + bootstrap: + initdb: + database: {{ .Values.cnpg.database | quote }} + owner: {{ .Values.cnpg.owner | quote }} + secret: + name: {{ include "plateforme-ebs.cnpgClusterName" . }}-credentials + postInitApplicationSQL: + - ALTER ROLE {{ .Values.cnpg.owner }} CREATEDB; + storage: + size: {{ .Values.cnpg.storage.size | quote }} + {{- if .Values.cnpg.storage.storageClass }} + storageClass: {{ .Values.cnpg.storage.storageClass | quote }} + {{- end }} + {{- with .Values.cnpg.resources }} + resources: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/chart/templates/secrets.yaml b/helm/chart/templates/secrets.yaml index 3efba32..5ac964a 100644 --- a/helm/chart/templates/secrets.yaml +++ b/helm/chart/templates/secrets.yaml @@ -6,8 +6,9 @@ metadata: {{- include "plateforme-ebs.labels" . | nindent 4 }} type: Opaque data: - {{- if .Values.postgresql.enabled }} - database-url: {{ printf "pgsql://%s:%s@%s-postgresql/%s?serverVersion=14&charset=utf8" .Values.postgresql.global.postgresql.auth.username .Values.postgresql.global.postgresql.auth.password .Release.Name .Values.postgresql.global.postgresql.auth.database | b64enc | quote }} + {{- if .Values.cnpg.enabled }} + database-url: {{ printf "postgresql://%s:%s@%s-rw/%s?serverVersion=%s&charset=utf8" .Values.cnpg.owner .Values.cnpg.credentials.password (include "plateforme-ebs.cnpgClusterName" .) .Values.cnpg.database .Values.cnpg.postgresql.version | b64enc | quote }} + cnpg-password: {{ .Values.cnpg.credentials.password | b64enc | quote }} {{- else }} database-url: {{ .Values.postgresql.url | b64enc | quote }} {{- end }} @@ -23,5 +24,5 @@ data: {{- end }} sms-dsn: {{ .Values.sms.dsn | b64enc | quote }} payum-apikey: {{ .Values.payum.apikey | b64enc | quote }} - php-storage-key: {{ .Values.php.storage.key | b64enc | quote }} - php-storage-secret: {{ .Values.php.storage.secret | b64enc | quote }} \ No newline at end of file + php-storage-key: {{ .Values.php.storage.key | default "" | b64enc | quote }} + php-storage-secret: {{ .Values.php.storage.secret | default "" | b64enc | quote }} \ No newline at end of file diff --git a/helm/chart/values-minikube.yml b/helm/chart/values-minikube.yml index 3f3a035..3e14a8f 100644 --- a/helm/chart/values-minikube.yml +++ b/helm/chart/values-minikube.yml @@ -14,10 +14,9 @@ payum: gateway: 'mollie' apikey: 'test' -postgresql: - auth: - # PostgreSQL password is set only the first time chart in installed - postgresPassword: change_me +cnpg: + credentials: + password: change_me maildev: enabled: true diff --git a/helm/chart/values-nonprod.yml b/helm/chart/values-nonprod.yml index 529a7af..066fc70 100644 --- a/helm/chart/values-nonprod.yml +++ b/helm/chart/values-nonprod.yml @@ -27,8 +27,9 @@ redis: storageClass: "standard" size: "1Gi" -postgresql: - url: change_me +cnpg: + credentials: + password: change_me php: fixtureJob: diff --git a/helm/chart/values-prod.yml b/helm/chart/values-prod.yml index 1eeb108..14fab08 100644 --- a/helm/chart/values-prod.yml +++ b/helm/chart/values-prod.yml @@ -27,8 +27,9 @@ redis: storageClass: "standard" size: "1Gi" -postgresql: - url: change_me +cnpg: + credentials: + password: change_me php: fixtureJob: diff --git a/helm/chart/values.yaml b/helm/chart/values.yaml index c84e867..f2a466e 100644 --- a/helm/chart/values.yaml +++ b/helm/chart/values.yaml @@ -69,34 +69,32 @@ mercure: jwtSecret: "!ChangeThisMercureHubJWTSecretKey!" extraDirectives: cors_origins http://ghcr.io https://ghcr.io -# Full configuration: https://github.com/bitnami/charts/tree/master/bitnami/postgresql -postgresql: +# CloudNativePG cluster configuration. +# The CNPG operator must be pre-installed in the cluster. +# See: https://cloudnative-pg.io/documentation/current/installation_upgrade/ +cnpg: enabled: true - # If bringing your own PostgreSQL, the full uri to use - # url: postgresql://plateforme-ebs:!ChangeMe!@database:5432/api?serverVersion=13&charset=utf8 - global: - postgresql: - auth: - username: "example" - password: "!ChangeMe!" - database: "api" - postgresPassword: "!ChangeMe!" - # Persistent Volume Storage configuration. - # ref: https://kubernetes.io/docs/user-guide/persistent-volumes - pullPolicy: IfNotPresent - image: - registry: docker.io - repository: bitnamilegacy/postgresql - tag: 14 - primary: - persistence: - enabled: true - storageClass: standard - size: 1Gi - resources: - requests: - memory: 50Mi - cpu: 1m + instances: 1 + postgresql: + version: "16" + database: app + owner: app + credentials: + # IMPORTANT: use only alphanumeric characters. Special characters (@, #, %, :) + # will break the DATABASE_URL parsing by PHP's parse_url(). + password: "ChangeMe" + storage: + size: 1Gi + storageClass: standard + resources: + requests: + memory: 50Mi + cpu: 1m + +# External PostgreSQL URL, used when cnpg.enabled is false. +# url: postgresql://app:!ChangeMe!@database:5432/app?serverVersion=16&charset=utf8 +postgresql: + url: "" payum: # @see https://my.mollie.com/dashboard/org_XXXXXXXX/developers/api-keys