Skip to content

Commit 659c3bf

Browse files
author
Mengqi Yu
authored
ensure output annotation format match input (#460)
* ensure output annotation format match input * skip resources that have none of the legacy or new annotations when determining format
1 parent 4792973 commit 659c3bf

File tree

2 files changed

+296
-77
lines changed

2 files changed

+296
-77
lines changed

ts/kpt-functions/src/run.ts

Lines changed: 226 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ import { FunctionConfigError } from './errors';
2121
import {
2222
getAnnotation,
2323
addAnnotation,
24+
removeAnnotation,
2425
SOURCE_PATH_ANNOTATION,
2526
SOURCE_INDEX_ANNOTATION,
2627
ID_ANNOTATION,
2728
LEGACY_SOURCE_PATH_ANNOTATION,
2829
LEGACY_SOURCE_INDEX_ANNOTATION,
2930
LEGACY_ID_ANNOTATION,
31+
ANNOTATION_PREFIX,
3032
} from './metadata';
3133

3234
const INVOCATIONS = `
@@ -65,6 +67,9 @@ Example invocations:
6567
This is a convenient way to populate the functionConfig if it's a ConfigMap.
6668
`;
6769

70+
const RESOURCE_ID_ANNOTATION =
71+
'internal.kubernetes.io/annotations-migration-resource-id';
72+
6873
enum ExitCode {
6974
RESULT_ERROR = 1,
7075
EXCEPTION_ERROR,
@@ -169,7 +174,7 @@ Use this ONLY if the function accepts a ConfigMap.`,
169174

170175
export async function runFnWithConfigs(fn: KptFunc, configs: Configs) {
171176
// Save the original annotation values.
172-
const m = recordOriginalAnnotation(configs);
177+
const m = preprocessResourceForInternalAnnotationsMigration(configs);
173178

174179
// Run the function.
175180
await fn(configs);
@@ -178,96 +183,249 @@ export async function runFnWithConfigs(fn: KptFunc, configs: Configs) {
178183
reconcileAnnotations(configs, m);
179184
}
180185

181-
class filePathAndIndex {
182-
constructor(path: string | undefined, index: string | undefined) {
183-
this.path = path;
184-
this.index = index;
186+
function getInternalAnnotations(obj: KubernetesObject): {
187+
[key: string]: string;
188+
} {
189+
const m: { [index: string]: string } = {};
190+
if (!obj.metadata) {
191+
return m;
185192
}
186-
187-
path: string | undefined;
188-
index: string | undefined;
193+
if (!obj.metadata.annotations) {
194+
return m;
195+
}
196+
for (const key in obj.metadata.annotations) {
197+
if (
198+
key.startsWith(ANNOTATION_PREFIX) ||
199+
key === LEGACY_SOURCE_PATH_ANNOTATION ||
200+
key === LEGACY_SOURCE_INDEX_ANNOTATION ||
201+
key === LEGACY_ID_ANNOTATION
202+
) {
203+
m[key] = obj.metadata.annotations[key];
204+
}
205+
}
206+
return m;
189207
}
190208

191-
function recordOriginalAnnotation(
209+
function preprocessResourceForInternalAnnotationsMigration(
192210
configs: Configs
193-
): Map<string, filePathAndIndex> {
211+
): Map<string, { [key: string]: string }> {
194212
const m = new Map();
195-
for (var obj of configs.getAll()) {
196-
let idAnno = getAnnotation(obj, ID_ANNOTATION);
197-
if (!idAnno) {
198-
idAnno = getAnnotation(obj, LEGACY_ID_ANNOTATION);
199-
}
200-
if (!idAnno) {
213+
let id = 0;
214+
for (const obj of configs.getAll()) {
215+
const idStr = id.toString();
216+
addAnnotation(obj, RESOURCE_ID_ANNOTATION, idStr);
217+
m.set(idStr, getInternalAnnotations(obj));
218+
id++;
219+
220+
checkMismatchAnnos(obj.metadata.annotations);
221+
}
222+
return m;
223+
}
224+
225+
function checkMismatchAnnos(
226+
annotations: { [key: string]: string } | undefined
227+
) {
228+
if (!annotations) {
229+
return;
230+
}
231+
const path = annotations[SOURCE_PATH_ANNOTATION];
232+
const legacyPath = annotations[LEGACY_SOURCE_PATH_ANNOTATION];
233+
if (path && legacyPath && path !== legacyPath) {
234+
throw new AnnotationsValueMismatchError();
235+
}
236+
237+
const index = annotations[SOURCE_INDEX_ANNOTATION];
238+
const legacyIndex = annotations[LEGACY_SOURCE_INDEX_ANNOTATION];
239+
if (index && legacyIndex && index !== legacyIndex) {
240+
throw new AnnotationsValueMismatchError();
241+
}
242+
243+
const id = annotations[ID_ANNOTATION];
244+
const legacyId = annotations[LEGACY_ID_ANNOTATION];
245+
if (id && legacyId && id !== legacyId) {
246+
throw new AnnotationsValueMismatchError();
247+
}
248+
}
249+
250+
// determineAnnotationFormat returns 2 values:
251+
// - if the internal format should be used.
252+
// - if the legacy format should be used.
253+
function determineAnnotationFormat(
254+
m: Map<string, { [key: string]: string }>
255+
): [boolean, boolean] {
256+
if (m.size === 0) {
257+
return [true, true];
258+
}
259+
let internal: boolean | undefined, legacy: boolean | undefined;
260+
for (const [, annotations] of m) {
261+
const path = annotations[SOURCE_PATH_ANNOTATION];
262+
const index = annotations[SOURCE_INDEX_ANNOTATION];
263+
const id = annotations[ID_ANNOTATION];
264+
const legacyPath = annotations[LEGACY_SOURCE_PATH_ANNOTATION];
265+
const legacyIndex = annotations[LEGACY_SOURCE_INDEX_ANNOTATION];
266+
const legacyId = annotations[LEGACY_ID_ANNOTATION];
267+
268+
if (!(path || index || id || legacyPath || legacyIndex || legacyId)) {
201269
continue;
202270
}
203-
addAnnotation(obj, ID_ANNOTATION, idAnno);
204-
addAnnotation(obj, LEGACY_ID_ANNOTATION, idAnno);
205271

206-
let pathAnno = getAnnotation(obj, SOURCE_PATH_ANNOTATION);
207-
if (!pathAnno) {
208-
pathAnno = getAnnotation(obj, LEGACY_SOURCE_PATH_ANNOTATION);
272+
const foundOneOf =
273+
path !== undefined || index !== undefined || id !== undefined;
274+
if (!internal) {
275+
internal = foundOneOf;
209276
}
210-
if (pathAnno) {
211-
addAnnotation(obj, SOURCE_PATH_ANNOTATION, pathAnno);
212-
addAnnotation(obj, LEGACY_SOURCE_PATH_ANNOTATION, pathAnno);
277+
if ((foundOneOf && !internal) || (!foundOneOf && internal)) {
278+
throw new AnnotationsFormatMismatchError();
213279
}
214280

215-
let indexAnno = getAnnotation(obj, SOURCE_INDEX_ANNOTATION);
216-
if (!indexAnno) {
217-
indexAnno = getAnnotation(obj, LEGACY_SOURCE_INDEX_ANNOTATION);
281+
const foundOneOfLegacy =
282+
legacyPath !== undefined ||
283+
legacyIndex !== undefined ||
284+
legacyId !== undefined;
285+
if (!legacy) {
286+
legacy = foundOneOfLegacy;
218287
}
219-
if (indexAnno) {
220-
addAnnotation(obj, SOURCE_INDEX_ANNOTATION, indexAnno);
221-
addAnnotation(obj, LEGACY_SOURCE_INDEX_ANNOTATION, indexAnno);
288+
if ((foundOneOfLegacy && !legacy) || (!foundOneOfLegacy && legacy)) {
289+
throw new AnnotationsFormatMismatchError();
222290
}
291+
}
292+
if (internal !== undefined && legacy !== undefined) {
293+
return [internal, legacy];
294+
}
295+
return [true, true];
296+
}
297+
298+
function setMissingAnnotations(obj: KubernetesObject) {
299+
setMissingAnnotation(
300+
obj,
301+
SOURCE_PATH_ANNOTATION,
302+
LEGACY_SOURCE_PATH_ANNOTATION
303+
);
304+
setMissingAnnotation(
305+
obj,
306+
SOURCE_INDEX_ANNOTATION,
307+
LEGACY_SOURCE_INDEX_ANNOTATION
308+
);
309+
setMissingAnnotation(obj, ID_ANNOTATION, LEGACY_ID_ANNOTATION);
310+
}
223311

224-
m.set(idAnno, new filePathAndIndex(pathAnno, indexAnno));
312+
function setMissingAnnotation(
313+
obj: KubernetesObject,
314+
internalKey: string,
315+
legacyKey: string
316+
) {
317+
const internalVal = getAnnotation(obj, internalKey);
318+
const legacyVal = getAnnotation(obj, legacyKey);
319+
if (!internalVal && !legacyVal) {
320+
return;
321+
} else if (!internalVal && legacyVal) {
322+
addAnnotation(obj, internalKey, legacyVal);
323+
} else if (!legacyVal && internalVal) {
324+
addAnnotation(obj, legacyKey, internalVal);
225325
}
226-
return m;
227326
}
228327

229-
function reconcileAnnotations(
230-
configs: Configs,
231-
m: Map<string, filePathAndIndex>
328+
function checkAnnotationsAltered(
329+
obj: KubernetesObject,
330+
idToAnnos: Map<string, { [key: string]: string }>
232331
) {
233-
// for (var obj of configs.getAll()) {
234-
configs.getAll().forEach(function (obj) {
235-
let idAnno = getAnnotation(obj, ID_ANNOTATION);
236-
if (!idAnno) {
237-
idAnno = getAnnotation(obj, LEGACY_ID_ANNOTATION);
238-
}
239-
if (!idAnno) {
240-
return;
241-
}
332+
const path = getAnnotation(obj, SOURCE_PATH_ANNOTATION);
333+
const index = getAnnotation(obj, SOURCE_INDEX_ANNOTATION);
334+
const legacyPath = getAnnotation(obj, LEGACY_SOURCE_PATH_ANNOTATION);
335+
const legacyIndex = getAnnotation(obj, LEGACY_SOURCE_INDEX_ANNOTATION);
336+
337+
const rid = getAnnotation(obj, RESOURCE_ID_ANNOTATION);
338+
if (!rid) {
339+
return;
340+
}
341+
const originalAnnotations = idToAnnos.get(rid);
342+
if (!originalAnnotations) {
343+
return;
344+
}
242345

243-
const originalPathIndex = m.get(idAnno);
244-
if (!originalPathIndex) {
245-
return;
246-
}
247-
const origPath = originalPathIndex.path;
248-
const origIndex = originalPathIndex.index;
249-
250-
// Infer the user's intend by comparing if there are changes to either the
251-
// new annotation or the legacy annotation.
252-
const pathAnno = getAnnotation(obj, SOURCE_PATH_ANNOTATION);
253-
const legacyPathAnno = getAnnotation(obj, LEGACY_SOURCE_PATH_ANNOTATION);
254-
if (pathAnno && (pathAnno != origPath || !legacyPathAnno)) {
255-
addAnnotation(obj, LEGACY_SOURCE_PATH_ANNOTATION, pathAnno);
256-
} else if (legacyPathAnno && (legacyPathAnno != origPath || !pathAnno)) {
257-
addAnnotation(obj, SOURCE_PATH_ANNOTATION, legacyPathAnno);
346+
let originalPath = originalAnnotations[SOURCE_PATH_ANNOTATION];
347+
if (!originalPath) {
348+
originalPath = originalAnnotations[LEGACY_SOURCE_PATH_ANNOTATION];
349+
}
350+
if (originalPath) {
351+
if (
352+
path &&
353+
legacyPath &&
354+
originalPath !== path &&
355+
originalPath !== legacyPath &&
356+
path !== legacyPath
357+
) {
358+
throw new AnnotationsValueMismatchError();
359+
} else if (path && originalPath !== path) {
360+
addAnnotation(obj, LEGACY_SOURCE_PATH_ANNOTATION, path);
361+
} else if (legacyPath && originalPath !== legacyPath) {
362+
addAnnotation(obj, SOURCE_PATH_ANNOTATION, legacyPath);
258363
}
364+
}
259365

260-
const indexAnno = getAnnotation(obj, SOURCE_INDEX_ANNOTATION);
261-
const legacyIndexAnno = getAnnotation(obj, LEGACY_SOURCE_INDEX_ANNOTATION);
262-
if (indexAnno && (indexAnno != origIndex || !legacyIndexAnno)) {
263-
addAnnotation(obj, LEGACY_SOURCE_INDEX_ANNOTATION, indexAnno);
264-
} else if (
265-
legacyIndexAnno &&
266-
(legacyIndexAnno != origIndex || !indexAnno)
366+
let originalIndex = originalAnnotations[SOURCE_INDEX_ANNOTATION];
367+
if (!originalIndex) {
368+
originalIndex = originalAnnotations[LEGACY_SOURCE_INDEX_ANNOTATION];
369+
}
370+
if (originalIndex) {
371+
if (
372+
index &&
373+
legacyIndex &&
374+
originalIndex !== index &&
375+
originalIndex !== legacyIndex &&
376+
index !== legacyIndex
267377
) {
268-
addAnnotation(obj, SOURCE_INDEX_ANNOTATION, legacyIndexAnno);
378+
throw new AnnotationsValueMismatchError();
379+
} else if (index && originalIndex !== index) {
380+
addAnnotation(obj, LEGACY_SOURCE_INDEX_ANNOTATION, index);
381+
} else if (legacyIndex && originalIndex !== legacyIndex) {
382+
addAnnotation(obj, SOURCE_INDEX_ANNOTATION, legacyIndex);
269383
}
270-
});
384+
}
385+
}
386+
387+
function formatInternalAnnotations(
388+
obj: KubernetesObject,
389+
useInternal: boolean,
390+
useLegacy: boolean
391+
) {
392+
if (!useInternal) {
393+
removeAnnotation(obj, SOURCE_PATH_ANNOTATION);
394+
removeAnnotation(obj, SOURCE_INDEX_ANNOTATION);
395+
removeAnnotation(obj, ID_ANNOTATION);
396+
}
397+
if (!useLegacy) {
398+
removeAnnotation(obj, LEGACY_SOURCE_PATH_ANNOTATION);
399+
removeAnnotation(obj, LEGACY_SOURCE_INDEX_ANNOTATION);
400+
removeAnnotation(obj, LEGACY_ID_ANNOTATION);
401+
}
402+
}
403+
404+
function reconcileAnnotations(
405+
configs: Configs,
406+
m: Map<string, { [key: string]: string }>
407+
) {
408+
const [useInternal, useLegacy] = determineAnnotationFormat(m);
409+
410+
for (const obj of configs.getAll()) {
411+
setMissingAnnotations(obj);
412+
checkAnnotationsAltered(obj, m);
413+
formatInternalAnnotations(obj, useInternal, useLegacy);
414+
checkMismatchAnnos(obj.metadata.annotations);
415+
removeAnnotation(obj, RESOURCE_ID_ANNOTATION);
416+
}
417+
}
418+
419+
class AnnotationsValueMismatchError extends Error {
420+
constructor() {
421+
super('the legacy and internal annotation values mismatch');
422+
}
423+
}
424+
425+
class AnnotationsFormatMismatchError extends Error {
426+
constructor() {
427+
super('the legacy and internal annotation formats mismatch');
428+
}
271429
}
272430

273431
class ResultError extends Error {

0 commit comments

Comments
 (0)