Skip to content

Commit f94c201

Browse files
authored
Merge pull request #1206 from snyk/fix/caching
Fix/caching
2 parents 3ae7468 + a439412 commit f94c201

File tree

7 files changed

+44
-65
lines changed

7 files changed

+44
-65
lines changed

config.default.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
},
1010
"WORKLOADS_SCANNED_CACHE": {
1111
"MAX_SIZE": 10000,
12-
"MAX_AGE_MS": 60000
12+
"MAX_AGE_MS": 86400000
1313
},
1414
"WORKERS_COUNT": 5,
1515
"REQUEST_QUEUE_LENGTH": 2,

src/scanner/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export async function scanImagesAndSendResults(
138138
const imageState = await getWorkloadImageAlreadyScanned(
139139
workload,
140140
workload.imageName,
141+
workload.imageId,
141142
);
142143
if (workloadState === undefined && imageState === undefined) {
143144
logger.info(

src/state.ts

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import LruCache from 'lru-cache';
44
import { config } from './common/config';
55
import { extractNamespaceName } from './supervisor/watchers/internal-namespaces';
66

7-
const imagesLruCacheOptions: LruCache.Options<string, string> = {
7+
const imagesLruCacheOptions: LruCache.Options<string, Set<string>> = {
88
// limit cache size so we don't exceed memory limit
99
max: config.IMAGES_SCANNED_CACHE.MAX_SIZE,
1010
// limit cache life so if our backend loses track of an image's data,
@@ -35,56 +35,61 @@ interface WorkloadImagesAlreadyScanned {
3535
imageIds: string[];
3636
}
3737

38-
function getWorkloadAlreadyScannedKey(
39-
workload: WorkloadAlreadyScanned,
40-
): string {
41-
return `${workload.namespace}/${workload.type}/${workload.uid}`;
42-
}
43-
4438
function getWorkloadImageAlreadyScannedKey(
4539
workload: WorkloadAlreadyScanned,
46-
imageId: string,
40+
imageName: string,
4741
): string {
48-
return `${workload.namespace}/${workload.type}/${workload.uid}/${imageId}`;
42+
return `${workload.uid}/${imageName}`;
4943
}
5044

5145
export async function getWorkloadAlreadyScanned(
5246
workload: WorkloadAlreadyScanned,
5347
): Promise<string | undefined> {
54-
const key = getWorkloadAlreadyScannedKey(workload);
48+
const key = workload.uid;
5549
return state.workloadsAlreadyScanned.get(key);
5650
}
5751

5852
export async function setWorkloadAlreadyScanned(
5953
workload: WorkloadAlreadyScanned,
60-
value: string,
54+
revision: string,
6155
): Promise<boolean> {
62-
const key = getWorkloadAlreadyScannedKey(workload);
63-
return state.workloadsAlreadyScanned.set(key, value);
56+
const key = workload.uid;
57+
return state.workloadsAlreadyScanned.set(key, revision);
6458
}
6559

6660
export async function deleteWorkloadAlreadyScanned(
6761
workload: WorkloadAlreadyScanned,
6862
): Promise<void> {
69-
const key = getWorkloadAlreadyScannedKey(workload);
63+
const key = workload.uid;
7064
state.workloadsAlreadyScanned.del(key);
7165
}
7266

7367
export async function getWorkloadImageAlreadyScanned(
7468
workload: WorkloadAlreadyScanned,
69+
imageName: string,
7570
imageId: string,
7671
): Promise<string | undefined> {
77-
const key = getWorkloadImageAlreadyScannedKey(workload, imageId);
78-
return state.imagesAlreadyScanned.get(key);
72+
const key = getWorkloadImageAlreadyScannedKey(workload, imageName);
73+
const hasImageId = state.imagesAlreadyScanned.get(key)?.has(imageId);
74+
return hasImageId ? imageId : undefined;
7975
}
8076

8177
export async function setWorkloadImageAlreadyScanned(
8278
workload: WorkloadAlreadyScanned,
79+
imageName: string,
8380
imageId: string,
84-
value: string,
8581
): Promise<boolean> {
86-
const key = getWorkloadImageAlreadyScannedKey(workload, imageId);
87-
return state.imagesAlreadyScanned.set(key, value);
82+
const key = getWorkloadImageAlreadyScannedKey(workload, imageName);
83+
const images = state.imagesAlreadyScanned.get(key);
84+
if (images !== undefined) {
85+
images.add(imageId);
86+
} else {
87+
const set = new Set<string>();
88+
set.add(imageId);
89+
state.imagesAlreadyScanned.set(key, set);
90+
}
91+
92+
return true;
8893
}
8994

9095
export async function deleteWorkloadImagesAlreadyScanned(
@@ -126,7 +131,9 @@ export function deleteNamespace(namespace: V1Namespace): void {
126131

127132
export const state = {
128133
shutdownInProgress: false,
129-
imagesAlreadyScanned: new LruCache<string, string>(imagesLruCacheOptions),
134+
imagesAlreadyScanned: new LruCache<string, Set<string>>(
135+
imagesLruCacheOptions,
136+
),
130137
workloadsAlreadyScanned: new LruCache<string, string>(
131138
workloadsLruCacheOptions,
132139
),

src/supervisor/watchers/handlers/informer-config.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ADD, CHANGE, DELETE, UPDATE } from '@kubernetes/client-node';
1+
import { ADD, DELETE, UPDATE } from '@kubernetes/client-node';
22

33
import { WorkloadKind } from '../../types';
44
import * as pod from './pod';
@@ -37,7 +37,6 @@ export const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
3737
namespacedEndpoint: '/api/v1/namespaces/{namespace}/pods',
3838
handlers: {
3939
[ADD]: pod.podWatchHandler,
40-
[CHANGE]: async () => {},
4140
[DELETE]: pod.podDeletedHandler,
4241
[UPDATE]: pod.podWatchHandler,
4342
},
@@ -50,10 +49,7 @@ export const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
5049
namespacedEndpoint:
5150
'/api/v1/watch/namespaces/{namespace}/replicationcontrollers',
5251
handlers: {
53-
[ADD]: async () => {},
54-
[CHANGE]: async () => {},
5552
[DELETE]: replicationController.replicationControllerWatchHandler,
56-
[UPDATE]: async () => {},
5753
},
5854
clusterListFactory: () => () =>
5955
replicationController.paginatedClusterReplicationControllerList(),
@@ -66,10 +62,7 @@ export const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
6662
clusterEndpoint: '/apis/batch/v1/cronjobs',
6763
namespacedEndpoint: '/apis/batch/v1/watch/namespaces/{namespace}/cronjobs',
6864
handlers: {
69-
[ADD]: async () => {},
70-
[CHANGE]: async () => {},
7165
[DELETE]: cronJob.cronJobWatchHandler,
72-
[UPDATE]: async () => {},
7366
},
7467
clusterListFactory: () => () => cronJob.paginatedClusterCronJobList(),
7568
namespacedListFactory: (namespace) => () =>
@@ -80,10 +73,7 @@ export const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
8073
namespacedEndpoint:
8174
'/apis/batch/v1beta1/watch/namespaces/{namespace}/cronjobs',
8275
handlers: {
83-
[ADD]: async () => {},
84-
[CHANGE]: async () => {},
8576
[DELETE]: cronJob.cronJobWatchHandler,
86-
[UPDATE]: async () => {},
8777
},
8878
clusterListFactory: () => () =>
8979
cronJob.paginatedClusterCronJobV1Beta1List(),
@@ -94,10 +84,7 @@ export const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
9484
clusterEndpoint: '/apis/batch/v1/jobs',
9585
namespacedEndpoint: '/apis/batch/v1/watch/namespaces/{namespace}/jobs',
9686
handlers: {
97-
[ADD]: async () => {},
98-
[CHANGE]: async () => {},
9987
[DELETE]: job.jobWatchHandler,
100-
[UPDATE]: async () => {},
10188
},
10289
clusterListFactory: () => () => job.paginatedClusterJobList(),
10390
namespacedListFactory: (namespace) => () =>
@@ -107,10 +94,7 @@ export const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
10794
clusterEndpoint: '/apis/apps/v1/daemonsets',
10895
namespacedEndpoint: '/apis/apps/v1/watch/namespaces/{namespace}/daemonsets',
10996
handlers: {
110-
[ADD]: async () => {},
111-
[CHANGE]: async () => {},
11297
[DELETE]: daemonSet.daemonSetWatchHandler,
113-
[UPDATE]: async () => {},
11498
},
11599
clusterListFactory: () => () => daemonSet.paginatedClusterDaemonSetList(),
116100
namespacedListFactory: (namespace) => () =>
@@ -121,10 +105,7 @@ export const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
121105
namespacedEndpoint:
122106
'/apis/apps/v1/watch/namespaces/{namespace}/deployments',
123107
handlers: {
124-
[ADD]: async () => {},
125-
[CHANGE]: async () => {},
126108
[DELETE]: deployment.deploymentWatchHandler,
127-
[UPDATE]: async () => {},
128109
},
129110
clusterListFactory: () => () => deployment.paginatedClusterDeploymentList(),
130111
namespacedListFactory: (namespace) => () =>
@@ -135,10 +116,7 @@ export const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
135116
namespacedEndpoint:
136117
'/apis/apps/v1/watch/namespaces/{namespace}/replicasets',
137118
handlers: {
138-
[ADD]: async () => {},
139-
[CHANGE]: async () => {},
140119
[DELETE]: replicaSet.replicaSetWatchHandler,
141-
[UPDATE]: async () => {},
142120
},
143121
clusterListFactory: () => () => replicaSet.paginatedClusterReplicaSetList(),
144122
namespacedListFactory: (namespace) => () =>
@@ -149,10 +127,7 @@ export const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
149127
namespacedEndpoint:
150128
'/apis/apps/v1/watch/namespaces/{namespace}/statefulsets',
151129
handlers: {
152-
[ADD]: async () => {},
153-
[CHANGE]: async () => {},
154130
[DELETE]: statefulSet.statefulSetWatchHandler,
155-
[UPDATE]: async () => {},
156131
},
157132
clusterListFactory: () => () =>
158133
statefulSet.paginatedClusterStatefulSetList(),
@@ -165,10 +140,7 @@ export const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
165140
namespacedEndpoint:
166141
'/apis/apps.openshift.io/v1/watch/namespaces/{namespace}/deploymentconfigs',
167142
handlers: {
168-
[ADD]: async () => {},
169-
[CHANGE]: async () => {},
170143
[DELETE]: deploymentConfig.deploymentConfigWatchHandler,
171-
[UPDATE]: async () => {},
172144
},
173145
clusterListFactory: () => () =>
174146
deploymentConfig.paginatedClusterDeploymentConfigList(),
@@ -180,10 +152,7 @@ export const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
180152
namespacedEndpoint:
181153
'/apis/argoproj.io/v1alpha1/watch/namespaces/{namespace}/rollouts',
182154
handlers: {
183-
[ADD]: async () => {},
184-
[CHANGE]: async () => {},
185155
[DELETE]: rollout.argoRolloutWatchHandler,
186-
[UPDATE]: async () => {},
187156
},
188157
clusterListFactory: () => () => rollout.paginatedClusterArgoRolloutList(),
189158
namespacedListFactory: (namespace) => () =>

src/supervisor/watchers/handlers/pod.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export async function handleReadyPod(
3333
const scanned = await getWorkloadImageAlreadyScanned(
3434
workload,
3535
workload.imageName,
36+
workload.imageId,
3637
);
3738
// ImageID contains the resolved image digest.
3839
// ImageName may contain a tag. The image behind this tag can be mutated and can change over time.
@@ -154,9 +155,11 @@ export async function podWatchHandler(pod: V1Pod): Promise<void> {
154155
const workloadRevision = workloadMember.revision
155156
? workloadMember.revision.toString()
156157
: '';
157-
const scanned = await getWorkloadAlreadyScanned(workloadMember);
158-
if (scanned !== workloadRevision) {
159-
// either not exists or different
158+
const scannedRevision = await getWorkloadAlreadyScanned(workloadMember);
159+
const isRevisionDifferent =
160+
scannedRevision === undefined ||
161+
Number(workloadRevision) > Number(scannedRevision);
162+
if (isRevisionDifferent) {
160163
await setWorkloadAlreadyScanned(workloadMember, workloadRevision);
161164
await sendWorkloadMetadata(workloadMetadataPayload);
162165
}

src/supervisor/watchers/handlers/types.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import { IncomingMessage } from 'http';
1414
export const FALSY_WORKLOAD_NAME_MARKER = 'falsy workload name';
1515

1616
export type KubernetesInformerVerb = ADD | CHANGE | DELETE | UPDATE;
17+
18+
type WorkloadHandlers = Partial<
19+
Record<KubernetesInformerVerb, WorkloadHandlerFunc>
20+
>;
21+
1722
type WorkloadHandlerFunc = (workload: any) => Promise<void>;
1823

1924
type ListNamespacedWorkloadFunctionFactory = (
@@ -32,9 +37,7 @@ export interface IWorkloadWatchMetadata {
3237
[workloadKind: string]: {
3338
clusterEndpoint: string;
3439
namespacedEndpoint: string;
35-
handlers: {
36-
[kubernetesInformerVerb in KubernetesInformerVerb]: WorkloadHandlerFunc;
37-
};
40+
handlers: WorkloadHandlers;
3841
clusterListFactory: ListClusterWorkloadFunctionFactory;
3942
namespacedListFactory: ListNamespacedWorkloadFunctionFactory;
4043
};

test/unit/scanner/scan-results-caching.spec.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,8 @@ describe('scan results caching', () => {
206206
const sendScanResultsMock = jest.spyOn(transmitter, 'sendScanResults');
207207

208208
// Act
209-
await state.setWorkloadAlreadyScanned(workload, undefined as any);
210-
await state.setWorkloadImageAlreadyScanned(
211-
workload,
212-
workload.imageName,
213-
undefined as any,
214-
);
209+
state.state.workloadsAlreadyScanned.reset();
210+
state.state.imagesAlreadyScanned.reset();
215211

216212
const workloadName = 'mock';
217213
const pulledImages = [];

0 commit comments

Comments
 (0)