diff --git a/lib/plugins/aws/deploy/lib/check-for-changes.js b/lib/plugins/aws/deploy/lib/check-for-changes.js index 77bc91bc7..5742b770f 100644 --- a/lib/plugins/aws/deploy/lib/check-for-changes.js +++ b/lib/plugins/aws/deploy/lib/check-for-changes.js @@ -21,6 +21,16 @@ function getDeploymentDirectoryTimestamp(directory) { return Number(directory.slice(0, directory.indexOf('-'))); } +function isCloudFormationMissingResourceError(error) { + const message = error && error.message; + return ( + typeof message === 'string' && + (message.includes('does not exist for stack') || + message === 'Resource does not exist' || + (message.startsWith('Resource ') && message.includes(' does not exist'))) + ); +} + module.exports = { async checkForChanges() { this.serverless.service.provider.shouldNotDeploy = false; @@ -413,7 +423,8 @@ module.exports = { ); return physicalResourceId === StackResourceDetail.PhysicalResourceId; - } catch { + } catch (error) { + if (!isCloudFormationMissingResourceError(error)) throw error; return false; } }, diff --git a/test/unit/lib/plugins/aws/deploy/lib/check-for-changes.test.js b/test/unit/lib/plugins/aws/deploy/lib/check-for-changes.test.js index ae7e009ac..ca93a3b95 100644 --- a/test/unit/lib/plugins/aws/deploy/lib/check-for-changes.test.js +++ b/test/unit/lib/plugins/aws/deploy/lib/check-for-changes.test.js @@ -1533,6 +1533,69 @@ describe('test/unit/lib/plugins/aws/deploy/lib/checkForChanges.test.js', () => { expect(deleteStub).to.not.have.been.called; }); + it('treats missing CloudFormation subscription filter resources as external', async () => { + const awsDeploy = createAwsDeployTestInstance(); + const requestStub = sandbox + .stub(awsDeploy.provider, 'request') + .rejects( + new Error( + `Resource ${awsDeploy.provider.naming.getCloudWatchLogLogicalId( + 'Fn1', + 1 + )} does not exist for stack ${awsDeploy.provider.naming.getStackName()}` + ) + ); + + try { + await expect( + awsDeploy.isInternalSubscriptionFilter( + awsDeploy.provider.naming.getStackName(), + awsDeploy.provider.naming.getCloudWatchLogLogicalId('Fn1', 1), + 'physical-id' + ) + ).to.eventually.equal(false); + expect(requestStub).to.have.been.calledOnce; + } finally { + awsDeploy.provider.request.restore(); + } + }); + + it('surfaces CloudFormation access errors during subscription filter classification', async () => { + const awsDeploy = createAwsDeployTestInstance(); + sandbox + .stub(awsDeploy.provider, 'request') + .rejects(Object.assign(new Error('denied'), { code: 'AccessDenied' })); + + try { + await expect( + awsDeploy.isInternalSubscriptionFilter( + awsDeploy.provider.naming.getStackName(), + awsDeploy.provider.naming.getCloudWatchLogLogicalId('Fn1', 1), + 'physical-id' + ) + ).to.be.rejectedWith('denied'); + } finally { + awsDeploy.provider.request.restore(); + } + }); + + it('does not treat missing stacks with Resource in the name as missing resources', async () => { + const awsDeploy = createAwsDeployTestInstance(); + sandbox.stub(awsDeploy.provider, 'request').rejects( + Object.assign(new Error('Stack with id MyResourceStack does not exist'), { + code: 'ValidationError', + }) + ); + + try { + await expect( + awsDeploy.isInternalSubscriptionFilter('MyResourceStack', 'LogicalId', 'physical-id') + ).to.be.rejectedWith('MyResourceStack'); + } finally { + awsDeploy.provider.request.restore(); + } + }); + it('should not attempt to delete filter for 2 subscription filter per log group include externals', async () => { const deleteStub = sandbox.stub(); let serverless;