@tinybirdco/sdk 0.0.76 → 0.0.78
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 +27 -2
- package/dist/api/deploy.d.ts.map +1 -1
- package/dist/api/deploy.js +26 -0
- package/dist/api/deploy.js.map +1 -1
- package/dist/api/deploy.test.js +26 -2
- package/dist/api/deploy.test.js.map +1 -1
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +14 -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 +15 -1
- package/dist/generator/connection.js.map +1 -1
- package/dist/generator/connection.test.js +10 -1
- package/dist/generator/connection.test.js.map +1 -1
- package/dist/generator/datasource.d.ts.map +1 -1
- package/dist/generator/datasource.js +17 -1
- package/dist/generator/datasource.js.map +1 -1
- package/dist/generator/datasource.test.js +44 -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 +28 -0
- 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 +60 -6
- package/dist/migrate/parse-connection.js.map +1 -1
- package/dist/migrate/parse-connection.test.js +18 -0
- package/dist/migrate/parse-connection.test.js.map +1 -1
- package/dist/migrate/parse-datasource.d.ts.map +1 -1
- package/dist/migrate/parse-datasource.js +27 -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 +46 -1
- package/dist/schema/connection.d.ts.map +1 -1
- package/dist/schema/connection.js +39 -0
- package/dist/schema/connection.js.map +1 -1
- package/dist/schema/connection.test.js +46 -1
- package/dist/schema/connection.test.js.map +1 -1
- package/dist/schema/datasource.d.ts +14 -1
- package/dist/schema/datasource.d.ts.map +1 -1
- package/dist/schema/datasource.js +2 -2
- package/dist/schema/datasource.js.map +1 -1
- package/dist/schema/datasource.test.js +2 -2
- package/dist/schema/datasource.test.js.map +1 -1
- package/package.json +1 -1
- package/src/api/deploy.test.ts +42 -2
- package/src/api/deploy.ts +34 -0
- package/src/cli/commands/migrate.test.ts +95 -0
- package/src/cli/commands/migrate.ts +17 -1
- package/src/generator/connection.test.ts +17 -0
- package/src/generator/connection.ts +18 -0
- package/src/generator/datasource.test.ts +54 -1
- package/src/generator/datasource.ts +24 -1
- package/src/index.ts +5 -0
- package/src/migrate/emit-ts.ts +44 -3
- package/src/migrate/parse-connection.test.ts +41 -0
- package/src/migrate/parse-connection.ts +86 -7
- package/src/migrate/parse-datasource.ts +35 -2
- package/src/migrate/types.ts +18 -1
- package/src/schema/connection.test.ts +65 -0
- package/src/schema/connection.ts +76 -1
- package/src/schema/datasource.test.ts +2 -2
- package/src/schema/datasource.ts +21 -2
|
@@ -716,6 +716,101 @@ IMPORT_SCHEDULE '@on-demand'
|
|
|
716
716
|
expect(output).toContain('schedule: "@on-demand"');
|
|
717
717
|
});
|
|
718
718
|
|
|
719
|
+
it("migrates dynamodb connection and import datasource directives", async () => {
|
|
720
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
|
|
721
|
+
tempDirs.push(tempDir);
|
|
722
|
+
|
|
723
|
+
writeFile(
|
|
724
|
+
tempDir,
|
|
725
|
+
"dynamo_sample.connection",
|
|
726
|
+
`TYPE dynamodb
|
|
727
|
+
DYNAMODB_ARN {{ tb_secret("DYNAMODB_ROLE_ARN") }}
|
|
728
|
+
DYNAMODB_REGION us-east-1
|
|
729
|
+
`
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
writeFile(
|
|
733
|
+
tempDir,
|
|
734
|
+
"events_dynamo.datasource",
|
|
735
|
+
`SCHEMA >
|
|
736
|
+
id String \`json:$.Id\`,
|
|
737
|
+
_record String \`json:$.NewImage\`
|
|
738
|
+
|
|
739
|
+
ENGINE "ReplacingMergeTree"
|
|
740
|
+
ENGINE_SORTING_KEY "id"
|
|
741
|
+
IMPORT_CONNECTION_NAME dynamo_sample
|
|
742
|
+
IMPORT_TABLE_ARN arn:aws:dynamodb:us-east-1:123456789012:table/events
|
|
743
|
+
IMPORT_EXPORT_BUCKET s3://my-export-bucket
|
|
744
|
+
`
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
const result = await runMigrate({
|
|
748
|
+
cwd: tempDir,
|
|
749
|
+
patterns: ["."],
|
|
750
|
+
strict: true,
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
expect(result.success).toBe(true);
|
|
754
|
+
expect(result.errors).toHaveLength(0);
|
|
755
|
+
expect(result.migrated.filter((resource) => resource.kind === "connection")).toHaveLength(1);
|
|
756
|
+
expect(result.migrated.filter((resource) => resource.kind === "datasource")).toHaveLength(1);
|
|
757
|
+
|
|
758
|
+
const output = fs.readFileSync(result.outputPath, "utf-8");
|
|
759
|
+
expect(output).toContain("defineDynamoDBConnection");
|
|
760
|
+
expect(output).toContain('export const dynamoSample = defineDynamoDBConnection("dynamo_sample", {');
|
|
761
|
+
expect(output).toContain('region: "us-east-1"');
|
|
762
|
+
expect(output).toContain('arn: secret("DYNAMODB_ROLE_ARN")');
|
|
763
|
+
expect(output).toContain("dynamodb: {");
|
|
764
|
+
expect(output).toContain("connection: dynamoSample");
|
|
765
|
+
expect(output).toContain(
|
|
766
|
+
'tableArn: "arn:aws:dynamodb:us-east-1:123456789012:table/events"'
|
|
767
|
+
);
|
|
768
|
+
expect(output).toContain('exportBucket: "s3://my-export-bucket"');
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
it("reports an error when DynamoDB directives use a non-dynamodb connection type", async () => {
|
|
772
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
|
|
773
|
+
tempDirs.push(tempDir);
|
|
774
|
+
|
|
775
|
+
writeFile(
|
|
776
|
+
tempDir,
|
|
777
|
+
"s3sample.connection",
|
|
778
|
+
`TYPE s3
|
|
779
|
+
S3_REGION us-east-1
|
|
780
|
+
S3_ARN "arn:aws:iam::123456789012:role/tinybird-s3-access"
|
|
781
|
+
`
|
|
782
|
+
);
|
|
783
|
+
|
|
784
|
+
writeFile(
|
|
785
|
+
tempDir,
|
|
786
|
+
"events_dynamo.datasource",
|
|
787
|
+
`SCHEMA >
|
|
788
|
+
id String
|
|
789
|
+
|
|
790
|
+
ENGINE "ReplacingMergeTree"
|
|
791
|
+
ENGINE_SORTING_KEY "id"
|
|
792
|
+
IMPORT_CONNECTION_NAME s3sample
|
|
793
|
+
IMPORT_TABLE_ARN arn:aws:dynamodb:us-east-1:123456789012:table/events
|
|
794
|
+
IMPORT_EXPORT_BUCKET s3://my-export-bucket
|
|
795
|
+
`
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
const result = await runMigrate({
|
|
799
|
+
cwd: tempDir,
|
|
800
|
+
patterns: ["."],
|
|
801
|
+
strict: true,
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
expect(result.success).toBe(false);
|
|
805
|
+
expect(
|
|
806
|
+
result.errors.some((error) =>
|
|
807
|
+
error.message.includes(
|
|
808
|
+
'Datasource DynamoDB ingestion requires a dynamodb connection, found "s3".'
|
|
809
|
+
)
|
|
810
|
+
)
|
|
811
|
+
).toBe(true);
|
|
812
|
+
});
|
|
813
|
+
|
|
719
814
|
it("reports an error when import directives use a non-bucket connection type", async () => {
|
|
720
815
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-migrate-"));
|
|
721
816
|
tempDirs.push(tempDir);
|
|
@@ -135,7 +135,8 @@ export async function runMigrate(
|
|
|
135
135
|
const referencedConnectionName =
|
|
136
136
|
datasource.kafka?.connectionName ??
|
|
137
137
|
datasource.s3?.connectionName ??
|
|
138
|
-
datasource.gcs?.connectionName
|
|
138
|
+
datasource.gcs?.connectionName ??
|
|
139
|
+
datasource.dynamodb?.connectionName;
|
|
139
140
|
|
|
140
141
|
if (
|
|
141
142
|
referencedConnectionName &&
|
|
@@ -186,6 +187,21 @@ export async function runMigrate(
|
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
if (datasource.dynamodb) {
|
|
191
|
+
const dynamodbConnectionType = parsedConnectionTypeByName.get(
|
|
192
|
+
datasource.dynamodb.connectionName
|
|
193
|
+
);
|
|
194
|
+
if (dynamodbConnectionType !== "dynamodb") {
|
|
195
|
+
errors.push({
|
|
196
|
+
filePath: datasource.filePath,
|
|
197
|
+
resourceName: datasource.name,
|
|
198
|
+
resourceKind: datasource.kind,
|
|
199
|
+
message: `Datasource DynamoDB ingestion requires a dynamodb connection, found "${dynamodbConnectionType ?? "(none)"}".`,
|
|
200
|
+
});
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
189
205
|
try {
|
|
190
206
|
validateResourceForEmission(datasource);
|
|
191
207
|
migrated.push(datasource);
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
defineKafkaConnection,
|
|
5
5
|
defineS3Connection,
|
|
6
6
|
defineGCSConnection,
|
|
7
|
+
defineDynamoDBConnection,
|
|
7
8
|
} from "../schema/connection.js";
|
|
8
9
|
|
|
9
10
|
describe("Connection Generator", () => {
|
|
@@ -218,6 +219,22 @@ describe("Connection Generator", () => {
|
|
|
218
219
|
'GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON {{ tb_secret("GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON") }}'
|
|
219
220
|
);
|
|
220
221
|
});
|
|
222
|
+
|
|
223
|
+
it("generates DynamoDB connection with arn and region", () => {
|
|
224
|
+
const conn = defineDynamoDBConnection("my_dynamo", {
|
|
225
|
+
region: "us-east-1",
|
|
226
|
+
arn: '{{ tb_secret("DYNAMODB_ROLE_ARN") }}',
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const result = generateConnection(conn);
|
|
230
|
+
|
|
231
|
+
expect(result.name).toBe("my_dynamo");
|
|
232
|
+
expect(result.content).toBe(
|
|
233
|
+
['TYPE dynamodb', 'DYNAMODB_ARN {{ tb_secret("DYNAMODB_ROLE_ARN") }}', "DYNAMODB_REGION us-east-1"].join(
|
|
234
|
+
"\n"
|
|
235
|
+
)
|
|
236
|
+
);
|
|
237
|
+
});
|
|
221
238
|
});
|
|
222
239
|
|
|
223
240
|
describe("generateAllConnections", () => {
|
|
@@ -7,10 +7,12 @@ import type {
|
|
|
7
7
|
ConnectionDefinition,
|
|
8
8
|
KafkaConnectionDefinition,
|
|
9
9
|
GCSConnectionDefinition,
|
|
10
|
+
DynamoDBConnectionDefinition,
|
|
10
11
|
} from "../schema/connection.js";
|
|
11
12
|
import {
|
|
12
13
|
isS3ConnectionDefinition,
|
|
13
14
|
isGCSConnectionDefinition,
|
|
15
|
+
isDynamoDBConnectionDefinition,
|
|
14
16
|
type S3ConnectionDefinition,
|
|
15
17
|
} from "../schema/connection.js";
|
|
16
18
|
|
|
@@ -123,6 +125,20 @@ function generateGCSConnection(connection: GCSConnectionDefinition): string {
|
|
|
123
125
|
return parts.join("\n");
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Generate a DynamoDB connection content
|
|
130
|
+
*/
|
|
131
|
+
function generateDynamoDBConnection(connection: DynamoDBConnectionDefinition): string {
|
|
132
|
+
const parts: string[] = [];
|
|
133
|
+
const options = connection.options;
|
|
134
|
+
|
|
135
|
+
parts.push("TYPE dynamodb");
|
|
136
|
+
parts.push(`DYNAMODB_ARN ${options.arn}`);
|
|
137
|
+
parts.push(`DYNAMODB_REGION ${options.region}`);
|
|
138
|
+
|
|
139
|
+
return parts.join("\n");
|
|
140
|
+
}
|
|
141
|
+
|
|
126
142
|
/**
|
|
127
143
|
* Generate a .connection file content from a ConnectionDefinition
|
|
128
144
|
*
|
|
@@ -160,6 +176,8 @@ export function generateConnection(
|
|
|
160
176
|
content = generateS3Connection(connection);
|
|
161
177
|
} else if (isGCSConnectionDefinition(connection)) {
|
|
162
178
|
content = generateGCSConnection(connection);
|
|
179
|
+
} else if (isDynamoDBConnectionDefinition(connection)) {
|
|
180
|
+
content = generateDynamoDBConnection(connection);
|
|
163
181
|
} else {
|
|
164
182
|
throw new Error("Unsupported connection type.");
|
|
165
183
|
}
|
|
@@ -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, defineGCSConnection } from '../schema/connection.js';
|
|
4
|
+
import { defineKafkaConnection, defineS3Connection, defineGCSConnection, defineDynamoDBConnection } 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';
|
|
@@ -629,6 +629,59 @@ describe('Datasource Generator', () => {
|
|
|
629
629
|
});
|
|
630
630
|
});
|
|
631
631
|
|
|
632
|
+
describe('DynamoDB configuration', () => {
|
|
633
|
+
it('includes DynamoDB import directives', () => {
|
|
634
|
+
const dynamoConn = defineDynamoDBConnection('my_dynamo', {
|
|
635
|
+
region: 'us-east-1',
|
|
636
|
+
arn: '{{ tb_secret("DYNAMODB_ROLE_ARN") }}',
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
const ds = defineDatasource('dynamo_events', {
|
|
640
|
+
schema: {
|
|
641
|
+
id: t.string(),
|
|
642
|
+
_record: t.string(),
|
|
643
|
+
},
|
|
644
|
+
engine: engine.replacingMergeTree({ sortingKey: ['id'] }),
|
|
645
|
+
dynamodb: {
|
|
646
|
+
connection: dynamoConn,
|
|
647
|
+
tableArn: 'arn:aws:dynamodb:us-east-1:123456789012:table/events',
|
|
648
|
+
exportBucket: 's3://my-export-bucket',
|
|
649
|
+
},
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
const result = generateDatasource(ds);
|
|
653
|
+
|
|
654
|
+
expect(result.content).toContain('IMPORT_CONNECTION_NAME my_dynamo');
|
|
655
|
+
expect(result.content).toContain(
|
|
656
|
+
'IMPORT_TABLE_ARN arn:aws:dynamodb:us-east-1:123456789012:table/events'
|
|
657
|
+
);
|
|
658
|
+
expect(result.content).toContain('IMPORT_EXPORT_BUCKET s3://my-export-bucket');
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('rejects mixing DynamoDB with other ingestion options', () => {
|
|
662
|
+
const dynamoConn = defineDynamoDBConnection('my_dynamo', {
|
|
663
|
+
region: 'us-east-1',
|
|
664
|
+
arn: '{{ tb_secret("DYNAMODB_ROLE_ARN") }}',
|
|
665
|
+
});
|
|
666
|
+
const s3Conn = defineS3Connection('my_s3', {
|
|
667
|
+
region: 'us-east-1',
|
|
668
|
+
arn: 'arn:aws:iam::123456789012:role/x',
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
expect(() =>
|
|
672
|
+
defineDatasource('mixed', {
|
|
673
|
+
schema: { id: t.string() },
|
|
674
|
+
dynamodb: {
|
|
675
|
+
connection: dynamoConn,
|
|
676
|
+
tableArn: 'arn:aws:dynamodb:us-east-1:123456789012:table/events',
|
|
677
|
+
exportBucket: 's3://my-export-bucket',
|
|
678
|
+
},
|
|
679
|
+
s3: { connection: s3Conn, bucketUri: 's3://b/*.csv' },
|
|
680
|
+
})
|
|
681
|
+
).toThrow('only define one ingestion option');
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
|
|
632
685
|
describe('Token generation', () => {
|
|
633
686
|
it('generates TOKEN lines with inline config', () => {
|
|
634
687
|
const ds = defineDatasource('test_ds', {
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
KafkaConfig,
|
|
11
11
|
S3Config,
|
|
12
12
|
GCSConfig,
|
|
13
|
+
DynamoDBConfig,
|
|
13
14
|
TokenConfig,
|
|
14
15
|
DatasourceIndex,
|
|
15
16
|
} from "../schema/datasource.js";
|
|
@@ -199,6 +200,19 @@ function generateImportConfig(importConfig: S3Config | GCSConfig): string {
|
|
|
199
200
|
return parts.join("\n");
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Generate DynamoDB import configuration lines
|
|
205
|
+
*/
|
|
206
|
+
function generateDynamoDBConfig(dynamodb: DynamoDBConfig): string {
|
|
207
|
+
const parts: string[] = [];
|
|
208
|
+
|
|
209
|
+
parts.push(`IMPORT_CONNECTION_NAME ${dynamodb.connection._name}`);
|
|
210
|
+
parts.push(`IMPORT_TABLE_ARN ${dynamodb.tableArn}`);
|
|
211
|
+
parts.push(`IMPORT_EXPORT_BUCKET ${dynamodb.exportBucket}`);
|
|
212
|
+
|
|
213
|
+
return parts.join("\n");
|
|
214
|
+
}
|
|
215
|
+
|
|
202
216
|
/**
|
|
203
217
|
* Generate forward query section
|
|
204
218
|
*/
|
|
@@ -318,9 +332,12 @@ export function generateDatasource(
|
|
|
318
332
|
datasource.options.kafka,
|
|
319
333
|
datasource.options.s3,
|
|
320
334
|
datasource.options.gcs,
|
|
335
|
+
datasource.options.dynamodb,
|
|
321
336
|
].filter(Boolean).length;
|
|
322
337
|
if (ingestionConfigCount > 1) {
|
|
323
|
-
throw new Error(
|
|
338
|
+
throw new Error(
|
|
339
|
+
"Datasource can only define one ingestion option: `kafka`, `s3`, `gcs`, or `dynamodb`."
|
|
340
|
+
);
|
|
324
341
|
}
|
|
325
342
|
|
|
326
343
|
// Add description if present
|
|
@@ -369,6 +386,12 @@ export function generateDatasource(
|
|
|
369
386
|
parts.push(generateImportConfig(datasource.options.gcs));
|
|
370
387
|
}
|
|
371
388
|
|
|
389
|
+
// Add DynamoDB configuration if present
|
|
390
|
+
if (datasource.options.dynamodb) {
|
|
391
|
+
parts.push("");
|
|
392
|
+
parts.push(generateDynamoDBConfig(datasource.options.dynamodb));
|
|
393
|
+
}
|
|
394
|
+
|
|
372
395
|
// Add forward query if present
|
|
373
396
|
const forwardQuery = generateForwardQuery(datasource.options.forwardQuery);
|
|
374
397
|
if (forwardQuery) {
|
package/src/index.ts
CHANGED
|
@@ -116,6 +116,7 @@ export type {
|
|
|
116
116
|
KafkaConfig,
|
|
117
117
|
S3Config,
|
|
118
118
|
GCSConfig,
|
|
119
|
+
DynamoDBConfig,
|
|
119
120
|
DatasourceIndex,
|
|
120
121
|
} from "./schema/datasource.js";
|
|
121
122
|
|
|
@@ -125,10 +126,12 @@ export {
|
|
|
125
126
|
createKafkaConnection,
|
|
126
127
|
defineS3Connection,
|
|
127
128
|
defineGCSConnection,
|
|
129
|
+
defineDynamoDBConnection,
|
|
128
130
|
isConnectionDefinition,
|
|
129
131
|
isKafkaConnectionDefinition,
|
|
130
132
|
isS3ConnectionDefinition,
|
|
131
133
|
isGCSConnectionDefinition,
|
|
134
|
+
isDynamoDBConnectionDefinition,
|
|
132
135
|
getConnectionType,
|
|
133
136
|
} from "./schema/connection.js";
|
|
134
137
|
export type {
|
|
@@ -142,6 +145,8 @@ export type {
|
|
|
142
145
|
S3ConnectionOptions,
|
|
143
146
|
GCSConnectionDefinition,
|
|
144
147
|
GCSConnectionOptions,
|
|
148
|
+
DynamoDBConnectionDefinition,
|
|
149
|
+
DynamoDBConnectionOptions,
|
|
145
150
|
} from "./schema/connection.js";
|
|
146
151
|
|
|
147
152
|
// ============ 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
|
+
DynamoDBConnectionModel,
|
|
7
8
|
GCSConnectionModel,
|
|
8
9
|
KafkaConnectionModel,
|
|
9
10
|
ParsedResource,
|
|
@@ -72,6 +73,9 @@ function hasSecretTemplate(resources: ParsedResource[]): boolean {
|
|
|
72
73
|
if (resource.arn) values.push(resource.arn);
|
|
73
74
|
if (resource.accessKey) values.push(resource.accessKey);
|
|
74
75
|
if (resource.secret) values.push(resource.secret);
|
|
76
|
+
} else if (resource.connectionType === "dynamodb") {
|
|
77
|
+
values.push(resource.region);
|
|
78
|
+
values.push(resource.arn);
|
|
75
79
|
} else {
|
|
76
80
|
values.push(resource.serviceAccountCredentialsJson);
|
|
77
81
|
}
|
|
@@ -95,6 +99,10 @@ function hasSecretTemplate(resources: ParsedResource[]): boolean {
|
|
|
95
99
|
if (resource.gcs.schedule) values.push(resource.gcs.schedule);
|
|
96
100
|
if (resource.gcs.fromTimestamp) values.push(resource.gcs.fromTimestamp);
|
|
97
101
|
}
|
|
102
|
+
if (resource.dynamodb) {
|
|
103
|
+
values.push(resource.dynamodb.tableArn);
|
|
104
|
+
values.push(resource.dynamodb.exportBucket);
|
|
105
|
+
}
|
|
98
106
|
continue;
|
|
99
107
|
}
|
|
100
108
|
}
|
|
@@ -388,6 +396,15 @@ function emitDatasource(ds: DatasourceModel): string {
|
|
|
388
396
|
lines.push(" },");
|
|
389
397
|
}
|
|
390
398
|
|
|
399
|
+
if (ds.dynamodb) {
|
|
400
|
+
const connectionVar = toCamelCase(ds.dynamodb.connectionName);
|
|
401
|
+
lines.push(" dynamodb: {");
|
|
402
|
+
lines.push(` connection: ${connectionVar},`);
|
|
403
|
+
lines.push(` tableArn: ${emitStringOrSecret(ds.dynamodb.tableArn)},`);
|
|
404
|
+
lines.push(` exportBucket: ${emitStringOrSecret(ds.dynamodb.exportBucket)},`);
|
|
405
|
+
lines.push(" },");
|
|
406
|
+
}
|
|
407
|
+
|
|
391
408
|
if (ds.forwardQuery) {
|
|
392
409
|
lines.push(" forwardQuery: `");
|
|
393
410
|
lines.push(escapeTemplateLiteral(ds.forwardQuery));
|
|
@@ -416,7 +433,11 @@ function emitDatasource(ds: DatasourceModel): string {
|
|
|
416
433
|
}
|
|
417
434
|
|
|
418
435
|
function emitConnection(
|
|
419
|
-
connection:
|
|
436
|
+
connection:
|
|
437
|
+
| KafkaConnectionModel
|
|
438
|
+
| S3ConnectionModel
|
|
439
|
+
| GCSConnectionModel
|
|
440
|
+
| DynamoDBConnectionModel
|
|
420
441
|
): string {
|
|
421
442
|
const variableName = toCamelCase(connection.name);
|
|
422
443
|
const lines: string[] = [];
|
|
@@ -486,6 +507,17 @@ function emitConnection(
|
|
|
486
507
|
return lines.join("\n");
|
|
487
508
|
}
|
|
488
509
|
|
|
510
|
+
if (connection.connectionType === "dynamodb") {
|
|
511
|
+
lines.push(
|
|
512
|
+
`export const ${variableName} = defineDynamoDBConnection(${escapeString(connection.name)}, {`
|
|
513
|
+
);
|
|
514
|
+
lines.push(` region: ${emitStringOrSecret(connection.region)},`);
|
|
515
|
+
lines.push(` arn: ${emitStringOrSecret(connection.arn)},`);
|
|
516
|
+
lines.push("});");
|
|
517
|
+
lines.push("");
|
|
518
|
+
return lines.join("\n");
|
|
519
|
+
}
|
|
520
|
+
|
|
489
521
|
lines.push(
|
|
490
522
|
`export const ${variableName} = defineGCSConnection(${escapeString(connection.name)}, {`
|
|
491
523
|
);
|
|
@@ -625,8 +657,13 @@ function emitPipe(pipe: PipeModel): string {
|
|
|
625
657
|
|
|
626
658
|
export function emitMigrationFileContent(resources: ParsedResource[]): string {
|
|
627
659
|
const connections = resources.filter(
|
|
628
|
-
(
|
|
629
|
-
resource
|
|
660
|
+
(
|
|
661
|
+
resource
|
|
662
|
+
): resource is
|
|
663
|
+
| KafkaConnectionModel
|
|
664
|
+
| S3ConnectionModel
|
|
665
|
+
| GCSConnectionModel
|
|
666
|
+
| DynamoDBConnectionModel => resource.kind === "connection"
|
|
630
667
|
);
|
|
631
668
|
const datasources = resources.filter(
|
|
632
669
|
(resource): resource is DatasourceModel => resource.kind === "datasource"
|
|
@@ -655,6 +692,9 @@ export function emitMigrationFileContent(resources: ParsedResource[]): string {
|
|
|
655
692
|
if (connections.some((connection) => connection.connectionType === "gcs")) {
|
|
656
693
|
imports.add("defineGCSConnection");
|
|
657
694
|
}
|
|
695
|
+
if (connections.some((connection) => connection.connectionType === "dynamodb")) {
|
|
696
|
+
imports.add("defineDynamoDBConnection");
|
|
697
|
+
}
|
|
658
698
|
if (needsParams) {
|
|
659
699
|
imports.add("p");
|
|
660
700
|
}
|
|
@@ -672,6 +712,7 @@ export function emitMigrationFileContent(resources: ParsedResource[]): string {
|
|
|
672
712
|
"defineKafkaConnection",
|
|
673
713
|
"defineS3Connection",
|
|
674
714
|
"defineGCSConnection",
|
|
715
|
+
"defineDynamoDBConnection",
|
|
675
716
|
"defineDatasource",
|
|
676
717
|
"definePipe",
|
|
677
718
|
"defineMaterializedView",
|
|
@@ -122,4 +122,45 @@ KAFKA_SASL_OAUTHBEARER_AWS_EXTERNAL_ID {{ tb_secret("KAFKA_AWS_EXTERNAL_ID") }}`
|
|
|
122
122
|
'{{ tb_secret("KAFKA_AWS_EXTERNAL_ID") }}'
|
|
123
123
|
);
|
|
124
124
|
});
|
|
125
|
+
|
|
126
|
+
it("parses basic DynamoDB connection", () => {
|
|
127
|
+
const result = parseConnectionFile(
|
|
128
|
+
resource(
|
|
129
|
+
"my_dynamo",
|
|
130
|
+
`TYPE dynamodb
|
|
131
|
+
DYNAMODB_ARN {{ tb_secret("DYNAMODB_ROLE_ARN") }}
|
|
132
|
+
DYNAMODB_REGION us-east-1`
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(result.connectionType).toBe("dynamodb");
|
|
137
|
+
expect(result).toHaveProperty("arn", '{{ tb_secret("DYNAMODB_ROLE_ARN") }}');
|
|
138
|
+
expect(result).toHaveProperty("region", "us-east-1");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("throws when DynamoDB connection is missing DYNAMODB_ARN", () => {
|
|
142
|
+
expect(() =>
|
|
143
|
+
parseConnectionFile(
|
|
144
|
+
resource(
|
|
145
|
+
"my_dynamo",
|
|
146
|
+
`TYPE dynamodb
|
|
147
|
+
DYNAMODB_REGION us-east-1`
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
).toThrow("DYNAMODB_ARN is required");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("throws when DynamoDB connection mixes S3 directives", () => {
|
|
154
|
+
expect(() =>
|
|
155
|
+
parseConnectionFile(
|
|
156
|
+
resource(
|
|
157
|
+
"my_dynamo",
|
|
158
|
+
`TYPE dynamodb
|
|
159
|
+
DYNAMODB_ARN {{ tb_secret("DYNAMODB_ROLE_ARN") }}
|
|
160
|
+
DYNAMODB_REGION us-east-1
|
|
161
|
+
S3_ARN arn:aws:iam::1:role/x`
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
).toThrow("Kafka/S3/GCS directives are not valid for dynamodb connections.");
|
|
165
|
+
});
|
|
125
166
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
DynamoDBConnectionModel,
|
|
2
3
|
GCSConnectionModel,
|
|
3
4
|
KafkaConnectionModel,
|
|
4
5
|
ResourceFile,
|
|
@@ -31,6 +32,8 @@ const CONNECTION_DIRECTIVES = new Set([
|
|
|
31
32
|
"S3_ACCESS_KEY",
|
|
32
33
|
"S3_SECRET",
|
|
33
34
|
"GCS_SERVICE_ACCOUNT_CREDENTIALS_JSON",
|
|
35
|
+
"DYNAMODB_ARN",
|
|
36
|
+
"DYNAMODB_REGION",
|
|
34
37
|
]);
|
|
35
38
|
|
|
36
39
|
function isConnectionDirectiveLine(line: string): boolean {
|
|
@@ -43,7 +46,7 @@ function isConnectionDirectiveLine(line: string): boolean {
|
|
|
43
46
|
|
|
44
47
|
export function parseConnectionFile(
|
|
45
48
|
resource: ResourceFile
|
|
46
|
-
): KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel {
|
|
49
|
+
): KafkaConnectionModel | S3ConnectionModel | GCSConnectionModel | DynamoDBConnectionModel {
|
|
47
50
|
const lines = splitLines(resource.content);
|
|
48
51
|
let connectionType: string | undefined;
|
|
49
52
|
|
|
@@ -74,6 +77,9 @@ export function parseConnectionFile(
|
|
|
74
77
|
let accessSecret: string | undefined;
|
|
75
78
|
let serviceAccountCredentialsJson: string | undefined;
|
|
76
79
|
|
|
80
|
+
let dynamodbArn: string | undefined;
|
|
81
|
+
let dynamodbRegion: string | undefined;
|
|
82
|
+
|
|
77
83
|
let i = 0;
|
|
78
84
|
while (i < lines.length) {
|
|
79
85
|
const rawLine = lines[i] ?? "";
|
|
@@ -177,6 +183,12 @@ export function parseConnectionFile(
|
|
|
177
183
|
}
|
|
178
184
|
serviceAccountCredentialsJson = parseQuotedValue(value);
|
|
179
185
|
break;
|
|
186
|
+
case "DYNAMODB_ARN":
|
|
187
|
+
dynamodbArn = parseQuotedValue(value);
|
|
188
|
+
break;
|
|
189
|
+
case "DYNAMODB_REGION":
|
|
190
|
+
dynamodbRegion = parseQuotedValue(value);
|
|
191
|
+
break;
|
|
180
192
|
default:
|
|
181
193
|
throw new MigrationParseError(
|
|
182
194
|
resource.filePath,
|
|
@@ -198,12 +210,20 @@ export function parseConnectionFile(
|
|
|
198
210
|
}
|
|
199
211
|
|
|
200
212
|
if (connectionType === "kafka") {
|
|
201
|
-
if (
|
|
213
|
+
if (
|
|
214
|
+
region ||
|
|
215
|
+
arn ||
|
|
216
|
+
accessKey ||
|
|
217
|
+
accessSecret ||
|
|
218
|
+
serviceAccountCredentialsJson ||
|
|
219
|
+
dynamodbArn ||
|
|
220
|
+
dynamodbRegion
|
|
221
|
+
) {
|
|
202
222
|
throw new MigrationParseError(
|
|
203
223
|
resource.filePath,
|
|
204
224
|
"connection",
|
|
205
225
|
resource.name,
|
|
206
|
-
"S3/GCS directives are not valid for kafka connections."
|
|
226
|
+
"S3/GCS/DynamoDB directives are not valid for kafka connections."
|
|
207
227
|
);
|
|
208
228
|
}
|
|
209
229
|
|
|
@@ -248,13 +268,15 @@ export function parseConnectionFile(
|
|
|
248
268
|
secret ||
|
|
249
269
|
schemaRegistryUrl ||
|
|
250
270
|
sslCaPem ||
|
|
251
|
-
serviceAccountCredentialsJson
|
|
271
|
+
serviceAccountCredentialsJson ||
|
|
272
|
+
dynamodbArn ||
|
|
273
|
+
dynamodbRegion
|
|
252
274
|
) {
|
|
253
275
|
throw new MigrationParseError(
|
|
254
276
|
resource.filePath,
|
|
255
277
|
"connection",
|
|
256
278
|
resource.name,
|
|
257
|
-
"Kafka/GCS directives are not valid for s3 connections."
|
|
279
|
+
"Kafka/GCS/DynamoDB directives are not valid for s3 connections."
|
|
258
280
|
);
|
|
259
281
|
}
|
|
260
282
|
|
|
@@ -313,13 +335,15 @@ export function parseConnectionFile(
|
|
|
313
335
|
region ||
|
|
314
336
|
arn ||
|
|
315
337
|
accessKey ||
|
|
316
|
-
accessSecret
|
|
338
|
+
accessSecret ||
|
|
339
|
+
dynamodbArn ||
|
|
340
|
+
dynamodbRegion
|
|
317
341
|
) {
|
|
318
342
|
throw new MigrationParseError(
|
|
319
343
|
resource.filePath,
|
|
320
344
|
"connection",
|
|
321
345
|
resource.name,
|
|
322
|
-
"Kafka/S3 directives are not valid for gcs connections."
|
|
346
|
+
"Kafka/S3/DynamoDB directives are not valid for gcs connections."
|
|
323
347
|
);
|
|
324
348
|
}
|
|
325
349
|
|
|
@@ -341,6 +365,61 @@ export function parseConnectionFile(
|
|
|
341
365
|
};
|
|
342
366
|
}
|
|
343
367
|
|
|
368
|
+
if (connectionType === "dynamodb") {
|
|
369
|
+
if (
|
|
370
|
+
bootstrapServers ||
|
|
371
|
+
securityProtocol ||
|
|
372
|
+
saslMechanism ||
|
|
373
|
+
saslOauthbearerMethod ||
|
|
374
|
+
saslOauthbearerAwsRegion ||
|
|
375
|
+
saslOauthbearerAwsRoleArn ||
|
|
376
|
+
saslOauthbearerAwsExternalId ||
|
|
377
|
+
key ||
|
|
378
|
+
secret ||
|
|
379
|
+
schemaRegistryUrl ||
|
|
380
|
+
sslCaPem ||
|
|
381
|
+
region ||
|
|
382
|
+
arn ||
|
|
383
|
+
accessKey ||
|
|
384
|
+
accessSecret ||
|
|
385
|
+
serviceAccountCredentialsJson
|
|
386
|
+
) {
|
|
387
|
+
throw new MigrationParseError(
|
|
388
|
+
resource.filePath,
|
|
389
|
+
"connection",
|
|
390
|
+
resource.name,
|
|
391
|
+
"Kafka/S3/GCS directives are not valid for dynamodb connections."
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!dynamodbArn) {
|
|
396
|
+
throw new MigrationParseError(
|
|
397
|
+
resource.filePath,
|
|
398
|
+
"connection",
|
|
399
|
+
resource.name,
|
|
400
|
+
"DYNAMODB_ARN is required for dynamodb connections."
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!dynamodbRegion) {
|
|
405
|
+
throw new MigrationParseError(
|
|
406
|
+
resource.filePath,
|
|
407
|
+
"connection",
|
|
408
|
+
resource.name,
|
|
409
|
+
"DYNAMODB_REGION is required for dynamodb connections."
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
kind: "connection",
|
|
415
|
+
name: resource.name,
|
|
416
|
+
filePath: resource.filePath,
|
|
417
|
+
connectionType: "dynamodb",
|
|
418
|
+
arn: dynamodbArn,
|
|
419
|
+
region: dynamodbRegion,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
344
423
|
throw new MigrationParseError(
|
|
345
424
|
resource.filePath,
|
|
346
425
|
"connection",
|