Skip to content

Commit

Permalink
Merge pull request #3453 from snyk/feat/iac-test-add-remote-repo-url-…
Browse files Browse the repository at this point in the history
…flag

feat: add --remote-repo-url to "iac test"
  • Loading branch information
francescomari committed Jul 14, 2022
2 parents b278096 + 2a12048 commit 80eabae
Show file tree
Hide file tree
Showing 18 changed files with 451 additions and 191 deletions.
17 changes: 17 additions & 0 deletions src/cli/commands/test/iac/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import config from '../../../../lib/config';
import { UnsupportedEntitlementError } from '../../../../lib/errors/unsupported-entitlement-error';
import { scan } from './scan';
import { buildOutput, buildSpinner, printHeader } from './output';
import { InvalidRemoteUrlError } from '../../../../lib/errors/invalid-remote-url-error';

export default async function(...args: MethodArgs): Promise<TestCommandResult> {
const { options: originalOptions, paths } = processCommandArgs(...args);

const options = setDefaultTestOptions(originalOptions);
validateTestOptions(options);
validateCredentials(options);
const remoteRepoUrl = getRemoteRepoUrl(options);

const orgPublicId = (options.org as string) ?? config.org;
const iacOrgSettings = await getIacOrgSettings(orgPublicId);
Expand Down Expand Up @@ -69,6 +71,7 @@ export default async function(...args: MethodArgs): Promise<TestCommandResult> {
orgPublicId,
buildOciRegistry,
projectRoot,
remoteRepoUrl,
);

return buildOutput({
Expand All @@ -84,3 +87,17 @@ export default async function(...args: MethodArgs): Promise<TestCommandResult> {
testSpinner,
});
}

function getRemoteRepoUrl(options: any) {
const remoteRepoUrl = options['remote-repo-url'];

if (!remoteRepoUrl) {
return;
}

if (typeof remoteRepoUrl !== 'string') {
throw new InvalidRemoteUrlError();
}

return remoteRepoUrl;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const keys: (keyof IaCTestFlags)[] = [
// PolicyOptions
'ignore-policy',
'policy-path',
'remote-repo-url',
];
const allowed = new Set<string>(keys);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,41 @@ import {
} from '../types';
import { convertIacResultToScanResult } from '../../../../../../lib/iac/envelope-formatters';
import { Policy } from '../../../../../../lib/policy/find-and-load-policy';
import { getInfo } from '../../../../../../lib/project-metadata/target-builders/git';
import { GitTarget } from '../../../../../../lib/ecosystems/types';
import { Contributor } from '../../../../../../lib/types';
import {
Contributor,
IacOutputMeta,
ProjectAttributes,
Tag,
} from '../../../../../../lib/types';
import * as analytics from '../../../../../../lib/analytics';
import { getContributors } from '../../../../../../lib/monitor/dev-count-analysis';
import * as Debug from 'debug';
import { AuthFailedError, ValidationError } from '../../../../../../lib/errors';
import * as pathLib from 'path';
import { TestLimitReachedError } from '../usage-tracking';

const debug = Debug('iac-cli-share-results');
import { ProjectAttributes, Tag } from '../../../../../../lib/types';
import { TestLimitReachedError } from '../usage-tracking';
import { getRepositoryRootForPath } from '../../../../../../lib/iac/git';

export async function shareResults({
results,
policy,
tags,
attributes,
options,
projectRoot,
meta,
}: {
results: IacShareResultsFormat[];
policy: Policy | undefined;
tags?: Tag[];
attributes?: ProjectAttributes;
options?: IaCTestFlags;
projectRoot: string;
meta: IacOutputMeta;
}): Promise<ShareResultsOutput> {
const gitTarget = await readGitInfoForProjectRoot(projectRoot);
const scanResults = results.map((result) =>
convertIacResultToScanResult(result, policy, gitTarget, options),
convertIacResultToScanResult(result, policy, meta, options),
);

let contributors: Contributor[] = [];
if (gitTarget.remoteUrl) {
if (meta.gitRemoteUrl) {
if (analytics.allowAnalytics()) {
try {
contributors = await getContributors();
Expand Down Expand Up @@ -79,29 +78,5 @@ export async function shareResults({
);
}

return { projectPublicIds: body, gitRemoteUrl: gitTarget?.remoteUrl };
}

async function readGitInfoForProjectRoot(
projectRoot: string,
): Promise<GitTarget> {
const repositoryRoot = getRepositoryRootForPath(projectRoot);

const resolvedRepositoryRoot = pathLib.resolve(repositoryRoot);
const resolvedProjectRoot = pathLib.resolve(projectRoot);

if (resolvedRepositoryRoot != resolvedProjectRoot) {
return {};
}

const gitInfo = await getInfo({
isFromContainer: false,
cwd: projectRoot,
});

if (gitInfo) {
return gitInfo;
}

return {};
return { projectPublicIds: body, gitRemoteUrl: meta.gitRemoteUrl };
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Policy } from '../../../../../../lib/policy/find-and-load-policy';
import { ProjectAttributes, Tag } from '../../../../../../lib/types';
import {
IacOutputMeta,
ProjectAttributes,
Tag,
} from '../../../../../../lib/types';
import {
FormattedResult,
IacFileScanResult,
Expand All @@ -26,6 +30,7 @@ export class SingleGroupResultsProcessor implements ResultsProcessor {
private orgPublicId: string,
private iacOrgSettings: IacOrgSettings,
private options: IaCTestFlags,
private meta: IacOutputMeta,
) {}

processResults(
Expand All @@ -43,6 +48,7 @@ export class SingleGroupResultsProcessor implements ResultsProcessor {
attributes,
this.options,
this.projectRoot,
this.meta,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { filterIgnoredIssues } from './policy';
import { formatAndShareResults } from './share-results';
import { formatScanResultsV2 } from '../measurable-methods';
import { Policy } from '../../../../../../lib/policy/find-and-load-policy';
import { ProjectAttributes, Tag } from '../../../../../../lib/types';
import {
IacOutputMeta,
ProjectAttributes,
Tag,
} from '../../../../../../lib/types';
import {
FormattedResult,
IacFileScanResult,
Expand All @@ -19,6 +23,7 @@ export async function processResults(
attributes: ProjectAttributes | undefined,
options: IaCTestFlags,
projectRoot: string,
meta: IacOutputMeta,
): Promise<{
filteredIssues: FormattedResult[];
ignoreCount: number;
Expand All @@ -35,6 +40,7 @@ export async function processResults(
tags,
attributes,
projectRoot,
meta,
}));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { IacFileScanResult, IacShareResultsFormat } from '../types';
import * as path from 'path';
import { IacOutputMeta } from '../../../../../../lib/types';

export function formatShareResults(
projectRoot: string,
scanResults: IacFileScanResult[],
meta: IacOutputMeta,
): IacShareResultsFormat[] {
const resultsGroupedByFilePath = groupByFilePath(scanResults);

return resultsGroupedByFilePath.map((result) => {
const { projectName, targetFile } = computePaths(
projectRoot,
result.filePath,
meta,
);

return {
Expand Down Expand Up @@ -45,16 +48,16 @@ function groupByFilePath(scanResults: IacFileScanResult[]) {
function computePaths(
projectRoot: string,
filePath: string,
meta: IacOutputMeta,
): { targetFilePath: string; projectName: string; targetFile: string } {
const projectDirectory = path.resolve(projectRoot);
const currentDirectoryName = path.basename(projectDirectory);
const absoluteFilePath = path.resolve(filePath);
const relativeFilePath = path.relative(projectDirectory, absoluteFilePath);
const unixRelativeFilePath = relativeFilePath.split(path.sep).join('/');

return {
targetFilePath: absoluteFilePath,
projectName: currentDirectoryName,
projectName: meta.projectName,
targetFile: unixRelativeFilePath,
};
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { isFeatureFlagSupportedForOrg } from '../../../../../../lib/feature-flags';
import { shareResults } from './cli-share-results';
import { Policy } from '../../../../../../lib/policy/find-and-load-policy';
import { ProjectAttributes, Tag } from '../../../../../../lib/types';
import {
IacOutputMeta,
ProjectAttributes,
Tag,
} from '../../../../../../lib/types';
import { FeatureFlagError } from '../assert-iac-options-flag';
import { formatShareResults } from './share-results-formatter';
import { IacFileScanResult, IaCTestFlags, ShareResultsOutput } from '../types';
Expand All @@ -14,6 +18,7 @@ export async function formatAndShareResults({
tags,
attributes,
projectRoot,
meta,
}: {
results: IacFileScanResult[];
options: IaCTestFlags;
Expand All @@ -22,6 +27,7 @@ export async function formatAndShareResults({
tags?: Tag[];
attributes?: ProjectAttributes;
projectRoot: string;
meta: IacOutputMeta;
}): Promise<ShareResultsOutput> {
const isCliReportEnabled = await isFeatureFlagSupportedForOrg(
'iacCliShareResults',
Expand All @@ -31,14 +37,14 @@ export async function formatAndShareResults({
throw new FeatureFlagError('report', 'iacCliShareResults');
}

const formattedResults = formatShareResults(projectRoot, results);
const formattedResults = formatShareResults(projectRoot, results, meta);

return await shareResults({
results: formattedResults,
policy,
tags,
attributes,
options,
projectRoot,
meta,
});
}
1 change: 1 addition & 0 deletions src/cli/commands/test/iac/local-execution/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export type IaCTestFlags = Pick<
| 'ignore-policy'
| 'policy-path'
| 'tags'
| 'remote-repo-url'
> & {
// Supported flags not yet covered by Options or TestOptions
'json-file-output'?: string;
Expand Down
86 changes: 86 additions & 0 deletions src/cli/commands/test/iac/meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { IacOutputMeta } from '../../../../lib/types';
import { IacOrgSettings } from './local-execution/types';
import * as pathLib from 'path';

export interface GitRepository {
readonly path: string;

readRemoteUrl(): Promise<string | undefined>;
}

export interface GitRepositoryFinder {
findRepositoryForPath(path: string): Promise<GitRepository | undefined>;
}

export async function buildMeta(
repositoryFinder: GitRepositoryFinder,
orgSettings: IacOrgSettings,
projectRoot: string,
remoteRepoUrl?: string,
): Promise<IacOutputMeta> {
const gitRemoteUrl = await getGitRemoteUrl(
repositoryFinder,
projectRoot,
remoteRepoUrl,
);
const projectName = getProjectName(projectRoot, gitRemoteUrl);
const orgName = getOrgName(orgSettings);
return { projectName, orgName, gitRemoteUrl };
}

function getProjectName(projectRoot: string, gitRemoteUrl?: string): string {
if (gitRemoteUrl) {
return getProjectNameFromGitUrl(gitRemoteUrl);
}

return pathLib.basename(pathLib.resolve(projectRoot));
}

function getOrgName(orgSettings: IacOrgSettings): string {
return orgSettings.meta.org;
}

async function getGitRemoteUrl(
repositoryFinder: GitRepositoryFinder,
projectRoot: string,
remoteRepoUrl?: string,
): Promise<string | undefined> {
if (remoteRepoUrl) {
return remoteRepoUrl;
}

const repository = await repositoryFinder.findRepositoryForPath(projectRoot);

if (!repository) {
return;
}

const resolvedRepositoryRoot = pathLib.resolve(repository.path);
const resolvedProjectRoot = pathLib.resolve(projectRoot);

if (resolvedRepositoryRoot != resolvedProjectRoot) {
return;
}

return await repository.readRemoteUrl();
}

export function getProjectNameFromGitUrl(url: string) {
const regexps = [
/^ssh:\/\/([^@]+@)?[^:/]+(:[^/]+)?\/(?<name>.*).git\/?$/,
/^(git|https?|ftp):\/\/[^:/]+(:[^/]+)?\/(?<name>.*).git\/?$/,
/^[^@]+@[^:]+:(?<name>.*).git$/,
];

const trimmed = url.trim();

for (const regexp of regexps) {
const match = trimmed.match(regexp);

if (match && match.groups) {
return match.groups.name;
}
}

return trimmed;
}
Loading

0 comments on commit 80eabae

Please sign in to comment.