@fjall/deploy-core 0.89.5 → 0.89.6
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 +50 -21
- package/README.md +25 -0
- package/dist/.minified +1 -0
- package/dist/src/__test-utils__/awsMockHelpers.d.ts +20 -0
- package/dist/src/__test-utils__/awsMockHelpers.js +1 -0
- package/dist/src/__test-utils__/index.d.ts +1 -0
- package/dist/src/__test-utils__/index.js +1 -0
- package/dist/src/aws/AwsProvider.js +0 -1
- package/dist/src/aws/SimpleAwsProvider.js +1 -70
- package/dist/src/aws/index.d.ts +4 -2
- package/dist/src/aws/index.js +1 -3
- package/dist/src/aws/organisations/accounts.js +10 -10
- package/dist/src/aws/organisations/backup.js +4 -2
- package/dist/src/aws/organisations/costAllocation.js +4 -2
- package/dist/src/aws/organisations/delegatedAdmin.d.ts +9 -0
- package/dist/src/aws/organisations/delegatedAdmin.js +43 -0
- package/dist/src/aws/organisations/identityCentre.d.ts +1 -1
- package/dist/src/aws/organisations/identityCentre.js +6 -2
- package/dist/src/aws/organisations/index.d.ts +4 -3
- package/dist/src/aws/organisations/index.js +1 -12
- package/dist/src/aws/organisations/ipam.js +4 -2
- package/dist/src/aws/organisations/organisation.js +27 -18
- package/dist/src/aws/organisations/organisationalUnits.d.ts +26 -6
- package/dist/src/aws/organisations/organisationalUnits.js +149 -35
- package/dist/src/aws/organisations/policies.js +4 -3
- package/dist/src/aws/organisations/ram.js +6 -2
- package/dist/src/aws/organisations/serviceAccess.js +12 -6
- package/dist/src/aws/organisations/trustedAccess.js +6 -2
- package/dist/src/aws/organisations/types.d.ts +23 -1
- package/dist/src/aws/organisations/types.js +1 -16
- package/dist/src/aws/utils/__tests__/cloudformationTestHelpers.d.ts +6 -0
- package/dist/src/aws/utils/__tests__/cloudformationTestHelpers.js +1 -0
- package/dist/src/aws/utils/cloudformationEventHelpers.d.ts +48 -0
- package/dist/src/aws/utils/cloudformationEventHelpers.js +1 -0
- package/dist/src/aws/utils/cloudformationEventTypes.d.ts +45 -0
- package/dist/src/aws/utils/cloudformationEventTypes.js +1 -0
- package/dist/src/aws/utils/cloudformationEvents.d.ts +8 -54
- package/dist/src/aws/utils/cloudformationEvents.js +1 -596
- package/dist/src/aws/utils/index.d.ts +5 -0
- package/dist/src/aws/utils/index.js +1 -0
- package/dist/src/aws/utils/stackStatus.js +1 -90
- package/dist/src/events/index.d.ts +13 -0
- package/dist/src/events/index.js +1 -0
- package/dist/src/index.d.ts +34 -17
- package/dist/src/index.js +41 -21
- package/dist/src/orchestration/__tests__/cascadeTestHelpers.d.ts +12 -0
- package/dist/src/orchestration/__tests__/cascadeTestHelpers.js +78 -0
- package/dist/src/orchestration/activeDeploymentGuard.d.ts +10 -0
- package/dist/src/orchestration/activeDeploymentGuard.js +39 -0
- package/dist/src/orchestration/applicationDeploy.js +46 -229
- package/dist/src/orchestration/applicationDeployHelpers.d.ts +39 -0
- package/dist/src/orchestration/applicationDeployHelpers.js +223 -0
- package/dist/src/orchestration/applicationDestroy.d.ts +14 -0
- package/dist/src/orchestration/applicationDestroy.js +131 -0
- package/dist/src/orchestration/builders/dockerBuilder.d.ts +17 -0
- package/dist/src/orchestration/builders/dockerBuilder.js +98 -0
- package/dist/src/orchestration/builders/frameworkRegistry.d.ts +23 -0
- package/dist/src/orchestration/builders/frameworkRegistry.js +1 -0
- package/dist/src/orchestration/builders/index.d.ts +4 -0
- package/dist/src/orchestration/builders/index.js +1 -0
- package/dist/src/orchestration/builders/openNextBuilder.d.ts +21 -0
- package/dist/src/orchestration/builders/openNextBuilder.js +144 -0
- package/dist/src/orchestration/cascadeDestroyHelpers.d.ts +30 -0
- package/dist/src/orchestration/cascadeDestroyHelpers.js +1 -0
- package/dist/src/orchestration/cascadeHelpers.d.ts +46 -0
- package/dist/src/orchestration/cascadeHelpers.js +160 -0
- package/dist/src/orchestration/contextHelpers.d.ts +46 -2
- package/dist/src/orchestration/contextHelpers.js +93 -1
- package/dist/src/orchestration/destroy.d.ts +13 -0
- package/dist/src/orchestration/destroy.js +67 -0
- package/dist/src/orchestration/detectionPipeline.d.ts +2 -11
- package/dist/src/orchestration/detectionPipeline.js +29 -10
- package/dist/src/orchestration/dockerBuildHelper.d.ts +10 -0
- package/dist/src/orchestration/dockerBuildHelper.js +49 -0
- package/dist/src/orchestration/dockerInterface.d.ts +4 -2
- package/dist/src/orchestration/index.d.ts +8 -1
- package/dist/src/orchestration/index.js +1 -3
- package/dist/src/orchestration/manifestSecretParser.d.ts +11 -0
- package/dist/src/orchestration/manifestSecretParser.js +1 -0
- package/dist/src/orchestration/openNextBuild.d.ts +28 -0
- package/dist/src/orchestration/openNextBuild.js +243 -0
- package/dist/src/orchestration/organisationDeploy.js +110 -233
- package/dist/src/orchestration/organisationDestroy.d.ts +24 -0
- package/dist/src/orchestration/organisationDestroy.js +189 -0
- package/dist/src/orchestration/organisationSetup.d.ts +6 -4
- package/dist/src/orchestration/organisationSetup.js +28 -8
- package/dist/src/orchestration/resolveOperation.js +68 -6
- package/dist/src/orchestration/serviceFactory.d.ts +4 -0
- package/dist/src/orchestration/serviceFactory.js +1 -16
- package/dist/src/orchestration/spawnHelpers.d.ts +47 -0
- package/dist/src/orchestration/spawnHelpers.js +1 -0
- package/dist/src/orchestration/stackCleanup.d.ts +39 -0
- package/dist/src/orchestration/stackCleanup.js +1 -0
- package/dist/src/orchestration/welcomeImageHelper.d.ts +15 -0
- package/dist/src/orchestration/welcomeImageHelper.js +64 -0
- package/dist/src/services/application/ApplicationStackService.d.ts +21 -30
- package/dist/src/services/application/ApplicationStackService.js +16 -234
- package/dist/src/services/application/applicationStackHelpers.d.ts +46 -0
- package/dist/src/services/application/applicationStackHelpers.js +248 -0
- package/dist/src/services/application/index.d.ts +1 -0
- package/dist/src/services/application/index.js +1 -1
- package/dist/src/services/index.d.ts +6 -0
- package/dist/src/services/index.js +1 -0
- package/dist/src/services/infrastructure/CdkArgumentBuilder.js +1 -67
- package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +10 -2
- package/dist/src/services/infrastructure/CdkCommandRunner.js +18 -15
- package/dist/src/services/infrastructure/CdkErrorFormatter.js +16 -194
- package/dist/src/services/infrastructure/CdkEventMonitoring.js +1 -41
- package/dist/src/services/infrastructure/CdkOutputAnalyser.js +1 -1
- package/dist/src/services/infrastructure/CdkOutputParser.js +2 -33
- package/dist/src/services/infrastructure/CdkProcessManager.d.ts +5 -0
- package/dist/src/services/infrastructure/CdkProcessManager.js +81 -47
- package/dist/src/services/infrastructure/CdkService.d.ts +7 -53
- package/dist/src/services/infrastructure/CdkService.js +41 -83
- package/dist/src/services/infrastructure/CdkServiceTypes.d.ts +50 -0
- package/dist/src/services/infrastructure/CdkServiceTypes.js +0 -0
- package/dist/src/services/infrastructure/CloudFormationService.js +9 -10
- package/dist/src/services/infrastructure/ICdkProcessManager.d.ts +27 -0
- package/dist/src/services/infrastructure/ICdkProcessManager.js +1 -0
- package/dist/src/services/infrastructure/__tests__/cloudFormationTestHelpers.d.ts +9 -0
- package/dist/src/services/infrastructure/__tests__/cloudFormationTestHelpers.js +1 -0
- package/dist/src/services/infrastructure/cdkServiceHelpers.d.ts +9 -0
- package/dist/src/services/infrastructure/cdkServiceHelpers.js +1 -0
- package/dist/src/services/infrastructure/constructMapEnrichment.d.ts +7 -0
- package/dist/src/services/infrastructure/constructMapEnrichment.js +1 -0
- package/dist/src/services/infrastructure/index.d.ts +3 -1
- package/dist/src/services/infrastructure/index.js +1 -7
- package/dist/src/services/supporting/TemplateHashService.js +1 -1
- package/dist/src/services/supporting/helpers.js +1 -81
- package/dist/src/services/supporting/index.js +1 -3
- package/dist/src/steps/index.d.ts +1 -0
- package/dist/src/steps/index.js +1 -0
- package/dist/src/steps/stepRegistry.d.ts +71 -0
- package/dist/src/steps/stepRegistry.js +505 -0
- package/dist/src/types/FjallState.js +1 -118
- package/dist/src/types/ProgressEvent.js +1 -48
- package/dist/src/types/application/ApplicationServiceTypes.js +1 -30
- package/dist/src/types/application/index.js +1 -1
- package/dist/src/types/callbacks.d.ts +76 -4
- package/dist/src/types/callbacks.js +0 -1
- package/dist/src/types/constants.d.ts +2 -0
- package/dist/src/types/constants.js +1 -6
- package/dist/src/types/credentials.js +0 -1
- package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +5 -2
- package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -1
- package/dist/src/types/deployment/DeploymentTypes.js +0 -1
- package/dist/src/types/deployment/cloudformation.js +0 -1
- package/dist/src/types/deployment/index.d.ts +3 -1
- package/dist/src/types/deployment/index.js +1 -1
- package/dist/src/types/deployment/parallel.js +1 -10
- package/dist/src/types/deploymentEventSchema.d.ts +158 -0
- package/dist/src/types/deploymentEventSchema.js +1 -0
- package/dist/src/types/detection.d.ts +22 -0
- package/dist/src/types/detection.js +1 -0
- package/dist/src/types/entitlements.d.ts +31 -0
- package/dist/src/types/entitlements.js +0 -0
- package/dist/src/types/errors/CdkError.js +1 -20
- package/dist/src/types/errors/ServiceError.d.ts +2 -1
- package/dist/src/types/errors/ServiceError.js +1 -119
- package/dist/src/types/errors/index.d.ts +2 -0
- package/dist/src/types/errors/index.js +1 -0
- package/dist/src/types/events.d.ts +3 -9
- package/dist/src/types/events.js +0 -5
- package/dist/src/types/frameworkBuilder.d.ts +96 -0
- package/dist/src/types/frameworkBuilder.js +8 -0
- package/dist/src/types/index.d.ts +19 -4
- package/dist/src/types/index.js +1 -9
- package/dist/src/types/operations.d.ts +3 -2
- package/dist/src/types/operations.js +1 -285
- package/dist/src/types/orgConfig.d.ts +2 -10
- package/dist/src/types/orgConfig.js +0 -11
- package/dist/src/types/params.d.ts +60 -1
- package/dist/src/types/patternDetection.d.ts +14 -16
- package/dist/src/types/patternDetection.js +14 -18
- package/dist/src/types/patternTypes.d.ts +19 -0
- package/dist/src/types/patternTypes.js +1 -0
- package/dist/src/types/stepDefinitions.d.ts +163 -0
- package/dist/src/types/stepDefinitions.js +98 -0
- package/dist/src/types/validation.js +0 -1
- package/dist/src/util/dockerfileDetection.d.ts +5 -0
- package/dist/src/util/dockerfileDetection.js +1 -0
- package/dist/src/util/index.d.ts +4 -3
- package/dist/src/util/index.js +1 -3
- package/dist/src/util/sequencedCallbacks.d.ts +44 -0
- package/dist/src/util/sequencedCallbacks.js +1 -0
- package/package.json +49 -8
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +0 -32
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +0 -228
- package/dist/src/aws/utils/errors.d.ts +0 -26
- package/dist/src/aws/utils/errors.js +0 -59
- package/dist/src/util/fsHelpers.d.ts +0 -4
- package/dist/src/util/fsHelpers.js +0 -16
- package/dist/src/util/securityHelpers.d.ts +0 -31
- package/dist/src/util/securityHelpers.js +0 -124
- package/dist/src/util/singleton.d.ts +0 -2
- package/dist/src/util/singleton.js +0 -9
- package/dist/src/util/sleep.d.ts +0 -4
- package/dist/src/util/sleep.js +0 -4
package/LICENSE
CHANGED
|
@@ -1,21 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Fjall
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
to
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
Fjall Proprietary Software Licence
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fjall. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software, including all source, object, bundled, and minified forms
|
|
6
|
+
("the Software"), is the proprietary and confidential property of Fjall.
|
|
7
|
+
|
|
8
|
+
1. Permitted Use. Subject to the terms of this Licence, Fjall grants you
|
|
9
|
+
a non-exclusive, non-transferable, revocable licence to install the
|
|
10
|
+
Software via the npm registry and to execute it solely for the purpose
|
|
11
|
+
of deploying, operating, and managing your own applications and
|
|
12
|
+
infrastructure on cloud providers.
|
|
13
|
+
|
|
14
|
+
2. Restrictions. You may NOT, and may not permit any third party to:
|
|
15
|
+
(a) copy, redistribute, sublicense, sell, rent, lease, or otherwise
|
|
16
|
+
transfer the Software;
|
|
17
|
+
(b) modify, adapt, translate, or create derivative works of the Software;
|
|
18
|
+
(c) reverse engineer, decompile, disassemble, deminify, or otherwise
|
|
19
|
+
attempt to derive the source code, structure, or organisation of
|
|
20
|
+
the Software, except to the minimum extent expressly permitted by
|
|
21
|
+
applicable mandatory law;
|
|
22
|
+
(d) use the Software, or any portion of it, to develop, train, or
|
|
23
|
+
improve any product or service that competes with Fjall;
|
|
24
|
+
(e) remove, alter, or obscure any proprietary notices contained in
|
|
25
|
+
the Software;
|
|
26
|
+
(f) publish, share, or otherwise disclose the Software or its contents
|
|
27
|
+
to any third party.
|
|
28
|
+
|
|
29
|
+
3. Ownership. All right, title, and interest in and to the Software,
|
|
30
|
+
including all intellectual property rights, remain with Fjall. No
|
|
31
|
+
rights are granted except as expressly set out in this Licence.
|
|
32
|
+
|
|
33
|
+
4. Termination. This Licence terminates automatically if you breach any
|
|
34
|
+
of its terms. Upon termination you must cease all use of the Software
|
|
35
|
+
and destroy all copies in your possession.
|
|
36
|
+
|
|
37
|
+
5. Disclaimer of Warranty. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT
|
|
38
|
+
WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
|
|
39
|
+
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
|
|
40
|
+
AND NON-INFRINGEMENT.
|
|
41
|
+
|
|
42
|
+
6. Limitation of Liability. IN NO EVENT SHALL FJALL BE LIABLE FOR ANY
|
|
43
|
+
INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES
|
|
44
|
+
ARISING OUT OF OR RELATED TO THE SOFTWARE, EVEN IF ADVISED OF THE
|
|
45
|
+
POSSIBILITY OF SUCH DAMAGES.
|
|
46
|
+
|
|
47
|
+
7. Governing Law. This Licence is governed by the laws of England and
|
|
48
|
+
Wales, without regard to conflict of laws principles.
|
|
49
|
+
|
|
50
|
+
For commercial licensing enquiries, contact: contact@fjall.io
|
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @fjall/deploy-core
|
|
2
|
+
|
|
3
|
+
Shared deployment engine for Fjall. Used by both `@fjall/cli` and the Fjall webapp deployment worker to provide a single source of truth for build, synth, and deploy orchestration across CDK stacks.
|
|
4
|
+
|
|
5
|
+
Provides `AwsProvider`, `FrameworkBuilder`, `StepRegistry`, and the `DeployCallbacks` contract. Pure orchestration — environment-agnostic and free of UI concerns.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @fjall/deploy-core
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Subpath exports
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { AwsProvider } from "@fjall/deploy-core/aws";
|
|
17
|
+
import { FrameworkBuilder } from "@fjall/deploy-core/builders";
|
|
18
|
+
import { StepRegistry } from "@fjall/deploy-core/steps";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
See [docs.fjall.io](https://docs.fjall.io) for full usage.
|
|
22
|
+
|
|
23
|
+
## Licence
|
|
24
|
+
|
|
25
|
+
Proprietary — see [LICENSE](./LICENSE).
|
package/dist/.minified
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
114 files minified at 2026-04-20T23:47:40.999Z
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
/**
|
|
3
|
+
* Create an AWS error with the correct name property for SDK v3 error matching.
|
|
4
|
+
* AWS SDK v3 checks error.name for typed error handling (e.g. "AccountAlreadyRegisteredException").
|
|
5
|
+
* Object.defineProperty is required because Error.name is non-writable by default.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createAwsError(name: string, message: string): Error;
|
|
8
|
+
/**
|
|
9
|
+
* Create a mock AWS SDK v3 client with a spied `send` method.
|
|
10
|
+
* The returned object is typed as the specified client type for test convenience.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* ```ts
|
|
14
|
+
* const mockClient = createMockAwsClient<OrganizationsClient>();
|
|
15
|
+
* vi.mocked(mockClient.send).mockResolvedValue({ ... });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function createMockAwsClient<T>(): T & {
|
|
19
|
+
send: ReturnType<typeof vi.fn>;
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{vi as n}from"vitest";function c(e,t){const r=new Error(t);return Object.defineProperty(r,"name",{value:e,writable:!0}),r}function i(){return{send:n.fn()}}export{c as createAwsError,i as createMockAwsClient};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createAwsError, createMockAwsClient } from "./awsMockHelpers.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createAwsError as o,createMockAwsClient as t}from"./awsMockHelpers.js";export{o as createAwsError,t as createMockAwsClient};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,70 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
/**
|
|
3
|
-
* Minimal AWS provider for pre-assumed credentials.
|
|
4
|
-
*
|
|
5
|
-
* Used by the webapp worker, which assumes an OIDC role before
|
|
6
|
-
* calling deploy-core. No SSO, no credential discovery, no caching.
|
|
7
|
-
*/
|
|
8
|
-
export class SimpleAwsProvider {
|
|
9
|
-
credentials;
|
|
10
|
-
region;
|
|
11
|
-
accountId;
|
|
12
|
-
clientCache = new Map();
|
|
13
|
-
constructor(awsCredentials) {
|
|
14
|
-
this.credentials = {
|
|
15
|
-
accessKeyId: awsCredentials.accessKeyId,
|
|
16
|
-
secretAccessKey: awsCredentials.secretAccessKey,
|
|
17
|
-
sessionToken: awsCredentials.sessionToken
|
|
18
|
-
};
|
|
19
|
-
this.region = awsCredentials.region;
|
|
20
|
-
this.accountId = awsCredentials.accountId;
|
|
21
|
-
}
|
|
22
|
-
getClient(ClientClass) {
|
|
23
|
-
const key = ClientClass.name;
|
|
24
|
-
const cached = this.clientCache.get(key);
|
|
25
|
-
if (cached !== undefined)
|
|
26
|
-
return cached;
|
|
27
|
-
const client = new ClientClass({
|
|
28
|
-
region: this.region,
|
|
29
|
-
credentials: this.credentials
|
|
30
|
-
});
|
|
31
|
-
this.clientCache.set(key, client);
|
|
32
|
-
return client;
|
|
33
|
-
}
|
|
34
|
-
getRegion() {
|
|
35
|
-
return this.region;
|
|
36
|
-
}
|
|
37
|
-
getAccountId() {
|
|
38
|
-
return this.accountId;
|
|
39
|
-
}
|
|
40
|
-
getCredentials() {
|
|
41
|
-
return this.credentials;
|
|
42
|
-
}
|
|
43
|
-
exportToEnv() {
|
|
44
|
-
process.env.AWS_ACCESS_KEY_ID = this.credentials.accessKeyId;
|
|
45
|
-
process.env.AWS_SECRET_ACCESS_KEY = this.credentials.secretAccessKey;
|
|
46
|
-
process.env.AWS_REGION = this.region;
|
|
47
|
-
if (this.credentials.sessionToken) {
|
|
48
|
-
process.env.AWS_SESSION_TOKEN = this.credentials.sessionToken;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
async assumeRole(roleArn, sessionName) {
|
|
52
|
-
const stsClient = new STSClient({
|
|
53
|
-
region: this.region,
|
|
54
|
-
credentials: this.credentials
|
|
55
|
-
});
|
|
56
|
-
const response = await stsClient.send(new AssumeRoleCommand({
|
|
57
|
-
RoleArn: roleArn,
|
|
58
|
-
RoleSessionName: sessionName
|
|
59
|
-
}), { abortSignal: AbortSignal.timeout(30_000) });
|
|
60
|
-
const assumed = response.Credentials;
|
|
61
|
-
if (!assumed?.AccessKeyId || !assumed?.SecretAccessKey) {
|
|
62
|
-
throw new Error(`AssumeRole for ${roleArn} returned incomplete credentials`);
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
accessKeyId: assumed.AccessKeyId,
|
|
66
|
-
secretAccessKey: assumed.SecretAccessKey,
|
|
67
|
-
sessionToken: assumed.SessionToken
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
}
|
|
1
|
+
import{STSClient as i,AssumeRoleCommand as r}from"@aws-sdk/client-sts";import{NodeHttpHandler as d}from"@smithy/node-http-handler";const o=3e4;class u{credentials;region;accountId;clientCache=new Map;constructor(e){this.credentials={accessKeyId:e.accessKeyId,secretAccessKey:e.secretAccessKey,sessionToken:e.sessionToken},this.region=e.region,this.accountId=e.accountId}getClient(e){const t=e.name,n=this.clientCache.get(t);if(n!==void 0)return n;const c={region:this.region,credentials:this.credentials,requestHandler:new d({requestTimeout:o})},s=new e(c);return this.clientCache.set(t,s),s}getRegion(){return this.region}getAccountId(){return this.accountId}getCredentials(){return this.credentials}exportToEnv(){process.env.AWS_ACCESS_KEY_ID=this.credentials.accessKeyId,process.env.AWS_SECRET_ACCESS_KEY=this.credentials.secretAccessKey,process.env.AWS_REGION=this.region,this.credentials.sessionToken?process.env.AWS_SESSION_TOKEN=this.credentials.sessionToken:delete process.env.AWS_SESSION_TOKEN}async assumeRole(e,t){const s=(await this.getClient(i).send(new r({RoleArn:e,RoleSessionName:t}),{abortSignal:AbortSignal.timeout(o)})).Credentials;if(!s?.AccessKeyId||!s?.SecretAccessKey)throw new Error(`AssumeRole for ${e} returned incomplete credentials`);return{accessKeyId:s.AccessKeyId,secretAccessKey:s.SecretAccessKey,sessionToken:s.SessionToken}}}export{u as SimpleAwsProvider};
|
package/dist/src/aws/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type { AwsProvider, AwsProviderCredentials, AwsSdkClientConstructor } from "./AwsProvider.js";
|
|
2
2
|
export { SimpleAwsProvider } from "./SimpleAwsProvider.js";
|
|
3
|
-
export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, activateCostAllocationTags, checkIdentityCentreStatus } from "./organisations/index.js";
|
|
4
|
-
export type { OrgDetails, OUMap, AccountPlacementResult, AccountInfo, IdentityCentreStatus, BackupGlobalSettings, CostAllocationTag, CreateAccountResult } from "./organisations/index.js";
|
|
3
|
+
export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap, activateCostAllocationTags, checkIdentityCentreStatus, extractErrorName, isOULeaf, registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS } from "./organisations/index.js";
|
|
4
|
+
export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, IdentityCentreStatus, BackupGlobalSettings, CostAllocationTag, CreateAccountResult } from "./organisations/index.js";
|
|
5
|
+
export { CloudFormationEventMonitor } from "./utils/cloudformationEvents.js";
|
|
6
|
+
export type { AwsClientLike, EventLogWriter, EventFailureAnalyser, EventLogWriterFactory, EventMonitorDeps } from "./utils/cloudformationEvents.js";
|
package/dist/src/aws/index.js
CHANGED
|
@@ -1,3 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// Organisation setup primitives
|
|
3
|
-
export { ensureOrganisationExists, describeOrganisation, enablePolicyTypes, enableServiceAccess, enableRamSharing, activateTrustedAccess, enableIpamDelegatedAdmin, updateBackupGlobalSettings, listAccounts, findAccount, createAccount, ensureOrganisationalUnitsExist, placeAccountsInOUs, activateCostAllocationTags, checkIdentityCentreStatus } from "./organisations/index.js";
|
|
1
|
+
import{SimpleAwsProvider as a}from"./SimpleAwsProvider.js";import{ensureOrganisationExists as n,describeOrganisation as r,enablePolicyTypes as s,enableServiceAccess as c,enableRamSharing as o,activateTrustedAccess as l,enableIpamDelegatedAdmin as u,updateBackupGlobalSettings as A,listAccounts as d,findAccount as p,createAccount as g,ensureOrganisationalUnitsExist as m,placeAccountsInOUs as S,buildAccountToOUMap as b,activateCostAllocationTags as E,checkIdentityCentreStatus as I,extractErrorName as x,isOULeaf as C,registerSecurityDelegates as O,SECURITY_SERVICE_PRINCIPALS as f}from"./organisations/index.js";import{CloudFormationEventMonitor as T}from"./utils/cloudformationEvents.js";export{T as CloudFormationEventMonitor,f as SECURITY_SERVICE_PRINCIPALS,a as SimpleAwsProvider,E as activateCostAllocationTags,l as activateTrustedAccess,b as buildAccountToOUMap,I as checkIdentityCentreStatus,g as createAccount,r as describeOrganisation,u as enableIpamDelegatedAdmin,s as enablePolicyTypes,o as enableRamSharing,c as enableServiceAccess,n as ensureOrganisationExists,m as ensureOrganisationalUnitsExist,x as extractErrorName,p as findAccount,C as isOULeaf,d as listAccounts,S as placeAccountsInOUs,O as registerSecurityDelegates,A as updateBackupGlobalSettings};
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { ListAccountsCommand, CreateAccountCommand, CreateAccountState, DescribeCreateAccountStatusCommand } from "@aws-sdk/client-organizations";
|
|
2
2
|
import { success, failure } from "@fjall/generator";
|
|
3
|
-
import { sleep } from "
|
|
4
|
-
import { extractErrorName } from "./types.js";
|
|
3
|
+
import { sleep, getErrorMessage } from "@fjall/util";
|
|
4
|
+
import { extractErrorName, SDK_TIMEOUT_MS, AWS_ERROR_NAMES } from "./types.js";
|
|
5
5
|
/**
|
|
6
6
|
* List all accounts in the organisation, handling pagination.
|
|
7
7
|
*/
|
|
8
8
|
export async function listAccounts(client) {
|
|
9
9
|
try {
|
|
10
|
-
const response = await client.send(new ListAccountsCommand({ MaxResults: 20 }));
|
|
10
|
+
const response = await client.send(new ListAccountsCommand({ MaxResults: 20 }), { abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS) });
|
|
11
11
|
let accounts = response.Accounts ?? [];
|
|
12
12
|
let nextToken = response.NextToken;
|
|
13
13
|
while (nextToken) {
|
|
14
14
|
const nextResponse = await client.send(new ListAccountsCommand({
|
|
15
15
|
MaxResults: 20,
|
|
16
16
|
NextToken: nextToken
|
|
17
|
-
}));
|
|
17
|
+
}), { abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS) });
|
|
18
18
|
accounts = accounts.concat(nextResponse.Accounts ?? []);
|
|
19
19
|
nextToken = nextResponse.NextToken;
|
|
20
20
|
}
|
|
@@ -22,10 +22,10 @@ export async function listAccounts(client) {
|
|
|
22
22
|
}
|
|
23
23
|
catch (error) {
|
|
24
24
|
const errorName = extractErrorName(error);
|
|
25
|
-
if (errorName ===
|
|
25
|
+
if (errorName === AWS_ERROR_NAMES.ORGS_NOT_IN_USE) {
|
|
26
26
|
return failure(new Error("AWS Organisations is not enabled for this account"));
|
|
27
27
|
}
|
|
28
|
-
return failure(new Error(`Failed to list accounts: ${
|
|
28
|
+
return failure(new Error(`Failed to list accounts: ${getErrorMessage(error)}`));
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
@@ -51,11 +51,11 @@ export async function createAccount(client, accountName, email, maxAttempts = 18
|
|
|
51
51
|
response = await client.send(new CreateAccountCommand({
|
|
52
52
|
AccountName: accountName,
|
|
53
53
|
Email: email
|
|
54
|
-
}));
|
|
54
|
+
}), { abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS) });
|
|
55
55
|
}
|
|
56
56
|
catch (error) {
|
|
57
57
|
const errorName = extractErrorName(error);
|
|
58
|
-
if (errorName ===
|
|
58
|
+
if (errorName === AWS_ERROR_NAMES.ACCESS_DENIED) {
|
|
59
59
|
return failure(new Error(`Access denied when creating account "${accountName}". ` +
|
|
60
60
|
"Ensure your credentials have organizations:CreateAccount permission."));
|
|
61
61
|
}
|
|
@@ -74,7 +74,7 @@ export async function createAccount(client, accountName, email, maxAttempts = 18
|
|
|
74
74
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
75
75
|
const statusResponse = await client.send(new DescribeCreateAccountStatusCommand({
|
|
76
76
|
CreateAccountRequestId: requestId
|
|
77
|
-
}));
|
|
77
|
+
}), { abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS) });
|
|
78
78
|
const status = statusResponse.CreateAccountStatus;
|
|
79
79
|
if (!status) {
|
|
80
80
|
return failure(new Error(`No status returned for CreateAccount request ${requestId}`));
|
|
@@ -94,6 +94,6 @@ export async function createAccount(client, accountName, email, maxAttempts = 18
|
|
|
94
94
|
return failure(new Error(`Account creation for "${accountName}" timed out after ${(maxAttempts * delayMs) / 1000} seconds`));
|
|
95
95
|
}
|
|
96
96
|
catch (error) {
|
|
97
|
-
return failure(new Error(`Failed to create account "${accountName}": ${
|
|
97
|
+
return failure(new Error(`Failed to create account "${accountName}": ${getErrorMessage(error)}`));
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { UpdateGlobalSettingsCommand } from "@aws-sdk/client-backup";
|
|
2
2
|
import { success, failure } from "@fjall/generator";
|
|
3
|
+
import { getErrorMessage } from "@fjall/util";
|
|
4
|
+
import { SDK_TIMEOUT_MS } from "./types.js";
|
|
3
5
|
const DEFAULT_BACKUP_SETTINGS = {
|
|
4
6
|
enableCrossAccountBackup: true,
|
|
5
7
|
enableDelegatedAdministrator: true,
|
|
@@ -19,10 +21,10 @@ export async function updateBackupGlobalSettings(client, settings) {
|
|
|
19
21
|
};
|
|
20
22
|
await client.send(new UpdateGlobalSettingsCommand({
|
|
21
23
|
GlobalSettings: globalSettings
|
|
22
|
-
}));
|
|
24
|
+
}), { abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS) });
|
|
23
25
|
return success(undefined);
|
|
24
26
|
}
|
|
25
27
|
catch (error) {
|
|
26
|
-
return failure(new Error(`Failed to update backup global settings: ${
|
|
28
|
+
return failure(new Error(`Failed to update backup global settings: ${getErrorMessage(error)}`));
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { UpdateCostAllocationTagsStatusCommand } from "@aws-sdk/client-cost-explorer";
|
|
2
2
|
import { success, failure } from "@fjall/generator";
|
|
3
|
+
import { getErrorMessage } from "@fjall/util";
|
|
4
|
+
import { SDK_TIMEOUT_MS } from "./types.js";
|
|
3
5
|
/**
|
|
4
6
|
* Activate cost allocation tags for billing tracking.
|
|
5
7
|
* Idempotent — activating already-active tags is a no-op.
|
|
@@ -17,10 +19,10 @@ export async function activateCostAllocationTags(client, tags) {
|
|
|
17
19
|
}));
|
|
18
20
|
await client.send(new UpdateCostAllocationTagsStatusCommand({
|
|
19
21
|
CostAllocationTagsStatus: costAllocationTagsStatus
|
|
20
|
-
}));
|
|
22
|
+
}), { abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS) });
|
|
21
23
|
return success(undefined);
|
|
22
24
|
}
|
|
23
25
|
catch (error) {
|
|
24
|
-
return failure(new Error(`Failed to activate cost allocation tags: ${
|
|
26
|
+
return failure(new Error(`Failed to activate cost allocation tags: ${getErrorMessage(error)}`));
|
|
25
27
|
}
|
|
26
28
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type OrganizationsClient } from "@aws-sdk/client-organizations";
|
|
2
|
+
import { type Result } from "@fjall/generator";
|
|
3
|
+
declare const SECURITY_SERVICE_PRINCIPALS: readonly ["guardduty.amazonaws.com", "securityhub.amazonaws.com", "config.amazonaws.com", "inspector2.amazonaws.com", "access-analyzer.amazonaws.com"];
|
|
4
|
+
/**
|
|
5
|
+
* Register delegated administrators for security services.
|
|
6
|
+
* Idempotent — already-registered delegates are silently skipped.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerSecurityDelegates(client: OrganizationsClient, delegateAccountId: string, services?: readonly string[]): Promise<Result<void>>;
|
|
9
|
+
export { SECURITY_SERVICE_PRINCIPALS };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { RegisterDelegatedAdministratorCommand } from "@aws-sdk/client-organizations";
|
|
2
|
+
import { success, failure } from "@fjall/generator";
|
|
3
|
+
import { extractErrorName, SDK_TIMEOUT_MS, AWS_ERROR_NAMES } from "./types.js";
|
|
4
|
+
import { getErrorMessage } from "@fjall/util";
|
|
5
|
+
const SECURITY_SERVICE_PRINCIPALS = [
|
|
6
|
+
"guardduty.amazonaws.com",
|
|
7
|
+
"securityhub.amazonaws.com",
|
|
8
|
+
"config.amazonaws.com",
|
|
9
|
+
"inspector2.amazonaws.com",
|
|
10
|
+
"access-analyzer.amazonaws.com"
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* Register delegated administrators for security services.
|
|
14
|
+
* Idempotent — already-registered delegates are silently skipped.
|
|
15
|
+
*/
|
|
16
|
+
export async function registerSecurityDelegates(client, delegateAccountId, services = SECURITY_SERVICE_PRINCIPALS) {
|
|
17
|
+
const errors = [];
|
|
18
|
+
for (const service of services) {
|
|
19
|
+
try {
|
|
20
|
+
await client.send(new RegisterDelegatedAdministratorCommand({
|
|
21
|
+
AccountId: delegateAccountId,
|
|
22
|
+
ServicePrincipal: service
|
|
23
|
+
}), { abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS) });
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const errorName = extractErrorName(error);
|
|
27
|
+
if (errorName === AWS_ERROR_NAMES.ACCOUNT_ALREADY_REGISTERED) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (errorName === AWS_ERROR_NAMES.ACCESS_DENIED) {
|
|
31
|
+
const prefix = errors.length > 0 ? `${errors.join("; ")}; ` : "";
|
|
32
|
+
return failure(new Error(`${prefix}Access denied when registering delegated admin for ${service}. ` +
|
|
33
|
+
"Ensure your credentials have organizations:RegisterDelegatedAdministrator permission."));
|
|
34
|
+
}
|
|
35
|
+
errors.push(`${service}: ${getErrorMessage(error)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (errors.length > 0) {
|
|
39
|
+
return failure(new Error(`Failed to register security delegates for account ${delegateAccountId}:\n ${errors.join("\n ")}`));
|
|
40
|
+
}
|
|
41
|
+
return success(undefined);
|
|
42
|
+
}
|
|
43
|
+
export { SECURITY_SERVICE_PRINCIPALS };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type SSOAdminClient } from "@aws-sdk/client-sso-admin";
|
|
2
2
|
import { type Result } from "@fjall/generator";
|
|
3
|
-
import type
|
|
3
|
+
import { type IdentityCentreStatus } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Check if AWS Identity Centre (SSO) is enabled.
|
|
6
6
|
* Returns the number of SSO instances found.
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { ListInstancesCommand } from "@aws-sdk/client-sso-admin";
|
|
2
2
|
import { success, failure } from "@fjall/generator";
|
|
3
|
+
import { getErrorMessage } from "@fjall/util";
|
|
4
|
+
import { SDK_TIMEOUT_MS } from "./types.js";
|
|
3
5
|
/**
|
|
4
6
|
* Check if AWS Identity Centre (SSO) is enabled.
|
|
5
7
|
* Returns the number of SSO instances found.
|
|
6
8
|
*/
|
|
7
9
|
export async function checkIdentityCentreStatus(client) {
|
|
8
10
|
try {
|
|
9
|
-
const response = await client.send(new ListInstancesCommand({})
|
|
11
|
+
const response = await client.send(new ListInstancesCommand({}), {
|
|
12
|
+
abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS)
|
|
13
|
+
});
|
|
10
14
|
const instances = response.Instances ?? [];
|
|
11
15
|
return success({
|
|
12
16
|
enabled: instances.length > 0,
|
|
@@ -14,6 +18,6 @@ export async function checkIdentityCentreStatus(client) {
|
|
|
14
18
|
});
|
|
15
19
|
}
|
|
16
20
|
catch (error) {
|
|
17
|
-
return failure(new Error(`Failed to check Identity Centre status: ${
|
|
21
|
+
return failure(new Error(`Failed to check Identity Centre status: ${getErrorMessage(error)}`));
|
|
18
22
|
}
|
|
19
23
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { OrgDetails, OUMap, AccountPlacementResult, AccountInfo, IdentityCentreStatus } from "./types.js";
|
|
2
|
-
export { extractErrorName } from "./types.js";
|
|
1
|
+
export type { OrgDetails, OUMap, OUTree, AccountPlacementResult, AccountInfo, IdentityCentreStatus } from "./types.js";
|
|
2
|
+
export { extractErrorName, isOULeaf } from "./types.js";
|
|
3
3
|
export type { BackupGlobalSettings } from "./backup.js";
|
|
4
4
|
export type { CostAllocationTag } from "./costAllocation.js";
|
|
5
5
|
export type { CreateAccountResult } from "./accounts.js";
|
|
@@ -11,6 +11,7 @@ export { activateTrustedAccess } from "./trustedAccess.js";
|
|
|
11
11
|
export { enableIpamDelegatedAdmin } from "./ipam.js";
|
|
12
12
|
export { updateBackupGlobalSettings } from "./backup.js";
|
|
13
13
|
export { listAccounts, findAccount, createAccount } from "./accounts.js";
|
|
14
|
-
export { ensureOrganisationalUnitsExist, placeAccountsInOUs } from "./organisationalUnits.js";
|
|
14
|
+
export { ensureOrganisationalUnitsExist, placeAccountsInOUs, buildAccountToOUMap } from "./organisationalUnits.js";
|
|
15
15
|
export { activateCostAllocationTags } from "./costAllocation.js";
|
|
16
16
|
export { checkIdentityCentreStatus } from "./identityCentre.js";
|
|
17
|
+
export { registerSecurityDelegates, SECURITY_SERVICE_PRINCIPALS } from "./delegatedAdmin.js";
|
|
@@ -1,12 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { ensureOrganisationExists, describeOrganisation } from "./organisation.js";
|
|
3
|
-
export { enablePolicyTypes } from "./policies.js";
|
|
4
|
-
export { enableServiceAccess } from "./serviceAccess.js";
|
|
5
|
-
export { enableRamSharing } from "./ram.js";
|
|
6
|
-
export { activateTrustedAccess } from "./trustedAccess.js";
|
|
7
|
-
export { enableIpamDelegatedAdmin } from "./ipam.js";
|
|
8
|
-
export { updateBackupGlobalSettings } from "./backup.js";
|
|
9
|
-
export { listAccounts, findAccount, createAccount } from "./accounts.js";
|
|
10
|
-
export { ensureOrganisationalUnitsExist, placeAccountsInOUs } from "./organisationalUnits.js";
|
|
11
|
-
export { activateCostAllocationTags } from "./costAllocation.js";
|
|
12
|
-
export { checkIdentityCentreStatus } from "./identityCentre.js";
|
|
1
|
+
import{extractErrorName as r,isOULeaf as o}from"./types.js";import{ensureOrganisationExists as c,describeOrganisation as s}from"./organisation.js";import{enablePolicyTypes as i}from"./policies.js";import{enableServiceAccess as m}from"./serviceAccess.js";import{enableRamSharing as f}from"./ram.js";import{activateTrustedAccess as u}from"./trustedAccess.js";import{enableIpamDelegatedAdmin as g}from"./ipam.js";import{updateBackupGlobalSettings as S}from"./backup.js";import{listAccounts as I,findAccount as E,createAccount as O}from"./accounts.js";import{ensureOrganisationalUnitsExist as T,placeAccountsInOUs as U,buildAccountToOUMap as y}from"./organisationalUnits.js";import{activateCostAllocationTags as v}from"./costAllocation.js";import{checkIdentityCentreStatus as h}from"./identityCentre.js";import{registerSecurityDelegates as D,SECURITY_SERVICE_PRINCIPALS as L}from"./delegatedAdmin.js";export{L as SECURITY_SERVICE_PRINCIPALS,v as activateCostAllocationTags,u as activateTrustedAccess,y as buildAccountToOUMap,h as checkIdentityCentreStatus,O as createAccount,s as describeOrganisation,g as enableIpamDelegatedAdmin,i as enablePolicyTypes,f as enableRamSharing,m as enableServiceAccess,c as ensureOrganisationExists,T as ensureOrganisationalUnitsExist,r as extractErrorName,E as findAccount,o as isOULeaf,I as listAccounts,U as placeAccountsInOUs,D as registerSecurityDelegates,S as updateBackupGlobalSettings};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { EnableIpamOrganizationAdminAccountCommand } from "@aws-sdk/client-ec2";
|
|
2
2
|
import { success, failure } from "@fjall/generator";
|
|
3
|
+
import { getErrorMessage } from "@fjall/util";
|
|
4
|
+
import { SDK_TIMEOUT_MS } from "./types.js";
|
|
3
5
|
/**
|
|
4
6
|
* Enable IPAM delegated administrator for the given account.
|
|
5
7
|
* Idempotent — calling when already delegated is a no-op.
|
|
@@ -9,10 +11,10 @@ export async function enableIpamDelegatedAdmin(client, delegatedAdminAccountId)
|
|
|
9
11
|
await client.send(new EnableIpamOrganizationAdminAccountCommand({
|
|
10
12
|
DryRun: false,
|
|
11
13
|
DelegatedAdminAccountId: delegatedAdminAccountId
|
|
12
|
-
}));
|
|
14
|
+
}), { abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS) });
|
|
13
15
|
return success(undefined);
|
|
14
16
|
}
|
|
15
17
|
catch (error) {
|
|
16
|
-
return failure(new Error(`Failed to enable IPAM delegation for account ${delegatedAdminAccountId}: ${
|
|
18
|
+
return failure(new Error(`Failed to enable IPAM delegation for account ${delegatedAdminAccountId}: ${getErrorMessage(error)}`));
|
|
17
19
|
}
|
|
18
20
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DescribeOrganizationCommand, CreateOrganizationCommand, ListRootsCommand } from "@aws-sdk/client-organizations";
|
|
2
2
|
import { success, failure } from "@fjall/generator";
|
|
3
|
-
import {
|
|
3
|
+
import { getErrorMessage } from "@fjall/util";
|
|
4
|
+
import { extractErrorName, SDK_TIMEOUT_MS, AWS_ERROR_NAMES } from "./types.js";
|
|
4
5
|
/**
|
|
5
6
|
* Ensure an AWS Organisation exists, creating one if necessary.
|
|
6
7
|
* Idempotent — safe to call when the organisation already exists.
|
|
@@ -13,7 +14,7 @@ export async function ensureOrganisationExists(client) {
|
|
|
13
14
|
if (!org) {
|
|
14
15
|
// Create new organisation with all features
|
|
15
16
|
try {
|
|
16
|
-
const createResponse = await client.send(new CreateOrganizationCommand({ FeatureSet: "ALL" }));
|
|
17
|
+
const createResponse = await client.send(new CreateOrganizationCommand({ FeatureSet: "ALL" }), { abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS) });
|
|
17
18
|
org = createResponse.Organization ?? null;
|
|
18
19
|
created = true;
|
|
19
20
|
}
|
|
@@ -35,20 +36,18 @@ export async function ensureOrganisationExists(client) {
|
|
|
35
36
|
return failure(new Error("Organisation is missing required fields (Id or MasterAccountId)"));
|
|
36
37
|
}
|
|
37
38
|
// Get root ID
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return failure(new Error("No organisation root found"));
|
|
42
|
-
}
|
|
39
|
+
const rootResult = await getOrganisationRootId(client);
|
|
40
|
+
if (!rootResult.success)
|
|
41
|
+
return rootResult;
|
|
43
42
|
return success({
|
|
44
43
|
orgId: org.Id,
|
|
45
|
-
rootId:
|
|
44
|
+
rootId: rootResult.data,
|
|
46
45
|
managementAccountId: org.MasterAccountId,
|
|
47
46
|
created
|
|
48
47
|
});
|
|
49
48
|
}
|
|
50
49
|
catch (error) {
|
|
51
|
-
return failure(new Error(`Failed to ensure organisation exists: ${
|
|
50
|
+
return failure(new Error(`Failed to ensure organisation exists: ${getErrorMessage(error)}`));
|
|
52
51
|
}
|
|
53
52
|
}
|
|
54
53
|
/**
|
|
@@ -63,30 +62,40 @@ export async function describeOrganisation(client) {
|
|
|
63
62
|
if (!org.Id || !org.MasterAccountId) {
|
|
64
63
|
return failure(new Error("Organisation is missing required fields (Id or MasterAccountId)"));
|
|
65
64
|
}
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
return failure(new Error("No organisation root found"));
|
|
70
|
-
}
|
|
65
|
+
const rootResult = await getOrganisationRootId(client);
|
|
66
|
+
if (!rootResult.success)
|
|
67
|
+
return rootResult;
|
|
71
68
|
return success({
|
|
72
69
|
orgId: org.Id,
|
|
73
|
-
rootId:
|
|
70
|
+
rootId: rootResult.data,
|
|
74
71
|
managementAccountId: org.MasterAccountId,
|
|
75
72
|
created: false
|
|
76
73
|
});
|
|
77
74
|
}
|
|
78
75
|
catch (error) {
|
|
79
|
-
return failure(new Error(`Failed to describe organisation: ${
|
|
76
|
+
return failure(new Error(`Failed to describe organisation: ${getErrorMessage(error)}`));
|
|
80
77
|
}
|
|
81
78
|
}
|
|
79
|
+
async function getOrganisationRootId(client) {
|
|
80
|
+
const rootsResponse = await client.send(new ListRootsCommand({}), {
|
|
81
|
+
abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS)
|
|
82
|
+
});
|
|
83
|
+
const roots = rootsResponse.Roots ?? [];
|
|
84
|
+
if (roots.length === 0 || !roots[0]?.Id) {
|
|
85
|
+
return failure(new Error("No organisation root found"));
|
|
86
|
+
}
|
|
87
|
+
return success(roots[0].Id);
|
|
88
|
+
}
|
|
82
89
|
async function describeOrganisationRaw(client) {
|
|
83
90
|
try {
|
|
84
|
-
const response = await client.send(new DescribeOrganizationCommand({})
|
|
91
|
+
const response = await client.send(new DescribeOrganizationCommand({}), {
|
|
92
|
+
abortSignal: AbortSignal.timeout(SDK_TIMEOUT_MS)
|
|
93
|
+
});
|
|
85
94
|
return response.Organization ?? null;
|
|
86
95
|
}
|
|
87
96
|
catch (error) {
|
|
88
97
|
const errorName = extractErrorName(error);
|
|
89
|
-
if (errorName ===
|
|
98
|
+
if (errorName === AWS_ERROR_NAMES.ORGS_NOT_IN_USE) {
|
|
90
99
|
return null;
|
|
91
100
|
}
|
|
92
101
|
throw error;
|
|
@@ -1,19 +1,39 @@
|
|
|
1
1
|
import { type OrganizationsClient } from "@aws-sdk/client-organizations";
|
|
2
2
|
import { type Result } from "@fjall/generator";
|
|
3
|
-
import { type OUMap, type AccountInfo, type AccountPlacementResult } from "./types.js";
|
|
3
|
+
import { type OUMap, type OUTree, type AccountInfo, type AccountPlacementResult } from "./types.js";
|
|
4
4
|
/**
|
|
5
|
-
* Ensure the specified organisational units exist
|
|
6
|
-
*
|
|
5
|
+
* Ensure the specified organisational units exist. Accepts either:
|
|
6
|
+
* - `string[]` (flat): creates OUs directly under root (original behaviour)
|
|
7
|
+
* - `OUTree` (nested): creates a recursive OU hierarchy
|
|
8
|
+
*
|
|
9
|
+
* Returns a map of OU key (lowercase) to OU ID. For nested trees, keys
|
|
10
|
+
* use dot notation (e.g., "workloads.production") with shorthand aliases
|
|
11
|
+
* for leaf names that don't collide with top-level keys.
|
|
7
12
|
*
|
|
8
13
|
* Idempotent — existing OUs are adopted, not duplicated.
|
|
9
14
|
*
|
|
10
|
-
*
|
|
15
|
+
* AWS cannot move OUs between parents. Migrating from flat to nested
|
|
16
|
+
* creates new OUs under new parents; old empty OUs remain at root.
|
|
17
|
+
*/
|
|
18
|
+
export declare function ensureOrganisationalUnitsExist(client: OrganizationsClient, rootId: string, ouConfig: string[] | OUTree): Promise<Result<OUMap>>;
|
|
19
|
+
/**
|
|
20
|
+
* Build a flat map of accountName (lowercase) to OU ID from an OUTree
|
|
21
|
+
* and its resolved OUMap.
|
|
22
|
+
*
|
|
23
|
+
* Walks the tree recursively. For each leaf (string[]), maps each account
|
|
24
|
+
* name to the parent OU's ID using the dotted OUMap key.
|
|
25
|
+
*
|
|
26
|
+
* Callers must pass account names that match the tree leaf values.
|
|
11
27
|
*/
|
|
12
|
-
export declare function
|
|
28
|
+
export declare function buildAccountToOUMap(tree: OUTree, ouMap: OUMap, prefix?: string): Record<string, string>;
|
|
13
29
|
/**
|
|
14
30
|
* Place accounts into the correct organisational units.
|
|
15
31
|
* Skips accounts with environment "root" and accounts already in the target OU.
|
|
16
32
|
*
|
|
33
|
+
* When `accountToOU` is provided, looks up the target OU by account name
|
|
34
|
+
* (lowercase) instead of by environment. This supports tree-based placement
|
|
35
|
+
* where the OU is determined by which leaf the account appears in.
|
|
36
|
+
*
|
|
17
37
|
* Idempotent — accounts already in the correct OU are counted but not moved.
|
|
18
38
|
*/
|
|
19
|
-
export declare function placeAccountsInOUs(client: OrganizationsClient, ouMap: OUMap, accounts: AccountInfo[]): Promise<Result<AccountPlacementResult>>;
|
|
39
|
+
export declare function placeAccountsInOUs(client: OrganizationsClient, ouMap: OUMap, accounts: AccountInfo[], accountToOU?: Record<string, string>): Promise<Result<AccountPlacementResult>>;
|