diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..31d9b42 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,156 @@ +name: CI/CD + +on: + push: + branches: [main] + pull_request: + branches: [main] + release: + types: [published] + +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') || github.event_name == 'release' + 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 + type=raw,value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }} + + - 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 + type=raw,value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }} + + - 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 + + 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(); 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..2ae3d39 --- /dev/null +++ b/gitops/apps/wizard-rpg/frontend-nginx-configmap.yaml @@ -0,0 +1,40 @@ +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; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + 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: