1import * as androidmanagement from "@distilled.cloud/gcp/androidmanagement_v1";
2import * as Effect from "effect/Effect";
3import { isResolved } from "../../Diff.ts";
4import * as Provider from "../../Provider.ts";
5import { Resource } from "../../Resource.ts";
6import { GcpEnvironment } from "../Environment.ts";
7import type { Providers } from "../Providers.ts";
8import {
9 DEFAULT_DURATION,
10 getEnrollmentToken,
11 jsonEqual,
12 lastSegment,
13 parentOf,
14 replaceOnIdentity,
15 toEnterpriseName,
16 toEnrollmentTokenName,
17} from "./internal.ts";
18
19export type EnterprisesEnrollmentTokenProps = {
20 /**
21 * Parent enterprise (`enterprises/{enterprise}` or `{enterprise}`).
22 * Immutable — changing it replaces the token.
23 */
24 parent: string;
25 /**
26 * Policy initially applied to the enrolled device
27 * (`enterprises/{enterprise}/policies/{policy}` or `{policy}`).
28 * Immutable — changing it replaces the token.
29 */
30 policyName?: string;
31 /**
32 * Whether personal usage is allowed on a device provisioned with this
33 * token. Immutable — changing it replaces the token.
34 */
35 allowPersonalUsage?:
36 | androidmanagement.EnrollmentTokenAllowPersonalUsageEnum
37 | (string & {});
38 /**
39 * Google authentication options during enrollment. Immutable —
40 * changing them replaces the token.
41 */
42 googleAuthenticationOptions?: androidmanagement.GoogleAuthenticationOptions;
43 /**
44 * One-time-use token. Immutable — changing it replaces the token.
45 */
46 oneTimeOnly?: boolean;
47 /**
48 * Token lifetime as a duration string (for example `3600s`). Defaults
49 * to ten years so the resource stays usable until deleted.
50 * Immutable — changing it replaces the token.
51 * @default "315360000s"
52 */
53 duration?: string;
54 /**
55 * Arbitrary data exposed on the Device after enrollment (max 1024
56 * characters). List/get return only a partial view and omit this
57 * field. Immutable — changing it replaces the token.
58 */
59 additionalData?: string;
60};
61
62export type EnterprisesEnrollmentToken = Resource<
63 "GCP.AndroidManagement.EnterprisesEnrollmentToken",
64 EnterprisesEnrollmentTokenProps,
65 {
66 /** Resource name `enterprises/{enterprise}/enrollmentTokens/{token}`. */
67 name: string;
68 /** Token id (last path segment). */
69 enrollmentTokenId: string;
70 /** Parent enterprise name. */
71 parent: string;
72 /** Project id used when the token was reconciled. */
73 project: string;
74 /** Initial policy name. */
75 policyName: string | undefined;
76 /** Personal-usage setting. */
77 allowPersonalUsage: string | undefined;
78 /** Google authentication options. */
79 googleAuthenticationOptions:
80 | androidmanagement.GoogleAuthenticationOptions
81 | undefined;
82 /** One-time-use flag. */
83 oneTimeOnly: boolean | undefined;
84 /** Requested lifetime. */
85 duration: string | undefined;
86 /** Additional data exposed on the enrolled device. */
87 additionalData: string | undefined;
88 /** Token value passed to the device. */
89 value: string | undefined;
90 /** QR code payload for enrollment. */
91 qrCode: string | undefined;
92 /** RFC3339 expiration timestamp. */
93 expirationTimestamp: string | undefined;
94 },
95 never,
96 Providers
97>;
98
99/**
100 * An Android Management enrollment token.
101 *
102 * Tokens are create-only (no patch API). Identity is the parent
103 * enterprise plus the server-assigned name. Changing parent, policy,
104 * duration, personal-usage, or additional data replaces the token.
105 * List/get return a partial view (`name`, `expirationTimestamp`,
106 * `allowPersonalUsage`, `value`, `qrCode`) and tokens have no labels,
107 * so Alchemy tracks a token only by the name recorded in state.
108 *
109 * ### Creating an Enrollment Token
110 * **Example:** Long-lived token
111 * ```typescript
112 * const token = yield* GCP.AndroidManagement.EnterprisesEnrollmentToken(
113 * "Enroll",
114 * { parent: enterprise.name },
115 * );
116 * ```
117 *
118 * **Example:** One-time token for a policy
119 * ```typescript
120 * const token = yield* GCP.AndroidManagement.EnterprisesEnrollmentToken(
121 * "Enroll",
122 * {
123 * parent: enterprise.name,
124 * policyName: "default",
125 * oneTimeOnly: true,
126 * duration: "86400s",
127 * },
128 * );
129 * ```
130 *
131 * @resource
132 * @category AndroidManagement
133 */
134export const EnterprisesEnrollmentToken = Resource<EnterprisesEnrollmentToken>(
135 "GCP.AndroidManagement.EnterprisesEnrollmentToken",
136);
137
138const toAttrs = (
139 token: androidmanagement.EnrollmentToken,
140 project: string,
141 extras?: {
142 parent?: string;
143 policyName?: string;
144 googleAuthenticationOptions?: androidmanagement.GoogleAuthenticationOptions;
145 oneTimeOnly?: boolean;
146 duration?: string;
147 additionalData?: string;
148 },
149) => {
150 const name = token.name ?? "";
151 return {
152 name,
153 enrollmentTokenId: lastSegment(name),
154 parent: extras?.parent ?? parentOf(name),
155 project,
156 policyName: extras?.policyName ?? token.policyName,
157 allowPersonalUsage: token.allowPersonalUsage,
158 googleAuthenticationOptions:
159 extras?.googleAuthenticationOptions ?? token.googleAuthenticationOptions,
160 oneTimeOnly: extras?.oneTimeOnly ?? token.oneTimeOnly,
161 duration: extras?.duration ?? token.duration,
162 additionalData: extras?.additionalData ?? token.additionalData,
163 value: token.value,
164 qrCode: token.qrCode,
165 expirationTimestamp: token.expirationTimestamp,
166 };
167};
168
169export const EnterprisesEnrollmentTokenProvider = () =>
170 Provider.succeed(EnterprisesEnrollmentToken, {
171 stables: [
172 "name",
173 "enrollmentTokenId",
174 "parent",
175 "project",
176 "expirationTimestamp",
177 ],
178
179 diff: Effect.fn(function* ({ news, olds, output }) {
180 if (!isResolved(news)) return undefined;
181 const identity = replaceOnIdentity({
182 previousParent: olds?.parent ?? output?.parent,
183 nextParent: news.parent,
184 extra:
185 (news.policyName !== undefined &&
186 olds?.policyName !== undefined &&
187 news.policyName !== olds.policyName) ||
188 (news.duration !== undefined &&
189 olds?.duration !== undefined &&
190 news.duration !== olds.duration) ||
191 (news.oneTimeOnly !== undefined &&
192 olds?.oneTimeOnly !== undefined &&
193 news.oneTimeOnly !== olds.oneTimeOnly) ||
194 (news.allowPersonalUsage !== undefined &&
195 output?.allowPersonalUsage !== undefined &&
196 news.allowPersonalUsage !== output.allowPersonalUsage) ||
197 (news.additionalData !== undefined &&
198 olds?.additionalData !== undefined &&
199 news.additionalData !== olds.additionalData) ||
200 (news.googleAuthenticationOptions !== undefined &&
201 olds?.googleAuthenticationOptions !== undefined &&
202 !jsonEqual(
203 news.googleAuthenticationOptions,
204 olds.googleAuthenticationOptions,
205 )),
206 });
207 return identity;
208 }),
209
210 read: Effect.fn(function* ({ olds, output }) {
211 const env = yield* GcpEnvironment.current;
212 const parent = olds?.parent ?? output?.parent ?? "";
213 const existing = yield* getEnrollmentToken(output?.name ?? "");
214 if (existing === undefined) return undefined;
215 return toAttrs(existing, env.project, {
216 parent,
217 policyName: olds?.policyName ?? output?.policyName,
218 googleAuthenticationOptions:
219 olds?.googleAuthenticationOptions ??
220 output?.googleAuthenticationOptions,
221 oneTimeOnly: olds?.oneTimeOnly ?? output?.oneTimeOnly,
222 duration: olds?.duration ?? output?.duration,
223 additionalData: olds?.additionalData ?? output?.additionalData,
224 });
225 }),
226
227 reconcile: Effect.fn(function* ({ news, output }) {
228 const env = yield* GcpEnvironment.current;
229 const parent = toEnterpriseName(news.parent);
230 const additionalData = news.additionalData;
231 const duration = news.duration ?? output?.duration ?? DEFAULT_DURATION;
232 const desired: androidmanagement.EnrollmentToken = {
233 policyName: news.policyName,
234 allowPersonalUsage: news.allowPersonalUsage,
235 googleAuthenticationOptions: news.googleAuthenticationOptions,
236 oneTimeOnly: news.oneTimeOnly,
237 duration,
238 additionalData,
239 };
240
241 let current = yield* getEnrollmentToken(
242 toEnrollmentTokenName(parent, output?.name),
243 );
244
245 if (current === undefined) {
246 current = yield* androidmanagement.createEnterprisesEnrollmentTokens({
247 parent,
248 body: desired,
249 });
250 }
251
252 const name = current.name ?? output?.name ?? "";
253 const fresh = (yield* getEnrollmentToken(name)) ?? current;
254 return toAttrs(fresh, env.project, {
255 parent,
256 policyName: news.policyName ?? output?.policyName,
257 googleAuthenticationOptions:
258 news.googleAuthenticationOptions ??
259 output?.googleAuthenticationOptions,
260 oneTimeOnly: news.oneTimeOnly ?? output?.oneTimeOnly,
261 duration,
262 additionalData,
263 });
264 }),
265
266 delete: Effect.fn(function* ({ output }) {
267 if (output.name.length === 0) return;
268 yield* androidmanagement
269 .deleteEnterprisesEnrollmentTokens({
270 name: output.name,
271 })
272 .pipe(Effect.catchTag("NotFound", () => Effect.void));
273 }),
274 });