DC-153 - Implement “Request replacement card” flow in self-service portal #473
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy (ECR) | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| image_tag: | |
| description: "Image tag to push/deploy (default: git sha)" | |
| required: false | |
| type: string | |
| permissions: | |
| contents: read | |
| deployments: write | |
| pull-requests: write | |
| concurrency: | |
| group: deploy-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| # In PRs plan only if tofu files changed | |
| check-infra-changes: | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| changed: ${{ steps.changes.outputs.tofu }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Check for tofu changes | |
| id: changes | |
| uses: dorny/paths-filter@v3 | |
| with: | |
| filters: | | |
| tofu: | |
| - 'tofu/**' | |
| plan-dc: | |
| needs: check-infra-changes | |
| if: needs.check-infra-changes.outputs.changed == 'true' | |
| uses: ./.github/workflows/plan.yaml | |
| with: | |
| environment: dev-dc | |
| config: dev-dc | |
| image_tag: ${{ github.sha }} | |
| secrets: inherit | |
| plan-co: | |
| needs: check-infra-changes | |
| if: needs.check-infra-changes.outputs.changed == 'true' | |
| uses: ./.github/workflows/plan.yaml | |
| with: | |
| environment: dev-co | |
| config: dev-co | |
| image_tag: ${{ github.sha }} | |
| secrets: inherit | |
| plan-comment: | |
| needs: [plan-dc, plan-co] | |
| if: always() && (needs.plan-dc.outputs.plan || needs.plan-co.outputs.plan) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Post plan to PR | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(comment => { | |
| return comment.user.type === 'Bot' && comment.body.includes('## Plan output'); | |
| }); | |
| let output = ''; | |
| const dcPlan = `${{ needs.plan-dc.outputs.plan }}`; | |
| const coPlan = `${{ needs.plan-co.outputs.plan }}`; | |
| if (dcPlan) { | |
| output += `## Plan output — dev-dc\n\n\`\`\`\n${dcPlan}\n\`\`\`\n\n`; | |
| } | |
| if (coPlan) { | |
| output += `## Plan output — dev-co\n\n\`\`\`\n${coPlan}\n\`\`\``; | |
| } | |
| if (botComment) { | |
| github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: output, | |
| }); | |
| } else { | |
| github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: output, | |
| }); | |
| } | |
| # Build and deploy only on push to main or manual trigger, not on PRs | |
| build: | |
| if: github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| image_tag: ${{ steps.tag.outputs.tag }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Compute image tag | |
| id: tag | |
| run: | | |
| if [ -n "${{ inputs.image_tag }}" ]; then | |
| echo "tag=${{ inputs.image_tag }}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=${GITHUB_SHA}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Setup .NET | |
| uses: actions/setup-dotnet@v5.2.0 | |
| with: | |
| dotnet-version: "10.0.200" | |
| - name: Checkout state-connector | |
| uses: actions/checkout@v6 | |
| with: | |
| repository: codeforamerica/sebt-self-service-portal-state-connector | |
| token: ${{ secrets.SEBT_CONNECTOR_REPOSITORY_PAT }} | |
| path: state-connector | |
| - name: Checkout dc-connector | |
| uses: actions/checkout@v6 | |
| with: | |
| repository: codeforamerica/sebt-self-service-portal-dc-connector | |
| token: ${{ secrets.SEBT_CONNECTOR_REPOSITORY_PAT }} | |
| path: dc-connector | |
| - name: Checkout co-connector | |
| uses: actions/checkout@v6 | |
| with: | |
| repository: codeforamerica/sebt-self-service-portal-co-connector | |
| token: ${{ secrets.SEBT_CONNECTOR_REPOSITORY_PAT }} | |
| path: co-connector | |
| - name: Build plugin interfaces (NuGet package) | |
| run: | | |
| dotnet build state-connector/src/SEBT.Portal.StatesPlugins.Interfaces/SEBT.Portal.StatesPlugins.Interfaces.csproj \ | |
| -c Release | |
| - name: Publish DC plugin | |
| run: | | |
| dotnet publish dc-connector/src/SEBT.Portal.StatePlugins.DC/SEBT.Portal.StatePlugins.DC.csproj \ | |
| -c Release \ | |
| -p:CopyPlugins=false | |
| - name: Publish CO plugin | |
| run: | | |
| dotnet publish co-connector/src/SEBT.Portal.StatePlugins.CO/SEBT.Portal.StatePlugins.CO.csproj \ | |
| -c Release \ | |
| -p:CopyPlugins=false | |
| - name: Stage plugin artifacts for Docker build | |
| run: | | |
| mkdir -p nuget-store | |
| cp -r ~/nuget-store/* nuget-store/ | |
| mkdir -p src/SEBT.Portal.Api/plugins-dc | |
| cp dc-connector/src/SEBT.Portal.StatePlugins.DC/bin/Release/net10.0/publish/*.dll \ | |
| src/SEBT.Portal.Api/plugins-dc/ | |
| mkdir -p src/SEBT.Portal.Api/plugins-co | |
| cp co-connector/src/SEBT.Portal.StatePlugins.CO/bin/Release/net10.0/publish/*.dll \ | |
| src/SEBT.Portal.Api/plugins-co/ | |
| - name: Build API image | |
| run: | | |
| docker build \ | |
| --platform linux/amd64 \ | |
| -f src/SEBT.Portal.Api/Dockerfile \ | |
| -t sebt-portal-api:${{ steps.tag.outputs.tag }} \ | |
| . | |
| - name: Build Web DC image | |
| run: | | |
| docker build \ | |
| --platform linux/amd64 \ | |
| -f src/SEBT.Portal.Web/Dockerfile \ | |
| --build-arg STATE=dc \ | |
| -t sebt-portal-web-dc:${{ steps.tag.outputs.tag }} \ | |
| . | |
| - name: Build Web CO image | |
| run: | | |
| docker build \ | |
| --platform linux/amd64 \ | |
| -f src/SEBT.Portal.Web/Dockerfile \ | |
| --build-arg STATE=co \ | |
| -t sebt-portal-web-co:${{ steps.tag.outputs.tag }} \ | |
| . | |
| - name: Save Docker images | |
| run: | | |
| docker save \ | |
| sebt-portal-api:${{ steps.tag.outputs.tag }} \ | |
| sebt-portal-web-dc:${{ steps.tag.outputs.tag }} \ | |
| sebt-portal-web-co:${{ steps.tag.outputs.tag }} \ | |
| -o /tmp/docker-images.tar | |
| - name: Upload Docker images | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: docker-images | |
| path: /tmp/docker-images.tar | |
| retention-days: 1 | |
| deploy-dc: | |
| if: github.event_name != 'pull_request' | |
| needs: build | |
| runs-on: ubuntu-latest | |
| environment: dev-dc | |
| outputs: | |
| web_url: ${{ steps.deploy-url.outputs.url }} | |
| env: | |
| AWS_REGION: us-east-1 | |
| TF_VAR_domain: ${{ vars.DOMAIN }} | |
| TF_VAR_sender_email: ${{ vars.SENDER_EMAIL }} | |
| TF_VAR_vpc_cidr: ${{ vars.VPC_CIDR }} | |
| TF_VAR_private_subnets: ${{ vars.PRIVATE_SUBNETS }} | |
| TF_VAR_public_subnets: ${{ vars.PUBLIC_SUBNETS }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Download Docker images | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: docker-images | |
| path: /tmp | |
| - name: Load Docker images | |
| run: docker load -i /tmp/docker-images.tar | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v6 | |
| with: | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| aws-region: ${{ env.AWS_REGION }} | |
| - name: Login to Amazon ECR | |
| id: login-ecr | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| - name: Push images to ECR | |
| run: | | |
| IMAGE_TAG="${{ needs.build.outputs.image_tag }}" | |
| API_REPO="${{ secrets.ECR_API_REPOSITORY_URL }}" | |
| WEB_REPO="${{ secrets.ECR_WEB_REPOSITORY_URL }}" | |
| docker tag sebt-portal-api:${IMAGE_TAG} ${API_REPO}:${IMAGE_TAG} | |
| docker tag sebt-portal-web-dc:${IMAGE_TAG} ${WEB_REPO}:${IMAGE_TAG} | |
| docker push ${API_REPO}:${IMAGE_TAG} | |
| docker push ${WEB_REPO}:${IMAGE_TAG} | |
| - name: Install OpenTofu | |
| uses: opentofu/setup-opentofu@v1 | |
| with: | |
| tofu_version: "1.10.0" | |
| - name: OpenTofu Init | |
| working-directory: tofu/config/dev-dc | |
| run: tofu init -upgrade | |
| - name: OpenTofu Apply | |
| working-directory: tofu/config/dev-dc | |
| run: | | |
| tofu apply -auto-approve \ | |
| -var="image_tag=${{ needs.build.outputs.image_tag }}" | |
| - name: Get deployment URL | |
| id: deploy-url | |
| working-directory: tofu/config/dev-dc | |
| run: | | |
| WEB_URL=$(tofu output -raw web_endpoint_url 2>/dev/null || echo "") | |
| echo "url=$WEB_URL" >> "$GITHUB_OUTPUT" | |
| echo "Deployment URL: $WEB_URL" | |
| deploy-co: | |
| if: github.event_name != 'pull_request' | |
| needs: build | |
| runs-on: ubuntu-latest | |
| environment: dev-co | |
| outputs: | |
| web_url: ${{ steps.deploy-url.outputs.url }} | |
| env: | |
| AWS_REGION: us-east-1 | |
| TF_VAR_domain: ${{ vars.DOMAIN }} | |
| TF_VAR_sender_email: ${{ vars.SENDER_EMAIL }} | |
| TF_VAR_vpc_cidr: ${{ vars.VPC_CIDR }} | |
| TF_VAR_private_subnets: ${{ vars.PRIVATE_SUBNETS }} | |
| TF_VAR_public_subnets: ${{ vars.PUBLIC_SUBNETS }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Download Docker images | |
| uses: actions/download-artifact@v7 | |
| with: | |
| name: docker-images | |
| path: /tmp | |
| - name: Load Docker images | |
| run: docker load -i /tmp/docker-images.tar | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v6 | |
| with: | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| aws-region: ${{ env.AWS_REGION }} | |
| - name: Login to Amazon ECR | |
| id: login-ecr | |
| uses: aws-actions/amazon-ecr-login@v2 | |
| - name: Push images to ECR | |
| run: | | |
| IMAGE_TAG="${{ needs.build.outputs.image_tag }}" | |
| API_REPO="${{ secrets.ECR_API_REPOSITORY_URL }}" | |
| WEB_REPO="${{ secrets.ECR_WEB_REPOSITORY_URL }}" | |
| docker tag sebt-portal-api:${IMAGE_TAG} ${API_REPO}:${IMAGE_TAG} | |
| docker tag sebt-portal-web-co:${IMAGE_TAG} ${WEB_REPO}:${IMAGE_TAG} | |
| docker push ${API_REPO}:${IMAGE_TAG} | |
| docker push ${WEB_REPO}:${IMAGE_TAG} | |
| - name: Install OpenTofu | |
| uses: opentofu/setup-opentofu@v1 | |
| with: | |
| tofu_version: "1.10.0" | |
| - name: OpenTofu Init | |
| working-directory: tofu/config/dev-co | |
| run: tofu init -upgrade | |
| - name: OpenTofu Apply | |
| working-directory: tofu/config/dev-co | |
| run: | | |
| tofu apply -auto-approve \ | |
| -var="image_tag=${{ needs.build.outputs.image_tag }}" | |
| - name: Get deployment URL | |
| id: deploy-url | |
| working-directory: tofu/config/dev-co | |
| run: | | |
| WEB_URL=$(tofu output -raw web_endpoint_url 2>/dev/null || echo "") | |
| echo "url=$WEB_URL" >> "$GITHUB_OUTPUT" | |
| echo "Deployment URL: $WEB_URL" | |
| create-deployment-record: | |
| needs: [deploy-dc, deploy-co] | |
| if: always() && (needs.deploy-dc.result == 'success' || needs.deploy-co.result == 'success') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| deployments: write | |
| contents: read | |
| steps: | |
| - name: Create GitHub deployment for DC | |
| if: needs.deploy-dc.result == 'success' | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const rawUrl = '${{ needs.deploy-dc.outputs.web_url }}'; | |
| const webUrl = rawUrl && !rawUrl.startsWith('http') ? `https://${rawUrl}` : rawUrl; | |
| const ref = context.sha; | |
| const deployment = await github.rest.repos.createDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref, | |
| environment: 'dev-dc', | |
| auto_merge: false, | |
| required_contexts: [], | |
| description: `Deploy dev-dc (${ref.slice(0, 7)})` | |
| }); | |
| const deploymentId = deployment.data.id; | |
| if (deploymentId) { | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: deploymentId, | |
| state: 'success', | |
| environment_url: webUrl || undefined, | |
| description: 'Deployment finished successfully' | |
| }); | |
| } | |
| - name: Create GitHub deployment for CO | |
| if: needs.deploy-co.result == 'success' | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const rawUrl = '${{ needs.deploy-co.outputs.web_url }}'; | |
| const webUrl = rawUrl && !rawUrl.startsWith('http') ? `https://${rawUrl}` : rawUrl; | |
| const ref = context.sha; | |
| const deployment = await github.rest.repos.createDeployment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref, | |
| environment: 'dev-co', | |
| auto_merge: false, | |
| required_contexts: [], | |
| description: `Deploy dev-co (${ref.slice(0, 7)})` | |
| }); | |
| const deploymentId = deployment.data.id; | |
| if (deploymentId) { | |
| await github.rest.repos.createDeploymentStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| deployment_id: deploymentId, | |
| state: 'success', | |
| environment_url: webUrl || undefined, | |
| description: 'Deployment finished successfully' | |
| }); | |
| } |