@stacksolo/plugin-gcp-kernel 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/LICENSE +21 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +253 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
- package/service/Dockerfile +23 -0
- package/service/package-lock.json +3231 -0
- package/service/package.json +26 -0
- package/service/src/index.ts +77 -0
- package/service/src/routes/auth.ts +51 -0
- package/service/src/routes/events.ts +148 -0
- package/service/src/routes/files.ts +230 -0
- package/service/src/routes/health.ts +22 -0
- package/service/src/services/firebase.ts +67 -0
- package/service/src/services/pubsub.ts +373 -0
- package/service/src/services/storage.ts +204 -0
- package/service/tsconfig.json +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 MonkeyBarrels
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Plugin } from '@stacksolo/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GCP Kernel Plugin Types
|
|
5
|
+
*/
|
|
6
|
+
interface GcpKernelConfig {
|
|
7
|
+
name: string;
|
|
8
|
+
memory?: string;
|
|
9
|
+
cpu?: string;
|
|
10
|
+
minInstances?: number;
|
|
11
|
+
maxInstances?: number;
|
|
12
|
+
firebaseProjectId: string;
|
|
13
|
+
storageBucket: string;
|
|
14
|
+
eventRetentionDays?: number;
|
|
15
|
+
}
|
|
16
|
+
interface GcpKernelOutputs {
|
|
17
|
+
url: string;
|
|
18
|
+
serviceAccount: string;
|
|
19
|
+
eventsTopic: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* StackSolo GCP Kernel Plugin
|
|
24
|
+
*
|
|
25
|
+
* GCP-native kernel implementation using Cloud Run + Pub/Sub.
|
|
26
|
+
* This is the serverless alternative to the NATS-based kernel plugin.
|
|
27
|
+
*
|
|
28
|
+
* Endpoints:
|
|
29
|
+
* - GET /health - Health check
|
|
30
|
+
* - POST /auth/validate - Validate Firebase token
|
|
31
|
+
* - POST /files/* - File operations (upload-url, download-url, list, delete, move, metadata)
|
|
32
|
+
* - POST /events/publish - Publish event to Pub/Sub
|
|
33
|
+
* - POST /events/subscribe - Register HTTP push subscription
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
declare const plugin: Plugin;
|
|
37
|
+
|
|
38
|
+
export { type GcpKernelConfig, type GcpKernelOutputs, plugin as default, plugin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// src/resources/gcp-kernel.ts
|
|
2
|
+
import { defineResource } from "@stacksolo/core";
|
|
3
|
+
function toVariableName(name) {
|
|
4
|
+
return name.replace(/[^a-zA-Z0-9]/g, "_").replace(/^(\d)/, "_$1");
|
|
5
|
+
}
|
|
6
|
+
var gcpKernelResource = defineResource({
|
|
7
|
+
id: "gcp-kernel:gcp_kernel",
|
|
8
|
+
provider: "gcp-kernel",
|
|
9
|
+
name: "GCP Kernel",
|
|
10
|
+
description: "Serverless kernel using Cloud Run + Pub/Sub (alternative to NATS-based K8s kernel)",
|
|
11
|
+
icon: "cpu",
|
|
12
|
+
configSchema: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
name: {
|
|
16
|
+
type: "string",
|
|
17
|
+
title: "Name",
|
|
18
|
+
description: "Resource name for references (@gcp-kernel/<name>)",
|
|
19
|
+
default: "kernel"
|
|
20
|
+
},
|
|
21
|
+
location: {
|
|
22
|
+
type: "string",
|
|
23
|
+
title: "Region",
|
|
24
|
+
description: "GCP region (defaults to project region)"
|
|
25
|
+
},
|
|
26
|
+
cpu: {
|
|
27
|
+
type: "string",
|
|
28
|
+
title: "CPU",
|
|
29
|
+
description: "CPU allocation",
|
|
30
|
+
default: "1",
|
|
31
|
+
enum: ["1", "2", "4"]
|
|
32
|
+
},
|
|
33
|
+
memory: {
|
|
34
|
+
type: "string",
|
|
35
|
+
title: "Memory",
|
|
36
|
+
description: "Memory allocation",
|
|
37
|
+
default: "512Mi",
|
|
38
|
+
enum: ["256Mi", "512Mi", "1Gi", "2Gi"]
|
|
39
|
+
},
|
|
40
|
+
minInstances: {
|
|
41
|
+
type: "number",
|
|
42
|
+
title: "Min Instances",
|
|
43
|
+
description: "Minimum instances (0 for scale-to-zero)",
|
|
44
|
+
default: 0
|
|
45
|
+
},
|
|
46
|
+
maxInstances: {
|
|
47
|
+
type: "number",
|
|
48
|
+
title: "Max Instances",
|
|
49
|
+
description: "Maximum instances",
|
|
50
|
+
default: 10
|
|
51
|
+
},
|
|
52
|
+
firebaseProjectId: {
|
|
53
|
+
type: "string",
|
|
54
|
+
title: "Firebase Project ID",
|
|
55
|
+
description: "Firebase project for auth token validation"
|
|
56
|
+
},
|
|
57
|
+
storageBucket: {
|
|
58
|
+
type: "string",
|
|
59
|
+
title: "Storage Bucket",
|
|
60
|
+
description: "GCS bucket for file uploads"
|
|
61
|
+
},
|
|
62
|
+
eventRetentionDays: {
|
|
63
|
+
type: "number",
|
|
64
|
+
title: "Event Retention Days",
|
|
65
|
+
description: "How long to retain events in Pub/Sub",
|
|
66
|
+
default: 7
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
required: ["firebaseProjectId", "storageBucket"]
|
|
70
|
+
},
|
|
71
|
+
defaultConfig: {
|
|
72
|
+
name: "kernel",
|
|
73
|
+
cpu: "1",
|
|
74
|
+
memory: "512Mi",
|
|
75
|
+
minInstances: 0,
|
|
76
|
+
maxInstances: 10,
|
|
77
|
+
eventRetentionDays: 7
|
|
78
|
+
},
|
|
79
|
+
generate: (config) => {
|
|
80
|
+
const varName = toVariableName(config.name);
|
|
81
|
+
const name = config.name || "kernel";
|
|
82
|
+
const location = config.location || "${var.region}";
|
|
83
|
+
const cpu = config.cpu || "1";
|
|
84
|
+
const memory = config.memory || "512Mi";
|
|
85
|
+
const minInstances = config.minInstances ?? 0;
|
|
86
|
+
const maxInstances = config.maxInstances ?? 10;
|
|
87
|
+
const firebaseProjectId = config.firebaseProjectId;
|
|
88
|
+
const storageBucket = config.storageBucket;
|
|
89
|
+
const eventRetentionDays = config.eventRetentionDays ?? 7;
|
|
90
|
+
const projectId = config.projectId || "${var.project_id}";
|
|
91
|
+
const messageRetentionSeconds = eventRetentionDays * 24 * 60 * 60;
|
|
92
|
+
const code = `// =============================================================================
|
|
93
|
+
// GCP Kernel - Serverless kernel using Cloud Run + Pub/Sub
|
|
94
|
+
// =============================================================================
|
|
95
|
+
|
|
96
|
+
// Service account for the GCP kernel
|
|
97
|
+
const ${varName}Sa = new ServiceAccount(this, '${name}-sa', {
|
|
98
|
+
accountId: '${name}-gcp-kernel',
|
|
99
|
+
displayName: 'GCP Kernel Service Account',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Grant storage access for files service
|
|
103
|
+
new ProjectIamMember(this, '${name}-storage-iam', {
|
|
104
|
+
project: '${projectId}',
|
|
105
|
+
role: 'roles/storage.objectAdmin',
|
|
106
|
+
member: \`serviceAccount:\${${varName}Sa.email}\`,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Grant Pub/Sub access for events service
|
|
110
|
+
new ProjectIamMember(this, '${name}-pubsub-iam', {
|
|
111
|
+
project: '${projectId}',
|
|
112
|
+
role: 'roles/pubsub.editor',
|
|
113
|
+
member: \`serviceAccount:\${${varName}Sa.email}\`,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// =============================================================================
|
|
117
|
+
// Pub/Sub Topics for Events
|
|
118
|
+
// =============================================================================
|
|
119
|
+
|
|
120
|
+
// Main events topic
|
|
121
|
+
const ${varName}EventsTopic = new PubsubTopic(this, '${name}-events-topic', {
|
|
122
|
+
name: 'stacksolo-${name}-events',
|
|
123
|
+
messageRetentionDuration: '${messageRetentionSeconds}s',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Dead letter topic for failed message delivery
|
|
127
|
+
const ${varName}DlqTopic = new PubsubTopic(this, '${name}-dlq-topic', {
|
|
128
|
+
name: 'stacksolo-${name}-events-dlq',
|
|
129
|
+
messageRetentionDuration: '${messageRetentionSeconds * 2}s',
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// =============================================================================
|
|
133
|
+
// Cloud Run Service
|
|
134
|
+
// =============================================================================
|
|
135
|
+
|
|
136
|
+
const ${varName}Service = new CloudRunV2Service(this, '${name}', {
|
|
137
|
+
name: '${name}-gcp-kernel',
|
|
138
|
+
location: '${location}',
|
|
139
|
+
ingress: 'INGRESS_TRAFFIC_ALL',
|
|
140
|
+
|
|
141
|
+
template: {
|
|
142
|
+
serviceAccount: ${varName}Sa.email,
|
|
143
|
+
containers: [{
|
|
144
|
+
image: 'gcr.io/${projectId}/stacksolo-gcp-kernel:latest',
|
|
145
|
+
ports: { containerPort: 8080 },
|
|
146
|
+
resources: {
|
|
147
|
+
limits: {
|
|
148
|
+
cpu: '${cpu}',
|
|
149
|
+
memory: '${memory}',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
env: [
|
|
153
|
+
{ name: 'PORT', value: '8080' },
|
|
154
|
+
{ name: 'GCP_PROJECT_ID', value: '${projectId}' },
|
|
155
|
+
{ name: 'FIREBASE_PROJECT_ID', value: '${firebaseProjectId}' },
|
|
156
|
+
{ name: 'GCS_BUCKET', value: '${storageBucket}' },
|
|
157
|
+
{ name: 'PUBSUB_EVENTS_TOPIC', value: \`\${${varName}EventsTopic.name}\` },
|
|
158
|
+
{ name: 'PUBSUB_DLQ_TOPIC', value: \`\${${varName}DlqTopic.name}\` },
|
|
159
|
+
{ name: 'KERNEL_TYPE', value: 'gcp' },
|
|
160
|
+
],
|
|
161
|
+
}],
|
|
162
|
+
scaling: {
|
|
163
|
+
minInstanceCount: ${minInstances},
|
|
164
|
+
maxInstanceCount: ${maxInstances},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Allow unauthenticated access (kernel validates tokens internally)
|
|
170
|
+
new CloudRunV2ServiceIamMember(this, '${name}-public', {
|
|
171
|
+
name: ${varName}Service.name,
|
|
172
|
+
location: ${varName}Service.location,
|
|
173
|
+
role: 'roles/run.invoker',
|
|
174
|
+
member: 'allUsers',
|
|
175
|
+
});`;
|
|
176
|
+
return {
|
|
177
|
+
imports: [
|
|
178
|
+
"import { ServiceAccount } from '@cdktf/provider-google/lib/service-account';",
|
|
179
|
+
"import { ProjectIamMember } from '@cdktf/provider-google/lib/project-iam-member';",
|
|
180
|
+
"import { CloudRunV2Service } from '@cdktf/provider-google/lib/cloud-run-v2-service';",
|
|
181
|
+
"import { CloudRunV2ServiceIamMember } from '@cdktf/provider-google/lib/cloud-run-v2-service-iam-member';",
|
|
182
|
+
"import { PubsubTopic } from '@cdktf/provider-google/lib/pubsub-topic';"
|
|
183
|
+
],
|
|
184
|
+
code,
|
|
185
|
+
outputs: [
|
|
186
|
+
`export const ${varName}Url = ${varName}Service.uri;`,
|
|
187
|
+
`export const ${varName}AuthUrl = \`\${${varName}Service.uri}/auth/validate\`;`,
|
|
188
|
+
`export const ${varName}FilesUrl = \`\${${varName}Service.uri}/files\`;`,
|
|
189
|
+
`export const ${varName}EventsUrl = \`\${${varName}Service.uri}/events\`;`,
|
|
190
|
+
`export const ${varName}EventsTopic = ${varName}EventsTopic.name;`
|
|
191
|
+
]
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
estimateCost: (config) => {
|
|
195
|
+
const minInstances = config.minInstances ?? 0;
|
|
196
|
+
const cpu = parseFloat(config.cpu || "1");
|
|
197
|
+
const memory = parseFloat((config.memory || "512Mi").replace("Mi", "")) / 1024 || 0.5;
|
|
198
|
+
let cloudRunCost = 0;
|
|
199
|
+
if (minInstances > 0) {
|
|
200
|
+
const hoursPerMonth = 730;
|
|
201
|
+
const cpuCost = minInstances * cpu * hoursPerMonth * 24e-6 * 3600;
|
|
202
|
+
const memoryCost = minInstances * memory * hoursPerMonth * 25e-7 * 3600;
|
|
203
|
+
cloudRunCost = cpuCost + memoryCost;
|
|
204
|
+
} else {
|
|
205
|
+
const estimatedSeconds = 1e5 * 0.2;
|
|
206
|
+
cloudRunCost = estimatedSeconds * cpu * 24e-6 + estimatedSeconds * memory * 25e-7;
|
|
207
|
+
}
|
|
208
|
+
const pubsubCost = 0.04;
|
|
209
|
+
return {
|
|
210
|
+
monthly: Math.round(cloudRunCost + pubsubCost),
|
|
211
|
+
currency: "USD",
|
|
212
|
+
breakdown: [
|
|
213
|
+
{ item: `Cloud Run (${minInstances > 0 ? "always-on" : "scale-to-zero"})`, amount: Math.round(cloudRunCost) },
|
|
214
|
+
{ item: "Pub/Sub (~100k messages)", amount: Math.round(pubsubCost * 100) / 100 }
|
|
215
|
+
]
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// src/index.ts
|
|
221
|
+
var VERSION = "0.1.0";
|
|
222
|
+
var plugin = {
|
|
223
|
+
name: "@stacksolo/plugin-gcp-kernel",
|
|
224
|
+
version: VERSION,
|
|
225
|
+
resources: [gcpKernelResource],
|
|
226
|
+
services: [
|
|
227
|
+
{
|
|
228
|
+
name: "gcp-kernel",
|
|
229
|
+
image: `ghcr.io/monkeybarrels/stacksolo-gcp-kernel:${VERSION}`,
|
|
230
|
+
sourcePath: "./service",
|
|
231
|
+
ports: {
|
|
232
|
+
http: 8080
|
|
233
|
+
},
|
|
234
|
+
env: {
|
|
235
|
+
PORT: "8080",
|
|
236
|
+
GCP_PROJECT_ID: "",
|
|
237
|
+
FIREBASE_PROJECT_ID: "",
|
|
238
|
+
GCS_BUCKET: "",
|
|
239
|
+
PUBSUB_EVENTS_TOPIC: ""
|
|
240
|
+
},
|
|
241
|
+
resources: {
|
|
242
|
+
cpu: "1",
|
|
243
|
+
memory: "512Mi"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
};
|
|
248
|
+
var index_default = plugin;
|
|
249
|
+
export {
|
|
250
|
+
index_default as default,
|
|
251
|
+
plugin
|
|
252
|
+
};
|
|
253
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stacksolo/plugin-gcp-kernel",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "GCP-native kernel plugin for StackSolo (Cloud Run + Pub/Sub)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"service"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@cdktf/provider-google": "^14.29.0",
|
|
20
|
+
"cdktf": "^0.20.11",
|
|
21
|
+
"@stacksolo/core": "0.1.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.11.0",
|
|
25
|
+
"tsup": "^8.5.1",
|
|
26
|
+
"typescript": "^5.7.3"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@stacksolo/core": ">=0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"stacksolo",
|
|
33
|
+
"plugin",
|
|
34
|
+
"gcp",
|
|
35
|
+
"kernel",
|
|
36
|
+
"cloud-run",
|
|
37
|
+
"pubsub"
|
|
38
|
+
],
|
|
39
|
+
"author": "MonkeyBarrels",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/monkeybarrels/stacksolo.git",
|
|
44
|
+
"directory": "plugins/gcp-kernel"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"dev": "tsup --watch",
|
|
49
|
+
"lint": "eslint src/",
|
|
50
|
+
"typecheck": "tsc --noEmit"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
FROM node:20-alpine
|
|
2
|
+
|
|
3
|
+
# Create app directory
|
|
4
|
+
WORKDIR /app
|
|
5
|
+
|
|
6
|
+
# Copy package files
|
|
7
|
+
COPY package*.json ./
|
|
8
|
+
|
|
9
|
+
# Install production dependencies
|
|
10
|
+
RUN npm ci --only=production
|
|
11
|
+
|
|
12
|
+
# Copy compiled source
|
|
13
|
+
COPY dist/ ./dist/
|
|
14
|
+
|
|
15
|
+
# Expose port (Cloud Run only uses 8080)
|
|
16
|
+
EXPOSE 8080
|
|
17
|
+
|
|
18
|
+
# Health check
|
|
19
|
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
|
20
|
+
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
|
|
21
|
+
|
|
22
|
+
# Start service
|
|
23
|
+
CMD ["node", "dist/index.js"]
|