@r8s/r8s-controller 0.1.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/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # r8s-controller
2
+
3
+ Kubernetes-native controller for r8s. Renders TSX to YAML in-cluster.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ ┌─────────────┐ ┌─────────────────┐ ┌─────────────┐
9
+ │ Git Repo │────▶│ r8s-controller │────▶│ Kubernetes │
10
+ │ (r8s.tsx) │ │ (renders YAML) │ │ (applied) │
11
+ └─────────────┘ └─────────────────┘ └─────────────┘
12
+ ```
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ kubectl apply -f config/crd.yaml
18
+ kubectl apply -f config/deploy.yaml
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```yaml
24
+ apiVersion: r8s.berget.ai/v1
25
+ kind: R8sSource
26
+ metadata:
27
+ name: my-app
28
+ namespace: default
29
+ spec:
30
+ git:
31
+ url: https://github.com/my-org/my-app
32
+ ref: main
33
+ entry: k8s/r8s.tsx
34
+ includeOperators: false
35
+ ```
36
+
37
+ ## Status
38
+
39
+ ```bash
40
+ kubectl get r8ssource
41
+ # NAME READY REVISION RESOURCES AGE
42
+ # my-app True main 12 5m
43
+ ```
@@ -0,0 +1,11 @@
1
+ export declare class R8sController {
2
+ private k8sApi;
3
+ private watch;
4
+ private namespace;
5
+ constructor(namespace?: string);
6
+ start(): Promise<void>;
7
+ private handleEvent;
8
+ private reconcile;
9
+ private updateStatus;
10
+ }
11
+ //# sourceMappingURL=controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"AA2CA,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,KAAK,CAAO;IACpB,OAAO,CAAC,SAAS,CAAQ;gBAEb,SAAS,SAAY;IAQ3B,KAAK;YAmBG,WAAW;YAeX,SAAS;YA0FT,YAAY;CAkB3B"}
@@ -0,0 +1,138 @@
1
+ import { KubeConfig, CustomObjectsApi, Watch } from '@kubernetes/client-node';
2
+ import { renderToYaml } from '@r8s/cli/src/renderer.js';
3
+ import { execSync } from 'child_process';
4
+ import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'fs';
5
+ import { tmpdir } from 'os';
6
+ import { join } from 'path';
7
+ const GROUP = 'r8s.berget.ai';
8
+ const VERSION = 'v1';
9
+ const PLURAL = 'r8ssources';
10
+ export class R8sController {
11
+ k8sApi;
12
+ watch;
13
+ namespace;
14
+ constructor(namespace = 'default') {
15
+ const kc = new KubeConfig();
16
+ kc.loadFromDefault();
17
+ this.k8sApi = kc.makeApiClient(CustomObjectsApi);
18
+ this.watch = new Watch(kc);
19
+ this.namespace = namespace;
20
+ }
21
+ async start() {
22
+ console.log('[r8s-controller] Starting...');
23
+ console.log(`[r8s-controller] Watching ${PLURAL} in namespace ${this.namespace}`);
24
+ // Watch for R8sSource changes
25
+ await this.watch.watch(`/apis/${GROUP}/${VERSION}/namespaces/${this.namespace}/${PLURAL}`, {}, (type, obj) => {
26
+ this.handleEvent(type, obj);
27
+ }, (err) => {
28
+ console.error('[r8s-controller] Watch error:', err);
29
+ // Restart watch after error
30
+ setTimeout(() => this.start(), 5000);
31
+ });
32
+ }
33
+ async handleEvent(type, obj) {
34
+ if (type === 'DELETED') {
35
+ console.log(`[r8s-controller] Deleted: ${obj.metadata?.name}`);
36
+ return;
37
+ }
38
+ if (obj.spec.suspend) {
39
+ console.log(`[r8s-controller] Suspended: ${obj.metadata?.name}`);
40
+ return;
41
+ }
42
+ console.log(`[r8s-controller] Reconciling: ${obj.metadata?.name}`);
43
+ await this.reconcile(obj);
44
+ }
45
+ async reconcile(obj) {
46
+ const name = obj.metadata?.name || 'unknown';
47
+ const namespace = obj.metadata?.namespace || 'default';
48
+ const generation = obj.metadata?.generation || 0;
49
+ try {
50
+ // Update status to Progressing
51
+ await this.updateStatus(name, namespace, {
52
+ observedGeneration: generation,
53
+ conditions: [
54
+ {
55
+ type: 'Ready',
56
+ status: 'Unknown',
57
+ reason: 'Progressing',
58
+ message: 'Reconciliation in progress',
59
+ lastTransitionTime: new Date().toISOString(),
60
+ },
61
+ ],
62
+ });
63
+ // 1. Clone repo
64
+ const tmpDir = mkdtempSync(join(tmpdir(), 'r8s-'));
65
+ const repoUrl = obj.spec.git.url;
66
+ const ref = obj.spec.git.ref || 'main';
67
+ console.log(`[r8s-controller] Cloning ${repoUrl}@${ref}`);
68
+ execSync(`git clone --depth 1 --branch ${ref} ${repoUrl} ${tmpDir}`, {
69
+ timeout: 60000,
70
+ });
71
+ // 2. Render TSX → YAML
72
+ const entryPath = join(tmpDir, obj.spec.entry || 'k8s/r8s.tsx');
73
+ const outputDir = join(tmpDir, obj.spec.output || 'rendered/');
74
+ mkdirSync(outputDir, { recursive: true });
75
+ console.log(`[r8s-controller] Rendering ${entryPath}`);
76
+ const yaml = await renderToYaml(entryPath, {
77
+ includeOperators: obj.spec.includeOperators,
78
+ });
79
+ const outputFile = join(outputDir, 'manifest.yaml');
80
+ writeFileSync(outputFile, yaml);
81
+ // 3. Apply with kubectl
82
+ console.log(`[r8s-controller] Applying ${outputFile}`);
83
+ execSync(`kubectl apply -f ${outputFile} --server-side`, {
84
+ timeout: 60000,
85
+ });
86
+ // 4. Count resources
87
+ const resourceCount = yaml.split('---').length - 1;
88
+ // 5. Update status to Ready
89
+ await this.updateStatus(name, namespace, {
90
+ observedGeneration: generation,
91
+ lastAppliedRevision: ref,
92
+ resources: resourceCount,
93
+ conditions: [
94
+ {
95
+ type: 'Ready',
96
+ status: 'True',
97
+ reason: 'ReconciliationSucceeded',
98
+ message: `Applied ${resourceCount} resources`,
99
+ lastTransitionTime: new Date().toISOString(),
100
+ },
101
+ ],
102
+ });
103
+ console.log(`[r8s-controller] Reconciled: ${name} (${resourceCount} resources)`);
104
+ // Cleanup
105
+ rmSync(tmpDir, { recursive: true, force: true });
106
+ }
107
+ catch (error) {
108
+ console.error(`[r8s-controller] Failed: ${name}:`, error.message);
109
+ await this.updateStatus(name, namespace, {
110
+ observedGeneration: generation,
111
+ conditions: [
112
+ {
113
+ type: 'Ready',
114
+ status: 'False',
115
+ reason: 'ReconciliationFailed',
116
+ message: error.message,
117
+ lastTransitionTime: new Date().toISOString(),
118
+ },
119
+ ],
120
+ });
121
+ }
122
+ }
123
+ async updateStatus(name, namespace, status) {
124
+ try {
125
+ await this.k8sApi.patchNamespacedCustomObjectStatus(GROUP, VERSION, namespace, PLURAL, name, { status }, undefined, undefined, undefined, { headers: { 'Content-Type': 'application/merge-patch+json' } });
126
+ }
127
+ catch (error) {
128
+ console.error(`[r8s-controller] Failed to update status:`, error.message);
129
+ }
130
+ }
131
+ }
132
+ // Start controller if run directly
133
+ if (import.meta.url === `file://${process.argv[1]}`) {
134
+ const namespace = process.env.NAMESPACE || 'default';
135
+ const controller = new R8sController(namespace);
136
+ controller.start().catch(console.error);
137
+ }
138
+ //# sourceMappingURL=controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.js","sourceRoot":"","sources":["../src/controller.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@r8s/r8s-controller",
3
+ "version": "0.1.0",
4
+ "description": "r8s Kubernetes controller - renders TSX to YAML in-cluster",
5
+ "license": "MIT",
6
+ "author": "Berget AI AB",
7
+ "homepage": "https://r8s.berget.ai",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/berget-ai/r8s.git",
11
+ "directory": "packages/r8s-controller"
12
+ },
13
+ "bugs": "https://github.com/berget-ai/r8s/issues",
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "main": "./dist/controller.js",
21
+ "type": "module",
22
+ "scripts": {
23
+ "build": "tsc",
24
+ "dev": "tsx src/controller.ts",
25
+ "start": "node dist/controller.js",
26
+ "test": "echo 'No tests for r8s-controller'",
27
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
28
+ },
29
+ "dependencies": {
30
+ "@kubernetes/client-node": "^0.20.0",
31
+ "@r8s/core": "^0.1.0",
32
+ "@r8s/cli": "^0.1.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/node": "^20.0.0",
36
+ "tsx": "^4.0.0",
37
+ "typescript": "^5.3.0"
38
+ }
39
+ }