import * as datasync from "@distilled.cloud/aws/datasync";
import * as Effect from "effect/Effect";
import * as Schedule from "effect/Schedule";
import * as Stream from "effect/Stream";
import { diffTags } from "../../Tags.ts";
/**
* A freshly-created IAM role (or a just-attached inline policy) takes a
* while to propagate to DataSync: `CreateLocation*` transiently rejects it
* as `LocationRoleNotAssumable` (patched from `InvalidRequestException` +
* "Invalid IAM role") or fails its location access test as
* `LocationAccessTestFailed` (patched from `InvalidRequestException` +
* "location access test failed"). Bounded retry (~60s), explicitly typed so
* declaration emit never widens the provider layer (see PATTERNS ยง7).
*/
export const retryWhileRoleNotAssumable = (
self: Effect.Effect,
): Effect.Effect =>
Effect.retry(self, {
while: (e) =>
e._tag === "LocationRoleNotAssumable" ||
e._tag === "LocationAccessTestFailed",
schedule: Schedule.max([Schedule.fixed("3 seconds"), Schedule.recurs(20)]),
});
/** Convert a DataSync tag list to a plain record. */
export const dsTagsToRecord = (
tags: readonly datasync.TagListEntry[] | undefined,
): Record =>
Object.fromEntries((tags ?? []).map((t) => [t.Key, t.Value ?? ""]));
/** Read the observed tags currently attached to a DataSync resource. */
export const readObservedTags = Effect.fn(function* (resourceArn: string) {
const res = yield* datasync.listTagsForResource({ ResourceArn: resourceArn });
return dsTagsToRecord(res.Tags);
});
/**
* Diff observed cloud tags against the desired set and apply the delta.
* DataSync `tagResource` upserts; `untagResource` removes by key.
*/
export const syncTags = Effect.fn(function* (
resourceArn: string,
observed: Record,
desired: Record,
) {
const { upsert, removed } = diffTags(observed, desired);
if (upsert.length > 0) {
yield* datasync.tagResource({
ResourceArn: resourceArn,
Tags: upsert.map((t) => ({ Key: t.Key, Value: t.Value })),
});
}
if (removed.length > 0) {
yield* datasync.untagResource({ ResourceArn: resourceArn, Keys: removed });
}
});
/**
* Scan every DataSync location and return the ARN of the one whose
* `LocationUri` matches (ignoring a trailing slash). DataSync has no
* create-idempotency token, so this makes reconcile idempotent across state
* loss.
*/
export const findLocationArnByUri = Effect.fn(function* (expectedUri: string) {
const strip = (u: string) => u.replace(/\/+$/, "");
const target = strip(expectedUri);
const locations = yield* datasync.listLocations.pages({}).pipe(
Stream.runCollect,
Effect.map((chunk) => Array.from(chunk).flatMap((p) => p.Locations ?? [])),
);
return locations.find(
(l) => l.LocationUri !== undefined && strip(l.LocationUri) === target,
)?.LocationArn;
});