@beesolve/aws-accounts 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +189 -0
- package/dist/accountCreation.js +135 -0
- package/dist/applyLogic.js +1203 -0
- package/dist/awsClientConfig.js +26 -0
- package/dist/awsConfig.js +1365 -0
- package/dist/cli.js +201 -0
- package/dist/commands/graveyard.js +46 -0
- package/dist/commands/regenerate.js +17 -0
- package/dist/commands/remote.js +925 -0
- package/dist/diff.js +1012 -0
- package/dist/error.js +66 -0
- package/dist/helpers.js +21 -0
- package/dist/lambda/handler.js +375 -0
- package/dist/lambdaClient.js +220 -0
- package/dist/logger.js +26 -0
- package/dist/operations.js +218 -0
- package/dist/remoteStateCache.js +38 -0
- package/dist/reservedOuDeletion.js +46 -0
- package/dist/scanLogic.js +456 -0
- package/dist/state.js +618 -0
- package/dist/tags.js +14 -0
- package/dist-lambda/handler.mjs +3558 -0
- package/dist-lambda/lambda.zip +0 -0
- package/package.json +59 -0
|
@@ -0,0 +1,3558 @@
|
|
|
1
|
+
// src/lambda/handler.ts
|
|
2
|
+
import {
|
|
3
|
+
GetObjectCommand,
|
|
4
|
+
PutObjectCommand,
|
|
5
|
+
S3Client,
|
|
6
|
+
S3ServiceException
|
|
7
|
+
} from "@aws-sdk/client-s3";
|
|
8
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
9
|
+
import { OrganizationsClient as OrganizationsClient3 } from "@aws-sdk/client-organizations";
|
|
10
|
+
import { SSOAdminClient as SSOAdminClient3 } from "@aws-sdk/client-sso-admin";
|
|
11
|
+
import { IdentitystoreClient as IdentitystoreClient3 } from "@aws-sdk/client-identitystore";
|
|
12
|
+
import { AccountClient as AccountClient2 } from "@aws-sdk/client-account";
|
|
13
|
+
|
|
14
|
+
// node_modules/valibot/dist/index.mjs
|
|
15
|
+
var store$4;
|
|
16
|
+
var DEFAULT_CONFIG = {
|
|
17
|
+
lang: void 0,
|
|
18
|
+
message: void 0,
|
|
19
|
+
abortEarly: void 0,
|
|
20
|
+
abortPipeEarly: void 0
|
|
21
|
+
};
|
|
22
|
+
// @__NO_SIDE_EFFECTS__
|
|
23
|
+
function getGlobalConfig(config$1) {
|
|
24
|
+
if (!config$1 && !store$4) return DEFAULT_CONFIG;
|
|
25
|
+
return {
|
|
26
|
+
lang: config$1?.lang ?? store$4?.lang,
|
|
27
|
+
message: config$1?.message,
|
|
28
|
+
abortEarly: config$1?.abortEarly ?? store$4?.abortEarly,
|
|
29
|
+
abortPipeEarly: config$1?.abortPipeEarly ?? store$4?.abortPipeEarly
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
var store$3;
|
|
33
|
+
// @__NO_SIDE_EFFECTS__
|
|
34
|
+
function getGlobalMessage(lang) {
|
|
35
|
+
return store$3?.get(lang);
|
|
36
|
+
}
|
|
37
|
+
var store$2;
|
|
38
|
+
// @__NO_SIDE_EFFECTS__
|
|
39
|
+
function getSchemaMessage(lang) {
|
|
40
|
+
return store$2?.get(lang);
|
|
41
|
+
}
|
|
42
|
+
var store$1;
|
|
43
|
+
// @__NO_SIDE_EFFECTS__
|
|
44
|
+
function getSpecificMessage(reference, lang) {
|
|
45
|
+
return store$1?.get(reference)?.get(lang);
|
|
46
|
+
}
|
|
47
|
+
// @__NO_SIDE_EFFECTS__
|
|
48
|
+
function _stringify(input) {
|
|
49
|
+
const type = typeof input;
|
|
50
|
+
if (type === "string") return `"${input}"`;
|
|
51
|
+
if (type === "number" || type === "bigint" || type === "boolean") return `${input}`;
|
|
52
|
+
if (type === "object" || type === "function") return (input && Object.getPrototypeOf(input)?.constructor?.name) ?? "null";
|
|
53
|
+
return type;
|
|
54
|
+
}
|
|
55
|
+
function _addIssue(context, label, dataset, config$1, other) {
|
|
56
|
+
const input = other && "input" in other ? other.input : dataset.value;
|
|
57
|
+
const expected = other?.expected ?? context.expects ?? null;
|
|
58
|
+
const received = other?.received ?? /* @__PURE__ */ _stringify(input);
|
|
59
|
+
const issue = {
|
|
60
|
+
kind: context.kind,
|
|
61
|
+
type: context.type,
|
|
62
|
+
input,
|
|
63
|
+
expected,
|
|
64
|
+
received,
|
|
65
|
+
message: `Invalid ${label}: ${expected ? `Expected ${expected} but r` : "R"}eceived ${received}`,
|
|
66
|
+
requirement: context.requirement,
|
|
67
|
+
path: other?.path,
|
|
68
|
+
issues: other?.issues,
|
|
69
|
+
lang: config$1.lang,
|
|
70
|
+
abortEarly: config$1.abortEarly,
|
|
71
|
+
abortPipeEarly: config$1.abortPipeEarly
|
|
72
|
+
};
|
|
73
|
+
const isSchema = context.kind === "schema";
|
|
74
|
+
const message$1 = other?.message ?? context.message ?? /* @__PURE__ */ getSpecificMessage(context.reference, issue.lang) ?? (isSchema ? /* @__PURE__ */ getSchemaMessage(issue.lang) : null) ?? config$1.message ?? /* @__PURE__ */ getGlobalMessage(issue.lang);
|
|
75
|
+
if (message$1 !== void 0) issue.message = typeof message$1 === "function" ? message$1(issue) : message$1;
|
|
76
|
+
if (isSchema) dataset.typed = false;
|
|
77
|
+
if (dataset.issues) dataset.issues.push(issue);
|
|
78
|
+
else dataset.issues = [issue];
|
|
79
|
+
}
|
|
80
|
+
var _standardCache = /* @__PURE__ */ new WeakMap();
|
|
81
|
+
// @__NO_SIDE_EFFECTS__
|
|
82
|
+
function _getStandardProps(context) {
|
|
83
|
+
let cached = _standardCache.get(context);
|
|
84
|
+
if (!cached) {
|
|
85
|
+
cached = {
|
|
86
|
+
version: 1,
|
|
87
|
+
vendor: "valibot",
|
|
88
|
+
validate(value$1) {
|
|
89
|
+
return context["~run"]({ value: value$1 }, /* @__PURE__ */ getGlobalConfig());
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
_standardCache.set(context, cached);
|
|
93
|
+
}
|
|
94
|
+
return cached;
|
|
95
|
+
}
|
|
96
|
+
// @__NO_SIDE_EFFECTS__
|
|
97
|
+
function _isValidObjectKey(object$1, key) {
|
|
98
|
+
return Object.prototype.hasOwnProperty.call(object$1, key) && key !== "__proto__" && key !== "prototype" && key !== "constructor";
|
|
99
|
+
}
|
|
100
|
+
// @__NO_SIDE_EFFECTS__
|
|
101
|
+
function _joinExpects(values$1, separator) {
|
|
102
|
+
const list = [...new Set(values$1)];
|
|
103
|
+
if (list.length > 1) return `(${list.join(` ${separator} `)})`;
|
|
104
|
+
return list[0] ?? "never";
|
|
105
|
+
}
|
|
106
|
+
var ValiError = class extends Error {
|
|
107
|
+
/**
|
|
108
|
+
* Creates a Valibot error with useful information.
|
|
109
|
+
*
|
|
110
|
+
* @param issues The error issues.
|
|
111
|
+
*/
|
|
112
|
+
constructor(issues) {
|
|
113
|
+
super(issues[0].message);
|
|
114
|
+
this.name = "ValiError";
|
|
115
|
+
this.issues = issues;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var EMOJI_REGEX = new RegExp("^(?:[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation}))*)+$", "u");
|
|
119
|
+
// @__NO_SIDE_EFFECTS__
|
|
120
|
+
function minLength(requirement, message$1) {
|
|
121
|
+
return {
|
|
122
|
+
kind: "validation",
|
|
123
|
+
type: "min_length",
|
|
124
|
+
reference: minLength,
|
|
125
|
+
async: false,
|
|
126
|
+
expects: `>=${requirement}`,
|
|
127
|
+
requirement,
|
|
128
|
+
message: message$1,
|
|
129
|
+
"~run"(dataset, config$1) {
|
|
130
|
+
if (dataset.typed && dataset.value.length < this.requirement) _addIssue(this, "length", dataset, config$1, { received: `${dataset.value.length}` });
|
|
131
|
+
return dataset;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
var ABORT_EARLY_CONFIG = { abortEarly: true };
|
|
136
|
+
// @__NO_SIDE_EFFECTS__
|
|
137
|
+
function getFallback(schema, dataset, config$1) {
|
|
138
|
+
return typeof schema.fallback === "function" ? schema.fallback(dataset, config$1) : schema.fallback;
|
|
139
|
+
}
|
|
140
|
+
// @__NO_SIDE_EFFECTS__
|
|
141
|
+
function getDefault(schema, dataset, config$1) {
|
|
142
|
+
return typeof schema.default === "function" ? schema.default(dataset, config$1) : schema.default;
|
|
143
|
+
}
|
|
144
|
+
// @__NO_SIDE_EFFECTS__
|
|
145
|
+
function array(item, message$1) {
|
|
146
|
+
return {
|
|
147
|
+
kind: "schema",
|
|
148
|
+
type: "array",
|
|
149
|
+
reference: array,
|
|
150
|
+
expects: "Array",
|
|
151
|
+
async: false,
|
|
152
|
+
item,
|
|
153
|
+
message: message$1,
|
|
154
|
+
get "~standard"() {
|
|
155
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
156
|
+
},
|
|
157
|
+
"~run"(dataset, config$1) {
|
|
158
|
+
const input = dataset.value;
|
|
159
|
+
if (Array.isArray(input)) {
|
|
160
|
+
dataset.typed = true;
|
|
161
|
+
dataset.value = [];
|
|
162
|
+
for (let key = 0; key < input.length; key++) {
|
|
163
|
+
const value$1 = input[key];
|
|
164
|
+
const itemDataset = this.item["~run"]({ value: value$1 }, config$1);
|
|
165
|
+
if (itemDataset.issues) {
|
|
166
|
+
const pathItem = {
|
|
167
|
+
type: "array",
|
|
168
|
+
origin: "value",
|
|
169
|
+
input,
|
|
170
|
+
key,
|
|
171
|
+
value: value$1
|
|
172
|
+
};
|
|
173
|
+
for (const issue of itemDataset.issues) {
|
|
174
|
+
if (issue.path) issue.path.unshift(pathItem);
|
|
175
|
+
else issue.path = [pathItem];
|
|
176
|
+
dataset.issues?.push(issue);
|
|
177
|
+
}
|
|
178
|
+
if (!dataset.issues) dataset.issues = itemDataset.issues;
|
|
179
|
+
if (config$1.abortEarly) {
|
|
180
|
+
dataset.typed = false;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (!itemDataset.typed) dataset.typed = false;
|
|
185
|
+
dataset.value.push(itemDataset.value);
|
|
186
|
+
}
|
|
187
|
+
} else _addIssue(this, "type", dataset, config$1);
|
|
188
|
+
return dataset;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// @__NO_SIDE_EFFECTS__
|
|
193
|
+
function boolean(message$1) {
|
|
194
|
+
return {
|
|
195
|
+
kind: "schema",
|
|
196
|
+
type: "boolean",
|
|
197
|
+
reference: boolean,
|
|
198
|
+
expects: "boolean",
|
|
199
|
+
async: false,
|
|
200
|
+
message: message$1,
|
|
201
|
+
get "~standard"() {
|
|
202
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
203
|
+
},
|
|
204
|
+
"~run"(dataset, config$1) {
|
|
205
|
+
if (typeof dataset.value === "boolean") dataset.typed = true;
|
|
206
|
+
else _addIssue(this, "type", dataset, config$1);
|
|
207
|
+
return dataset;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
// @__NO_SIDE_EFFECTS__
|
|
212
|
+
function literal(literal_, message$1) {
|
|
213
|
+
return {
|
|
214
|
+
kind: "schema",
|
|
215
|
+
type: "literal",
|
|
216
|
+
reference: literal,
|
|
217
|
+
expects: /* @__PURE__ */ _stringify(literal_),
|
|
218
|
+
async: false,
|
|
219
|
+
literal: literal_,
|
|
220
|
+
message: message$1,
|
|
221
|
+
get "~standard"() {
|
|
222
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
223
|
+
},
|
|
224
|
+
"~run"(dataset, config$1) {
|
|
225
|
+
if (dataset.value === this.literal) dataset.typed = true;
|
|
226
|
+
else _addIssue(this, "type", dataset, config$1);
|
|
227
|
+
return dataset;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
// @__NO_SIDE_EFFECTS__
|
|
232
|
+
function nullable(wrapped, default_) {
|
|
233
|
+
return {
|
|
234
|
+
kind: "schema",
|
|
235
|
+
type: "nullable",
|
|
236
|
+
reference: nullable,
|
|
237
|
+
expects: `(${wrapped.expects} | null)`,
|
|
238
|
+
async: false,
|
|
239
|
+
wrapped,
|
|
240
|
+
default: default_,
|
|
241
|
+
get "~standard"() {
|
|
242
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
243
|
+
},
|
|
244
|
+
"~run"(dataset, config$1) {
|
|
245
|
+
if (dataset.value === null) {
|
|
246
|
+
if (this.default !== void 0) dataset.value = /* @__PURE__ */ getDefault(this, dataset, config$1);
|
|
247
|
+
if (dataset.value === null) {
|
|
248
|
+
dataset.typed = true;
|
|
249
|
+
return dataset;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return this.wrapped["~run"](dataset, config$1);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
// @__NO_SIDE_EFFECTS__
|
|
257
|
+
function number(message$1) {
|
|
258
|
+
return {
|
|
259
|
+
kind: "schema",
|
|
260
|
+
type: "number",
|
|
261
|
+
reference: number,
|
|
262
|
+
expects: "number",
|
|
263
|
+
async: false,
|
|
264
|
+
message: message$1,
|
|
265
|
+
get "~standard"() {
|
|
266
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
267
|
+
},
|
|
268
|
+
"~run"(dataset, config$1) {
|
|
269
|
+
if (typeof dataset.value === "number" && !isNaN(dataset.value)) dataset.typed = true;
|
|
270
|
+
else _addIssue(this, "type", dataset, config$1);
|
|
271
|
+
return dataset;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
// @__NO_SIDE_EFFECTS__
|
|
276
|
+
function optional(wrapped, default_) {
|
|
277
|
+
return {
|
|
278
|
+
kind: "schema",
|
|
279
|
+
type: "optional",
|
|
280
|
+
reference: optional,
|
|
281
|
+
expects: `(${wrapped.expects} | undefined)`,
|
|
282
|
+
async: false,
|
|
283
|
+
wrapped,
|
|
284
|
+
default: default_,
|
|
285
|
+
get "~standard"() {
|
|
286
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
287
|
+
},
|
|
288
|
+
"~run"(dataset, config$1) {
|
|
289
|
+
if (dataset.value === void 0) {
|
|
290
|
+
if (this.default !== void 0) dataset.value = /* @__PURE__ */ getDefault(this, dataset, config$1);
|
|
291
|
+
if (dataset.value === void 0) {
|
|
292
|
+
dataset.typed = true;
|
|
293
|
+
return dataset;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return this.wrapped["~run"](dataset, config$1);
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
// @__NO_SIDE_EFFECTS__
|
|
301
|
+
function picklist(options, message$1) {
|
|
302
|
+
return {
|
|
303
|
+
kind: "schema",
|
|
304
|
+
type: "picklist",
|
|
305
|
+
reference: picklist,
|
|
306
|
+
expects: /* @__PURE__ */ _joinExpects(options.map(_stringify), "|"),
|
|
307
|
+
async: false,
|
|
308
|
+
options,
|
|
309
|
+
message: message$1,
|
|
310
|
+
get "~standard"() {
|
|
311
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
312
|
+
},
|
|
313
|
+
"~run"(dataset, config$1) {
|
|
314
|
+
if (this.options.includes(dataset.value)) dataset.typed = true;
|
|
315
|
+
else _addIssue(this, "type", dataset, config$1);
|
|
316
|
+
return dataset;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// @__NO_SIDE_EFFECTS__
|
|
321
|
+
function record(key, value$1, message$1) {
|
|
322
|
+
return {
|
|
323
|
+
kind: "schema",
|
|
324
|
+
type: "record",
|
|
325
|
+
reference: record,
|
|
326
|
+
expects: "Object",
|
|
327
|
+
async: false,
|
|
328
|
+
key,
|
|
329
|
+
value: value$1,
|
|
330
|
+
message: message$1,
|
|
331
|
+
get "~standard"() {
|
|
332
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
333
|
+
},
|
|
334
|
+
"~run"(dataset, config$1) {
|
|
335
|
+
const input = dataset.value;
|
|
336
|
+
if (input && typeof input === "object") {
|
|
337
|
+
dataset.typed = true;
|
|
338
|
+
dataset.value = {};
|
|
339
|
+
for (const entryKey in input) if (/* @__PURE__ */ _isValidObjectKey(input, entryKey)) {
|
|
340
|
+
const entryValue = input[entryKey];
|
|
341
|
+
const keyDataset = this.key["~run"]({ value: entryKey }, config$1);
|
|
342
|
+
if (keyDataset.issues) {
|
|
343
|
+
const pathItem = {
|
|
344
|
+
type: "object",
|
|
345
|
+
origin: "key",
|
|
346
|
+
input,
|
|
347
|
+
key: entryKey,
|
|
348
|
+
value: entryValue
|
|
349
|
+
};
|
|
350
|
+
for (const issue of keyDataset.issues) {
|
|
351
|
+
issue.path = [pathItem];
|
|
352
|
+
dataset.issues?.push(issue);
|
|
353
|
+
}
|
|
354
|
+
if (!dataset.issues) dataset.issues = keyDataset.issues;
|
|
355
|
+
if (config$1.abortEarly) {
|
|
356
|
+
dataset.typed = false;
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const valueDataset = this.value["~run"]({ value: entryValue }, config$1);
|
|
361
|
+
if (valueDataset.issues) {
|
|
362
|
+
const pathItem = {
|
|
363
|
+
type: "object",
|
|
364
|
+
origin: "value",
|
|
365
|
+
input,
|
|
366
|
+
key: entryKey,
|
|
367
|
+
value: entryValue
|
|
368
|
+
};
|
|
369
|
+
for (const issue of valueDataset.issues) {
|
|
370
|
+
if (issue.path) issue.path.unshift(pathItem);
|
|
371
|
+
else issue.path = [pathItem];
|
|
372
|
+
dataset.issues?.push(issue);
|
|
373
|
+
}
|
|
374
|
+
if (!dataset.issues) dataset.issues = valueDataset.issues;
|
|
375
|
+
if (config$1.abortEarly) {
|
|
376
|
+
dataset.typed = false;
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (!keyDataset.typed || !valueDataset.typed) dataset.typed = false;
|
|
381
|
+
if (keyDataset.typed) dataset.value[keyDataset.value] = valueDataset.value;
|
|
382
|
+
}
|
|
383
|
+
} else _addIssue(this, "type", dataset, config$1);
|
|
384
|
+
return dataset;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
// @__NO_SIDE_EFFECTS__
|
|
389
|
+
function strictObject(entries$1, message$1) {
|
|
390
|
+
return {
|
|
391
|
+
kind: "schema",
|
|
392
|
+
type: "strict_object",
|
|
393
|
+
reference: strictObject,
|
|
394
|
+
expects: "Object",
|
|
395
|
+
async: false,
|
|
396
|
+
entries: entries$1,
|
|
397
|
+
message: message$1,
|
|
398
|
+
get "~standard"() {
|
|
399
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
400
|
+
},
|
|
401
|
+
"~run"(dataset, config$1) {
|
|
402
|
+
const input = dataset.value;
|
|
403
|
+
if (input && typeof input === "object") {
|
|
404
|
+
dataset.typed = true;
|
|
405
|
+
dataset.value = {};
|
|
406
|
+
for (const key in this.entries) {
|
|
407
|
+
const valueSchema = this.entries[key];
|
|
408
|
+
if (key in input || (valueSchema.type === "exact_optional" || valueSchema.type === "optional" || valueSchema.type === "nullish") && valueSchema.default !== void 0) {
|
|
409
|
+
const value$1 = key in input ? input[key] : /* @__PURE__ */ getDefault(valueSchema);
|
|
410
|
+
const valueDataset = valueSchema["~run"]({ value: value$1 }, config$1);
|
|
411
|
+
if (valueDataset.issues) {
|
|
412
|
+
const pathItem = {
|
|
413
|
+
type: "object",
|
|
414
|
+
origin: "value",
|
|
415
|
+
input,
|
|
416
|
+
key,
|
|
417
|
+
value: value$1
|
|
418
|
+
};
|
|
419
|
+
for (const issue of valueDataset.issues) {
|
|
420
|
+
if (issue.path) issue.path.unshift(pathItem);
|
|
421
|
+
else issue.path = [pathItem];
|
|
422
|
+
dataset.issues?.push(issue);
|
|
423
|
+
}
|
|
424
|
+
if (!dataset.issues) dataset.issues = valueDataset.issues;
|
|
425
|
+
if (config$1.abortEarly) {
|
|
426
|
+
dataset.typed = false;
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (!valueDataset.typed) dataset.typed = false;
|
|
431
|
+
dataset.value[key] = valueDataset.value;
|
|
432
|
+
} else if (valueSchema.fallback !== void 0) dataset.value[key] = /* @__PURE__ */ getFallback(valueSchema);
|
|
433
|
+
else if (valueSchema.type !== "exact_optional" && valueSchema.type !== "optional" && valueSchema.type !== "nullish") {
|
|
434
|
+
_addIssue(this, "key", dataset, config$1, {
|
|
435
|
+
input: void 0,
|
|
436
|
+
expected: `"${key}"`,
|
|
437
|
+
path: [{
|
|
438
|
+
type: "object",
|
|
439
|
+
origin: "key",
|
|
440
|
+
input,
|
|
441
|
+
key,
|
|
442
|
+
value: input[key]
|
|
443
|
+
}]
|
|
444
|
+
});
|
|
445
|
+
if (config$1.abortEarly) break;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (!dataset.issues || !config$1.abortEarly) {
|
|
449
|
+
for (const key in input) if (!(key in this.entries)) {
|
|
450
|
+
_addIssue(this, "key", dataset, config$1, {
|
|
451
|
+
input: key,
|
|
452
|
+
expected: "never",
|
|
453
|
+
path: [{
|
|
454
|
+
type: "object",
|
|
455
|
+
origin: "key",
|
|
456
|
+
input,
|
|
457
|
+
key,
|
|
458
|
+
value: input[key]
|
|
459
|
+
}]
|
|
460
|
+
});
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
} else _addIssue(this, "type", dataset, config$1);
|
|
465
|
+
return dataset;
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
// @__NO_SIDE_EFFECTS__
|
|
470
|
+
function string(message$1) {
|
|
471
|
+
return {
|
|
472
|
+
kind: "schema",
|
|
473
|
+
type: "string",
|
|
474
|
+
reference: string,
|
|
475
|
+
expects: "string",
|
|
476
|
+
async: false,
|
|
477
|
+
message: message$1,
|
|
478
|
+
get "~standard"() {
|
|
479
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
480
|
+
},
|
|
481
|
+
"~run"(dataset, config$1) {
|
|
482
|
+
if (typeof dataset.value === "string") dataset.typed = true;
|
|
483
|
+
else _addIssue(this, "type", dataset, config$1);
|
|
484
|
+
return dataset;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
// @__NO_SIDE_EFFECTS__
|
|
489
|
+
function _subIssues(datasets) {
|
|
490
|
+
let issues;
|
|
491
|
+
if (datasets) for (const dataset of datasets) if (issues) for (const issue of dataset.issues) issues.push(issue);
|
|
492
|
+
else issues = dataset.issues;
|
|
493
|
+
return issues;
|
|
494
|
+
}
|
|
495
|
+
// @__NO_SIDE_EFFECTS__
|
|
496
|
+
function union(options, message$1) {
|
|
497
|
+
return {
|
|
498
|
+
kind: "schema",
|
|
499
|
+
type: "union",
|
|
500
|
+
reference: union,
|
|
501
|
+
expects: /* @__PURE__ */ _joinExpects(options.map((option) => option.expects), "|"),
|
|
502
|
+
async: false,
|
|
503
|
+
options,
|
|
504
|
+
message: message$1,
|
|
505
|
+
get "~standard"() {
|
|
506
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
507
|
+
},
|
|
508
|
+
"~run"(dataset, config$1) {
|
|
509
|
+
let validDataset;
|
|
510
|
+
let typedDatasets;
|
|
511
|
+
let untypedDatasets;
|
|
512
|
+
for (const schema of this.options) {
|
|
513
|
+
const optionDataset = schema["~run"]({ value: dataset.value }, config$1);
|
|
514
|
+
if (optionDataset.typed) if (optionDataset.issues) if (typedDatasets) typedDatasets.push(optionDataset);
|
|
515
|
+
else typedDatasets = [optionDataset];
|
|
516
|
+
else {
|
|
517
|
+
validDataset = optionDataset;
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
else if (untypedDatasets) untypedDatasets.push(optionDataset);
|
|
521
|
+
else untypedDatasets = [optionDataset];
|
|
522
|
+
}
|
|
523
|
+
if (validDataset) return validDataset;
|
|
524
|
+
if (typedDatasets) {
|
|
525
|
+
if (typedDatasets.length === 1) return typedDatasets[0];
|
|
526
|
+
_addIssue(this, "type", dataset, config$1, { issues: /* @__PURE__ */ _subIssues(typedDatasets) });
|
|
527
|
+
dataset.typed = true;
|
|
528
|
+
} else if (untypedDatasets?.length === 1) return untypedDatasets[0];
|
|
529
|
+
else _addIssue(this, "type", dataset, config$1, { issues: /* @__PURE__ */ _subIssues(untypedDatasets) });
|
|
530
|
+
return dataset;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
// @__NO_SIDE_EFFECTS__
|
|
535
|
+
function variant(key, options, message$1) {
|
|
536
|
+
return {
|
|
537
|
+
kind: "schema",
|
|
538
|
+
type: "variant",
|
|
539
|
+
reference: variant,
|
|
540
|
+
expects: "Object",
|
|
541
|
+
async: false,
|
|
542
|
+
key,
|
|
543
|
+
options,
|
|
544
|
+
message: message$1,
|
|
545
|
+
get "~standard"() {
|
|
546
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
547
|
+
},
|
|
548
|
+
"~run"(dataset, config$1) {
|
|
549
|
+
const input = dataset.value;
|
|
550
|
+
if (input && typeof input === "object") {
|
|
551
|
+
let outputDataset;
|
|
552
|
+
let maxDiscriminatorPriority = 0;
|
|
553
|
+
let invalidDiscriminatorKey = this.key;
|
|
554
|
+
let expectedDiscriminators = [];
|
|
555
|
+
const parseOptions = (variant$1, allKeys) => {
|
|
556
|
+
for (const schema of variant$1.options) {
|
|
557
|
+
if (schema.type === "variant") parseOptions(schema, new Set(allKeys).add(schema.key));
|
|
558
|
+
else {
|
|
559
|
+
let keysAreValid = true;
|
|
560
|
+
let currentPriority = 0;
|
|
561
|
+
for (const currentKey of allKeys) {
|
|
562
|
+
const discriminatorSchema = schema.entries[currentKey];
|
|
563
|
+
if (currentKey in input ? discriminatorSchema["~run"]({
|
|
564
|
+
typed: false,
|
|
565
|
+
value: input[currentKey]
|
|
566
|
+
}, ABORT_EARLY_CONFIG).issues : discriminatorSchema.type !== "exact_optional" && discriminatorSchema.type !== "optional" && discriminatorSchema.type !== "nullish") {
|
|
567
|
+
keysAreValid = false;
|
|
568
|
+
if (invalidDiscriminatorKey !== currentKey && (maxDiscriminatorPriority < currentPriority || maxDiscriminatorPriority === currentPriority && currentKey in input && !(invalidDiscriminatorKey in input))) {
|
|
569
|
+
maxDiscriminatorPriority = currentPriority;
|
|
570
|
+
invalidDiscriminatorKey = currentKey;
|
|
571
|
+
expectedDiscriminators = [];
|
|
572
|
+
}
|
|
573
|
+
if (invalidDiscriminatorKey === currentKey) expectedDiscriminators.push(schema.entries[currentKey].expects);
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
currentPriority++;
|
|
577
|
+
}
|
|
578
|
+
if (keysAreValid) {
|
|
579
|
+
const optionDataset = schema["~run"]({ value: input }, config$1);
|
|
580
|
+
if (!outputDataset || !outputDataset.typed && optionDataset.typed) outputDataset = optionDataset;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (outputDataset && !outputDataset.issues) break;
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
parseOptions(this, /* @__PURE__ */ new Set([this.key]));
|
|
587
|
+
if (outputDataset) return outputDataset;
|
|
588
|
+
_addIssue(this, "type", dataset, config$1, {
|
|
589
|
+
input: input[invalidDiscriminatorKey],
|
|
590
|
+
expected: /* @__PURE__ */ _joinExpects(expectedDiscriminators, "|"),
|
|
591
|
+
path: [{
|
|
592
|
+
type: "object",
|
|
593
|
+
origin: "value",
|
|
594
|
+
input,
|
|
595
|
+
key: invalidDiscriminatorKey,
|
|
596
|
+
value: input[invalidDiscriminatorKey]
|
|
597
|
+
}]
|
|
598
|
+
});
|
|
599
|
+
} else _addIssue(this, "type", dataset, config$1);
|
|
600
|
+
return dataset;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function parse(schema, input, config$1) {
|
|
605
|
+
const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig(config$1));
|
|
606
|
+
if (dataset.issues) throw new ValiError(dataset.issues);
|
|
607
|
+
return dataset.value;
|
|
608
|
+
}
|
|
609
|
+
// @__NO_SIDE_EFFECTS__
|
|
610
|
+
function pipe(...pipe$1) {
|
|
611
|
+
return {
|
|
612
|
+
...pipe$1[0],
|
|
613
|
+
pipe: pipe$1,
|
|
614
|
+
get "~standard"() {
|
|
615
|
+
return /* @__PURE__ */ _getStandardProps(this);
|
|
616
|
+
},
|
|
617
|
+
"~run"(dataset, config$1) {
|
|
618
|
+
for (const item of pipe$1) if (item.kind !== "metadata") {
|
|
619
|
+
if (dataset.issues && (item.kind === "schema" || item.kind === "transformation")) {
|
|
620
|
+
dataset.typed = false;
|
|
621
|
+
break;
|
|
622
|
+
}
|
|
623
|
+
if (!dataset.issues || !config$1.abortEarly && !config$1.abortPipeEarly) dataset = item["~run"](dataset, config$1);
|
|
624
|
+
}
|
|
625
|
+
return dataset;
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
// @__NO_SIDE_EFFECTS__
|
|
630
|
+
function safeParse(schema, input, config$1) {
|
|
631
|
+
const dataset = schema["~run"]({ value: input }, /* @__PURE__ */ getGlobalConfig(config$1));
|
|
632
|
+
return {
|
|
633
|
+
typed: dataset.typed,
|
|
634
|
+
success: !dataset.issues,
|
|
635
|
+
output: dataset.value,
|
|
636
|
+
issues: dataset.issues
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/operations.ts
|
|
641
|
+
var moveAccountOperationSchema = strictObject({
|
|
642
|
+
kind: literal("moveAccount"),
|
|
643
|
+
accountId: string(),
|
|
644
|
+
accountName: string(),
|
|
645
|
+
fromOuId: string(),
|
|
646
|
+
fromOuName: string(),
|
|
647
|
+
toOuId: string(),
|
|
648
|
+
toOuName: string()
|
|
649
|
+
});
|
|
650
|
+
var createOuOperationSchema = strictObject({
|
|
651
|
+
kind: literal("createOu"),
|
|
652
|
+
ouName: string(),
|
|
653
|
+
parentOuId: string(),
|
|
654
|
+
parentOuName: string()
|
|
655
|
+
});
|
|
656
|
+
var renameOuOperationSchema = strictObject({
|
|
657
|
+
kind: literal("renameOu"),
|
|
658
|
+
ouId: string(),
|
|
659
|
+
fromOuName: string(),
|
|
660
|
+
toOuName: string(),
|
|
661
|
+
parentOuId: string(),
|
|
662
|
+
parentOuName: string()
|
|
663
|
+
});
|
|
664
|
+
var deleteOuOperationSchema = strictObject({
|
|
665
|
+
kind: literal("deleteOu"),
|
|
666
|
+
ouId: string(),
|
|
667
|
+
ouName: string(),
|
|
668
|
+
parentOuId: string(),
|
|
669
|
+
parentOuName: string()
|
|
670
|
+
});
|
|
671
|
+
var createAccountOperationSchema = strictObject({
|
|
672
|
+
kind: literal("createAccount"),
|
|
673
|
+
accountName: string(),
|
|
674
|
+
accountEmail: string(),
|
|
675
|
+
targetOuId: string(),
|
|
676
|
+
targetOuName: string()
|
|
677
|
+
});
|
|
678
|
+
var updateAccountTagsOperationSchema = strictObject({
|
|
679
|
+
kind: literal("updateAccountTags"),
|
|
680
|
+
accountId: string(),
|
|
681
|
+
accountName: string(),
|
|
682
|
+
tags: record(string(), string())
|
|
683
|
+
});
|
|
684
|
+
var updateAccountNameOperationSchema = strictObject({
|
|
685
|
+
kind: literal("updateAccountName"),
|
|
686
|
+
accountId: string(),
|
|
687
|
+
fromAccountName: string(),
|
|
688
|
+
toAccountName: string()
|
|
689
|
+
});
|
|
690
|
+
var removeAccountOperationSchema = strictObject({
|
|
691
|
+
kind: literal("removeAccount"),
|
|
692
|
+
accountId: string(),
|
|
693
|
+
accountName: string(),
|
|
694
|
+
fromOuId: string(),
|
|
695
|
+
fromOuName: string(),
|
|
696
|
+
toOuId: string(),
|
|
697
|
+
toOuName: string()
|
|
698
|
+
});
|
|
699
|
+
var createIdcUserOperationSchema = strictObject({
|
|
700
|
+
kind: literal("createIdcUser"),
|
|
701
|
+
userName: string(),
|
|
702
|
+
displayName: string(),
|
|
703
|
+
email: string()
|
|
704
|
+
});
|
|
705
|
+
var updateIdcUserOperationSchema = strictObject({
|
|
706
|
+
kind: literal("updateIdcUser"),
|
|
707
|
+
userName: string(),
|
|
708
|
+
displayName: string(),
|
|
709
|
+
email: string()
|
|
710
|
+
});
|
|
711
|
+
var deleteIdcUserOperationSchema = strictObject({
|
|
712
|
+
kind: literal("deleteIdcUser"),
|
|
713
|
+
userName: string()
|
|
714
|
+
});
|
|
715
|
+
var createIdcGroupOperationSchema = strictObject({
|
|
716
|
+
kind: literal("createIdcGroup"),
|
|
717
|
+
groupDisplayName: string(),
|
|
718
|
+
description: string()
|
|
719
|
+
});
|
|
720
|
+
var updateIdcGroupDescriptionOperationSchema = strictObject({
|
|
721
|
+
kind: literal("updateIdcGroupDescription"),
|
|
722
|
+
groupDisplayName: string(),
|
|
723
|
+
description: string()
|
|
724
|
+
});
|
|
725
|
+
var deleteIdcGroupOperationSchema = strictObject({
|
|
726
|
+
kind: literal("deleteIdcGroup"),
|
|
727
|
+
groupDisplayName: string()
|
|
728
|
+
});
|
|
729
|
+
var addIdcGroupMembershipOperationSchema = strictObject({
|
|
730
|
+
kind: literal("addIdcGroupMembership"),
|
|
731
|
+
groupDisplayName: string(),
|
|
732
|
+
userName: string()
|
|
733
|
+
});
|
|
734
|
+
var removeIdcGroupMembershipOperationSchema = strictObject({
|
|
735
|
+
kind: literal("removeIdcGroupMembership"),
|
|
736
|
+
groupDisplayName: string(),
|
|
737
|
+
userName: string()
|
|
738
|
+
});
|
|
739
|
+
var createIdcPermissionSetOperationSchema = strictObject({
|
|
740
|
+
kind: literal("createIdcPermissionSet"),
|
|
741
|
+
permissionSetName: string(),
|
|
742
|
+
description: string()
|
|
743
|
+
});
|
|
744
|
+
var updateIdcPermissionSetDescriptionOperationSchema = strictObject({
|
|
745
|
+
kind: literal("updateIdcPermissionSetDescription"),
|
|
746
|
+
permissionSetName: string(),
|
|
747
|
+
description: string()
|
|
748
|
+
});
|
|
749
|
+
var deleteIdcPermissionSetOperationSchema = strictObject({
|
|
750
|
+
kind: literal("deleteIdcPermissionSet"),
|
|
751
|
+
permissionSetName: string()
|
|
752
|
+
});
|
|
753
|
+
var putIdcPermissionSetInlinePolicyOperationSchema = strictObject({
|
|
754
|
+
kind: literal("putIdcPermissionSetInlinePolicy"),
|
|
755
|
+
permissionSetName: string(),
|
|
756
|
+
inlinePolicy: string()
|
|
757
|
+
});
|
|
758
|
+
var deleteIdcPermissionSetInlinePolicyOperationSchema = strictObject({
|
|
759
|
+
kind: literal("deleteIdcPermissionSetInlinePolicy"),
|
|
760
|
+
permissionSetName: string()
|
|
761
|
+
});
|
|
762
|
+
var attachIdcManagedPolicyToPermissionSetOperationSchema = strictObject({
|
|
763
|
+
kind: literal("attachIdcManagedPolicyToPermissionSet"),
|
|
764
|
+
permissionSetName: string(),
|
|
765
|
+
managedPolicyArn: string()
|
|
766
|
+
});
|
|
767
|
+
var detachIdcManagedPolicyFromPermissionSetOperationSchema = strictObject({
|
|
768
|
+
kind: literal("detachIdcManagedPolicyFromPermissionSet"),
|
|
769
|
+
permissionSetName: string(),
|
|
770
|
+
managedPolicyArn: string()
|
|
771
|
+
});
|
|
772
|
+
var attachIdcCustomerManagedPolicyReferenceToPermissionSetOperationSchema = strictObject({
|
|
773
|
+
kind: literal("attachIdcCustomerManagedPolicyReferenceToPermissionSet"),
|
|
774
|
+
permissionSetName: string(),
|
|
775
|
+
customerManagedPolicyName: string(),
|
|
776
|
+
customerManagedPolicyPath: string()
|
|
777
|
+
});
|
|
778
|
+
var detachIdcCustomerManagedPolicyReferenceFromPermissionSetOperationSchema = strictObject({
|
|
779
|
+
kind: literal("detachIdcCustomerManagedPolicyReferenceFromPermissionSet"),
|
|
780
|
+
permissionSetName: string(),
|
|
781
|
+
customerManagedPolicyName: string(),
|
|
782
|
+
customerManagedPolicyPath: string()
|
|
783
|
+
});
|
|
784
|
+
var provisionIdcPermissionSetOperationSchema = strictObject({
|
|
785
|
+
kind: literal("provisionIdcPermissionSet"),
|
|
786
|
+
permissionSetName: string(),
|
|
787
|
+
targetScope: literal("ALL_PROVISIONED_ACCOUNTS")
|
|
788
|
+
});
|
|
789
|
+
var grantIdcAccountAssignmentOperationSchema = strictObject({
|
|
790
|
+
kind: literal("grantIdcAccountAssignment"),
|
|
791
|
+
accountName: string(),
|
|
792
|
+
permissionSetName: string(),
|
|
793
|
+
principalType: picklist(["GROUP", "USER"]),
|
|
794
|
+
principalName: string()
|
|
795
|
+
});
|
|
796
|
+
var revokeIdcAccountAssignmentOperationSchema = strictObject({
|
|
797
|
+
kind: literal("revokeIdcAccountAssignment"),
|
|
798
|
+
accountName: string(),
|
|
799
|
+
permissionSetName: string(),
|
|
800
|
+
principalType: picklist(["GROUP", "USER"]),
|
|
801
|
+
principalName: string()
|
|
802
|
+
});
|
|
803
|
+
var operationSchema = variant("kind", [
|
|
804
|
+
moveAccountOperationSchema,
|
|
805
|
+
createOuOperationSchema,
|
|
806
|
+
renameOuOperationSchema,
|
|
807
|
+
deleteOuOperationSchema,
|
|
808
|
+
createAccountOperationSchema,
|
|
809
|
+
updateAccountTagsOperationSchema,
|
|
810
|
+
updateAccountNameOperationSchema,
|
|
811
|
+
removeAccountOperationSchema,
|
|
812
|
+
createIdcUserOperationSchema,
|
|
813
|
+
updateIdcUserOperationSchema,
|
|
814
|
+
deleteIdcUserOperationSchema,
|
|
815
|
+
createIdcGroupOperationSchema,
|
|
816
|
+
updateIdcGroupDescriptionOperationSchema,
|
|
817
|
+
deleteIdcGroupOperationSchema,
|
|
818
|
+
addIdcGroupMembershipOperationSchema,
|
|
819
|
+
removeIdcGroupMembershipOperationSchema,
|
|
820
|
+
createIdcPermissionSetOperationSchema,
|
|
821
|
+
updateIdcPermissionSetDescriptionOperationSchema,
|
|
822
|
+
deleteIdcPermissionSetOperationSchema,
|
|
823
|
+
putIdcPermissionSetInlinePolicyOperationSchema,
|
|
824
|
+
deleteIdcPermissionSetInlinePolicyOperationSchema,
|
|
825
|
+
attachIdcManagedPolicyToPermissionSetOperationSchema,
|
|
826
|
+
detachIdcManagedPolicyFromPermissionSetOperationSchema,
|
|
827
|
+
attachIdcCustomerManagedPolicyReferenceToPermissionSetOperationSchema,
|
|
828
|
+
detachIdcCustomerManagedPolicyReferenceFromPermissionSetOperationSchema,
|
|
829
|
+
provisionIdcPermissionSetOperationSchema,
|
|
830
|
+
grantIdcAccountAssignmentOperationSchema,
|
|
831
|
+
revokeIdcAccountAssignmentOperationSchema
|
|
832
|
+
]);
|
|
833
|
+
var unsupportedDiffKindSchema = picklist([
|
|
834
|
+
"ambiguousOuRename",
|
|
835
|
+
"reparentedOu",
|
|
836
|
+
"newOuWithUnknownParent",
|
|
837
|
+
"newAccountWithUnknownOu",
|
|
838
|
+
"removedOu"
|
|
839
|
+
]);
|
|
840
|
+
var unsupportedDiffCategorySchema = picklist([
|
|
841
|
+
"destructive",
|
|
842
|
+
"unsupportedMutation"
|
|
843
|
+
]);
|
|
844
|
+
var unsupportedDiffSchema = strictObject({
|
|
845
|
+
kind: unsupportedDiffKindSchema,
|
|
846
|
+
category: unsupportedDiffCategorySchema,
|
|
847
|
+
description: string()
|
|
848
|
+
});
|
|
849
|
+
var planSchema = strictObject({
|
|
850
|
+
operations: array(operationSchema),
|
|
851
|
+
unsupported: array(unsupportedDiffSchema)
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
// src/helpers.ts
|
|
855
|
+
function assertUnreachable(value, message = JSON.stringify(value)) {
|
|
856
|
+
throw Error("An unreachable state reached!\n" + message);
|
|
857
|
+
}
|
|
858
|
+
function toRecordByProperty(input, key, keyTransformer = (key2) => key2) {
|
|
859
|
+
return Object.fromEntries(
|
|
860
|
+
input.map((item) => [
|
|
861
|
+
keyTransformer(typeof key === "function" ? key(item) : item[key]),
|
|
862
|
+
item
|
|
863
|
+
])
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
async function delay(ms) {
|
|
867
|
+
await new Promise((resolve) => {
|
|
868
|
+
setTimeout(resolve, ms);
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
// src/state.ts
|
|
873
|
+
var nonEmptyString = pipe(string(), minLength(1));
|
|
874
|
+
var principalTypeSchema = picklist(["GROUP", "USER"]);
|
|
875
|
+
var organizationalUnitSchema = strictObject({
|
|
876
|
+
id: nonEmptyString,
|
|
877
|
+
parentId: nonEmptyString,
|
|
878
|
+
arn: nonEmptyString,
|
|
879
|
+
name: nonEmptyString
|
|
880
|
+
});
|
|
881
|
+
var accountTagSchema = strictObject({
|
|
882
|
+
key: nonEmptyString,
|
|
883
|
+
value: string()
|
|
884
|
+
});
|
|
885
|
+
var accountSchema = strictObject({
|
|
886
|
+
id: nonEmptyString,
|
|
887
|
+
arn: nonEmptyString,
|
|
888
|
+
name: nonEmptyString,
|
|
889
|
+
email: nonEmptyString,
|
|
890
|
+
status: nonEmptyString,
|
|
891
|
+
parentId: nonEmptyString,
|
|
892
|
+
tags: array(accountTagSchema)
|
|
893
|
+
});
|
|
894
|
+
var userSchema = strictObject({
|
|
895
|
+
userId: nonEmptyString,
|
|
896
|
+
userName: nonEmptyString,
|
|
897
|
+
displayName: string(),
|
|
898
|
+
email: string()
|
|
899
|
+
});
|
|
900
|
+
var groupSchema = strictObject({
|
|
901
|
+
groupId: nonEmptyString,
|
|
902
|
+
displayName: nonEmptyString,
|
|
903
|
+
description: optional(string())
|
|
904
|
+
});
|
|
905
|
+
var groupMembershipSchema = strictObject({
|
|
906
|
+
membershipId: nonEmptyString,
|
|
907
|
+
groupId: nonEmptyString,
|
|
908
|
+
userId: nonEmptyString
|
|
909
|
+
});
|
|
910
|
+
var customerManagedPolicyReferenceSchema = strictObject({
|
|
911
|
+
name: nonEmptyString,
|
|
912
|
+
path: nonEmptyString
|
|
913
|
+
});
|
|
914
|
+
var permissionSetSchema = strictObject({
|
|
915
|
+
permissionSetArn: nonEmptyString,
|
|
916
|
+
name: nonEmptyString,
|
|
917
|
+
description: string(),
|
|
918
|
+
inlinePolicy: nullable(nonEmptyString),
|
|
919
|
+
awsManagedPolicies: array(nonEmptyString),
|
|
920
|
+
customerManagedPolicies: array(customerManagedPolicyReferenceSchema)
|
|
921
|
+
});
|
|
922
|
+
var accountAssignmentSchema = strictObject({
|
|
923
|
+
accountId: nonEmptyString,
|
|
924
|
+
permissionSetArn: nonEmptyString,
|
|
925
|
+
principalId: nonEmptyString,
|
|
926
|
+
principalType: principalTypeSchema
|
|
927
|
+
});
|
|
928
|
+
var accessRoleSchema = strictObject({
|
|
929
|
+
accountId: nonEmptyString,
|
|
930
|
+
permissionSetArn: nonEmptyString,
|
|
931
|
+
principalId: nonEmptyString,
|
|
932
|
+
principalType: principalTypeSchema,
|
|
933
|
+
roleName: nonEmptyString
|
|
934
|
+
});
|
|
935
|
+
var stateSchema = strictObject({
|
|
936
|
+
version: nonEmptyString,
|
|
937
|
+
generatedAt: nonEmptyString,
|
|
938
|
+
organization: strictObject({
|
|
939
|
+
rootId: nonEmptyString,
|
|
940
|
+
organizationalUnits: array(organizationalUnitSchema),
|
|
941
|
+
accounts: array(accountSchema)
|
|
942
|
+
}),
|
|
943
|
+
identityCenter: strictObject({
|
|
944
|
+
instanceArn: nonEmptyString,
|
|
945
|
+
identityStoreId: nonEmptyString,
|
|
946
|
+
users: array(userSchema),
|
|
947
|
+
groups: array(groupSchema),
|
|
948
|
+
groupMemberships: array(groupMembershipSchema),
|
|
949
|
+
permissionSets: array(permissionSetSchema),
|
|
950
|
+
accountAssignments: array(accountAssignmentSchema),
|
|
951
|
+
accessRoles: array(accessRoleSchema)
|
|
952
|
+
})
|
|
953
|
+
});
|
|
954
|
+
function createWorkingState(props) {
|
|
955
|
+
return {
|
|
956
|
+
version: props.state.version,
|
|
957
|
+
generatedAt: props.state.generatedAt,
|
|
958
|
+
organization: {
|
|
959
|
+
rootId: props.state.organization.rootId,
|
|
960
|
+
organizationalUnitsById: toRecordByProperty(
|
|
961
|
+
props.state.organization.organizationalUnits,
|
|
962
|
+
"id"
|
|
963
|
+
),
|
|
964
|
+
accountsById: toRecordByProperty(props.state.organization.accounts, "id"),
|
|
965
|
+
accountsByName: toRecordByProperty(
|
|
966
|
+
props.state.organization.accounts,
|
|
967
|
+
"name"
|
|
968
|
+
)
|
|
969
|
+
},
|
|
970
|
+
identityCenter: createWorkingIdentityCenterState({
|
|
971
|
+
identityCenter: props.state.identityCenter
|
|
972
|
+
})
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function materializeWorkingState(props) {
|
|
976
|
+
return {
|
|
977
|
+
version: props.workingState.version,
|
|
978
|
+
generatedAt: props.workingState.generatedAt,
|
|
979
|
+
organization: {
|
|
980
|
+
rootId: props.workingState.organization.rootId,
|
|
981
|
+
organizationalUnits: Object.values(
|
|
982
|
+
props.workingState.organization.organizationalUnitsById
|
|
983
|
+
),
|
|
984
|
+
accounts: Object.values(props.workingState.organization.accountsById)
|
|
985
|
+
},
|
|
986
|
+
identityCenter: {
|
|
987
|
+
instanceArn: props.workingState.identityCenter.instanceArn,
|
|
988
|
+
identityStoreId: props.workingState.identityCenter.identityStoreId,
|
|
989
|
+
users: structuredClone(props.workingState.identityCenter.users),
|
|
990
|
+
groups: structuredClone(props.workingState.identityCenter.groups),
|
|
991
|
+
groupMemberships: structuredClone(
|
|
992
|
+
props.workingState.identityCenter.groupMemberships
|
|
993
|
+
),
|
|
994
|
+
permissionSets: structuredClone(
|
|
995
|
+
props.workingState.identityCenter.permissionSets
|
|
996
|
+
),
|
|
997
|
+
accountAssignments: structuredClone(
|
|
998
|
+
props.workingState.identityCenter.accountAssignments
|
|
999
|
+
),
|
|
1000
|
+
accessRoles: structuredClone(
|
|
1001
|
+
props.workingState.identityCenter.accessRoles
|
|
1002
|
+
)
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function moveAccountInWorkingState(props) {
|
|
1007
|
+
const currentAccount = props.workingState.organization.accountsById[props.accountId];
|
|
1008
|
+
if (currentAccount == null || currentAccount.parentId === props.parentId) {
|
|
1009
|
+
return props.workingState;
|
|
1010
|
+
}
|
|
1011
|
+
return {
|
|
1012
|
+
...props.workingState,
|
|
1013
|
+
organization: {
|
|
1014
|
+
...props.workingState.organization,
|
|
1015
|
+
accountsById: {
|
|
1016
|
+
...props.workingState.organization.accountsById,
|
|
1017
|
+
[props.accountId]: {
|
|
1018
|
+
...currentAccount,
|
|
1019
|
+
parentId: props.parentId
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
accountsByName: {
|
|
1023
|
+
...props.workingState.organization.accountsByName,
|
|
1024
|
+
[currentAccount.name]: {
|
|
1025
|
+
...currentAccount,
|
|
1026
|
+
parentId: props.parentId
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
function upsertAccountInWorkingState(props) {
|
|
1033
|
+
const currentAccount = props.workingState.organization.accountsById[props.account.id];
|
|
1034
|
+
if (currentAccount != null && currentAccount.id === props.account.id && currentAccount.arn === props.account.arn && currentAccount.name === props.account.name && currentAccount.email === props.account.email && currentAccount.status === props.account.status && currentAccount.parentId === props.account.parentId && JSON.stringify(normalizeAccountTags(currentAccount.tags)) === JSON.stringify(normalizeAccountTags(props.account.tags))) {
|
|
1035
|
+
return props.workingState;
|
|
1036
|
+
}
|
|
1037
|
+
let accountsByName = {
|
|
1038
|
+
...props.workingState.organization.accountsByName
|
|
1039
|
+
};
|
|
1040
|
+
if (currentAccount != null && currentAccount.name !== props.account.name) {
|
|
1041
|
+
const { [currentAccount.name]: _removed, ...rest } = accountsByName;
|
|
1042
|
+
accountsByName = rest;
|
|
1043
|
+
}
|
|
1044
|
+
accountsByName = {
|
|
1045
|
+
...accountsByName,
|
|
1046
|
+
[props.account.name]: props.account
|
|
1047
|
+
};
|
|
1048
|
+
return {
|
|
1049
|
+
...props.workingState,
|
|
1050
|
+
organization: {
|
|
1051
|
+
...props.workingState.organization,
|
|
1052
|
+
accountsById: {
|
|
1053
|
+
...props.workingState.organization.accountsById,
|
|
1054
|
+
[props.account.id]: props.account
|
|
1055
|
+
},
|
|
1056
|
+
accountsByName
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
function upsertOrganizationalUnitInWorkingState(props) {
|
|
1061
|
+
const currentOrganizationalUnit = props.workingState.organization.organizationalUnitsById[props.organizationalUnit.id];
|
|
1062
|
+
if (currentOrganizationalUnit != null && currentOrganizationalUnit.id === props.organizationalUnit.id && currentOrganizationalUnit.parentId === props.organizationalUnit.parentId && currentOrganizationalUnit.arn === props.organizationalUnit.arn && currentOrganizationalUnit.name === props.organizationalUnit.name) {
|
|
1063
|
+
return props.workingState;
|
|
1064
|
+
}
|
|
1065
|
+
return {
|
|
1066
|
+
...props.workingState,
|
|
1067
|
+
organization: {
|
|
1068
|
+
...props.workingState.organization,
|
|
1069
|
+
organizationalUnitsById: {
|
|
1070
|
+
...props.workingState.organization.organizationalUnitsById,
|
|
1071
|
+
[props.organizationalUnit.id]: props.organizationalUnit
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function renameOrganizationalUnitInWorkingState(props) {
|
|
1077
|
+
const currentOrganizationalUnit = props.workingState.organization.organizationalUnitsById[props.organizationalUnitId];
|
|
1078
|
+
if (currentOrganizationalUnit == null || currentOrganizationalUnit.name === props.name) {
|
|
1079
|
+
return props.workingState;
|
|
1080
|
+
}
|
|
1081
|
+
return {
|
|
1082
|
+
...props.workingState,
|
|
1083
|
+
organization: {
|
|
1084
|
+
...props.workingState.organization,
|
|
1085
|
+
organizationalUnitsById: {
|
|
1086
|
+
...props.workingState.organization.organizationalUnitsById,
|
|
1087
|
+
[props.organizationalUnitId]: {
|
|
1088
|
+
...currentOrganizationalUnit,
|
|
1089
|
+
name: props.name
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function removeOrganizationalUnitFromWorkingState(props) {
|
|
1096
|
+
if (props.workingState.organization.organizationalUnitsById[props.organizationalUnitId] == null) {
|
|
1097
|
+
return props.workingState;
|
|
1098
|
+
}
|
|
1099
|
+
const nextOrganizationalUnitsById = {
|
|
1100
|
+
...props.workingState.organization.organizationalUnitsById
|
|
1101
|
+
};
|
|
1102
|
+
delete nextOrganizationalUnitsById[props.organizationalUnitId];
|
|
1103
|
+
return {
|
|
1104
|
+
...props.workingState,
|
|
1105
|
+
organization: {
|
|
1106
|
+
...props.workingState.organization,
|
|
1107
|
+
organizationalUnitsById: nextOrganizationalUnitsById
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
function createAccountAssignmentKey(props) {
|
|
1112
|
+
return [
|
|
1113
|
+
props.accountId,
|
|
1114
|
+
props.permissionSetArn,
|
|
1115
|
+
props.principalId,
|
|
1116
|
+
props.principalType
|
|
1117
|
+
].join("|");
|
|
1118
|
+
}
|
|
1119
|
+
function createGroupMembershipKey(props) {
|
|
1120
|
+
return [props.groupId, props.userId].join("|");
|
|
1121
|
+
}
|
|
1122
|
+
function upsertIdcUserInWorkingState(props) {
|
|
1123
|
+
const currentUser = props.workingState.identityCenter.usersByUserName[props.user.userName];
|
|
1124
|
+
if (currentUser != null && currentUser.userId === props.user.userId && currentUser.displayName === props.user.displayName && currentUser.userName === props.user.userName && currentUser.email === props.user.email) {
|
|
1125
|
+
return props.workingState;
|
|
1126
|
+
}
|
|
1127
|
+
const remainingUsers = props.workingState.identityCenter.users.filter(
|
|
1128
|
+
(user) => user.userName !== props.user.userName
|
|
1129
|
+
);
|
|
1130
|
+
return {
|
|
1131
|
+
...props.workingState,
|
|
1132
|
+
identityCenter: createWorkingIdentityCenterState({
|
|
1133
|
+
identityCenter: {
|
|
1134
|
+
...materializeWorkingIdentityCenterState({
|
|
1135
|
+
identityCenter: props.workingState.identityCenter
|
|
1136
|
+
}),
|
|
1137
|
+
users: [...remainingUsers, props.user]
|
|
1138
|
+
}
|
|
1139
|
+
})
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
function removeIdcUserFromWorkingState(props) {
|
|
1143
|
+
const user = props.workingState.identityCenter.usersByUserName[props.userName];
|
|
1144
|
+
if (user == null) {
|
|
1145
|
+
return props.workingState;
|
|
1146
|
+
}
|
|
1147
|
+
return {
|
|
1148
|
+
...props.workingState,
|
|
1149
|
+
identityCenter: createWorkingIdentityCenterState({
|
|
1150
|
+
identityCenter: {
|
|
1151
|
+
...materializeWorkingIdentityCenterState({
|
|
1152
|
+
identityCenter: props.workingState.identityCenter
|
|
1153
|
+
}),
|
|
1154
|
+
users: props.workingState.identityCenter.users.filter(
|
|
1155
|
+
(currentUser) => currentUser.userName !== props.userName
|
|
1156
|
+
),
|
|
1157
|
+
groupMemberships: props.workingState.identityCenter.groupMemberships.filter(
|
|
1158
|
+
(groupMembership) => groupMembership.userId !== user.userId
|
|
1159
|
+
),
|
|
1160
|
+
accountAssignments: props.workingState.identityCenter.accountAssignments.filter(
|
|
1161
|
+
(accountAssignment) => accountAssignment.principalType !== "USER" || accountAssignment.principalId !== user.userId
|
|
1162
|
+
)
|
|
1163
|
+
}
|
|
1164
|
+
})
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
function upsertIdcGroupInWorkingState(props) {
|
|
1168
|
+
const currentGroup = props.workingState.identityCenter.groupsByDisplayName[props.group.displayName];
|
|
1169
|
+
if (currentGroup != null && currentGroup.groupId === props.group.groupId && currentGroup.displayName === props.group.displayName && (currentGroup.description ?? "") === (props.group.description ?? "")) {
|
|
1170
|
+
return props.workingState;
|
|
1171
|
+
}
|
|
1172
|
+
const remainingGroups = props.workingState.identityCenter.groups.filter(
|
|
1173
|
+
(group) => group.displayName !== props.group.displayName
|
|
1174
|
+
);
|
|
1175
|
+
return {
|
|
1176
|
+
...props.workingState,
|
|
1177
|
+
identityCenter: createWorkingIdentityCenterState({
|
|
1178
|
+
identityCenter: {
|
|
1179
|
+
...materializeWorkingIdentityCenterState({
|
|
1180
|
+
identityCenter: props.workingState.identityCenter
|
|
1181
|
+
}),
|
|
1182
|
+
groups: [...remainingGroups, props.group]
|
|
1183
|
+
}
|
|
1184
|
+
})
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
function removeIdcGroupFromWorkingState(props) {
|
|
1188
|
+
const group = props.workingState.identityCenter.groupsByDisplayName[props.groupDisplayName];
|
|
1189
|
+
if (group == null) {
|
|
1190
|
+
return props.workingState;
|
|
1191
|
+
}
|
|
1192
|
+
return {
|
|
1193
|
+
...props.workingState,
|
|
1194
|
+
identityCenter: createWorkingIdentityCenterState({
|
|
1195
|
+
identityCenter: {
|
|
1196
|
+
...materializeWorkingIdentityCenterState({
|
|
1197
|
+
identityCenter: props.workingState.identityCenter
|
|
1198
|
+
}),
|
|
1199
|
+
groups: props.workingState.identityCenter.groups.filter(
|
|
1200
|
+
(currentGroup) => currentGroup.displayName !== props.groupDisplayName
|
|
1201
|
+
),
|
|
1202
|
+
groupMemberships: props.workingState.identityCenter.groupMemberships.filter(
|
|
1203
|
+
(groupMembership) => groupMembership.groupId !== group.groupId
|
|
1204
|
+
),
|
|
1205
|
+
accountAssignments: props.workingState.identityCenter.accountAssignments.filter(
|
|
1206
|
+
(accountAssignment) => accountAssignment.principalType !== "GROUP" || accountAssignment.principalId !== group.groupId
|
|
1207
|
+
)
|
|
1208
|
+
}
|
|
1209
|
+
})
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
function upsertIdcPermissionSetInWorkingState(props) {
|
|
1213
|
+
const currentPermissionSet = props.workingState.identityCenter.permissionSetsByName[props.permissionSet.name];
|
|
1214
|
+
if (currentPermissionSet != null && currentPermissionSet.permissionSetArn === props.permissionSet.permissionSetArn && currentPermissionSet.name === props.permissionSet.name && currentPermissionSet.description === props.permissionSet.description && currentPermissionSet.inlinePolicy === props.permissionSet.inlinePolicy && JSON.stringify(currentPermissionSet.awsManagedPolicies) === JSON.stringify(props.permissionSet.awsManagedPolicies) && JSON.stringify(currentPermissionSet.customerManagedPolicies) === JSON.stringify(props.permissionSet.customerManagedPolicies)) {
|
|
1215
|
+
return props.workingState;
|
|
1216
|
+
}
|
|
1217
|
+
const remainingPermissionSets = props.workingState.identityCenter.permissionSets.filter(
|
|
1218
|
+
(permissionSet) => permissionSet.name !== props.permissionSet.name
|
|
1219
|
+
);
|
|
1220
|
+
return {
|
|
1221
|
+
...props.workingState,
|
|
1222
|
+
identityCenter: createWorkingIdentityCenterState({
|
|
1223
|
+
identityCenter: {
|
|
1224
|
+
...materializeWorkingIdentityCenterState({
|
|
1225
|
+
identityCenter: props.workingState.identityCenter
|
|
1226
|
+
}),
|
|
1227
|
+
permissionSets: [...remainingPermissionSets, props.permissionSet]
|
|
1228
|
+
}
|
|
1229
|
+
})
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
function removeIdcPermissionSetFromWorkingState(props) {
|
|
1233
|
+
const permissionSet = props.workingState.identityCenter.permissionSetsByName[props.permissionSetName];
|
|
1234
|
+
if (permissionSet == null) {
|
|
1235
|
+
return props.workingState;
|
|
1236
|
+
}
|
|
1237
|
+
return {
|
|
1238
|
+
...props.workingState,
|
|
1239
|
+
identityCenter: createWorkingIdentityCenterState({
|
|
1240
|
+
identityCenter: {
|
|
1241
|
+
...materializeWorkingIdentityCenterState({
|
|
1242
|
+
identityCenter: props.workingState.identityCenter
|
|
1243
|
+
}),
|
|
1244
|
+
permissionSets: props.workingState.identityCenter.permissionSets.filter(
|
|
1245
|
+
(currentPermissionSet) => currentPermissionSet.name !== props.permissionSetName
|
|
1246
|
+
),
|
|
1247
|
+
accountAssignments: props.workingState.identityCenter.accountAssignments.filter(
|
|
1248
|
+
(accountAssignment) => accountAssignment.permissionSetArn !== permissionSet.permissionSetArn
|
|
1249
|
+
)
|
|
1250
|
+
}
|
|
1251
|
+
})
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
function addAccountAssignmentToWorkingState(props) {
|
|
1255
|
+
const assignmentKey = createAccountAssignmentKey({
|
|
1256
|
+
accountId: props.accountAssignment.accountId,
|
|
1257
|
+
permissionSetArn: props.accountAssignment.permissionSetArn,
|
|
1258
|
+
principalId: props.accountAssignment.principalId,
|
|
1259
|
+
principalType: props.accountAssignment.principalType
|
|
1260
|
+
});
|
|
1261
|
+
if (props.workingState.identityCenter.accountAssignmentsByKey[assignmentKey] != null) {
|
|
1262
|
+
return props.workingState;
|
|
1263
|
+
}
|
|
1264
|
+
return {
|
|
1265
|
+
...props.workingState,
|
|
1266
|
+
identityCenter: createWorkingIdentityCenterState({
|
|
1267
|
+
identityCenter: {
|
|
1268
|
+
...materializeWorkingIdentityCenterState({
|
|
1269
|
+
identityCenter: props.workingState.identityCenter
|
|
1270
|
+
}),
|
|
1271
|
+
accountAssignments: [
|
|
1272
|
+
...props.workingState.identityCenter.accountAssignments,
|
|
1273
|
+
props.accountAssignment
|
|
1274
|
+
]
|
|
1275
|
+
}
|
|
1276
|
+
})
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
function addGroupMembershipToWorkingState(props) {
|
|
1280
|
+
const membershipKey = createGroupMembershipKey({
|
|
1281
|
+
groupId: props.groupMembership.groupId,
|
|
1282
|
+
userId: props.groupMembership.userId
|
|
1283
|
+
});
|
|
1284
|
+
if (props.workingState.identityCenter.groupMembershipsByKey[membershipKey] != null) {
|
|
1285
|
+
return props.workingState;
|
|
1286
|
+
}
|
|
1287
|
+
return {
|
|
1288
|
+
...props.workingState,
|
|
1289
|
+
identityCenter: createWorkingIdentityCenterState({
|
|
1290
|
+
identityCenter: {
|
|
1291
|
+
...materializeWorkingIdentityCenterState({
|
|
1292
|
+
identityCenter: props.workingState.identityCenter
|
|
1293
|
+
}),
|
|
1294
|
+
groupMemberships: [
|
|
1295
|
+
...props.workingState.identityCenter.groupMemberships,
|
|
1296
|
+
props.groupMembership
|
|
1297
|
+
]
|
|
1298
|
+
}
|
|
1299
|
+
})
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
function removeGroupMembershipFromWorkingState(props) {
|
|
1303
|
+
const membershipKey = createGroupMembershipKey({
|
|
1304
|
+
groupId: props.groupMembership.groupId,
|
|
1305
|
+
userId: props.groupMembership.userId
|
|
1306
|
+
});
|
|
1307
|
+
if (props.workingState.identityCenter.groupMembershipsByKey[membershipKey] == null) {
|
|
1308
|
+
return props.workingState;
|
|
1309
|
+
}
|
|
1310
|
+
return {
|
|
1311
|
+
...props.workingState,
|
|
1312
|
+
identityCenter: createWorkingIdentityCenterState({
|
|
1313
|
+
identityCenter: {
|
|
1314
|
+
...materializeWorkingIdentityCenterState({
|
|
1315
|
+
identityCenter: props.workingState.identityCenter
|
|
1316
|
+
}),
|
|
1317
|
+
groupMemberships: props.workingState.identityCenter.groupMemberships.filter(
|
|
1318
|
+
(groupMembership) => createGroupMembershipKey({
|
|
1319
|
+
groupId: groupMembership.groupId,
|
|
1320
|
+
userId: groupMembership.userId
|
|
1321
|
+
}) !== membershipKey
|
|
1322
|
+
)
|
|
1323
|
+
}
|
|
1324
|
+
})
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
function removeAccountAssignmentFromWorkingState(props) {
|
|
1328
|
+
const assignmentKey = createAccountAssignmentKey({
|
|
1329
|
+
accountId: props.accountAssignment.accountId,
|
|
1330
|
+
permissionSetArn: props.accountAssignment.permissionSetArn,
|
|
1331
|
+
principalId: props.accountAssignment.principalId,
|
|
1332
|
+
principalType: props.accountAssignment.principalType
|
|
1333
|
+
});
|
|
1334
|
+
if (props.workingState.identityCenter.accountAssignmentsByKey[assignmentKey] == null) {
|
|
1335
|
+
return props.workingState;
|
|
1336
|
+
}
|
|
1337
|
+
return {
|
|
1338
|
+
...props.workingState,
|
|
1339
|
+
identityCenter: createWorkingIdentityCenterState({
|
|
1340
|
+
identityCenter: {
|
|
1341
|
+
...materializeWorkingIdentityCenterState({
|
|
1342
|
+
identityCenter: props.workingState.identityCenter
|
|
1343
|
+
}),
|
|
1344
|
+
accountAssignments: props.workingState.identityCenter.accountAssignments.filter(
|
|
1345
|
+
(accountAssignment) => createAccountAssignmentKey({
|
|
1346
|
+
accountId: accountAssignment.accountId,
|
|
1347
|
+
permissionSetArn: accountAssignment.permissionSetArn,
|
|
1348
|
+
principalId: accountAssignment.principalId,
|
|
1349
|
+
principalType: accountAssignment.principalType
|
|
1350
|
+
}) !== assignmentKey
|
|
1351
|
+
)
|
|
1352
|
+
}
|
|
1353
|
+
})
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
function createAccessRoleName(assignment) {
|
|
1357
|
+
return `AWSReservedSSO_${assignment.permissionSetArn.split("/").at(-1) ?? "PermissionSet"}_${assignment.accountId}`;
|
|
1358
|
+
}
|
|
1359
|
+
function createWorkingIdentityCenterState(props) {
|
|
1360
|
+
const users = structuredClone(props.identityCenter.users);
|
|
1361
|
+
const groups = structuredClone(props.identityCenter.groups);
|
|
1362
|
+
const groupMemberships = structuredClone(
|
|
1363
|
+
props.identityCenter.groupMemberships
|
|
1364
|
+
);
|
|
1365
|
+
const permissionSets = structuredClone(props.identityCenter.permissionSets);
|
|
1366
|
+
const accountAssignments = structuredClone(
|
|
1367
|
+
props.identityCenter.accountAssignments
|
|
1368
|
+
);
|
|
1369
|
+
return {
|
|
1370
|
+
instanceArn: props.identityCenter.instanceArn,
|
|
1371
|
+
identityStoreId: props.identityCenter.identityStoreId,
|
|
1372
|
+
users,
|
|
1373
|
+
usersByUserName: toRecordByProperty(users, "userName"),
|
|
1374
|
+
groups,
|
|
1375
|
+
groupsByDisplayName: toRecordByProperty(groups, "displayName"),
|
|
1376
|
+
groupMemberships,
|
|
1377
|
+
groupMembershipsByKey: toRecordByProperty(
|
|
1378
|
+
groupMemberships,
|
|
1379
|
+
createGroupMembershipKey
|
|
1380
|
+
),
|
|
1381
|
+
permissionSets,
|
|
1382
|
+
permissionSetsByName: toRecordByProperty(permissionSets, "name"),
|
|
1383
|
+
accountAssignments,
|
|
1384
|
+
accountAssignmentsByKey: toRecordByProperty(
|
|
1385
|
+
accountAssignments,
|
|
1386
|
+
createAccountAssignmentKey
|
|
1387
|
+
),
|
|
1388
|
+
accessRoles: createAccessRoles({
|
|
1389
|
+
accountAssignments
|
|
1390
|
+
})
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
function materializeWorkingIdentityCenterState(props) {
|
|
1394
|
+
return {
|
|
1395
|
+
instanceArn: props.identityCenter.instanceArn,
|
|
1396
|
+
identityStoreId: props.identityCenter.identityStoreId,
|
|
1397
|
+
users: structuredClone(props.identityCenter.users),
|
|
1398
|
+
groups: structuredClone(props.identityCenter.groups),
|
|
1399
|
+
groupMemberships: structuredClone(props.identityCenter.groupMemberships),
|
|
1400
|
+
permissionSets: structuredClone(props.identityCenter.permissionSets),
|
|
1401
|
+
accountAssignments: structuredClone(
|
|
1402
|
+
props.identityCenter.accountAssignments
|
|
1403
|
+
),
|
|
1404
|
+
accessRoles: structuredClone(props.identityCenter.accessRoles)
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
function createAccessRoles(props) {
|
|
1408
|
+
return props.accountAssignments.map((accountAssignment) => ({
|
|
1409
|
+
accountId: accountAssignment.accountId,
|
|
1410
|
+
permissionSetArn: accountAssignment.permissionSetArn,
|
|
1411
|
+
principalId: accountAssignment.principalId,
|
|
1412
|
+
principalType: accountAssignment.principalType,
|
|
1413
|
+
roleName: createAccessRoleName(accountAssignment)
|
|
1414
|
+
}));
|
|
1415
|
+
}
|
|
1416
|
+
function compareByKeys(...values) {
|
|
1417
|
+
for (let index = 0; index < values.length; index += 2) {
|
|
1418
|
+
const left = values[index] ?? "";
|
|
1419
|
+
const right = values[index + 1] ?? "";
|
|
1420
|
+
const compared = left.localeCompare(right);
|
|
1421
|
+
if (compared !== 0) {
|
|
1422
|
+
return compared;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
return 0;
|
|
1426
|
+
}
|
|
1427
|
+
function normalizeAccountTags(tags) {
|
|
1428
|
+
if (tags == null || tags.length === 0) {
|
|
1429
|
+
return [];
|
|
1430
|
+
}
|
|
1431
|
+
return [...tags].sort(
|
|
1432
|
+
(left, right) => compareByKeys(left.key, right.key, left.value, right.value)
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// src/scanLogic.ts
|
|
1437
|
+
import {
|
|
1438
|
+
ListGroupMembershipsCommand,
|
|
1439
|
+
ListGroupsCommand,
|
|
1440
|
+
ListUsersCommand
|
|
1441
|
+
} from "@aws-sdk/client-identitystore";
|
|
1442
|
+
import {
|
|
1443
|
+
ListAccountsCommand,
|
|
1444
|
+
ListOrganizationalUnitsForParentCommand,
|
|
1445
|
+
ListParentsCommand,
|
|
1446
|
+
ListRootsCommand,
|
|
1447
|
+
ListTagsForResourceCommand
|
|
1448
|
+
} from "@aws-sdk/client-organizations";
|
|
1449
|
+
import {
|
|
1450
|
+
DescribePermissionSetCommand,
|
|
1451
|
+
GetInlinePolicyForPermissionSetCommand,
|
|
1452
|
+
ListAccountAssignmentsCommand,
|
|
1453
|
+
ListAccountsForProvisionedPermissionSetCommand,
|
|
1454
|
+
ListCustomerManagedPolicyReferencesInPermissionSetCommand,
|
|
1455
|
+
ListInstancesCommand,
|
|
1456
|
+
ListManagedPoliciesInPermissionSetCommand,
|
|
1457
|
+
ListPermissionSetsCommand
|
|
1458
|
+
} from "@aws-sdk/client-sso-admin";
|
|
1459
|
+
async function scanOrganization(props) {
|
|
1460
|
+
const roots = await props.organizationsClient.send(new ListRootsCommand({}));
|
|
1461
|
+
const root = roots.Roots?.[0];
|
|
1462
|
+
if (root?.Id == null) {
|
|
1463
|
+
throw new Error("No organization root found.");
|
|
1464
|
+
}
|
|
1465
|
+
const organizationalUnits = await collectOrganizationalUnits({
|
|
1466
|
+
organizationsClient: props.organizationsClient,
|
|
1467
|
+
parentId: root.Id
|
|
1468
|
+
});
|
|
1469
|
+
const accounts = [];
|
|
1470
|
+
let nextToken;
|
|
1471
|
+
do {
|
|
1472
|
+
const response = await props.organizationsClient.send(
|
|
1473
|
+
new ListAccountsCommand({ NextToken: nextToken })
|
|
1474
|
+
);
|
|
1475
|
+
for (const account of response.Accounts ?? []) {
|
|
1476
|
+
if (account.Id == null || account.Arn == null || account.Name == null || account.Email == null || account.Status == null) {
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1479
|
+
const parents = await props.organizationsClient.send(
|
|
1480
|
+
new ListParentsCommand({ ChildId: account.Id })
|
|
1481
|
+
);
|
|
1482
|
+
const parentId = parents.Parents?.[0]?.Id ?? root.Id;
|
|
1483
|
+
const tagsResponse = await props.organizationsClient.send(
|
|
1484
|
+
new ListTagsForResourceCommand({
|
|
1485
|
+
ResourceId: account.Id
|
|
1486
|
+
})
|
|
1487
|
+
);
|
|
1488
|
+
accounts.push({
|
|
1489
|
+
id: account.Id,
|
|
1490
|
+
arn: account.Arn,
|
|
1491
|
+
name: account.Name,
|
|
1492
|
+
email: account.Email,
|
|
1493
|
+
status: account.Status,
|
|
1494
|
+
parentId,
|
|
1495
|
+
tags: (tagsResponse.Tags ?? []).flatMap((tag) => {
|
|
1496
|
+
if (tag.Key == null) {
|
|
1497
|
+
return [];
|
|
1498
|
+
}
|
|
1499
|
+
return [
|
|
1500
|
+
{
|
|
1501
|
+
key: tag.Key,
|
|
1502
|
+
value: tag.Value ?? ""
|
|
1503
|
+
}
|
|
1504
|
+
];
|
|
1505
|
+
})
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
nextToken = response.NextToken;
|
|
1509
|
+
} while (nextToken != null);
|
|
1510
|
+
return {
|
|
1511
|
+
rootId: root.Id,
|
|
1512
|
+
organizationalUnits,
|
|
1513
|
+
accounts
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
async function collectOrganizationalUnits(props) {
|
|
1517
|
+
const children = [];
|
|
1518
|
+
let nextToken;
|
|
1519
|
+
do {
|
|
1520
|
+
const response = await props.organizationsClient.send(
|
|
1521
|
+
new ListOrganizationalUnitsForParentCommand({
|
|
1522
|
+
ParentId: props.parentId,
|
|
1523
|
+
NextToken: nextToken
|
|
1524
|
+
})
|
|
1525
|
+
);
|
|
1526
|
+
for (const ou of response.OrganizationalUnits ?? []) {
|
|
1527
|
+
if (ou.Id == null || ou.Arn == null || ou.Name == null) {
|
|
1528
|
+
continue;
|
|
1529
|
+
}
|
|
1530
|
+
children.push({
|
|
1531
|
+
id: ou.Id,
|
|
1532
|
+
parentId: props.parentId,
|
|
1533
|
+
arn: ou.Arn,
|
|
1534
|
+
name: ou.Name
|
|
1535
|
+
});
|
|
1536
|
+
const descendants = await collectOrganizationalUnits({
|
|
1537
|
+
organizationsClient: props.organizationsClient,
|
|
1538
|
+
parentId: ou.Id
|
|
1539
|
+
});
|
|
1540
|
+
children.push(...descendants);
|
|
1541
|
+
}
|
|
1542
|
+
nextToken = response.NextToken;
|
|
1543
|
+
} while (nextToken != null);
|
|
1544
|
+
return children;
|
|
1545
|
+
}
|
|
1546
|
+
async function scanIdentityCenter(props) {
|
|
1547
|
+
const instancesResponse = await props.ssoAdminClient.send(
|
|
1548
|
+
new ListInstancesCommand({})
|
|
1549
|
+
);
|
|
1550
|
+
const instances = instancesResponse.Instances ?? [];
|
|
1551
|
+
if (instances.length === 0) {
|
|
1552
|
+
throw new Error("No IAM Identity Center instance found.");
|
|
1553
|
+
}
|
|
1554
|
+
const instance = selectIdentityCenterInstance({
|
|
1555
|
+
instances,
|
|
1556
|
+
requestedInstanceArn: props.requestedInstanceArn
|
|
1557
|
+
});
|
|
1558
|
+
const [users, groups, permissionSets] = await Promise.all([
|
|
1559
|
+
listIdentityStoreUsers({
|
|
1560
|
+
identityStoreClient: props.identityStoreClient,
|
|
1561
|
+
identityStoreId: instance.identityStoreId
|
|
1562
|
+
}),
|
|
1563
|
+
listIdentityStoreGroups({
|
|
1564
|
+
identityStoreClient: props.identityStoreClient,
|
|
1565
|
+
identityStoreId: instance.identityStoreId
|
|
1566
|
+
}),
|
|
1567
|
+
listPermissionSets({
|
|
1568
|
+
ssoAdminClient: props.ssoAdminClient,
|
|
1569
|
+
instanceArn: instance.instanceArn
|
|
1570
|
+
})
|
|
1571
|
+
]);
|
|
1572
|
+
const groupMemberships = await listGroupMemberships({
|
|
1573
|
+
identityStoreClient: props.identityStoreClient,
|
|
1574
|
+
identityStoreId: instance.identityStoreId,
|
|
1575
|
+
groups
|
|
1576
|
+
});
|
|
1577
|
+
const accountAssignments = await listAccountAssignments({
|
|
1578
|
+
ssoAdminClient: props.ssoAdminClient,
|
|
1579
|
+
instanceArn: instance.instanceArn,
|
|
1580
|
+
permissionSets
|
|
1581
|
+
});
|
|
1582
|
+
const accessRoles = accountAssignments.map((assignment) => ({
|
|
1583
|
+
...assignment,
|
|
1584
|
+
roleName: createAccessRoleName(assignment)
|
|
1585
|
+
}));
|
|
1586
|
+
return {
|
|
1587
|
+
instanceArn: instance.instanceArn,
|
|
1588
|
+
identityStoreId: instance.identityStoreId,
|
|
1589
|
+
users,
|
|
1590
|
+
groups,
|
|
1591
|
+
groupMemberships,
|
|
1592
|
+
permissionSets,
|
|
1593
|
+
accountAssignments,
|
|
1594
|
+
accessRoles
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
function selectIdentityCenterInstance(props) {
|
|
1598
|
+
if (props.requestedInstanceArn != null) {
|
|
1599
|
+
const selected2 = props.instances.find(
|
|
1600
|
+
(instance) => instance.InstanceArn === props.requestedInstanceArn
|
|
1601
|
+
);
|
|
1602
|
+
if (selected2?.InstanceArn == null || selected2.IdentityStoreId == null) {
|
|
1603
|
+
throw new Error(
|
|
1604
|
+
`Identity Center instance not found for --instance-arn: ${props.requestedInstanceArn}`
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
return {
|
|
1608
|
+
instanceArn: selected2.InstanceArn,
|
|
1609
|
+
identityStoreId: selected2.IdentityStoreId
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
if (props.instances.length > 1) {
|
|
1613
|
+
const knownArns = props.instances.map((instance) => instance.InstanceArn).filter((value) => value != null).join(", ");
|
|
1614
|
+
throw new Error(
|
|
1615
|
+
`Multiple IAM Identity Center instances found. Re-run scan with --instance-arn. Available: ${knownArns}`
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
const selected = props.instances[0];
|
|
1619
|
+
if (selected?.InstanceArn == null || selected.IdentityStoreId == null) {
|
|
1620
|
+
throw new Error("IAM Identity Center instance is missing required fields.");
|
|
1621
|
+
}
|
|
1622
|
+
return {
|
|
1623
|
+
instanceArn: selected.InstanceArn,
|
|
1624
|
+
identityStoreId: selected.IdentityStoreId
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
async function listIdentityStoreUsers(props) {
|
|
1628
|
+
const users = [];
|
|
1629
|
+
let nextToken;
|
|
1630
|
+
do {
|
|
1631
|
+
const response = await props.identityStoreClient.send(
|
|
1632
|
+
new ListUsersCommand({
|
|
1633
|
+
IdentityStoreId: props.identityStoreId,
|
|
1634
|
+
NextToken: nextToken
|
|
1635
|
+
})
|
|
1636
|
+
);
|
|
1637
|
+
for (const user of response.Users ?? []) {
|
|
1638
|
+
if (user.UserId == null || user.UserName == null) {
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
users.push({
|
|
1642
|
+
userId: user.UserId,
|
|
1643
|
+
userName: user.UserName,
|
|
1644
|
+
displayName: user.DisplayName ?? "",
|
|
1645
|
+
email: resolveIdentityStoreUserEmail({
|
|
1646
|
+
emails: user.Emails ?? []
|
|
1647
|
+
})
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
nextToken = response.NextToken;
|
|
1651
|
+
} while (nextToken != null);
|
|
1652
|
+
return users;
|
|
1653
|
+
}
|
|
1654
|
+
async function listIdentityStoreGroups(props) {
|
|
1655
|
+
const groups = [];
|
|
1656
|
+
let nextToken;
|
|
1657
|
+
do {
|
|
1658
|
+
const response = await props.identityStoreClient.send(
|
|
1659
|
+
new ListGroupsCommand({
|
|
1660
|
+
IdentityStoreId: props.identityStoreId,
|
|
1661
|
+
NextToken: nextToken
|
|
1662
|
+
})
|
|
1663
|
+
);
|
|
1664
|
+
for (const group of response.Groups ?? []) {
|
|
1665
|
+
if (group.GroupId == null || group.DisplayName == null) {
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
groups.push({
|
|
1669
|
+
groupId: group.GroupId,
|
|
1670
|
+
displayName: group.DisplayName,
|
|
1671
|
+
description: group.Description ?? ""
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
nextToken = response.NextToken;
|
|
1675
|
+
} while (nextToken != null);
|
|
1676
|
+
return groups;
|
|
1677
|
+
}
|
|
1678
|
+
async function listGroupMemberships(props) {
|
|
1679
|
+
const groupMemberships = [];
|
|
1680
|
+
for (const group of props.groups) {
|
|
1681
|
+
let nextToken;
|
|
1682
|
+
do {
|
|
1683
|
+
const response = await props.identityStoreClient.send(
|
|
1684
|
+
new ListGroupMembershipsCommand({
|
|
1685
|
+
IdentityStoreId: props.identityStoreId,
|
|
1686
|
+
GroupId: group.groupId,
|
|
1687
|
+
NextToken: nextToken
|
|
1688
|
+
})
|
|
1689
|
+
);
|
|
1690
|
+
for (const groupMembership of response.GroupMemberships ?? []) {
|
|
1691
|
+
const userId = groupMembership.MemberId?.UserId;
|
|
1692
|
+
if (groupMembership.MembershipId == null || groupMembership.GroupId == null || userId == null) {
|
|
1693
|
+
continue;
|
|
1694
|
+
}
|
|
1695
|
+
groupMemberships.push({
|
|
1696
|
+
membershipId: groupMembership.MembershipId,
|
|
1697
|
+
groupId: groupMembership.GroupId,
|
|
1698
|
+
userId
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
nextToken = response.NextToken;
|
|
1702
|
+
} while (nextToken != null);
|
|
1703
|
+
}
|
|
1704
|
+
return groupMemberships;
|
|
1705
|
+
}
|
|
1706
|
+
function resolveIdentityStoreUserEmail(props) {
|
|
1707
|
+
const primaryEmail = props.emails.find(
|
|
1708
|
+
(email) => email.Primary === true && email.Value != null && email.Value.length > 0
|
|
1709
|
+
);
|
|
1710
|
+
if (primaryEmail?.Value != null) {
|
|
1711
|
+
return primaryEmail.Value;
|
|
1712
|
+
}
|
|
1713
|
+
return props.emails.find((email) => email.Value != null && email.Value.length > 0)?.Value ?? "";
|
|
1714
|
+
}
|
|
1715
|
+
async function listPermissionSets(props) {
|
|
1716
|
+
const permissionSetArns = [];
|
|
1717
|
+
let nextToken;
|
|
1718
|
+
do {
|
|
1719
|
+
const response = await props.ssoAdminClient.send(
|
|
1720
|
+
new ListPermissionSetsCommand({
|
|
1721
|
+
InstanceArn: props.instanceArn,
|
|
1722
|
+
NextToken: nextToken
|
|
1723
|
+
})
|
|
1724
|
+
);
|
|
1725
|
+
permissionSetArns.push(...response.PermissionSets ?? []);
|
|
1726
|
+
nextToken = response.NextToken;
|
|
1727
|
+
} while (nextToken != null);
|
|
1728
|
+
const describeResponses = await Promise.all(
|
|
1729
|
+
permissionSetArns.map(
|
|
1730
|
+
(permissionSetArn) => props.ssoAdminClient.send(
|
|
1731
|
+
new DescribePermissionSetCommand({
|
|
1732
|
+
InstanceArn: props.instanceArn,
|
|
1733
|
+
PermissionSetArn: permissionSetArn
|
|
1734
|
+
})
|
|
1735
|
+
)
|
|
1736
|
+
)
|
|
1737
|
+
);
|
|
1738
|
+
const permissionSets = await Promise.all(
|
|
1739
|
+
describeResponses.map(async (response) => {
|
|
1740
|
+
const permissionSet = response.PermissionSet;
|
|
1741
|
+
if (permissionSet?.PermissionSetArn == null || permissionSet.Name == null) {
|
|
1742
|
+
return void 0;
|
|
1743
|
+
}
|
|
1744
|
+
const [
|
|
1745
|
+
inlinePolicy,
|
|
1746
|
+
awsManagedPolicies,
|
|
1747
|
+
customerManagedPolicies
|
|
1748
|
+
] = await Promise.all([
|
|
1749
|
+
getInlinePolicyForPermissionSet({
|
|
1750
|
+
ssoAdminClient: props.ssoAdminClient,
|
|
1751
|
+
instanceArn: props.instanceArn,
|
|
1752
|
+
permissionSetArn: permissionSet.PermissionSetArn
|
|
1753
|
+
}),
|
|
1754
|
+
listManagedPoliciesInPermissionSet({
|
|
1755
|
+
ssoAdminClient: props.ssoAdminClient,
|
|
1756
|
+
instanceArn: props.instanceArn,
|
|
1757
|
+
permissionSetArn: permissionSet.PermissionSetArn
|
|
1758
|
+
}),
|
|
1759
|
+
listCustomerManagedPoliciesInPermissionSet({
|
|
1760
|
+
ssoAdminClient: props.ssoAdminClient,
|
|
1761
|
+
instanceArn: props.instanceArn,
|
|
1762
|
+
permissionSetArn: permissionSet.PermissionSetArn
|
|
1763
|
+
})
|
|
1764
|
+
]);
|
|
1765
|
+
return {
|
|
1766
|
+
permissionSetArn: permissionSet.PermissionSetArn,
|
|
1767
|
+
name: permissionSet.Name,
|
|
1768
|
+
description: permissionSet.Description ?? "",
|
|
1769
|
+
inlinePolicy,
|
|
1770
|
+
awsManagedPolicies,
|
|
1771
|
+
customerManagedPolicies
|
|
1772
|
+
};
|
|
1773
|
+
})
|
|
1774
|
+
);
|
|
1775
|
+
return permissionSets.filter(
|
|
1776
|
+
(permissionSet) => permissionSet != null
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
async function getInlinePolicyForPermissionSet(props) {
|
|
1780
|
+
const response = await props.ssoAdminClient.send(
|
|
1781
|
+
new GetInlinePolicyForPermissionSetCommand({
|
|
1782
|
+
InstanceArn: props.instanceArn,
|
|
1783
|
+
PermissionSetArn: props.permissionSetArn
|
|
1784
|
+
})
|
|
1785
|
+
);
|
|
1786
|
+
const inlinePolicy = response.InlinePolicy?.trim();
|
|
1787
|
+
return inlinePolicy != null && inlinePolicy.length > 0 ? inlinePolicy : null;
|
|
1788
|
+
}
|
|
1789
|
+
async function listManagedPoliciesInPermissionSet(props) {
|
|
1790
|
+
const managedPolicies = [];
|
|
1791
|
+
let nextToken;
|
|
1792
|
+
do {
|
|
1793
|
+
const response = await props.ssoAdminClient.send(
|
|
1794
|
+
new ListManagedPoliciesInPermissionSetCommand({
|
|
1795
|
+
InstanceArn: props.instanceArn,
|
|
1796
|
+
PermissionSetArn: props.permissionSetArn,
|
|
1797
|
+
NextToken: nextToken
|
|
1798
|
+
})
|
|
1799
|
+
);
|
|
1800
|
+
for (const attachedManagedPolicy of response.AttachedManagedPolicies ?? []) {
|
|
1801
|
+
if (attachedManagedPolicy.Arn == null) {
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
managedPolicies.push(attachedManagedPolicy.Arn);
|
|
1805
|
+
}
|
|
1806
|
+
nextToken = response.NextToken;
|
|
1807
|
+
} while (nextToken != null);
|
|
1808
|
+
return managedPolicies;
|
|
1809
|
+
}
|
|
1810
|
+
async function listCustomerManagedPoliciesInPermissionSet(props) {
|
|
1811
|
+
const customerManagedPolicies = [];
|
|
1812
|
+
let nextToken;
|
|
1813
|
+
do {
|
|
1814
|
+
const response = await props.ssoAdminClient.send(
|
|
1815
|
+
new ListCustomerManagedPolicyReferencesInPermissionSetCommand({
|
|
1816
|
+
InstanceArn: props.instanceArn,
|
|
1817
|
+
PermissionSetArn: props.permissionSetArn,
|
|
1818
|
+
NextToken: nextToken
|
|
1819
|
+
})
|
|
1820
|
+
);
|
|
1821
|
+
for (const customerManagedPolicyReference of response.CustomerManagedPolicyReferences ?? []) {
|
|
1822
|
+
if (customerManagedPolicyReference.Name == null || customerManagedPolicyReference.Path == null) {
|
|
1823
|
+
continue;
|
|
1824
|
+
}
|
|
1825
|
+
customerManagedPolicies.push({
|
|
1826
|
+
name: customerManagedPolicyReference.Name,
|
|
1827
|
+
path: customerManagedPolicyReference.Path
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
nextToken = response.NextToken;
|
|
1831
|
+
} while (nextToken != null);
|
|
1832
|
+
return customerManagedPolicies;
|
|
1833
|
+
}
|
|
1834
|
+
async function listAccountAssignments(props) {
|
|
1835
|
+
const assignments = [];
|
|
1836
|
+
for (const permissionSet of props.permissionSets) {
|
|
1837
|
+
const accountIds = await listAccountsForPermissionSet({
|
|
1838
|
+
ssoAdminClient: props.ssoAdminClient,
|
|
1839
|
+
instanceArn: props.instanceArn,
|
|
1840
|
+
permissionSetArn: permissionSet.permissionSetArn
|
|
1841
|
+
});
|
|
1842
|
+
for (const accountId of accountIds) {
|
|
1843
|
+
let nextToken;
|
|
1844
|
+
do {
|
|
1845
|
+
const response = await props.ssoAdminClient.send(
|
|
1846
|
+
new ListAccountAssignmentsCommand({
|
|
1847
|
+
InstanceArn: props.instanceArn,
|
|
1848
|
+
AccountId: accountId,
|
|
1849
|
+
PermissionSetArn: permissionSet.permissionSetArn,
|
|
1850
|
+
NextToken: nextToken
|
|
1851
|
+
})
|
|
1852
|
+
);
|
|
1853
|
+
for (const assignment of response.AccountAssignments ?? []) {
|
|
1854
|
+
if (assignment.AccountId == null || assignment.PermissionSetArn == null || assignment.PrincipalId == null || assignment.PrincipalType == null) {
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
assignments.push({
|
|
1858
|
+
accountId: assignment.AccountId,
|
|
1859
|
+
permissionSetArn: assignment.PermissionSetArn,
|
|
1860
|
+
principalId: assignment.PrincipalId,
|
|
1861
|
+
principalType: assignment.PrincipalType
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
nextToken = response.NextToken;
|
|
1865
|
+
} while (nextToken != null);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return assignments;
|
|
1869
|
+
}
|
|
1870
|
+
async function listAccountsForPermissionSet(props) {
|
|
1871
|
+
const accountIds = [];
|
|
1872
|
+
let nextToken;
|
|
1873
|
+
do {
|
|
1874
|
+
const response = await props.ssoAdminClient.send(
|
|
1875
|
+
new ListAccountsForProvisionedPermissionSetCommand({
|
|
1876
|
+
InstanceArn: props.instanceArn,
|
|
1877
|
+
PermissionSetArn: props.permissionSetArn,
|
|
1878
|
+
NextToken: nextToken
|
|
1879
|
+
})
|
|
1880
|
+
);
|
|
1881
|
+
accountIds.push(...response.AccountIds ?? []);
|
|
1882
|
+
nextToken = response.NextToken;
|
|
1883
|
+
} while (nextToken != null);
|
|
1884
|
+
return accountIds;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
// src/applyLogic.ts
|
|
1888
|
+
import {
|
|
1889
|
+
PutAccountNameCommand
|
|
1890
|
+
} from "@aws-sdk/client-account";
|
|
1891
|
+
import {
|
|
1892
|
+
CreateOrganizationalUnitCommand,
|
|
1893
|
+
DeleteOrganizationalUnitCommand,
|
|
1894
|
+
ListAccountsForParentCommand,
|
|
1895
|
+
ListOrganizationalUnitsForParentCommand as ListOrganizationalUnitsForParentCommand2,
|
|
1896
|
+
MoveAccountCommand as MoveAccountCommand2,
|
|
1897
|
+
TagResourceCommand,
|
|
1898
|
+
UntagResourceCommand,
|
|
1899
|
+
UpdateOrganizationalUnitCommand
|
|
1900
|
+
} from "@aws-sdk/client-organizations";
|
|
1901
|
+
import {
|
|
1902
|
+
CreateGroupMembershipCommand,
|
|
1903
|
+
CreateGroupCommand,
|
|
1904
|
+
CreateUserCommand,
|
|
1905
|
+
DeleteGroupCommand,
|
|
1906
|
+
DeleteGroupMembershipCommand,
|
|
1907
|
+
DeleteUserCommand,
|
|
1908
|
+
GetGroupMembershipIdCommand,
|
|
1909
|
+
UpdateGroupCommand,
|
|
1910
|
+
UpdateUserCommand
|
|
1911
|
+
} from "@aws-sdk/client-identitystore";
|
|
1912
|
+
import {
|
|
1913
|
+
CreateAccountAssignmentCommand,
|
|
1914
|
+
AttachCustomerManagedPolicyReferenceToPermissionSetCommand,
|
|
1915
|
+
AttachManagedPolicyToPermissionSetCommand,
|
|
1916
|
+
CreatePermissionSetCommand,
|
|
1917
|
+
DeleteAccountAssignmentCommand,
|
|
1918
|
+
DeleteInlinePolicyFromPermissionSetCommand,
|
|
1919
|
+
DeletePermissionSetCommand,
|
|
1920
|
+
DescribeAccountAssignmentCreationStatusCommand,
|
|
1921
|
+
DescribeAccountAssignmentDeletionStatusCommand,
|
|
1922
|
+
DescribePermissionSetProvisioningStatusCommand,
|
|
1923
|
+
DetachCustomerManagedPolicyReferenceFromPermissionSetCommand,
|
|
1924
|
+
DetachManagedPolicyFromPermissionSetCommand,
|
|
1925
|
+
ProvisionPermissionSetCommand,
|
|
1926
|
+
PutInlinePolicyToPermissionSetCommand,
|
|
1927
|
+
UpdatePermissionSetCommand
|
|
1928
|
+
} from "@aws-sdk/client-sso-admin";
|
|
1929
|
+
|
|
1930
|
+
// src/accountCreation.ts
|
|
1931
|
+
import {
|
|
1932
|
+
CreateAccountCommand,
|
|
1933
|
+
DescribeCreateAccountStatusCommand,
|
|
1934
|
+
ListAccountsCommand as ListAccountsCommand2,
|
|
1935
|
+
MoveAccountCommand
|
|
1936
|
+
} from "@aws-sdk/client-organizations";
|
|
1937
|
+
async function createAccountAndMoveToOu(props) {
|
|
1938
|
+
props.logger.log(
|
|
1939
|
+
`Creating account "${props.accountName}" (${props.accountEmail})...`
|
|
1940
|
+
);
|
|
1941
|
+
const createResponse = await props.organizationsClient.send(
|
|
1942
|
+
new CreateAccountCommand({
|
|
1943
|
+
AccountName: props.accountName,
|
|
1944
|
+
Email: props.accountEmail
|
|
1945
|
+
})
|
|
1946
|
+
);
|
|
1947
|
+
const createRequestId = createResponse.CreateAccountStatus?.Id;
|
|
1948
|
+
if (createRequestId == null) {
|
|
1949
|
+
throw new Error("CreateAccount did not return a request id.");
|
|
1950
|
+
}
|
|
1951
|
+
const accountId = await pollCreateAccountStatusUntilTerminal({
|
|
1952
|
+
organizationsClient: props.organizationsClient,
|
|
1953
|
+
logger: props.logger,
|
|
1954
|
+
createRequestId,
|
|
1955
|
+
timeoutInMs: props.timeoutInMs,
|
|
1956
|
+
pollIntervalInMs: props.pollIntervalInMs
|
|
1957
|
+
});
|
|
1958
|
+
props.logger.log(
|
|
1959
|
+
`Moving account "${props.accountName}" (${accountId}) to destination OU (${props.destinationParentId})...`
|
|
1960
|
+
);
|
|
1961
|
+
await props.organizationsClient.send(
|
|
1962
|
+
new MoveAccountCommand({
|
|
1963
|
+
AccountId: accountId,
|
|
1964
|
+
SourceParentId: props.sourceParentId,
|
|
1965
|
+
DestinationParentId: props.destinationParentId
|
|
1966
|
+
})
|
|
1967
|
+
);
|
|
1968
|
+
const account = await resolveCreatedAccountRecord({
|
|
1969
|
+
organizationsClient: props.organizationsClient,
|
|
1970
|
+
accountId,
|
|
1971
|
+
destinationParentId: props.destinationParentId
|
|
1972
|
+
});
|
|
1973
|
+
return {
|
|
1974
|
+
accountId,
|
|
1975
|
+
account
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
async function pollCreateAccountStatusUntilTerminal(props) {
|
|
1979
|
+
const startedAt = Date.now();
|
|
1980
|
+
let lastStatus;
|
|
1981
|
+
while (Date.now() - startedAt < props.timeoutInMs) {
|
|
1982
|
+
const response = await props.organizationsClient.send(
|
|
1983
|
+
new DescribeCreateAccountStatusCommand({
|
|
1984
|
+
CreateAccountRequestId: props.createRequestId
|
|
1985
|
+
})
|
|
1986
|
+
);
|
|
1987
|
+
const status = response.CreateAccountStatus;
|
|
1988
|
+
const state = status?.State ?? "UNKNOWN";
|
|
1989
|
+
if (state !== lastStatus) {
|
|
1990
|
+
props.logger.log(`CreateAccount status: ${state}`);
|
|
1991
|
+
lastStatus = state;
|
|
1992
|
+
}
|
|
1993
|
+
if (state === "SUCCEEDED") {
|
|
1994
|
+
if (status?.AccountId == null) {
|
|
1995
|
+
throw new Error(
|
|
1996
|
+
"CreateAccount succeeded but response did not include AccountId."
|
|
1997
|
+
);
|
|
1998
|
+
}
|
|
1999
|
+
return status.AccountId;
|
|
2000
|
+
}
|
|
2001
|
+
if (state === "FAILED") {
|
|
2002
|
+
throw new Error(
|
|
2003
|
+
`CreateAccount failed: ${status?.FailureReason ?? "unknown reason"}.`
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
await delay(props.pollIntervalInMs);
|
|
2007
|
+
}
|
|
2008
|
+
throw new Error(
|
|
2009
|
+
`CreateAccount timed out after ${props.timeoutInMs}ms. Check AWS Organizations and retry.`
|
|
2010
|
+
);
|
|
2011
|
+
}
|
|
2012
|
+
async function resolveCreatedAccountRecord(props) {
|
|
2013
|
+
const account = await findAccountById({
|
|
2014
|
+
organizationsClient: props.organizationsClient,
|
|
2015
|
+
accountId: props.accountId
|
|
2016
|
+
});
|
|
2017
|
+
if (account == null) {
|
|
2018
|
+
throw new Error(
|
|
2019
|
+
`Created account "${props.accountId}" could not be resolved from AWS Organizations list.`
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
return {
|
|
2023
|
+
id: account.id,
|
|
2024
|
+
arn: account.arn,
|
|
2025
|
+
name: account.name,
|
|
2026
|
+
email: account.email,
|
|
2027
|
+
status: account.status,
|
|
2028
|
+
parentId: props.destinationParentId
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
async function findAccountById(props) {
|
|
2032
|
+
let nextToken;
|
|
2033
|
+
do {
|
|
2034
|
+
const response = await props.organizationsClient.send(
|
|
2035
|
+
new ListAccountsCommand2({ NextToken: nextToken })
|
|
2036
|
+
);
|
|
2037
|
+
const matched = (response.Accounts ?? []).find(
|
|
2038
|
+
(account) => isCompleteAccountWithStatus(account, props.accountId)
|
|
2039
|
+
);
|
|
2040
|
+
if (matched?.Id != null && matched.Arn != null && matched.Name != null && matched.Email != null && matched.Status != null) {
|
|
2041
|
+
return {
|
|
2042
|
+
id: matched.Id,
|
|
2043
|
+
arn: matched.Arn,
|
|
2044
|
+
name: matched.Name,
|
|
2045
|
+
email: matched.Email,
|
|
2046
|
+
status: matched.Status
|
|
2047
|
+
};
|
|
2048
|
+
}
|
|
2049
|
+
nextToken = response.NextToken;
|
|
2050
|
+
} while (nextToken != null);
|
|
2051
|
+
return void 0;
|
|
2052
|
+
}
|
|
2053
|
+
function isCompleteAccountWithStatus(account, expectedAccountId) {
|
|
2054
|
+
if (account.Id == null || account.Arn == null || account.Name == null || account.Email == null || account.Status == null) {
|
|
2055
|
+
return false;
|
|
2056
|
+
}
|
|
2057
|
+
if (expectedAccountId == null) {
|
|
2058
|
+
return true;
|
|
2059
|
+
}
|
|
2060
|
+
return account.Id === expectedAccountId;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// src/applyLogic.ts
|
|
2064
|
+
async function executeOperation(props) {
|
|
2065
|
+
const operation = props.operation;
|
|
2066
|
+
if (operation.kind === "moveAccount") {
|
|
2067
|
+
props.logger.log(
|
|
2068
|
+
`Moving "${operation.accountName}" (${operation.accountId}): ${operation.fromOuName} -> ${operation.toOuName}`
|
|
2069
|
+
);
|
|
2070
|
+
await props.organizationsClient.send(
|
|
2071
|
+
new MoveAccountCommand2({
|
|
2072
|
+
AccountId: operation.accountId,
|
|
2073
|
+
SourceParentId: operation.fromOuId,
|
|
2074
|
+
DestinationParentId: operation.toOuId
|
|
2075
|
+
})
|
|
2076
|
+
);
|
|
2077
|
+
props.logger.log(`Done: "${operation.accountName}"`);
|
|
2078
|
+
return moveAccountInWorkingState({
|
|
2079
|
+
workingState: props.state,
|
|
2080
|
+
accountId: operation.accountId,
|
|
2081
|
+
parentId: operation.toOuId
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
if (operation.kind === "createOu") {
|
|
2085
|
+
props.logger.log(
|
|
2086
|
+
`Creating OU "${operation.ouName}" under ${operation.parentOuName}...`
|
|
2087
|
+
);
|
|
2088
|
+
const response = await props.organizationsClient.send(
|
|
2089
|
+
new CreateOrganizationalUnitCommand({
|
|
2090
|
+
ParentId: operation.parentOuId,
|
|
2091
|
+
Name: operation.ouName
|
|
2092
|
+
})
|
|
2093
|
+
);
|
|
2094
|
+
const createdOu = response.OrganizationalUnit;
|
|
2095
|
+
if (createdOu?.Id == null || createdOu.Arn == null || createdOu.Name == null) {
|
|
2096
|
+
throw new Error(
|
|
2097
|
+
`CreateOrganizationalUnit for "${operation.ouName}" returned incomplete OU data.`
|
|
2098
|
+
);
|
|
2099
|
+
}
|
|
2100
|
+
props.logger.log(`Done: "${createdOu.Name}"`);
|
|
2101
|
+
return upsertOrganizationalUnitInWorkingState({
|
|
2102
|
+
workingState: props.state,
|
|
2103
|
+
organizationalUnit: {
|
|
2104
|
+
id: createdOu.Id,
|
|
2105
|
+
parentId: operation.parentOuId,
|
|
2106
|
+
arn: createdOu.Arn,
|
|
2107
|
+
name: createdOu.Name
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
if (operation.kind === "renameOu") {
|
|
2112
|
+
props.logger.log(
|
|
2113
|
+
`Renaming OU "${operation.fromOuName}" -> "${operation.toOuName}"...`
|
|
2114
|
+
);
|
|
2115
|
+
await props.organizationsClient.send(
|
|
2116
|
+
new UpdateOrganizationalUnitCommand({
|
|
2117
|
+
OrganizationalUnitId: operation.ouId,
|
|
2118
|
+
Name: operation.toOuName
|
|
2119
|
+
})
|
|
2120
|
+
);
|
|
2121
|
+
props.logger.log(`Done: "${operation.toOuName}"`);
|
|
2122
|
+
return renameOrganizationalUnitInWorkingState({
|
|
2123
|
+
workingState: props.state,
|
|
2124
|
+
organizationalUnitId: operation.ouId,
|
|
2125
|
+
name: operation.toOuName
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
if (operation.kind === "deleteOu") {
|
|
2129
|
+
props.logger.log(`Deleting OU "${operation.ouName}"...`);
|
|
2130
|
+
await assertOrganizationalUnitIsEmpty({
|
|
2131
|
+
organizationsClient: props.organizationsClient,
|
|
2132
|
+
organizationalUnitId: operation.ouId,
|
|
2133
|
+
organizationalUnitName: operation.ouName
|
|
2134
|
+
});
|
|
2135
|
+
await props.organizationsClient.send(
|
|
2136
|
+
new DeleteOrganizationalUnitCommand({
|
|
2137
|
+
OrganizationalUnitId: operation.ouId
|
|
2138
|
+
})
|
|
2139
|
+
);
|
|
2140
|
+
props.logger.log(`Done: "${operation.ouName}"`);
|
|
2141
|
+
return removeOrganizationalUnitFromWorkingState({
|
|
2142
|
+
workingState: props.state,
|
|
2143
|
+
organizationalUnitId: operation.ouId
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
if (operation.kind === "createAccount") {
|
|
2147
|
+
const result = await createAccountAndMoveToOu({
|
|
2148
|
+
organizationsClient: props.organizationsClient,
|
|
2149
|
+
logger: props.logger,
|
|
2150
|
+
accountName: operation.accountName,
|
|
2151
|
+
accountEmail: operation.accountEmail,
|
|
2152
|
+
sourceParentId: props.context.organization.rootId,
|
|
2153
|
+
destinationParentId: operation.targetOuId,
|
|
2154
|
+
timeoutInMs: props.runtime.createAccount.timeoutInMs,
|
|
2155
|
+
pollIntervalInMs: props.runtime.createAccount.pollIntervalInMs
|
|
2156
|
+
});
|
|
2157
|
+
return upsertAccountInWorkingState({
|
|
2158
|
+
workingState: props.state,
|
|
2159
|
+
account: {
|
|
2160
|
+
id: result.account.id,
|
|
2161
|
+
arn: result.account.arn,
|
|
2162
|
+
name: result.account.name,
|
|
2163
|
+
email: result.account.email,
|
|
2164
|
+
status: result.account.status,
|
|
2165
|
+
parentId: operation.targetOuId,
|
|
2166
|
+
tags: []
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
}
|
|
2170
|
+
if (operation.kind === "updateAccountTags") {
|
|
2171
|
+
const account = props.state.organization.accountsById[operation.accountId];
|
|
2172
|
+
if (account == null) {
|
|
2173
|
+
throw new Error(
|
|
2174
|
+
`Could not resolve account "${operation.accountName}" (${operation.accountId}) in working state.`
|
|
2175
|
+
);
|
|
2176
|
+
}
|
|
2177
|
+
const currentTags = new Map(
|
|
2178
|
+
(account.tags ?? []).map((tag) => [tag.key, tag.value])
|
|
2179
|
+
);
|
|
2180
|
+
const desiredTags = new Map(Object.entries(operation.tags));
|
|
2181
|
+
const tagsToApply = [...desiredTags.entries()].filter(([key, value]) => currentTags.get(key) !== value).map(([Key, Value]) => ({ Key, Value }));
|
|
2182
|
+
const tagKeysToRemove = [...currentTags.keys()].filter(
|
|
2183
|
+
(key) => desiredTags.has(key) === false
|
|
2184
|
+
);
|
|
2185
|
+
props.logger.log(
|
|
2186
|
+
`Updating account tags "${operation.accountName}" (${operation.accountId})...`
|
|
2187
|
+
);
|
|
2188
|
+
if (tagsToApply.length > 0) {
|
|
2189
|
+
await props.organizationsClient.send(
|
|
2190
|
+
new TagResourceCommand({
|
|
2191
|
+
ResourceId: operation.accountId,
|
|
2192
|
+
Tags: tagsToApply
|
|
2193
|
+
})
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
if (tagKeysToRemove.length > 0) {
|
|
2197
|
+
await props.organizationsClient.send(
|
|
2198
|
+
new UntagResourceCommand({
|
|
2199
|
+
ResourceId: operation.accountId,
|
|
2200
|
+
TagKeys: tagKeysToRemove
|
|
2201
|
+
})
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
props.logger.log(`Done: tags updated for "${operation.accountName}"`);
|
|
2205
|
+
return upsertAccountInWorkingState({
|
|
2206
|
+
workingState: props.state,
|
|
2207
|
+
account: {
|
|
2208
|
+
...account,
|
|
2209
|
+
tags: Object.entries(operation.tags).map(([key, value]) => ({ key, value }))
|
|
2210
|
+
}
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2213
|
+
if (operation.kind === "updateAccountName") {
|
|
2214
|
+
props.logger.log(
|
|
2215
|
+
`Renaming account (${operation.accountId}): "${operation.fromAccountName}" -> "${operation.toAccountName}"...`
|
|
2216
|
+
);
|
|
2217
|
+
await props.accountClient.send(
|
|
2218
|
+
new PutAccountNameCommand({
|
|
2219
|
+
AccountId: operation.accountId,
|
|
2220
|
+
AccountName: operation.toAccountName
|
|
2221
|
+
})
|
|
2222
|
+
);
|
|
2223
|
+
props.logger.log(
|
|
2224
|
+
`Done: account "${operation.toAccountName}" (${operation.accountId})`
|
|
2225
|
+
);
|
|
2226
|
+
const account = props.state.organization.accountsById[operation.accountId];
|
|
2227
|
+
if (account == null) {
|
|
2228
|
+
throw new Error(
|
|
2229
|
+
`Could not resolve account (${operation.accountId}) in working state after rename.`
|
|
2230
|
+
);
|
|
2231
|
+
}
|
|
2232
|
+
return upsertAccountInWorkingState({
|
|
2233
|
+
workingState: props.state,
|
|
2234
|
+
account: {
|
|
2235
|
+
...account,
|
|
2236
|
+
name: operation.toAccountName
|
|
2237
|
+
}
|
|
2238
|
+
});
|
|
2239
|
+
}
|
|
2240
|
+
if (operation.kind === "removeAccount") {
|
|
2241
|
+
props.logger.log(
|
|
2242
|
+
`Moving removed account "${operation.accountName}" (${operation.accountId}) to ${operation.toOuName}...`
|
|
2243
|
+
);
|
|
2244
|
+
await props.organizationsClient.send(
|
|
2245
|
+
new MoveAccountCommand2({
|
|
2246
|
+
AccountId: operation.accountId,
|
|
2247
|
+
SourceParentId: operation.fromOuId,
|
|
2248
|
+
DestinationParentId: operation.toOuId
|
|
2249
|
+
})
|
|
2250
|
+
);
|
|
2251
|
+
props.logger.log(`Done: "${operation.accountName}" -> ${operation.toOuName}`);
|
|
2252
|
+
return moveAccountInWorkingState({
|
|
2253
|
+
workingState: props.state,
|
|
2254
|
+
accountId: operation.accountId,
|
|
2255
|
+
parentId: operation.toOuId
|
|
2256
|
+
});
|
|
2257
|
+
}
|
|
2258
|
+
if (operation.kind === "createIdcUser") {
|
|
2259
|
+
props.logger.log(`Creating IdC user "${operation.userName}"...`);
|
|
2260
|
+
const response = await props.identityStoreClient.send(
|
|
2261
|
+
new CreateUserCommand({
|
|
2262
|
+
IdentityStoreId: props.state.identityCenter.identityStoreId,
|
|
2263
|
+
UserName: operation.userName,
|
|
2264
|
+
DisplayName: operation.displayName,
|
|
2265
|
+
Name: buildIdentityStoreUserName({
|
|
2266
|
+
userName: operation.userName,
|
|
2267
|
+
displayName: operation.displayName
|
|
2268
|
+
}),
|
|
2269
|
+
Emails: operation.email.length > 0 ? [
|
|
2270
|
+
{
|
|
2271
|
+
Value: operation.email,
|
|
2272
|
+
Type: "Work",
|
|
2273
|
+
Primary: true
|
|
2274
|
+
}
|
|
2275
|
+
] : void 0
|
|
2276
|
+
})
|
|
2277
|
+
);
|
|
2278
|
+
if (response.UserId == null) {
|
|
2279
|
+
throw new Error(
|
|
2280
|
+
`CreateUser for "${operation.userName}" returned no user id.`
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2283
|
+
props.logger.log(`Done: "${operation.userName}"`);
|
|
2284
|
+
return upsertIdcUserInWorkingState({
|
|
2285
|
+
workingState: props.state,
|
|
2286
|
+
user: {
|
|
2287
|
+
userId: response.UserId,
|
|
2288
|
+
userName: operation.userName,
|
|
2289
|
+
displayName: operation.displayName,
|
|
2290
|
+
email: operation.email
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
if (operation.kind === "updateIdcUser") {
|
|
2295
|
+
const user = resolveUserByName({
|
|
2296
|
+
state: props.state,
|
|
2297
|
+
userName: operation.userName
|
|
2298
|
+
});
|
|
2299
|
+
const operations = [];
|
|
2300
|
+
if (user.displayName !== operation.displayName) {
|
|
2301
|
+
operations.push({
|
|
2302
|
+
AttributePath: "displayName",
|
|
2303
|
+
AttributeValue: operation.displayName
|
|
2304
|
+
});
|
|
2305
|
+
operations.push({
|
|
2306
|
+
AttributePath: "name",
|
|
2307
|
+
AttributeValue: buildIdentityStoreUserName({
|
|
2308
|
+
userName: operation.userName,
|
|
2309
|
+
displayName: operation.displayName
|
|
2310
|
+
})
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
if (user.email !== operation.email && operation.email.length > 0) {
|
|
2314
|
+
operations.push({
|
|
2315
|
+
AttributePath: "emails",
|
|
2316
|
+
AttributeValue: [
|
|
2317
|
+
{
|
|
2318
|
+
Value: operation.email,
|
|
2319
|
+
Type: "Work",
|
|
2320
|
+
Primary: true
|
|
2321
|
+
}
|
|
2322
|
+
]
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
if (operations.length === 0) {
|
|
2326
|
+
return props.state;
|
|
2327
|
+
}
|
|
2328
|
+
props.logger.log(`Updating IdC user "${operation.userName}"...`);
|
|
2329
|
+
await props.identityStoreClient.send(
|
|
2330
|
+
new UpdateUserCommand({
|
|
2331
|
+
IdentityStoreId: props.state.identityCenter.identityStoreId,
|
|
2332
|
+
UserId: user.userId,
|
|
2333
|
+
Operations: operations
|
|
2334
|
+
})
|
|
2335
|
+
);
|
|
2336
|
+
props.logger.log(`Done: "${operation.userName}"`);
|
|
2337
|
+
return upsertIdcUserInWorkingState({
|
|
2338
|
+
workingState: props.state,
|
|
2339
|
+
user: {
|
|
2340
|
+
...user,
|
|
2341
|
+
displayName: operation.displayName,
|
|
2342
|
+
email: operation.email.length > 0 ? operation.email : user.email
|
|
2343
|
+
}
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
if (operation.kind === "deleteIdcUser") {
|
|
2347
|
+
const user = resolveUserByName({
|
|
2348
|
+
state: props.state,
|
|
2349
|
+
userName: operation.userName
|
|
2350
|
+
});
|
|
2351
|
+
props.logger.log(`Deleting IdC user "${operation.userName}"...`);
|
|
2352
|
+
await props.identityStoreClient.send(
|
|
2353
|
+
new DeleteUserCommand({
|
|
2354
|
+
IdentityStoreId: props.state.identityCenter.identityStoreId,
|
|
2355
|
+
UserId: user.userId
|
|
2356
|
+
})
|
|
2357
|
+
);
|
|
2358
|
+
props.logger.log(`Done: "${operation.userName}"`);
|
|
2359
|
+
return removeIdcUserFromWorkingState({
|
|
2360
|
+
workingState: props.state,
|
|
2361
|
+
userName: operation.userName
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
if (operation.kind === "createIdcGroup") {
|
|
2365
|
+
props.logger.log(`Creating IdC group "${operation.groupDisplayName}"...`);
|
|
2366
|
+
const response = await props.identityStoreClient.send(
|
|
2367
|
+
new CreateGroupCommand({
|
|
2368
|
+
IdentityStoreId: props.state.identityCenter.identityStoreId,
|
|
2369
|
+
DisplayName: operation.groupDisplayName,
|
|
2370
|
+
Description: operation.description.trim().length > 0 ? operation.description : void 0
|
|
2371
|
+
})
|
|
2372
|
+
);
|
|
2373
|
+
if (response.GroupId == null) {
|
|
2374
|
+
throw new Error(
|
|
2375
|
+
`CreateGroup for "${operation.groupDisplayName}" returned no group id.`
|
|
2376
|
+
);
|
|
2377
|
+
}
|
|
2378
|
+
props.logger.log(`Done: "${operation.groupDisplayName}"`);
|
|
2379
|
+
return upsertIdcGroupInWorkingState({
|
|
2380
|
+
workingState: props.state,
|
|
2381
|
+
group: {
|
|
2382
|
+
groupId: response.GroupId,
|
|
2383
|
+
displayName: operation.groupDisplayName,
|
|
2384
|
+
description: operation.description
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
if (operation.kind === "updateIdcGroupDescription") {
|
|
2389
|
+
const group = resolveGroupByDisplayName({
|
|
2390
|
+
state: props.state,
|
|
2391
|
+
groupDisplayName: operation.groupDisplayName
|
|
2392
|
+
});
|
|
2393
|
+
props.logger.log(
|
|
2394
|
+
`Updating IdC group description for "${operation.groupDisplayName}"...`
|
|
2395
|
+
);
|
|
2396
|
+
await props.identityStoreClient.send(
|
|
2397
|
+
new UpdateGroupCommand({
|
|
2398
|
+
IdentityStoreId: props.state.identityCenter.identityStoreId,
|
|
2399
|
+
GroupId: group.groupId,
|
|
2400
|
+
Operations: [
|
|
2401
|
+
{
|
|
2402
|
+
AttributePath: "description",
|
|
2403
|
+
AttributeValue: operation.description
|
|
2404
|
+
}
|
|
2405
|
+
]
|
|
2406
|
+
})
|
|
2407
|
+
);
|
|
2408
|
+
props.logger.log(`Done: group "${operation.groupDisplayName}"`);
|
|
2409
|
+
return upsertIdcGroupInWorkingState({
|
|
2410
|
+
workingState: props.state,
|
|
2411
|
+
group: {
|
|
2412
|
+
...group,
|
|
2413
|
+
description: operation.description
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
if (operation.kind === "deleteIdcGroup") {
|
|
2418
|
+
const group = resolveGroupByDisplayName({
|
|
2419
|
+
state: props.state,
|
|
2420
|
+
groupDisplayName: operation.groupDisplayName
|
|
2421
|
+
});
|
|
2422
|
+
props.logger.log(`Deleting IdC group "${operation.groupDisplayName}"...`);
|
|
2423
|
+
await props.identityStoreClient.send(
|
|
2424
|
+
new DeleteGroupCommand({
|
|
2425
|
+
IdentityStoreId: props.state.identityCenter.identityStoreId,
|
|
2426
|
+
GroupId: group.groupId
|
|
2427
|
+
})
|
|
2428
|
+
);
|
|
2429
|
+
props.logger.log(`Done: "${operation.groupDisplayName}"`);
|
|
2430
|
+
return removeIdcGroupFromWorkingState({
|
|
2431
|
+
workingState: props.state,
|
|
2432
|
+
groupDisplayName: operation.groupDisplayName
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
if (operation.kind === "addIdcGroupMembership") {
|
|
2436
|
+
const resolvedMembership = resolveGroupMembershipDependencies({
|
|
2437
|
+
state: props.state,
|
|
2438
|
+
groupDisplayName: operation.groupDisplayName,
|
|
2439
|
+
userName: operation.userName
|
|
2440
|
+
});
|
|
2441
|
+
props.logger.log(
|
|
2442
|
+
`Adding user "${operation.userName}" to IdC group "${operation.groupDisplayName}"...`
|
|
2443
|
+
);
|
|
2444
|
+
const response = await props.identityStoreClient.send(
|
|
2445
|
+
new CreateGroupMembershipCommand({
|
|
2446
|
+
IdentityStoreId: props.state.identityCenter.identityStoreId,
|
|
2447
|
+
GroupId: resolvedMembership.groupId,
|
|
2448
|
+
MemberId: {
|
|
2449
|
+
UserId: resolvedMembership.userId
|
|
2450
|
+
}
|
|
2451
|
+
})
|
|
2452
|
+
);
|
|
2453
|
+
if (response.MembershipId == null) {
|
|
2454
|
+
throw new Error(
|
|
2455
|
+
`CreateGroupMembership for group "${operation.groupDisplayName}" and user "${operation.userName}" returned no membership id.`
|
|
2456
|
+
);
|
|
2457
|
+
}
|
|
2458
|
+
props.logger.log(
|
|
2459
|
+
`Done: user "${operation.userName}" -> group "${operation.groupDisplayName}"`
|
|
2460
|
+
);
|
|
2461
|
+
return addGroupMembershipToWorkingState({
|
|
2462
|
+
workingState: props.state,
|
|
2463
|
+
groupMembership: {
|
|
2464
|
+
membershipId: response.MembershipId,
|
|
2465
|
+
groupId: resolvedMembership.groupId,
|
|
2466
|
+
userId: resolvedMembership.userId
|
|
2467
|
+
}
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
if (operation.kind === "createIdcPermissionSet") {
|
|
2471
|
+
props.logger.log(
|
|
2472
|
+
`Creating IdC permission set "${operation.permissionSetName}"...`
|
|
2473
|
+
);
|
|
2474
|
+
const response = await props.ssoAdminClient.send(
|
|
2475
|
+
new CreatePermissionSetCommand({
|
|
2476
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2477
|
+
Name: operation.permissionSetName,
|
|
2478
|
+
Description: operation.description.length > 0 ? operation.description : void 0
|
|
2479
|
+
})
|
|
2480
|
+
);
|
|
2481
|
+
const permissionSetArn = response.PermissionSet?.PermissionSetArn;
|
|
2482
|
+
if (permissionSetArn == null) {
|
|
2483
|
+
throw new Error(
|
|
2484
|
+
`CreatePermissionSet for "${operation.permissionSetName}" returned no permission set arn.`
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
props.logger.log(`Done: "${operation.permissionSetName}"`);
|
|
2488
|
+
return upsertIdcPermissionSetInWorkingState({
|
|
2489
|
+
workingState: props.state,
|
|
2490
|
+
permissionSet: {
|
|
2491
|
+
permissionSetArn,
|
|
2492
|
+
name: operation.permissionSetName,
|
|
2493
|
+
description: operation.description,
|
|
2494
|
+
inlinePolicy: null,
|
|
2495
|
+
awsManagedPolicies: [],
|
|
2496
|
+
customerManagedPolicies: []
|
|
2497
|
+
}
|
|
2498
|
+
});
|
|
2499
|
+
}
|
|
2500
|
+
if (operation.kind === "updateIdcPermissionSetDescription") {
|
|
2501
|
+
const permissionSet = resolvePermissionSetByName({
|
|
2502
|
+
state: props.state,
|
|
2503
|
+
permissionSetName: operation.permissionSetName
|
|
2504
|
+
});
|
|
2505
|
+
props.logger.log(
|
|
2506
|
+
`Updating IdC permission set description for "${operation.permissionSetName}"...`
|
|
2507
|
+
);
|
|
2508
|
+
await props.ssoAdminClient.send(
|
|
2509
|
+
new UpdatePermissionSetCommand({
|
|
2510
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2511
|
+
PermissionSetArn: permissionSet.permissionSetArn,
|
|
2512
|
+
Description: operation.description.trim().length > 0 ? operation.description : void 0
|
|
2513
|
+
})
|
|
2514
|
+
);
|
|
2515
|
+
props.logger.log(`Done: "${operation.permissionSetName}"`);
|
|
2516
|
+
return upsertIdcPermissionSetInWorkingState({
|
|
2517
|
+
workingState: props.state,
|
|
2518
|
+
permissionSet: {
|
|
2519
|
+
...permissionSet,
|
|
2520
|
+
description: operation.description
|
|
2521
|
+
}
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
if (operation.kind === "deleteIdcPermissionSet") {
|
|
2525
|
+
const permissionSet = resolvePermissionSetByName({
|
|
2526
|
+
state: props.state,
|
|
2527
|
+
permissionSetName: operation.permissionSetName
|
|
2528
|
+
});
|
|
2529
|
+
props.logger.log(
|
|
2530
|
+
`Deleting IdC permission set "${operation.permissionSetName}"...`
|
|
2531
|
+
);
|
|
2532
|
+
await props.ssoAdminClient.send(
|
|
2533
|
+
new DeletePermissionSetCommand({
|
|
2534
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2535
|
+
PermissionSetArn: permissionSet.permissionSetArn
|
|
2536
|
+
})
|
|
2537
|
+
);
|
|
2538
|
+
props.logger.log(`Done: "${operation.permissionSetName}"`);
|
|
2539
|
+
return removeIdcPermissionSetFromWorkingState({
|
|
2540
|
+
workingState: props.state,
|
|
2541
|
+
permissionSetName: operation.permissionSetName
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
if (operation.kind === "putIdcPermissionSetInlinePolicy") {
|
|
2545
|
+
const permissionSet = resolvePermissionSetByName({
|
|
2546
|
+
state: props.state,
|
|
2547
|
+
permissionSetName: operation.permissionSetName
|
|
2548
|
+
});
|
|
2549
|
+
props.logger.log(
|
|
2550
|
+
`Putting inline policy on IdC permission set "${operation.permissionSetName}"...`
|
|
2551
|
+
);
|
|
2552
|
+
await props.ssoAdminClient.send(
|
|
2553
|
+
new PutInlinePolicyToPermissionSetCommand({
|
|
2554
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2555
|
+
PermissionSetArn: permissionSet.permissionSetArn,
|
|
2556
|
+
InlinePolicy: operation.inlinePolicy
|
|
2557
|
+
})
|
|
2558
|
+
);
|
|
2559
|
+
props.logger.log(`Done: "${operation.permissionSetName}"`);
|
|
2560
|
+
return upsertPermissionSetPolicyState({
|
|
2561
|
+
state: props.state,
|
|
2562
|
+
permissionSetName: operation.permissionSetName,
|
|
2563
|
+
update: (currentPermissionSet) => ({
|
|
2564
|
+
...currentPermissionSet,
|
|
2565
|
+
inlinePolicy: operation.inlinePolicy
|
|
2566
|
+
})
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
if (operation.kind === "deleteIdcPermissionSetInlinePolicy") {
|
|
2570
|
+
const permissionSet = resolvePermissionSetByName({
|
|
2571
|
+
state: props.state,
|
|
2572
|
+
permissionSetName: operation.permissionSetName
|
|
2573
|
+
});
|
|
2574
|
+
props.logger.log(
|
|
2575
|
+
`Deleting inline policy from IdC permission set "${operation.permissionSetName}"...`
|
|
2576
|
+
);
|
|
2577
|
+
await props.ssoAdminClient.send(
|
|
2578
|
+
new DeleteInlinePolicyFromPermissionSetCommand({
|
|
2579
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2580
|
+
PermissionSetArn: permissionSet.permissionSetArn
|
|
2581
|
+
})
|
|
2582
|
+
);
|
|
2583
|
+
props.logger.log(`Done: "${operation.permissionSetName}"`);
|
|
2584
|
+
return upsertPermissionSetPolicyState({
|
|
2585
|
+
state: props.state,
|
|
2586
|
+
permissionSetName: operation.permissionSetName,
|
|
2587
|
+
update: (currentPermissionSet) => ({
|
|
2588
|
+
...currentPermissionSet,
|
|
2589
|
+
inlinePolicy: null
|
|
2590
|
+
})
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
if (operation.kind === "attachIdcManagedPolicyToPermissionSet") {
|
|
2594
|
+
const permissionSet = resolvePermissionSetByName({
|
|
2595
|
+
state: props.state,
|
|
2596
|
+
permissionSetName: operation.permissionSetName
|
|
2597
|
+
});
|
|
2598
|
+
props.logger.log(
|
|
2599
|
+
`Attaching managed policy "${operation.managedPolicyArn}" to IdC permission set "${operation.permissionSetName}"...`
|
|
2600
|
+
);
|
|
2601
|
+
await props.ssoAdminClient.send(
|
|
2602
|
+
new AttachManagedPolicyToPermissionSetCommand({
|
|
2603
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2604
|
+
PermissionSetArn: permissionSet.permissionSetArn,
|
|
2605
|
+
ManagedPolicyArn: operation.managedPolicyArn
|
|
2606
|
+
})
|
|
2607
|
+
);
|
|
2608
|
+
props.logger.log(`Done: "${operation.permissionSetName}"`);
|
|
2609
|
+
return upsertPermissionSetPolicyState({
|
|
2610
|
+
state: props.state,
|
|
2611
|
+
permissionSetName: operation.permissionSetName,
|
|
2612
|
+
update: (currentPermissionSet) => ({
|
|
2613
|
+
...currentPermissionSet,
|
|
2614
|
+
awsManagedPolicies: [
|
|
2615
|
+
...currentPermissionSet.awsManagedPolicies,
|
|
2616
|
+
operation.managedPolicyArn
|
|
2617
|
+
]
|
|
2618
|
+
})
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
if (operation.kind === "detachIdcManagedPolicyFromPermissionSet") {
|
|
2622
|
+
const permissionSet = resolvePermissionSetByName({
|
|
2623
|
+
state: props.state,
|
|
2624
|
+
permissionSetName: operation.permissionSetName
|
|
2625
|
+
});
|
|
2626
|
+
props.logger.log(
|
|
2627
|
+
`Detaching managed policy "${operation.managedPolicyArn}" from IdC permission set "${operation.permissionSetName}"...`
|
|
2628
|
+
);
|
|
2629
|
+
await props.ssoAdminClient.send(
|
|
2630
|
+
new DetachManagedPolicyFromPermissionSetCommand({
|
|
2631
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2632
|
+
PermissionSetArn: permissionSet.permissionSetArn,
|
|
2633
|
+
ManagedPolicyArn: operation.managedPolicyArn
|
|
2634
|
+
})
|
|
2635
|
+
);
|
|
2636
|
+
props.logger.log(`Done: "${operation.permissionSetName}"`);
|
|
2637
|
+
return upsertPermissionSetPolicyState({
|
|
2638
|
+
state: props.state,
|
|
2639
|
+
permissionSetName: operation.permissionSetName,
|
|
2640
|
+
update: (currentPermissionSet) => ({
|
|
2641
|
+
...currentPermissionSet,
|
|
2642
|
+
awsManagedPolicies: currentPermissionSet.awsManagedPolicies.filter(
|
|
2643
|
+
(managedPolicyArn) => managedPolicyArn !== operation.managedPolicyArn
|
|
2644
|
+
)
|
|
2645
|
+
})
|
|
2646
|
+
});
|
|
2647
|
+
}
|
|
2648
|
+
if (operation.kind === "attachIdcCustomerManagedPolicyReferenceToPermissionSet") {
|
|
2649
|
+
const permissionSet = resolvePermissionSetByName({
|
|
2650
|
+
state: props.state,
|
|
2651
|
+
permissionSetName: operation.permissionSetName
|
|
2652
|
+
});
|
|
2653
|
+
props.logger.log(
|
|
2654
|
+
`Attaching customer-managed policy "${operation.customerManagedPolicyPath}${operation.customerManagedPolicyName}" to IdC permission set "${operation.permissionSetName}"...`
|
|
2655
|
+
);
|
|
2656
|
+
await props.ssoAdminClient.send(
|
|
2657
|
+
new AttachCustomerManagedPolicyReferenceToPermissionSetCommand({
|
|
2658
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2659
|
+
PermissionSetArn: permissionSet.permissionSetArn,
|
|
2660
|
+
CustomerManagedPolicyReference: {
|
|
2661
|
+
Name: operation.customerManagedPolicyName,
|
|
2662
|
+
Path: operation.customerManagedPolicyPath
|
|
2663
|
+
}
|
|
2664
|
+
})
|
|
2665
|
+
);
|
|
2666
|
+
props.logger.log(`Done: "${operation.permissionSetName}"`);
|
|
2667
|
+
return upsertPermissionSetPolicyState({
|
|
2668
|
+
state: props.state,
|
|
2669
|
+
permissionSetName: operation.permissionSetName,
|
|
2670
|
+
update: (currentPermissionSet) => ({
|
|
2671
|
+
...currentPermissionSet,
|
|
2672
|
+
customerManagedPolicies: [
|
|
2673
|
+
...currentPermissionSet.customerManagedPolicies,
|
|
2674
|
+
{
|
|
2675
|
+
name: operation.customerManagedPolicyName,
|
|
2676
|
+
path: operation.customerManagedPolicyPath
|
|
2677
|
+
}
|
|
2678
|
+
]
|
|
2679
|
+
})
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
if (operation.kind === "detachIdcCustomerManagedPolicyReferenceFromPermissionSet") {
|
|
2683
|
+
const permissionSet = resolvePermissionSetByName({
|
|
2684
|
+
state: props.state,
|
|
2685
|
+
permissionSetName: operation.permissionSetName
|
|
2686
|
+
});
|
|
2687
|
+
props.logger.log(
|
|
2688
|
+
`Detaching customer-managed policy "${operation.customerManagedPolicyPath}${operation.customerManagedPolicyName}" from IdC permission set "${operation.permissionSetName}"...`
|
|
2689
|
+
);
|
|
2690
|
+
await props.ssoAdminClient.send(
|
|
2691
|
+
new DetachCustomerManagedPolicyReferenceFromPermissionSetCommand({
|
|
2692
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2693
|
+
PermissionSetArn: permissionSet.permissionSetArn,
|
|
2694
|
+
CustomerManagedPolicyReference: {
|
|
2695
|
+
Name: operation.customerManagedPolicyName,
|
|
2696
|
+
Path: operation.customerManagedPolicyPath
|
|
2697
|
+
}
|
|
2698
|
+
})
|
|
2699
|
+
);
|
|
2700
|
+
props.logger.log(`Done: "${operation.permissionSetName}"`);
|
|
2701
|
+
return upsertPermissionSetPolicyState({
|
|
2702
|
+
state: props.state,
|
|
2703
|
+
permissionSetName: operation.permissionSetName,
|
|
2704
|
+
update: (currentPermissionSet) => ({
|
|
2705
|
+
...currentPermissionSet,
|
|
2706
|
+
customerManagedPolicies: currentPermissionSet.customerManagedPolicies.filter(
|
|
2707
|
+
(customerManagedPolicy) => customerManagedPolicy.name !== operation.customerManagedPolicyName || customerManagedPolicy.path !== operation.customerManagedPolicyPath
|
|
2708
|
+
)
|
|
2709
|
+
})
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
if (operation.kind === "provisionIdcPermissionSet") {
|
|
2713
|
+
const permissionSet = resolvePermissionSetByName({
|
|
2714
|
+
state: props.state,
|
|
2715
|
+
permissionSetName: operation.permissionSetName
|
|
2716
|
+
});
|
|
2717
|
+
props.logger.log(
|
|
2718
|
+
`Provisioning IdC permission set "${operation.permissionSetName}" to all provisioned accounts...`
|
|
2719
|
+
);
|
|
2720
|
+
const response = await props.ssoAdminClient.send(
|
|
2721
|
+
new ProvisionPermissionSetCommand({
|
|
2722
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2723
|
+
PermissionSetArn: permissionSet.permissionSetArn,
|
|
2724
|
+
TargetType: operation.targetScope
|
|
2725
|
+
})
|
|
2726
|
+
);
|
|
2727
|
+
const requestId = response.PermissionSetProvisioningStatus?.RequestId ?? void 0;
|
|
2728
|
+
if (requestId == null) {
|
|
2729
|
+
throw new Error(
|
|
2730
|
+
`ProvisionPermissionSet for "${operation.permissionSetName}" returned no request id.`
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
2733
|
+
await waitForPermissionSetProvisioningSuccess({
|
|
2734
|
+
ssoAdminClient: props.ssoAdminClient,
|
|
2735
|
+
logger: props.logger,
|
|
2736
|
+
instanceArn: props.state.identityCenter.instanceArn,
|
|
2737
|
+
requestId,
|
|
2738
|
+
timeoutInMs: props.runtime.permissionSetProvisioning.timeoutInMs,
|
|
2739
|
+
pollIntervalInMs: props.runtime.permissionSetProvisioning.pollIntervalInMs,
|
|
2740
|
+
operationLabel: `"${operation.permissionSetName}"`
|
|
2741
|
+
});
|
|
2742
|
+
props.logger.log(`Done: "${operation.permissionSetName}"`);
|
|
2743
|
+
return props.state;
|
|
2744
|
+
}
|
|
2745
|
+
if (operation.kind === "removeIdcGroupMembership") {
|
|
2746
|
+
const resolvedMembership = resolveGroupMembershipDependencies({
|
|
2747
|
+
state: props.state,
|
|
2748
|
+
groupDisplayName: operation.groupDisplayName,
|
|
2749
|
+
userName: operation.userName
|
|
2750
|
+
});
|
|
2751
|
+
const membershipId = await resolveGroupMembershipId({
|
|
2752
|
+
state: props.state,
|
|
2753
|
+
identityStoreClient: props.identityStoreClient,
|
|
2754
|
+
groupId: resolvedMembership.groupId,
|
|
2755
|
+
userId: resolvedMembership.userId
|
|
2756
|
+
});
|
|
2757
|
+
props.logger.log(
|
|
2758
|
+
`Removing user "${operation.userName}" from IdC group "${operation.groupDisplayName}"...`
|
|
2759
|
+
);
|
|
2760
|
+
await props.identityStoreClient.send(
|
|
2761
|
+
new DeleteGroupMembershipCommand({
|
|
2762
|
+
IdentityStoreId: props.state.identityCenter.identityStoreId,
|
|
2763
|
+
MembershipId: membershipId
|
|
2764
|
+
})
|
|
2765
|
+
);
|
|
2766
|
+
props.logger.log(
|
|
2767
|
+
`Done: user "${operation.userName}" x group "${operation.groupDisplayName}"`
|
|
2768
|
+
);
|
|
2769
|
+
return removeGroupMembershipFromWorkingState({
|
|
2770
|
+
workingState: props.state,
|
|
2771
|
+
groupMembership: {
|
|
2772
|
+
groupId: resolvedMembership.groupId,
|
|
2773
|
+
userId: resolvedMembership.userId
|
|
2774
|
+
}
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
if (operation.kind === "grantIdcAccountAssignment") {
|
|
2778
|
+
const resolvedAssignment = resolveAssignmentDependencies({
|
|
2779
|
+
state: props.state,
|
|
2780
|
+
accountName: operation.accountName,
|
|
2781
|
+
permissionSetName: operation.permissionSetName,
|
|
2782
|
+
principalType: operation.principalType,
|
|
2783
|
+
principalName: operation.principalName
|
|
2784
|
+
});
|
|
2785
|
+
props.logger.log(
|
|
2786
|
+
`Granting IdC assignment "${operation.permissionSetName}" to ${formatPrincipalLabel(
|
|
2787
|
+
{
|
|
2788
|
+
principalType: operation.principalType,
|
|
2789
|
+
principalName: operation.principalName
|
|
2790
|
+
}
|
|
2791
|
+
)} on "${operation.accountName}"...`
|
|
2792
|
+
);
|
|
2793
|
+
const response = await props.ssoAdminClient.send(
|
|
2794
|
+
new CreateAccountAssignmentCommand({
|
|
2795
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2796
|
+
TargetId: resolvedAssignment.accountId,
|
|
2797
|
+
TargetType: "AWS_ACCOUNT",
|
|
2798
|
+
PermissionSetArn: resolvedAssignment.permissionSetArn,
|
|
2799
|
+
PrincipalType: resolvedAssignment.principalType,
|
|
2800
|
+
PrincipalId: resolvedAssignment.principalId
|
|
2801
|
+
})
|
|
2802
|
+
);
|
|
2803
|
+
const requestId = response.AccountAssignmentCreationStatus?.RequestId;
|
|
2804
|
+
if (requestId == null) {
|
|
2805
|
+
throw new Error(
|
|
2806
|
+
`CreateAccountAssignment for "${operation.permissionSetName}" on "${operation.accountName}" returned no request id.`
|
|
2807
|
+
);
|
|
2808
|
+
}
|
|
2809
|
+
await waitForAccountAssignmentCreationSuccess({
|
|
2810
|
+
ssoAdminClient: props.ssoAdminClient,
|
|
2811
|
+
logger: props.logger,
|
|
2812
|
+
instanceArn: props.state.identityCenter.instanceArn,
|
|
2813
|
+
requestId,
|
|
2814
|
+
timeoutInMs: props.runtime.accountAssignment.timeoutInMs,
|
|
2815
|
+
pollIntervalInMs: props.runtime.accountAssignment.pollIntervalInMs,
|
|
2816
|
+
operationLabel: `"${operation.permissionSetName}" on "${operation.accountName}"`
|
|
2817
|
+
});
|
|
2818
|
+
props.logger.log(
|
|
2819
|
+
`Done: "${operation.permissionSetName}" -> "${operation.accountName}"`
|
|
2820
|
+
);
|
|
2821
|
+
return addAccountAssignmentToWorkingState({
|
|
2822
|
+
workingState: props.state,
|
|
2823
|
+
accountAssignment: {
|
|
2824
|
+
accountId: resolvedAssignment.accountId,
|
|
2825
|
+
permissionSetArn: resolvedAssignment.permissionSetArn,
|
|
2826
|
+
principalId: resolvedAssignment.principalId,
|
|
2827
|
+
principalType: resolvedAssignment.principalType
|
|
2828
|
+
}
|
|
2829
|
+
});
|
|
2830
|
+
}
|
|
2831
|
+
if (operation.kind === "revokeIdcAccountAssignment") {
|
|
2832
|
+
const resolvedAssignment = resolveAssignmentDependencies({
|
|
2833
|
+
state: props.state,
|
|
2834
|
+
accountName: operation.accountName,
|
|
2835
|
+
permissionSetName: operation.permissionSetName,
|
|
2836
|
+
principalType: operation.principalType,
|
|
2837
|
+
principalName: operation.principalName
|
|
2838
|
+
});
|
|
2839
|
+
props.logger.log(
|
|
2840
|
+
`Revoking IdC assignment "${operation.permissionSetName}" from ${formatPrincipalLabel(
|
|
2841
|
+
{
|
|
2842
|
+
principalType: operation.principalType,
|
|
2843
|
+
principalName: operation.principalName
|
|
2844
|
+
}
|
|
2845
|
+
)} on "${operation.accountName}"...`
|
|
2846
|
+
);
|
|
2847
|
+
const response = await props.ssoAdminClient.send(
|
|
2848
|
+
new DeleteAccountAssignmentCommand({
|
|
2849
|
+
InstanceArn: props.state.identityCenter.instanceArn,
|
|
2850
|
+
TargetId: resolvedAssignment.accountId,
|
|
2851
|
+
TargetType: "AWS_ACCOUNT",
|
|
2852
|
+
PermissionSetArn: resolvedAssignment.permissionSetArn,
|
|
2853
|
+
PrincipalType: resolvedAssignment.principalType,
|
|
2854
|
+
PrincipalId: resolvedAssignment.principalId
|
|
2855
|
+
})
|
|
2856
|
+
);
|
|
2857
|
+
const requestId = response.AccountAssignmentDeletionStatus?.RequestId;
|
|
2858
|
+
if (requestId == null) {
|
|
2859
|
+
throw new Error(
|
|
2860
|
+
`DeleteAccountAssignment for "${operation.permissionSetName}" on "${operation.accountName}" returned no request id.`
|
|
2861
|
+
);
|
|
2862
|
+
}
|
|
2863
|
+
await waitForAccountAssignmentDeletionSuccess({
|
|
2864
|
+
ssoAdminClient: props.ssoAdminClient,
|
|
2865
|
+
logger: props.logger,
|
|
2866
|
+
instanceArn: props.state.identityCenter.instanceArn,
|
|
2867
|
+
requestId,
|
|
2868
|
+
timeoutInMs: props.runtime.accountAssignment.timeoutInMs,
|
|
2869
|
+
pollIntervalInMs: props.runtime.accountAssignment.pollIntervalInMs,
|
|
2870
|
+
operationLabel: `"${operation.permissionSetName}" on "${operation.accountName}"`
|
|
2871
|
+
});
|
|
2872
|
+
props.logger.log(
|
|
2873
|
+
`Done: "${operation.permissionSetName}" x "${operation.accountName}"`
|
|
2874
|
+
);
|
|
2875
|
+
return removeAccountAssignmentFromWorkingState({
|
|
2876
|
+
workingState: props.state,
|
|
2877
|
+
accountAssignment: {
|
|
2878
|
+
accountId: resolvedAssignment.accountId,
|
|
2879
|
+
permissionSetArn: resolvedAssignment.permissionSetArn,
|
|
2880
|
+
principalId: resolvedAssignment.principalId,
|
|
2881
|
+
principalType: resolvedAssignment.principalType
|
|
2882
|
+
}
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
assertUnreachable(operation, "Unsupported operation kind in apply.");
|
|
2886
|
+
}
|
|
2887
|
+
function resolveAssignmentDependencies(props) {
|
|
2888
|
+
const account = props.state.organization.accountsByName[props.accountName];
|
|
2889
|
+
if (account == null) {
|
|
2890
|
+
throw new Error(
|
|
2891
|
+
`Could not resolve account "${props.accountName}" in working state.`
|
|
2892
|
+
);
|
|
2893
|
+
}
|
|
2894
|
+
const permissionSet = props.state.identityCenter.permissionSetsByName[props.permissionSetName];
|
|
2895
|
+
if (permissionSet == null) {
|
|
2896
|
+
throw new Error(
|
|
2897
|
+
`Could not resolve permission set "${props.permissionSetName}" in working state.`
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
if (props.principalType === "GROUP") {
|
|
2901
|
+
const group = props.state.identityCenter.groupsByDisplayName[props.principalName];
|
|
2902
|
+
if (group == null) {
|
|
2903
|
+
throw new Error(
|
|
2904
|
+
`Could not resolve group "${props.principalName}" in working state.`
|
|
2905
|
+
);
|
|
2906
|
+
}
|
|
2907
|
+
return {
|
|
2908
|
+
accountId: account.id,
|
|
2909
|
+
permissionSetArn: permissionSet.permissionSetArn,
|
|
2910
|
+
principalId: group.groupId,
|
|
2911
|
+
principalType: props.principalType
|
|
2912
|
+
};
|
|
2913
|
+
}
|
|
2914
|
+
const user = props.state.identityCenter.usersByUserName[props.principalName];
|
|
2915
|
+
if (user == null) {
|
|
2916
|
+
throw new Error(
|
|
2917
|
+
`Could not resolve user "${props.principalName}" in working state.`
|
|
2918
|
+
);
|
|
2919
|
+
}
|
|
2920
|
+
return {
|
|
2921
|
+
accountId: account.id,
|
|
2922
|
+
permissionSetArn: permissionSet.permissionSetArn,
|
|
2923
|
+
principalId: user.userId,
|
|
2924
|
+
principalType: props.principalType
|
|
2925
|
+
};
|
|
2926
|
+
}
|
|
2927
|
+
function resolveUserByName(props) {
|
|
2928
|
+
const user = props.state.identityCenter.usersByUserName[props.userName];
|
|
2929
|
+
if (user == null) {
|
|
2930
|
+
throw new Error(
|
|
2931
|
+
`Could not resolve user "${props.userName}" in working state.`
|
|
2932
|
+
);
|
|
2933
|
+
}
|
|
2934
|
+
return user;
|
|
2935
|
+
}
|
|
2936
|
+
function resolveGroupByDisplayName(props) {
|
|
2937
|
+
const group = props.state.identityCenter.groupsByDisplayName[props.groupDisplayName];
|
|
2938
|
+
if (group == null) {
|
|
2939
|
+
throw new Error(
|
|
2940
|
+
`Could not resolve group "${props.groupDisplayName}" in working state.`
|
|
2941
|
+
);
|
|
2942
|
+
}
|
|
2943
|
+
return group;
|
|
2944
|
+
}
|
|
2945
|
+
function resolvePermissionSetByName(props) {
|
|
2946
|
+
const permissionSet = props.state.identityCenter.permissionSetsByName[props.permissionSetName];
|
|
2947
|
+
if (permissionSet == null) {
|
|
2948
|
+
throw new Error(
|
|
2949
|
+
`Could not resolve permission set "${props.permissionSetName}" in working state.`
|
|
2950
|
+
);
|
|
2951
|
+
}
|
|
2952
|
+
return permissionSet;
|
|
2953
|
+
}
|
|
2954
|
+
function upsertPermissionSetPolicyState(props) {
|
|
2955
|
+
const permissionSet = resolvePermissionSetByName({
|
|
2956
|
+
state: props.state,
|
|
2957
|
+
permissionSetName: props.permissionSetName
|
|
2958
|
+
});
|
|
2959
|
+
const nextPermissionSet = props.update(permissionSet);
|
|
2960
|
+
return upsertIdcPermissionSetInWorkingState({
|
|
2961
|
+
workingState: props.state,
|
|
2962
|
+
permissionSet: {
|
|
2963
|
+
...nextPermissionSet,
|
|
2964
|
+
awsManagedPolicies: [...new Set(nextPermissionSet.awsManagedPolicies)].sort(
|
|
2965
|
+
(left, right) => left.localeCompare(right)
|
|
2966
|
+
),
|
|
2967
|
+
customerManagedPolicies: [
|
|
2968
|
+
...nextPermissionSet.customerManagedPolicies
|
|
2969
|
+
].sort((left, right) => {
|
|
2970
|
+
const pathComparison = left.path.localeCompare(right.path);
|
|
2971
|
+
if (pathComparison !== 0) {
|
|
2972
|
+
return pathComparison;
|
|
2973
|
+
}
|
|
2974
|
+
return left.name.localeCompare(right.name);
|
|
2975
|
+
})
|
|
2976
|
+
}
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
function buildIdentityStoreUserName(props) {
|
|
2980
|
+
const normalizedDisplayName = props.displayName.trim();
|
|
2981
|
+
const fallbackName = normalizedDisplayName.length > 0 ? normalizedDisplayName : props.userName;
|
|
2982
|
+
const [givenName, ...familyNameParts] = fallbackName.split(/\s+/).filter((part) => part.length > 0);
|
|
2983
|
+
const familyName = familyNameParts.join(" ");
|
|
2984
|
+
return {
|
|
2985
|
+
Formatted: fallbackName,
|
|
2986
|
+
GivenName: givenName ?? fallbackName,
|
|
2987
|
+
FamilyName: familyName.length > 0 ? familyName : fallbackName
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
async function assertOrganizationalUnitIsEmpty(props) {
|
|
2991
|
+
const childOrganizationalUnit = await listFirstChildOrganizationalUnit({
|
|
2992
|
+
organizationsClient: props.organizationsClient,
|
|
2993
|
+
parentId: props.organizationalUnitId
|
|
2994
|
+
});
|
|
2995
|
+
if (childOrganizationalUnit != null) {
|
|
2996
|
+
throw new Error(
|
|
2997
|
+
`Refusing to delete OU "${props.organizationalUnitName}": live AWS preflight failed [child-ou-present]: ${formatLivePreflightResource({
|
|
2998
|
+
resourceType: "child OU",
|
|
2999
|
+
name: childOrganizationalUnit.Name,
|
|
3000
|
+
id: childOrganizationalUnit.Id
|
|
3001
|
+
})} is still attached.`
|
|
3002
|
+
);
|
|
3003
|
+
}
|
|
3004
|
+
const account = await listFirstAccountForParent({
|
|
3005
|
+
organizationsClient: props.organizationsClient,
|
|
3006
|
+
parentId: props.organizationalUnitId
|
|
3007
|
+
});
|
|
3008
|
+
if (account != null) {
|
|
3009
|
+
throw new Error(
|
|
3010
|
+
`Refusing to delete OU "${props.organizationalUnitName}": live AWS preflight failed [account-present]: ${formatLivePreflightResource({
|
|
3011
|
+
resourceType: "account",
|
|
3012
|
+
name: account.Name,
|
|
3013
|
+
id: account.Id
|
|
3014
|
+
})} is still attached.`
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
function formatLivePreflightResource(props) {
|
|
3019
|
+
const quotedName = props.name != null ? `"${props.name}"` : void 0;
|
|
3020
|
+
if (quotedName != null && props.id != null) {
|
|
3021
|
+
return `${props.resourceType} ${quotedName} (${props.id})`;
|
|
3022
|
+
}
|
|
3023
|
+
if (quotedName != null) {
|
|
3024
|
+
return `${props.resourceType} ${quotedName}`;
|
|
3025
|
+
}
|
|
3026
|
+
if (props.id != null) {
|
|
3027
|
+
return `${props.resourceType} (${props.id})`;
|
|
3028
|
+
}
|
|
3029
|
+
return `${props.resourceType} "unknown"`;
|
|
3030
|
+
}
|
|
3031
|
+
async function listFirstChildOrganizationalUnit(props) {
|
|
3032
|
+
let nextToken;
|
|
3033
|
+
do {
|
|
3034
|
+
const response = await props.organizationsClient.send(
|
|
3035
|
+
new ListOrganizationalUnitsForParentCommand2({
|
|
3036
|
+
ParentId: props.parentId,
|
|
3037
|
+
NextToken: nextToken
|
|
3038
|
+
})
|
|
3039
|
+
);
|
|
3040
|
+
const organizationalUnit = response.OrganizationalUnits?.[0];
|
|
3041
|
+
if (organizationalUnit != null) {
|
|
3042
|
+
return organizationalUnit;
|
|
3043
|
+
}
|
|
3044
|
+
nextToken = response.NextToken;
|
|
3045
|
+
} while (nextToken != null);
|
|
3046
|
+
return void 0;
|
|
3047
|
+
}
|
|
3048
|
+
async function listFirstAccountForParent(props) {
|
|
3049
|
+
let nextToken;
|
|
3050
|
+
do {
|
|
3051
|
+
const response = await props.organizationsClient.send(
|
|
3052
|
+
new ListAccountsForParentCommand({
|
|
3053
|
+
ParentId: props.parentId,
|
|
3054
|
+
NextToken: nextToken
|
|
3055
|
+
})
|
|
3056
|
+
);
|
|
3057
|
+
const account = response.Accounts?.[0];
|
|
3058
|
+
if (account != null) {
|
|
3059
|
+
return account;
|
|
3060
|
+
}
|
|
3061
|
+
nextToken = response.NextToken;
|
|
3062
|
+
} while (nextToken != null);
|
|
3063
|
+
return void 0;
|
|
3064
|
+
}
|
|
3065
|
+
async function waitForAccountAssignmentCreationSuccess(props) {
|
|
3066
|
+
const startedAt = Date.now();
|
|
3067
|
+
let lastStatus;
|
|
3068
|
+
while (Date.now() - startedAt < props.timeoutInMs) {
|
|
3069
|
+
const response = await props.ssoAdminClient.send(
|
|
3070
|
+
new DescribeAccountAssignmentCreationStatusCommand({
|
|
3071
|
+
InstanceArn: props.instanceArn,
|
|
3072
|
+
AccountAssignmentCreationRequestId: props.requestId
|
|
3073
|
+
})
|
|
3074
|
+
);
|
|
3075
|
+
const status = response.AccountAssignmentCreationStatus;
|
|
3076
|
+
const state = status?.Status ?? "UNKNOWN";
|
|
3077
|
+
if (state !== lastStatus) {
|
|
3078
|
+
props.logger.log(`CreateAccountAssignment status: ${state}`);
|
|
3079
|
+
lastStatus = state;
|
|
3080
|
+
}
|
|
3081
|
+
if (state === "SUCCEEDED") {
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
3084
|
+
if (state === "FAILED") {
|
|
3085
|
+
throw new Error(
|
|
3086
|
+
`CreateAccountAssignment failed for ${props.operationLabel}: ${status?.FailureReason ?? "unknown reason"}.`
|
|
3087
|
+
);
|
|
3088
|
+
}
|
|
3089
|
+
await delay(props.pollIntervalInMs);
|
|
3090
|
+
}
|
|
3091
|
+
throw new Error(
|
|
3092
|
+
`CreateAccountAssignment timed out after ${props.timeoutInMs}ms for ${props.operationLabel}.`
|
|
3093
|
+
);
|
|
3094
|
+
}
|
|
3095
|
+
async function waitForAccountAssignmentDeletionSuccess(props) {
|
|
3096
|
+
const startedAt = Date.now();
|
|
3097
|
+
let lastStatus;
|
|
3098
|
+
while (Date.now() - startedAt < props.timeoutInMs) {
|
|
3099
|
+
const response = await props.ssoAdminClient.send(
|
|
3100
|
+
new DescribeAccountAssignmentDeletionStatusCommand({
|
|
3101
|
+
InstanceArn: props.instanceArn,
|
|
3102
|
+
AccountAssignmentDeletionRequestId: props.requestId
|
|
3103
|
+
})
|
|
3104
|
+
);
|
|
3105
|
+
const status = response.AccountAssignmentDeletionStatus;
|
|
3106
|
+
const state = status?.Status ?? "UNKNOWN";
|
|
3107
|
+
if (state !== lastStatus) {
|
|
3108
|
+
props.logger.log(`DeleteAccountAssignment status: ${state}`);
|
|
3109
|
+
lastStatus = state;
|
|
3110
|
+
}
|
|
3111
|
+
if (state === "SUCCEEDED") {
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3114
|
+
if (state === "FAILED") {
|
|
3115
|
+
throw new Error(
|
|
3116
|
+
`DeleteAccountAssignment failed for ${props.operationLabel}: ${status?.FailureReason ?? "unknown reason"}.`
|
|
3117
|
+
);
|
|
3118
|
+
}
|
|
3119
|
+
await delay(props.pollIntervalInMs);
|
|
3120
|
+
}
|
|
3121
|
+
throw new Error(
|
|
3122
|
+
`DeleteAccountAssignment timed out after ${props.timeoutInMs}ms for ${props.operationLabel}.`
|
|
3123
|
+
);
|
|
3124
|
+
}
|
|
3125
|
+
async function waitForPermissionSetProvisioningSuccess(props) {
|
|
3126
|
+
const startedAt = Date.now();
|
|
3127
|
+
let lastStatus;
|
|
3128
|
+
while (Date.now() - startedAt < props.timeoutInMs) {
|
|
3129
|
+
const response = await props.ssoAdminClient.send(
|
|
3130
|
+
new DescribePermissionSetProvisioningStatusCommand({
|
|
3131
|
+
InstanceArn: props.instanceArn,
|
|
3132
|
+
ProvisionPermissionSetRequestId: props.requestId
|
|
3133
|
+
})
|
|
3134
|
+
);
|
|
3135
|
+
const status = response.PermissionSetProvisioningStatus;
|
|
3136
|
+
const state = status?.Status ?? "UNKNOWN";
|
|
3137
|
+
if (state !== lastStatus) {
|
|
3138
|
+
props.logger.log(`ProvisionPermissionSet status: ${state}`);
|
|
3139
|
+
lastStatus = state;
|
|
3140
|
+
}
|
|
3141
|
+
if (state === "SUCCEEDED") {
|
|
3142
|
+
return;
|
|
3143
|
+
}
|
|
3144
|
+
if (state === "FAILED") {
|
|
3145
|
+
throw new Error(
|
|
3146
|
+
`ProvisionPermissionSet failed for ${props.operationLabel}: ${status?.FailureReason ?? "unknown reason"}.`
|
|
3147
|
+
);
|
|
3148
|
+
}
|
|
3149
|
+
await delay(props.pollIntervalInMs);
|
|
3150
|
+
}
|
|
3151
|
+
throw new Error(
|
|
3152
|
+
`ProvisionPermissionSet timed out after ${props.timeoutInMs}ms for ${props.operationLabel}.`
|
|
3153
|
+
);
|
|
3154
|
+
}
|
|
3155
|
+
function formatPrincipalLabel(props) {
|
|
3156
|
+
if (props.principalType === "GROUP") {
|
|
3157
|
+
return `group "${props.principalName}"`;
|
|
3158
|
+
}
|
|
3159
|
+
return `user "${props.principalName}"`;
|
|
3160
|
+
}
|
|
3161
|
+
function resolveGroupMembershipDependencies(props) {
|
|
3162
|
+
const group = props.state.identityCenter.groupsByDisplayName[props.groupDisplayName];
|
|
3163
|
+
if (group == null) {
|
|
3164
|
+
throw new Error(
|
|
3165
|
+
`Could not resolve group "${props.groupDisplayName}" in working state.`
|
|
3166
|
+
);
|
|
3167
|
+
}
|
|
3168
|
+
const user = props.state.identityCenter.usersByUserName[props.userName];
|
|
3169
|
+
if (user == null) {
|
|
3170
|
+
throw new Error(
|
|
3171
|
+
`Could not resolve user "${props.userName}" in working state.`
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
return {
|
|
3175
|
+
groupId: group.groupId,
|
|
3176
|
+
userId: user.userId
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
async function resolveGroupMembershipId(props) {
|
|
3180
|
+
const existingMembership = props.state.identityCenter.groupMembershipsByKey[createGroupMembershipKey({
|
|
3181
|
+
groupId: props.groupId,
|
|
3182
|
+
userId: props.userId
|
|
3183
|
+
})];
|
|
3184
|
+
if (existingMembership?.membershipId != null) {
|
|
3185
|
+
return existingMembership.membershipId;
|
|
3186
|
+
}
|
|
3187
|
+
const response = await props.identityStoreClient.send(
|
|
3188
|
+
new GetGroupMembershipIdCommand({
|
|
3189
|
+
IdentityStoreId: props.state.identityCenter.identityStoreId,
|
|
3190
|
+
GroupId: props.groupId,
|
|
3191
|
+
MemberId: {
|
|
3192
|
+
UserId: props.userId
|
|
3193
|
+
}
|
|
3194
|
+
})
|
|
3195
|
+
);
|
|
3196
|
+
if (response.MembershipId == null) {
|
|
3197
|
+
throw new Error(
|
|
3198
|
+
`GetGroupMembershipId returned no membership id for group "${props.groupId}" and user "${props.userId}".`
|
|
3199
|
+
);
|
|
3200
|
+
}
|
|
3201
|
+
return response.MembershipId;
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// src/lambda/handler.ts
|
|
3205
|
+
var scanRequestSchema = strictObject({
|
|
3206
|
+
action: literal("scan")
|
|
3207
|
+
});
|
|
3208
|
+
var getStateUrlRequestSchema = strictObject({
|
|
3209
|
+
action: literal("getStateUrl")
|
|
3210
|
+
});
|
|
3211
|
+
var applyRequestSchema = strictObject({
|
|
3212
|
+
action: literal("apply"),
|
|
3213
|
+
operations: pipe(array(operationSchema), minLength(1)),
|
|
3214
|
+
allowDestructive: boolean()
|
|
3215
|
+
});
|
|
3216
|
+
var lambdaRequestSchema = variant("action", [
|
|
3217
|
+
scanRequestSchema,
|
|
3218
|
+
getStateUrlRequestSchema,
|
|
3219
|
+
applyRequestSchema
|
|
3220
|
+
]);
|
|
3221
|
+
var scanResponseSchema = strictObject({
|
|
3222
|
+
action: literal("scan"),
|
|
3223
|
+
success: literal(true),
|
|
3224
|
+
summary: strictObject({
|
|
3225
|
+
organizationalUnits: number(),
|
|
3226
|
+
accounts: number(),
|
|
3227
|
+
users: number(),
|
|
3228
|
+
groups: number(),
|
|
3229
|
+
permissionSets: number(),
|
|
3230
|
+
accountAssignments: number()
|
|
3231
|
+
}),
|
|
3232
|
+
state: stateSchema
|
|
3233
|
+
});
|
|
3234
|
+
var getStateUrlResponseSchema = strictObject({
|
|
3235
|
+
action: literal("getStateUrl"),
|
|
3236
|
+
success: literal(true),
|
|
3237
|
+
url: string(),
|
|
3238
|
+
expiresInSeconds: number()
|
|
3239
|
+
});
|
|
3240
|
+
var applySuccessResponseSchema = strictObject({
|
|
3241
|
+
action: literal("apply"),
|
|
3242
|
+
success: literal(true),
|
|
3243
|
+
operationsCompleted: number(),
|
|
3244
|
+
state: stateSchema
|
|
3245
|
+
});
|
|
3246
|
+
var errorResponseSchema = strictObject({
|
|
3247
|
+
success: literal(false),
|
|
3248
|
+
error: strictObject({
|
|
3249
|
+
kind: picklist([
|
|
3250
|
+
"validation",
|
|
3251
|
+
"concurrencyConflict",
|
|
3252
|
+
"operationFailed",
|
|
3253
|
+
"internal"
|
|
3254
|
+
]),
|
|
3255
|
+
message: string(),
|
|
3256
|
+
details: optional(
|
|
3257
|
+
strictObject({
|
|
3258
|
+
failedOperation: optional(number()),
|
|
3259
|
+
operationsCompleted: optional(number()),
|
|
3260
|
+
partialState: optional(stateSchema),
|
|
3261
|
+
validationIssues: optional(array(string()))
|
|
3262
|
+
})
|
|
3263
|
+
)
|
|
3264
|
+
})
|
|
3265
|
+
});
|
|
3266
|
+
var lambdaResponseSchema = union([
|
|
3267
|
+
scanResponseSchema,
|
|
3268
|
+
getStateUrlResponseSchema,
|
|
3269
|
+
applySuccessResponseSchema,
|
|
3270
|
+
errorResponseSchema
|
|
3271
|
+
]);
|
|
3272
|
+
var STATE_KEY = "state.json";
|
|
3273
|
+
var PRESIGNED_URL_EXPIRY_SECONDS = 3600;
|
|
3274
|
+
var RUNTIME_DEFAULTS = {
|
|
3275
|
+
createAccount: {
|
|
3276
|
+
timeoutInMs: 3e5,
|
|
3277
|
+
pollIntervalInMs: 5e3
|
|
3278
|
+
},
|
|
3279
|
+
accountAssignment: {
|
|
3280
|
+
timeoutInMs: 6e4,
|
|
3281
|
+
pollIntervalInMs: 2e3
|
|
3282
|
+
},
|
|
3283
|
+
permissionSetProvisioning: {
|
|
3284
|
+
timeoutInMs: 6e4,
|
|
3285
|
+
pollIntervalInMs: 2e3
|
|
3286
|
+
}
|
|
3287
|
+
};
|
|
3288
|
+
var lambdaLogger = {
|
|
3289
|
+
log: (...args) => console.log(...args),
|
|
3290
|
+
info: (...args) => console.info(...args),
|
|
3291
|
+
warn: (...args) => console.warn(...args),
|
|
3292
|
+
error: (...args) => console.error(...args),
|
|
3293
|
+
debug: (...args) => console.debug(...args),
|
|
3294
|
+
trace: (...args) => console.trace(...args)
|
|
3295
|
+
};
|
|
3296
|
+
var s3Client = new S3Client({});
|
|
3297
|
+
var organizationsClient = new OrganizationsClient3({});
|
|
3298
|
+
var ssoAdminClient = new SSOAdminClient3({});
|
|
3299
|
+
var identityStoreClient = new IdentitystoreClient3({});
|
|
3300
|
+
var accountClient = new AccountClient2({});
|
|
3301
|
+
async function handler(event) {
|
|
3302
|
+
try {
|
|
3303
|
+
const parseResult = safeParse(lambdaRequestSchema, event);
|
|
3304
|
+
if (!parseResult.success) {
|
|
3305
|
+
const issues = parseResult.issues.map(
|
|
3306
|
+
(issue) => `${issue.path?.map((p) => p.key).join(".") ?? "root"}: ${issue.message}`
|
|
3307
|
+
);
|
|
3308
|
+
const response = buildErrorResponse(
|
|
3309
|
+
"validation",
|
|
3310
|
+
"Invalid request payload.",
|
|
3311
|
+
{ validationIssues: issues }
|
|
3312
|
+
);
|
|
3313
|
+
return validateResponse(response);
|
|
3314
|
+
}
|
|
3315
|
+
const request = parseResult.output;
|
|
3316
|
+
const bucket = process.env.STATE_BUCKET_NAME;
|
|
3317
|
+
if (bucket == null || bucket.length === 0) {
|
|
3318
|
+
const response = buildErrorResponse(
|
|
3319
|
+
"internal",
|
|
3320
|
+
"STATE_BUCKET_NAME environment variable is not configured."
|
|
3321
|
+
);
|
|
3322
|
+
return validateResponse(response);
|
|
3323
|
+
}
|
|
3324
|
+
if (request.action === "scan") {
|
|
3325
|
+
const response = await handleScan({ s3Client, bucket, organizationsClient, ssoAdminClient, identityStoreClient });
|
|
3326
|
+
return validateResponse(response);
|
|
3327
|
+
}
|
|
3328
|
+
if (request.action === "getStateUrl") {
|
|
3329
|
+
const response = await handleGetStateUrl({ s3Client, bucket });
|
|
3330
|
+
return validateResponse(response);
|
|
3331
|
+
}
|
|
3332
|
+
if (request.action === "apply") {
|
|
3333
|
+
const response = await handleApply({
|
|
3334
|
+
s3Client,
|
|
3335
|
+
bucket,
|
|
3336
|
+
operations: request.operations,
|
|
3337
|
+
allowDestructive: request.allowDestructive,
|
|
3338
|
+
organizationsClient,
|
|
3339
|
+
ssoAdminClient,
|
|
3340
|
+
identityStoreClient,
|
|
3341
|
+
accountClient
|
|
3342
|
+
});
|
|
3343
|
+
return validateResponse(response);
|
|
3344
|
+
}
|
|
3345
|
+
assertUnreachable(request, "Unsupported action in handler.");
|
|
3346
|
+
} catch (error) {
|
|
3347
|
+
const message = error instanceof Error ? error.message : "An unexpected error occurred.";
|
|
3348
|
+
const response = buildErrorResponse("internal", message);
|
|
3349
|
+
return validateResponse(response);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
function buildErrorResponse(kind, message, details) {
|
|
3353
|
+
return {
|
|
3354
|
+
success: false,
|
|
3355
|
+
error: {
|
|
3356
|
+
kind,
|
|
3357
|
+
message,
|
|
3358
|
+
...details != null ? { details } : {}
|
|
3359
|
+
}
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
function validateResponse(response) {
|
|
3363
|
+
const result = safeParse(lambdaResponseSchema, response);
|
|
3364
|
+
if (!result.success) {
|
|
3365
|
+
return {
|
|
3366
|
+
success: false,
|
|
3367
|
+
error: {
|
|
3368
|
+
kind: "internal",
|
|
3369
|
+
message: "Response validation failed before returning.",
|
|
3370
|
+
details: {
|
|
3371
|
+
validationIssues: result.issues.map(
|
|
3372
|
+
(issue) => `${issue.path?.map((p) => p.key).join(".") ?? "root"}: ${issue.message}`
|
|
3373
|
+
)
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3378
|
+
return result.output;
|
|
3379
|
+
}
|
|
3380
|
+
async function readStateFromS3(props) {
|
|
3381
|
+
const response = await props.s3Client.send(
|
|
3382
|
+
new GetObjectCommand({
|
|
3383
|
+
Bucket: props.bucket,
|
|
3384
|
+
Key: STATE_KEY
|
|
3385
|
+
})
|
|
3386
|
+
);
|
|
3387
|
+
const body = await response.Body?.transformToString();
|
|
3388
|
+
if (body == null) {
|
|
3389
|
+
throw new Error("State not found. Run remote scan first.");
|
|
3390
|
+
}
|
|
3391
|
+
const parsed = JSON.parse(body);
|
|
3392
|
+
const state = parse(stateSchema, parsed);
|
|
3393
|
+
const etag = response.ETag ?? "";
|
|
3394
|
+
return { state, etag };
|
|
3395
|
+
}
|
|
3396
|
+
async function writeStateToS3(props) {
|
|
3397
|
+
await props.s3Client.send(new PutObjectCommand({
|
|
3398
|
+
Bucket: props.bucket,
|
|
3399
|
+
Key: STATE_KEY,
|
|
3400
|
+
Body: JSON.stringify(props.state, null, 2),
|
|
3401
|
+
ContentType: "application/json",
|
|
3402
|
+
IfMatch: props.ifMatch
|
|
3403
|
+
}));
|
|
3404
|
+
}
|
|
3405
|
+
function isS3PreconditionFailed(error) {
|
|
3406
|
+
if (error instanceof S3ServiceException) {
|
|
3407
|
+
return error.name === "PreconditionFailed" || error.$metadata?.httpStatusCode === 412;
|
|
3408
|
+
}
|
|
3409
|
+
return false;
|
|
3410
|
+
}
|
|
3411
|
+
async function handleScan(props) {
|
|
3412
|
+
const identityCenterInstanceArn = process.env.IDENTITY_CENTER_INSTANCE_ARN || void 0;
|
|
3413
|
+
const [organization, identityCenter] = await Promise.all([
|
|
3414
|
+
scanOrganization({ organizationsClient: props.organizationsClient }),
|
|
3415
|
+
scanIdentityCenter({
|
|
3416
|
+
ssoAdminClient: props.ssoAdminClient,
|
|
3417
|
+
identityStoreClient: props.identityStoreClient,
|
|
3418
|
+
requestedInstanceArn: identityCenterInstanceArn
|
|
3419
|
+
})
|
|
3420
|
+
]);
|
|
3421
|
+
const state = {
|
|
3422
|
+
version: "1",
|
|
3423
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3424
|
+
organization,
|
|
3425
|
+
identityCenter
|
|
3426
|
+
};
|
|
3427
|
+
await writeStateToS3({
|
|
3428
|
+
s3Client: props.s3Client,
|
|
3429
|
+
bucket: props.bucket,
|
|
3430
|
+
state
|
|
3431
|
+
});
|
|
3432
|
+
return {
|
|
3433
|
+
action: "scan",
|
|
3434
|
+
success: true,
|
|
3435
|
+
summary: {
|
|
3436
|
+
organizationalUnits: state.organization.organizationalUnits.length,
|
|
3437
|
+
accounts: state.organization.accounts.length,
|
|
3438
|
+
users: state.identityCenter.users.length,
|
|
3439
|
+
groups: state.identityCenter.groups.length,
|
|
3440
|
+
permissionSets: state.identityCenter.permissionSets.length,
|
|
3441
|
+
accountAssignments: state.identityCenter.accountAssignments.length
|
|
3442
|
+
},
|
|
3443
|
+
state
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
async function handleGetStateUrl(props) {
|
|
3447
|
+
const command = new GetObjectCommand({
|
|
3448
|
+
Bucket: props.bucket,
|
|
3449
|
+
Key: STATE_KEY
|
|
3450
|
+
});
|
|
3451
|
+
const url = await getSignedUrl(props.s3Client, command, {
|
|
3452
|
+
expiresIn: PRESIGNED_URL_EXPIRY_SECONDS
|
|
3453
|
+
});
|
|
3454
|
+
return {
|
|
3455
|
+
action: "getStateUrl",
|
|
3456
|
+
success: true,
|
|
3457
|
+
url,
|
|
3458
|
+
expiresInSeconds: PRESIGNED_URL_EXPIRY_SECONDS
|
|
3459
|
+
};
|
|
3460
|
+
}
|
|
3461
|
+
async function handleApply(props) {
|
|
3462
|
+
const stateResult = await loadStateForApply({
|
|
3463
|
+
s3Client: props.s3Client,
|
|
3464
|
+
bucket: props.bucket
|
|
3465
|
+
});
|
|
3466
|
+
if (!stateResult.ok) {
|
|
3467
|
+
return stateResult.response;
|
|
3468
|
+
}
|
|
3469
|
+
const { state: currentState, etag } = stateResult;
|
|
3470
|
+
let workingState = createWorkingState({ state: currentState });
|
|
3471
|
+
let operationsCompleted = 0;
|
|
3472
|
+
for (let i = 0; i < props.operations.length; i++) {
|
|
3473
|
+
const operation = props.operations[i];
|
|
3474
|
+
try {
|
|
3475
|
+
workingState = await executeOperation({
|
|
3476
|
+
state: workingState,
|
|
3477
|
+
organizationsClient: props.organizationsClient,
|
|
3478
|
+
accountClient: props.accountClient,
|
|
3479
|
+
ssoAdminClient: props.ssoAdminClient,
|
|
3480
|
+
identityStoreClient: props.identityStoreClient,
|
|
3481
|
+
logger: lambdaLogger,
|
|
3482
|
+
context: {
|
|
3483
|
+
organization: {
|
|
3484
|
+
rootId: workingState.organization.rootId
|
|
3485
|
+
}
|
|
3486
|
+
},
|
|
3487
|
+
runtime: RUNTIME_DEFAULTS,
|
|
3488
|
+
operation
|
|
3489
|
+
});
|
|
3490
|
+
operationsCompleted++;
|
|
3491
|
+
} catch (error) {
|
|
3492
|
+
const partialState = materializeWorkingState({ workingState });
|
|
3493
|
+
try {
|
|
3494
|
+
await writeStateToS3({
|
|
3495
|
+
s3Client: props.s3Client,
|
|
3496
|
+
bucket: props.bucket,
|
|
3497
|
+
state: partialState,
|
|
3498
|
+
ifMatch: etag
|
|
3499
|
+
});
|
|
3500
|
+
} catch (writeError) {
|
|
3501
|
+
if (isS3PreconditionFailed(writeError)) {
|
|
3502
|
+
return buildErrorResponse(
|
|
3503
|
+
"concurrencyConflict",
|
|
3504
|
+
"Concurrent state modification detected while writing partial state."
|
|
3505
|
+
);
|
|
3506
|
+
}
|
|
3507
|
+
lambdaLogger.error(
|
|
3508
|
+
"Failed to write partial state after operation failure:",
|
|
3509
|
+
writeError
|
|
3510
|
+
);
|
|
3511
|
+
}
|
|
3512
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown operation error";
|
|
3513
|
+
return buildErrorResponse("operationFailed", errorMessage, {
|
|
3514
|
+
failedOperation: i,
|
|
3515
|
+
operationsCompleted,
|
|
3516
|
+
partialState
|
|
3517
|
+
});
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
const finalState = materializeWorkingState({ workingState });
|
|
3521
|
+
try {
|
|
3522
|
+
await writeStateToS3({
|
|
3523
|
+
s3Client: props.s3Client,
|
|
3524
|
+
bucket: props.bucket,
|
|
3525
|
+
state: finalState,
|
|
3526
|
+
ifMatch: etag
|
|
3527
|
+
});
|
|
3528
|
+
} catch (error) {
|
|
3529
|
+
if (isS3PreconditionFailed(error)) {
|
|
3530
|
+
return buildErrorResponse(
|
|
3531
|
+
"concurrencyConflict",
|
|
3532
|
+
"Concurrent state modification detected. Another apply may have completed while this one was running."
|
|
3533
|
+
);
|
|
3534
|
+
}
|
|
3535
|
+
throw error;
|
|
3536
|
+
}
|
|
3537
|
+
return {
|
|
3538
|
+
action: "apply",
|
|
3539
|
+
success: true,
|
|
3540
|
+
operationsCompleted,
|
|
3541
|
+
state: finalState
|
|
3542
|
+
};
|
|
3543
|
+
}
|
|
3544
|
+
async function loadStateForApply(props) {
|
|
3545
|
+
try {
|
|
3546
|
+
const result = await readStateFromS3({
|
|
3547
|
+
s3Client: props.s3Client,
|
|
3548
|
+
bucket: props.bucket
|
|
3549
|
+
});
|
|
3550
|
+
return { ok: true, state: result.state, etag: result.etag };
|
|
3551
|
+
} catch (error) {
|
|
3552
|
+
const message = error instanceof Error ? error.message : "Failed to read state from S3.";
|
|
3553
|
+
return { ok: false, response: buildErrorResponse("internal", message) };
|
|
3554
|
+
}
|
|
3555
|
+
}
|
|
3556
|
+
export {
|
|
3557
|
+
handler
|
|
3558
|
+
};
|