pepr 0.14.2 → 0.16.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/BEST_PRACTICES.md +37 -0
- package/dist/cli.js +100 -47
- package/dist/controller.js +1 -1
- package/dist/lib/assets/deploy.d.ts.map +1 -1
- package/dist/lib/assets/index.d.ts +1 -1
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/rbac.d.ts +2 -1
- package/dist/lib/assets/rbac.d.ts.map +1 -1
- package/dist/lib/assets/yaml.d.ts +1 -1
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/capability.d.ts +25 -0
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller/index.d.ts.map +1 -1
- package/dist/lib/controller/store.d.ts +2 -1
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/helpers.d.ts +12 -0
- package/dist/lib/helpers.d.ts.map +1 -0
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/schedule.d.ts +76 -0
- package/dist/lib/schedule.d.ts.map +1 -0
- package/dist/lib/storage.d.ts +14 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/types.d.ts +1 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib.d.ts +3 -6
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +236 -9
- package/dist/lib.js.map +4 -4
- package/package.json +16 -16
- package/src/lib/assets/deploy.ts +4 -3
- package/src/lib/assets/index.ts +2 -2
- package/src/lib/assets/rbac.ts +27 -11
- package/src/lib/assets/yaml.ts +2 -2
- package/src/lib/capability.ts +72 -0
- package/src/lib/controller/index.ts +5 -1
- package/src/lib/controller/store.ts +29 -11
- package/src/lib/helpers.ts +52 -0
- package/src/lib/module.ts +1 -0
- package/src/lib/schedule.ts +175 -0
- package/src/lib/storage.ts +33 -0
- package/src/lib/types.ts +1 -0
- package/src/lib.ts +9 -16
- package/src/templates/capabilities/hello-pepr.ts +16 -11
- package/website/.linkinator.config.json +8 -0
- package/website/.markdownlint.json +6 -0
- package/website/.prettierignore +12 -0
- package/website/LICENSE +201 -0
- package/website/README.md +50 -0
- package/website/archetypes/default.md +6 -0
- package/website/assets/img/doug.svg +345 -0
- package/website/assets/img/pepr.svg +212 -0
- package/website/assets/scss/_styles_project.scss +3 -0
- package/website/assets/scss/_variables_project.scss +1 -0
- package/website/content/en/docs/OnSchedule.md +86 -0
- package/website/content/en/docs/_index.md +9 -0
- package/website/content/en/docs/cli.md +64 -0
- package/website/content/en/docs/codeSample.txt +31 -0
- package/website/content/en/docs/concepts.md +238 -0
- package/website/content/en/docs/customresources.md +167 -0
- package/website/content/en/docs/diagrams.txt +18 -0
- package/website/content/en/docs/metrics.md +74 -0
- package/website/content/en/docs/rbac.md +153 -0
- package/website/content/en/docs/store.md +48 -0
- package/website/content/en/docs/webassembly.md +189 -0
- package/website/go.mod +8 -0
- package/website/go.sum +4 -0
- package/website/package-lock.json +3907 -0
- package/website/package.json +30 -0
- package/website/renovate.json +16 -0
- package/website/static/favicons/android-144x144.png +0 -0
- package/website/static/favicons/android-192x192.png +0 -0
- package/website/static/favicons/android-36x36.png +0 -0
- package/website/static/favicons/android-48x48.png +0 -0
- package/website/static/favicons/android-72x72.png +0 -0
- package/website/static/favicons/android-96x96.png +0 -0
- package/website/static/favicons/android-chrome-192x192.png +0 -0
- package/website/static/favicons/android-chrome-512x512.png +0 -0
- package/website/static/favicons/android-chrome-maskable-192x192.png +0 -0
- package/website/static/favicons/android-chrome-maskable-512x512.png +0 -0
- package/website/static/favicons/apple-touch-icon-180x180.png +0 -0
- package/website/static/favicons/apple-touch-icon.png +0 -0
- package/website/static/favicons/favicon-16x16.png +0 -0
- package/website/static/favicons/favicon-32x32.png +0 -0
- package/website/static/favicons/favicon.ico +0 -0
- package/website/static/img/how-to-use.png +0 -0
package/src/lib/assets/rbac.ts
CHANGED
|
@@ -2,26 +2,42 @@
|
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
4
|
import { kind } from "kubernetes-fluent-client";
|
|
5
|
-
|
|
5
|
+
import { CapabilityExport } from "../types";
|
|
6
|
+
import { createRBACMap } from "../helpers";
|
|
6
7
|
/**
|
|
7
8
|
* Grants the controller access to cluster resources beyond the mutating webhook.
|
|
8
9
|
*
|
|
9
10
|
* @todo: should dynamically generate this based on resources used by the module. will also need to explore how this should work for multiple modules.
|
|
10
11
|
* @returns
|
|
11
12
|
*/
|
|
12
|
-
export function clusterRole(name: string): kind.ClusterRole {
|
|
13
|
+
export function clusterRole(name: string, capabilities: CapabilityExport[], rbacMode: string = ""): kind.ClusterRole {
|
|
14
|
+
const rbacMap = createRBACMap(capabilities);
|
|
13
15
|
return {
|
|
14
16
|
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
15
17
|
kind: "ClusterRole",
|
|
16
18
|
metadata: { name },
|
|
17
|
-
rules:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
rules:
|
|
20
|
+
rbacMode === "scoped"
|
|
21
|
+
? [
|
|
22
|
+
...Object.keys(rbacMap).map(key => {
|
|
23
|
+
// let group:string, version:string, kind:string;
|
|
24
|
+
let group: string;
|
|
25
|
+
key.split("/").length < 3 ? (group = "") : (group = key.split("/")[0]);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
apiGroups: [group],
|
|
29
|
+
resources: [rbacMap[key].plural],
|
|
30
|
+
verbs: rbacMap[key].verbs,
|
|
31
|
+
};
|
|
32
|
+
}),
|
|
33
|
+
]
|
|
34
|
+
: [
|
|
35
|
+
{
|
|
36
|
+
apiGroups: ["*"],
|
|
37
|
+
resources: ["*"],
|
|
38
|
+
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
25
41
|
};
|
|
26
42
|
}
|
|
27
43
|
|
|
@@ -64,7 +80,7 @@ export function storeRole(name: string): kind.Role {
|
|
|
64
80
|
metadata: { name, namespace: "pepr-system" },
|
|
65
81
|
rules: [
|
|
66
82
|
{
|
|
67
|
-
apiGroups: ["pepr.dev
|
|
83
|
+
apiGroups: ["pepr.dev"],
|
|
68
84
|
resources: ["peprstores"],
|
|
69
85
|
resourceNames: [""],
|
|
70
86
|
verbs: ["create", "get", "patch", "watch"],
|
package/src/lib/assets/yaml.ts
CHANGED
|
@@ -40,7 +40,7 @@ export function zarfYaml({ name, image, config }: Assets, path: string) {
|
|
|
40
40
|
return dumpYaml(zarfCfg, { noRefs: true });
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export async function allYaml(assets: Assets) {
|
|
43
|
+
export async function allYaml(assets: Assets, rbacMode: string) {
|
|
44
44
|
const { name, tls, apiToken, path } = assets;
|
|
45
45
|
|
|
46
46
|
const code = await fs.readFile(path);
|
|
@@ -54,7 +54,7 @@ export async function allYaml(assets: Assets) {
|
|
|
54
54
|
|
|
55
55
|
const resources = [
|
|
56
56
|
namespace,
|
|
57
|
-
clusterRole(name),
|
|
57
|
+
clusterRole(name, assets.capabilities, rbacMode),
|
|
58
58
|
clusterRoleBinding(name),
|
|
59
59
|
serviceAccount(name),
|
|
60
60
|
apiTokenSecret(name, apiToken),
|
package/src/lib/capability.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { pickBy } from "ramda";
|
|
|
8
8
|
import Log from "./logger";
|
|
9
9
|
import { isBuildMode, isDevMode, isWatchMode } from "./module";
|
|
10
10
|
import { PeprStore, Storage } from "./storage";
|
|
11
|
+
import { OnSchedule, Schedule } from "./schedule";
|
|
11
12
|
import {
|
|
12
13
|
Binding,
|
|
13
14
|
BindingFilter,
|
|
@@ -34,7 +35,39 @@ export class Capability implements CapabilityExport {
|
|
|
34
35
|
#namespaces?: string[] | undefined;
|
|
35
36
|
#bindings: Binding[] = [];
|
|
36
37
|
#store = new Storage();
|
|
38
|
+
#scheduleStore = new Storage();
|
|
37
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
|
+
|
|
52
|
+
if (process.env.PEPR_WATCH_MODE === "true") {
|
|
53
|
+
// Only create/watch schedule store if necessary
|
|
54
|
+
this.hasSchedule = true;
|
|
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
|
+
};
|
|
38
71
|
|
|
39
72
|
/**
|
|
40
73
|
* Store is a key-value data store that can be used to persist data that should be shared
|
|
@@ -50,6 +83,24 @@ export class Capability implements CapabilityExport {
|
|
|
50
83
|
setItem: this.#store.setItem,
|
|
51
84
|
subscribe: this.#store.subscribe,
|
|
52
85
|
onReady: this.#store.onReady,
|
|
86
|
+
setItemAndWait: this.#store.setItemAndWait,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* ScheduleStore is a key-value data store used to persist schedule data that should be shared
|
|
91
|
+
* between intervals. Each Schedule shares store, and the data is persisted in Kubernetes
|
|
92
|
+
* in the `pepr-system` namespace.
|
|
93
|
+
*
|
|
94
|
+
* Note: There is no direct access to schedule store
|
|
95
|
+
*/
|
|
96
|
+
ScheduleStore: PeprStore = {
|
|
97
|
+
clear: this.#scheduleStore.clear,
|
|
98
|
+
getItem: this.#scheduleStore.getItem,
|
|
99
|
+
removeItem: this.#scheduleStore.removeItem,
|
|
100
|
+
setItemAndWait: this.#scheduleStore.setItemAndWait,
|
|
101
|
+
setItem: this.#scheduleStore.setItem,
|
|
102
|
+
subscribe: this.#scheduleStore.subscribe,
|
|
103
|
+
onReady: this.#scheduleStore.onReady,
|
|
53
104
|
};
|
|
54
105
|
|
|
55
106
|
get bindings() {
|
|
@@ -72,11 +123,32 @@ export class Capability implements CapabilityExport {
|
|
|
72
123
|
this.#name = cfg.name;
|
|
73
124
|
this.#description = cfg.description;
|
|
74
125
|
this.#namespaces = cfg.namespaces;
|
|
126
|
+
this.hasSchedule = false;
|
|
75
127
|
|
|
76
128
|
Log.info(`Capability ${this.#name} registered`);
|
|
77
129
|
Log.debug(cfg);
|
|
78
130
|
}
|
|
79
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Register the store with the capability. This is called automatically by the Pepr controller.
|
|
134
|
+
*
|
|
135
|
+
* @param store
|
|
136
|
+
*/
|
|
137
|
+
registerScheduleStore = () => {
|
|
138
|
+
Log.info(`Registering schedule store for ${this.#name}`);
|
|
139
|
+
|
|
140
|
+
if (this.#scheduleRegistered) {
|
|
141
|
+
throw new Error(`Schedule store already registered for ${this.#name}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.#scheduleRegistered = true;
|
|
145
|
+
|
|
146
|
+
// Pass back any ready callback to the controller
|
|
147
|
+
return {
|
|
148
|
+
scheduleStore: this.#scheduleStore,
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
|
|
80
152
|
/**
|
|
81
153
|
* Register the store with the capability. This is called automatically by the Pepr controller.
|
|
82
154
|
*
|
|
@@ -44,10 +44,14 @@ export class Controller {
|
|
|
44
44
|
this.#capabilities = capabilities;
|
|
45
45
|
|
|
46
46
|
// Initialize the Pepr store for each capability
|
|
47
|
-
new PeprControllerStore(config, capabilities, () => {
|
|
47
|
+
new PeprControllerStore(config, capabilities, `pepr-${config.uuid}-store`, () => {
|
|
48
48
|
this.#bindEndpoints();
|
|
49
49
|
onReady && onReady();
|
|
50
50
|
Log.info("✅ Controller startup complete");
|
|
51
|
+
// Initialize the schedule store for each capability
|
|
52
|
+
new PeprControllerStore(config, capabilities, `pepr-${config.uuid}-schedule`, () => {
|
|
53
|
+
Log.info("✅ Scheduling processed");
|
|
54
|
+
});
|
|
51
55
|
});
|
|
52
56
|
|
|
53
57
|
// Middleware for logging requests
|
|
@@ -12,7 +12,7 @@ import { ModuleConfig } from "../module";
|
|
|
12
12
|
import { DataOp, DataSender, DataStore, Storage } from "../storage";
|
|
13
13
|
|
|
14
14
|
const namespace = "pepr-system";
|
|
15
|
-
const debounceBackoff = 5000;
|
|
15
|
+
export const debounceBackoff = 5000;
|
|
16
16
|
|
|
17
17
|
export class PeprControllerStore {
|
|
18
18
|
#name: string;
|
|
@@ -20,22 +20,40 @@ export class PeprControllerStore {
|
|
|
20
20
|
#sendDebounce: NodeJS.Timeout | undefined;
|
|
21
21
|
#onReady?: () => void;
|
|
22
22
|
|
|
23
|
-
constructor(config: ModuleConfig, capabilities: Capability[], onReady?: () => void) {
|
|
23
|
+
constructor(config: ModuleConfig, capabilities: Capability[], name: string, onReady?: () => void) {
|
|
24
24
|
this.#onReady = onReady;
|
|
25
25
|
|
|
26
26
|
// Setup Pepr State bindings
|
|
27
|
-
this.#name =
|
|
27
|
+
this.#name = name;
|
|
28
|
+
|
|
29
|
+
if (name.includes("schedule")) {
|
|
30
|
+
// Establish the store for each capability
|
|
31
|
+
for (const { name, registerScheduleStore, hasSchedule } of capabilities) {
|
|
32
|
+
// Guard Clause to exit early
|
|
33
|
+
if (hasSchedule !== true) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Register the scheduleStore with the capability
|
|
37
|
+
const { scheduleStore } = registerScheduleStore();
|
|
38
|
+
|
|
39
|
+
// Bind the store sender to the capability
|
|
40
|
+
scheduleStore.registerSender(this.#send(name));
|
|
28
41
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
// Store the storage instance
|
|
43
|
+
this.#stores[name] = scheduleStore;
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
// Establish the store for each capability
|
|
47
|
+
for (const { name, registerStore } of capabilities) {
|
|
48
|
+
// Register the store with the capability
|
|
49
|
+
const { store } = registerStore();
|
|
33
50
|
|
|
34
|
-
|
|
35
|
-
|
|
51
|
+
// Bind the store sender to the capability
|
|
52
|
+
store.registerSender(this.#send(name));
|
|
36
53
|
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
// Store the storage instance
|
|
55
|
+
this.#stores[name] = store;
|
|
56
|
+
}
|
|
39
57
|
}
|
|
40
58
|
|
|
41
59
|
// Add a jitter to the Store creation to avoid collisions
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { CapabilityExport } from "./types";
|
|
5
|
+
import { promises as fs } from "fs";
|
|
6
|
+
|
|
7
|
+
type RBACMap = {
|
|
8
|
+
[key: string]: {
|
|
9
|
+
verbs: string[];
|
|
10
|
+
plural: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const addVerbIfNotExists = (verbs: string[], verb: string) => {
|
|
15
|
+
if (!verbs.includes(verb)) {
|
|
16
|
+
verbs.push(verb);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const createRBACMap = (capabilities: CapabilityExport[]): RBACMap => {
|
|
21
|
+
return capabilities.reduce((acc: RBACMap, capability: CapabilityExport) => {
|
|
22
|
+
capability.bindings.forEach(binding => {
|
|
23
|
+
const key = `${binding.kind.group}/${binding.kind.version}/${binding.kind.kind}`;
|
|
24
|
+
|
|
25
|
+
acc["pepr.dev/v1/peprstore"] = {
|
|
26
|
+
verbs: ["create", "get", "patch", "watch"],
|
|
27
|
+
plural: "peprstores",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (!acc[key] && binding.isWatch) {
|
|
31
|
+
acc[key] = {
|
|
32
|
+
verbs: ["watch"],
|
|
33
|
+
plural: binding.kind.plural || `${binding.kind.kind.toLowerCase()}s`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return acc;
|
|
39
|
+
}, {});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export async function createDirectoryIfNotExists(path: string) {
|
|
43
|
+
try {
|
|
44
|
+
await fs.access(path);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.code === "ENOENT") {
|
|
47
|
+
await fs.mkdir(path, { recursive: true });
|
|
48
|
+
} else {
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/lib/module.ts
CHANGED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
import { PeprStore } from "./storage";
|
|
5
|
+
|
|
6
|
+
type Unit = "seconds" | "second" | "minute" | "minutes" | "hours" | "hour";
|
|
7
|
+
|
|
8
|
+
export interface Schedule {
|
|
9
|
+
/**
|
|
10
|
+
* * The name of the store
|
|
11
|
+
*/
|
|
12
|
+
name: string;
|
|
13
|
+
/**
|
|
14
|
+
* The value associated with a unit of time
|
|
15
|
+
*/
|
|
16
|
+
every: number;
|
|
17
|
+
/**
|
|
18
|
+
* The unit of time
|
|
19
|
+
*/
|
|
20
|
+
unit: Unit;
|
|
21
|
+
/**
|
|
22
|
+
* The code to run
|
|
23
|
+
*/
|
|
24
|
+
run: () => void;
|
|
25
|
+
/**
|
|
26
|
+
* The start time of the schedule
|
|
27
|
+
*/
|
|
28
|
+
startTime?: Date | undefined;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The number of times the schedule has run
|
|
32
|
+
*/
|
|
33
|
+
completions?: number | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Tje intervalID to clear the interval
|
|
36
|
+
*/
|
|
37
|
+
intervalID?: NodeJS.Timeout;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class OnSchedule implements Schedule {
|
|
41
|
+
intervalId: NodeJS.Timeout | null = null;
|
|
42
|
+
store: PeprStore | undefined;
|
|
43
|
+
name!: string;
|
|
44
|
+
completions?: number | undefined;
|
|
45
|
+
every: number;
|
|
46
|
+
unit: Unit;
|
|
47
|
+
run!: () => void;
|
|
48
|
+
startTime?: Date | undefined;
|
|
49
|
+
duration: number | undefined;
|
|
50
|
+
lastTimestamp: Date | undefined;
|
|
51
|
+
|
|
52
|
+
constructor(schedule: Schedule) {
|
|
53
|
+
this.name = schedule.name;
|
|
54
|
+
this.run = schedule.run;
|
|
55
|
+
this.every = schedule.every;
|
|
56
|
+
this.unit = schedule.unit;
|
|
57
|
+
this.startTime = schedule?.startTime;
|
|
58
|
+
this.completions = schedule?.completions;
|
|
59
|
+
}
|
|
60
|
+
setStore(store: PeprStore) {
|
|
61
|
+
this.store = store;
|
|
62
|
+
this.startInterval();
|
|
63
|
+
}
|
|
64
|
+
startInterval() {
|
|
65
|
+
this.checkStore();
|
|
66
|
+
this.getDuration();
|
|
67
|
+
this.setupInterval();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Checks the store for this schedule and sets the values if it exists
|
|
71
|
+
* @returns
|
|
72
|
+
*/
|
|
73
|
+
checkStore() {
|
|
74
|
+
const result = this.store && this.store.getItem(this.name);
|
|
75
|
+
if (result) {
|
|
76
|
+
const storedSchedule = JSON.parse(result);
|
|
77
|
+
this.completions = storedSchedule?.completions;
|
|
78
|
+
this.startTime = storedSchedule?.startTime;
|
|
79
|
+
this.lastTimestamp = storedSchedule?.lastTimestamp;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Saves the schedule to the store
|
|
85
|
+
* @returns
|
|
86
|
+
*/
|
|
87
|
+
saveToStore() {
|
|
88
|
+
const schedule = {
|
|
89
|
+
completions: this.completions,
|
|
90
|
+
startTime: this.startTime,
|
|
91
|
+
lastTimestamp: new Date(),
|
|
92
|
+
name: this.name,
|
|
93
|
+
};
|
|
94
|
+
this.store && this.store.setItem(this.name, JSON.stringify(schedule));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Gets the durations in milliseconds
|
|
99
|
+
*/
|
|
100
|
+
getDuration() {
|
|
101
|
+
switch (this.unit) {
|
|
102
|
+
case "seconds":
|
|
103
|
+
if (this.every < 10) throw new Error("10 Seconds in the smallest interval allowed");
|
|
104
|
+
this.duration = 1000 * this.every;
|
|
105
|
+
break;
|
|
106
|
+
case "minutes":
|
|
107
|
+
case "minute":
|
|
108
|
+
this.duration = 1000 * 60 * this.every;
|
|
109
|
+
break;
|
|
110
|
+
case "hours":
|
|
111
|
+
case "hour":
|
|
112
|
+
this.duration = 1000 * 60 * 60 * this.every;
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
throw new Error("Invalid time unit");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Sets up the interval
|
|
121
|
+
*/
|
|
122
|
+
setupInterval() {
|
|
123
|
+
const now = new Date();
|
|
124
|
+
let delay: number | undefined;
|
|
125
|
+
|
|
126
|
+
if (this.lastTimestamp && this.startTime) {
|
|
127
|
+
this.startTime = undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (this.startTime) {
|
|
131
|
+
delay = this.startTime.getTime() - now.getTime();
|
|
132
|
+
} else if (this.lastTimestamp && this.duration) {
|
|
133
|
+
const lastTimestamp = new Date(this.lastTimestamp);
|
|
134
|
+
delay = this.duration - (now.getTime() - lastTimestamp.getTime());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (delay === undefined || delay <= 0) {
|
|
138
|
+
this.start();
|
|
139
|
+
} else {
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
this.start();
|
|
142
|
+
}, delay);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Starts the interval
|
|
148
|
+
*/
|
|
149
|
+
start() {
|
|
150
|
+
this.intervalId = setInterval(() => {
|
|
151
|
+
if (this.completions === 0) {
|
|
152
|
+
this.stop();
|
|
153
|
+
return;
|
|
154
|
+
} else {
|
|
155
|
+
this.run();
|
|
156
|
+
|
|
157
|
+
if (this.completions && this.completions !== 0) {
|
|
158
|
+
this.completions -= 1;
|
|
159
|
+
}
|
|
160
|
+
this.saveToStore();
|
|
161
|
+
}
|
|
162
|
+
}, this.duration);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Stops the interval
|
|
167
|
+
*/
|
|
168
|
+
stop() {
|
|
169
|
+
if (this.intervalId) {
|
|
170
|
+
clearInterval(this.intervalId);
|
|
171
|
+
this.intervalId = null;
|
|
172
|
+
}
|
|
173
|
+
this.store && this.store.removeItem(this.name);
|
|
174
|
+
}
|
|
175
|
+
}
|
package/src/lib/storage.ts
CHANGED
|
@@ -10,6 +10,7 @@ export type DataSender = (op: DataOp, keys: string[], value?: string) => void;
|
|
|
10
10
|
export type DataReceiver = (data: DataStore) => void;
|
|
11
11
|
export type Unsubscribe = () => void;
|
|
12
12
|
|
|
13
|
+
const MAX_WAIT_TIME = 15000;
|
|
13
14
|
export interface PeprStore {
|
|
14
15
|
/**
|
|
15
16
|
* Returns the current value associated with the given key, or null if the given key does not exist.
|
|
@@ -40,6 +41,12 @@ export interface PeprStore {
|
|
|
40
41
|
* Register a function to be called when the store is ready.
|
|
41
42
|
*/
|
|
42
43
|
onReady(callback: DataReceiver): void;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
|
|
47
|
+
* Resolves when the key/value show up in the store.
|
|
48
|
+
*/
|
|
49
|
+
setItemAndWait(key: string, value: string): Promise<void>;
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
/**
|
|
@@ -88,6 +95,32 @@ export class Storage implements PeprStore {
|
|
|
88
95
|
this.#dispatchUpdate("add", [key], value);
|
|
89
96
|
};
|
|
90
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Creates a promise and subscribes to the store, the promise resolves when
|
|
100
|
+
* the key and value are seen in the store.
|
|
101
|
+
*
|
|
102
|
+
* @param key - The key to add into the store
|
|
103
|
+
* @param value - The value of the key
|
|
104
|
+
* @returns
|
|
105
|
+
*/
|
|
106
|
+
setItemAndWait = (key: string, value: string) => {
|
|
107
|
+
this.#dispatchUpdate("add", [key], value);
|
|
108
|
+
return new Promise<void>((resolve, reject) => {
|
|
109
|
+
const unsubscribe = this.subscribe(data => {
|
|
110
|
+
if (data[key] === value) {
|
|
111
|
+
unsubscribe();
|
|
112
|
+
resolve();
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// If promise has not resolved before MAX_WAIT_TIME reject
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
unsubscribe();
|
|
119
|
+
return reject();
|
|
120
|
+
}, MAX_WAIT_TIME);
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
|
|
91
124
|
subscribe = (subscriber: DataReceiver) => {
|
|
92
125
|
const idx = this.#subscriberId++;
|
|
93
126
|
this.#subscribers[idx] = subscriber;
|
package/src/lib/types.ts
CHANGED
package/src/lib.ts
CHANGED
|
@@ -1,32 +1,25 @@
|
|
|
1
|
-
import { K8s, RegisterKind,
|
|
1
|
+
import { K8s, RegisterKind, kind as a, fetch, fetchStatus, kind } from "kubernetes-fluent-client";
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
|
|
4
4
|
import { Capability } from "./lib/capability";
|
|
5
5
|
import Log from "./lib/logger";
|
|
6
6
|
import { PeprModule } from "./lib/module";
|
|
7
7
|
import { PeprMutateRequest } from "./lib/mutate-request";
|
|
8
|
-
import { PeprValidateRequest } from "./lib/validate-request";
|
|
9
8
|
import * as PeprUtils from "./lib/utils";
|
|
10
|
-
|
|
11
|
-
// Import type information for external packages
|
|
12
|
-
import type * as RTypes from "ramda";
|
|
9
|
+
import { PeprValidateRequest } from "./lib/validate-request";
|
|
13
10
|
|
|
14
11
|
export {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
Capability,
|
|
13
|
+
K8s,
|
|
14
|
+
Log,
|
|
18
15
|
PeprModule,
|
|
19
16
|
PeprMutateRequest,
|
|
20
|
-
PeprValidateRequest,
|
|
21
17
|
PeprUtils,
|
|
22
|
-
|
|
23
|
-
K8s,
|
|
24
|
-
Capability,
|
|
25
|
-
Log,
|
|
18
|
+
PeprValidateRequest,
|
|
26
19
|
R,
|
|
20
|
+
RegisterKind,
|
|
21
|
+
a,
|
|
27
22
|
fetch,
|
|
28
23
|
fetchStatus,
|
|
29
|
-
|
|
30
|
-
// Export the imported type information for external packages
|
|
31
|
-
RTypes,
|
|
24
|
+
kind,
|
|
32
25
|
};
|
|
@@ -52,19 +52,24 @@ When(a.Namespace)
|
|
|
52
52
|
.Watch(async ns => {
|
|
53
53
|
Log.info("Namespace pepr-demo-2 was created.");
|
|
54
54
|
|
|
55
|
+
try {
|
|
56
|
+
// Apply the ConfigMap using K8s server-side apply
|
|
57
|
+
await K8s(kind.ConfigMap).Apply({
|
|
58
|
+
metadata: {
|
|
59
|
+
name: "pepr-ssa-demo",
|
|
60
|
+
namespace: "pepr-demo-2",
|
|
61
|
+
},
|
|
62
|
+
data: {
|
|
63
|
+
"ns-uid": ns.metadata.uid,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// You can use the Log object to log messages to the Pepr controller pod
|
|
68
|
+
Log.error(error, "Failed to apply ConfigMap using server-side apply.");
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
// You can share data between actions using the Store, including between different types of actions
|
|
56
72
|
Store.setItem("watch-data", "This data was stored by a Watch Action.");
|
|
57
|
-
|
|
58
|
-
// Apply the ConfigMap using K8s server-side apply
|
|
59
|
-
await K8s(kind.ConfigMap).Apply({
|
|
60
|
-
metadata: {
|
|
61
|
-
name: "pepr-ssa-demo",
|
|
62
|
-
namespace: "pepr-demo-2",
|
|
63
|
-
},
|
|
64
|
-
data: {
|
|
65
|
-
"ns-uid": ns.metadata.uid,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
73
|
});
|
|
69
74
|
|
|
70
75
|
/**
|