Skip to content

Commit

Permalink
redirect domains to docs
Browse files Browse the repository at this point in the history
  • Loading branch information
lukeshay committed Nov 28, 2023
1 parent 6d2b1a6 commit ba8b9ce
Show file tree
Hide file tree
Showing 13 changed files with 1,616 additions and 273 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- uses: pnpm/action-setup@v2
with:
version: "8.7.6"
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node_version }}
cache: "pnpm"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
description: The version of node to use
required: false
type: string
default: "18"
default: "20"
pnpm_version:
description: The version of PNPM to use
required: false
Expand Down Expand Up @@ -84,7 +84,7 @@ jobs:
- uses: pnpm/action-setup@v2
with:
version: ${{ inputs.pnpm_version }}
- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node_version }}
cache: "pnpm"
Expand Down
6 changes: 0 additions & 6 deletions apps/infra/cdk.context.json

This file was deleted.

2 changes: 2 additions & 0 deletions apps/infra/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"dependencies": {
"@astro-aws/constructs": "workspace:^",
"@astro-aws/docs": "workspace:^",
"@aws-sdk/client-acm": "^3.460.0",
"aws-cdk": "^2.109.0",
"aws-cdk-lib": "^2.109.0",
"constructs": "^10.3.0",
Expand All @@ -40,6 +41,7 @@
},
"devDependencies": {
"@astro-aws/scripts": "workspace:^",
"@types/aws-lambda": "^8.10.126",
"@types/node": "^18.18.0",
"eslint-config-get-off-my-lawn": "^7.2.0"
},
Expand Down
42 changes: 20 additions & 22 deletions apps/infra/src/bin/infra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
ENVIRONMENT_PROPS,
} from "../lib/constants/environments.js"
import { WebsiteStack } from "../lib/stacks/website-stack.js"
import { CertificateStack } from "../lib/stacks/certificate-stack.js"
import { GitHubOIDCStack } from "../lib/stacks/github-oidc-stack.js"
import { GitHubUsersStack } from "../lib/stacks/github-users-stack.js"
import { RedirectStack } from "../lib/stacks/redirect-stack.js"

const app = new App()

Expand All @@ -20,30 +20,13 @@ const createStackName = (

Object.entries(ENVIRONMENT_PROPS).forEach(([environment, environmentProps]) => {
environmentProps.websites.forEach((websiteProps) => {
let certificateStack: CertificateStack | undefined

if (environment === Environments.DEV) {
// eslint-disable-next-line no-param-reassign
delete websiteProps.hostedZoneName
// eslint-disable-next-line no-param-reassign
delete websiteProps.aliases
}

if (websiteProps.hostedZoneName && websiteProps.aliases) {
certificateStack = new CertificateStack(
app,
createStackName(
environment,
"Certificate",
websiteProps.runtime,
websiteProps.mode,
),
{
...environmentProps,
aliases: websiteProps.aliases,
hostedZoneName: websiteProps.hostedZoneName,
},
)
// eslint-disable-next-line no-param-reassign
delete websiteProps.redirectAliases
}

new WebsiteStack(
Expand All @@ -57,10 +40,25 @@ Object.entries(ENVIRONMENT_PROPS).forEach(([environment, environmentProps]) => {
{
...environmentProps,
...websiteProps,
certificate: certificateStack?.certificate,
hostedZone: certificateStack?.hostedZone,
},
)

if (websiteProps.redirectAliases && websiteProps.hostedZoneName) {
new RedirectStack(
app,
createStackName(
environment,
"Redirect",
websiteProps.runtime,
websiteProps.mode,
),
{
...environmentProps,
aliases: websiteProps.redirectAliases,
hostedZoneName: websiteProps.hostedZoneName,
},
)
}
})

if (environment === Environments.DEV) {
Expand Down
16 changes: 9 additions & 7 deletions apps/infra/src/lib/constants/environments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ const Environments = {

type Environment = (typeof Environments)[keyof typeof Environments]

type EnvironmentProps = Readonly<
AstroAWSStackProps & {
websites: readonly (Partial<AstroAWSStackProps> & StaticWebsiteStackProps)[]
}
>
type EnvironmentProps = AstroAWSStackProps & {
websites: readonly (Partial<AstroAWSStackProps> &
StaticWebsiteStackProps & {
redirectAliases?: [string, ...string[]]
})[]
}

const ENVIRONMENT_PROPS: Record<Environment, EnvironmentProps> = {
[Environments.DEV]: {
Expand Down Expand Up @@ -96,10 +97,11 @@ const ENVIRONMENT_PROPS: Record<Environment, EnvironmentProps> = {
environment: Environments.PROD,
websites: [
{
aliases: ["www.docs", "docs"],
aliases: ["www.docs"],
hostedZoneName: "astro-aws.org",
mode: "static",
package: "@astro-aws/docs",
redirectAliases: ["", "www", "docs"],
runtime: "nodejs20",
},
],
Expand All @@ -125,6 +127,6 @@ const ENVIRONMENT_PROPS: Record<Environment, EnvironmentProps> = {
},
],
},
} as const
}

export { Environments, ENVIRONMENT_PROPS, type Environment }
136 changes: 136 additions & 0 deletions apps/infra/src/lib/constructs/cross-region-certificate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// eslint-disable-next-line max-classes-per-file
import * as path from "node:path"

import { Stack, CustomResource, Duration } from "aws-cdk-lib/core"
import { Provider } from "aws-cdk-lib/custom-resources"
import { Construct } from "constructs"
import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda"
import { PolicyStatement } from "aws-cdk-lib/aws-iam"
import {
Certificate,
type ICertificate,
} from "aws-cdk-lib/aws-certificatemanager"

class CrossRegionCertificateProvider extends Construct {
private readonly provider: Provider

public constructor(scope: Construct, id: string) {
super(scope, id)

const onEventHandler = new Function(
this,
"CrossRegionCertificateProviderEventHandler",
{
code: Code.fromAsset(
path.resolve(".", "support", "cross-region-certificate"),
),
handler: "index.onEvent",
initialPolicy: [
new PolicyStatement({
actions: ["acm:RequestCertificate", "acm:DeleteCertificate"],
resources: ["*"],
}),
],
runtime: Runtime.NODEJS_20_X,
},
)

const isCompleteHandler = new Function(
this,
"CrossRegionCertificateProviderCompleteHandler",
{
code: Code.fromAsset(
path.resolve(".", "support", "cross-region-certificate"),
),
handler: "index.isComplete",
initialPolicy: [
new PolicyStatement({
actions: ["acm:DescribeCertificate"],
resources: ["*"],
}),
],
runtime: Runtime.NODEJS_20_X,
},
)

this.provider = new Provider(this, "CrossRegionCertificateProvider", {
isCompleteHandler,
onEventHandler,
queryInterval: Duration.seconds(15),
totalTimeout: Duration.minutes(20),
})
}

public static getOrCreate(scope: Construct) {
const stack = Stack.of(scope)
const id =
"org.astro-aws.cdk.custom-resources.cross-region-certificate-provider"
const x =
(stack.node.tryFindChild(id) as
| CrossRegionCertificateProvider
| undefined) ?? new CrossRegionCertificateProvider(stack, id)

return x.provider.serviceToken
}
}

type CerticateStatus =
| "EXPIRED"
| "FAILED"
| "INACTIVE"
| "ISSUED"
| "PENDING_VALIDATION"
| "REVOKED"
| "VALIDATION_TIMED_OUT"
| undefined

type CrossRegionCertificateProperties = {
domainName: string
alternateNames?: string[]
region?: string
}

class CrossRegionCertificate extends Construct {
public readonly certificateArn: string
public readonly domainName: string
public readonly alternateNames: string[]
public readonly region: string
public readonly status: CerticateStatus
public readonly certificate: ICertificate

public constructor(
scope: Construct,
id: string,
properties: CrossRegionCertificateProperties,
) {
super(scope, id)

const resource = new CustomResource(this, "Resource", {
properties: {
alternateNames: properties.alternateNames ?? [],
domainName: properties.domainName,
idempotenceToken: this.node.addr,
region: properties.region ?? Stack.of(this).region,
},
resourceType: "Custom::CrossRegionCertificate",
serviceToken: CrossRegionCertificateProvider.getOrCreate(this),
})

this.certificateArn = resource.getAttString("certificateArn")
this.domainName = resource.getAttString("domainName")
this.alternateNames = resource.getAtt("alternateNames").toStringList()
this.region = resource.getAttString("region")
this.status = resource.getAttString("status") as CerticateStatus
this.certificate = Certificate.fromCertificateArn(
this,
"Certificate",
this.certificateArn,
)
}
}

export {
type CrossRegionCertificateProperties,
type CerticateStatus,
CrossRegionCertificate,
}
2 changes: 1 addition & 1 deletion apps/infra/src/lib/stacks/certificate-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class CertificateStack extends Stack {

const { hostedZoneName, aliases } = props
const [domainName, ...restDomainNames] = aliases.map((alias) =>
[alias, hostedZoneName].join("."),
[alias, hostedZoneName].filter(Boolean).join("."),
) as [string, ...string[]]

this.hostedZone = HostedZone.fromLookup(this, "HostedZone", {
Expand Down
Loading

0 comments on commit ba8b9ce

Please sign in to comment.