diff --git a/.doc_gen/metadata/controltower_metadata.yaml b/.doc_gen/metadata/controltower_metadata.yaml index 083079b4db1..01f3fcfc2ab 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.list_landing_zones.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..09d4fcc4a63 --- /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#L28) (`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/ControlTowerActions.java) + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [DisableBaseline](src/main/java/com/example/controltower/ControlTowerActions.java#L241) +- [DisableControl](src/main/java/com/example/controltower/ControlTowerActions.java#L431) +- [EnableBaseline](src/main/java/com/example/controltower/ControlTowerActions.java#L188) +- [EnableControl](src/main/java/com/example/controltower/ControlTowerActions.java#L377) +- [GetBaselineOperation](src/main/java/com/example/controltower/ControlTowerActions.java#L38) +- [GetControlOperation](src/main/java/com/example/controltower/ControlTowerActions.java#L474) +- [ListBaselines](src/main/java/com/example/controltower/ControlTowerActions.java#L88) +- [ListEnabledBaselines](src/main/java/com/example/controltower/ControlTowerActions.java#L138) +- [ListEnabledControls](src/main/java/com/example/controltower/ControlTowerActions.java#L324) +- [ListLandingZones](src/main/java/com/example/controltower/ControlTowerActions.java#L38) +- [ResetEnabledBaseline](src/main/java/com/example/controltower/ControlTowerActions.java#L548) + + + + + +## 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..9fad0ea76a1 --- /dev/null +++ b/javav2/example_code/controltower/pom.xml @@ -0,0 +1,94 @@ + + + 4.0.0 + aws.example + controltower + 1.0-SNAPSHOT + + 17 + 17 + UTF-8 + + + + + software.amazon.awssdk + bom + 2.28.11 + 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 + + + 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..ea3c4081e1e --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/HelloControlTower.java @@ -0,0 +1,77 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.example.controltower; + +import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; +import software.amazon.awssdk.regions.Region; +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.err.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..676f81bfdf5 --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerActions.java @@ -0,0 +1,980 @@ +// 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.auth.credentials.ProfileCredentialsProvider; +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.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.format("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.format("Landing zone ARN: {}", lz.arn()); + landingZones.add(lz); + }); + } else { + System.out.println("Page contained no landing zones."); + } + }) + .thenRun(() -> System.out.println("Successfully retrieved {} landing zones."+ landingZones.size())) + .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.format("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 -> { + System.out.format("Baseline: {}", baseline.name()); + baselines.add(baseline); + }); + } else { + System.out.format("Page contained no baselines."); + } + }) + .thenRun(() -> + System.out.format("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.format("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 -> { + System.out.format("Enabled baseline: {}", baseline.baselineIdentifier()); + enabledBaselines.add(baseline); + }); + } else { + System.out.format("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 ("ResourceNotFoundException".equals(errorCode)) { + throw new CompletionException( + "Target not found 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] + /** + * Enables a baseline for a specified target. + * + * @param baselineIdentifier the identifier of the baseline to enable + * @param baselineVersion the version of the baseline 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 enableBaselineAsync( + String baselineIdentifier, + String baselineVersion, + String targetIdentifier) { + + EnableBaselineRequest request = EnableBaselineRequest.builder() + .baselineIdentifier(baselineIdentifier) + .baselineVersion(baselineVersion) + .targetIdentifier(targetIdentifier) + .build(); + + return getAsyncClient().enableBaseline(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(); + + switch (errorCode) { + case "ValidationException": + if (e.getMessage() != null + && e.getMessage().contains("already enabled")) { + throw new CompletionException( + "Baseline is already enabled for this target", + e + ); + } + throw new CompletionException( + "Validation error enabling baseline: " + e.getMessage(), + e + ); + + case "ConflictException": + throw new CompletionException( + "Conflict enabling baseline: " + e.getMessage(), + e + ); + + default: + throw new CompletionException( + "Error enabling baseline: " + e.getMessage(), + e + ); + } + } + + if (cause instanceof SdkException) { + throw new CompletionException( + "SDK error enabling baseline: " + cause.getMessage(), + cause + ); + } + + throw new CompletionException( + "Unexpected error enabling baseline: " + exception.getMessage(), + exception + ); + } + }) + .thenApply(response -> { + String operationId = response.operationIdentifier(); + return "Enabled baseline with operation ID: " + operationId; + }); + } + // 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) { + + DisableBaselineRequest request = DisableBaselineRequest.builder() + .enabledBaselineIdentifier(enabledBaselineIdentifier) + .build(); + + return getAsyncClient().disableBaseline(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(); + + switch (errorCode) { + case "ConflictException": + throw new CompletionException( + "Conflict disabling baseline: %s" + .formatted(e.getMessage()), + e + ); + + case "ResourceNotFoundException": + throw new CompletionException( + "Baseline not found for disabling: %s" + .formatted(e.getMessage()), + e + ); + + default: + throw new CompletionException( + "Error disabling baseline: %s" + .formatted(e.getMessage()), + e + ); + } + } + + if (cause instanceof SdkException) { + throw new CompletionException( + "SDK error disabling baseline: %s" + .formatted(cause.getMessage()), + cause + ); + } + + throw new CompletionException( + "Failed to disable baseline", + cause + ); + } + }) + .thenApply(response -> { + String operationId = response.operationIdentifier(); + return "Disabled baseline with operation ID: " + operationId; + }); + } + // 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.format("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 -> { + System.out.println("Enabled control: {}"+ control.controlIdentifier()); + enabledControls.add(control); + }); + } else { + System.out.println("Page contained no enabled controls."); + } + }) + .thenRun(() -> System.out.format( + "Successfully retrieved {} enabled controls for target {}", + enabledControls.size(), + 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) + .whenComplete((response, exception) -> { + if (exception != null) { + Throwable cause = exception.getCause() != null + ? exception.getCause() + : exception; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + + switch (errorCode) { + case "ValidationException": + if (e.getMessage() != null + && e.getMessage().contains("already enabled")) { + // Preserve sync behavior: treat as no-op + return; + } + + throw new CompletionException( + "Validation error enabling control: %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( + "Control not found: %s" + .formatted(e.getMessage()), + e + ); + + default: + throw new CompletionException( + "Error enabling control: %s" + .formatted(e.getMessage()), + 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 + ); + } + }) + .thenApply(response -> + response != null ? response.operationIdentifier() : null + ); + } + + // 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) + .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 not found for disabling: %s" + .formatted(e.getMessage()), + e + ); + } + + throw new CompletionException( + "Error disabling control: %s" + .formatted(e.getMessage()), + e + ); + } + + if (cause instanceof SdkException) { + throw new CompletionException( + "SDK error disabling control: %s" + .formatted(cause.getMessage()), + cause + ); + } + + throw new CompletionException( + "Failed to disable control", + cause + ); + } + }) + .thenApply(response -> response.operationIdentifier()); + } + // 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."+ controls.size())) + .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…"); + ResetEnabledBaselineRequest request = ResetEnabledBaselineRequest.builder() + .enabledBaselineIdentifier(enabledBaselineIdentifier) + .build(); + + return getAsyncClient().resetEnabledBaseline(request) + .thenApply(response -> { + String operationId = response.operationIdentifier(); + System.out.println("Reset enabled baseline with operation ID: {}" + operationId); + return operationId; + }) + .exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + + if (cause instanceof ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "ResourceNotFoundException": + System.out.println("Target not found: {}"+ e.getMessage()); + break; + default: + System.out.println("Couldn't reset enabled baseline. Here's why: {}" + e.getMessage()); + } + throw new CompletionException(e); + } + + if (cause instanceof SdkException sdkEx) { + System.out.println("SDK error resetting enabled baseline: {}"+ sdkEx.getMessage()); + throw new CompletionException(sdkEx); + } + + throw new CompletionException("Failed to reset enabled baseline", cause); + }); + } + // 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..ad7aaa041ca --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerScenario.java @@ -0,0 +1,278 @@ +// 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.util.List; +import java.util.Scanner; + +import static java.lang.System.in; +import static java.lang.System.out; + + +/** + * 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.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) { + + out.println(DASHES); + out.println("Welcome to the AWS Control Tower basics scenario!"); + out.println(DASHES); + + try { + runScenarioAsync(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // ----------------------------- + // Utilities + // ----------------------------- + private static boolean askYesNo(String msg) { + out.print(msg); + return scanner.nextLine().trim().toLowerCase().startsWith("y"); + } + + private static void runScenarioAsync() { + ControlTowerActions actions = new ControlTowerActions(); + + // ----------------------------- + // Step 1: Landing Zones + // ----------------------------- + out.println(DASHES); + 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 + """); + + out.println("Step 1: Listing landing zones..."); + waitForInputToContinue(scanner); + + List landingZones = + actions.listLandingZonesAsync().join(); + + if (landingZones.isEmpty()) { + out.println("No landing zones found. Landing-zone-dependent steps will be skipped."); + useLandingZone = false; + waitForInputToContinue(scanner); + } else { + out.println("\nAvailable Landing Zones:"); + for (int i = 0; i < landingZones.size(); i++) { + 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; + out.print("Enter landing zone ARN: "); + landingZoneArn = scanner.nextLine().trim(); + } else { + out.println("Proceeding without a landing zone."); + useLandingZone = false; + waitForInputToContinue(scanner); + } + } + + // ----------------------------- + // Setup Organization + Sandbox OU + // ----------------------------- + if (useLandingZone) { + out.println("Using landing zone ARN: " + landingZoneArn); + + ControlTowerActions.OrgSetupResult result = + actions.setupOrganizationAsync().join(); + + ouArn = result.sandboxOuArn(); + ouId = result.sandboxOuArn(); + + out.println("Organization ID: " + result.orgId()); + out.println("Using Sandbox OU ARN: " + ouArn); + } + + // ----------------------------- + // Step 2: Baselines + // ----------------------------- + out.println(DASHES); + out.println("Step 2: Listing available baselines..."); + waitForInputToContinue(scanner); + + List baselines = + actions.listBaselinesAsync().join(); + + BaselineSummary controlTowerBaseline = null; + for (BaselineSummary b : baselines) { + out.println("Baseline: " + b.name()); + out.println(" ARN: " + b.arn()); + if ("AWSControlTowerBaseline".equals(b.name())) { + controlTowerBaseline = b; + } + } + + waitForInputToContinue(scanner); + + if (useLandingZone && controlTowerBaseline != null) { + + out.println("\nListing enabled baselines:"); + List enabledBaselines = + actions.listEnabledBaselinesAsync().join(); + + EnabledBaselineSummary identityCenterBaseline = null; + for (EnabledBaselineSummary eb : enabledBaselines) { + out.println(eb.baselineIdentifier()); + if (eb.baselineIdentifier().contains("baseline/LN25R72TTG6IGPTQ")) { + identityCenterBaseline = eb; + } + } + + if (askYesNo("Do you want to enable the Control Tower Baseline? (y/n): ")) { + out.println("\nEnabling Control Tower Baseline..."); + + String enabledBaselineId = + actions.enableBaselineAsync( + ouArn, + controlTowerBaseline.arn(), + "4.0" + ).join(); + + out.println("Enabled baseline operation ID: " + enabledBaselineId); + + if (askYesNo("Do you want to reset the Control Tower Baseline? (y/n): ")) { + String operationId = + actions.resetEnabledBaselineAsync(enabledBaselineId).join(); + 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(); + out.println("Disabled baseline operation ID: " + operationId); + + // Re-enable for next steps + actions.enableBaselineAsync( + ouArn, + controlTowerBaseline.arn(), + "4.0" + ).join(); + } + } + } + + // ----------------------------- + // Step 3: Controls + // ----------------------------- + out.println(DASHES); + out.println("Step 3: Managing Controls:"); + waitForInputToContinue(scanner); + + List controls = + actions.listControlsAsync().join(); + + 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.format("%d. %s - %s".formatted(i + 1, c.name(), c.arn())); + } + + if (useLandingZone) { + waitForInputToContinue(scanner); + + List enabledControls = + actions.listEnabledControlsAsync(ouArn).join(); + + out.println("\nListing enabled controls:"); + for (int i = 0; i < enabledControls.size(); i++) { + 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(); + + 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(); + + out.println("Disable operation ID: " + operationId); + } + } + + out.println("\nThis concludes the example scenario."); + out.println("Thanks for watching!"); + out.println(DASHES); + } + + + + + private static void waitForInputToContinue(Scanner sc) { + out.println("\nEnter 'c' then to continue:"); + while (true) { + String input = sc.nextLine(); + if ("c".equalsIgnoreCase(input.trim())) { + 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..1e8aa3cadd4 --- /dev/null +++ b/javav2/example_code/controltower/src/test/java/ControlTowerTest.java @@ -0,0 +1,111 @@ +// 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.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(() -> { + String simulatedInput = String.join("\n", + Arrays.asList( + "c", "y", "c", "c", "y", "n", "n", "c", "y", "n", "c" + )) + "\n"; + + InputStream originalIn = System.in; + PrintStream originalOut = System.out; + + try { + // Simulate user input + ByteArrayInputStream testIn = new ByteArrayInputStream(simulatedInput.getBytes()); + System.setIn(testIn); + + // Capture output + 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