Skip to content

Commit

Permalink
Merge pull request #3048 from snyk/issues/CFG-1519
Browse files Browse the repository at this point in the history
feat: Add support for IaC project tags and attributes
  • Loading branch information
francescomari committed Mar 25, 2022
2 parents 46781e0 + 03b24df commit 6d19b05
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ const keys: (keyof IaCTestFlags)[] = [
'quiet',
'scan',
'report',

// Tags and attributes
'tags',
'project-tags',
'project-environment',
'project-lifecycle',
'project-business-criticality',
// PolicyOptions
'ignore-policy',
'policy-path',
Expand Down
20 changes: 20 additions & 0 deletions src/cli/commands/test/iac-local-execution/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { isFeatureFlagSupportedForOrg } from '../../../../lib/feature-flags';
import { initRules } from './rules';
import { NoFilesToScanError } from './file-loader';
import { formatAndShareResults } from './share-results';
import { generateProjectAttributes, generateTags } from '../../monitor';

// this method executes the local processing engine and then formats the results to adapt with the CLI output.
// this flow is the default GA flow for IAC scanning.
Expand All @@ -47,6 +48,11 @@ export async function test(
throw new UnsupportedEntitlementError('infrastructureAsCode');
}

// Parse tags and attributes right now, so we can exit early if the user
// provided invalid values.
const tags = parseTags(options);
const attributes = parseAttributes(options);

const rulesOrigin = await initRules(iacOrgSettings, options);

const policy = await findAndLoadPolicy(pathToScan, 'iac', options);
Expand Down Expand Up @@ -130,6 +136,8 @@ export async function test(
options,
orgPublicId,
policy,
tags,
attributes,
);
}

Expand Down Expand Up @@ -186,3 +194,15 @@ export function removeFileContent({
projectType,
};
}

function parseTags(options: IaCTestFlags) {
if (options.report) {
return generateTags(options);
}
}

function parseAttributes(options: IaCTestFlags) {
if (options.report) {
return generateProjectAttributes(options);
}
}
5 changes: 4 additions & 1 deletion src/cli/commands/test/iac-local-execution/share-results.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isFeatureFlagSupportedForOrg } from '../../../../lib/feature-flags';
import { shareResults } from '../../../../lib/iac/cli-share-results';
import { Policy } from '../../../../lib/policy/find-and-load-policy';
import { ProjectAttributes, Tag } from '../../../../lib/types';
import { FeatureFlagError } from './assert-iac-options-flag';
import { formatShareResults } from './share-results-formatter';
import { IacFileScanResult, IaCTestFlags } from './types';
Expand All @@ -10,6 +11,8 @@ export async function formatAndShareResults(
options: IaCTestFlags,
orgPublicId: string,
policy: Policy | undefined,
tags?: Tag[],
attributes?: ProjectAttributes,
): Promise<Record<string, string>> {
const isCliReportEnabled = await isFeatureFlagSupportedForOrg(
'iacCliShareResults',
Expand All @@ -21,5 +24,5 @@ export async function formatAndShareResults(

const formattedResults = formatShareResults(results, options);

return await shareResults(formattedResults, policy);
return await shareResults(formattedResults, policy, tags, attributes);
}
7 changes: 6 additions & 1 deletion src/cli/commands/test/iac-local-execution/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,10 @@ export type IaCTestFlags = Pick<
| 'json'
| 'sarif'
| 'report'

// PolicyOptions
| 'ignore-policy'
| 'policy-path'
| 'tags'
> & {
// Supported flags not yet covered by Options or TestOptions
'json-file-output'?: string;
Expand All @@ -197,6 +197,11 @@ export type IaCTestFlags = Pick<
path?: string;
// Allows the caller to provide the path to a WASM bundle.
rules?: string;
// Tags and attributes
'project-tags'?: string;
'project-environment'?: string;
'project-lifecycle'?: string;
'project-business-criticality'?: string;
} & TerraformPlanFlags;

// Flags specific for Terraform plan scanning
Expand Down
5 changes: 5 additions & 0 deletions src/lib/iac/cli-share-results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ import * as analytics from '../analytics';
import { getContributors } from '../monitor/dev-count-analysis';
import * as Debug from 'debug';
const debug = Debug('iac-cli-share-results');
import { ProjectAttributes, Tag } from '../types';

export async function shareResults(
results: IacShareResultsFormat[],
policy: Policy | undefined,
tags?: Tag[],
attributes?: ProjectAttributes,
): Promise<Record<string, string>> {
const gitTarget = (await getInfo(false)) as GitTarget;
const scanResults = results.map((result) =>
Expand All @@ -42,6 +45,8 @@ export async function shareResults(
body: {
scanResults,
contributors,
tags,
attributes,
},
});

Expand Down
86 changes: 86 additions & 0 deletions test/jest/acceptance/iac/cli-share-results.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,91 @@ describe('CLI Share Results', () => {
}),
);
});

it('forwards project tags', async () => {
const { exitCode } = await run(
'snyk iac test ./iac/arm/rule_test.json --report --tags=foo=bar',
);

expect(exitCode).toEqual(1);

const requests = server
.getRequests()
.filter((request) => request.url.includes('/iac-cli-share-results'));

expect(requests.length).toEqual(1);

const [request] = requests;

expect(request.body).toMatchObject({
tags: [{ key: 'foo', value: 'bar' }],
});
});

it('forwards project environment', async () => {
const { exitCode } = await run(
'snyk iac test ./iac/arm/rule_test.json --report --project-environment=saas',
);

expect(exitCode).toEqual(1);

const requests = server
.getRequests()
.filter((request) => request.url.includes('/iac-cli-share-results'));

expect(requests.length).toEqual(1);

const [request] = requests;

expect(request.body).toMatchObject({
attributes: {
environment: ['saas'],
},
});
});

it('forwards project lifecycle', async () => {
const { exitCode } = await run(
'snyk iac test ./iac/arm/rule_test.json --report --project-lifecycle=sandbox',
);

expect(exitCode).toEqual(1);

const requests = server
.getRequests()
.filter((request) => request.url.includes('/iac-cli-share-results'));

expect(requests.length).toEqual(1);

const [request] = requests;

expect(request.body).toMatchObject({
attributes: {
lifecycle: ['sandbox'],
},
});
});

it('forwards project business criticality', async () => {
const { exitCode } = await run(
'snyk iac test ./iac/arm/rule_test.json --report --project-business-criticality=high',
);

expect(exitCode).toEqual(1);

const requests = server
.getRequests()
.filter((request) => request.url.includes('/iac-cli-share-results'));

expect(requests.length).toEqual(1);

const [request] = requests;

expect(request.body).toMatchObject({
attributes: {
criticality: ['high'],
},
});
});
});
});

0 comments on commit 6d19b05

Please sign in to comment.