serverless-appsync-cloudfront
Advanced tools
Comparing version 0.9.7 to 1.0.0
213
index.js
@@ -1,8 +0,8 @@ | ||
const path = require('path'); | ||
const _ = require('lodash'); | ||
const chalk = require('chalk'); | ||
const yaml = require('js-yaml'); | ||
const fs = require('fs'); | ||
const path = require("path"); | ||
const _ = require("lodash"); | ||
const chalk = require("chalk"); | ||
const yaml = require("js-yaml"); | ||
const fs = require("fs"); | ||
const certStatuses = ['PENDING_VALIDATION', 'ISSUED', 'INACTIVE']; | ||
const certStatuses = ["PENDING_VALIDATION", "ISSUED", "INACTIVE"]; | ||
@@ -13,7 +13,9 @@ class ServerlessAppSyncCloudFrontPlugin { | ||
this.options = options; | ||
this.givenDomainName = this.serverless.service.custom.appSyncCloudFront.domainName; | ||
this.givenDomainName = this.getConfig("domainName", null); | ||
this.hooks = { | ||
'package:createDeploymentArtifacts': this.createDeploymentArtifacts.bind(this), | ||
'aws:info:displayStackOutputs': this.printSummary.bind(this), | ||
"package:createDeploymentArtifacts": this.createDeploymentArtifacts.bind( | ||
this | ||
), | ||
"aws:info:displayStackOutputs": this.printSummary.bind(this), | ||
}; | ||
@@ -23,12 +25,14 @@ } | ||
async createDeploymentArtifacts() { | ||
if (this.serverless.service.custom.appSyncCloudFront.enabled !== false) { | ||
this.givenDomainName = this.serverless.service.custom.appSyncCloudFront.domainName; | ||
if (this.getConfig("enabled", true) !== false) { | ||
const credentials = this.serverless.providers.aws.getCredentials(); | ||
const acmCredentials = Object.assign({}, credentials, { region: 'us-east-1' }); | ||
const acmCredentials = Object.assign({}, credentials, { | ||
region: "us-east-1", | ||
}); | ||
this.acm = new this.serverless.providers.aws.sdk.ACM(acmCredentials); | ||
this.route53 = new this.serverless.providers.aws.sdk.Route53(credentials); | ||
const baseResources = this.serverless.service.provider.compiledCloudFormationTemplate; | ||
const baseResources = this.serverless.service.provider | ||
.compiledCloudFormationTemplate; | ||
const filename = path.resolve(__dirname, 'resources.yml'); | ||
const content = fs.readFileSync(filename, 'utf-8'); | ||
const filename = path.resolve(__dirname, "resources.yml"); | ||
const content = fs.readFileSync(filename, "utf-8"); | ||
const resources = yaml.safeLoad(content, { filename }); | ||
@@ -42,5 +46,6 @@ | ||
async printSummary() { | ||
const awsInfo = _.find(this.serverless.pluginManager.getPlugins(), plugin => ( | ||
plugin.constructor.name === 'AwsInfo' | ||
)); | ||
const awsInfo = _.find( | ||
this.serverless.pluginManager.getPlugins(), | ||
(plugin) => plugin.constructor.name === "AwsInfo" | ||
); | ||
@@ -52,5 +57,6 @@ if (!awsInfo || !awsInfo.gatheredData) { | ||
const { outputs } = awsInfo.gatheredData; | ||
const apiDistributionDomain = _.find(outputs, output => ( | ||
output.OutputKey === 'AppSyncApiDistribution' | ||
)); | ||
const apiDistributionDomain = _.find( | ||
outputs, | ||
(output) => output.OutputKey === "AppSyncApiDistribution" | ||
); | ||
@@ -61,10 +67,16 @@ if (!apiDistributionDomain || !apiDistributionDomain.OutputValue) { | ||
const cnameDomain = this.getConfig('domainName', null); | ||
this.serverless.cli.consoleLog(chalk.yellow('CloudFront domain name')); | ||
this.serverless.cli.consoleLog(` ${apiDistributionDomain.OutputValue} (CNAME: ${cnameDomain || '-'})`); | ||
const cnameDomain = this.getConfig("domainName", null); | ||
this.serverless.cli.consoleLog(chalk.yellow("CloudFront domain name")); | ||
this.serverless.cli.consoleLog( | ||
` ${apiDistributionDomain.OutputValue} (CNAME: ${cnameDomain || "-"})` | ||
); | ||
if (cnameDomain) { | ||
this.serverless.cli.consoleLog(`AppSync: ${chalk.yellow(`Creating Route53 records for ${cnameDomain}...`)}`); | ||
this.serverless.cli.consoleLog( | ||
`AppSync: ${chalk.yellow( | ||
`Creating Route53 records for ${cnameDomain}...` | ||
)}` | ||
); | ||
this.changeResourceRecordSet('UPSERT', apiDistributionDomain.OutputValue); | ||
this.changeResourceRecordSet("UPSERT", apiDistributionDomain.OutputValue); | ||
} | ||
@@ -78,8 +90,8 @@ } | ||
let certificateArn; // The arn of the choosen certificate | ||
let { certificateName } = this.serverless.service.custom.appSyncCloudFront; // The certificate name | ||
const { domainName } = this.serverless.service.custom.appSyncCloudFront; // Domain name | ||
let certificateName = this.getConfig("certificateName", null); | ||
if (!certificateName && !this.givenDomainName) return; | ||
try { | ||
const certData = await this.acm.listCertificates( | ||
{ CertificateStatuses: certStatuses }, | ||
).promise(); | ||
const certData = await this.acm | ||
.listCertificates({ CertificateStatuses: certStatuses }) | ||
.promise(); | ||
@@ -92,4 +104,5 @@ // The more specific name will be the longest | ||
if (certificateName != null) { | ||
const foundCertificate = certificates | ||
.find(certificate => (certificate.DomainName === certificateName)); | ||
const foundCertificate = certificates.find( | ||
(certificate) => certificate.DomainName === certificateName | ||
); | ||
if (foundCertificate != null) { | ||
@@ -99,7 +112,7 @@ certificateArn = foundCertificate.CertificateArn; | ||
} else { | ||
certificateName = domainName; | ||
certificateName = this.givenDomainName; | ||
certificates.forEach((certificate) => { | ||
let certificateListName = certificate.DomainName; | ||
// Looks for wild card and takes it out when checking | ||
if (certificateListName[0] === '*') { | ||
if (certificateListName[0] === "*") { | ||
certificateListName = certificateListName.substr(1); | ||
@@ -109,4 +122,6 @@ } | ||
// Also checks if the name is more specific than previous ones | ||
if (certificateName.includes(certificateListName) | ||
&& certificateListName.length > nameLength) { | ||
if ( | ||
certificateName.includes(certificateListName) && | ||
certificateListName.length > nameLength | ||
) { | ||
nameLength = certificateListName.length; | ||
@@ -118,3 +133,5 @@ certificateArn = certificate.CertificateArn; | ||
} catch (err) { | ||
throw Error(`Error: Could not list certificates in Certificate Manager.\n${err}`); | ||
throw Error( | ||
`Error: Could not list certificates in Certificate Manager.\n${err}` | ||
); | ||
} | ||
@@ -128,8 +145,8 @@ if (certificateArn == null) { | ||
/** | ||
* Change A Alias record through Route53 based on given action | ||
* @param action: String descriptor of change to be made. Valid actions are ['UPSERT', 'DELETE'] | ||
* @param domain: DomainInfo object containing info about custom domain | ||
*/ | ||
* Change A Alias record through Route53 based on given action | ||
* @param action: String descriptor of change to be made. Valid actions are ['UPSERT', 'DELETE'] | ||
* @param domain: DomainInfo object containing info about custom domain | ||
*/ | ||
async changeResourceRecordSet(action, domain) { | ||
if (action !== 'UPSERT' && action !== 'DELETE') { | ||
if (action !== "UPSERT" && action !== "DELETE") { | ||
throw new Error(`Error: Invalid action "${action}" when changing Route53 Record. | ||
@@ -139,5 +156,5 @@ Action must be either UPSERT or DELETE.\n`); | ||
const createRoute53Record = this.getConfig('createRoute53Record', null); | ||
const createRoute53Record = this.getConfig("createRoute53Record", null); | ||
if (createRoute53Record !== undefined && createRoute53Record === false) { | ||
this.serverless.cli.log('Skipping creation of Route53 record.'); | ||
this.serverless.cli.log("Skipping creation of Route53 record."); | ||
return; | ||
@@ -147,3 +164,3 @@ } | ||
const route53HostedZoneId = await this.getRoute53HostedZoneId(); | ||
const Changes = ['A', 'AAAA'].map(Type => ({ | ||
const Changes = ["A", "AAAA"].map((Type) => ({ | ||
Action: action, | ||
@@ -154,3 +171,3 @@ ResourceRecordSet: { | ||
EvaluateTargetHealth: false, | ||
HostedZoneId: 'Z2FDTNDATAQYW2', // CloudFront HZID is always Z2FDTNDATAQYW2 | ||
HostedZoneId: "Z2FDTNDATAQYW2", // CloudFront HZID is always Z2FDTNDATAQYW2 | ||
}, | ||
@@ -164,3 +181,3 @@ Name: this.givenDomainName, | ||
Changes, | ||
Comment: 'Record created by serverless-appsync-cloudfront', | ||
Comment: "Record created by serverless-appsync-cloudfront", | ||
}, | ||
@@ -173,3 +190,5 @@ HostedZoneId: route53HostedZoneId, | ||
} catch (err) { | ||
throw new Error(`Error: Failed to ${action} A Alias for ${this.givenDomainName}\n`); | ||
throw new Error( | ||
`Error: Failed to ${action} A Alias for ${this.givenDomainName}\n` | ||
); | ||
} | ||
@@ -184,3 +203,4 @@ } | ||
this.serverless.cli.log( | ||
`Selected specific hostedZoneId ${this.serverless.service.custom.appSyncCloudFront.hostedZoneId}`); | ||
`Selected specific hostedZoneId ${this.serverless.service.custom.appSyncCloudFront.hostedZoneId}` | ||
); | ||
return this.serverless.service.custom.appSyncCloudFront.hostedZoneId; | ||
@@ -191,16 +211,16 @@ } | ||
if (filterZone && this.hostedZonePrivate) { | ||
this.serverless.cli.log('Filtering to only private zones.'); | ||
this.serverless.cli.log("Filtering to only private zones."); | ||
} else if (filterZone && !this.hostedZonePrivate) { | ||
this.serverless.cli.log('Filtering to only public zones.'); | ||
this.serverless.cli.log("Filtering to only public zones."); | ||
} | ||
let hostedZoneData; | ||
const givenDomainNameReverse = this.givenDomainName.split('.').reverse(); | ||
const givenDomainNameReverse = this.givenDomainName.split(".").reverse(); | ||
try { | ||
hostedZoneData = await this.route53.listHostedZones({}).promise(); | ||
const targetHostedZone = hostedZoneData.HostedZones | ||
.filter((hostedZone) => { | ||
const targetHostedZone = hostedZoneData.HostedZones.filter( | ||
(hostedZone) => { | ||
let hostedZoneName; | ||
if (hostedZone.Name.endsWith('.')) { | ||
if (hostedZone.Name.endsWith(".")) { | ||
hostedZoneName = hostedZone.Name.slice(0, -1); | ||
@@ -210,7 +230,12 @@ } else { | ||
} | ||
if (!filterZone || this.hostedZonePrivate === hostedZone.Config.PrivateZone) { | ||
const hostedZoneNameReverse = hostedZoneName.split('.').reverse(); | ||
if ( | ||
!filterZone || | ||
this.hostedZonePrivate === hostedZone.Config.PrivateZone | ||
) { | ||
const hostedZoneNameReverse = hostedZoneName.split(".").reverse(); | ||
if (givenDomainNameReverse.length === 1 | ||
|| (givenDomainNameReverse.length >= hostedZoneNameReverse.length)) { | ||
if ( | ||
givenDomainNameReverse.length === 1 || | ||
givenDomainNameReverse.length >= hostedZoneNameReverse.length | ||
) { | ||
for (let i = 0; i < hostedZoneNameReverse.length; i += 1) { | ||
@@ -225,3 +250,4 @@ if (givenDomainNameReverse[i] !== hostedZoneNameReverse[i]) { | ||
return false; | ||
}) | ||
} | ||
) | ||
.sort((zone1, zone2) => zone2.Name.length - zone1.Name.length) | ||
@@ -233,3 +259,3 @@ .shift(); | ||
// Extracts the hostzone Id | ||
const startPos = hostedZoneId.indexOf('e/') + 2; | ||
const startPos = hostedZoneId.indexOf("e/") + 2; | ||
const endPos = hostedZoneId.length; | ||
@@ -242,8 +268,10 @@ return hostedZoneId.substring(startPos, endPos); | ||
} | ||
throw new Error(`Error: Could not find hosted zone "${this.givenDomainName}"`); | ||
throw new Error( | ||
`Error: Could not find hosted zone "${this.givenDomainName}"` | ||
); | ||
} | ||
async prepareResources(resources) { | ||
const distributionConfig = resources.Resources.AppSyncApiDistribution.Properties.DistributionConfig; | ||
const distributionConfig = | ||
resources.Resources.AppSyncApiDistribution.Properties.DistributionConfig; | ||
@@ -253,3 +281,2 @@ this.prepareLogging(distributionConfig); | ||
this.preparePriceClass(distributionConfig); | ||
// this.prepareOrigins(distributionConfig); | ||
this.prepareCookies(distributionConfig); | ||
@@ -266,7 +293,7 @@ this.prepareHeaders(distributionConfig); | ||
prepareLogging(distributionConfig) { | ||
const loggingBucket = this.getConfig('logging.bucket', null); | ||
const loggingBucket = this.getConfig("logging.bucket", null); | ||
if (loggingBucket !== null) { | ||
distributionConfig.Logging.Bucket = loggingBucket; | ||
distributionConfig.Logging.Prefix = this.getConfig('logging.prefix', ''); | ||
distributionConfig.Logging.Prefix = this.getConfig("logging.prefix", ""); | ||
} else { | ||
@@ -278,3 +305,3 @@ delete distributionConfig.Logging; | ||
prepareDomain(distributionConfig) { | ||
const domain = this.getConfig('domainName', null); | ||
const domain = this.getConfig("domainName", null); | ||
if (domain !== null) { | ||
@@ -288,3 +315,3 @@ distributionConfig.Aliases = Array.isArray(domain) ? domain : [domain]; | ||
preparePriceClass(distributionConfig) { | ||
const priceClass = this.getConfig('priceClass', 'PriceClass_All'); | ||
const priceClass = this.getConfig("priceClass", "PriceClass_All"); | ||
distributionConfig.PriceClass = priceClass; | ||
@@ -298,4 +325,8 @@ } | ||
prepareCookies(distributionConfig) { | ||
const forwardCookies = this.getConfig('cookies', 'all'); | ||
distributionConfig.DefaultCacheBehavior.ForwardedValues.Cookies.Forward = Array.isArray(forwardCookies) ? 'whitelist' : forwardCookies; | ||
const forwardCookies = this.getConfig("cookies", "all"); | ||
distributionConfig.DefaultCacheBehavior.ForwardedValues.Cookies.Forward = Array.isArray( | ||
forwardCookies | ||
) | ||
? "whitelist" | ||
: forwardCookies; | ||
if (Array.isArray(forwardCookies)) { | ||
@@ -307,3 +338,3 @@ distributionConfig.DefaultCacheBehavior.ForwardedValues.Cookies.WhitelistedNames = forwardCookies; | ||
prepareHeaders(distributionConfig) { | ||
const forwardHeaders = this.getConfig('headers', 'none'); | ||
const forwardHeaders = this.getConfig("headers", "none"); | ||
@@ -313,3 +344,4 @@ if (Array.isArray(forwardHeaders)) { | ||
} else { | ||
distributionConfig.DefaultCacheBehavior.ForwardedValues.Headers = forwardHeaders === 'none' ? [] : ['*']; | ||
distributionConfig.DefaultCacheBehavior.ForwardedValues.Headers = | ||
forwardHeaders === "none" ? [] : ["*"]; | ||
} | ||
@@ -319,3 +351,3 @@ } | ||
prepareQueryString(distributionConfig) { | ||
const forwardQueryString = this.getConfig('querystring', 'all'); | ||
const forwardQueryString = this.getConfig("querystring", "all"); | ||
@@ -326,3 +358,4 @@ if (Array.isArray(forwardQueryString)) { | ||
} else { | ||
distributionConfig.DefaultCacheBehavior.ForwardedValues.QueryString = forwardQueryString === 'all'; | ||
distributionConfig.DefaultCacheBehavior.ForwardedValues.QueryString = | ||
forwardQueryString === "all"; | ||
} | ||
@@ -332,3 +365,3 @@ } | ||
prepareComment(distributionConfig) { | ||
const name = this.serverless.getProvider('aws').naming.getApiGatewayName(); | ||
const name = this.serverless.getProvider("aws").naming.getApiGatewayName(); | ||
distributionConfig.Comment = `Serverless Managed ${name}`; | ||
@@ -338,8 +371,10 @@ } | ||
async prepareCertificate(distributionConfig) { | ||
const certificate = this.getConfig('certificate', null) || await this.getCertArn(); | ||
if (certificate !== null) { | ||
const certificate = | ||
this.getConfig("certificate", null) || (await this.getCertArn()); | ||
if (certificate) { | ||
distributionConfig.ViewerCertificate.AcmCertificateArn = certificate; | ||
} else { | ||
delete distributionConfig.ViewerCertificate; | ||
delete distributionConfig.ViewerCertificate.AcmCertificateArn; | ||
delete distributionConfig.ViewerCertificate.SslSupportMethod; | ||
distributionConfig.ViewerCertificate.CloudFrontDefaultCertificate = true; | ||
} | ||
@@ -349,3 +384,3 @@ } | ||
prepareWaf(distributionConfig) { | ||
const waf = this.getConfig('waf', null); | ||
const waf = this.getConfig("waf", null); | ||
@@ -360,7 +395,11 @@ if (waf !== null) { | ||
prepareCompress(distributionConfig) { | ||
distributionConfig.DefaultCacheBehavior.Compress = (this.getConfig('compress', false) === true); | ||
distributionConfig.DefaultCacheBehavior.Compress = | ||
this.getConfig("compress", false) === true; | ||
} | ||
prepareMinimumProtocolVersion(distributionConfig) { | ||
const minimumProtocolVersion = this.getConfig('minimumProtocolVersion', undefined); | ||
const minimumProtocolVersion = this.getConfig( | ||
"minimumProtocolVersion", | ||
undefined | ||
); | ||
@@ -373,3 +412,7 @@ if (minimumProtocolVersion) { | ||
getConfig(field, defaultValue) { | ||
return _.get(this.serverless, `service.custom.appSyncCloudFront.${field}`, defaultValue); | ||
return _.get( | ||
this.serverless, | ||
`service.custom.appSyncCloudFront.${field}`, | ||
defaultValue | ||
); | ||
} | ||
@@ -376,0 +419,0 @@ } |
{ | ||
"name": "serverless-appsync-cloudfront", | ||
"version": "0.9.7", | ||
"version": "1.0.0", | ||
"engines": { | ||
@@ -5,0 +5,0 @@ "node": ">=8.10" |
@@ -18,10 +18,12 @@ # serverless-appsync-cloudfront | ||
Either point to this repository from your package.json or clone this repo to `.serverless_plugins` folder in your project. | ||
`npm i --save-dev serverless-appsync-cloudfront` | ||
TODO: pending NPM release | ||
or | ||
`yarn add -D serverless-appsync-cloudfront` | ||
## Configuration | ||
* All appSyncCloudFront configuration parameters are optional - e.g. don't provide ACM Certificate ARN to use default CloudFront certificate (which works only for default cloudfront.net domain). | ||
* First deployment may be quite long (e.g. 10 min) as Serverless is waiting for CloudFormation to deploy CloudFront distribution. | ||
- All appSyncCloudFront configuration parameters are optional - e.g. don't provide ACM Certificate ARN to use default CloudFront certificate (which works only for default cloudfront.net domain). | ||
- First deployment may be quite long (e.g. 10 min) as Serverless is waiting for CloudFormation to deploy CloudFront distribution. | ||
@@ -34,2 +36,3 @@ ``` | ||
# All of these custom parameters are optional | ||
custom: | ||
@@ -57,3 +60,3 @@ appSyncCloudFront: | ||
* `domain` can be list, so if you want to add more domains, instead string you list multiple ones: | ||
- `domain` can be list, so if you want to add more domains, instead string you list multiple ones: | ||
@@ -66,3 +69,4 @@ ``` | ||
* `cookies` can be *all* (default), *none* or a list that lists the cookies to whitelist | ||
- `cookies` can be _all_ (default), _none_ or a list that lists the cookies to whitelist | ||
``` | ||
@@ -74,3 +78,3 @@ cookies: | ||
* [`headers`][headers-default-cache] can be *all*, *none* (default) or a list of headers ([see CloudFront custom behaviour][headers-list]): | ||
- [`headers`][headers-default-cache] can be _all_, _none_ (default) or a list of headers ([see CloudFront custom behaviour][headers-list]): | ||
@@ -84,3 +88,3 @@ ``` | ||
* `querystring` can be *all* (default), *none* or a list, in which case all querystring parameters are forwarded, but cache is based on the list: | ||
- `querystring` can be _all_ (default), _none_ or a list, in which case all querystring parameters are forwarded, but cache is based on the list: | ||
@@ -91,5 +95,4 @@ ``` | ||
* [`priceClass`][price-class] can be `PriceClass_All` (default), `PriceClass_100` or `PriceClass_200`: | ||
- [`priceClass`][price-class] can be `PriceClass_All` (default), `PriceClass_100` or `PriceClass_200`: | ||
``` | ||
@@ -101,5 +104,4 @@ priceClass: PriceClass_All | ||
* [`minimumProtocolVersion`][minimum-protocol-version] can be `TLSv1` (default), `TLSv1_2016`, `TLSv1.1_2016`, `TLSv1.2_2018` or `SSLv3`: | ||
- [`minimumProtocolVersion`][minimum-protocol-version] can be `TLSv1` (default), `TLSv1_2016`, `TLSv1.1_2016`, `TLSv1.2_2018` or `SSLv3`: | ||
``` | ||
@@ -111,3 +113,3 @@ minimumProtocolVersion: TLSv1 | ||
* `enabled` can be `true` (default) or `false`. Can be used to disable cloudfront distribution deployment. | ||
- `enabled` can be `true` (default) or `false`. Can be used to disable cloudfront distribution deployment. | ||
@@ -124,8 +126,8 @@ ``` | ||
* `cloudfront:CreateDistribution` | ||
* `cloudfront:GetDistribution` | ||
* `cloudfront:UpdateDistribution` | ||
* `cloudfront:DeleteDistribution` | ||
* `cloudfront:TagResource` | ||
- `cloudfront:CreateDistribution` | ||
- `cloudfront:GetDistribution` | ||
- `cloudfront:UpdateDistribution` | ||
- `cloudfront:DeleteDistribution` | ||
- `cloudfront:TagResource` | ||
You can read more about IAM profiles and policies in the [Serverless documentation](https://serverless.com/framework/docs/providers/aws/guide/credentials#creating-aws-access-keys). |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
21419
345
1
124