Skip to content

Commit c112807

Browse files
author
Amir Moualem
authored
Merge pull request #294 from snyk/chore/images
Chore/images
2 parents 66b2775 + a86d849 commit c112807

File tree

5 files changed

+118
-131
lines changed

5 files changed

+118
-131
lines changed

src/scanner/image/index.ts

Lines changed: 0 additions & 73 deletions
This file was deleted.

src/scanner/images/index.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import { unlink } from 'fs';
2+
import * as plugin from 'snyk-docker-plugin';
3+
14
import logger = require('../../common/logger');
25
import { pull as skopeoCopy, getDestinationForImage } from './skopeo';
3-
import { unlink } from 'fs';
6+
import config = require('../../common/config');
47
import { IPullableImage } from './types';
8+
import { IStaticAnalysisOptions, StaticAnalysisImageType, IScanResult } from '../types';
59

610
export async function pullImages(images: IPullableImage[]): Promise<IPullableImage[]> {
711
const pulledImages: IPullableImage[] = [];
@@ -32,3 +36,64 @@ export async function removePulledImages(images: IPullableImage[]): Promise<void
3236
}
3337
}
3438
}
39+
40+
// Exported for testing
41+
export function removeTagFromImage(imageWithTag: string): string {
42+
return imageWithTag.split('@')[0].split(':')[0];
43+
}
44+
45+
// Exported for testing
46+
export function getImageTag(imageWithTag: string): string {
47+
const imageParts: string[] = imageWithTag.split(':');
48+
if (imageParts.length === 2) { // image@sha256:hash or image:tag
49+
return imageParts[1];
50+
}
51+
52+
return '';
53+
}
54+
55+
// Exported for testing
56+
export function constructStaticAnalysisOptions(
57+
fileSystemPath: string,
58+
): { staticAnalysisOptions: IStaticAnalysisOptions } {
59+
return {
60+
staticAnalysisOptions: {
61+
imagePath: fileSystemPath,
62+
imageType: StaticAnalysisImageType.DockerArchive,
63+
tmpDirPath: config.IMAGE_STORAGE_ROOT,
64+
},
65+
};
66+
}
67+
68+
export async function scanImages(images: IPullableImage[]): Promise<IScanResult[]> {
69+
const scannedImages: IScanResult[] = [];
70+
71+
const dockerfile = undefined;
72+
73+
for (const {imageName, fileSystemPath} of images) {
74+
try {
75+
const options = constructStaticAnalysisOptions(fileSystemPath);
76+
77+
const result = await plugin.inspect(imageName, dockerfile, options);
78+
79+
if (!result || !result.package || !result.package.dependencies) {
80+
throw Error('Unexpected empty result from docker-plugin');
81+
}
82+
83+
result.imageMetadata = {
84+
image: removeTagFromImage(imageName),
85+
imageTag: getImageTag(imageName),
86+
};
87+
88+
scannedImages.push({
89+
image: removeTagFromImage(imageName),
90+
imageWithTag: imageName,
91+
pluginResult: result,
92+
});
93+
} catch (error) {
94+
logger.warn({error, image: imageName}, 'failed to scan image');
95+
}
96+
}
97+
98+
return scannedImages;
99+
}

src/scanner/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logger = require('../common/logger');
2-
import { pullImages, removePulledImages, getImagesWithFileSystemPath } from './images';
3-
import { scanImages } from './image';
2+
import { pullImages, removePulledImages, getImagesWithFileSystemPath, scanImages } from './images';
43
import { deleteWorkload, sendDepGraph } from '../transmitter';
54
import { constructDeleteWorkloadPayload, constructDepGraph } from '../transmitter/payload';
65
import { IWorkload, ILocalWorkloadLocator } from '../transmitter/types';

test/unit/scanner/image.test.ts

Lines changed: 0 additions & 49 deletions
This file was deleted.

test/unit/scanner/images.test.ts

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import * as tap from 'tap';
2-
import { getImagesWithFileSystemPath, pullImages } from '../../../src/scanner/images';
2+
33
import { IPullableImage } from '../../../src/scanner/images/types';
44
import config = require('../../../src/common/config');
5+
import * as scannerImages from '../../../src/scanner/images';
6+
57

68
tap.test('getImagesWithFileSystemPath()', async (t) => {
79
const noImages: string[] = [];
8-
const noImagesResult = getImagesWithFileSystemPath(noImages);
10+
const noImagesResult = scannerImages.getImagesWithFileSystemPath(noImages);
911
t.same(noImagesResult, [], 'correctly maps an empty array');
1012

1113
const image = ['nginx:latest'];
12-
const imageResult = getImagesWithFileSystemPath(image);
14+
const imageResult = scannerImages.getImagesWithFileSystemPath(image);
1315
t.same(imageResult.length, 1, 'expected 1 item');
1416

1517
const resultWithExpectedPath = imageResult[0];
@@ -27,8 +29,8 @@ tap.test('getImagesWithFileSystemPath()', async (t) => {
2729

2830
// Ensure that two consecutive calls do not return the same file system path
2931
const someImage = ['centos:latest'];
30-
const firstCallResult = getImagesWithFileSystemPath(someImage)[0];
31-
const secondCallResult = getImagesWithFileSystemPath(someImage)[0];
32+
const firstCallResult = scannerImages.getImagesWithFileSystemPath(someImage)[0];
33+
const secondCallResult = scannerImages.getImagesWithFileSystemPath(someImage)[0];
3234
t.ok(
3335
firstCallResult.fileSystemPath !== secondCallResult.fileSystemPath,
3436
'consecutive calls to the function with the same data return different file system paths',
@@ -37,6 +39,49 @@ tap.test('getImagesWithFileSystemPath()', async (t) => {
3739

3840
tap.test('pullImages() skips on missing file system path', async (t) => {
3941
const badImage = [{imageName: 'nginx:latest'}];
40-
const result = await pullImages(badImage as IPullableImage[]);
42+
const result = await scannerImages.pullImages(badImage as IPullableImage[]);
4143
t.same(result, [], 'expect to skip images missing file system path');
4244
});
45+
46+
tap.test('constructStaticAnalysisOptions() tests', async (t) => {
47+
t.plan(1);
48+
49+
const somePath = '/var/tmp/file.tar';
50+
const options = scannerImages.constructStaticAnalysisOptions(somePath);
51+
const expectedResult = {
52+
staticAnalysisOptions: {
53+
imagePath: somePath,
54+
imageType: 'docker-archive',
55+
tmpDirPath: '/var/tmp',
56+
},
57+
};
58+
59+
t.deepEqual(options, expectedResult, 'returned options match expectations');
60+
});
61+
62+
tap.test('getImageTag() tests', async (t) => {
63+
t.plan(4);
64+
65+
const imageWithSha = 'nginx@sha256:1234567890abcdef';
66+
const imageWithShaResult = scannerImages.getImageTag(imageWithSha);
67+
t.same(imageWithShaResult, '1234567890abcdef', 'image sha is returned');
68+
69+
const imageWithTag = 'nginx:latest';
70+
const imageWithTagResult = scannerImages.getImageTag(imageWithTag);
71+
t.same(imageWithTagResult, 'latest', 'image tag is returned');
72+
73+
const imageWithoutTag = 'nginx';
74+
const imageWithoutTagResult = scannerImages.getImageTag(imageWithoutTag);
75+
t.same(imageWithoutTagResult, '', 'empty tag returned when no tag is specified');
76+
77+
const imageWithManySeparators = 'nginx@abc:tag@bad:reallybad';
78+
const imageWithManySeparatorsResult = scannerImages.getImageTag(imageWithManySeparators);
79+
t.same(imageWithManySeparatorsResult, '', 'empty tag is returned on malformed image name and tag');
80+
});
81+
82+
tap.test('removeTagFromImage() tests', async (t) => {
83+
t.plan(2);
84+
85+
t.same(scannerImages.removeTagFromImage('nginx:latest'), 'nginx', 'removed image:tag');
86+
t.same(scannerImages.removeTagFromImage('nginx:@sha256:1234567890abcdef'), 'nginx', 'removed image@sha:hex');
87+
});

0 commit comments

Comments
 (0)