|
diff --git a/node_modules/serverless/lib/plugins/aws/invokeLocal/index.js b/node_modules/serverless/lib/plugins/aws/invokeLocal/index.js |
|
index 123e8e1..5e099e3 100644 |
|
--- a/node_modules/serverless/lib/plugins/aws/invokeLocal/index.js |
|
+++ b/node_modules/serverless/lib/plugins/aws/invokeLocal/index.js |
|
@@ -15,6 +15,7 @@ const download = require('download'); |
|
const { ensureDir } = require('fs-extra'); |
|
const cachedir = require('cachedir'); |
|
const decompress = require('decompress'); |
|
+const { v4: uuidv4 } = require('uuid'); |
|
const dirExists = require('../../../utils/fs/dirExists'); |
|
const fileExists = require('../../../utils/fs/fileExists'); |
|
const isStandalone = require('../../../utils/isStandaloneExecutable'); |
|
@@ -126,10 +127,11 @@ class AwsInvokeLocal { |
|
}); |
|
} |
|
|
|
- getCredentialEnvVars() { |
|
+ async getCredentialEnvVars() { |
|
const credentialEnvVars = {}; |
|
const { credentials } = this.provider.getCredentials(); |
|
if (credentials) { |
|
+ await credentials.getPromise().catch(() => null); // best effort |
|
if (credentials.accessKeyId) { |
|
credentialEnvVars.AWS_ACCESS_KEY_ID = credentials.accessKeyId; |
|
} |
|
@@ -149,64 +151,61 @@ class AwsInvokeLocal { |
|
return _.merge(providerEnvVars, functionEnvVars); |
|
} |
|
|
|
- loadEnvVars() { |
|
- return BbPromise.try(() => { |
|
- const lambdaName = this.options.functionObj.name; |
|
- const memorySize = |
|
- Number(this.options.functionObj.memorySize) || |
|
- Number(this.serverless.service.provider.memorySize) || |
|
- 1024; |
|
- |
|
- const lambdaDefaultEnvVars = { |
|
- LANG: 'en_US.UTF-8', |
|
- LD_LIBRARY_PATH: |
|
- '/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib', // eslint-disable-line max-len |
|
- LAMBDA_TASK_ROOT: '/var/task', |
|
- LAMBDA_RUNTIME_DIR: '/var/runtime', |
|
- AWS_REGION: this.provider.getRegion(), |
|
- AWS_DEFAULT_REGION: this.provider.getRegion(), |
|
- AWS_LAMBDA_LOG_GROUP_NAME: this.provider.naming.getLogGroupName(lambdaName), |
|
- AWS_LAMBDA_LOG_STREAM_NAME: '2016/12/02/[$LATEST]f77ff5e4026c45bda9a9ebcec6bc9cad', |
|
- AWS_LAMBDA_FUNCTION_NAME: lambdaName, |
|
- AWS_LAMBDA_FUNCTION_MEMORY_SIZE: memorySize, |
|
- AWS_LAMBDA_FUNCTION_VERSION: '$LATEST', |
|
- NODE_PATH: '/var/runtime:/var/task:/var/runtime/node_modules', |
|
- }; |
|
+ async loadEnvVars() { |
|
+ const lambdaName = this.options.functionObj.name; |
|
+ const memorySize = |
|
+ Number(this.options.functionObj.memorySize) || |
|
+ Number(this.serverless.service.provider.memorySize) || |
|
+ 1024; |
|
+ |
|
+ const lambdaDefaultEnvVars = { |
|
+ LANG: 'en_US.UTF-8', |
|
+ LD_LIBRARY_PATH: |
|
+ '/usr/local/lib64/node-v4.3.x/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib', // eslint-disable-line max-len |
|
+ LAMBDA_TASK_ROOT: '/var/task', |
|
+ LAMBDA_RUNTIME_DIR: '/var/runtime', |
|
+ AWS_REGION: this.provider.getRegion(), |
|
+ AWS_DEFAULT_REGION: this.provider.getRegion(), |
|
+ AWS_LAMBDA_LOG_GROUP_NAME: this.provider.naming.getLogGroupName(lambdaName), |
|
+ AWS_LAMBDA_LOG_STREAM_NAME: '2016/12/02/[$LATEST]f77ff5e4026c45bda9a9ebcec6bc9cad', |
|
+ AWS_LAMBDA_FUNCTION_NAME: lambdaName, |
|
+ AWS_LAMBDA_FUNCTION_MEMORY_SIZE: memorySize, |
|
+ AWS_LAMBDA_FUNCTION_VERSION: '$LATEST', |
|
+ NODE_PATH: '/var/runtime:/var/task:/var/runtime/node_modules', |
|
+ }; |
|
|
|
- const credentialEnvVars = this.getCredentialEnvVars(); |
|
+ const credentialEnvVars = await this.getCredentialEnvVars(); |
|
|
|
- // profile override from config |
|
- const profileOverride = this.provider.getProfile(); |
|
- if (profileOverride) { |
|
- lambdaDefaultEnvVars.AWS_PROFILE = profileOverride; |
|
- } |
|
+ // profile override from config |
|
+ const profileOverride = this.provider.getProfile(); |
|
+ if (profileOverride) { |
|
+ lambdaDefaultEnvVars.AWS_PROFILE = profileOverride; |
|
+ } |
|
|
|
- const configuredEnvVars = this.getConfiguredEnvVars(); |
|
+ const configuredEnvVars = this.getConfiguredEnvVars(); |
|
|
|
- const promises = Object.entries(configuredEnvVars).map(async ([name, value]) => { |
|
- if (!_.isObject(value)) return; |
|
- try { |
|
- if (value['Fn::ImportValue']) { |
|
- configuredEnvVars[name] = await resolveCfImportValue( |
|
- this.provider, |
|
- value['Fn::ImportValue'] |
|
- ); |
|
- } else if (value.Ref) { |
|
- configuredEnvVars[name] = await resolveCfRefValue(this.provider, value.Ref); |
|
- } else { |
|
- throw new Error(`Unsupported format: ${inspect(value)}`); |
|
- } |
|
- } catch (error) { |
|
- throw new this.serverless.classes.Error( |
|
- `Could not resolve "${name}" environment variable: ${error.message}` |
|
+ const promises = Object.entries(configuredEnvVars).map(async ([name, value]) => { |
|
+ if (!_.isObject(value)) return; |
|
+ try { |
|
+ if (value['Fn::ImportValue']) { |
|
+ configuredEnvVars[name] = await resolveCfImportValue( |
|
+ this.provider, |
|
+ value['Fn::ImportValue'] |
|
); |
|
+ } else if (value.Ref) { |
|
+ configuredEnvVars[name] = await resolveCfRefValue(this.provider, value.Ref); |
|
+ } else { |
|
+ throw new Error(`Unsupported format: ${inspect(value)}`); |
|
} |
|
- }); |
|
- |
|
- return BbPromise.all(promises).then(() => { |
|
- _.merge(process.env, lambdaDefaultEnvVars, credentialEnvVars, configuredEnvVars); |
|
- }); |
|
+ } catch (error) { |
|
+ throw new this.serverless.classes.Error( |
|
+ `Could not resolve "${name}" environment variable: ${error.message}` |
|
+ ); |
|
+ } |
|
}); |
|
+ |
|
+ await Promise.all(promises); |
|
+ _.merge(process.env, lambdaDefaultEnvVars, credentialEnvVars, configuredEnvVars); |
|
} |
|
|
|
invokeLocal() { |
|
@@ -466,77 +465,72 @@ class AwsInvokeLocal { |
|
return this.serverless.pluginManager.spawn('package'); |
|
} |
|
|
|
- invokeLocalDocker() { |
|
+ async invokeLocalDocker() { |
|
const handler = this.options.functionObj.handler; |
|
const runtime = this.getRuntime(); |
|
|
|
- return this.ensurePackage() |
|
- .then(() => this.checkDockerDaemonStatus()) |
|
- .then(() => |
|
- BbPromise.all([ |
|
- this.checkDockerImage(`lambci/lambda:${runtime}`).then(exists => { |
|
- return exists ? {} : this.pullDockerImage(); |
|
- }), |
|
- this.getLayerPaths().then(layerPaths => this.buildDockerImage(layerPaths)), |
|
- this.extractArtifact(), |
|
- ]).then(results => { |
|
- const imageName = results[1]; |
|
- const artifactPath = results[2]; |
|
- |
|
- const lambdaName = this.options.functionObj.name; |
|
- const memorySize = |
|
- Number(this.options.functionObj.memorySize) || |
|
- Number(this.serverless.service.provider.memorySize) || |
|
- 1024; |
|
- const lambdaDefaultEnvVars = { |
|
- AWS_REGION: this.provider.getRegion(), |
|
- AWS_DEFAULT_REGION: this.provider.getRegion(), |
|
- AWS_LAMBDA_LOG_GROUP_NAME: this.provider.naming.getLogGroupName(lambdaName), |
|
- AWS_LAMBDA_FUNCTION_NAME: lambdaName, |
|
- AWS_LAMBDA_FUNCTION_MEMORY_SIZE: memorySize, |
|
- }; |
|
- const credentialEnvVars = this.getCredentialEnvVars(); |
|
- const configuredEnvVars = this.getConfiguredEnvVars(); |
|
- const envVarsFromOptions = this.getEnvVarsFromOptions(); |
|
- const envVars = _.merge( |
|
- lambdaDefaultEnvVars, |
|
- credentialEnvVars, |
|
- configuredEnvVars, |
|
- envVarsFromOptions |
|
- ); |
|
- const envVarsDockerArgs = _.flatMap(envVars, (value, key) => [ |
|
- '--env', |
|
- `${key}=${value}`, |
|
- ]); |
|
- |
|
- const dockerArgsFromOptions = this.getDockerArgsFromOptions(); |
|
- const dockerArgs = [ |
|
- 'run', |
|
- '--rm', |
|
- '-v', |
|
- `${artifactPath}:/var/task:ro,delegated`, |
|
- ].concat(envVarsDockerArgs, dockerArgsFromOptions, [ |
|
- imageName, |
|
- handler, |
|
- JSON.stringify(this.options.data), |
|
- ]); |
|
- |
|
- return spawnExt('docker', dockerArgs).then( |
|
- ({ stdBuffer }) => { |
|
- if (stdBuffer) { |
|
- process.stdout.write(stdBuffer); |
|
- } |
|
- return imageName; |
|
- }, |
|
- ({ code, stdBuffer }) => { |
|
- if (stdBuffer) { |
|
- process.stdout.write(stdBuffer); |
|
- } |
|
- throw new Error(`Failed to run docker for ${runtime} image (exit code ${code}})`); |
|
- } |
|
- ); |
|
- }) |
|
- ); |
|
+ await this.ensurePackage(); |
|
+ await this.checkDockerDaemonStatus(); |
|
+ const results = await Promise.all([ |
|
+ this.checkDockerImage(`lambci/lambda:${runtime}`).then(exists => { |
|
+ return exists ? {} : this.pullDockerImage(); |
|
+ }), |
|
+ this.getLayerPaths().then(layerPaths => this.buildDockerImage(layerPaths)), |
|
+ this.extractArtifact(), |
|
+ ]); |
|
+ |
|
+ const imageName = results[1]; |
|
+ const artifactPath = results[2]; |
|
+ |
|
+ const lambdaName = this.options.functionObj.name; |
|
+ const memorySize = |
|
+ Number(this.options.functionObj.memorySize) || |
|
+ Number(this.serverless.service.provider.memorySize) || |
|
+ 1024; |
|
+ const lambdaDefaultEnvVars = { |
|
+ AWS_REGION: this.provider.getRegion(), |
|
+ AWS_DEFAULT_REGION: this.provider.getRegion(), |
|
+ AWS_LAMBDA_LOG_GROUP_NAME: this.provider.naming.getLogGroupName(lambdaName), |
|
+ AWS_LAMBDA_FUNCTION_NAME: lambdaName, |
|
+ AWS_LAMBDA_FUNCTION_MEMORY_SIZE: memorySize, |
|
+ }; |
|
+ const credentialEnvVars = await this.getCredentialEnvVars(); |
|
+ const configuredEnvVars = this.getConfiguredEnvVars(); |
|
+ const envVarsFromOptions = this.getEnvVarsFromOptions(); |
|
+ const envVars = _.merge( |
|
+ lambdaDefaultEnvVars, |
|
+ credentialEnvVars, |
|
+ configuredEnvVars, |
|
+ envVarsFromOptions |
|
+ ); |
|
+ const envVarsDockerArgs = _.flatMap(envVars, (value, key) => ['--env', `${key}=${value}`]); |
|
+ |
|
+ const dockerArgsFromOptions = this.getDockerArgsFromOptions(); |
|
+ const dockerArgs = [ |
|
+ 'run', |
|
+ '--rm', |
|
+ '-v', |
|
+ `${artifactPath}:/var/task:ro,delegated`, |
|
+ ].concat(envVarsDockerArgs, dockerArgsFromOptions, [ |
|
+ imageName, |
|
+ handler, |
|
+ JSON.stringify(this.options.data), |
|
+ ]); |
|
+ |
|
+ return spawnExt('docker', dockerArgs).then( |
|
+ ({ stdBuffer }) => { |
|
+ if (stdBuffer) { |
|
+ process.stdout.write(stdBuffer); |
|
+ } |
|
+ return imageName; |
|
+ }, |
|
+ ({ code, stdBuffer }) => { |
|
+ if (stdBuffer) { |
|
+ process.stdout.write(stdBuffer); |
|
+ } |
|
+ throw new Error(`Failed to run docker for ${runtime} image (exit code ${code}})`); |
|
+ } |
|
+ ); |
|
} |
|
|
|
getDockerArgsFromOptions() { |
|
@@ -834,7 +828,7 @@ class AwsInvokeLocal { |
|
Number(this.serverless.service.provider.timeout) || |
|
6; |
|
let context = { |
|
- awsRequestId: 'id', |
|
+ awsRequestId: uuidv4(), |
|
invokeid: 'id', |
|
logGroupName: this.provider.naming.getLogGroupName(this.options.functionObj.name), |
|
logStreamName: '2015/09/22/[HEAD]13370a84ca4ed8b77c427af260', |
|
diff --git a/node_modules/serverless/lib/plugins/aws/invokeLocal/index.test.js b/node_modules/serverless/lib/plugins/aws/invokeLocal/index.test.js |
|
index 9474818..091717a 100644 |
|
--- a/node_modules/serverless/lib/plugins/aws/invokeLocal/index.test.js |
|
+++ b/node_modules/serverless/lib/plugins/aws/invokeLocal/index.test.js |
|
@@ -68,7 +68,7 @@ describe('AwsInvokeLocal', () => { |
|
serverless.processedInput = { commands: ['invoke'] }; |
|
provider = new AwsProvider(serverless, options); |
|
provider.cachedCredentials = { |
|
- credentials: { accessKeyId: 'foo', secretAccessKey: 'bar' }, |
|
+ credentials: { getPromise: async () => null, accessKeyId: 'foo', secretAccessKey: 'bar' }, |
|
}; |
|
serverless.setProvider('aws', provider); |
|
awsInvokeLocal = new AwsInvokeLocal(serverless, options); |
|
@@ -291,18 +291,19 @@ describe('AwsInvokeLocal', () => { |
|
}); |
|
|
|
describe('#getCredentialEnvVars()', () => { |
|
- it('returns empty object when credentials is not set', () => { |
|
+ it('returns empty object when credentials is not set', async () => { |
|
provider.cachedCredentials = null; |
|
serverless.service.provider.credentials = null; |
|
|
|
- const credentialEnvVars = awsInvokeLocal.getCredentialEnvVars(); |
|
+ const credentialEnvVars = await awsInvokeLocal.getCredentialEnvVars(); |
|
|
|
expect(credentialEnvVars).to.be.eql({}); |
|
}); |
|
|
|
- it('returns credential env vars from cached credentials', () => { |
|
+ it('returns credential env vars from cached credentials', async () => { |
|
provider.cachedCredentials = { |
|
credentials: { |
|
+ getPromise: async () => {}, |
|
accessKeyId: 'ID', |
|
secretAccessKey: 'SECRET', |
|
sessionToken: 'TOKEN', |
|
@@ -310,7 +311,7 @@ describe('AwsInvokeLocal', () => { |
|
}; |
|
serverless.service.provider.credentials = null; |
|
|
|
- const credentialEnvVars = awsInvokeLocal.getCredentialEnvVars(); |
|
+ const credentialEnvVars = await awsInvokeLocal.getCredentialEnvVars(); |
|
|
|
expect(credentialEnvVars).to.be.eql({ |
|
AWS_ACCESS_KEY_ID: 'ID', |
|
@@ -319,15 +320,16 @@ describe('AwsInvokeLocal', () => { |
|
}); |
|
}); |
|
|
|
- it('returns credential env vars from credentials config', () => { |
|
+ it('returns credential env vars from credentials config', async () => { |
|
provider.cachedCredentials = null; |
|
serverless.service.provider.credentials = { |
|
+ getPromise: async () => {}, |
|
accessKeyId: 'ID', |
|
secretAccessKey: 'SECRET', |
|
sessionToken: 'TOKEN', |
|
}; |
|
|
|
- const credentialEnvVars = awsInvokeLocal.getCredentialEnvVars(); |
|
+ const credentialEnvVars = await awsInvokeLocal.getCredentialEnvVars(); |
|
|
|
expect(credentialEnvVars).to.be.eql({ |
|
AWS_ACCESS_KEY_ID: 'ID', |
|
@@ -401,6 +403,7 @@ describe('AwsInvokeLocal', () => { |
|
it('it should set credential env vars #1', () => { |
|
provider.cachedCredentials = { |
|
credentials: { |
|
+ getPromise: async () => null, |
|
accessKeyId: 'ID', |
|
secretAccessKey: 'SECRET', |
|
}, |
|
@@ -416,6 +419,7 @@ describe('AwsInvokeLocal', () => { |
|
it('it should set credential env vars #2', () => { |
|
provider.cachedCredentials = { |
|
credentials: { |
|
+ getPromise: async () => null, |
|
sessionToken: 'TOKEN', |
|
}, |
|
}; |
|
@@ -1079,7 +1083,7 @@ describe('AwsInvokeLocal', () => { |
|
() => { |
|
log.debug('test target %o', serverless.cli.consoleLog.lastCall.args); |
|
const result = JSON.parse(serverless.cli.consoleLog.lastCall.args[0]); |
|
- expect(result.deadlineMs).to.be.closeTo(Date.now() + 6000, 1000); |
|
+ expect(result.deadlineMs).to.be.closeTo(Date.now() + 6000, 2000); |
|
}, |
|
error => { |
|
if (error.code === 'ENOENT' && error.path === 'ruby') { |
|
diff --git a/node_modules/serverless/lib/plugins/aws/provider/awsCredentials.js b/node_modules/serverless/lib/plugins/aws/provider/awsCredentials.js |
|
new file mode 100644 |
|
index 0000000..dda61ea |
|
--- /dev/null |
|
+++ b/node_modules/serverless/lib/plugins/aws/provider/awsCredentials.js |
|
@@ -0,0 +1,75 @@ |
|
+'use strict'; |
|
+ |
|
+const AWS = require('aws-sdk'); |
|
+const readline = require('readline'); |
|
+ |
|
+const notEmpty = s => typeof s === 'string' && s.trim().length > 0; |
|
+ |
|
+module.exports = class AwsCredentials extends AWS.Credentials { |
|
+ constructor() { |
|
+ super(); |
|
+ this.chain = new AWS.CredentialProviderChain([]); // providers are added explicitly |
|
+ } |
|
+ |
|
+ refresh(callback) { |
|
+ this.chain.resolve((err, res) => { |
|
+ if (err) { |
|
+ callback(err); |
|
+ } else { |
|
+ AWS.Credentials.call(this, res); |
|
+ callback(); |
|
+ } |
|
+ }); |
|
+ } |
|
+ |
|
+ /** |
|
+ * Add credentials, if present and valid, from provider config |
|
+ * @param credentials The credentials to test for validity |
|
+ */ |
|
+ addConfig(credentials) { |
|
+ if (credentials) { |
|
+ if ( |
|
+ notEmpty(credentials.accessKeyId) && |
|
+ (notEmpty(credentials.secretAccessKey) || notEmpty(credentials.sessionToken)) |
|
+ ) { |
|
+ this.chain.providers.push(new AWS.Credentials(credentials)); |
|
+ } |
|
+ } |
|
+ } |
|
+ |
|
+ /** |
|
+ * Add credentials, if present, from the environment |
|
+ * @param prefix The environment variable prefix to use in extracting credentials |
|
+ */ |
|
+ addEnvironment(prefix) { |
|
+ if (prefix) { |
|
+ const environmentCredentials = new AWS.EnvironmentCredentials(prefix); |
|
+ this.chain.providers.push(environmentCredentials); |
|
+ } |
|
+ } |
|
+ |
|
+ /** |
|
+ * Add credentials from a profile |
|
+ * @param profile The profile to load credentials from |
|
+ */ |
|
+ addProfile(profile) { |
|
+ if (profile) { |
|
+ const params = { profile }; |
|
+ if (process.env.AWS_SHARED_CREDENTIALS_FILE) { |
|
+ params.filename = process.env.AWS_SHARED_CREDENTIALS_FILE; |
|
+ } |
|
+ |
|
+ // Setup a MFA callback for asking the code from the user. |
|
+ params.tokenCodeFn = (mfaSerial, callback) => { |
|
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); |
|
+ rl.question(`Enter MFA code for ${mfaSerial}: `, answer => { |
|
+ rl.close(); |
|
+ callback(null, answer); |
|
+ }); |
|
+ }; |
|
+ |
|
+ this.chain.providers.push(() => new AWS.SharedIniFileCredentials(params)); |
|
+ this.chain.providers.push(() => new AWS.ProcessCredentials(params)); |
|
+ } |
|
+ } |
|
+}; |
|
diff --git a/node_modules/serverless/lib/plugins/aws/provider/awsProvider.js b/node_modules/serverless/lib/plugins/aws/provider/awsProvider.js |
|
index f4eb592..3426f94 100644 |
|
--- a/node_modules/serverless/lib/plugins/aws/provider/awsProvider.js |
|
+++ b/node_modules/serverless/lib/plugins/aws/provider/awsProvider.js |
|
@@ -11,17 +11,45 @@ const https = require('https'); |
|
const fs = require('fs'); |
|
const objectHash = require('object-hash'); |
|
const PromiseQueue = require('promise-queue'); |
|
+const AwsCredentials = require('./awsCredentials'); |
|
const getS3EndpointForRegion = require('../utils/getS3EndpointForRegion'); |
|
-const readline = require('readline'); |
|
const { ALB_LISTENER_REGEXP } = require('../package/compile/events/alb/lib/validate'); |
|
const path = require('path'); |
|
|
|
const isLambdaArn = RegExp.prototype.test.bind(/^arn:[^:]+:lambda:/); |
|
|
|
+function caseInsensitive(str) { |
|
+ return { type: 'string', regexp: new RegExp(`^${str}$`, 'i').toString() }; |
|
+} |
|
+ |
|
const constants = { |
|
providerName: 'aws', |
|
}; |
|
|
|
+const apiGatewayUsagePlan = { |
|
+ type: 'object', |
|
+ properties: { |
|
+ quota: { |
|
+ type: 'object', |
|
+ properties: { |
|
+ limit: { type: 'integer', minimum: 0 }, |
|
+ offset: { type: 'integer', minimum: 0 }, |
|
+ period: { enum: ['DAY', 'WEEK', 'MONTH'] }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ }, |
|
+ throttle: { |
|
+ type: 'object', |
|
+ properties: { |
|
+ burstLimit: { type: 'integer', minimum: 0 }, |
|
+ rateLimit: { type: 'integer', minimum: 0 }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+}; |
|
+ |
|
PromiseQueue.configure(BbPromise.Promise); |
|
|
|
const MAX_RETRIES = (() => { |
|
@@ -29,101 +57,6 @@ const MAX_RETRIES = (() => { |
|
return userValue >= 0 ? userValue : 4; |
|
})(); |
|
|
|
-const impl = { |
|
- /** |
|
- * Determine whether the given credentials are valid. It turned out that detecting invalid |
|
- * credentials was more difficult than detecting the positive cases we know about. Hooray for |
|
- * whak-a-mole! |
|
- * @param credentials The credentials to test for validity |
|
- * @return {boolean} Whether the given credentials were valid |
|
- */ |
|
- validCredentials: credentials => { |
|
- let result = false; |
|
- if (credentials) { |
|
- if ( |
|
- // valid credentials loaded |
|
- (credentials.accessKeyId && |
|
- credentials.accessKeyId !== 'undefined' && |
|
- credentials.secretAccessKey && |
|
- credentials.secretAccessKey !== 'undefined') || |
|
- // a role to assume has been successfully loaded, the associated STS request has been |
|
- // sent, and the temporary credentials will be asynchronously delivered. |
|
- credentials.roleArn |
|
- ) { |
|
- result = true; |
|
- } |
|
- } |
|
- return result; |
|
- }, |
|
- /** |
|
- * Add credentials, if present, to the given results |
|
- * @param results The results to add the given credentials to if they are valid |
|
- * @param credentials The credentials to validate and add to the results if valid |
|
- */ |
|
- addCredentials: (results, credentials) => { |
|
- if (impl.validCredentials(credentials)) { |
|
- results.credentials = credentials; // eslint-disable-line no-param-reassign |
|
- } |
|
- }, |
|
- /** |
|
- * Add credentials, if present, from the environment |
|
- * @param results The results to add environment credentials to |
|
- * @param prefix The environment variable prefix to use in extracting credentials |
|
- */ |
|
- addEnvironmentCredentials: (results, prefix) => { |
|
- if (prefix) { |
|
- const environmentCredentials = new AWS.EnvironmentCredentials(prefix); |
|
- impl.addCredentials(results, environmentCredentials); |
|
- } |
|
- }, |
|
- /** |
|
- * Add credentials from a profile, if the profile and credentials for it exists |
|
- * @param results The results to add profile credentials to |
|
- * @param profile The profile to load credentials from |
|
- */ |
|
- addProfileCredentials: (results, profile) => { |
|
- if (profile) { |
|
- const params = { profile }; |
|
- if (process.env.AWS_SHARED_CREDENTIALS_FILE) { |
|
- params.filename = process.env.AWS_SHARED_CREDENTIALS_FILE; |
|
- } |
|
- |
|
- // Setup a MFA callback for asking the code from the user. |
|
- params.tokenCodeFn = (mfaSerial, callback) => { |
|
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); |
|
- rl.question(`Enter MFA code for ${mfaSerial}: `, answer => { |
|
- rl.close(); |
|
- callback(null, answer); |
|
- }); |
|
- }; |
|
- |
|
- const profileCredentials = new AWS.SharedIniFileCredentials(params); |
|
- if ( |
|
- !( |
|
- profileCredentials.accessKeyId || |
|
- profileCredentials.sessionToken || |
|
- profileCredentials.roleArn |
|
- ) |
|
- ) { |
|
- throw new Error(`Profile ${profile} does not exist`); |
|
- } |
|
- |
|
- impl.addCredentials(results, profileCredentials); |
|
- } |
|
- }, |
|
- /** |
|
- * Add credentials, if present, from a profile that is specified within the environment |
|
- * @param results The prefix of the profile's declaration in the environment |
|
- * @param prefix The prefix for the environment variable |
|
- */ |
|
- addEnvironmentProfile: (results, prefix) => { |
|
- if (prefix) { |
|
- const profile = process.env[`${prefix}_PROFILE`]; |
|
- impl.addProfileCredentials(results, profile); |
|
- } |
|
- }, |
|
-}; |
|
- |
|
const baseAlbAuthorizerProperties = { |
|
onUnauthenticatedRequest: { enum: ['allow', 'authenticate', 'deny'] }, |
|
requestExtraParams: { |
|
@@ -173,10 +106,6 @@ const cognitoAlbAuthorizer = { |
|
}; |
|
|
|
class AwsProvider { |
|
- static getProviderName() { |
|
- return constants.providerName; |
|
- } |
|
- |
|
constructor(serverless, options) { |
|
this.naming = { provider: this }; |
|
this.options = options; |
|
@@ -202,7 +131,7 @@ class AwsProvider { |
|
pattern: '^[a-zA-Z0-9._\\-]+$', |
|
}, |
|
awsArn: { |
|
- oneOf: [ |
|
+ anyOf: [ |
|
{ $ref: '#/definitions/awsArnString' }, |
|
{ $ref: '#/definitions/awsCfFunction' }, |
|
], |
|
@@ -212,7 +141,7 @@ class AwsProvider { |
|
pattern: '^arn:', |
|
}, |
|
awsCfFunction: { |
|
- oneOf: [ |
|
+ anyOf: [ |
|
{ $ref: '#/definitions/awsCfImport' }, |
|
{ $ref: '#/definitions/awsCfJoin' }, |
|
{ $ref: '#/definitions/awsCfGetAtt' }, |
|
@@ -250,7 +179,7 @@ class AwsProvider { |
|
required: ['Fn::ImportValue'], |
|
}, |
|
awsCfInstruction: { |
|
- oneOf: [{ type: 'string', minLength: 1 }, { $ref: '#/definitions/awsCfFunction' }], |
|
+ anyOf: [{ type: 'string', minLength: 1 }, { $ref: '#/definitions/awsCfFunction' }], |
|
}, |
|
awsCfJoin: { |
|
type: 'object', |
|
@@ -259,7 +188,7 @@ class AwsProvider { |
|
type: 'array', |
|
minItems: 2, |
|
maxItems: 2, |
|
- items: [{ type: 'string', minLength: 1 }, { type: 'array' }], |
|
+ items: [{ type: 'string' }, { type: 'array' }], |
|
additionalItems: false, |
|
}, |
|
}, |
|
@@ -282,6 +211,58 @@ class AwsProvider { |
|
required: ['Fn::Sub'], |
|
additionalProperties: false, |
|
}, |
|
+ awsIamPolicyAction: { type: 'array', items: { type: 'string' } }, |
|
+ awsIamPolicyPrincipal: { |
|
+ anyOf: [ |
|
+ { const: '*' }, |
|
+ { |
|
+ type: 'object', |
|
+ properties: { |
|
+ AWS: { |
|
+ anyOf: [ |
|
+ { const: '*' }, |
|
+ { type: 'array', items: { $ref: '#/definitions/awsArn' } }, |
|
+ ], |
|
+ }, |
|
+ Federated: { type: 'array', items: { type: 'string' } }, |
|
+ Service: { type: 'array', items: { type: 'string' } }, |
|
+ CanonicalUser: { type: 'array', items: { type: 'string' } }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ }, |
|
+ ], |
|
+ }, |
|
+ awsIamPolicyResource: { |
|
+ anyOf: [ |
|
+ { const: '*' }, |
|
+ { $ref: '#/definitions/awsArn' }, |
|
+ { type: 'array', items: { $ref: '#/definitions/awsArn' } }, |
|
+ ], |
|
+ }, |
|
+ // Definition of Statement taken from https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_grammar.html#policies-grammar-bnf |
|
+ awsIamPolicyStatements: { |
|
+ type: 'array', |
|
+ items: { |
|
+ type: 'object', |
|
+ properties: { |
|
+ Sid: { type: 'string' }, |
|
+ Effect: { enum: ['Allow', 'Deny'] }, |
|
+ Action: { $ref: '#/definitions/awsIamPolicyAction' }, |
|
+ NotAction: { $ref: '#/definitions/awsIamPolicyAction' }, |
|
+ Principal: { $ref: '#/definitions/awsIamPolicyPrincipal' }, |
|
+ NotPrincipal: { $ref: '#/definitions/awsIamPolicyPrincipal' }, |
|
+ Resource: { $ref: '#/definitions/awsIamPolicyResource' }, |
|
+ NotResource: { $ref: '#/definitions/awsIamPolicyResource' }, |
|
+ Condition: { type: 'object' }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ allOf: [ |
|
+ { required: ['Effect'] }, |
|
+ { oneOf: [{ required: ['Action'] }, { required: ['NotAction'] }] }, |
|
+ { oneOf: [{ required: ['Resource'] }, { required: ['NotResource'] }] }, |
|
+ ], |
|
+ }, |
|
+ }, |
|
awsLambdaEnvironment: { |
|
type: 'object', |
|
patternProperties: { |
|
@@ -339,10 +320,12 @@ class AwsProvider { |
|
}, |
|
}, |
|
}, |
|
- awsResourceCondition: { type: 'string' }, |
|
- awsResourceDependsOn: { |
|
- oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], |
|
+ awsLogGroupName: { |
|
+ type: 'string', |
|
+ pattern: '^[/#A-Za-z0-9-_.]+$', |
|
}, |
|
+ awsResourceCondition: { type: 'string' }, |
|
+ awsResourceDependsOn: { type: 'array', items: { type: 'string' } }, |
|
awsResourceProperties: { |
|
Properties: { type: 'object' }, |
|
CreationPolicy: { type: 'object' }, |
|
@@ -356,22 +339,89 @@ class AwsProvider { |
|
awsResourceTags: { |
|
type: 'object', |
|
patternProperties: { |
|
- '^(?!aws:)[\\w./=+-]{1,128}$': { |
|
+ '^(?!aws:)[\\w./=+:-]{1,128}$': { |
|
type: 'string', |
|
- pattern: '^(?!aws:)[\\w./=+-]*$', |
|
maxLength: 256, |
|
}, |
|
}, |
|
additionalProperties: false, |
|
}, |
|
- awsLogGroupName: { |
|
+ awsS3BucketName: { |
|
type: 'string', |
|
- pattern: '^[/#A-Za-z0-9-_.]+$', |
|
+ // pattern sourced from https://stackoverflow.com/questions/50480924/regex-for-s3-bucket-name |
|
+ pattern: |
|
+ '(?!^(\\d{1,3}\\.){3}\\d{1,3}$)(^(([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])\\.)*([a-z0-9]|[a-z0-9][a-z0-9-]*[a-z0-9])$)', |
|
+ minLength: 3, |
|
+ maxLength: 63, |
|
}, |
|
}, |
|
provider: { |
|
properties: { |
|
- apiGateway: { type: 'object', properties: { websocketApiId: { type: 'string' } } }, |
|
+ apiGateway: { |
|
+ type: 'object', |
|
+ properties: { |
|
+ apiKeySourceType: { |
|
+ anyOf: ['HEADER', 'AUTHORIZER'].map(caseInsensitive), |
|
+ }, |
|
+ binaryMediaTypes: { |
|
+ type: 'array', |
|
+ items: { type: 'string', pattern: '^\\S+\\/\\S+$' }, |
|
+ }, |
|
+ description: { type: 'string' }, |
|
+ metrics: { type: 'boolean' }, |
|
+ minimumCompressionSize: { type: 'integer', minimum: 0, maximum: 10485760 }, |
|
+ restApiId: { $ref: '#/definitions/awsCfInstruction' }, |
|
+ restApiResources: { |
|
+ anyOf: [ |
|
+ { |
|
+ type: 'array', |
|
+ items: { |
|
+ type: 'object', |
|
+ properties: { |
|
+ path: { type: 'string' }, |
|
+ resourceId: { type: 'string' }, |
|
+ }, |
|
+ required: [], |
|
+ additionalProperties: false, |
|
+ }, |
|
+ }, |
|
+ { type: 'object' }, |
|
+ ], |
|
+ }, |
|
+ restApiRootResourceId: { $ref: '#/definitions/awsCfInstruction' }, |
|
+ shouldStartNameWithService: { const: true }, |
|
+ websocketApiId: { $ref: '#/definitions/awsCfInstruction' }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ }, |
|
+ apiKeys: { |
|
+ type: 'array', |
|
+ items: { |
|
+ anyOf: [ |
|
+ { type: 'string' }, |
|
+ { |
|
+ type: 'object', |
|
+ properties: { |
|
+ name: { type: 'string' }, |
|
+ value: { type: 'string' }, |
|
+ description: { type: 'string' }, |
|
+ customerId: { type: 'string' }, |
|
+ }, |
|
+ anyOf: [{ required: ['name'] }, { required: ['value'] }], |
|
+ additionalProperties: false, |
|
+ }, |
|
+ { |
|
+ type: 'object', |
|
+ maxProperties: 1, |
|
+ additionalProperties: { |
|
+ type: 'array', |
|
+ items: { type: 'string' }, |
|
+ }, |
|
+ }, |
|
+ ], |
|
+ }, |
|
+ }, |
|
+ apiName: { type: 'string' }, |
|
alb: { |
|
type: 'object', |
|
properties: { |
|
@@ -379,12 +429,37 @@ class AwsProvider { |
|
authorizers: { |
|
type: 'object', |
|
additionalProperties: { |
|
- oneOf: [oidcAlbAuthorizer, cognitoAlbAuthorizer], |
|
+ anyOf: [oidcAlbAuthorizer, cognitoAlbAuthorizer], |
|
}, |
|
}, |
|
}, |
|
additionalProperties: false, |
|
}, |
|
+ cfnRole: { $ref: '#/definitions/awsArn' }, |
|
+ deploymentBucket: { |
|
+ anyOf: [ |
|
+ { $ref: '#/definitions/awsS3BucketName' }, |
|
+ { |
|
+ type: 'object', |
|
+ properties: { |
|
+ name: { $ref: '#/definitions/awsS3BucketName' }, |
|
+ maxPreviousDeploymentArtifacts: { type: 'integer', minimum: 0 }, |
|
+ serverSideEncryption: { enum: ['AES256', 'aws:kms'] }, |
|
+ sseCustomerAlgorithim: { type: 'string' }, |
|
+ sseCustomerKey: { type: 'string' }, |
|
+ sseCustomerKeyMD5: { type: 'string' }, |
|
+ sseKMSKeyId: { type: 'string' }, |
|
+ tags: { $ref: '#/definitions/awsResourceTags' }, |
|
+ blockPublicAccess: { type: 'boolean' }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ }, |
|
+ ], |
|
+ }, |
|
+ deploymentPrefix: { type: 'string' }, |
|
+ endpointType: { |
|
+ anyOf: ['REGIONAL', 'EDGE', 'PRIVATE'].map(caseInsensitive), |
|
+ }, |
|
environment: { $ref: '#/definitions/awsLambdaEnvironment' }, |
|
httpApi: { |
|
type: 'object', |
|
@@ -398,12 +473,11 @@ class AwsProvider { |
|
identitySource: { $ref: '#/definitions/awsCfInstruction' }, |
|
issuerUrl: { $ref: '#/definitions/awsCfInstruction' }, |
|
audience: { |
|
- oneOf: [ |
|
+ anyOf: [ |
|
{ $ref: '#/definitions/awsCfInstruction' }, |
|
{ |
|
type: 'array', |
|
items: { $ref: '#/definitions/awsCfInstruction' }, |
|
- additionalItems: false, |
|
}, |
|
], |
|
}, |
|
@@ -413,24 +487,16 @@ class AwsProvider { |
|
}, |
|
}, |
|
cors: { |
|
- oneOf: [ |
|
+ anyOf: [ |
|
{ type: 'boolean' }, |
|
{ |
|
type: 'object', |
|
properties: { |
|
allowCredentials: { type: 'boolean' }, |
|
- allowedHeaders: { |
|
- oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], |
|
- }, |
|
- allowedMethods: { |
|
- oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], |
|
- }, |
|
- allowedOrigins: { |
|
- oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], |
|
- }, |
|
- exposedResponseHeaders: { |
|
- oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], |
|
- }, |
|
+ allowedHeaders: { type: 'array', items: { type: 'string' } }, |
|
+ allowedMethods: { type: 'array', items: { type: 'string' } }, |
|
+ allowedOrigins: { type: 'array', items: { type: 'string' } }, |
|
+ exposedResponseHeaders: { type: 'array', items: { type: 'string' } }, |
|
maxAge: { type: 'integer', minimum: 0 }, |
|
}, |
|
additionalProperties: false, |
|
@@ -438,7 +504,7 @@ class AwsProvider { |
|
], |
|
}, |
|
id: { |
|
- oneOf: [ |
|
+ anyOf: [ |
|
{ type: 'string' }, |
|
{ $ref: '#/definitions/awsCfImportLocallyResolvable' }, |
|
], |
|
@@ -448,13 +514,19 @@ class AwsProvider { |
|
}, |
|
additionalProperties: false, |
|
}, |
|
+ iamManagedPolicies: { type: 'array', items: { $ref: '#/definitions/awsArnString' } }, |
|
+ iamRoleStatements: { $ref: '#/definitions/awsIamPolicyStatements' }, |
|
kmsKeyArn: { $ref: '#/definitions/awsKmsArn' }, |
|
layers: { $ref: '#/definitions/awsLambdaLayers' }, |
|
+ logRetentionInDays: { |
|
+ enum: [1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653], |
|
+ }, |
|
logs: { |
|
type: 'object', |
|
properties: { |
|
+ frameworkLambda: { type: 'boolean' }, |
|
httpApi: { |
|
- oneOf: [ |
|
+ anyOf: [ |
|
{ type: 'boolean' }, |
|
{ |
|
type: 'object', |
|
@@ -465,8 +537,26 @@ class AwsProvider { |
|
}, |
|
], |
|
}, |
|
+ restApi: { |
|
+ anyOf: [ |
|
+ { type: 'boolean' }, |
|
+ { |
|
+ type: 'object', |
|
+ properties: { |
|
+ accessLogging: { type: 'boolean' }, |
|
+ executionLogging: { type: 'boolean' }, |
|
+ format: { type: 'string' }, |
|
+ fullExecutionData: { type: 'boolean' }, |
|
+ level: { enum: ['INFO', 'ERROR'] }, |
|
+ role: { $ref: '#/definitions/awsArn' }, |
|
+ roleManagedExternally: { type: 'boolean' }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ }, |
|
+ ], |
|
+ }, |
|
websocket: { |
|
- oneOf: [ |
|
+ anyOf: [ |
|
{ type: 'boolean' }, |
|
{ |
|
type: 'object', |
|
@@ -480,14 +570,81 @@ class AwsProvider { |
|
}, |
|
}, |
|
memorySize: { $ref: '#/definitions/awsLambdaMemorySize' }, |
|
- resourcePolicy: { |
|
+ notificationArns: { type: 'array', items: { $ref: '#/definitions/awsArnString' } }, |
|
+ profile: { type: 'string' }, |
|
+ region: { |
|
+ enum: [ |
|
+ 'us-east-1', |
|
+ 'us-east-2', |
|
+ 'us-gov-east-1', |
|
+ 'us-gov-west-1', |
|
+ 'us-west-1', |
|
+ 'us-west-2', |
|
+ 'af-south-1', |
|
+ 'ap-east-1', |
|
+ 'ap-northeast-1', |
|
+ 'ap-northeast-2', |
|
+ 'ap-northeast-3', |
|
+ 'ap-south-1', |
|
+ 'ap-southeast-1', |
|
+ 'ap-southeast-2', |
|
+ 'ca-central-1', |
|
+ 'cn-north-1', |
|
+ 'cn-northwest-1', |
|
+ 'eu-central-1', |
|
+ 'eu-north-1', |
|
+ 'eu-south-1', |
|
+ 'eu-west-1', |
|
+ 'eu-west-2', |
|
+ 'eu-west-3', |
|
+ 'me-south-1', |
|
+ 'sa-east-1', |
|
+ ], |
|
+ }, |
|
+ resourcePolicy: { $ref: '#/definitions/awsIamPolicyStatements' }, |
|
+ role: { $ref: '#/definitions/awsLambdaRole' }, |
|
+ rolePermissionsBoundary: { $ref: '#/definitions/awsArnString' }, |
|
+ rollbackConfiguration: { |
|
+ type: 'object', |
|
+ properties: { |
|
+ RollbackTriggers: { |
|
+ type: 'array', |
|
+ items: { |
|
+ type: 'object', |
|
+ properties: { |
|
+ Arn: { $ref: '#/definitions/awsArnString' }, |
|
+ Type: { const: 'AWS::CloudWatch::Alarm' }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ required: ['Arn', 'Type'], |
|
+ }, |
|
+ }, |
|
+ MonitoringTimeInMinutes: { type: 'integer', minimum: 0 }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ }, |
|
+ runtime: { $ref: '#/definitions/awsLambdaRuntime' }, |
|
+ stage: { type: 'string' }, |
|
+ stackName: { |
|
+ type: 'string', |
|
+ pattern: '^[a-zA-Z][a-zA-Z0-9-]*$', |
|
+ maxLength: 128, |
|
+ }, |
|
+ stackParameters: { |
|
type: 'array', |
|
items: { |
|
type: 'object', |
|
+ properties: { |
|
+ ParameterKey: { type: 'string' }, |
|
+ ParameterValue: { type: 'string' }, |
|
+ UsePreviousValue: { type: 'boolean' }, |
|
+ ResolvedValue: { type: 'string' }, |
|
+ }, |
|
+ additionalProperties: false, |
|
}, |
|
}, |
|
- role: { $ref: '#/definitions/awsLambdaRole' }, |
|
- runtime: { $ref: '#/definitions/awsLambdaRuntime' }, |
|
+ stackPolicy: { $ref: '#/definitions/awsIamPolicyStatements' }, |
|
+ stackTags: { $ref: '#/definitions/awsResourceTags' }, |
|
tags: { $ref: '#/definitions/awsResourceTags' }, |
|
timeout: { $ref: '#/definitions/awsLambdaTimeout' }, |
|
tracing: { |
|
@@ -498,7 +655,24 @@ class AwsProvider { |
|
}, |
|
additionalProperties: false, |
|
}, |
|
+ usagePlan: { |
|
+ anyOf: [ |
|
+ apiGatewayUsagePlan, |
|
+ { |
|
+ type: 'array', |
|
+ items: { |
|
+ type: 'object', |
|
+ additionalProperties: apiGatewayUsagePlan, |
|
+ maxProperties: 1, |
|
+ }, |
|
+ }, |
|
+ ], |
|
+ }, |
|
vpc: { $ref: '#/definitions/awsLambdaVpcConfig' }, |
|
+ vpcEndpointIds: { |
|
+ type: 'array', |
|
+ items: { $ref: '#/definitions/awsCfInstruction' }, |
|
+ }, |
|
versionFunctions: { $ref: '#/definitions/awsLambdaVersionning' }, |
|
websocketsApiName: { type: 'string' }, |
|
websocketsApiRouteSelectionExpression: { type: 'string' }, |
|
@@ -510,13 +684,21 @@ class AwsProvider { |
|
condition: { $ref: '#/definitions/awsResourceCondition' }, |
|
dependsOn: { $ref: '#/definitions/awsResourceDependsOn' }, |
|
description: { type: 'string', maxLength: 256 }, |
|
+ destinations: { |
|
+ type: 'object', |
|
+ properties: { |
|
+ onSuccess: { type: 'string', minLength: 1 }, |
|
+ onFailure: { type: 'string', minLength: 1 }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ }, |
|
disableLogs: { type: 'boolean' }, |
|
environment: { $ref: '#/definitions/awsLambdaEnvironment' }, |
|
fileSystemConfig: { |
|
type: 'object', |
|
properties: { |
|
arn: { |
|
- oneOf: [ |
|
+ anyOf: [ |
|
{ |
|
type: 'string', |
|
pattern: |
|
@@ -535,9 +717,11 @@ class AwsProvider { |
|
handler: { type: 'string' }, |
|
kmsKeyArn: { $ref: '#/definitions/awsKmsArn' }, |
|
layers: { $ref: '#/definitions/awsLambdaLayers' }, |
|
+ maximumEventAge: { type: 'integer', minimum: 60, maximum: 21600 }, |
|
+ maximumRetryAttempts: { type: 'integer', minimum: 0, maximum: 2 }, |
|
memorySize: { $ref: '#/definitions/awsLambdaMemorySize' }, |
|
onError: { |
|
- oneOf: [ |
|
+ anyOf: [ |
|
{ type: 'string', pattern: '^arn:aws[a-z-]*:sns' }, |
|
{ $ref: '#/definitions/awsCfFunction' }, |
|
], |
|
@@ -564,6 +748,47 @@ class AwsProvider { |
|
}, |
|
additionalProperties: false, |
|
}, |
|
+ layers: { |
|
+ type: 'object', |
|
+ additionalProperties: { |
|
+ type: 'object', |
|
+ properties: { |
|
+ allowedAccounts: { |
|
+ type: 'array', |
|
+ items: { |
|
+ type: 'string', |
|
+ pattern: '^(\\d{12}|\\*|arn:(aws[a-zA-Z-]*):iam::\\d{12}:root)$', |
|
+ }, |
|
+ }, |
|
+ compatibleRuntimes: { |
|
+ type: 'array', |
|
+ items: { $ref: '#/definitions/awsLambdaRuntime' }, |
|
+ maxItems: 5, |
|
+ }, |
|
+ description: { type: 'string', maxLength: 256 }, |
|
+ licenseInfo: { type: 'string', maxLength: 512 }, |
|
+ name: { |
|
+ type: 'string', |
|
+ minLength: 1, |
|
+ maxLength: 140, |
|
+ pattern: |
|
+ '^((arn:[a-zA-Z0-9-]+:lambda:[a-zA-Z0-9-]+:\\d{12}:layer:[a-zA-Z0-9-_]+)|[a-zA-Z0-9-_]+)$', |
|
+ }, |
|
+ package: { |
|
+ type: 'object', |
|
+ properties: { |
|
+ artifact: { type: 'string' }, |
|
+ exclude: { type: 'array', items: { type: 'string' } }, |
|
+ include: { type: 'array', items: { type: 'string' } }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ }, |
|
+ path: { type: 'string' }, |
|
+ retain: { type: 'boolean' }, |
|
+ }, |
|
+ additionalProperties: false, |
|
+ }, |
|
+ }, |
|
resources: { |
|
properties: { |
|
AWSTemplateFormatVersion: { |
|
@@ -597,6 +822,17 @@ class AwsProvider { |
|
// See also https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html |
|
Resources: { |
|
type: 'object', |
|
+ properties: { |
|
+ 'Fn::Transform': { |
|
+ type: 'object', |
|
+ properties: { |
|
+ Name: { type: 'string' }, |
|
+ Parameters: { type: 'object' }, |
|
+ }, |
|
+ required: ['Name'], |
|
+ additionalProperties: false, |
|
+ }, |
|
+ }, |
|
patternProperties: { |
|
'^[a-zA-Z0-9]{1,255}$': { |
|
type: 'object', |
|
@@ -718,6 +954,10 @@ class AwsProvider { |
|
} |
|
} |
|
|
|
+ static getProviderName() { |
|
+ return constants.providerName; |
|
+ } |
|
+ |
|
/** |
|
* Execute an AWS request by calling the AWS SDK |
|
* @param {string} service - Service name |
|
@@ -729,14 +969,13 @@ class AwsProvider { |
|
*/ |
|
request(service, method, params, options) { |
|
const that = this; |
|
- const credentials = Object.assign({}, that.getCredentials()); |
|
- credentials.region = this.getRegion(); |
|
// Make sure options is an object (honors wrong calls of request) |
|
const requestOptions = _.isObject(options) ? options : {}; |
|
const shouldCache = _.get(requestOptions, 'useCache', false); |
|
- const paramsWithRegion = _.merge({}, params, { |
|
- region: _.get(options, 'region'), |
|
- }); |
|
+ const { credentials } = that.getCredentials(); |
|
+ const region = requestOptions.region || that.getRegion(); |
|
+ const serviceParams = { credentials, region }; |
|
+ const paramsWithRegion = { ...params, region }; |
|
const paramsHash = objectHash.sha1(paramsWithRegion); |
|
const BASE_BACKOFF = 5000; |
|
const persistentRequest = f => |
|
@@ -783,8 +1022,20 @@ class AwsProvider { |
|
} |
|
|
|
// Support S3 Transfer Acceleration |
|
- if (this.canUseS3TransferAcceleration(service, method)) { |
|
- this.enableS3TransferAcceleration(credentials); |
|
+ if (service === 'S3') { |
|
+ // Support S3 Transfer Acceleration |
|
+ if (this.canUseS3TransferAcceleration(service, method)) { |
|
+ this.enableS3TransferAcceleration(serviceParams); |
|
+ } |
|
+ |
|
+ const deploymentBucketObject = this.serverless.service.provider.deploymentBucketObject; |
|
+ if ( |
|
+ deploymentBucketObject && |
|
+ deploymentBucketObject.serverSideEncryption && |
|
+ deploymentBucketObject.serverSideEncryption === 'aws:kms' |
|
+ ) { |
|
+ serviceParams.signatureVersion = 'v4'; |
|
+ } |
|
} |
|
|
|
if (shouldCache) { |
|
@@ -796,11 +1047,8 @@ class AwsProvider { |
|
|
|
const request = this.requestQueue.add(() => |
|
persistentRequest(() => { |
|
- if (options && options.region) { |
|
- credentials.region = options.region; |
|
- } |
|
const Service = _.get(that.sdk, service); |
|
- const awsService = new Service(credentials); |
|
+ const awsService = new Service(serviceParams); |
|
const req = awsService[method](params); |
|
|
|
// TODO: Add listeners, put Debug statements here... |
|
@@ -812,7 +1060,7 @@ class AwsProvider { |
|
req.send(cb); |
|
}); |
|
return promise.catch(err => { |
|
- let message = err.message != null ? err.message : err.code; |
|
+ let message = err.message != null ? err.message : String(err.code); |
|
if (message.startsWith('Missing credentials in config')) { |
|
// Credentials error |
|
// If failed at last resort (EC2 Metadata check) expose a meaningful error |
|
@@ -865,49 +1113,37 @@ class AwsProvider { |
|
/** |
|
* Fetch credentials directly or using a profile from serverless yml configuration or from the |
|
* well known environment variables |
|
- * @returns {{region: *}} |
|
+ * If using outside of AWS, you are expected to use credentials.get/Promise to ensure they have |
|
+ * not expired (https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Credentials.html#getPromise-property) |
|
+ * @returns {credentials: AWS.Credentials, region: string} |
|
*/ |
|
getCredentials() { |
|
if (this.cachedCredentials) { |
|
// We have already created the credentials object once, so return it. |
|
return this.cachedCredentials; |
|
} |
|
- const result = {}; |
|
- const stageUpper = this.getStage() ? this.getStage().toUpperCase() : null; |
|
- |
|
- // add specified credentials, overriding with more specific declarations |
|
- try { |
|
- impl.addProfileCredentials(result, 'default'); |
|
- } catch (err) { |
|
- if (err.message !== 'Profile default does not exist') { |
|
- throw err; |
|
- } |
|
- } |
|
- impl.addCredentials(result, this.serverless.service.provider.credentials); // config creds |
|
+ const credentials = new AwsCredentials(); |
|
+ const stageUpperCase = this.getStage() ? this.getStage().toUpperCase() : null; |
|
+ |
|
+ // add credentials to resolve from most-to-least specific |
|
+ credentials.addProfile(this.options['aws-profile']); // CLI option profile |
|
+ |
|
+ credentials.addProfile(process.env[`AWS_${stageUpperCase}_PROFILE`]); // stage specific creds |
|
+ credentials.addEnvironment(`AWS_${stageUpperCase}`); |
|
+ |
|
+ credentials.addProfile(process.env.AWS_PROFILE); // creds for all stages |
|
+ credentials.addEnvironment('AWS'); |
|
if (this.serverless.service.provider.profile && !this.options['aws-profile']) { |
|
// config profile |
|
- impl.addProfileCredentials(result, this.serverless.service.provider.profile); |
|
- } |
|
- impl.addEnvironmentCredentials(result, 'AWS'); // creds for all stages |
|
- impl.addEnvironmentProfile(result, 'AWS'); |
|
- impl.addEnvironmentCredentials(result, `AWS_${stageUpper}`); // stage specific creds |
|
- impl.addEnvironmentProfile(result, `AWS_${stageUpper}`); |
|
- if (this.options['aws-profile']) { |
|
- impl.addProfileCredentials(result, this.options['aws-profile']); // CLI option profile |
|
- } |
|
- |
|
- const deploymentBucketObject = this.serverless.service.provider.deploymentBucketObject; |
|
- if ( |
|
- deploymentBucketObject && |
|
- deploymentBucketObject.serverSideEncryption && |
|
- deploymentBucketObject.serverSideEncryption === 'aws:kms' |
|
- ) { |
|
- result.signatureVersion = 'v4'; |
|
+ credentials.addProfile(this.serverless.service.provider.profile); |
|
} |
|
+ credentials.addConfig(this.serverless.service.provider.credentials); // config creds |
|
+ credentials.addProfile(process.env.AWS_DEFAULT_PROFILE || 'default'); |
|
|
|
// Store the credentials to avoid creating them again (messes up MFA). |
|
- this.cachedCredentials = result; |
|
- return result; |
|
+ const region = this.getRegion(); |
|
+ this.cachedCredentials = { credentials, region }; |
|
+ return this.cachedCredentials; |
|
} |
|
|
|
canUseS3TransferAcceleration(service, method) { |
|
@@ -940,9 +1176,9 @@ class AwsProvider { |
|
delete this.options['aws-s3-accelerate']; |
|
} |
|
|
|
- enableS3TransferAcceleration(credentials) { |
|
+ enableS3TransferAcceleration(serviceParams) { |
|
this.serverless.cli.log('Using S3 Transfer Acceleration Endpoint...'); |
|
- credentials.useAccelerateEndpoint = true; // eslint-disable-line no-param-reassign |
|
+ serviceParams.useAccelerateEndpoint = true; // eslint-disable-line no-param-reassign |
|
} |
|
|
|
getValues(source, objectPaths) { |
|
@@ -951,6 +1187,7 @@ class AwsProvider { |
|
value: _.get(source, objectPath.join('.')), |
|
})); |
|
} |
|
+ |
|
firstValue(values) { |
|
return values.reduce((result, current) => { |
|
return result.value ? result : current; |
|
@@ -965,6 +1202,7 @@ class AwsProvider { |
|
]); |
|
return this.firstValue(values); |
|
} |
|
+ |
|
getRegion() { |
|
const defaultRegion = 'us-east-1'; |
|
const regionSourceValue = this.getRegionSourceValue(); |
|
@@ -975,6 +1213,7 @@ class AwsProvider { |
|
const values = this.getValues(this, [['serverless', 'service', 'provider', 'runtime']]); |
|
return this.firstValue(values); |
|
} |
|
+ |
|
getRuntime(runtime) { |
|
const defaultRuntime = 'nodejs12.x'; |
|
const runtimeSourceValue = this.getRuntimeSourceValue(); |
|
@@ -991,6 +1230,7 @@ class AwsProvider { |
|
const firstVal = this.firstValue(values); |
|
return firstVal ? firstVal.value : null; |
|
} |
|
+ |
|
getProfile() { |
|
return this.getProfileSourceValue(); |
|
} |
|
@@ -1077,17 +1317,7 @@ class AwsProvider { |
|
} |
|
|
|
getLogRetentionInDays() { |
|
- if (!this.serverless.service.provider.logRetentionInDays) { |
|
- return null; |
|
- } |
|
- const rawRetentionInDays = this.serverless.service.provider.logRetentionInDays; |
|
- const retentionInDays = parseInt(rawRetentionInDays, 10); |
|
- if (retentionInDays > 0) { |
|
- return retentionInDays; |
|
- } |
|
- |
|
- const errorMessage = `logRetentionInDays should be an integer over 0 but ${rawRetentionInDays}`; |
|
- throw new this.serverless.classes.Error(errorMessage); |
|
+ return this.serverless.service.provider.logRetentionInDays; |
|
} |
|
|
|
getStageSourceValue() { |
|
@@ -1098,6 +1328,7 @@ class AwsProvider { |
|
]); |
|
return this.firstValue(values); |
|
} |
|
+ |
|
getStage() { |
|
const defaultStage = 'dev'; |
|
const stageSourceValue = this.getStageSourceValue(); |
|
@@ -1174,10 +1405,6 @@ class AwsProvider { |
|
return this.serverless.service.provider.apiGateway.restApiResources; |
|
} |
|
|
|
- if (typeof this.serverless.service.provider.apiGateway.restApiResources !== 'object') { |
|
- throw new Error('REST API resource must be an array of object'); |
|
- } |
|
- |
|
return Object.keys(this.serverless.service.provider.apiGateway.restApiResources).map(key => ({ |
|
path: key, |
|
resourceId: this.serverless.service.provider.apiGateway.restApiResources[key], |
|
diff --git a/node_modules/serverless/lib/plugins/aws/provider/awsProvider.test.js b/node_modules/serverless/lib/plugins/aws/provider/awsProvider.test.js |
|
index 03d44a1..34198a5 100644 |
|
--- a/node_modules/serverless/lib/plugins/aws/provider/awsProvider.test.js |
|
+++ b/node_modules/serverless/lib/plugins/aws/provider/awsProvider.test.js |
|
@@ -59,7 +59,7 @@ describe('AwsProvider', () => { |
|
}); |
|
|
|
it('should have no AWS logger', () => { |
|
- expect(awsProvider.sdk.config.logger).to.be.null; |
|
+ expect(awsProvider.sdk.config.logger == null).to.be.true; |
|
}); |
|
|
|
it('should set AWS logger', () => { |
|
@@ -699,6 +699,43 @@ describe('AwsProvider', () => { |
|
}); |
|
}); |
|
|
|
+ it('should set the signatureVersion to v4 if the serverSideEncryption is aws:kms', () => { |
|
+ // mocking S3 for testing |
|
+ class FakeS3 { |
|
+ constructor(config) { |
|
+ this.config = config; |
|
+ } |
|
+ |
|
+ putObject() { |
|
+ return { |
|
+ send: cb => cb(null, { config: this.config }), |
|
+ }; |
|
+ } |
|
+ } |
|
+ awsProvider.sdk = { |
|
+ S3: FakeS3, |
|
+ }; |
|
+ awsProvider.serverless.service.environment = { |
|
+ vars: {}, |
|
+ stages: { |
|
+ dev: { |
|
+ vars: { |
|
+ profile: 'default', |
|
+ }, |
|
+ regions: {}, |
|
+ }, |
|
+ }, |
|
+ }; |
|
+ |
|
+ awsProvider.serverless.service.provider.deploymentBucketObject = { |
|
+ serverSideEncryption: 'aws:kms', |
|
+ }; |
|
+ |
|
+ return awsProvider.request('S3', 'putObject', {}).then(data => { |
|
+ expect(data.config.signatureVersion).to.equal('v4'); |
|
+ }); |
|
+ }); |
|
+ |
|
describe('using the request cache', () => { |
|
it('should call correct aws method', () => { |
|
// mocking CF for testing |
|
@@ -836,25 +873,38 @@ describe('AwsProvider', () => { |
|
}); |
|
|
|
describe('STS tokens', () => { |
|
- let newAwsProvider; |
|
- let originalProviderProfile; |
|
- let originalEnvironmentVariables; |
|
const relevantEnvironment = { |
|
AWS_SHARED_CREDENTIALS_FILE: getTmpFilePath('credentials'), |
|
}; |
|
|
|
+ let newAwsProvider; |
|
+ |
|
+ let originalProviderProfile; |
|
+ let originalEnvironmentVariables; |
|
+ |
|
beforeEach(() => { |
|
originalProviderProfile = serverless.service.provider.profile; |
|
originalEnvironmentVariables = replaceEnv(relevantEnvironment); |
|
+ // fake an asynchronous credential process: |
|
+ const credProcessCode = ` |
|
+ const credentials = { |
|
+ Version: 1, |
|
+ AccessKeyId: 'async_access_key_id', |
|
+ SessionToken: 'async_session_token', |
|
+ } |
|
+ console.log(JSON.stringify(credentials)); |
|
+ `; |
|
+ const nodePath = process.execPath; |
|
+ const credProcessPath = getTmpFilePath('script.js'); |
|
+ serverless.utils.writeFileSync(credProcessPath, credProcessCode); |
|
serverless.utils.writeFileSync( |
|
relevantEnvironment.AWS_SHARED_CREDENTIALS_FILE, |
|
- '[default]\n' + |
|
- 'aws_access_key_id = 1111\n' + |
|
- 'aws_secret_access_key = 22222\n' + |
|
+ '[no_default]\n' + |
|
+ 'aws_access_key_id = default_access_key_id\n' + |
|
+ 'aws_secret_access_key = default_aws_secret_access_key\n' + |
|
'\n' + |
|
- '[async]\n' + |
|
- 'role_arn = arn:123\n' + |
|
- 'source_profile = default' |
|
+ '[async_profile]\n' + |
|
+ `credential_process = ${nodePath} ${credProcessPath}\n` |
|
); |
|
newAwsProvider = new AwsProvider(serverless, options); |
|
}); |
|
@@ -864,17 +914,21 @@ describe('AwsProvider', () => { |
|
serverless.service.provider.profile = originalProviderProfile; |
|
}); |
|
|
|
- it('should retain reference to STS tokens when updated via SDK', () => { |
|
- const expectedToken = '123'; |
|
+ it('should retain reference to STS tokens when updated via SDK', async () => { |
|
+ const expectedToken = '_sts_refreshed_access_token_'; |
|
+ |
|
+ serverless.service.provider.profile = 'async_profile'; |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
|
|
- serverless.service.provider.profile = 'async'; |
|
- const startToken = newAwsProvider.getCredentials().credentials.sessionToken; |
|
+ const startToken = credentials.sessionToken; |
|
+ expect(startToken).to.equal('async_session_token'); |
|
expect(startToken).to.not.equal(expectedToken); |
|
|
|
class FakeCloudFormation { |
|
- constructor(credentials) { |
|
+ constructor(config) { |
|
// Not sure where the the SDK resolves the STS, so for the test it's here |
|
- this.credentials = credentials; |
|
+ this.credentials = config; |
|
this.credentials.credentials.sessionToken = expectedToken; |
|
} |
|
|
|
@@ -933,11 +987,35 @@ describe('AwsProvider', () => { |
|
region: 'testregion', |
|
}; |
|
const fakeCredentials = { |
|
- accessKeyId: 'AABBCCDDEEFF', |
|
- secretAccessKey: '0123456789876543', |
|
- sessionToken: '981237917391273918273918723987129837129873', |
|
- roleArn: 'a:role:arn', |
|
- sourceProfile: 'notDefaultTemporary', |
|
+ yaml: { |
|
+ accessKeyId: 'yaml_aws_access_key_id', |
|
+ secretAccessKey: 'yaml_aws_secret_access_key', |
|
+ sessionToken: 'yaml_aws_session_token', |
|
+ }, |
|
+ default: { |
|
+ accessKeyId: 'default_access_key_id', |
|
+ secretAccessKey: 'default_aws_secret_access_key', |
|
+ }, |
|
+ otherDefault: { |
|
+ name: 'other_default', |
|
+ accessKeyId: 'other_default_access_key_id', |
|
+ secretAccessKey: 'other_default_aws_secret_access_key', |
|
+ }, |
|
+ aProfile: { |
|
+ name: 'a_profile', |
|
+ accessKeyId: 'a_profile_access_key_id', |
|
+ secretAccessKey: 'a_profile_aws_secret_access_key', |
|
+ }, |
|
+ anotherProfile: { |
|
+ name: 'another_profile', |
|
+ accessKeyId: 'another_profile_access_key_id', |
|
+ secretAccessKey: 'another_profile_aws_secret_access_key', |
|
+ }, |
|
+ asyncProfile: { |
|
+ name: 'async_profile', |
|
+ accessKeyId: 'async_access_key_id', |
|
+ sessionToken: 'async_session_token', |
|
+ }, |
|
}; |
|
|
|
let originalProviderCredentials; |
|
@@ -948,21 +1026,31 @@ describe('AwsProvider', () => { |
|
originalProviderCredentials = serverless.service.provider.credentials; |
|
originalProviderProfile = serverless.service.provider.profile; |
|
originalEnvironmentVariables = replaceEnv(relevantEnvironment); |
|
+ // fake a credential process provider |
|
+ const credProcessCode = ` |
|
+ const credentials = { |
|
+ Version: 1, |
|
+ AccessKeyId: '${fakeCredentials.asyncProfile.accessKeyId}', |
|
+ SessionToken: '${fakeCredentials.asyncProfile.sessionToken}', |
|
+ } |
|
+ console.log(JSON.stringify(credentials)); |
|
+ `; |
|
+ const nodePath = process.execPath; |
|
+ const credProcessPath = getTmpFilePath('script.js'); |
|
+ serverless.utils.writeFileSync(credProcessPath, credProcessCode); |
|
// make temporary credentials file |
|
serverless.utils.writeFileSync( |
|
relevantEnvironment.AWS_SHARED_CREDENTIALS_FILE, |
|
- '[notDefault]\n' + |
|
- `aws_access_key_id = ${fakeCredentials.accessKeyId}\n` + |
|
- `aws_secret_access_key = ${fakeCredentials.secretAccessKey}\n` + |
|
+ `[${fakeCredentials.aProfile.name}]\n` + |
|
+ `aws_access_key_id = ${fakeCredentials.aProfile.accessKeyId}\n` + |
|
+ `aws_secret_access_key = ${fakeCredentials.aProfile.secretAccessKey}\n` + |
|
'\n' + |
|
- '[notDefaultTemporary]\n' + |
|
- `aws_access_key_id = ${fakeCredentials.accessKeyId}\n` + |
|
- `aws_secret_access_key = ${fakeCredentials.secretAccessKey}\n` + |
|
- `aws_session_token = ${fakeCredentials.sessionToken}\n` + |
|
+ `[${fakeCredentials.anotherProfile.name}]\n` + |
|
+ `aws_access_key_id = ${fakeCredentials.anotherProfile.accessKeyId}\n` + |
|
+ `aws_secret_access_key = ${fakeCredentials.anotherProfile.secretAccessKey}\n` + |
|
'\n' + |
|
- '[notDefaultAsync]\n' + |
|
- `role_arn = ${fakeCredentials.roleArn}\n` + |
|
- `source_profile = ${fakeCredentials.sourceProfile}\n` |
|
+ `[${fakeCredentials.asyncProfile.name}]\n` + |
|
+ `credential_process = ${nodePath} ${credProcessPath}\n` |
|
); |
|
newAwsProvider = new AwsProviderProxyquired(serverless, newOptions); |
|
}); |
|
@@ -973,92 +1061,100 @@ describe('AwsProvider', () => { |
|
serverless.service.provider.credentials = originalProviderCredentials; |
|
}); |
|
|
|
- it('should not set credentials if credentials is an empty object', () => { |
|
+ it('should not set credentials from provider if credentials is an empty object', async () => { |
|
serverless.service.provider.credentials = {}; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials).to.eql({}); |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise().catch(() => null); // no valid credentials |
|
+ expect(credentials.accessKeyId).to.be.undefined; |
|
+ expect(credentials.secretAccessKey).to.be.undefined; |
|
+ expect(credentials.sessionToken).to.be.undefined; |
|
}); |
|
|
|
- it('should not set credentials if credentials has undefined values', () => { |
|
+ it('should not set credentials from provider if credentials has undefined values', async () => { |
|
serverless.service.provider.credentials = { |
|
accessKeyId: undefined, |
|
secretAccessKey: undefined, |
|
sessionToken: undefined, |
|
}; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials).to.eql({}); |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise().catch(() => null); // no valid credentials |
|
+ expect(credentials.accessKeyId).to.be.undefined; |
|
+ expect(credentials.secretAccessKey).to.be.undefined; |
|
+ expect(credentials.sessionToken).to.be.undefined; |
|
}); |
|
|
|
- it('should not set credentials if credentials has empty string values', () => { |
|
+ it('should not set credentials from provider if credentials has empty string values', async () => { |
|
serverless.service.provider.credentials = { |
|
accessKeyId: '', |
|
secretAccessKey: '', |
|
sessionToken: '', |
|
}; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials).to.eql({}); |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise().catch(() => null); // no valid credentials |
|
+ expect(credentials.accessKeyId).to.be.undefined; |
|
+ expect(credentials.secretAccessKey).to.be.undefined; |
|
+ expect(credentials.sessionToken).to.be.undefined; |
|
}); |
|
|
|
- it('should get credentials from provider declared credentials', () => { |
|
+ it('should get credentials from provider declared secret access key credentials', async () => { |
|
serverless.service.provider.credentials = { |
|
- accessKeyId: 'accessKeyId', |
|
- secretAccessKey: 'secretAccessKey', |
|
- sessionToken: 'sessionToken', |
|
+ accessKeyId: fakeCredentials.yaml.accessKeyId, |
|
+ secretAccessKey: fakeCredentials.yaml.secretAccessKey, |
|
}; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.credentials).to.deep.eql(serverless.service.provider.credentials); |
|
- }); |
|
- |
|
- it('should load profile credentials from AWS_SHARED_CREDENTIALS_FILE', () => { |
|
- serverless.service.provider.profile = 'notDefault'; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.credentials.profile).to.equal(serverless.service.provider.profile); |
|
- expect(credentials.credentials.accessKeyId).to.equal(fakeCredentials.accessKeyId); |
|
- expect(credentials.credentials.secretAccessKey).to.equal(fakeCredentials.secretAccessKey); |
|
- expect(credentials.credentials.sessionToken).to.equal(undefined); |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(fakeCredentials.yaml.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.equal(fakeCredentials.yaml.secretAccessKey); |
|
+ expect(credentials.sessionToken).to.be.undefined; |
|
}); |
|
|
|
- it('should load async profiles properly', () => { |
|
- serverless.service.provider.profile = 'notDefaultAsync'; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.credentials.roleArn).to.equal(fakeCredentials.roleArn); |
|
- }); |
|
- |
|
- it('should throw an error if a non-existent profile is set', () => { |
|
- serverless.service.provider.profile = 'not-a-defined-profile'; |
|
- expect(() => { |
|
- newAwsProvider.getCredentials(); |
|
- }).to.throw(Error); |
|
+ it('should get credentials from provider declared session token credentials', async () => { |
|
+ serverless.service.provider.credentials = { |
|
+ accessKeyId: fakeCredentials.yaml.accessKeyId, |
|
+ sessionToken: fakeCredentials.yaml.sessionToken, |
|
+ }; |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(fakeCredentials.yaml.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.be.undefined; |
|
+ expect(credentials.sessionToken).to.equal(fakeCredentials.yaml.sessionToken); |
|
}); |
|
|
|
- it('should not set credentials if empty profile is set', () => { |
|
- serverless.service.provider.profile = ''; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials).to.eql({}); |
|
+ it('should return the region', async () => { |
|
+ serverless.service.provider.credentials = { |
|
+ accessKeyId: fakeCredentials.yaml.accessKeyId, |
|
+ secretAccessKey: fakeCredentials.yaml.secretAccessKey, |
|
+ }; |
|
+ const { region } = newAwsProvider.getCredentials(); |
|
+ expect(region).to.equal('testregion'); |
|
}); |
|
|
|
- it('should not set credentials if profile is not set', () => { |
|
- serverless.service.provider.profile = undefined; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials).to.eql({}); |
|
+ it('should load profile credentials from AWS_SHARED_CREDENTIALS_FILE', async () => { |
|
+ serverless.service.provider.profile = fakeCredentials.aProfile.name; |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(fakeCredentials.aProfile.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.equal(fakeCredentials.aProfile.secretAccessKey); |
|
}); |
|
|
|
- it('should not set credentials if empty profile is set', () => { |
|
- serverless.service.provider.profile = ''; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials).to.eql({}); |
|
+ it('should load async profiles properly', async () => { |
|
+ serverless.service.provider.profile = fakeCredentials.asyncProfile.name; |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(fakeCredentials.asyncProfile.accessKeyId); |
|
+ expect(credentials.sessionToken).to.equal(fakeCredentials.asyncProfile.sessionToken); |
|
}); |
|
|
|
- it('should get credentials from provider declared temporary profile', () => { |
|
- serverless.service.provider.profile = 'notDefaultTemporary'; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.credentials.profile).to.equal(serverless.service.provider.profile); |
|
- expect(credentials.credentials.accessKeyId).to.equal(fakeCredentials.accessKeyId); |
|
- expect(credentials.credentials.secretAccessKey).to.equal(fakeCredentials.secretAccessKey); |
|
- expect(credentials.credentials.sessionToken).to.equal(fakeCredentials.sessionToken); |
|
+ it('should throw an error if a non-existent profile is set', done => { |
|
+ serverless.service.provider.profile = 'not-a-defined-profile'; |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ credentials.get(err => { |
|
+ expect(err).not.to.be.undefined; |
|
+ done(); |
|
+ }); |
|
}); |
|
|
|
- it('should get credentials from environment declared for-all-stages credentials', () => { |
|
+ it('should get credentials from environment declared for-all-stages credentials', async () => { |
|
const testVal = { |
|
accessKeyId: 'accessKeyId', |
|
secretAccessKey: 'secretAccessKey', |
|
@@ -1068,76 +1164,117 @@ describe('AwsProvider', () => { |
|
process.env.AWS_SECRET_ACCESS_KEY = testVal.secretAccessKey; |
|
process.env.AWS_SESSION_TOKEN = testVal.sessionToken; |
|
|
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.credentials.accessKeyId).to.equal(testVal.accessKeyId); |
|
- expect(credentials.credentials.secretAccessKey).to.equal(testVal.secretAccessKey); |
|
- expect(credentials.credentials.sessionToken).to.equal(testVal.sessionToken); |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(testVal.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.equal(testVal.secretAccessKey); |
|
+ expect(credentials.sessionToken).to.equal(testVal.sessionToken); |
|
}); |
|
|
|
- it('should get credentials from environment declared stage specific credentials', () => { |
|
+ it('should get credentials from environment declared stage specific credentials', async () => { |
|
const testVal = { |
|
- accessKeyId: 'accessKeyId', |
|
- secretAccessKey: 'secretAccessKey', |
|
- sessionToken: 'sessionToken', |
|
+ accessKeyId: 'stageAccessKeyId', |
|
+ secretAccessKey: 'stageSecretAccessKey', |
|
+ sessionToken: 'stageSessionToken', |
|
}; |
|
process.env.AWS_TESTSTAGE_ACCESS_KEY_ID = testVal.accessKeyId; |
|
process.env.AWS_TESTSTAGE_SECRET_ACCESS_KEY = testVal.secretAccessKey; |
|
process.env.AWS_TESTSTAGE_SESSION_TOKEN = testVal.sessionToken; |
|
|
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.credentials.accessKeyId).to.equal(testVal.accessKeyId); |
|
- expect(credentials.credentials.secretAccessKey).to.equal(testVal.secretAccessKey); |
|
- expect(credentials.credentials.sessionToken).to.equal(testVal.sessionToken); |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(testVal.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.equal(testVal.secretAccessKey); |
|
+ expect(credentials.sessionToken).to.equal(testVal.sessionToken); |
|
}); |
|
|
|
- it('should get credentials from environment declared for-all-stages profile', () => { |
|
- process.env.AWS_PROFILE = 'notDefault'; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.credentials.profile).to.equal('notDefault'); |
|
+ it('should get credentials from environment declared for-all-stages profile', async () => { |
|
+ process.env.AWS_PROFILE = fakeCredentials.aProfile.name; |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(fakeCredentials.aProfile.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.equal(fakeCredentials.aProfile.secretAccessKey); |
|
}); |
|
|
|
- it('should get credentials from environment declared stage-specific profile', () => { |
|
- process.env.AWS_TESTSTAGE_PROFILE = 'notDefault'; |
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.credentials.profile).to.equal('notDefault'); |
|
+ it('should get credentials from environment declared from preferred stage-specific profile', async () => { |
|
+ process.env.AWS_PROFILE = fakeCredentials.anotherProfile.name; |
|
+ process.env.AWS_TESTSTAGE_PROFILE = fakeCredentials.aProfile.name; |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(fakeCredentials.aProfile.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.equal(fakeCredentials.aProfile.secretAccessKey); |
|
}); |
|
|
|
- it('should get credentials when profile is provied via --aws-profile option', () => { |
|
- process.env.AWS_PROFILE = 'notDefaultTemporary'; |
|
- newAwsProvider.options['aws-profile'] = 'notDefault'; |
|
+ it('should get credentials when profile is provided via --aws-profile option', async () => { |
|
+ process.env.AWS_PROFILE = fakeCredentials.anotherProfile.name; |
|
+ newAwsProvider.options['aws-profile'] = fakeCredentials.aProfile.name; |
|
|
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.credentials.profile).to.equal('notDefault'); |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(fakeCredentials.aProfile.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.equal(fakeCredentials.aProfile.secretAccessKey); |
|
}); |
|
|
|
- it('should get credentials when profile is provied via --aws-profile option even if profile is defined in serverless.yml', () => { |
|
+ it('should get credentials when profile is provided via --aws-profile option even if profile is defined in serverless.yml', async () => { |
|
// eslint-disable-line max-len |
|
- process.env.AWS_PROFILE = 'notDefaultTemporary'; |
|
- newAwsProvider.options['aws-profile'] = 'notDefault'; |
|
+ process.env.AWS_PROFILE = fakeCredentials.anotherProfile.name; |
|
+ serverless.service.provider.profile = fakeCredentials.anotherProfile.name; |
|
|
|
- serverless.service.provider.profile = 'notDefaultTemporary2'; |
|
+ newAwsProvider.options['aws-profile'] = fakeCredentials.aProfile.name; |
|
|
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.credentials.profile).to.equal('notDefault'); |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(fakeCredentials.aProfile.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.equal(fakeCredentials.aProfile.secretAccessKey); |
|
}); |
|
|
|
- it('should get credentials when profile is provied via process.env.AWS_PROFILE even if profile is defined in serverless.yml', () => { |
|
+ it('should get credentials when profile is provided via process.env.AWS_PROFILE even if profile is defined in serverless.yml', async () => { |
|
// eslint-disable-line max-len |
|
- process.env.AWS_PROFILE = 'notDefault'; |
|
+ process.env.AWS_PROFILE = fakeCredentials.aProfile.name; |
|
|
|
- serverless.service.provider.profile = 'notDefaultTemporary'; |
|
+ serverless.service.provider.profile = fakeCredentials.anotherProfile.name; |
|
|
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.credentials.profile).to.equal('notDefault'); |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(fakeCredentials.aProfile.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.equal(fakeCredentials.aProfile.secretAccessKey); |
|
}); |
|
|
|
- it('should set the signatureVersion to v4 if the serverSideEncryption is aws:kms', () => { |
|
- newAwsProvider.serverless.service.provider.deploymentBucketObject = { |
|
- serverSideEncryption: 'aws:kms', |
|
- }; |
|
+ it('should get credentials from process.env.AWS_DEFAULT_PROFILE, not "default"', async () => { |
|
+ process.env.AWS_DEFAULT_PROFILE = fakeCredentials.otherDefault.name; |
|
+ newAwsProvider.options['aws-profile'] = undefined; |
|
+ |
|
+ serverless.utils.appendFileSync( |
|
+ relevantEnvironment.AWS_SHARED_CREDENTIALS_FILE, |
|
+ '[default]\n' + |
|
+ `aws_access_key_id = ${fakeCredentials.default.accessKeyId}\n` + |
|
+ `aws_secret_access_key = ${fakeCredentials.default.secretAccessKey}\n` + |
|
+ '\n' + |
|
+ `[${fakeCredentials.otherDefault.name}]\n` + |
|
+ `aws_access_key_id = ${fakeCredentials.otherDefault.accessKeyId}\n` + |
|
+ `aws_secret_access_key = ${fakeCredentials.otherDefault.secretAccessKey}\n` |
|
+ ); |
|
+ |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(fakeCredentials.otherDefault.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.equal(fakeCredentials.otherDefault.secretAccessKey); |
|
+ }); |
|
+ |
|
+ it('should get "default" credentials in lieu of anything else', async () => { |
|
+ newAwsProvider.options['aws-profile'] = undefined; |
|
+ |
|
+ serverless.utils.appendFileSync( |
|
+ relevantEnvironment.AWS_SHARED_CREDENTIALS_FILE, |
|
+ '[default]\n' + |
|
+ `aws_access_key_id = ${fakeCredentials.default.accessKeyId}\n` + |
|
+ `aws_secret_access_key = ${fakeCredentials.default.secretAccessKey}\n` |
|
+ ); |
|
|
|
- const credentials = newAwsProvider.getCredentials(); |
|
- expect(credentials.signatureVersion).to.equal('v4'); |
|
+ const { credentials } = newAwsProvider.getCredentials(); |
|
+ await credentials.getPromise(); |
|
+ expect(credentials.accessKeyId).to.equal(fakeCredentials.default.accessKeyId); |
|
+ expect(credentials.secretAccessKey).to.equal(fakeCredentials.default.secretAccessKey); |
|
}); |
|
}); |
|
|
|
diff --git a/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/.NETCoreApp,Version=v3.1.AssemblyAttributes.cs b/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/.NETCoreApp,Version=v3.1.AssemblyAttributes.cs |
|
new file mode 100644 |
|
index 0000000..03fd1de |
|
--- /dev/null |
|
+++ b/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/.NETCoreApp,Version=v3.1.AssemblyAttributes.cs |
|
@@ -0,0 +1,4 @@ |
|
+// <autogenerated /> |
|
+using System; |
|
+using System.Reflection; |
|
+[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v3.1", FrameworkDisplayName = "")] |
|
diff --git a/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/aws-csharp.AssemblyInfo.cs b/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/aws-csharp.AssemblyInfo.cs |
|
new file mode 100644 |
|
index 0000000..158b34a |
|
--- /dev/null |
|
+++ b/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/aws-csharp.AssemblyInfo.cs |
|
@@ -0,0 +1,23 @@ |
|
+//------------------------------------------------------------------------------ |
|
+// <auto-generated> |
|
+// This code was generated by a tool. |
|
+// Runtime Version:4.0.30319.42000 |
|
+// |
|
+// Changes to this file may cause incorrect behavior and will be lost if |
|
+// the code is regenerated. |
|
+// </auto-generated> |
|
+//------------------------------------------------------------------------------ |
|
+ |
|
+using System; |
|
+using System.Reflection; |
|
+ |
|
+[assembly: System.Reflection.AssemblyCompanyAttribute("CsharpHandlers")] |
|
+[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] |
|
+[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] |
|
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] |
|
+[assembly: System.Reflection.AssemblyProductAttribute("CsharpHandlers")] |
|
+[assembly: System.Reflection.AssemblyTitleAttribute("CsharpHandlers")] |
|
+[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] |
|
+ |
|
+// Generated by the MSBuild WriteCodeFragment class. |
|
+ |
|
diff --git a/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/aws-csharp.AssemblyInfoInputs.cache b/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/aws-csharp.AssemblyInfoInputs.cache |
|
new file mode 100644 |
|
index 0000000..77f182d |
|
--- /dev/null |
|
+++ b/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/aws-csharp.AssemblyInfoInputs.cache |
|
@@ -0,0 +1 @@ |
|
+a49277692051e4f935ec0560b75544b62478532d |
|
diff --git a/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/aws-csharp.csprojAssemblyReference.cache b/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/aws-csharp.csprojAssemblyReference.cache |
|
new file mode 100644 |
|
index 0000000..4a1a56a |
|
Binary files /dev/null and b/node_modules/serverless/lib/plugins/create/templates/aws-csharp/obj/Debug/netcoreapp3.1/aws-csharp.csprojAssemblyReference.cache differ |
|
diff --git a/node_modules/serverless/lib/plugins/create/templates/azure-csharp/obj/Debug/netcoreapp3.1/.NETCoreApp,Version=v3.1.AssemblyAttributes.cs b/node_modules/serverless/lib/plugins/create/templates/azure-csharp/obj/Debug/netcoreapp3.1/.NETCoreApp,Version=v3.1.AssemblyAttributes.cs |
|
new file mode 100644 |
|
index 0000000..03fd1de |
|
--- /dev/null |
|
+++ b/node_modules/serverless/lib/plugins/create/templates/azure-csharp/obj/Debug/netcoreapp3.1/.NETCoreApp,Version=v3.1.AssemblyAttributes.cs |
|
@@ -0,0 +1,4 @@ |
|
+// <autogenerated /> |
|
+using System; |
|
+using System.Reflection; |
|
+[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v3.1", FrameworkDisplayName = "")] |
|
diff --git a/node_modules/serverless/lib/plugins/create/templates/azure-csharp/obj/Debug/netcoreapp3.1/azure-csharp.AssemblyInfo.cs b/node_modules/serverless/lib/plugins/create/templates/azure-csharp/obj/Debug/netcoreapp3.1/azure-csharp.AssemblyInfo.cs |
|
new file mode 100644 |
|
index 0000000..12c1d63 |
|
--- /dev/null |
|
+++ b/node_modules/serverless/lib/plugins/create/templates/azure-csharp/obj/Debug/netcoreapp3.1/azure-csharp.AssemblyInfo.cs |
|
@@ -0,0 +1,23 @@ |
|
+//------------------------------------------------------------------------------ |
|
+// <auto-generated> |
|
+// This code was generated by a tool. |
|
+// Runtime Version:4.0.30319.42000 |
|
+// |
|
+// Changes to this file may cause incorrect behavior and will be lost if |
|
+// the code is regenerated. |
|
+// </auto-generated> |
|
+//------------------------------------------------------------------------------ |
|
+ |
|
+using System; |
|
+using System.Reflection; |
|
+ |
|
+[assembly: System.Reflection.AssemblyCompanyAttribute("azure-csharp")] |
|
+[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] |
|
+[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] |
|
+[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")] |
|
+[assembly: System.Reflection.AssemblyProductAttribute("azure-csharp")] |
|
+[assembly: System.Reflection.AssemblyTitleAttribute("azure-csharp")] |
|
+[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] |
|
+ |
|
+// Generated by the MSBuild WriteCodeFragment class. |
|
+ |
|
diff --git a/node_modules/serverless/lib/plugins/create/templates/azure-csharp/obj/Debug/netcoreapp3.1/azure-csharp.AssemblyInfoInputs.cache b/node_modules/serverless/lib/plugins/create/templates/azure-csharp/obj/Debug/netcoreapp3.1/azure-csharp.AssemblyInfoInputs.cache |
|
new file mode 100644 |
|
index 0000000..c0f76f8 |
|
--- /dev/null |
|
+++ b/node_modules/serverless/lib/plugins/create/templates/azure-csharp/obj/Debug/netcoreapp3.1/azure-csharp.AssemblyInfoInputs.cache |
|
@@ -0,0 +1 @@ |
|
+779601599c1884a13eb5f8cc85c28c97c14d133e |