# Set up CI/CD

Set up a deployment pipeline that builds your static website once and deploys it to S3/CloudFront across environments.

## Before you begin

- Set up [CloudFront static website infrastructure](create-new-application.md).
- Have a GitHub repository in the `oslokommune` organization for your application (not your IaC repository), preferably prefixed with your team name.

## Step 1: Protect your default branch

Go to **Settings** > **Custom properties** and set `gp-repository-type` to `app`.
Your repository then automatically inherits branch rulesets from the GitHub organization.

After setting the custom property, verify that **Settings** > **Rulesets** shows rules inherited from the organization.

## Step 2: Create a GitHub Actions environment for production

This restricts production deployments to the default branch. This should **only** be set up for production, not for other environments.

<!-- Python-Markdown needs 4-space indentation to nest a list inside a list item. -->
<!-- prettier-ignore -->
1. Go to **Settings** > **Environments** > **New environment**
1. Set the name to match your production environment:
    - For an application repository: `<environment>`, such as `pirates-prod`
    - For an application in a monorepo `<environment>-<app-name>`, such as `pirates-prod-zebra`
1. Under **Deployment branches and tags**, select **Selected branches** and add your default branch (`main` or `master`)

<!-- prettier-ignore-start -->
!!! Tip "Manual approval"
    To enable manual approval, open your GitHub Actions environment for production:

    1. Set **Required reviewers** to your GitHub team
    1. Click **Save protection rules**
<!-- prettier-ignore-end -->

## Step 3: Add configuration files

Create a new branch and add the following configuration files.

### Step 3.1: Add `.gp.cicd.json`

The configuration differs depending on your repository type:

- **Application repository** — the repository contains code for a single application, while the associated infrastructure lives in a separate IaC repository, such as `pirates-iac`.
- **Application monorepo** — the repository contains code for many applications, while the associated infrastructure lives in a separate IaC repository, such as `pirates-iac`.

=== "Application repository"

    Download `.gp.cicd.json`:

    ```bash
    gh api repos/oslokommune/golden-path-templates/contents/templates/gh-cicd-app/.gp.cicd.json \
      --jq '.content' | base64 -d > .gp.cicd.json
    ```

    Update these values (many of which can be found in `common-config.yml` in your infrastructure repository):

    | Field                     | Description                            | Example        |
    |---------------------------|----------------------------------------|----------------|
    | `<team-name>`             | Your team name                         | `pirates`      |
    | `<repo-iac>`              | Infrastructure-as-code repository name | `pirates-iac`  |
    | `<dev-aws-account-id>`    | AWS account ID for `dev`                 | `123456789012` |
    | `<prod-aws-account-id>`   | AWS account ID for `prod`                | `987654321098` |
    | `<dev-environment-name>`  | Name of your `dev` AWS environment       | `pirates-dev`  |
    | `<prod-environment-name>` | Name of your `prod` AWS environment      | `pirates-prod` |
    | `<dev-iac-directory>`     | The directory containing IaC for your `dev` environment  | `stacks/dev`   |
    | `<prod-iac-directory>`    | The directory containing IaC for your `prod` environment | `stacks/prod`  |
    | `<aws-region>`            | Your AWS region                        | `eu-west-1`    |

=== "Application monorepo"

    Download `.gp.cicd.json`:

    ```bash
    gh api repos/oslokommune/golden-path-templates/contents/templates/gh-cicd-app-monorepo/.gp.cicd.json \
      --jq '.content' | base64 -d > .gp.cicd.json
    ```

    Update these values (many of which can be found in `common-config.yml` in your infrastructure repository):

    | Field                     | Description                            | Example        |
    |---------------------------|----------------------------------------|----------------|
    | `<team-name>`             | Your team name                         | `pirates`      |
    | `<repo-iac>`     | Infrastructure-as-code repository name | `pirates-iac`  |
    | `<dev-aws-account-id>`    | AWS account ID for `dev`                 | `123456789012` |
    | `<prod-aws-account-id>`   | AWS account ID for `prod`                | `987654321098` |
    | `<dev-environment-name>`  | Name of your `dev` AWS environment       | `pirates-dev`  |
    | `<prod-environment-name>` | Name of your `prod` AWS environment      | `pirates-prod` |
    | `<dev-iac-directory>`     | The directory containing IaC for your `dev` environment  | `stacks/dev`   |
    | `<prod-iac-directory>`    | The directory containing IaC for your `prod` environment | `stacks/prod`  |
    | `<aws-region>`            | Your AWS region                        | `eu-west-1`    |

### Step 3.2: Add CODEOWNERS

Add a `CODEOWNERS` file to define who owns the repository and approves PRs:

```text title="repo-app/.github/CODEOWNERS"
* @oslokommune/<github-team-name>
```

| Field                | Description                                                                                 | Example        |
| -------------------- | ------------------------------------------------------------------------------------------- | -------------- |
| `<github-team-name>` | The name of your [GitHub team](https://github.com/orgs/oslokommune/teams){:target="_blank"} | `utviklerflyt` |

### Step 3.3: Add Renovate configuration

Download [renovate.json5](https://github.com/oslokommune/golden-path-templates/blob/main/templates/gh-cicd-app/renovate.json5){:target="_blank"} to the repository root:

```bash
gh api repos/oslokommune/golden-path-templates/contents/templates/gh-cicd-app/renovate.json5 \
  --jq '.content' | base64 -d > renovate.json5
```

<!-- prettier-ignore-start -->
!!! info "What is Renovate?"
    Renovate automatically creates pull requests to keep your dependencies up to date. Think of it as a better Dependabot. Learn more in the [Renovate reference documentation](../../terraform-automation/renovate.md).
<!-- prettier-ignore-end -->

## Step 4: Add the deployment workflow

Download the [zebra deployment workflow](https://github.com/oslokommune/pirates-app-zebra/blob/main/.github/workflows/zebra_deployment.yml){:target="_blank"} as a starting point and save it to `.github/workflows/<app-name>_deployment.yml`.

Address the `TODO` comments in the workflow. The main adaptation is in the `build-and-upload-artifact` job, where you customize the build and test steps to match your application.

<!-- prettier-ignore-start -->
<!-- "promote" is the CI/CD term for artifact promotion — moving one immutable build through environments — not corporate jargon. -->
<!-- vale NeedsRewrite.CorporateJargon = NO -->
!!! info "Build once deploy many"
    The deployment pipeline is designed to build, upload and promote the **exact** same immutable artifact across environments ("build once, deploy many").
<!-- vale NeedsRewrite.CorporateJargon = YES -->

    For static websites where configuration typically differs per environment, see [how zebra handles environment configuration](https://github.com/oslokommune/pirates-app-zebra/blob/main/src/config.ts){:target="_blank"}.

!!! tip "Post-deployment testing"
    The deployment pipeline contains a job `test-dev` and `test-prod` that runs after deployment to development and production, respectively. Feel free to add more tests to these jobs to check the behavior of your running application in AWS after a deployment, such as smoke tests and end-to-end tests.
<!-- prettier-ignore-end -->

## Step 5: Create a pull request

Push the branch and create a pull request.

Verify that the build job succeeds and that no deployment occurs.

## Step 6: Merge and deploy

1. Merge the pull request.
1. The workflow deploys to your `dev` environment automatically. Verify the site is accessible by checking that the `Test webapp` job succeeds — a green check means the job curled your `dev` URL and got a response.
1. Approve the production deployment when prompted. Verify that the production site is accessible.
