1import * as config from "@distilled.cloud/aws/config-service";
2import type * as Duration from "effect/Duration";
3import * as Effect from "effect/Effect";
4import * as Stream from "effect/Stream";
5import * as Provider from "../../Provider.ts";
6import { Resource } from "../../Resource.ts";
7import { toWireDays } from "../../Util/Duration.ts";
8import type { Providers } from "../Providers.ts";
9
10export interface RetentionConfigurationProps {
11 /**
12 * How long AWS Config retains your recorded configuration items. Accepts
13 * any `Duration.Input` (e.g. `"90 days"`, `Duration.days(365)`); converted
14 * to whole days on the wire (`RetentionPeriodInDays`). AWS accepts between
15 * 30 days and 7 years (2557 days).
16 */
17 retentionPeriod: Duration.Input;
18}
19
20export interface RetentionConfiguration extends Resource<
21 "AWS.Config.RetentionConfiguration",
22 RetentionConfigurationProps,
23 {
24 /** Name of the retention configuration. AWS always names it `default`. */
25 retentionConfigurationName: string;
26 /** The retention period in whole days. */
27 retentionPeriodInDays: number;
28 },
29 never,
30 Providers
31> {}
32
33/**
34 * The AWS Config retention configuration that controls how long AWS Config
35 * retains your recorded configuration items.
36 *
37 * AWS allows only **one** retention configuration per account per region and
38 * always names it `default` — treat this resource as an account-region
39 * singleton.
40 * ### Configuring Retention
41 * **Example:** Retain configuration items for one year
42 * ```typescript
43 * import * as Config from "alchemy/AWS/Config";
44 *
45 * const retention = yield* Config.RetentionConfiguration("Retention", {
46 * retentionPeriod: "365 days",
47 * });
48 * ```
49 *
50 * **Example:** Minimum retention
51 * ```typescript
52 * const retention = yield* Config.RetentionConfiguration("Retention", {
53 * retentionPeriod: "30 days",
54 * });
55 * ```
56 *
57 * @resource
58 */
59export const RetentionConfiguration = Resource<RetentionConfiguration>(
60 "AWS.Config.RetentionConfiguration",
61);
62
63export const RetentionConfigurationProvider = () =>
64 Provider.effect(
65 RetentionConfiguration,
66 Effect.gen(function* () {
67 // The API names the singleton object `default`; describe with no name
68 // filter returns it (or nothing).
69 const observeRetention = config.describeRetentionConfigurations({}).pipe(
70 Effect.map((r) => (r.RetentionConfigurations ?? []).at(0)),
71 Effect.catchTag("NoSuchRetentionConfigurationException", () =>
72 Effect.succeed(undefined),
73 ),
74 );
75
76 const toAttrs = (retention: config.RetentionConfiguration) => ({
77 retentionConfigurationName: retention.Name,
78 retentionPeriodInDays: retention.RetentionPeriodInDays,
79 });
80
81 return RetentionConfiguration.Provider.of({
82 stables: ["retentionConfigurationName"],
83 list: () =>
84 config.describeRetentionConfigurations.items({}).pipe(
85 Stream.runCollect,
86 Effect.map((retentions) => Array.from(retentions).map(toAttrs)),
87 ),
88 // Retention configurations are not taggable and AWS fixes the name to
89 // `default`, so there is no ownership marker — an existing retention
90 // configuration is treated as ours (singleton adoption), matching the
91 // DeliveryChannel convention.
92 read: Effect.fn(function* () {
93 const retention = yield* observeRetention;
94 return retention === undefined ? undefined : toAttrs(retention);
95 }),
96 reconcile: Effect.fn(function* ({ news, session }) {
97 const desiredDays = toWireDays(news.retentionPeriod)!;
98
99 // 1. OBSERVE — cloud state is authoritative.
100 const observed = yield* observeRetention;
101
102 // 2+3. ENSURE + SYNC — PutRetentionConfiguration is a full upsert
103 // of the account-region singleton; skip the API on no-op.
104 if (
105 observed === undefined ||
106 observed.RetentionPeriodInDays !== desiredDays
107 ) {
108 yield* config.putRetentionConfiguration({
109 RetentionPeriodInDays: desiredDays,
110 });
111 }
112
113 const name = observed?.Name ?? "default";
114 yield* session.note(`${name} (${desiredDays} days)`);
115 return {
116 retentionConfigurationName: name,
117 retentionPeriodInDays: desiredDays,
118 };
119 }),
120 delete: Effect.fn(function* ({ output }) {
121 yield* config
122 .deleteRetentionConfiguration({
123 RetentionConfigurationName: output.retentionConfigurationName,
124 })
125 .pipe(
126 Effect.catchTag(
127 "NoSuchRetentionConfigurationException",
128 () => Effect.void,
129 ),
130 );
131 }),
132 });
133 }),
134 );