Skip to content

Commit 393fa09

Browse files
authored
Merge pull request #585 from snyk/fix/revert-to-pulling-by-tag
feat: revert to pulling by tag
2 parents 87d17f2 + 1c56950 commit 393fa09

File tree

9 files changed

+32
-124
lines changed

9 files changed

+32
-124
lines changed

src/scanner/images/index.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,31 @@ import * as plugin from 'snyk-docker-plugin';
33

44
import logger = require('../../common/logger');
55
import { pull as skopeoCopy, getDestinationForImage } from './skopeo';
6-
import { IPullableImage, IScanImage } from './types';
6+
import { IPullableImage } from './types';
77
import { IStaticAnalysisOptions, StaticAnalysisImageType, IScanResult, IPluginOptions } from '../types';
88

99
export async function pullImages(images: IPullableImage[]): Promise<IPullableImage[]> {
1010
const pulledImages: IPullableImage[] = [];
1111

1212
for (const image of images) {
13-
const {imageWithDigest, fileSystemPath} = image;
13+
const {imageName, fileSystemPath} = image;
1414
if (!fileSystemPath) {
1515
continue;
1616
}
1717

1818
try {
19-
await skopeoCopy(imageWithDigest, fileSystemPath);
19+
await skopeoCopy(imageName, fileSystemPath);
2020
pulledImages.push(image);
2121
} catch (error) {
22-
logger.error({error, image: imageWithDigest}, 'failed to pull image');
22+
logger.error({error, image: imageName}, 'failed to pull image');
2323
}
2424
}
2525

2626
return pulledImages;
2727
}
2828

29-
export function getImagesWithFileSystemPath(images: IScanImage[]): IPullableImage[] {
30-
return images.map((image) => ({ ...image, fileSystemPath: getDestinationForImage(image.imageName) }));
29+
export function getImagesWithFileSystemPath(images: string[]): IPullableImage[] {
30+
return images.map((image) => ({ imageName: image, fileSystemPath: getDestinationForImage(image) }));
3131
}
3232

3333
export async function removePulledImages(images: IPullableImage[]): Promise<void> {
@@ -41,15 +41,15 @@ export async function removePulledImages(images: IPullableImage[]): Promise<void
4141
}
4242

4343
// Exported for testing
44-
export function getImageParts(imageWithTag: string) : {imageName: string, imageTag: string, imageDigest: string} {
44+
export function getImageParts(imageWithTag: string) : {imageName: string, imageTag: string} {
4545
// we're matching pattern: <registry:port_number>(optional)/<image_name>(mandatory):<image_tag>(optional)@<tag_identifier>(optional)
4646
// extracted from https://github.com/docker/distribution/blob/master/reference/regexp.go
4747
const regex = /^((?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(?:(?:\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(?::[0-9]+)?\/)?[a-z0-9]+(?:(?:(?:[._]|__|[-]*)[a-z0-9]+)+)?(?:(?:\/[a-z0-9]+(?:(?:(?:[._]|__|[-]*)[a-z0-9]+)+)?)+)?)(?::([\w][\w.-]{0,127}))?(?:@([A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][A-Fa-f0-9]{32,}))?$/ig;
4848
const groups = regex.exec(imageWithTag);
49-
49+
5050
if(!groups){
5151
logger.error({image: imageWithTag}, 'Image with tag is malformed, cannot extract valid parts');
52-
return { imageName: imageWithTag, imageTag: '', imageDigest: '' };
52+
return {imageName: imageWithTag, imageTag: ''};
5353
}
5454

5555
const IMAGE_NAME_GROUP = 1;
@@ -58,8 +58,8 @@ export function getImageParts(imageWithTag: string) : {imageName: string, imageT
5858

5959
return {
6060
imageName: groups[IMAGE_NAME_GROUP],
61-
imageTag: groups[IMAGE_TAG_GROUP] || '',
62-
imageDigest: groups[IMAGE_DIGEST_GROUP] || '',
61+
// prefer tag over digest
62+
imageTag: groups[IMAGE_TAG_GROUP] || groups[IMAGE_DIGEST_GROUP] || '',
6363
};
6464
}
6565

@@ -78,7 +78,7 @@ export async function scanImages(images: IPullableImage[]): Promise<IScanResult[
7878

7979
const dockerfile = undefined;
8080

81-
for (const { imageName, fileSystemPath, imageWithDigest } of images) {
81+
for (const {imageName, fileSystemPath} of images) {
8282
try {
8383
const staticAnalysisOptions = constructStaticAnalysisOptions(fileSystemPath);
8484
const options: IPluginOptions = {
@@ -92,18 +92,16 @@ export async function scanImages(images: IPullableImage[]): Promise<IScanResult[
9292
throw Error('Unexpected empty result from docker-plugin');
9393
}
9494

95-
const imageParts = getImageParts(imageName);
95+
const imageParts: {imageName: string, imageTag: string} = getImageParts(imageName);
9696

9797
result.imageMetadata = {
9898
image: imageParts.imageName,
9999
imageTag: imageParts.imageTag,
100-
imageDigest: getImageParts(imageWithDigest).imageDigest,
101100
};
102101

103102
scannedImages.push({
104103
image: imageParts.imageName,
105104
imageWithTag: imageName,
106-
imageWithDigest: imageWithDigest,
107105
pluginResult: result,
108106
});
109107
} catch (error) {

src/scanner/images/types.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
export interface IScanImage {
2-
imageName: string;
3-
imageWithDigest: string;
4-
}
5-
61
export interface IPullableImage {
72
imageName: string;
8-
imageWithDigest: string;
93
fileSystemPath: string;
104
}
115

src/scanner/index.ts

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import logger = require('../common/logger');
2-
import { pullImages, removePulledImages, getImagesWithFileSystemPath, scanImages, getImageParts } from './images';
2+
import { pullImages, removePulledImages, getImagesWithFileSystemPath, scanImages } from './images';
33
import { deleteWorkload, sendDepGraph } from '../transmitter';
44
import { constructDeleteWorkload, constructDepGraph } from '../transmitter/payload';
55
import { IWorkload, ILocalWorkloadLocator } from '../transmitter/types';
6-
import { IPullableImage, IScanImage } from './images/types';
6+
import { IPullableImage } from './images/types';
77

88
export async function processWorkload(workloadMetadata: IWorkload[]): Promise<void> {
99
// every workload metadata references the same workload name, grab it from the first one
1010
const workloadName = workloadMetadata[0].name;
11-
const uniqueImages = getUniqueImages(workloadMetadata);
11+
const allImages = workloadMetadata.map((meta) => meta.imageName);
12+
logger.info({workloadName, imageCount: allImages.length}, 'queried workloads');
13+
const uniqueImages = [...new Set<string>(allImages)];
1214

13-
logger.info({ workloadName, imageCount: uniqueImages.length }, 'pulling unique images');
15+
logger.info({workloadName, imageCount: uniqueImages.length}, 'pulling unique images');
1416
const imagesWithFileSystemPath = getImagesWithFileSystemPath(uniqueImages);
1517
const pulledImages = await pullImages(imagesWithFileSystemPath);
1618
if (pulledImages.length === 0) {
17-
logger.info({ workloadName }, 'no images were pulled, halting scanner process.');
19+
logger.info({workloadName}, 'no images were pulled, halting scanner process.');
1820
return;
1921
}
2022

@@ -33,31 +35,6 @@ export async function sendDeleteWorkloadRequest(workloadName: string, localWorkl
3335
await deleteWorkload(deletePayload);
3436
}
3537

36-
export function getUniqueImages(workloadMetadata: IWorkload[]): IScanImage[] {
37-
const uniqueImages: { [key: string]: IScanImage } = workloadMetadata.reduce((accum, meta) => {
38-
// example: For DCR "redis:latest"
39-
// example: For GCR "gcr.io/test-dummy/redis:latest"
40-
// example: For ECR "291964488713.dkr.ecr.us-east-2.amazonaws.com/snyk/redis:latest"
41-
// meta.imageName can be different depends on CR
42-
const { imageName } = getImageParts(meta.imageName);
43-
// meta.imageId can be different depends on CR
44-
// example: For DCR "docker.io/library/redis@sha256:8e9f8546050da8aae393a41d65ad37166b4f0d8131d627a520c0f0451742e9d6"
45-
// example: For GCR "sha256:8e9f8546050da8aae393a41d65ad37166b4f0d8131d627a520c0f0451742e9d6"
46-
// example: For ECR "sha256:8e9f8546050da8aae393a41d65ad37166b4f0d8131d627a520c0f0451742e9d6"
47-
const digest = meta.imageId.substring(meta.imageId.lastIndexOf('@') + 1);
48-
const imageWithDigest = `${imageName}@${digest}`;
49-
50-
accum[imageWithDigest] = {
51-
imageWithDigest,
52-
imageName: meta.imageName, // Image name with tag
53-
};
54-
55-
return accum;
56-
}, {});
57-
58-
return Object.values(uniqueImages);
59-
}
60-
6138
async function scanImagesAndSendResults(
6239
workloadName: string,
6340
pulledImages: IPullableImage[],

src/scanner/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export interface IScanResult {
22
image: string;
3-
imageWithDigest: string;
43
imageWithTag: string;
54
pluginResult: any;
65
}

src/transmitter/payload.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export function constructDepGraph(
2727
const imageLocator: IImageLocator = {
2828
userLocator: config.INTEGRATION_ID,
2929
imageId: scannedImage.image,
30-
imageWithDigest: scannedImage.imageWithDigest,
3130
cluster,
3231
namespace,
3332
type,

src/transmitter/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export interface IWorkloadMetadata {
2525

2626
export interface IImageLocator extends IWorkloadLocator {
2727
imageId: string;
28-
imageWithDigest: string;
2928
}
3029

3130
export interface IKubernetesMonitorMetadata {

test/unit/scanner/images.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
import * as tap from 'tap';
22

3-
import {IPullableImage, IScanImage} from '../../../src/scanner/images/types';
3+
import { IPullableImage } from '../../../src/scanner/images/types';
44
import config = require('../../../src/common/config');
55
import * as scannerImages from '../../../src/scanner/images';
66

7+
78
tap.test('getImagesWithFileSystemPath()', async (t) => {
8-
const noImages: IScanImage[] = [];
9+
const noImages: string[] = [];
910
const noImagesResult = scannerImages.getImagesWithFileSystemPath(noImages);
1011
t.same(noImagesResult, [], 'correctly maps an empty array');
1112

12-
const image: IScanImage[] = [{
13-
imageName: 'nginx:latest',
14-
imageWithDigest: 'nginx@sha256:4949aa7259aa6f827450207db5ad94cabaa9248277c6d736d5e1975d200c7e43',
15-
}];
13+
const image = ['nginx:latest'];
1614
const imageResult = scannerImages.getImagesWithFileSystemPath(image);
1715
t.same(imageResult.length, 1, 'expected 1 item');
1816

@@ -30,10 +28,7 @@ tap.test('getImagesWithFileSystemPath()', async (t) => {
3028
t.ok(expectedPattern, 'the file system path starts with an expected pattern');
3129

3230
// Ensure that two consecutive calls do not return the same file system path
33-
const someImage = [{
34-
imageName: 'centos:latest',
35-
imageWithDigest: 'centos@sha256:fc4a234b91cc4b542bac8a6ad23b2ddcee60ae68fc4dbd4a52efb5f1b0baad71',
36-
}];
31+
const someImage = ['centos:latest'];
3732
const firstCallResult = scannerImages.getImagesWithFileSystemPath(someImage)[0];
3833
const secondCallResult = scannerImages.getImagesWithFileSystemPath(someImage)[0];
3934
t.ok(
@@ -49,6 +44,8 @@ tap.test('pullImages() skips on missing file system path', async (t) => {
4944
});
5045

5146
tap.test('constructStaticAnalysisOptions() tests', async (t) => {
47+
t.plan(1);
48+
5249
const somePath = '/var/tmp/file.tar';
5350
const options = scannerImages.constructStaticAnalysisOptions(somePath);
5451
const expectedResult = {
@@ -60,10 +57,11 @@ tap.test('constructStaticAnalysisOptions() tests', async (t) => {
6057
});
6158

6259
tap.test('extracted image tag tests', async (t) => {
60+
t.plan(6);
61+
6362
const imageWithSha = 'nginx@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2';
6463
const imageWithShaResult = scannerImages.getImageParts(imageWithSha);
65-
t.same(imageWithShaResult.imageTag, '', 'image tag is empty');
66-
t.same(imageWithShaResult.imageDigest, 'sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2', 'image digest is returned');
64+
t.same(imageWithShaResult.imageTag, 'sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2', 'image sha is returned');
6765

6866
const imageWithTag = 'nginx:latest';
6967
const imageWithTagResult = scannerImages.getImageParts(imageWithTag);
@@ -87,6 +85,8 @@ tap.test('extracted image tag tests', async (t) => {
8785
});
8886

8987
tap.test('extracted image name tests', async (t) => {
88+
t.plan(5);
89+
9090
t.same(scannerImages.getImageParts('nginx:latest').imageName, 'nginx', 'removed image:tag');
9191
t.same(scannerImages.getImageParts('node@sha256:215a9fbef4df2c1ceb7c79481d3cfd94ad8f1f0105bade39f3be907bf386c5e1').imageName, 'node', 'removed image@sha:hex');
9292
t.same(scannerImages.getImageParts('kind-registry:5000/python:rc-buster').imageName, 'kind-registry:5000/python', 'removed repository/image:tag');

test/unit/scanner/index.test.ts

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

test/unit/transmitter-payload.test.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,11 @@ tap.test('constructDepGraph breaks when workloadMetadata is missing items', asyn
1111
{
1212
image: 'myImage',
1313
imageWithTag: 'myImage:tag',
14-
imageWithDigest: 'myImage@sha256:idontcarewhatissha',
1514
pluginResult: 'whatever1',
1615
},
1716
{
1817
image: 'anotherImage',
1918
imageWithTag: 'anotherImage:1.2.3-alpha',
20-
imageWithDigest: 'myImage@sha256:somuchdifferentsha256',
2119
pluginResult: 'whatever3',
2220
},
2321
];
@@ -50,7 +48,6 @@ tap.test('constructDepGraph happy flow', async (t) => {
5048
{
5149
image: 'myImage',
5250
imageWithTag: 'myImage:tag',
53-
imageWithDigest: 'myImage@sha256:idontcarewhatissha',
5451
pluginResult: 'whatever1',
5552
},
5653
];

0 commit comments

Comments
 (0)