mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
2170 lines
69 KiB
JavaScript
2170 lines
69 KiB
JavaScript
'use strict'
|
|
|
|
/* eslint-disable no-unused-expressions */
|
|
|
|
const _ = require('lodash')
|
|
const chai = require('chai')
|
|
const path = require('path')
|
|
const fs = require('fs-extra')
|
|
const os = require('os')
|
|
const proxyquire = require('proxyquire')
|
|
const sinon = require('sinon')
|
|
const overrideEnv = require('process-utils/override-env')
|
|
|
|
const AwsProvider = require('../../../../../lib/plugins/aws/provider')
|
|
const Serverless = require('../../../../../lib/serverless')
|
|
const runServerless = require('../../../../utils/run-serverless')
|
|
|
|
chai.use(require('chai-as-promised'))
|
|
chai.use(require('sinon-chai'))
|
|
|
|
const expect = chai.expect
|
|
|
|
describe('AwsProvider', () => {
|
|
let awsProvider
|
|
let serverless
|
|
let restoreEnv
|
|
const options = {
|
|
stage: 'dev',
|
|
region: 'us-east-1',
|
|
}
|
|
|
|
beforeEach(() => {
|
|
;({ restoreEnv } = overrideEnv())
|
|
serverless = new Serverless({ ...options, commands: [], options: {} })
|
|
serverless.cli = new serverless.classes.CLI()
|
|
awsProvider = new AwsProvider(serverless, options)
|
|
})
|
|
afterEach(() => restoreEnv())
|
|
|
|
describe('#constructor()', () => {
|
|
it('should set Serverless instance', () => {
|
|
expect(typeof awsProvider.serverless).to.not.equal('undefined')
|
|
})
|
|
|
|
it('should set the provider property', () => {
|
|
expect(awsProvider.provider).to.equal(awsProvider)
|
|
})
|
|
|
|
describe('stage name validation', () => {
|
|
const stages = ['myStage', 'my-stage', 'my_stage', "${opt:stage, 'prod'}"]
|
|
stages.forEach((stage) => {
|
|
it(`should not throw an error before variable population
|
|
even if http event is present and stage is ${stage}`, () => {
|
|
const config = {
|
|
stage,
|
|
commands: [],
|
|
options: {},
|
|
}
|
|
serverless = new Serverless(config)
|
|
|
|
const serverlessYml = {
|
|
service: 'new-service',
|
|
provider: {
|
|
name: 'aws',
|
|
stage,
|
|
},
|
|
functions: {
|
|
first: {
|
|
events: [
|
|
{
|
|
http: {
|
|
path: 'foo',
|
|
method: 'GET',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
serverless.service = new serverless.classes.Service(
|
|
serverless,
|
|
serverlessYml,
|
|
)
|
|
expect(() => new AwsProvider(serverless, config)).to.not.throw(Error)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('deploymentBucket configuration', () => {
|
|
it('should do nothing if not defined', () => {
|
|
serverless.service.provider.deploymentBucket = undefined
|
|
|
|
const newAwsProvider = new AwsProvider(serverless, options)
|
|
|
|
expect(
|
|
newAwsProvider.serverless.service.provider.deploymentBucket,
|
|
).to.equal(undefined)
|
|
})
|
|
|
|
it('should do nothing if the value is a string', () => {
|
|
serverless.service.provider.deploymentBucket = 'my.deployment.bucket'
|
|
|
|
const newAwsProvider = new AwsProvider(serverless, options)
|
|
|
|
expect(
|
|
newAwsProvider.serverless.service.provider.deploymentBucket,
|
|
).to.equal('my.deployment.bucket')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('values', () => {
|
|
const obj = {
|
|
a: 'b',
|
|
c: {
|
|
d: 'e',
|
|
f: {
|
|
g: 'h',
|
|
},
|
|
},
|
|
}
|
|
const paths = [['a'], ['c', 'd'], ['c', 'f', 'g']]
|
|
const getExpected = [
|
|
{ path: paths[0], value: obj.a },
|
|
{ path: paths[1], value: obj.c.d },
|
|
{ path: paths[2], value: obj.c.f.g },
|
|
]
|
|
describe('#getValues', () => {
|
|
it('should return an array of values given paths to them', () => {
|
|
expect(awsProvider.getValues(obj, paths)).to.eql(getExpected)
|
|
})
|
|
})
|
|
describe('#firstValue', () => {
|
|
it("should ignore entries without a 'value' attribute", () => {
|
|
const input = _.cloneDeep(getExpected)
|
|
delete input[0].value
|
|
delete input[2].value
|
|
expect(awsProvider.firstValue(input)).to.eql(getExpected[1])
|
|
})
|
|
it("should ignore entries with an undefined 'value' attribute", () => {
|
|
const input = _.cloneDeep(getExpected)
|
|
input[0].value = undefined
|
|
input[2].value = undefined
|
|
expect(awsProvider.firstValue(input)).to.eql(getExpected[1])
|
|
})
|
|
it('should return the first value', () => {
|
|
expect(awsProvider.firstValue(getExpected)).to.equal(getExpected[0])
|
|
})
|
|
it('should return the middle value', () => {
|
|
const input = _.cloneDeep(getExpected)
|
|
delete input[0].value
|
|
delete input[2].value
|
|
expect(awsProvider.firstValue(input)).to.equal(input[1])
|
|
})
|
|
it('should return the last value', () => {
|
|
const input = _.cloneDeep(getExpected)
|
|
delete input[0].value
|
|
delete input[1].value
|
|
expect(awsProvider.firstValue(input)).to.equal(input[2])
|
|
})
|
|
it('should return the last object if none have valid values', () => {
|
|
const input = _.cloneDeep(getExpected)
|
|
delete input[0].value
|
|
delete input[1].value
|
|
delete input[2].value
|
|
expect(awsProvider.firstValue(input)).to.equal(input[2])
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('#request()', () => {
|
|
let awsRequestStub
|
|
let awsProviderProxied
|
|
|
|
beforeEach(() => {
|
|
awsRequestStub = sinon.stub().resolves()
|
|
awsRequestStub.memoized = sinon.stub().resolves()
|
|
const AwsProviderProxyquired = proxyquire
|
|
.noCallThru()
|
|
.load('../../../../../lib/plugins/aws/provider.js', {
|
|
'../../aws/request': awsRequestStub,
|
|
'@serverless/utils/log': {
|
|
log: {
|
|
debug: sinon.stub(),
|
|
},
|
|
},
|
|
})
|
|
awsProviderProxied = new AwsProviderProxyquired(serverless, options)
|
|
})
|
|
|
|
afterEach(() => {})
|
|
|
|
it('should pass resolved credentials as expected', async () => {
|
|
awsProviderProxied.cachedCredentials = {
|
|
accessKeyId: 'accessKeyId',
|
|
secretAccessKey: 'secretAccessKey',
|
|
sessionToken: 'sessionToken',
|
|
}
|
|
await awsProviderProxied.request('S3', 'getObject', {})
|
|
expect(awsRequestStub.args[0][0]).to.deep.equal({
|
|
name: 'S3',
|
|
params: {
|
|
...awsProviderProxied.cachedCredentials,
|
|
region: 'us-east-1',
|
|
isS3TransferAccelerationEnabled: false,
|
|
},
|
|
})
|
|
})
|
|
|
|
it('should trigger the expected AWS SDK invokation', async () => {
|
|
return awsProviderProxied.request('S3', 'getObject', {}).then(() => {
|
|
expect(awsRequestStub).to.have.been.calledOnce
|
|
})
|
|
})
|
|
|
|
it('should use local cache when using {useCache: true}', async () => {
|
|
return awsProviderProxied
|
|
.request('S3', 'getObject', {}, { useCache: true })
|
|
.then(() =>
|
|
awsProviderProxied.request('S3', 'getObject', {}, { useCache: true }),
|
|
)
|
|
.then(() => {
|
|
expect(awsRequestStub).to.not.have.been.called
|
|
expect(awsRequestStub.memoized).to.have.been.calledTwice
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('#getServerlessDeploymentBucketName()', () => {
|
|
it('should return the name of the serverless deployment bucket', async () => {
|
|
const describeStackResourcesStub = sinon
|
|
.stub(awsProvider, 'request')
|
|
.resolves({
|
|
StackResourceDetail: {
|
|
PhysicalResourceId: 'serverlessDeploymentBucketName',
|
|
},
|
|
})
|
|
|
|
return awsProvider
|
|
.getServerlessDeploymentBucketName()
|
|
.then((bucketName) => {
|
|
expect(bucketName).to.equal('serverlessDeploymentBucketName')
|
|
expect(describeStackResourcesStub.calledOnce).to.be.equal(true)
|
|
expect(
|
|
describeStackResourcesStub.calledWithExactly(
|
|
'CloudFormation',
|
|
'describeStackResource',
|
|
{
|
|
StackName: awsProvider.naming.getStackName(),
|
|
LogicalResourceId:
|
|
awsProvider.naming.getDeploymentBucketLogicalId(),
|
|
},
|
|
),
|
|
).to.be.equal(true)
|
|
awsProvider.request.restore()
|
|
})
|
|
})
|
|
|
|
it('should return the name of the custom deployment bucket', async () => {
|
|
awsProvider.serverless.service.provider.deploymentBucket = 'custom-bucket'
|
|
|
|
const describeStackResourcesStub = sinon
|
|
.stub(awsProvider, 'request')
|
|
.resolves({
|
|
StackResourceDetail: {
|
|
PhysicalResourceId: 'serverlessDeploymentBucketName',
|
|
},
|
|
})
|
|
|
|
return awsProvider
|
|
.getServerlessDeploymentBucketName()
|
|
.then((bucketName) => {
|
|
expect(describeStackResourcesStub.called).to.be.equal(false)
|
|
expect(bucketName).to.equal('custom-bucket')
|
|
awsProvider.request.restore()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('#getAccountInfo()', () => {
|
|
it('should return the AWS account id and partition', async () => {
|
|
const accountId = '12345678'
|
|
const partition = 'aws'
|
|
|
|
const stsGetCallerIdentityStub = sinon
|
|
.stub(awsProvider, 'request')
|
|
.resolves({
|
|
ResponseMetadata: {
|
|
RequestId: '12345678-1234-1234-1234-123456789012',
|
|
},
|
|
UserId: 'ABCDEFGHIJKLMNOPQRSTU:VWXYZ',
|
|
Account: accountId,
|
|
Arn: 'arn:aws:sts::123456789012:assumed-role/ROLE-NAME/VWXYZ',
|
|
})
|
|
|
|
return awsProvider.getAccountInfo().then((result) => {
|
|
expect(stsGetCallerIdentityStub.calledOnce).to.equal(true)
|
|
expect(result.accountId).to.equal(accountId)
|
|
expect(result.partition).to.equal(partition)
|
|
awsProvider.request.restore()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('#getAccountId()', () => {
|
|
it('should return the AWS account id', async () => {
|
|
const accountId = '12345678'
|
|
|
|
const stsGetCallerIdentityStub = sinon
|
|
.stub(awsProvider, 'request')
|
|
.resolves({
|
|
ResponseMetadata: {
|
|
RequestId: '12345678-1234-1234-1234-123456789012',
|
|
},
|
|
UserId: 'ABCDEFGHIJKLMNOPQRSTU:VWXYZ',
|
|
Account: accountId,
|
|
Arn: 'arn:aws:sts::123456789012:assumed-role/ROLE-NAME/VWXYZ',
|
|
})
|
|
|
|
return awsProvider.getAccountId().then((result) => {
|
|
expect(stsGetCallerIdentityStub.calledOnce).to.equal(true)
|
|
expect(result).to.equal(accountId)
|
|
awsProvider.request.restore()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('#isS3TransferAccelerationEnabled()', () => {
|
|
it('should return false by default', () => {
|
|
awsProvider.options['aws-s3-accelerate'] = undefined
|
|
return expect(awsProvider.isS3TransferAccelerationEnabled()).to.equal(
|
|
false,
|
|
)
|
|
})
|
|
it('should return true when CLI option is provided', () => {
|
|
awsProvider.options['aws-s3-accelerate'] = true
|
|
return expect(awsProvider.isS3TransferAccelerationEnabled()).to.equal(
|
|
true,
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('#disableTransferAccelerationForCurrentDeploy()', () => {
|
|
it('should remove the corresponding option for the current deploy', () => {
|
|
awsProvider.options['aws-s3-accelerate'] = true
|
|
awsProvider.disableTransferAccelerationForCurrentDeploy()
|
|
return expect(awsProvider.options['aws-s3-accelerate']).to.be.undefined
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('test/unit/lib/plugins/aws/provider.test.js', () => {
|
|
describe('#getProviderName and #sessionCache', () => {
|
|
let sls
|
|
const expectedToken = '123'
|
|
|
|
before(async () => {
|
|
// Fake service that update credentials
|
|
class FakeCloudFormation {
|
|
constructor(credentials) {
|
|
this.credentials = credentials
|
|
this.credentials.credentials.sessionToken = expectedToken
|
|
}
|
|
describeStacks() {
|
|
return { promise: async () => {} }
|
|
}
|
|
}
|
|
// Stub functions for the credentials creation in the provider
|
|
class SharedIniFileCredentials {
|
|
constructor() {
|
|
this.sessionToken = 'abc'
|
|
this.accessKeyId = 'keyId'
|
|
this.secretAccessKey = 'secret'
|
|
}
|
|
}
|
|
class EnvironmentCredentials {
|
|
constructor() {
|
|
this.sessionToken = 'env'
|
|
this.accessKeyId = 'keyId'
|
|
this.secretAccessKey = 'secret'
|
|
}
|
|
}
|
|
class FakeMetadataService {}
|
|
|
|
const modulesCacheStub = {
|
|
'aws-sdk': {
|
|
SharedIniFileCredentials,
|
|
EnvironmentCredentials,
|
|
CloudFormation: FakeCloudFormation,
|
|
config: {},
|
|
},
|
|
'aws-sdk/lib/metadata_service': FakeMetadataService,
|
|
}
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
modulesCacheStub,
|
|
})
|
|
sls = serverless
|
|
})
|
|
|
|
it('`AwsProvider.getProviderName()` should resolve provider name', () => {
|
|
expect(AwsProvider.getProviderName()).to.equal('aws')
|
|
})
|
|
|
|
it('should retain sessionToken eventually updated internally by SDK', async () => {
|
|
expect(
|
|
sls.getProvider('aws').getCredentials().credentials.sessionToken,
|
|
).not.to.equal(expectedToken)
|
|
await sls.getProvider('aws').request('CloudFormation', 'describeStacks')
|
|
expect(
|
|
sls.getProvider('aws').getCredentials().credentials.sessionToken,
|
|
).to.equal(expectedToken)
|
|
})
|
|
})
|
|
|
|
describe('#getCredentials()', () => {
|
|
before(async () => {
|
|
// create default aws credentials file in before so that grouped run can use it
|
|
await fs.ensureDir(path.resolve(os.homedir(), './.aws'))
|
|
await fs.outputFile(
|
|
path.resolve(os.homedir(), './.aws/credentials'),
|
|
`
|
|
[default]
|
|
aws_access_key_id = DEFAULTKEYID
|
|
aws_secret_access_key = DEFAULTSECRET
|
|
|
|
[notDefault]
|
|
aws_access_key_id = NOTDEFAULTKEYID
|
|
aws_secret_access_key = NOTDEFAULTSECRET
|
|
|
|
[notDefaultWithRole]
|
|
source_profile = notDefault
|
|
role_arn = NOTDEFAULTWITHROLEROLE
|
|
`,
|
|
{ flag: 'w+' },
|
|
)
|
|
})
|
|
|
|
it('should get credentials from default AWS profile', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
})
|
|
const awsCredentials = serverless.getProvider('aws').getCredentials()
|
|
expect(awsCredentials.credentials.accessKeyId).to.equal('DEFAULTKEYID')
|
|
})
|
|
|
|
it('should get credentials from custom default AWS profile, set by AWS_DEFAULT_PROFILE', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
})
|
|
// getCredentials resolve the env when called
|
|
let awsCredentials
|
|
overrideEnv(() => {
|
|
process.env.AWS_DEFAULT_PROFILE = 'notDefault'
|
|
awsCredentials = serverless.getProvider('aws').getCredentials()
|
|
})
|
|
expect(awsCredentials.credentials.accessKeyId).to.equal('NOTDEFAULTKEYID')
|
|
})
|
|
|
|
describe('assume role with provider.profile', () => {
|
|
let awsCredentials
|
|
before(async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
configExt: {
|
|
provider: {
|
|
profile: 'notDefaultWithRole',
|
|
},
|
|
},
|
|
})
|
|
awsCredentials = serverless.getProvider('aws').getCredentials()
|
|
})
|
|
|
|
it('should get credentials from `provider.profile`', () => {
|
|
expect(awsCredentials.credentials.profile).to.equal(
|
|
'notDefaultWithRole',
|
|
)
|
|
})
|
|
|
|
it('should accept a role to assume on credentials', () => {
|
|
expect(awsCredentials.credentials.roleArn).to.equal(
|
|
'NOTDEFAULTWITHROLEROLE',
|
|
)
|
|
})
|
|
})
|
|
|
|
it('should get credentials from environment variables', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
})
|
|
let awsCredentials
|
|
// getCredentials resolve the env when called
|
|
overrideEnv(() => {
|
|
process.env.AWS_ACCESS_KEY_ID = 'ENVKEYID'
|
|
process.env.AWS_SECRET_ACCESS_KEY = 'ENVSECRET'
|
|
awsCredentials = serverless.getProvider('aws').getCredentials()
|
|
})
|
|
expect(awsCredentials.credentials.accessKeyId).to.equal('ENVKEYID')
|
|
})
|
|
|
|
describe('profile with non default credentials file', () => {
|
|
let awsCredentials
|
|
before(async () => {
|
|
await fs.outputFile(
|
|
path.resolve(os.homedir(), './custom_credentials'),
|
|
`
|
|
[default]
|
|
aws_access_key_id = DEFAULTKEYID
|
|
aws_secret_access_key = DEFAULTSECRET
|
|
|
|
[customProfile]
|
|
aws_access_key_id = CUSTOMKEYID
|
|
aws_secret_access_key = CUSTOMSECRET
|
|
`,
|
|
{ flag: 'w+' },
|
|
)
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
})
|
|
// getCredentials resolve the env when called
|
|
overrideEnv(() => {
|
|
process.env.AWS_PROFILE = 'customProfile'
|
|
process.env.AWS_SHARED_CREDENTIALS_FILE = path
|
|
.resolve(os.homedir(), './custom_credentials')
|
|
.toString()
|
|
awsCredentials = serverless.getProvider('aws').getCredentials()
|
|
})
|
|
})
|
|
|
|
after(async () => {
|
|
await fs.remove(path.resolve(os.homedir(), './custom_credentials'))
|
|
})
|
|
|
|
it('should get credentials from AWS_PROFILE environment variable', () => {
|
|
expect(awsCredentials.credentials.profile).to.equal('customProfile')
|
|
})
|
|
|
|
it('should get credentials from AWS_SHARED_CREDENTIALS_FILE environment variable', () => {
|
|
expect(awsCredentials.credentials.accessKeyId).to.equal('CUSTOMKEYID')
|
|
})
|
|
})
|
|
|
|
it('should get credentials from stage specific environment variables', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
configExt: {
|
|
provider: {
|
|
stage: 'testStage',
|
|
},
|
|
},
|
|
})
|
|
let awsCredentials
|
|
overrideEnv(() => {
|
|
process.env.AWS_TESTSTAGE_ACCESS_KEY_ID = 'TESTSTAGEACCESSKEYID'
|
|
process.env.AWS_TESTSTAGE_SECRET_ACCESS_KEY = 'TESTSTAGESECRET'
|
|
awsCredentials = serverless.getProvider('aws').getCredentials()
|
|
})
|
|
expect(awsCredentials.credentials.accessKeyId).to.equal(
|
|
'TESTSTAGEACCESSKEYID',
|
|
)
|
|
})
|
|
|
|
it('should get credentials from AWS_{stage}_PROFILE environment variable', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
configExt: {
|
|
provider: {
|
|
stage: 'testStage',
|
|
},
|
|
},
|
|
})
|
|
let awsCredentials
|
|
overrideEnv(() => {
|
|
process.env.AWS_TESTSTAGE_PROFILE = 'notDefault'
|
|
awsCredentials = serverless.getProvider('aws').getCredentials()
|
|
})
|
|
expect(awsCredentials.credentials.accessKeyId).to.equal('NOTDEFAULTKEYID')
|
|
})
|
|
|
|
describe('profile with cli and encryption', () => {
|
|
let awsCredentials
|
|
before(async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
options: {
|
|
'aws-profile': 'notDefault',
|
|
},
|
|
configExt: {
|
|
provider: {
|
|
deploymentBucket: {
|
|
serverSideEncryption: 'aws:kms',
|
|
},
|
|
},
|
|
},
|
|
})
|
|
awsCredentials = serverless.getProvider('aws').getCredentials()
|
|
})
|
|
|
|
it('should get credentials "--aws-profile" CLI option', () => {
|
|
expect(awsCredentials.credentials.accessKeyId).to.equal(
|
|
'NOTDEFAULTKEYID',
|
|
)
|
|
})
|
|
|
|
it('should set the signatureVersion to v4 if the serverSideEncryption is aws:kms', () => {
|
|
expect(awsCredentials.signatureVersion).to.equal('v4')
|
|
})
|
|
})
|
|
|
|
it('should throw an error if a non-existent profile is set', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
options: {
|
|
'aws-profile': 'nonExistent',
|
|
},
|
|
})
|
|
expect(() => serverless.getProvider('aws').getCredentials()).to.throw(
|
|
Error,
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('#getRegion()', () => {
|
|
it('should default to "us-east-1"', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
})
|
|
expect(serverless.getProvider('aws').getRegion()).to.equal('us-east-1')
|
|
})
|
|
|
|
it('should allow to override via `provider.region`', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
configExt: {
|
|
provider: {
|
|
region: 'eu-central-1',
|
|
},
|
|
},
|
|
})
|
|
expect(serverless.getProvider('aws').getRegion()).to.equal('eu-central-1')
|
|
})
|
|
|
|
it('should allow to override via CLI `--region` param', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
options: { region: 'us-west-1' },
|
|
configExt: {
|
|
provider: {
|
|
region: 'eu-central-1',
|
|
},
|
|
},
|
|
})
|
|
expect(serverless.getProvider('aws').getRegion()).to.equal('us-west-1')
|
|
})
|
|
})
|
|
|
|
describe('#getProfile()', () => {
|
|
it('should allow to set via `provider.profile`', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
configExt: {
|
|
provider: {
|
|
profile: 'test-profile',
|
|
},
|
|
},
|
|
})
|
|
expect(serverless.getProvider('aws').getProfile()).to.equal(
|
|
'test-profile',
|
|
)
|
|
})
|
|
|
|
it('should allow to set via CLI `--profile` param', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
options: { profile: 'cli-override' },
|
|
configExt: {
|
|
provider: {
|
|
profile: 'test-profile',
|
|
},
|
|
},
|
|
})
|
|
expect(serverless.getProvider('aws').getProfile()).to.equal(
|
|
'cli-override',
|
|
)
|
|
})
|
|
|
|
it('should allow to set via CLI `--aws-profile` param', async () => {
|
|
// Test with `provider.profile` `--profile` and `--aws-pofile` CLI param set
|
|
// Confirm that `--aws-profile` overrides
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
options: {
|
|
profile: 'cli-override',
|
|
'aws-profile': 'aws-override',
|
|
},
|
|
configExt: {
|
|
provider: {
|
|
profile: 'test-profile',
|
|
},
|
|
},
|
|
})
|
|
expect(serverless.getProvider('aws').getProfile()).to.equal(
|
|
'aws-override',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('#getDeploymentPrefix()', () => {
|
|
it('should put all artifacts in namespaced folder', async () => {
|
|
const { cfTemplate } = await runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
})
|
|
const functions = Object.values(cfTemplate.Resources).filter(
|
|
(r) => r.Type === 'AWS::Lambda::Function',
|
|
)
|
|
expect(functions.length).to.be.greaterThanOrEqual(1)
|
|
for (const f of functions) {
|
|
expect(f.Properties.Code.S3Key)
|
|
.to.be.a('string')
|
|
.and.satisfy((key) => key.startsWith('serverless/'))
|
|
}
|
|
})
|
|
|
|
it('should support custom namespaced folder', async () => {
|
|
const { cfTemplate } = await runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
configExt: {
|
|
provider: {
|
|
deploymentPrefix: 'test-prefix',
|
|
},
|
|
},
|
|
})
|
|
const functions = Object.values(cfTemplate.Resources).filter(
|
|
(r) => r.Type === 'AWS::Lambda::Function',
|
|
)
|
|
expect(functions.length).to.be.greaterThanOrEqual(1)
|
|
for (const f of functions) {
|
|
expect(f.Properties.Code.S3Key)
|
|
.to.be.a('string')
|
|
.and.satisfy((key) => key.startsWith('test-prefix/'))
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('#getAlbTargetGroupPrefix()', () => {
|
|
it('should support `provider.alb.targetGroupPrefix`', async () => {
|
|
const albId = '50dc6c495c0c9188'
|
|
const validBaseEventConfig = {
|
|
listenerArn: `arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-load-balancer/${albId}/f2f7dc8efc522ab2`,
|
|
conditions: {
|
|
path: '/',
|
|
},
|
|
}
|
|
const { cfTemplate } = await runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
configExt: {
|
|
provider: {
|
|
alb: {
|
|
targetGroupPrefix: 'a-prefix',
|
|
},
|
|
},
|
|
functions: {
|
|
fnTargetGroupName: {
|
|
handler: 'index.handler',
|
|
events: [
|
|
{
|
|
alb: {
|
|
...validBaseEventConfig,
|
|
priority: 1,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
})
|
|
const targetGroups = Object.values(cfTemplate.Resources).filter(
|
|
(r) => r.Type === 'AWS::ElasticLoadBalancingV2::TargetGroup',
|
|
)
|
|
expect(targetGroups.length).to.be.greaterThanOrEqual(1)
|
|
for (const t of targetGroups) {
|
|
expect(t.Properties.Name)
|
|
.to.be.a('string')
|
|
.and.satisfy((key) => key.startsWith('a-prefix'))
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('#getStage()', () => {
|
|
it('should default to "dev"', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
})
|
|
expect(serverless.getProvider('aws').getStage()).to.equal('dev')
|
|
})
|
|
|
|
it('should allow to override via `provider.stage`', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
configExt: {
|
|
provider: {
|
|
stage: 'staging',
|
|
},
|
|
},
|
|
})
|
|
expect(serverless.getProvider('aws').getStage()).to.equal('staging')
|
|
})
|
|
|
|
it('should allow to override via CLI `--stage` param', async () => {
|
|
const { serverless } = await runServerless({
|
|
fixture: 'aws',
|
|
command: 'print',
|
|
options: { stage: 'production' },
|
|
configExt: {
|
|
provider: {
|
|
stage: 'staging',
|
|
},
|
|
},
|
|
})
|
|
expect(serverless.getProvider('aws').getStage()).to.equal('production')
|
|
})
|
|
})
|
|
|
|
describe('when resolving images', () => {
|
|
it('should fail if `functions[].image` references image with both buildOptions and uri', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
invalidimage: {
|
|
buildOptions: ['--no-cache'],
|
|
uri: '000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
functions: {
|
|
fnProviderInvalidImage: {
|
|
image: 'invalidimage',
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'ECR_IMAGE_URI_AND_BUILDOPTIONS_DEFINED_ERROR',
|
|
)
|
|
})
|
|
|
|
it('should fail if `functions[].image` references image with both path and uri', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
invalidimage: {
|
|
path: './',
|
|
uri: '000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
functions: {
|
|
fnProviderInvalidImage: {
|
|
image: 'invalidimage',
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'ECR_IMAGE_BOTH_URI_AND_PATH_DEFINED_ERROR',
|
|
)
|
|
})
|
|
|
|
it('should fail if `functions[].image` references image with both buildArgs and uri', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
invalidimage: {
|
|
buildArgs: {
|
|
TESTKEY: 'TESTVAL',
|
|
},
|
|
uri: '000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
functions: {
|
|
fnProviderInvalidImage: {
|
|
image: 'invalidimage',
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'ECR_IMAGE_BOTH_URI_AND_BUILDARGS_DEFINED_ERROR',
|
|
)
|
|
})
|
|
|
|
it('should fail if `functions[].image` references image with both cacheFrom and uri', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
invalidimage: {
|
|
cacheFrom: ['my-image:latest'],
|
|
uri: '000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
functions: {
|
|
fnProviderInvalidImage: {
|
|
image: 'invalidimage',
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'ECR_IMAGE_BOTH_URI_AND_CACHEFROM_DEFINED_ERROR',
|
|
)
|
|
})
|
|
|
|
it('should fail if `functions[].image` references image with both platform and uri', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
invalidimage: {
|
|
platform: 'TESTVAL',
|
|
uri: '000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
functions: {
|
|
fnProviderInvalidImage: {
|
|
image: 'invalidimage',
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'ECR_IMAGE_BOTH_URI_AND_PLATFORM_DEFINED_ERROR',
|
|
)
|
|
})
|
|
|
|
it('should fail if `functions[].image` references image without path and uri', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
invalidimage: {},
|
|
},
|
|
},
|
|
},
|
|
functions: {
|
|
fnProviderInvalidImage: {
|
|
image: 'invalidimage',
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'ECR_IMAGE_NEITHER_URI_NOR_PATH_DEFINED_ERROR',
|
|
)
|
|
})
|
|
|
|
it('should fail if `functions[].image` references image from `provider.ecr.images` that has invalid path', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
shouldStubSpawn: true,
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
baseimage: {
|
|
path: './invalid',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'DOCKERFILE_NOT_AVAILABLE_ERROR',
|
|
)
|
|
})
|
|
|
|
it('should fail if `functions[].image` references image not defined in `provider.ecr.images`', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
configExt: {
|
|
functions: {
|
|
fnInvalid: {
|
|
image: 'undefinedimage',
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'REFERENCED_FUNCTION_IMAGE_NOT_DEFINED_IN_PROVIDER',
|
|
)
|
|
})
|
|
|
|
it('should fail if both uri and name is provided for an image', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
shouldStubSpawn: true,
|
|
configExt: {
|
|
functions: {
|
|
foo: {
|
|
image: {
|
|
name: 'baseimage',
|
|
uri: '000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'FUNCTION_IMAGE_BOTH_URI_AND_NAME_DEFINED_ERROR',
|
|
)
|
|
})
|
|
|
|
it('should fail if neither uri nor name is provided for an image', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
shouldStubSpawn: true,
|
|
configExt: {
|
|
functions: {
|
|
foo: {
|
|
image: {},
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'FUNCTION_IMAGE_NEITHER_URI_NOR_NAME_DEFINED_ERROR',
|
|
)
|
|
})
|
|
|
|
const findVersionCfConfig = (cfResources, fnLogicalId) =>
|
|
Object.values(cfResources).find(
|
|
(resource) =>
|
|
resource.Type === 'AWS::Lambda::Version' &&
|
|
resource.Properties.FunctionName.Ref === fnLogicalId,
|
|
).Properties
|
|
|
|
describe('with `functions[].image` referencing existing images', () => {
|
|
let cfResources
|
|
let naming
|
|
let serviceConfig
|
|
const imageSha =
|
|
'6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38'
|
|
const imageWithSha = `000000000000.dkr.ecr.us-east-1.amazonaws.com/test-lambda-docker@sha256:${imageSha}`
|
|
const imageDigestFromECR =
|
|
'sha256:2e6b10a4b1ca0f6d3563a8a1f034dde7c4d7c93b50aa91f24311765d0822186b'
|
|
const describeImagesStub = sinon
|
|
.stub()
|
|
.resolves({ imageDetails: [{ imageDigest: imageDigestFromECR }] })
|
|
const awsRequestStubMap = {
|
|
ECR: {
|
|
describeImages: describeImagesStub,
|
|
},
|
|
}
|
|
|
|
before(async () => {
|
|
const { awsNaming, cfTemplate, fixtureData } = await runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
imagewithexplicituri: {
|
|
uri: imageWithSha,
|
|
},
|
|
imagewithimplicituri: imageWithSha,
|
|
},
|
|
},
|
|
},
|
|
functions: {
|
|
fnImage: {
|
|
image: imageWithSha,
|
|
},
|
|
fnImageWithTag: {
|
|
image:
|
|
'000000000000.dkr.ecr.us-east-1.amazonaws.com/test-lambda-docker:stable',
|
|
},
|
|
fnImageWithTagAndRepoWithSlashes: {
|
|
image:
|
|
'000000000000.dkr.ecr.us-east-1.amazonaws.com/test-lambda/repo-docker:stable',
|
|
},
|
|
fnImageWithExplicitUri: {
|
|
image: {
|
|
uri: imageWithSha,
|
|
},
|
|
},
|
|
fnProviderImageWithExplicitUri: {
|
|
image: 'imagewithexplicituri',
|
|
},
|
|
fnProviderImageWithImplicitUri: {
|
|
image: 'imagewithimplicituri',
|
|
},
|
|
},
|
|
},
|
|
awsRequestStubMap,
|
|
})
|
|
cfResources = cfTemplate.Resources
|
|
naming = awsNaming
|
|
serviceConfig = fixtureData.serviceConfig
|
|
})
|
|
|
|
it('should support `functions[].image` with implicit uri with sha', () => {
|
|
const functionServiceConfig = serviceConfig.functions.fnImage
|
|
const functionCfLogicalId = naming.getLambdaLogicalId('fnImage')
|
|
const functionCfConfig = cfResources[functionCfLogicalId].Properties
|
|
|
|
expect(functionCfConfig.Code).to.deep.equal({
|
|
ImageUri: functionServiceConfig.image,
|
|
})
|
|
expect(functionCfConfig).to.not.have.property('Handler')
|
|
expect(functionCfConfig).to.not.have.property('Runtime')
|
|
|
|
const imageDigest = functionServiceConfig.image.slice(
|
|
functionServiceConfig.image.lastIndexOf('@') + 1,
|
|
)
|
|
expect(imageDigest).to.match(/^sha256:[a-f0-9]{64}$/)
|
|
const imageDigestSha = imageDigest.slice('sha256:'.length)
|
|
const versionCfConfig = findVersionCfConfig(
|
|
cfResources,
|
|
functionCfLogicalId,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageDigestSha)
|
|
})
|
|
|
|
it('should support `functions[].image` with explicit uri with sha', () => {
|
|
const functionServiceConfig =
|
|
serviceConfig.functions.fnImageWithExplicitUri
|
|
const functionCfLogicalId = naming.getLambdaLogicalId(
|
|
'fnImageWithExplicitUri',
|
|
)
|
|
const functionCfConfig = cfResources[functionCfLogicalId].Properties
|
|
|
|
expect(functionCfConfig.Code).to.deep.equal({
|
|
ImageUri: functionServiceConfig.image.uri,
|
|
})
|
|
expect(functionCfConfig).to.not.have.property('Handler')
|
|
expect(functionCfConfig).to.not.have.property('Runtime')
|
|
|
|
const imageDigest = functionServiceConfig.image.uri.slice(
|
|
functionServiceConfig.image.uri.lastIndexOf('@') + 1,
|
|
)
|
|
expect(imageDigest).to.match(/^sha256:[a-f0-9]{64}$/)
|
|
const imageDigestSha = imageDigest.slice('sha256:'.length)
|
|
const versionCfConfig = findVersionCfConfig(
|
|
cfResources,
|
|
functionCfLogicalId,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageDigestSha)
|
|
})
|
|
|
|
it('should support `functions[].image` with tag', () => {
|
|
const functionServiceConfig = serviceConfig.functions.fnImageWithTag
|
|
const functionCfLogicalId = naming.getLambdaLogicalId('fnImageWithTag')
|
|
const functionCfConfig = cfResources[functionCfLogicalId].Properties
|
|
|
|
expect(functionCfConfig.Code).to.deep.equal({
|
|
ImageUri: `${
|
|
functionServiceConfig.image.split(':')[0]
|
|
}@${imageDigestFromECR}`,
|
|
})
|
|
expect(functionCfConfig).to.not.have.property('Handler')
|
|
expect(functionCfConfig).to.not.have.property('Runtime')
|
|
|
|
const versionCfConfig = findVersionCfConfig(
|
|
cfResources,
|
|
functionCfLogicalId,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(
|
|
imageDigestFromECR.slice('sha256:'.length),
|
|
)
|
|
expect(describeImagesStub).to.be.calledWith({
|
|
imageIds: [{ imageTag: 'stable' }],
|
|
registryId: '000000000000',
|
|
repositoryName: 'test-lambda-docker',
|
|
})
|
|
})
|
|
|
|
it('should support `functions[].image` with tag and repository name with slash', () => {
|
|
const functionServiceConfig =
|
|
serviceConfig.functions.fnImageWithTagAndRepoWithSlashes
|
|
const functionCfLogicalId = naming.getLambdaLogicalId(
|
|
'fnImageWithTagAndRepoWithSlashes',
|
|
)
|
|
const functionCfConfig = cfResources[functionCfLogicalId].Properties
|
|
|
|
expect(functionCfConfig.Code).to.deep.equal({
|
|
ImageUri: `${
|
|
functionServiceConfig.image.split(':')[0]
|
|
}@${imageDigestFromECR}`,
|
|
})
|
|
expect(functionCfConfig).to.not.have.property('Handler')
|
|
expect(functionCfConfig).to.not.have.property('Runtime')
|
|
|
|
const versionCfConfig = findVersionCfConfig(
|
|
cfResources,
|
|
functionCfLogicalId,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(
|
|
imageDigestFromECR.slice('sha256:'.length),
|
|
)
|
|
expect(describeImagesStub).to.be.calledWith({
|
|
imageIds: [{ imageTag: 'stable' }],
|
|
registryId: '000000000000',
|
|
repositoryName: 'test-lambda/repo-docker',
|
|
})
|
|
})
|
|
|
|
it('should support `functions[].image` that references provider.ecr.images defined with explicit uri', () => {
|
|
const functionCfLogicalId = naming.getLambdaLogicalId(
|
|
'fnProviderImageWithExplicitUri',
|
|
)
|
|
const functionCfConfig = cfResources[functionCfLogicalId].Properties
|
|
|
|
expect(functionCfConfig.Code).to.deep.equal({
|
|
ImageUri: imageWithSha,
|
|
})
|
|
expect(functionCfConfig).to.not.have.property('Handler')
|
|
expect(functionCfConfig).to.not.have.property('Runtime')
|
|
|
|
const versionCfConfig = findVersionCfConfig(
|
|
cfResources,
|
|
functionCfLogicalId,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
})
|
|
|
|
it('should support `functions[].image` that references provider.ecr.images defined with implicit uri', () => {
|
|
const functionCfLogicalId = naming.getLambdaLogicalId(
|
|
'fnProviderImageWithImplicitUri',
|
|
)
|
|
const functionCfConfig = cfResources[functionCfLogicalId].Properties
|
|
|
|
expect(functionCfConfig.Code).to.deep.equal({
|
|
ImageUri: imageWithSha,
|
|
})
|
|
expect(functionCfConfig).to.not.have.property('Handler')
|
|
expect(functionCfConfig).to.not.have.property('Runtime')
|
|
|
|
const versionCfConfig = findVersionCfConfig(
|
|
cfResources,
|
|
functionCfLogicalId,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
})
|
|
|
|
it('should fail when `functions[].image` when image uri region does not match the provider region', async () => {
|
|
const imageRegion = 'sa-east-1'
|
|
const imageWithoutSha = `000000000000.dkr.ecr.${imageRegion}.amazonaws.com/test-lambda-docker`
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'function',
|
|
command: 'package',
|
|
configExt: {
|
|
provider: {
|
|
region: 'us-east-1',
|
|
},
|
|
functions: {
|
|
fnImageWithExplicitUriInvalidRegion: {
|
|
image: imageWithoutSha,
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'LAMBDA_ECR_REGION_MISMATCH_ERROR',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('with `functions[].image` referencing images that require building', () => {
|
|
const imageSha =
|
|
'6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38'
|
|
const repositoryUri =
|
|
'999999999999.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker'
|
|
const authorizationToken = 'YXdzOmRvY2tlcmF1dGh0b2tlbg=='
|
|
const proxyEndpoint = `https://${repositoryUri}`
|
|
const describeRepositoriesStub = sinon.stub()
|
|
const createRepositoryStub = sinon.stub()
|
|
const createRepositoryStubScanOnPush = sinon.stub()
|
|
const baseAwsRequestStubMap = {
|
|
STS: {
|
|
getCallerIdentity: {
|
|
ResponseMetadata: {
|
|
RequestId: 'ffffffff-ffff-ffff-ffff-ffffffffffff',
|
|
},
|
|
UserId: 'XXXXXXXXXXXXXXXXXXXXX',
|
|
Account: '999999999999',
|
|
Arn: 'arn:aws:iam::999999999999:user/test',
|
|
},
|
|
},
|
|
ECR: {
|
|
describeRepositories: {
|
|
repositories: [{ repositoryUri }],
|
|
},
|
|
getAuthorizationToken: {
|
|
authorizationData: [
|
|
{
|
|
proxyEndpoint,
|
|
authorizationToken,
|
|
},
|
|
],
|
|
},
|
|
},
|
|
}
|
|
const spawnExtStub = sinon.stub().returns({
|
|
stdBuffer: `digest: sha256:${imageSha} size: 1787`,
|
|
})
|
|
const modulesCacheStub = {
|
|
'child-process-ext/spawn': spawnExtStub,
|
|
'./lib/utils/telemetry/generate-payload.js': () => ({}),
|
|
}
|
|
|
|
beforeEach(() => {
|
|
describeRepositoriesStub.reset()
|
|
createRepositoryStub.reset()
|
|
spawnExtStub.resetHistory()
|
|
})
|
|
|
|
it('should work correctly when repository exists beforehand', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.resolves({
|
|
repositories: [{ repositoryUri }],
|
|
}),
|
|
createRepository: createRepositoryStub,
|
|
},
|
|
}
|
|
const {
|
|
awsNaming,
|
|
cfTemplate,
|
|
fixtureData: { servicePath: serviceDir },
|
|
} = await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub,
|
|
})
|
|
|
|
const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo')
|
|
const functionCfConfig =
|
|
cfTemplate.Resources[functionCfLogicalId].Properties
|
|
const versionCfConfig = findVersionCfConfig(
|
|
cfTemplate.Resources,
|
|
functionCfLogicalId,
|
|
)
|
|
|
|
expect(functionCfConfig.Code.ImageUri).to.deep.equal(
|
|
`${repositoryUri}@sha256:${imageSha}`,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
expect(describeRepositoriesStub).to.be.calledOnce
|
|
expect(createRepositoryStub.notCalled).to.be.true
|
|
expect(spawnExtStub).to.be.calledWith('docker', ['--version'])
|
|
expect(spawnExtStub).not.to.be.calledWith('docker', [
|
|
'login',
|
|
'--username',
|
|
'AWS',
|
|
'--password',
|
|
'dockerauthtoken',
|
|
proxyEndpoint,
|
|
])
|
|
expect(spawnExtStub).to.be.calledWith('docker', [
|
|
'build',
|
|
'-t',
|
|
`${awsNaming.getEcrRepositoryName()}:baseimage`,
|
|
'-f',
|
|
path.join(serviceDir, 'Dockerfile'),
|
|
'./',
|
|
])
|
|
expect(spawnExtStub).to.be.calledWith('docker', [
|
|
'tag',
|
|
`${awsNaming.getEcrRepositoryName()}:baseimage`,
|
|
`${repositoryUri}:baseimage`,
|
|
])
|
|
expect(spawnExtStub).to.be.calledWith('docker', [
|
|
'push',
|
|
`${repositoryUri}:baseimage`,
|
|
])
|
|
})
|
|
|
|
it('should work correctly when repository does not exist beforehand and scanOnPush is set', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.throws({
|
|
providerError: { code: 'RepositoryNotFoundException' },
|
|
}),
|
|
createRepository: createRepositoryStubScanOnPush.resolves({
|
|
repository: { repositoryUri },
|
|
}),
|
|
},
|
|
}
|
|
|
|
const { awsNaming, cfTemplate } = await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub,
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
scanOnPush: true,
|
|
images: {
|
|
baseimage: {
|
|
path: './',
|
|
file: 'Dockerfile.dev',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo')
|
|
const functionCfConfig =
|
|
cfTemplate.Resources[functionCfLogicalId].Properties
|
|
const versionCfConfig = findVersionCfConfig(
|
|
cfTemplate.Resources,
|
|
functionCfLogicalId,
|
|
)
|
|
|
|
expect(functionCfConfig.Code.ImageUri).to.deep.equal(
|
|
`${repositoryUri}@sha256:${imageSha}`,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
expect(describeRepositoriesStub).to.be.calledOnce
|
|
expect(createRepositoryStubScanOnPush).to.be.calledOnce
|
|
expect(
|
|
createRepositoryStubScanOnPush.args[0][0].imageScanningConfiguration,
|
|
).to.deep.equal({
|
|
scanOnPush: true,
|
|
})
|
|
})
|
|
|
|
it('should work correctly when repository does not exist beforehand', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.throws({
|
|
providerError: { code: 'RepositoryNotFoundException' },
|
|
}),
|
|
createRepository: createRepositoryStub.resolves({
|
|
repository: { repositoryUri },
|
|
}),
|
|
},
|
|
}
|
|
|
|
const { awsNaming, cfTemplate } = await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub,
|
|
})
|
|
|
|
const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo')
|
|
const functionCfConfig =
|
|
cfTemplate.Resources[functionCfLogicalId].Properties
|
|
const versionCfConfig = findVersionCfConfig(
|
|
cfTemplate.Resources,
|
|
functionCfLogicalId,
|
|
)
|
|
|
|
expect(functionCfConfig.Code.ImageUri).to.deep.equal(
|
|
`${repositoryUri}@sha256:${imageSha}`,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
expect(describeRepositoriesStub).to.be.calledOnce
|
|
expect(createRepositoryStub).to.be.calledOnce
|
|
})
|
|
|
|
it('should login and retry when docker push fails with no basic auth credentials error', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.resolves({
|
|
repositories: [{ repositoryUri }],
|
|
}),
|
|
createRepository: createRepositoryStub,
|
|
},
|
|
}
|
|
const innerSpawnExtStub = sinon
|
|
.stub()
|
|
.returns({
|
|
stdBuffer: `digest: sha256:${imageSha} size: 1787`,
|
|
})
|
|
.onCall(3)
|
|
.throws({ stdBuffer: 'no basic auth credentials' })
|
|
const {
|
|
awsNaming,
|
|
cfTemplate,
|
|
fixtureData: { servicePath: serviceDir },
|
|
} = await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub: {
|
|
...modulesCacheStub,
|
|
'child-process-ext/spawn': innerSpawnExtStub,
|
|
},
|
|
})
|
|
|
|
const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo')
|
|
const functionCfConfig =
|
|
cfTemplate.Resources[functionCfLogicalId].Properties
|
|
const versionCfConfig = findVersionCfConfig(
|
|
cfTemplate.Resources,
|
|
functionCfLogicalId,
|
|
)
|
|
|
|
expect(functionCfConfig.Code.ImageUri).to.deep.equal(
|
|
`${repositoryUri}@sha256:${imageSha}`,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
expect(describeRepositoriesStub).to.be.calledOnce
|
|
expect(createRepositoryStub.notCalled).to.be.true
|
|
expect(innerSpawnExtStub).to.be.calledWith('docker', ['--version'])
|
|
expect(innerSpawnExtStub).to.be.calledWith('docker', [
|
|
'build',
|
|
'-t',
|
|
`${awsNaming.getEcrRepositoryName()}:baseimage`,
|
|
'-f',
|
|
path.join(serviceDir, 'Dockerfile'),
|
|
'./',
|
|
])
|
|
expect(innerSpawnExtStub).to.be.calledWith('docker', [
|
|
'tag',
|
|
`${awsNaming.getEcrRepositoryName()}:baseimage`,
|
|
`${repositoryUri}:baseimage`,
|
|
])
|
|
expect(innerSpawnExtStub).to.be.calledWith('docker', [
|
|
'push',
|
|
`${repositoryUri}:baseimage`,
|
|
])
|
|
expect(innerSpawnExtStub).to.be.calledWith('docker', [
|
|
'login',
|
|
'--username',
|
|
'AWS',
|
|
'--password',
|
|
'dockerauthtoken',
|
|
proxyEndpoint,
|
|
])
|
|
})
|
|
|
|
it('should login and retry when docker push fails with token has expired error', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.resolves({
|
|
repositories: [{ repositoryUri }],
|
|
}),
|
|
createRepository: createRepositoryStub,
|
|
},
|
|
}
|
|
const innerSpawnExtStub = sinon
|
|
.stub()
|
|
.returns({
|
|
stdBuffer: `digest: sha256:${imageSha} size: 1787`,
|
|
})
|
|
.onCall(3)
|
|
.throws({ stdBuffer: 'authorization token has expired' })
|
|
await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub: {
|
|
...modulesCacheStub,
|
|
'child-process-ext/spawn': innerSpawnExtStub,
|
|
},
|
|
})
|
|
|
|
expect(innerSpawnExtStub).to.be.calledWith('docker', [
|
|
'push',
|
|
`${repositoryUri}:baseimage`,
|
|
])
|
|
expect(innerSpawnExtStub).to.be.calledWith('docker', [
|
|
'login',
|
|
'--username',
|
|
'AWS',
|
|
'--password',
|
|
'dockerauthtoken',
|
|
proxyEndpoint,
|
|
])
|
|
})
|
|
|
|
it('should work correctly when image is defined with `buildOptions` set', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.resolves({
|
|
repositories: [{ repositoryUri }],
|
|
}),
|
|
createRepository: createRepositoryStub,
|
|
},
|
|
}
|
|
const {
|
|
awsNaming,
|
|
cfTemplate,
|
|
fixtureData: { servicePath: serviceDir },
|
|
} = await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub,
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
baseimage: {
|
|
path: './',
|
|
file: 'Dockerfile.dev',
|
|
buildOptions: ['--ssh', 'default=/path/to/file'],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo')
|
|
const functionCfConfig =
|
|
cfTemplate.Resources[functionCfLogicalId].Properties
|
|
const versionCfConfig = Object.values(cfTemplate.Resources).find(
|
|
(resource) =>
|
|
resource.Type === 'AWS::Lambda::Version' &&
|
|
resource.Properties.FunctionName.Ref === functionCfLogicalId,
|
|
).Properties
|
|
|
|
expect(functionCfConfig.Code.ImageUri).to.deep.equal(
|
|
`${repositoryUri}@sha256:${imageSha}`,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
expect(describeRepositoriesStub).to.be.calledOnce
|
|
expect(createRepositoryStub.notCalled).to.be.true
|
|
expect(spawnExtStub).to.be.calledWith('docker', [
|
|
'build',
|
|
'-t',
|
|
`${awsNaming.getEcrRepositoryName()}:baseimage`,
|
|
'-f',
|
|
path.join(serviceDir, 'Dockerfile.dev'),
|
|
'--ssh',
|
|
'default=/path/to/file',
|
|
'./',
|
|
])
|
|
})
|
|
|
|
it('should work correctly when image is defined with implicit path in provider', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.resolves({
|
|
repositories: [{ repositoryUri }],
|
|
}),
|
|
createRepository: createRepositoryStub,
|
|
},
|
|
}
|
|
const { awsNaming, cfTemplate } = await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub,
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
baseimage: './',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo')
|
|
const functionCfConfig =
|
|
cfTemplate.Resources[functionCfLogicalId].Properties
|
|
const versionCfConfig = Object.values(cfTemplate.Resources).find(
|
|
(resource) =>
|
|
resource.Type === 'AWS::Lambda::Version' &&
|
|
resource.Properties.FunctionName.Ref === functionCfLogicalId,
|
|
).Properties
|
|
|
|
expect(functionCfConfig.Code.ImageUri).to.deep.equal(
|
|
`${repositoryUri}@sha256:${imageSha}`,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
expect(describeRepositoriesStub).to.be.calledOnce
|
|
expect(createRepositoryStub.notCalled).to.be.true
|
|
})
|
|
|
|
it('should work correctly when image is defined with `file` set', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.resolves({
|
|
repositories: [{ repositoryUri }],
|
|
}),
|
|
createRepository: createRepositoryStub,
|
|
},
|
|
}
|
|
const {
|
|
awsNaming,
|
|
cfTemplate,
|
|
fixtureData: { servicePath: serviceDir },
|
|
} = await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub,
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
baseimage: {
|
|
path: './',
|
|
file: 'Dockerfile.dev',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo')
|
|
const functionCfConfig =
|
|
cfTemplate.Resources[functionCfLogicalId].Properties
|
|
const versionCfConfig = Object.values(cfTemplate.Resources).find(
|
|
(resource) =>
|
|
resource.Type === 'AWS::Lambda::Version' &&
|
|
resource.Properties.FunctionName.Ref === functionCfLogicalId,
|
|
).Properties
|
|
|
|
expect(functionCfConfig.Code.ImageUri).to.deep.equal(
|
|
`${repositoryUri}@sha256:${imageSha}`,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
expect(describeRepositoriesStub).to.be.calledOnce
|
|
expect(createRepositoryStub.notCalled).to.be.true
|
|
expect(spawnExtStub).to.be.calledWith('docker', [
|
|
'build',
|
|
'-t',
|
|
`${awsNaming.getEcrRepositoryName()}:baseimage`,
|
|
'-f',
|
|
path.join(serviceDir, 'Dockerfile.dev'),
|
|
'./',
|
|
])
|
|
})
|
|
|
|
it('should work correctly when image is defined with `cacheFrom` set', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.resolves({
|
|
repositories: [{ repositoryUri }],
|
|
}),
|
|
createRepository: createRepositoryStub,
|
|
},
|
|
}
|
|
const {
|
|
awsNaming,
|
|
cfTemplate,
|
|
fixtureData: { servicePath: serviceDir },
|
|
} = await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub,
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
baseimage: {
|
|
path: './',
|
|
file: 'Dockerfile.dev',
|
|
cacheFrom: ['my-image:latest'],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo')
|
|
const functionCfConfig =
|
|
cfTemplate.Resources[functionCfLogicalId].Properties
|
|
const versionCfConfig = Object.values(cfTemplate.Resources).find(
|
|
(resource) =>
|
|
resource.Type === 'AWS::Lambda::Version' &&
|
|
resource.Properties.FunctionName.Ref === functionCfLogicalId,
|
|
).Properties
|
|
|
|
expect(functionCfConfig.Code.ImageUri).to.deep.equal(
|
|
`${repositoryUri}@sha256:${imageSha}`,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
expect(describeRepositoriesStub).to.be.calledOnce
|
|
expect(createRepositoryStub.notCalled).to.be.true
|
|
expect(spawnExtStub).to.be.calledWith('docker', [
|
|
'build',
|
|
'-t',
|
|
`${awsNaming.getEcrRepositoryName()}:baseimage`,
|
|
'-f',
|
|
path.join(serviceDir, 'Dockerfile.dev'),
|
|
'--cache-from',
|
|
'my-image:latest',
|
|
'./',
|
|
])
|
|
})
|
|
|
|
it('should work correctly when image is defined with `buildArgs` set', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.resolves({
|
|
repositories: [{ repositoryUri }],
|
|
}),
|
|
createRepository: createRepositoryStub,
|
|
},
|
|
}
|
|
const {
|
|
awsNaming,
|
|
cfTemplate,
|
|
fixtureData: { servicePath: serviceDir },
|
|
} = await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub,
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
baseimage: {
|
|
path: './',
|
|
file: 'Dockerfile.dev',
|
|
buildArgs: {
|
|
TESTKEY: 'TESTVAL',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo')
|
|
const functionCfConfig =
|
|
cfTemplate.Resources[functionCfLogicalId].Properties
|
|
const versionCfConfig = Object.values(cfTemplate.Resources).find(
|
|
(resource) =>
|
|
resource.Type === 'AWS::Lambda::Version' &&
|
|
resource.Properties.FunctionName.Ref === functionCfLogicalId,
|
|
).Properties
|
|
|
|
expect(functionCfConfig.Code.ImageUri).to.deep.equal(
|
|
`${repositoryUri}@sha256:${imageSha}`,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
expect(describeRepositoriesStub).to.be.calledOnce
|
|
expect(createRepositoryStub.notCalled).to.be.true
|
|
expect(spawnExtStub).to.be.calledWith('docker', [
|
|
'build',
|
|
'-t',
|
|
`${awsNaming.getEcrRepositoryName()}:baseimage`,
|
|
'-f',
|
|
path.join(serviceDir, 'Dockerfile.dev'),
|
|
'--build-arg',
|
|
'TESTKEY=TESTVAL',
|
|
'./',
|
|
])
|
|
})
|
|
|
|
it('should work correctly when image is defined with `platform` set', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.resolves({
|
|
repositories: [{ repositoryUri }],
|
|
}),
|
|
createRepository: createRepositoryStub,
|
|
},
|
|
}
|
|
const {
|
|
awsNaming,
|
|
cfTemplate,
|
|
fixtureData: { servicePath: serviceDir },
|
|
} = await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub,
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
baseimage: {
|
|
path: './',
|
|
file: 'Dockerfile.dev',
|
|
platform: 'TESTVAL',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo')
|
|
const functionCfConfig =
|
|
cfTemplate.Resources[functionCfLogicalId].Properties
|
|
const versionCfConfig = Object.values(cfTemplate.Resources).find(
|
|
(resource) =>
|
|
resource.Type === 'AWS::Lambda::Version' &&
|
|
resource.Properties.FunctionName.Ref === functionCfLogicalId,
|
|
).Properties
|
|
|
|
expect(functionCfConfig.Code.ImageUri).to.deep.equal(
|
|
`${repositoryUri}@sha256:${imageSha}`,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
expect(describeRepositoriesStub).to.be.calledOnce
|
|
expect(createRepositoryStub.notCalled).to.be.true
|
|
expect(spawnExtStub).to.be.calledWith('docker', [
|
|
'build',
|
|
'-t',
|
|
`${awsNaming.getEcrRepositoryName()}:baseimage`,
|
|
'-f',
|
|
path.join(serviceDir, 'Dockerfile.dev'),
|
|
'./',
|
|
'--platform=TESTVAL',
|
|
])
|
|
})
|
|
|
|
it('should work correctly when `functions[].image` is defined with explicit name', async () => {
|
|
const awsRequestStubMap = {
|
|
...baseAwsRequestStubMap,
|
|
ECR: {
|
|
...baseAwsRequestStubMap.ECR,
|
|
describeRepositories: describeRepositoriesStub.resolves({
|
|
repositories: [{ repositoryUri }],
|
|
}),
|
|
createRepository: createRepositoryStub,
|
|
},
|
|
}
|
|
const { awsNaming, cfTemplate } = await runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap,
|
|
modulesCacheStub,
|
|
configExt: {
|
|
provider: {
|
|
ecr: {
|
|
images: {
|
|
baseimage: './',
|
|
},
|
|
},
|
|
},
|
|
functions: {
|
|
foo: {
|
|
image: {
|
|
name: 'baseimage',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo')
|
|
const functionCfConfig =
|
|
cfTemplate.Resources[functionCfLogicalId].Properties
|
|
const versionCfConfig = findVersionCfConfig(
|
|
cfTemplate.Resources,
|
|
functionCfLogicalId,
|
|
)
|
|
expect(functionCfConfig.Code.ImageUri).to.deep.equal(
|
|
`${repositoryUri}@sha256:${imageSha}`,
|
|
)
|
|
expect(versionCfConfig.CodeSha256).to.equal(imageSha)
|
|
})
|
|
|
|
it('should fail when docker command is not available', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap: baseAwsRequestStubMap,
|
|
modulesCacheStub: {
|
|
'child-process-ext/spawn': sinon.stub().throws(),
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'DOCKER_COMMAND_NOT_AVAILABLE',
|
|
)
|
|
})
|
|
|
|
it('should fail when docker build fails', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap: baseAwsRequestStubMap,
|
|
modulesCacheStub: {
|
|
...modulesCacheStub,
|
|
'child-process-ext/spawn': sinon
|
|
.stub()
|
|
.returns({})
|
|
.onSecondCall()
|
|
.throws(),
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'DOCKER_BUILD_ERROR',
|
|
)
|
|
})
|
|
|
|
it('should fail when docker tag fails', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap: baseAwsRequestStubMap,
|
|
modulesCacheStub: {
|
|
...modulesCacheStub,
|
|
'child-process-ext/spawn': sinon
|
|
.stub()
|
|
.returns({})
|
|
.onCall(2)
|
|
.throws(),
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'DOCKER_TAG_ERROR',
|
|
)
|
|
})
|
|
|
|
it('should fail when docker push fails', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap: baseAwsRequestStubMap,
|
|
modulesCacheStub: {
|
|
...modulesCacheStub,
|
|
'child-process-ext/spawn': sinon
|
|
.stub()
|
|
.returns({})
|
|
.onCall(3)
|
|
.throws(),
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'DOCKER_PUSH_ERROR',
|
|
)
|
|
})
|
|
|
|
it('should fail when docker login fails', async () => {
|
|
await expect(
|
|
runServerless({
|
|
fixture: 'ecr',
|
|
command: 'package',
|
|
awsRequestStubMap: baseAwsRequestStubMap,
|
|
modulesCacheStub: {
|
|
...modulesCacheStub,
|
|
'child-process-ext/spawn': sinon
|
|
.stub()
|
|
.returns({})
|
|
.onCall(3)
|
|
.throws({ stdBuffer: 'no basic auth credentials' })
|
|
.onCall(4)
|
|
.throws(),
|
|
},
|
|
}),
|
|
).to.be.eventually.rejected.and.have.property(
|
|
'code',
|
|
'DOCKER_LOGIN_ERROR',
|
|
)
|
|
})
|
|
})
|
|
})
|
|
})
|