pepr 0.28.7 → 0.29.0
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/README.md +1 -1
- package/dist/cli.js +1 -1
- package/dist/controller.js +26 -18
- package/dist/lib/helpers.d.ts +3 -0
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib.d.ts +2 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +62 -17
- package/dist/lib.js.map +4 -4
- package/dist/sdk/sdk.d.ts +38 -0
- package/dist/sdk/sdk.d.ts.map +1 -0
- package/package.json +4 -3
- package/src/lib/capability.ts +1 -1
- package/src/lib/helpers.ts +11 -0
- package/src/lib.ts +2 -2
- package/src/runtime/controller.ts +16 -20
- package/src/sdk/sdk.ts +116 -0
- package/dist/lib/module-helpers.d.ts +0 -5
- package/dist/lib/module-helpers.d.ts.map +0 -1
- package/src/lib/module-helpers.ts +0 -27
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { PeprValidateRequest } from "../lib/validate-request";
|
|
2
|
+
import { PeprMutateRequest } from "../lib/mutate-request";
|
|
3
|
+
import { a } from "../lib";
|
|
4
|
+
import { V1OwnerReference } from "@kubernetes/client-node";
|
|
5
|
+
import { GenericKind } from "kubernetes-fluent-client";
|
|
6
|
+
import { kind } from "kubernetes-fluent-client";
|
|
7
|
+
/**
|
|
8
|
+
* Returns all containers in a pod
|
|
9
|
+
* @param request the request/pod to get the containers from
|
|
10
|
+
* @param containerType the type of container to get
|
|
11
|
+
* @returns the list of containers in the pod
|
|
12
|
+
*/
|
|
13
|
+
export declare function containers(request: PeprValidateRequest<a.Pod> | PeprMutateRequest<a.Pod>, containerType?: "containers" | "initContainers" | "ephemeralContainers"): import("@kubernetes/client-node").V1Container[];
|
|
14
|
+
/**
|
|
15
|
+
* Write a K8s event for a CRD
|
|
16
|
+
*
|
|
17
|
+
* @param cr The custom resource to write the event for
|
|
18
|
+
* @param event The event to write, should contain a human-readable message for the event
|
|
19
|
+
* @param eventType The type of event to write, for example "Warning"
|
|
20
|
+
* @param eventReason The reason for the event, for example "ReconciliationFailed"
|
|
21
|
+
* @param reportingComponent The component that is reporting the event, for example "uds.dev/operator"
|
|
22
|
+
* @param reportingInstance The instance of the component that is reporting the event, for example process.env.HOSTNAME
|
|
23
|
+
*/
|
|
24
|
+
export declare function writeEvent(cr: GenericKind, event: Partial<kind.CoreEvent>, eventType: string, eventReason: string, reportingComponent: string, reportingInstance: string): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Get the owner reference for a custom resource
|
|
27
|
+
* @param cr the custom resource to get the owner reference for
|
|
28
|
+
* @returns the owner reference for the custom resource
|
|
29
|
+
*/
|
|
30
|
+
export declare function getOwnerRefFrom(cr: GenericKind): V1OwnerReference[];
|
|
31
|
+
/**
|
|
32
|
+
* Sanitize a resource name to make it a valid Kubernetes resource name.
|
|
33
|
+
*
|
|
34
|
+
* @param name the name of the resource to sanitize
|
|
35
|
+
* @returns the sanitized resource name
|
|
36
|
+
*/
|
|
37
|
+
export declare function sanitizeResourceName(name: string): string;
|
|
38
|
+
//# sourceMappingURL=sdk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/sdk/sdk.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAO,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAGrD;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,EAC9D,aAAa,CAAC,EAAE,YAAY,GAAG,gBAAgB,GAAG,qBAAqB,mDAgBxE;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,EAAE,MAAM,iBAwB1B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,WAAW,GAAG,gBAAgB,EAAE,CAWnE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,UAYhD"}
|
package/package.json
CHANGED
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
"engines": {
|
|
10
10
|
"node": ">=18.0.0"
|
|
11
11
|
},
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.29.0",
|
|
13
13
|
"main": "dist/lib.js",
|
|
14
14
|
"types": "dist/lib.d.ts",
|
|
15
15
|
"scripts": {
|
|
16
16
|
"gen-data-json": "node hack/build-template-data.js",
|
|
17
17
|
"prebuild": "rm -fr dist/* && npm run gen-data-json",
|
|
18
18
|
"build": "tsc && node build.mjs",
|
|
19
|
+
"build:image": "npm run build && docker buildx build --tag pepr:dev .",
|
|
19
20
|
"test": "npm run test:unit && npm run test:journey",
|
|
20
21
|
"test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage",
|
|
21
22
|
"test:journey": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run",
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"format:fix": "eslint src --fix && prettier src --write"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@types/ramda": "0.29.
|
|
35
|
+
"@types/ramda": "0.29.12",
|
|
35
36
|
"express": "4.19.2",
|
|
36
37
|
"fast-json-patch": "3.1.1",
|
|
37
38
|
"kubernetes-fluent-client": "2.3.0",
|
|
@@ -44,7 +45,7 @@
|
|
|
44
45
|
"@commitlint/cli": "19.2.1",
|
|
45
46
|
"@commitlint/config-conventional": "19.1.0",
|
|
46
47
|
"@jest/globals": "29.7.0",
|
|
47
|
-
"@types/eslint": "8.56.
|
|
48
|
+
"@types/eslint": "8.56.7",
|
|
48
49
|
"@types/express": "4.17.21",
|
|
49
50
|
"@types/node": "18.x.x",
|
|
50
51
|
"@types/node-forge": "1.3.11",
|
package/src/lib/capability.ts
CHANGED
|
@@ -50,7 +50,7 @@ export class Capability implements CapabilityExport {
|
|
|
50
50
|
const { name, every, unit, run, startTime, completions } = schedule;
|
|
51
51
|
this.hasSchedule = true;
|
|
52
52
|
|
|
53
|
-
if (process.env.PEPR_WATCH_MODE === "true") {
|
|
53
|
+
if (process.env.PEPR_WATCH_MODE === "true" || process.env.PEPR_MODE === "dev") {
|
|
54
54
|
// Only create/watch schedule store if necessary
|
|
55
55
|
|
|
56
56
|
// Create a new schedule
|
package/src/lib/helpers.ts
CHANGED
|
@@ -6,6 +6,17 @@ import { K8s, KubernetesObject, kind } from "kubernetes-fluent-client";
|
|
|
6
6
|
import Log from "./logger";
|
|
7
7
|
import { Binding, CapabilityExport } from "./types";
|
|
8
8
|
|
|
9
|
+
export class ValidationError extends Error {}
|
|
10
|
+
|
|
11
|
+
export function validateHash(expectedHash: string): void {
|
|
12
|
+
// Require the hash to be a valid SHA-256 hash (64 characters, hexadecimal)
|
|
13
|
+
const sha256Regex = /^[a-f0-9]{64}$/i;
|
|
14
|
+
if (!expectedHash || !sha256Regex.test(expectedHash)) {
|
|
15
|
+
Log.error(`Invalid hash. Expected a valid SHA-256 hash, got ${expectedHash}`);
|
|
16
|
+
throw new ValidationError("Invalid hash");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
type RBACMap = {
|
|
10
21
|
[key: string]: {
|
|
11
22
|
verbs: string[];
|
package/src/lib.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { PeprModule } from "./lib/module";
|
|
|
7
7
|
import { PeprMutateRequest } from "./lib/mutate-request";
|
|
8
8
|
import * as PeprUtils from "./lib/utils";
|
|
9
9
|
import { PeprValidateRequest } from "./lib/validate-request";
|
|
10
|
-
import
|
|
10
|
+
import * as sdk from "./sdk/sdk";
|
|
11
11
|
|
|
12
12
|
export {
|
|
13
13
|
Capability,
|
|
@@ -23,5 +23,5 @@ export {
|
|
|
23
23
|
fetch,
|
|
24
24
|
fetchStatus,
|
|
25
25
|
kind,
|
|
26
|
-
|
|
26
|
+
sdk,
|
|
27
27
|
};
|
|
@@ -11,17 +11,9 @@ import { K8s, kind } from "kubernetes-fluent-client";
|
|
|
11
11
|
import Log from "../lib/logger";
|
|
12
12
|
import { packageJSON } from "../templates/data.json";
|
|
13
13
|
import { peprStoreCRD } from "../lib/assets/store";
|
|
14
|
-
|
|
14
|
+
import { validateHash } from "../lib/helpers";
|
|
15
15
|
const { version } = packageJSON;
|
|
16
16
|
|
|
17
|
-
function validateHash(expectedHash: string) {
|
|
18
|
-
// Require the hash to be 64 characters long
|
|
19
|
-
if (!expectedHash || expectedHash.length !== 64) {
|
|
20
|
-
Log.error("Invalid hash");
|
|
21
|
-
process.exit(1);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
17
|
function runModule(expectedHash: string) {
|
|
26
18
|
const gzPath = `/app/load/module-${expectedHash}.js.gz`;
|
|
27
19
|
const jsPath = `/app/module-${expectedHash}.js`;
|
|
@@ -31,8 +23,7 @@ function runModule(expectedHash: string) {
|
|
|
31
23
|
|
|
32
24
|
// Check if the path is a valid file
|
|
33
25
|
if (!fs.existsSync(gzPath)) {
|
|
34
|
-
|
|
35
|
-
process.exit(1);
|
|
26
|
+
throw new Error(`File not found: ${gzPath}`);
|
|
36
27
|
}
|
|
37
28
|
|
|
38
29
|
try {
|
|
@@ -46,9 +37,10 @@ function runModule(expectedHash: string) {
|
|
|
46
37
|
const actualHash = crypto.createHash("sha256").update(code).digest("hex");
|
|
47
38
|
|
|
48
39
|
// If the hash doesn't match, exit
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
// This is a timing safe comparison to prevent timing attacks
|
|
41
|
+
// https://en.wikipedia.org/wiki/Timing_attack
|
|
42
|
+
if (!crypto.timingSafeEqual(Buffer.from(expectedHash, "hex"), Buffer.from(actualHash, "hex"))) {
|
|
43
|
+
throw new Error(`File hash does not match, expected ${expectedHash} but got ${actualHash}`);
|
|
52
44
|
}
|
|
53
45
|
|
|
54
46
|
Log.info(`File hash matches, running module`);
|
|
@@ -59,8 +51,7 @@ function runModule(expectedHash: string) {
|
|
|
59
51
|
// Run the module
|
|
60
52
|
fork(jsPath);
|
|
61
53
|
} catch (e) {
|
|
62
|
-
|
|
63
|
-
process.exit(1);
|
|
54
|
+
throw new Error(`Failed to decompress module: ${e}`);
|
|
64
55
|
}
|
|
65
56
|
}
|
|
66
57
|
|
|
@@ -69,11 +60,16 @@ Log.info(`Pepr Controller (v${version})`);
|
|
|
69
60
|
const hash = process.argv[2];
|
|
70
61
|
|
|
71
62
|
const startup = async () => {
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
try {
|
|
64
|
+
Log.info("Applying the Pepr Store CRD if it doesn't exist");
|
|
65
|
+
await K8s(kind.CustomResourceDefinition).Apply(peprStoreCRD, { force: true });
|
|
74
66
|
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
validateHash(hash);
|
|
68
|
+
runModule(hash);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
Log.error(err);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
77
73
|
};
|
|
78
74
|
|
|
79
75
|
startup().catch(err => Log.error(err));
|
package/src/sdk/sdk.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { PeprValidateRequest } from "../lib/validate-request";
|
|
5
|
+
import { PeprMutateRequest } from "../lib/mutate-request";
|
|
6
|
+
import { a } from "../lib";
|
|
7
|
+
import { V1OwnerReference } from "@kubernetes/client-node";
|
|
8
|
+
import { GenericKind } from "kubernetes-fluent-client";
|
|
9
|
+
import { K8s, kind } from "kubernetes-fluent-client";
|
|
10
|
+
import Log from "../lib/logger";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns all containers in a pod
|
|
14
|
+
* @param request the request/pod to get the containers from
|
|
15
|
+
* @param containerType the type of container to get
|
|
16
|
+
* @returns the list of containers in the pod
|
|
17
|
+
*/
|
|
18
|
+
export function containers(
|
|
19
|
+
request: PeprValidateRequest<a.Pod> | PeprMutateRequest<a.Pod>,
|
|
20
|
+
containerType?: "containers" | "initContainers" | "ephemeralContainers",
|
|
21
|
+
) {
|
|
22
|
+
const containers = request.Raw.spec?.containers || [];
|
|
23
|
+
const initContainers = request.Raw.spec?.initContainers || [];
|
|
24
|
+
const ephemeralContainers = request.Raw.spec?.ephemeralContainers || [];
|
|
25
|
+
|
|
26
|
+
if (containerType === "containers") {
|
|
27
|
+
return containers;
|
|
28
|
+
}
|
|
29
|
+
if (containerType === "initContainers") {
|
|
30
|
+
return initContainers;
|
|
31
|
+
}
|
|
32
|
+
if (containerType === "ephemeralContainers") {
|
|
33
|
+
return ephemeralContainers;
|
|
34
|
+
}
|
|
35
|
+
return [...containers, ...initContainers, ...ephemeralContainers];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Write a K8s event for a CRD
|
|
40
|
+
*
|
|
41
|
+
* @param cr The custom resource to write the event for
|
|
42
|
+
* @param event The event to write, should contain a human-readable message for the event
|
|
43
|
+
* @param eventType The type of event to write, for example "Warning"
|
|
44
|
+
* @param eventReason The reason for the event, for example "ReconciliationFailed"
|
|
45
|
+
* @param reportingComponent The component that is reporting the event, for example "uds.dev/operator"
|
|
46
|
+
* @param reportingInstance The instance of the component that is reporting the event, for example process.env.HOSTNAME
|
|
47
|
+
*/
|
|
48
|
+
export async function writeEvent(
|
|
49
|
+
cr: GenericKind,
|
|
50
|
+
event: Partial<kind.CoreEvent>,
|
|
51
|
+
eventType: string,
|
|
52
|
+
eventReason: string,
|
|
53
|
+
reportingComponent: string,
|
|
54
|
+
reportingInstance: string,
|
|
55
|
+
) {
|
|
56
|
+
Log.debug(cr.metadata, `Writing event: ${event.message}`);
|
|
57
|
+
|
|
58
|
+
await K8s(kind.CoreEvent).Create({
|
|
59
|
+
type: eventType,
|
|
60
|
+
reason: eventReason,
|
|
61
|
+
...event,
|
|
62
|
+
// Fixed values
|
|
63
|
+
metadata: {
|
|
64
|
+
namespace: cr.metadata!.namespace,
|
|
65
|
+
generateName: cr.metadata!.name,
|
|
66
|
+
},
|
|
67
|
+
involvedObject: {
|
|
68
|
+
apiVersion: cr.apiVersion,
|
|
69
|
+
kind: cr.kind,
|
|
70
|
+
name: cr.metadata!.name,
|
|
71
|
+
namespace: cr.metadata!.namespace,
|
|
72
|
+
uid: cr.metadata!.uid,
|
|
73
|
+
},
|
|
74
|
+
firstTimestamp: new Date(),
|
|
75
|
+
reportingComponent: reportingComponent,
|
|
76
|
+
reportingInstance: reportingInstance,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get the owner reference for a custom resource
|
|
82
|
+
* @param cr the custom resource to get the owner reference for
|
|
83
|
+
* @returns the owner reference for the custom resource
|
|
84
|
+
*/
|
|
85
|
+
export function getOwnerRefFrom(cr: GenericKind): V1OwnerReference[] {
|
|
86
|
+
const { name, uid } = cr.metadata!;
|
|
87
|
+
|
|
88
|
+
return [
|
|
89
|
+
{
|
|
90
|
+
apiVersion: cr.apiVersion!,
|
|
91
|
+
kind: cr.kind!,
|
|
92
|
+
uid: uid!,
|
|
93
|
+
name: name!,
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Sanitize a resource name to make it a valid Kubernetes resource name.
|
|
100
|
+
*
|
|
101
|
+
* @param name the name of the resource to sanitize
|
|
102
|
+
* @returns the sanitized resource name
|
|
103
|
+
*/
|
|
104
|
+
export function sanitizeResourceName(name: string) {
|
|
105
|
+
return (
|
|
106
|
+
name
|
|
107
|
+
// The name must be lowercase
|
|
108
|
+
.toLowerCase()
|
|
109
|
+
// Replace sequences of non-alphanumeric characters with a single '-'
|
|
110
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
111
|
+
// Truncate the name to 250 characters
|
|
112
|
+
.slice(0, 250)
|
|
113
|
+
// Remove leading and trailing non-letter characters
|
|
114
|
+
.replace(/^[^a-z]+|[^a-z]+$/g, "")
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { PeprValidateRequest } from "./validate-request";
|
|
2
|
-
import { PeprMutateRequest } from "./mutate-request";
|
|
3
|
-
import { a } from "../lib";
|
|
4
|
-
export declare function containers(request: PeprValidateRequest<a.Pod> | PeprMutateRequest<a.Pod>, containerType?: "containers" | "initContainers" | "ephemeralContainers"): import("@kubernetes/client-node").V1Container[];
|
|
5
|
-
//# sourceMappingURL=module-helpers.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"module-helpers.d.ts","sourceRoot":"","sources":["../../src/lib/module-helpers.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAG3B,wBAAgB,UAAU,CACxB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,EAC9D,aAAa,CAAC,EAAE,YAAY,GAAG,gBAAgB,GAAG,qBAAqB,mDAgBxE"}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
-
|
|
4
|
-
import { PeprValidateRequest } from "./validate-request";
|
|
5
|
-
import { PeprMutateRequest } from "./mutate-request";
|
|
6
|
-
import { a } from "../lib";
|
|
7
|
-
|
|
8
|
-
// Returns all containers in the pod
|
|
9
|
-
export function containers(
|
|
10
|
-
request: PeprValidateRequest<a.Pod> | PeprMutateRequest<a.Pod>,
|
|
11
|
-
containerType?: "containers" | "initContainers" | "ephemeralContainers",
|
|
12
|
-
) {
|
|
13
|
-
const containers = request.Raw.spec?.containers || [];
|
|
14
|
-
const initContainers = request.Raw.spec?.initContainers || [];
|
|
15
|
-
const ephemeralContainers = request.Raw.spec?.ephemeralContainers || [];
|
|
16
|
-
|
|
17
|
-
if (containerType === "containers") {
|
|
18
|
-
return containers;
|
|
19
|
-
}
|
|
20
|
-
if (containerType === "initContainers") {
|
|
21
|
-
return initContainers;
|
|
22
|
-
}
|
|
23
|
-
if (containerType === "ephemeralContainers") {
|
|
24
|
-
return ephemeralContainers;
|
|
25
|
-
}
|
|
26
|
-
return [...containers, ...initContainers, ...ephemeralContainers];
|
|
27
|
-
}
|