1import * as account from "@distilled.cloud/aws/account";
2import * as Effect from "effect/Effect";
3import * as Schedule from "effect/Schedule";
4import { isResolved } from "../../Diff.ts";
5import * as Provider from "../../Provider.ts";
6import { Resource } from "../../Resource.ts";
7import type { Providers } from "../Providers.ts";
8import { unwrapSensitive } from "./internal.ts";
9
10export interface AccountNameProps {
11 /**
12 * The new name for the account. Between 1 and 50 characters.
13 */
14 accountName: string;
15 /**
16 * Account ID to operate on. Only usable from an Organizations management or
17 * delegated-admin account with trusted access enabled; omit to target the
18 * calling account.
19 */
20 accountId?: string;
21}
22
23export interface AccountName extends Resource<
24 "AWS.Account.AccountName",
25 AccountNameProps,
26 {
27 /** The account's current name. */
28 accountName: string;
29 /** The 12-digit AWS account ID the name belongs to. */
30 accountId: string;
31 /** The state of the account, e.g. `ACTIVE`. */
32 accountState?: string;
33 /** ISO-8601 timestamp of when the account was created. */
34 accountCreatedDate?: string;
35 },
36 never,
37 Providers
38> {}
39
40/**
41 * The display name of an AWS account. Every account has exactly one name; this
42 * account-global singleton sets it via `account:PutAccountName`. Deleting the
43 * resource stops managing the name and leaves the last value in place (an
44 * account always has a name).
45 *
46 * ### Naming the Account
47 * **Example:** Set the Calling Account's Name
48 * ```typescript
49 * const name = yield* AccountName("Name", {
50 * accountName: "acme-prod",
51 * });
52 * ```
53 *
54 * **Example:** Rename an Organizations Member Account
55 * ```typescript
56 * const name = yield* AccountName("MemberName", {
57 * accountName: "acme-sandbox",
58 * accountId: "123456789012",
59 * });
60 * ```
61 *
62 * @resource
63 */
64export const AccountName = Resource<AccountName>("AWS.Account.AccountName");
65
66export const AccountNameProvider = () =>
67 Provider.effect(
68 AccountName,
69 Effect.gen(function* () {
70 const observe = (accountId: string | undefined) =>
71 account.getAccountInformation({ AccountId: accountId }).pipe(
72 Effect.map((info) => ({
73 accountName: unwrapSensitive(info.AccountName) ?? "",
74 accountId: info.AccountId ?? accountId ?? "",
75 accountState: info.AccountState,
76 accountCreatedDate: info.AccountCreatedDate?.toISOString(),
77 })),
78 );
79
80 return {
81 // Account-global singleton setting: nuke must not rename accounts.
82 nuke: { skip: true },
83 stables: [],
84 // Targeting a different account manages a different singleton, so an
85 // accountId change replaces rather than renaming the old target.
86 diff: Effect.fn(function* ({ olds, news }) {
87 if (!isResolved(news)) return;
88 if ((olds?.accountId ?? news.accountId) !== news.accountId) {
89 return { action: "replace" } as const;
90 }
91 }),
92 read: Effect.fn(function* ({ olds }) {
93 return yield* observe(olds?.accountId);
94 }),
95 reconcile: Effect.fn(function* ({ news, session }) {
96 yield* account.putAccountName({
97 AccountName: news.accountName,
98 AccountId: news.accountId,
99 });
100 // putAccountName is eventually consistent: getAccountInformation
101 // can keep returning the previous name for ~20 seconds. Poll
102 // (bounded) until the rename is observable; if it is still stale
103 // after the budget, report the desired name — the put succeeded,
104 // so it is authoritative.
105 const observed = yield* observe(news.accountId).pipe(
106 Effect.repeat({
107 schedule: Schedule.spaced("3 seconds"),
108 until: (o): boolean => o.accountName === news.accountName,
109 times: 10,
110 }),
111 );
112 yield* session.note(news.accountName);
113 return observed.accountName === news.accountName
114 ? observed
115 : { ...observed, accountName: news.accountName };
116 }),
117 // Account-global singleton: the one name always exists.
118 list: () =>
119 observe(undefined).pipe(Effect.map((observed) => [observed])),
120 // An AWS account always has a name — there is nothing to delete.
121 // Destroy just stops managing it, mirroring the console behavior.
122 delete: Effect.fn(function* () {}),
123 };
124 }),
125 );