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,234 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { dumpYaml } from "@kubernetes/client-node";
|
|
5
|
+
import crypto from "crypto";
|
|
6
|
+
import { promises as fs } from "fs";
|
|
7
|
+
|
|
8
|
+
import { Assets } from ".";
|
|
9
|
+
import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
|
|
10
|
+
import { deployment, moduleSecret, namespace, watcher } from "./pods";
|
|
11
|
+
import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
|
|
12
|
+
import { webhookConfig } from "./webhooks";
|
|
13
|
+
|
|
14
|
+
// Helm Chart overrides file (values.yaml) generated from assets
|
|
15
|
+
export async function overridesFile({ hash, name, image, config, apiToken }: Assets, path: string) {
|
|
16
|
+
const overrides = {
|
|
17
|
+
secrets: {
|
|
18
|
+
apiToken: Buffer.from(apiToken).toString("base64"),
|
|
19
|
+
},
|
|
20
|
+
hash,
|
|
21
|
+
namespace: {
|
|
22
|
+
annotations: {},
|
|
23
|
+
labels: {
|
|
24
|
+
"pepr.dev": "",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
uuid: name,
|
|
28
|
+
admission: {
|
|
29
|
+
terminationGracePeriodSeconds: 5,
|
|
30
|
+
failurePolicy: config.onError === "reject" ? "Fail" : "Ignore",
|
|
31
|
+
webhookTimeout: config.webhookTimeout,
|
|
32
|
+
env: [
|
|
33
|
+
{ name: "PEPR_WATCH_MODE", value: "false" },
|
|
34
|
+
{ name: "PEPR_PRETTY_LOG", value: "false" },
|
|
35
|
+
{ name: "LOG_LEVEL", value: "info" },
|
|
36
|
+
],
|
|
37
|
+
image,
|
|
38
|
+
annotations: {
|
|
39
|
+
"pepr.dev/description": `${config.description}` || "",
|
|
40
|
+
},
|
|
41
|
+
labels: {
|
|
42
|
+
app: name,
|
|
43
|
+
"pepr.dev/controller": "admission",
|
|
44
|
+
"pepr.dev/uuid": config.uuid,
|
|
45
|
+
},
|
|
46
|
+
securityContext: {
|
|
47
|
+
runAsUser: 65532,
|
|
48
|
+
runAsGroup: 65532,
|
|
49
|
+
runAsNonRoot: true,
|
|
50
|
+
fsGroup: 65532,
|
|
51
|
+
},
|
|
52
|
+
resources: {
|
|
53
|
+
requests: {
|
|
54
|
+
memory: "64Mi",
|
|
55
|
+
cpu: "100m",
|
|
56
|
+
},
|
|
57
|
+
limits: {
|
|
58
|
+
memory: "256Mi",
|
|
59
|
+
cpu: "500m",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
containerSecurityContext: {
|
|
63
|
+
runAsUser: 65532,
|
|
64
|
+
runAsGroup: 65532,
|
|
65
|
+
runAsNonRoot: true,
|
|
66
|
+
allowPrivilegeEscalation: false,
|
|
67
|
+
capabilities: {
|
|
68
|
+
drop: ["ALL"],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
podAnnotations: {},
|
|
72
|
+
nodeSelector: {},
|
|
73
|
+
tolerations: [],
|
|
74
|
+
extraVolumeMounts: [],
|
|
75
|
+
extraVolumes: [],
|
|
76
|
+
affinity: {},
|
|
77
|
+
},
|
|
78
|
+
watcher: {
|
|
79
|
+
terminationGracePeriodSeconds: 5,
|
|
80
|
+
env: [
|
|
81
|
+
{ name: "PEPR_WATCH_MODE", value: "true" },
|
|
82
|
+
{ name: "PEPR_PRETTY_LOG", value: "false" },
|
|
83
|
+
{ name: "LOG_LEVEL", value: "info" },
|
|
84
|
+
],
|
|
85
|
+
image,
|
|
86
|
+
annotations: {
|
|
87
|
+
"pepr.dev/description": `${config.description}` || "",
|
|
88
|
+
},
|
|
89
|
+
labels: {
|
|
90
|
+
app: `${name}-watcher`,
|
|
91
|
+
"pepr.dev/controller": "watcher",
|
|
92
|
+
"pepr.dev/uuid": config.uuid,
|
|
93
|
+
},
|
|
94
|
+
securityContext: {
|
|
95
|
+
runAsUser: 65532,
|
|
96
|
+
runAsGroup: 65532,
|
|
97
|
+
runAsNonRoot: true,
|
|
98
|
+
fsGroup: 65532,
|
|
99
|
+
},
|
|
100
|
+
resources: {
|
|
101
|
+
requests: {
|
|
102
|
+
memory: "64Mi",
|
|
103
|
+
cpu: "100m",
|
|
104
|
+
},
|
|
105
|
+
limits: {
|
|
106
|
+
memory: "256Mi",
|
|
107
|
+
cpu: "500m",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
containerSecurityContext: {
|
|
111
|
+
runAsUser: 65532,
|
|
112
|
+
runAsGroup: 65532,
|
|
113
|
+
runAsNonRoot: true,
|
|
114
|
+
allowPrivilegeEscalation: false,
|
|
115
|
+
capabilities: {
|
|
116
|
+
drop: ["ALL"],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
nodeSelector: {},
|
|
120
|
+
tolerations: [],
|
|
121
|
+
extraVolumeMounts: [],
|
|
122
|
+
extraVolumes: [],
|
|
123
|
+
affinity: {},
|
|
124
|
+
podAnnotations: {},
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
if (process.env.PEPR_MODE === "dev") {
|
|
128
|
+
overrides.admission.env.push({ name: "ZARF_VAR", value: "###ZARF_VAR_THING###" });
|
|
129
|
+
overrides.watcher.env.push({ name: "ZARF_VAR", value: "###ZARF_VAR_THING###" });
|
|
130
|
+
overrides.admission.env.push({ name: "MY_CUSTOM_VAR", value: "example-value" });
|
|
131
|
+
overrides.watcher.env.push({ name: "MY_CUSTOM_VAR", value: "example-value" });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await fs.writeFile(path, dumpYaml(overrides, { noRefs: true, forceQuotes: true }));
|
|
135
|
+
}
|
|
136
|
+
export function zarfYaml({ name, image, config }: Assets, path: string) {
|
|
137
|
+
const zarfCfg = {
|
|
138
|
+
kind: "ZarfPackageConfig",
|
|
139
|
+
metadata: {
|
|
140
|
+
name,
|
|
141
|
+
description: `Pepr Module: ${config.description}`,
|
|
142
|
+
url: "https://github.com/defenseunicorns/pepr",
|
|
143
|
+
version: `${config.appVersion || "0.0.1"}`,
|
|
144
|
+
},
|
|
145
|
+
components: [
|
|
146
|
+
{
|
|
147
|
+
name: "module",
|
|
148
|
+
required: true,
|
|
149
|
+
manifests: [
|
|
150
|
+
{
|
|
151
|
+
name: "module",
|
|
152
|
+
namespace: "pepr-system",
|
|
153
|
+
files: [path],
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
images: [image],
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return dumpYaml(zarfCfg, { noRefs: true });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function zarfYamlChart({ name, image, config }: Assets, path: string) {
|
|
165
|
+
const zarfCfg = {
|
|
166
|
+
kind: "ZarfPackageConfig",
|
|
167
|
+
metadata: {
|
|
168
|
+
name,
|
|
169
|
+
description: `Pepr Module: ${config.description}`,
|
|
170
|
+
url: "https://github.com/defenseunicorns/pepr",
|
|
171
|
+
version: `${config.appVersion || "0.0.1"}`,
|
|
172
|
+
},
|
|
173
|
+
components: [
|
|
174
|
+
{
|
|
175
|
+
name: "module",
|
|
176
|
+
required: true,
|
|
177
|
+
charts: [
|
|
178
|
+
{
|
|
179
|
+
name: "module",
|
|
180
|
+
namespace: "pepr-system",
|
|
181
|
+
version: `${config.appVersion || "0.0.1"}`,
|
|
182
|
+
localPath: path,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
images: [image],
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return dumpYaml(zarfCfg, { noRefs: true });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function allYaml(assets: Assets, rbacMode: string) {
|
|
194
|
+
const { name, tls, apiToken, path } = assets;
|
|
195
|
+
|
|
196
|
+
const code = await fs.readFile(path);
|
|
197
|
+
|
|
198
|
+
// Generate a hash of the code
|
|
199
|
+
assets.hash = crypto.createHash("sha256").update(code).digest("hex");
|
|
200
|
+
|
|
201
|
+
const mutateWebhook = await webhookConfig(assets, "mutate", assets.config.webhookTimeout);
|
|
202
|
+
const validateWebhook = await webhookConfig(assets, "validate", assets.config.webhookTimeout);
|
|
203
|
+
const watchDeployment = watcher(assets, assets.hash, assets.buildTimestamp);
|
|
204
|
+
|
|
205
|
+
const resources = [
|
|
206
|
+
namespace(assets.config.customLabels?.namespace),
|
|
207
|
+
clusterRole(name, assets.capabilities, rbacMode),
|
|
208
|
+
clusterRoleBinding(name),
|
|
209
|
+
serviceAccount(name),
|
|
210
|
+
apiTokenSecret(name, apiToken),
|
|
211
|
+
tlsSecret(name, tls),
|
|
212
|
+
deployment(assets, assets.hash, assets.buildTimestamp),
|
|
213
|
+
service(name),
|
|
214
|
+
watcherService(name),
|
|
215
|
+
moduleSecret(name, code, assets.hash),
|
|
216
|
+
storeRole(name),
|
|
217
|
+
storeRoleBinding(name),
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
if (mutateWebhook) {
|
|
221
|
+
resources.push(mutateWebhook);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (validateWebhook) {
|
|
225
|
+
resources.push(validateWebhook);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (watchDeployment) {
|
|
229
|
+
resources.push(watchDeployment);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Convert the resources to a single YAML string
|
|
233
|
+
return resources.map(r => dumpYaml(r, { noRefs: true })).join("---\n");
|
|
234
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { GenericClass, GroupVersionKind, modelToGroupVersionKind } from "kubernetes-fluent-client";
|
|
5
|
+
import { WatchAction } from "kubernetes-fluent-client/dist/fluent/types";
|
|
6
|
+
import { pickBy } from "ramda";
|
|
7
|
+
|
|
8
|
+
import Log from "./logger";
|
|
9
|
+
import { isBuildMode, isDevMode, isWatchMode } from "./module";
|
|
10
|
+
import { PeprStore, Storage } from "./storage";
|
|
11
|
+
import { OnSchedule, Schedule } from "./schedule";
|
|
12
|
+
import {
|
|
13
|
+
Binding,
|
|
14
|
+
BindingFilter,
|
|
15
|
+
BindingWithName,
|
|
16
|
+
CapabilityCfg,
|
|
17
|
+
CapabilityExport,
|
|
18
|
+
Event,
|
|
19
|
+
MutateAction,
|
|
20
|
+
MutateActionChain,
|
|
21
|
+
ValidateAction,
|
|
22
|
+
ValidateActionChain,
|
|
23
|
+
WhenSelector,
|
|
24
|
+
} from "./types";
|
|
25
|
+
|
|
26
|
+
const registerAdmission = isBuildMode() || !isWatchMode();
|
|
27
|
+
const registerWatch = isBuildMode() || isWatchMode() || isDevMode();
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A capability is a unit of functionality that can be registered with the Pepr runtime.
|
|
31
|
+
*/
|
|
32
|
+
export class Capability implements CapabilityExport {
|
|
33
|
+
#name: string;
|
|
34
|
+
#description: string;
|
|
35
|
+
#namespaces?: string[] | undefined;
|
|
36
|
+
#bindings: Binding[] = [];
|
|
37
|
+
#store = new Storage();
|
|
38
|
+
#scheduleStore = new Storage();
|
|
39
|
+
#registered = false;
|
|
40
|
+
#scheduleRegistered = false;
|
|
41
|
+
hasSchedule: boolean;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Run code on a schedule with the capability.
|
|
45
|
+
*
|
|
46
|
+
* @param schedule The schedule to run the code on
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
OnSchedule: (schedule: Schedule) => void = (schedule: Schedule) => {
|
|
50
|
+
const { name, every, unit, run, startTime, completions } = schedule;
|
|
51
|
+
this.hasSchedule = true;
|
|
52
|
+
|
|
53
|
+
if (process.env.PEPR_WATCH_MODE === "true" || process.env.PEPR_MODE === "dev") {
|
|
54
|
+
// Only create/watch schedule store if necessary
|
|
55
|
+
|
|
56
|
+
// Create a new schedule
|
|
57
|
+
const newSchedule: Schedule = {
|
|
58
|
+
name,
|
|
59
|
+
every,
|
|
60
|
+
unit,
|
|
61
|
+
run,
|
|
62
|
+
startTime,
|
|
63
|
+
completions,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
this.#scheduleStore.onReady(() => {
|
|
67
|
+
new OnSchedule(newSchedule).setStore(this.#scheduleStore);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Store is a key-value data store that can be used to persist data that should be shared
|
|
74
|
+
* between requests. Each capability has its own store, and the data is persisted in Kubernetes
|
|
75
|
+
* in the `pepr-system` namespace.
|
|
76
|
+
*
|
|
77
|
+
* Note: You should only access the store from within an action.
|
|
78
|
+
*/
|
|
79
|
+
Store: PeprStore = {
|
|
80
|
+
clear: this.#store.clear,
|
|
81
|
+
getItem: this.#store.getItem,
|
|
82
|
+
removeItem: this.#store.removeItem,
|
|
83
|
+
removeItemAndWait: this.#store.removeItemAndWait,
|
|
84
|
+
setItem: this.#store.setItem,
|
|
85
|
+
subscribe: this.#store.subscribe,
|
|
86
|
+
onReady: this.#store.onReady,
|
|
87
|
+
setItemAndWait: this.#store.setItemAndWait,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* ScheduleStore is a key-value data store used to persist schedule data that should be shared
|
|
92
|
+
* between intervals. Each Schedule shares store, and the data is persisted in Kubernetes
|
|
93
|
+
* in the `pepr-system` namespace.
|
|
94
|
+
*
|
|
95
|
+
* Note: There is no direct access to schedule store
|
|
96
|
+
*/
|
|
97
|
+
ScheduleStore: PeprStore = {
|
|
98
|
+
clear: this.#scheduleStore.clear,
|
|
99
|
+
getItem: this.#scheduleStore.getItem,
|
|
100
|
+
removeItemAndWait: this.#scheduleStore.removeItemAndWait,
|
|
101
|
+
removeItem: this.#scheduleStore.removeItem,
|
|
102
|
+
setItemAndWait: this.#scheduleStore.setItemAndWait,
|
|
103
|
+
setItem: this.#scheduleStore.setItem,
|
|
104
|
+
subscribe: this.#scheduleStore.subscribe,
|
|
105
|
+
onReady: this.#scheduleStore.onReady,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
get bindings() {
|
|
109
|
+
return this.#bindings;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
get name() {
|
|
113
|
+
return this.#name;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get description() {
|
|
117
|
+
return this.#description;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get namespaces() {
|
|
121
|
+
return this.#namespaces || [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
constructor(cfg: CapabilityCfg) {
|
|
125
|
+
this.#name = cfg.name;
|
|
126
|
+
this.#description = cfg.description;
|
|
127
|
+
this.#namespaces = cfg.namespaces;
|
|
128
|
+
this.hasSchedule = false;
|
|
129
|
+
|
|
130
|
+
Log.info(`Capability ${this.#name} registered`);
|
|
131
|
+
Log.debug(cfg);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Register the store with the capability. This is called automatically by the Pepr controller.
|
|
136
|
+
*
|
|
137
|
+
* @param store
|
|
138
|
+
*/
|
|
139
|
+
registerScheduleStore = () => {
|
|
140
|
+
Log.info(`Registering schedule store for ${this.#name}`);
|
|
141
|
+
|
|
142
|
+
if (this.#scheduleRegistered) {
|
|
143
|
+
throw new Error(`Schedule store already registered for ${this.#name}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.#scheduleRegistered = true;
|
|
147
|
+
|
|
148
|
+
// Pass back any ready callback to the controller
|
|
149
|
+
return {
|
|
150
|
+
scheduleStore: this.#scheduleStore,
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Register the store with the capability. This is called automatically by the Pepr controller.
|
|
156
|
+
*
|
|
157
|
+
* @param store
|
|
158
|
+
*/
|
|
159
|
+
registerStore = () => {
|
|
160
|
+
Log.info(`Registering store for ${this.#name}`);
|
|
161
|
+
|
|
162
|
+
if (this.#registered) {
|
|
163
|
+
throw new Error(`Store already registered for ${this.#name}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this.#registered = true;
|
|
167
|
+
|
|
168
|
+
// Pass back any ready callback to the controller
|
|
169
|
+
return {
|
|
170
|
+
store: this.#store,
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The When method is used to register a action to be executed when a Kubernetes resource is
|
|
176
|
+
* processed by Pepr. The action will be executed if the resource matches the specified kind and any
|
|
177
|
+
* filters that are applied.
|
|
178
|
+
*
|
|
179
|
+
* @param model the KubernetesObject model to match
|
|
180
|
+
* @param kind if using a custom KubernetesObject not available in `a.*`, specify the GroupVersionKind
|
|
181
|
+
* @returns
|
|
182
|
+
*/
|
|
183
|
+
When = <T extends GenericClass>(model: T, kind?: GroupVersionKind): WhenSelector<T> => {
|
|
184
|
+
const matchedKind = modelToGroupVersionKind(model.name);
|
|
185
|
+
|
|
186
|
+
// If the kind is not specified and the model is not a KubernetesObject, throw an error
|
|
187
|
+
if (!matchedKind && !kind) {
|
|
188
|
+
throw new Error(`Kind not specified for ${model.name}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const binding: Binding = {
|
|
192
|
+
model,
|
|
193
|
+
// If the kind is not specified, use the matched kind from the model
|
|
194
|
+
kind: kind || matchedKind,
|
|
195
|
+
event: Event.Any,
|
|
196
|
+
filters: {
|
|
197
|
+
name: "",
|
|
198
|
+
namespaces: [],
|
|
199
|
+
labels: {},
|
|
200
|
+
annotations: {},
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const bindings = this.#bindings;
|
|
205
|
+
const prefix = `${this.#name}: ${model.name}`;
|
|
206
|
+
const commonChain = { WithLabel, WithAnnotation, Mutate, Validate, Watch, Reconcile };
|
|
207
|
+
const isNotEmpty = (value: object) => Object.keys(value).length > 0;
|
|
208
|
+
const log = (message: string, cbString: string) => {
|
|
209
|
+
const filteredObj = pickBy(isNotEmpty, binding.filters);
|
|
210
|
+
|
|
211
|
+
Log.info(`${message} configured for ${binding.event}`, prefix);
|
|
212
|
+
Log.info(filteredObj, prefix);
|
|
213
|
+
Log.debug(cbString, prefix);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
function Validate(validateCallback: ValidateAction<T>): ValidateActionChain<T> {
|
|
217
|
+
if (registerAdmission) {
|
|
218
|
+
log("Validate Action", validateCallback.toString());
|
|
219
|
+
|
|
220
|
+
// Push the binding to the list of bindings for this capability as a new BindingAction
|
|
221
|
+
// with the callback function to preserve
|
|
222
|
+
bindings.push({
|
|
223
|
+
...binding,
|
|
224
|
+
isValidate: true,
|
|
225
|
+
validateCallback,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return { Watch, Reconcile };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function Mutate(mutateCallback: MutateAction<T>): MutateActionChain<T> {
|
|
233
|
+
if (registerAdmission) {
|
|
234
|
+
log("Mutate Action", mutateCallback.toString());
|
|
235
|
+
|
|
236
|
+
// Push the binding to the list of bindings for this capability as a new BindingAction
|
|
237
|
+
// with the callback function to preserve
|
|
238
|
+
bindings.push({
|
|
239
|
+
...binding,
|
|
240
|
+
isMutate: true,
|
|
241
|
+
mutateCallback,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Now only allow adding actions to the same binding
|
|
246
|
+
return { Watch, Validate, Reconcile };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function Watch(watchCallback: WatchAction<T>) {
|
|
250
|
+
if (registerWatch) {
|
|
251
|
+
log("Watch Action", watchCallback.toString());
|
|
252
|
+
|
|
253
|
+
bindings.push({
|
|
254
|
+
...binding,
|
|
255
|
+
isWatch: true,
|
|
256
|
+
watchCallback,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function Reconcile(watchCallback: WatchAction<T>) {
|
|
262
|
+
if (registerWatch) {
|
|
263
|
+
log("Reconcile Action", watchCallback.toString());
|
|
264
|
+
|
|
265
|
+
bindings.push({
|
|
266
|
+
...binding,
|
|
267
|
+
isWatch: true,
|
|
268
|
+
isQueue: true,
|
|
269
|
+
watchCallback,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function InNamespace(...namespaces: string[]): BindingWithName<T> {
|
|
275
|
+
Log.debug(`Add namespaces filter ${namespaces}`, prefix);
|
|
276
|
+
binding.filters.namespaces.push(...namespaces);
|
|
277
|
+
return { ...commonChain, WithName };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function WithName(name: string): BindingFilter<T> {
|
|
281
|
+
Log.debug(`Add name filter ${name}`, prefix);
|
|
282
|
+
binding.filters.name = name;
|
|
283
|
+
return commonChain;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function WithLabel(key: string, value = ""): BindingFilter<T> {
|
|
287
|
+
Log.debug(`Add label filter ${key}=${value}`, prefix);
|
|
288
|
+
binding.filters.labels[key] = value;
|
|
289
|
+
return commonChain;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function WithAnnotation(key: string, value = ""): BindingFilter<T> {
|
|
293
|
+
Log.debug(`Add annotation filter ${key}=${value}`, prefix);
|
|
294
|
+
binding.filters.annotations[key] = value;
|
|
295
|
+
return commonChain;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function bindEvent(event: Event) {
|
|
299
|
+
binding.event = event;
|
|
300
|
+
return {
|
|
301
|
+
...commonChain,
|
|
302
|
+
InNamespace,
|
|
303
|
+
WithName,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
IsCreatedOrUpdated: () => bindEvent(Event.CreateOrUpdate),
|
|
309
|
+
IsCreated: () => bindEvent(Event.Create),
|
|
310
|
+
IsUpdated: () => bindEvent(Event.Update),
|
|
311
|
+
IsDeleted: () => bindEvent(Event.Delete),
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
}
|