@tinybirdco/sdk 0.0.49 → 0.0.50
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 +19 -2
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +36 -1
- package/dist/cli/commands/migrate.js.map +1 -1
- package/dist/cli/commands/migrate.test.js +60 -0
- package/dist/cli/commands/migrate.test.js.map +1 -1
- package/dist/generator/connection.d.ts.map +1 -1
- package/dist/generator/connection.js +14 -1
- package/dist/generator/connection.js.map +1 -1
- package/dist/generator/connection.test.js +20 -4
- package/dist/generator/connection.test.js.map +1 -1
- package/dist/generator/datasource.d.ts.map +1 -1
- package/dist/generator/datasource.js +20 -10
- package/dist/generator/datasource.js.map +1 -1
- package/dist/generator/datasource.test.js +26 -1
- package/dist/generator/datasource.test.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/migrate/emit-ts.d.ts.map +1 -1
- package/dist/migrate/emit-ts.js +45 -11
- package/dist/migrate/emit-ts.js.map +1 -1
- package/dist/migrate/parse-connection.d.ts +2 -2
- package/dist/migrate/parse-connection.d.ts.map +1 -1
- package/dist/migrate/parse-connection.js +34 -4
- package/dist/migrate/parse-connection.js.map +1 -1
- package/dist/migrate/parse-datasource.js +2 -2
- package/dist/migrate/parse-datasource.js.map +1 -1
- package/dist/migrate/types.d.ts +15 -1
- package/dist/migrate/types.d.ts.map +1 -1
- package/dist/schema/connection.d.ts +34 -1
- package/dist/schema/connection.d.ts.map +1 -1
- package/dist/schema/connection.js +26 -0
- package/dist/schema/connection.js.map +1 -1
- package/dist/schema/connection.test.js +35 -1
- package/dist/schema/connection.test.js.map +1 -1
- package/dist/schema/datasource.d.ts +16 -1
- package/dist/schema/datasource.d.ts.map +1 -1
- package/dist/schema/datasource.js +3 -2
- package/dist/schema/datasource.js.map +1 -1
- package/dist/schema/datasource.test.js +32 -3
- package/dist/schema/datasource.test.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/migrate.test.ts +91 -0
- package/src/cli/commands/migrate.ts +39 -1
- package/src/generator/connection.test.ts +29 -4
- package/src/generator/connection.ts +25 -2
- package/src/generator/datasource.test.ts +30 -1
- package/src/generator/datasource.ts +22 -10
- package/src/index.ts +5 -0
- package/src/migrate/emit-ts.ts +54 -14
- package/src/migrate/parse-connection.ts +56 -6
- package/src/migrate/parse-datasource.ts +2 -2
- package/src/migrate/types.ts +18 -1
- package/src/schema/connection.test.ts +48 -0
- package/src/schema/connection.ts +60 -1
- package/src/schema/datasource.test.ts +38 -3
- package/src/schema/datasource.ts +24 -3
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { generateConnection, generateAllConnections } from "./connection.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
defineKafkaConnection,
|
|
5
|
+
defineS3Connection,
|
|
6
|
+
defineGCSConnection,
|
|
7
|
+
} from "../schema/connection.js";
|
|
4
8
|
|
|
5
9
|
describe("Connection Generator", () => {
|
|
6
10
|
describe("generateConnection", () => {
|
|
@@ -152,6 +156,20 @@ describe("Connection Generator", () => {
|
|
|
152
156
|
expect(result.content).toContain('S3_ACCESS_KEY {{ tb_secret("S3_ACCESS_KEY") }}');
|
|
153
157
|
expect(result.content).toContain('S3_SECRET {{ tb_secret("S3_SECRET") }}');
|
|
154
158
|
});
|
|
159
|
+
|
|
160
|
+
it("generates GCS connection with service account credentials", () => {
|
|
161
|
+
const conn = defineGCSConnection("my_gcs", {
|
|
162
|
+
serviceAccountCredentialsJson: '{{ tb_secret("GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON") }}',
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const result = generateConnection(conn);
|
|
166
|
+
|
|
167
|
+
expect(result.name).toBe("my_gcs");
|
|
168
|
+
expect(result.content).toContain("TYPE gcs");
|
|
169
|
+
expect(result.content).toContain(
|
|
170
|
+
'GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON {{ tb_secret("GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON") }}'
|
|
171
|
+
);
|
|
172
|
+
});
|
|
155
173
|
});
|
|
156
174
|
|
|
157
175
|
describe("generateAllConnections", () => {
|
|
@@ -163,11 +181,18 @@ describe("Connection Generator", () => {
|
|
|
163
181
|
region: "us-east-1",
|
|
164
182
|
arn: "arn:aws:iam::123456789012:role/tinybird-s3-access",
|
|
165
183
|
});
|
|
184
|
+
const conn3 = defineGCSConnection("gcs_landing", {
|
|
185
|
+
serviceAccountCredentialsJson: '{{ tb_secret("GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON") }}',
|
|
186
|
+
});
|
|
166
187
|
|
|
167
|
-
const results = generateAllConnections({
|
|
188
|
+
const results = generateAllConnections({
|
|
189
|
+
kafka1: conn1,
|
|
190
|
+
s3_logs: conn2,
|
|
191
|
+
gcs_landing: conn3,
|
|
192
|
+
});
|
|
168
193
|
|
|
169
|
-
expect(results).toHaveLength(
|
|
170
|
-
expect(results.map((r) => r.name).sort()).toEqual(["kafka1", "s3_logs"]);
|
|
194
|
+
expect(results).toHaveLength(3);
|
|
195
|
+
expect(results.map((r) => r.name).sort()).toEqual(["gcs_landing", "kafka1", "s3_logs"]);
|
|
171
196
|
});
|
|
172
197
|
|
|
173
198
|
it("returns empty array for empty connections", () => {
|
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
* Converts ConnectionDefinition to native .connection file format
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
7
|
-
|
|
6
|
+
import type {
|
|
7
|
+
ConnectionDefinition,
|
|
8
|
+
KafkaConnectionDefinition,
|
|
9
|
+
GCSConnectionDefinition,
|
|
10
|
+
} from "../schema/connection.js";
|
|
11
|
+
import {
|
|
12
|
+
isS3ConnectionDefinition,
|
|
13
|
+
isGCSConnectionDefinition,
|
|
14
|
+
type S3ConnectionDefinition,
|
|
15
|
+
} from "../schema/connection.js";
|
|
8
16
|
|
|
9
17
|
/**
|
|
10
18
|
* Generated connection content
|
|
@@ -78,6 +86,19 @@ function generateS3Connection(connection: S3ConnectionDefinition): string {
|
|
|
78
86
|
return parts.join("\n");
|
|
79
87
|
}
|
|
80
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Generate a GCS connection content
|
|
91
|
+
*/
|
|
92
|
+
function generateGCSConnection(connection: GCSConnectionDefinition): string {
|
|
93
|
+
const parts: string[] = [];
|
|
94
|
+
const options = connection.options;
|
|
95
|
+
|
|
96
|
+
parts.push("TYPE gcs");
|
|
97
|
+
parts.push(`GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON ${options.serviceAccountCredentialsJson}`);
|
|
98
|
+
|
|
99
|
+
return parts.join("\n");
|
|
100
|
+
}
|
|
101
|
+
|
|
81
102
|
/**
|
|
82
103
|
* Generate a .connection file content from a ConnectionDefinition
|
|
83
104
|
*
|
|
@@ -113,6 +134,8 @@ export function generateConnection(
|
|
|
113
134
|
content = generateKafkaConnection(connection as KafkaConnectionDefinition);
|
|
114
135
|
} else if (isS3ConnectionDefinition(connection)) {
|
|
115
136
|
content = generateS3Connection(connection);
|
|
137
|
+
} else if (isGCSConnectionDefinition(connection)) {
|
|
138
|
+
content = generateGCSConnection(connection);
|
|
116
139
|
} else {
|
|
117
140
|
throw new Error("Unsupported connection type.");
|
|
118
141
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import { generateDatasource, generateAllDatasources } from './datasource.js';
|
|
3
3
|
import { defineDatasource } from '../schema/datasource.js';
|
|
4
|
-
import { defineKafkaConnection, defineS3Connection } from '../schema/connection.js';
|
|
4
|
+
import { defineKafkaConnection, defineS3Connection, defineGCSConnection } from '../schema/connection.js';
|
|
5
5
|
import { defineToken } from '../schema/token.js';
|
|
6
6
|
import { t } from '../schema/types.js';
|
|
7
7
|
import { engine } from '../schema/engines.js';
|
|
@@ -529,6 +529,35 @@ describe('Datasource Generator', () => {
|
|
|
529
529
|
});
|
|
530
530
|
});
|
|
531
531
|
|
|
532
|
+
describe('GCS configuration', () => {
|
|
533
|
+
it('includes GCS import directives', () => {
|
|
534
|
+
const gcsConn = defineGCSConnection('my_gcs', {
|
|
535
|
+
serviceAccountCredentialsJson: '{{ tb_secret("GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON") }}',
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
const ds = defineDatasource('gcs_events', {
|
|
539
|
+
schema: {
|
|
540
|
+
timestamp: t.dateTime(),
|
|
541
|
+
event: t.string(),
|
|
542
|
+
},
|
|
543
|
+
engine: engine.mergeTree({ sortingKey: ['timestamp'] }),
|
|
544
|
+
gcs: {
|
|
545
|
+
connection: gcsConn,
|
|
546
|
+
bucketUri: 'gs://my-bucket/events/*.csv',
|
|
547
|
+
schedule: '@auto',
|
|
548
|
+
fromTimestamp: '2024-01-01T00:00:00Z',
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
const result = generateDatasource(ds);
|
|
553
|
+
|
|
554
|
+
expect(result.content).toContain('IMPORT_CONNECTION_NAME my_gcs');
|
|
555
|
+
expect(result.content).toContain('IMPORT_BUCKET_URI gs://my-bucket/events/*.csv');
|
|
556
|
+
expect(result.content).toContain('IMPORT_SCHEDULE @auto');
|
|
557
|
+
expect(result.content).toContain('IMPORT_FROM_TIMESTAMP 2024-01-01T00:00:00Z');
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
|
|
532
561
|
describe('Token generation', () => {
|
|
533
562
|
it('generates TOKEN lines with inline config', () => {
|
|
534
563
|
const ds = defineDatasource('test_ds', {
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ColumnDefinition,
|
|
10
10
|
KafkaConfig,
|
|
11
11
|
S3Config,
|
|
12
|
+
GCSConfig,
|
|
12
13
|
TokenConfig,
|
|
13
14
|
} from "../schema/datasource.js";
|
|
14
15
|
import type { AnyTypeValidator, TypeModifiers } from "../schema/types.js";
|
|
@@ -176,18 +177,18 @@ function generateKafkaConfig(kafka: KafkaConfig): string {
|
|
|
176
177
|
/**
|
|
177
178
|
* Generate S3 import configuration lines
|
|
178
179
|
*/
|
|
179
|
-
function
|
|
180
|
+
function generateImportConfig(importConfig: S3Config | GCSConfig): string {
|
|
180
181
|
const parts: string[] = [];
|
|
181
182
|
|
|
182
|
-
parts.push(`IMPORT_CONNECTION_NAME ${
|
|
183
|
-
parts.push(`IMPORT_BUCKET_URI ${
|
|
183
|
+
parts.push(`IMPORT_CONNECTION_NAME ${importConfig.connection._name}`);
|
|
184
|
+
parts.push(`IMPORT_BUCKET_URI ${importConfig.bucketUri}`);
|
|
184
185
|
|
|
185
|
-
if (
|
|
186
|
-
parts.push(`IMPORT_SCHEDULE ${
|
|
186
|
+
if (importConfig.schedule) {
|
|
187
|
+
parts.push(`IMPORT_SCHEDULE ${importConfig.schedule}`);
|
|
187
188
|
}
|
|
188
189
|
|
|
189
|
-
if (
|
|
190
|
-
parts.push(`IMPORT_FROM_TIMESTAMP ${
|
|
190
|
+
if (importConfig.fromTimestamp) {
|
|
191
|
+
parts.push(`IMPORT_FROM_TIMESTAMP ${importConfig.fromTimestamp}`);
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
return parts.join("\n");
|
|
@@ -291,8 +292,13 @@ export function generateDatasource(
|
|
|
291
292
|
): GeneratedDatasource {
|
|
292
293
|
const parts: string[] = [];
|
|
293
294
|
|
|
294
|
-
|
|
295
|
-
|
|
295
|
+
const ingestionConfigCount = [
|
|
296
|
+
datasource.options.kafka,
|
|
297
|
+
datasource.options.s3,
|
|
298
|
+
datasource.options.gcs,
|
|
299
|
+
].filter(Boolean).length;
|
|
300
|
+
if (ingestionConfigCount > 1) {
|
|
301
|
+
throw new Error("Datasource can only define one ingestion option: `kafka`, `s3`, or `gcs`.");
|
|
296
302
|
}
|
|
297
303
|
|
|
298
304
|
// Add description if present
|
|
@@ -320,7 +326,13 @@ export function generateDatasource(
|
|
|
320
326
|
// Add S3 configuration if present
|
|
321
327
|
if (datasource.options.s3) {
|
|
322
328
|
parts.push("");
|
|
323
|
-
parts.push(
|
|
329
|
+
parts.push(generateImportConfig(datasource.options.s3));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Add GCS configuration if present
|
|
333
|
+
if (datasource.options.gcs) {
|
|
334
|
+
parts.push("");
|
|
335
|
+
parts.push(generateImportConfig(datasource.options.gcs));
|
|
324
336
|
}
|
|
325
337
|
|
|
326
338
|
// Add forward query if present
|
package/src/index.ts
CHANGED
|
@@ -113,6 +113,7 @@ export type {
|
|
|
113
113
|
ExtractSchema,
|
|
114
114
|
KafkaConfig,
|
|
115
115
|
S3Config,
|
|
116
|
+
GCSConfig,
|
|
116
117
|
} from "./schema/datasource.js";
|
|
117
118
|
|
|
118
119
|
// ============ Connection ============
|
|
@@ -120,9 +121,11 @@ export {
|
|
|
120
121
|
defineKafkaConnection,
|
|
121
122
|
createKafkaConnection,
|
|
122
123
|
defineS3Connection,
|
|
124
|
+
defineGCSConnection,
|
|
123
125
|
isConnectionDefinition,
|
|
124
126
|
isKafkaConnectionDefinition,
|
|
125
127
|
isS3ConnectionDefinition,
|
|
128
|
+
isGCSConnectionDefinition,
|
|
126
129
|
getConnectionType,
|
|
127
130
|
} from "./schema/connection.js";
|
|
128
131
|
export type {
|
|
@@ -133,6 +136,8 @@ export type {
|
|
|
133
136
|
KafkaSaslMechanism,
|
|
134
137
|
S3ConnectionDefinition,
|
|
135
138
|
S3ConnectionOptions,
|
|
139
|
+
GCSConnectionDefinition,
|
|
140
|
+
GCSConnectionOptions,
|
|
136
141
|
} from "./schema/connection.js";
|
|
137
142
|
|
|
138
143
|
// ============ Token ============
|
package/src/migrate/emit-ts.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { parseLiteralFromDatafile, toTsLiteral } from "./parser-utils.js";
|
|
|
4
4
|
import type {
|
|
5
5
|
DatasourceModel,
|
|
6
6
|
DatasourceEngineModel,
|
|
7
|
+
GCSConnectionModel,
|
|
7
8
|
KafkaConnectionModel,
|
|
8
9
|
ParsedResource,
|
|
9
10
|
PipeModel,
|
|
@@ -59,11 +60,13 @@ function hasSecretTemplate(resources: ParsedResource[]): boolean {
|
|
|
59
60
|
if (resource.secret) values.push(resource.secret);
|
|
60
61
|
if (resource.sslCaPem) values.push(resource.sslCaPem);
|
|
61
62
|
if (resource.schemaRegistryUrl) values.push(resource.schemaRegistryUrl);
|
|
62
|
-
} else {
|
|
63
|
+
} else if (resource.connectionType === "s3") {
|
|
63
64
|
values.push(resource.region);
|
|
64
65
|
if (resource.arn) values.push(resource.arn);
|
|
65
66
|
if (resource.accessKey) values.push(resource.accessKey);
|
|
66
67
|
if (resource.secret) values.push(resource.secret);
|
|
68
|
+
} else {
|
|
69
|
+
values.push(resource.serviceAccountCredentialsJson);
|
|
67
70
|
}
|
|
68
71
|
continue;
|
|
69
72
|
}
|
|
@@ -80,6 +83,11 @@ function hasSecretTemplate(resources: ParsedResource[]): boolean {
|
|
|
80
83
|
if (resource.s3.schedule) values.push(resource.s3.schedule);
|
|
81
84
|
if (resource.s3.fromTimestamp) values.push(resource.s3.fromTimestamp);
|
|
82
85
|
}
|
|
86
|
+
if (resource.gcs) {
|
|
87
|
+
values.push(resource.gcs.bucketUri);
|
|
88
|
+
if (resource.gcs.schedule) values.push(resource.gcs.schedule);
|
|
89
|
+
if (resource.gcs.fromTimestamp) values.push(resource.gcs.fromTimestamp);
|
|
90
|
+
}
|
|
83
91
|
continue;
|
|
84
92
|
}
|
|
85
93
|
}
|
|
@@ -337,6 +345,20 @@ function emitDatasource(ds: DatasourceModel): string {
|
|
|
337
345
|
lines.push(" },");
|
|
338
346
|
}
|
|
339
347
|
|
|
348
|
+
if (ds.gcs) {
|
|
349
|
+
const connectionVar = toCamelCase(ds.gcs.connectionName);
|
|
350
|
+
lines.push(" gcs: {");
|
|
351
|
+
lines.push(` connection: ${connectionVar},`);
|
|
352
|
+
lines.push(` bucketUri: ${emitStringOrSecret(ds.gcs.bucketUri)},`);
|
|
353
|
+
if (ds.gcs.schedule) {
|
|
354
|
+
lines.push(` schedule: ${emitStringOrSecret(ds.gcs.schedule)},`);
|
|
355
|
+
}
|
|
356
|
+
if (ds.gcs.fromTimestamp) {
|
|
357
|
+
lines.push(` fromTimestamp: ${emitStringOrSecret(ds.gcs.fromTimestamp)},`);
|
|
358
|
+
}
|
|
359
|
+
lines.push(" },");
|
|
360
|
+
}
|
|
361
|
+
|
|
340
362
|
if (ds.forwardQuery) {
|
|
341
363
|
lines.push(" forwardQuery: `");
|
|
342
364
|
lines.push(ds.forwardQuery.replace(/`/g, "\\`").replace(/\${/g, "\\${"));
|
|
@@ -364,7 +386,9 @@ function emitDatasource(ds: DatasourceModel): string {
|
|
|
364
386
|
return lines.join("\n");
|
|
365
387
|
}
|
|
366
388
|
|
|
367
|
-
function emitConnection(
|
|
389
|
+
function emitConnection(
|
|
390
|
+
connection: KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel
|
|
391
|
+
): string {
|
|
368
392
|
const variableName = toCamelCase(connection.name);
|
|
369
393
|
const lines: string[] = [];
|
|
370
394
|
|
|
@@ -396,19 +420,31 @@ function emitConnection(connection: KafkaConnectionModel | S3ConnectionModel): s
|
|
|
396
420
|
return lines.join("\n");
|
|
397
421
|
}
|
|
398
422
|
|
|
423
|
+
if (connection.connectionType === "s3") {
|
|
424
|
+
lines.push(
|
|
425
|
+
`export const ${variableName} = defineS3Connection(${escapeString(connection.name)}, {`
|
|
426
|
+
);
|
|
427
|
+
lines.push(` region: ${emitStringOrSecret(connection.region)},`);
|
|
428
|
+
if (connection.arn) {
|
|
429
|
+
lines.push(` arn: ${emitStringOrSecret(connection.arn)},`);
|
|
430
|
+
}
|
|
431
|
+
if (connection.accessKey) {
|
|
432
|
+
lines.push(` accessKey: ${emitStringOrSecret(connection.accessKey)},`);
|
|
433
|
+
}
|
|
434
|
+
if (connection.secret) {
|
|
435
|
+
lines.push(` secret: ${emitStringOrSecret(connection.secret)},`);
|
|
436
|
+
}
|
|
437
|
+
lines.push("});");
|
|
438
|
+
lines.push("");
|
|
439
|
+
return lines.join("\n");
|
|
440
|
+
}
|
|
441
|
+
|
|
399
442
|
lines.push(
|
|
400
|
-
`export const ${variableName} =
|
|
443
|
+
`export const ${variableName} = defineGCSConnection(${escapeString(connection.name)}, {`
|
|
444
|
+
);
|
|
445
|
+
lines.push(
|
|
446
|
+
` serviceAccountCredentialsJson: ${emitStringOrSecret(connection.serviceAccountCredentialsJson)},`
|
|
401
447
|
);
|
|
402
|
-
lines.push(` region: ${emitStringOrSecret(connection.region)},`);
|
|
403
|
-
if (connection.arn) {
|
|
404
|
-
lines.push(` arn: ${emitStringOrSecret(connection.arn)},`);
|
|
405
|
-
}
|
|
406
|
-
if (connection.accessKey) {
|
|
407
|
-
lines.push(` accessKey: ${emitStringOrSecret(connection.accessKey)},`);
|
|
408
|
-
}
|
|
409
|
-
if (connection.secret) {
|
|
410
|
-
lines.push(` secret: ${emitStringOrSecret(connection.secret)},`);
|
|
411
|
-
}
|
|
412
448
|
lines.push("});");
|
|
413
449
|
lines.push("");
|
|
414
450
|
return lines.join("\n");
|
|
@@ -542,7 +578,7 @@ function emitPipe(pipe: PipeModel): string {
|
|
|
542
578
|
|
|
543
579
|
export function emitMigrationFileContent(resources: ParsedResource[]): string {
|
|
544
580
|
const connections = resources.filter(
|
|
545
|
-
(resource): resource is KafkaConnectionModel | S3ConnectionModel =>
|
|
581
|
+
(resource): resource is KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel =>
|
|
546
582
|
resource.kind === "connection"
|
|
547
583
|
);
|
|
548
584
|
const datasources = resources.filter(
|
|
@@ -569,6 +605,9 @@ export function emitMigrationFileContent(resources: ParsedResource[]): string {
|
|
|
569
605
|
if (connections.some((connection) => connection.connectionType === "s3")) {
|
|
570
606
|
imports.add("defineS3Connection");
|
|
571
607
|
}
|
|
608
|
+
if (connections.some((connection) => connection.connectionType === "gcs")) {
|
|
609
|
+
imports.add("defineGCSConnection");
|
|
610
|
+
}
|
|
572
611
|
if (needsParams) {
|
|
573
612
|
imports.add("p");
|
|
574
613
|
}
|
|
@@ -585,6 +624,7 @@ export function emitMigrationFileContent(resources: ParsedResource[]): string {
|
|
|
585
624
|
const orderedImports = [
|
|
586
625
|
"defineKafkaConnection",
|
|
587
626
|
"defineS3Connection",
|
|
627
|
+
"defineGCSConnection",
|
|
588
628
|
"defineDatasource",
|
|
589
629
|
"definePipe",
|
|
590
630
|
"defineMaterializedView",
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
GCSConnectionModel,
|
|
3
|
+
KafkaConnectionModel,
|
|
4
|
+
ResourceFile,
|
|
5
|
+
S3ConnectionModel,
|
|
6
|
+
} from "./types.js";
|
|
2
7
|
import {
|
|
3
8
|
MigrationParseError,
|
|
4
9
|
isBlank,
|
|
@@ -9,7 +14,7 @@ import {
|
|
|
9
14
|
|
|
10
15
|
export function parseConnectionFile(
|
|
11
16
|
resource: ResourceFile
|
|
12
|
-
): KafkaConnectionModel | S3ConnectionModel {
|
|
17
|
+
): KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel {
|
|
13
18
|
const lines = splitLines(resource.content);
|
|
14
19
|
let connectionType: string | undefined;
|
|
15
20
|
|
|
@@ -34,6 +39,7 @@ export function parseConnectionFile(
|
|
|
34
39
|
let arn: string | undefined;
|
|
35
40
|
let accessKey: string | undefined;
|
|
36
41
|
let accessSecret: string | undefined;
|
|
42
|
+
let serviceAccountCredentialsJson: string | undefined;
|
|
37
43
|
|
|
38
44
|
for (const rawLine of lines) {
|
|
39
45
|
const line = rawLine.trim();
|
|
@@ -100,6 +106,9 @@ export function parseConnectionFile(
|
|
|
100
106
|
case "S3_SECRET":
|
|
101
107
|
accessSecret = parseQuotedValue(value);
|
|
102
108
|
break;
|
|
109
|
+
case "GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON":
|
|
110
|
+
serviceAccountCredentialsJson = parseQuotedValue(value);
|
|
111
|
+
break;
|
|
103
112
|
default:
|
|
104
113
|
throw new MigrationParseError(
|
|
105
114
|
resource.filePath,
|
|
@@ -120,12 +129,12 @@ export function parseConnectionFile(
|
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
if (connectionType === "kafka") {
|
|
123
|
-
if (region || arn || accessKey || accessSecret) {
|
|
132
|
+
if (region || arn || accessKey || accessSecret || serviceAccountCredentialsJson) {
|
|
124
133
|
throw new MigrationParseError(
|
|
125
134
|
resource.filePath,
|
|
126
135
|
"connection",
|
|
127
136
|
resource.name,
|
|
128
|
-
"S3 directives are not valid for kafka connections."
|
|
137
|
+
"S3/GCS directives are not valid for kafka connections."
|
|
129
138
|
);
|
|
130
139
|
}
|
|
131
140
|
|
|
@@ -161,13 +170,14 @@ export function parseConnectionFile(
|
|
|
161
170
|
key ||
|
|
162
171
|
secret ||
|
|
163
172
|
schemaRegistryUrl ||
|
|
164
|
-
sslCaPem
|
|
173
|
+
sslCaPem ||
|
|
174
|
+
serviceAccountCredentialsJson
|
|
165
175
|
) {
|
|
166
176
|
throw new MigrationParseError(
|
|
167
177
|
resource.filePath,
|
|
168
178
|
"connection",
|
|
169
179
|
resource.name,
|
|
170
|
-
"Kafka directives are not valid for s3 connections."
|
|
180
|
+
"Kafka/GCS directives are not valid for s3 connections."
|
|
171
181
|
);
|
|
172
182
|
}
|
|
173
183
|
|
|
@@ -210,6 +220,46 @@ export function parseConnectionFile(
|
|
|
210
220
|
};
|
|
211
221
|
}
|
|
212
222
|
|
|
223
|
+
if (connectionType === "gcs") {
|
|
224
|
+
if (
|
|
225
|
+
bootstrapServers ||
|
|
226
|
+
securityProtocol ||
|
|
227
|
+
saslMechanism ||
|
|
228
|
+
key ||
|
|
229
|
+
secret ||
|
|
230
|
+
schemaRegistryUrl ||
|
|
231
|
+
sslCaPem ||
|
|
232
|
+
region ||
|
|
233
|
+
arn ||
|
|
234
|
+
accessKey ||
|
|
235
|
+
accessSecret
|
|
236
|
+
) {
|
|
237
|
+
throw new MigrationParseError(
|
|
238
|
+
resource.filePath,
|
|
239
|
+
"connection",
|
|
240
|
+
resource.name,
|
|
241
|
+
"Kafka/S3 directives are not valid for gcs connections."
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!serviceAccountCredentialsJson) {
|
|
246
|
+
throw new MigrationParseError(
|
|
247
|
+
resource.filePath,
|
|
248
|
+
"connection",
|
|
249
|
+
resource.name,
|
|
250
|
+
"GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON is required for gcs connections."
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
kind: "connection",
|
|
256
|
+
name: resource.name,
|
|
257
|
+
filePath: resource.filePath,
|
|
258
|
+
connectionType: "gcs",
|
|
259
|
+
serviceAccountCredentialsJson,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
213
263
|
throw new MigrationParseError(
|
|
214
264
|
resource.filePath,
|
|
215
265
|
"connection",
|
|
@@ -518,7 +518,7 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
|
518
518
|
resource.filePath,
|
|
519
519
|
"datasource",
|
|
520
520
|
resource.name,
|
|
521
|
-
"IMPORT_CONNECTION_NAME and IMPORT_BUCKET_URI are required when
|
|
521
|
+
"IMPORT_CONNECTION_NAME and IMPORT_BUCKET_URI are required when import directives are used."
|
|
522
522
|
);
|
|
523
523
|
}
|
|
524
524
|
|
|
@@ -527,7 +527,7 @@ export function parseDatasourceFile(resource: ResourceFile): DatasourceModel {
|
|
|
527
527
|
resource.filePath,
|
|
528
528
|
"datasource",
|
|
529
529
|
resource.name,
|
|
530
|
-
"Datasource cannot mix Kafka directives with
|
|
530
|
+
"Datasource cannot mix Kafka directives with import directives."
|
|
531
531
|
);
|
|
532
532
|
}
|
|
533
533
|
|
package/src/migrate/types.ts
CHANGED
|
@@ -52,6 +52,13 @@ export interface DatasourceS3Model {
|
|
|
52
52
|
fromTimestamp?: string;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
export interface DatasourceGCSModel {
|
|
56
|
+
connectionName: string;
|
|
57
|
+
bucketUri: string;
|
|
58
|
+
schedule?: string;
|
|
59
|
+
fromTimestamp?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
export interface DatasourceTokenModel {
|
|
56
63
|
name: string;
|
|
57
64
|
scope: "READ" | "APPEND";
|
|
@@ -66,6 +73,7 @@ export interface DatasourceModel {
|
|
|
66
73
|
engine?: DatasourceEngineModel;
|
|
67
74
|
kafka?: DatasourceKafkaModel;
|
|
68
75
|
s3?: DatasourceS3Model;
|
|
76
|
+
gcs?: DatasourceGCSModel;
|
|
69
77
|
forwardQuery?: string;
|
|
70
78
|
tokens: DatasourceTokenModel[];
|
|
71
79
|
sharedWith: string[];
|
|
@@ -156,11 +164,20 @@ export interface S3ConnectionModel {
|
|
|
156
164
|
secret?: string;
|
|
157
165
|
}
|
|
158
166
|
|
|
167
|
+
export interface GCSConnectionModel {
|
|
168
|
+
kind: "connection";
|
|
169
|
+
name: string;
|
|
170
|
+
filePath: string;
|
|
171
|
+
connectionType: "gcs";
|
|
172
|
+
serviceAccountCredentialsJson: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
159
175
|
export type ParsedResource =
|
|
160
176
|
| DatasourceModel
|
|
161
177
|
| PipeModel
|
|
162
178
|
| KafkaConnectionModel
|
|
163
|
-
| S3ConnectionModel
|
|
179
|
+
| S3ConnectionModel
|
|
180
|
+
| GCSConnectionModel;
|
|
164
181
|
|
|
165
182
|
export interface MigrationResult {
|
|
166
183
|
success: boolean;
|
|
@@ -2,9 +2,11 @@ import { describe, it, expect } from "vitest";
|
|
|
2
2
|
import {
|
|
3
3
|
defineKafkaConnection,
|
|
4
4
|
defineS3Connection,
|
|
5
|
+
defineGCSConnection,
|
|
5
6
|
isConnectionDefinition,
|
|
6
7
|
isKafkaConnectionDefinition,
|
|
7
8
|
isS3ConnectionDefinition,
|
|
9
|
+
isGCSConnectionDefinition,
|
|
8
10
|
getConnectionType,
|
|
9
11
|
} from "./connection.js";
|
|
10
12
|
|
|
@@ -153,6 +155,29 @@ describe("Connection Schema", () => {
|
|
|
153
155
|
});
|
|
154
156
|
});
|
|
155
157
|
|
|
158
|
+
describe("defineGCSConnection", () => {
|
|
159
|
+
it("creates a GCS connection with required fields", () => {
|
|
160
|
+
const conn = defineGCSConnection("my_gcs", {
|
|
161
|
+
serviceAccountCredentialsJson: '{{ tb_secret("GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON") }}',
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(conn._name).toBe("my_gcs");
|
|
165
|
+
expect(conn._type).toBe("connection");
|
|
166
|
+
expect(conn._connectionType).toBe("gcs");
|
|
167
|
+
expect(conn.options.serviceAccountCredentialsJson).toBe(
|
|
168
|
+
'{{ tb_secret("GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON") }}'
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("throws when credentials json is empty", () => {
|
|
173
|
+
expect(() =>
|
|
174
|
+
defineGCSConnection("my_gcs", {
|
|
175
|
+
serviceAccountCredentialsJson: " ",
|
|
176
|
+
})
|
|
177
|
+
).toThrow("GCS connection `serviceAccountCredentialsJson` is required.");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
156
181
|
describe("isConnectionDefinition", () => {
|
|
157
182
|
it("returns true for valid connection", () => {
|
|
158
183
|
const conn = defineKafkaConnection("my_kafka", {
|
|
@@ -203,6 +228,21 @@ describe("Connection Schema", () => {
|
|
|
203
228
|
});
|
|
204
229
|
});
|
|
205
230
|
|
|
231
|
+
describe("isGCSConnectionDefinition", () => {
|
|
232
|
+
it("returns true for GCS connection", () => {
|
|
233
|
+
const conn = defineGCSConnection("my_gcs", {
|
|
234
|
+
serviceAccountCredentialsJson: '{{ tb_secret("GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON") }}',
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
expect(isGCSConnectionDefinition(conn)).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("returns false for non-GCS objects", () => {
|
|
241
|
+
expect(isGCSConnectionDefinition({})).toBe(false);
|
|
242
|
+
expect(isGCSConnectionDefinition(null)).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
206
246
|
describe("getConnectionType", () => {
|
|
207
247
|
it("returns the connection type", () => {
|
|
208
248
|
const conn = defineKafkaConnection("my_kafka", {
|
|
@@ -220,5 +260,13 @@ describe("Connection Schema", () => {
|
|
|
220
260
|
|
|
221
261
|
expect(getConnectionType(conn)).toBe("s3");
|
|
222
262
|
});
|
|
263
|
+
|
|
264
|
+
it("returns the gcs connection type", () => {
|
|
265
|
+
const conn = defineGCSConnection("my_gcs", {
|
|
266
|
+
serviceAccountCredentialsJson: '{{ tb_secret("GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON") }}',
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(getConnectionType(conn)).toBe("gcs");
|
|
270
|
+
});
|
|
223
271
|
});
|
|
224
272
|
});
|