diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 127766651e..8e0b29f47a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -20,7 +20,7 @@ src/cli/commands/test/iac-output.ts @snyk/group-infrastructure-as-code src/cli/commands/test/iac-test-shim.ts @snyk/group-infrastructure-as-code src/cli/commands/describe.ts @snyk/group-infrastructure-as-code src/cli/commands/report/ @snyk/group-infrastructure-as-code -src/cli/commands/gen-driftignore.ts @snyk/group-infrastructure-as-code +src/cli/commands/update-exclude-policy.ts @snyk/group-infrastructure-as-code src/cli/commands/apps @snyk/moose src/lib/apps @snyk/moose src/lib/cloud-config-projects.ts @snyk/group-infrastructure-as-code diff --git a/help/cli-commands/iac-gen-driftignore.md b/help/cli-commands/iac-gen-driftignore.md deleted file mode 100644 index e77f558ec2..0000000000 --- a/help/cli-commands/iac-gen-driftignore.md +++ /dev/null @@ -1,66 +0,0 @@ -# IAC gen-driftignore - -## Usage - -`snyk iac gen-driftignore []` - -## Description - -The `snyk iac gen-driftignore` command generates driftignore rules to be used by `snyk iac test`. - -See also the help for [`iac test`](https://docs.snyk.io/snyk-cli/commands/iac-test) and [`iac describe`](https://docs.snyk.io/snyk-cli/commands/iac-describe)`` - -For more information see [Ignore resources](https://docs.snyk.io/products/snyk-infrastructure-as-code/detect-drift-and-manually-created-resources/ignore-resources). - -## Exit codes - -Possible exit codes and their meaning: - -**0**: success, driftignore generated successfully\ -**1**: error, something wrong happened during ignore file generation - -## Configure the Snyk CLI - -You can use environment variables to configure the Snyk CLI and also set variables to configure the Snyk CLI to connect with the Snyk API. See [Configure the Snyk CLI](https://docs.snyk.io/snyk-cli/configure-the-snyk-cli). - -## Debug - -Use the `-d` option to output the debug logs. - -## Options - -### `--input` - -Input from which the JSON should be parsed. - -Default: stdin - -Example: - -``` -$ snyk iac gen-driftignore --input=output.json --output=/dev/stdout -``` - -### `--output=` - -Output file path to which to write the driftignore. - -Default: `.driftignore` - -Example: - -``` -$ snyk iac describe --output=json://output.json -``` - -### `--exclude-changed` - -Exclude resources that changed on cloud provider. - -### `--exclude-missing` - -Exclude missing resources. - -### `--exclude-unmanaged` - -Exclude resources not managed by IaC. diff --git a/help/cli-commands/iac-update-exclude-policy.md b/help/cli-commands/iac-update-exclude-policy.md new file mode 100644 index 0000000000..554c7ad01a --- /dev/null +++ b/help/cli-commands/iac-update-exclude-policy.md @@ -0,0 +1,46 @@ +# snyk iac update-exclude-policy -- generate ignore rules based on scan result + +## Usage + +`snyk iac update-exclude-policy []` + +## Description + +The `snyk iac update-exclude-policy` can generate exclude policy rules to be used by `snyk iac scan`. + +## Exit codes + +Possible exit codes and their meaning: + +**0**: success, exclude rules generated successfully +**1**: error, something wrong happened during exclude rules generation + +## Configure the Snyk CLI + +You can use environment variables to configure the Snyk CLI and also set variables to configure the Snyk CLI to connect with the Snyk API. +See [Configure the Snyk CLI](https://docs.snyk.io/features/snyk-cli/configure-the-snyk-cli). + +## Debug + +Use the `-d` option to output the debug logs. + +## Options + +### `--exclude-changed` + +Exclude resources that changed on cloud provider + +### `--exclude-missing` + +Exclude missing resources + +### `--exclude-unmanaged` + +Exclude resources not managed by IaC + +## Usage + +``` +$ snyk iac scan --output=json://output.json +$ snyk iac describe --json --all | snyk iac update-exclude-policy +``` diff --git a/src/cli/commands/gen-driftignore.ts b/src/cli/commands/gen-driftignore.ts deleted file mode 100644 index 964444ed5d..0000000000 --- a/src/cli/commands/gen-driftignore.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { MethodArgs } from '../args'; -import { processCommandArgs } from './process-command-args'; -import * as legacyError from '../../lib/errors/legacy-errors'; -import { runDriftCTL } from '../../lib/iac/drift'; -import { getIacOrgSettings } from './test/iac-local-execution/org-settings/get-iac-org-settings'; -import { UnsupportedEntitlementCommandError } from './test/iac-local-execution/assert-iac-options-flag'; -import config from '../../lib/config'; - -export default async (...args: MethodArgs): Promise => { - const { options } = processCommandArgs(...args); - - // Ensure that this gen-driftignore command can only be runned when using `snyk iac gen-driftignore` - // Avoid `snyk gen-driftignore` direct usage - if (options.iac != true) { - return legacyError('gen-driftignore'); - } - - // Ensure that we are allowed to run that command - // by checking the entitlement - const orgPublicId = options.org ?? config.org; - const iacOrgSettings = await getIacOrgSettings(orgPublicId); - if (!iacOrgSettings.entitlements?.iacDrift) { - throw new UnsupportedEntitlementCommandError('gen-driftignore', 'iacDrift'); - } - - try { - const ret = await runDriftCTL({ - options: { kind: 'gen-driftignore', ...options }, - stdio: 'inherit', - }); - process.exit(ret.code); - } catch (e) { - const err = new Error('Error running `iac gen-driftignore` ' + e); - return Promise.reject(err); - } -}; diff --git a/src/cli/commands/index.js b/src/cli/commands/index.js index c71f15158c..597a57e709 100644 --- a/src/cli/commands/index.js +++ b/src/cli/commands/index.js @@ -9,8 +9,8 @@ async function callModule(mod, args) { const commands = { auth: async (...args) => callModule(import('./auth'), args), config: async (...args) => callModule(import('./config'), args), - 'gen-driftignore': async (...args) => - callModule(import('./gen-driftignore'), args), + 'update-exclude-policy': async (...args) => + callModule(import('./update-exclude-policy'), args), describe: async (...args) => callModule(import('./describe'), args), help: async (...args) => callModule(import('./help'), args), ignore: async (...args) => callModule(import('./ignore'), args), diff --git a/src/cli/commands/update-exclude-policy.ts b/src/cli/commands/update-exclude-policy.ts new file mode 100644 index 0000000000..e6eb0233ae --- /dev/null +++ b/src/cli/commands/update-exclude-policy.ts @@ -0,0 +1,57 @@ +import { MethodArgs } from '../args'; +import { processCommandArgs } from './process-command-args'; +import * as legacyError from '../../lib/errors/legacy-errors'; +import * as fs from 'fs'; +import * as snykPolicyLib from 'snyk-policy'; +import { getIacOrgSettings } from './test/iac-local-execution/org-settings/get-iac-org-settings'; +import { UnsupportedEntitlementCommandError } from './test/iac-local-execution/assert-iac-options-flag'; +import config from '../../lib/config'; +import { + parseDriftAnalysisResults, + updateExcludeInPolicy, +} from '../../lib/iac/drift'; +import { Policy } from '../../lib/policy/find-and-load-policy'; + +export default async (...args: MethodArgs): Promise => { + const { options } = processCommandArgs(...args); + + // Ensure that this update-exclude-policy command can only be runned when using `snyk iac update-exclude-policy` + // Avoid `snyk update-exclude-policy` direct usage + if (options.iac != true) { + return legacyError('update-exclude-policy'); + } + + // Ensure that we are allowed to run that command + // by checking the entitlement + const orgPublicId = options.org ?? config.org; + const iacOrgSettings = await getIacOrgSettings(orgPublicId); + if (!iacOrgSettings.entitlements?.iacDrift) { + throw new UnsupportedEntitlementCommandError( + 'update-exclude-policy', + 'iacDrift', + ); + } + + try { + // There's an open bug for this in Windows in the current version of node when called with no stdinput. + // See https://github.com/nodejs/node/issues/19831 + // The actual error handling behavior is enough for now but may be improved if needed + const analysis = parseDriftAnalysisResults(fs.readFileSync(0).toString()); + let policy: Policy; + try { + policy = await snykPolicyLib.load(); + } catch (error) { + if (error.code === 'ENOENT') { + // policy file does not exist - create it + policy = await snykPolicyLib.create(); + } else { + throw error; + } + } + await updateExcludeInPolicy(policy, analysis, options); + await snykPolicyLib.save(policy); + } catch (e) { + const err = new Error('Error running `iac update-exclude-policy` ' + e); + return Promise.reject(err); + } +}; diff --git a/src/cli/modes.ts b/src/cli/modes.ts index 197ef4f064..a0b91faebe 100644 --- a/src/cli/modes.ts +++ b/src/cli/modes.ts @@ -23,7 +23,7 @@ const modes: Record = { }, }, iac: { - allowedCommands: ['test', 'gen-driftignore', 'describe', 'report'], + allowedCommands: ['test', 'update-exclude-policy', 'describe', 'report'], config: (args): [] => { args['iac'] = true; diff --git a/src/lib/iac/drift.ts b/src/lib/iac/drift.ts index 228cb38050..513c9a52d4 100644 --- a/src/lib/iac/drift.ts +++ b/src/lib/iac/drift.ts @@ -120,10 +120,6 @@ export const generateArgs = ( return generateScanFlags(options as DescribeOptions, driftIgnore); } - if (options.kind === 'gen-driftignore') { - return generateGenDriftIgnoreFlags(options as GenDriftIgnoreOptions); - } - if (options.kind === 'fmt') { return generateFmtFlags(options as FmtOptions); } @@ -131,36 +127,6 @@ export const generateArgs = ( throw 'Unsupported command'; }; -export const generateGenDriftIgnoreFlags = ( - options: GenDriftIgnoreOptions, -): string[] => { - const args: string[] = ['gen-driftignore', ...driftctlDefaultOptions]; - - if (options.input) { - args.push('--input'); - args.push(options.input); - } - - if (options.output) { - args.push('--output'); - args.push(options.output); - } - - if (options['exclude-changed']) { - args.push('--exclude-changed'); - } - - if (options['exclude-missing']) { - args.push('--exclude-missing'); - } - - if (options['exclude-unmanaged']) { - args.push('--exclude-unmanaged'); - } - - return args; -}; - const generateFmtFlags = (options: FmtOptions): string[] => { const args: string[] = ['fmt', ...driftctlDefaultOptions]; @@ -535,6 +501,33 @@ export function driftignoreFromPolicy(policy: Policy | undefined): string[] { return policy.exclude[excludeSection]; } +export const updateExcludeInPolicy = ( + policy: Policy, + analysis: DriftAnalysis, + options: GenDriftIgnoreOptions, +): void => { + const excludedResources = driftignoreFromPolicy(policy); + const addResource = (res) => excludedResources.push(`${res.type}.${res.id}`); + + if (!options['exclude-changed'] && analysis.summary.total_changed > 0) { + analysis.differences?.forEach((change) => addResource(change.res)); + } + + if (!options['exclude-missing'] && analysis.summary.total_missing > 0) { + analysis.missing?.forEach((res) => addResource(res)); + } + + if (!options['exclude-unmanaged'] && analysis.summary.total_unmanaged > 0) { + analysis.unmanaged?.forEach((res) => addResource(res)); + } + + if (!policy.exclude) { + policy.exclude = {}; + } + + policy.exclude['iac-drift'] = excludedResources; +}; + export function processDriftctlOutput( options: DescribeOptions, stdout: string, diff --git a/src/lib/iac/types.d.ts b/src/lib/iac/types.d.ts index a7fc91db47..9943d10f04 100644 --- a/src/lib/iac/types.d.ts +++ b/src/lib/iac/types.d.ts @@ -14,9 +14,7 @@ export interface FmtOptions extends DriftCTLOptions { 'html-file-output': string; } -export interface GenDriftIgnoreOptions extends DriftCTLOptions { - input?: string; - output?: string; +export interface GenDriftIgnoreOptions { 'exclude-changed'?: boolean; 'exclude-missing'?: boolean; 'exclude-unmanaged'?: boolean; diff --git a/src/lib/types.ts b/src/lib/types.ts index 99cd15d7f5..ece4c00b4d 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -257,7 +257,7 @@ export enum SupportedCliCommands { apps = 'apps', drift = 'drift', describe = 'describe', - 'gen-driftignore' = 'gen-driftignore', + 'update-exclude-policy' = 'update-exclude-policy', report = 'report', } diff --git a/test/fixtures/iac/drift/analysis.json b/test/fixtures/iac/drift/analysis.json new file mode 100644 index 0000000000..8e26ffcccb --- /dev/null +++ b/test/fixtures/iac/drift/analysis.json @@ -0,0 +1,75 @@ +{ + "options": { + "deep": true, + "only_managed": false, + "only_unmanaged": false + }, + "summary": { + "total_resources": 6, + "total_changed": 1, + "total_unmanaged": 2, + "total_missing": 2, + "total_managed": 2, + "total_iac_source_count": 3 + }, + "managed": [ + { + "id": "AKIA5QYBVVD25KFXJHYJ", + "type": "aws_iam_access_key" + }, + { + "id": "test-managed", + "type": "aws_iam_user" + } + ], + "unmanaged": [ + { + "id": "driftctl", + "type": "aws_s3_bucket_policy" + }, + { + "id": "driftctl", + "type": "aws_s3_bucket_notification" + } + ], + "missing": [ + { + "id": "test-driftctl2", + "type": "aws_iam_user" + }, + { + "id": "AKIA5QYBVVD2Y6PBAAPY", + "type": "aws_iam_access_key" + } + ], + "differences": [ + { + "res": { + "id": "AKIA5QYBVVD25KFXJHYJ", + "type": "aws_iam_access_key" + }, + "changelog": [ + { + "type": "update", + "path": [ + "status" + ], + "from": "Active", + "to": "Inactive", + "computed": false + } + ] + } + ], + "coverage": 33, + "alerts": { + "aws_iam_access_key": [ + { + "message": "This is an alert" + } + ] + }, + "scan_duration": 123, + "provider_name": "AWS", + "provider_version": "2.18.5" +} \ No newline at end of file diff --git a/test/jest/acceptance/iac/gen-driftignore.spec.ts b/test/jest/acceptance/iac/gen-driftignore.spec.ts deleted file mode 100644 index c9d1e42b8e..0000000000 --- a/test/jest/acceptance/iac/gen-driftignore.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { startMockServer } from './helpers'; -import * as os from 'os'; -import * as path from 'path'; -import { getFixturePath } from '../../util/getFixturePath'; -import * as fs from 'fs'; -import * as uuid from 'uuid'; -import * as rimraf from 'rimraf'; - -jest.setTimeout(50000); - -describe('iac gen-driftignore', () => { - let run: ( - cmd: string, - env: Record, - ) => Promise<{ stdout: string; stderr: string; exitCode: number }>; - let teardown: () => void; - - let tmpFolderPath: string; - let outputFile: string; - beforeEach(() => { - tmpFolderPath = fs.mkdtempSync(path.join(os.tmpdir(), 'dctl-')); - outputFile = path.join(tmpFolderPath, uuid.v4()); - }); - afterEach(() => { - rimraf.sync(tmpFolderPath); - }); - - beforeAll(async () => { - ({ run, teardown } = await startMockServer()); - }); - - afterAll(async () => teardown()); - - it('gen-driftignore fail without the right entitlement', async () => { - const { stdout, stderr, exitCode } = await run( - `snyk iac gen-driftignore --org=no-iac-drift-entitlements`, - {}, - ); - - expect(stdout).toMatch( - 'Command "gen-driftignore" is currently not supported for this org. To enable it, please contact snyk support.', - ); - expect(stderr).toMatch(''); - expect(exitCode).toBe(2); - }); - - if (os.platform() === 'win32') { - return; // skip following tests - } - - it('gen-driftignore successfully executed from SNYK_DRIFTCTL_PATH env var when org has the entitlement', async () => { - const { stderr, exitCode } = await run( - `snyk iac gen-driftignore --input=something.json --output=stdout --exclude-changed --exclude-missing --exclude-unmanaged`, - { - SNYK_FIXTURE_OUTPUT_PATH: outputFile, - SNYK_DRIFTCTL_PATH: path.join( - getFixturePath('iac'), - 'drift', - 'args-echo.sh', - ), - }, - ); - - const output = fs.readFileSync(outputFile).toString(); - - expect(output).toContain('DCTL_IS_SNYK=true'); - expect(output).toContain( - 'ARGS=gen-driftignore --no-version-check --input something.json --output stdout --exclude-changed --exclude-missing --exclude-unmanaged', - ); - expect(stderr).toMatch(''); - expect(exitCode).toBe(0); - }); -}); diff --git a/test/jest/acceptance/iac/update-exclude-policy.spec.ts b/test/jest/acceptance/iac/update-exclude-policy.spec.ts new file mode 100644 index 0000000000..0e1e76f906 --- /dev/null +++ b/test/jest/acceptance/iac/update-exclude-policy.spec.ts @@ -0,0 +1,76 @@ +import { run as Run, startMockServer } from './helpers'; +import * as os from 'os'; +import * as fs from 'fs'; +import * as rimraf from 'rimraf'; +import * as path from 'path'; +import { findAndLoadPolicy } from '../../../../src/lib/policy'; + +jest.setTimeout(50000); + +describe('iac update-exclude-policy', () => { + let run: typeof Run; + let teardown: () => void; + + let tmpFolderPath: string; + beforeEach(() => { + tmpFolderPath = fs.mkdtempSync(path.join(os.tmpdir(), 'dctl-')); + }); + afterEach(() => { + rimraf.sync(tmpFolderPath); + }); + + beforeAll(async () => { + ({ run, teardown } = await startMockServer()); + }); + + afterAll(async () => teardown()); + + it('update-exclude-policy fail without the right entitlement', async () => { + const { stdout, stderr, exitCode } = await run( + `snyk iac update-exclude-policy --org=no-iac-drift-entitlements`, + {}, + ); + + expect(stdout).toMatch( + 'Command "update-exclude-policy" is currently not supported for this org. To enable it, please contact snyk support.', + ); + expect(stderr).toMatch(''); + expect(exitCode).toBe(2); + }); + + if (os.platform() === 'win32') { + return; // skip following tests + } + + it('update-exclude-policy successfully executed when org has the entitlement', async () => { + const analysisPath = path.join( + __dirname, + '../../../fixtures/iac/drift/analysis.json', + ); + + const snykBinaryPath = path.join(__dirname, '../../../../bin/snyk'); + + const { stderr, stdout, exitCode } = await run( + `cat ${analysisPath} | ${snykBinaryPath} iac update-exclude-policy`, + {}, + tmpFolderPath, + ); + + const policy = await findAndLoadPolicy(tmpFolderPath, 'iac', {}); + const expectedExcludes = { + 'iac-drift': [ + 'aws_iam_access_key.AKIA5QYBVVD25KFXJHYJ', + 'aws_iam_user.test-driftctl2', + 'aws_iam_access_key.AKIA5QYBVVD2Y6PBAAPY', + 'aws_s3_bucket_policy.driftctl', + 'aws_s3_bucket_notification.driftctl', + ], + }; + + expect(stdout).toBe(''); + expect(stderr).toMatch(''); + expect(exitCode).toBe(0); + expect(policy).toBeDefined(); + expect(policy?.exclude).toStrictEqual(expectedExcludes); + }); +}); diff --git a/test/jest/unit/cli/commands/help/help.spec.ts b/test/jest/unit/cli/commands/help/help.spec.ts index 0af974ad49..6c329c911f 100644 --- a/test/jest/unit/cli/commands/help/help.spec.ts +++ b/test/jest/unit/cli/commands/help/help.spec.ts @@ -31,12 +31,12 @@ describe('findHelpFile', () => { ).toContain('iac-describe.md'); }); - it('returns correct help markdown path for a documented subcommand with `iac gen-driftignore`', () => { + it('returns correct help markdown path for a documented subcommand with `iac update-exclude-policy`', () => { expect( help.findHelpFile( - ['iac', 'gen-driftignore'], + ['iac', 'update-exclude-policy'], '../../../../help/cli-commands', ), - ).toContain('iac-gen-driftignore.md'); + ).toContain('iac-update-exclude-policy.md'); }); }); diff --git a/test/jest/unit/lib/iac/drift.spec.ts b/test/jest/unit/lib/iac/drift.spec.ts index cad96031d1..07cd542b0d 100644 --- a/test/jest/unit/lib/iac/drift.spec.ts +++ b/test/jest/unit/lib/iac/drift.spec.ts @@ -7,6 +7,7 @@ import { generateArgs, parseDriftAnalysisResults, translateExitCode, + updateExcludeInPolicy, validateArgs, } from '../../../../../src/lib/iac/drift'; import envPaths from 'env-paths'; @@ -48,11 +49,6 @@ describe('driftctl integration', () => { ]); }); - it('gen-driftignore: default arguments are correct', () => { - const args = generateArgs({ kind: 'gen-driftignore' }); - expect(args).toEqual(['gen-driftignore', '--no-version-check']); - }); - it('describe: passing options generate correct arguments', () => { const args = generateArgs( { @@ -151,29 +147,6 @@ describe('driftctl integration', () => { }).toThrow(new DescribeExclusiveArgumentError()); }); - it('gen-driftignore: passing options generate correct arguments', () => { - const args = generateArgs({ - kind: 'gen-driftignore', - 'exclude-changed': true, - 'exclude-missing': true, - 'exclude-unmanaged': true, - input: 'analysis.json', - output: '/dev/stdout', - org: 'testing-org', // Ensure that this should not be translated to args - } as GenDriftIgnoreOptions); - expect(args).toEqual([ - 'gen-driftignore', - '--no-version-check', - '--input', - 'analysis.json', - '--output', - '/dev/stdout', - '--exclude-changed', - '--exclude-missing', - '--exclude-unmanaged', - ]); - }); - it('run driftctl: exit code is translated', () => { expect(translateExitCode(DCTL_EXIT_CODES.EXIT_IN_SYNC)).toEqual(0); expect(translateExitCode(DCTL_EXIT_CODES.EXIT_NOT_IN_SYNC)).toEqual( @@ -319,37 +292,164 @@ describe('drift analytics', () => { expect(addAnalyticsSpy).toHaveBeenCalledWith('iac-drift-scan-scope', 'all'); }); }); -describe('driftignoreFromPolicy', () => { - const loadPolicy = async (name: string): Promise => { - const policyPath = path.join(__dirname, 'fixtures', name); - const policyText = fs.readFileSync(policyPath, 'utf-8'); - return await snykPolicy.loadFromText(policyText); - }; +const loadPolicyFixture = async (name: string): Promise => { + const policyPath = path.join(__dirname, 'fixtures', name); + const policyText = fs.readFileSync(policyPath, 'utf-8'); + return await snykPolicy.loadFromText(policyText); +}; + +describe('driftignoreFromPolicy', () => { it.each([ ['policy undefined', undefined, []], - ['policy with no excludes', loadPolicy('policy-no-excludes.yml'), []], + [ + 'policy with no excludes', + loadPolicyFixture('policy-no-excludes.yml'), + [], + ], [ 'policy with irrelevant excludes', - loadPolicy('policy-irrelevant-excludes.yml'), + loadPolicyFixture('policy-irrelevant-excludes.yml'), [], ], [ 'policy with empty drift excludes', - loadPolicy('policy-empty-drift-excludes.yml'), + loadPolicyFixture('policy-empty-drift-excludes.yml'), [], ], [ 'policy with one drift exclude', - loadPolicy('policy-one-drift-exclude.yml'), + loadPolicyFixture('policy-one-drift-exclude.yml'), ['foo'], ], [ 'policy with several drift excludes', - loadPolicy('policy-several-drift-excludes.yml'), + loadPolicyFixture('policy-several-drift-excludes.yml'), ['*', '!aws_iam_*', 'aws_s3_*', 'aws_s3_bucket.*', 'aws_s3_bucket.name*'], ], ])('%s', async (_, policy, expected) => { expect(driftignoreFromPolicy(await policy)).toEqual(expected); }); }); + +describe('updateExcludeInPolicy', () => { + const analysis = parseDriftAnalysisResults( + fs.readFileSync( + path.join(__dirname, 'fixtures', 'driftctl-analysis.json'), + 'utf-8', + ), + ); + it.each([ + [ + 'policy with no excludes', + 'policy-no-excludes.yml', + {}, + { + 'iac-drift': [ + 'aws_iam_access_key.AKIA5QYBVVD25KFXJHYJ', + 'aws_iam_user.test-driftctl2', + 'aws_iam_access_key.AKIA5QYBVVD2Y6PBAAPY', + 'aws_s3_bucket_policy.driftctl', + 'aws_s3_bucket_notification.driftctl', + ], + }, + ], + [ + 'policy with irrelevant excludes', + 'policy-irrelevant-excludes.yml', + {}, + { + foo: ['bar'], + 'iac-drift': [ + 'aws_iam_access_key.AKIA5QYBVVD25KFXJHYJ', + 'aws_iam_user.test-driftctl2', + 'aws_iam_access_key.AKIA5QYBVVD2Y6PBAAPY', + 'aws_s3_bucket_policy.driftctl', + 'aws_s3_bucket_notification.driftctl', + ], + }, + ], + [ + 'policy with empty drift excludes', + 'policy-empty-drift-excludes.yml', + {}, + { + 'iac-drift': [ + 'aws_iam_access_key.AKIA5QYBVVD25KFXJHYJ', + 'aws_iam_user.test-driftctl2', + 'aws_iam_access_key.AKIA5QYBVVD2Y6PBAAPY', + 'aws_s3_bucket_policy.driftctl', + 'aws_s3_bucket_notification.driftctl', + ], + }, + ], + [ + 'policy with several drift excludes', + 'policy-several-drift-excludes.yml', + {}, + { + 'iac-drift': [ + // Those are existing ones + '*', + '!aws_iam_*', + 'aws_s3_*', + 'aws_s3_bucket.*', + 'aws_s3_bucket.name*', + // Following exclude are the new ones + 'aws_iam_access_key.AKIA5QYBVVD25KFXJHYJ', + 'aws_iam_user.test-driftctl2', + 'aws_iam_access_key.AKIA5QYBVVD2Y6PBAAPY', + 'aws_s3_bucket_policy.driftctl', + 'aws_s3_bucket_notification.driftctl', + ], + }, + ], + [ + 'with exclude changed option', + 'policy-no-excludes.yml', + { + 'exclude-changed': true, + }, + { + 'iac-drift': [ + 'aws_iam_user.test-driftctl2', + 'aws_iam_access_key.AKIA5QYBVVD2Y6PBAAPY', + 'aws_s3_bucket_policy.driftctl', + 'aws_s3_bucket_notification.driftctl', + ], + }, + ], + [ + 'with exclude changed option', + 'policy-no-excludes.yml', + { + 'exclude-missing': true, + }, + { + 'iac-drift': [ + 'aws_iam_access_key.AKIA5QYBVVD25KFXJHYJ', + 'aws_s3_bucket_policy.driftctl', + 'aws_s3_bucket_notification.driftctl', + ], + }, + ], + [ + 'with exclude changed option', + 'policy-no-excludes.yml', + { + 'exclude-unmanaged': true, + }, + { + 'iac-drift': [ + 'aws_iam_access_key.AKIA5QYBVVD25KFXJHYJ', + 'aws_iam_user.test-driftctl2', + 'aws_iam_access_key.AKIA5QYBVVD2Y6PBAAPY', + ], + }, + ], + ])('%s', async (_, policyPath, options: GenDriftIgnoreOptions, expected) => { + const policy = await loadPolicyFixture(policyPath); + updateExcludeInPolicy(policy, analysis, options); + expect(policy.exclude).toEqual(expected); + }); +});