craigmadethis

Deploying SST Resources With GitHub Actions

Published: 25/10/2024, 17:30:00

Updated: 25/10/2024, 17:30:00

Oh look another post about SST. I'm currently in the middle of moving a big Next.js repo from GitLab to GitHub and off DigitalOcean and onto AWS. Naturally I've used SST to do this. It did take me a little while to get this setup properly though.

sst.config.ts

The first step is defining your profile correctly. If you don't set it to undefined when running a GitHub Action it ain't gonna work.

Here's an example config:

sst.config.ts
/// <reference path="./.sst/platform/config.d.ts" />
 
import { readdirSync } from 'fs';
 
export default $config({
  app(input) {
    return {
      name: 'app',
      removal: input?.stage === 'production' ? 'retain' : 'remove',
      home: 'aws',
      providers: {
        aws: {
          region: 'eu-west-2',
          profile: process.env.GITHUB_ACTIONS
            ? undefined
            : input?.stage === 'production'
            ? 'acme-production'
            : 'acme-dev',
        },
      },
    };
  },
  async run() {
    const outputs = {};
    for (const value of readdirSync('./infra/')) {
      const result = await import('./infra/' + value);
      if (result.outputs) Object.assign(outputs, result.outputs);
    }
    return outputs;
  },
});

Allowing GitHub Access To AWS

To allow GitHub access to AWS, you can Configure OpenID Connect in Amazon Web Services. We can actually do this in our infra code, rather than through our AWS account by using some of the Pulumi constructs built into SST.

Define the following in your sst.config.ts (or in an /infra directory), where GITHUB_ORG and GITHUB_REPO are your values:

sst.config.ts
export default $config (
  {...}, 
    async run() {
      const github = new aws.iam.OpenIdConnectProvider("GithubProvider", {
        url: "https://token.actions.githubusercontent.com",
        clientIdLists: ["sts.amazonaws.com"],
      });
      const githubRole = new aws.iam.Role("GithubRole", {
        name: [$app.name, $app.stage, "github"].join("-"),
        assumeRolePolicy: {
          Version: "2012-10-17",
          Statement: [
            {
              Effect: "Allow",
              Principal: {
                Federated: github.arn,
              },
              Action: "sts:AssumeRoleWithWebIdentity",
              Condition: {
                StringLike: github.url.apply((url) => ({
                  [`${url}:sub`]: "repo:${GITHUB_ORG}/${GITHUB_REPO}:*",
                })),
              },
            },
          ],
        },
      });
      new aws.iam.RolePolicyAttachment("GithubRolePolicy", {
        policyArn: "arn:aws:iam::aws:policy/AdministratorAccess",
        role: githubRole.name,
      });
    })

Then deploy this.

The GitHub Action

Once GitHub has access to your AWS resources, you can add a workflow file to your repo (again, make sure you add your app name, expected stage and AWS account number):

.github/workflows/deploy.yml
name: deploy-app
 
on:
  push-branches: 
    - [main]
  release:
    types: [published]
 
concurrency: 
  group: ${{ github.ref }}
 
permissions:
  id-token: write
  contents: read
 
jobs:
  install_dependencies:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: "20"
      - name: Cache dependencies
        uses: actions/cache@v3
        with:
          path: |
            **/node_modules
          key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-
      - name: Install dependencies
        run: yarn install --frozen-lockfile
 
  deploy:
    needs: [install_dependencies]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
 
      - uses: actions/setup-node@v3
        with:
          node-version: "20"
 
      - uses: actions/cache@v2
        with:
           path: |
             .sst
           key: ${{ runner.os }}-sst
 
      - run: "curl -fsSL https://ion.sst.dev/install | bash"
 
      - name: Configure AWS credentials (Production)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${AWS_ACCOUNT_NUMBER}:role/${APP_NAME}-${APP_STAGE}-github 
          aws-region: eu-west-2
      #
      - name: Deploy
        run: |
          sst install
          sst deploy --stage production --verbose

Yeah I'm still using yarn sorry.

Resources

Other Posts