@valon-technologies/gestalt 0.0.1-alpha.12 → 0.0.1-alpha.13
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 +1 -1
- package/gen/v1/agent_pb.ts +1916 -0
- package/gen/v1/authentication_pb.ts +1 -1
- package/gen/v1/authorization_pb.ts +28 -28
- package/gen/v1/cache_pb.ts +4 -4
- package/gen/v1/datastore_pb.ts +10 -10
- package/gen/v1/external_credential_pb.ts +274 -0
- package/gen/v1/plugin_pb.ts +35 -30
- package/gen/v1/pluginruntime_pb.ts +593 -0
- package/gen/v1/runtime_pb.ts +18 -3
- package/gen/v1/s3_pb.ts +19 -19
- package/gen/v1/secrets_pb.ts +1 -1
- package/gen/v1/workflow_pb.ts +248 -82
- package/package.json +1 -1
- package/src/agent-manager.ts +247 -0
- package/src/agent.ts +492 -0
- package/src/authorization.ts +88 -18
- package/src/index.ts +67 -0
- package/src/manifest-metadata.ts +1 -0
- package/src/plugin.ts +85 -4
- package/src/provider-kind.ts +6 -0
- package/src/provider.ts +12 -1
- package/src/runtime.ts +153 -44
- package/src/s3.ts +89 -38
- package/src/workflow-manager.ts +67 -9
package/src/s3.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { connect } from "node:net";
|
|
2
|
+
|
|
1
3
|
import { create } from "@bufbuild/protobuf";
|
|
2
4
|
import { EmptySchema } from "@bufbuild/protobuf/wkt";
|
|
3
5
|
import {
|
|
@@ -5,6 +7,7 @@ import {
|
|
|
5
7
|
ConnectError,
|
|
6
8
|
createClient,
|
|
7
9
|
type Client,
|
|
10
|
+
type Interceptor,
|
|
8
11
|
type ServiceImpl,
|
|
9
12
|
} from "@connectrpc/connect";
|
|
10
13
|
import { createGrpcTransport } from "@connectrpc/connect-node";
|
|
@@ -17,12 +20,19 @@ import {
|
|
|
17
20
|
PresignObjectResponseSchema,
|
|
18
21
|
ReadObjectChunkSchema,
|
|
19
22
|
S3 as S3Service,
|
|
23
|
+
type ByteRange as ProtoByteRange,
|
|
24
|
+
type ReadObjectRequest as ProtoReadObjectRequest,
|
|
25
|
+
type S3ObjectMeta as ProtoS3ObjectMeta,
|
|
26
|
+
type S3ObjectRef as ProtoS3ObjectRef,
|
|
20
27
|
WriteObjectResponseSchema,
|
|
21
28
|
} from "../gen/v1/s3_pb.ts";
|
|
22
29
|
import { errorMessage, type MaybePromise } from "./api.ts";
|
|
23
30
|
import { RuntimeProvider, type RuntimeProviderOptions } from "./provider.ts";
|
|
24
31
|
|
|
25
|
-
const ENV_S3_SOCKET = "GESTALT_S3_SOCKET";
|
|
32
|
+
export const ENV_S3_SOCKET = "GESTALT_S3_SOCKET";
|
|
33
|
+
const S3_SOCKET_TOKEN_SUFFIX = "_TOKEN";
|
|
34
|
+
const S3_RELAY_TOKEN_HEADER = "x-gestalt-host-service-relay-token";
|
|
35
|
+
export const ENV_S3_SOCKET_TOKEN = `${ENV_S3_SOCKET}${S3_SOCKET_TOKEN_SUFFIX}`;
|
|
26
36
|
const WRITE_CHUNK_SIZE = 64 * 1024;
|
|
27
37
|
const textEncoder = new TextEncoder();
|
|
28
38
|
|
|
@@ -35,6 +45,13 @@ export function s3SocketEnv(name?: string): string {
|
|
|
35
45
|
return `${ENV_S3_SOCKET}_${trimmed.replace(/[^A-Za-z0-9]/g, "_").toUpperCase()}`;
|
|
36
46
|
}
|
|
37
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Returns the environment variable name used to discover an S3 relay token.
|
|
50
|
+
*/
|
|
51
|
+
export function s3SocketTokenEnv(name?: string): string {
|
|
52
|
+
return `${s3SocketEnv(name)}${S3_SOCKET_TOKEN_SUFFIX}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
/**
|
|
39
56
|
* Error returned when an object reference does not exist.
|
|
40
57
|
*/
|
|
@@ -496,15 +513,30 @@ export class S3 {
|
|
|
496
513
|
|
|
497
514
|
constructor(name?: string) {
|
|
498
515
|
const envName = s3SocketEnv(name);
|
|
499
|
-
const
|
|
500
|
-
if (!
|
|
516
|
+
const target = process.env[envName];
|
|
517
|
+
if (!target) {
|
|
501
518
|
throw new Error(`${envName} is not set`);
|
|
502
519
|
}
|
|
520
|
+
const relayToken = process.env[s3SocketTokenEnv(name)]?.trim() ?? "";
|
|
521
|
+
const transportOptions = s3TransportOptions(target);
|
|
522
|
+
const interceptors: Interceptor[] = relayToken
|
|
523
|
+
? [
|
|
524
|
+
(next) => async (req) => {
|
|
525
|
+
req.header.set(S3_RELAY_TOKEN_HEADER, relayToken);
|
|
526
|
+
return await next(req);
|
|
527
|
+
},
|
|
528
|
+
]
|
|
529
|
+
: [];
|
|
503
530
|
const transport = createGrpcTransport({
|
|
504
|
-
|
|
505
|
-
nodeOptions
|
|
506
|
-
|
|
507
|
-
|
|
531
|
+
...transportOptions,
|
|
532
|
+
...(transportOptions.nodeOptions
|
|
533
|
+
? {
|
|
534
|
+
nodeOptions: {
|
|
535
|
+
createConnection: () => connect(transportOptions.nodeOptions!.path),
|
|
536
|
+
},
|
|
537
|
+
}
|
|
538
|
+
: {}),
|
|
539
|
+
interceptors,
|
|
508
540
|
});
|
|
509
541
|
this.client = createClient(S3Service, transport);
|
|
510
542
|
}
|
|
@@ -738,13 +770,46 @@ export class S3Object {
|
|
|
738
770
|
}
|
|
739
771
|
}
|
|
740
772
|
|
|
741
|
-
function
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
773
|
+
function s3TransportOptions(rawTarget: string): {
|
|
774
|
+
baseUrl: string;
|
|
775
|
+
nodeOptions?: { path: string };
|
|
776
|
+
} {
|
|
777
|
+
const target = rawTarget.trim();
|
|
778
|
+
if (!target) {
|
|
779
|
+
throw new Error("s3 transport target is required");
|
|
780
|
+
}
|
|
781
|
+
if (target.startsWith("tcp://")) {
|
|
782
|
+
const address = target.slice("tcp://".length).trim();
|
|
783
|
+
if (!address) {
|
|
784
|
+
throw new Error(`s3 tcp target ${JSON.stringify(rawTarget)} is missing host:port`);
|
|
785
|
+
}
|
|
786
|
+
return { baseUrl: `http://${address}` };
|
|
787
|
+
}
|
|
788
|
+
if (target.startsWith("tls://")) {
|
|
789
|
+
const address = target.slice("tls://".length).trim();
|
|
790
|
+
if (!address) {
|
|
791
|
+
throw new Error(`s3 tls target ${JSON.stringify(rawTarget)} is missing host:port`);
|
|
792
|
+
}
|
|
793
|
+
return { baseUrl: `https://${address}` };
|
|
794
|
+
}
|
|
795
|
+
if (target.startsWith("unix://")) {
|
|
796
|
+
const socketPath = target.slice("unix://".length).trim();
|
|
797
|
+
if (!socketPath) {
|
|
798
|
+
throw new Error(`s3 unix target ${JSON.stringify(rawTarget)} is missing a socket path`);
|
|
799
|
+
}
|
|
800
|
+
return {
|
|
801
|
+
baseUrl: "http://localhost",
|
|
802
|
+
nodeOptions: { path: socketPath },
|
|
803
|
+
};
|
|
746
804
|
}
|
|
747
|
-
|
|
805
|
+
if (target.includes("://")) {
|
|
806
|
+
const parsed = new URL(target);
|
|
807
|
+
throw new Error(`Unsupported s3 target scheme ${JSON.stringify(parsed.protocol.replace(/:$/, ""))}`);
|
|
808
|
+
}
|
|
809
|
+
return {
|
|
810
|
+
baseUrl: "http://localhost",
|
|
811
|
+
nodeOptions: { path: target },
|
|
812
|
+
};
|
|
748
813
|
}
|
|
749
814
|
|
|
750
815
|
async function invokeS3Provider<T>(label: string, fn: () => Promise<T>): Promise<T> {
|
|
@@ -974,7 +1039,7 @@ function toProtoObjectRef(ref: ObjectRef) {
|
|
|
974
1039
|
};
|
|
975
1040
|
}
|
|
976
1041
|
|
|
977
|
-
function fromProtoObjectRef(ref:
|
|
1042
|
+
function fromProtoObjectRef(ref: ProtoS3ObjectRef | undefined): ObjectRef {
|
|
978
1043
|
const value: ObjectRef = {
|
|
979
1044
|
bucket: ref?.bucket ?? "",
|
|
980
1045
|
key: ref?.key ?? "",
|
|
@@ -1008,15 +1073,7 @@ function toProtoObjectMeta(meta: ObjectMeta) {
|
|
|
1008
1073
|
return value;
|
|
1009
1074
|
}
|
|
1010
1075
|
|
|
1011
|
-
function fromProtoObjectMeta(meta: {
|
|
1012
|
-
ref?: { bucket?: string; key?: string; versionId?: string };
|
|
1013
|
-
etag?: string;
|
|
1014
|
-
size?: bigint;
|
|
1015
|
-
contentType?: string;
|
|
1016
|
-
lastModified?: { seconds?: bigint; nanos?: number };
|
|
1017
|
-
metadata?: Record<string, string>;
|
|
1018
|
-
storageClass?: string;
|
|
1019
|
-
} | undefined): ObjectMeta {
|
|
1076
|
+
function fromProtoObjectMeta(meta: ProtoS3ObjectMeta | undefined): ObjectMeta {
|
|
1020
1077
|
const value: ObjectMeta = {
|
|
1021
1078
|
ref: fromProtoObjectRef(meta?.ref),
|
|
1022
1079
|
etag: meta?.etag ?? "",
|
|
@@ -1048,27 +1105,21 @@ function toProtoReadOptions(options?: ReadOptions) {
|
|
|
1048
1105
|
return proto;
|
|
1049
1106
|
}
|
|
1050
1107
|
|
|
1051
|
-
function fromProtoReadOptions(request: {
|
|
1052
|
-
range?: { start?: bigint; end?: bigint };
|
|
1053
|
-
ifMatch?: string;
|
|
1054
|
-
ifNoneMatch?: string;
|
|
1055
|
-
ifModifiedSince?: { seconds?: bigint; nanos?: number };
|
|
1056
|
-
ifUnmodifiedSince?: { seconds?: bigint; nanos?: number };
|
|
1057
|
-
}): ReadOptions {
|
|
1108
|
+
function fromProtoReadOptions(request: ProtoReadObjectRequest | undefined): ReadOptions {
|
|
1058
1109
|
const options: ReadOptions = {};
|
|
1059
|
-
if (request
|
|
1110
|
+
if (request?.range) {
|
|
1060
1111
|
options.range = fromProtoByteRange(request.range);
|
|
1061
1112
|
}
|
|
1062
|
-
if (request
|
|
1113
|
+
if (request?.ifMatch) {
|
|
1063
1114
|
options.ifMatch = request.ifMatch;
|
|
1064
1115
|
}
|
|
1065
|
-
if (request
|
|
1116
|
+
if (request?.ifNoneMatch) {
|
|
1066
1117
|
options.ifNoneMatch = request.ifNoneMatch;
|
|
1067
1118
|
}
|
|
1068
|
-
if (request
|
|
1119
|
+
if (request?.ifModifiedSince) {
|
|
1069
1120
|
options.ifModifiedSince = fromProtoTimestamp(request.ifModifiedSince);
|
|
1070
1121
|
}
|
|
1071
|
-
if (request
|
|
1122
|
+
if (request?.ifUnmodifiedSince) {
|
|
1072
1123
|
options.ifUnmodifiedSince = fromProtoTimestamp(request.ifUnmodifiedSince);
|
|
1073
1124
|
}
|
|
1074
1125
|
return options;
|
|
@@ -1123,12 +1174,12 @@ function toProtoByteRange(range: ByteRange) {
|
|
|
1123
1174
|
return proto;
|
|
1124
1175
|
}
|
|
1125
1176
|
|
|
1126
|
-
function fromProtoByteRange(range:
|
|
1177
|
+
function fromProtoByteRange(range: ProtoByteRange | undefined): ByteRange {
|
|
1127
1178
|
const value: ByteRange = {};
|
|
1128
|
-
if (range
|
|
1179
|
+
if (range?.start !== undefined) {
|
|
1129
1180
|
value.start = range.start;
|
|
1130
1181
|
}
|
|
1131
|
-
if (range
|
|
1182
|
+
if (range?.end !== undefined) {
|
|
1132
1183
|
value.end = range.end;
|
|
1133
1184
|
}
|
|
1134
1185
|
return value;
|
package/src/workflow-manager.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { connect } from "node:net";
|
|
2
|
-
|
|
3
1
|
import type { MessageInitShape } from "@bufbuild/protobuf";
|
|
4
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createClient,
|
|
4
|
+
type Client,
|
|
5
|
+
type Interceptor,
|
|
6
|
+
} from "@connectrpc/connect";
|
|
5
7
|
import { createGrpcTransport } from "@connectrpc/connect-node";
|
|
6
8
|
|
|
7
9
|
import {
|
|
@@ -26,6 +28,9 @@ import {
|
|
|
26
28
|
import type { Request } from "./api.ts";
|
|
27
29
|
|
|
28
30
|
export const ENV_WORKFLOW_MANAGER_SOCKET = "GESTALT_WORKFLOW_MANAGER_SOCKET";
|
|
31
|
+
export const ENV_WORKFLOW_MANAGER_SOCKET_TOKEN = `${ENV_WORKFLOW_MANAGER_SOCKET}_TOKEN`;
|
|
32
|
+
const WORKFLOW_MANAGER_RELAY_TOKEN_HEADER =
|
|
33
|
+
"x-gestalt-host-service-relay-token";
|
|
29
34
|
|
|
30
35
|
export type ManagedWorkflowScheduleMessage = ManagedWorkflowSchedule;
|
|
31
36
|
export type ManagedWorkflowEventTriggerMessage = ManagedWorkflowEventTrigger;
|
|
@@ -79,18 +84,20 @@ export class WorkflowManager {
|
|
|
79
84
|
constructor(requestOrToken: Request | string) {
|
|
80
85
|
this.invocationToken = normalizeInvocationToken(requestOrToken);
|
|
81
86
|
|
|
82
|
-
const
|
|
83
|
-
if (!
|
|
87
|
+
const target = process.env[ENV_WORKFLOW_MANAGER_SOCKET];
|
|
88
|
+
if (!target) {
|
|
84
89
|
throw new Error(
|
|
85
90
|
`workflow manager: ${ENV_WORKFLOW_MANAGER_SOCKET} is not set`,
|
|
86
91
|
);
|
|
87
92
|
}
|
|
93
|
+
const relayToken =
|
|
94
|
+
process.env[ENV_WORKFLOW_MANAGER_SOCKET_TOKEN]?.trim() ?? "";
|
|
88
95
|
|
|
89
96
|
const transport = createGrpcTransport({
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
...workflowManagerTransportOptions(target),
|
|
98
|
+
interceptors: relayToken
|
|
99
|
+
? [workflowManagerRelayTokenInterceptor(relayToken)]
|
|
100
|
+
: [],
|
|
94
101
|
});
|
|
95
102
|
this.client = createClient(WorkflowManagerHostService, transport);
|
|
96
103
|
}
|
|
@@ -224,3 +231,54 @@ function normalizeInvocationToken(requestOrToken: Request | string): string {
|
|
|
224
231
|
}
|
|
225
232
|
return trimmed;
|
|
226
233
|
}
|
|
234
|
+
|
|
235
|
+
function workflowManagerTransportOptions(rawTarget: string): {
|
|
236
|
+
baseUrl: string;
|
|
237
|
+
nodeOptions?: { path: string };
|
|
238
|
+
} {
|
|
239
|
+
const target = rawTarget.trim();
|
|
240
|
+
if (!target) {
|
|
241
|
+
throw new Error("workflow manager: transport target is required");
|
|
242
|
+
}
|
|
243
|
+
if (target.startsWith("tcp://")) {
|
|
244
|
+
const address = target.slice("tcp://".length).trim();
|
|
245
|
+
if (!address) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`workflow manager: tcp target ${JSON.stringify(rawTarget)} is missing host:port`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return { baseUrl: `http://${address}` };
|
|
251
|
+
}
|
|
252
|
+
if (target.startsWith("tls://")) {
|
|
253
|
+
const address = target.slice("tls://".length).trim();
|
|
254
|
+
if (!address) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`workflow manager: tls target ${JSON.stringify(rawTarget)} is missing host:port`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return { baseUrl: `https://${address}` };
|
|
260
|
+
}
|
|
261
|
+
if (target.startsWith("unix://")) {
|
|
262
|
+
const socketPath = target.slice("unix://".length).trim();
|
|
263
|
+
if (!socketPath) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
`workflow manager: unix target ${JSON.stringify(rawTarget)} is missing a socket path`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
return { baseUrl: "http://localhost", nodeOptions: { path: socketPath } };
|
|
269
|
+
}
|
|
270
|
+
if (target.includes("://")) {
|
|
271
|
+
const parsed = new URL(target);
|
|
272
|
+
throw new Error(
|
|
273
|
+
`workflow manager: unsupported target scheme ${JSON.stringify(parsed.protocol.replace(/:$/, ""))}`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return { baseUrl: "http://localhost", nodeOptions: { path: target } };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function workflowManagerRelayTokenInterceptor(token: string): Interceptor {
|
|
280
|
+
return (next) => async (req) => {
|
|
281
|
+
req.header.set(WORKFLOW_MANAGER_RELAY_TOKEN_HEADER, token);
|
|
282
|
+
return next(req);
|
|
283
|
+
};
|
|
284
|
+
}
|