pepr 0.0.0-development
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/.prettierignore +1 -0
- package/CODE_OF_CONDUCT.md +133 -0
- package/LICENSE +201 -0
- package/README.md +151 -0
- package/SECURITY.md +18 -0
- package/SUPPORT.md +16 -0
- package/codecov.yaml +19 -0
- package/commitlint.config.js +1 -0
- package/package.json +70 -0
- package/src/cli.ts +48 -0
- package/src/lib/assets/deploy.ts +122 -0
- package/src/lib/assets/destroy.ts +33 -0
- package/src/lib/assets/helm.ts +219 -0
- package/src/lib/assets/index.ts +175 -0
- package/src/lib/assets/loader.ts +41 -0
- package/src/lib/assets/networking.ts +89 -0
- package/src/lib/assets/pods.ts +353 -0
- package/src/lib/assets/rbac.ts +111 -0
- package/src/lib/assets/store.ts +49 -0
- package/src/lib/assets/webhooks.ts +147 -0
- package/src/lib/assets/yaml.ts +234 -0
- package/src/lib/capability.ts +314 -0
- package/src/lib/controller/index.ts +326 -0
- package/src/lib/controller/store.ts +219 -0
- package/src/lib/errors.ts +20 -0
- package/src/lib/filter.ts +110 -0
- package/src/lib/helpers.ts +342 -0
- package/src/lib/included-files.ts +19 -0
- package/src/lib/k8s.ts +169 -0
- package/src/lib/logger.ts +27 -0
- package/src/lib/metrics.ts +120 -0
- package/src/lib/module.ts +136 -0
- package/src/lib/mutate-processor.ts +160 -0
- package/src/lib/mutate-request.ts +153 -0
- package/src/lib/queue.ts +89 -0
- package/src/lib/schedule.ts +175 -0
- package/src/lib/storage.ts +192 -0
- package/src/lib/tls.ts +90 -0
- package/src/lib/types.ts +215 -0
- package/src/lib/utils.ts +57 -0
- package/src/lib/validate-processor.ts +80 -0
- package/src/lib/validate-request.ts +102 -0
- package/src/lib/watch-processor.ts +124 -0
- package/src/lib.ts +27 -0
- package/src/runtime/controller.ts +75 -0
- package/src/sdk/sdk.ts +116 -0
- package/src/templates/.eslintrc.template.json +18 -0
- package/src/templates/.prettierrc.json +13 -0
- package/src/templates/README.md +21 -0
- package/src/templates/capabilities/hello-pepr.samples.json +160 -0
- package/src/templates/capabilities/hello-pepr.ts +426 -0
- package/src/templates/gitignore +4 -0
- package/src/templates/package.json +20 -0
- package/src/templates/pepr.code-snippets.json +21 -0
- package/src/templates/pepr.ts +17 -0
- package/src/templates/settings.json +10 -0
- package/src/templates/tsconfig.json +9 -0
- package/src/templates/tsconfig.module.json +19 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
/* eslint-disable class-methods-use-this */
|
|
5
|
+
|
|
6
|
+
import { KubernetesObject } from "kubernetes-fluent-client";
|
|
7
|
+
|
|
8
|
+
import { clone } from "ramda";
|
|
9
|
+
import { Operation, AdmissionRequest } from "./k8s";
|
|
10
|
+
import { ValidateActionResponse } from "./types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The RequestWrapper class provides methods to modify Kubernetes objects in the context
|
|
14
|
+
* of a mutating webhook request.
|
|
15
|
+
*/
|
|
16
|
+
export class PeprValidateRequest<T extends KubernetesObject> {
|
|
17
|
+
Raw: T;
|
|
18
|
+
|
|
19
|
+
#input: AdmissionRequest<T>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Provides access to the old resource in the request if available.
|
|
23
|
+
* @returns The old Kubernetes resource object or null if not available.
|
|
24
|
+
*/
|
|
25
|
+
get OldResource() {
|
|
26
|
+
return this.#input.oldObject;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Provides access to the request object.
|
|
31
|
+
* @returns The request object containing the Kubernetes resource.
|
|
32
|
+
*/
|
|
33
|
+
get Request() {
|
|
34
|
+
return this.#input;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates a new instance of the Action class.
|
|
39
|
+
* @param input - The request object containing the Kubernetes resource to modify.
|
|
40
|
+
*/
|
|
41
|
+
constructor(input: AdmissionRequest<T>) {
|
|
42
|
+
this.#input = input;
|
|
43
|
+
|
|
44
|
+
// If this is a DELETE operation, use the oldObject instead
|
|
45
|
+
if (input.operation.toUpperCase() === Operation.DELETE) {
|
|
46
|
+
this.Raw = clone(input.oldObject as T);
|
|
47
|
+
} else {
|
|
48
|
+
// Otherwise, use the incoming object
|
|
49
|
+
this.Raw = clone(input.object);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!this.Raw) {
|
|
53
|
+
throw new Error("unable to load the request object into PeprRequest.Raw");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if a label exists on the Kubernetes resource.
|
|
59
|
+
*
|
|
60
|
+
* @param key the label key to check
|
|
61
|
+
* @returns
|
|
62
|
+
*/
|
|
63
|
+
HasLabel = (key: string) => {
|
|
64
|
+
return this.Raw.metadata?.labels?.[key] !== undefined;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if an annotation exists on the Kubernetes resource.
|
|
69
|
+
*
|
|
70
|
+
* @param key the annotation key to check
|
|
71
|
+
* @returns
|
|
72
|
+
*/
|
|
73
|
+
HasAnnotation = (key: string) => {
|
|
74
|
+
return this.Raw.metadata?.annotations?.[key] !== undefined;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create a validation response that allows the request.
|
|
79
|
+
*
|
|
80
|
+
* @returns The validation response.
|
|
81
|
+
*/
|
|
82
|
+
Approve = (): ValidateActionResponse => {
|
|
83
|
+
return {
|
|
84
|
+
allowed: true,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create a validation response that denies the request.
|
|
90
|
+
*
|
|
91
|
+
* @param statusMessage Optional status message to return to the user.
|
|
92
|
+
* @param statusCode Optional status code to return to the user.
|
|
93
|
+
* @returns The validation response.
|
|
94
|
+
*/
|
|
95
|
+
Deny = (statusMessage?: string, statusCode?: number): ValidateActionResponse => {
|
|
96
|
+
return {
|
|
97
|
+
allowed: false,
|
|
98
|
+
statusCode,
|
|
99
|
+
statusMessage,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
import { K8s, KubernetesObject, WatchCfg, WatchEvent } from "kubernetes-fluent-client";
|
|
4
|
+
import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
|
|
5
|
+
import { Capability } from "./capability";
|
|
6
|
+
import { filterNoMatchReason } from "./helpers";
|
|
7
|
+
import Log from "./logger";
|
|
8
|
+
import { Queue } from "./queue";
|
|
9
|
+
import { Binding, Event } from "./types";
|
|
10
|
+
|
|
11
|
+
// Watch configuration
|
|
12
|
+
const watchCfg: WatchCfg = {
|
|
13
|
+
retryMax: process.env.PEPR_RETRYMAX ? parseInt(process.env.PEPR_RETRYMAX, 10) : 5,
|
|
14
|
+
retryDelaySec: process.env.PEPR_RETRYDELAYSECONDS ? parseInt(process.env.PEPR_RETRYDELAYSECONDS, 10) : 5,
|
|
15
|
+
resyncIntervalSec: process.env.PEPR_RESYNCINTERVALSECONDS
|
|
16
|
+
? parseInt(process.env.PEPR_RESYNCINTERVALSECONDS, 10)
|
|
17
|
+
: 300,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Map the event to the watch phase
|
|
21
|
+
const eventToPhaseMap = {
|
|
22
|
+
[Event.Create]: [WatchPhase.Added],
|
|
23
|
+
[Event.Update]: [WatchPhase.Modified],
|
|
24
|
+
[Event.CreateOrUpdate]: [WatchPhase.Added, WatchPhase.Modified],
|
|
25
|
+
[Event.Delete]: [WatchPhase.Deleted],
|
|
26
|
+
[Event.Any]: [WatchPhase.Added, WatchPhase.Modified, WatchPhase.Deleted],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Entrypoint for setting up watches for all capabilities
|
|
31
|
+
*
|
|
32
|
+
* @param capabilities The capabilities to load watches for
|
|
33
|
+
*/
|
|
34
|
+
export function setupWatch(capabilities: Capability[]) {
|
|
35
|
+
capabilities.map(capability =>
|
|
36
|
+
capability.bindings
|
|
37
|
+
.filter(binding => binding.isWatch)
|
|
38
|
+
.forEach(bindingElement => runBinding(bindingElement, capability.namespaces)),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Setup a watch for a binding
|
|
44
|
+
*
|
|
45
|
+
* @param binding the binding to watch
|
|
46
|
+
* @param capabilityNamespaces list of namespaces to filter on
|
|
47
|
+
*/
|
|
48
|
+
async function runBinding(binding: Binding, capabilityNamespaces: string[]) {
|
|
49
|
+
// Get the phases to match, fallback to any
|
|
50
|
+
const phaseMatch: WatchPhase[] = eventToPhaseMap[binding.event] || eventToPhaseMap[Event.Any];
|
|
51
|
+
|
|
52
|
+
// The watch callback is run when an object is received or dequeued
|
|
53
|
+
|
|
54
|
+
Log.debug({ watchCfg }, "Effective WatchConfig");
|
|
55
|
+
const watchCallback = async (obj: KubernetesObject, type: WatchPhase) => {
|
|
56
|
+
// First, filter the object based on the phase
|
|
57
|
+
if (phaseMatch.includes(type)) {
|
|
58
|
+
try {
|
|
59
|
+
// Then, check if the object matches the filter
|
|
60
|
+
const filterMatch = filterNoMatchReason(binding, obj, capabilityNamespaces);
|
|
61
|
+
if (filterMatch === "") {
|
|
62
|
+
await binding.watchCallback?.(obj, type);
|
|
63
|
+
} else {
|
|
64
|
+
Log.debug(filterMatch);
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {
|
|
67
|
+
// Errors in the watch callback should not crash the controller
|
|
68
|
+
Log.error(e, "Error executing watch callback");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const queue = new Queue();
|
|
74
|
+
queue.setReconcile(watchCallback);
|
|
75
|
+
|
|
76
|
+
// Setup the resource watch
|
|
77
|
+
const watcher = K8s(binding.model, binding.filters).Watch(async (obj, type) => {
|
|
78
|
+
Log.debug(obj, `Watch event ${type} received`);
|
|
79
|
+
|
|
80
|
+
// If the binding is a queue, enqueue the object
|
|
81
|
+
if (binding.isQueue) {
|
|
82
|
+
await queue.enqueue(obj, type);
|
|
83
|
+
} else {
|
|
84
|
+
// Otherwise, run the watch callback directly
|
|
85
|
+
await watchCallback(obj, type);
|
|
86
|
+
}
|
|
87
|
+
}, watchCfg);
|
|
88
|
+
|
|
89
|
+
// If failure continues, log and exit
|
|
90
|
+
watcher.events.on(WatchEvent.GIVE_UP, err => {
|
|
91
|
+
Log.error(err, "Watch failed after 5 attempts, giving up");
|
|
92
|
+
process.exit(1);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
watcher.events.on(WatchEvent.CONNECT, url => logEvent(WatchEvent.CONNECT, url));
|
|
96
|
+
|
|
97
|
+
watcher.events.on(WatchEvent.DATA_ERROR, err => logEvent(WatchEvent.DATA_ERROR, err.message));
|
|
98
|
+
watcher.events.on(WatchEvent.RECONNECT, (err, retryCount) =>
|
|
99
|
+
logEvent(WatchEvent.RECONNECT, err ? `Reconnecting after ${retryCount} attempts` : ""),
|
|
100
|
+
);
|
|
101
|
+
watcher.events.on(WatchEvent.RECONNECT_PENDING, () => logEvent(WatchEvent.RECONNECT_PENDING));
|
|
102
|
+
watcher.events.on(WatchEvent.GIVE_UP, err => logEvent(WatchEvent.GIVE_UP, err.message));
|
|
103
|
+
watcher.events.on(WatchEvent.ABORT, err => logEvent(WatchEvent.ABORT, err.message));
|
|
104
|
+
watcher.events.on(WatchEvent.OLD_RESOURCE_VERSION, err => logEvent(WatchEvent.OLD_RESOURCE_VERSION, err));
|
|
105
|
+
watcher.events.on(WatchEvent.NETWORK_ERROR, err => logEvent(WatchEvent.NETWORK_ERROR, err.message));
|
|
106
|
+
watcher.events.on(WatchEvent.LIST_ERROR, err => logEvent(WatchEvent.LIST_ERROR, err.message));
|
|
107
|
+
watcher.events.on(WatchEvent.LIST, list => logEvent(WatchEvent.LIST, JSON.stringify(list, undefined, 2)));
|
|
108
|
+
// Start the watch
|
|
109
|
+
try {
|
|
110
|
+
await watcher.start();
|
|
111
|
+
} catch (err) {
|
|
112
|
+
Log.error(err, "Error starting watch");
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function logEvent(type: WatchEvent, message: string = "", obj?: KubernetesObject) {
|
|
118
|
+
const logMessage = `Watch event ${type} received${message ? `. ${message}.` : "."}`;
|
|
119
|
+
if (obj) {
|
|
120
|
+
Log.debug(obj, logMessage);
|
|
121
|
+
} else {
|
|
122
|
+
Log.debug(logMessage);
|
|
123
|
+
}
|
|
124
|
+
}
|
package/src/lib.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { K8s, RegisterKind, kind as a, fetch, fetchStatus, kind } from "kubernetes-fluent-client";
|
|
2
|
+
import * as R from "ramda";
|
|
3
|
+
|
|
4
|
+
import { Capability } from "./lib/capability";
|
|
5
|
+
import Log from "./lib/logger";
|
|
6
|
+
import { PeprModule } from "./lib/module";
|
|
7
|
+
import { PeprMutateRequest } from "./lib/mutate-request";
|
|
8
|
+
import * as PeprUtils from "./lib/utils";
|
|
9
|
+
import { PeprValidateRequest } from "./lib/validate-request";
|
|
10
|
+
import * as sdk from "./sdk/sdk";
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
Capability,
|
|
14
|
+
K8s,
|
|
15
|
+
Log,
|
|
16
|
+
PeprModule,
|
|
17
|
+
PeprMutateRequest,
|
|
18
|
+
PeprUtils,
|
|
19
|
+
PeprValidateRequest,
|
|
20
|
+
R,
|
|
21
|
+
RegisterKind,
|
|
22
|
+
a,
|
|
23
|
+
fetch,
|
|
24
|
+
fetchStatus,
|
|
25
|
+
kind,
|
|
26
|
+
sdk,
|
|
27
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
5
|
+
|
|
6
|
+
import { fork } from "child_process";
|
|
7
|
+
import crypto from "crypto";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import { gunzipSync } from "zlib";
|
|
10
|
+
import { K8s, kind } from "kubernetes-fluent-client";
|
|
11
|
+
import Log from "../lib/logger";
|
|
12
|
+
import { packageJSON } from "../templates/data.json";
|
|
13
|
+
import { peprStoreCRD } from "../lib/assets/store";
|
|
14
|
+
import { validateHash } from "../lib/helpers";
|
|
15
|
+
const { version } = packageJSON;
|
|
16
|
+
|
|
17
|
+
function runModule(expectedHash: string) {
|
|
18
|
+
const gzPath = `/app/load/module-${expectedHash}.js.gz`;
|
|
19
|
+
const jsPath = `/app/module-${expectedHash}.js`;
|
|
20
|
+
|
|
21
|
+
// Set the log level
|
|
22
|
+
Log.level = "info";
|
|
23
|
+
|
|
24
|
+
// Check if the path is a valid file
|
|
25
|
+
if (!fs.existsSync(gzPath)) {
|
|
26
|
+
throw new Error(`File not found: ${gzPath}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
Log.info(`Loading module ${gzPath}`);
|
|
31
|
+
|
|
32
|
+
// Extract the code from the file
|
|
33
|
+
const codeGZ = fs.readFileSync(gzPath);
|
|
34
|
+
const code = gunzipSync(codeGZ);
|
|
35
|
+
|
|
36
|
+
// Get the hash of the extracted code
|
|
37
|
+
const actualHash = crypto.createHash("sha256").update(code).digest("hex");
|
|
38
|
+
|
|
39
|
+
// If the hash doesn't match, exit
|
|
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}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
Log.info(`File hash matches, running module`);
|
|
47
|
+
|
|
48
|
+
// Write the code to a file
|
|
49
|
+
fs.writeFileSync(jsPath, code);
|
|
50
|
+
|
|
51
|
+
// Run the module
|
|
52
|
+
fork(jsPath);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
throw new Error(`Failed to decompress module: ${e}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Log.info(`Pepr Controller (v${version})`);
|
|
59
|
+
|
|
60
|
+
const hash = process.argv[2];
|
|
61
|
+
|
|
62
|
+
const startup = async () => {
|
|
63
|
+
try {
|
|
64
|
+
Log.info("Applying the Pepr Store CRD if it doesn't exist");
|
|
65
|
+
await K8s(kind.CustomResourceDefinition).Apply(peprStoreCRD, { force: true });
|
|
66
|
+
|
|
67
|
+
validateHash(hash);
|
|
68
|
+
runModule(hash);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
Log.error(err, `Error starting Pepr Store CRD`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
startup().catch(err => Log.error(err, `Error starting Pepr Controller`));
|
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
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"browser": false,
|
|
4
|
+
"es2021": true
|
|
5
|
+
},
|
|
6
|
+
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
|
7
|
+
"parser": "@typescript-eslint/parser",
|
|
8
|
+
"parserOptions": {
|
|
9
|
+
"project": ["./tsconfig.json"],
|
|
10
|
+
"ecmaVersion": 2022
|
|
11
|
+
},
|
|
12
|
+
"plugins": ["@typescript-eslint"],
|
|
13
|
+
"ignorePatterns": ["node_modules", "dist"],
|
|
14
|
+
"root": true,
|
|
15
|
+
"rules": {
|
|
16
|
+
"@typescript-eslint/no-floating-promises": ["error"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"arrowParens": "avoid",
|
|
3
|
+
"bracketSameLine": false,
|
|
4
|
+
"bracketSpacing": true,
|
|
5
|
+
"embeddedLanguageFormatting": "auto",
|
|
6
|
+
"insertPragma": false,
|
|
7
|
+
"printWidth": 80,
|
|
8
|
+
"quoteProps": "as-needed",
|
|
9
|
+
"requirePragma": false,
|
|
10
|
+
"semi": true,
|
|
11
|
+
"tabWidth": 2,
|
|
12
|
+
"useTabs": false
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Pepr Module
|
|
2
|
+
|
|
3
|
+
This is a Pepr Module. [Pepr](https://github.com/defenseunicorns/pepr) is a type-safe Kubernetes middleware system.
|
|
4
|
+
|
|
5
|
+
The `capabilities` directory contains all the capabilities for this module. By default,
|
|
6
|
+
a capability is a single typescript file in the format of `capability-name.ts` that is
|
|
7
|
+
imported in the root `pepr.ts` file as `import { HelloPepr } from "./capabilities/hello-pepr";`.
|
|
8
|
+
Because this is typescript, you can organize this however you choose, e.g. creating a sub-folder
|
|
9
|
+
per-capability or common logic in shared files or folders.
|
|
10
|
+
|
|
11
|
+
Example Structure:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Module Root
|
|
15
|
+
├── package.json
|
|
16
|
+
├── pepr.ts
|
|
17
|
+
└── capabilities
|
|
18
|
+
├── example-one.ts
|
|
19
|
+
├── example-three.ts
|
|
20
|
+
└── example-two.ts
|
|
21
|
+
```
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"apiVersion": "v1",
|
|
4
|
+
"kind": "Namespace",
|
|
5
|
+
"metadata": {
|
|
6
|
+
"name": "pepr-demo",
|
|
7
|
+
"labels": {
|
|
8
|
+
"keep-me": "please",
|
|
9
|
+
"remove-me": "please"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"apiVersion": "v1",
|
|
15
|
+
"kind": "Namespace",
|
|
16
|
+
"metadata": {
|
|
17
|
+
"name": "pepr-demo-2"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"apiVersion": "v1",
|
|
22
|
+
"kind": "Secret",
|
|
23
|
+
"metadata": {
|
|
24
|
+
"name": "secret-1",
|
|
25
|
+
"namespace": "pepr-demo"
|
|
26
|
+
},
|
|
27
|
+
"data": {
|
|
28
|
+
"example": "dW5pY29ybiBtYWdpYw==",
|
|
29
|
+
"binary-data": "iCZQUg8xYucNUqD+8lyl2YcKjYYygvTtiDSEV9b9WKUkxSSLFJTgIWMJ9GcFFYs4T9JCdda51u74jfq8yHzRuEASl60EdTS/NfWgIIFTGqcNRfqMw+vgpyTMmCyJVaJEDFq6AA==",
|
|
30
|
+
"ascii-with-white-space": "VGhpcyBpcyBzb21lIHJhbmRvbSB0ZXh0OgoKICAgIC0gd2l0aCBsaW5lIGJyZWFrcwogICAgLSBhbmQgdGFicw=="
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"apiVersion": "v1",
|
|
35
|
+
"kind": "ConfigMap",
|
|
36
|
+
"metadata": {
|
|
37
|
+
"name": "example-1",
|
|
38
|
+
"namespace": "pepr-demo"
|
|
39
|
+
},
|
|
40
|
+
"data": {
|
|
41
|
+
"key": "ex-1-val"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"apiVersion": "v1",
|
|
46
|
+
"kind": "ConfigMap",
|
|
47
|
+
"metadata": {
|
|
48
|
+
"name": "example-2",
|
|
49
|
+
"namespace": "pepr-demo"
|
|
50
|
+
},
|
|
51
|
+
"data": {
|
|
52
|
+
"key": "ex-2-val"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"apiVersion": "v1",
|
|
57
|
+
"kind": "ConfigMap",
|
|
58
|
+
"metadata": {
|
|
59
|
+
"name": "example-evil-cm",
|
|
60
|
+
"namespace": "pepr-demo",
|
|
61
|
+
"annotations": {
|
|
62
|
+
"evil": "true"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"data": {
|
|
66
|
+
"key": "ex-evil-cm-val"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"apiVersion": "v1",
|
|
71
|
+
"kind": "ConfigMap",
|
|
72
|
+
"metadata": {
|
|
73
|
+
"name": "example-3",
|
|
74
|
+
"namespace": "pepr-demo",
|
|
75
|
+
"labels": {
|
|
76
|
+
"change": "by-label"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"data": {
|
|
80
|
+
"key": "ex-3-val"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"apiVersion": "v1",
|
|
85
|
+
"kind": "ConfigMap",
|
|
86
|
+
"metadata": {
|
|
87
|
+
"name": "example-4",
|
|
88
|
+
"namespace": "pepr-demo"
|
|
89
|
+
},
|
|
90
|
+
"data": {
|
|
91
|
+
"key": "ex-4-val"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"apiVersion": "v1",
|
|
96
|
+
"kind": "ConfigMap",
|
|
97
|
+
"metadata": {
|
|
98
|
+
"name": "example-4a",
|
|
99
|
+
"namespace": "pepr-demo-2"
|
|
100
|
+
},
|
|
101
|
+
"data": {
|
|
102
|
+
"key": "ex-4-val"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"apiVersion": "v1",
|
|
107
|
+
"kind": "ConfigMap",
|
|
108
|
+
"metadata": {
|
|
109
|
+
"name": "example-5",
|
|
110
|
+
"namespace": "pepr-demo",
|
|
111
|
+
"labels": {
|
|
112
|
+
"chuck-norris": "test"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"data": {
|
|
116
|
+
"key": "ex-5-val"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"apiVersion": "apiextensions.k8s.io/v1",
|
|
121
|
+
"kind": "CustomResourceDefinition",
|
|
122
|
+
"metadata": {
|
|
123
|
+
"name": "unicorns.pepr.dev"
|
|
124
|
+
},
|
|
125
|
+
"spec": {
|
|
126
|
+
"group": "pepr.dev",
|
|
127
|
+
"versions": [
|
|
128
|
+
{
|
|
129
|
+
"name": "v1",
|
|
130
|
+
"served": true,
|
|
131
|
+
"storage": true,
|
|
132
|
+
"schema": {
|
|
133
|
+
"openAPIV3Schema": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"properties": {
|
|
136
|
+
"spec": {
|
|
137
|
+
"type": "object",
|
|
138
|
+
"properties": {
|
|
139
|
+
"message": {
|
|
140
|
+
"type": "string"
|
|
141
|
+
},
|
|
142
|
+
"counter": {
|
|
143
|
+
"type": "number"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"scope": "Namespaced",
|
|
153
|
+
"names": {
|
|
154
|
+
"plural": "unicorns",
|
|
155
|
+
"singular": "unicorn",
|
|
156
|
+
"kind": "Unicorn"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
]
|