Skip to content

Commit 8afb4a8

Browse files
committed
Apply on detected remote change
1 parent 5c7190b commit 8afb4a8

File tree

4 files changed

+159
-106
lines changed

4 files changed

+159
-106
lines changed

src/lib/applyTemplate.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { Provider } from "./provider.js";
2+
import type { KubernetesObject } from "@kubernetes/client-node";
3+
4+
export const applyTemplate = async (
5+
template: KubernetesObject,
6+
{
7+
provider,
8+
group,
9+
stateId,
10+
}: { provider: Provider; group: string; stateId: string }
11+
) => {
12+
const current = await provider.objectApi.read(template).catch(() => null);
13+
14+
if (current) {
15+
if (
16+
current.metadata.labels?.["app.kubernetes.io/managed-by"] !== "kubeneko"
17+
) {
18+
throw new Error(
19+
"Resource already exists and is not managed by kubeneko!"
20+
);
21+
}
22+
if (current.metadata.annotations?.[`${group}/state-id`] !== stateId) {
23+
throw new Error("Resource is managed by another kubeneko state!");
24+
}
25+
}
26+
27+
const active =
28+
template.metadata?.annotations?.[`${group}/active`] !== "false";
29+
30+
if (active && current) {
31+
try {
32+
if (current) {
33+
return await provider.objectApi.replace(template);
34+
}
35+
} catch (e) {
36+
console.error(e);
37+
}
38+
} else if (active) {
39+
try {
40+
return await provider.objectApi.create(template);
41+
} catch (e) {
42+
console.error(e);
43+
}
44+
} else if (current) {
45+
try {
46+
await provider.objectApi.delete(template);
47+
return null;
48+
} catch (e) {
49+
console.error(e);
50+
}
51+
}
52+
};

src/lib/basics.ts

Lines changed: 56 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import merge from "deepmerge";
66
import dotenv from "dotenv";
77
import type { Provider } from "./provider.js";
88
import { State } from "./state.js";
9+
import { applyTemplate } from "./applyTemplate.js";
910

1011
dotenv.config();
1112
const stateId = process.env.KUBENEKO_STATE_ID;
@@ -17,9 +18,42 @@ state.subscribeToNewNamespace((namespace, provider) => {
1718
provider.watch.watch(
1819
`/api/v1/namespaces/${namespace}/events`,
1920
{},
20-
(type, event) => {
21-
console.log(`Type: ${type}`);
22-
console.log(`Event: ${JSON.stringify(event, null, 2)}`);
21+
async (type, event) => {
22+
// console.log(`Type: ${type}`);
23+
// console.log(`Event: ${JSON.stringify(event, null, 2)}`);
24+
const res = event.involvedObject;
25+
if (!res) return;
26+
if (type === "MODIFIED") {
27+
const stateEntry = state.state
28+
.get(provider)
29+
.resources.get(
30+
`${res.apiVersion}/${res.kind}/${res.namespace}/${res.name}`
31+
);
32+
if (!stateEntry) return;
33+
console.log(
34+
"remote resource changed:",
35+
stateEntry.resource.value.apiVersion,
36+
stateEntry.resource.value.kind,
37+
stateEntry.resource.value.metadata?.namespace,
38+
stateEntry.resource.value.metadata?.name
39+
);
40+
const resFromRemote = await provider.objectApi.read(
41+
stateEntry.resource.value
42+
);
43+
44+
const isEqual =
45+
JSON.stringify((resFromRemote as any).spec) ===
46+
JSON.stringify((stateEntry.resource.value as any).spec);
47+
if (isEqual) {
48+
stateEntry.resource.value = resFromRemote;
49+
} else {
50+
console.log("yikes!");
51+
stateEntry.resource.value = await applyTemplate(
52+
stateEntry.template.value,
53+
{ provider, group, stateId }
54+
);
55+
}
56+
}
2357
},
2458
(err) => {
2559
console.error("Error watching events:", err);
@@ -45,66 +79,15 @@ export const createTemplate = <T extends KubernetesObject | KubernetesObject>(
4579
console.log(
4680
`template updated:`,
4781
_template.apiVersion,
48-
_template.metadata?.namespace,
4982
_template.kind,
83+
_template.metadata?.namespace,
5084
_template.metadata?.name
5185
);
5286
template.value = _template;
5387
});
5488
return template as ReturnType<typeof createSignal<T>>;
5589
};
5690

57-
const applyTemplate = async (
58-
template: KubernetesObject,
59-
{ provider }: { provider: Provider }
60-
) => {
61-
const current = await provider.objectApi.read(template).catch(() => null);
62-
63-
if (current) {
64-
if (
65-
current.metadata.labels?.["app.kubernetes.io/managed-by"] !== "kubeneko"
66-
) {
67-
throw new Error(
68-
"Resource already exists and is not managed by kubeneko!"
69-
);
70-
}
71-
if (current.metadata.annotations?.[`${group}/state-id`] !== stateId) {
72-
throw new Error("Resource is managed by another kubeneko state!");
73-
}
74-
}
75-
76-
const active =
77-
template.metadata?.annotations?.[`${group}/active`] !== "false";
78-
79-
if (active && current) {
80-
try {
81-
if (current) {
82-
const res = await provider.objectApi.replace(template);
83-
state.add(res, provider);
84-
return res;
85-
}
86-
} catch (e) {
87-
console.error(e);
88-
}
89-
} else if (active) {
90-
try {
91-
const res = await provider.objectApi.create(template);
92-
state.add(res, provider);
93-
return res;
94-
} catch (e) {
95-
console.error(e);
96-
}
97-
} else if (current) {
98-
try {
99-
const res = await provider.objectApi.delete(template);
100-
state.remove(res, provider);
101-
return null;
102-
} catch (e) {
103-
console.error(e);
104-
}
105-
}
106-
};
107-
10891
export const createResource = <T extends KubernetesObject | KubernetesObject>(
10992
getTemplate: () => ReturnType<typeof createTemplate<T>>,
11093
{ provider }: { provider: Provider }
@@ -118,7 +101,12 @@ export const createResource = <T extends KubernetesObject | KubernetesObject>(
118101
const newTemp = template.value;
119102

120103
if (!oldTemp) {
121-
resource.value = await applyTemplate(newTemp, { provider });
104+
resource.value = await applyTemplate(newTemp, {
105+
provider,
106+
group,
107+
stateId,
108+
});
109+
state.add({ template, resource }, provider);
122110
resolve(resource);
123111
return;
124112
}
@@ -135,9 +123,19 @@ export const createResource = <T extends KubernetesObject | KubernetesObject>(
135123
) {
136124
const res = await provider.objectApi.delete(oldTemp);
137125
state.remove(res, provider);
138-
resource.value = await applyTemplate(newTemp, { provider });
126+
resource.value = await applyTemplate(newTemp, {
127+
provider,
128+
group,
129+
stateId,
130+
});
131+
state.add({ template, resource }, provider);
139132
} else {
140-
resource.value = await applyTemplate(newTemp, { provider });
133+
resource.value = await applyTemplate(newTemp, {
134+
provider,
135+
group,
136+
stateId,
137+
});
138+
state.add({ template, resource }, provider);
141139
}
142140
});
143141
});

src/lib/reactive.ts

Lines changed: 37 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,20 @@ class ReactiveContext {
1212
}
1313
}
1414

15-
// Notify all dependencies when a signal changes
16-
notify() {
17-
this.dependencies.forEach((signal) => {
18-
signal.notify();
19-
});
20-
}
15+
// // Notify all dependencies when a signal changes
16+
// notify() {
17+
// this.dependencies.forEach((signal) => {
18+
// signal.notify();
19+
// });
20+
// }
2121
}
2222

23+
export type Signal<T> = {
24+
oldValue: T | null;
25+
value: T;
26+
subscribe: (Function) => void;
27+
};
28+
2329
export function createSignal<T>(initialValue: T) {
2430
let _value = initialValue;
2531
const subscribers = new Set() as Set<() => void>;
@@ -46,51 +52,37 @@ export function createSignal<T>(initialValue: T) {
4652
subscribe: (subscriber) => {
4753
subscribers.add(subscriber);
4854
},
49-
};
55+
} as Signal<T>;
5056
}
5157

52-
class Signal<T> {
53-
private _value: T;
54-
public subscribers: Set<() => void> = new Set(); // To hold subscribers (reactive functions)
58+
// class Signal<T> {
59+
// private _value: T;
60+
// public subscribers: Set<() => void> = new Set(); // To hold subscribers (reactive functions)
5561

56-
constructor(initialValue: T) {
57-
this._value = initialValue;
58-
}
62+
// constructor(initialValue: T) {
63+
// this._value = initialValue;
64+
// }
5965

60-
// Getter for the signal value
61-
get(): T {
62-
if (currentContext) {
63-
currentContext.track(this); // Track this signal in the current context
64-
}
65-
return this._value;
66-
}
66+
// // Notify all subscribers
67+
// private notify() {
68+
// this.subscribers.forEach((subscriber) => subscriber());
69+
// }
6770

68-
// Setter for the signal value
69-
set(newValue: T) {
70-
this._value = newValue;
71-
this.notify(); // Notify subscribers when the value changes
72-
}
71+
// subscribe(subscriber) {
72+
// this.subscribers.add(subscriber);
73+
// }
7374

74-
// Notify all subscribers
75-
notify() {
76-
this.subscribers.forEach((subscriber) => subscriber());
77-
}
78-
79-
subscribe(subscriber) {
80-
this.subscribers.add(subscriber);
81-
}
82-
83-
get value() {
84-
if (currentContext) {
85-
currentContext.track(this); // Track this signal in the current context
86-
}
87-
return this._value;
88-
}
89-
set value(newValue: T) {
90-
this._value = newValue;
91-
this.notify(); // Notify subscribers when the value changes
92-
}
93-
}
75+
// get value() {
76+
// if (currentContext) {
77+
// currentContext.track(this); // Track this signal in the current context
78+
// }
79+
// return this._value;
80+
// }
81+
// set value(newValue: T) {
82+
// this._value = newValue;
83+
// this.notify(); // Notify subscribers when the value changes
84+
// }
85+
// }
9486

9587
// Create a reactive effect
9688
export function effect(fn: () => void) {

src/lib/state.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import * as k8s from "@kubernetes/client-node";
22
import type { KubernetesObject } from "@kubernetes/client-node";
33
import type { Provider } from "./provider.js";
4+
import type { Signal } from "./reactive.js";
5+
6+
type StateEntry = {
7+
template: Signal<KubernetesObject>;
8+
resource: Signal<KubernetesObject>;
9+
};
410

511
export class State {
612
private _state: Map<
713
Provider,
8-
{ namespaces: Set<string>; resources: Map<string, KubernetesObject> }
14+
{ namespaces: Set<string>; resources: Map<string, StateEntry> }
915
>;
1016
private subscribers: Set<Function>;
1117

@@ -24,21 +30,22 @@ export class State {
2430
this.subscribers.forEach((fn) => fn(namespace, provider));
2531
}
2632

27-
public add(res: KubernetesObject, provider: Provider) {
33+
public add(entry: StateEntry, provider: Provider) {
2834
if (!this._state.has(provider)) {
2935
this._state.set(provider, {
3036
namespaces: new Set(),
3137
resources: new Map(),
3238
});
3339
}
40+
const res = entry.template.value;
3441
const s = this._state.get(provider);
3542
if (!s.namespaces.has(res.metadata.namespace)) {
3643
this.notify(res.metadata.namespace, provider);
3744
}
3845
s.namespaces.add(res.metadata.namespace);
3946
s.resources.set(
4047
`${res.apiVersion}/${res.kind}/${res.metadata.namespace}/${res.metadata.name}`,
41-
res
48+
entry
4249
);
4350
}
4451

@@ -49,4 +56,8 @@ export class State {
4956
`${res.apiVersion}/${res.kind}/${res.metadata.namespace}/${res.metadata.name}`
5057
);
5158
}
59+
60+
get state() {
61+
return this._state;
62+
}
5263
}

0 commit comments

Comments
 (0)