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
|
@@ -0,0 +1,80 @@
|
|
|
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(
|
|
17
|
+
"Parent tenant in format: tenants/{tenant} (e.g., 'tenants/default')",
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const listNamespacesCommand = Command.make(
|
|
22
|
+
"list-namespaces",
|
|
23
|
+
{
|
|
24
|
+
parent: parentOption,
|
|
25
|
+
pageSize: pageSizeOption,
|
|
26
|
+
pageToken: pageTokenOption,
|
|
27
|
+
host: hostOption,
|
|
28
|
+
port: portOption,
|
|
29
|
+
},
|
|
30
|
+
({ parent, pageSize, pageToken, host, port }) =>
|
|
31
|
+
Effect.gen(function* () {
|
|
32
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
33
|
+
|
|
34
|
+
const s = p.spinner();
|
|
35
|
+
s.start("Fetching namespaces...");
|
|
36
|
+
|
|
37
|
+
const response = yield* WingsClusterMetadata.listNamespaces({
|
|
38
|
+
parent,
|
|
39
|
+
pageSize,
|
|
40
|
+
pageToken: Option.getOrUndefined(pageToken),
|
|
41
|
+
}).pipe(
|
|
42
|
+
Effect.provide(layer),
|
|
43
|
+
Effect.tapError(() =>
|
|
44
|
+
Effect.sync(() => s.stop("Failed to list namespaces")),
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
s.stop(`Found ${response.namespaces.length} namespace(s)`);
|
|
49
|
+
|
|
50
|
+
yield* Effect.sync(() => {
|
|
51
|
+
if (response.namespaces.length === 0) {
|
|
52
|
+
p.log.warn("No namespaces found");
|
|
53
|
+
} else {
|
|
54
|
+
printTable(
|
|
55
|
+
response.namespaces.map(
|
|
56
|
+
(namespace: {
|
|
57
|
+
name: string;
|
|
58
|
+
flushSizeBytes: bigint;
|
|
59
|
+
flushIntervalMillis: bigint;
|
|
60
|
+
objectStore?: string | null;
|
|
61
|
+
dataLake?: string | null;
|
|
62
|
+
}) => ({
|
|
63
|
+
name: namespace.name,
|
|
64
|
+
flush_size_bytes: namespace.flushSizeBytes.toString(),
|
|
65
|
+
flush_interval_millis: namespace.flushIntervalMillis.toString(),
|
|
66
|
+
object_store: namespace.objectStore || "-",
|
|
67
|
+
data_lake: namespace.dataLake || "-",
|
|
68
|
+
}),
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (response.nextPageToken) {
|
|
74
|
+
p.log.info(`Next page token: ${response.nextPageToken}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
p.outro("✓ Done");
|
|
78
|
+
});
|
|
79
|
+
}).pipe(Effect.catchAll(handleCliError("Failed to list namespaces"))),
|
|
80
|
+
).pipe(Command.withDescription("List all namespaces belonging to a tenant"));
|
|
@@ -0,0 +1,108 @@
|
|
|
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 { 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 objectStoreIdOption = Options.text("object-store-id").pipe(
|
|
15
|
+
Options.withDescription("Unique identifier for the object store"),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const bucketNameOption = Options.text("bucket-name").pipe(
|
|
19
|
+
Options.withDescription("AWS S3 bucket name"),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const prefixOption = Options.text("prefix").pipe(
|
|
23
|
+
Options.withDescription("Bucket prefix (optional)"),
|
|
24
|
+
Options.optional,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const accessKeyIdOption = Options.text("access-key-id").pipe(
|
|
28
|
+
Options.withDescription("AWS_ACCESS_KEY_ID"),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const secretAccessKeyOption = Options.text("secret-access-key").pipe(
|
|
32
|
+
Options.withDescription("AWS_SECRET_ACCESS_KEY"),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const regionOption = Options.text("region").pipe(
|
|
36
|
+
Options.withDescription("AWS_DEFAULT_REGION (e.g., us-east-1)"),
|
|
37
|
+
Options.optional,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const createObjectStoreAwsCommand = Command.make(
|
|
41
|
+
"aws",
|
|
42
|
+
{
|
|
43
|
+
parent: parentOption,
|
|
44
|
+
objectStoreId: objectStoreIdOption,
|
|
45
|
+
bucketName: bucketNameOption,
|
|
46
|
+
prefix: prefixOption,
|
|
47
|
+
accessKeyId: accessKeyIdOption,
|
|
48
|
+
secretAccessKey: secretAccessKeyOption,
|
|
49
|
+
region: regionOption,
|
|
50
|
+
host: hostOption,
|
|
51
|
+
port: portOption,
|
|
52
|
+
},
|
|
53
|
+
({
|
|
54
|
+
parent,
|
|
55
|
+
objectStoreId,
|
|
56
|
+
bucketName,
|
|
57
|
+
prefix,
|
|
58
|
+
accessKeyId,
|
|
59
|
+
secretAccessKey,
|
|
60
|
+
region,
|
|
61
|
+
host,
|
|
62
|
+
port,
|
|
63
|
+
}) =>
|
|
64
|
+
Effect.gen(function* () {
|
|
65
|
+
p.intro("🗄️ Create AWS S3 Object Store");
|
|
66
|
+
|
|
67
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
68
|
+
|
|
69
|
+
const s = p.spinner();
|
|
70
|
+
s.start("Creating AWS S3 object store...");
|
|
71
|
+
|
|
72
|
+
const result = yield* WingsClusterMetadata.createObjectStore({
|
|
73
|
+
parent,
|
|
74
|
+
objectStoreId,
|
|
75
|
+
objectStoreConfig: {
|
|
76
|
+
_tag: "aws",
|
|
77
|
+
aws: {
|
|
78
|
+
bucketName,
|
|
79
|
+
prefix: Option.getOrUndefined(prefix),
|
|
80
|
+
accessKeyId,
|
|
81
|
+
secretAccessKey,
|
|
82
|
+
region: Option.getOrUndefined(region),
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
}).pipe(
|
|
86
|
+
Effect.provide(layer),
|
|
87
|
+
Effect.tapError(() =>
|
|
88
|
+
Effect.sync(() => s.stop("Failed to create AWS object store")),
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
s.stop("AWS S3 object store created successfully");
|
|
93
|
+
|
|
94
|
+
yield* Effect.sync(() => {
|
|
95
|
+
printTable([
|
|
96
|
+
{
|
|
97
|
+
name: result.name,
|
|
98
|
+
type: "AWS S3",
|
|
99
|
+
bucket: bucketName,
|
|
100
|
+
region: Option.getOrUndefined(region) || "-",
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
p.outro("✓ Done");
|
|
104
|
+
});
|
|
105
|
+
}).pipe(
|
|
106
|
+
Effect.catchAll(handleCliError("Failed to create AWS object store")),
|
|
107
|
+
),
|
|
108
|
+
).pipe(Command.withDescription("Create a new AWS S3 object store"));
|
|
@@ -0,0 +1,100 @@
|
|
|
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 { 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 objectStoreIdOption = Options.text("object-store-id").pipe(
|
|
15
|
+
Options.withDescription("Unique identifier for the object store"),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const containerNameOption = Options.text("container-name").pipe(
|
|
19
|
+
Options.withDescription("Azure container name"),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const prefixOption = Options.text("prefix").pipe(
|
|
23
|
+
Options.withDescription("Container prefix (optional)"),
|
|
24
|
+
Options.optional,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const storageAccountNameOption = Options.text("storage-account-name").pipe(
|
|
28
|
+
Options.withDescription("AZURE_STORAGE_ACCOUNT_NAME"),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const storageAccountKeyOption = Options.text("storage-account-key").pipe(
|
|
32
|
+
Options.withDescription("AZURE_STORAGE_ACCOUNT_KEY"),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const createObjectStoreAzureCommand = Command.make(
|
|
36
|
+
"azure",
|
|
37
|
+
{
|
|
38
|
+
parent: parentOption,
|
|
39
|
+
objectStoreId: objectStoreIdOption,
|
|
40
|
+
containerName: containerNameOption,
|
|
41
|
+
prefix: prefixOption,
|
|
42
|
+
storageAccountName: storageAccountNameOption,
|
|
43
|
+
storageAccountKey: storageAccountKeyOption,
|
|
44
|
+
host: hostOption,
|
|
45
|
+
port: portOption,
|
|
46
|
+
},
|
|
47
|
+
({
|
|
48
|
+
parent,
|
|
49
|
+
objectStoreId,
|
|
50
|
+
containerName,
|
|
51
|
+
prefix,
|
|
52
|
+
storageAccountName,
|
|
53
|
+
storageAccountKey,
|
|
54
|
+
host,
|
|
55
|
+
port,
|
|
56
|
+
}) =>
|
|
57
|
+
Effect.gen(function* () {
|
|
58
|
+
p.intro("🗄️ Create Azure Blob Storage Object Store");
|
|
59
|
+
|
|
60
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
61
|
+
|
|
62
|
+
const s = p.spinner();
|
|
63
|
+
s.start("Creating Azure object store...");
|
|
64
|
+
|
|
65
|
+
const result = yield* WingsClusterMetadata.createObjectStore({
|
|
66
|
+
parent,
|
|
67
|
+
objectStoreId,
|
|
68
|
+
objectStoreConfig: {
|
|
69
|
+
_tag: "azure",
|
|
70
|
+
azure: {
|
|
71
|
+
containerName,
|
|
72
|
+
prefix: Option.getOrUndefined(prefix),
|
|
73
|
+
storageAccountName,
|
|
74
|
+
storageAccountKey,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}).pipe(
|
|
78
|
+
Effect.provide(layer),
|
|
79
|
+
Effect.tapError(() =>
|
|
80
|
+
Effect.sync(() => s.stop("Failed to create Azure object store")),
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
s.stop("Azure object store created successfully");
|
|
85
|
+
|
|
86
|
+
yield* Effect.sync(() => {
|
|
87
|
+
printTable([
|
|
88
|
+
{
|
|
89
|
+
name: result.name,
|
|
90
|
+
type: "Azure Blob",
|
|
91
|
+
container: containerName,
|
|
92
|
+
account: storageAccountName,
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
p.outro("✓ Done");
|
|
96
|
+
});
|
|
97
|
+
}).pipe(
|
|
98
|
+
Effect.catchAll(handleCliError("Failed to create Azure object store")),
|
|
99
|
+
),
|
|
100
|
+
).pipe(Command.withDescription("Create a new Azure Blob Storage object store"));
|
|
@@ -0,0 +1,102 @@
|
|
|
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 { 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 objectStoreIdOption = Options.text("object-store-id").pipe(
|
|
15
|
+
Options.withDescription("Unique identifier for the object store"),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const bucketNameOption = Options.text("bucket-name").pipe(
|
|
19
|
+
Options.withDescription("Google Cloud Storage bucket name"),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const prefixOption = Options.text("prefix").pipe(
|
|
23
|
+
Options.withDescription("Bucket prefix (optional)"),
|
|
24
|
+
Options.optional,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const serviceAccountOption = Options.text("service-account").pipe(
|
|
28
|
+
Options.withDescription("GOOGLE_SERVICE_ACCOUNT"),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const serviceAccountKeyOption = Options.text("service-account-key").pipe(
|
|
32
|
+
Options.withDescription("GOOGLE_SERVICE_ACCOUNT_KEY"),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const createObjectStoreGoogleCommand = Command.make(
|
|
36
|
+
"google",
|
|
37
|
+
{
|
|
38
|
+
parent: parentOption,
|
|
39
|
+
objectStoreId: objectStoreIdOption,
|
|
40
|
+
bucketName: bucketNameOption,
|
|
41
|
+
prefix: prefixOption,
|
|
42
|
+
serviceAccount: serviceAccountOption,
|
|
43
|
+
serviceAccountKey: serviceAccountKeyOption,
|
|
44
|
+
host: hostOption,
|
|
45
|
+
port: portOption,
|
|
46
|
+
},
|
|
47
|
+
({
|
|
48
|
+
parent,
|
|
49
|
+
objectStoreId,
|
|
50
|
+
bucketName,
|
|
51
|
+
prefix,
|
|
52
|
+
serviceAccount,
|
|
53
|
+
serviceAccountKey,
|
|
54
|
+
host,
|
|
55
|
+
port,
|
|
56
|
+
}) =>
|
|
57
|
+
Effect.gen(function* () {
|
|
58
|
+
p.intro("🗄️ Create Google Cloud Storage Object Store");
|
|
59
|
+
|
|
60
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
61
|
+
|
|
62
|
+
const s = p.spinner();
|
|
63
|
+
s.start("Creating Google Cloud Storage object store...");
|
|
64
|
+
|
|
65
|
+
const result = yield* WingsClusterMetadata.createObjectStore({
|
|
66
|
+
parent,
|
|
67
|
+
objectStoreId,
|
|
68
|
+
objectStoreConfig: {
|
|
69
|
+
_tag: "google",
|
|
70
|
+
google: {
|
|
71
|
+
bucketName,
|
|
72
|
+
prefix: Option.getOrUndefined(prefix),
|
|
73
|
+
serviceAccount,
|
|
74
|
+
serviceAccountKey,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}).pipe(
|
|
78
|
+
Effect.provide(layer),
|
|
79
|
+
Effect.tapError(() =>
|
|
80
|
+
Effect.sync(() => s.stop("Failed to create Google object store")),
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
s.stop("Google Cloud Storage object store created successfully");
|
|
85
|
+
|
|
86
|
+
yield* Effect.sync(() => {
|
|
87
|
+
printTable([
|
|
88
|
+
{
|
|
89
|
+
name: result.name,
|
|
90
|
+
type: "Google Cloud",
|
|
91
|
+
bucket: bucketName,
|
|
92
|
+
service_account: serviceAccount,
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
p.outro("✓ Done");
|
|
96
|
+
});
|
|
97
|
+
}).pipe(
|
|
98
|
+
Effect.catchAll(handleCliError("Failed to create Google object store")),
|
|
99
|
+
),
|
|
100
|
+
).pipe(
|
|
101
|
+
Command.withDescription("Create a new Google Cloud Storage object store"),
|
|
102
|
+
);
|
|
@@ -0,0 +1,125 @@
|
|
|
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 { 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 objectStoreIdOption = Options.text("object-store-id").pipe(
|
|
15
|
+
Options.withDescription("Unique identifier for the object store"),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const bucketNameOption = Options.text("bucket-name").pipe(
|
|
19
|
+
Options.withDescription("S3 bucket name"),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const prefixOption = Options.text("prefix").pipe(
|
|
23
|
+
Options.withDescription("Bucket prefix (optional)"),
|
|
24
|
+
Options.optional,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const accessKeyIdOption = Options.text("access-key-id").pipe(
|
|
28
|
+
Options.withDescription("AWS_ACCESS_KEY_ID"),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const secretAccessKeyOption = Options.text("secret-access-key").pipe(
|
|
32
|
+
Options.withDescription("AWS_SECRET_ACCESS_KEY"),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const regionOption = Options.text("region").pipe(
|
|
36
|
+
Options.withDescription("AWS_DEFAULT_REGION"),
|
|
37
|
+
Options.optional,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const endpointOption = Options.text("endpoint").pipe(
|
|
41
|
+
Options.withDescription(
|
|
42
|
+
"AWS_ENDPOINT (e.g., https://nyc3.digitaloceanspaces.com)",
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const createObjectStoreS3Command = Command.make(
|
|
47
|
+
"s3",
|
|
48
|
+
{
|
|
49
|
+
parent: parentOption,
|
|
50
|
+
objectStoreId: objectStoreIdOption,
|
|
51
|
+
bucketName: bucketNameOption,
|
|
52
|
+
prefix: prefixOption,
|
|
53
|
+
accessKeyId: accessKeyIdOption,
|
|
54
|
+
secretAccessKey: secretAccessKeyOption,
|
|
55
|
+
region: regionOption,
|
|
56
|
+
endpoint: endpointOption,
|
|
57
|
+
host: hostOption,
|
|
58
|
+
port: portOption,
|
|
59
|
+
},
|
|
60
|
+
({
|
|
61
|
+
parent,
|
|
62
|
+
objectStoreId,
|
|
63
|
+
bucketName,
|
|
64
|
+
prefix,
|
|
65
|
+
accessKeyId,
|
|
66
|
+
secretAccessKey,
|
|
67
|
+
region,
|
|
68
|
+
endpoint,
|
|
69
|
+
host,
|
|
70
|
+
port,
|
|
71
|
+
}) =>
|
|
72
|
+
Effect.gen(function* () {
|
|
73
|
+
p.intro("🗄️ Create S3-Compatible Object Store");
|
|
74
|
+
|
|
75
|
+
const layer = makeClusterMetadataLayer(host, port);
|
|
76
|
+
|
|
77
|
+
const s = p.spinner();
|
|
78
|
+
s.start("Creating S3-compatible object store...");
|
|
79
|
+
|
|
80
|
+
const result = yield* WingsClusterMetadata.createObjectStore({
|
|
81
|
+
parent,
|
|
82
|
+
objectStoreId,
|
|
83
|
+
objectStoreConfig: {
|
|
84
|
+
_tag: "s3Compatible",
|
|
85
|
+
s3Compatible: {
|
|
86
|
+
bucketName,
|
|
87
|
+
prefix: Option.getOrUndefined(prefix),
|
|
88
|
+
accessKeyId,
|
|
89
|
+
secretAccessKey,
|
|
90
|
+
region: Option.getOrUndefined(region),
|
|
91
|
+
endpoint,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
}).pipe(
|
|
95
|
+
Effect.provide(layer),
|
|
96
|
+
Effect.tapError(() =>
|
|
97
|
+
Effect.sync(() =>
|
|
98
|
+
s.stop("Failed to create S3-compatible object store"),
|
|
99
|
+
),
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
s.stop("S3-compatible object store created successfully");
|
|
104
|
+
|
|
105
|
+
yield* Effect.sync(() => {
|
|
106
|
+
printTable([
|
|
107
|
+
{
|
|
108
|
+
name: result.name,
|
|
109
|
+
type: "S3-Compatible",
|
|
110
|
+
bucket: bucketName,
|
|
111
|
+
endpoint,
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
p.outro("✓ Done");
|
|
115
|
+
});
|
|
116
|
+
}).pipe(
|
|
117
|
+
Effect.catchAll(
|
|
118
|
+
handleCliError("Failed to create S3-compatible object store"),
|
|
119
|
+
),
|
|
120
|
+
),
|
|
121
|
+
).pipe(
|
|
122
|
+
Command.withDescription(
|
|
123
|
+
"Create a new S3-compatible object store (MinIO, DigitalOcean, etc.)",
|
|
124
|
+
),
|
|
125
|
+
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Command } from "@effect/cli";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { createObjectStoreAwsCommand } from "./create-aws.js";
|
|
4
|
+
import { createObjectStoreAzureCommand } from "./create-azure.js";
|
|
5
|
+
import { createObjectStoreGoogleCommand } from "./create-google.js";
|
|
6
|
+
import { createObjectStoreS3Command } from "./create-s3.js";
|
|
7
|
+
|
|
8
|
+
export const createObjectStoreCommand = Command.make(
|
|
9
|
+
"create-object-store",
|
|
10
|
+
{},
|
|
11
|
+
() => Effect.void,
|
|
12
|
+
).pipe(
|
|
13
|
+
Command.withDescription("Create a new object store"),
|
|
14
|
+
Command.withSubcommands([
|
|
15
|
+
createObjectStoreAwsCommand,
|
|
16
|
+
createObjectStoreAzureCommand,
|
|
17
|
+
createObjectStoreGoogleCommand,
|
|
18
|
+
createObjectStoreS3Command,
|
|
19
|
+
]),
|
|
20
|
+
);
|
|
@@ -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
|
+
"Object store name in format: tenants/{tenant}/object-stores/{object-store}",
|
|
12
|
+
),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const deleteObjectStoreCommand = Command.make(
|
|
16
|
+
"delete-object-store",
|
|
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 Object Store");
|
|
26
|
+
|
|
27
|
+
if (!force) {
|
|
28
|
+
const confirm = yield* Effect.tryPromise({
|
|
29
|
+
try: () =>
|
|
30
|
+
p.confirm({
|
|
31
|
+
message: `Are you sure you want to delete object store ${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 object store...");
|
|
47
|
+
|
|
48
|
+
yield* WingsClusterMetadata.deleteObjectStore({ name }).pipe(
|
|
49
|
+
Effect.provide(layer),
|
|
50
|
+
Effect.tapError(() =>
|
|
51
|
+
Effect.sync(() => s.stop("Failed to delete object store")),
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
s.stop("Object store deleted successfully");
|
|
56
|
+
p.outro("✓ Done");
|
|
57
|
+
}).pipe(Effect.catchAll(handleCliError("Failed to delete object store"))),
|
|
58
|
+
).pipe(Command.withDescription("Delete an object store from the cluster"));
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
"Object store name in format: tenants/{tenant}/object-stores/{object-store}",
|
|
13
|
+
),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export const getObjectStoreCommand = Command.make(
|
|
17
|
+
"get-object-store",
|
|
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 object store...");
|
|
29
|
+
|
|
30
|
+
const objectStore = yield* WingsClusterMetadata.getObjectStore({
|
|
31
|
+
name,
|
|
32
|
+
}).pipe(
|
|
33
|
+
Effect.provide(layer),
|
|
34
|
+
Effect.tapError(() =>
|
|
35
|
+
Effect.sync(() => s.stop("Failed to get object store")),
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
s.stop("Object store retrieved");
|
|
40
|
+
|
|
41
|
+
yield* Effect.sync(() => {
|
|
42
|
+
printTable([
|
|
43
|
+
{
|
|
44
|
+
name: objectStore.name,
|
|
45
|
+
type: objectStore.objectStoreConfig._tag || "-",
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
p.outro("✓ Done");
|
|
49
|
+
});
|
|
50
|
+
}).pipe(Effect.catchAll(handleCliError("Failed to get object store"))),
|
|
51
|
+
).pipe(Command.withDescription("Get details of a specific object store"));
|