UNPKG

@studion/infra-code-blocks

Version:
278 lines (277 loc) 11 kB
"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;