@alchemy.run/daytona 0.0.0-stage → 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 +80 -2
- 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 +79 -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,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Credentials,
|
|
3
|
+
DEFAULT_ANALYTICS_BASE_URL,
|
|
4
|
+
DEFAULT_TOOLBOX_BASE_URL,
|
|
5
|
+
} from "@distilled.cloud/daytona/Credentials";
|
|
6
|
+
import * as Binding from "alchemy/Binding";
|
|
7
|
+
import * as Output from "alchemy/Output";
|
|
8
|
+
import type { RuntimeContext } from "alchemy/RuntimeContext";
|
|
9
|
+
import { createInternalTags } from "alchemy/Tags";
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as FetchHttpClient from "effect/http/FetchHttpClient";
|
|
12
|
+
import type * as HttpClient from "effect/http/HttpClient";
|
|
13
|
+
import * as Layer from "effect/Layer";
|
|
14
|
+
import { ApiKey, type Permission } from "./ApiKey.ts";
|
|
15
|
+
import { toLabels } from "./internal/Labels.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* How a Daytona binding reaches the API from any host.
|
|
19
|
+
*
|
|
20
|
+
* Daytona authorizes with API keys whose permissions are checked per kind
|
|
21
|
+
* of object, so each binding mints its own {@link ApiKey} for its host at
|
|
22
|
+
* deploy, with only the permissions it needs, and the host reads the key
|
|
23
|
+
* back at runtime through `RuntimeContext`. The deployer's key never
|
|
24
|
+
* leaves the deploying machine, and the host works on any runtime
|
|
25
|
+
* (Cloudflare Workers, Lambda, containers) because the key travels as a
|
|
26
|
+
* plain secret, not a cloud identity.
|
|
27
|
+
*/
|
|
28
|
+
export interface DaytonaAccess {
|
|
29
|
+
/**
|
|
30
|
+
* Run Daytona operations with the host's minted key. Provides
|
|
31
|
+
* `Credentials` and an `HttpClient`; the result requires only
|
|
32
|
+
* `RuntimeContext`.
|
|
33
|
+
*/
|
|
34
|
+
readonly authorize: <A, E, R>(
|
|
35
|
+
effect: Effect.Effect<A, E, R>,
|
|
36
|
+
) => Effect.Effect<A, E, Exclude<R, Credentials | HttpClient.HttpClient> | RuntimeContext>;
|
|
37
|
+
/** The organization the key acts on. */
|
|
38
|
+
readonly organizationId: Effect.Effect<string, never, RuntimeContext>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve once, when a binding's layer is built: returns a function that
|
|
43
|
+
* mints (at deploy) and reads back (at runtime) an API key for the current
|
|
44
|
+
* host, named after the host and `purpose`, with `permissions`.
|
|
45
|
+
*/
|
|
46
|
+
export const makeAccess = Effect.gen(function* () {
|
|
47
|
+
const Key = yield* ApiKey;
|
|
48
|
+
return Effect.fn(function* (purpose: string, permissions: Permission[]) {
|
|
49
|
+
const host = yield* Binding.Host;
|
|
50
|
+
const key = yield* Key(`${host?.LogicalId ?? ""}${purpose}ApiKey`, { permissions });
|
|
51
|
+
const value = yield* key.value;
|
|
52
|
+
const apiBaseUrl = yield* key.apiBaseUrl;
|
|
53
|
+
const organizationId = yield* key.organizationId;
|
|
54
|
+
const authorize = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
55
|
+
Effect.gen(function* () {
|
|
56
|
+
const config = {
|
|
57
|
+
apiKey: yield* value,
|
|
58
|
+
apiBaseUrl: yield* apiBaseUrl,
|
|
59
|
+
analyticsBaseUrl: DEFAULT_ANALYTICS_BASE_URL,
|
|
60
|
+
toolboxBaseUrl: DEFAULT_TOOLBOX_BASE_URL,
|
|
61
|
+
organizationId: yield* organizationId,
|
|
62
|
+
};
|
|
63
|
+
return yield* effect.pipe(
|
|
64
|
+
Effect.provide(
|
|
65
|
+
Layer.mergeAll(
|
|
66
|
+
Layer.succeed(Credentials, Effect.succeed(config)),
|
|
67
|
+
FetchHttpClient.layer,
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
authorize,
|
|
74
|
+
organizationId,
|
|
75
|
+
} satisfies DaytonaAccess;
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The labels a runtime binding puts on what it creates: the host's
|
|
81
|
+
* `alchemy::stack`, `alchemy::stage` and `alchemy::id`, as Daytona label
|
|
82
|
+
* keys (`alchemy.stack`, `alchemy.stage`, `alchemy.id`; Daytona doesn't
|
|
83
|
+
* accept `::` in a key). Computed at deploy from the host's identity and
|
|
84
|
+
* bound into the host, so the runtime reads them back rather than
|
|
85
|
+
* computing them.
|
|
86
|
+
*/
|
|
87
|
+
export const makeOwnerLabels = Effect.gen(function* () {
|
|
88
|
+
const host = yield* Binding.Host;
|
|
89
|
+
const id = host?.LogicalId ?? "";
|
|
90
|
+
const owner = Output.fromEffect(createInternalTags(id)) as unknown as {
|
|
91
|
+
bind(key: string): Effect.Effect<unknown>;
|
|
92
|
+
};
|
|
93
|
+
const tags = yield* owner.bind(`${id}DaytonaOwner`);
|
|
94
|
+
return Effect.map(
|
|
95
|
+
tags as Effect.Effect<Record<string, string> | undefined, never, RuntimeContext>,
|
|
96
|
+
(owner) => toLabels(owner),
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/** The owner labels as a description, for objects that only have one. */
|
|
101
|
+
export const ownerDescription = (labels: Record<string, string>) =>
|
|
102
|
+
`${labels["alchemy.stack"]}/${labels["alchemy.stage"]}/${labels["alchemy.id"]}`;
|
|
103
|
+
|
|
104
|
+
/** User labels with the owner labels on top: a user label never overwrites `alchemy.*`. */
|
|
105
|
+
export const withOwnerLabels = (
|
|
106
|
+
user: Record<string, string> | undefined,
|
|
107
|
+
owner: Record<string, string>,
|
|
108
|
+
) => ({
|
|
109
|
+
...Object.fromEntries(Object.entries(user ?? {}).filter(([k]) => !k.startsWith("alchemy."))),
|
|
110
|
+
...owner,
|
|
111
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
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 type * as Redacted from "effect/Redacted";
|
|
8
|
+
import { declarable } from "./internal/Declared.ts";
|
|
9
|
+
import { isOwnedName, resolveName } from "./internal/Names.ts";
|
|
10
|
+
import {
|
|
11
|
+
currentOrganizationId,
|
|
12
|
+
movesOrganization,
|
|
13
|
+
organizationIdFor,
|
|
14
|
+
type OrganizationRef,
|
|
15
|
+
} from "./internal/Organization.ts";
|
|
16
|
+
import { resolved, type Ref } from "./internal/Ref.ts";
|
|
17
|
+
import type { RoleRef } from "./OrganizationInvitation.ts";
|
|
18
|
+
import type { Providers } from "./Providers.ts";
|
|
19
|
+
|
|
20
|
+
export interface IdentityProviderProps {
|
|
21
|
+
/**
|
|
22
|
+
* Organization to act on, e.g. a `Daytona.Organization`. Defaults to the
|
|
23
|
+
* credentials' organization. Changing it replaces the resource.
|
|
24
|
+
*/
|
|
25
|
+
organization?: Ref<OrganizationRef>;
|
|
26
|
+
/** Display name. If omitted, generated from the stack, stage and logical ID. */
|
|
27
|
+
name?: string;
|
|
28
|
+
/** OIDC issuer URL. */
|
|
29
|
+
issuerUrl: string;
|
|
30
|
+
/** OIDC client ID. */
|
|
31
|
+
clientId: string;
|
|
32
|
+
/** OIDC client secret. Updated in place. */
|
|
33
|
+
clientSecret: string | Redacted.Redacted<string>;
|
|
34
|
+
/** Scopes to request. @default "openid email profile" */
|
|
35
|
+
scope?: string;
|
|
36
|
+
/** Email domains that sign in through this provider. */
|
|
37
|
+
emailDomains: string[];
|
|
38
|
+
/** Accept any email domain. @default false */
|
|
39
|
+
allowAnyDomain?: boolean;
|
|
40
|
+
/** Trust the provider's `email_verified` claim. @default false */
|
|
41
|
+
trustEmailVerified?: boolean;
|
|
42
|
+
/** Custom roles new members get. */
|
|
43
|
+
defaultRoles?: Ref<RoleRef>[];
|
|
44
|
+
/** Whether sign-in through it is enabled. @default true */
|
|
45
|
+
enabled?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface IdentityProviderAttributes {
|
|
49
|
+
/** Identity provider ID. */
|
|
50
|
+
identityProviderId: string;
|
|
51
|
+
/** Organization ID. */
|
|
52
|
+
organizationId: string;
|
|
53
|
+
/** Display name. */
|
|
54
|
+
name: string;
|
|
55
|
+
/** Email domains. */
|
|
56
|
+
emailDomains: string[];
|
|
57
|
+
/** Whether it is enabled. */
|
|
58
|
+
enabled: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type IdentityProvider = Resource<
|
|
62
|
+
"Daytona.IdentityProvider",
|
|
63
|
+
IdentityProviderProps,
|
|
64
|
+
IdentityProviderAttributes,
|
|
65
|
+
never,
|
|
66
|
+
Providers
|
|
67
|
+
>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* An OIDC identity provider for single sign-on into the Daytona
|
|
71
|
+
* organization. Needs an organization with SSO enabled (an enterprise
|
|
72
|
+
* plan); other organizations reject it.
|
|
73
|
+
*
|
|
74
|
+
* ### Single Sign-On
|
|
75
|
+
* **Example:** Sign in with your company's OIDC provider
|
|
76
|
+
* ```typescript
|
|
77
|
+
* export const Sso = Daytona.IdentityProvider("Sso", {
|
|
78
|
+
* issuerUrl: "https://login.example.com",
|
|
79
|
+
* clientId: "daytona",
|
|
80
|
+
* clientSecret: Config.Redacted("OIDC_CLIENT_SECRET"),
|
|
81
|
+
* emailDomains: ["example.com"],
|
|
82
|
+
* defaultRoles: [Developer],
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @resource
|
|
87
|
+
* @product Organization
|
|
88
|
+
*/
|
|
89
|
+
export const IdentityProvider = declarable<IdentityProvider>()(
|
|
90
|
+
Resource<IdentityProvider>("Daytona.IdentityProvider"),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const toAttributes = (
|
|
94
|
+
organizationId: string,
|
|
95
|
+
p: { id: string; name: string; emailDomains: string[]; enabled: boolean },
|
|
96
|
+
): IdentityProviderAttributes => ({
|
|
97
|
+
identityProviderId: p.id,
|
|
98
|
+
organizationId,
|
|
99
|
+
name: p.name,
|
|
100
|
+
emailDomains: p.emailDomains,
|
|
101
|
+
enabled: p.enabled,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const roleIds = (roles: IdentityProviderProps["defaultRoles"]) =>
|
|
105
|
+
Effect.succeed((roles ?? []).map((r) => resolved(r).roleId));
|
|
106
|
+
|
|
107
|
+
export const IdentityProviderProvider = () =>
|
|
108
|
+
Provider.effect(
|
|
109
|
+
IdentityProvider,
|
|
110
|
+
Effect.succeed({
|
|
111
|
+
stables: ["identityProviderId", "organizationId"],
|
|
112
|
+
list: Effect.fn(function* () {
|
|
113
|
+
const organizationId = yield* currentOrganizationId;
|
|
114
|
+
const all = yield* organizations.listOrganizationIdentityProviders({ organizationId });
|
|
115
|
+
return all.map((p) => toAttributes(organizationId, p));
|
|
116
|
+
}),
|
|
117
|
+
diff: Effect.fn(function* ({ news, output }) {
|
|
118
|
+
if (!isResolved(news) || output === undefined) return undefined;
|
|
119
|
+
if (yield* movesOrganization(news.organization, output))
|
|
120
|
+
return { action: "replace" } as const;
|
|
121
|
+
}),
|
|
122
|
+
read: Effect.fn(function* ({ id, olds, output }) {
|
|
123
|
+
const organizationId = output?.organizationId ?? (yield* currentOrganizationId);
|
|
124
|
+
const name = yield* resolveName(id, olds?.name, output?.name);
|
|
125
|
+
const all = yield* organizations.listOrganizationIdentityProviders({ organizationId });
|
|
126
|
+
const found =
|
|
127
|
+
all.find((p) => p.id === output?.identityProviderId) ?? all.find((p) => p.name === name);
|
|
128
|
+
if (found === undefined) return undefined;
|
|
129
|
+
const attrs = toAttributes(organizationId, found);
|
|
130
|
+
return (yield* isOwnedName(id, found.name, output !== undefined)) ? attrs : Unowned(attrs);
|
|
131
|
+
}),
|
|
132
|
+
reconcile: Effect.fn(function* ({ id, news, output }) {
|
|
133
|
+
const organizationId = yield* organizationIdFor(news.organization);
|
|
134
|
+
const name = yield* resolveName(id, news.name, output?.name);
|
|
135
|
+
const desired = {
|
|
136
|
+
name,
|
|
137
|
+
emailDomains: news.emailDomains,
|
|
138
|
+
allowAnyDomain: news.allowAnyDomain ?? false,
|
|
139
|
+
trustEmailVerified: news.trustEmailVerified ?? false,
|
|
140
|
+
defaultAssignedRoleIds: yield* roleIds(news.defaultRoles),
|
|
141
|
+
enabled: news.enabled ?? true,
|
|
142
|
+
};
|
|
143
|
+
const oidcConfig = {
|
|
144
|
+
issuerUrl: news.issuerUrl,
|
|
145
|
+
clientId: news.clientId,
|
|
146
|
+
clientSecret: news.clientSecret,
|
|
147
|
+
scope: news.scope,
|
|
148
|
+
};
|
|
149
|
+
// Observe
|
|
150
|
+
const all = yield* organizations.listOrganizationIdentityProviders({ organizationId });
|
|
151
|
+
const current =
|
|
152
|
+
all.find((p) => p.id === output?.identityProviderId) ?? all.find((p) => p.name === name);
|
|
153
|
+
// Ensure
|
|
154
|
+
if (current === undefined) {
|
|
155
|
+
const created = yield* organizations.createOrganizationIdentityProvider({
|
|
156
|
+
organizationId,
|
|
157
|
+
oidcConfig,
|
|
158
|
+
...desired,
|
|
159
|
+
});
|
|
160
|
+
return toAttributes(organizationId, created);
|
|
161
|
+
}
|
|
162
|
+
// Sync — the client secret can't be read back, so the full
|
|
163
|
+
// configuration is written on every deploy.
|
|
164
|
+
const updated = yield* organizations.updateOrganizationIdentityProvider({
|
|
165
|
+
organizationId,
|
|
166
|
+
id: current.id,
|
|
167
|
+
oidcConfig,
|
|
168
|
+
...desired,
|
|
169
|
+
});
|
|
170
|
+
return toAttributes(organizationId, updated);
|
|
171
|
+
}),
|
|
172
|
+
delete: Effect.fn(function* ({ output }) {
|
|
173
|
+
yield* organizations
|
|
174
|
+
.deleteOrganizationIdentityProvider({
|
|
175
|
+
organizationId: output.organizationId,
|
|
176
|
+
id: output.identityProviderId,
|
|
177
|
+
})
|
|
178
|
+
.pipe(Effect.catchTag("IdentityProviderNotFound", () => Effect.void));
|
|
179
|
+
}),
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as Binding from "alchemy/Binding";
|
|
2
|
+
import type { RuntimeContext } from "alchemy/RuntimeContext";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
import type { Sandbox } from "./Sandbox.ts";
|
|
6
|
+
import type { Volume } from "./Volume.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A volume a mount names: a `Daytona.Volume` (or the reference
|
|
10
|
+
* `MountVolume` returns), or a runtime handle from `CreateVolume`.
|
|
11
|
+
*/
|
|
12
|
+
export interface MountableVolume {
|
|
13
|
+
readonly volumeId: string | Effect.Effect<string, never, RuntimeContext>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A volume mounted at a path: an entry of `mounts`. */
|
|
17
|
+
export interface VolumeMount {
|
|
18
|
+
/** The volume. */
|
|
19
|
+
readonly volume: MountableVolume;
|
|
20
|
+
/** Absolute path in the sandbox. */
|
|
21
|
+
readonly path: string;
|
|
22
|
+
/** Mount only this directory of the volume. */
|
|
23
|
+
readonly subpath?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface MountVolumeOptions {
|
|
27
|
+
/** Absolute path in the sandbox. */
|
|
28
|
+
readonly path: string;
|
|
29
|
+
/** Mount only this directory of the volume. */
|
|
30
|
+
readonly subpath?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The volume ID behind a mount entry. */
|
|
34
|
+
export const volumeIdOf = (volume: MountableVolume) =>
|
|
35
|
+
typeof volume.volumeId === "string" ? Effect.succeed(volume.volumeId) : volume.volumeId;
|
|
36
|
+
|
|
37
|
+
const isSandboxHost = (value: unknown): value is Sandbox =>
|
|
38
|
+
typeof value === "object" &&
|
|
39
|
+
value !== null &&
|
|
40
|
+
(value as { Type?: string }).Type === "Daytona.Sandbox";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Mount a {@link Volume} at a path.
|
|
44
|
+
*
|
|
45
|
+
* Inside a sandbox's program, the volume is mounted into that sandbox:
|
|
46
|
+
* it joins the sandbox's `mounts`. A mount change replaces the sandbox,
|
|
47
|
+
* since Daytona only mounts volumes when it creates one. Inside any other
|
|
48
|
+
* host (a Worker, a Lambda), the binding resolves the volume and returns a
|
|
49
|
+
* mount reference to pass to `CreateSandbox`'s `create({ mounts })`.
|
|
50
|
+
*
|
|
51
|
+
* The reference has the same fields as a `mounts` entry: `volume`,
|
|
52
|
+
* `path` and `subpath`. Daytona has no per-volume access control, so the
|
|
53
|
+
* binding mints no credentials.
|
|
54
|
+
*
|
|
55
|
+
* ### Mounting a Volume in a Program
|
|
56
|
+
* **Example:** Serve the files of a volume
|
|
57
|
+
* ```typescript
|
|
58
|
+
* export default class Agent extends Daytona.Sandbox<Agent>()(
|
|
59
|
+
* "Agent",
|
|
60
|
+
* { main: import.meta.url, public: true },
|
|
61
|
+
* Effect.gen(function* () {
|
|
62
|
+
* const data = yield* Daytona.MountVolume(Data, { path: "/data" });
|
|
63
|
+
* const fs = yield* FileSystem.FileSystem;
|
|
64
|
+
* return {
|
|
65
|
+
* fetch: Effect.gen(function* () {
|
|
66
|
+
* return yield* HttpServerResponse.json(yield* fs.readDirectory(data.path));
|
|
67
|
+
* }),
|
|
68
|
+
* };
|
|
69
|
+
* }).pipe(Effect.provide(Daytona.MountVolumeLive)),
|
|
70
|
+
* ) {}
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* ### Mounting a Volume in Sandboxes You Create
|
|
74
|
+
* **Example:** A shared cache plus each user's home
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const cache = yield* Daytona.MountVolume(Cache, { path: "/cache" });
|
|
77
|
+
* const sandboxes = yield* Daytona.CreateSandbox(Base);
|
|
78
|
+
* const volumes = yield* Daytona.CreateVolume({ prefix: "user-" });
|
|
79
|
+
* return {
|
|
80
|
+
* fetch: Effect.gen(function* () {
|
|
81
|
+
* const home = yield* volumes.getOrCreate({ name: `user-${user}` });
|
|
82
|
+
* const sandbox = yield* sandboxes.create({
|
|
83
|
+
* mounts: [cache, { volume: home, path: "/home/daytona/work" }],
|
|
84
|
+
* });
|
|
85
|
+
* return yield* HttpServerResponse.json(yield* sandbox.exec("ls /cache"));
|
|
86
|
+
* }),
|
|
87
|
+
* };
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @binding
|
|
91
|
+
* @product Volume
|
|
92
|
+
*/
|
|
93
|
+
export interface MountVolume extends Binding.Service<
|
|
94
|
+
MountVolume,
|
|
95
|
+
"Daytona.MountVolume",
|
|
96
|
+
(volume: Volume, options: MountVolumeOptions) => Effect.Effect<VolumeMount>
|
|
97
|
+
> {}
|
|
98
|
+
|
|
99
|
+
export const MountVolume = Binding.Service<MountVolume>("Daytona.MountVolume");
|
|
100
|
+
|
|
101
|
+
/** {@link MountVolume}; works on any host. */
|
|
102
|
+
export const MountVolumeLive = Layer.effect(
|
|
103
|
+
MountVolume,
|
|
104
|
+
Effect.succeed(
|
|
105
|
+
Effect.fn(function* (volume: Volume, options: MountVolumeOptions) {
|
|
106
|
+
const volumeId = yield* volume.volumeId;
|
|
107
|
+
if (!globalThis.__ALCHEMY_RUNTIME__) {
|
|
108
|
+
const host = yield* Binding.Host;
|
|
109
|
+
if (isSandboxHost(host)) {
|
|
110
|
+
yield* host.bind`Daytona.MountVolume(${volume})`({
|
|
111
|
+
mounts: [{ volumeId: volume.volumeId, path: options.path, subpath: options.subpath }],
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
volume: { volumeId },
|
|
117
|
+
path: options.path,
|
|
118
|
+
subpath: options.subpath,
|
|
119
|
+
} satisfies VolumeMount;
|
|
120
|
+
}),
|
|
121
|
+
),
|
|
122
|
+
);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { makeOAuthClient } from "alchemy/Auth/OAuthFlow";
|
|
2
|
+
|
|
3
|
+
export { OAuthCredentials, OAuthError, type DeviceAuthorization } from "alchemy/Auth/OAuthFlow";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The Daytona CLI's public WorkOS client (no secret), published by Daytona's
|
|
7
|
+
* `GET /api/config` under `oidc.cli`. `daytona login` uses the same device
|
|
8
|
+
* flow. A user session can call what an organization API key can't: the
|
|
9
|
+
* organization, role, invitation and API key endpoints.
|
|
10
|
+
*/
|
|
11
|
+
export const OAUTH_CLIENT_ID = "client_01M392QZWMYD01SA05BPEYQ84V";
|
|
12
|
+
|
|
13
|
+
export const OAUTH_ENDPOINTS = {
|
|
14
|
+
deviceAuthorization: "https://api.auth.daytona.io/user_management/authorize/device",
|
|
15
|
+
token: "https://api.auth.daytona.io/user_management/authenticate",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const client = makeOAuthClient({
|
|
19
|
+
clientId: OAUTH_CLIENT_ID,
|
|
20
|
+
endpoints: OAUTH_ENDPOINTS,
|
|
21
|
+
auth: { kind: "pkce" },
|
|
22
|
+
// WorkOS answers on api.auth.daytona.io but serves the device page on
|
|
23
|
+
// auth.daytona.io.
|
|
24
|
+
verificationHosts: ["auth.daytona.io"],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const { deviceAuthorize, deviceToken, refresh, usesCurrentClient } = client;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import * as organizations from "@distilled.cloud/daytona/organizations";
|
|
2
|
+
import { isResolved } from "alchemy/Diff";
|
|
3
|
+
import * as Provider from "alchemy/Provider";
|
|
4
|
+
import { Resource } from "alchemy/Resource";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import { resolveName } from "./internal/Names.ts";
|
|
7
|
+
import type { Providers } from "./Providers.ts";
|
|
8
|
+
|
|
9
|
+
export interface OrganizationProps {
|
|
10
|
+
/** Organization name. If omitted, generated from the stack, stage and logical ID. */
|
|
11
|
+
name?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Region new sandboxes go to by default. Daytona sets it once, so changing
|
|
14
|
+
* it replaces the organization.
|
|
15
|
+
*/
|
|
16
|
+
defaultRegionId: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface OrganizationAttributes {
|
|
20
|
+
/** Organization ID. */
|
|
21
|
+
organizationId: string;
|
|
22
|
+
/** Name. */
|
|
23
|
+
name: string;
|
|
24
|
+
/** Default region. */
|
|
25
|
+
defaultRegionId: string | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type Organization = Resource<
|
|
29
|
+
"Daytona.Organization",
|
|
30
|
+
OrganizationProps,
|
|
31
|
+
OrganizationAttributes,
|
|
32
|
+
never,
|
|
33
|
+
Providers
|
|
34
|
+
>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A new Daytona organization, owned by the signed-in user. Creating
|
|
38
|
+
* organizations needs a user login, not an organization API key: an API
|
|
39
|
+
* key already belongs to exactly one organization. To configure the
|
|
40
|
+
* organization your key belongs to, use {@link OrganizationSettings}.
|
|
41
|
+
*
|
|
42
|
+
* ### Creating an Organization
|
|
43
|
+
* **Example:** A separate organization for a team
|
|
44
|
+
* ```typescript
|
|
45
|
+
* export const Team = Daytona.Organization("Team", { defaultRegionId: "us" });
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @resource
|
|
49
|
+
* @product Organization
|
|
50
|
+
*/
|
|
51
|
+
export const Organization = Resource<Organization>("Daytona.Organization");
|
|
52
|
+
|
|
53
|
+
const toAttributes = (o: organizations.Organization): OrganizationAttributes => ({
|
|
54
|
+
organizationId: o.id,
|
|
55
|
+
name: o.name,
|
|
56
|
+
defaultRegionId: o.defaultRegionId,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const OrganizationProvider = () =>
|
|
60
|
+
Provider.effect(
|
|
61
|
+
Organization,
|
|
62
|
+
Effect.succeed({
|
|
63
|
+
stables: ["organizationId"],
|
|
64
|
+
nuke: { skip: true },
|
|
65
|
+
list: () => Effect.succeed([]),
|
|
66
|
+
diff: Effect.fn(function* ({ id, news, output }) {
|
|
67
|
+
if (!isResolved(news) || output === undefined) return undefined;
|
|
68
|
+
const name = yield* resolveName(
|
|
69
|
+
id,
|
|
70
|
+
news.name,
|
|
71
|
+
news.name === undefined ? output.name : undefined,
|
|
72
|
+
);
|
|
73
|
+
// Reconcile finds an organization by name, so the old one must go
|
|
74
|
+
// first or the replacement would adopt it.
|
|
75
|
+
if (
|
|
76
|
+
name !== output.name ||
|
|
77
|
+
(output.defaultRegionId !== undefined && news.defaultRegionId !== output.defaultRegionId)
|
|
78
|
+
) {
|
|
79
|
+
return { action: "replace", deleteFirst: true } as const;
|
|
80
|
+
}
|
|
81
|
+
}),
|
|
82
|
+
read: Effect.fn(function* ({ output }) {
|
|
83
|
+
if (output === undefined) return undefined;
|
|
84
|
+
return yield* organizations.getOrganization({ organizationId: output.organizationId }).pipe(
|
|
85
|
+
Effect.map(toAttributes),
|
|
86
|
+
Effect.catchTag("OrganizationNotFound", () => Effect.undefined),
|
|
87
|
+
);
|
|
88
|
+
}),
|
|
89
|
+
reconcile: Effect.fn(function* ({ id, news, output }) {
|
|
90
|
+
const name = yield* resolveName(id, news.name, output?.name);
|
|
91
|
+
// Observe
|
|
92
|
+
let current = output
|
|
93
|
+
? yield* organizations
|
|
94
|
+
.getOrganization({ organizationId: output.organizationId })
|
|
95
|
+
.pipe(Effect.catchTag("OrganizationNotFound", () => Effect.undefined))
|
|
96
|
+
: (yield* organizations.listOrganizations({})).find((o) => o.name === name);
|
|
97
|
+
// Ensure
|
|
98
|
+
if (current === undefined) {
|
|
99
|
+
current = yield* organizations.createOrganization({
|
|
100
|
+
name,
|
|
101
|
+
defaultRegionId: news.defaultRegionId,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// Sync: Daytona only accepts a default region while none is set
|
|
105
|
+
// (an adopted organization); changing one replaces (see diff).
|
|
106
|
+
if (current.defaultRegionId === undefined) {
|
|
107
|
+
yield* organizations.setOrganizationDefaultRegion({
|
|
108
|
+
organizationId: current.id,
|
|
109
|
+
defaultRegionId: news.defaultRegionId,
|
|
110
|
+
});
|
|
111
|
+
current = yield* organizations.getOrganization({ organizationId: current.id });
|
|
112
|
+
}
|
|
113
|
+
return toAttributes(current);
|
|
114
|
+
}),
|
|
115
|
+
delete: Effect.fn(function* ({ output }) {
|
|
116
|
+
yield* organizations
|
|
117
|
+
.deleteOrganization({ organizationId: output.organizationId })
|
|
118
|
+
.pipe(Effect.catchTag("OrganizationNotFound", () => Effect.void));
|
|
119
|
+
}),
|
|
120
|
+
}),
|
|
121
|
+
);
|