@studion/infra-code-blocks
Version:
Studion common infra components
278 lines (277 loc) • 11 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NuxtSSR = void 0;
const pulumi = require("@pulumi/pulumi");
const aws = require("@pulumi/aws");
const random = require("@pulumi/random");
const constants_1 = require("../constants");
const acm_certificate_1 = require("./acm-certificate");
const ecs_service_1 = require("./ecs-service");
const defaults = {
healthCheckPath: '/',
};
class NuxtSSR extends pulumi.ComponentResource {
constructor(name, args, opts = {}) {
super('studion:NuxtSSR', name, args, opts);
const { vpcId, domain, hostedZoneId, tags } = args;
const hasCustomDomain = domain && hostedZoneId;
if (domain && !hostedZoneId) {
throw new Error('NuxtSSR:hostedZoneId must be provided when the domain is specified');
}
this.name = name;
if (hasCustomDomain) {
this.certificate = this.createTlsCertificate({ domain, hostedZoneId });
}
this.customCFHeader = this.createCustomCFHeader();
const { lb, lbTargetGroup, lbHttpListener, lbSecurityGroup } = this.createLoadBalancer(args);
this.lb = lb;
this.lbTargetGroup = lbTargetGroup;
this.lbHttpListener = lbHttpListener;
this.lbSecurityGroup = lbSecurityGroup;
this.serviceSecurityGroup = this.createSecurityGroup(vpcId);
this.service = this.createEcsService(args);
this.cloudfront = this.createCloudfrontDistribution({ domain, tags });
if (hasCustomDomain) {
this.createDnsRecord({ domain, hostedZoneId });
}
this.registerOutputs();
}
createTlsCertificate({ domain, hostedZoneId, }) {
const certificate = new acm_certificate_1.AcmCertificate(`${domain}-acm-certificate`, {
domain,
hostedZoneId,
}, { parent: this });
return certificate;
}
createCustomCFHeader() {
const headerNameOpts = {
length: 4,
special: false,
numeric: false,
lower: false,
upper: true,
};
const headerNameSegment1 = new random.RandomString(`${this.name}-cf-header-name-segment1`, headerNameOpts, { parent: this });
const headerNameSegment2 = new random.RandomString(`${this.name}-cf-header-name-segment2`, headerNameOpts, { parent: this });
const headerValue = new random.RandomString(`${this.name}-cf-header-value`, {
length: 36,
special: false,
numeric: true,
lower: true,
upper: true,
}, { parent: this });
const headerName = pulumi
.all([headerNameSegment1.result, headerNameSegment2.result])
.apply(([segment1, segment2]) => {
return `X-${segment1}-${segment2}`;
});
return { name: headerName, value: headerValue.result };
}
createLoadBalancer({ vpcId, publicSubnetIds, port, healthCheckPath, }) {
const lbSecurityGroup = new aws.ec2.SecurityGroup(`${this.name}-lb-security-group`, {
vpcId,
ingress: [
{
protocol: 'tcp',
fromPort: 80,
toPort: 80,
cidrBlocks: ['0.0.0.0/0'],
},
],
egress: [
{
fromPort: 0,
toPort: 0,
protocol: '-1',
cidrBlocks: ['0.0.0.0/0'],
},
],
tags: constants_1.commonTags,
}, { parent: this });
const lb = new aws.lb.LoadBalancer(`${this.name}-lb`, {
namePrefix: 'lb-',
loadBalancerType: 'application',
subnets: publicSubnetIds,
securityGroups: [lbSecurityGroup.id],
internal: false,
ipAddressType: 'ipv4',
tags: Object.assign(Object.assign({}, constants_1.commonTags), { Name: `${this.name}-lb` }),
}, { parent: this });
const lbTargetGroup = new aws.lb.TargetGroup(`${this.name}-lb-tg`, {
namePrefix: 'lb-tg-',
port,
protocol: 'HTTP',
targetType: 'ip',
vpcId,
healthCheck: {
healthyThreshold: 3,
unhealthyThreshold: 2,
interval: 60,
timeout: 5,
path: healthCheckPath || defaults.healthCheckPath,
},
tags: Object.assign(Object.assign({}, constants_1.commonTags), { Name: `${this.name}-lb-target-group` }),
}, { parent: this, dependsOn: [this.lb] });
const lbHttpListener = new aws.lb.Listener(`${this.name}-lb-listener-80`, {
loadBalancerArn: lb.arn,
port: 80,
defaultActions: [
{
type: 'fixed-response',
fixedResponse: {
statusCode: '403',
messageBody: 'Not Allowed',
contentType: 'text/plain',
},
},
],
tags: constants_1.commonTags,
}, { parent: this });
const lbHttpListenerRule = new aws.lb.ListenerRule(`${this.name}-lb-listener-rule`, {
listenerArn: lbHttpListener.arn,
priority: 1,
actions: [
{
type: 'forward',
targetGroupArn: lbTargetGroup.arn,
},
],
conditions: [
{
httpHeader: {
httpHeaderName: this.customCFHeader.name,
values: [this.customCFHeader.value],
},
},
],
}, { parent: this });
return {
lb,
lbTargetGroup,
lbHttpListener,
lbSecurityGroup,
};
}
createSecurityGroup(vpcId) {
const securityGroup = new aws.ec2.SecurityGroup(`${this.name}-security-group`, {
vpcId,
ingress: [
{
fromPort: 0,
toPort: 0,
protocol: '-1',
securityGroups: [this.lbSecurityGroup.id],
},
],
egress: [
{
fromPort: 0,
toPort: 0,
protocol: '-1',
cidrBlocks: ['0.0.0.0/0'],
},
],
tags: constants_1.commonTags,
}, { parent: this });
return securityGroup;
}
createEcsService(args) {
const service = new ecs_service_1.EcsService(this.name, Object.assign(Object.assign({}, args), { enableServiceAutoDiscovery: false, lbTargetGroupArn: this.lbTargetGroup.arn, assignPublicIp: true, subnetIds: args.publicSubnetIds, securityGroup: this.serviceSecurityGroup }), {
parent: this,
dependsOn: [this.lb, this.lbTargetGroup, this.lbHttpListener],
});
return service;
}
createCloudfrontDistribution({ domain, tags, }) {
const cachePolicy = new aws.cloudfront.CachePolicy(`${this.name}-cf-cache-policy`, {
comment: 'This cache policy is managed by Pulumi, changing its values will impact multiple services.',
defaultTtl: 0,
maxTtl: 31536000,
minTtl: 0,
parametersInCacheKeyAndForwardedToOrigin: {
cookiesConfig: {
cookieBehavior: 'none',
},
headersConfig: {
headerBehavior: 'none',
},
queryStringsConfig: {
queryStringBehavior: 'all',
},
},
}, { parent: this });
const originRequestPolicyId = aws.cloudfront
.getOriginRequestPolicyOutput({
name: 'Managed-AllViewer',
})
.apply(policy => policy.id);
const responseHeadersPolicyId = aws.cloudfront
.getResponseHeadersPolicyOutput({
name: 'Managed-SecurityHeadersPolicy',
})
.apply(policy => policy.id);
const cloudfront = new aws.cloudfront.Distribution(`${this.name}-cloudfront`, Object.assign(Object.assign({ enabled: true }, (domain && { aliases: [domain] })), { isIpv6Enabled: true, waitForDeployment: true, httpVersion: 'http2and3', viewerCertificate: Object.assign({}, (this.certificate
? {
acmCertificateArn: this.certificate.certificate.arn,
sslSupportMethod: 'sni-only',
minimumProtocolVersion: 'TLSv1.2_2021',
}
: {
cloudfrontDefaultCertificate: true,
})), origins: [
{
originId: this.lb.arn,
domainName: this.lb.dnsName,
connectionAttempts: 3,
connectionTimeout: 10,
customOriginConfig: {
originProtocolPolicy: 'http-only',
httpPort: 80,
httpsPort: 443,
originSslProtocols: ['SSLv3'],
},
customHeaders: [
{ name: 'X-Forwarded-Port', value: '443' },
{ name: 'X-Forwarded-Ssl', value: 'on' },
this.customCFHeader,
],
},
], defaultCacheBehavior: {
targetOriginId: this.lb.arn,
viewerProtocolPolicy: 'redirect-to-https',
allowedMethods: [
'GET',
'HEAD',
'OPTIONS',
'PUT',
'POST',
'PATCH',
'DELETE',
],
cachedMethods: ['GET', 'HEAD'],
compress: true,
cachePolicyId: cachePolicy.id,
originRequestPolicyId,
responseHeadersPolicyId,
}, priceClass: 'PriceClass_100', restrictions: {
geoRestriction: { restrictionType: 'none' },
}, tags: Object.assign(Object.assign({}, constants_1.commonTags), tags) }), { parent: this });
return cloudfront;
}
createDnsRecord({ domain, hostedZoneId, }) {
const cdnAliasRecord = new aws.route53.Record(`${this.name}-cdn-route53-record`, {
type: 'A',
name: domain,
zoneId: hostedZoneId,
aliases: [
{
name: this.cloudfront.domainName,
zoneId: this.cloudfront.hostedZoneId,
evaluateTargetHealth: true,
},
],
}, { parent: this });
return cdnAliasRecord;
}
}
exports.NuxtSSR = NuxtSSR;