1import * as rolesanywhere from "@distilled.cloud/aws/rolesanywhere";
2import * as Effect from "effect/Effect";
3import * as Redacted from "effect/Redacted";
4import { diffTags } from "../../Tags.ts";
5
6const unwrap = (v: string | Redacted.Redacted<string>): string =>
7 Redacted.isRedacted(v) ? Redacted.value(v) : v;
8
9/**
10 * Convert a RolesAnywhere wire tag list (`{ key, value }[]`, values possibly
11 * decoded as `Redacted`) into a plain record.
12 */
13export const toTagRecord = (
14 tags: ReadonlyArray<rolesanywhere.Tag> | undefined,
15): Record<string, string> =>
16 Object.fromEntries((tags ?? []).map((t) => [unwrap(t.key), unwrap(t.value)]));
17
18/**
19 * Convert a desired tag record into the RolesAnywhere wire tag list for
20 * create/import calls.
21 */
22export const toWireTags = (tags: Record<string, string>): rolesanywhere.Tag[] =>
23 Object.entries(tags).map(([key, value]) => ({ key, value }));
24
25/**
26 * Read the observed tags of a RolesAnywhere resource. Tag reads are
27 * best-effort — a failure (e.g. a race with deletion) reports no tags.
28 */
29export const readRolesAnywhereTags = Effect.fn(function* (arn: string) {
30 const response = yield* rolesanywhere
31 .listTagsForResource({ resourceArn: arn })
32 .pipe(Effect.catch(() => Effect.succeed(undefined)));
33 return toTagRecord(response?.tags);
34});
35
36/**
37 * Sync tags on a RolesAnywhere resource: diff the OBSERVED cloud tags against
38 * the desired set and apply only the delta.
39 */
40export const syncRolesAnywhereTags = Effect.fn(function* (
41 arn: string,
42 desiredTags: Record<string, string>,
43) {
44 const observedTags = yield* readRolesAnywhereTags(arn);
45 const { removed, upsert } = diffTags(observedTags, desiredTags);
46 if (upsert.length > 0) {
47 yield* rolesanywhere.tagResource({
48 resourceArn: arn,
49 tags: upsert.map(({ Key, Value }) => ({ key: Key, value: Value })),
50 });
51 }
52 if (removed.length > 0) {
53 yield* rolesanywhere.untagResource({ resourceArn: arn, tagKeys: removed });
54 }
55});