Pular para conteúdo

GitHub Actions

Workflows detalhados de CI/CD com GitHub Actions.

Workflow: CI (Continuous Integration)

.github/workflows/ci.yml:

name: CI

on:
  pull_request:
  push:
    branches: [dev, main]

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'

      - name: Install Python dependencies
        run: |
          pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt

      - name: Lint Python
        run: |
          ruff check .
          black --check .

      - name: Test Python
        run: |
          pytest --cov --cov-report=xml --cov-report=term

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage.xml
          flags: backend

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install Node dependencies
        run: npm ci

      - name: Lint Frontend
        run: npm run lint

      - name: Test Frontend
        run: npm test -- --coverage --watchAll=false

      - name: Upload frontend coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info
          flags: frontend

      - name: Setup SAM CLI
        uses: aws-actions/setup-sam@v2

      - name: Validate SAM templates
        run: sam validate --lint

      - name: Security scan (Python)
        run: |
          pip install bandit
          bandit -r app/ -f json -o bandit-report.json || true

      - name: Security scan (Node)
        run: npm audit --audit-level=moderate

Workflow: Deploy to Staging

.github/workflows/deploy-staging.yml:

name: Deploy to Staging

on:
  push:
    branches: [dev]
  workflow_dispatch:  # Manual trigger

concurrency:
  group: deploy-staging
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 20
    environment:
      name: staging
      url: https://staging.seuapp.com

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Setup SAM CLI
        uses: aws-actions/setup-sam@v2

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GitHubActionsRole
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Run database migrations
        env:
          DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
        run: |
          pip install alembic
          alembic upgrade head

      - name: Verify migration
        env:
          DATABASE_URL: ${{ secrets.STAGING_DATABASE_URL }}
        run: |
          alembic current
          python scripts/verify_schema.py

      - name: SAM Build
        run: sam build --use-container

      - name: SAM Deploy
        run: |
          sam deploy \
            --config-env staging \
            --no-confirm-changeset \
            --no-fail-on-empty-changeset \
            --parameter-overrides \
              Environment=staging \
              DatabaseUrl=${{ secrets.STAGING_DATABASE_URL }}

      - name: Run smoke tests
        run: |
          pip install pytest httpx
          pytest tests/smoke/ --env=staging -v

      - name: Notify team (Success)
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK }}
          payload: |
            {
              "text": "✅ Deploy to staging completed successfully!",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Deploy to Staging Success* ✅\n\n*Commit:* `${{ github.sha }}` \n*Author:* ${{ github.actor }}\n*URL:* https://staging.seuapp.com"
                  }
                },
                {
                  "type": "actions",
                  "elements": [
                    {
                      "type": "button",
                      "text": {"type": "plain_text", "text": "View Logs"},
                      "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
                    }
                  ]
                }
              ]
            }

      - name: Notify team (Failure)
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK }}
          payload: |
            {
              "text": "❌ Deploy to staging FAILED!",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Deploy to Staging Failed* ❌\n\n*Commit:* `${{ github.sha }}`\n*Author:* ${{ github.actor }}\n\n<!channel> Please check!"
                  }
                },
                {
                  "type": "actions",
                  "elements": [
                    {
                      "type": "button",
                      "text": {"type": "plain_text", "text": "View Logs"},
                      "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
                      "style": "danger"
                    }
                  ]
                }
              ]
            }

Workflow: Deploy to Production

.github/workflows/deploy-production.yml:

name: Deploy to Production

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      skip_tests:
        description: 'Skip smoke tests (emergency only)'
        required: false
        default: 'false'

concurrency:
  group: deploy-production
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    environment:
      name: production
      url: https://seuapp.com

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Setup SAM CLI
        uses: aws-actions/setup-sam@v2

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GitHubActionsRole
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Backup production database
        run: |
          aws rds create-db-snapshot \
            --db-instance-identifier prod-db \
            --db-snapshot-identifier "pre-deploy-$(date +%Y%m%d-%H%M%S)" \
            --tags Key=Type,Value=PreDeployment Key=Commit,Value=${{ github.sha }}

          echo "✅ Database snapshot created"

      - name: Run database migrations
        env:
          DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
        run: |
          pip install alembic psycopg2-binary
          alembic -c alembic.prod.ini upgrade head

      - name: Verify migration
        env:
          DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
        run: |
          alembic -c alembic.prod.ini current
          python scripts/verify_schema.py

      - name: SAM Build
        run: sam build --use-container

      - name: SAM Deploy to Production
        run: |
          sam deploy \
            --config-env production \
            --no-confirm-changeset \
            --no-fail-on-empty-changeset \
            --parameter-overrides \
              Environment=production \
              DatabaseUrl=${{ secrets.PROD_DATABASE_URL }}

      - name: Create release tag
        run: |
          VERSION=$(date +%Y.%m.%d-%H%M)
          git tag "v$VERSION"
          git push origin "v$VERSION"
          echo "✅ Created tag: v$VERSION"

      - name: Run smoke tests
        if: github.event.inputs.skip_tests != 'true'
        run: |
          pip install pytest httpx
          pytest tests/smoke/ --env=production -v

      - name: Notify team (Success)
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK }}
          payload: |
            {
              "text": "✅ Deploy to PRODUCTION completed successfully!",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Deploy to Production Success* ✅\n\n*Version:* `v${{ github.sha }}` \n*Author:* ${{ github.actor }}\n*URL:* https://seuapp.com\n\n🎉 Great job team!"
                  }
                }
              ]
            }

      - name: Rollback on failure
        if: failure()
        run: |
          echo "❌ Deploy failed, initiating rollback..."
          sam deploy \
            --config-env production \
            --no-confirm-changeset \
            --parameter-overrides PreviousVersion=true

      - name: Notify team (Failure)
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK }}
          payload: |
            {
              "text": "❌ Deploy to PRODUCTION FAILED!",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Deploy to Production Failed* ❌\n\n*Commit:* `${{ github.sha }}`\n*Author:* ${{ github.actor }}\n\n<!channel> URGENT: Production deploy failed!"
                  }
                },
                {
                  "type": "actions",
                  "elements": [
                    {
                      "type": "button",
                      "text": {"type": "plain_text", "text": "View Logs"},
                      "url": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
                      "style": "danger"
                    }
                  ]
                }
              ]
            }

OIDC Authentication

Setup IAM Role

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:seu-org/backend-api:*"
        }
      }
    }
  ]
}

Permissions

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "cloudformation:*",
        "s3:*",
        "lambda:*",
        "apigateway:*",
        "iam:GetRole",
        "iam:PassRole",
        "logs:*",
        "rds:CreateDBSnapshot"
      ],
      "Resource": "*"
    }
  ]
}

Troubleshooting

Workflow Failed

  1. Ver logs no GitHub Actions
  2. Rodar localmente se possível
  3. Verificar secrets configurados
  4. Checar AWS permissions

Deploy Timeout

  • Aumentar timeout-minutes
  • Verificar se Lambda está respondendo
  • Checar CloudWatch logs

Referências