@rsdk/cli 3.3.0-next.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/CHANGELOG.md +14 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +21 -0
- package/dist/bin.js.map +1 -0
- package/dist/kafka/commands/extract/extract.command.d.ts +2 -0
- package/dist/kafka/commands/extract/extract.command.js +17 -0
- package/dist/kafka/commands/extract/extract.command.js.map +1 -0
- package/dist/kafka/commands/extract/kafka-config.extractor.d.ts +6 -0
- package/dist/kafka/commands/extract/kafka-config.extractor.js +40 -0
- package/dist/kafka/commands/extract/kafka-config.extractor.js.map +1 -0
- package/dist/kafka/commands/extract/types.d.ts +85 -0
- package/dist/kafka/commands/extract/types.js +3 -0
- package/dist/kafka/commands/extract/types.js.map +1 -0
- package/dist/kafka/commands/schema/print-schema.d.ts +5 -0
- package/dist/kafka/commands/schema/print-schema.js +11 -0
- package/dist/kafka/commands/schema/print-schema.js.map +1 -0
- package/dist/kafka/commands/schema/schema.command.d.ts +2 -0
- package/dist/kafka/commands/schema/schema.command.js +12 -0
- package/dist/kafka/commands/schema/schema.command.js.map +1 -0
- package/dist/kafka/commands/sync/check-exist.d.ts +7 -0
- package/dist/kafka/commands/sync/check-exist.js +57 -0
- package/dist/kafka/commands/sync/check-exist.js.map +1 -0
- package/dist/kafka/commands/sync/context.d.ts +3 -0
- package/dist/kafka/commands/sync/context.js +3 -0
- package/dist/kafka/commands/sync/context.js.map +1 -0
- package/dist/kafka/commands/sync/create-topic.task.d.ts +5 -0
- package/dist/kafka/commands/sync/create-topic.task.js +28 -0
- package/dist/kafka/commands/sync/create-topic.task.js.map +1 -0
- package/dist/kafka/commands/sync/get-config.d.ts +5 -0
- package/dist/kafka/commands/sync/get-config.js +28 -0
- package/dist/kafka/commands/sync/get-config.js.map +1 -0
- package/dist/kafka/commands/sync/kafka.creds-provider.d.ts +4 -0
- package/dist/kafka/commands/sync/kafka.creds-provider.js +27 -0
- package/dist/kafka/commands/sync/kafka.creds-provider.js.map +1 -0
- package/dist/kafka/commands/sync/sync.command.d.ts +2 -0
- package/dist/kafka/commands/sync/sync.command.js +49 -0
- package/dist/kafka/commands/sync/sync.command.js.map +1 -0
- package/dist/kafka/config.schema.d.ts +34 -0
- package/dist/kafka/config.schema.js +26 -0
- package/dist/kafka/config.schema.js.map +1 -0
- package/dist/kafka/schemas/string-enum.d.ts +2 -0
- package/dist/kafka/schemas/string-enum.js +14 -0
- package/dist/kafka/schemas/string-enum.js.map +1 -0
- package/package.json +29 -0
- package/src/bin.ts +26 -0
- package/src/kafka/commands/extract/extract.command.ts +16 -0
- package/src/kafka/commands/extract/kafka-config.extractor.ts +47 -0
- package/src/kafka/commands/extract/types.ts +101 -0
- package/src/kafka/commands/schema/print-schema.ts +15 -0
- package/src/kafka/commands/schema/schema.command.ts +10 -0
- package/src/kafka/commands/sync/check-exist.ts +68 -0
- package/src/kafka/commands/sync/context.ts +3 -0
- package/src/kafka/commands/sync/create-topic.task.ts +35 -0
- package/src/kafka/commands/sync/get-config.ts +25 -0
- package/src/kafka/commands/sync/kafka.creds-provider.ts +21 -0
- package/src/kafka/commands/sync/sync.command.ts +66 -0
- package/src/kafka/config.schema.ts +31 -0
- package/src/kafka/schemas/string-enum.ts +15 -0
- package/tsconfig.json +7 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Change Log
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
+
|
|
6
|
+
## [3.3.0-next.0](https://github.com/R-Vision/rsdk/compare/v3.2.3...v3.3.0-next.0) (2023-08-10)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **cli:** create cli package and kafka commands ([7583da8](https://github.com/R-Vision/rsdk/commit/7583da8f0cdf2e88ff8b0065d10f386a8bdd2713))
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **cli:** handle empty topic name in `rsdk.json` ([f053d05](https://github.com/R-Vision/rsdk/commit/f053d0581d0eeea41f849df8ce284d3307b7200d))
|
package/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const extract_command_1 = require("./kafka/commands/extract/extract.command");
|
|
6
|
+
const schema_command_1 = require("./kafka/commands/schema/schema.command");
|
|
7
|
+
const sync_command_1 = require("./kafka/commands/sync/sync.command");
|
|
8
|
+
// TODO: сделать более понятным, переиспользуемым, консистентным и контролируем
|
|
9
|
+
process.env.RSDK_CLI_MODE = 'true';
|
|
10
|
+
const kafkaCommand = new commander_1.Command('kafka');
|
|
11
|
+
commander_1.program.addCommand(kafkaCommand
|
|
12
|
+
.addCommand(sync_command_1.KafkaSyncCommand)
|
|
13
|
+
.addCommand(extract_command_1.KafkaExtractCommand)
|
|
14
|
+
.addCommand(schema_command_1.SchemaCommand));
|
|
15
|
+
const execute = () => {
|
|
16
|
+
commander_1.program.parse();
|
|
17
|
+
};
|
|
18
|
+
if (require.main === module) {
|
|
19
|
+
execute();
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";;;AACA,yCAA6C;AAE7C,8EAA+E;AAC/E,2EAAuE;AACvE,qEAAsE;AAEtE,+EAA+E;AAC/E,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,MAAM,CAAC;AAEnC,MAAM,YAAY,GAAG,IAAI,mBAAO,CAAC,OAAO,CAAC,CAAC;AAE1C,mBAAO,CAAC,UAAU,CAChB,YAAY;KACT,UAAU,CAAC,+BAAgB,CAAC;KAC5B,UAAU,CAAC,qCAAmB,CAAC;KAC/B,UAAU,CAAC,8BAAa,CAAC,CAC7B,CAAC;AAEF,MAAM,OAAO,GAAG,GAAS,EAAE;IACzB,mBAAO,CAAC,KAAK,EAAE,CAAC;AAClB,CAAC,CAAC;AAEF,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE;IAC3B,OAAO,EAAE,CAAC;CACX"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KafkaExtractCommand = void 0;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const print_schema_1 = require("../schema/print-schema");
|
|
6
|
+
const kafka_config_extractor_1 = require("./kafka-config.extractor");
|
|
7
|
+
exports.KafkaExtractCommand = new commander_1.Command('extract')
|
|
8
|
+
.description('extract kafka.yml from rsdk.json')
|
|
9
|
+
.option('--with-schema', 'print and include $schema to yaml for autocomplete')
|
|
10
|
+
.action(async (_, cmd) => {
|
|
11
|
+
const options = cmd.opts();
|
|
12
|
+
if (options.withSchema) {
|
|
13
|
+
await (0, print_schema_1.printSchema)({ outFile: print_schema_1.printDefaultOutFile, pretty: true });
|
|
14
|
+
}
|
|
15
|
+
await kafka_config_extractor_1.KafkaConfigExtractor.extractCommand(print_schema_1.printDefaultOutFile);
|
|
16
|
+
});
|
|
17
|
+
//# sourceMappingURL=extract.command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.command.js","sourceRoot":"","sources":["../../../../src/kafka/commands/extract/extract.command.ts"],"names":[],"mappings":";;;AAAA,yCAAoC;AAEpC,yDAA0E;AAE1E,qEAAgE;AAEnD,QAAA,mBAAmB,GAAG,IAAI,mBAAO,CAAC,SAAS,CAAC;KACtD,WAAW,CAAC,kCAAkC,CAAC;KAC/C,MAAM,CAAC,eAAe,EAAE,oDAAoD,CAAC;KAC7E,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,GAAY,EAAE,EAAE;IAChC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,UAAU,EAAE;QACtB,MAAM,IAAA,0BAAW,EAAC,EAAE,OAAO,EAAE,kCAAmB,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;KACnE;IACD,MAAM,6CAAoB,CAAC,cAAc,CAAC,kCAAmB,CAAC,CAAC;AACjE,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Static } from '@sinclair/typebox';
|
|
2
|
+
import type { CliKafkaConfigSchema } from '../../config.schema';
|
|
3
|
+
export declare class KafkaConfigExtractor {
|
|
4
|
+
static extractCommand(includeSchema?: string): Promise<void>;
|
|
5
|
+
static extract(): Promise<Static<typeof CliKafkaConfigSchema>>;
|
|
6
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.KafkaConfigExtractor = void 0;
|
|
7
|
+
const core_1 = require("@rsdk/core");
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
11
|
+
class KafkaConfigExtractor {
|
|
12
|
+
static async extractCommand(includeSchema) {
|
|
13
|
+
const resultKafkaConfig = await this.extract();
|
|
14
|
+
await (0, promises_1.writeFile)((0, node_path_1.resolve)('kafka.yml'), yaml_1.default.stringify({
|
|
15
|
+
...(includeSchema && { $schema: includeSchema }),
|
|
16
|
+
...resultKafkaConfig,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
static async extract() {
|
|
20
|
+
const pathToRsdkJson = (0, node_path_1.resolve)('autodoc/rsdk.json');
|
|
21
|
+
const rsdkJson = JSON.parse(await (0, promises_1.readFile)(pathToRsdkJson, 'utf8'));
|
|
22
|
+
const kafkaMetadata = rsdkJson.rsdkMetadata
|
|
23
|
+
.filter(({ scope }) => scope === 'kafka-topic')
|
|
24
|
+
.map(({ value }) => value);
|
|
25
|
+
const topics = kafkaMetadata.map((meta) => {
|
|
26
|
+
const topicName = meta.topicName;
|
|
27
|
+
if (!topicName) {
|
|
28
|
+
throw new core_1.InputException('topicName must be provided', {
|
|
29
|
+
details: meta,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
name: topicName,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
return { topics };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.KafkaConfigExtractor = KafkaConfigExtractor;
|
|
40
|
+
//# sourceMappingURL=kafka-config.extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kafka-config.extractor.js","sourceRoot":"","sources":["../../../../src/kafka/commands/extract/kafka-config.extractor.ts"],"names":[],"mappings":";;;;;;AAAA,qCAA4C;AAE5C,+CAAuD;AACvD,yCAAoC;AACpC,gDAAwB;AAMxB,MAAa,oBAAoB;IAC/B,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,aAAsB;QAChD,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAE/C,MAAM,IAAA,oBAAS,EACb,IAAA,mBAAO,EAAC,WAAW,CAAC,EACpB,cAAI,CAAC,SAAS,CAAC;YACb,GAAG,CAAC,aAAa,IAAI,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;YAChD,GAAG,iBAAiB;SACrB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,OAAO;QAClB,MAAM,cAAc,GAAG,IAAA,mBAAO,EAAC,mBAAmB,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAS,IAAI,CAAC,KAAK,CAAC,MAAM,IAAA,mBAAQ,EAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;QAE1E,MAAM,aAAa,GAAG,QAAQ,CAAC,YAAY;aACxC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,KAAK,aAAa,CAAC;aAC9C,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;QAE7B,MAAM,MAAM,GACV,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACzB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;YACjC,IAAI,CAAC,SAAS,EAAE;gBACd,MAAM,IAAI,qBAAc,CAAC,4BAA4B,EAAE;oBACrD,OAAO,EAAE,IAAI;iBACd,CAAC,CAAC;aACJ;YACD,OAAO;gBACL,IAAI,EAAE,SAAS;aAChB,CAAC;QACJ,CAAC,CAAC,CAAC;QAEL,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;CACF;AApCD,oDAoCC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export interface Root {
|
|
2
|
+
metadata: Metadata;
|
|
3
|
+
rsdkMetadata: RsdkMetadaum[];
|
|
4
|
+
}
|
|
5
|
+
export interface Metadata {
|
|
6
|
+
name: string;
|
|
7
|
+
version: string;
|
|
8
|
+
description: string;
|
|
9
|
+
protocols: string[];
|
|
10
|
+
config: Config;
|
|
11
|
+
}
|
|
12
|
+
export interface Config {
|
|
13
|
+
prefix: string;
|
|
14
|
+
sections: Section[];
|
|
15
|
+
sources: any[];
|
|
16
|
+
properties: any[];
|
|
17
|
+
}
|
|
18
|
+
export interface Section {
|
|
19
|
+
tags?: string[];
|
|
20
|
+
constructorName: string;
|
|
21
|
+
properties: Property[];
|
|
22
|
+
name?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface Property {
|
|
25
|
+
expectedInEnv: boolean;
|
|
26
|
+
key: string;
|
|
27
|
+
description: string;
|
|
28
|
+
parser: Parser;
|
|
29
|
+
defaultValue: any;
|
|
30
|
+
}
|
|
31
|
+
export interface Parser {
|
|
32
|
+
type: string;
|
|
33
|
+
description: string;
|
|
34
|
+
}
|
|
35
|
+
export interface RsdkMetadaum {
|
|
36
|
+
scope: string;
|
|
37
|
+
value: Value;
|
|
38
|
+
}
|
|
39
|
+
export interface Value {
|
|
40
|
+
global?: boolean;
|
|
41
|
+
imports?: Import[];
|
|
42
|
+
providers?: Provider2[];
|
|
43
|
+
exports?: string[];
|
|
44
|
+
token?: string;
|
|
45
|
+
partitionKeyField: any;
|
|
46
|
+
topicName?: string;
|
|
47
|
+
group?: string;
|
|
48
|
+
type?: number;
|
|
49
|
+
eventType?: EventType;
|
|
50
|
+
options?: Options3;
|
|
51
|
+
constructorName?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface Import {
|
|
54
|
+
providers: Provider[];
|
|
55
|
+
exports: Export[];
|
|
56
|
+
}
|
|
57
|
+
export interface Provider {
|
|
58
|
+
inject: Inject | undefined[];
|
|
59
|
+
}
|
|
60
|
+
export interface Inject {
|
|
61
|
+
optional: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface Export {
|
|
64
|
+
inject: Inject2 | undefined[];
|
|
65
|
+
}
|
|
66
|
+
export interface Inject2 {
|
|
67
|
+
optional: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface Provider2 {
|
|
70
|
+
provide: string;
|
|
71
|
+
useValue: UseValue;
|
|
72
|
+
}
|
|
73
|
+
export interface UseValue {
|
|
74
|
+
context: string;
|
|
75
|
+
}
|
|
76
|
+
export interface EventType {
|
|
77
|
+
$group: string;
|
|
78
|
+
$partitionKeyField: any;
|
|
79
|
+
$type: string;
|
|
80
|
+
}
|
|
81
|
+
export interface Options3 {
|
|
82
|
+
key: string;
|
|
83
|
+
scope: string;
|
|
84
|
+
description: string;
|
|
85
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/kafka/commands/extract/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.printSchema = exports.printDefaultOutFile = void 0;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const config_schema_1 = require("../../config.schema");
|
|
6
|
+
exports.printDefaultOutFile = './kafka.schema.json';
|
|
7
|
+
async function printSchema(options) {
|
|
8
|
+
await (0, promises_1.writeFile)(options?.outFile ?? exports.printDefaultOutFile, JSON.stringify(config_schema_1.CliKafkaConfigSchema, null, options?.pretty ? 2 : 0));
|
|
9
|
+
}
|
|
10
|
+
exports.printSchema = printSchema;
|
|
11
|
+
//# sourceMappingURL=print-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"print-schema.js","sourceRoot":"","sources":["../../../../src/kafka/commands/schema/print-schema.ts"],"names":[],"mappings":";;;AAAA,+CAA6C;AAE7C,uDAA2D;AAE9C,QAAA,mBAAmB,GAAG,qBAAqB,CAAC;AAElD,KAAK,UAAU,WAAW,CAAC,OAGjC;IACC,MAAM,IAAA,oBAAS,EACb,OAAO,EAAE,OAAO,IAAI,2BAAmB,EACvC,IAAI,CAAC,SAAS,CAAC,oCAAoB,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACpE,CAAC;AACJ,CAAC;AARD,kCAQC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SchemaCommand = void 0;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const print_schema_1 = require("./print-schema");
|
|
6
|
+
exports.SchemaCommand = new commander_1.Command('schema')
|
|
7
|
+
.description('print json schema for kafka.yml')
|
|
8
|
+
.option('-o, --out-file', 'output file name', 'kafka.schema.json')
|
|
9
|
+
.action(async (_, cmd) => {
|
|
10
|
+
await (0, print_schema_1.printSchema)(cmd.opts().outFile);
|
|
11
|
+
});
|
|
12
|
+
//# sourceMappingURL=schema.command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.command.js","sourceRoot":"","sources":["../../../../src/kafka/commands/schema/schema.command.ts"],"names":[],"mappings":";;;AAAA,yCAAoC;AAEpC,iDAA6C;AAEhC,QAAA,aAAa,GAAG,IAAI,mBAAO,CAAC,QAAQ,CAAC;KAC/C,WAAW,CAAC,iCAAiC,CAAC;KAC9C,MAAM,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,mBAAmB,CAAC;KACjE,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,GAAY,EAAE,EAAE;IAChC,MAAM,IAAA,0BAAW,EAAC,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Static } from '@sinclair/typebox';
|
|
2
|
+
import type { Admin } from 'kafkajs';
|
|
3
|
+
import type { Listr, ListrRendererFactory, ListrTaskWrapper } from 'listr2';
|
|
4
|
+
import type { TopicSchema } from '../../config.schema';
|
|
5
|
+
export declare class KafkaTopicConfigurator {
|
|
6
|
+
static configure(topic: Static<typeof TopicSchema>, admin: Admin, parentTask: ListrTaskWrapper<unknown, ListrRendererFactory>): Promise<Listr | void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KafkaTopicConfigurator = void 0;
|
|
4
|
+
const core_1 = require("@rsdk/core");
|
|
5
|
+
const kafkajs_1 = require("kafkajs");
|
|
6
|
+
const listr2_1 = require("listr2");
|
|
7
|
+
class KafkaTopicConfigurator {
|
|
8
|
+
static async configure(topic, admin, parentTask) {
|
|
9
|
+
const metadata = await admin.fetchTopicMetadata({
|
|
10
|
+
topics: [topic.name],
|
|
11
|
+
});
|
|
12
|
+
const partitions = metadata.topics[0].partitions.length;
|
|
13
|
+
if (topic.spec?.partitions && partitions !== topic.spec.partitions) {
|
|
14
|
+
throw new Error(`partitoins for ${topic.name} settled to ${partitions}, but expected ${topic.spec.partitions}`);
|
|
15
|
+
}
|
|
16
|
+
const topicConfig = await admin.describeConfigs({
|
|
17
|
+
resources: [
|
|
18
|
+
{
|
|
19
|
+
type: kafkajs_1.ConfigResourceTypes.TOPIC,
|
|
20
|
+
configNames: ['cleanup.policy'],
|
|
21
|
+
name: topic.name,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
includeSynonyms: true,
|
|
25
|
+
});
|
|
26
|
+
const cleanupPolicy = topicConfig.resources.find(({ resourceName }) => resourceName === topic.name);
|
|
27
|
+
if (!cleanupPolicy) {
|
|
28
|
+
throw new core_1.InternalException('cleanupPolicy not found');
|
|
29
|
+
}
|
|
30
|
+
const currentValue = cleanupPolicy?.configEntries[0].configValue;
|
|
31
|
+
const configValue = topic?.spec?.settings?.['cleanup.policy'];
|
|
32
|
+
if (configValue && currentValue !== configValue) {
|
|
33
|
+
parentTask.output = `Alter cleanup.policy from ${currentValue} to ${configValue}`;
|
|
34
|
+
await (0, listr2_1.delay)(3e3);
|
|
35
|
+
await admin.alterConfigs({
|
|
36
|
+
validateOnly: false,
|
|
37
|
+
resources: [
|
|
38
|
+
{
|
|
39
|
+
type: kafkajs_1.ConfigResourceTypes.TOPIC,
|
|
40
|
+
configEntries: [
|
|
41
|
+
{
|
|
42
|
+
value: configValue,
|
|
43
|
+
name: 'cleanup.policy',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
name: topic.name,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
parentTask.output = `Alter cleanup.policy from ${currentValue} to ${configValue} completed!`;
|
|
51
|
+
await (0, listr2_1.delay)(3e3);
|
|
52
|
+
}
|
|
53
|
+
parentTask.title = `topic ${topic.name} exists and respond config`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.KafkaTopicConfigurator = KafkaTopicConfigurator;
|
|
57
|
+
//# sourceMappingURL=check-exist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-exist.js","sourceRoot":"","sources":["../../../../src/kafka/commands/sync/check-exist.ts"],"names":[],"mappings":";;;AAAA,qCAA+C;AAG/C,qCAA8C;AAE9C,mCAA+B;AAI/B,MAAa,sBAAsB;IACjC,MAAM,CAAC,KAAK,CAAC,SAAS,CACpB,KAAiC,EACjC,KAAY,EACZ,UAA2D;QAE3D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC;YAC9C,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC;SACrB,CAAC,CAAC;QACH,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;QACxD,IAAI,KAAK,CAAC,IAAI,EAAE,UAAU,IAAI,UAAU,KAAK,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE;YAClE,MAAM,IAAI,KAAK,CACb,kBAAkB,KAAK,CAAC,IAAI,eAAe,UAAU,kBAAkB,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,CAC/F,CAAC;SACH;QACD,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC;YAC9C,SAAS,EAAE;gBACT;oBACE,IAAI,EAAE,6BAAmB,CAAC,KAAK;oBAC/B,WAAW,EAAE,CAAC,gBAAgB,CAAC;oBAC/B,IAAI,EAAE,KAAK,CAAC,IAAI;iBACjB;aACF;YACD,eAAe,EAAE,IAAI;SACtB,CAAC,CAAC;QAEH,MAAM,aAAa,GAAG,WAAW,CAAC,SAAS,CAAC,IAAI,CAC9C,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,YAAY,KAAK,KAAK,CAAC,IAAI,CAClD,CAAC;QACF,IAAI,CAAC,aAAa,EAAE;YAClB,MAAM,IAAI,wBAAiB,CAAC,yBAAyB,CAAC,CAAC;SACxD;QACD,MAAM,YAAY,GAAG,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;QACjE,MAAM,WAAW,GAAG,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,gBAAgB,CAAC,CAAC;QAE9D,IAAI,WAAW,IAAI,YAAY,KAAK,WAAW,EAAE;YAC/C,UAAU,CAAC,MAAM,GAAG,6BAA6B,YAAY,OAAO,WAAW,EAAE,CAAC;YAClF,MAAM,IAAA,cAAK,EAAC,GAAG,CAAC,CAAC;YACjB,MAAM,KAAK,CAAC,YAAY,CAAC;gBACvB,YAAY,EAAE,KAAK;gBACnB,SAAS,EAAE;oBACT;wBACE,IAAI,EAAE,6BAAmB,CAAC,KAAK;wBAC/B,aAAa,EAAE;4BACb;gCACE,KAAK,EAAE,WAAW;gCAClB,IAAI,EAAE,gBAAgB;6BACvB;yBACF;wBACD,IAAI,EAAE,KAAK,CAAC,IAAI;qBACjB;iBACF;aACF,CAAC,CAAC;YACH,UAAU,CAAC,MAAM,GAAG,6BAA6B,YAAY,OAAO,WAAW,aAAa,CAAC;YAC7F,MAAM,IAAA,cAAK,EAAC,GAAG,CAAC,CAAC;SAClB;QACD,UAAU,CAAC,KAAK,GAAG,SAAS,KAAK,CAAC,IAAI,4BAA4B,CAAC;IACrE,CAAC;CACF;AA1DD,wDA0DC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../../src/kafka/commands/sync/context.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Static } from '@sinclair/typebox';
|
|
2
|
+
import type { Admin } from 'kafkajs';
|
|
3
|
+
import type { ListrRendererFactory, ListrTaskWrapper } from 'listr2';
|
|
4
|
+
import type { TopicSchema } from '../../config.schema';
|
|
5
|
+
export declare function createTopic(topic: Static<typeof TopicSchema>, admin: Admin, parentTask: ListrTaskWrapper<unknown, ListrRendererFactory>): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createTopic = void 0;
|
|
4
|
+
const listr2_1 = require("listr2");
|
|
5
|
+
async function createTopic(topic, admin, parentTask) {
|
|
6
|
+
await (0, listr2_1.delay)(1000);
|
|
7
|
+
const numPartitions = topic.spec?.partitions ?? 2;
|
|
8
|
+
const replicationFactor = topic.spec?.replicationFactor;
|
|
9
|
+
const result = await admin.createTopics({
|
|
10
|
+
topics: [
|
|
11
|
+
{
|
|
12
|
+
topic: topic.name,
|
|
13
|
+
...(replicationFactor && {
|
|
14
|
+
replicationFactor: replicationFactor,
|
|
15
|
+
}),
|
|
16
|
+
numPartitions: numPartitions,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
if (result) {
|
|
21
|
+
console.log('create topic %s complete!', topic.name);
|
|
22
|
+
parentTask.title = `create topic ${topic.name} complete!`;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`create topic ${topic.name} failed!`);
|
|
26
|
+
}
|
|
27
|
+
exports.createTopic = createTopic;
|
|
28
|
+
//# sourceMappingURL=create-topic.task.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-topic.task.js","sourceRoot":"","sources":["../../../../src/kafka/commands/sync/create-topic.task.ts"],"names":[],"mappings":";;;AAGA,mCAA+B;AAIxB,KAAK,UAAU,WAAW,CAC/B,KAAiC,EACjC,KAAY,EACZ,UAA2D;IAE3D,MAAM,IAAA,cAAK,EAAC,IAAI,CAAC,CAAC;IAClB,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,EAAE,UAAU,IAAI,CAAC,CAAC;IAElD,MAAM,iBAAiB,GAAG,KAAK,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAExD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC;QACtC,MAAM,EAAE;YACN;gBACE,KAAK,EAAE,KAAK,CAAC,IAAI;gBACjB,GAAG,CAAC,iBAAiB,IAAI;oBACvB,iBAAiB,EAAE,iBAAiB;iBACrC,CAAC;gBACF,aAAa,EAAE,aAAa;aAC7B;SACF;KACF,CAAC,CAAC;IACH,IAAI,MAAM,EAAE;QACV,OAAO,CAAC,GAAG,CAAC,2BAA2B,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACrD,UAAU,CAAC,KAAK,GAAG,gBAAgB,KAAK,CAAC,IAAI,YAAY,CAAC;QAC1D,OAAO;KACR;IACD,MAAM,IAAI,KAAK,CAAC,gBAAgB,KAAK,CAAC,IAAI,UAAU,CAAC,CAAC;AACxD,CAAC;AA3BD,kCA2BC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.KafkaExpectedConfigProvider = void 0;
|
|
7
|
+
const core_1 = require("@rsdk/core");
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const yaml_1 = __importDefault(require("yaml"));
|
|
11
|
+
const config_schema_1 = require("../../config.schema");
|
|
12
|
+
class KafkaExpectedConfigProvider {
|
|
13
|
+
static async getConfig() {
|
|
14
|
+
const kafkaYml = await (0, promises_1.readFile)((0, node_path_1.resolve)('kafka.yml'), {
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
});
|
|
17
|
+
const config = yaml_1.default.parse(kafkaYml);
|
|
18
|
+
const isValid = config_schema_1.CliKafkaConfigSchemaChecker.Check(config);
|
|
19
|
+
if (!isValid) {
|
|
20
|
+
throw new core_1.InputException('Invalid config', {
|
|
21
|
+
cause: [...config_schema_1.CliKafkaConfigSchemaChecker.Errors(config)],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return config;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.KafkaExpectedConfigProvider = KafkaExpectedConfigProvider;
|
|
28
|
+
//# sourceMappingURL=get-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-config.js","sourceRoot":"","sources":["../../../../src/kafka/commands/sync/get-config.ts"],"names":[],"mappings":";;;;;;AAAA,qCAA4C;AAE5C,+CAA4C;AAC5C,yCAAoC;AACpC,gDAAwB;AAGxB,uDAAkE;AAElE,MAAa,2BAA2B;IACtC,MAAM,CAAC,KAAK,CAAC,SAAS;QACpB,MAAM,QAAQ,GAAG,MAAM,IAAA,mBAAQ,EAAC,IAAA,mBAAO,EAAC,WAAW,CAAC,EAAE;YACpD,QAAQ,EAAE,MAAM;SACjB,CAAC,CAAC;QACH,MAAM,MAAM,GAAwC,cAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAEzE,MAAM,OAAO,GAAG,2CAA2B,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1D,IAAI,CAAC,OAAO,EAAE;YACZ,MAAM,IAAI,qBAAc,CAAC,gBAAgB,EAAE;gBACzC,KAAK,EAAE,CAAC,GAAG,2CAA2B,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;aACvD,CAAC,CAAC;SACJ;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AAfD,kEAeC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.KafkaCredsProvider = void 0;
|
|
7
|
+
const core_1 = require("@rsdk/core");
|
|
8
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
9
|
+
class KafkaCredsProvider {
|
|
10
|
+
static getKafkaCreds() {
|
|
11
|
+
const parsed = dotenv_1.default.config();
|
|
12
|
+
if (parsed.error) {
|
|
13
|
+
throw new core_1.InternalException('Error read config', {
|
|
14
|
+
cause: parsed.error,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const kafkaHost = parsed.parsed?.KAFKA_HOST;
|
|
18
|
+
if (!kafkaHost) {
|
|
19
|
+
throw new core_1.InternalException('KAFKA_HOST not found');
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
brokers: [kafkaHost],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.KafkaCredsProvider = KafkaCredsProvider;
|
|
27
|
+
//# sourceMappingURL=kafka.creds-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kafka.creds-provider.js","sourceRoot":"","sources":["../../../../src/kafka/commands/sync/kafka.creds-provider.ts"],"names":[],"mappings":";;;;;;AAAA,qCAA+C;AAC/C,oDAA4B;AAG5B,MAAa,kBAAkB;IAC7B,MAAM,CAAC,aAAa;QAClB,MAAM,MAAM,GAAG,gBAAM,CAAC,MAAM,EAAE,CAAC;QAC/B,IAAI,MAAM,CAAC,KAAK,EAAE;YAChB,MAAM,IAAI,wBAAiB,CAAC,mBAAmB,EAAE;gBAC/C,KAAK,EAAE,MAAM,CAAC,KAAK;aACpB,CAAC,CAAC;SACJ;QACD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC;QAC5C,IAAI,CAAC,SAAS,EAAE;YACd,MAAM,IAAI,wBAAiB,CAAC,sBAAsB,CAAC,CAAC;SACrD;QACD,OAAO;YACL,OAAO,EAAE,CAAC,SAAS,CAAC;SACrB,CAAC;IACJ,CAAC;CACF;AAhBD,gDAgBC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KafkaSyncCommand = void 0;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const kafkajs_1 = require("kafkajs");
|
|
6
|
+
const listr2_1 = require("listr2");
|
|
7
|
+
const kafka_config_extractor_1 = require("../extract/kafka-config.extractor");
|
|
8
|
+
const check_exist_1 = require("./check-exist");
|
|
9
|
+
const create_topic_task_1 = require("./create-topic.task");
|
|
10
|
+
const get_config_1 = require("./get-config");
|
|
11
|
+
const kafka_creds_provider_1 = require("./kafka.creds-provider");
|
|
12
|
+
async function createAdmin(kafkaCreds) {
|
|
13
|
+
const kafka = new kafkajs_1.Kafka(kafkaCreds);
|
|
14
|
+
const admin = kafka.admin();
|
|
15
|
+
await admin.connect();
|
|
16
|
+
return admin;
|
|
17
|
+
}
|
|
18
|
+
exports.KafkaSyncCommand = new commander_1.Command('sync')
|
|
19
|
+
.description('try sync kafka state from kafka.yml file')
|
|
20
|
+
.option('--from-rsdk', 'run command based on rsdk.json, instead of kafka.yml')
|
|
21
|
+
.action(async function (_, command) {
|
|
22
|
+
const options = command.opts();
|
|
23
|
+
const config = options.fromRsdk
|
|
24
|
+
? await kafka_config_extractor_1.KafkaConfigExtractor.extract()
|
|
25
|
+
: await get_config_1.KafkaExpectedConfigProvider.getConfig();
|
|
26
|
+
const kafkaCreds = kafka_creds_provider_1.KafkaCredsProvider.getKafkaCreds();
|
|
27
|
+
const admin = await createAdmin(kafkaCreds);
|
|
28
|
+
const existedTopic = await admin.listTopics();
|
|
29
|
+
await new listr2_1.Listr(config.topics.map((topic) => ({
|
|
30
|
+
title: topic.name,
|
|
31
|
+
task: async (_ctx, parentTask) => {
|
|
32
|
+
const requireCreate = !existedTopic.includes(topic.name);
|
|
33
|
+
return parentTask.newListr([
|
|
34
|
+
{
|
|
35
|
+
title: requireCreate ? 'create' : 'configure',
|
|
36
|
+
task: async () => {
|
|
37
|
+
if (requireCreate) {
|
|
38
|
+
await (0, create_topic_task_1.createTopic)(topic, admin, parentTask);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
await check_exist_1.KafkaTopicConfigurator.configure(topic, admin, parentTask);
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
], { concurrent: false });
|
|
45
|
+
},
|
|
46
|
+
})), { concurrent: true }).run({ requireCreate: true });
|
|
47
|
+
await admin.disconnect();
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=sync.command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.command.js","sourceRoot":"","sources":["../../../../src/kafka/commands/sync/sync.command.ts"],"names":[],"mappings":";;;AAAA,yCAAoC;AAEpC,qCAAgC;AAChC,mCAA+B;AAE/B,8EAAyE;AAEzE,+CAAuD;AAEvD,2DAAkD;AAClD,6CAA2D;AAC3D,iEAA4D;AAE5D,KAAK,UAAU,WAAW,CAAC,UAAuB;IAChD,MAAM,KAAK,GAAG,IAAI,eAAK,CAAC,UAAU,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;IAE5B,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;IACtB,OAAO,KAAK,CAAC;AACf,CAAC;AAEY,QAAA,gBAAgB,GAAG,IAAI,mBAAO,CAAC,MAAM,CAAC;KAChD,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,aAAa,EAAE,sDAAsD,CAAC;KAC7E,MAAM,CAAC,KAAK,WAAW,CAAC,EAAE,OAAgB;IACzC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ;QAC7B,CAAC,CAAC,MAAM,6CAAoB,CAAC,OAAO,EAAE;QACtC,CAAC,CAAC,MAAM,wCAA2B,CAAC,SAAS,EAAE,CAAC;IAElD,MAAM,UAAU,GAAG,yCAAkB,CAAC,aAAa,EAAE,CAAC;IACtD,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;IAE5C,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE,CAAC;IAE9C,MAAM,IAAI,cAAK,CACb,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5B,KAAK,EAAE,KAAK,CAAC,IAAI;QACjB,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAA4B,EAAE;YACzD,MAAM,aAAa,GAAG,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAEzD,OAAO,UAAU,CAAC,QAAQ,CACxB;gBACE;oBACE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW;oBAC7C,IAAI,EAAE,KAAK,IAA2B,EAAE;wBACtC,IAAI,aAAa,EAAE;4BACjB,MAAM,IAAA,+BAAW,EAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;4BAC5C,OAAO;yBACR;wBACD,MAAM,oCAAsB,CAAC,SAAS,CACpC,KAAK,EACL,KAAK,EACL,UAAU,CACX,CAAC;oBACJ,CAAC;iBACF;aACF,EACD,EAAE,UAAU,EAAE,KAAK,EAAE,CACtB,CAAC;QACJ,CAAC;KACF,CAAC,CAAC,EACH,EAAE,UAAU,EAAE,IAAI,EAAE,CACrB,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/B,MAAM,KAAK,CAAC,UAAU,EAAE,CAAC;AAC3B,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare const TopicSchema: import("@sinclair/typebox").TObject<{
|
|
2
|
+
name: import("@sinclair/typebox").TString;
|
|
3
|
+
spec: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
4
|
+
partitions: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
5
|
+
replicationFactor: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
6
|
+
settings: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
7
|
+
'cleanup.policy': import("@sinclair/typebox").TUnsafe<"delete" | "compact">;
|
|
8
|
+
}>>;
|
|
9
|
+
}>>>;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const CliKafkaConfigSchema: import("@sinclair/typebox").TObject<{
|
|
12
|
+
topics: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
13
|
+
name: import("@sinclair/typebox").TString;
|
|
14
|
+
spec: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
15
|
+
partitions: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
16
|
+
replicationFactor: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
17
|
+
settings: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
18
|
+
'cleanup.policy': import("@sinclair/typebox").TUnsafe<"delete" | "compact">;
|
|
19
|
+
}>>;
|
|
20
|
+
}>>>;
|
|
21
|
+
}>>;
|
|
22
|
+
}>;
|
|
23
|
+
export declare const CliKafkaConfigSchemaChecker: import("@sinclair/typebox/compiler").TypeCheck<import("@sinclair/typebox").TObject<{
|
|
24
|
+
topics: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
25
|
+
name: import("@sinclair/typebox").TString;
|
|
26
|
+
spec: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
27
|
+
partitions: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
28
|
+
replicationFactor: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TInteger>;
|
|
29
|
+
settings: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
|
|
30
|
+
'cleanup.policy': import("@sinclair/typebox").TUnsafe<"delete" | "compact">;
|
|
31
|
+
}>>;
|
|
32
|
+
}>>>;
|
|
33
|
+
}>>;
|
|
34
|
+
}>>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CliKafkaConfigSchemaChecker = exports.CliKafkaConfigSchema = exports.TopicSchema = void 0;
|
|
4
|
+
const typebox_1 = require("@sinclair/typebox");
|
|
5
|
+
const compiler_1 = require("@sinclair/typebox/compiler");
|
|
6
|
+
const string_enum_1 = require("./schemas/string-enum");
|
|
7
|
+
const specSchema = typebox_1.Type.Optional(typebox_1.Type.Partial(typebox_1.Type.Object({
|
|
8
|
+
partitions: typebox_1.Type.Integer({
|
|
9
|
+
default: 2,
|
|
10
|
+
}),
|
|
11
|
+
replicationFactor: typebox_1.Type.Integer({
|
|
12
|
+
default: 1,
|
|
13
|
+
}),
|
|
14
|
+
settings: typebox_1.Type.Object({
|
|
15
|
+
'cleanup.policy': (0, string_enum_1.StringEnum)(['delete', 'compact']),
|
|
16
|
+
}),
|
|
17
|
+
})));
|
|
18
|
+
exports.TopicSchema = typebox_1.Type.Object({
|
|
19
|
+
name: typebox_1.Type.String(),
|
|
20
|
+
spec: typebox_1.Type.Optional(specSchema),
|
|
21
|
+
});
|
|
22
|
+
exports.CliKafkaConfigSchema = typebox_1.Type.Object({
|
|
23
|
+
topics: typebox_1.Type.Array(exports.TopicSchema),
|
|
24
|
+
});
|
|
25
|
+
exports.CliKafkaConfigSchemaChecker = compiler_1.TypeCompiler.Compile(exports.CliKafkaConfigSchema);
|
|
26
|
+
//# sourceMappingURL=config.schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.schema.js","sourceRoot":"","sources":["../../src/kafka/config.schema.ts"],"names":[],"mappings":";;;AAAA,+CAAyC;AACzC,yDAA0D;AAE1D,uDAAmD;AAEnD,MAAM,UAAU,GAAG,cAAI,CAAC,QAAQ,CAC9B,cAAI,CAAC,OAAO,CACV,cAAI,CAAC,MAAM,CAAC;IACV,UAAU,EAAE,cAAI,CAAC,OAAO,CAAC;QACvB,OAAO,EAAE,CAAC;KACX,CAAC;IACF,iBAAiB,EAAE,cAAI,CAAC,OAAO,CAAC;QAC9B,OAAO,EAAE,CAAC;KACX,CAAC;IACF,QAAQ,EAAE,cAAI,CAAC,MAAM,CAAC;QACpB,gBAAgB,EAAE,IAAA,wBAAU,EAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;KACpD,CAAC;CACH,CAAC,CACH,CACF,CAAC;AAEW,QAAA,WAAW,GAAG,cAAI,CAAC,MAAM,CAAC;IACrC,IAAI,EAAE,cAAI,CAAC,MAAM,EAAE;IACnB,IAAI,EAAE,cAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;CAChC,CAAC,CAAC;AAEU,QAAA,oBAAoB,GAAG,cAAI,CAAC,MAAM,CAAC;IAC9C,MAAM,EAAE,cAAI,CAAC,KAAK,CAAC,mBAAW,CAAC;CAChC,CAAC,CAAC;AACU,QAAA,2BAA2B,GACtC,uBAAY,CAAC,OAAO,CAAC,4BAAoB,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StringEnum = void 0;
|
|
4
|
+
const typebox_1 = require("@sinclair/typebox");
|
|
5
|
+
typebox_1.TypeRegistry.Set('StringEnum', (schema, value) => {
|
|
6
|
+
return schema.enum.includes(value);
|
|
7
|
+
});
|
|
8
|
+
const StringEnum = (values) => typebox_1.Type.Unsafe({
|
|
9
|
+
[typebox_1.Kind]: 'StringEnum',
|
|
10
|
+
type: 'string',
|
|
11
|
+
enum: values,
|
|
12
|
+
});
|
|
13
|
+
exports.StringEnum = StringEnum;
|
|
14
|
+
//# sourceMappingURL=string-enum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"string-enum.js","sourceRoot":"","sources":["../../../src/kafka/schemas/string-enum.ts"],"names":[],"mappings":";;;AACA,+CAA6D;AAE7D,sBAAY,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,MAAW,EAAE,KAAU,EAAE,EAAE;IACzD,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACrC,CAAC,CAAC,CAAC;AAEI,MAAM,UAAU,GAAG,CACxB,MAAc,EACM,EAAE,CACtB,cAAI,CAAC,MAAM,CAAY;IACrB,CAAC,cAAI,CAAC,EAAE,YAAY;IACpB,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,MAAM;CACb,CAAC,CAAC;AAPQ,QAAA,UAAU,cAOlB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rsdk/cli",
|
|
3
|
+
"version": "3.3.0-next.0",
|
|
4
|
+
"description": "entry package to cli functions",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"rsdk": "dist/bin.js"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"rsdk",
|
|
14
|
+
"cli"
|
|
15
|
+
],
|
|
16
|
+
"license": "Apache License 2.0",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@sinclair/typebox": "^0.30.4",
|
|
19
|
+
"commander": "^11.0.0",
|
|
20
|
+
"dotenv": "^16.3.1",
|
|
21
|
+
"kafkajs": "^2.2.4",
|
|
22
|
+
"listr2": "^6.6.1",
|
|
23
|
+
"yaml": "^2.3.1"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@rsdk/core": "^3.3.0-next.0"
|
|
27
|
+
},
|
|
28
|
+
"gitHead": "58a1a6d19248e79dfa11c8a25c1ead3be02710ff"
|
|
29
|
+
}
|
package/src/bin.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command, program } from 'commander';
|
|
3
|
+
|
|
4
|
+
import { KafkaExtractCommand } from './kafka/commands/extract/extract.command';
|
|
5
|
+
import { SchemaCommand } from './kafka/commands/schema/schema.command';
|
|
6
|
+
import { KafkaSyncCommand } from './kafka/commands/sync/sync.command';
|
|
7
|
+
|
|
8
|
+
// TODO: сделать более понятным, переиспользуемым, консистентным и контролируем
|
|
9
|
+
process.env.RSDK_CLI_MODE = 'true';
|
|
10
|
+
|
|
11
|
+
const kafkaCommand = new Command('kafka');
|
|
12
|
+
|
|
13
|
+
program.addCommand(
|
|
14
|
+
kafkaCommand
|
|
15
|
+
.addCommand(KafkaSyncCommand)
|
|
16
|
+
.addCommand(KafkaExtractCommand)
|
|
17
|
+
.addCommand(SchemaCommand),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const execute = (): void => {
|
|
21
|
+
program.parse();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (require.main === module) {
|
|
25
|
+
execute();
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
import { printDefaultOutFile, printSchema } from '../schema/print-schema';
|
|
4
|
+
|
|
5
|
+
import { KafkaConfigExtractor } from './kafka-config.extractor';
|
|
6
|
+
|
|
7
|
+
export const KafkaExtractCommand = new Command('extract')
|
|
8
|
+
.description('extract kafka.yml from rsdk.json')
|
|
9
|
+
.option('--with-schema', 'print and include $schema to yaml for autocomplete')
|
|
10
|
+
.action(async (_, cmd: Command) => {
|
|
11
|
+
const options = cmd.opts();
|
|
12
|
+
if (options.withSchema) {
|
|
13
|
+
await printSchema({ outFile: printDefaultOutFile, pretty: true });
|
|
14
|
+
}
|
|
15
|
+
await KafkaConfigExtractor.extractCommand(printDefaultOutFile);
|
|
16
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { InputException } from '@rsdk/core';
|
|
2
|
+
import type { Static } from '@sinclair/typebox';
|
|
3
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import yaml from 'yaml';
|
|
6
|
+
|
|
7
|
+
import type { CliKafkaConfigSchema } from '../../config.schema';
|
|
8
|
+
|
|
9
|
+
import type { Root } from './types';
|
|
10
|
+
|
|
11
|
+
export class KafkaConfigExtractor {
|
|
12
|
+
static async extractCommand(includeSchema?: string): Promise<void> {
|
|
13
|
+
const resultKafkaConfig = await this.extract();
|
|
14
|
+
|
|
15
|
+
await writeFile(
|
|
16
|
+
resolve('kafka.yml'),
|
|
17
|
+
yaml.stringify({
|
|
18
|
+
...(includeSchema && { $schema: includeSchema }),
|
|
19
|
+
...resultKafkaConfig,
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static async extract(): Promise<Static<typeof CliKafkaConfigSchema>> {
|
|
25
|
+
const pathToRsdkJson = resolve('autodoc/rsdk.json');
|
|
26
|
+
const rsdkJson: Root = JSON.parse(await readFile(pathToRsdkJson, 'utf8'));
|
|
27
|
+
|
|
28
|
+
const kafkaMetadata = rsdkJson.rsdkMetadata
|
|
29
|
+
.filter(({ scope }) => scope === 'kafka-topic')
|
|
30
|
+
.map(({ value }) => value);
|
|
31
|
+
|
|
32
|
+
const topics: Static<typeof CliKafkaConfigSchema>['topics'] =
|
|
33
|
+
kafkaMetadata.map((meta) => {
|
|
34
|
+
const topicName = meta.topicName;
|
|
35
|
+
if (!topicName) {
|
|
36
|
+
throw new InputException('topicName must be provided', {
|
|
37
|
+
details: meta,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
name: topicName,
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return { topics };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export interface Root {
|
|
2
|
+
metadata: Metadata;
|
|
3
|
+
rsdkMetadata: RsdkMetadaum[];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface Metadata {
|
|
7
|
+
name: string;
|
|
8
|
+
version: string;
|
|
9
|
+
description: string;
|
|
10
|
+
protocols: string[];
|
|
11
|
+
config: Config;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Config {
|
|
15
|
+
prefix: string;
|
|
16
|
+
sections: Section[];
|
|
17
|
+
sources: any[];
|
|
18
|
+
properties: any[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Section {
|
|
22
|
+
tags?: string[];
|
|
23
|
+
constructorName: string;
|
|
24
|
+
properties: Property[];
|
|
25
|
+
name?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface Property {
|
|
29
|
+
expectedInEnv: boolean;
|
|
30
|
+
key: string;
|
|
31
|
+
description: string;
|
|
32
|
+
parser: Parser;
|
|
33
|
+
defaultValue: any;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Parser {
|
|
37
|
+
type: string;
|
|
38
|
+
description: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface RsdkMetadaum {
|
|
42
|
+
scope: string;
|
|
43
|
+
value: Value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface Value {
|
|
47
|
+
global?: boolean;
|
|
48
|
+
imports?: Import[];
|
|
49
|
+
providers?: Provider2[];
|
|
50
|
+
exports?: string[];
|
|
51
|
+
token?: string;
|
|
52
|
+
partitionKeyField: any;
|
|
53
|
+
topicName?: string;
|
|
54
|
+
group?: string;
|
|
55
|
+
type?: number;
|
|
56
|
+
eventType?: EventType;
|
|
57
|
+
options?: Options3;
|
|
58
|
+
constructorName?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface Import {
|
|
62
|
+
providers: Provider[];
|
|
63
|
+
exports: Export[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface Provider {
|
|
67
|
+
inject: Inject | undefined[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface Inject {
|
|
71
|
+
optional: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface Export {
|
|
75
|
+
inject: Inject2 | undefined[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface Inject2 {
|
|
79
|
+
optional: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface Provider2 {
|
|
83
|
+
provide: string;
|
|
84
|
+
useValue: UseValue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface UseValue {
|
|
88
|
+
context: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface EventType {
|
|
92
|
+
$group: string;
|
|
93
|
+
$partitionKeyField: any;
|
|
94
|
+
$type: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface Options3 {
|
|
98
|
+
key: string;
|
|
99
|
+
scope: string;
|
|
100
|
+
description: string;
|
|
101
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
import { CliKafkaConfigSchema } from '../../config.schema';
|
|
4
|
+
|
|
5
|
+
export const printDefaultOutFile = './kafka.schema.json';
|
|
6
|
+
|
|
7
|
+
export async function printSchema(options?: {
|
|
8
|
+
outFile?: string;
|
|
9
|
+
pretty?: boolean;
|
|
10
|
+
}): Promise<void> {
|
|
11
|
+
await writeFile(
|
|
12
|
+
options?.outFile ?? printDefaultOutFile,
|
|
13
|
+
JSON.stringify(CliKafkaConfigSchema, null, options?.pretty ? 2 : 0),
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
import { printSchema } from './print-schema';
|
|
4
|
+
|
|
5
|
+
export const SchemaCommand = new Command('schema')
|
|
6
|
+
.description('print json schema for kafka.yml')
|
|
7
|
+
.option('-o, --out-file', 'output file name', 'kafka.schema.json')
|
|
8
|
+
.action(async (_, cmd: Command) => {
|
|
9
|
+
await printSchema(cmd.opts().outFile);
|
|
10
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { InternalException } from '@rsdk/core';
|
|
2
|
+
import type { Static } from '@sinclair/typebox';
|
|
3
|
+
import type { Admin } from 'kafkajs';
|
|
4
|
+
import { ConfigResourceTypes } from 'kafkajs';
|
|
5
|
+
import type { Listr, ListrRendererFactory, ListrTaskWrapper } from 'listr2';
|
|
6
|
+
import { delay } from 'listr2';
|
|
7
|
+
|
|
8
|
+
import type { TopicSchema } from '../../config.schema';
|
|
9
|
+
|
|
10
|
+
export class KafkaTopicConfigurator {
|
|
11
|
+
static async configure(
|
|
12
|
+
topic: Static<typeof TopicSchema>,
|
|
13
|
+
admin: Admin,
|
|
14
|
+
parentTask: ListrTaskWrapper<unknown, ListrRendererFactory>,
|
|
15
|
+
): Promise<Listr | void> {
|
|
16
|
+
const metadata = await admin.fetchTopicMetadata({
|
|
17
|
+
topics: [topic.name],
|
|
18
|
+
});
|
|
19
|
+
const partitions = metadata.topics[0].partitions.length;
|
|
20
|
+
if (topic.spec?.partitions && partitions !== topic.spec.partitions) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`partitoins for ${topic.name} settled to ${partitions}, but expected ${topic.spec.partitions}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const topicConfig = await admin.describeConfigs({
|
|
26
|
+
resources: [
|
|
27
|
+
{
|
|
28
|
+
type: ConfigResourceTypes.TOPIC,
|
|
29
|
+
configNames: ['cleanup.policy'],
|
|
30
|
+
name: topic.name,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
includeSynonyms: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const cleanupPolicy = topicConfig.resources.find(
|
|
37
|
+
({ resourceName }) => resourceName === topic.name,
|
|
38
|
+
);
|
|
39
|
+
if (!cleanupPolicy) {
|
|
40
|
+
throw new InternalException('cleanupPolicy not found');
|
|
41
|
+
}
|
|
42
|
+
const currentValue = cleanupPolicy?.configEntries[0].configValue;
|
|
43
|
+
const configValue = topic?.spec?.settings?.['cleanup.policy'];
|
|
44
|
+
|
|
45
|
+
if (configValue && currentValue !== configValue) {
|
|
46
|
+
parentTask.output = `Alter cleanup.policy from ${currentValue} to ${configValue}`;
|
|
47
|
+
await delay(3e3);
|
|
48
|
+
await admin.alterConfigs({
|
|
49
|
+
validateOnly: false,
|
|
50
|
+
resources: [
|
|
51
|
+
{
|
|
52
|
+
type: ConfigResourceTypes.TOPIC,
|
|
53
|
+
configEntries: [
|
|
54
|
+
{
|
|
55
|
+
value: configValue,
|
|
56
|
+
name: 'cleanup.policy',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
name: topic.name,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
});
|
|
63
|
+
parentTask.output = `Alter cleanup.policy from ${currentValue} to ${configValue} completed!`;
|
|
64
|
+
await delay(3e3);
|
|
65
|
+
}
|
|
66
|
+
parentTask.title = `topic ${topic.name} exists and respond config`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Static } from '@sinclair/typebox';
|
|
2
|
+
import type { Admin } from 'kafkajs';
|
|
3
|
+
import type { ListrRendererFactory, ListrTaskWrapper } from 'listr2';
|
|
4
|
+
import { delay } from 'listr2';
|
|
5
|
+
|
|
6
|
+
import type { TopicSchema } from '../../config.schema';
|
|
7
|
+
|
|
8
|
+
export async function createTopic(
|
|
9
|
+
topic: Static<typeof TopicSchema>,
|
|
10
|
+
admin: Admin,
|
|
11
|
+
parentTask: ListrTaskWrapper<unknown, ListrRendererFactory>,
|
|
12
|
+
): Promise<void> {
|
|
13
|
+
await delay(1000);
|
|
14
|
+
const numPartitions = topic.spec?.partitions ?? 2;
|
|
15
|
+
|
|
16
|
+
const replicationFactor = topic.spec?.replicationFactor;
|
|
17
|
+
|
|
18
|
+
const result = await admin.createTopics({
|
|
19
|
+
topics: [
|
|
20
|
+
{
|
|
21
|
+
topic: topic.name,
|
|
22
|
+
...(replicationFactor && {
|
|
23
|
+
replicationFactor: replicationFactor,
|
|
24
|
+
}),
|
|
25
|
+
numPartitions: numPartitions,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
if (result) {
|
|
30
|
+
console.log('create topic %s complete!', topic.name);
|
|
31
|
+
parentTask.title = `create topic ${topic.name} complete!`;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`create topic ${topic.name} failed!`);
|
|
35
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { InputException } from '@rsdk/core';
|
|
2
|
+
import type { Static } from '@sinclair/typebox';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import yaml from 'yaml';
|
|
6
|
+
|
|
7
|
+
import type { CliKafkaConfigSchema } from '../../config.schema';
|
|
8
|
+
import { CliKafkaConfigSchemaChecker } from '../../config.schema';
|
|
9
|
+
|
|
10
|
+
export class KafkaExpectedConfigProvider {
|
|
11
|
+
static async getConfig(): Promise<Static<typeof CliKafkaConfigSchema>> {
|
|
12
|
+
const kafkaYml = await readFile(resolve('kafka.yml'), {
|
|
13
|
+
encoding: 'utf8',
|
|
14
|
+
});
|
|
15
|
+
const config: Static<typeof CliKafkaConfigSchema> = yaml.parse(kafkaYml);
|
|
16
|
+
|
|
17
|
+
const isValid = CliKafkaConfigSchemaChecker.Check(config);
|
|
18
|
+
if (!isValid) {
|
|
19
|
+
throw new InputException('Invalid config', {
|
|
20
|
+
cause: [...CliKafkaConfigSchemaChecker.Errors(config)],
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return config;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { InternalException } from '@rsdk/core';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import type { KafkaConfig } from 'kafkajs';
|
|
4
|
+
|
|
5
|
+
export class KafkaCredsProvider {
|
|
6
|
+
static getKafkaCreds(): KafkaConfig {
|
|
7
|
+
const parsed = dotenv.config();
|
|
8
|
+
if (parsed.error) {
|
|
9
|
+
throw new InternalException('Error read config', {
|
|
10
|
+
cause: parsed.error,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
const kafkaHost = parsed.parsed?.KAFKA_HOST;
|
|
14
|
+
if (!kafkaHost) {
|
|
15
|
+
throw new InternalException('KAFKA_HOST not found');
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
brokers: [kafkaHost],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { Admin, KafkaConfig } from 'kafkajs';
|
|
3
|
+
import { Kafka } from 'kafkajs';
|
|
4
|
+
import { Listr } from 'listr2';
|
|
5
|
+
|
|
6
|
+
import { KafkaConfigExtractor } from '../extract/kafka-config.extractor';
|
|
7
|
+
|
|
8
|
+
import { KafkaTopicConfigurator } from './check-exist';
|
|
9
|
+
import type { Context } from './context';
|
|
10
|
+
import { createTopic } from './create-topic.task';
|
|
11
|
+
import { KafkaExpectedConfigProvider } from './get-config';
|
|
12
|
+
import { KafkaCredsProvider } from './kafka.creds-provider';
|
|
13
|
+
|
|
14
|
+
async function createAdmin(kafkaCreds: KafkaConfig): Promise<Admin> {
|
|
15
|
+
const kafka = new Kafka(kafkaCreds);
|
|
16
|
+
const admin = kafka.admin();
|
|
17
|
+
|
|
18
|
+
await admin.connect();
|
|
19
|
+
return admin;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const KafkaSyncCommand = new Command('sync')
|
|
23
|
+
.description('try sync kafka state from kafka.yml file')
|
|
24
|
+
.option('--from-rsdk', 'run command based on rsdk.json, instead of kafka.yml')
|
|
25
|
+
.action(async function (_, command: Command) {
|
|
26
|
+
const options = command.opts();
|
|
27
|
+
const config = options.fromRsdk
|
|
28
|
+
? await KafkaConfigExtractor.extract()
|
|
29
|
+
: await KafkaExpectedConfigProvider.getConfig();
|
|
30
|
+
|
|
31
|
+
const kafkaCreds = KafkaCredsProvider.getKafkaCreds();
|
|
32
|
+
const admin = await createAdmin(kafkaCreds);
|
|
33
|
+
|
|
34
|
+
const existedTopic = await admin.listTopics();
|
|
35
|
+
|
|
36
|
+
await new Listr<Context>(
|
|
37
|
+
config.topics.map((topic) => ({
|
|
38
|
+
title: topic.name,
|
|
39
|
+
task: async (_ctx, parentTask): Promise<boolean | Listr> => {
|
|
40
|
+
const requireCreate = !existedTopic.includes(topic.name);
|
|
41
|
+
|
|
42
|
+
return parentTask.newListr(
|
|
43
|
+
[
|
|
44
|
+
{
|
|
45
|
+
title: requireCreate ? 'create' : 'configure',
|
|
46
|
+
task: async (): Promise<void | Listr> => {
|
|
47
|
+
if (requireCreate) {
|
|
48
|
+
await createTopic(topic, admin, parentTask);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
await KafkaTopicConfigurator.configure(
|
|
52
|
+
topic,
|
|
53
|
+
admin,
|
|
54
|
+
parentTask,
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
{ concurrent: false },
|
|
60
|
+
);
|
|
61
|
+
},
|
|
62
|
+
})),
|
|
63
|
+
{ concurrent: true },
|
|
64
|
+
).run({ requireCreate: true });
|
|
65
|
+
await admin.disconnect();
|
|
66
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Type } from '@sinclair/typebox';
|
|
2
|
+
import { TypeCompiler } from '@sinclair/typebox/compiler';
|
|
3
|
+
|
|
4
|
+
import { StringEnum } from './schemas/string-enum';
|
|
5
|
+
|
|
6
|
+
const specSchema = Type.Optional(
|
|
7
|
+
Type.Partial(
|
|
8
|
+
Type.Object({
|
|
9
|
+
partitions: Type.Integer({
|
|
10
|
+
default: 2,
|
|
11
|
+
}),
|
|
12
|
+
replicationFactor: Type.Integer({
|
|
13
|
+
default: 1,
|
|
14
|
+
}),
|
|
15
|
+
settings: Type.Object({
|
|
16
|
+
'cleanup.policy': StringEnum(['delete', 'compact']),
|
|
17
|
+
}),
|
|
18
|
+
}),
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const TopicSchema = Type.Object({
|
|
23
|
+
name: Type.String(),
|
|
24
|
+
spec: Type.Optional(specSchema),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const CliKafkaConfigSchema = Type.Object({
|
|
28
|
+
topics: Type.Array(TopicSchema),
|
|
29
|
+
});
|
|
30
|
+
export const CliKafkaConfigSchemaChecker =
|
|
31
|
+
TypeCompiler.Compile(CliKafkaConfigSchema);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TUnsafe } from '@sinclair/typebox';
|
|
2
|
+
import { Kind, Type, TypeRegistry } from '@sinclair/typebox';
|
|
3
|
+
|
|
4
|
+
TypeRegistry.Set('StringEnum', (schema: any, value: any) => {
|
|
5
|
+
return schema.enum.includes(value);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const StringEnum = <T extends string[]>(
|
|
9
|
+
values: [...T],
|
|
10
|
+
): TUnsafe<T[number]> =>
|
|
11
|
+
Type.Unsafe<T[number]>({
|
|
12
|
+
[Kind]: 'StringEnum',
|
|
13
|
+
type: 'string',
|
|
14
|
+
enum: values,
|
|
15
|
+
});
|