Skip to content
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

Add pulumi to the project to start deploying infrastructure #290

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .devcontainer/Dockerfile.devcontainer
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ FROM ghcr.io/iainlane/dotfiles-rust-tools:git-24a7c0cfa3e9b909f954a85dd0b4163f60

FROM public.ecr.aws/aws-cli/aws-cli:2.16.3 AS aws-cli

FROM pulumi/pulumi-base:3.118.0 AS pulumi

FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:1-22-bookworm

RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
Expand All @@ -13,6 +15,8 @@ RUN ln -s /usr/local/aws-cli/v2/current/bin/aws /usr/local/bin/aws

COPY --from=rust-tools /usr/local/bin/* /usr/local/bin/

COPY --from=pulumi /pulumi/bin/* /usr/bin/

RUN corepack enable

USER node
5 changes: 4 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"source=${localWorkspaceFolderBasename}-node_modules-met.no,target=${containerWorkspaceFolder}/gen/met.no/node_modules,type=volume",
"source=${localWorkspaceFolderBasename}-node_modules-geojs,target=${containerWorkspaceFolder}/gen/geojs/node_modules,type=volume",
"source=${localWorkspaceFolderBasename}-yarn_cache,target=/home/node/.cache/yarn,type=volume",
"source=${localWorkspaceFolderBasename}-node_cache,target=/home/node/.cache/node,type=volume"
"source=${localWorkspaceFolderBasename}-node_cache,target=/home/node/.cache/node,type=volume",
"source=${localWorkspaceFolderBasename}-pulumi-config,target=/home/node/.pulumi,type=volume"
],

"containerUser": "node",
Expand All @@ -20,6 +21,8 @@
"AWS_SDK_LOAD_CONFIG": "true"
},

"runArgs": ["--env-file", ".devcontainer/.env"],

"onCreateCommand": "${containerWorkspaceFolder}/.devcontainer/onCreateCommand.sh",

"customizations": {
Expand Down
3 changes: 2 additions & 1 deletion .devcontainer/onCreateCommand.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ sudo chown node:node /home/node/.cache
sudo chown node:node /home/node/.cache/node
sudo chown node:node /home/node/.cache/yarn
sudo chown node:node node_modules */*/node_modules
sudo chown node:node /home/node/.pulumi

yarn install --immutable < /dev/null
yarn serverless dynamodb install
yarn serverless dynamodb install --stage=local
59 changes: 59 additions & 0 deletions .github/workflows/oidc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
on:
pull_request:

push:
branches:
- main

name: Authenticate with AWS

jobs:
oidc:
permissions:
contents: read
id-token: write
pull-requests: write

runs-on: ubuntu-latest

env:
AWS_REGION: eu-west-2
STATE_BUCKET: coldoutsi.de-pulumi-state

steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2
with:
audience: coldoutsi.de-dev
aws-region: ${{ env.AWS_REGION }}
role-to-assume: arn:aws:iam::072248381277:role/oidcRole-715afe8

- name: Print session info
run: aws sts get-caller-identity

- name: Check out
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6

- name: Enable corepack
run: |
corepack enable

- name: Set up Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version-file: "package.json"
cache: "yarn"

- name: Install dependencies
run: yarn workspace pulumi install --immutable

- name: Pulumi preview
uses: pulumi/actions@18b5a33fc447ab919feb61f2bb41147a1b30ab40 # v5.2.4
with:
cloud-url:
s3://${{ env.STATE_BUCKET }}?region=${{ env.AWS_REGION }}&awssdk=v2
stack-name: organization/coldoutsi.de/dev
command: preview
comment-on-pr: true
comment-on-summary: true
work-dir: pulumi
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ coverage/

# Personal settings
.vscode/settings.json
.devcontainer/.env

# Secrets in here
.env*
7 changes: 5 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ environment which supports [dev containers][devcontainers].
git clone https://github.com/iainlane/coldoutsi.de.git
```

2. Open the project in the IDE
2. If using AWS SSO and a non-default profile and/or region, create a file
`.devcontainer/.env` and set `AWS_PROFILE` and/or `AWS_REGION` accordingly.

3. If using VS Code, click "Reopen in Container" when prompted. This will build
3. Open the project in the IDE

4. If using VS Code, click "Reopen in Container" when prompted. This will build
and start the container with all the necessary dependencies installed.

[devcontainers]: https://containers.dev/
Expand Down
6 changes: 6 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ export default tseslint.config(
"unicorn/no-typeof-undefined": "error",
},
},
{
files: ["pulumi/**/*.ts"],
rules: {
"@typescript-eslint/no-unused-vars": "off",
},
},
{
files: ["**/*.test.ts"],
...jestPlugin.configs["flat/recommended"],
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"workspaces": {
"packages": [
"app",
"gen/*"
"gen/*",
"pulumi"
]
},
"type": "module",
Expand Down
2 changes: 2 additions & 0 deletions pulumi/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bin/
node_modules/
6 changes: 6 additions & 0 deletions pulumi/Pulumi.dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
secretsprovider: awskms://12e131e6-2150-4361-913b-803c04bd5ed5?region=eu-west-2&awssdk=v2
encryptedkey: AQICAHhLE3kXzgyhKhfd8kMt7I2EBNdrJw7DPra9AQz3o1duvwFP5X23eeRpxqRKtjmP4VNoAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMDGX3lZXomIJHHVIMAgEQgDuk/UkIZt7A+8db7RGO9aWvexlDZxGmSK6m7Wda/LXX0gblOSKbjyYxW+cheqvz0Jvx8fkHNPep0dZgSA==
config:
gitHubRepo: iainlane/coldoutsi.de
targetDomain: dev
targetZone: coldoutsi.de
11 changes: 11 additions & 0 deletions pulumi/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: coldoutsi.de
runtime:
name: nodejs
options:
# https://github.com/TypeStrong/ts-node/issues/1007
nodeargs: "--loader ts-node/esm --no-warnings"
description: Pulumi program for coldoutsi.de
config:
pulumi:tags:
value:
pulumi:template: typescript
155 changes: 155 additions & 0 deletions pulumi/cloudfront.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import * as aws from "@pulumi/aws";
import * as aws_native from "@pulumi/aws-native";
import * as pulumi from "@pulumi/pulumi";

const config = new pulumi.Config();

const targetZone = config.require("targetZone");
const targetDomain = config.require("targetDomain");

const targetDomainFull = `${targetDomain}.${targetZone}`;

const hostedZone = new aws.route53.Zone("zone", {
name: targetZone,
});

const logsBucket = new aws_native.s3.Bucket("requestLogs", {
bucketName: `${targetDomainFull}-logs`,
ownershipControls: {
rules: [
{
objectOwnership:
aws_native.s3.BucketOwnershipControlsRuleObjectOwnership
.BucketOwnerPreferred,
},
],
},
publicAccessBlockConfiguration: {
blockPublicAcls: true,
blockPublicPolicy: true,
ignorePublicAcls: true,
restrictPublicBuckets: true,
},
});

const awsUsEast = new aws.Provider("aws-us-east-1", {
profile: aws_native.config.profile,
region: "us-east-1",
});

const certificate = new aws.acm.Certificate(
`${targetDomain}-cert`,
{
domainName: targetDomainFull,
validationMethod: "DNS",
},
{
provider: awsUsEast,
},
);

const certificateValidationDomain = new aws.route53.Record(
`${targetDomain}-cert-validation`,
{
name: certificate.domainValidationOptions[0].resourceRecordName,
zoneId: hostedZone.zoneId,
type: certificate.domainValidationOptions[0].resourceRecordType,
records: [certificate.domainValidationOptions[0].resourceRecordValue],
ttl: 60,
},
);

const certificateValidation = new aws.acm.CertificateValidation(
"certificateValidation",
{
certificateArn: certificate.arn,
validationRecordFqdns: [certificateValidationDomain.fqdn],
},
{
provider: awsUsEast,
},
);

const origin = {
customOriginConfig: {
originProtocolPolicy: "https-only",
},
domainName: `api.${targetDomainFull}`,
id: `api-${targetDomainFull}`,
} satisfies aws_native.types.input.cloudfront.DistributionOriginArgs;

const cachePolicy = new aws_native.cloudfront.CachePolicy(
"coldoutsi.de-cache-policy",
{
cachePolicyConfig: {
name: "coldoutsi-de-cache-policy",
defaultTtl: 60 * 60, // 1 hour
minTtl: 60, // 1 minute
maxTtl: 60 * 60 * 24, // 1 day
parametersInCacheKeyAndForwardedToOrigin: {
cookiesConfig: {
cookieBehavior: "none",
},
enableAcceptEncodingGzip: true,
enableAcceptEncodingBrotli: true,
headersConfig: {
headerBehavior: "whitelist",
headers: [
"Accept",
"Accept-Language",
"CloudFront-Viewer-Latitude",
"CloudFront-Viewer-Longitude",
"Content-Type",
"If-None-Match",
"Last-Modified",
],
},
queryStringsConfig: {
queryStringBehavior: "whitelist",
queryStrings: ["format"],
},
},
},
},
);

const cloudFrontDistribution = new aws_native.cloudfront.Distribution(
"coldoutsi.de-dev",
{
distributionConfig: {
aliases: [targetDomainFull],
defaultCacheBehavior: {
cachePolicyId: cachePolicy.id,
targetOriginId: origin.id,
viewerProtocolPolicy: "redirect-to-https",
},
enabled: true,
httpVersion: "http3",
ipv6Enabled: true,
logging: {
bucket: logsBucket.domainName,
includeCookies: false,
},
origins: [origin],
// https://aws.amazon.com/cloudfront/pricing/
priceClass: "PriceClass_100",
viewerCertificate: {
acmCertificateArn: certificate.arn,
sslSupportMethod: "sni-only",
},
},
},
);

const aliasRecord = new aws.route53.Record(`dns-${targetDomainFull}`, {
name: targetDomainFull,
zoneId: hostedZone.zoneId,
type: "A",
aliases: [
{
evaluateTargetHealth: true,
name: cloudFrontDistribution.domainName,
zoneId: "Z2FDTNDATAQYW2", // CloudFront zone ID
},
],
});
3 changes: 3 additions & 0 deletions pulumi/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// TODO: why do we have to give the extension here?
import "./cloudfront.ts";
export * from "./oidc.ts";
Loading
Loading