-
Notifications
You must be signed in to change notification settings - Fork 17
CICD and Infra #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
42dc497
fc20482
5b98a74
a8b91ee
b8cad49
120c9b1
f9c85cd
1fd8af0
da2473b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,22 @@ | ||||||||||
| --- | ||||||||||
| description: "Use when: authoring or reviewing Azure Infrastructure as Code (Bicep), GitHub Actions CI/CD pipelines, or Git commit messages and PR titles. Handles IaC creation, pipelines as code, and commit/PR conventions. Trigger phrases: bicep, iac, infra, azure resources, main.bicep, bicepparam, AVM, azd, github actions, workflow, CI, CD, pipeline, deploy, OIDC, reusable workflow, commit message, conventional commit, PR title." | ||||||||||
| name: "DevOps" | ||||||||||
| tools: [read, edit, search, execute, todo, azure/*, bicep/*, github/*] | ||||||||||
| user-invocable: false | ||||||||||
| tools: | ||||||||||
| [ | ||||||||||
| vscode/askQuestions, | ||||||||||
| execute, | ||||||||||
| read, | ||||||||||
| edit, | ||||||||||
| search, | ||||||||||
| "github/*", | ||||||||||
| "azure-mcp/*", | ||||||||||
| "bicep/*", | ||||||||||
| "com.microsoft/azure/*", | ||||||||||
| "github/*", | ||||||||||
| "github/*", | ||||||||||
| todo, | ||||||||||
|
Comment on lines
+15
to
+17
|
||||||||||
| "github/*", | |
| "github/*", | |
| todo, | |
| todo |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| name: Deploy (reusable) | ||
|
|
||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| environment: | ||
| required: true | ||
| type: string | ||
| bicepParamFile: | ||
| required: true | ||
| type: string | ||
|
|
||
| permissions: | ||
| id-token: write | ||
| contents: read | ||
|
|
||
| jobs: | ||
| deploy: | ||
| name: ${{ inputs.environment }} | ||
| runs-on: ubuntu-latest | ||
| environment: ${{ inputs.environment }} | ||
| env: | ||
| SQL_ADMIN_OBJECT_ID: ${{ vars.SQL_ADMIN_OBJECT_ID }} | ||
| SQL_ADMIN_PRINCIPAL_NAME: ${{ vars.SQL_ADMIN_PRINCIPAL_NAME }} | ||
| AZURE_DEPLOYER_OBJECT_ID: ${{ vars.AZURE_DEPLOYER_OBJECT_ID }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: azure/login@v2 | ||
| with: | ||
| client-id: ${{ vars.AZURE_CLIENT_ID }} | ||
| tenant-id: ${{ vars.AZURE_TENANT_ID }} | ||
| subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} | ||
|
|
||
| # ---------- Infra ---------- | ||
| - name: Ensure resource group | ||
| run: | | ||
| az group create \ | ||
| --name "${{ vars.AZURE_RESOURCE_GROUP }}" \ | ||
| --location "${{ vars.AZURE_LOCATION }}" \ | ||
| --tags workload=notes environment=${{ inputs.environment }} managed-by=bicep >/dev/null | ||
|
|
||
| - name: Deploy Bicep | ||
| id: bicep | ||
| run: | | ||
| outputs=$(az deployment group create \ | ||
| --resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \ | ||
| --template-file infra/main.bicep \ | ||
| --parameters "${{ inputs.bicepParamFile }}" \ | ||
| --query properties.outputs \ | ||
| -o json) | ||
| echo "$outputs" | jq -r 'to_entries[] | "\(.key)=\(.value.value)"' >> "$GITHUB_OUTPUT" | ||
|
|
||
| # ---------- Secrets ---------- | ||
| - name: Seed JWT signing key (if missing) | ||
| env: | ||
| KV_NAME: ${{ steps.bicep.outputs.keyVaultName }} | ||
| run: | | ||
| if ! az keyvault secret show --vault-name "$KV_NAME" --name jwt-signing-key >/dev/null 2>&1; then | ||
| echo "Creating jwt-signing-key" | ||
| value=$(openssl rand -base64 64 | tr -d '\n') | ||
| az keyvault secret set --vault-name "$KV_NAME" --name jwt-signing-key --value "$value" --only-show-errors >/dev/null | ||
| else | ||
| echo "jwt-signing-key already exists" | ||
| fi | ||
|
|
||
| # ---------- SQL: grant App Service MI access ---------- | ||
| - name: Grant App Service MI access to SQL DB | ||
| env: | ||
| SQL_SERVER: ${{ steps.bicep.outputs.sqlServerName }} | ||
| SQL_DB: ${{ steps.bicep.outputs.sqlDatabaseName }} | ||
| APP_NAME: ${{ steps.bicep.outputs.appServiceName }} | ||
| APP_MI_ID: ${{ steps.bicep.outputs.appServicePrincipalId }} | ||
| run: | | ||
| set -euo pipefail | ||
| fqdn="${SQL_SERVER}.database.windows.net" | ||
|
|
||
| # Allow this runner's public IP temporarily so sqlcmd can connect. | ||
| runner_ip=$(curl -s https://api.ipify.org) | ||
| az sql server firewall-rule create \ | ||
| --resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \ | ||
| --server "$SQL_SERVER" \ | ||
| --name "gh-runner-${{ github.run_id }}" \ | ||
| --start-ip-address "$runner_ip" \ | ||
| --end-ip-address "$runner_ip" >/dev/null | ||
|
|
||
| # Install sqlcmd (go edition). | ||
| curl -fsSL https://github.com/microsoft/go-sqlcmd/releases/download/v1.8.0/sqlcmd-linux-amd64.tar.bz2 \ | ||
| | tar -xj -C /tmp | ||
| sudo mv /tmp/sqlcmd /usr/local/bin/sqlcmd | ||
|
|
||
| # Acquire access token as the deployer SP (already logged in via OIDC). | ||
| token=$(az account get-access-token --resource https://database.windows.net/ --query accessToken -o tsv) | ||
|
|
||
| cat <<SQL > /tmp/grant.sql | ||
| IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = N'${APP_NAME}') | ||
| BEGIN | ||
| CREATE USER [${APP_NAME}] FROM EXTERNAL PROVIDER; | ||
| END | ||
| ALTER ROLE db_datareader ADD MEMBER [${APP_NAME}]; | ||
| ALTER ROLE db_datawriter ADD MEMBER [${APP_NAME}]; | ||
| ALTER ROLE db_ddladmin ADD MEMBER [${APP_NAME}]; | ||
| SQL | ||
|
|
||
| sqlcmd -S "$fqdn" -d "$SQL_DB" --access-token "$token" -i /tmp/grant.sql | ||
|
|
||
| # Clean up firewall rule. | ||
| az sql server firewall-rule delete \ | ||
| --resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \ | ||
| --server "$SQL_SERVER" \ | ||
| --name "gh-runner-${{ github.run_id }}" >/dev/null | ||
|
Comment on lines
+78
to
+111
|
||
|
|
||
| # ---------- Backend ---------- | ||
| - uses: actions/download-artifact@v4 | ||
| with: | ||
| name: api | ||
| path: . | ||
|
|
||
| - name: Deploy API | ||
| uses: azure/webapps-deploy@v3 | ||
| with: | ||
| app-name: ${{ steps.bicep.outputs.appServiceName }} | ||
| package: api.zip | ||
|
|
||
| # ---------- Frontend ---------- | ||
| - uses: actions/download-artifact@v4 | ||
| with: | ||
| name: frontend-src | ||
| path: frontend-src | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: "22" | ||
|
|
||
| - name: Build frontend with environment API URL | ||
| working-directory: frontend-src | ||
| env: | ||
| VITE_API_BASE_URL: https://${{ steps.bicep.outputs.appServiceDefaultHostName }} | ||
| run: | | ||
| npm ci | ||
| npm run build | ||
|
|
||
| - name: Get SWA deployment token | ||
| id: swa | ||
| run: | | ||
| token=$(az staticwebapp secrets list \ | ||
| --name "${{ steps.bicep.outputs.staticWebAppName }}" \ | ||
| --resource-group "${{ vars.AZURE_RESOURCE_GROUP }}" \ | ||
| --query properties.apiKey -o tsv) | ||
| echo "::add-mask::$token" | ||
| echo "token=$token" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Deploy SWA | ||
| uses: Azure/static-web-apps-deploy@v1 | ||
| with: | ||
| azure_static_web_apps_api_token: ${{ steps.swa.outputs.token }} | ||
| action: upload | ||
| app_location: frontend-src/dist | ||
| skip_app_build: true | ||
| skip_api_build: true | ||
|
|
||
| - name: Summary | ||
| run: | | ||
| { | ||
| echo "## Deployment (${{ inputs.environment }})"; | ||
| echo "- API: https://${{ steps.bicep.outputs.appServiceDefaultHostName }}"; | ||
| echo "- SWA: https://${{ steps.bicep.outputs.staticWebAppDefaultHostName }}"; | ||
| echo "- SQL: ${{ steps.bicep.outputs.sqlServerName }} / ${{ steps.bicep.outputs.sqlDatabaseName }}"; | ||
| echo "- KeyVault: ${{ steps.bicep.outputs.keyVaultName }}"; | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| name: CD | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ main ] | ||
| workflow_dispatch: | ||
| inputs: | ||
| environment: | ||
| description: Target environment | ||
| required: true | ||
| default: dev | ||
| type: choice | ||
| options: [ dev, prod ] | ||
|
|
||
| permissions: | ||
| id-token: write | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: cd-${{ github.ref }} | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| build-backend: | ||
| name: Build backend | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: "10.x" | ||
| - name: Publish | ||
| run: | | ||
| dotnet publish backend/Notes.Api/Notes.Api.csproj \ | ||
| --configuration Release \ | ||
| --runtime linux-x64 \ | ||
| --self-contained false \ | ||
| --output ./publish | ||
| - name: Zip | ||
| run: | | ||
| cd publish | ||
| zip -r ../api.zip . | ||
| - uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: api | ||
| path: api.zip | ||
| retention-days: 7 | ||
|
|
||
| build-frontend: | ||
| name: Build frontend | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: "22" | ||
| cache: npm | ||
| cache-dependency-path: frontend/package-lock.json | ||
| - run: npm ci | ||
| working-directory: frontend | ||
| - name: Build (base URL set at deploy time) | ||
| run: npm run build | ||
| working-directory: frontend | ||
| env: | ||
| # The API base URL is stamped in per-environment during deploy by | ||
| # rebuilding there. This build is for CI sanity only. | ||
| VITE_API_BASE_URL: https://placeholder.invalid | ||
| - uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: frontend-src | ||
| path: | | ||
| frontend | ||
| !frontend/node_modules | ||
| !frontend/dist | ||
| retention-days: 1 | ||
|
|
||
| deploy-dev: | ||
| name: Deploy dev | ||
| needs: [ build-backend, build-frontend ] | ||
| if: github.event_name == 'push' || github.event.inputs.environment == 'dev' | ||
| uses: ./.github/workflows/_deploy.yml | ||
| with: | ||
| environment: dev | ||
| bicepParamFile: infra/main.dev.bicepparam | ||
| secrets: inherit | ||
|
|
||
| deploy-prod: | ||
| name: Deploy prod | ||
| needs: [ deploy-dev ] | ||
| if: github.event_name == 'push' || github.event.inputs.environment == 'prod' | ||
| uses: ./.github/workflows/_deploy.yml | ||
| with: | ||
| environment: prod | ||
| bicepParamFile: infra/main.prod.bicepparam | ||
| secrets: inherit |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| name: CI | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [ main ] | ||
| push: | ||
| branches: [ main ] | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: ci-${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| backend: | ||
| name: Backend build & test | ||
| runs-on: ubuntu-latest | ||
| defaults: | ||
| run: | ||
| working-directory: backend | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: "10.x" | ||
|
|
||
| - name: Cache NuGet | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: ~/.nuget/packages | ||
| key: ${{ runner.os }}-nuget-${{ hashFiles('backend/**/*.csproj') }} | ||
| restore-keys: ${{ runner.os }}-nuget- | ||
|
|
||
| - run: dotnet restore | ||
| - run: dotnet build --no-restore --configuration Release | ||
| - run: dotnet test --no-build --configuration Release --logger | ||
| "trx;LogFileName=test-results.trx" | ||
|
|
||
| - name: Upload test results | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: backend-test-results | ||
| path: backend/**/TestResults/*.trx | ||
| if-no-files-found: ignore | ||
|
|
||
| frontend: | ||
| name: Frontend build & test | ||
| runs-on: ubuntu-latest | ||
| defaults: | ||
| run: | ||
| working-directory: frontend | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: "22" | ||
| cache: npm | ||
| cache-dependency-path: frontend/package-lock.json | ||
|
|
||
| - run: npm ci | ||
| - run: npm run lint | ||
| - run: npm run typecheck | ||
| - run: npm test -- --run | ||
| - run: npm run build | ||
|
|
||
| bicep: | ||
| name: Bicep lint & build | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Build main.bicep | ||
| run: az bicep build --file infra/main.bicep |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The frontmatter
tools:list uses a bracketed flow sequence with a trailing comma (todo,before the closing]). If the agent config loader uses a strict YAML parser, this can fail to parse. Remove trailing commas in the flow sequence to avoid breaking agent loading.