airfoil 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +6 -0
- package/CHANGELOG.json +20 -0
- package/CHANGELOG.md +13 -0
- package/dist/index.js +100608 -0
- package/package.json +36 -0
- package/src/commands/cluster/data-lake/create-iceberg.ts +61 -0
- package/src/commands/cluster/data-lake/create-parquet.ts +61 -0
- package/src/commands/cluster/data-lake/create.ts +16 -0
- package/src/commands/cluster/data-lake/delete.ts +58 -0
- package/src/commands/cluster/data-lake/get.ts +46 -0
- package/src/commands/cluster/data-lake/list.ts +72 -0
- package/src/commands/cluster/index.ts +52 -0
- package/src/commands/cluster/namespace/create.ts +107 -0
- package/src/commands/cluster/namespace/delete.ts +62 -0
- package/src/commands/cluster/namespace/get.ts +52 -0
- package/src/commands/cluster/namespace/list.ts +80 -0
- package/src/commands/cluster/object-store/create-aws.ts +108 -0
- package/src/commands/cluster/object-store/create-azure.ts +100 -0
- package/src/commands/cluster/object-store/create-google.ts +102 -0
- package/src/commands/cluster/object-store/create-s3.ts +125 -0
- package/src/commands/cluster/object-store/create.ts +20 -0
- package/src/commands/cluster/object-store/delete.ts +58 -0
- package/src/commands/cluster/object-store/get.ts +51 -0
- package/src/commands/cluster/object-store/list.ts +72 -0
- package/src/commands/cluster/tenant/create.ts +48 -0
- package/src/commands/cluster/tenant/delete.ts +62 -0
- package/src/commands/cluster/tenant/get.ts +44 -0
- package/src/commands/cluster/tenant/list.ts +60 -0
- package/src/commands/cluster/topic/create.ts +306 -0
- package/src/commands/cluster/topic/delete.ts +67 -0
- package/src/commands/cluster/topic/get.ts +51 -0
- package/src/commands/cluster/topic/list.ts +76 -0
- package/src/commands/dev.ts +235 -0
- package/src/commands/sql.ts +121 -0
- package/src/index.ts +18 -0
- package/src/scripts/compile-all.ts +21 -0
- package/src/utils/client.ts +6 -0
- package/src/utils/effect.ts +11 -0
- package/src/utils/options.ts +36 -0
- package/src/utils/wings.ts +116 -0
- package/test-commands.sh +209 -0
- package/tsconfig.json +31 -0
- package/useairfoil-0.1.0.tgz +0 -0
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "airfoil",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"module": "index.ts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"airfoil": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"lint": "biome check .",
|
|
12
|
+
"lint:fix": "pnpm lint --write",
|
|
13
|
+
"format": "biome format --write .",
|
|
14
|
+
"airfoil:dev": "bun run src/index.ts",
|
|
15
|
+
"build": "bun build src/index.ts --outdir dist --target node",
|
|
16
|
+
"compile": "bun build src/index.ts --compile --outfile airfoil",
|
|
17
|
+
"compile:all": "bun run src/scripts/compile-all.ts"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/bun": "^1.3.5"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"typescript": "^5"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@useairfoil/flight": "0.0.0",
|
|
27
|
+
"@useairfoil/wings": "0.0.0",
|
|
28
|
+
"@clack/prompts": "^0.11.0",
|
|
29
|
+
"@effect/cli": "^0.73.1",
|
|
30
|
+
"@effect/platform-node": "^0.104.1",
|
|
31
|
+
"console-table-printer": "^2.15.0",
|
|
32
|
+
"effect": "^3.19.15",
|
|
33
|
+
"nice-grpc": "^2.1.13",
|
|
34
|
+
"nice-grpc-common": "^2.0.2"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { WingsClusterMetadata } from "@airfoil/wings";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import { Command, Options } from "@effect/cli";
|
|
4
|
+
import { printTable } from "console-table-printer";
|
|
5
|
+
import { Effect } from "effect";
|
|
6
|
+
import { makeClusterMetadataLayer } from "../../../utils/client.js";
|
|
7
|
+
import { handleCliError } from "../../../utils/effect.js";
|
|
8
|
+
import { hostOption, portOption } from "../../../utils/options.js";
|
|
9
|
+
|
|
10
|
+
const parentOption = Options.text("parent").pipe(
|
|
11
|
+
Options.withDescription("Parent tenant in format: tenants/{tenant}"),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const dataLakeIdOption = Options.text("data-lake-id").pipe(
|
|
15
|
+
Options.withDescription("Unique identifier for the data lake"),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const createDataLakeIcebergCommand = Command.make(
|
|
19
|
+
"iceberg",
|
|
20
|
+
{
|
|
21
|
+
parent: parentOption,
|
|
22
|
+
dataLakeId: dataLakeIdOption,
|
|
23
|
+
host: hostOption,
|
|
24
|
+
port: portOption,
|
|
25
|
+
},
|
|
26
|
+
({ parent, dataLakeId, host, port }) =>
|
|
27
|
+
Effect.gen(function* () {
|
|
28
|
+
p.intro("🏞️ Create Iceberg Data Lake");
|
|
29
|
+
|
|
30
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
31
|
+
|
|
32
|
+
const s = p.spinner();
|
|
33
|
+
s.start("Creating Iceberg data lake...");
|
|
34
|
+
|
|
35
|
+
const result = yield* WingsClusterMetadata.createDataLake({
|
|
36
|
+
parent,
|
|
37
|
+
dataLakeId,
|
|
38
|
+
dataLakeConfig: {
|
|
39
|
+
_tag: "iceberg",
|
|
40
|
+
iceberg: {},
|
|
41
|
+
},
|
|
42
|
+
}).pipe(
|
|
43
|
+
Effect.provide(layer),
|
|
44
|
+
Effect.tapError(() =>
|
|
45
|
+
Effect.sync(() => s.stop("Failed to create data lake")),
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
s.stop("Iceberg data lake created successfully");
|
|
50
|
+
|
|
51
|
+
yield* Effect.sync(() => {
|
|
52
|
+
printTable([
|
|
53
|
+
{
|
|
54
|
+
name: result.name,
|
|
55
|
+
type: "Iceberg",
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
p.outro("✓ Done");
|
|
59
|
+
});
|
|
60
|
+
}).pipe(Effect.catchAll(handleCliError("Failed to create data lake"))),
|
|
61
|
+
).pipe(Command.withDescription("Create a new Iceberg data lake"));
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { WingsClusterMetadata } from "@airfoil/wings";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import { Command, Options } from "@effect/cli";
|
|
4
|
+
import { printTable } from "console-table-printer";
|
|
5
|
+
import { Effect } from "effect";
|
|
6
|
+
import { makeClusterMetadataLayer } from "../../../utils/client.js";
|
|
7
|
+
import { handleCliError } from "../../../utils/effect.js";
|
|
8
|
+
import { hostOption, portOption } from "../../../utils/options.js";
|
|
9
|
+
|
|
10
|
+
const parentOption = Options.text("parent").pipe(
|
|
11
|
+
Options.withDescription("Parent tenant in format: tenants/{tenant}"),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const dataLakeIdOption = Options.text("data-lake-id").pipe(
|
|
15
|
+
Options.withDescription("Unique identifier for the data lake"),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
export const createDataLakeParquetCommand = Command.make(
|
|
19
|
+
"parquet",
|
|
20
|
+
{
|
|
21
|
+
parent: parentOption,
|
|
22
|
+
dataLakeId: dataLakeIdOption,
|
|
23
|
+
host: hostOption,
|
|
24
|
+
port: portOption,
|
|
25
|
+
},
|
|
26
|
+
({ parent, dataLakeId, host, port }) =>
|
|
27
|
+
Effect.gen(function* () {
|
|
28
|
+
p.intro("🏞️ Create Parquet Data Lake");
|
|
29
|
+
|
|
30
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
31
|
+
|
|
32
|
+
const s = p.spinner();
|
|
33
|
+
s.start("Creating Parquet data lake...");
|
|
34
|
+
|
|
35
|
+
const result = yield* WingsClusterMetadata.createDataLake({
|
|
36
|
+
parent,
|
|
37
|
+
dataLakeId,
|
|
38
|
+
dataLakeConfig: {
|
|
39
|
+
_tag: "parquet",
|
|
40
|
+
parquet: {},
|
|
41
|
+
},
|
|
42
|
+
}).pipe(
|
|
43
|
+
Effect.provide(layer),
|
|
44
|
+
Effect.tapError(() =>
|
|
45
|
+
Effect.sync(() => s.stop("Failed to create data lake")),
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
s.stop("Parquet data lake created successfully");
|
|
50
|
+
|
|
51
|
+
yield* Effect.sync(() => {
|
|
52
|
+
printTable([
|
|
53
|
+
{
|
|
54
|
+
name: result.name,
|
|
55
|
+
type: "Parquet",
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
p.outro("✓ Done");
|
|
59
|
+
});
|
|
60
|
+
}).pipe(Effect.catchAll(handleCliError("Failed to create data lake"))),
|
|
61
|
+
).pipe(Command.withDescription("Create a new Parquet data lake"));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from "@effect/cli";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { createDataLakeIcebergCommand } from "./create-iceberg.js";
|
|
4
|
+
import { createDataLakeParquetCommand } from "./create-parquet.js";
|
|
5
|
+
|
|
6
|
+
export const createDataLakeCommand = Command.make(
|
|
7
|
+
"create-data-lake",
|
|
8
|
+
{},
|
|
9
|
+
() => Effect.void,
|
|
10
|
+
).pipe(
|
|
11
|
+
Command.withDescription("Create a new data lake"),
|
|
12
|
+
Command.withSubcommands([
|
|
13
|
+
createDataLakeIcebergCommand,
|
|
14
|
+
createDataLakeParquetCommand,
|
|
15
|
+
]),
|
|
16
|
+
);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { WingsClusterMetadata } from "@airfoil/wings";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import { Command, Options } from "@effect/cli";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import { makeClusterMetadataLayer } from "../../../utils/client.js";
|
|
6
|
+
import { handleCliError } from "../../../utils/effect.js";
|
|
7
|
+
import { forceOption, hostOption, portOption } from "../../../utils/options.js";
|
|
8
|
+
|
|
9
|
+
const nameOption = Options.text("name").pipe(
|
|
10
|
+
Options.withDescription(
|
|
11
|
+
"Data lake name in format: tenants/{tenant}/data-lakes/{data-lake}",
|
|
12
|
+
),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const deleteDataLakeCommand = Command.make(
|
|
16
|
+
"delete-data-lake",
|
|
17
|
+
{
|
|
18
|
+
name: nameOption,
|
|
19
|
+
force: forceOption,
|
|
20
|
+
host: hostOption,
|
|
21
|
+
port: portOption,
|
|
22
|
+
},
|
|
23
|
+
({ name, force, host, port }) =>
|
|
24
|
+
Effect.gen(function* () {
|
|
25
|
+
p.intro("🗑️ Delete Data Lake");
|
|
26
|
+
|
|
27
|
+
if (!force) {
|
|
28
|
+
const confirm = yield* Effect.tryPromise({
|
|
29
|
+
try: () =>
|
|
30
|
+
p.confirm({
|
|
31
|
+
message: `Are you sure you want to delete data lake ${name}?`,
|
|
32
|
+
initialValue: false,
|
|
33
|
+
}),
|
|
34
|
+
catch: () => new Error("Failed to confirm deletion"),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
38
|
+
p.cancel("Deletion cancelled");
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
44
|
+
|
|
45
|
+
const s = p.spinner();
|
|
46
|
+
s.start("Deleting data lake...");
|
|
47
|
+
|
|
48
|
+
yield* WingsClusterMetadata.deleteDataLake({ name }).pipe(
|
|
49
|
+
Effect.provide(layer),
|
|
50
|
+
Effect.tapError(() =>
|
|
51
|
+
Effect.sync(() => s.stop("Failed to delete data lake")),
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
s.stop("Data lake deleted successfully");
|
|
56
|
+
p.outro("✓ Done");
|
|
57
|
+
}).pipe(Effect.catchAll(handleCliError("Failed to delete data lake"))),
|
|
58
|
+
).pipe(Command.withDescription("Delete a data lake from the cluster"));
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { WingsClusterMetadata } from "@airfoil/wings";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import { Command, Options } from "@effect/cli";
|
|
4
|
+
import { printTable } from "console-table-printer";
|
|
5
|
+
import { Effect } from "effect";
|
|
6
|
+
import { makeClusterMetadataLayer } from "../../../utils/client.js";
|
|
7
|
+
import { handleCliError } from "../../../utils/effect.js";
|
|
8
|
+
import { hostOption, portOption } from "../../../utils/options.js";
|
|
9
|
+
|
|
10
|
+
const nameOption = Options.text("name").pipe(
|
|
11
|
+
Options.withDescription(
|
|
12
|
+
"Data lake name in format: tenants/{tenant}/data-lakes/{data-lake}",
|
|
13
|
+
),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export const getDataLakeCommand = Command.make(
|
|
17
|
+
"get-data-lake",
|
|
18
|
+
{
|
|
19
|
+
name: nameOption,
|
|
20
|
+
host: hostOption,
|
|
21
|
+
port: portOption,
|
|
22
|
+
},
|
|
23
|
+
({ name, host, port }) =>
|
|
24
|
+
Effect.gen(function* () {
|
|
25
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
26
|
+
|
|
27
|
+
const s = p.spinner();
|
|
28
|
+
s.start("Fetching data lake...");
|
|
29
|
+
|
|
30
|
+
const dataLake = yield* WingsClusterMetadata.getDataLake({ name }).pipe(
|
|
31
|
+
Effect.provide(layer),
|
|
32
|
+
Effect.tapError(() =>
|
|
33
|
+
Effect.sync(() => s.stop("Failed to get data lake")),
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
s.stop("Data lake retrieved");
|
|
38
|
+
|
|
39
|
+
yield* Effect.sync(() => {
|
|
40
|
+
printTable([
|
|
41
|
+
{ name: dataLake.name, type: dataLake.dataLakeConfig._tag || "-" },
|
|
42
|
+
]);
|
|
43
|
+
p.outro("✓ Done");
|
|
44
|
+
});
|
|
45
|
+
}).pipe(Effect.catchAll(handleCliError("Failed to get data lake"))),
|
|
46
|
+
).pipe(Command.withDescription("Get details of a specific data lake"));
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { WingsClusterMetadata } from "@airfoil/wings";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import { Command, Options } from "@effect/cli";
|
|
4
|
+
import { printTable } from "console-table-printer";
|
|
5
|
+
import { Effect, Option } from "effect";
|
|
6
|
+
import { makeClusterMetadataLayer } from "../../../utils/client.js";
|
|
7
|
+
import { handleCliError } from "../../../utils/effect.js";
|
|
8
|
+
import {
|
|
9
|
+
hostOption,
|
|
10
|
+
pageSizeOption,
|
|
11
|
+
pageTokenOption,
|
|
12
|
+
portOption,
|
|
13
|
+
} from "../../../utils/options.js";
|
|
14
|
+
|
|
15
|
+
const parentOption = Options.text("parent").pipe(
|
|
16
|
+
Options.withDescription("Parent tenant in format: tenants/{tenant}"),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export const listDataLakesCommand = Command.make(
|
|
20
|
+
"list-data-lakes",
|
|
21
|
+
{
|
|
22
|
+
parent: parentOption,
|
|
23
|
+
pageSize: pageSizeOption,
|
|
24
|
+
pageToken: pageTokenOption,
|
|
25
|
+
host: hostOption,
|
|
26
|
+
port: portOption,
|
|
27
|
+
},
|
|
28
|
+
({ parent, pageSize, pageToken, host, port }) =>
|
|
29
|
+
Effect.gen(function* () {
|
|
30
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
31
|
+
|
|
32
|
+
const s = p.spinner();
|
|
33
|
+
s.start("Fetching data lakes...");
|
|
34
|
+
|
|
35
|
+
const response = yield* WingsClusterMetadata.listDataLakes({
|
|
36
|
+
parent,
|
|
37
|
+
pageSize,
|
|
38
|
+
pageToken: Option.getOrUndefined(pageToken),
|
|
39
|
+
}).pipe(
|
|
40
|
+
Effect.provide(layer),
|
|
41
|
+
Effect.tapError(() =>
|
|
42
|
+
Effect.sync(() => s.stop("Failed to list data lakes")),
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
s.stop(`Found ${response.dataLakes.length} data lake(s)`);
|
|
47
|
+
|
|
48
|
+
yield* Effect.sync(() => {
|
|
49
|
+
if (response.dataLakes.length === 0) {
|
|
50
|
+
p.log.warn("No data lakes found");
|
|
51
|
+
} else {
|
|
52
|
+
printTable(
|
|
53
|
+
response.dataLakes.map(
|
|
54
|
+
(dataLake: {
|
|
55
|
+
name: string;
|
|
56
|
+
dataLakeConfig: { _tag?: string | null };
|
|
57
|
+
}) => ({
|
|
58
|
+
name: dataLake.name,
|
|
59
|
+
type: dataLake.dataLakeConfig._tag || "-",
|
|
60
|
+
}),
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (response.nextPageToken) {
|
|
66
|
+
p.log.info(`Next page token: ${response.nextPageToken}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
p.outro("✓ Done");
|
|
70
|
+
});
|
|
71
|
+
}).pipe(Effect.catchAll(handleCliError("Failed to list data lakes"))),
|
|
72
|
+
).pipe(Command.withDescription("List all data lakes belonging to a tenant"));
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Command } from "@effect/cli";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { createDataLakeCommand } from "./data-lake/create.js";
|
|
4
|
+
import { deleteDataLakeCommand } from "./data-lake/delete.js";
|
|
5
|
+
import { getDataLakeCommand } from "./data-lake/get.js";
|
|
6
|
+
import { listDataLakesCommand } from "./data-lake/list.js";
|
|
7
|
+
import { createNamespaceCommand } from "./namespace/create.js";
|
|
8
|
+
import { deleteNamespaceCommand } from "./namespace/delete.js";
|
|
9
|
+
import { getNamespaceCommand } from "./namespace/get.js";
|
|
10
|
+
import { listNamespacesCommand } from "./namespace/list.js";
|
|
11
|
+
import { createObjectStoreCommand } from "./object-store/create.js";
|
|
12
|
+
import { deleteObjectStoreCommand } from "./object-store/delete.js";
|
|
13
|
+
import { getObjectStoreCommand } from "./object-store/get.js";
|
|
14
|
+
import { listObjectStoresCommand } from "./object-store/list.js";
|
|
15
|
+
import { createTenantCommand } from "./tenant/create.js";
|
|
16
|
+
import { deleteTenantCommand } from "./tenant/delete.js";
|
|
17
|
+
import { getTenantCommand } from "./tenant/get.js";
|
|
18
|
+
import { listTenantsCommand } from "./tenant/list.js";
|
|
19
|
+
import { createTopicCommand } from "./topic/create.js";
|
|
20
|
+
import { deleteTopicCommand } from "./topic/delete.js";
|
|
21
|
+
import { getTopicCommand } from "./topic/get.js";
|
|
22
|
+
import { listTopicsCommand } from "./topic/list.js";
|
|
23
|
+
|
|
24
|
+
export const clusterCommand = Command.make(
|
|
25
|
+
"cluster",
|
|
26
|
+
{},
|
|
27
|
+
() => Effect.void,
|
|
28
|
+
).pipe(
|
|
29
|
+
Command.withDescription("Interact with the cluster metadata server"),
|
|
30
|
+
Command.withSubcommands([
|
|
31
|
+
createTenantCommand,
|
|
32
|
+
listTenantsCommand,
|
|
33
|
+
getTenantCommand,
|
|
34
|
+
deleteTenantCommand,
|
|
35
|
+
createNamespaceCommand,
|
|
36
|
+
listNamespacesCommand,
|
|
37
|
+
getNamespaceCommand,
|
|
38
|
+
deleteNamespaceCommand,
|
|
39
|
+
createTopicCommand,
|
|
40
|
+
listTopicsCommand,
|
|
41
|
+
getTopicCommand,
|
|
42
|
+
deleteTopicCommand,
|
|
43
|
+
createObjectStoreCommand,
|
|
44
|
+
listObjectStoresCommand,
|
|
45
|
+
getObjectStoreCommand,
|
|
46
|
+
deleteObjectStoreCommand,
|
|
47
|
+
createDataLakeCommand,
|
|
48
|
+
listDataLakesCommand,
|
|
49
|
+
getDataLakeCommand,
|
|
50
|
+
deleteDataLakeCommand,
|
|
51
|
+
]),
|
|
52
|
+
);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { WingsClusterMetadata } from "@airfoil/wings";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import { Command, Options } from "@effect/cli";
|
|
4
|
+
import { printTable } from "console-table-printer";
|
|
5
|
+
import { Effect } from "effect";
|
|
6
|
+
import { makeClusterMetadataLayer } from "../../../utils/client.js";
|
|
7
|
+
import { handleCliError } from "../../../utils/effect.js";
|
|
8
|
+
import { hostOption, portOption } from "../../../utils/options.js";
|
|
9
|
+
|
|
10
|
+
const parentOption = Options.text("parent").pipe(
|
|
11
|
+
Options.withDescription(
|
|
12
|
+
"Parent tenant in format: tenants/{tenant} (e.g., 'tenants/default')",
|
|
13
|
+
),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const namespaceIdOption = Options.text("namespace-id").pipe(
|
|
17
|
+
Options.withDescription(
|
|
18
|
+
"Unique identifier for the namespace (e.g., 'production')",
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const flushSizeBytesOption = Options.integer("flush-size-bytes").pipe(
|
|
23
|
+
Options.withDescription(
|
|
24
|
+
"Size at which the current segment is flushed to object storage",
|
|
25
|
+
),
|
|
26
|
+
Options.withDefault(0),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const flushIntervalMillisOption = Options.integer("flush-interval-millis").pipe(
|
|
30
|
+
Options.withDescription(
|
|
31
|
+
"Maximum interval at which the current segment is flushed (milliseconds)",
|
|
32
|
+
),
|
|
33
|
+
Options.withDefault(0),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const objectStoreOption = Options.text("object-store").pipe(
|
|
37
|
+
Options.withDescription(
|
|
38
|
+
"Object store used by this namespace (format: tenants/{tenant}/object-stores/{object-store})",
|
|
39
|
+
),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const dataLakeOption = Options.text("data-lake").pipe(
|
|
43
|
+
Options.withDescription(
|
|
44
|
+
"Data lake used by this namespace (format: tenants/{tenant}/data-lakes/{data-lake})",
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export const createNamespaceCommand = Command.make(
|
|
49
|
+
"create-namespace",
|
|
50
|
+
{
|
|
51
|
+
parent: parentOption,
|
|
52
|
+
namespaceId: namespaceIdOption,
|
|
53
|
+
flushSizeBytes: flushSizeBytesOption,
|
|
54
|
+
flushIntervalMillis: flushIntervalMillisOption,
|
|
55
|
+
objectStore: objectStoreOption,
|
|
56
|
+
dataLake: dataLakeOption,
|
|
57
|
+
host: hostOption,
|
|
58
|
+
port: portOption,
|
|
59
|
+
},
|
|
60
|
+
({
|
|
61
|
+
parent,
|
|
62
|
+
namespaceId,
|
|
63
|
+
flushSizeBytes,
|
|
64
|
+
flushIntervalMillis,
|
|
65
|
+
objectStore,
|
|
66
|
+
dataLake,
|
|
67
|
+
host,
|
|
68
|
+
port,
|
|
69
|
+
}) =>
|
|
70
|
+
Effect.gen(function* () {
|
|
71
|
+
p.intro("📁 Create Namespace");
|
|
72
|
+
|
|
73
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
74
|
+
|
|
75
|
+
const s = p.spinner();
|
|
76
|
+
s.start("Creating namespace...");
|
|
77
|
+
|
|
78
|
+
const namespace = yield* WingsClusterMetadata.createNamespace({
|
|
79
|
+
parent,
|
|
80
|
+
namespaceId,
|
|
81
|
+
flushSizeBytes: BigInt(flushSizeBytes),
|
|
82
|
+
flushIntervalMillis: BigInt(flushIntervalMillis),
|
|
83
|
+
objectStore,
|
|
84
|
+
dataLake,
|
|
85
|
+
}).pipe(
|
|
86
|
+
Effect.provide(layer),
|
|
87
|
+
Effect.tapError(() =>
|
|
88
|
+
Effect.sync(() => s.stop("Failed to create namespace")),
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
s.stop("Namespace created successfully");
|
|
93
|
+
|
|
94
|
+
yield* Effect.sync(() => {
|
|
95
|
+
printTable([
|
|
96
|
+
{
|
|
97
|
+
name: namespace.name,
|
|
98
|
+
flush_size_bytes: namespace.flushSizeBytes.toString(),
|
|
99
|
+
flush_interval_millis: namespace.flushIntervalMillis.toString(),
|
|
100
|
+
object_store: namespace.objectStore || "-",
|
|
101
|
+
data_lake: namespace.dataLake || "-",
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
p.outro("✓ Done");
|
|
105
|
+
});
|
|
106
|
+
}).pipe(Effect.catchAll(handleCliError("Failed to create namespace"))),
|
|
107
|
+
).pipe(Command.withDescription("Create a new namespace belonging to a tenant"));
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { WingsClusterMetadata } from "@airfoil/wings";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import { Command, Options } from "@effect/cli";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
import { makeClusterMetadataLayer } from "../../../utils/client.js";
|
|
6
|
+
import { handleCliError } from "../../../utils/effect.js";
|
|
7
|
+
import { forceOption, hostOption, portOption } from "../../../utils/options.js";
|
|
8
|
+
|
|
9
|
+
const nameOption = Options.text("name").pipe(
|
|
10
|
+
Options.withDescription(
|
|
11
|
+
"Namespace name in format: tenants/{tenant}/namespaces/{namespace}",
|
|
12
|
+
),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const deleteNamespaceCommand = Command.make(
|
|
16
|
+
"delete-namespace",
|
|
17
|
+
{
|
|
18
|
+
name: nameOption,
|
|
19
|
+
force: forceOption,
|
|
20
|
+
host: hostOption,
|
|
21
|
+
port: portOption,
|
|
22
|
+
},
|
|
23
|
+
({ name, force, host, port }) =>
|
|
24
|
+
Effect.gen(function* () {
|
|
25
|
+
p.intro("🗑️ Delete Namespace");
|
|
26
|
+
|
|
27
|
+
if (!force) {
|
|
28
|
+
const confirm = yield* Effect.tryPromise({
|
|
29
|
+
try: () =>
|
|
30
|
+
p.confirm({
|
|
31
|
+
message: `Are you sure you want to delete namespace ${name}?`,
|
|
32
|
+
initialValue: false,
|
|
33
|
+
}),
|
|
34
|
+
catch: () => new Error("Failed to confirm deletion"),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (p.isCancel(confirm) || !confirm) {
|
|
38
|
+
p.cancel("Deletion cancelled");
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
44
|
+
|
|
45
|
+
const s = p.spinner();
|
|
46
|
+
s.start("Deleting namespace...");
|
|
47
|
+
|
|
48
|
+
yield* WingsClusterMetadata.deleteNamespace({ name }).pipe(
|
|
49
|
+
Effect.provide(layer),
|
|
50
|
+
Effect.tapError(() =>
|
|
51
|
+
Effect.sync(() => s.stop("Failed to delete namespace")),
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
s.stop("Namespace deleted successfully");
|
|
56
|
+
p.outro("✓ Done");
|
|
57
|
+
}).pipe(Effect.catchAll(handleCliError("Failed to delete namespace"))),
|
|
58
|
+
).pipe(
|
|
59
|
+
Command.withDescription(
|
|
60
|
+
"Delete a namespace from the cluster (fails if namespace has any topics)",
|
|
61
|
+
),
|
|
62
|
+
);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { WingsClusterMetadata } from "@airfoil/wings";
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import { Command, Options } from "@effect/cli";
|
|
4
|
+
import { printTable } from "console-table-printer";
|
|
5
|
+
import { Effect } from "effect";
|
|
6
|
+
import { makeClusterMetadataLayer } from "../../../utils/client.js";
|
|
7
|
+
import { handleCliError } from "../../../utils/effect.js";
|
|
8
|
+
import { hostOption, portOption } from "../../../utils/options.js";
|
|
9
|
+
|
|
10
|
+
const nameOption = Options.text("name").pipe(
|
|
11
|
+
Options.withDescription(
|
|
12
|
+
"Namespace name in format: tenants/{tenant}/namespaces/{namespace}",
|
|
13
|
+
),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export const getNamespaceCommand = Command.make(
|
|
17
|
+
"get-namespace",
|
|
18
|
+
{
|
|
19
|
+
name: nameOption,
|
|
20
|
+
host: hostOption,
|
|
21
|
+
port: portOption,
|
|
22
|
+
},
|
|
23
|
+
({ name, host, port }) =>
|
|
24
|
+
Effect.gen(function* () {
|
|
25
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
26
|
+
|
|
27
|
+
const s = p.spinner();
|
|
28
|
+
s.start("Fetching namespace...");
|
|
29
|
+
|
|
30
|
+
const namespace = yield* WingsClusterMetadata.getNamespace({ name }).pipe(
|
|
31
|
+
Effect.provide(layer),
|
|
32
|
+
Effect.tapError(() =>
|
|
33
|
+
Effect.sync(() => s.stop("Failed to get namespace")),
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
s.stop("Namespace retrieved");
|
|
38
|
+
|
|
39
|
+
yield* Effect.sync(() => {
|
|
40
|
+
printTable([
|
|
41
|
+
{
|
|
42
|
+
name: namespace.name,
|
|
43
|
+
flush_size_bytes: namespace.flushSizeBytes.toString(),
|
|
44
|
+
flush_interval_millis: namespace.flushIntervalMillis.toString(),
|
|
45
|
+
object_store: namespace.objectStore || "-",
|
|
46
|
+
data_lake: namespace.dataLake || "-",
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
p.outro("✓ Done");
|
|
50
|
+
});
|
|
51
|
+
}).pipe(Effect.catchAll(handleCliError("Failed to get namespace"))),
|
|
52
|
+
).pipe(Command.withDescription("Get details of a specific namespace"));
|