airfoil 0.3.0 → 0.4.1

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.
Files changed (42) hide show
  1. package/dist/index.js +843 -99650
  2. package/package.json +21 -22
  3. package/src/commands/cluster/index.ts +15 -42
  4. package/src/commands/cluster/namespace/create.ts +280 -73
  5. package/src/commands/cluster/namespace/delete.ts +12 -20
  6. package/src/commands/cluster/namespace/get.ts +15 -21
  7. package/src/commands/cluster/namespace/list.ts +17 -44
  8. package/src/commands/cluster/table/create.ts +279 -0
  9. package/src/commands/cluster/table/delete.ts +54 -0
  10. package/src/commands/cluster/table/get.ts +62 -0
  11. package/src/commands/cluster/table/list.ts +61 -0
  12. package/src/commands/dev.ts +73 -172
  13. package/src/commands/sql.ts +31 -62
  14. package/src/index.ts +13 -6
  15. package/src/utils/client.ts +3 -3
  16. package/src/utils/docker.ts +137 -0
  17. package/src/utils/options.ts +2 -4
  18. package/src/utils/wings.ts +72 -64
  19. package/src/commands/cluster/data-lake/create-iceberg.ts +0 -61
  20. package/src/commands/cluster/data-lake/create-parquet.ts +0 -61
  21. package/src/commands/cluster/data-lake/create.ts +0 -16
  22. package/src/commands/cluster/data-lake/delete.ts +0 -58
  23. package/src/commands/cluster/data-lake/get.ts +0 -46
  24. package/src/commands/cluster/data-lake/list.ts +0 -72
  25. package/src/commands/cluster/object-store/create-aws.ts +0 -106
  26. package/src/commands/cluster/object-store/create-azure.ts +0 -100
  27. package/src/commands/cluster/object-store/create-google.ts +0 -102
  28. package/src/commands/cluster/object-store/create-s3.ts +0 -126
  29. package/src/commands/cluster/object-store/create.ts +0 -20
  30. package/src/commands/cluster/object-store/delete.ts +0 -58
  31. package/src/commands/cluster/object-store/get.ts +0 -51
  32. package/src/commands/cluster/object-store/list.ts +0 -72
  33. package/src/commands/cluster/tenant/create.ts +0 -46
  34. package/src/commands/cluster/tenant/delete.ts +0 -62
  35. package/src/commands/cluster/tenant/get.ts +0 -44
  36. package/src/commands/cluster/tenant/list.ts +0 -60
  37. package/src/commands/cluster/topic/create.ts +0 -317
  38. package/src/commands/cluster/topic/delete.ts +0 -67
  39. package/src/commands/cluster/topic/get.ts +0 -51
  40. package/src/commands/cluster/topic/list.ts +0 -70
  41. package/src/scripts/compile-all.ts +0 -21
  42. package/src/utils/effect.ts +0 -11
@@ -1,8 +1,10 @@
1
+ import { Effect, FileSystem, Path } from "effect";
2
+ import { HttpClient, HttpClientResponse } from "effect/unstable/http";
1
3
  import { createHash } from "node:crypto";
2
4
  import { homedir } from "node:os";
3
5
  import { join } from "node:path";
4
6
 
5
- const WINGS_DIR = join(homedir(), ".airfoil", "wings");
7
+ const WINGS_DIR = join(homedir(), ".local", "share", "wings");
6
8
  const GITHUB_RELEASES_URL = "https://github.com/useairfoil/wings/releases";
7
9
 
8
10
  /**
@@ -38,79 +40,85 @@ function getPlatformString(): string {
38
40
  */
39
41
  function getBinaryFilename(isStress: boolean): string {
40
42
  const platformString = getPlatformString();
41
- return isStress
42
- ? `wings-stress-${platformString}`
43
- : `wings-${platformString}`;
43
+ return isStress ? `wings-stress-${platformString}` : `wings-${platformString}`;
44
44
  }
45
45
 
46
- export function getWingsPath(version: string, isStress = false): string {
47
- const binaryFilename = getBinaryFilename(isStress);
48
- return join(WINGS_DIR, `${binaryFilename}-${version}`);
49
- }
46
+ export const getWingsPath = (version: string, isStress = false) =>
47
+ Effect.gen(function* () {
48
+ const binaryFilename = getBinaryFilename(isStress);
49
+ const path = yield* Path.Path;
50
+ return path.join(WINGS_DIR, `${binaryFilename}-${version}`);
51
+ });
50
52
 
51
- export async function downloadWings(
52
- version: string,
53
- targetPath: string,
54
- isStress = false,
55
- ): Promise<void> {
56
- const filename = getBinaryFilename(isStress);
53
+ export const downloadWings = (version: string, targetPath: string, isStress = false) =>
54
+ Effect.gen(function* () {
55
+ const fs = yield* FileSystem.FileSystem;
56
+ const client = yield* HttpClient.HttpClient;
57
57
 
58
- const downloadPath =
59
- version === "latest" ? "latest/download" : `download/${version}`;
60
- const url = `${GITHUB_RELEASES_URL}/${downloadPath}/${filename}`;
58
+ const filename = getBinaryFilename(isStress);
59
+ const tempPath = `${targetPath}.download`;
61
60
 
62
- await Bun.$`mkdir -p ${WINGS_DIR}`;
61
+ const downloadPath = version === "latest" ? "latest/download" : `download/${version}`;
62
+ const url = `${GITHUB_RELEASES_URL}/${downloadPath}/${filename}`;
63
63
 
64
- const response = await fetch(url);
64
+ yield* fs.makeDirectory(WINGS_DIR, { recursive: true });
65
+ yield* fs.remove(tempPath).pipe(Effect.ignore);
65
66
 
66
- if (!response.ok) {
67
- throw new Error(
68
- `Failed to download Wings from ${url}: ${response.status} ${response.statusText}`,
69
- );
70
- }
67
+ const download = Effect.gen(function* () {
68
+ const response = yield* client.get(url);
69
+ const buffer = yield* HttpClientResponse.matchStatus({
70
+ 200: (response) => response.arrayBuffer,
71
+ orElse: (response) =>
72
+ Effect.fail(new Error(`Failed to download Wings from ${url}: ${response.status}`)),
73
+ })(response);
71
74
 
72
- const buffer = await response.arrayBuffer();
73
- await Bun.write(targetPath, buffer);
74
- }
75
+ yield* fs.writeFile(tempPath, new Uint8Array(buffer));
76
+ yield* verifyChecksum(version, tempPath, isStress);
77
+ yield* fs.chmod(tempPath, 0o755);
78
+ yield* fs.rename(tempPath, targetPath);
79
+ });
80
+
81
+ yield* download.pipe(Effect.onError(() => fs.remove(tempPath).pipe(Effect.ignore)));
82
+ });
75
83
 
76
84
  /**
77
85
  * Download and verify the checksum of a Wings binary
78
86
  * The hash file format is: "<hash> <path>/wings"
79
87
  */
80
- export async function verifyChecksum(
81
- version: string,
82
- filePath: string,
83
- isStress = false,
84
- ): Promise<boolean> {
85
- const filename = getBinaryFilename(isStress);
86
- const hashFilename = `${filename}-hash.txt`;
87
-
88
- const downloadPath =
89
- version === "latest" ? "latest/download" : `download/${version}`;
90
- const hashUrl = `${GITHUB_RELEASES_URL}/${downloadPath}/${hashFilename}`;
91
-
92
- const hashResponse = await fetch(hashUrl);
93
-
94
- if (!hashResponse.ok) {
95
- throw new Error(
96
- `Failed to download hash file from ${hashUrl}: ${hashResponse.status} ${hashResponse.statusText}`,
97
- );
98
- }
99
-
100
- const hashContent = await hashResponse.text();
101
-
102
- const expectedChecksum = hashContent.trim().split(/\s+/)[0];
103
-
104
- if (!expectedChecksum) {
105
- throw new Error("Invalid hash file format");
106
- }
107
-
108
- const file = Bun.file(filePath);
109
- const buffer = await file.arrayBuffer();
110
-
111
- const hash = createHash("sha256");
112
- hash.update(new Uint8Array(buffer));
113
- const actualChecksum = hash.digest("hex");
114
-
115
- return actualChecksum === expectedChecksum;
116
- }
88
+ export const verifyChecksum = (version: string, filePath: string, isStress = false) =>
89
+ Effect.gen(function* () {
90
+ const fs = yield* FileSystem.FileSystem;
91
+ const client = yield* HttpClient.HttpClient;
92
+
93
+ const filename = getBinaryFilename(isStress);
94
+ const hashFilename = `${filename}-hash.txt`;
95
+
96
+ const downloadPath = version === "latest" ? "latest/download" : `download/${version}`;
97
+ const url = `${GITHUB_RELEASES_URL}/${downloadPath}/${hashFilename}`;
98
+
99
+ const response = yield* client.get(url);
100
+ const hashContent = yield* HttpClientResponse.matchStatus({
101
+ 200: (response) => response.text,
102
+ orElse: (response) => {
103
+ return Effect.fail(
104
+ new Error(`Failed to download hash file from ${url}: ${response.status}`),
105
+ );
106
+ },
107
+ })(response);
108
+
109
+ const expectedChecksum = hashContent.trim().split(/\s+/)[0];
110
+
111
+ if (!expectedChecksum) {
112
+ return yield* Effect.fail(new Error("Invalid hash file format"));
113
+ }
114
+
115
+ const fileBuffer = yield* fs.readFile(filePath);
116
+
117
+ const hash = createHash("sha256");
118
+ hash.update(fileBuffer);
119
+ const actualChecksum = hash.digest("hex");
120
+
121
+ if (actualChecksum !== expectedChecksum) {
122
+ return yield* Effect.fail(new Error("Wings binary checksum mismatch"));
123
+ }
124
+ });
@@ -1,61 +0,0 @@
1
- import * as p from "@clack/prompts";
2
- import { WingsClusterMetadata } from "@useairfoil/wings";
3
- import { printTable } from "console-table-printer";
4
- import { Effect } from "effect";
5
- import { Command, Flag } from "effect/unstable/cli";
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 = Flag.string("parent").pipe(
11
- Flag.withDescription("Parent tenant in format: tenants/{tenant}"),
12
- );
13
-
14
- const dataLakeIdOption = Flag.string("data-lake-id").pipe(
15
- Flag.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.catch(handleCliError("Failed to create data lake"))),
61
- ).pipe(Command.withDescription("Create a new Iceberg data lake"));
@@ -1,61 +0,0 @@
1
- import * as p from "@clack/prompts";
2
- import { WingsClusterMetadata } from "@useairfoil/wings";
3
- import { printTable } from "console-table-printer";
4
- import { Effect } from "effect";
5
- import { Command, Flag } from "effect/unstable/cli";
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 = Flag.string("parent").pipe(
11
- Flag.withDescription("Parent tenant in format: tenants/{tenant}"),
12
- );
13
-
14
- const dataLakeIdOption = Flag.string("data-lake-id").pipe(
15
- Flag.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.catch(handleCliError("Failed to create data lake"))),
61
- ).pipe(Command.withDescription("Create a new Parquet data lake"));
@@ -1,16 +0,0 @@
1
- import { Effect } from "effect";
2
- import { Command } from "effect/unstable/cli";
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
- );
@@ -1,58 +0,0 @@
1
- import * as p from "@clack/prompts";
2
- import { WingsClusterMetadata } from "@useairfoil/wings";
3
- import { Effect } from "effect";
4
- import { Command, Flag } from "effect/unstable/cli";
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 = Flag.string("name").pipe(
10
- Flag.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.catch(handleCliError("Failed to delete data lake"))),
58
- ).pipe(Command.withDescription("Delete a data lake from the cluster"));
@@ -1,46 +0,0 @@
1
- import * as p from "@clack/prompts";
2
- import { WingsClusterMetadata } from "@useairfoil/wings";
3
- import { printTable } from "console-table-printer";
4
- import { Effect } from "effect";
5
- import { Command, Flag } from "effect/unstable/cli";
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 = Flag.string("name").pipe(
11
- Flag.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.catch(handleCliError("Failed to get data lake"))),
46
- ).pipe(Command.withDescription("Get details of a specific data lake"));
@@ -1,72 +0,0 @@
1
- import * as p from "@clack/prompts";
2
- import { WingsClusterMetadata } from "@useairfoil/wings";
3
- import { printTable } from "console-table-printer";
4
- import { Effect, Option } from "effect";
5
- import { Command, Flag } from "effect/unstable/cli";
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 = Flag.string("parent").pipe(
16
- Flag.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.catch(handleCliError("Failed to list data lakes"))),
72
- ).pipe(Command.withDescription("List all data lakes belonging to a tenant"));
@@ -1,106 +0,0 @@
1
- import * as p from "@clack/prompts";
2
- import { WingsClusterMetadata } from "@useairfoil/wings";
3
- import { printTable } from "console-table-printer";
4
- import { Effect, Option } from "effect";
5
- import { Command, Flag } from "effect/unstable/cli";
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 = Flag.string("parent").pipe(
11
- Flag.withDescription("Parent tenant in format: tenants/{tenant}"),
12
- );
13
-
14
- const objectStoreIdOption = Flag.string("object-store-id").pipe(
15
- Flag.withDescription("Unique identifier for the object store"),
16
- );
17
-
18
- const bucketNameOption = Flag.string("bucket-name").pipe(
19
- Flag.withDescription("AWS S3 bucket name"),
20
- );
21
-
22
- const prefixOption = Flag.string("prefix").pipe(
23
- Flag.withDescription("Bucket prefix (optional)"),
24
- Flag.optional,
25
- );
26
-
27
- const accessKeyIdOption = Flag.string("access-key-id").pipe(
28
- Flag.withDescription("AWS_ACCESS_KEY_ID"),
29
- );
30
-
31
- const secretAccessKeyOption = Flag.string("secret-access-key").pipe(
32
- Flag.withDescription("AWS_SECRET_ACCESS_KEY"),
33
- );
34
-
35
- const regionOption = Flag.string("region").pipe(
36
- Flag.withDescription("AWS_DEFAULT_REGION (e.g., us-east-1)"),
37
- Flag.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(Effect.catch(handleCliError("Failed to create AWS object store"))),
106
- ).pipe(Command.withDescription("Create a new AWS S3 object store"));
@@ -1,100 +0,0 @@
1
- import * as p from "@clack/prompts";
2
- import { WingsClusterMetadata } from "@useairfoil/wings";
3
- import { printTable } from "console-table-printer";
4
- import { Effect, Option } from "effect";
5
- import { Command, Flag } from "effect/unstable/cli";
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 = Flag.string("parent").pipe(
11
- Flag.withDescription("Parent tenant in format: tenants/{tenant}"),
12
- );
13
-
14
- const objectStoreIdOption = Flag.string("object-store-id").pipe(
15
- Flag.withDescription("Unique identifier for the object store"),
16
- );
17
-
18
- const containerNameOption = Flag.string("container-name").pipe(
19
- Flag.withDescription("Azure container name"),
20
- );
21
-
22
- const prefixOption = Flag.string("prefix").pipe(
23
- Flag.withDescription("Container prefix (optional)"),
24
- Flag.optional,
25
- );
26
-
27
- const storageAccountNameOption = Flag.string("storage-account-name").pipe(
28
- Flag.withDescription("AZURE_STORAGE_ACCOUNT_NAME"),
29
- );
30
-
31
- const storageAccountKeyOption = Flag.string("storage-account-key").pipe(
32
- Flag.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.catch(handleCliError("Failed to create Azure object store")),
99
- ),
100
- ).pipe(Command.withDescription("Create a new Azure Blob Storage object store"));