@alchemy.run/daytona 0.0.0 → 2.0.0-beta.86
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/LICENSE +201 -0
- package/README.md +76 -4
- package/lib/ApiKey.d.ts +99 -0
- package/lib/ApiKey.d.ts.map +1 -0
- package/lib/ApiKey.js +152 -0
- package/lib/ApiKey.js.map +1 -0
- package/lib/AuthProvider.d.ts +55 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +280 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/Bootstrap.d.ts +8 -0
- package/lib/Bootstrap.d.ts.map +1 -0
- package/lib/Bootstrap.js +28 -0
- package/lib/Bootstrap.js.map +1 -0
- package/lib/ConnectSandbox.d.ts +72 -0
- package/lib/ConnectSandbox.d.ts.map +1 -0
- package/lib/ConnectSandbox.js +3 -0
- package/lib/ConnectSandbox.js.map +1 -0
- package/lib/ConnectSandboxHttp.d.ts +5 -0
- package/lib/ConnectSandboxHttp.d.ts.map +1 -0
- package/lib/ConnectSandboxHttp.js +22 -0
- package/lib/ConnectSandboxHttp.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/CreateSandbox.d.ts +86 -0
- package/lib/CreateSandbox.d.ts.map +1 -0
- package/lib/CreateSandbox.js +3 -0
- package/lib/CreateSandbox.js.map +1 -0
- package/lib/CreateSandboxHttp.d.ts +5 -0
- package/lib/CreateSandboxHttp.d.ts.map +1 -0
- package/lib/CreateSandboxHttp.js +65 -0
- package/lib/CreateSandboxHttp.js.map +1 -0
- package/lib/CreateSecret.d.ts +56 -0
- package/lib/CreateSecret.d.ts.map +1 -0
- package/lib/CreateSecret.js +3 -0
- package/lib/CreateSecret.js.map +1 -0
- package/lib/CreateSecretHttp.d.ts +5 -0
- package/lib/CreateSecretHttp.d.ts.map +1 -0
- package/lib/CreateSecretHttp.js +39 -0
- package/lib/CreateSecretHttp.js.map +1 -0
- package/lib/CreateSnapshot.d.ts +80 -0
- package/lib/CreateSnapshot.d.ts.map +1 -0
- package/lib/CreateSnapshot.js +3 -0
- package/lib/CreateSnapshot.js.map +1 -0
- package/lib/CreateSnapshotHttp.d.ts +5 -0
- package/lib/CreateSnapshotHttp.d.ts.map +1 -0
- package/lib/CreateSnapshotHttp.js +47 -0
- package/lib/CreateSnapshotHttp.js.map +1 -0
- package/lib/CreateVolume.d.ts +81 -0
- package/lib/CreateVolume.d.ts.map +1 -0
- package/lib/CreateVolume.js +10 -0
- package/lib/CreateVolume.js.map +1 -0
- package/lib/CreateVolumeHttp.d.ts +5 -0
- package/lib/CreateVolumeHttp.d.ts.map +1 -0
- package/lib/CreateVolumeHttp.js +31 -0
- package/lib/CreateVolumeHttp.js.map +1 -0
- package/lib/Credentials.d.ts +9 -0
- package/lib/Credentials.d.ts.map +1 -0
- package/lib/Credentials.js +18 -0
- package/lib/Credentials.js.map +1 -0
- package/lib/DaytonaBindings.d.ts +51 -0
- package/lib/DaytonaBindings.d.ts.map +1 -0
- package/lib/DaytonaBindings.js +61 -0
- package/lib/DaytonaBindings.js.map +1 -0
- package/lib/IdentityProvider.d.ts +80 -0
- package/lib/IdentityProvider.d.ts.map +1 -0
- package/lib/IdentityProvider.js +111 -0
- package/lib/IdentityProvider.js.map +1 -0
- package/lib/MountVolume.d.ts +86 -0
- package/lib/MountVolume.d.ts.map +1 -0
- package/lib/MountVolume.js +27 -0
- package/lib/MountVolume.js.map +1 -0
- package/lib/OAuthClient.d.ts +14 -0
- package/lib/OAuthClient.d.ts.map +1 -0
- package/lib/OAuthClient.js +23 -0
- package/lib/OAuthClient.js.map +1 -0
- package/lib/Organization.d.ts +40 -0
- package/lib/Organization.d.ts.map +1 -0
- package/lib/Organization.js +80 -0
- package/lib/Organization.js.map +1 -0
- package/lib/OrganizationInvitation.d.ts +70 -0
- package/lib/OrganizationInvitation.d.ts.map +1 -0
- package/lib/OrganizationInvitation.js +109 -0
- package/lib/OrganizationInvitation.js.map +1 -0
- package/lib/OrganizationMember.d.ts +86 -0
- package/lib/OrganizationMember.d.ts.map +1 -0
- package/lib/OrganizationMember.js +109 -0
- package/lib/OrganizationMember.js.map +1 -0
- package/lib/OrganizationOtelConfig.d.ts +59 -0
- package/lib/OrganizationOtelConfig.d.ts.map +1 -0
- package/lib/OrganizationOtelConfig.js +87 -0
- package/lib/OrganizationOtelConfig.js.map +1 -0
- package/lib/OrganizationRole.d.ts +66 -0
- package/lib/OrganizationRole.d.ts.map +1 -0
- package/lib/OrganizationRole.js +106 -0
- package/lib/OrganizationRole.js.map +1 -0
- package/lib/OrganizationSettings.d.ts +76 -0
- package/lib/OrganizationSettings.d.ts.map +1 -0
- package/lib/OrganizationSettings.js +107 -0
- package/lib/OrganizationSettings.js.map +1 -0
- package/lib/Providers.d.ts +14 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +53 -0
- package/lib/Providers.js.map +1 -0
- package/lib/ReadAuditLogs.d.ts +30 -0
- package/lib/ReadAuditLogs.d.ts.map +1 -0
- package/lib/ReadAuditLogs.js +17 -0
- package/lib/ReadAuditLogs.js.map +1 -0
- package/lib/ReadOrganization.d.ts +39 -0
- package/lib/ReadOrganization.d.ts.map +1 -0
- package/lib/ReadOrganization.js +39 -0
- package/lib/ReadOrganization.js.map +1 -0
- package/lib/ReadUsage.d.ts +31 -0
- package/lib/ReadUsage.d.ts.map +1 -0
- package/lib/ReadUsage.js +17 -0
- package/lib/ReadUsage.js.map +1 -0
- package/lib/Region.d.ts +78 -0
- package/lib/Region.d.ts.map +1 -0
- package/lib/Region.js +169 -0
- package/lib/Region.js.map +1 -0
- package/lib/Registry.d.ts +84 -0
- package/lib/Registry.d.ts.map +1 -0
- package/lib/Registry.js +97 -0
- package/lib/Registry.js.map +1 -0
- package/lib/Runner.d.ts +66 -0
- package/lib/Runner.d.ts.map +1 -0
- package/lib/Runner.js +116 -0
- package/lib/Runner.js.map +1 -0
- package/lib/Sandbox.d.ts +376 -0
- package/lib/Sandbox.d.ts.map +1 -0
- package/lib/Sandbox.js +565 -0
- package/lib/Sandbox.js.map +1 -0
- package/lib/SandboxClient.d.ts +258 -0
- package/lib/SandboxClient.d.ts.map +1 -0
- package/lib/SandboxClient.js +233 -0
- package/lib/SandboxClient.js.map +1 -0
- package/lib/Secret.d.ts +93 -0
- package/lib/Secret.d.ts.map +1 -0
- package/lib/Secret.js +130 -0
- package/lib/Secret.js.map +1 -0
- package/lib/Snapshot.d.ts +327 -0
- package/lib/Snapshot.d.ts.map +1 -0
- package/lib/Snapshot.js +447 -0
- package/lib/Snapshot.js.map +1 -0
- package/lib/Volume.d.ts +99 -0
- package/lib/Volume.d.ts.map +1 -0
- package/lib/Volume.js +152 -0
- package/lib/Volume.js.map +1 -0
- package/lib/WarmPool.d.ts +61 -0
- package/lib/WarmPool.d.ts.map +1 -0
- package/lib/WarmPool.js +81 -0
- package/lib/WarmPool.js.map +1 -0
- package/lib/Webhooks.d.ts +58 -0
- package/lib/Webhooks.d.ts.map +1 -0
- package/lib/Webhooks.js +75 -0
- package/lib/Webhooks.js.map +1 -0
- package/lib/index.d.ts +37 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +36 -0
- package/lib/index.js.map +1 -0
- package/lib/internal/BuildContext.d.ts +16 -0
- package/lib/internal/BuildContext.d.ts.map +1 -0
- package/lib/internal/BuildContext.js +50 -0
- package/lib/internal/BuildContext.js.map +1 -0
- package/lib/internal/Declared.d.ts +29 -0
- package/lib/internal/Declared.d.ts.map +1 -0
- package/lib/internal/Declared.js +31 -0
- package/lib/internal/Declared.js.map +1 -0
- package/lib/internal/Derived.d.ts +40 -0
- package/lib/internal/Derived.d.ts.map +1 -0
- package/lib/internal/Derived.js +64 -0
- package/lib/internal/Derived.js.map +1 -0
- package/lib/internal/Errors.d.ts +4 -0
- package/lib/internal/Errors.d.ts.map +1 -0
- package/lib/internal/Errors.js +1 -0
- package/lib/internal/Errors.js.map +1 -0
- package/lib/internal/Labels.d.ts +20 -0
- package/lib/internal/Labels.d.ts.map +1 -0
- package/lib/internal/Labels.js +22 -0
- package/lib/internal/Labels.js.map +1 -0
- package/lib/internal/Names.d.ts +18 -0
- package/lib/internal/Names.d.ts.map +1 -0
- package/lib/internal/Names.js +25 -0
- package/lib/internal/Names.js.map +1 -0
- package/lib/internal/Organization.d.ts +27 -0
- package/lib/internal/Organization.d.ts.map +1 -0
- package/lib/internal/Organization.js +32 -0
- package/lib/internal/Organization.js.map +1 -0
- package/lib/internal/Process.d.ts +14 -0
- package/lib/internal/Process.d.ts.map +1 -0
- package/lib/internal/Process.js +15 -0
- package/lib/internal/Process.js.map +1 -0
- package/lib/internal/Program.d.ts +83 -0
- package/lib/internal/Program.d.ts.map +1 -0
- package/lib/internal/Program.js +169 -0
- package/lib/internal/Program.js.map +1 -0
- package/lib/internal/Ref.d.ts +14 -0
- package/lib/internal/Ref.d.ts.map +1 -0
- package/lib/internal/Ref.js +7 -0
- package/lib/internal/Ref.js.map +1 -0
- package/lib/internal/Scoped.d.ts +31 -0
- package/lib/internal/Scoped.d.ts.map +1 -0
- package/lib/internal/Scoped.js +48 -0
- package/lib/internal/Scoped.js.map +1 -0
- package/lib/internal/Tar.d.ts +13 -0
- package/lib/internal/Tar.d.ts.map +1 -0
- package/lib/internal/Tar.js +62 -0
- package/lib/internal/Tar.js.map +1 -0
- package/lib/internal/Toolbox.d.ts +11 -0
- package/lib/internal/Toolbox.d.ts.map +1 -0
- package/lib/internal/Toolbox.js +20 -0
- package/lib/internal/Toolbox.js.map +1 -0
- package/lib/internal/Wait.d.ts +49 -0
- package/lib/internal/Wait.d.ts.map +1 -0
- package/lib/internal/Wait.js +57 -0
- package/lib/internal/Wait.js.map +1 -0
- package/package.json +64 -3
- package/src/ApiKey.ts +221 -0
- package/src/AuthProvider.ts +406 -0
- package/src/Bootstrap.ts +55 -0
- package/src/ConnectSandbox.ts +76 -0
- package/src/ConnectSandboxHttp.ts +26 -0
- package/src/Contributions.ts +9 -0
- package/src/CreateSandbox.ts +107 -0
- package/src/CreateSandboxHttp.ts +97 -0
- package/src/CreateSecret.ts +73 -0
- package/src/CreateSecretHttp.ts +58 -0
- package/src/CreateSnapshot.ts +133 -0
- package/src/CreateSnapshotHttp.ts +77 -0
- package/src/CreateVolume.ts +104 -0
- package/src/CreateVolumeHttp.ts +50 -0
- package/src/Credentials.ts +37 -0
- package/src/DaytonaBindings.ts +111 -0
- package/src/IdentityProvider.ts +181 -0
- package/src/MountVolume.ts +122 -0
- package/src/OAuthClient.ts +27 -0
- package/src/Organization.ts +121 -0
- package/src/OrganizationInvitation.ts +174 -0
- package/src/OrganizationMember.ts +174 -0
- package/src/OrganizationOtelConfig.ts +138 -0
- package/src/OrganizationRole.ts +167 -0
- package/src/OrganizationSettings.ts +187 -0
- package/src/Providers.ts +93 -0
- package/src/ReadAuditLogs.ts +50 -0
- package/src/ReadOrganization.ts +83 -0
- package/src/ReadUsage.ts +51 -0
- package/src/Region.ts +226 -0
- package/src/Registry.ts +164 -0
- package/src/Runner.ts +166 -0
- package/src/Sandbox.ts +927 -0
- package/src/SandboxClient.ts +625 -0
- package/src/Secret.ts +203 -0
- package/src/Snapshot.ts +685 -0
- package/src/Volume.ts +229 -0
- package/src/WarmPool.ts +128 -0
- package/src/Webhooks.ts +121 -0
- package/src/globals.d.ts +2 -0
- package/src/index.ts +46 -0
- package/src/internal/BuildContext.ts +62 -0
- package/src/internal/Declared.ts +80 -0
- package/src/internal/Derived.ts +97 -0
- package/src/internal/Errors.ts +4 -0
- package/src/internal/Labels.ts +39 -0
- package/src/internal/Names.ts +37 -0
- package/src/internal/Organization.ts +40 -0
- package/src/internal/Process.ts +25 -0
- package/src/internal/Program.ts +239 -0
- package/src/internal/Ref.ts +15 -0
- package/src/internal/Scoped.ts +102 -0
- package/src/internal/Tar.ts +72 -0
- package/src/internal/Toolbox.ts +27 -0
- package/src/internal/Wait.ts +105 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import * as organizations from "@distilled.cloud/daytona/organizations";
|
|
2
|
+
import { deepEqual, isResolved } from "alchemy/Diff";
|
|
3
|
+
import * as Provider from "alchemy/Provider";
|
|
4
|
+
import { Resource } from "alchemy/Resource";
|
|
5
|
+
import * as Duration from "effect/Duration";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import { declarable } from "./internal/Declared.ts";
|
|
8
|
+
import {
|
|
9
|
+
currentOrganizationId,
|
|
10
|
+
movesOrganization,
|
|
11
|
+
organizationIdFor,
|
|
12
|
+
type OrganizationRef,
|
|
13
|
+
} from "./internal/Organization.ts";
|
|
14
|
+
import type { Ref } from "./internal/Ref.ts";
|
|
15
|
+
import type { Providers } from "./Providers.ts";
|
|
16
|
+
|
|
17
|
+
export interface OrganizationSettingsProps {
|
|
18
|
+
/**
|
|
19
|
+
* Organization to act on, e.g. a `Daytona.Organization`. Defaults to the
|
|
20
|
+
* credentials' organization. Changing it replaces the resource.
|
|
21
|
+
*/
|
|
22
|
+
organization?: Ref<OrganizationRef>;
|
|
23
|
+
/** Region new sandboxes go to when they don't name one. */
|
|
24
|
+
defaultRegionId?: string;
|
|
25
|
+
/**
|
|
26
|
+
* How long a sandbox create may wait in the queue for capacity before it
|
|
27
|
+
* fails. `null` waits indefinitely.
|
|
28
|
+
*/
|
|
29
|
+
defaultQueueTimeout?: Duration.Input | null;
|
|
30
|
+
/** Experimental feature flags, as Daytona names them. */
|
|
31
|
+
experimentalConfig?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Captured {
|
|
35
|
+
defaultRegionId: string | undefined;
|
|
36
|
+
defaultQueueTimeout: number | null;
|
|
37
|
+
experimentalConfig: unknown;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface OrganizationSettingsAttributes {
|
|
41
|
+
/** Organization ID. */
|
|
42
|
+
organizationId: string;
|
|
43
|
+
/** Organization name. */
|
|
44
|
+
name: string;
|
|
45
|
+
/** Default region. */
|
|
46
|
+
defaultRegionId: string | undefined;
|
|
47
|
+
/** Default queue timeout in seconds, or `null`. */
|
|
48
|
+
defaultQueueTimeout: number | null;
|
|
49
|
+
/** Experimental flags. */
|
|
50
|
+
experimentalConfig: unknown;
|
|
51
|
+
/** The settings before alchemy first changed them; restored on destroy. */
|
|
52
|
+
original: Captured;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type OrganizationSettings = Resource<
|
|
56
|
+
"Daytona.OrganizationSettings",
|
|
57
|
+
OrganizationSettingsProps,
|
|
58
|
+
OrganizationSettingsAttributes,
|
|
59
|
+
never,
|
|
60
|
+
Providers
|
|
61
|
+
>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Settings of the Daytona organization the credentials belong to: the
|
|
65
|
+
* default region, the queue timeout for creates waiting on capacity, and
|
|
66
|
+
* experimental flags. Only the settings you declare are changed.
|
|
67
|
+
*
|
|
68
|
+
* The organization always exists, so this resource captures the settings
|
|
69
|
+
* it finds on first deploy and puts them back on destroy.
|
|
70
|
+
*
|
|
71
|
+
* ### Configuring the Organization
|
|
72
|
+
* **Example:** Default region and a one-minute queue timeout
|
|
73
|
+
* ```typescript
|
|
74
|
+
* export const Settings = Daytona.OrganizationSettings("Settings", {
|
|
75
|
+
* defaultRegionId: "eu",
|
|
76
|
+
* defaultQueueTimeout: "1 minute",
|
|
77
|
+
* });
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @resource
|
|
81
|
+
* @product Organization
|
|
82
|
+
*/
|
|
83
|
+
export const OrganizationSettings = declarable<OrganizationSettings>()(
|
|
84
|
+
Resource<OrganizationSettings>("Daytona.OrganizationSettings"),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const seconds = (d: Duration.Input | null) =>
|
|
88
|
+
d === null ? null : Math.round(Duration.toSeconds(d));
|
|
89
|
+
|
|
90
|
+
const toAttributes = (
|
|
91
|
+
org: organizations.Organization,
|
|
92
|
+
original: Captured,
|
|
93
|
+
): OrganizationSettingsAttributes => ({
|
|
94
|
+
organizationId: org.id,
|
|
95
|
+
name: org.name,
|
|
96
|
+
defaultRegionId: org.defaultRegionId,
|
|
97
|
+
defaultQueueTimeout: org.defaultQueueTimeout,
|
|
98
|
+
experimentalConfig: org.experimentalConfig,
|
|
99
|
+
original,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const apply = Effect.fn(function* (
|
|
103
|
+
org: organizations.Organization,
|
|
104
|
+
want: {
|
|
105
|
+
defaultRegionId?: string | undefined;
|
|
106
|
+
defaultQueueTimeout?: number | null | undefined;
|
|
107
|
+
experimentalConfig?: unknown;
|
|
108
|
+
},
|
|
109
|
+
) {
|
|
110
|
+
if (want.defaultRegionId !== undefined && want.defaultRegionId !== org.defaultRegionId) {
|
|
111
|
+
yield* organizations.setOrganizationDefaultRegion({
|
|
112
|
+
organizationId: org.id,
|
|
113
|
+
defaultRegionId: want.defaultRegionId,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (
|
|
117
|
+
want.defaultQueueTimeout !== undefined &&
|
|
118
|
+
want.defaultQueueTimeout !== org.defaultQueueTimeout
|
|
119
|
+
) {
|
|
120
|
+
yield* organizations.updateOrganizationDefaultQueueTimeout({
|
|
121
|
+
organizationId: org.id,
|
|
122
|
+
defaultQueueTimeout: want.defaultQueueTimeout,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (
|
|
126
|
+
want.experimentalConfig !== undefined &&
|
|
127
|
+
!deepEqual(want.experimentalConfig, org.experimentalConfig)
|
|
128
|
+
) {
|
|
129
|
+
yield* organizations.updateExperimentalConfig({
|
|
130
|
+
organizationId: org.id,
|
|
131
|
+
body: want.experimentalConfig as organizations.UpdateExperimentalConfigRequest["body"],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
export const OrganizationSettingsProvider = () =>
|
|
137
|
+
Provider.effect(
|
|
138
|
+
OrganizationSettings,
|
|
139
|
+
Effect.succeed({
|
|
140
|
+
stables: ["organizationId", "original"],
|
|
141
|
+
nuke: { singleton: true },
|
|
142
|
+
list: () => Effect.succeed([]),
|
|
143
|
+
diff: Effect.fn(function* ({ news, output }) {
|
|
144
|
+
if (!isResolved(news) || output === undefined) return undefined;
|
|
145
|
+
if (yield* movesOrganization(news.organization, output))
|
|
146
|
+
return { action: "replace" } as const;
|
|
147
|
+
}),
|
|
148
|
+
read: Effect.fn(function* ({ output }) {
|
|
149
|
+
const org = yield* organizations.getOrganization({
|
|
150
|
+
organizationId: output?.organizationId ?? (yield* currentOrganizationId),
|
|
151
|
+
});
|
|
152
|
+
return toAttributes(
|
|
153
|
+
org,
|
|
154
|
+
output?.original ?? {
|
|
155
|
+
defaultRegionId: org.defaultRegionId,
|
|
156
|
+
defaultQueueTimeout: org.defaultQueueTimeout,
|
|
157
|
+
experimentalConfig: org.experimentalConfig,
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
}),
|
|
161
|
+
reconcile: Effect.fn(function* ({ news, output }) {
|
|
162
|
+
const organizationId = yield* organizationIdFor(news.organization);
|
|
163
|
+
// Observe
|
|
164
|
+
const org = yield* organizations.getOrganization({ organizationId });
|
|
165
|
+
const original = output?.original ?? {
|
|
166
|
+
defaultRegionId: org.defaultRegionId,
|
|
167
|
+
defaultQueueTimeout: org.defaultQueueTimeout,
|
|
168
|
+
experimentalConfig: org.experimentalConfig,
|
|
169
|
+
};
|
|
170
|
+
// Sync only what is declared, against observed settings.
|
|
171
|
+
yield* apply(org, {
|
|
172
|
+
defaultRegionId: news.defaultRegionId,
|
|
173
|
+
defaultQueueTimeout:
|
|
174
|
+
news.defaultQueueTimeout === undefined ? undefined : seconds(news.defaultQueueTimeout),
|
|
175
|
+
experimentalConfig: news.experimentalConfig,
|
|
176
|
+
});
|
|
177
|
+
return toAttributes(yield* organizations.getOrganization({ organizationId }), original);
|
|
178
|
+
}),
|
|
179
|
+
delete: Effect.fn(function* ({ output }) {
|
|
180
|
+
const org = yield* organizations
|
|
181
|
+
.getOrganization({ organizationId: output.organizationId })
|
|
182
|
+
.pipe(Effect.catchTag("OrganizationNotFound", () => Effect.undefined));
|
|
183
|
+
if (org === undefined) return;
|
|
184
|
+
yield* apply(org, output.original);
|
|
185
|
+
}),
|
|
186
|
+
}),
|
|
187
|
+
);
|
package/src/Providers.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { DockerLive } from "@alchemy.run/docker/Docker";
|
|
2
|
+
import { CredentialsStoreLive } from "alchemy/Auth/Credentials";
|
|
3
|
+
import { ProfileStoreLive } from "alchemy/Auth/Profile";
|
|
4
|
+
import * as Provider from "alchemy/Provider";
|
|
5
|
+
import * as FetchHttpClient from "effect/http/FetchHttpClient";
|
|
6
|
+
import * as Layer from "effect/Layer";
|
|
7
|
+
import { ApiKey, ApiKeyProvider } from "./ApiKey.ts";
|
|
8
|
+
import { DaytonaAuth } from "./AuthProvider.ts";
|
|
9
|
+
import * as Credentials from "./Credentials.ts";
|
|
10
|
+
import { IdentityProvider, IdentityProviderProvider } from "./IdentityProvider.ts";
|
|
11
|
+
import { Organization, OrganizationProvider } from "./Organization.ts";
|
|
12
|
+
import {
|
|
13
|
+
OrganizationInvitation,
|
|
14
|
+
OrganizationInvitationProvider,
|
|
15
|
+
} from "./OrganizationInvitation.ts";
|
|
16
|
+
import { OrganizationMember, OrganizationMemberProvider } from "./OrganizationMember.ts";
|
|
17
|
+
import {
|
|
18
|
+
OrganizationOtelConfig,
|
|
19
|
+
OrganizationOtelConfigProvider,
|
|
20
|
+
} from "./OrganizationOtelConfig.ts";
|
|
21
|
+
import { OrganizationRole, OrganizationRoleProvider } from "./OrganizationRole.ts";
|
|
22
|
+
import { OrganizationSettings, OrganizationSettingsProvider } from "./OrganizationSettings.ts";
|
|
23
|
+
import { Region, RegionProvider } from "./Region.ts";
|
|
24
|
+
import { Registry, RegistryProvider } from "./Registry.ts";
|
|
25
|
+
import { Runner, RunnerProvider } from "./Runner.ts";
|
|
26
|
+
import { Sandbox, SandboxProvider } from "./Sandbox.ts";
|
|
27
|
+
import { Secret, SecretProvider } from "./Secret.ts";
|
|
28
|
+
import { Snapshot, SnapshotProvider } from "./Snapshot.ts";
|
|
29
|
+
import { Volume, VolumeProvider } from "./Volume.ts";
|
|
30
|
+
import { WarmPool, WarmPoolProvider } from "./WarmPool.ts";
|
|
31
|
+
import { Webhooks, WebhooksProvider } from "./Webhooks.ts";
|
|
32
|
+
|
|
33
|
+
export class Providers extends Provider.ProviderCollection<Providers>()("Daytona") {}
|
|
34
|
+
|
|
35
|
+
export type ProviderRequirements = Layer.Services<ReturnType<typeof providers>>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Daytona providers and credentials: every Daytona resource, and the
|
|
39
|
+
* Daytona AuthProvider so `alchemy profile edit` can configure it.
|
|
40
|
+
*/
|
|
41
|
+
export const providers = () =>
|
|
42
|
+
Layer.effect(
|
|
43
|
+
Providers,
|
|
44
|
+
Provider.collection([
|
|
45
|
+
ApiKey,
|
|
46
|
+
IdentityProvider,
|
|
47
|
+
Organization,
|
|
48
|
+
OrganizationInvitation,
|
|
49
|
+
OrganizationMember,
|
|
50
|
+
OrganizationOtelConfig,
|
|
51
|
+
OrganizationRole,
|
|
52
|
+
OrganizationSettings,
|
|
53
|
+
Region,
|
|
54
|
+
Registry,
|
|
55
|
+
Runner,
|
|
56
|
+
Sandbox,
|
|
57
|
+
Secret,
|
|
58
|
+
Snapshot,
|
|
59
|
+
Volume,
|
|
60
|
+
WarmPool,
|
|
61
|
+
Webhooks,
|
|
62
|
+
]),
|
|
63
|
+
).pipe(
|
|
64
|
+
Layer.provide(
|
|
65
|
+
Layer.mergeAll(
|
|
66
|
+
ApiKeyProvider(),
|
|
67
|
+
IdentityProviderProvider(),
|
|
68
|
+
OrganizationProvider(),
|
|
69
|
+
OrganizationInvitationProvider(),
|
|
70
|
+
OrganizationMemberProvider(),
|
|
71
|
+
OrganizationOtelConfigProvider(),
|
|
72
|
+
OrganizationRoleProvider(),
|
|
73
|
+
OrganizationSettingsProvider(),
|
|
74
|
+
RegionProvider(),
|
|
75
|
+
RegistryProvider(),
|
|
76
|
+
RunnerProvider(),
|
|
77
|
+
SandboxProvider(),
|
|
78
|
+
SecretProvider(),
|
|
79
|
+
SnapshotProvider(),
|
|
80
|
+
VolumeProvider(),
|
|
81
|
+
WarmPoolProvider(),
|
|
82
|
+
WebhooksProvider(),
|
|
83
|
+
),
|
|
84
|
+
),
|
|
85
|
+
// Snapshots Daytona can't build are built with local Docker.
|
|
86
|
+
Layer.provideMerge(DockerLive),
|
|
87
|
+
Layer.provideMerge(Credentials.fromAuthProvider()),
|
|
88
|
+
Layer.provideMerge(FetchHttpClient.layer),
|
|
89
|
+
Layer.provideMerge(DaytonaAuth),
|
|
90
|
+
Layer.provideMerge(ProfileStoreLive),
|
|
91
|
+
Layer.provideMerge(CredentialsStoreLive),
|
|
92
|
+
Layer.orDie,
|
|
93
|
+
);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as audit from "@distilled.cloud/daytona/audit";
|
|
2
|
+
import * as Binding from "alchemy/Binding";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
import { makeAccess } from "./DaytonaBindings.ts";
|
|
6
|
+
import { scopeModule, type OrganizationFixed, type ScopedModule } from "./internal/Scoped.ts";
|
|
7
|
+
import { lazyScoped } from "./ReadOrganization.ts";
|
|
8
|
+
|
|
9
|
+
/** The organization's audit log. */
|
|
10
|
+
export interface ReadAuditLogsClient extends ScopedModule<typeof audit, OrganizationFixed> {}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read the organization's audit log at runtime: who created, started,
|
|
14
|
+
* deleted or changed what, and with which API key.
|
|
15
|
+
*
|
|
16
|
+
* The host gets its own Daytona API key with `read:audit_logs`.
|
|
17
|
+
*
|
|
18
|
+
* ### Reading the Audit Log
|
|
19
|
+
* **Example:** The latest entries
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const audit = yield* Daytona.ReadAuditLogs();
|
|
22
|
+
* const page = yield* audit.getOrganizationAuditLogs({ limit: 50 });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @binding
|
|
26
|
+
* @product Organization
|
|
27
|
+
*/
|
|
28
|
+
export interface ReadAuditLogs extends Binding.Service<
|
|
29
|
+
ReadAuditLogs,
|
|
30
|
+
"Daytona.ReadAuditLogs",
|
|
31
|
+
() => Effect.Effect<ReadAuditLogsClient>
|
|
32
|
+
> {}
|
|
33
|
+
|
|
34
|
+
export const ReadAuditLogs = Binding.Service<ReadAuditLogs>("Daytona.ReadAuditLogs");
|
|
35
|
+
|
|
36
|
+
/** {@link ReadAuditLogs} over Daytona's HTTP API; works on any host. */
|
|
37
|
+
export const ReadAuditLogsHttp = Layer.effect(
|
|
38
|
+
ReadAuditLogs,
|
|
39
|
+
Effect.gen(function* () {
|
|
40
|
+
const access = yield* makeAccess;
|
|
41
|
+
return Effect.fn(function* () {
|
|
42
|
+
const { authorize, organizationId } = yield* access("AuditLogs", ["read:audit_logs"]);
|
|
43
|
+
return lazyScoped(organizationId, (id) =>
|
|
44
|
+
scopeModule<typeof audit, OrganizationFixed>(audit, { organizationId: id }, (effect) =>
|
|
45
|
+
authorize(effect),
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as organizations from "@distilled.cloud/daytona/organizations";
|
|
2
|
+
import * as regions from "@distilled.cloud/daytona/regions";
|
|
3
|
+
import * as webhooks from "@distilled.cloud/daytona/webhooks";
|
|
4
|
+
import * as Binding from "alchemy/Binding";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Layer from "effect/Layer";
|
|
7
|
+
import { makeAccess } from "./DaytonaBindings.ts";
|
|
8
|
+
import { pick, scopeModule, type ScopedModule } from "./internal/Scoped.ts";
|
|
9
|
+
|
|
10
|
+
const ORGANIZATION_READS = [
|
|
11
|
+
"getGpuCapacity",
|
|
12
|
+
"listAvailableSandboxClasses",
|
|
13
|
+
"getOrganizationUsageOverview",
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
const WEBHOOK_READS = [
|
|
17
|
+
"getWebhookControllerAppPortalAccess",
|
|
18
|
+
"getWebhookControllerInitializationStatus",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
/** Read-only views of the organization: quotas, usage, GPU capacity, regions and the webhook portal. */
|
|
22
|
+
export interface ReadOrganizationClient
|
|
23
|
+
extends
|
|
24
|
+
ScopedModule<Pick<typeof organizations, (typeof ORGANIZATION_READS)[number]>>,
|
|
25
|
+
ScopedModule<typeof regions>,
|
|
26
|
+
ScopedModule<Pick<typeof webhooks, (typeof WEBHOOK_READS)[number]>> {}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read the organization's limits and usage at runtime: current usage
|
|
30
|
+
* against quotas, which sandbox classes and GPUs are available, and the
|
|
31
|
+
* shared regions. Use it to pick a region or refuse work before hitting a
|
|
32
|
+
* quota.
|
|
33
|
+
*
|
|
34
|
+
* The host gets its own Daytona API key with `read:limits`.
|
|
35
|
+
*
|
|
36
|
+
* ### Checking Capacity
|
|
37
|
+
* **Example:** Refuse a GPU job when no GPUs are free
|
|
38
|
+
* ```typescript
|
|
39
|
+
* const org = yield* Daytona.ReadOrganization();
|
|
40
|
+
* const capacity = yield* org.getGpuCapacity();
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @binding
|
|
44
|
+
* @product Organization
|
|
45
|
+
*/
|
|
46
|
+
export interface ReadOrganization extends Binding.Service<
|
|
47
|
+
ReadOrganization,
|
|
48
|
+
"Daytona.ReadOrganization",
|
|
49
|
+
() => Effect.Effect<ReadOrganizationClient>
|
|
50
|
+
> {}
|
|
51
|
+
|
|
52
|
+
export const ReadOrganization = Binding.Service<ReadOrganization>("Daytona.ReadOrganization");
|
|
53
|
+
|
|
54
|
+
/** {@link ReadOrganization} over Daytona's HTTP API; works on any host. */
|
|
55
|
+
export const ReadOrganizationHttp = Layer.effect(
|
|
56
|
+
ReadOrganization,
|
|
57
|
+
Effect.gen(function* () {
|
|
58
|
+
const access = yield* makeAccess;
|
|
59
|
+
return Effect.fn(function* () {
|
|
60
|
+
const { authorize, organizationId } = yield* access("Organization", ["read:limits"]);
|
|
61
|
+
const run = <A, E, R>(effect: Effect.Effect<A, E, R>) => authorize(effect);
|
|
62
|
+
return lazyScoped(organizationId, (id) => ({
|
|
63
|
+
...scopeModule(pick(organizations, ORGANIZATION_READS), { organizationId: id }, run),
|
|
64
|
+
...scopeModule(regions, {}, run),
|
|
65
|
+
...scopeModule(pick(webhooks, WEBHOOK_READS), { organizationId: id }, run),
|
|
66
|
+
}));
|
|
67
|
+
});
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
/** Build a scoped client whose organization id resolves at runtime. */
|
|
72
|
+
export const lazyScoped = <C extends object>(
|
|
73
|
+
organizationId: Effect.Effect<string, never, any>,
|
|
74
|
+
make: (organizationId: string) => C,
|
|
75
|
+
): C =>
|
|
76
|
+
new Proxy({} as C, {
|
|
77
|
+
get:
|
|
78
|
+
(_t, key: string) =>
|
|
79
|
+
(...args: unknown[]) =>
|
|
80
|
+
// `key` is a property of C (the Proxy only sees C's members), but a
|
|
81
|
+
// Proxy trap types it as string, so the lookup is untyped here.
|
|
82
|
+
Effect.flatMap(organizationId, (id) => (make(id) as any)[key](...args)),
|
|
83
|
+
});
|
package/src/ReadUsage.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as usage from "@distilled.cloud/daytona/usage";
|
|
2
|
+
import * as Binding from "alchemy/Binding";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
import { makeAccess } from "./DaytonaBindings.ts";
|
|
6
|
+
import { scopeModule, type OrganizationFixed, type ScopedModule } from "./internal/Scoped.ts";
|
|
7
|
+
import { lazyScoped } from "./ReadOrganization.ts";
|
|
8
|
+
|
|
9
|
+
/** Usage from Daytona's analytics API: totals, charts and per-sandbox usage. */
|
|
10
|
+
export interface ReadUsageClient extends ScopedModule<typeof usage, OrganizationFixed> {}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read the organization's compute usage at runtime: aggregated totals, a
|
|
14
|
+
* chart over time, and usage per sandbox. Use it to bill your own users
|
|
15
|
+
* for the sandboxes you run for them.
|
|
16
|
+
*
|
|
17
|
+
* The host gets its own Daytona API key with `read:billing`.
|
|
18
|
+
*
|
|
19
|
+
* ### Usage per Sandbox
|
|
20
|
+
* **Example:** Usage over the last day
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const usage = yield* Daytona.ReadUsage();
|
|
23
|
+
* const rows = yield* usage.listOrganizationUsageSandbox({ from, to });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @binding
|
|
27
|
+
* @product Organization
|
|
28
|
+
*/
|
|
29
|
+
export interface ReadUsage extends Binding.Service<
|
|
30
|
+
ReadUsage,
|
|
31
|
+
"Daytona.ReadUsage",
|
|
32
|
+
() => Effect.Effect<ReadUsageClient>
|
|
33
|
+
> {}
|
|
34
|
+
|
|
35
|
+
export const ReadUsage = Binding.Service<ReadUsage>("Daytona.ReadUsage");
|
|
36
|
+
|
|
37
|
+
/** {@link ReadUsage} over Daytona's analytics API; works on any host. */
|
|
38
|
+
export const ReadUsageHttp = Layer.effect(
|
|
39
|
+
ReadUsage,
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const access = yield* makeAccess;
|
|
42
|
+
return Effect.fn(function* () {
|
|
43
|
+
const { authorize, organizationId } = yield* access("Usage", ["read:billing"]);
|
|
44
|
+
return lazyScoped(organizationId, (id) =>
|
|
45
|
+
scopeModule<typeof usage, OrganizationFixed>(usage, { organizationId: id }, (effect) =>
|
|
46
|
+
authorize(effect),
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
}),
|
|
51
|
+
);
|
package/src/Region.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import * as organizations from "@distilled.cloud/daytona/organizations";
|
|
2
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
3
|
+
import { isResolved } from "alchemy/Diff";
|
|
4
|
+
import * as Provider from "alchemy/Provider";
|
|
5
|
+
import { Resource } from "alchemy/Resource";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Redacted from "effect/Redacted";
|
|
8
|
+
import { isOwnedName, resolveName } from "./internal/Names.ts";
|
|
9
|
+
import type { Providers } from "./Providers.ts";
|
|
10
|
+
|
|
11
|
+
export interface RegionProps {
|
|
12
|
+
/** Region name. If omitted, generated from the stack, stage and logical ID. Changing it replaces the region. */
|
|
13
|
+
name?: string;
|
|
14
|
+
/** URL of the region's sandbox proxy. */
|
|
15
|
+
proxyUrl?: string;
|
|
16
|
+
/** URL of the region's SSH gateway. */
|
|
17
|
+
sshGatewayUrl?: string;
|
|
18
|
+
/** URL of the region's snapshot manager (image registry). */
|
|
19
|
+
snapshotManagerUrl?: string;
|
|
20
|
+
/** OTLP endpoint the region's runners export to. */
|
|
21
|
+
otelEndpoint?: string;
|
|
22
|
+
/** S3 bucket for Dockerfile build contexts. */
|
|
23
|
+
buildContextBucket?: string;
|
|
24
|
+
/** Region of that bucket. */
|
|
25
|
+
buildContextBucketRegion?: string;
|
|
26
|
+
/** IAM role Daytona assumes to use the bucket. */
|
|
27
|
+
buildContextRoleArn?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Bump a counter to rotate that credential on the next deploy.
|
|
30
|
+
*/
|
|
31
|
+
rotate?: { proxyApiKey?: number; sshGatewayApiKey?: number; snapshotManagerCredentials?: number };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RegionAttributes {
|
|
35
|
+
/** Region ID; what runners, snapshots and sandboxes take. */
|
|
36
|
+
regionId: string;
|
|
37
|
+
/** Region name. */
|
|
38
|
+
name: string;
|
|
39
|
+
/** API key the region's proxy authenticates with. */
|
|
40
|
+
proxyApiKey: Redacted.Redacted<string> | undefined;
|
|
41
|
+
/** API key the region's SSH gateway authenticates with. */
|
|
42
|
+
sshGatewayApiKey: Redacted.Redacted<string> | undefined;
|
|
43
|
+
/** Snapshot manager user name. */
|
|
44
|
+
snapshotManagerUsername: string | undefined;
|
|
45
|
+
/** Snapshot manager password. */
|
|
46
|
+
snapshotManagerPassword: Redacted.Redacted<string> | undefined;
|
|
47
|
+
/** Rotation counters applied. */
|
|
48
|
+
rotated: { proxyApiKey: number; sshGatewayApiKey: number; snapshotManagerCredentials: number };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type Region = Resource<"Daytona.Region", RegionProps, RegionAttributes, never, Providers>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A custom region: your own compute (see {@link Runner}) registered with
|
|
55
|
+
* Daytona, so sandboxes run on machines you operate while Daytona runs
|
|
56
|
+
* the control plane. Needs an organization allowed to bring its own
|
|
57
|
+
* compute.
|
|
58
|
+
*
|
|
59
|
+
* Daytona shows the region's API keys only when they are created or
|
|
60
|
+
* rotated, so alchemy keeps them in state as `Redacted` values. Bump a
|
|
61
|
+
* `rotate` counter to rotate one.
|
|
62
|
+
* @see https://www.daytona.io/docs/en/regions/
|
|
63
|
+
*
|
|
64
|
+
* ### Bringing Your Own Compute
|
|
65
|
+
* **Example:** A region with its own proxy and SSH gateway
|
|
66
|
+
* ```typescript
|
|
67
|
+
* export const Home = Daytona.Region("Home", {
|
|
68
|
+
* proxyUrl: "https://proxy.daytona.example.com",
|
|
69
|
+
* sshGatewayUrl: "ssh.daytona.example.com:2222",
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @resource
|
|
74
|
+
* @product Region
|
|
75
|
+
*/
|
|
76
|
+
export const Region = Resource<Region>("Daytona.Region");
|
|
77
|
+
|
|
78
|
+
const redact = (value: string | Redacted.Redacted<string> | null | undefined) =>
|
|
79
|
+
value === null || value === undefined
|
|
80
|
+
? undefined
|
|
81
|
+
: Redacted.isRedacted(value)
|
|
82
|
+
? value
|
|
83
|
+
: Redacted.make(value);
|
|
84
|
+
|
|
85
|
+
const getById = (id: string) =>
|
|
86
|
+
organizations.getRegion({ id }).pipe(Effect.catchTag("RegionNotFound", () => Effect.undefined));
|
|
87
|
+
|
|
88
|
+
const findByName = (name: string) =>
|
|
89
|
+
organizations
|
|
90
|
+
.listAvailableRegions({})
|
|
91
|
+
.pipe(Effect.map((all) => all.find((r) => r.name === name)));
|
|
92
|
+
|
|
93
|
+
export const RegionProvider = () =>
|
|
94
|
+
Provider.effect(
|
|
95
|
+
Region,
|
|
96
|
+
Effect.succeed({
|
|
97
|
+
stables: ["regionId"],
|
|
98
|
+
list: Effect.fn(function* () {
|
|
99
|
+
const all = yield* organizations.listAvailableRegions({});
|
|
100
|
+
return all
|
|
101
|
+
.filter((r) => r.organizationId)
|
|
102
|
+
.map((r) => ({
|
|
103
|
+
regionId: r.id,
|
|
104
|
+
name: r.name,
|
|
105
|
+
proxyApiKey: undefined,
|
|
106
|
+
sshGatewayApiKey: undefined,
|
|
107
|
+
snapshotManagerUsername: undefined,
|
|
108
|
+
snapshotManagerPassword: undefined,
|
|
109
|
+
rotated: { proxyApiKey: 0, sshGatewayApiKey: 0, snapshotManagerCredentials: 0 },
|
|
110
|
+
}));
|
|
111
|
+
}),
|
|
112
|
+
diff: Effect.fn(function* ({ id, news, output }) {
|
|
113
|
+
if (!isResolved(news) || output === undefined) return undefined;
|
|
114
|
+
const name = yield* resolveName(
|
|
115
|
+
id,
|
|
116
|
+
news.name,
|
|
117
|
+
news.name === undefined ? output.name : undefined,
|
|
118
|
+
);
|
|
119
|
+
if (name !== output.name) return { action: "replace", deleteFirst: true } as const;
|
|
120
|
+
}),
|
|
121
|
+
read: Effect.fn(function* ({ id, olds, output }) {
|
|
122
|
+
const name = yield* resolveName(id, olds?.name, output?.name);
|
|
123
|
+
const found =
|
|
124
|
+
(output ? yield* getById(output.regionId) : undefined) ?? (yield* findByName(name));
|
|
125
|
+
if (found === undefined) return undefined;
|
|
126
|
+
const attrs: RegionAttributes = output ?? {
|
|
127
|
+
regionId: found.id,
|
|
128
|
+
name: found.name,
|
|
129
|
+
proxyApiKey: undefined,
|
|
130
|
+
sshGatewayApiKey: undefined,
|
|
131
|
+
snapshotManagerUsername: undefined,
|
|
132
|
+
snapshotManagerPassword: undefined,
|
|
133
|
+
rotated: { proxyApiKey: 0, sshGatewayApiKey: 0, snapshotManagerCredentials: 0 },
|
|
134
|
+
};
|
|
135
|
+
return (yield* isOwnedName(id, found.name, output !== undefined)) ? attrs : Unowned(attrs);
|
|
136
|
+
}),
|
|
137
|
+
reconcile: Effect.fn(function* ({ id, news, output }) {
|
|
138
|
+
const name = yield* resolveName(id, news.name, output?.name);
|
|
139
|
+
const settings = {
|
|
140
|
+
proxyUrl: news.proxyUrl ?? null,
|
|
141
|
+
sshGatewayUrl: news.sshGatewayUrl ?? null,
|
|
142
|
+
snapshotManagerUrl: news.snapshotManagerUrl ?? null,
|
|
143
|
+
otelEndpoint: news.otelEndpoint ?? null,
|
|
144
|
+
buildContextBucket: news.buildContextBucket ?? null,
|
|
145
|
+
buildContextBucketRegion: news.buildContextBucketRegion ?? null,
|
|
146
|
+
buildContextRoleArn: news.buildContextRoleArn ?? null,
|
|
147
|
+
};
|
|
148
|
+
// Observe
|
|
149
|
+
const current =
|
|
150
|
+
(output ? yield* getById(output.regionId) : undefined) ?? (yield* findByName(name));
|
|
151
|
+
let attrs: RegionAttributes;
|
|
152
|
+
// Ensure
|
|
153
|
+
if (current === undefined) {
|
|
154
|
+
const created = yield* organizations.createRegion({ name, ...settings });
|
|
155
|
+
attrs = {
|
|
156
|
+
regionId: created.id,
|
|
157
|
+
name,
|
|
158
|
+
proxyApiKey: redact(created.proxyApiKey),
|
|
159
|
+
sshGatewayApiKey: redact(created.sshGatewayApiKey),
|
|
160
|
+
snapshotManagerUsername: created.snapshotManagerUsername ?? undefined,
|
|
161
|
+
snapshotManagerPassword: redact(created.snapshotManagerPassword),
|
|
162
|
+
rotated: {
|
|
163
|
+
proxyApiKey: news.rotate?.proxyApiKey ?? 0,
|
|
164
|
+
sshGatewayApiKey: news.rotate?.sshGatewayApiKey ?? 0,
|
|
165
|
+
snapshotManagerCredentials: news.rotate?.snapshotManagerCredentials ?? 0,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
} else {
|
|
169
|
+
attrs = output ?? {
|
|
170
|
+
regionId: current.id,
|
|
171
|
+
name,
|
|
172
|
+
proxyApiKey: undefined,
|
|
173
|
+
sshGatewayApiKey: undefined,
|
|
174
|
+
snapshotManagerUsername: undefined,
|
|
175
|
+
snapshotManagerPassword: undefined,
|
|
176
|
+
rotated: { proxyApiKey: 0, sshGatewayApiKey: 0, snapshotManagerCredentials: 0 },
|
|
177
|
+
};
|
|
178
|
+
// Sync settings against the observed region.
|
|
179
|
+
const drift = (Object.keys(settings) as (keyof typeof settings)[]).some(
|
|
180
|
+
(k) => (current[k] ?? null) !== settings[k],
|
|
181
|
+
);
|
|
182
|
+
if (drift) yield* organizations.updateRegion({ id: current.id, ...settings });
|
|
183
|
+
}
|
|
184
|
+
// Sync rotations.
|
|
185
|
+
const want = news.rotate ?? {};
|
|
186
|
+
if ((want.proxyApiKey ?? 0) !== attrs.rotated.proxyApiKey) {
|
|
187
|
+
const { apiKey } = yield* organizations.regenerateProxyApiKey({ id: attrs.regionId });
|
|
188
|
+
attrs = {
|
|
189
|
+
...attrs,
|
|
190
|
+
proxyApiKey: redact(apiKey),
|
|
191
|
+
rotated: { ...attrs.rotated, proxyApiKey: want.proxyApiKey ?? 0 },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if ((want.sshGatewayApiKey ?? 0) !== attrs.rotated.sshGatewayApiKey) {
|
|
195
|
+
const { apiKey } = yield* organizations.regenerateSshGatewayApiKey({
|
|
196
|
+
id: attrs.regionId,
|
|
197
|
+
});
|
|
198
|
+
attrs = {
|
|
199
|
+
...attrs,
|
|
200
|
+
sshGatewayApiKey: redact(apiKey),
|
|
201
|
+
rotated: { ...attrs.rotated, sshGatewayApiKey: want.sshGatewayApiKey ?? 0 },
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if ((want.snapshotManagerCredentials ?? 0) !== attrs.rotated.snapshotManagerCredentials) {
|
|
205
|
+
const creds = yield* organizations.regenerateSnapshotManagerCredentials({
|
|
206
|
+
id: attrs.regionId,
|
|
207
|
+
});
|
|
208
|
+
attrs = {
|
|
209
|
+
...attrs,
|
|
210
|
+
snapshotManagerUsername: creds.username,
|
|
211
|
+
snapshotManagerPassword: redact(creds.password),
|
|
212
|
+
rotated: {
|
|
213
|
+
...attrs.rotated,
|
|
214
|
+
snapshotManagerCredentials: want.snapshotManagerCredentials ?? 0,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return attrs;
|
|
219
|
+
}),
|
|
220
|
+
delete: Effect.fn(function* ({ output }) {
|
|
221
|
+
yield* organizations
|
|
222
|
+
.deleteRegion({ id: output.regionId })
|
|
223
|
+
.pipe(Effect.catchTag("RegionNotFound", () => Effect.void));
|
|
224
|
+
}),
|
|
225
|
+
}),
|
|
226
|
+
);
|