1import * as ag from "@distilled.cloud/aws/api-gateway";
2import * as Effect from "effect/Effect";
3import { isResolved } from "../../Diff.ts";
4import * as Provider from "../../Provider.ts";
5import { Resource } from "../../Resource.ts";
6import type { Providers } from "../Providers.ts";
7import { retryOnApiStatusUpdating } from "./common.ts";
8
9export interface AccountProps {
10 /**
11 * IAM role ARN for API Gateway to push logs to CloudWatch.
12 */
13 cloudwatchRoleArn?: string;
14}
15
16/** @resource */
17export interface Account extends Resource<
18 "AWS.ApiGateway.Account",
19 AccountProps,
20 {
21 cloudwatchRoleArn: string | undefined;
22 /**
23 * True when this stack last applied a desired `cloudwatchRoleArn` (including clearing it).
24 * Used so destroy does not remove a role the stack never configured.
25 */
26 managesCloudwatchRoleArn: boolean;
27 },
28 never,
29 Providers
30> {}
31
32/**
33 * Account-level settings for Amazon API Gateway in the current region
34 * (CloudWatch logging role, etc.).
35 *
36 * ### Account settings
37 * **Example:** Set logging role
38 * ```typescript
39 * yield* ApiGateway.Account("Account", {
40 * cloudwatchRoleArn: role.roleArn,
41 * });
42 * ```
43 */
44const AccountResource = Resource<Account>("AWS.ApiGateway.Account");
45
46export { AccountResource as Account };
47
48export const AccountProvider = () =>
49 Provider.effect(
50 AccountResource,
51 Effect.gen(function* () {
52 return {
53 diff: Effect.fn(function* ({ news: newsIn, olds, output }) {
54 if (!isResolved(newsIn)) return;
55 const news = newsIn as AccountProps;
56 const prevManages = output?.managesCloudwatchRoleArn ?? false;
57 const nextManages = news.cloudwatchRoleArn !== undefined;
58 if (nextManages) {
59 if (news.cloudwatchRoleArn !== olds.cloudwatchRoleArn) {
60 return { action: "update" } as const;
61 }
62 } else if (prevManages) {
63 return { action: "update" } as const;
64 }
65 }),
66 read: Effect.fn(function* ({ output }) {
67 const a = yield* ag
68 .getAccount({})
69 .pipe(
70 Effect.catchTag("NotFoundException", () =>
71 Effect.succeed(undefined),
72 ),
73 );
74 return {
75 cloudwatchRoleArn: a?.cloudwatchRoleArn,
76 managesCloudwatchRoleArn: output?.managesCloudwatchRoleArn ?? false,
77 };
78 }),
79 // Account settings are an account/region singleton — there is no
80 // collection API, only `getAccount`. Return the single instance as a
81 // one-element array (account singleton pattern).
82 list: () =>
83 ag.getAccount({}).pipe(
84 Effect.map((a) => [
85 {
86 cloudwatchRoleArn: a.cloudwatchRoleArn,
87 managesCloudwatchRoleArn: false,
88 },
89 ]),
90 Effect.catchTag("NotFoundException", () => Effect.succeed([])),
91 ),
92 reconcile: Effect.fn(function* ({ news: newsIn, session }) {
93 if (!isResolved(newsIn)) {
94 return yield* Effect.die("Account props were not resolved");
95 }
96 const news = newsIn as AccountProps;
97
98 // Observe — read the current account-level CloudWatch role from
99 // API Gateway. The account settings resource is a singleton per
100 // region, so there is no `ensure` step: we always sync.
101 const observed = yield* ag
102 .getAccount({})
103 .pipe(
104 Effect.catchTag("NotFoundException", () =>
105 Effect.succeed(undefined),
106 ),
107 );
108 const observedRoleArn = observed?.cloudwatchRoleArn;
109
110 // Sync the cloudwatchRoleArn — observed ↔ desired. We treat
111 // `undefined` desired as "do not manage the field" so the user
112 // can opt out without us clobbering whatever was already set
113 // out of band.
114 const manages = news.cloudwatchRoleArn !== undefined;
115 if (manages) {
116 const desired = news.cloudwatchRoleArn;
117 if (desired !== observedRoleArn) {
118 yield* retryOnApiStatusUpdating(
119 ag.updateAccount({
120 patchOperations: desired
121 ? [
122 {
123 op: "replace",
124 path: "/cloudwatchRoleArn",
125 value: desired,
126 },
127 ]
128 : [{ op: "remove", path: "/cloudwatchRoleArn" }],
129 }),
130 );
131 yield* session.note("Updated API Gateway account settings");
132 }
133 }
134
135 const final = yield* ag.getAccount({});
136 return {
137 cloudwatchRoleArn: final.cloudwatchRoleArn,
138 managesCloudwatchRoleArn: manages,
139 };
140 }),
141 delete: Effect.fn(function* ({ output, session }) {
142 if (output.managesCloudwatchRoleArn) {
143 yield* retryOnApiStatusUpdating(
144 ag
145 .updateAccount({
146 patchOperations: [
147 { op: "remove", path: "/cloudwatchRoleArn" },
148 ],
149 })
150 .pipe(
151 Effect.catchTag("BadRequestException", () => Effect.void),
152 ),
153 );
154 yield* session.note("Cleared API Gateway account CloudWatch role");
155 }
156 }),
157 };
158 }),
159 );