pepr 0.15.0 → 0.17.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/README.md +4 -2
- package/dist/cli.js +73 -47
- package/dist/controller.js +1 -1
- package/dist/lib/assets/webhooks.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 +1 -0
- package/dist/lib/helpers.d.ts.map +1 -1
- 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 +13 -13
- package/src/lib/assets/webhooks.ts +10 -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 +13 -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/website/assets/scss/_variables_project.scss +1 -1
- package/website/content/en/docs/OnSchedule.md +86 -0
- package/website/content/en/docs/cli.md +5 -2
- package/website/content/en/docs/store.md +48 -0
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
|
package/src/lib/helpers.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
4
|
import { CapabilityExport } from "./types";
|
|
5
|
+
import { promises as fs } from "fs";
|
|
5
6
|
|
|
6
7
|
type RBACMap = {
|
|
7
8
|
[key: string]: {
|
|
@@ -37,3 +38,15 @@ export const createRBACMap = (capabilities: CapabilityExport[]): RBACMap => {
|
|
|
37
38
|
return acc;
|
|
38
39
|
}, {});
|
|
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
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
$secondary: #EFCA81;
|
|
1
|
+
$secondary: #EFCA81;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: OnSchedule
|
|
3
|
+
linkTitle: OnSchedule
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# OnSchedule
|
|
7
|
+
|
|
8
|
+
The `OnSchedule` feature allows you to schedule and automate the execution of specific code at predefined intervals or schedules. This feature is designed to simplify recurring tasks and can serve as an alternative to traditional CronJobs. This code is designed to be run at the top level on a Capability, not within a function like `When`.
|
|
9
|
+
|
|
10
|
+
> **Note -** To use this feature in dev mode you MUST set `PEPR_WATCH_MODE="true"`. This is because the scheduler only runs on the watch controller and the watch controller is not started by default in dev mode.
|
|
11
|
+
|
|
12
|
+
For example: `PEPR_WATCH_MODE="true" npx pepr dev`
|
|
13
|
+
|
|
14
|
+
## Best Practices
|
|
15
|
+
|
|
16
|
+
`OnSchedule` is designed for targeting intervals equal to or larger than 30 seconds due to the storage mechanism used to archive schedule info.
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Create a recurring task execution by calling the OnSchedule function with the following parameters:
|
|
21
|
+
|
|
22
|
+
**name** - The unique name of the schedule.
|
|
23
|
+
|
|
24
|
+
**every** - An integer that represents the frequency of the schedule in number of _units_.
|
|
25
|
+
|
|
26
|
+
**unit** - A string specifying the time unit for the schedule (e.g., `seconds`, `minute`, `minutes`, `hour`, `hours`).
|
|
27
|
+
|
|
28
|
+
**startTime** - (Optional) A UTC timestamp indicating when the schedule should start. All date times must be provided in GMT. If not specified the schedule will start when the schedule store reports ready.
|
|
29
|
+
|
|
30
|
+
**run** - A function that contains the code you want to execute on the defined schedule.
|
|
31
|
+
|
|
32
|
+
**completions** - (Optional) An integer indicating the maximum number of times the schedule should run to completion. If not specified the schedule will run indefinitely.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
Update the curr ConfigMap every 15 seconds and use the store to track the current count:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
OnSchedule({
|
|
41
|
+
name: "hello-interval",
|
|
42
|
+
every: 30,
|
|
43
|
+
unit: "seconds",
|
|
44
|
+
run: async () => {
|
|
45
|
+
Log.info("Wait 30 seconds and create/update a ConfigMap");
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await K8s(kind.ConfigMap).Apply({
|
|
49
|
+
metadata: {
|
|
50
|
+
name: "last-updated",
|
|
51
|
+
namespace: "default",
|
|
52
|
+
},
|
|
53
|
+
data: {
|
|
54
|
+
count: `${new Date()}`,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
} catch (error) {
|
|
59
|
+
Log.error(error, "Failed to apply ConfigMap using server-side apply.");
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Refresh an AWSToken every 24 hours, with a delayed start of 30 seconds, running a total of 3 times:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
|
|
69
|
+
OnSchedule({
|
|
70
|
+
name: "refresh-aws-token",
|
|
71
|
+
every: 24,
|
|
72
|
+
unit: "hours",
|
|
73
|
+
startTime: new Date(new Date().getTime() + 1000 * 30),
|
|
74
|
+
run: async () => {
|
|
75
|
+
await RefreshAWSToken();
|
|
76
|
+
},
|
|
77
|
+
completions: 3,
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Advantages
|
|
82
|
+
|
|
83
|
+
- Simplifies scheduling recurring tasks without the need for complex CronJob configurations.
|
|
84
|
+
- Provides flexibility to define schedules in a human-readable format.
|
|
85
|
+
- Allows you to execute code with precision at specified intervals.
|
|
86
|
+
- Supports limiting the number of schedule completions for finite tasks.
|
|
@@ -59,6 +59,9 @@ Create a [zarf.yaml](https://zarf.dev) and K8s manifest for the current module.
|
|
|
59
59
|
|
|
60
60
|
**Options:**
|
|
61
61
|
|
|
62
|
-
- `-r, --registry-info [<registry>/<username>]` - Registry Info: Image registry and username. Note: You must be signed into the registry
|
|
63
|
-
- `--rbac-mode [admin|scoped]` - Rbac Mode: admin, scoped (default: admin)
|
|
64
62
|
- `-l, --log-level [level]` - Log level: debug, info, warn, error (default: "info")
|
|
63
|
+
- `-e, --entry-point [file]` - Specify the entry point file to build with. (default: "pepr.ts")
|
|
64
|
+
- `-n, --no-embed` - Disables embedding of deployment files into output module. Useful when creating library modules intended solely for reuse/distribution via NPM
|
|
65
|
+
- `-r, --registry-info [<registry>/<username>]` - Registry Info: Image registry and username. Note: You must be signed into the registry
|
|
66
|
+
- `-o, --output-dir [output directory]` - Define where to place build output
|
|
67
|
+
- `--rbac-mode [admin|scoped]` - Rbac Mode: admin, scoped (default: admin) (choices: "admin", "scoped", default: "admin")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Store
|
|
3
|
+
linkTitle: Store
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Pepr Store: A Lightweight Key-Value Store for Pepr Modules
|
|
7
|
+
|
|
8
|
+
The nature of admission controllers and general watch operations (the `Mutate`, `Validate` and `Watch` actions in Pepr) make some types of complex and long-running operations difficult. There are also times when you need to share data between different actions. While you could manually create your own K8s resources and manage their cleanup, this can be very hard to track and keep performant at scale.
|
|
9
|
+
|
|
10
|
+
The Pepr Store solves this by exposing a simple, [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Storage)-compatible mechanism for use within capabilities. Additionally, as Pepr runs multiple replicas of the admission controller along with a watch controller, the Pepr Store provides a unique way to share data between these different instances automatically.
|
|
11
|
+
|
|
12
|
+
Each Pepr Capability has a `Store` instance that can be used to get, set and delete data as well as subscribe to any changes to the Store. Behind the scenes, all capability store instances in a single Pepr Module are stored within a single CRD in the cluster. This CRD is automatically created when the Pepr Module is deployed. Care is taken to make the read and write operations as efficient as possible by using K8s watches, batch processing and patch operations for writes.
|
|
13
|
+
|
|
14
|
+
## Key Features
|
|
15
|
+
|
|
16
|
+
- **Asynchronous Key-Value Store**: Provides an asynchronous interface for storing small amounts of data, making it ideal for sharing information between various actions and capabilities.
|
|
17
|
+
- **Web Storage API Compatibility**: The store's API is aligned with the standard [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Storage), simplifying the learning curve.
|
|
18
|
+
- **Real-time Updates**: The `.subscribe()` and `onReady()` methods enable real-time updates, allowing you to react to changes in the data store instantaneously.
|
|
19
|
+
|
|
20
|
+
- **Automatic CRD Management**: Each Pepr Module has its data stored within a single Custom Resource Definition (CRD) that is automatically created upon deployment.
|
|
21
|
+
- **Efficient Operations**: Pepr Store uses Kubernetes watches, batch processing, and patch operations to make read and write operations as efficient as possible.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// Example usage for Pepr Store
|
|
27
|
+
Store.setItem("example-1", "was-here");
|
|
28
|
+
Store.setItem("example-1-data", JSON.stringify(request.Raw.data));
|
|
29
|
+
Store.onReady(data => {
|
|
30
|
+
Log.info(data, "Pepr Store Ready");
|
|
31
|
+
});
|
|
32
|
+
const unsubscribe = Store.subscribe(data => {
|
|
33
|
+
Log.info(data, "Pepr Store Updated");
|
|
34
|
+
unsubscribe();
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API Reference
|
|
39
|
+
|
|
40
|
+
### Methods
|
|
41
|
+
|
|
42
|
+
- `getItem(key: string)`: Retrieves a value by its key. Returns `null` if the key doesn't exist.
|
|
43
|
+
- `setItem(key: string, value: string)`: Sets a value for a given key. Creates a new key-value pair if the key doesn't exist.
|
|
44
|
+
- `setItemAndWait(key: string, value: string)`: Sets a value for a given key. Creates a new key-value pair if the key doesn't exist. Returns a promise when the new key and value show up in the store. Should only be used on a `Watch` to avoid [timeouts](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts).
|
|
45
|
+
- `removeItem(key: string)`: Deletes a key-value pair by its key.
|
|
46
|
+
- `clear()`: Clears all key-value pairs from the store.
|
|
47
|
+
- `subscribe(listener: DataReceiver)`: Subscribes to store updates.
|
|
48
|
+
- `onReady(callback: DataReceiver)`: Executes a callback when the store is ready.
|