diff --git a/.github/workflows/deploy-pr.yaml b/.github/workflows/deploy-pr.yaml index 54b6b33..c59f484 100644 --- a/.github/workflows/deploy-pr.yaml +++ b/.github/workflows/deploy-pr.yaml @@ -20,13 +20,26 @@ jobs: pr_number: ${{ github.event.pull_request.number }} secrets: inherit + get_urls: + if: github.event.action != 'closed' + name: Generate Preview URLs + uses: ./.github/workflows/url_generator.yml + with: + runner: vhs + mode: pr + pr_number: ${{ github.event.pull_request.number }} + base_domain: ${{ vars.DEV_BASE_DOMAIN }} + secrets: inherit + frontend: if: github.event.action != 'closed' name: Frontend - Build and Deploy to Cloudflare Pages (PR) + needs: [get_urls] uses: ./.github/workflows/frontend-pages.yml with: mode: pr pr_number: ${{ github.event.pull_request.number }} + backend_url_scheme: ${{ needs.get_urls.outputs.backend_url_scheme }} secrets: inherit deploy: @@ -36,7 +49,7 @@ jobs: concurrency: group: pr-${{ github.event.pull_request.number }} cancel-in-progress: false - needs: [build, frontend] + needs: [build, frontend, get_urls] steps: - name: Checkout uses: actions/checkout@v4 @@ -62,25 +75,24 @@ jobs: DEV_BASE_DOMAIN: ${{ secrets.BASE_DOMAIN }} RABBITMQ_PASSWORD: ${{ secrets.PROD_RABBITMQ_PASSWORD }} DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }} - IMAGE_REPO: ${{ needs.build.outputs.image_repo }} DIGEST: ${{ needs.build.outputs.digest }} + DOMAIN: "${{ needs.get_urls.outputs.backend_url }}" + DOMAIN_SCHEME: "${{ needs.get_urls.outputs.backend_url_scheme }}" + FRONTEND_DOMAIN: "${{ needs.get_urls.outputs.frontend_url }}" + FRONTEND_DOMAIN_SCHEME: "${{ needs.get_urls.outputs.frontend_url_scheme }}" run: | PR=${{ github.event.pull_request.number }} - if [ -z "$PR" ]; then echo "PR number missing"; exit 1; fi - if [ -z "$DEV_BASE_DOMAIN" ]; then echo "Secret DEV_BASE_DOMAIN is required (e.g., dev.example.com)"; exit 1; fi - if [ -z "$RABBITMQ_PASSWORD" ]; then echo "Secret DEV_RABBITMQ_PASSWORD is required"; exit 1; fi - if [ -z "$DB_PASSWORD" ]; then echo "Secret DEV_DB_PASSWORD is required"; exit 1; fi RELEASE=myapp-pr-$PR NAMESPACE=pr-$PR - DOMAIN=pr-$PR.$DEV_BASE_DOMAIN - if [ -z "$IMAGE_REPO" ]; then IMAGE_REPO="lukastrkan/cc-app-demo"; fi helm upgrade --install "$RELEASE" ./7project/charts/myapp-chart \ -n "$NAMESPACE" --create-namespace \ -f 7project/charts/myapp-chart/values-dev.yaml \ --set prNumber="$PR" \ --set deployment="pr-$PR" \ --set domain="$DOMAIN" \ - --set image.repository="$IMAGE_REPO" \ + --set domain_scheme="$DOMAIN_SCHEME" \ + --set frontend_domain="$FRONTEND_DOMAIN" \ + --set frontend_domain_scheme="$FRONTEND_DOMAIN_SCHEME" \ --set image.digest="$DIGEST" \ --set-string rabbitmq.password="$RABBITMQ_PASSWORD" \ --set-string database.password="$DB_PASSWORD" @@ -88,19 +100,16 @@ jobs: - name: Post preview URLs as PR comment uses: actions/github-script@v7 env: - DEV_BASE_DOMAIN: ${{ secrets.BASE_DOMAIN }} - FRONTEND_URL: ${{ needs.frontend.outputs.deployed_url }} + BACKEND_URL: ${{ needs.get_urls.outputs.backend_url_scheme }} + FRONTEND_URL: ${{ needs.get_urls.outputs.frontend_url_scheme }} with: script: | const pr = context.payload.pull_request; if (!pr) { core.setFailed('No pull_request context'); return; } const prNumber = pr.number; - const domainBase = process.env.DEV_BASE_DOMAIN; - if (!domainBase) { core.setFailed('DEV_BASE_DOMAIN is required'); return; } - const backendDomain = `pr-${prNumber}.${domainBase}`; - const backendUrl = `https://${backendDomain}`; + const backendUrl = process.env.BACKEND_URL || '(not available)'; const frontendUrl = process.env.FRONTEND_URL || '(not available)'; - const marker = ''; + const marker = ''; const body = `${marker}\nPreview environment is running\n- Frontend: ${frontendUrl}\n- Backend: ${backendUrl}\n`; const { owner, repo } = context.repo; const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: prNumber, per_page: 100 }); @@ -139,4 +148,4 @@ jobs: NAMESPACE=pr-$PR helm uninstall "$RELEASE" -n "$NAMESPACE" || true # Optionally delete the namespace if empty - kubectl delete namespace "$NAMESPACE" --ignore-not-found=true || true + kubectl delete namespace "$NAMESPACE" --ignore-not-found=true || true \ No newline at end of file diff --git a/.github/workflows/deploy-prod.yaml b/.github/workflows/deploy-prod.yaml index 91fda92..b42ee4a 100644 --- a/.github/workflows/deploy-prod.yaml +++ b/.github/workflows/deploy-prod.yaml @@ -30,17 +30,28 @@ jobs: context: 7project/backend secrets: inherit + get_urls: + name: Generate Production URLs + uses: ./.github/workflows/url_generator.yml + with: + mode: prod + runner: vhs + base_domain: ${{ vars.PROD_DOMAIN }} + secrets: inherit + frontend: name: Frontend - Build and Deploy to Cloudflare Pages (prod) + needs: [get_urls] uses: ./.github/workflows/frontend-pages.yml with: mode: prod + backend_url_scheme: ${{ needs.get_urls.outputs.backend_url_scheme }} secrets: inherit deploy: name: Helm upgrade/install (prod) runs-on: vhs - needs: [build, frontend] + needs: [build, frontend, get_urls] steps: - name: Checkout uses: actions/checkout@v4 @@ -63,25 +74,31 @@ jobs: - name: Helm upgrade/install prod env: - DOMAIN: ${{ secrets.PROD_DOMAIN }} + DOMAIN: ${{ needs.get_urls.outputs.backend_url }} + DOMAIN_SCHEME: ${{ needs.get_urls.outputs.backend_url_scheme }} + FRONTEND_DOMAIN: ${{ needs.get_urls.outputs.frontend_url }} + FRONTEND_DOMAIN_SCHEME: ${{ needs.get_urls.outputs.frontend_url_scheme }} RABBITMQ_PASSWORD: ${{ secrets.PROD_RABBITMQ_PASSWORD }} DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }} - IMAGE_REPO: ${{ needs.build.outputs.image_repo }} DIGEST: ${{ needs.build.outputs.digest }} + BANKID_CLIENT_ID: ${{ secrets.BANKID_CLIENT_ID }} + BANKID_CLIENT_SECRET: ${{ secrets.BANKID_CLIENT_SECRET }} + MOJEID_CLIENT_ID: ${{ secrets.MOJEID_CLIENT_ID }} + MOJEID_CLIENT_SECRET: ${{ secrets.MOJEID_CLIENT_SECRET }} + run: | - if [ -z "$DOMAIN" ]; then - echo "Secret PROD_DOMAIN is required (e.g., app.example.com)"; exit 1; fi - if [ -z "$RABBITMQ_PASSWORD" ]; then - echo "Secret PROD_RABBITMQ_PASSWORD is required"; exit 1; fi - if [ -z "$DB_PASSWORD" ]; then - echo "Secret PROD_DB_PASSWORD is required"; exit 1; fi - if [ -z "$IMAGE_REPO" ]; then IMAGE_REPO="lukastrkan/cc-app-demo"; fi helm upgrade --install myapp ./7project/charts/myapp-chart \ -n prod --create-namespace \ -f 7project/charts/myapp-chart/values-prod.yaml \ --set deployment="prod" \ --set domain="$DOMAIN" \ - --set image.repository="$IMAGE_REPO" \ + --set domain_scheme="$DOMAIN_SCHEME" \ + --set frontend_domain="$FRONTEND_DOMAIN" \ + --set frontend_domain_scheme="$FRONTEND_DOMAIN_SCHEME" \ --set image.digest="$DIGEST" \ --set-string rabbitmq.password="$RABBITMQ_PASSWORD" \ - --set-string database.password="$DB_PASSWORD" + --set-string database.password="$DB_PASSWORD" + --set-string oauth.bankid.clientId="$BANKID_CLIENT_ID" \ + --set-string oauth.bankid.clientSecret="$BANKID_CLIENT_SECRET" \ + --set-string oauth.mojeid.clientId="$MOJEID_CLIENT_ID" \ + --set-string oauth.mojeid.clientSecret="$MOJEID_CLIENT_SECRET" \ No newline at end of file diff --git a/.github/workflows/frontend-pages.yml b/.github/workflows/frontend-pages.yml index cf47997..7b46a6f 100644 --- a/.github/workflows/frontend-pages.yml +++ b/.github/workflows/frontend-pages.yml @@ -15,6 +15,10 @@ on: description: 'Cloudflare Pages project name (overrides default)' required: false type: string + backend_url_scheme: + description: 'The full scheme URL for the backend (e.g., https://api.example.com)' + required: true + type: string secrets: CLOUDFLARE_API_TOKEN: required: true @@ -25,14 +29,6 @@ on: description: 'URL of deployed frontend' value: ${{ jobs.deploy.outputs.deployed_url }} -# Required repository secrets: -# CLOUDFLARE_API_TOKEN - API token with Pages:Edit (or Account:Workers Scripts:Edit) permissions -# CLOUDFLARE_ACCOUNT_ID - Your Cloudflare account ID -# Optional repository variables: -# CF_PAGES_PROJECT_NAME - Default Cloudflare Pages project name -# PROD_DOMAIN - App domain for prod releases (e.g., api.example.com or https://api.example.com) -# BACKEND_URL_PR_TEMPLATE - Template for PR backend URL. Use {PR} placeholder for PR number (e.g., https://api-pr-{PR}.example.com) - jobs: build: name: Build frontend @@ -54,50 +50,9 @@ jobs: - name: Install dependencies run: npm ci - - name: Compute backend URL for Vite - id: be - env: - EVENT_NAME: ${{ github.event_name }} - PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }} - PR_TEMPLATE: ${{ vars.BACKEND_URL_PR_TEMPLATE }} - DEV_BASE_DOMAIN: ${{ secrets.BASE_DOMAIN }} - PROD_DOMAIN_VAR: ${{ vars.PROD_DOMAIN }} - PROD_DOMAIN_SECRET: ${{ secrets.PROD_DOMAIN }} - BACKEND_URL_OVERRIDE: ${{ vars.BACKEND_URL || secrets.BACKEND_URL }} - MODE: ${{ inputs.mode }} + - name: Set backend URL from workflow input run: | - set -euo pipefail - URL="" - # 1) Explicit override wins (from repo var or secret) - if [ -n "${BACKEND_URL_OVERRIDE:-}" ]; then - if echo "$BACKEND_URL_OVERRIDE" | grep -Eiq '^https?://'; then - URL="$BACKEND_URL_OVERRIDE" - else - URL="https://${BACKEND_URL_OVERRIDE}" - fi - else - # 2) PR-specific URL when building for PR - if [ "${MODE:-}" = "pr" ] || [ "${EVENT_NAME}" = "pull_request" ]; then - if [ -n "${PR_TEMPLATE:-}" ] && [ -n "${PR_NUMBER:-}" ] ; then - URL="${PR_TEMPLATE//\{PR\}/${PR_NUMBER}}" - elif [ -n "${DEV_BASE_DOMAIN:-}" ] && [ -n "${PR_NUMBER:-}" ]; then - URL="https://pr-${PR_NUMBER}.${DEV_BASE_DOMAIN}" - fi - fi - # 3) Fallback to PROD_DOMAIN (prefer repo var, then secret) - if [ -z "$URL" ]; then - PROD_DOMAIN="${PROD_DOMAIN_VAR:-${PROD_DOMAIN_SECRET:-}}" - if [ -n "$PROD_DOMAIN" ]; then - if echo "$PROD_DOMAIN" | grep -Eiq '^https?://'; then - URL="$PROD_DOMAIN" - else - URL="https://${PROD_DOMAIN}" - fi - fi - fi - fi - echo "Using backend URL: ${URL:-}" - echo "VITE_BACKEND_URL=${URL}" >> $GITHUB_ENV + echo "VITE_BACKEND_URL=${{ inputs.backend_url_scheme }}" >> $GITHUB_ENV - name: Build run: npm run build @@ -177,4 +132,4 @@ jobs: else URL="https://${PBRANCH}.${PNAME}.pages.dev" fi - echo "deployed_url=$URL" >> $GITHUB_OUTPUT + echo "deployed_url=$URL" >> $GITHUB_OUTPUT \ No newline at end of file diff --git a/.github/workflows/url_generator.yml b/.github/workflows/url_generator.yml new file mode 100644 index 0000000..31121d4 --- /dev/null +++ b/.github/workflows/url_generator.yml @@ -0,0 +1,74 @@ +name: Generate Preview or Production URLs + +on: + workflow_call: + inputs: + mode: + description: "Build mode: 'prod' or 'pr'" + required: true + type: string + pr_number: + description: 'PR number (required when mode=pr)' + required: false + type: string + runner: + description: 'The runner to use for this job' + required: false + type: string + default: 'ubuntu-latest' + base_domain: + description: 'The base domain for production URLs (e.g., example.com)' + required: true + type: string + + outputs: + backend_url: + description: "The backend URL without scheme (e.g., api.example.com)" + value: ${{ jobs.generate-urls.outputs.backend_url }} + frontend_url: + description: "The frontend URL without scheme (e.g., app.example.com)" + value: ${{ jobs.generate-urls.outputs.frontend_url }} + backend_url_scheme: + description: "The backend URL with scheme (e.g., https://api.example.com)" + value: ${{ jobs.generate-urls.outputs.backend_url_scheme }} + frontend_url_scheme: + description: "The frontend URL with scheme (e.g., https://app.example.com)" + value: ${{ jobs.generate-urls.outputs.frontend_url_scheme }} + +jobs: + generate-urls: + permissions: + contents: none + runs-on: ${{ inputs.runner }} + + outputs: + backend_url: ${{ steps.set_urls.outputs.backend_url }} + frontend_url: ${{ steps.set_urls.outputs.frontend_url }} + backend_url_scheme: ${{ steps.set_urls.outputs.backend_url_scheme }} + frontend_url_scheme: ${{ steps.set_urls.outputs.frontend_url_scheme }} + + steps: + - name: Generate URLs + id: set_urls + env: + BASE_DOMAIN: ${{ inputs.base_domain }} + run: | + set -euo pipefail + + if [ "${{ inputs.mode }}" = "prod" ]; then + BACKEND_URL="api.${BASE_DOMAIN}" + FRONTEND_URL="finance.${BASE_DOMAIN}" + else + # This is your current logic + FRONTEND_URL="pr-${{ inputs.pr_number }}.group-8-frontend.pages.dev" + BACKEND_URL="api-pr-${{ inputs.pr_number }}.${BASE_DOMAIN}" + fi + + FRONTEND_URL_SCHEME="https://$FRONTEND_URL" + BACKEND_URL_SCHEME="https://$BACKEND_URL" + + # This part correctly writes to GITHUB_OUTPUT for the step + echo "backend_url_scheme=$BACKEND_URL_SCHEME" >> $GITHUB_OUTPUT + echo "frontend_url_scheme=$FRONTEND_URL_SCHEME" >> $GITHUB_OUTPUT + echo "backend_url=$BACKEND_URL" >> $GITHUB_OUTPUT + echo "frontend_url=$FRONTEND_URL" >> $GITHUB_OUTPUT \ No newline at end of file diff --git a/7project/charts/myapp-chart/templates/NOTES.txt b/7project/charts/myapp-chart/templates/NOTES.txt deleted file mode 100644 index 9eeb1e5..0000000 --- a/7project/charts/myapp-chart/templates/NOTES.txt +++ /dev/null @@ -1,54 +0,0 @@ -Thank you for installing myapp-chart. - -This chart packages all Kubernetes manifests from the original deployment directory and parameterizes environment, database name (with optional PR suffix), image, and domain for external access. - -Namespaces per developer (important): -- Install each developer's environment into their own namespace using Helm's -n/--namespace flag. -- No hardcoded namespace is used in templates; resources are created in .Release.Namespace. -- Example namespaces: dev-alice, dev-bob, pr-123, etc. - -Key values: -- deployment -> used as Database CR name and DB username (MARIADB_DB and MARIADB_USER) -- image.repository/tag or image.digest -> container image -- domain -> public FQDN used by TunnelBinding (required to expose app) -- app/worker names, replicas, ports - -Examples: -- Dev install (Alice): - helm upgrade --install myapp ./7project/charts/myapp-chart \ - -n dev-alice --create-namespace \ - -f values-dev.yaml \ - --set domain=alice.demo.example.com \ - --set-string rabbitmq.password="$RABBITMQ_PASSWORD" \ - --set-string database.password="$DB_PASSWORD" - -- Dev install (Bob): - helm upgrade --install myapp ./7project/charts/myapp-chart \ - -n dev-bob --create-namespace \ - -f values-dev.yaml \ - --set domain=bob.demo.example.com - -- Prod install (different cleanupPolicy): - helm upgrade --install myapp ./7project/charts/myapp-chart \ - -n prod --create-namespace \ - -f values-prod.yaml \ - --set domain=app.example.com - -- PR (preview) install with DB name containing PR number (also its own namespace): - PR=123 - helm upgrade --install myapp-pr-$PR ./7project/charts/myapp-chart \ - -n pr-$PR --create-namespace \ - -f values-dev.yaml \ - --set prNumber=$PR \ - --set deployment=preview-$PR \ - --set domain=pr-$PR.example.com - -- Use a custom deployment identifier to suffix DB name, DB username and Secret name: - helm upgrade --install myapp ./7project/charts/myapp-chart \ - -n dev-alice --create-namespace \ - -f values-dev.yaml \ - --set deployment=alice \ - --set domain=alice.demo.example.com - -Render locally (dry run): - helm template ./7project/charts/myapp-chart -f values-dev.yaml --set prNumber=456 --set deployment=test --set domain=demo.example.com --namespace dev-test | sed -n '/kind: Database/,$p' | head -n 30 diff --git a/7project/charts/myapp-chart/templates/app-deployment.yaml b/7project/charts/myapp-chart/templates/app-deployment.yaml index 1d264ab..499c1e4 100644 --- a/7project/charts/myapp-chart/templates/app-deployment.yaml +++ b/7project/charts/myapp-chart/templates/app-deployment.yaml @@ -52,6 +52,22 @@ spec: value: {{ .Values.rabbitmq.vhost | default "/" | quote }} - name: MAIL_QUEUE value: {{ .Values.worker.mailQueueName | default "mail_queue" | quote }} + - name: MOJEID_CLIENT_ID + value: {{ .Values.oauth.mojeid.clientId | quote }} + - name: MOJEID_CLIENT_SECRET + value: {{ .Values.oauth.mojeid.clientSecret | quote }} + - name: BANKID_CLIENT_ID + value: {{ .Values.oauth.bankid.clientId | quote }} + - name: BANKID_CLIENT_SECRET + value: {{ .Values.oauth.bankid.clientSecret | quote }} + - name: DOMAIN + value: {{ required "Set .Values.domain" .Values.domain | quote }} + - name: DOMAIN_SCHEME + value: {{ required "Set .Values.domain_scheme" .Values.domain_scheme | quote }} + - name: FRONTEND_DOMAIN + value: {{ required "Set .Values.frontend_domain" .Values.frontend_domain | quote }} + - name: FRONTEND_DOMAIN_SCHEME + value: {{ required "Set .Values.frontend_domain_scheme" .Values.frontend_domain_scheme | quote }} livenessProbe: httpGet: path: / diff --git a/7project/charts/myapp-chart/values.yaml b/7project/charts/myapp-chart/values.yaml index 4b36e35..6186a32 100644 --- a/7project/charts/myapp-chart/values.yaml +++ b/7project/charts/myapp-chart/values.yaml @@ -11,6 +11,10 @@ deployment: "" # Public domain to expose the app under (used by TunnelBinding fqdn) # Set at install time: --set domain=example.com domain: "" +domain_scheme: "" + +frontend_domain: "" +frontend_domain_scheme: "" image: repository: lukastrkan/cc-app-demo @@ -33,6 +37,14 @@ worker: service: port: 80 +oauth: + bankid: + clientId: "" + clientSecret: "" + mojeid: + clientId: "" + clientSecret: "" + rabbitmq: create: true replicas: 1