@fjall/deploy-core 0.89.2
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/dist/src/aws/AwsProvider.d.ts +39 -0
- package/dist/src/aws/AwsProvider.js +1 -0
- package/dist/src/aws/SimpleAwsProvider.d.ts +22 -0
- package/dist/src/aws/SimpleAwsProvider.js +73 -0
- package/dist/src/aws/index.d.ts +4 -0
- package/dist/src/aws/index.js +3 -0
- package/dist/src/aws/organisations/accounts.d.ts +21 -0
- package/dist/src/aws/organisations/accounts.js +99 -0
- package/dist/src/aws/organisations/backup.d.ts +12 -0
- package/dist/src/aws/organisations/backup.js +28 -0
- package/dist/src/aws/organisations/costAllocation.d.ts +12 -0
- package/dist/src/aws/organisations/costAllocation.js +26 -0
- package/dist/src/aws/organisations/identityCentre.d.ts +8 -0
- package/dist/src/aws/organisations/identityCentre.js +19 -0
- package/dist/src/aws/organisations/index.d.ts +16 -0
- package/dist/src/aws/organisations/index.js +12 -0
- package/dist/src/aws/organisations/ipam.d.ts +7 -0
- package/dist/src/aws/organisations/ipam.js +18 -0
- package/dist/src/aws/organisations/organisation.d.ts +12 -0
- package/dist/src/aws/organisations/organisation.js +94 -0
- package/dist/src/aws/organisations/organisationalUnits.d.ts +19 -0
- package/dist/src/aws/organisations/organisationalUnits.js +125 -0
- package/dist/src/aws/organisations/policies.d.ts +7 -0
- package/dist/src/aws/organisations/policies.js +36 -0
- package/dist/src/aws/organisations/ram.d.ts +7 -0
- package/dist/src/aws/organisations/ram.js +15 -0
- package/dist/src/aws/organisations/serviceAccess.d.ts +7 -0
- package/dist/src/aws/organisations/serviceAccess.js +38 -0
- package/dist/src/aws/organisations/trustedAccess.d.ts +7 -0
- package/dist/src/aws/organisations/trustedAccess.js +15 -0
- package/dist/src/aws/organisations/types.d.ts +29 -0
- package/dist/src/aws/organisations/types.js +16 -0
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.d.ts +32 -0
- package/dist/src/aws/utils/CloudFormationFailureAnalyser.js +228 -0
- package/dist/src/aws/utils/cloudformationEvents.d.ts +98 -0
- package/dist/src/aws/utils/cloudformationEvents.js +596 -0
- package/dist/src/aws/utils/errors.d.ts +26 -0
- package/dist/src/aws/utils/errors.js +59 -0
- package/dist/src/aws/utils/regions.d.ts +1 -0
- package/dist/src/aws/utils/regions.js +1 -0
- package/dist/src/aws/utils/stackStatus.d.ts +23 -0
- package/dist/src/aws/utils/stackStatus.js +90 -0
- package/dist/src/index.d.ts +35 -0
- package/dist/src/index.js +45 -0
- package/dist/src/orchestration/applicationDeploy.d.ts +11 -0
- package/dist/src/orchestration/applicationDeploy.js +327 -0
- package/dist/src/orchestration/contextHelpers.d.ts +9 -0
- package/dist/src/orchestration/contextHelpers.js +14 -0
- package/dist/src/orchestration/deploy.d.ts +10 -0
- package/dist/src/orchestration/deploy.js +42 -0
- package/dist/src/orchestration/detectionPipeline.d.ts +23 -0
- package/dist/src/orchestration/detectionPipeline.js +65 -0
- package/dist/src/orchestration/dockerInterface.d.ts +56 -0
- package/dist/src/orchestration/dockerInterface.js +1 -0
- package/dist/src/orchestration/domainInterface.d.ts +37 -0
- package/dist/src/orchestration/domainInterface.js +1 -0
- package/dist/src/orchestration/index.d.ts +8 -0
- package/dist/src/orchestration/index.js +3 -0
- package/dist/src/orchestration/organisationDeploy.d.ts +16 -0
- package/dist/src/orchestration/organisationDeploy.js +382 -0
- package/dist/src/orchestration/organisationSetup.d.ts +42 -0
- package/dist/src/orchestration/organisationSetup.js +227 -0
- package/dist/src/orchestration/resolveOperation.d.ts +10 -0
- package/dist/src/orchestration/resolveOperation.js +53 -0
- package/dist/src/orchestration/serviceFactory.d.ts +15 -0
- package/dist/src/orchestration/serviceFactory.js +16 -0
- package/dist/src/services/application/ApplicationStackService.d.ts +93 -0
- package/dist/src/services/application/ApplicationStackService.js +436 -0
- package/dist/src/services/application/index.d.ts +1 -0
- package/dist/src/services/application/index.js +1 -0
- package/dist/src/services/infrastructure/CdkArgumentBuilder.d.ts +12 -0
- package/dist/src/services/infrastructure/CdkArgumentBuilder.js +67 -0
- package/dist/src/services/infrastructure/CdkCommandRunner.d.ts +30 -0
- package/dist/src/services/infrastructure/CdkCommandRunner.js +241 -0
- package/dist/src/services/infrastructure/CdkErrorFormatter.d.ts +4 -0
- package/dist/src/services/infrastructure/CdkErrorFormatter.js +194 -0
- package/dist/src/services/infrastructure/CdkEventMonitoring.d.ts +19 -0
- package/dist/src/services/infrastructure/CdkEventMonitoring.js +41 -0
- package/dist/src/services/infrastructure/CdkOutputAnalyser.d.ts +43 -0
- package/dist/src/services/infrastructure/CdkOutputAnalyser.js +125 -0
- package/dist/src/services/infrastructure/CdkOutputParser.d.ts +8 -0
- package/dist/src/services/infrastructure/CdkOutputParser.js +33 -0
- package/dist/src/services/infrastructure/CdkProcessManager.d.ts +20 -0
- package/dist/src/services/infrastructure/CdkProcessManager.js +244 -0
- package/dist/src/services/infrastructure/CdkService.d.ts +71 -0
- package/dist/src/services/infrastructure/CdkService.js +254 -0
- package/dist/src/services/infrastructure/CloudFormationService.d.ts +79 -0
- package/dist/src/services/infrastructure/CloudFormationService.js +249 -0
- package/dist/src/services/infrastructure/index.d.ts +8 -0
- package/dist/src/services/infrastructure/index.js +7 -0
- package/dist/src/services/supporting/CdkContextBuilder.d.ts +49 -0
- package/dist/src/services/supporting/CdkContextBuilder.js +44 -0
- package/dist/src/services/supporting/TemplateHashService.d.ts +67 -0
- package/dist/src/services/supporting/TemplateHashService.js +152 -0
- package/dist/src/services/supporting/helpers.d.ts +46 -0
- package/dist/src/services/supporting/helpers.js +81 -0
- package/dist/src/services/supporting/index.d.ts +3 -0
- package/dist/src/services/supporting/index.js +3 -0
- package/dist/src/types/FjallState.d.ts +50 -0
- package/dist/src/types/FjallState.js +118 -0
- package/dist/src/types/ProgressEvent.d.ts +35 -0
- package/dist/src/types/ProgressEvent.js +48 -0
- package/dist/src/types/apiClient.d.ts +34 -0
- package/dist/src/types/apiClient.js +1 -0
- package/dist/src/types/application/ApplicationServiceTypes.d.ts +56 -0
- package/dist/src/types/application/ApplicationServiceTypes.js +30 -0
- package/dist/src/types/application/index.d.ts +1 -0
- package/dist/src/types/application/index.js +1 -0
- package/dist/src/types/callbacks.d.ts +36 -0
- package/dist/src/types/callbacks.js +1 -0
- package/dist/src/types/constants.d.ts +6 -0
- package/dist/src/types/constants.js +6 -0
- package/dist/src/types/credentials.d.ts +30 -0
- package/dist/src/types/credentials.js +1 -0
- package/dist/src/types/deployment/DeploymentServiceTypes.d.ts +23 -0
- package/dist/src/types/deployment/DeploymentServiceTypes.js +1 -0
- package/dist/src/types/deployment/DeploymentTypes.d.ts +29 -0
- package/dist/src/types/deployment/DeploymentTypes.js +1 -0
- package/dist/src/types/deployment/cloudformation.d.ts +14 -0
- package/dist/src/types/deployment/cloudformation.js +1 -0
- package/dist/src/types/deployment/index.d.ts +5 -0
- package/dist/src/types/deployment/index.js +1 -0
- package/dist/src/types/deployment/parallel.d.ts +46 -0
- package/dist/src/types/deployment/parallel.js +10 -0
- package/dist/src/types/errors/CdkError.d.ts +14 -0
- package/dist/src/types/errors/CdkError.js +20 -0
- package/dist/src/types/errors/ServiceError.d.ts +86 -0
- package/dist/src/types/errors/ServiceError.js +119 -0
- package/dist/src/types/events.d.ts +40 -0
- package/dist/src/types/events.js +5 -0
- package/dist/src/types/index.d.ts +20 -0
- package/dist/src/types/index.js +9 -0
- package/dist/src/types/operations.d.ts +193 -0
- package/dist/src/types/operations.js +285 -0
- package/dist/src/types/orgConfig.d.ts +28 -0
- package/dist/src/types/orgConfig.js +11 -0
- package/dist/src/types/params.d.ts +74 -0
- package/dist/src/types/params.js +1 -0
- package/dist/src/types/patternDetection.d.ts +43 -0
- package/dist/src/types/patternDetection.js +92 -0
- package/dist/src/types/validation.d.ts +12 -0
- package/dist/src/types/validation.js +1 -0
- package/dist/src/util/fsHelpers.d.ts +4 -0
- package/dist/src/util/fsHelpers.js +16 -0
- package/dist/src/util/index.d.ts +3 -0
- package/dist/src/util/index.js +3 -0
- package/dist/src/util/securityHelpers.d.ts +31 -0
- package/dist/src/util/securityHelpers.js +124 -0
- package/dist/src/util/singleton.d.ts +2 -0
- package/dist/src/util/singleton.js +9 -0
- package/dist/src/util/sleep.d.ts +4 -0
- package/dist/src/util/sleep.js +4 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fjall
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for AWS credential and client management.
|
|
3
|
+
*
|
|
4
|
+
* CLI provides: its full AwsContext (SSO, OIDC, credential caching).
|
|
5
|
+
* Worker provides: SimpleAwsProvider (pre-assumed OIDC credentials).
|
|
6
|
+
*
|
|
7
|
+
* deploy-core services use this interface — they never care HOW
|
|
8
|
+
* credentials were obtained.
|
|
9
|
+
*/
|
|
10
|
+
export interface AwsProvider {
|
|
11
|
+
/** Get a configured AWS SDK v3 client instance. */
|
|
12
|
+
getClient<T>(ClientClass: AwsSdkClientConstructor<T>): T;
|
|
13
|
+
/** AWS region for this context. */
|
|
14
|
+
getRegion(): string;
|
|
15
|
+
/** AWS account ID (resolved via STS GetCallerIdentity). */
|
|
16
|
+
getAccountId(): string | undefined;
|
|
17
|
+
/** Raw credentials for subprocess environments (CDK). */
|
|
18
|
+
getCredentials(): AwsProviderCredentials | undefined;
|
|
19
|
+
/** Export credentials to process.env for spawned subprocesses. */
|
|
20
|
+
exportToEnv(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Assume an IAM role and return credentials for the assumed session.
|
|
23
|
+
* Used by organisation cascade to assume roles in member accounts.
|
|
24
|
+
*/
|
|
25
|
+
assumeRole?(roleArn: string, sessionName: string): Promise<AwsProviderCredentials>;
|
|
26
|
+
}
|
|
27
|
+
export interface AwsProviderCredentials {
|
|
28
|
+
accessKeyId: string;
|
|
29
|
+
secretAccessKey: string;
|
|
30
|
+
sessionToken?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Constructor type for AWS SDK v3 clients.
|
|
34
|
+
* Matches the signature of all @aws-sdk/client-* default exports.
|
|
35
|
+
*/
|
|
36
|
+
export type AwsSdkClientConstructor<T> = new (config: {
|
|
37
|
+
region?: string;
|
|
38
|
+
credentials?: AwsProviderCredentials;
|
|
39
|
+
}) => T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AwsCredentials } from "../types/credentials.js";
|
|
2
|
+
import type { AwsProvider, AwsProviderCredentials, AwsSdkClientConstructor } from "./AwsProvider.js";
|
|
3
|
+
/**
|
|
4
|
+
* Minimal AWS provider for pre-assumed credentials.
|
|
5
|
+
*
|
|
6
|
+
* Used by the webapp worker, which assumes an OIDC role before
|
|
7
|
+
* calling deploy-core. No SSO, no credential discovery, no caching.
|
|
8
|
+
*/
|
|
9
|
+
export declare class SimpleAwsProvider implements AwsProvider {
|
|
10
|
+
private readonly credentials;
|
|
11
|
+
private readonly region;
|
|
12
|
+
private accountId;
|
|
13
|
+
private readonly clientCache;
|
|
14
|
+
constructor(awsCredentials: AwsCredentials);
|
|
15
|
+
getClient<T>(ClientClass: AwsSdkClientConstructor<T>): T;
|
|
16
|
+
getRegion(): string;
|
|
17
|
+
getAccountId(): string | undefined;
|
|
18
|
+
setAccountId(accountId: string): void;
|
|
19
|
+
getCredentials(): AwsProviderCredentials;
|
|
20
|
+
exportToEnv(): void;
|
|
21
|
+
assumeRole(roleArn: string, sessionName: string): Promise<AwsProviderCredentials>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
|
|
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
|
+
setAccountId(accountId) {
|
|
41
|
+
this.accountId = accountId;
|
|
42
|
+
}
|
|
43
|
+
getCredentials() {
|
|
44
|
+
return this.credentials;
|
|
45
|
+
}
|
|
46
|
+
exportToEnv() {
|
|
47
|
+
process.env.AWS_ACCESS_KEY_ID = this.credentials.accessKeyId;
|
|
48
|
+
process.env.AWS_SECRET_ACCESS_KEY = this.credentials.secretAccessKey;
|
|
49
|
+
process.env.AWS_REGION = this.region;
|
|
50
|
+
if (this.credentials.sessionToken) {
|
|
51
|
+
process.env.AWS_SESSION_TOKEN = this.credentials.sessionToken;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async assumeRole(roleArn, sessionName) {
|
|
55
|
+
const stsClient = new STSClient({
|
|
56
|
+
region: this.region,
|
|
57
|
+
credentials: this.credentials
|
|
58
|
+
});
|
|
59
|
+
const response = await stsClient.send(new AssumeRoleCommand({
|
|
60
|
+
RoleArn: roleArn,
|
|
61
|
+
RoleSessionName: sessionName
|
|
62
|
+
}));
|
|
63
|
+
const assumed = response.Credentials;
|
|
64
|
+
if (!assumed?.AccessKeyId || !assumed?.SecretAccessKey) {
|
|
65
|
+
throw new Error(`AssumeRole for ${roleArn} returned incomplete credentials`);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
accessKeyId: assumed.AccessKeyId,
|
|
69
|
+
secretAccessKey: assumed.SecretAccessKey,
|
|
70
|
+
sessionToken: assumed.SessionToken
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { AwsProvider, AwsProviderCredentials, AwsSdkClientConstructor } from "./AwsProvider.js";
|
|
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";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { SimpleAwsProvider } from "./SimpleAwsProvider.js";
|
|
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";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type OrganizationsClient, type Account } from "@aws-sdk/client-organizations";
|
|
2
|
+
import { type Result } from "@fjall/generator";
|
|
3
|
+
export interface CreateAccountResult {
|
|
4
|
+
accountId: string;
|
|
5
|
+
accountName: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* List all accounts in the organisation, handling pagination.
|
|
9
|
+
*/
|
|
10
|
+
export declare function listAccounts(client: OrganizationsClient): Promise<Result<Account[]>>;
|
|
11
|
+
/**
|
|
12
|
+
* Find an account by ID in the organisation.
|
|
13
|
+
*/
|
|
14
|
+
export declare function findAccount(client: OrganizationsClient, accountId: string): Promise<Result<Account | undefined>>;
|
|
15
|
+
/**
|
|
16
|
+
* Create an AWS account and poll until creation completes.
|
|
17
|
+
*
|
|
18
|
+
* @param maxAttempts Maximum polling attempts (default: 180 = ~15 minutes at 5s intervals)
|
|
19
|
+
* @param delayMs Delay between polling attempts in milliseconds (default: 5000)
|
|
20
|
+
*/
|
|
21
|
+
export declare function createAccount(client: OrganizationsClient, accountName: string, email: string, maxAttempts?: number, delayMs?: number): Promise<Result<CreateAccountResult>>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ListAccountsCommand, CreateAccountCommand, CreateAccountState, DescribeCreateAccountStatusCommand } from "@aws-sdk/client-organizations";
|
|
2
|
+
import { success, failure } from "@fjall/generator";
|
|
3
|
+
import { sleep } from "../../util/sleep.js";
|
|
4
|
+
import { extractErrorName } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* List all accounts in the organisation, handling pagination.
|
|
7
|
+
*/
|
|
8
|
+
export async function listAccounts(client) {
|
|
9
|
+
try {
|
|
10
|
+
const response = await client.send(new ListAccountsCommand({ MaxResults: 20 }));
|
|
11
|
+
let accounts = response.Accounts ?? [];
|
|
12
|
+
let nextToken = response.NextToken;
|
|
13
|
+
while (nextToken) {
|
|
14
|
+
const nextResponse = await client.send(new ListAccountsCommand({
|
|
15
|
+
MaxResults: 20,
|
|
16
|
+
NextToken: nextToken
|
|
17
|
+
}));
|
|
18
|
+
accounts = accounts.concat(nextResponse.Accounts ?? []);
|
|
19
|
+
nextToken = nextResponse.NextToken;
|
|
20
|
+
}
|
|
21
|
+
return success(accounts);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const errorName = extractErrorName(error);
|
|
25
|
+
if (errorName === "AWSOrganizationsNotInUseException") {
|
|
26
|
+
return failure(new Error("AWS Organisations is not enabled for this account"));
|
|
27
|
+
}
|
|
28
|
+
return failure(new Error(`Failed to list accounts: ${error instanceof Error ? error.message : String(error)}`));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Find an account by ID in the organisation.
|
|
33
|
+
*/
|
|
34
|
+
export async function findAccount(client, accountId) {
|
|
35
|
+
const listResult = await listAccounts(client);
|
|
36
|
+
if (!listResult.success) {
|
|
37
|
+
return listResult;
|
|
38
|
+
}
|
|
39
|
+
return success(listResult.data.find((acc) => acc.Id === accountId));
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create an AWS account and poll until creation completes.
|
|
43
|
+
*
|
|
44
|
+
* @param maxAttempts Maximum polling attempts (default: 180 = ~15 minutes at 5s intervals)
|
|
45
|
+
* @param delayMs Delay between polling attempts in milliseconds (default: 5000)
|
|
46
|
+
*/
|
|
47
|
+
export async function createAccount(client, accountName, email, maxAttempts = 180, delayMs = 5000) {
|
|
48
|
+
try {
|
|
49
|
+
let response;
|
|
50
|
+
try {
|
|
51
|
+
response = await client.send(new CreateAccountCommand({
|
|
52
|
+
AccountName: accountName,
|
|
53
|
+
Email: email
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const errorName = extractErrorName(error);
|
|
58
|
+
if (errorName === "AccessDeniedException") {
|
|
59
|
+
return failure(new Error(`Access denied when creating account "${accountName}". ` +
|
|
60
|
+
"Ensure your credentials have organizations:CreateAccount permission."));
|
|
61
|
+
}
|
|
62
|
+
if (errorName === "DuplicateAccountException") {
|
|
63
|
+
return failure(new Error(`An account with the email "${email}" already exists in the organisation.`));
|
|
64
|
+
}
|
|
65
|
+
if (errorName === "FinalizingOrganizationException") {
|
|
66
|
+
return failure(new Error("The organisation is still being initialised. Please wait and try again."));
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
const requestId = response.CreateAccountStatus?.Id;
|
|
71
|
+
if (!requestId) {
|
|
72
|
+
return failure(new Error(`CreateAccount request for "${accountName}" did not return a status ID`));
|
|
73
|
+
}
|
|
74
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
75
|
+
const statusResponse = await client.send(new DescribeCreateAccountStatusCommand({
|
|
76
|
+
CreateAccountRequestId: requestId
|
|
77
|
+
}));
|
|
78
|
+
const status = statusResponse.CreateAccountStatus;
|
|
79
|
+
if (!status) {
|
|
80
|
+
return failure(new Error(`No status returned for CreateAccount request ${requestId}`));
|
|
81
|
+
}
|
|
82
|
+
if (status.State === CreateAccountState.SUCCEEDED) {
|
|
83
|
+
if (!status.AccountId) {
|
|
84
|
+
return failure(new Error(`Account creation succeeded but no account ID returned for "${accountName}"`));
|
|
85
|
+
}
|
|
86
|
+
return success({ accountId: status.AccountId, accountName });
|
|
87
|
+
}
|
|
88
|
+
if (status.State === CreateAccountState.FAILED) {
|
|
89
|
+
return failure(new Error(`Account creation failed for "${accountName}": ${status.FailureReason}`));
|
|
90
|
+
}
|
|
91
|
+
// Still IN_PROGRESS
|
|
92
|
+
await sleep(delayMs);
|
|
93
|
+
}
|
|
94
|
+
return failure(new Error(`Account creation for "${accountName}" timed out after ${(maxAttempts * delayMs) / 1000} seconds`));
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return failure(new Error(`Failed to create account "${accountName}": ${error instanceof Error ? error.message : String(error)}`));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type BackupClient } from "@aws-sdk/client-backup";
|
|
2
|
+
import { type Result } from "@fjall/generator";
|
|
3
|
+
export interface BackupGlobalSettings {
|
|
4
|
+
enableCrossAccountBackup?: boolean;
|
|
5
|
+
enableDelegatedAdministrator?: boolean;
|
|
6
|
+
enableMpa?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Update AWS Backup global settings for the organisation.
|
|
10
|
+
* Idempotent — safe to call repeatedly with the same settings.
|
|
11
|
+
*/
|
|
12
|
+
export declare function updateBackupGlobalSettings(client: BackupClient, settings?: BackupGlobalSettings): Promise<Result<void>>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { UpdateGlobalSettingsCommand } from "@aws-sdk/client-backup";
|
|
2
|
+
import { success, failure } from "@fjall/generator";
|
|
3
|
+
const DEFAULT_BACKUP_SETTINGS = {
|
|
4
|
+
enableCrossAccountBackup: true,
|
|
5
|
+
enableDelegatedAdministrator: true,
|
|
6
|
+
enableMpa: false
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Update AWS Backup global settings for the organisation.
|
|
10
|
+
* Idempotent — safe to call repeatedly with the same settings.
|
|
11
|
+
*/
|
|
12
|
+
export async function updateBackupGlobalSettings(client, settings) {
|
|
13
|
+
try {
|
|
14
|
+
const finalSettings = { ...DEFAULT_BACKUP_SETTINGS, ...settings };
|
|
15
|
+
const globalSettings = {
|
|
16
|
+
isCrossAccountBackupEnabled: finalSettings.enableCrossAccountBackup.toString(),
|
|
17
|
+
isDelegatedAdministratorEnabled: finalSettings.enableDelegatedAdministrator.toString(),
|
|
18
|
+
isMpaEnabled: finalSettings.enableMpa.toString()
|
|
19
|
+
};
|
|
20
|
+
await client.send(new UpdateGlobalSettingsCommand({
|
|
21
|
+
GlobalSettings: globalSettings
|
|
22
|
+
}));
|
|
23
|
+
return success(undefined);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return failure(new Error(`Failed to update backup global settings: ${error instanceof Error ? error.message : String(error)}`));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type CostExplorerClient } from "@aws-sdk/client-cost-explorer";
|
|
2
|
+
import { type Result } from "@fjall/generator";
|
|
3
|
+
export interface CostAllocationTag {
|
|
4
|
+
TagKey: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Activate cost allocation tags for billing tracking.
|
|
8
|
+
* Idempotent — activating already-active tags is a no-op.
|
|
9
|
+
*
|
|
10
|
+
* Returns success with no action if the tags array is empty.
|
|
11
|
+
*/
|
|
12
|
+
export declare function activateCostAllocationTags(client: CostExplorerClient, tags: CostAllocationTag[]): Promise<Result<void>>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { UpdateCostAllocationTagsStatusCommand } from "@aws-sdk/client-cost-explorer";
|
|
2
|
+
import { success, failure } from "@fjall/generator";
|
|
3
|
+
/**
|
|
4
|
+
* Activate cost allocation tags for billing tracking.
|
|
5
|
+
* Idempotent — activating already-active tags is a no-op.
|
|
6
|
+
*
|
|
7
|
+
* Returns success with no action if the tags array is empty.
|
|
8
|
+
*/
|
|
9
|
+
export async function activateCostAllocationTags(client, tags) {
|
|
10
|
+
if (tags.length === 0) {
|
|
11
|
+
return success(undefined);
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const costAllocationTagsStatus = tags.map((tag) => ({
|
|
15
|
+
TagKey: tag.TagKey,
|
|
16
|
+
Status: "Active"
|
|
17
|
+
}));
|
|
18
|
+
await client.send(new UpdateCostAllocationTagsStatusCommand({
|
|
19
|
+
CostAllocationTagsStatus: costAllocationTagsStatus
|
|
20
|
+
}));
|
|
21
|
+
return success(undefined);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return failure(new Error(`Failed to activate cost allocation tags: ${error instanceof Error ? error.message : String(error)}`));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type SSOAdminClient } from "@aws-sdk/client-sso-admin";
|
|
2
|
+
import { type Result } from "@fjall/generator";
|
|
3
|
+
import type { IdentityCentreStatus } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Check if AWS Identity Centre (SSO) is enabled.
|
|
6
|
+
* Returns the number of SSO instances found.
|
|
7
|
+
*/
|
|
8
|
+
export declare function checkIdentityCentreStatus(client: SSOAdminClient): Promise<Result<IdentityCentreStatus>>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ListInstancesCommand } from "@aws-sdk/client-sso-admin";
|
|
2
|
+
import { success, failure } from "@fjall/generator";
|
|
3
|
+
/**
|
|
4
|
+
* Check if AWS Identity Centre (SSO) is enabled.
|
|
5
|
+
* Returns the number of SSO instances found.
|
|
6
|
+
*/
|
|
7
|
+
export async function checkIdentityCentreStatus(client) {
|
|
8
|
+
try {
|
|
9
|
+
const response = await client.send(new ListInstancesCommand({}));
|
|
10
|
+
const instances = response.Instances ?? [];
|
|
11
|
+
return success({
|
|
12
|
+
enabled: instances.length > 0,
|
|
13
|
+
instanceCount: instances.length
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
return failure(new Error(`Failed to check Identity Centre status: ${error instanceof Error ? error.message : String(error)}`));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type { OrgDetails, OUMap, AccountPlacementResult, AccountInfo, IdentityCentreStatus } from "./types.js";
|
|
2
|
+
export { extractErrorName } from "./types.js";
|
|
3
|
+
export type { BackupGlobalSettings } from "./backup.js";
|
|
4
|
+
export type { CostAllocationTag } from "./costAllocation.js";
|
|
5
|
+
export type { CreateAccountResult } from "./accounts.js";
|
|
6
|
+
export { ensureOrganisationExists, describeOrganisation } from "./organisation.js";
|
|
7
|
+
export { enablePolicyTypes } from "./policies.js";
|
|
8
|
+
export { enableServiceAccess } from "./serviceAccess.js";
|
|
9
|
+
export { enableRamSharing } from "./ram.js";
|
|
10
|
+
export { activateTrustedAccess } from "./trustedAccess.js";
|
|
11
|
+
export { enableIpamDelegatedAdmin } from "./ipam.js";
|
|
12
|
+
export { updateBackupGlobalSettings } from "./backup.js";
|
|
13
|
+
export { listAccounts, findAccount, createAccount } from "./accounts.js";
|
|
14
|
+
export { ensureOrganisationalUnitsExist, placeAccountsInOUs } from "./organisationalUnits.js";
|
|
15
|
+
export { activateCostAllocationTags } from "./costAllocation.js";
|
|
16
|
+
export { checkIdentityCentreStatus } from "./identityCentre.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { extractErrorName } from "./types.js";
|
|
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";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type EC2Client } from "@aws-sdk/client-ec2";
|
|
2
|
+
import { type Result } from "@fjall/generator";
|
|
3
|
+
/**
|
|
4
|
+
* Enable IPAM delegated administrator for the given account.
|
|
5
|
+
* Idempotent — calling when already delegated is a no-op.
|
|
6
|
+
*/
|
|
7
|
+
export declare function enableIpamDelegatedAdmin(client: EC2Client, delegatedAdminAccountId: string): Promise<Result<void>>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EnableIpamOrganizationAdminAccountCommand } from "@aws-sdk/client-ec2";
|
|
2
|
+
import { success, failure } from "@fjall/generator";
|
|
3
|
+
/**
|
|
4
|
+
* Enable IPAM delegated administrator for the given account.
|
|
5
|
+
* Idempotent — calling when already delegated is a no-op.
|
|
6
|
+
*/
|
|
7
|
+
export async function enableIpamDelegatedAdmin(client, delegatedAdminAccountId) {
|
|
8
|
+
try {
|
|
9
|
+
await client.send(new EnableIpamOrganizationAdminAccountCommand({
|
|
10
|
+
DryRun: false,
|
|
11
|
+
DelegatedAdminAccountId: delegatedAdminAccountId
|
|
12
|
+
}));
|
|
13
|
+
return success(undefined);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return failure(new Error(`Failed to enable IPAM delegation for account ${delegatedAdminAccountId}: ${error instanceof Error ? error.message : String(error)}`));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type OrganizationsClient } from "@aws-sdk/client-organizations";
|
|
2
|
+
import { type Result } from "@fjall/generator";
|
|
3
|
+
import { type OrgDetails } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Ensure an AWS Organisation exists, creating one if necessary.
|
|
6
|
+
* Idempotent — safe to call when the organisation already exists.
|
|
7
|
+
*/
|
|
8
|
+
export declare function ensureOrganisationExists(client: OrganizationsClient): Promise<Result<OrgDetails>>;
|
|
9
|
+
/**
|
|
10
|
+
* Describe the current AWS Organisation, returning null if none exists.
|
|
11
|
+
*/
|
|
12
|
+
export declare function describeOrganisation(client: OrganizationsClient): Promise<Result<OrgDetails | null>>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { DescribeOrganizationCommand, CreateOrganizationCommand, ListRootsCommand } from "@aws-sdk/client-organizations";
|
|
2
|
+
import { success, failure } from "@fjall/generator";
|
|
3
|
+
import { extractErrorName } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Ensure an AWS Organisation exists, creating one if necessary.
|
|
6
|
+
* Idempotent — safe to call when the organisation already exists.
|
|
7
|
+
*/
|
|
8
|
+
export async function ensureOrganisationExists(client) {
|
|
9
|
+
try {
|
|
10
|
+
let created = false;
|
|
11
|
+
// Try to describe existing organisation
|
|
12
|
+
let org = await describeOrganisationRaw(client);
|
|
13
|
+
if (!org) {
|
|
14
|
+
// Create new organisation with all features
|
|
15
|
+
try {
|
|
16
|
+
const createResponse = await client.send(new CreateOrganizationCommand({ FeatureSet: "ALL" }));
|
|
17
|
+
org = createResponse.Organization ?? null;
|
|
18
|
+
created = true;
|
|
19
|
+
}
|
|
20
|
+
catch (createError) {
|
|
21
|
+
const errorName = extractErrorName(createError);
|
|
22
|
+
if (errorName === "AlreadyInOrganizationException") {
|
|
23
|
+
// Race condition — another process created it
|
|
24
|
+
org = await describeOrganisationRaw(client);
|
|
25
|
+
if (!org) {
|
|
26
|
+
return failure(new Error("Account is already in an organisation but DescribeOrganization returned nothing"));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
throw createError;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (!org?.Id || !org.MasterAccountId) {
|
|
35
|
+
return failure(new Error("Organisation is missing required fields (Id or MasterAccountId)"));
|
|
36
|
+
}
|
|
37
|
+
// Get root ID
|
|
38
|
+
const rootsResponse = await client.send(new ListRootsCommand({}));
|
|
39
|
+
const roots = rootsResponse.Roots ?? [];
|
|
40
|
+
if (roots.length === 0 || !roots[0]?.Id) {
|
|
41
|
+
return failure(new Error("No organisation root found"));
|
|
42
|
+
}
|
|
43
|
+
return success({
|
|
44
|
+
orgId: org.Id,
|
|
45
|
+
rootId: roots[0].Id,
|
|
46
|
+
managementAccountId: org.MasterAccountId,
|
|
47
|
+
created
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return failure(new Error(`Failed to ensure organisation exists: ${error instanceof Error ? error.message : String(error)}`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Describe the current AWS Organisation, returning null if none exists.
|
|
56
|
+
*/
|
|
57
|
+
export async function describeOrganisation(client) {
|
|
58
|
+
try {
|
|
59
|
+
const org = await describeOrganisationRaw(client);
|
|
60
|
+
if (!org) {
|
|
61
|
+
return success(null);
|
|
62
|
+
}
|
|
63
|
+
if (!org.Id || !org.MasterAccountId) {
|
|
64
|
+
return failure(new Error("Organisation is missing required fields (Id or MasterAccountId)"));
|
|
65
|
+
}
|
|
66
|
+
const rootsResponse = await client.send(new ListRootsCommand({}));
|
|
67
|
+
const roots = rootsResponse.Roots ?? [];
|
|
68
|
+
if (roots.length === 0 || !roots[0]?.Id) {
|
|
69
|
+
return failure(new Error("No organisation root found"));
|
|
70
|
+
}
|
|
71
|
+
return success({
|
|
72
|
+
orgId: org.Id,
|
|
73
|
+
rootId: roots[0].Id,
|
|
74
|
+
managementAccountId: org.MasterAccountId,
|
|
75
|
+
created: false
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return failure(new Error(`Failed to describe organisation: ${error instanceof Error ? error.message : String(error)}`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function describeOrganisationRaw(client) {
|
|
83
|
+
try {
|
|
84
|
+
const response = await client.send(new DescribeOrganizationCommand({}));
|
|
85
|
+
return response.Organization ?? null;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const errorName = extractErrorName(error);
|
|
89
|
+
if (errorName === "AWSOrganizationsNotInUseException") {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type OrganizationsClient } from "@aws-sdk/client-organizations";
|
|
2
|
+
import { type Result } from "@fjall/generator";
|
|
3
|
+
import { type OUMap, type AccountInfo, type AccountPlacementResult } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Ensure the specified organisational units exist under the root.
|
|
6
|
+
* Creates any missing OUs. Returns a map of OU name (lowercase) → OU ID.
|
|
7
|
+
*
|
|
8
|
+
* Idempotent — existing OUs are adopted, not duplicated.
|
|
9
|
+
*
|
|
10
|
+
* @param ouNames Array of OU names to ensure exist (will be capitalised)
|
|
11
|
+
*/
|
|
12
|
+
export declare function ensureOrganisationalUnitsExist(client: OrganizationsClient, rootId: string, ouNames: string[]): Promise<Result<OUMap>>;
|
|
13
|
+
/**
|
|
14
|
+
* Place accounts into the correct organisational units.
|
|
15
|
+
* Skips accounts with environment "root" and accounts already in the target OU.
|
|
16
|
+
*
|
|
17
|
+
* Idempotent — accounts already in the correct OU are counted but not moved.
|
|
18
|
+
*/
|
|
19
|
+
export declare function placeAccountsInOUs(client: OrganizationsClient, ouMap: OUMap, accounts: AccountInfo[]): Promise<Result<AccountPlacementResult>>;
|