alchemy 2.0.0-beta.64 → 2.0.0-beta.65
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/lib/AWS/EC2/InternetGateway.d.ts.map +1 -1
- package/lib/AWS/EC2/InternetGateway.js +8 -3
- package/lib/AWS/EC2/InternetGateway.js.map +1 -1
- package/lib/AWS/EC2/LingeringEnis.d.ts +26 -0
- package/lib/AWS/EC2/LingeringEnis.d.ts.map +1 -0
- package/lib/AWS/EC2/LingeringEnis.js +128 -0
- package/lib/AWS/EC2/LingeringEnis.js.map +1 -0
- package/lib/AWS/EC2/SecurityGroup.d.ts.map +1 -1
- package/lib/AWS/EC2/SecurityGroup.js +14 -14
- package/lib/AWS/EC2/SecurityGroup.js.map +1 -1
- package/lib/AWS/EC2/Subnet.d.ts.map +1 -1
- package/lib/AWS/EC2/Subnet.js +13 -21
- package/lib/AWS/EC2/Subnet.js.map +1 -1
- package/lib/AWS/ECR/Image.d.ts +1 -0
- package/lib/AWS/ECR/Image.d.ts.map +1 -1
- package/lib/AWS/EKS/Addon.d.ts.map +1 -1
- package/lib/AWS/EKS/Addon.js +15 -3
- package/lib/AWS/EKS/Addon.js.map +1 -1
- package/lib/AWS/EKS/Deployment.d.ts +10 -1
- package/lib/AWS/EKS/Deployment.d.ts.map +1 -1
- package/lib/AWS/EKS/Deployment.js +10 -4
- package/lib/AWS/EKS/Deployment.js.map +1 -1
- package/lib/AWS/EKS/HyperPod.d.ts +57 -0
- package/lib/AWS/EKS/HyperPod.d.ts.map +1 -0
- package/lib/AWS/EKS/HyperPod.js +46 -0
- package/lib/AWS/EKS/HyperPod.js.map +1 -0
- package/lib/AWS/EKS/Job.d.ts +10 -1
- package/lib/AWS/EKS/Job.d.ts.map +1 -1
- package/lib/AWS/EKS/Job.js +16 -6
- package/lib/AWS/EKS/Job.js.map +1 -1
- package/lib/AWS/EKS/internal/client.d.ts +4 -3
- package/lib/AWS/EKS/internal/client.d.ts.map +1 -1
- package/lib/AWS/EKS/internal/client.js +33 -2
- package/lib/AWS/EKS/internal/client.js.map +1 -1
- package/lib/AWS/Lambda/Function.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.js +1 -1
- package/lib/AWS/Lambda/Function.js.map +1 -1
- package/lib/AWS/Providers.d.ts.map +1 -1
- package/lib/AWS/Providers.js +4 -1
- package/lib/AWS/Providers.js.map +1 -1
- package/lib/AWS/SageMaker/Cluster.d.ts +327 -0
- package/lib/AWS/SageMaker/Cluster.d.ts.map +1 -0
- package/lib/AWS/SageMaker/Cluster.js +469 -0
- package/lib/AWS/SageMaker/Cluster.js.map +1 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.d.ts +96 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.d.ts.map +1 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.js +270 -0
- package/lib/AWS/SageMaker/ClusterSchedulerConfig.js.map +1 -0
- package/lib/AWS/SageMaker/ComputeQuota.d.ts +106 -0
- package/lib/AWS/SageMaker/ComputeQuota.d.ts.map +1 -0
- package/lib/AWS/SageMaker/ComputeQuota.js +268 -0
- package/lib/AWS/SageMaker/ComputeQuota.js.map +1 -0
- package/lib/AWS/SageMaker/index.d.ts +3 -0
- package/lib/AWS/SageMaker/index.d.ts.map +1 -1
- package/lib/AWS/SageMaker/index.js +3 -0
- package/lib/AWS/SageMaker/index.js.map +1 -1
- package/lib/Bundle/PurePlugin.d.ts +29 -7
- package/lib/Bundle/PurePlugin.d.ts.map +1 -1
- package/lib/Bundle/PurePlugin.js +56 -33
- package/lib/Bundle/PurePlugin.js.map +1 -1
- package/lib/Cli/commands/nuke.d.ts.map +1 -1
- package/lib/Cli/commands/nuke.js +231 -37
- package/lib/Cli/commands/nuke.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +1 -1
- package/lib/Cloudflare/Workers/Assets.d.ts +26 -8
- package/lib/Cloudflare/Workers/Assets.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Assets.js +46 -1
- package/lib/Cloudflare/Workers/Assets.js.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts +2 -2
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js +39 -6
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +7 -5
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +0 -1
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts +2 -2
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts +20 -1
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.js +11 -0
- package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.js +10 -4
- package/lib/Cloudflare/Workers/WorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.d.ts +2 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.d.ts.map +1 -1
- package/lib/Cloudflare/Workflows/WorkflowBridge.js +5 -5
- package/lib/Cloudflare/Workflows/WorkflowBridge.js.map +1 -1
- package/lib/Cloudflare/Workflows/index.d.ts +1 -1
- package/lib/Cloudflare/Workflows/index.d.ts.map +1 -1
- package/lib/Cloudflare/Workflows/index.js +1 -1
- package/lib/Cloudflare/Workflows/index.js.map +1 -1
- package/lib/Docker/Container.d.ts +26 -0
- package/lib/Docker/Container.d.ts.map +1 -1
- package/lib/Docker/Container.js +20 -1
- package/lib/Docker/Container.js.map +1 -1
- package/lib/Docker/Docker.d.ts +2 -0
- package/lib/Docker/Docker.d.ts.map +1 -1
- package/lib/Docker/Docker.js.map +1 -1
- package/lib/Output.d.ts +4 -1
- package/lib/Output.d.ts.map +1 -1
- package/lib/Output.js.map +1 -1
- package/lib/Provider.d.ts +20 -0
- package/lib/Provider.d.ts.map +1 -1
- package/lib/Provider.js.map +1 -1
- package/lib/Random.d.ts +1 -1
- package/lib/Random.d.ts.map +1 -1
- package/lib/Resource.d.ts +14 -1
- package/lib/Resource.d.ts.map +1 -1
- package/lib/Resource.js.map +1 -1
- package/lib/SQL/D1.d.ts +1 -1
- package/lib/SQL/D1.js +1 -1
- package/lib/SQL/Postgres.d.ts +1 -1
- package/lib/SQL/Postgres.js +1 -1
- package/lib/State/HttpStateApi.d.ts +11 -11
- package/lib/Test/Alchemy.d.ts +1 -1
- package/lib/Test/Alchemy.js +1 -1
- package/lib/Test/Vitest.d.ts +58 -0
- package/lib/Test/Vitest.d.ts.map +1 -0
- package/lib/Test/Vitest.js +114 -0
- package/lib/Test/Vitest.js.map +1 -0
- package/lib/Util/proxy-chain.d.ts +5 -0
- package/lib/Util/proxy-chain.d.ts.map +1 -1
- package/lib/Util/proxy-chain.js +52 -4
- package/lib/Util/proxy-chain.js.map +1 -1
- package/package.json +23 -15
- package/src/AWS/EC2/InternetGateway.ts +8 -3
- package/src/AWS/EC2/LingeringEnis.ts +169 -0
- package/src/AWS/EC2/SecurityGroup.ts +24 -28
- package/src/AWS/EC2/Subnet.ts +23 -34
- package/src/AWS/EKS/Addon.ts +17 -4
- package/src/AWS/EKS/Deployment.ts +26 -5
- package/src/AWS/EKS/HyperPod.ts +97 -0
- package/src/AWS/EKS/Job.ts +32 -7
- package/src/AWS/EKS/internal/client.ts +40 -3
- package/src/AWS/Lambda/Function.ts +5 -3
- package/src/AWS/Providers.ts +6 -0
- package/src/AWS/SageMaker/Cluster.ts +775 -0
- package/src/AWS/SageMaker/ClusterSchedulerConfig.ts +430 -0
- package/src/AWS/SageMaker/ComputeQuota.ts +426 -0
- package/src/AWS/SageMaker/index.ts +3 -0
- package/src/Bundle/PurePlugin.ts +92 -39
- package/src/Cli/commands/nuke.ts +295 -56
- package/src/Cloudflare/Workers/Assets.ts +53 -1
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +46 -7
- package/src/Cloudflare/Workers/Worker.ts +7 -5
- package/src/Cloudflare/Workers/WorkerBundle.ts +31 -1
- package/src/Cloudflare/Workers/WorkerProvider.ts +18 -4
- package/src/Cloudflare/Workflows/WorkflowBridge.ts +5 -5
- package/src/Cloudflare/Workflows/index.ts +1 -1
- package/src/Docker/Container.ts +30 -1
- package/src/Docker/Docker.ts +2 -0
- package/src/Output.ts +19 -11
- package/src/Provider.ts +20 -0
- package/src/Resource.ts +18 -1
- package/src/SQL/D1.ts +1 -1
- package/src/SQL/Postgres.ts +1 -1
- package/src/Test/Alchemy.ts +1 -1
- package/src/Test/Vitest.ts +244 -0
- package/src/Util/proxy-chain.ts +73 -4
- package/lib/SQL/index.d.ts +0 -4
- package/lib/SQL/index.d.ts.map +0 -1
- package/lib/SQL/index.js +0 -4
- package/lib/SQL/index.js.map +0 -1
- package/src/SQL/index.ts +0 -3
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import * as sagemaker from "@distilled.cloud/aws/sagemaker";
|
|
2
|
+
import * as Data from "effect/Data";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Schedule from "effect/Schedule";
|
|
5
|
+
import * as EffectStream from "effect/Stream";
|
|
6
|
+
import { Unowned } from "../../AdoptPolicy.ts";
|
|
7
|
+
import { isResolved } from "../../Diff.ts";
|
|
8
|
+
import { createPhysicalName } from "../../PhysicalName.ts";
|
|
9
|
+
import * as Provider from "../../Provider.ts";
|
|
10
|
+
import { Resource } from "../../Resource.ts";
|
|
11
|
+
import {
|
|
12
|
+
createInternalTags,
|
|
13
|
+
diffTags,
|
|
14
|
+
hasAlchemyTags,
|
|
15
|
+
type Tags,
|
|
16
|
+
} from "../../Tags.ts";
|
|
17
|
+
import type { Providers } from "../Providers.ts";
|
|
18
|
+
|
|
19
|
+
export type SchedulerResourceStatus = sagemaker.SchedulerResourceStatus;
|
|
20
|
+
|
|
21
|
+
export interface ClusterSchedulerConfigProps {
|
|
22
|
+
/**
|
|
23
|
+
* Name of the cluster policy. Maximum 63 characters, alphanumeric and
|
|
24
|
+
* hyphens.
|
|
25
|
+
* @default ${app}-${stage}-${id}
|
|
26
|
+
*/
|
|
27
|
+
name?: string;
|
|
28
|
+
/**
|
|
29
|
+
* ARN of the EKS-orchestrated HyperPod cluster the policy applies to.
|
|
30
|
+
* Changing the cluster replaces the policy. AWS allows ONE cluster
|
|
31
|
+
* policy per cluster — creating a second fails with the typed
|
|
32
|
+
* `ClusterSchedulerConfigAlreadyExists` error.
|
|
33
|
+
*/
|
|
34
|
+
clusterArn: string;
|
|
35
|
+
/**
|
|
36
|
+
* The policy itself: task priority classes, fair-share allocation, and
|
|
37
|
+
* idle-resource sharing.
|
|
38
|
+
*/
|
|
39
|
+
schedulerConfig?: sagemaker.SchedulerConfig;
|
|
40
|
+
/**
|
|
41
|
+
* A description of the cluster policy.
|
|
42
|
+
*/
|
|
43
|
+
description?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Tags to associate with the cluster policy. Merged with internal Alchemy
|
|
46
|
+
* tags.
|
|
47
|
+
*/
|
|
48
|
+
tags?: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ClusterSchedulerConfig extends Resource<
|
|
52
|
+
"AWS.SageMaker.ClusterSchedulerConfig",
|
|
53
|
+
ClusterSchedulerConfigProps,
|
|
54
|
+
{
|
|
55
|
+
/**
|
|
56
|
+
* The cluster policy's ID.
|
|
57
|
+
*/
|
|
58
|
+
clusterSchedulerConfigId: string;
|
|
59
|
+
/**
|
|
60
|
+
* ARN of the cluster policy.
|
|
61
|
+
*/
|
|
62
|
+
clusterSchedulerConfigArn: string;
|
|
63
|
+
/**
|
|
64
|
+
* The cluster policy's name.
|
|
65
|
+
*/
|
|
66
|
+
name: string;
|
|
67
|
+
/**
|
|
68
|
+
* ARN of the HyperPod cluster the policy applies to.
|
|
69
|
+
*/
|
|
70
|
+
clusterArn: string;
|
|
71
|
+
/**
|
|
72
|
+
* The policy's current version (incremented on every update).
|
|
73
|
+
*/
|
|
74
|
+
clusterSchedulerConfigVersion: number;
|
|
75
|
+
},
|
|
76
|
+
never,
|
|
77
|
+
Providers
|
|
78
|
+
> {}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A SageMaker HyperPod cluster policy (task governance) — configures how an
|
|
82
|
+
* EKS-orchestrated HyperPod cluster prioritizes tasks and allocates idle
|
|
83
|
+
* compute across teams via priority classes and fair-share weights.
|
|
84
|
+
* @resource
|
|
85
|
+
* @section Creating Cluster Policies
|
|
86
|
+
* @example Priority Classes with Fair-Share
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import * as AWS from "alchemy/AWS";
|
|
89
|
+
*
|
|
90
|
+
* const policy = yield* AWS.SageMaker.ClusterSchedulerConfig("Scheduler", {
|
|
91
|
+
* clusterArn: hyperpod.clusterArn,
|
|
92
|
+
* schedulerConfig: {
|
|
93
|
+
* PriorityClasses: [
|
|
94
|
+
* { Name: "inference", Weight: 100 },
|
|
95
|
+
* { Name: "training", Weight: 75 },
|
|
96
|
+
* ],
|
|
97
|
+
* FairShare: "Enabled",
|
|
98
|
+
* },
|
|
99
|
+
* description: "Prioritize inference over training",
|
|
100
|
+
* });
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export const ClusterSchedulerConfig = Resource<ClusterSchedulerConfig>(
|
|
104
|
+
"AWS.SageMaker.ClusterSchedulerConfig",
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const createConfigName = (id: string, props: { name?: string | undefined }) =>
|
|
108
|
+
props.name
|
|
109
|
+
? Effect.succeed(props.name)
|
|
110
|
+
: createPhysicalName({ id, maxLength: 63 });
|
|
111
|
+
|
|
112
|
+
const describeConfigOrUndefined = (configId: string) =>
|
|
113
|
+
sagemaker
|
|
114
|
+
.describeClusterSchedulerConfig({ ClusterSchedulerConfigId: configId })
|
|
115
|
+
.pipe(Effect.catchTag("ResourceNotFound", () => Effect.succeed(undefined)));
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Look a cluster policy up by exact name — used when state was lost (read
|
|
119
|
+
* without output) or a create raced.
|
|
120
|
+
*/
|
|
121
|
+
const findConfigByName = Effect.fn(function* (name: string) {
|
|
122
|
+
const summaries = yield* sagemaker.listClusterSchedulerConfigs
|
|
123
|
+
.pages({ NameContains: name })
|
|
124
|
+
.pipe(
|
|
125
|
+
EffectStream.runCollect,
|
|
126
|
+
Effect.map((chunk) =>
|
|
127
|
+
Array.from(chunk).flatMap(
|
|
128
|
+
(page) => page.ClusterSchedulerConfigSummaries ?? [],
|
|
129
|
+
),
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
return summaries.find((s) => s.Name === name && s.Status !== "Deleted");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const fetchConfigTags = Effect.fn(function* (arn: string) {
|
|
136
|
+
const response = yield* sagemaker
|
|
137
|
+
.listTags({ ResourceArn: arn })
|
|
138
|
+
.pipe(
|
|
139
|
+
Effect.catchTag("AccessDeniedException", () => Effect.succeed(undefined)),
|
|
140
|
+
);
|
|
141
|
+
return Object.fromEntries(
|
|
142
|
+
(response?.Tags ?? []).flatMap((tag) =>
|
|
143
|
+
tag.Key !== undefined ? [[tag.Key, tag.Value ?? ""]] : [],
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const toAttrs = (
|
|
149
|
+
described: sagemaker.DescribeClusterSchedulerConfigResponse,
|
|
150
|
+
): ClusterSchedulerConfig["Attributes"] => ({
|
|
151
|
+
clusterSchedulerConfigId: described.ClusterSchedulerConfigId,
|
|
152
|
+
clusterSchedulerConfigArn: described.ClusterSchedulerConfigArn,
|
|
153
|
+
name: described.Name,
|
|
154
|
+
clusterArn: described.ClusterArn ?? "",
|
|
155
|
+
clusterSchedulerConfigVersion: described.ClusterSchedulerConfigVersion,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The cluster policy is still transitioning toward the awaited state —
|
|
160
|
+
* retried by the bounded wait schedule.
|
|
161
|
+
*/
|
|
162
|
+
class SchedulerConfigNotReady extends Data.TaggedError(
|
|
163
|
+
"SchedulerConfigNotReady",
|
|
164
|
+
)<{
|
|
165
|
+
readonly configId: string;
|
|
166
|
+
readonly status: string | undefined;
|
|
167
|
+
}> {}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The cluster policy converged to a terminal failed status.
|
|
171
|
+
*/
|
|
172
|
+
export class SchedulerConfigFailed extends Data.TaggedError(
|
|
173
|
+
"SchedulerConfigFailed",
|
|
174
|
+
)<{
|
|
175
|
+
readonly configId: string;
|
|
176
|
+
readonly status: string | undefined;
|
|
177
|
+
readonly message: string | undefined;
|
|
178
|
+
}> {}
|
|
179
|
+
|
|
180
|
+
const FAILED_STATUSES: string[] = [
|
|
181
|
+
"CreateFailed",
|
|
182
|
+
"CreateRollbackFailed",
|
|
183
|
+
"UpdateFailed",
|
|
184
|
+
"UpdateRollbackFailed",
|
|
185
|
+
"DeleteFailed",
|
|
186
|
+
"DeleteRollbackFailed",
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
// Explicitly-typed retry wrapper — an inline `Effect.retry` in provider
|
|
190
|
+
// lifecycle code leaks `Retry.Return`'s conditional type into declaration
|
|
191
|
+
// emit and widens the provider layer to `unknown` for every consumer of
|
|
192
|
+
// `AWS.providers()`.
|
|
193
|
+
const retryWhileNotReady = <A, E extends { readonly _tag: string }, R>(
|
|
194
|
+
self: Effect.Effect<A, E, R>,
|
|
195
|
+
): Effect.Effect<A, E, R> =>
|
|
196
|
+
Effect.retry(self, {
|
|
197
|
+
while: (e) => e._tag === "SchedulerConfigNotReady",
|
|
198
|
+
schedule: Schedule.max([Schedule.spaced("5 seconds"), Schedule.recurs(60)]),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const waitForConfig = (configId: string, target: "Ready" | "Gone") =>
|
|
202
|
+
retryWhileNotReady(
|
|
203
|
+
Effect.gen(function* () {
|
|
204
|
+
const described = yield* describeConfigOrUndefined(configId);
|
|
205
|
+
if (target === "Gone") {
|
|
206
|
+
if (described === undefined || described.Status === "Deleted") return;
|
|
207
|
+
if (FAILED_STATUSES.includes(described.Status)) {
|
|
208
|
+
return yield* Effect.fail(
|
|
209
|
+
new SchedulerConfigFailed({
|
|
210
|
+
configId,
|
|
211
|
+
status: described.Status,
|
|
212
|
+
message: described.FailureReason,
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return yield* Effect.fail(
|
|
217
|
+
new SchedulerConfigNotReady({ configId, status: described.Status }),
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (described?.Status === "Created" || described?.Status === "Updated") {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (
|
|
224
|
+
described !== undefined &&
|
|
225
|
+
FAILED_STATUSES.includes(described.Status)
|
|
226
|
+
) {
|
|
227
|
+
return yield* Effect.fail(
|
|
228
|
+
new SchedulerConfigFailed({
|
|
229
|
+
configId,
|
|
230
|
+
status: described.Status,
|
|
231
|
+
message: described.FailureReason,
|
|
232
|
+
}),
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return yield* Effect.fail(
|
|
236
|
+
new SchedulerConfigNotReady({ configId, status: described?.Status }),
|
|
237
|
+
);
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
export const ClusterSchedulerConfigProvider = () =>
|
|
242
|
+
Provider.effect(
|
|
243
|
+
ClusterSchedulerConfig,
|
|
244
|
+
Effect.gen(function* () {
|
|
245
|
+
return {
|
|
246
|
+
stables: [
|
|
247
|
+
"clusterSchedulerConfigId",
|
|
248
|
+
"clusterSchedulerConfigArn",
|
|
249
|
+
"name",
|
|
250
|
+
"clusterArn",
|
|
251
|
+
],
|
|
252
|
+
list: () =>
|
|
253
|
+
Effect.gen(function* () {
|
|
254
|
+
const summaries = yield* sagemaker.listClusterSchedulerConfigs
|
|
255
|
+
.pages({})
|
|
256
|
+
.pipe(
|
|
257
|
+
EffectStream.runCollect,
|
|
258
|
+
Effect.map((chunk) =>
|
|
259
|
+
Array.from(chunk).flatMap(
|
|
260
|
+
(page) => page.ClusterSchedulerConfigSummaries ?? [],
|
|
261
|
+
),
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
return summaries.flatMap((s) =>
|
|
265
|
+
s.ClusterSchedulerConfigId !== undefined && s.Status !== "Deleted"
|
|
266
|
+
? [
|
|
267
|
+
{
|
|
268
|
+
clusterSchedulerConfigId: s.ClusterSchedulerConfigId,
|
|
269
|
+
clusterSchedulerConfigArn:
|
|
270
|
+
s.ClusterSchedulerConfigArn ?? "",
|
|
271
|
+
name: s.Name ?? "",
|
|
272
|
+
clusterArn: s.ClusterArn ?? "",
|
|
273
|
+
clusterSchedulerConfigVersion:
|
|
274
|
+
s.ClusterSchedulerConfigVersion ?? 1,
|
|
275
|
+
},
|
|
276
|
+
]
|
|
277
|
+
: [],
|
|
278
|
+
);
|
|
279
|
+
}),
|
|
280
|
+
read: Effect.fn(function* ({ id, olds, output }) {
|
|
281
|
+
const configId =
|
|
282
|
+
output?.clusterSchedulerConfigId ??
|
|
283
|
+
(yield* findConfigByName(yield* createConfigName(id, olds ?? {})))
|
|
284
|
+
?.ClusterSchedulerConfigId;
|
|
285
|
+
if (configId === undefined) return undefined;
|
|
286
|
+
const described = yield* describeConfigOrUndefined(configId);
|
|
287
|
+
if (
|
|
288
|
+
!described ||
|
|
289
|
+
described.Status === "Deleting" ||
|
|
290
|
+
described.Status === "Deleted"
|
|
291
|
+
) {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
const attrs = toAttrs(described);
|
|
295
|
+
const tags = yield* fetchConfigTags(attrs.clusterSchedulerConfigArn);
|
|
296
|
+
return (yield* hasAlchemyTags(id, tags as Tags))
|
|
297
|
+
? attrs
|
|
298
|
+
: Unowned(attrs);
|
|
299
|
+
}),
|
|
300
|
+
diff: Effect.fn(function* ({ id, news, olds }) {
|
|
301
|
+
if (!isResolved(news)) return;
|
|
302
|
+
if (olds === undefined) return;
|
|
303
|
+
const oldName = yield* createConfigName(id, olds);
|
|
304
|
+
const newName = yield* createConfigName(id, news);
|
|
305
|
+
// The name and target cluster are fixed at creation.
|
|
306
|
+
if (oldName !== newName || olds.clusterArn !== news.clusterArn) {
|
|
307
|
+
return { action: "replace" } as const;
|
|
308
|
+
}
|
|
309
|
+
}),
|
|
310
|
+
reconcile: Effect.fn(function* ({ id, news, output, session }) {
|
|
311
|
+
if (!news) {
|
|
312
|
+
return yield* Effect.fail(
|
|
313
|
+
new Error("SageMaker ClusterSchedulerConfig requires props"),
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
const name = yield* createConfigName(id, news);
|
|
317
|
+
const internalTags = yield* createInternalTags(id);
|
|
318
|
+
const desiredTags = { ...internalTags, ...news.tags };
|
|
319
|
+
|
|
320
|
+
// Observe — by cached id first, then by name (lost state or race).
|
|
321
|
+
let configId = output?.clusterSchedulerConfigId;
|
|
322
|
+
let described =
|
|
323
|
+
configId !== undefined
|
|
324
|
+
? yield* describeConfigOrUndefined(configId)
|
|
325
|
+
: undefined;
|
|
326
|
+
if (described === undefined) {
|
|
327
|
+
const found = yield* findConfigByName(name);
|
|
328
|
+
described =
|
|
329
|
+
found?.ClusterSchedulerConfigId !== undefined
|
|
330
|
+
? yield* describeConfigOrUndefined(
|
|
331
|
+
found.ClusterSchedulerConfigId,
|
|
332
|
+
)
|
|
333
|
+
: undefined;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Ensure — create if missing; a Conflict means a concurrent
|
|
337
|
+
// create won the race, so re-observe by name.
|
|
338
|
+
if (described === undefined) {
|
|
339
|
+
const created = yield* sagemaker
|
|
340
|
+
.createClusterSchedulerConfig({
|
|
341
|
+
Name: name,
|
|
342
|
+
ClusterArn: news.clusterArn,
|
|
343
|
+
SchedulerConfig: news.schedulerConfig,
|
|
344
|
+
Description: news.description,
|
|
345
|
+
Tags: Object.entries(desiredTags).map(([Key, Value]) => ({
|
|
346
|
+
Key,
|
|
347
|
+
Value,
|
|
348
|
+
})),
|
|
349
|
+
})
|
|
350
|
+
.pipe(
|
|
351
|
+
Effect.catchTag("ConflictException", () =>
|
|
352
|
+
Effect.succeed(undefined),
|
|
353
|
+
),
|
|
354
|
+
);
|
|
355
|
+
configId =
|
|
356
|
+
created?.ClusterSchedulerConfigId ??
|
|
357
|
+
(yield* findConfigByName(name))?.ClusterSchedulerConfigId;
|
|
358
|
+
if (configId === undefined) {
|
|
359
|
+
return yield* Effect.fail(
|
|
360
|
+
new Error(`failed to create cluster scheduler config ${name}`),
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
yield* session.note(`Creating cluster policy ${name}...`);
|
|
364
|
+
yield* waitForConfig(configId, "Ready");
|
|
365
|
+
} else {
|
|
366
|
+
configId = described.ClusterSchedulerConfigId;
|
|
367
|
+
// Wait out any in-flight transition before diffing.
|
|
368
|
+
yield* waitForConfig(configId, "Ready");
|
|
369
|
+
described = yield* describeConfigOrUndefined(configId);
|
|
370
|
+
// Sync — diff observed policy against desired.
|
|
371
|
+
if (
|
|
372
|
+
described !== undefined &&
|
|
373
|
+
(JSON.stringify(described.SchedulerConfig) !==
|
|
374
|
+
JSON.stringify(news.schedulerConfig) ||
|
|
375
|
+
(described.Description ?? undefined) !==
|
|
376
|
+
(news.description ?? undefined))
|
|
377
|
+
) {
|
|
378
|
+
yield* sagemaker.updateClusterSchedulerConfig({
|
|
379
|
+
ClusterSchedulerConfigId: configId,
|
|
380
|
+
TargetVersion: described.ClusterSchedulerConfigVersion,
|
|
381
|
+
SchedulerConfig: news.schedulerConfig,
|
|
382
|
+
Description: news.description,
|
|
383
|
+
});
|
|
384
|
+
yield* session.note(`Updating cluster policy ${name}...`);
|
|
385
|
+
yield* waitForConfig(configId, "Ready");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
described = yield* describeConfigOrUndefined(configId);
|
|
390
|
+
if (described === undefined) {
|
|
391
|
+
return yield* Effect.fail(
|
|
392
|
+
new Error(
|
|
393
|
+
`failed to read reconciled cluster scheduler config ${name}`,
|
|
394
|
+
),
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
const attrs = toAttrs(described);
|
|
398
|
+
|
|
399
|
+
// Sync tags — diff against OBSERVED cloud tags.
|
|
400
|
+
const currentTags = yield* fetchConfigTags(
|
|
401
|
+
attrs.clusterSchedulerConfigArn,
|
|
402
|
+
);
|
|
403
|
+
const { removed, upsert } = diffTags(currentTags, desiredTags);
|
|
404
|
+
if (removed.length > 0) {
|
|
405
|
+
yield* sagemaker.deleteTags({
|
|
406
|
+
ResourceArn: attrs.clusterSchedulerConfigArn,
|
|
407
|
+
TagKeys: removed,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
if (upsert.length > 0) {
|
|
411
|
+
yield* sagemaker.addTags({
|
|
412
|
+
ResourceArn: attrs.clusterSchedulerConfigArn,
|
|
413
|
+
Tags: upsert.map(({ Key, Value }) => ({ Key, Value })),
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
yield* session.note(attrs.clusterSchedulerConfigArn);
|
|
418
|
+
return attrs;
|
|
419
|
+
}),
|
|
420
|
+
delete: Effect.fn(function* ({ output }) {
|
|
421
|
+
yield* sagemaker
|
|
422
|
+
.deleteClusterSchedulerConfig({
|
|
423
|
+
ClusterSchedulerConfigId: output.clusterSchedulerConfigId,
|
|
424
|
+
})
|
|
425
|
+
.pipe(Effect.catchTag("ResourceNotFound", () => Effect.void));
|
|
426
|
+
yield* waitForConfig(output.clusterSchedulerConfigId, "Gone");
|
|
427
|
+
}),
|
|
428
|
+
};
|
|
429
|
+
}),
|
|
430
|
+
);
|