diff --git a/.doc_gen/metadata/controltower_metadata.yaml b/.doc_gen/metadata/controltower_metadata.yaml index 083079b4db1..c603f788b78 100644 --- a/.doc_gen/metadata/controltower_metadata.yaml +++ b/.doc_gen/metadata/controltower_metadata.yaml @@ -4,6 +4,15 @@ controltower_Hello: synopsis: get started using &CTower;. category: Hello languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.hello.main Python: versions: - sdk_version: 3 @@ -25,6 +34,15 @@ controltower_Hello: controltower_ListBaselines: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.list_baselines.main Python: versions: - sdk_version: 3 @@ -47,6 +65,15 @@ controltower_ListBaselines: controltower_ListEnabledBaselines: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.list_enabled_baselines.main Python: versions: - sdk_version: 3 @@ -69,6 +96,15 @@ controltower_ListEnabledBaselines: controltower_EnableBaseline: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.enable_baseline.main Python: versions: - sdk_version: 3 @@ -91,6 +127,15 @@ controltower_EnableBaseline: controltower_ResetEnabledBaseline: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.reset_enabled_baseline.main Python: versions: - sdk_version: 3 @@ -113,6 +158,15 @@ controltower_ResetEnabledBaseline: controltower_DisableBaseline: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.disable_baseline.main Python: versions: - sdk_version: 3 @@ -135,6 +189,15 @@ controltower_DisableBaseline: controltower_ListEnabledControls: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.list_enabled_controls.main Python: versions: - sdk_version: 3 @@ -157,6 +220,15 @@ controltower_ListEnabledControls: controltower_EnableControl: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.enable_control.main Python: versions: - sdk_version: 3 @@ -179,6 +251,15 @@ controltower_EnableControl: controltower_GetControlOperation: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.get_control_operation.main Python: versions: - sdk_version: 3 @@ -201,6 +282,15 @@ controltower_GetControlOperation: controltower_DisableControl: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.disable_control.main Python: versions: - sdk_version: 3 @@ -223,6 +313,15 @@ controltower_DisableControl: controltower_ListLandingZones: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.list_landing_zones.main Python: versions: - sdk_version: 3 @@ -245,6 +344,15 @@ controltower_ListLandingZones: controltower_GetBaselineOperation: languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: + snippet_tags: + - controltower.java2.get_baseline_operation.main Python: versions: - sdk_version: 3 @@ -272,6 +380,16 @@ controltower_Scenario: - List, enable, get, and disable controls. category: Basics languages: + Java: + versions: + - sdk_version: 2 + github: javav2/example_code/controltower + sdkguide: + excerpts: + - description: Run an interactive scenario demonstrating &CTowerlong; features. + snippet_tags: + - controltower.java2.controltower_scenario.main + - controltower.java2.controltower_actions.main Python: versions: - sdk_version: 3 diff --git a/javav2/example_code/controltower/README.md b/javav2/example_code/controltower/README.md new file mode 100644 index 00000000000..86b1e0139ee --- /dev/null +++ b/javav2/example_code/controltower/README.md @@ -0,0 +1,119 @@ +# AWS Control Tower code examples for the SDK for Java 2.x + +## Overview + +Shows how to use the AWS SDK for Java 2.x to work with AWS Control Tower. + + + + +_AWS Control Tower enables you to enforce and manage governance rules for security, operations, and compliance at scale across all your organizations and accounts._ + +## ⚠ Important + +* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/). +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + + + + +## Code examples + +### Prerequisites + +For prerequisites, see the [README](../../README.md#Prerequisites) in the `javav2` folder. + + + + + +### Get started + +- [Hello AWS Control Tower](src/main/java/com/example/controltower/HelloControlTower.java#L27) (`ListBaselines`) + + +### Basics + +Code examples that show you how to perform the essential operations within a service. + +- [Learn the basics](src/main/java/com/example/controltower/scenario/ControlTowerActions.java) + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [DisableBaseline](src/main/java/com/example/controltower/scenario/ControlTowerActions.java#L493) +- [DisableControl](src/main/java/com/example/controltower/scenario/ControlTowerActions.java#L803) +- [EnableBaseline](src/main/java/com/example/controltower/scenario/ControlTowerActions.java#L387) +- [EnableControl](src/main/java/com/example/controltower/scenario/ControlTowerActions.java#L706) +- [GetBaselineOperation](src/main/java/com/example/controltower/scenario/ControlTowerActions.java#L574) +- [GetControlOperation](src/main/java/com/example/controltower/scenario/ControlTowerActions.java#L886) +- [ListBaselines](src/main/java/com/example/controltower/scenario/ControlTowerActions.java#L257) +- [ListEnabledBaselines](src/main/java/com/example/controltower/scenario/ControlTowerActions.java#L317) +- [ListEnabledControls](src/main/java/com/example/controltower/scenario/ControlTowerActions.java#L638) +- [ListLandingZones](src/main/java/com/example/controltower/scenario/ControlTowerActions.java#L204) +- [ResetEnabledBaseline](src/main/java/com/example/controltower/scenario/ControlTowerActions.java#L986) + + + + + +## Run the examples + +### Instructions + + + + + +#### Hello AWS Control Tower + +This example shows you how to get started using AWS Control Tower. + + +#### Learn the basics + +This example shows you how to do the following: + +- List landing zones. +- List, enable, get, reset, and disable baselines. +- List, enable, get, and disable controls. + + + + + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../../README.md#Tests) +in the `javav2` folder. + + + + + + +## Additional resources + +- [AWS Control Tower User Guide](https://docs.aws.amazon.com/controltower/latest/userguide/what-is-control-tower.html) +- [AWS Control Tower API Reference](https://docs.aws.amazon.com/controltower/latest/APIReference/Welcome.html) +- [SDK for Java 2.x AWS Control Tower reference](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/controltower/package-summary.html) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 diff --git a/javav2/example_code/controltower/pom.xml b/javav2/example_code/controltower/pom.xml new file mode 100644 index 00000000000..7dab4008da6 --- /dev/null +++ b/javav2/example_code/controltower/pom.xml @@ -0,0 +1,115 @@ + + + 4.0.0 + aws.example + controltower + 1.0-SNAPSHOT + + 17 + 17 + UTF-8 + + + + + software.amazon.awssdk + bom + 2.28.11 + pom + import + + + + org.apache.logging.log4j + log4j-bom + 2.23.1 + pom + import + + + + + + software.amazon.awssdk + controltower + + + software.amazon.awssdk + controlcatalog + + + software.amazon.awssdk + netty-nio-client + + + software.amazon.awssdk + aws-crt-client + 2.25.35 + + + + software.amazon.awssdk + organizations + + + + software.amazon.awssdk + url-connection-client + 2.36.2 + compile + + + org.slf4j + slf4j-api + 2.0.13 + + + org.apache.logging.log4j + log4j-core + + + org.apache.logging.log4j + log4j-slf4j2-impl + + + software.amazon.awssdk + sdk-core + + + org.junit.jupiter + junit-jupiter-engine + 5.10.0 + test + + + org.junit.jupiter + junit-jupiter-api + 5.10.0 + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + com.example.controltower.scenario.ControlTowerScenario + us-east-1 + + + + + \ No newline at end of file diff --git a/javav2/example_code/controltower/src/main/java/com/example/controltower/HelloControlTower.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/HelloControlTower.java new file mode 100644 index 00000000000..fadc5141fac --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/HelloControlTower.java @@ -0,0 +1,75 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.example.controltower; + +import software.amazon.awssdk.services.controltower.ControlTowerClient; +import software.amazon.awssdk.services.controltower.model.ControlTowerException; +import software.amazon.awssdk.services.controltower.model.ListBaselinesRequest; +import software.amazon.awssdk.services.controltower.paginators.ListBaselinesIterable; +import java.util.ArrayList; +import java.util.List; + +/** + * Before running this Java V2 code example, set up your development + * environment, including your credentials. + * + * For more information, see the following documentation topic: + * + * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html + * + * Use the AWS SDK for Java (v2) to create an AWS Control Tower client + * and list all available baselines. + * This example uses the default settings specified in your shared credentials + * and config files. + */ + +// snippet-start:[controltower.java2.hello.main] +public class HelloControlTower { + + public static void main(String[] args) { + try { + ControlTowerClient controlTowerClient = ControlTowerClient.builder() + .build() ; + helloControlTower(controlTowerClient); + } catch (ControlTowerException e) { + System.out.println("Control Tower error occurred: " + e.awsErrorDetails().errorMessage()); + } + } + + /** + * Use the AWS SDK for Java (v2) to create an AWS Control Tower client + * and list all available baselines. + * This example uses the default settings specified in your shared credentials + * and config files. + * + * @param controlTowerClient A ControlTowerClient object. This object wraps + * the low-level AWS Control Tower service API. + */ + public static void helloControlTower(ControlTowerClient controlTowerClient) { + System.out.println("Hello, AWS Control Tower! Let's list available baselines:\n"); + + ListBaselinesIterable paginator = controlTowerClient.listBaselinesPaginator( + ListBaselinesRequest.builder().build()); + List baselineNames = new ArrayList<>(); + + try { + paginator.stream() + .flatMap(response -> response.baselines().stream()) + .forEach(baseline -> baselineNames.add(baseline.name())); + + System.out.println(baselineNames.size() + " baseline(s) retrieved."); + for (String baselineName : baselineNames) { + System.out.println("\t" + baselineName); + } + + } catch (ControlTowerException e) { + if ("AccessDeniedException".equals(e.awsErrorDetails().errorCode())) { + System.out.println("Access denied. Please ensure you have the necessary permissions."); + } else { + System.out.println("An error occurred: " + e.getMessage()); + } + } + } +} +// snippet-end:[controltower.java2.hello.main] diff --git a/javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerActions.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerActions.java new file mode 100644 index 00000000000..644a8b49192 --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerActions.java @@ -0,0 +1,1064 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.example.controltower.scenario; + +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.crt.AwsCrtAsyncHttpClient; +import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; +import software.amazon.awssdk.services.controlcatalog.ControlCatalogAsyncClient; +import software.amazon.awssdk.services.controltower.ControlTowerAsyncClient; +import software.amazon.awssdk.services.controltower.model.*; +import software.amazon.awssdk.services.controltower.paginators.ListBaselinesPublisher; +import software.amazon.awssdk.services.controltower.paginators.ListEnabledBaselinesPublisher; +import software.amazon.awssdk.services.controltower.paginators.ListEnabledControlsPublisher; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.controlcatalog.model.ControlSummary; +import software.amazon.awssdk.services.controlcatalog.model.ListControlsRequest; +import software.amazon.awssdk.services.controlcatalog.paginators.ListControlsPublisher; +import software.amazon.awssdk.services.controltower.paginators.ListLandingZonesPublisher; +import software.amazon.awssdk.services.organizations.OrganizationsAsyncClient; +import software.amazon.awssdk.services.organizations.model.CreateOrganizationRequest; +import software.amazon.awssdk.services.organizations.model.ListOrganizationalUnitsForParentRequest; +import software.amazon.awssdk.services.organizations.model.Organization; +import software.amazon.awssdk.services.organizations.model.OrganizationFeatureSet; +import software.amazon.awssdk.services.organizations.model.OrganizationalUnit; +import software.amazon.awssdk.services.organizations.paginators.ListOrganizationalUnitsForParentPublisher; +import java.time.Duration; +import java.util.List; +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Before running this Java V2 code example, set up your development + * environment, including your credentials. + *

+ * For more information, see the following documentation topic: + *

+ * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html + *

+ * This Java code example shows how to perform AWS Control Tower operations. + */ + +// snippet-start:[controltower.java2.controltower_actions.main] +public class ControlTowerActions { + private static ControlCatalogAsyncClient controlCatalogAsyncClient; + private static ControlTowerAsyncClient controlTowerAsyncClient; + private static OrganizationsAsyncClient orgAsyncClient; + + private static OrganizationsAsyncClient getAsyncOrgClient() { + if (orgAsyncClient == null) { + SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient.builder() + .maxConcurrency(50) + .connectionTimeout(Duration.ofSeconds(60)) + .readTimeout(Duration.ofSeconds(60)) + .writeTimeout(Duration.ofSeconds(60)) + .build(); + + ClientOverrideConfiguration overrideConfig = ClientOverrideConfiguration.builder() + .apiCallTimeout(Duration.ofMinutes(2)) + .apiCallAttemptTimeout(Duration.ofSeconds(90)) + .build(); + + orgAsyncClient = OrganizationsAsyncClient.builder() + .httpClient(httpClient) + .overrideConfiguration(overrideConfig) + .build(); + } + return orgAsyncClient; + } + + private static ControlCatalogAsyncClient getAsyncCatClient() { + if (controlCatalogAsyncClient == null) { + SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient.builder() + .maxConcurrency(100) + .connectionTimeout(Duration.ofSeconds(60)) + .readTimeout(Duration.ofSeconds(60)) + .writeTimeout(Duration.ofSeconds(60)) + .build(); + + ClientOverrideConfiguration overrideConfig = ClientOverrideConfiguration.builder() + .apiCallTimeout(Duration.ofMinutes(2)) + .apiCallAttemptTimeout(Duration.ofSeconds(90)) + .retryStrategy(RetryMode.STANDARD) + .build(); + + controlCatalogAsyncClient = ControlCatalogAsyncClient.builder() + .httpClient(httpClient) + .overrideConfiguration(overrideConfig) + .build(); + } + return controlCatalogAsyncClient; + } + + private static ControlTowerAsyncClient getAsyncClient() { + if (controlTowerAsyncClient == null) { + + SdkAsyncHttpClient httpClient = + AwsCrtAsyncHttpClient.builder() + .maxConcurrency(100) + .connectionTimeout(Duration.ofSeconds(60)) + .build(); + + ClientOverrideConfiguration overrideConfig = + ClientOverrideConfiguration.builder() + .apiCallTimeout(Duration.ofMinutes(2)) + .apiCallAttemptTimeout(Duration.ofSeconds(90)) + .retryStrategy(RetryMode.STANDARD) + .build(); + + controlTowerAsyncClient = + ControlTowerAsyncClient.builder() + .httpClient(httpClient) + .overrideConfiguration(overrideConfig) + .build(); + } + + return controlTowerAsyncClient; + } + + public record OrgSetupResult(String orgId, String sandboxOuArn) { + } + + public CompletableFuture setupOrganizationAsync() { + System.out.println("Starting organization setup…"); + + OrganizationsAsyncClient client = getAsyncOrgClient(); + + // Step 1: Describe or create organization + CompletableFuture orgFuture = client.describeOrganization() + .thenApply(desc -> { + System.out.println("Organization exists: " + desc.organization().id()); + return desc.organization(); + }) + .exceptionallyCompose(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + if (cause instanceof AwsServiceException awsEx && + "AWSOrganizationsNotInUseException".equals(awsEx.awsErrorDetails().errorCode())) { + System.out.println("No organization found. Creating one…"); + return client.createOrganization(CreateOrganizationRequest.builder() + .featureSet(OrganizationFeatureSet.ALL) + .build()) + .thenApply(createResp -> { + System.out.println("Created organization: {}" + createResp.organization().id()); + return createResp.organization(); + }); + } + return CompletableFuture.failedFuture( + new CompletionException("Failed to describe or create organization", cause) + ); + }); + + // Step 2: Locate Sandbox OU + return orgFuture.thenCompose(org -> { + String orgId = org.id(); + System.out.println("Organization ID: {}" + orgId); + + return client.listRoots() + .thenCompose(rootsResp -> { + if (rootsResp.roots().isEmpty()) { + return CompletableFuture.failedFuture( + new RuntimeException("No root found in organization") + ); + } + String rootId = rootsResp.roots().get(0).id(); + + ListOrganizationalUnitsForParentRequest ouRequest = + ListOrganizationalUnitsForParentRequest.builder() + .parentId(rootId) + .build(); + + ListOrganizationalUnitsForParentPublisher paginator = + client.listOrganizationalUnitsForParentPaginator(ouRequest); + + AtomicReference sandboxOuArnRef = new AtomicReference<>(); + return paginator.subscribe(page -> { + for (OrganizationalUnit ou : page.organizationalUnits()) { + if ("Sandbox".equals(ou.name())) { + sandboxOuArnRef.set(ou.arn()); + System.out.println("Found Sandbox OU: " + ou.id()); + break; + } + } + }) + .thenApply(v -> { + String sandboxArn = sandboxOuArnRef.get(); + if (sandboxArn == null) { + System.out.println("Sandbox OU not found."); + } + return new OrgSetupResult(orgId, sandboxArn); + }); + }); + }).exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + System.out.println("Failed to setup organization: {}" + cause.getMessage()); + throw new CompletionException(cause); + }); + } + // snippet-start:[controltower.java2.list_landing_zones.main] + + /** + * Lists all landing zones using pagination to retrieve complete results. + * + * @return a list of all landing zones + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public CompletableFuture> listLandingZonesAsync() { + System.out.println("Starting list landing zones paginator…"); + + ListLandingZonesRequest request = ListLandingZonesRequest.builder().build(); + ListLandingZonesPublisher paginator = getAsyncClient().listLandingZonesPaginator(request); + List landingZones = new ArrayList<>(); + + return paginator.subscribe(response -> { + if (response.landingZones() != null && !response.landingZones().isEmpty()) { + response.landingZones().forEach(lz -> { + System.out.println("Landing zone ARN: " + lz.arn()); + landingZones.add(lz); + }); + } else { + System.out.println("Page contained no landing zones."); + } + }) + .thenRun(() -> System.out.println("Successfully retrieved "+ landingZones.size() + " landing zones." )) + .thenApply(v -> landingZones) + .exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "AccessDeniedException": + throw new CompletionException( + "Access denied when listing landing zones: " + e.getMessage(), e); + default: + throw new CompletionException( + "Error listing landing zones: " + e.getMessage(), e); + } + } + + if (cause instanceof SdkException) { + throw new CompletionException( + "SDK error listing landing zones: " + cause.getMessage(), cause); + } + + throw new CompletionException("Failed to list landing zones", cause); + }); + } + // snippet-end:[controltower.java2.list_landing_zones.main] + + // snippet-start:[controltower.java2.list_baselines.main] + + /** + * Lists all available baselines using pagination to retrieve complete results. + * + * @return a list of all baselines + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public CompletableFuture> listBaselinesAsync() { + System.out.println("Starting list baselines paginator…"); + ListBaselinesRequest request = ListBaselinesRequest.builder().build(); + ListBaselinesPublisher paginator = + getAsyncClient().listBaselinesPaginator(request); + + List baselines = new ArrayList<>(); + return paginator.subscribe(response -> { + if (response.baselines() != null && !response.baselines().isEmpty()) { + response.baselines().forEach(baseline -> { + baselines.add(baseline); + }); + } else { + System.out.println("Page contained no baselines."); + } + }) + .thenRun(() -> + System.out.println("Successfully listed baselines. Total: " + baselines.size()) + ) + .thenApply(v -> baselines) + .exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + + if ("AccessDeniedException".equals(errorCode)) { + throw new CompletionException( + "Access denied when listing baselines: %s".formatted(e.getMessage()), + e + ); + } + + throw new CompletionException( + "Error listing baselines: %s".formatted(e.getMessage()), + e + ); + } + + if (cause instanceof SdkException) { + throw new CompletionException( + "SDK error listing baselines: %s".formatted(cause.getMessage()), + cause + ); + } + + throw new CompletionException("Failed to list baselines", cause); + }); + } + // snippet-end:[controltower.java2.list_baselines.main] + + // snippet-start:[controltower.java2.list_enabled_baselines.main] + /** + * Lists all enabled baselines using pagination to retrieve complete results. + * + * @return a list of all enabled baselines + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public CompletableFuture> listEnabledBaselinesAsync() { + System.out.println("Starting list enabled baselines paginator…"); + + ListEnabledBaselinesRequest request = + ListEnabledBaselinesRequest.builder().build(); + + ListEnabledBaselinesPublisher paginator = + getAsyncClient().listEnabledBaselinesPaginator(request); + + List enabledBaselines = new ArrayList<>(); + return paginator.subscribe(response -> { + if (response.enabledBaselines() != null + && !response.enabledBaselines().isEmpty()) { + + response.enabledBaselines().forEach(baseline -> { + enabledBaselines.add(baseline); + }); + } else { + System.out.println("Page contained no enabled baselines."); + } + }) + .thenRun(() -> + System.out.println( + "Successfully listed enabled baselines. Total: " + + enabledBaselines.size() + ) + ) + .thenApply(v -> enabledBaselines) + .exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + + if ("AccessDeniedException".equals(errorCode)) { + throw new CompletionException( + "Access denied when listing enabled baselines: %s".formatted(e.getMessage()), e); + } + + throw new CompletionException( + "Error listing enabled baselines: %s" + .formatted(e.getMessage()), + e + ); + } + + if (cause instanceof SdkException) { + throw new CompletionException( + "SDK error listing enabled baselines: %s" + .formatted(cause.getMessage()), + cause + ); + } + + throw new CompletionException( + "Failed to list enabled baselines", + cause + ); + }); + } + // snippet-end:[controltower.java2.list_enabled_baselines.main] + + // snippet-start:[controltower.java2.enable_baseline.main] + + /** + * Asynchronously enables a baseline for the specified target if not already enabled. + * + * @param targetIdentifier The ARN of the target (OU or account). + * @param baselineIdentifier The baseline definition ARN to enable. + * @param baselineVersion The baseline version to enable. + * @return A CompletableFuture containing the enabled baseline ARN, or null if already enabled. + */ + public CompletableFuture enableBaselineAsync( + String targetIdentifier, + String baselineIdentifier, + String baselineVersion + ) { + EnableBaselineRequest request = EnableBaselineRequest.builder() + .baselineIdentifier(baselineIdentifier) + .baselineVersion(baselineVersion) + .targetIdentifier(targetIdentifier) + .build(); + + return getAsyncClient().enableBaseline(request) + .handle((resp, exception) -> { + if (exception != null) { + Throwable cause = exception.getCause() != null ? exception.getCause() : exception; + if (cause instanceof ControlTowerException e) { + String code = e.awsErrorDetails() != null ? e.awsErrorDetails().errorCode() : "UNKNOWN"; + String msg = e.awsErrorDetails() != null ? e.awsErrorDetails().errorMessage() : e.getMessage(); + + if ("ValidationException".equals(code) && msg.contains("already enabled")) { + System.out.println("Baseline is already enabled for this target → fetching ARN..."); + return fetchEnabledBaselineArn(targetIdentifier, baselineIdentifier) + .join(); // fetch existing ARN synchronously + } + + throw new RuntimeException("Error enabling baseline: " + code + " - " + msg, e); + } + + throw new RuntimeException("Unexpected error enabling baseline: " + cause.getMessage(), cause); + } + + return resp; + }) + .thenCompose(result -> { + if (result instanceof EnableBaselineResponse resp) { + String operationId = resp.operationIdentifier(); + String enabledBaselineArn = resp.arn(); + System.out.println("Baseline enable started. ARN: " + enabledBaselineArn + + ", operation ID: " + operationId); + + // Inline polling + return CompletableFuture.supplyAsync(() -> { + while (true) { + GetBaselineOperationRequest opReq = GetBaselineOperationRequest.builder() + .operationIdentifier(operationId) + .build(); + + GetBaselineOperationResponse opResp = getAsyncClient().getBaselineOperation(opReq).join(); + BaselineOperation op = opResp.baselineOperation(); + BaselineOperationStatus status = op.status(); + System.out.println("Operation " + operationId + " status: " + status); + + if (status == BaselineOperationStatus.SUCCEEDED) { + return enabledBaselineArn; + } else if (status == BaselineOperationStatus.FAILED) { + String opId = op.operationIdentifier(); + String reason = op.statusMessage() != null ? op.statusMessage() : "No failure reason provided"; + throw new RuntimeException("Baseline operation failed (ID: " + opId + "), status: " + + status + ", reason: " + reason); + } + + try { + Thread.sleep(Duration.ofSeconds(15).toMillis()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + }); + } else if (result instanceof String existingArn) { + // Already enabled branch + return CompletableFuture.completedFuture(existingArn); + } + + return CompletableFuture.completedFuture(null); + }); + } + + + /** + * Fetches the ARN of an already-enabled baseline for the target asynchronously. + */ + private CompletableFuture fetchEnabledBaselineArn(String targetIdentifier, String baselineIdentifier) { + return getAsyncClient().listEnabledBaselines(ListEnabledBaselinesRequest.builder().build()) + .thenApply(listResp -> { + for (EnabledBaselineSummary eb : listResp.enabledBaselines()) { + if (baselineIdentifier.equals(eb.baselineIdentifier()) + && targetIdentifier.equals(eb.targetIdentifier())) { + return eb.arn(); + } + } + return null; // not yet available + }); + } + // snippet-end:[controltower.java2.enable_baseline.main] + + // snippet-start:[controltower.java2.disable_baseline.main] + + /** + * Disables a baseline for a specified target. + * + * @param enabledBaselineIdentifier the identifier of the enabled baseline to disable + * @return the operation identifier + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public CompletableFuture disableBaselineAsync(String enabledBaselineIdentifier) { + + System.out.println("Starting disable of enabled baseline…"); + System.out.println("This operation will check the status every 15 seconds until it completes (SUCCEEDED or FAILED)."); + + DisableBaselineRequest request = DisableBaselineRequest.builder() + .enabledBaselineIdentifier(enabledBaselineIdentifier) + .build(); + + return getAsyncClient().disableBaseline(request) + .thenCompose(response -> { + String operationId = response.operationIdentifier(); + System.out.println("Disable baseline operation ID: " + operationId); + + // CompletableFuture that will be completed when operation finishes + CompletableFuture resultFuture = new CompletableFuture<>(); + + // Polling loop + Runnable poller = new Runnable() { + @Override + public void run() { + getBaselineOperationAsync(operationId) + .thenAccept(statusObj -> { + String status = statusObj.toString(); // Convert enum/status to string for printing + System.out.println("Current disable operation status: " + status + " → waiting for SUCCEEDED or FAILED..."); + + if ("SUCCEEDED".equalsIgnoreCase(status) || "FAILED".equalsIgnoreCase(status)) { + System.out.println("Disable operation finished with status: " + status); + resultFuture.complete(operationId); + } else { + // Schedule next poll in 15 seconds + CompletableFuture.delayedExecutor(15, TimeUnit.SECONDS) + .execute(this); + } + }) + .exceptionally(ex -> { + System.out.println("Error checking baseline operation status: " + ex.getMessage()); + resultFuture.completeExceptionally(ex); + return null; + }); + } + }; + + // Start first poll immediately + poller.run(); + + return resultFuture; + }) + .exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails() != null ? e.awsErrorDetails().errorCode() : "UNKNOWN"; + String errorMessage = e.awsErrorDetails() != null ? e.awsErrorDetails().errorMessage() : e.getMessage(); + + System.out.println("ControlTowerException caught while disabling baseline: Code=" + errorCode + ", Message=" + errorMessage); + return null; + } + + if (cause instanceof SdkException sdkEx) { + System.out.println("SDK exception caught while disabling baseline: " + sdkEx.getMessage()); + return null; + } + + System.out.println("Unexpected exception while disabling baseline: " + cause.getMessage()); + return null; + }); + } + + // snippet-end:[controltower.java2.disable_baseline.main] + + // snippet-start:[controltower.java2.get_baseline_operation.main] + + /** + * Gets the status of a baseline operation. + * + * @param operationIdentifier the identifier of the operation + * @return the operation status + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public CompletableFuture getBaselineOperationAsync( + String operationIdentifier) { + + GetBaselineOperationRequest request = GetBaselineOperationRequest.builder() + .operationIdentifier(operationIdentifier) + .build(); + + return getAsyncClient().getBaselineOperation(request) + .whenComplete((response, exception) -> { + if (exception != null) { + Throwable cause = exception.getCause() != null + ? exception.getCause() + : exception; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + + if ("ResourceNotFoundException".equals(errorCode)) { + throw new CompletionException( + "Baseline operation not found: %s" + .formatted(e.getMessage()), + e + ); + } + + throw new CompletionException( + "Error getting baseline operation status: %s" + .formatted(e.getMessage()), + e + ); + } + + if (cause instanceof SdkException) { + throw new CompletionException( + "SDK error getting baseline operation status: %s" + .formatted(cause.getMessage()), + cause + ); + } + + throw new CompletionException( + "Failed to get baseline operation status", + cause + ); + } + }) + .thenApply(response -> { + BaselineOperationStatus status = + response.baselineOperation().status(); + return status; + }); + } + // snippet-end:[controltower.java2.get_baseline_operation.main] + + // snippet-start:[controltower.java2.list_enabled_controls.main] + + /** + * Lists all enabled controls for a specific target using pagination. + * + * @param targetIdentifier the identifier of the target (e.g., OU ARN) + * @return a list of enabled controls + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public CompletableFuture> listEnabledControlsAsync(String targetIdentifier) { + System.out.println("Starting list enabled controls paginator for target " + targetIdentifier); + ListEnabledControlsRequest request = ListEnabledControlsRequest.builder() + .targetIdentifier(targetIdentifier) + .build(); + + ListEnabledControlsPublisher paginator = getAsyncClient().listEnabledControlsPaginator(request); + List enabledControls = new ArrayList<>(); + + // Subscribe to the paginator asynchronously + return paginator.subscribe(response -> { + if (response.enabledControls() != null && !response.enabledControls().isEmpty()) { + response.enabledControls().forEach(control -> { + enabledControls.add(control); + }); + } else { + System.out.println("Page contained no enabled controls."); + } + }) + .thenRun(() -> System.out.println( + "Successfully retrieved "+enabledControls.size() +" enabled controls for target "+targetIdentifier + )) + .thenApply(v -> enabledControls) + .exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + + switch (errorCode) { + case "AccessDeniedException": + throw new CompletionException( + "Access denied when listing enabled controls: %s".formatted(e.getMessage()), e); + + case "ResourceNotFoundException": + if (e.getMessage() != null && e.getMessage().contains("not registered with AWS Control Tower")) { + throw new CompletionException( + "Control Tower must be enabled to work with controls", e); + } + throw new CompletionException( + "Target not found when listing enabled controls: %s".formatted(e.getMessage()), e); + + default: + throw new CompletionException( + "Error listing enabled controls: %s".formatted(e.getMessage()), e); + } + } + + if (cause instanceof SdkException) { + throw new CompletionException( + "SDK error listing enabled controls: %s".formatted(cause.getMessage()), cause); + } + + throw new CompletionException("Failed to list enabled controls", cause); + }); + } + // snippet-end:[controltower.java2.list_enabled_controls.main] + + // snippet-start:[controltower.java2.enable_control.main] + + /** + * Enables a control for a specified target. + * + * @param controlIdentifier the identifier of the control to enable + * @param targetIdentifier the identifier of the target (e.g., OU ARN) + * @return the operation identifier + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public CompletableFuture enableControlAsync( + String controlIdentifier, + String targetIdentifier) { + + EnableControlRequest request = EnableControlRequest.builder() + .controlIdentifier(controlIdentifier) + .targetIdentifier(targetIdentifier) + .build(); + + return getAsyncClient().enableControl(request) + .thenCompose(response -> { + String operationId = response.operationIdentifier(); + System.out.println("Enable control operation started. Operation ID: " + operationId); + + CompletableFuture resultFuture = new CompletableFuture<>(); + + Runnable poller = new Runnable() { + @Override + public void run() { + getControlOperationAsync(operationId) + .thenAccept(status -> { + System.out.println("Control operation status: " + status); + + if (status == ControlOperationStatus.SUCCEEDED + || status == ControlOperationStatus.FAILED) { + resultFuture.complete(operationId); + } else { + // Poll again after 30 seconds + CompletableFuture.delayedExecutor(30, TimeUnit.SECONDS) + .execute(this); + } + }) + .exceptionally(ex -> { + resultFuture.completeExceptionally(ex); + return null; + }); + } + }; + + // Start polling immediately + poller.run(); + + return resultFuture; + }) + .exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + String message = e.getMessage() != null ? e.getMessage() : ""; + + if ("ValidationException".equals(errorCode) + && message.contains("already enabled")) { + System.out.println("Control is already enabled for this target"); + return null; + } + + if ("ResourceNotFoundException".equals(errorCode) + && message.contains("not registered with AWS Control Tower")) { + System.out.println( + "Control Tower must be enabled to work with controls."); + return null; + } + + throw new CompletionException( + "Couldn't enable control: %s".formatted(message), + e + ); + } + + if (cause instanceof SdkException) { + throw new CompletionException( + "SDK error enabling control: %s" + .formatted(cause.getMessage()), + cause + ); + } + + throw new CompletionException( + "Failed to enable control", + cause + ); + }); + } + // snippet-end:[controltower.java2.enable_control.main] + + // snippet-start:[controltower.java2.disable_control.main] + + /** + * Disables a control for a specified target. + * + * @param controlIdentifier the identifier of the control to disable + * @param targetIdentifier the identifier of the target (e.g., OU ARN) + * @return the operation identifier + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public CompletableFuture disableControlAsync( + String controlIdentifier, + String targetIdentifier) { + + DisableControlRequest request = DisableControlRequest.builder() + .controlIdentifier(controlIdentifier) + .targetIdentifier(targetIdentifier) + .build(); + + return getAsyncClient().disableControl(request) + .thenCompose(response -> { + String operationId = response.operationIdentifier(); + System.out.println("Disable control operation started. Operation ID: " + operationId); + + CompletableFuture resultFuture = new CompletableFuture<>(); + + Runnable poller = new Runnable() { + @Override + public void run() { + getControlOperationAsync(operationId) + .thenAccept(status -> { + System.out.println("Control operation status: " + status); + + if (status == ControlOperationStatus.SUCCEEDED + || status == ControlOperationStatus.FAILED) { + resultFuture.complete(operationId); + } else { + // poll again after 30 seconds + CompletableFuture.delayedExecutor(30, TimeUnit.SECONDS) + .execute(this); + } + }) + .exceptionally(ex -> { + resultFuture.completeExceptionally(ex); + return null; + }); + } + }; + + // start polling immediately + poller.run(); + + return resultFuture; + }) + .exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + + if ("ResourceNotFoundException".equals(errorCode)) { + // SPEC: notify user and continue + System.out.println("Control not found for disabling: " + e.getMessage()); + return null; + } + + throw new CompletionException( + "Error disabling control: " + e.getMessage(), e); + } + + if (cause instanceof SdkException) { + throw new CompletionException( + "SDK error disabling control: " + cause.getMessage(), cause); + } + + throw new CompletionException( + "Failed to disable control", cause); + }); + } + + // snippet-end:[controltower.java2.disable_control.main] + + // snippet-start:[controltower.java2.get_control_operation.main] + + /** + * Gets the status of a control operation. + * + * @param operationIdentifier the identifier of the operation + * @return the operation status + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public CompletableFuture getControlOperationAsync( + String operationIdentifier) { + + GetControlOperationRequest request = GetControlOperationRequest.builder() + .operationIdentifier(operationIdentifier) + .build(); + + return getAsyncClient().getControlOperation(request) + .whenComplete((response, exception) -> { + if (exception != null) { + Throwable cause = exception.getCause() != null ? exception.getCause() : exception; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + + if ("ResourceNotFoundException".equals(errorCode)) { + throw new CompletionException( + "Control operation not found: %s".formatted(e.getMessage()), + e + ); + } + + throw new CompletionException( + "Error getting control operation status: %s".formatted(e.getMessage()), + e + ); + } + + if (cause instanceof SdkException) { + throw new CompletionException( + "SDK error getting control operation status: %s".formatted(cause.getMessage()), + cause + ); + } + + throw new CompletionException("Failed to get control operation status", cause); + } + }) + .thenApply(response -> response.controlOperation().status()); + } + // snippet-end:[controltower.java2.get_control_operation.main] + + // snippet-start:[controltower.java2.list_controls.main] + + /** + * Lists all controls in the Control Tower control catalog. + * + * @return a list of controls + * @throws SdkException if a service-specific error occurs + */ + public CompletableFuture> listControlsAsync() { + System.out.println("Starting list controls paginator…"); + + ListControlsRequest request = ListControlsRequest.builder().build(); + ListControlsPublisher paginator = getAsyncCatClient().listControlsPaginator(request); + List controls = new ArrayList<>(); + + return paginator.subscribe(response -> { + if (response.controls() != null && !response.controls().isEmpty()) { + response.controls().forEach(control -> { + controls.add(control); + }); + } else { + System.out.println("Page contained no controls."); + } + }) + .thenRun(() -> System.out.println("Successfully retrieved " + controls.size() +" controls.")) + .thenApply(v -> controls) + .exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + + if (cause instanceof SdkException sdkEx) { + if (sdkEx.getMessage() != null && sdkEx.getMessage().contains("AccessDeniedException")) { + throw new CompletionException( + "Access denied when listing controls. Please ensure you have the necessary permissions.", + sdkEx + ); + } else { + throw new CompletionException( + "SDK error listing controls: %s".formatted(sdkEx.getMessage()), + sdkEx + ); + } + } + + throw new CompletionException("Failed to list controls", cause); + }); + } + // snippet-end:[controltower.java2.list_controls.main] + + // snippet-start:[controltower.java2.reset_enabled_baseline.main] + /** + * Resets an enabled baseline for a specific target. + * + * @param enabledBaselineIdentifier the identifier of the enabled baseline to reset + * @return the operation identifier + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public CompletableFuture resetEnabledBaselineAsync(String enabledBaselineIdentifier) { + + System.out.println("Starting reset of enabled baseline…"); + System.out.println("This operation will check the status every 15 seconds until it completes (SUCCEEDED or FAILED)."); + + ResetEnabledBaselineRequest request = ResetEnabledBaselineRequest.builder() + .enabledBaselineIdentifier(enabledBaselineIdentifier) + .build(); + + return getAsyncClient().resetEnabledBaseline(request) + .thenCompose(response -> { + String operationId = response.operationIdentifier(); + System.out.println("Reset enabled baseline operation ID: " + operationId); + + // Polling loop + CompletableFuture resultFuture = new CompletableFuture<>(); + + Runnable poller = new Runnable() { + @Override + public void run() { + getBaselineOperationAsync(operationId) + .thenAccept(statusObj -> { + String status = statusObj.toString(); // Convert enum/status to string for printing + System.out.println("Current baseline operation status: " + status + " → waiting for SUCCEEDED or FAILED..."); + + if ("SUCCEEDED".equalsIgnoreCase(status) || "FAILED".equalsIgnoreCase(status)) { + System.out.println("Baseline operation finished with status: " + status); + resultFuture.complete(operationId); + } else { + // Schedule next poll in 15 seconds + CompletableFuture.delayedExecutor(15, TimeUnit.SECONDS) + .execute(this); + } + }) + .exceptionally(ex -> { + System.out.println("Error checking baseline operation status: " + ex.getMessage()); + resultFuture.completeExceptionally(ex); + return null; + }); + } + }; + + // Start first poll immediately + poller.run(); + + return resultFuture; + }) + .exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails() != null ? e.awsErrorDetails().errorCode() : "UNKNOWN"; + String errorMessage = e.awsErrorDetails() != null ? e.awsErrorDetails().errorMessage() : e.getMessage(); + + System.out.println("ControlTowerException caught: Code=" + errorCode + ", Message=" + errorMessage); + return null; + } + + if (cause instanceof SdkException sdkEx) { + System.out.println("SDK exception caught: " + sdkEx.getMessage()); + return null; + } + + System.out.println("Unexpected exception resetting baseline: " + cause.getMessage()); + return null; + }); + } + // snippet-end:[controltower.java2.reset_enabled_baseline.main] +} +// snippet-end:[controltower.java2.controltower_actions.main] \ No newline at end of file diff --git a/javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerScenario.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerScenario.java new file mode 100644 index 00000000000..19eefa16350 --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerScenario.java @@ -0,0 +1,327 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.example.controltower.scenario; + +import software.amazon.awssdk.services.controlcatalog.ControlCatalogClient; +import software.amazon.awssdk.services.controlcatalog.model.ControlSummary; +import software.amazon.awssdk.services.controltower.model.*; +import software.amazon.awssdk.services.organizations.OrganizationsClient; + +import java.time.Duration; +import java.util.List; +import java.util.Scanner; +import java.util.concurrent.CompletionException; +import static java.lang.System.in; + +/** + * Before running this Java V2 code example, set up your development + * environment, including your credentials. + * + * For more information, see the following documentation topic: + * + * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html + * + * Use the AWS SDK for Java (v2) to create an AWS Control Tower client + * and list all available baselines. + * This example uses the default settings specified in your shared credentials + * and config files. + * + * In addition, set up a landing zone by following this documentation: + * + * https://docs.aws.amazon.com/controltower/latest/userguide/quick-start.html + */ + +// snippet-start:[controltower.java2.controltower_scenario.main] +public class ControlTowerScenario { + public static final String DASHES = new String(new char[80]).replace("\0", "-"); + private static final Scanner scanner = new Scanner(in); + + private static OrganizationsClient orgClient; + private static ControlCatalogClient catClient; + + private static String ouId = null; + private static String ouArn = null; + private static String landingZoneArn = null; + private static boolean useLandingZone = false; + + private String stack = null; + private String accountId = null; + + public static void main(String[] args) { + + System.out.println(DASHES); + System.out.println("Welcome to the AWS Control Tower basics scenario!"); + System.out.println(DASHES); + + try { + runScenarioAsync(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // ----------------------------- + // Utilities + // ----------------------------- + private static boolean askYesNo(String msg) { + System.out.println(msg); + return scanner.nextLine().trim().toLowerCase().startsWith("y"); + } + + private static void runScenarioAsync() { + try { + ControlTowerActions actions = new ControlTowerActions(); + + // ----------------------------- + // Step 1: Landing Zones + // ----------------------------- + System.out.println(DASHES); + System.out.println(""" + Some demo operations require the use of a landing zone. + You can use an existing landing zone or opt out of these operations in the demo. + For instructions on how to set up a landing zone, + see https://docs.aws.amazon.com/controltower/latest/userguide/getting-started-from-console.html + """); + + System.out.println("Step 1: Listing landing zones..."); + waitForInputToContinue(scanner); + + List landingZones = + actions.listLandingZonesAsync().join(); + + if (landingZones.isEmpty()) { + System.out.println("No landing zones found. Landing-zone-dependent steps will be skipped."); + useLandingZone = false; + waitForInputToContinue(scanner); + } else { + System.out.println("\nAvailable Landing Zones:"); + for (int i = 0; i < landingZones.size(); i++) { + System.out.printf("%d) %s%n", i + 1, landingZones.get(i).arn()); + } + + if (askYesNo("Do you want to use the first landing zone in the list (" + + landingZones.get(0).arn() + ")? (y/n): ")) { + useLandingZone = true; + landingZoneArn = landingZones.get(0).arn(); + } else if (askYesNo("Do you want to use a different existing Landing Zone for this demo? (y/n): ")) { + useLandingZone = true; + System.out.println("Enter landing zone ARN: "); + landingZoneArn = scanner.nextLine().trim(); + } else { + System.out.println("Proceeding without a landing zone."); + useLandingZone = false; + waitForInputToContinue(scanner); + } + } + + // ----------------------------- + // Setup Organization + Sandbox OU + // ----------------------------- + if (useLandingZone) { + System.out.println("Using landing zone ARN: " + landingZoneArn); + + ControlTowerActions.OrgSetupResult result = + actions.setupOrganizationAsync().join(); + + ouArn = result.sandboxOuArn(); + ouId = result.sandboxOuArn(); + + System.out.println("Organization ID: " + result.orgId()); + System.out.println("Using Sandbox OU ARN: " + ouArn); + } + + // ----------------------------- + // Step 2: Baselines + // ----------------------------- + System.out.println(DASHES); + System.out.println("Step 2: Listing available baselines..."); + System.out.println(""" +In this step, the program lists available AWS Control Tower baselines and may perform +baseline-related operations (enable, disable, reset) if requested. + +NOTE: +AWS Control Tower enforces governance through baselines and mandatory controls +(guardrails). Mandatory controls are required for landing zone governance and may +restrict certain operations depending on the account, region, or organizational policy. + +For more information, see: +- Types of baselines in AWS Control Tower: + https://docs.aws.amazon.com/controltower/latest/userguide/types-of-baselines.html +- Mandatory controls (guardrails) in AWS Control Tower: + https://docs.aws.amazon.com/controltower/latest/controlreference/mandatory-controls.html +- Baseline API examples: + https://docs.aws.amazon.com/controltower/latest/userguide/baseline-api-examples.html +"""); + + + + waitForInputToContinue(scanner); + List baselines = + actions.listBaselinesAsync().join(); + + BaselineSummary controlTowerBaseline = null; + for (BaselineSummary b : baselines) { + System.out.println("Baseline: " + b.name()); + System.out.println(" ARN: " + b.arn()); + if ("AWSControlTowerBaseline".equals(b.name())) { + controlTowerBaseline = b; + } + } + + waitForInputToContinue(scanner); + + if (useLandingZone && controlTowerBaseline != null) { + + System.out.println("\nListing enabled baselines:"); + List enabledBaselines = + actions.listEnabledBaselinesAsync().join(); + + String enabledBaselineArn = null; + for (EnabledBaselineSummary eb : enabledBaselines) { + System.out.println("Checking enabled baseline ARN: " + eb.arn()); + if (eb.baselineIdentifier().equals(controlTowerBaseline.arn())) { + enabledBaselineArn = eb.arn(); // correct enabled ARN for this baseline + break; // stop after finding the matching one + } + } + + if (enabledBaselineArn == null) { + System.out.println("No enabled baseline found for " + controlTowerBaseline.arn()); + } else { + System.out.println("Selected enabled baseline ARN for reset/disable: " + enabledBaselineArn); + } + + + // Enable the Baseline + if (askYesNo("Do you want to enable the Control Tower Baseline? (y/n): ")) { + System.out.println("\nEnabling Control Tower Baseline..."); + + String baselineId = controlTowerBaseline.arn(); + String enabledBaselineId = + actions.enableBaselineAsync( + ouArn, // targetIdentifier → the OU or account ARN + baselineId, // baselineIdentifier → the Control Tower baseline ARN + "5.0" // baselineVersion → version string + ).join(); + + + System.out.println("Enabled baseline operation ID: " + enabledBaselineId); + if (enabledBaselineId == null) { + enabledBaselineId = enabledBaselineArn; + } + + // Reset the Baseline + if (askYesNo("Do you want to reset the Control Tower Baseline? (y/n): ")) { + String operationId = + actions.resetEnabledBaselineAsync(enabledBaselineId).join(); + System.out.println("Reset baseline operation ID: " + operationId); + } + + if (askYesNo("Do you want to disable the Control Tower Baseline? (y/n): ")) { + String operationId = + actions.disableBaselineAsync(enabledBaselineId).join(); + System.out.println("Disabled baseline operation ID: " + operationId); + + System.out.println("Now we will re‑enable the baseline and wait 1 minute before making the call..."); + try { + Thread.sleep(Duration.ofMinutes(1).toMillis()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + System.out.println("Wait interrupted"); + } + String reEnabledBaselineId = actions.enableBaselineAsync( + ouArn, + baselineId, // reuse baseline definition ARN + "5.0" + ).join(); + + System.out.println("Re-enabled baseline operation ID: " + reEnabledBaselineId); + } + } + } + + // ----------------------------- + // Step 3: Controls + // ----------------------------- + System.out.println(DASHES); + System.out.println("Step 3: Managing Controls:"); + waitForInputToContinue(scanner); + + List controls = + actions.listControlsAsync().join(); + + System.out.println("\nListing first 5 available Controls:"); + for (int i = 0; i < Math.min(5, controls.size()); i++) { + ControlSummary c = controls.get(i); + System.out.println("%d. %s - %s".formatted(i + 1, c.name(), c.arn())); + } + + if (useLandingZone) { + waitForInputToContinue(scanner); + + List enabledControls = + actions.listEnabledControlsAsync(ouArn).join(); + + System.out.println("\nListing enabled controls:"); + for (int i = 0; i < enabledControls.size(); i++) { + System.out.println("%d. %s".formatted(i + 1, enabledControls.get(i).controlIdentifier())); + } + + String controlArnToEnable = null; + for (ControlSummary control : controls) { + boolean enabled = enabledControls.stream() + .anyMatch(ec -> ec.controlIdentifier().equals(control.arn())); + if (!enabled) { + controlArnToEnable = control.arn(); + break; + } + } + + waitForInputToContinue(scanner); + if (controlArnToEnable != null && + askYesNo("Do you want to enable the control " + controlArnToEnable + "? (y/n): ")) { + + String operationId = + actions.enableControlAsync(controlArnToEnable, ouArn).join(); + + System.out.println("Enabled control with operation ID: " + operationId); + } + + waitForInputToContinue(scanner); + + if (controlArnToEnable != null && + askYesNo("Do you want to disable the control? (y/n): ")) { + + String operationId = + actions.disableControlAsync(controlArnToEnable, ouArn).join(); + + System.out.println("Disable operation ID: " + operationId); + } + } + System.out.println("\nThis concludes the example scenario."); + System.out.println("Thanks for watching!"); + System.out.println(DASHES); + + } catch (CompletionException e) { + Throwable cause = e.getCause() != null ? e.getCause() : e; + System.out.println("Scenario failed: " + cause.getMessage()); + throw e; // bubble up for tests / callers + } catch (Exception e) { + System.out.println("Unexpected error running scenario: " + e.getMessage()); + throw new RuntimeException(e); + } + } + + private static void waitForInputToContinue(Scanner sc) { + System.out.println("\nEnter 'c' then to continue:"); + while (true) { + String input = sc.nextLine(); + if ("c".equalsIgnoreCase(input.trim())) { + System.out.println("Continuing..."); + break; + } + } + } +} +// snippet-end:[controltower.java2.controltower_scenario.main] \ No newline at end of file diff --git a/javav2/example_code/controltower/src/test/java/ControlTowerTest.java b/javav2/example_code/controltower/src/test/java/ControlTowerTest.java new file mode 100644 index 00000000000..27ddd65c2ac --- /dev/null +++ b/javav2/example_code/controltower/src/test/java/ControlTowerTest.java @@ -0,0 +1,136 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import com.example.controltower.HelloControlTower; +import com.example.controltower.scenario.ControlTowerActions; +import com.example.controltower.scenario.ControlTowerScenario; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.controlcatalog.model.ControlSummary; +import software.amazon.awssdk.services.controltower.ControlTowerClient; +import software.amazon.awssdk.services.controltower.model.BaselineSummary; +import software.amazon.awssdk.services.controltower.model.LandingZoneSummary; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@TestInstance(TestInstance.Lifecycle.PER_METHOD) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class ControlTowerTest { + private static ControlTowerClient controlTowerClient; + @BeforeAll + public static void setUp() { + controlTowerClient = ControlTowerClient.builder() + .region(Region.US_EAST_1) + .credentialsProvider(ProfileCredentialsProvider.create("default")) + .build(); + } + + @Test + @Order(1) + public void testHelloService() { + assertDoesNotThrow(() -> { + HelloControlTower.helloControlTower(controlTowerClient); + }); + System.out.println("Test 1 passed"); + } + + @Test + @Order(2) + public void testControlTowerActionsAsync() { + assertDoesNotThrow(() -> { + // Create an instance of the async actions class + ControlTowerActions actions = new ControlTowerActions(); + + // SAFE: read-only, no admin role required + List landingZones = actions.listLandingZonesAsync().join(); + List baselines = actions.listBaselinesAsync().join(); + List controls = actions.listControlsAsync().join(); + + // Simple sanity checks + assertNotNull(landingZones, "Landing zones list should not be null"); + assertNotNull(baselines, "Baselines list should not be null"); + assertNotNull(controls, "Controls list should not be null"); + + System.out.println("Landing Zones: " + landingZones.size()); + System.out.println("Baselines: " + baselines.size()); + System.out.println("Controls: " + controls.size()); + }); + + System.out.println("Test 2 passed"); + } + + + @Test + @Tag("IntegrationTest") + @Order(3) + public void testControlTowerScenarioEndToEnd() { + assertDoesNotThrow(() -> { + + // Build simulated input: + // - "c" for all waitForInputToContinue calls + // - "y"/"n" for Yes/No prompts + List simulatedInputs = new ArrayList<>(); + + // Step 1: Landing zone + simulatedInputs.add("c"); // waitForInput + simulatedInputs.add("y"); // use first landing zone + // If it asks for another landing zone, we choose no (automatically continue) + simulatedInputs.add("c"); // continue + + // Step 2: Baselines + simulatedInputs.add("c"); // waitForInput + simulatedInputs.add("c"); // waitForInput after listing baselines + simulatedInputs.add("y"); // enable baseline + simulatedInputs.add("y"); // reset baseline + simulatedInputs.add("n"); // disable baseline + simulatedInputs.add("c"); // continue for re-enable prompt + + // Step 3: Controls + simulatedInputs.add("c"); // waitForInput + simulatedInputs.add("c"); // continue after listing controls + simulatedInputs.add("y"); // enable first control + simulatedInputs.add("c"); // waitForInput before disable + simulatedInputs.add("n"); // do not disable control + + // Convert all to single input string + String simulatedInput = String.join("\n", simulatedInputs) + "\n"; + + InputStream originalIn = System.in; + PrintStream originalOut = System.out; + + try { + // Simulate user input + ByteArrayInputStream testIn = new ByteArrayInputStream(simulatedInput.getBytes()); + System.setIn(testIn); + + // Capture output (optional) + System.setOut(new PrintStream(new ByteArrayOutputStream())); + + // Run the scenario + ControlTowerScenario.main(new String[]{}); + + } finally { + // Restore original I/O + System.setIn(originalIn); + System.setOut(originalOut); + } + }); + + System.out.println("Test 3 (Control Tower scenario end-to-end) passed"); + } + +} \ No newline at end of file