@stacksolo/plugin-gcp-kernel 0.1.0 → 0.1.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 +508 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
# @stacksolo/plugin-gcp-kernel
|
|
2
|
+
|
|
3
|
+
A GCP-native kernel plugin for StackSolo that provides authentication, file storage, and event publishing using fully serverless GCP services.
|
|
4
|
+
|
|
5
|
+
## What is the GCP Kernel?
|
|
6
|
+
|
|
7
|
+
The GCP kernel is a shared infrastructure service that handles common operations for your apps. Unlike the regular kernel (which uses NATS), the GCP kernel uses native GCP services:
|
|
8
|
+
|
|
9
|
+
| Feature | GCP Kernel | Regular Kernel |
|
|
10
|
+
|---------|-----------|----------------|
|
|
11
|
+
| **Transport** | HTTP | HTTP + NATS |
|
|
12
|
+
| **Events** | Cloud Pub/Sub | NATS JetStream |
|
|
13
|
+
| **Deployment** | Cloud Run | Cloud Run + NATS |
|
|
14
|
+
| **Scaling** | Auto-scales to 0 | Needs min 1 instance |
|
|
15
|
+
| **Cost** | Pay-per-use | ~$44/mo always-on |
|
|
16
|
+
|
|
17
|
+
**The GCP kernel provides:**
|
|
18
|
+
|
|
19
|
+
1. **Auth** - Validates Firebase tokens (so you know who's logged in)
|
|
20
|
+
2. **Files** - Generates secure upload/download URLs for Google Cloud Storage
|
|
21
|
+
3. **Events** - Publishes events to Cloud Pub/Sub
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
### Step 1: Add GCP kernel to your config
|
|
28
|
+
|
|
29
|
+
In your `stacksolo.config.json`:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"project": {
|
|
34
|
+
"name": "my-app",
|
|
35
|
+
"gcpProjectId": "my-gcp-project",
|
|
36
|
+
"region": "us-central1",
|
|
37
|
+
"plugins": [
|
|
38
|
+
"@stacksolo/plugin-gcp-cdktf",
|
|
39
|
+
"@stacksolo/plugin-gcp-kernel"
|
|
40
|
+
],
|
|
41
|
+
|
|
42
|
+
"buckets": [
|
|
43
|
+
{ "name": "uploads" }
|
|
44
|
+
],
|
|
45
|
+
|
|
46
|
+
"gcpKernel": {
|
|
47
|
+
"name": "kernel",
|
|
48
|
+
"firebaseProjectId": "my-gcp-project",
|
|
49
|
+
"storageBucket": "uploads"
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
"networks": [{
|
|
53
|
+
"name": "default",
|
|
54
|
+
"containers": [{
|
|
55
|
+
"name": "api",
|
|
56
|
+
"env": {
|
|
57
|
+
"KERNEL_URL": "@gcp-kernel/kernel.url",
|
|
58
|
+
"KERNEL_TYPE": "gcp"
|
|
59
|
+
}
|
|
60
|
+
}]
|
|
61
|
+
}]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Step 2: Deploy
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
stacksolo deploy
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Step 3: Use in your code
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// Validate a user's token
|
|
76
|
+
const response = await fetch(`${process.env.KERNEL_URL}/auth/validate`, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: { 'Content-Type': 'application/json' },
|
|
79
|
+
body: JSON.stringify({ token: userFirebaseToken }),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const { valid, uid, email } = await response.json();
|
|
83
|
+
|
|
84
|
+
if (valid) {
|
|
85
|
+
console.log(`User ${uid} is logged in!`);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Configuration Reference
|
|
92
|
+
|
|
93
|
+
Add this to your `stacksolo.config.json` under `project`:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"gcpKernel": {
|
|
98
|
+
"name": "kernel",
|
|
99
|
+
"firebaseProjectId": "your-firebase-project-id",
|
|
100
|
+
"storageBucket": "your-gcs-bucket-name"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Required Fields
|
|
106
|
+
|
|
107
|
+
| Field | What it does | Example |
|
|
108
|
+
|-------|--------------|---------|
|
|
109
|
+
| `firebaseProjectId` | The Firebase project used for user authentication | `"my-app-prod"` |
|
|
110
|
+
| `storageBucket` | The GCS bucket for file uploads/downloads | `"my-app-uploads"` |
|
|
111
|
+
|
|
112
|
+
### Optional Fields
|
|
113
|
+
|
|
114
|
+
| Field | Default | What it does |
|
|
115
|
+
|-------|---------|--------------|
|
|
116
|
+
| `name` | `"gcp-kernel"` | Name used in references like `@gcp-kernel/kernel` |
|
|
117
|
+
| `minInstances` | `0` | Minimum instances (0 = scale to zero) |
|
|
118
|
+
| `memory` | `"512Mi"` | Memory for the service |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## How to Reference the GCP Kernel
|
|
123
|
+
|
|
124
|
+
In your container or function config, use these references:
|
|
125
|
+
|
|
126
|
+
| Reference | What you get | Example value |
|
|
127
|
+
|-----------|--------------|---------------|
|
|
128
|
+
| `@gcp-kernel/kernel.url` | Base URL of the kernel | `https://kernel-abc123.run.app` |
|
|
129
|
+
|
|
130
|
+
**Example:**
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"containers": [{
|
|
135
|
+
"name": "api",
|
|
136
|
+
"env": {
|
|
137
|
+
"KERNEL_URL": "@gcp-kernel/kernel.url",
|
|
138
|
+
"KERNEL_TYPE": "gcp"
|
|
139
|
+
}
|
|
140
|
+
}]
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Using the Auth Service
|
|
147
|
+
|
|
148
|
+
The auth service validates Firebase ID tokens via HTTP.
|
|
149
|
+
|
|
150
|
+
### Code Example: Express Middleware
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// middleware/auth.ts
|
|
154
|
+
|
|
155
|
+
export async function requireAuth(req, res, next) {
|
|
156
|
+
const authHeader = req.headers.authorization;
|
|
157
|
+
|
|
158
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
159
|
+
return res.status(401).json({ error: 'No token provided' });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const token = authHeader.replace('Bearer ', '');
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const response = await fetch(`${process.env.KERNEL_URL}/auth/validate`, {
|
|
166
|
+
method: 'POST',
|
|
167
|
+
headers: { 'Content-Type': 'application/json' },
|
|
168
|
+
body: JSON.stringify({ token }),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const data = await response.json();
|
|
172
|
+
|
|
173
|
+
if (!data.valid) {
|
|
174
|
+
return res.status(401).json({ error: data.error });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
req.user = {
|
|
178
|
+
uid: data.uid,
|
|
179
|
+
email: data.email,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
next();
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error('Auth validation failed:', error);
|
|
185
|
+
return res.status(500).json({ error: 'Auth service unavailable' });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Auth API Reference
|
|
191
|
+
|
|
192
|
+
**Endpoint:** `POST /auth/validate`
|
|
193
|
+
|
|
194
|
+
**Request:**
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6..."
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Success Response (200):**
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"valid": true,
|
|
205
|
+
"uid": "abc123",
|
|
206
|
+
"email": "user@example.com",
|
|
207
|
+
"claims": { ... }
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Error Response (401):**
|
|
212
|
+
```json
|
|
213
|
+
{
|
|
214
|
+
"valid": false,
|
|
215
|
+
"error": "Token has expired",
|
|
216
|
+
"code": "TOKEN_EXPIRED"
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Using the Files Service
|
|
223
|
+
|
|
224
|
+
The files service provides HTTP endpoints for file operations.
|
|
225
|
+
|
|
226
|
+
### Code Example: File Upload
|
|
227
|
+
|
|
228
|
+
**Backend:**
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
app.post('/api/files/upload-url', requireAuth, async (req, res) => {
|
|
232
|
+
const { filename, contentType } = req.body;
|
|
233
|
+
const path = `users/${req.user.uid}/uploads/${Date.now()}-${filename}`;
|
|
234
|
+
|
|
235
|
+
const response = await fetch(`${process.env.KERNEL_URL}/files/upload-url`, {
|
|
236
|
+
method: 'POST',
|
|
237
|
+
headers: { 'Content-Type': 'application/json' },
|
|
238
|
+
body: JSON.stringify({ path, contentType }),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const result = await response.json();
|
|
242
|
+
res.json(result);
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Frontend:**
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
async function uploadFile(file: File, firebaseToken: string) {
|
|
250
|
+
// Get signed upload URL
|
|
251
|
+
const urlResponse = await fetch('/api/files/upload-url', {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: {
|
|
254
|
+
'Authorization': `Bearer ${firebaseToken}`,
|
|
255
|
+
'Content-Type': 'application/json',
|
|
256
|
+
},
|
|
257
|
+
body: JSON.stringify({
|
|
258
|
+
filename: file.name,
|
|
259
|
+
contentType: file.type,
|
|
260
|
+
}),
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const { uploadUrl, path } = await urlResponse.json();
|
|
264
|
+
|
|
265
|
+
// Upload directly to GCS
|
|
266
|
+
await fetch(uploadUrl, {
|
|
267
|
+
method: 'PUT',
|
|
268
|
+
headers: { 'Content-Type': file.type },
|
|
269
|
+
body: file,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
return path;
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Files API Reference (HTTP)
|
|
277
|
+
|
|
278
|
+
**POST /files/upload-url**
|
|
279
|
+
|
|
280
|
+
Request:
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"path": "users/123/uploads/photo.jpg",
|
|
284
|
+
"contentType": "image/jpeg"
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Response:
|
|
289
|
+
```json
|
|
290
|
+
{
|
|
291
|
+
"uploadUrl": "https://storage.googleapis.com/...",
|
|
292
|
+
"path": "users/123/uploads/photo.jpg",
|
|
293
|
+
"expiresAt": "2024-01-01T12:00:00.000Z"
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**POST /files/download-url**
|
|
298
|
+
|
|
299
|
+
Request:
|
|
300
|
+
```json
|
|
301
|
+
{
|
|
302
|
+
"path": "users/123/uploads/photo.jpg"
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Response:
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"downloadUrl": "https://storage.googleapis.com/...",
|
|
310
|
+
"path": "users/123/uploads/photo.jpg",
|
|
311
|
+
"expiresAt": "2024-01-01T12:00:00.000Z"
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**POST /files/list**
|
|
316
|
+
|
|
317
|
+
Request:
|
|
318
|
+
```json
|
|
319
|
+
{
|
|
320
|
+
"prefix": "users/123/",
|
|
321
|
+
"maxResults": 100
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Response:
|
|
326
|
+
```json
|
|
327
|
+
{
|
|
328
|
+
"files": [
|
|
329
|
+
{
|
|
330
|
+
"path": "users/123/photo.jpg",
|
|
331
|
+
"size": 12345,
|
|
332
|
+
"contentType": "image/jpeg",
|
|
333
|
+
"created": "2024-01-01T10:00:00.000Z",
|
|
334
|
+
"updated": "2024-01-01T10:00:00.000Z"
|
|
335
|
+
}
|
|
336
|
+
],
|
|
337
|
+
"nextPageToken": null
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**POST /files/delete**
|
|
342
|
+
|
|
343
|
+
Request:
|
|
344
|
+
```json
|
|
345
|
+
{
|
|
346
|
+
"path": "users/123/uploads/photo.jpg"
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**POST /files/move**
|
|
351
|
+
|
|
352
|
+
Request:
|
|
353
|
+
```json
|
|
354
|
+
{
|
|
355
|
+
"sourcePath": "users/123/old-photo.jpg",
|
|
356
|
+
"destinationPath": "users/123/new-photo.jpg"
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
**POST /files/metadata**
|
|
361
|
+
|
|
362
|
+
Request:
|
|
363
|
+
```json
|
|
364
|
+
{
|
|
365
|
+
"path": "users/123/uploads/photo.jpg"
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Using the Events Service
|
|
372
|
+
|
|
373
|
+
The events service publishes to Cloud Pub/Sub via HTTP.
|
|
374
|
+
|
|
375
|
+
### Code Example: Publishing Events
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
async function publishEvent(type: string, data: any) {
|
|
379
|
+
await fetch(`${process.env.KERNEL_URL}/events/publish`, {
|
|
380
|
+
method: 'POST',
|
|
381
|
+
headers: { 'Content-Type': 'application/json' },
|
|
382
|
+
body: JSON.stringify({
|
|
383
|
+
type,
|
|
384
|
+
data,
|
|
385
|
+
metadata: {
|
|
386
|
+
source: 'api-service',
|
|
387
|
+
timestamp: new Date().toISOString(),
|
|
388
|
+
},
|
|
389
|
+
}),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Usage
|
|
394
|
+
await publishEvent('user.signed-up', { userId: '123', email: 'user@example.com' });
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Events API Reference
|
|
398
|
+
|
|
399
|
+
**POST /events/publish**
|
|
400
|
+
|
|
401
|
+
Request:
|
|
402
|
+
```json
|
|
403
|
+
{
|
|
404
|
+
"type": "user.signed-up",
|
|
405
|
+
"data": {
|
|
406
|
+
"userId": "123",
|
|
407
|
+
"email": "user@example.com"
|
|
408
|
+
},
|
|
409
|
+
"metadata": {
|
|
410
|
+
"source": "api-service"
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Response:
|
|
416
|
+
```json
|
|
417
|
+
{
|
|
418
|
+
"messageId": "1234567890",
|
|
419
|
+
"published": true
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## GCP Kernel vs Regular Kernel
|
|
426
|
+
|
|
427
|
+
Choose GCP Kernel when:
|
|
428
|
+
- You want to scale to zero
|
|
429
|
+
- You don't need real-time messaging
|
|
430
|
+
- You want lower costs for light usage
|
|
431
|
+
- You prefer fully managed services
|
|
432
|
+
|
|
433
|
+
Choose Regular Kernel when:
|
|
434
|
+
- You need real-time pub/sub (WebSocket-style)
|
|
435
|
+
- You have consistent traffic
|
|
436
|
+
- You need NATS features (request/reply, JetStream)
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Local Development
|
|
441
|
+
|
|
442
|
+
### Running with stacksolo dev
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
stacksolo dev
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
The GCP kernel will be built and run automatically if `gcpKernel` is in your config.
|
|
449
|
+
|
|
450
|
+
### Testing the endpoints
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
# Test health check
|
|
454
|
+
curl http://localhost:8080/health
|
|
455
|
+
|
|
456
|
+
# Test auth (requires Firebase token)
|
|
457
|
+
curl -X POST http://localhost:8080/auth/validate \
|
|
458
|
+
-H "Content-Type: application/json" \
|
|
459
|
+
-d '{"token":"your-firebase-token"}'
|
|
460
|
+
|
|
461
|
+
# Test file upload URL
|
|
462
|
+
curl -X POST http://localhost:8080/files/upload-url \
|
|
463
|
+
-H "Content-Type: application/json" \
|
|
464
|
+
-d '{"path":"test.txt","contentType":"text/plain"}'
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## Troubleshooting
|
|
470
|
+
|
|
471
|
+
### "Auth service unavailable"
|
|
472
|
+
|
|
473
|
+
**Problem:** Your app can't reach the kernel.
|
|
474
|
+
|
|
475
|
+
**Solution:**
|
|
476
|
+
1. Make sure `KERNEL_URL` is set correctly
|
|
477
|
+
2. Check if the kernel service is running: `curl $KERNEL_URL/health`
|
|
478
|
+
|
|
479
|
+
### "TOKEN_EXPIRED"
|
|
480
|
+
|
|
481
|
+
**Problem:** The Firebase token has expired.
|
|
482
|
+
|
|
483
|
+
**Solution:** Refresh the token in your frontend:
|
|
484
|
+
```typescript
|
|
485
|
+
const token = await firebase.auth().currentUser.getIdToken(true);
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### File upload fails
|
|
489
|
+
|
|
490
|
+
**Common causes:**
|
|
491
|
+
1. **Wrong content type:** The `contentType` in your request must match the file
|
|
492
|
+
2. **URL expired:** Signed URLs expire after 1 hour
|
|
493
|
+
3. **Invalid path:** Paths can't start with `/` or contain `..`
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Summary
|
|
498
|
+
|
|
499
|
+
| What | How to use |
|
|
500
|
+
|------|------------|
|
|
501
|
+
| **Validate a user** | POST to `KERNEL_URL/auth/validate` |
|
|
502
|
+
| **Get upload URL** | POST to `KERNEL_URL/files/upload-url` |
|
|
503
|
+
| **Get download URL** | POST to `KERNEL_URL/files/download-url` |
|
|
504
|
+
| **List files** | POST to `KERNEL_URL/files/list` |
|
|
505
|
+
| **Delete file** | POST to `KERNEL_URL/files/delete` |
|
|
506
|
+
| **Move file** | POST to `KERNEL_URL/files/move` |
|
|
507
|
+
| **Get metadata** | POST to `KERNEL_URL/files/metadata` |
|
|
508
|
+
| **Publish event** | POST to `KERNEL_URL/events/publish` |
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/resources/gcp-kernel.ts","../src/index.ts"],"sourcesContent":["import { defineResource, type ResourceConfig } from '@stacksolo/core';\n\nfunction toVariableName(name: string): string {\n return name.replace(/[^a-zA-Z0-9]/g, '_').replace(/^(\\d)/, '_$1');\n}\n\n/**\n * GCP Kernel Resource\n *\n * A fully GCP-native kernel implementation using:\n * - Cloud Run for HTTP endpoints (auth, files, events)\n * - Cloud Pub/Sub for event messaging (replaces NATS/JetStream)\n * - Cloud Storage for file operations\n * - Firebase Admin SDK for token validation\n *\n * This is the serverless alternative to the K8s kernel which uses embedded NATS.\n */\nexport const gcpKernelResource = defineResource({\n id: 'gcp-kernel:gcp_kernel',\n provider: 'gcp-kernel',\n name: 'GCP Kernel',\n description: 'Serverless kernel using Cloud Run + Pub/Sub (alternative to NATS-based K8s kernel)',\n icon: 'cpu',\n\n configSchema: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n title: 'Name',\n description: 'Resource name for references (@gcp-kernel/<name>)',\n default: 'kernel',\n },\n location: {\n type: 'string',\n title: 'Region',\n description: 'GCP region (defaults to project region)',\n },\n cpu: {\n type: 'string',\n title: 'CPU',\n description: 'CPU allocation',\n default: '1',\n enum: ['1', '2', '4'],\n },\n memory: {\n type: 'string',\n title: 'Memory',\n description: 'Memory allocation',\n default: '512Mi',\n enum: ['256Mi', '512Mi', '1Gi', '2Gi'],\n },\n minInstances: {\n type: 'number',\n title: 'Min Instances',\n description: 'Minimum instances (0 for scale-to-zero)',\n default: 0,\n },\n maxInstances: {\n type: 'number',\n title: 'Max Instances',\n description: 'Maximum instances',\n default: 10,\n },\n firebaseProjectId: {\n type: 'string',\n title: 'Firebase Project ID',\n description: 'Firebase project for auth token validation',\n },\n storageBucket: {\n type: 'string',\n title: 'Storage Bucket',\n description: 'GCS bucket for file uploads',\n },\n eventRetentionDays: {\n type: 'number',\n title: 'Event Retention Days',\n description: 'How long to retain events in Pub/Sub',\n default: 7,\n },\n },\n required: ['firebaseProjectId', 'storageBucket'],\n },\n\n defaultConfig: {\n name: 'kernel',\n cpu: '1',\n memory: '512Mi',\n minInstances: 0,\n maxInstances: 10,\n eventRetentionDays: 7,\n },\n\n generate: (config: ResourceConfig) => {\n const varName = toVariableName(config.name as string);\n const name = (config.name as string) || 'kernel';\n const location = (config.location as string) || '${var.region}';\n const cpu = (config.cpu as string) || '1';\n const memory = (config.memory as string) || '512Mi';\n const minInstances = (config.minInstances as number) ?? 0;\n const maxInstances = (config.maxInstances as number) ?? 10;\n const firebaseProjectId = config.firebaseProjectId as string;\n const storageBucket = config.storageBucket as string;\n const eventRetentionDays = (config.eventRetentionDays as number) ?? 7;\n const projectId = (config.projectId as string) || '${var.project_id}';\n\n // Convert retention days to seconds\n const messageRetentionSeconds = eventRetentionDays * 24 * 60 * 60;\n\n const code = `// =============================================================================\n// GCP Kernel - Serverless kernel using Cloud Run + Pub/Sub\n// =============================================================================\n\n// Service account for the GCP kernel\nconst ${varName}Sa = new ServiceAccount(this, '${name}-sa', {\n accountId: '${name}-gcp-kernel',\n displayName: 'GCP Kernel Service Account',\n});\n\n// Grant storage access for files service\nnew ProjectIamMember(this, '${name}-storage-iam', {\n project: '${projectId}',\n role: 'roles/storage.objectAdmin',\n member: \\`serviceAccount:\\${${varName}Sa.email}\\`,\n});\n\n// Grant Pub/Sub access for events service\nnew ProjectIamMember(this, '${name}-pubsub-iam', {\n project: '${projectId}',\n role: 'roles/pubsub.editor',\n member: \\`serviceAccount:\\${${varName}Sa.email}\\`,\n});\n\n// =============================================================================\n// Pub/Sub Topics for Events\n// =============================================================================\n\n// Main events topic\nconst ${varName}EventsTopic = new PubsubTopic(this, '${name}-events-topic', {\n name: 'stacksolo-${name}-events',\n messageRetentionDuration: '${messageRetentionSeconds}s',\n});\n\n// Dead letter topic for failed message delivery\nconst ${varName}DlqTopic = new PubsubTopic(this, '${name}-dlq-topic', {\n name: 'stacksolo-${name}-events-dlq',\n messageRetentionDuration: '${messageRetentionSeconds * 2}s',\n});\n\n// =============================================================================\n// Cloud Run Service\n// =============================================================================\n\nconst ${varName}Service = new CloudRunV2Service(this, '${name}', {\n name: '${name}-gcp-kernel',\n location: '${location}',\n ingress: 'INGRESS_TRAFFIC_ALL',\n\n template: {\n serviceAccount: ${varName}Sa.email,\n containers: [{\n image: 'gcr.io/${projectId}/stacksolo-gcp-kernel:latest',\n ports: { containerPort: 8080 },\n resources: {\n limits: {\n cpu: '${cpu}',\n memory: '${memory}',\n },\n },\n env: [\n { name: 'PORT', value: '8080' },\n { name: 'GCP_PROJECT_ID', value: '${projectId}' },\n { name: 'FIREBASE_PROJECT_ID', value: '${firebaseProjectId}' },\n { name: 'GCS_BUCKET', value: '${storageBucket}' },\n { name: 'PUBSUB_EVENTS_TOPIC', value: \\`\\${${varName}EventsTopic.name}\\` },\n { name: 'PUBSUB_DLQ_TOPIC', value: \\`\\${${varName}DlqTopic.name}\\` },\n { name: 'KERNEL_TYPE', value: 'gcp' },\n ],\n }],\n scaling: {\n minInstanceCount: ${minInstances},\n maxInstanceCount: ${maxInstances},\n },\n },\n});\n\n// Allow unauthenticated access (kernel validates tokens internally)\nnew CloudRunV2ServiceIamMember(this, '${name}-public', {\n name: ${varName}Service.name,\n location: ${varName}Service.location,\n role: 'roles/run.invoker',\n member: 'allUsers',\n});`;\n\n return {\n imports: [\n \"import { ServiceAccount } from '@cdktf/provider-google/lib/service-account';\",\n \"import { ProjectIamMember } from '@cdktf/provider-google/lib/project-iam-member';\",\n \"import { CloudRunV2Service } from '@cdktf/provider-google/lib/cloud-run-v2-service';\",\n \"import { CloudRunV2ServiceIamMember } from '@cdktf/provider-google/lib/cloud-run-v2-service-iam-member';\",\n \"import { PubsubTopic } from '@cdktf/provider-google/lib/pubsub-topic';\",\n ],\n code,\n outputs: [\n `export const ${varName}Url = ${varName}Service.uri;`,\n `export const ${varName}AuthUrl = \\`\\${${varName}Service.uri}/auth/validate\\`;`,\n `export const ${varName}FilesUrl = \\`\\${${varName}Service.uri}/files\\`;`,\n `export const ${varName}EventsUrl = \\`\\${${varName}Service.uri}/events\\`;`,\n `export const ${varName}EventsTopic = ${varName}EventsTopic.name;`,\n ],\n };\n },\n\n estimateCost: (config: ResourceConfig) => {\n const minInstances = (config.minInstances as number) ?? 0;\n const cpu = parseFloat((config.cpu as string) || '1');\n const memory = parseFloat(((config.memory as string) || '512Mi').replace('Mi', '')) / 1024 || 0.5;\n\n // Cloud Run pricing\n let cloudRunCost = 0;\n if (minInstances > 0) {\n // Always-on instances\n const hoursPerMonth = 730;\n const cpuCost = minInstances * cpu * hoursPerMonth * 0.00002400 * 3600;\n const memoryCost = minInstances * memory * hoursPerMonth * 0.00000250 * 3600;\n cloudRunCost = cpuCost + memoryCost;\n } else {\n // Pay-per-use estimate (assuming 100k requests/month @ 200ms avg)\n const estimatedSeconds = 100000 * 0.2;\n cloudRunCost = estimatedSeconds * cpu * 0.00002400 + estimatedSeconds * memory * 0.00000250;\n }\n\n // Pub/Sub pricing (~$0.40/million messages, assume 100k messages/month)\n const pubsubCost = 0.04;\n\n return {\n monthly: Math.round(cloudRunCost + pubsubCost),\n currency: 'USD',\n breakdown: [\n { item: `Cloud Run (${minInstances > 0 ? 'always-on' : 'scale-to-zero'})`, amount: Math.round(cloudRunCost) },\n { item: 'Pub/Sub (~100k messages)', amount: Math.round(pubsubCost * 100) / 100 },\n ],\n };\n },\n});\n","/**\n * StackSolo GCP Kernel Plugin\n *\n * GCP-native kernel implementation using Cloud Run + Pub/Sub.\n * This is the serverless alternative to the NATS-based kernel plugin.\n *\n * Endpoints:\n * - GET /health - Health check\n * - POST /auth/validate - Validate Firebase token\n * - POST /files/* - File operations (upload-url, download-url, list, delete, move, metadata)\n * - POST /events/publish - Publish event to Pub/Sub\n * - POST /events/subscribe - Register HTTP push subscription\n */\n\nimport type { Plugin } from '@stacksolo/core';\nimport { gcpKernelResource } from './resources/index';\n\n/** Plugin version - must match package.json */\nconst VERSION = '0.1.0';\n\nexport const plugin: Plugin = {\n name: '@stacksolo/plugin-gcp-kernel',\n version: VERSION,\n resources: [gcpKernelResource],\n services: [\n {\n name: 'gcp-kernel',\n image: `ghcr.io/monkeybarrels/stacksolo-gcp-kernel:${VERSION}`,\n sourcePath: './service',\n ports: {\n http: 8080,\n },\n env: {\n PORT: '8080',\n GCP_PROJECT_ID: '',\n FIREBASE_PROJECT_ID: '',\n GCS_BUCKET: '',\n PUBSUB_EVENTS_TOPIC: '',\n },\n resources: {\n cpu: '1',\n memory: '512Mi',\n },\n },\n ],\n};\n\nexport default plugin;\n\n// Re-export types\nexport type { GcpKernelConfig, GcpKernelOutputs } from './types';\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/resources/gcp-kernel.ts","../src/index.ts"],"sourcesContent":["import { defineResource, type ResourceConfig } from '@stacksolo/core';\n\nfunction toVariableName(name: string): string {\n return name.replace(/[^a-zA-Z0-9]/g, '_').replace(/^(\\d)/, '_$1');\n}\n\n/**\n * GCP Kernel Resource\n *\n * A fully GCP-native kernel implementation using:\n * - Cloud Run for HTTP endpoints (auth, files, events)\n * - Cloud Pub/Sub for event messaging (replaces NATS/JetStream)\n * - Cloud Storage for file operations\n * - Firebase Admin SDK for token validation\n *\n * This is the serverless alternative to the K8s kernel which uses embedded NATS.\n */\nexport const gcpKernelResource = defineResource({\n id: 'gcp-kernel:gcp_kernel',\n provider: 'gcp-kernel',\n name: 'GCP Kernel',\n description: 'Serverless kernel using Cloud Run + Pub/Sub (alternative to NATS-based K8s kernel)',\n icon: 'cpu',\n\n configSchema: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n title: 'Name',\n description: 'Resource name for references (@gcp-kernel/<name>)',\n default: 'kernel',\n },\n location: {\n type: 'string',\n title: 'Region',\n description: 'GCP region (defaults to project region)',\n },\n cpu: {\n type: 'string',\n title: 'CPU',\n description: 'CPU allocation',\n default: '1',\n enum: ['1', '2', '4'],\n },\n memory: {\n type: 'string',\n title: 'Memory',\n description: 'Memory allocation',\n default: '512Mi',\n enum: ['256Mi', '512Mi', '1Gi', '2Gi'],\n },\n minInstances: {\n type: 'number',\n title: 'Min Instances',\n description: 'Minimum instances (0 for scale-to-zero)',\n default: 0,\n },\n maxInstances: {\n type: 'number',\n title: 'Max Instances',\n description: 'Maximum instances',\n default: 10,\n },\n firebaseProjectId: {\n type: 'string',\n title: 'Firebase Project ID',\n description: 'Firebase project for auth token validation',\n },\n storageBucket: {\n type: 'string',\n title: 'Storage Bucket',\n description: 'GCS bucket for file uploads',\n },\n eventRetentionDays: {\n type: 'number',\n title: 'Event Retention Days',\n description: 'How long to retain events in Pub/Sub',\n default: 7,\n },\n },\n required: ['firebaseProjectId', 'storageBucket'],\n },\n\n defaultConfig: {\n name: 'kernel',\n cpu: '1',\n memory: '512Mi',\n minInstances: 0,\n maxInstances: 10,\n eventRetentionDays: 7,\n },\n\n generate: (config: ResourceConfig) => {\n const varName = toVariableName(config.name as string);\n const name = (config.name as string) || 'kernel';\n const location = (config.location as string) || '${var.region}';\n const cpu = (config.cpu as string) || '1';\n const memory = (config.memory as string) || '512Mi';\n const minInstances = (config.minInstances as number) ?? 0;\n const maxInstances = (config.maxInstances as number) ?? 10;\n const firebaseProjectId = config.firebaseProjectId as string;\n const storageBucket = config.storageBucket as string;\n const eventRetentionDays = (config.eventRetentionDays as number) ?? 7;\n const projectId = (config.projectId as string) || '${var.project_id}';\n\n // Convert retention days to seconds\n const messageRetentionSeconds = eventRetentionDays * 24 * 60 * 60;\n\n const code = `// =============================================================================\n// GCP Kernel - Serverless kernel using Cloud Run + Pub/Sub\n// =============================================================================\n\n// Service account for the GCP kernel\nconst ${varName}Sa = new ServiceAccount(this, '${name}-sa', {\n accountId: '${name}-gcp-kernel',\n displayName: 'GCP Kernel Service Account',\n});\n\n// Grant storage access for files service\nnew ProjectIamMember(this, '${name}-storage-iam', {\n project: '${projectId}',\n role: 'roles/storage.objectAdmin',\n member: \\`serviceAccount:\\${${varName}Sa.email}\\`,\n});\n\n// Grant Pub/Sub access for events service\nnew ProjectIamMember(this, '${name}-pubsub-iam', {\n project: '${projectId}',\n role: 'roles/pubsub.editor',\n member: \\`serviceAccount:\\${${varName}Sa.email}\\`,\n});\n\n// =============================================================================\n// Pub/Sub Topics for Events\n// =============================================================================\n\n// Main events topic\nconst ${varName}EventsTopic = new PubsubTopic(this, '${name}-events-topic', {\n name: 'stacksolo-${name}-events',\n messageRetentionDuration: '${messageRetentionSeconds}s',\n});\n\n// Dead letter topic for failed message delivery\nconst ${varName}DlqTopic = new PubsubTopic(this, '${name}-dlq-topic', {\n name: 'stacksolo-${name}-events-dlq',\n messageRetentionDuration: '${messageRetentionSeconds * 2}s',\n});\n\n// =============================================================================\n// Cloud Run Service\n// =============================================================================\n\nconst ${varName}Service = new CloudRunV2Service(this, '${name}', {\n name: '${name}-gcp-kernel',\n location: '${location}',\n ingress: 'INGRESS_TRAFFIC_ALL',\n\n template: {\n serviceAccount: ${varName}Sa.email,\n containers: [{\n image: 'gcr.io/${projectId}/stacksolo-gcp-kernel:latest',\n ports: { containerPort: 8080 },\n resources: {\n limits: {\n cpu: '${cpu}',\n memory: '${memory}',\n },\n },\n env: [\n { name: 'PORT', value: '8080' },\n { name: 'GCP_PROJECT_ID', value: '${projectId}' },\n { name: 'FIREBASE_PROJECT_ID', value: '${firebaseProjectId}' },\n { name: 'GCS_BUCKET', value: '${storageBucket}' },\n { name: 'PUBSUB_EVENTS_TOPIC', value: \\`\\${${varName}EventsTopic.name}\\` },\n { name: 'PUBSUB_DLQ_TOPIC', value: \\`\\${${varName}DlqTopic.name}\\` },\n { name: 'KERNEL_TYPE', value: 'gcp' },\n ],\n }],\n scaling: {\n minInstanceCount: ${minInstances},\n maxInstanceCount: ${maxInstances},\n },\n },\n});\n\n// Allow unauthenticated access (kernel validates tokens internally)\nnew CloudRunV2ServiceIamMember(this, '${name}-public', {\n name: ${varName}Service.name,\n location: ${varName}Service.location,\n role: 'roles/run.invoker',\n member: 'allUsers',\n});`;\n\n return {\n imports: [\n \"import { ServiceAccount } from '@cdktf/provider-google/lib/service-account';\",\n \"import { ProjectIamMember } from '@cdktf/provider-google/lib/project-iam-member';\",\n \"import { CloudRunV2Service } from '@cdktf/provider-google/lib/cloud-run-v2-service';\",\n \"import { CloudRunV2ServiceIamMember } from '@cdktf/provider-google/lib/cloud-run-v2-service-iam-member';\",\n \"import { PubsubTopic } from '@cdktf/provider-google/lib/pubsub-topic';\",\n ],\n code,\n outputs: [\n `export const ${varName}Url = ${varName}Service.uri;`,\n `export const ${varName}AuthUrl = \\`\\${${varName}Service.uri}/auth/validate\\`;`,\n `export const ${varName}FilesUrl = \\`\\${${varName}Service.uri}/files\\`;`,\n `export const ${varName}EventsUrl = \\`\\${${varName}Service.uri}/events\\`;`,\n `export const ${varName}EventsTopic = ${varName}EventsTopic.name;`,\n ],\n };\n },\n\n estimateCost: (config: ResourceConfig) => {\n const minInstances = (config.minInstances as number) ?? 0;\n const cpu = parseFloat((config.cpu as string) || '1');\n const memory = parseFloat(((config.memory as string) || '512Mi').replace('Mi', '')) / 1024 || 0.5;\n\n // Cloud Run pricing\n let cloudRunCost = 0;\n if (minInstances > 0) {\n // Always-on instances\n const hoursPerMonth = 730;\n const cpuCost = minInstances * cpu * hoursPerMonth * 0.00002400 * 3600;\n const memoryCost = minInstances * memory * hoursPerMonth * 0.00000250 * 3600;\n cloudRunCost = cpuCost + memoryCost;\n } else {\n // Pay-per-use estimate (assuming 100k requests/month @ 200ms avg)\n const estimatedSeconds = 100000 * 0.2;\n cloudRunCost = estimatedSeconds * cpu * 0.00002400 + estimatedSeconds * memory * 0.00000250;\n }\n\n // Pub/Sub pricing (~$0.40/million messages, assume 100k messages/month)\n const pubsubCost = 0.04;\n\n return {\n monthly: Math.round(cloudRunCost + pubsubCost),\n currency: 'USD',\n breakdown: [\n { item: `Cloud Run (${minInstances > 0 ? 'always-on' : 'scale-to-zero'})`, amount: Math.round(cloudRunCost) },\n { item: 'Pub/Sub (~100k messages)', amount: Math.round(pubsubCost * 100) / 100 },\n ],\n };\n },\n});\n","/**\n * StackSolo GCP Kernel Plugin\n *\n * GCP-native kernel implementation using Cloud Run + Pub/Sub.\n * This is the serverless alternative to the NATS-based kernel plugin.\n *\n * Endpoints:\n * - GET /health - Health check\n * - POST /auth/validate - Validate Firebase token\n * - POST /files/* - File operations (upload-url, download-url, list, delete, move, metadata)\n * - POST /events/publish - Publish event to Pub/Sub\n * - POST /events/subscribe - Register HTTP push subscription\n */\n\nimport type { Plugin } from '@stacksolo/core';\nimport { gcpKernelResource } from './resources/index';\n\n/** Plugin version - must match package.json */\nconst VERSION = '0.1.1';\n\nexport const plugin: Plugin = {\n name: '@stacksolo/plugin-gcp-kernel',\n version: VERSION,\n resources: [gcpKernelResource],\n services: [\n {\n name: 'gcp-kernel',\n image: `ghcr.io/monkeybarrels/stacksolo-gcp-kernel:${VERSION}`,\n sourcePath: './service',\n ports: {\n http: 8080,\n },\n env: {\n PORT: '8080',\n GCP_PROJECT_ID: '',\n FIREBASE_PROJECT_ID: '',\n GCS_BUCKET: '',\n PUBSUB_EVENTS_TOPIC: '',\n },\n resources: {\n cpu: '1',\n memory: '512Mi',\n },\n },\n ],\n};\n\nexport default plugin;\n\n// Re-export types\nexport type { GcpKernelConfig, GcpKernelOutputs } from './types';\n"],"mappings":"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","names":[]}
|