@vibesdotdev/infra-doks 0.0.1
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 +107 -0
- package/SPEC.md +285 -0
- package/dist/client/digitalocean-app-deploy.client.d.ts +46 -0
- package/dist/client/digitalocean-app-deploy.client.d.ts.map +1 -0
- package/dist/client/digitalocean-app-deploy.client.js +135 -0
- package/dist/client/digitalocean-app-deploy.client.js.map +1 -0
- package/dist/client/index.d.ts +15 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +18 -0
- package/dist/client/index.js.map +1 -0
- package/dist/cloud/base.d.ts +33 -0
- package/dist/cloud/base.d.ts.map +1 -0
- package/dist/cloud/base.js +86 -0
- package/dist/cloud/base.js.map +1 -0
- package/dist/cloud/digitalocean.d.ts +33 -0
- package/dist/cloud/digitalocean.d.ts.map +1 -0
- package/dist/cloud/digitalocean.js +258 -0
- package/dist/cloud/digitalocean.js.map +1 -0
- package/dist/cloud/factory.d.ts +28 -0
- package/dist/cloud/factory.d.ts.map +1 -0
- package/dist/cloud/factory.js +151 -0
- package/dist/cloud/factory.js.map +1 -0
- package/dist/cloud/index.d.ts +12 -0
- package/dist/cloud/index.d.ts.map +1 -0
- package/dist/cloud/index.js +11 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/doks.plugin.d.ts +41 -0
- package/dist/doks.plugin.d.ts.map +1 -0
- package/dist/doks.plugin.js +287 -0
- package/dist/doks.plugin.js.map +1 -0
- package/dist/implementations/deployment.impl.d.ts +34 -0
- package/dist/implementations/deployment.impl.d.ts.map +1 -0
- package/dist/implementations/deployment.impl.js +86 -0
- package/dist/implementations/deployment.impl.js.map +1 -0
- package/dist/implementations/droplet.impl.d.ts +85 -0
- package/dist/implementations/droplet.impl.d.ts.map +1 -0
- package/dist/implementations/droplet.impl.js +113 -0
- package/dist/implementations/droplet.impl.js.map +1 -0
- package/dist/implementations/gitea.impl.d.ts +68 -0
- package/dist/implementations/gitea.impl.d.ts.map +1 -0
- package/dist/implementations/gitea.impl.js +295 -0
- package/dist/implementations/gitea.impl.js.map +1 -0
- package/dist/implementations/managed-db.impl.d.ts +25 -0
- package/dist/implementations/managed-db.impl.d.ts.map +1 -0
- package/dist/implementations/managed-db.impl.js +31 -0
- package/dist/implementations/managed-db.impl.js.map +1 -0
- package/dist/implementations/managed-redis.impl.d.ts +37 -0
- package/dist/implementations/managed-redis.impl.d.ts.map +1 -0
- package/dist/implementations/managed-redis.impl.js +40 -0
- package/dist/implementations/managed-redis.impl.js.map +1 -0
- package/dist/implementations/spaces.impl.d.ts +36 -0
- package/dist/implementations/spaces.impl.d.ts.map +1 -0
- package/dist/implementations/spaces.impl.js +40 -0
- package/dist/implementations/spaces.impl.js.map +1 -0
- package/dist/implementations/statefulset.impl.d.ts +65 -0
- package/dist/implementations/statefulset.impl.d.ts.map +1 -0
- package/dist/implementations/statefulset.impl.js +165 -0
- package/dist/implementations/statefulset.impl.js.map +1 -0
- package/dist/implementations/verdaccio.impl.d.ts +65 -0
- package/dist/implementations/verdaccio.impl.d.ts.map +1 -0
- package/dist/implementations/verdaccio.impl.js +259 -0
- package/dist/implementations/verdaccio.impl.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/kubernetes/index.d.ts +95 -0
- package/dist/kubernetes/index.d.ts.map +1 -0
- package/dist/kubernetes/index.js +625 -0
- package/dist/kubernetes/index.js.map +1 -0
- package/dist/secrets/index.d.ts +4 -0
- package/dist/secrets/index.d.ts.map +1 -0
- package/dist/secrets/index.js +4 -0
- package/dist/secrets/index.js.map +1 -0
- package/dist/secrets/vault.descriptor.d.ts +10 -0
- package/dist/secrets/vault.descriptor.d.ts.map +1 -0
- package/dist/secrets/vault.descriptor.js +25 -0
- package/dist/secrets/vault.descriptor.js.map +1 -0
- package/dist/secrets/vault.impl.cloud.d.ts +40 -0
- package/dist/secrets/vault.impl.cloud.d.ts.map +1 -0
- package/dist/secrets/vault.impl.cloud.js +178 -0
- package/dist/secrets/vault.impl.cloud.js.map +1 -0
- package/dist/secrets/vault.impl.d.ts +29 -0
- package/dist/secrets/vault.impl.d.ts.map +1 -0
- package/dist/secrets/vault.impl.js +137 -0
- package/dist/secrets/vault.impl.js.map +1 -0
- package/dist/types.d.ts +509 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +47 -0
- package/dist/types.js.map +1 -0
- package/package.json +145 -0
- package/src/client/digitalocean-app-deploy.client.ts +226 -0
- package/src/client/index.ts +24 -0
- package/src/cloud/base.ts +149 -0
- package/src/cloud/digitalocean.ts +363 -0
- package/src/cloud/factory.ts +190 -0
- package/src/cloud/index.ts +81 -0
- package/src/doks.plugin.ts +401 -0
- package/src/implementations/deployment.impl.ts +93 -0
- package/src/implementations/droplet.impl.ts +157 -0
- package/src/implementations/gitea.impl.ts +319 -0
- package/src/implementations/managed-db.impl.ts +37 -0
- package/src/implementations/managed-redis.impl.ts +49 -0
- package/src/implementations/spaces.impl.ts +52 -0
- package/src/implementations/statefulset.impl.ts +186 -0
- package/src/implementations/verdaccio.impl.ts +300 -0
- package/src/index.ts +136 -0
- package/src/kubernetes/index.ts +754 -0
- package/src/secrets/index.ts +9 -0
- package/src/secrets/vault.descriptor.ts +28 -0
- package/src/secrets/vault.impl.cloud.ts +278 -0
- package/src/secrets/vault.impl.ts +149 -0
- package/src/types.ts +563 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOKS Runtime Plugin
|
|
3
|
+
*
|
|
4
|
+
* Registers DigitalOcean Kubernetes infrastructure implementations with the runtime.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createRuntimePlugin, createRuntimeImplementation, createRuntimeAsset } from '@vibesdotdev/runtime';
|
|
8
|
+
import {
|
|
9
|
+
generateK8sDeployment,
|
|
10
|
+
DoksDeploymentDescriptorSchema,
|
|
11
|
+
type DoksDeploymentDescriptorInput
|
|
12
|
+
} from './implementations/deployment.impl';
|
|
13
|
+
import type { K8sDeployment } from './types.ts';
|
|
14
|
+
import { stringify } from 'yaml';
|
|
15
|
+
import { resolve } from 'node:path';
|
|
16
|
+
import {
|
|
17
|
+
generateK8sStatefulSet,
|
|
18
|
+
DoksQueueDescriptorSchema,
|
|
19
|
+
DoksDatabaseDescriptorSchema,
|
|
20
|
+
type DoksQueueDescriptorInput,
|
|
21
|
+
type DoksDatabaseDescriptorInput
|
|
22
|
+
} from './implementations/statefulset.impl';
|
|
23
|
+
import {
|
|
24
|
+
generateDOManagedDB,
|
|
25
|
+
DOManagedPostgresDescriptorSchema,
|
|
26
|
+
type DOManagedPostgresDescriptorInput
|
|
27
|
+
} from './implementations/managed-db.impl';
|
|
28
|
+
import {
|
|
29
|
+
generateDOManagedRedis,
|
|
30
|
+
DOManagedRedisDescriptorSchema,
|
|
31
|
+
type DOManagedRedisDescriptorInput
|
|
32
|
+
} from './implementations/managed-redis.impl';
|
|
33
|
+
import {
|
|
34
|
+
generateDOSpaces,
|
|
35
|
+
DOSpacesDescriptorSchema,
|
|
36
|
+
type DOSpacesDescriptorInput
|
|
37
|
+
} from './implementations/spaces.impl';
|
|
38
|
+
import {
|
|
39
|
+
generateGiteaManifest,
|
|
40
|
+
GiteaDoksDescriptorSchema,
|
|
41
|
+
type GiteaDoksDescriptorInput
|
|
42
|
+
} from './implementations/gitea.impl';
|
|
43
|
+
import {
|
|
44
|
+
generateVerdaccioManifest,
|
|
45
|
+
VerdaccioDoksDescriptorSchema,
|
|
46
|
+
type VerdaccioDoksDescriptorInput
|
|
47
|
+
} from './implementations/verdaccio.impl';
|
|
48
|
+
import { createDoDropletImpl } from './implementations/droplet.impl';
|
|
49
|
+
|
|
50
|
+
// Factory functions that wrap implementation generators in RuntimeImplementation
|
|
51
|
+
|
|
52
|
+
function createDeploymentImpl(input: DoksDeploymentDescriptorInput) {
|
|
53
|
+
const descriptor = DoksDeploymentDescriptorSchema.parse(input);
|
|
54
|
+
return createRuntimeImplementation({
|
|
55
|
+
id: 'doks-deployment',
|
|
56
|
+
kind: 'infra/worker',
|
|
57
|
+
priority: 20,
|
|
58
|
+
implementation: {
|
|
59
|
+
generateConfig: () => generateK8sDeployment(descriptor)
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createQueueStatefulSetImpl(input: DoksQueueDescriptorInput) {
|
|
65
|
+
const descriptor = DoksQueueDescriptorSchema.parse(input);
|
|
66
|
+
return createRuntimeImplementation({
|
|
67
|
+
id: 'doks-statefulset',
|
|
68
|
+
kind: 'infra/queue',
|
|
69
|
+
priority: 20,
|
|
70
|
+
implementation: {
|
|
71
|
+
generateConfig: () => generateK8sStatefulSet(descriptor)
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createDatabaseStatefulSetImpl(input: DoksDatabaseDescriptorInput) {
|
|
77
|
+
const descriptor = DoksDatabaseDescriptorSchema.parse(input);
|
|
78
|
+
return createRuntimeImplementation({
|
|
79
|
+
id: 'doks-statefulset',
|
|
80
|
+
kind: 'infra/database',
|
|
81
|
+
priority: 20,
|
|
82
|
+
implementation: {
|
|
83
|
+
generateConfig: () => generateK8sStatefulSet(descriptor)
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function createManagedPostgresImpl(input: DOManagedPostgresDescriptorInput) {
|
|
89
|
+
const descriptor = DOManagedPostgresDescriptorSchema.parse(input);
|
|
90
|
+
return createRuntimeImplementation({
|
|
91
|
+
id: 'do-managed-postgres',
|
|
92
|
+
kind: 'infra/database',
|
|
93
|
+
priority: 30,
|
|
94
|
+
implementation: {
|
|
95
|
+
generateConfig: () => generateDOManagedDB(descriptor)
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function createManagedRedisImpl(input: DOManagedRedisDescriptorInput) {
|
|
101
|
+
const descriptor = DOManagedRedisDescriptorSchema.parse(input);
|
|
102
|
+
return createRuntimeImplementation({
|
|
103
|
+
id: 'do-managed-redis',
|
|
104
|
+
kind: 'infra/cache',
|
|
105
|
+
priority: 20,
|
|
106
|
+
implementation: {
|
|
107
|
+
generateConfig: () => generateDOManagedRedis(descriptor)
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function createSpacesImpl(input: DOSpacesDescriptorInput) {
|
|
113
|
+
const descriptor = DOSpacesDescriptorSchema.parse(input);
|
|
114
|
+
return createRuntimeImplementation({
|
|
115
|
+
id: 'do-spaces',
|
|
116
|
+
kind: 'infra/object-storage',
|
|
117
|
+
priority: 20,
|
|
118
|
+
implementation: {
|
|
119
|
+
generateConfig: () => generateDOSpaces(descriptor)
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function createGiteaImpl(input: GiteaDoksDescriptorInput) {
|
|
125
|
+
const descriptor = GiteaDoksDescriptorSchema.parse(input);
|
|
126
|
+
return createRuntimeImplementation({
|
|
127
|
+
id: 'gitea-doks',
|
|
128
|
+
kind: 'infra/git-hosting',
|
|
129
|
+
priority: 20,
|
|
130
|
+
implementation: {
|
|
131
|
+
generateConfig: () => generateGiteaManifest(descriptor)
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function createVerdaccioImpl(input: VerdaccioDoksDescriptorInput) {
|
|
137
|
+
const descriptor = VerdaccioDoksDescriptorSchema.parse(input);
|
|
138
|
+
return createRuntimeImplementation({
|
|
139
|
+
id: 'verdaccio-doks',
|
|
140
|
+
kind: 'infra/package-registry',
|
|
141
|
+
priority: 20,
|
|
142
|
+
implementation: {
|
|
143
|
+
generateConfig: () => generateVerdaccioManifest(descriptor)
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
createDeploymentImpl,
|
|
150
|
+
createQueueStatefulSetImpl,
|
|
151
|
+
createDatabaseStatefulSetImpl,
|
|
152
|
+
createManagedPostgresImpl,
|
|
153
|
+
createManagedRedisImpl,
|
|
154
|
+
createSpacesImpl,
|
|
155
|
+
createGiteaImpl,
|
|
156
|
+
createVerdaccioImpl
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export default createRuntimePlugin({
|
|
160
|
+
id: 'infra-doks',
|
|
161
|
+
name: 'DigitalOcean Kubernetes Infrastructure',
|
|
162
|
+
dependencies: ['infra'],
|
|
163
|
+
|
|
164
|
+
onActivate: async (runtime) => {
|
|
165
|
+
// infra/worker: DOKS Deployment
|
|
166
|
+
runtime.registerLoader(
|
|
167
|
+
'infra/worker',
|
|
168
|
+
'doks-deployment',
|
|
169
|
+
async () => {
|
|
170
|
+
return createDeploymentImpl({
|
|
171
|
+
kind: 'infra/worker',
|
|
172
|
+
id: 'default-worker',
|
|
173
|
+
entrypoint: 'src/worker.ts',
|
|
174
|
+
queues: [],
|
|
175
|
+
concurrency: 1,
|
|
176
|
+
config: {
|
|
177
|
+
adapter: 'doks-deployment'
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
{ priority: 20, origin: 'infra-doks' }
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// infra/queue: DOKS StatefulSet for Kafka/Redpanda
|
|
185
|
+
runtime.registerLoader(
|
|
186
|
+
'infra/queue',
|
|
187
|
+
'doks-statefulset',
|
|
188
|
+
async () => {
|
|
189
|
+
return createQueueStatefulSetImpl({
|
|
190
|
+
kind: 'infra/queue',
|
|
191
|
+
id: 'default-queue',
|
|
192
|
+
engine: 'kafka',
|
|
193
|
+
config: {
|
|
194
|
+
adapter: 'doks-statefulset'
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
},
|
|
198
|
+
{ priority: 20, origin: 'infra-doks' }
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// infra/database: DOKS StatefulSet for self-hosted Postgres
|
|
202
|
+
runtime.registerLoader(
|
|
203
|
+
'infra/database',
|
|
204
|
+
'doks-statefulset',
|
|
205
|
+
async () => {
|
|
206
|
+
return createDatabaseStatefulSetImpl({
|
|
207
|
+
kind: 'infra/database',
|
|
208
|
+
id: 'default-db',
|
|
209
|
+
engine: 'postgres',
|
|
210
|
+
replicas: 0,
|
|
211
|
+
config: {
|
|
212
|
+
adapter: 'doks-statefulset'
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
{ priority: 20, origin: 'infra-doks' }
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// infra/database: DO Managed Postgres (higher priority than StatefulSet)
|
|
220
|
+
runtime.registerLoader(
|
|
221
|
+
'infra/database',
|
|
222
|
+
'do-managed-postgres',
|
|
223
|
+
async () => {
|
|
224
|
+
return createManagedPostgresImpl({
|
|
225
|
+
kind: 'infra/database',
|
|
226
|
+
id: 'default-managed-db',
|
|
227
|
+
engine: 'postgres',
|
|
228
|
+
replicas: 0,
|
|
229
|
+
config: {
|
|
230
|
+
adapter: 'do-managed-postgres'
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
{ priority: 30, origin: 'infra-doks' }
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// infra/cache: DO Managed Redis
|
|
238
|
+
runtime.registerLoader(
|
|
239
|
+
'infra/cache',
|
|
240
|
+
'do-managed-redis',
|
|
241
|
+
async () => {
|
|
242
|
+
return createManagedRedisImpl({
|
|
243
|
+
kind: 'infra/cache',
|
|
244
|
+
id: 'default-redis',
|
|
245
|
+
engine: 'redis',
|
|
246
|
+
config: {
|
|
247
|
+
adapter: 'do-managed-redis'
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
{ priority: 20, origin: 'infra-doks' }
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// infra/object-storage: DO Spaces
|
|
255
|
+
runtime.registerLoader(
|
|
256
|
+
'infra/object-storage',
|
|
257
|
+
'do-spaces',
|
|
258
|
+
async () => {
|
|
259
|
+
return createSpacesImpl({
|
|
260
|
+
kind: 'infra/object-storage',
|
|
261
|
+
id: 'default-spaces',
|
|
262
|
+
publicAccess: false,
|
|
263
|
+
config: {
|
|
264
|
+
adapter: 'do-spaces'
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
},
|
|
268
|
+
{ priority: 20, origin: 'infra-doks' }
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// infra/git-hosting: Gitea on DOKS
|
|
272
|
+
runtime.registerLoader(
|
|
273
|
+
'infra/git-hosting',
|
|
274
|
+
'gitea-doks',
|
|
275
|
+
async () => {
|
|
276
|
+
return createGiteaImpl({
|
|
277
|
+
kind: 'infra/git-hosting',
|
|
278
|
+
id: 'gitea',
|
|
279
|
+
url: 'https://git.vibes.dev',
|
|
280
|
+
hostname: 'git.vibes.dev',
|
|
281
|
+
type: 'gitea',
|
|
282
|
+
storagePath: '/data',
|
|
283
|
+
authMethod: 'token',
|
|
284
|
+
ssl: true,
|
|
285
|
+
config: {
|
|
286
|
+
adapter: 'gitea-doks',
|
|
287
|
+
dropletSize: 's-2vcpu-2gb',
|
|
288
|
+
dockerImage: 'gitea/gitea:1.21',
|
|
289
|
+
volumeSizeGb: 50,
|
|
290
|
+
domain: 'git.vibes.dev',
|
|
291
|
+
sshPort: 22,
|
|
292
|
+
httpPort: 3000
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
},
|
|
296
|
+
{ priority: 20, origin: 'infra-doks' }
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// infra/sandbox: DigitalOcean droplet provisioner
|
|
300
|
+
runtime.registerLoader(
|
|
301
|
+
'infra/sandbox',
|
|
302
|
+
'digitalocean-droplet',
|
|
303
|
+
async () =>
|
|
304
|
+
createRuntimeImplementation({
|
|
305
|
+
id: 'digitalocean-droplet',
|
|
306
|
+
kind: 'infra/sandbox',
|
|
307
|
+
priority: 20,
|
|
308
|
+
implementation: createDoDropletImpl()
|
|
309
|
+
}),
|
|
310
|
+
{ priority: 20, origin: 'infra-doks' }
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// infra/package-registry: Verdaccio on DOKS
|
|
314
|
+
runtime.registerLoader(
|
|
315
|
+
'infra/package-registry',
|
|
316
|
+
'verdaccio-doks',
|
|
317
|
+
async () => {
|
|
318
|
+
return createVerdaccioImpl({
|
|
319
|
+
kind: 'infra/package-registry',
|
|
320
|
+
id: 'verdaccio',
|
|
321
|
+
url: 'https://packages.vibes.dev',
|
|
322
|
+
registryType: 'npm',
|
|
323
|
+
scope: '@vibesdotdev',
|
|
324
|
+
upstreamUrl: 'https://registry.npmjs.org',
|
|
325
|
+
authRequired: true,
|
|
326
|
+
ssl: true,
|
|
327
|
+
config: {
|
|
328
|
+
adapter: 'verdaccio-doks',
|
|
329
|
+
dockerImage: 'verdaccio/verdaccio:5',
|
|
330
|
+
storageSize: '10Gi',
|
|
331
|
+
domain: 'packages.vibes.dev',
|
|
332
|
+
upstreamNpmUrl: 'https://registry.npmjs.org',
|
|
333
|
+
authEnabled: true
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
},
|
|
337
|
+
{ priority: 20, origin: 'infra-doks' }
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
// Canonical DigitalOcean App Platform Deploy client (`runtime/client` kind).
|
|
341
|
+
// Consumers resolve via `getDigitalOceanAppDeployClient()` or
|
|
342
|
+
// `getVibesClient<DigitalOceanAppDeployClient>('digitalocean-app-deploy')`.
|
|
343
|
+
runtime.registerDescriptor('runtime/client', {
|
|
344
|
+
id: 'digitalocean-app-deploy',
|
|
345
|
+
kind: 'runtime/client',
|
|
346
|
+
description:
|
|
347
|
+
'DigitalOcean App Platform deploy client — creates/updates DO Apps from AppDeployment manifests.'
|
|
348
|
+
});
|
|
349
|
+
runtime.registerLoader('runtime/client', 'digitalocean-app-deploy', async () => {
|
|
350
|
+
const { DigitalOceanAppDeployClient } = await import(
|
|
351
|
+
'./client/digitalocean-app-deploy.client'
|
|
352
|
+
);
|
|
353
|
+
return { impl: DigitalOceanAppDeployClient };
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// infra/artifact: K8s Deployment generator
|
|
357
|
+
runtime.registerDescriptor('infra/artifact', {
|
|
358
|
+
id: 'k8s-deployment',
|
|
359
|
+
kind: 'infra/artifact',
|
|
360
|
+
description: 'Generates Kubernetes Deployment YAML from infra/worker descriptors'
|
|
361
|
+
});
|
|
362
|
+
runtime.registerLoader('infra/artifact', 'k8s-deployment', async () => {
|
|
363
|
+
return createRuntimeImplementation({
|
|
364
|
+
id: 'k8s-deployment',
|
|
365
|
+
kind: 'infra/artifact',
|
|
366
|
+
priority: 20,
|
|
367
|
+
implementation: {
|
|
368
|
+
async generate(context?: any) {
|
|
369
|
+
const resources = context?.resources || [];
|
|
370
|
+
const outputDir = context?.outputDir || './infra/generated';
|
|
371
|
+
const workerDescriptors = resources.filter((r: any) => r.kind === 'infra/worker');
|
|
372
|
+
|
|
373
|
+
if (workerDescriptors.length === 0) {
|
|
374
|
+
return {
|
|
375
|
+
content: '# No infra/worker descriptors found\n',
|
|
376
|
+
filename: 'k8s-deployment.yaml',
|
|
377
|
+
path: resolve(outputDir, 'kubernetes', 'k8s-deployment.yaml'),
|
|
378
|
+
metadata: { sourceKind: 'infra/worker', format: 'yaml', generator: 'k8s-deployment' }
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const artifacts: string[] = [];
|
|
383
|
+
for (const descriptor of workerDescriptors) {
|
|
384
|
+
const parsed = DoksDeploymentDescriptorSchema.parse(descriptor);
|
|
385
|
+
const deployment: K8sDeployment = generateK8sDeployment(parsed);
|
|
386
|
+
const yamlContent = stringify(deployment);
|
|
387
|
+
artifacts.push(`---\n# Resource: ${descriptor.id}\n${yamlContent}`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
content: artifacts.join('\n'),
|
|
392
|
+
filename: 'k8s-deployment.yaml',
|
|
393
|
+
path: resolve(outputDir, 'kubernetes', 'k8s-deployment.yaml'),
|
|
394
|
+
metadata: { sourceKind: 'infra/worker', format: 'yaml', generator: 'k8s-deployment' }
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implementation: infra/worker -> kubernetes (doks-deployment)
|
|
3
|
+
*
|
|
4
|
+
* Generates a K8s Deployment manifest for worker descriptors.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as z from 'zod/v4';
|
|
8
|
+
import { DoksDeploymentWorkerConfigSchema } from '@vibesdotdev/infra-core/kinds';
|
|
9
|
+
import type { K8sDeployment } from '../types.ts';
|
|
10
|
+
|
|
11
|
+
export const DoksDeploymentDescriptorSchema = z.object({
|
|
12
|
+
kind: z.literal('infra/worker'),
|
|
13
|
+
id: z.string().min(1),
|
|
14
|
+
name: z.string().optional(),
|
|
15
|
+
description: z.string().optional(),
|
|
16
|
+
/** Path to worker entrypoint */
|
|
17
|
+
entrypoint: z.string().min(1),
|
|
18
|
+
/** Queue names to consume from */
|
|
19
|
+
queues: z.array(z.string().min(1)).default([]),
|
|
20
|
+
/** Worker concurrency */
|
|
21
|
+
concurrency: z.number().int().positive().default(1),
|
|
22
|
+
/** Scaling configuration */
|
|
23
|
+
scaling: z
|
|
24
|
+
.object({
|
|
25
|
+
min: z.number().int().nonnegative().default(1),
|
|
26
|
+
max: z.number().int().positive().default(1),
|
|
27
|
+
cpu: z.string().optional(),
|
|
28
|
+
memory: z.string().optional()
|
|
29
|
+
})
|
|
30
|
+
.optional(),
|
|
31
|
+
config: DoksDeploymentWorkerConfigSchema.extend({
|
|
32
|
+
adapter: z.literal('doks-deployment')
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export type DoksDeploymentDescriptorInput = z.input<typeof DoksDeploymentDescriptorSchema>;
|
|
37
|
+
export type DoksDeploymentDescriptor = z.infer<typeof DoksDeploymentDescriptorSchema>;
|
|
38
|
+
|
|
39
|
+
export function generateK8sDeployment(descriptor: DoksDeploymentDescriptor): K8sDeployment {
|
|
40
|
+
const parsed = DoksDeploymentDescriptorSchema.parse(descriptor);
|
|
41
|
+
const ns = parsed.config?.namespace ?? 'vibes';
|
|
42
|
+
const image = parsed.config?.image ?? `vibes/${parsed.id}:latest`;
|
|
43
|
+
const healthPort = parsed.config?.healthPort ?? 8080;
|
|
44
|
+
const healthPath = parsed.config?.healthPath ?? '/healthz';
|
|
45
|
+
const readyPath = parsed.config?.readyPath ?? '/readyz';
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
apiVersion: 'apps/v1',
|
|
49
|
+
kind: 'Deployment',
|
|
50
|
+
metadata: {
|
|
51
|
+
name: `vibes-${parsed.id}`,
|
|
52
|
+
namespace: ns,
|
|
53
|
+
labels: { app: parsed.id, 'app.kubernetes.io/part-of': 'vibes' }
|
|
54
|
+
},
|
|
55
|
+
spec: {
|
|
56
|
+
replicas: parsed.scaling?.min ?? 1,
|
|
57
|
+
selector: { matchLabels: { app: parsed.id } },
|
|
58
|
+
template: {
|
|
59
|
+
metadata: {
|
|
60
|
+
labels: { app: parsed.id }
|
|
61
|
+
},
|
|
62
|
+
spec: {
|
|
63
|
+
containers: [
|
|
64
|
+
{
|
|
65
|
+
name: parsed.id,
|
|
66
|
+
image,
|
|
67
|
+
command: ['bun', 'run', parsed.entrypoint],
|
|
68
|
+
resources: {
|
|
69
|
+
requests: {
|
|
70
|
+
cpu: parsed.scaling?.cpu ?? '250m',
|
|
71
|
+
memory: parsed.scaling?.memory ?? '256Mi'
|
|
72
|
+
},
|
|
73
|
+
limits: {
|
|
74
|
+
cpu: parsed.scaling?.cpu ?? '1',
|
|
75
|
+
memory: parsed.scaling?.memory ?? '512Mi'
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
envFrom: [{ configMapRef: { name: `vibes-${parsed.id}-config` } }],
|
|
79
|
+
livenessProbe: {
|
|
80
|
+
httpGet: { path: healthPath, port: healthPort },
|
|
81
|
+
periodSeconds: 30
|
|
82
|
+
},
|
|
83
|
+
readinessProbe: {
|
|
84
|
+
httpGet: { path: readyPath, port: healthPort },
|
|
85
|
+
periodSeconds: 10
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implementation: infra/sandbox → digitalocean-droplet
|
|
3
|
+
*
|
|
4
|
+
* Wires the existing `DigitalOceanProvider` (./cloud/digitalocean.ts) into
|
|
5
|
+
* the runtime kind resolution path. The resolved impl exposes a
|
|
6
|
+
* `provision(descriptor)` method that creates a DigitalOcean droplet via
|
|
7
|
+
* the v2 API, waits for `status: 'active'`, and returns the platform-side
|
|
8
|
+
* `SandboxProvisionResult` shape that
|
|
9
|
+
* `apps/platform-web/src/lib/hosts/server/queries/host-provision.ts`
|
|
10
|
+
* consumes.
|
|
11
|
+
*
|
|
12
|
+
* The DO API token resolves in this order:
|
|
13
|
+
* 1. `descriptor.config.apiToken` (explicit override; tests use this).
|
|
14
|
+
* 2. `process.env.DIGITALOCEAN_API_TOKEN` (the existing dev fallback,
|
|
15
|
+
* matching the `DigitalOceanAppDeployClient` in `./client/`).
|
|
16
|
+
* 3. `packages/secrets` `cloud-credentials.digitalocean.token` —
|
|
17
|
+
* brokerage path, currently DEFERRED to the migration debt entry
|
|
18
|
+
* in `infra-deploy/SPEC.md`. When that lands, this impl resolves
|
|
19
|
+
* the same secret per actor scope.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import * as z from 'zod/v4';
|
|
23
|
+
import { DigitalOceanDropletSandboxConfigSchema } from '@vibesdotdev/infra-core/kinds';
|
|
24
|
+
import { DigitalOceanProvider } from '../cloud/digitalocean.ts';
|
|
25
|
+
import type { CloudInstance } from '../types.ts';
|
|
26
|
+
|
|
27
|
+
export const DoDropletConfigSchema = DigitalOceanDropletSandboxConfigSchema.extend({
|
|
28
|
+
adapter: z.literal('digitalocean-droplet'),
|
|
29
|
+
region: z.string().default('nyc3'),
|
|
30
|
+
image: z.string().default('ubuntu-24-04-x64'),
|
|
31
|
+
tags: z.array(z.string()).default([]),
|
|
32
|
+
sshKeyIds: z.array(z.string()).default([]),
|
|
33
|
+
apiToken: z.string().optional()
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export const DoDropletDescriptorSchema = z.object({
|
|
37
|
+
kind: z.literal('infra/sandbox'),
|
|
38
|
+
id: z.string().min(1),
|
|
39
|
+
name: z.string().optional(),
|
|
40
|
+
description: z.string().optional(),
|
|
41
|
+
config: DoDropletConfigSchema,
|
|
42
|
+
cloudInitScript: z.string().optional()
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export type DoDropletDescriptorInput = z.input<typeof DoDropletDescriptorSchema>;
|
|
46
|
+
export type DoDropletDescriptor = z.infer<typeof DoDropletDescriptorSchema>;
|
|
47
|
+
|
|
48
|
+
export interface SandboxProvisionResult {
|
|
49
|
+
instanceId: string;
|
|
50
|
+
publicIp?: string;
|
|
51
|
+
privateIp?: string;
|
|
52
|
+
region: string;
|
|
53
|
+
createdAt: Date;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
import type { InstanceFilters } from '../types.ts';
|
|
57
|
+
|
|
58
|
+
export type ProviderFactory = (
|
|
59
|
+
apiToken: string,
|
|
60
|
+
defaultRegion: string
|
|
61
|
+
) => {
|
|
62
|
+
createInstance(config: {
|
|
63
|
+
name: string;
|
|
64
|
+
type: string;
|
|
65
|
+
region: string;
|
|
66
|
+
image: string;
|
|
67
|
+
sshKeys?: string[];
|
|
68
|
+
userData?: string;
|
|
69
|
+
tags: Record<string, string>;
|
|
70
|
+
}): Promise<CloudInstance>;
|
|
71
|
+
listInstances(filters?: InstanceFilters): Promise<CloudInstance[]>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function resolveApiToken(config: z.infer<typeof DoDropletConfigSchema>): string {
|
|
75
|
+
if (config.apiToken) return config.apiToken;
|
|
76
|
+
const envToken =
|
|
77
|
+
(typeof process !== 'undefined' && process.env?.DIGITALOCEAN_API_TOKEN) || '';
|
|
78
|
+
if (envToken) return envToken;
|
|
79
|
+
throw new Error(
|
|
80
|
+
'digitalocean-droplet provision failed: no API token. Pass config.apiToken or set DIGITALOCEAN_API_TOKEN.'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function tagsArrayToRecord(tags: string[]): Record<string, string> {
|
|
85
|
+
const record: Record<string, string> = {};
|
|
86
|
+
for (const tag of tags) record[tag] = '';
|
|
87
|
+
return record;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* `DoDropletSandbox` is a singleton-friendly impl: a single instance is
|
|
92
|
+
* registered per worker and serves every `provision(descriptor)` call.
|
|
93
|
+
* Per-call descriptors carry their own API token + region, so multi-tenant
|
|
94
|
+
* use is safe — the impl holds no per-tenant state.
|
|
95
|
+
*
|
|
96
|
+
* For unit tests, pass `providerFactory` to swap in a mock
|
|
97
|
+
* `DigitalOceanProvider`.
|
|
98
|
+
*/
|
|
99
|
+
export class DoDropletSandbox {
|
|
100
|
+
private readonly providerFactory: ProviderFactory;
|
|
101
|
+
|
|
102
|
+
constructor(
|
|
103
|
+
providerFactory: ProviderFactory = (token, region) => new DigitalOceanProvider(token, region)
|
|
104
|
+
) {
|
|
105
|
+
this.providerFactory = providerFactory;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async provision(descriptorInput: DoDropletDescriptorInput): Promise<SandboxProvisionResult> {
|
|
109
|
+
const descriptor = DoDropletDescriptorSchema.parse(descriptorInput);
|
|
110
|
+
const cfg = descriptor.config;
|
|
111
|
+
const provider = this.providerFactory(resolveApiToken(cfg), cfg.region);
|
|
112
|
+
const resourceTag = `vibes:resourceId=${descriptor.id}`;
|
|
113
|
+
|
|
114
|
+
// Idempotency: list existing droplets carrying our resource tag.
|
|
115
|
+
// Returning early prevents double-create on retry / restart while
|
|
116
|
+
// the upstream DB row still tracks the provisioning intent (per
|
|
117
|
+
// SPEC §Hard rule "Droplet creation is idempotent against the
|
|
118
|
+
// descriptor id").
|
|
119
|
+
const existing = await provider.listInstances({ tags: { vibes: resourceTag.replace('vibes:', '') } });
|
|
120
|
+
if (existing.length > 0) {
|
|
121
|
+
const instance = existing[0];
|
|
122
|
+
return {
|
|
123
|
+
instanceId: instance.id,
|
|
124
|
+
publicIp: instance.publicIp,
|
|
125
|
+
privateIp: instance.privateIp,
|
|
126
|
+
region: instance.region,
|
|
127
|
+
createdAt: instance.createdAt
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Inject the resource tag onto user-supplied tags. Duplicates are
|
|
132
|
+
// harmless — Record dedupes by key.
|
|
133
|
+
const tags = tagsArrayToRecord([...cfg.tags, resourceTag]);
|
|
134
|
+
const instance = await provider.createInstance({
|
|
135
|
+
name: descriptor.name ?? `vibes-${descriptor.id}`,
|
|
136
|
+
type: cfg.dropletSize,
|
|
137
|
+
region: cfg.region,
|
|
138
|
+
image: cfg.image,
|
|
139
|
+
sshKeys: cfg.sshKeyIds,
|
|
140
|
+
userData: descriptor.cloudInitScript,
|
|
141
|
+
tags
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
instanceId: instance.id,
|
|
145
|
+
publicIp: instance.publicIp,
|
|
146
|
+
privateIp: instance.privateIp,
|
|
147
|
+
region: instance.region,
|
|
148
|
+
createdAt: instance.createdAt
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function createDoDropletImpl(
|
|
154
|
+
providerFactory?: ProviderFactory
|
|
155
|
+
): DoDropletSandbox {
|
|
156
|
+
return new DoDropletSandbox(providerFactory);
|
|
157
|
+
}
|