pepr 0.47.0 → 0.48.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/init/enums.d.ts +1 -0
- package/dist/cli/init/enums.d.ts.map +1 -1
- package/dist/cli/init/index.d.ts.map +1 -1
- package/dist/cli/init/walkthrough.d.ts.map +1 -1
- package/dist/cli.js +5 -6
- package/dist/controller.js +1 -1
- package/dist/lib/common-types.d.ts +1 -0
- package/dist/lib/common-types.d.ts.map +1 -1
- package/dist/lib/controller/index.util.d.ts.map +1 -1
- package/dist/lib/controller/migrateStore.d.ts +11 -0
- package/dist/lib/controller/migrateStore.d.ts.map +1 -0
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/processors/validate-processor.d.ts.map +1 -1
- package/dist/lib/processors/watch-processor.d.ts +8 -0
- package/dist/lib/processors/watch-processor.d.ts.map +1 -1
- package/dist/lib/validate-request.d.ts +2 -2
- package/dist/lib/validate-request.d.ts.map +1 -1
- package/dist/lib.js +116 -81
- package/dist/lib.js.map +4 -4
- package/package.json +4 -4
- package/src/cli/init/enums.ts +2 -0
- package/src/cli/init/index.ts +3 -4
- package/src/cli/init/walkthrough.ts +3 -3
- package/src/lib/common-types.ts +1 -0
- package/src/lib/controller/index.util.ts +10 -0
- package/src/lib/controller/migrateStore.ts +56 -0
- package/src/lib/controller/store.ts +11 -40
- package/src/lib/core/module.ts +1 -1
- package/src/lib/processors/validate-processor.ts +5 -0
- package/src/lib/processors/watch-processor.ts +19 -12
- package/src/lib/validate-request.ts +9 -3
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"!src/fixtures/**",
|
|
17
17
|
"!dist/**/*.test.d.ts*"
|
|
18
18
|
],
|
|
19
|
-
"version": "0.
|
|
19
|
+
"version": "0.48.1",
|
|
20
20
|
"main": "dist/lib.js",
|
|
21
21
|
"types": "dist/lib.d.ts",
|
|
22
22
|
"scripts": {
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"test:journey-wasm": "npm run test:journey:k3d && npm run build && npm run test:journey:image && npm run test:journey:run-wasm",
|
|
37
37
|
"test:journey-wasm:unicorn": "npm run test:journey:k3d && npm run build && npm run test:journey:image:unicorn && npm run test:journey:run-wasm",
|
|
38
38
|
"test:journey:image": "docker buildx build --output type=docker --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev",
|
|
39
|
-
"test:journey:image:unicorn": "docker buildx build --output type=docker --tag pepr:dev $(node scripts/read-unicorn-build-args.mjs) . && k3d image import pepr:dev -c pepr-dev",
|
|
39
|
+
"test:journey:image:unicorn": "npm run build && docker buildx build --output type=docker --tag pepr:dev $(node scripts/read-unicorn-build-args.mjs) . && k3d image import pepr:dev -c pepr-dev",
|
|
40
40
|
"test:journey:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0' --wait && kubectl rollout status deployment -n kube-system",
|
|
41
41
|
"test:journey:run": "jest --detectOpenHandles journey/entrypoint.test.ts && npm run test:journey:upgrade",
|
|
42
42
|
"test:journey:run-wasm": "jest --detectOpenHandles journey/entrypoint-wasm.test.ts",
|
|
43
|
-
"test:journey:unicorn": "npm run test:journey:k3d && npm run
|
|
43
|
+
"test:journey:unicorn": "npm run test:journey:k3d && npm run test:journey:image:unicorn && npm run test:journey:run",
|
|
44
44
|
"test:journey:upgrade": "npm run test:journey:k3d && npm run test:journey:image && jest --detectOpenHandles journey/pepr-upgrade.test.ts",
|
|
45
45
|
"test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage --testPathIgnorePatterns='build-artifact.test.ts'",
|
|
46
46
|
"format:check": "eslint src && prettier --config .prettierrc src --check",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"heredoc": "^1.3.1",
|
|
55
55
|
"http-status-codes": "^2.3.0",
|
|
56
56
|
"json-pointer": "^0.6.2",
|
|
57
|
-
"kubernetes-fluent-client": "3.4.
|
|
57
|
+
"kubernetes-fluent-client": "3.4.10",
|
|
58
58
|
"pino": "9.6.0",
|
|
59
59
|
"pino-pretty": "13.0.0",
|
|
60
60
|
"prom-client": "15.1.3",
|
package/src/cli/init/enums.ts
CHANGED
package/src/cli/init/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import { createDir, sanitizeName, write } from "./utils";
|
|
24
24
|
import { confirm, PromptOptions, walkthrough } from "./walkthrough";
|
|
25
25
|
import { ErrorList } from "../../lib/errors";
|
|
26
|
+
import { UUID_LENGTH_LIMIT } from "./enums";
|
|
26
27
|
|
|
27
28
|
export default function (program: RootCmd): void {
|
|
28
29
|
let response = {} as PromptOptions;
|
|
@@ -37,11 +38,9 @@ export default function (program: RootCmd): void {
|
|
|
37
38
|
.option(`--errorBehavior <${ErrorList.join("|")}>`, "Set an errorBehavior.")
|
|
38
39
|
.option(
|
|
39
40
|
"--uuid [string]",
|
|
40
|
-
"Unique identifier for your module with a max length of
|
|
41
|
+
"Unique identifier for your module with a max length of 36 characters.",
|
|
41
42
|
(uuid: string): string => {
|
|
42
|
-
|
|
43
|
-
// length of generated uuid
|
|
44
|
-
if (uuid.length > uuidLengthLimit) {
|
|
43
|
+
if (uuid.length > UUID_LENGTH_LIMIT) {
|
|
45
44
|
throw new Error("The UUID must be 36 characters or fewer.");
|
|
46
45
|
}
|
|
47
46
|
return uuid.toLocaleLowerCase();
|
|
@@ -6,7 +6,7 @@ import prompt, { Answers, PromptObject } from "prompts";
|
|
|
6
6
|
|
|
7
7
|
import { eslint, gitignore, prettier, readme, tsConfig } from "./templates";
|
|
8
8
|
import { sanitizeName } from "./utils";
|
|
9
|
-
import { OnError } from "./enums";
|
|
9
|
+
import { OnError, UUID_LENGTH_LIMIT } from "./enums";
|
|
10
10
|
import { ErrorList } from "../../lib/errors";
|
|
11
11
|
|
|
12
12
|
export type PromptOptions = {
|
|
@@ -33,9 +33,9 @@ async function setUUID(uuid?: string): Promise<Answers<string>> {
|
|
|
33
33
|
name: "uuid",
|
|
34
34
|
message: "Enter a unique identifier for the new Pepr module.\n",
|
|
35
35
|
validate: (val: string) => {
|
|
36
|
-
const uuidLengthLimit = 36;
|
|
37
36
|
return (
|
|
38
|
-
val.length <=
|
|
37
|
+
val.length <= UUID_LENGTH_LIMIT ||
|
|
38
|
+
`The UUID must be ${UUID_LENGTH_LIMIT} characters or fewer.`
|
|
39
39
|
);
|
|
40
40
|
},
|
|
41
41
|
};
|
package/src/lib/common-types.ts
CHANGED
|
@@ -22,12 +22,21 @@ export function karForMutate(mr: MutateResponse): KubeAdmissionReview {
|
|
|
22
22
|
export function karForValidate(ar: AdmissionRequest, vr: ValidateResponse[]): KubeAdmissionReview {
|
|
23
23
|
const isAllowed = vr.filter(r => !r.allowed).length === 0;
|
|
24
24
|
|
|
25
|
+
// Collect all warnings from the ValidateResponse array
|
|
26
|
+
const warnings = vr.reduce<string[]>((acc, curr) => {
|
|
27
|
+
if (curr.warnings && curr.warnings.length > 0) {
|
|
28
|
+
return [...acc, ...curr.warnings];
|
|
29
|
+
}
|
|
30
|
+
return acc;
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
25
33
|
const resp: ValidateResponse =
|
|
26
34
|
vr.length === 0
|
|
27
35
|
? {
|
|
28
36
|
uid: ar.uid,
|
|
29
37
|
allowed: true,
|
|
30
38
|
status: { code: 200, message: "no in-scope validations -- allowed!" },
|
|
39
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
31
40
|
}
|
|
32
41
|
: {
|
|
33
42
|
uid: vr[0].uid,
|
|
@@ -39,6 +48,7 @@ export function karForValidate(ar: AdmissionRequest, vr: ValidateResponse[]): Ku
|
|
|
39
48
|
.map(curr => curr.status?.message)
|
|
40
49
|
.join("; "),
|
|
41
50
|
},
|
|
51
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
42
52
|
};
|
|
43
53
|
return {
|
|
44
54
|
apiVersion: "admission.k8s.io/v1",
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { DataStore, Storage } from "../core/storage";
|
|
2
|
+
import { startsWith } from "ramda";
|
|
3
|
+
import Log, { redactedStore } from "../telemetry/logger";
|
|
4
|
+
import { K8s } from "kubernetes-fluent-client";
|
|
5
|
+
import { Store } from "../k8s";
|
|
6
|
+
import { Operation } from "fast-json-patch";
|
|
7
|
+
import { fillStoreCache, sendUpdatesAndFlushCache } from "./storeCache";
|
|
8
|
+
|
|
9
|
+
export interface StoreMigration {
|
|
10
|
+
name: string;
|
|
11
|
+
namespace: string;
|
|
12
|
+
store: Store;
|
|
13
|
+
stores: Record<string, Storage>;
|
|
14
|
+
setupWatch: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function migrateAndSetupWatch(storeData: StoreMigration): Promise<void> {
|
|
18
|
+
const { store, namespace, name, stores, setupWatch } = storeData;
|
|
19
|
+
|
|
20
|
+
Log.debug(redactedStore(store), "Pepr Store migration");
|
|
21
|
+
// Add cacheID label to store
|
|
22
|
+
await K8s(Store, { namespace, name }).Patch([
|
|
23
|
+
{
|
|
24
|
+
op: "add",
|
|
25
|
+
path: "/metadata/labels/pepr.dev-cacheID",
|
|
26
|
+
value: `${Date.now()}`,
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const data: DataStore = store.data;
|
|
31
|
+
let storeCache: Record<string, Operation> = {};
|
|
32
|
+
|
|
33
|
+
for (const name of Object.keys(stores)) {
|
|
34
|
+
// Get the prefix offset for the keys
|
|
35
|
+
const offset = `${name}-`.length;
|
|
36
|
+
|
|
37
|
+
// Loop over each key in the store
|
|
38
|
+
for (const key of Object.keys(data)) {
|
|
39
|
+
// Match on the capability name as a prefix for non v2 keys
|
|
40
|
+
if (startsWith(name, key) && !startsWith(`${name}-v2`, key)) {
|
|
41
|
+
// populate migrate cache
|
|
42
|
+
storeCache = fillStoreCache(storeCache, name, "remove", {
|
|
43
|
+
key: [key.slice(offset)],
|
|
44
|
+
value: data[key],
|
|
45
|
+
});
|
|
46
|
+
storeCache = fillStoreCache(storeCache, name, "add", {
|
|
47
|
+
key: [key.slice(offset)],
|
|
48
|
+
value: data[key],
|
|
49
|
+
version: "v2",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
storeCache = await sendUpdatesAndFlushCache(storeCache, namespace, name);
|
|
55
|
+
setupWatch();
|
|
56
|
+
}
|
|
@@ -10,6 +10,7 @@ import { Store } from "../k8s";
|
|
|
10
10
|
import Log, { redactedPatch, redactedStore } from "../telemetry/logger";
|
|
11
11
|
import { DataOp, DataSender, DataStore, Storage } from "../core/storage";
|
|
12
12
|
import { fillStoreCache, sendUpdatesAndFlushCache } from "./storeCache";
|
|
13
|
+
import { migrateAndSetupWatch } from "./migrateStore";
|
|
13
14
|
|
|
14
15
|
const namespace = "pepr-system";
|
|
15
16
|
const debounceBackoffReceive = 1000;
|
|
@@ -56,7 +57,16 @@ export class StoreController {
|
|
|
56
57
|
K8s(Store)
|
|
57
58
|
.InNamespace(namespace)
|
|
58
59
|
.Get(this.#name)
|
|
59
|
-
.then(
|
|
60
|
+
.then(
|
|
61
|
+
async (store: Store) =>
|
|
62
|
+
await migrateAndSetupWatch({
|
|
63
|
+
name,
|
|
64
|
+
namespace,
|
|
65
|
+
store,
|
|
66
|
+
stores: this.#stores,
|
|
67
|
+
setupWatch: this.#setupWatch,
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
60
70
|
.catch(this.#createStoreResource),
|
|
61
71
|
Math.random() * 3000, // Add a jitter to the Store creation to avoid collisions
|
|
62
72
|
);
|
|
@@ -67,45 +77,6 @@ export class StoreController {
|
|
|
67
77
|
watcher.start().catch(e => Log.error(e, "Error starting Pepr store watch"));
|
|
68
78
|
};
|
|
69
79
|
|
|
70
|
-
#migrateAndSetupWatch = async (store: Store): Promise<void> => {
|
|
71
|
-
Log.debug(redactedStore(store), "Pepr Store migration");
|
|
72
|
-
// Add cacheID label to store
|
|
73
|
-
await K8s(Store, { namespace, name: this.#name }).Patch([
|
|
74
|
-
{
|
|
75
|
-
op: "add",
|
|
76
|
-
path: "/metadata/labels/pepr.dev-cacheID",
|
|
77
|
-
value: `${Date.now()}`,
|
|
78
|
-
},
|
|
79
|
-
]);
|
|
80
|
-
|
|
81
|
-
const data: DataStore = store.data || {};
|
|
82
|
-
let storeCache: Record<string, Operation> = {};
|
|
83
|
-
|
|
84
|
-
for (const name of Object.keys(this.#stores)) {
|
|
85
|
-
// Get the prefix offset for the keys
|
|
86
|
-
const offset = `${name}-`.length;
|
|
87
|
-
|
|
88
|
-
// Loop over each key in the store
|
|
89
|
-
for (const key of Object.keys(data)) {
|
|
90
|
-
// Match on the capability name as a prefix for non v2 keys
|
|
91
|
-
if (startsWith(name, key) && !startsWith(`${name}-v2`, key)) {
|
|
92
|
-
// populate migrate cache
|
|
93
|
-
storeCache = fillStoreCache(storeCache, name, "remove", {
|
|
94
|
-
key: [key.slice(offset)],
|
|
95
|
-
value: data[key],
|
|
96
|
-
});
|
|
97
|
-
storeCache = fillStoreCache(storeCache, name, "add", {
|
|
98
|
-
key: [key.slice(offset)],
|
|
99
|
-
value: data[key],
|
|
100
|
-
version: "v2",
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
storeCache = await sendUpdatesAndFlushCache(storeCache, namespace, this.#name);
|
|
106
|
-
this.#setupWatch();
|
|
107
|
-
};
|
|
108
|
-
|
|
109
80
|
#receive = (store: Store): void => {
|
|
110
81
|
Log.debug(redactedStore(store), "Pepr Store update");
|
|
111
82
|
|
package/src/lib/core/module.ts
CHANGED
|
@@ -62,7 +62,7 @@ export class PeprModule {
|
|
|
62
62
|
const controllerHooks: ControllerHooks = {
|
|
63
63
|
beforeHook: opts.beforeHook,
|
|
64
64
|
afterHook: opts.afterHook,
|
|
65
|
-
onReady: (): void => {
|
|
65
|
+
onReady: async (): Promise<void> => {
|
|
66
66
|
// Wait for the controller to be ready before setting up watches
|
|
67
67
|
if (isWatchMode() || isDevMode()) {
|
|
68
68
|
try {
|
|
@@ -41,6 +41,11 @@ export async function processRequest(
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// Transfer any warnings from the callback response to the validation response
|
|
45
|
+
if (callbackResp.warnings && callbackResp.warnings.length > 0) {
|
|
46
|
+
valResp.warnings = callbackResp.warnings;
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
Log.info(
|
|
45
50
|
actionMetadata,
|
|
46
51
|
`Validation action complete (${label}): ${callbackResp.allowed ? "allowed" : "denied"}`,
|
|
@@ -86,13 +86,11 @@ const eventToPhaseMap = {
|
|
|
86
86
|
* @param capabilities The capabilities to load watches for
|
|
87
87
|
*/
|
|
88
88
|
export function setupWatch(capabilities: Capability[], ignoredNamespaces?: string[]): void {
|
|
89
|
-
|
|
90
|
-
capability.bindings
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
),
|
|
95
|
-
);
|
|
89
|
+
for (const capability of capabilities) {
|
|
90
|
+
for (const binding of capability.bindings.filter(b => b.isWatch)) {
|
|
91
|
+
runBinding(binding, capability.namespaces, ignoredNamespaces);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
96
94
|
}
|
|
97
95
|
|
|
98
96
|
/**
|
|
@@ -101,7 +99,7 @@ export function setupWatch(capabilities: Capability[], ignoredNamespaces?: strin
|
|
|
101
99
|
* @param binding the binding to watch
|
|
102
100
|
* @param capabilityNamespaces list of namespaces to filter on
|
|
103
101
|
*/
|
|
104
|
-
async function runBinding(
|
|
102
|
+
export async function runBinding(
|
|
105
103
|
binding: Binding,
|
|
106
104
|
capabilityNamespaces: string[],
|
|
107
105
|
ignoredNamespaces?: string[],
|
|
@@ -185,14 +183,20 @@ async function runBinding(
|
|
|
185
183
|
);
|
|
186
184
|
|
|
187
185
|
// Register event handlers
|
|
188
|
-
|
|
186
|
+
try {
|
|
187
|
+
registerWatchEventHandlers(watcher, logEvent, metricsCollector);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"WatchEventHandler Registration Error: Unable to register event watch handler.",
|
|
191
|
+
{ cause: err },
|
|
192
|
+
);
|
|
193
|
+
}
|
|
189
194
|
|
|
190
195
|
// Start the watch
|
|
191
196
|
try {
|
|
192
197
|
await watcher.start();
|
|
193
198
|
} catch (err) {
|
|
194
|
-
|
|
195
|
-
process.exit(1);
|
|
199
|
+
throw new Error("WatchStart Error: Unable to start watch.", { cause: err });
|
|
196
200
|
}
|
|
197
201
|
}
|
|
198
202
|
|
|
@@ -235,7 +239,10 @@ export function registerWatchEventHandlers(
|
|
|
235
239
|
[WatchEvent.GIVE_UP]: err => {
|
|
236
240
|
// If failure continues, log and exit
|
|
237
241
|
logEvent(WatchEvent.GIVE_UP, err.message);
|
|
238
|
-
|
|
242
|
+
throw new Error(
|
|
243
|
+
"WatchEvent GiveUp Error: The watch has failed to start after several attempts.",
|
|
244
|
+
{ cause: err },
|
|
245
|
+
);
|
|
239
246
|
},
|
|
240
247
|
[WatchEvent.CONNECT]: url => logEvent(WatchEvent.CONNECT, url),
|
|
241
248
|
[WatchEvent.DATA_ERROR]: err => logEvent(WatchEvent.DATA_ERROR, err.message),
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import { KubernetesObject } from "kubernetes-fluent-client";
|
|
7
7
|
|
|
8
8
|
import { clone } from "ramda";
|
|
9
|
-
import { Operation } from "./enums";
|
|
10
9
|
import { AdmissionRequest, ValidateActionResponse } from "./common-types";
|
|
10
|
+
import { Operation } from "./enums";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* The RequestWrapper class provides methods to modify Kubernetes objects in the context
|
|
@@ -79,9 +79,10 @@ export class PeprValidateRequest<T extends KubernetesObject> {
|
|
|
79
79
|
*
|
|
80
80
|
* @returns The validation response.
|
|
81
81
|
*/
|
|
82
|
-
Approve = (): ValidateActionResponse => {
|
|
82
|
+
Approve = (warnings?: string[]): ValidateActionResponse => {
|
|
83
83
|
return {
|
|
84
84
|
allowed: true,
|
|
85
|
+
warnings,
|
|
85
86
|
};
|
|
86
87
|
};
|
|
87
88
|
|
|
@@ -92,11 +93,16 @@ export class PeprValidateRequest<T extends KubernetesObject> {
|
|
|
92
93
|
* @param statusCode Optional status code to return to the user.
|
|
93
94
|
* @returns The validation response.
|
|
94
95
|
*/
|
|
95
|
-
Deny = (
|
|
96
|
+
Deny = (
|
|
97
|
+
statusMessage?: string,
|
|
98
|
+
statusCode?: number,
|
|
99
|
+
warnings?: string[],
|
|
100
|
+
): ValidateActionResponse => {
|
|
96
101
|
return {
|
|
97
102
|
allowed: false,
|
|
98
103
|
statusCode,
|
|
99
104
|
statusMessage,
|
|
105
|
+
warnings,
|
|
100
106
|
};
|
|
101
107
|
};
|
|
102
108
|
}
|