From 3b3857207e2f4c641efff0bc68e1a0e162d2d175 Mon Sep 17 00:00:00 2001 From: Ricardo Campos Date: Thu, 16 Apr 2026 18:55:34 +0200 Subject: [PATCH] chore: add files back after renaming --- .github/workflows/cd-main.yml | 172 +++++++++++++++++++++++++ .github/workflows/cd-pr.yml | 136 +++++++++++++++++++ .github/workflows/ci-main-backend.yml | 101 +++++++++++++++ .github/workflows/ci-main-frontend.yml | 83 ++++++++++++ .github/workflows/ci-pr-backend.yml | 102 +++++++++++++++ .github/workflows/ci-pr-frontend.yml | 107 +++++++++++++++ 6 files changed, 701 insertions(+) create mode 100644 .github/workflows/cd-main.yml create mode 100644 .github/workflows/cd-pr.yml create mode 100644 .github/workflows/ci-main-backend.yml create mode 100644 .github/workflows/ci-main-frontend.yml create mode 100644 .github/workflows/ci-pr-backend.yml create mode 100644 .github/workflows/ci-pr-frontend.yml diff --git a/.github/workflows/cd-main.yml b/.github/workflows/cd-main.yml new file mode 100644 index 0000000..770a612 --- /dev/null +++ b/.github/workflows/cd-main.yml @@ -0,0 +1,172 @@ +name: Main CD-Deploy to Prod + +on: + workflow_dispatch: + inputs: + backend_image: + description: "Backend image tag (full image reference)" + required: false + frontend_image: + description: "Frontend image tag (full image reference)" + required: false + apply: + description: "Apply changes after plan" + required: false + default: "true" + workflow_run: + workflows: [ "Main CI-Backend", "Main CI-Frontend" ] + types: [ completed ] + +jobs: + terraform-plan: + if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-latest + outputs: + no_changes: ${{ steps.check-changes.outputs.no_changes }} + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Setup kubectl + uses: azure/setup-kubectl@v4 + + - name: Setup Kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_DATA }}" | base64 -d > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Validate cluster access + run: | + kubectl cluster-info + kubectl get namespace tasknote + + - name: Determine deployment values + id: deploy-vars + run: | + backend_image="${{ github.event.inputs.backend_image }}" + frontend_image="${{ github.event.inputs.frontend_image }}" + + latest_backend_tag_tmp="$(git tag --list 'api-v*' | sort -V | tail -n1)" + latest_backend_tag="${latest_backend_tag_tmp#api-v}" + latest_frontend_tag="$(git tag --list 'app-v*' | sort -V | tail -n1)" + + if [ -z "$latest_backend_tag" ]; then + echo "No backend tag found matching [0-9]*" >&2 + exit 1 + fi + + if [ -z "$latest_frontend_tag" ]; then + echo "No frontend tag found matching app-v*" >&2 + exit 1 + fi + + if [ -z "$backend_image" ]; then + backend_image="ghcr.io/rmcampos/tasknote/api:$latest_backend_tag" + fi + if [ -z "$frontend_image" ]; then + frontend_image="ghcr.io/rmcampos/tasknote/app:$latest_frontend_tag" + fi + + echo "Resolved backend_image=$backend_image" + echo "Resolved frontend_image=$frontend_image" + + echo "backend_image=$backend_image" >> "$GITHUB_OUTPUT" + echo "frontend_image=$frontend_image" >> "$GITHUB_OUTPUT" + + - name: Terraform Fmt -check -diff + working-directory: terraform + run: terraform fmt -check -diff + + - name: Terraform Init + working-directory: terraform + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: terraform init -input=false + + - name: Terraform Validate + working-directory: terraform + run: terraform validate + + - name: Terraform Plan + id: check-changes + working-directory: terraform + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: | + timeout 1m terraform plan -input=false -out=tfplan \ + -var="db_user=${{ secrets.DB_USER }}" \ + -var="db_password=${{ secrets.DB_PASSWORD }}" \ + -var="db_name=${{ secrets.DB_NAME }}" \ + -var="security_key=${{ secrets.JWT_SECURITY_KEY }}" \ + -var="mailgun_apikey=${{ secrets.MAILGUN_API_KEY }}" \ + -var="backend_image=${{ steps.deploy-vars.outputs.backend_image }}" \ + -var="frontend_image=${{ steps.deploy-vars.outputs.frontend_image }}" + terraform show -json tfplan > tfplan.json + if jq -e '.resource_changes | length == 0' tfplan.json >/dev/null; then + echo "no_changes=true" >> "$GITHUB_OUTPUT" + echo "No changes to apply." + exit 0 + else + echo "Changes detected. Proceeding with apply" + echo "no_changes=false" >> "$GITHUB_OUTPUT" + fi + + - name: Upload plan artifact + uses: actions/upload-artifact@v4 + with: + name: tfplan + path: terraform/tfplan + + terraform-apply: + runs-on: ubuntu-latest + needs: terraform-plan + if: > + (github.event_name == 'push' || github.event_name == 'workflow_run' || inputs.apply == 'true') + && needs.terraform-plan.outputs.no_changes == 'false' + environment: + name: production + url: https://tasknote.darkroasted.vps-kinghost.net + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Download plan artifact + uses: actions/download-artifact@v4 + with: + name: tfplan + path: terraform + + - name: Setup Kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_DATA }}" | base64 -d > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Terraform Init + working-directory: terraform + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: terraform init -input=false + + - name: Terraform Apply + working-directory: terraform + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: timeout 1m terraform apply tfplan diff --git a/.github/workflows/cd-pr.yml b/.github/workflows/cd-pr.yml new file mode 100644 index 0000000..79b4f23 --- /dev/null +++ b/.github/workflows/cd-pr.yml @@ -0,0 +1,136 @@ +name: Pull Request CD-Deploy to Staging + +on: + workflow_dispatch: + workflow_run: + workflows: [ "Pull Request CI-Backend", "Pull Request CI-Frontend" ] + types: [ completed ] + +jobs: + terraform-plan-stg: + name: Plan changs to staging + runs-on: ubuntu-latest + outputs: + no_changes: ${{ steps.check-changes.outputs.no_changes }} + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Setup kubectl + uses: azure/setup-kubectl@v4 + + - name: Setup Kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_DATA }}" | base64 -d > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Validate cluster access + run: | + kubectl cluster-info + kubectl get namespace tasknote-stg + + - name: Determine deployment values + id: deploy-vars + run: | + backend_image="ghcr.io/rmcampos/tasknote/api:candidate" + frontend_image="ghcr.io/rmcampos/tasknote/app:candidate" + + echo "backend_image=$backend_image" >> "$GITHUB_OUTPUT" + echo "frontend_image=$frontend_image" >> "$GITHUB_OUTPUT" + + - name: Terraform Fmt -check -diff + working-directory: terraform-stg + run: terraform fmt -check -diff + + - name: Terraform Init + working-directory: terraform-stg + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: terraform init -input=false + + - name: Terraform Validate + working-directory: terraform-stg + run: terraform validate + + - name: Terraform Plan + id: check-changes + working-directory: terraform-stg + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: | + timeout 1m terraform plan -input=false -out=tfplan \ + -var="db_user=${{ secrets.DB_USER }}" \ + -var="db_password=${{ secrets.DB_PASSWORD }}" \ + -var="db_name=${{ secrets.DB_NAME }}" \ + -var="security_key=${{ secrets.JWT_SECURITY_KEY }}" \ + -var="mailgun_apikey=${{ secrets.MAILGUN_API_KEY }}" \ + -var="backend_image=${{ steps.deploy-vars.outputs.backend_image }}" \ + -var="frontend_image=${{ steps.deploy-vars.outputs.frontend_image }}" \ + -var="deploy_version=${{ github.run_id }}" + terraform show -json tfplan > tfplan.json + if jq -e '.resource_changes | length == 0' tfplan.json >/dev/null; then + echo "no_changes=true" >> "$GITHUB_OUTPUT" + echo "No changes to apply." + exit 0 + else + echo "Changes detected. Proceeding with apply" + echo "no_changes=false" >> "$GITHUB_OUTPUT" + fi + + - name: Upload plan artifact + uses: actions/upload-artifact@v4 + with: + name: tfplan + path: terraform-stg/tfplan + + terraform-apply: + runs-on: ubuntu-latest + needs: terraform-plan-stg + if: needs.terraform-plan-stg.outputs.no_changes == 'false' + environment: + name: staging + url: https://tasknote-stg.darkroasted.vps-kinghost.net + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Download plan artifact + uses: actions/download-artifact@v4 + with: + name: tfplan + path: terraform-stg + + - name: Setup Kubeconfig + run: | + mkdir -p ~/.kube + echo "${{ secrets.KUBECONFIG_DATA }}" | base64 -d > ~/.kube/config + chmod 600 ~/.kube/config + + - name: Terraform Init + working-directory: terraform-stg + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: terraform init -input=false + + - name: Terraform Apply + working-directory: terraform-stg + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + run: timeout 1m terraform apply tfplan diff --git a/.github/workflows/ci-main-backend.yml b/.github/workflows/ci-main-backend.yml new file mode 100644 index 0000000..a96c526 --- /dev/null +++ b/.github/workflows/ci-main-backend.yml @@ -0,0 +1,101 @@ +name: Main CI-Backend + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'server/**/*.java' + - 'server/**/*.xml' + - 'server/pom.xml' + - '.github/workflows/main-server.yml' + +jobs: + build-and-push: + name: Build & Push + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set lowercase repo name + id: repo + run: echo "name=${GITHUB_REPOSITORY,,}" >> $GITHUB_OUTPUT + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + + - name: Increment version in pom.xml + id: version + working-directory: ./server + run: | + # Extract current version from pom.xml + CURRENT_VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "Current version: ${CURRENT_VERSION}" + + # Increment version + NEW_VERSION=$((CURRENT_VERSION + 1)) + echo "New version: ${NEW_VERSION}" + + # Update pom.xml with new version + ./mvnw versions:set -DnewVersion=${NEW_VERSION} -DgenerateBackupFiles=false -q + + # Output for later steps + echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT + + - name: Commit version bump + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add server/pom.xml + git commit -m "chore: bump api version to ${{ steps.version.outputs.version }} [skip ci]" + git push + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Find PR number + id: find_pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=$(gh pr list --search "${{ github.sha }}" --state merged --json number --jq '.[0].number') + if [ -z "$PR_NUMBER" ]; then + echo "No merged PR found for this commit. Falling back to 'candidate' tag." + PR_NUMBER="candidate" + else + PR_NUMBER="pr-${PR_NUMBER}" + fi + echo "tag=${PR_NUMBER}" >> $GITHUB_OUTPUT + + - name: Promote Docker image + run: | + docker buildx imagetools create \ + --tag ghcr.io/${{ steps.repo.outputs.name }}/api:latest \ + --tag ghcr.io/${{ steps.repo.outputs.name }}/api:${{ steps.version.outputs.version }} \ + ghcr.io/${{ steps.repo.outputs.name }}/api:${{ steps.find_pr.outputs.tag }} + + - name: Create and push Git tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a api-v${{ steps.version.outputs.version }} -m "Release API v${{ steps.version.outputs.version }}" + git push origin api-v${{ steps.version.outputs.version }} diff --git a/.github/workflows/ci-main-frontend.yml b/.github/workflows/ci-main-frontend.yml new file mode 100644 index 0000000..d7855ad --- /dev/null +++ b/.github/workflows/ci-main-frontend.yml @@ -0,0 +1,83 @@ +name: Main CI-Frontend + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'client/**/*.html' + - 'client/**/*.png' + - 'client/**/*.json' + - 'client/**/*.txt' + - 'client/**/*.ts' + - 'client/**/*.tsx' + - 'client/**/*.js' + - 'client/Dockerfile' + - 'client/Caddyfile' + - '.github/workflows/main-client.yml' + +jobs: + build-and-push: + name: Build & Push + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set lowercase repo name + id: repo + run: echo "name=${GITHUB_REPOSITORY,,}" >> $GITHUB_OUTPUT + + - name: Generate version tag + id: version + run: | + DATE=$(date +'%Y.%m.%d') + TAG="app-v${DATE}.${{ github.run_number }}" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "Generated tag: ${TAG}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Find PR number + id: find_pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=$(gh pr list --search "${{ github.sha }}" --state merged --json number --jq '.[0].number') + if [ -z "$PR_NUMBER" ]; then + echo "No merged PR found for this commit. Falling back to 'candidate' tag." + PR_NUMBER="candidate" + else + PR_NUMBER="pr-${PR_NUMBER}" + fi + echo "tag=${PR_NUMBER}" >> $GITHUB_OUTPUT + + - name: Promote Docker image + run: | + docker buildx imagetools create \ + --tag ghcr.io/${{ steps.repo.outputs.name }}/app:latest \ + --tag ghcr.io/${{ steps.repo.outputs.name }}/app:${{ steps.version.outputs.tag }} \ + ghcr.io/${{ steps.repo.outputs.name }}/app:${{ steps.find_pr.outputs.tag }} + + - name: Create and push Git tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a ${{ steps.version.outputs.tag }} -m "Release ${{ steps.version.outputs.tag }}" + git push origin ${{ steps.version.outputs.tag }} + diff --git a/.github/workflows/ci-pr-backend.yml b/.github/workflows/ci-pr-backend.yml new file mode 100644 index 0000000..678d94e --- /dev/null +++ b/.github/workflows/ci-pr-backend.yml @@ -0,0 +1,102 @@ +name: Pull Request CI-Backend + +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened] + branches: + - 'main' + paths: + - 'server/**/*.java' + - 'server/**/*.xml' + - 'server/pom.xml' + - '.github/workflows/server-ci.yml' + +jobs: + run-checks: + name: Checks + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + cache-dependency-path: 'server/pom.xml' + + - name: Run Check Style + working-directory: ./server + run: ./mvnw --no-transfer-progress checkstyle:check -Dcheckstyle.skip=false + + - name: Run build + working-directory: ./server + run: ./mvnw --no-transfer-progress clean compile -DskipTests + + - name: Run tests + working-directory: ./server + run: ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml + + build-and-push: + name: Build & Push + runs-on: ubuntu-latest + needs: ["run-checks"] + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set lowercase repo name + id: repo + run: echo "name=${GITHUB_REPOSITORY,,}" >> $GITHUB_OUTPUT + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '25' + cache: 'maven' + cache-dependency-path: 'server/pom.xml' + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Cache Buildpack layers + uses: actions/cache@v4 + with: + path: | + ~/.cache/reproducible-builds + key: ${{ runner.os }}-buildpack-${{ hashFiles('server/pom.xml') }} + restore-keys: | + ${{ runner.os }}-buildpack- + + - name: Build Docker image with Spring Boot + working-directory: ./server + run: | + ./mvnw -Pnative -DskipTests spring-boot:build-image \ + -Dspring-boot.build-image.imageName=ghcr.io/${{ steps.repo.outputs.name }}/api:latest \ + -Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:latest + + - name: Tag and push Docker image + run: | + docker tag ghcr.io/${{ steps.repo.outputs.name }}/api:latest ghcr.io/${{ steps.repo.outputs.name }}/api:candidate + docker tag ghcr.io/${{ steps.repo.outputs.name }}/api:latest ghcr.io/${{ steps.repo.outputs.name }}/api:pr-${{ github.event.pull_request.number }} + docker push ghcr.io/${{ steps.repo.outputs.name }}/api:candidate + docker push ghcr.io/${{ steps.repo.outputs.name }}/api:pr-${{ github.event.pull_request.number }} diff --git a/.github/workflows/ci-pr-frontend.yml b/.github/workflows/ci-pr-frontend.yml new file mode 100644 index 0000000..90005bf --- /dev/null +++ b/.github/workflows/ci-pr-frontend.yml @@ -0,0 +1,107 @@ +name: Pull Request CI-Frontend + +on: + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened] + branches: + - 'main' + paths: + - 'client/**/*.html' + - 'client/**/*.png' + - 'client/**/*.json' + - 'client/**/*.txt' + - 'client/**/*.ts' + - 'client/**/*.tsx' + - 'client/**/*.js' + - 'client/Dockerfile' + - 'client/Caddyfile' + - '.github/workflows/client-ci.yml' + +jobs: + run-checks: + name: Checks + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm install + working-directory: ./client + + - name: Run lint + run: npm run lint + working-directory: ./client + + - name: Run build + run: npm run build + working-directory: ./client + + - name: Run tests + run: npm run test:no-watch + working-directory: ./client + + build-and-push: + name: Build & Push + runs-on: ubuntu-latest + needs: ["run-checks"] + permissions: + contents: write + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }}/app + tags: | + type=raw,value=candidate + type=raw,value=pr-${{ github.event.pull_request.number }} + + - name: Generate version tag + id: version + run: | + DATE=$(date +'%Y.%m.%d') + TAG="app-v${DATE}.${{ github.run_number }}" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "Generated tag: ${TAG}" + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: ./client + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + VITE_BUILD=${{ steps.version.outputs.tag }}