From fbb66019739225164708225f5aefc1cd34afb849 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 21:57:24 +0000 Subject: [PATCH 1/4] Initial plan From e2ae7458d3929ce0900904e0369847dde23fcd82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 22:02:47 +0000 Subject: [PATCH 2/4] Add CI/CD workflow and update GitOps for frontend/backend images - Create .github/workflows/ci.yml with test, build, and Docker push jobs - Update backend deployment image to ghcr.io/jwraats/wizard-rpg-api:latest - Add frontend GitOps resources (deployment, service, nginx ConfigMap) - Update ingress to route through frontend (which proxies API calls to backend) - Update kustomization to include new frontend resources Agent-Logs-Url: https://github.com/jwraats/Wizard-RPG/sessions/0d412dbc-f7fc-45c8-ab7d-8b7beec9cf90 Co-authored-by: jwraats <3438726+jwraats@users.noreply.github.com> --- .github/workflows/ci.yml | 114 ++++++++++++++++++ gitops/apps/wizard-rpg/deployment.yaml | 3 +- .../apps/wizard-rpg/frontend-deployment.yaml | 58 +++++++++ .../wizard-rpg/frontend-nginx-configmap.yaml | 39 ++++++ gitops/apps/wizard-rpg/frontend-service.yaml | 17 +++ gitops/apps/wizard-rpg/ingress.yaml | 2 +- gitops/apps/wizard-rpg/kustomization.yaml | 3 + 7 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 gitops/apps/wizard-rpg/frontend-deployment.yaml create mode 100644 gitops/apps/wizard-rpg/frontend-nginx-configmap.yaml create mode 100644 gitops/apps/wizard-rpg/frontend-service.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f30bb83 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,114 @@ +name: CI/CD + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + packages: write + +jobs: + test-backend: + name: Test Backend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Restore dependencies + run: dotnet restore + working-directory: backend + + - name: Build + run: dotnet build --no-restore + working-directory: backend + + - name: Test + run: dotnet test --no-build --verbosity normal + working-directory: backend + + test-frontend: + name: Test Frontend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + working-directory: frontend + + - name: Build (includes type-check) + run: npm run build + working-directory: frontend + + build-and-push: + name: Build and Push Docker Images + needs: [test-backend, test-frontend] + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + + - 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: Extract metadata for backend + id: meta-backend + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository_owner }}/wizard-rpg-api + tags: | + type=sha + type=raw,value=latest + + - name: Build and push backend + uses: docker/build-push-action@v6 + with: + context: ./backend + file: ./backend/WizardRPG.Api/Dockerfile + push: true + tags: ${{ steps.meta-backend.outputs.tags }} + labels: ${{ steps.meta-backend.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Extract metadata for frontend + id: meta-frontend + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository_owner }}/wizard-rpg-frontend + tags: | + type=sha + type=raw,value=latest + + - name: Build and push frontend + uses: docker/build-push-action@v6 + with: + context: ./frontend + file: ./frontend/Dockerfile + push: true + tags: ${{ steps.meta-frontend.outputs.tags }} + labels: ${{ steps.meta-frontend.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/gitops/apps/wizard-rpg/deployment.yaml b/gitops/apps/wizard-rpg/deployment.yaml index 1634dd4..146a169 100644 --- a/gitops/apps/wizard-rpg/deployment.yaml +++ b/gitops/apps/wizard-rpg/deployment.yaml @@ -19,8 +19,7 @@ spec: spec: containers: - name: wizard-rpg - # Update this image reference once the Docker image is built and pushed. - image: ghcr.io/jwraats/wizard-rpg:latest + image: ghcr.io/jwraats/wizard-rpg-api:latest imagePullPolicy: Always ports: - name: http diff --git a/gitops/apps/wizard-rpg/frontend-deployment.yaml b/gitops/apps/wizard-rpg/frontend-deployment.yaml new file mode 100644 index 0000000..2e37f7e --- /dev/null +++ b/gitops/apps/wizard-rpg/frontend-deployment.yaml @@ -0,0 +1,58 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: wizard-rpg-frontend + namespace: wizard-rpg + labels: + app.kubernetes.io/name: wizard-rpg-frontend + app.kubernetes.io/part-of: wizard-rpg +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: wizard-rpg-frontend + template: + metadata: + labels: + app.kubernetes.io/name: wizard-rpg-frontend + app.kubernetes.io/part-of: wizard-rpg + spec: + containers: + - name: wizard-rpg-frontend + image: ghcr.io/jwraats/wizard-rpg-frontend:latest + imagePullPolicy: Always + ports: + - name: http + containerPort: 80 + protocol: TCP + volumeMounts: + - name: nginx-config + mountPath: /etc/nginx/conf.d/default.conf + subPath: default.conf + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 20 + timeoutSeconds: 3 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 3 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + volumes: + - name: nginx-config + configMap: + name: wizard-rpg-frontend-nginx diff --git a/gitops/apps/wizard-rpg/frontend-nginx-configmap.yaml b/gitops/apps/wizard-rpg/frontend-nginx-configmap.yaml new file mode 100644 index 0000000..925dbca --- /dev/null +++ b/gitops/apps/wizard-rpg/frontend-nginx-configmap.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: wizard-rpg-frontend-nginx + namespace: wizard-rpg + labels: + app.kubernetes.io/name: wizard-rpg-frontend + app.kubernetes.io/part-of: wizard-rpg +data: + default.conf: | + server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location /api/ { + proxy_pass http://wizard-rpg:80/api/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /hubs/ { + proxy_pass http://wizard-rpg:80/hubs/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + + location / { + try_files $uri $uri/ /index.html; + } + } diff --git a/gitops/apps/wizard-rpg/frontend-service.yaml b/gitops/apps/wizard-rpg/frontend-service.yaml new file mode 100644 index 0000000..78b3132 --- /dev/null +++ b/gitops/apps/wizard-rpg/frontend-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: wizard-rpg-frontend + namespace: wizard-rpg + labels: + app.kubernetes.io/name: wizard-rpg-frontend + app.kubernetes.io/part-of: wizard-rpg +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: wizard-rpg-frontend + ports: + - name: http + port: 80 + targetPort: http + protocol: TCP diff --git a/gitops/apps/wizard-rpg/ingress.yaml b/gitops/apps/wizard-rpg/ingress.yaml index 80ee12b..ded9850 100644 --- a/gitops/apps/wizard-rpg/ingress.yaml +++ b/gitops/apps/wizard-rpg/ingress.yaml @@ -20,7 +20,7 @@ spec: pathType: Prefix backend: service: - name: wizard-rpg + name: wizard-rpg-frontend port: name: http # Uncomment to enable TLS diff --git a/gitops/apps/wizard-rpg/kustomization.yaml b/gitops/apps/wizard-rpg/kustomization.yaml index 6f6bad0..65138ef 100644 --- a/gitops/apps/wizard-rpg/kustomization.yaml +++ b/gitops/apps/wizard-rpg/kustomization.yaml @@ -9,6 +9,9 @@ resources: - secret.yaml - deployment.yaml - service.yaml + - frontend-nginx-configmap.yaml + - frontend-deployment.yaml + - frontend-service.yaml - ingress.yaml commonLabels: From 5ecbe899f06c2adcb84314b2203c6abddd953a2c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 22:05:02 +0000 Subject: [PATCH 3/4] Add X-Forwarded-For header to /hubs/ nginx proxy block for consistency Agent-Logs-Url: https://github.com/jwraats/Wizard-RPG/sessions/0d412dbc-f7fc-45c8-ab7d-8b7beec9cf90 Co-authored-by: jwraats <3438726+jwraats@users.noreply.github.com> --- gitops/apps/wizard-rpg/frontend-nginx-configmap.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/gitops/apps/wizard-rpg/frontend-nginx-configmap.yaml b/gitops/apps/wizard-rpg/frontend-nginx-configmap.yaml index 925dbca..2ae3d39 100644 --- a/gitops/apps/wizard-rpg/frontend-nginx-configmap.yaml +++ b/gitops/apps/wizard-rpg/frontend-nginx-configmap.yaml @@ -31,6 +31,7 @@ data: proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location / { From 9a6424520eab8822aa07ba21073c26e6376167d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 22:22:53 +0000 Subject: [PATCH 4/4] Add version tags on release, GitOps auto-update via kustomize, and fix integration tests - CI workflow: add release trigger, version-tagged Docker images, update-gitops job - Fix integration tests: move Guid.NewGuid() outside AddDbContext lambda to share in-memory DB across requests (fixes 6/10 test failures) Agent-Logs-Url: https://github.com/jwraats/Wizard-RPG/sessions/b51228a4-19e9-4a19-a58c-90d8c8d0a944 Co-authored-by: jwraats <3438726+jwraats@users.noreply.github.com> --- .github/workflows/ci.yml | 44 ++++++++++++++++++- .../Controllers/AuthControllerTests.cs | 3 +- .../Controllers/PlayerControllerTests.cs | 3 +- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f30bb83..31d9b42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,8 @@ on: branches: [main] pull_request: branches: [main] + release: + types: [published] permissions: contents: read @@ -59,7 +61,7 @@ jobs: name: Build and Push Docker Images needs: [test-backend, test-frontend] runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'release' steps: - uses: actions/checkout@v4 @@ -81,6 +83,7 @@ jobs: tags: | type=sha type=raw,value=latest + type=raw,value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }} - name: Build and push backend uses: docker/build-push-action@v6 @@ -101,6 +104,7 @@ jobs: tags: | type=sha type=raw,value=latest + type=raw,value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }} - name: Build and push frontend uses: docker/build-push-action@v6 @@ -112,3 +116,41 @@ jobs: labels: ${{ steps.meta-frontend.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max + + update-gitops: + name: Update GitOps Image Tags + needs: [build-and-push] + runs-on: ubuntu-latest + if: github.event_name == 'release' + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: main + + - name: Setup kustomize + run: | + curl -sfLo kustomize.tar.gz "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.6.0/kustomize_v5.6.0_linux_amd64.tar.gz" + tar xzf kustomize.tar.gz + sudo mv kustomize /usr/local/bin/ + rm kustomize.tar.gz + + - name: Update kustomize image tags + run: | + cd gitops/apps/wizard-rpg + kustomize edit set image \ + "ghcr.io/jwraats/wizard-rpg-api:${{ github.event.release.tag_name }}" \ + "ghcr.io/jwraats/wizard-rpg-frontend:${{ github.event.release.tag_name }}" + + - name: Commit and push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add gitops/apps/wizard-rpg/kustomization.yaml + if git diff --cached --quiet; then + echo "No changes to commit" + else + git commit -m "chore: update image tags to ${{ github.event.release.tag_name }}" + git push + fi diff --git a/backend/WizardRPG.Tests.Integration/Controllers/AuthControllerTests.cs b/backend/WizardRPG.Tests.Integration/Controllers/AuthControllerTests.cs index c7a8183..1e10c18 100644 --- a/backend/WizardRPG.Tests.Integration/Controllers/AuthControllerTests.cs +++ b/backend/WizardRPG.Tests.Integration/Controllers/AuthControllerTests.cs @@ -16,6 +16,7 @@ public class AuthControllerTests : IClassFixture> public AuthControllerTests(WebApplicationFactory factory) { + var dbName = "IntegrationTestDb_Auth_" + Guid.NewGuid(); _factory = factory.WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); @@ -28,7 +29,7 @@ public AuthControllerTests(WebApplicationFactory factory) services.Remove(descriptor); services.AddDbContext(options => - options.UseInMemoryDatabase("IntegrationTestDb_Auth_" + Guid.NewGuid())); + options.UseInMemoryDatabase(dbName)); // Ensure DB is created var sp = services.BuildServiceProvider(); diff --git a/backend/WizardRPG.Tests.Integration/Controllers/PlayerControllerTests.cs b/backend/WizardRPG.Tests.Integration/Controllers/PlayerControllerTests.cs index 117e6ba..f9ae790 100644 --- a/backend/WizardRPG.Tests.Integration/Controllers/PlayerControllerTests.cs +++ b/backend/WizardRPG.Tests.Integration/Controllers/PlayerControllerTests.cs @@ -18,6 +18,7 @@ public class PlayerControllerTests : IClassFixture factory) { + var dbName = "IntegrationTestDb_Player_" + Guid.NewGuid(); _factory = factory.WithWebHostBuilder(builder => { builder.UseEnvironment("Testing"); @@ -29,7 +30,7 @@ public PlayerControllerTests(WebApplicationFactory factory) services.Remove(descriptor); services.AddDbContext(options => - options.UseInMemoryDatabase("IntegrationTestDb_Player_" + Guid.NewGuid())); + options.UseInMemoryDatabase(dbName)); var sp = services.BuildServiceProvider(); using var scope = sp.CreateScope();