|
1 | 1 | import { promises as fs, existsSync } from 'fs'; |
2 | 2 | import * as os from 'os'; |
3 | 3 | import * as path from 'path'; |
4 | | -import { integTest, cloneDirectory, shell, withDefaultFixture, retry, sleep, randomInteger, withSamIntegrationFixture, RESOURCES_DIR, withCDKMigrateFixture, withExtendedTimeoutFixture } from '../../lib'; |
| 4 | +import { integTest, cloneDirectory, shell, withDefaultFixture, retry, sleep, randomInteger, withSamIntegrationFixture, RESOURCES_DIR, withCDKMigrateFixture, withExtendedTimeoutFixture, randomString } from '../../lib'; |
5 | 5 |
|
6 | 6 | jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime |
7 | 7 |
|
@@ -944,6 +944,50 @@ integTest('cdk diff --quiet does not print \'There were no differences\' message |
944 | 944 | expect(diff).not.toContain('There were no differences'); |
945 | 945 | })); |
946 | 946 |
|
| 947 | +integTest('cdk diff picks up changes that are only present in changeset', withDefaultFixture(async (fixture) => { |
| 948 | + // GIVEN |
| 949 | + await fixture.aws.ssm('putParameter', { |
| 950 | + Name: 'for-queue-name-defined-by-ssm-param', |
| 951 | + Value: randomString(), |
| 952 | + Type: 'String', |
| 953 | + Overwrite: true, |
| 954 | + }); |
| 955 | + |
| 956 | + try { |
| 957 | + await fixture.cdkDeploy('queue-name-defined-by-ssm-param'); |
| 958 | + |
| 959 | + // WHEN |
| 960 | + // We want to change the ssm value. Then the CFN changeset will detect that the queue will be changed upon deploy. |
| 961 | + await fixture.aws.ssm('putParameter', { |
| 962 | + Name: 'for-queue-name-defined-by-ssm-param', |
| 963 | + Value: randomString(), |
| 964 | + Type: 'String', |
| 965 | + Overwrite: true, |
| 966 | + }); |
| 967 | + |
| 968 | + const diff = await fixture.cdk(['diff', fixture.fullStackName('queue-name-defined-by-ssm-param')]); |
| 969 | + |
| 970 | + // THEN |
| 971 | + const normalizedPlainTextOutput = diff.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '') // remove all color and formatting (bolding, italic, etc) |
| 972 | + .replace(/ /g, '') // remove all spaces |
| 973 | + .replace(/\n/g, '') // remove all new lines |
| 974 | + .replace(/\d+/g, ''); // remove all digits |
| 975 | + |
| 976 | + const normalizedExpectedOutput = ` |
| 977 | + Resources |
| 978 | + [~] AWS::SQS::Queue DiffFromChangeSetQueue DiffFromChangeSetQueue06622C07 replace |
| 979 | + └─ [~] QueueName (requires replacement) |
| 980 | + [~] AWS::SSM::Parameter DiffFromChangeSetSSMParam DiffFromChangeSetSSMParam92A9A723 |
| 981 | + └─ [~] Value` |
| 982 | + .replace(/ /g, '') |
| 983 | + .replace(/\n/g, '') |
| 984 | + .replace(/\d+/g, ''); |
| 985 | + expect(normalizedPlainTextOutput).toContain(normalizedExpectedOutput); |
| 986 | + } finally { |
| 987 | + await fixture.cdkDestroy('queue-name-defined-by-ssm-param'); |
| 988 | + } |
| 989 | +})); |
| 990 | + |
947 | 991 | integTest('deploy stack with docker asset', withDefaultFixture(async (fixture) => { |
948 | 992 | await fixture.cdkDeploy('docker'); |
949 | 993 | })); |
@@ -1575,37 +1619,60 @@ integTest('skips notice refresh', withDefaultFixture(async (fixture) => { |
1575 | 1619 | })); |
1576 | 1620 |
|
1577 | 1621 | /** |
1578 | | - * Create a queue with a fresh name, redeploy orphaning the queue, then import it again |
| 1622 | + * Create a queue, orphan that queue, then import the queue. |
| 1623 | + * |
| 1624 | + * We want to test with a large template to make sure large templates can work with import. |
1579 | 1625 | */ |
1580 | 1626 | integTest('test resource import', withDefaultFixture(async (fixture) => { |
1581 | | - const outputsFile = path.join(fixture.integTestDir, 'outputs', 'outputs.json'); |
| 1627 | + // GIVEN |
| 1628 | + const randomPrefix = randomString(); |
| 1629 | + const uniqueOutputsFileName = `${randomPrefix}Outputs.json`; // other tests use the outputs file. Make sure we don't collide. |
| 1630 | + const outputsFile = path.join(fixture.integTestDir, 'outputs', uniqueOutputsFileName); |
1582 | 1631 | await fs.mkdir(path.dirname(outputsFile), { recursive: true }); |
1583 | 1632 |
|
1584 | | - // Initial deploy |
| 1633 | + // First, create a stack that includes many queues, and one queue that will be removed from the stack but NOT deleted from AWS. |
1585 | 1634 | await fixture.cdkDeploy('importable-stack', { |
1586 | | - modEnv: { ORPHAN_TOPIC: '1' }, |
| 1635 | + modEnv: { LARGE_TEMPLATE: '1', INCLUDE_SINGLE_QUEUE: '1', RETAIN_SINGLE_QUEUE: '1' }, |
1587 | 1636 | options: ['--outputs-file', outputsFile], |
1588 | 1637 | }); |
1589 | 1638 |
|
1590 | | - const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString()); |
1591 | | - const queueName = outputs.QueueName; |
1592 | | - const queueLogicalId = outputs.QueueLogicalId; |
1593 | | - fixture.log(`Setup complete, created queue ${queueName}`); |
1594 | 1639 | try { |
1595 | | - // Deploy again, orphaning the queue |
| 1640 | + |
| 1641 | + // Second, now the queue we will remove is in the stack and has a logicalId. We can now make the resource mapping file. |
| 1642 | + // This resource mapping file will be used to tell the import operation what queue to bring into the stack. |
| 1643 | + const fullStackName = fixture.fullStackName('importable-stack'); |
| 1644 | + const outputs = JSON.parse((await fs.readFile(outputsFile, { encoding: 'utf-8' })).toString()); |
| 1645 | + const queueLogicalId = outputs[fullStackName].QueueLogicalId; |
| 1646 | + const queueResourceMap = { |
| 1647 | + [queueLogicalId]: { QueueUrl: outputs[fullStackName].QueueUrl }, |
| 1648 | + }; |
| 1649 | + const mappingFile = path.join(fixture.integTestDir, 'outputs', `${randomPrefix}Mapping.json`); |
| 1650 | + await fs.writeFile( |
| 1651 | + mappingFile, |
| 1652 | + JSON.stringify(queueResourceMap), |
| 1653 | + { encoding: 'utf-8' }, |
| 1654 | + ); |
| 1655 | + |
| 1656 | + // Third, remove the queue from the stack, but don't delete the queue from AWS. |
1596 | 1657 | await fixture.cdkDeploy('importable-stack', { |
1597 | | - modEnv: { OMIT_TOPIC: '1' }, |
| 1658 | + modEnv: { LARGE_TEMPLATE: '1', INCLUDE_SINGLE_QUEUE: '0', RETAIN_SINGLE_QUEUE: '0' }, |
1598 | 1659 | }); |
| 1660 | + const cfnTemplateBeforeImport = await fixture.aws.cloudFormation('getTemplate', { StackName: fullStackName }); |
| 1661 | + expect(cfnTemplateBeforeImport.TemplateBody).not.toContain(queueLogicalId); |
1599 | 1662 |
|
1600 | | - // Write a resource mapping file based on the ID from step one, then run an import |
1601 | | - const mappingFile = path.join(fixture.integTestDir, 'outputs', 'mapping.json'); |
1602 | | - await fs.writeFile(mappingFile, JSON.stringify({ [queueLogicalId]: { QueueName: queueName } }), { encoding: 'utf-8' }); |
| 1663 | + // WHEN |
| 1664 | + await fixture.cdk( |
| 1665 | + ['import', '--resource-mapping', mappingFile, fixture.fullStackName('importable-stack')], |
| 1666 | + { modEnv: { LARGE_TEMPLATE: '1', INCLUDE_SINGLE_QUEUE: '1', RETAIN_SINGLE_QUEUE: '0' } }, |
| 1667 | + ); |
1603 | 1668 |
|
1604 | | - await fixture.cdk(['import', |
1605 | | - '--resource-mapping', mappingFile, |
1606 | | - fixture.fullStackName('importable-stack')]); |
| 1669 | + // THEN |
| 1670 | + const describeStacksResponse = await fixture.aws.cloudFormation('describeStacks', { StackName: fullStackName }); |
| 1671 | + const cfnTemplateAfterImport = await fixture.aws.cloudFormation('getTemplate', { StackName: fullStackName }); |
| 1672 | + expect(describeStacksResponse.Stacks![0].StackStatus).toEqual('IMPORT_COMPLETE'); |
| 1673 | + expect(cfnTemplateAfterImport.TemplateBody).toContain(queueLogicalId); |
1607 | 1674 | } finally { |
1608 | | - // Cleanup |
| 1675 | + // Clean up |
1609 | 1676 | await fixture.cdkDestroy('importable-stack'); |
1610 | 1677 | } |
1611 | 1678 | })); |
|
0 commit comments