From 1b57f997f910168fc439b0ee5a80d0db272ed650 Mon Sep 17 00:00:00 2001 From: Scott Macdonald Date: Thu, 4 Dec 2025 10:25:41 -0500 Subject: [PATCH 1/9] Add Java V2 examples for Control Tower --- javav2/example_code/controltower/README.md | 102 +++ javav2/example_code/controltower/pom.xml | 90 +++ .../controltower/ControlTowerActions.java | 588 ++++++++++++++++++ .../controltower/ControlTowerScenario.java | 159 +++++ .../controltower/HelloControlTower.java | 78 +++ .../src/main/java/resources/config.properties | 3 + .../src/main/java/resources/log4j2.xml | 32 + .../src/test/java/ControlTowerTest.java | 51 ++ 8 files changed, 1103 insertions(+) create mode 100644 javav2/example_code/controltower/README.md create mode 100644 javav2/example_code/controltower/pom.xml create mode 100644 javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java create mode 100644 javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java create mode 100644 javav2/example_code/controltower/src/main/java/com/example/controltower/HelloControlTower.java create mode 100644 javav2/example_code/controltower/src/main/java/resources/config.properties create mode 100644 javav2/example_code/controltower/src/main/java/resources/log4j2.xml create mode 100644 javav2/example_code/controltower/src/test/java/ControlTowerTest.java diff --git a/javav2/example_code/controltower/README.md b/javav2/example_code/controltower/README.md new file mode 100644 index 00000000000..400f3615ae3 --- /dev/null +++ b/javav2/example_code/controltower/README.md @@ -0,0 +1,102 @@ +# AWS Control Tower code examples for the SDK for Java 2.x + +## Overview + +This is a workspace where you can find the following AWS SDK for Java 2.x AWS Control Tower examples. + +## ⚠ Important + +* Running this code might result in charges to your AWS account. +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. + +## Code examples + +### Actions + +The following examples show you how to perform actions using the AWS SDK for Java 2.x. + +* [List landing zones](src/main/java/com/example/controltower/ControlTowerActions.java) (`ListLandingZones`) +* [List baselines](src/main/java/com/example/controltower/ControlTowerActions.java) (`ListBaselines`) +* [List enabled baselines](src/main/java/com/example/controltower/ControlTowerActions.java) (`ListEnabledBaselines`) +* [Enable baseline](src/main/java/com/example/controltower/ControlTowerActions.java) (`EnableBaseline`) +* [Disable baseline](src/main/java/com/example/controltower/ControlTowerActions.java) (`DisableBaseline`) +* [Get baseline operation](src/main/java/com/example/controltower/ControlTowerActions.java) (`GetBaselineOperation`) +* [List enabled controls](src/main/java/com/example/controltower/ControlTowerActions.java) (`ListEnabledControls`) +* [Enable control](src/main/java/com/example/controltower/ControlTowerActions.java) (`EnableControl`) +* [Disable control](src/main/java/com/example/controltower/ControlTowerActions.java) (`DisableControl`) +* [Get control operation](src/main/java/com/example/controltower/ControlTowerActions.java) (`GetControlOperation`) + +### Scenarios + +The following examples show you how to implement common scenarios. + +* [Learn the basics](src/main/java/com/example/controltower/ControlTowerScenario.java) - Learn the basics by checking setup, managing baselines and controls. + +### Hello + +* [Hello Control Tower](src/main/java/com/example/controltower/HelloControlTower.java) - Get started with AWS Control Tower. + +## Prerequisites + +- You must have an AWS account, and have your default credentials and AWS Region configured. +- Java 17 or later +- Maven 3.6 or later +- AWS Control Tower must be set up in your account + +## Install + +To build and run the examples, navigate to the directory that contains a `pom.xml` file and run the following command: + +``` +mvn compile +``` + +## Run the examples + +### Instructions + +All examples can be run individually. For example: + +``` +mvn exec:java -Dexec.mainClass="com.example.controltower.HelloControlTower" -Dexec.args="us-east-1" +``` + +### Hello Control Tower + +This example shows you how to get started using AWS Control Tower. + +``` +mvn exec:java -Dexec.mainClass="com.example.controltower.HelloControlTower" -Dexec.args="us-east-1" +``` + +### Learn the basics + +This interactive scenario runs at a command prompt and shows you how to use AWS Control Tower to do the following: + +1. Check Control Tower setup and list landing zones +2. List available baselines for governance +3. List currently enabled baselines +4. Enable baselines for organizational units +5. List and enable controls for compliance +6. Monitor operation status +7. Clean up resources + +``` +mvn exec:java -Dexec.mainClass="com.example.controltower.ControlTowerScenario" -Dexec.args="us-east-1" +``` + +## Run the tests + +Unit tests in this module use JUnit 5. To run all of the tests, +run the following in your [GitHub root]/javav2/example_code/controltower folder. + +``` +mvn test +``` + +## Additional resources + +- [AWS Control Tower User Guide](https://docs.aws.amazon.com/controltower/latest/userguide/) +- [AWS Control Tower API Reference](https://docs.aws.amazon.com/controltower/latest/APIReference/) +- [AWS SDK for Java 2.x (AWS Control Tower)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/controltower/package-summary.html) \ No newline at end of file diff --git a/javav2/example_code/controltower/pom.xml b/javav2/example_code/controltower/pom.xml new file mode 100644 index 00000000000..46e87718415 --- /dev/null +++ b/javav2/example_code/controltower/pom.xml @@ -0,0 +1,90 @@ + + + 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 + + + org.slf4j + slf4j-api + 2.0.7 + + + ch.qos.logback + logback-classic + 1.4.8 + + + + 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.ControlTowerScenario + us-east-1 + + + + + \ No newline at end of file diff --git a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java new file mode 100644 index 00000000000..29b2f6db20b --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java @@ -0,0 +1,588 @@ +// 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.*; +import software.amazon.awssdk.services.controltower.paginators.ListBaselinesIterable; +import software.amazon.awssdk.services.controltower.paginators.ListEnabledBaselinesIterable; +import software.amazon.awssdk.services.controltower.paginators.ListEnabledControlsIterable; +import software.amazon.awssdk.services.controltower.paginators.ListLandingZonesIterable; +import software.amazon.awssdk.services.controlcatalog.ControlCatalogClient; +import software.amazon.awssdk.services.controlcatalog.paginators.ListControlsIterable; +import software.amazon.awssdk.core.exception.SdkException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.ArrayList; + +/** + * 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 final Logger logger = LoggerFactory.getLogger(ControlTowerActions.class); + + // snippet-start:[controltower.java2.list_landing_zones.main] + /** + * Lists all landing zones using pagination to retrieve complete results. + * + * @param controlTowerClient the Control Tower client to use for the operation + * @return a list of all landing zones + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public static List listLandingZones(ControlTowerClient controlTowerClient) { + try { + List landingZones = new ArrayList<>(); + + String nextToken = null; + + do { + ListLandingZonesRequest.Builder requestBuilder = ListLandingZonesRequest.builder(); + if (nextToken != null) { + requestBuilder.nextToken(nextToken); + } + + ListLandingZonesResponse response = controlTowerClient.listLandingZones(requestBuilder.build()); + + if (response.landingZones() != null) { + landingZones.addAll(response.landingZones()); + } + + nextToken = response.nextToken(); + } while (nextToken != null); + + logger.info("Retrieved {} landing zones", landingZones.size()); + return landingZones; + + } catch (ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "AccessDeniedException": + logger.error("Access denied when listing landing zones: {}", e.getMessage()); + break; + default: + logger.error("Error listing landing zones: {}", e.getMessage()); + } + throw e; + } catch (SdkException e) { + logger.error("SDK error listing landing zones: {}", e.getMessage()); + throw e; + } + } + // 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. + * + * @param controlTowerClient the Control Tower client to use for the operation + * @return a list of all baselines + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public static List listBaselines(ControlTowerClient controlTowerClient) { + try { + List baselines = new ArrayList<>(); + + String nextToken = null; + + do { + ListBaselinesRequest.Builder requestBuilder = ListBaselinesRequest.builder(); + if (nextToken != null) { + requestBuilder.nextToken(nextToken); + } + + ListBaselinesResponse response = controlTowerClient.listBaselines(requestBuilder.build()); + + if (response.baselines() != null) { + baselines.addAll(response.baselines()); + } + + nextToken = response.nextToken(); + } while (nextToken != null); + + logger.info("Retrieved {} baselines", baselines.size()); + return baselines; + + } catch (ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "AccessDeniedException": + logger.error("Access denied when listing baselines: {}", e.getMessage()); + break; + default: + logger.error("Error listing baselines: {}", e.getMessage()); + } + throw e; + } catch (SdkException e) { + logger.error("SDK error listing baselines: {}", e.getMessage()); + throw e; + } + } + // 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. + * + * @param controlTowerClient the Control Tower client to use for the operation + * @return a list of all enabled baselines + * @throws ControlTowerException if a service-specific error occurs + * @throws SdkException if an SDK error occurs + */ + public static List listEnabledBaselines(ControlTowerClient controlTowerClient) { + try { + List enabledBaselines = new ArrayList<>(); + + String nextToken = null; + + do { + ListEnabledBaselinesRequest.Builder requestBuilder = ListEnabledBaselinesRequest.builder(); + if (nextToken != null) { + requestBuilder.nextToken(nextToken); + } + + ListEnabledBaselinesResponse response = controlTowerClient.listEnabledBaselines(requestBuilder.build()); + + if (response.enabledBaselines() != null) { + enabledBaselines.addAll(response.enabledBaselines()); + } + + nextToken = response.nextToken(); + } while (nextToken != null); + + logger.info("Retrieved {} enabled baselines", enabledBaselines.size()); + return enabledBaselines; + + } catch (ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "ResourceNotFoundException": + logger.error("Target not found when listing enabled baselines: {}", e.getMessage()); + break; + default: + logger.error("Error listing enabled baselines: {}", e.getMessage()); + } + throw e; + } catch (SdkException e) { + logger.error("SDK error listing enabled baselines: {}", e.getMessage()); + throw e; + } + } + // snippet-end:[controltower.java2.list_enabled_baselines.main] + + // snippet-start:[controltower.java2.enable_baseline.main] + /** + * Enables a baseline for a specified target. + * + * @param controlTowerClient the Control Tower client to use for the operation + * @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 static String enableBaseline(ControlTowerClient controlTowerClient, + String baselineIdentifier, + String baselineVersion, + String targetIdentifier) { + try { + EnableBaselineRequest request = EnableBaselineRequest.builder() + .baselineIdentifier(baselineIdentifier) + .baselineVersion(baselineVersion) + .targetIdentifier(targetIdentifier) + .build(); + + EnableBaselineResponse response = controlTowerClient.enableBaseline(request); + String operationId = response.operationIdentifier(); + + logger.info("Enabled baseline with operation ID: {}", operationId); + return operationId; + + } catch (ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "ValidationException": + if (e.getMessage().contains("already enabled")) { + logger.info("Baseline is already enabled for this target"); + return null; + } + logger.error("Validation error enabling baseline: {}", e.getMessage()); + break; + case "ConflictException": + logger.error("Conflict enabling baseline: {}", e.getMessage()); + break; + default: + logger.error("Error enabling baseline: {}", e.getMessage()); + } + throw e; + } catch (SdkException e) { + logger.error("SDK error enabling baseline: {}", e.getMessage()); + throw e; + } + } + // snippet-end:[controltower.java2.enable_baseline.main] + + // snippet-start:[controltower.java2.disable_baseline.main] + /** + * Disables a baseline for a specified target. + * + * @param controlTowerClient the Control Tower client to use for the operation + * @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 static String disableBaseline(ControlTowerClient controlTowerClient, + String enabledBaselineIdentifier) { + try { + DisableBaselineRequest request = DisableBaselineRequest.builder() + .enabledBaselineIdentifier(enabledBaselineIdentifier) + .build(); + + DisableBaselineResponse response = controlTowerClient.disableBaseline(request); + String operationId = response.operationIdentifier(); + + logger.info("Disabled baseline with operation ID: {}", operationId); + return operationId; + + } catch (ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "ConflictException": + logger.error("Conflict disabling baseline: {}", e.getMessage()); + break; + case "ResourceNotFoundException": + logger.error("Baseline not found for disabling: {}", e.getMessage()); + break; + default: + logger.error("Error disabling baseline: {}", e.getMessage()); + } + throw e; + } catch (SdkException e) { + logger.error("SDK error disabling baseline: {}", e.getMessage()); + throw e; + } + } + // snippet-end:[controltower.java2.disable_baseline.main] + + // snippet-start:[controltower.java2.get_baseline_operation.main] + /** + * Gets the status of a baseline operation. + * + * @param controlTowerClient the Control Tower client to use for the 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 static BaselineOperationStatus getBaselineOperation(ControlTowerClient controlTowerClient, + String operationIdentifier) { + try { + GetBaselineOperationRequest request = GetBaselineOperationRequest.builder() + .operationIdentifier(operationIdentifier) + .build(); + + GetBaselineOperationResponse response = controlTowerClient.getBaselineOperation(request); + BaselineOperationStatus status = response.baselineOperation().status(); + + logger.info("Baseline operation status: {}", status); + return status; + + } catch (ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "ResourceNotFoundException": + logger.error("Baseline operation not found: {}", e.getMessage()); + break; + default: + logger.error("Error getting baseline operation status: {}", e.getMessage()); + } + throw e; + } catch (SdkException e) { + logger.error("SDK error getting baseline operation status: {}", e.getMessage()); + throw e; + } + } + // 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 controlTowerClient the Control Tower client to use for the operation + * @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 static List listEnabledControls(ControlTowerClient controlTowerClient, + String targetIdentifier) { + try { + List enabledControls = new ArrayList<>(); + + // Use paginator to retrieve all results + ListEnabledControlsIterable listIterable = controlTowerClient.listEnabledControlsPaginator( + ListEnabledControlsRequest.builder() + .targetIdentifier(targetIdentifier) + .build() + ); + + listIterable.stream() + .flatMap(response -> response.enabledControls().stream()) + .forEach(enabledControls::add); + + logger.info("Retrieved {} enabled controls for target {}", enabledControls.size(), targetIdentifier); + return enabledControls; + + } catch (ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "AccessDeniedException": + logger.error("Access denied when listing enabled controls: {}", e.getMessage()); + break; + case "ResourceNotFoundException": + if (e.getMessage().contains("not registered with AWS Control Tower")) { + logger.error("Control Tower must be enabled to work with controls"); + } else { + logger.error("Target not found when listing enabled controls: {}", e.getMessage()); + } + break; + default: + logger.error("Error listing enabled controls: {}", e.getMessage()); + } + throw e; + } catch (SdkException e) { + logger.error("SDK error listing enabled controls: {}", e.getMessage()); + throw e; + } + } + // snippet-end:[controltower.java2.list_enabled_controls.main] + + // snippet-start:[controltower.java2.enable_control.main] + /** + * Enables a control for a specified target. + * + * @param controlTowerClient the Control Tower client to use for the operation + * @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 static String enableControl(ControlTowerClient controlTowerClient, + String controlIdentifier, + String targetIdentifier) { + try { + EnableControlRequest request = EnableControlRequest.builder() + .controlIdentifier(controlIdentifier) + .targetIdentifier(targetIdentifier) + .build(); + + EnableControlResponse response = controlTowerClient.enableControl(request); + String operationId = response.operationIdentifier(); + + logger.info("Enabled control with operation ID: {}", operationId); + return operationId; + + } catch (ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "ValidationException": + if (e.getMessage().contains("already enabled")) { + logger.info("Control is already enabled for this target"); + return null; + } + logger.error("Validation error enabling control: {}", e.getMessage()); + break; + case "ResourceNotFoundException": + if (e.getMessage().contains("not registered with AWS Control Tower")) { + logger.error("Control Tower must be enabled to work with controls"); + } else { + logger.error("Control not found: {}", e.getMessage()); + } + break; + default: + logger.error("Error enabling control: {}", e.getMessage()); + } + throw e; + } catch (SdkException e) { + logger.error("SDK error enabling control: {}", e.getMessage()); + throw e; + } + } + // snippet-end:[controltower.java2.enable_control.main] + + // snippet-start:[controltower.java2.disable_control.main] + /** + * Disables a control for a specified target. + * + * @param controlTowerClient the Control Tower client to use for the operation + * @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 static String disableControl(ControlTowerClient controlTowerClient, + String controlIdentifier, + String targetIdentifier) { + try { + DisableControlRequest request = DisableControlRequest.builder() + .controlIdentifier(controlIdentifier) + .targetIdentifier(targetIdentifier) + .build(); + + DisableControlResponse response = controlTowerClient.disableControl(request); + String operationId = response.operationIdentifier(); + + logger.info("Disabled control with operation ID: {}", operationId); + return operationId; + + } catch (ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "ResourceNotFoundException": + logger.error("Control not found for disabling: {}", e.getMessage()); + break; + default: + logger.error("Error disabling control: {}", e.getMessage()); + } + throw e; + } catch (SdkException e) { + logger.error("SDK error disabling control: {}", e.getMessage()); + throw e; + } + } + // snippet-end:[controltower.java2.disable_control.main] + + // snippet-start:[controltower.java2.get_control_operation.main] + /** + * Gets the status of a control operation. + * + * @param controlTowerClient the Control Tower client to use for the 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 static ControlOperationStatus getControlOperation(ControlTowerClient controlTowerClient, + String operationIdentifier) { + try { + GetControlOperationRequest request = GetControlOperationRequest.builder() + .operationIdentifier(operationIdentifier) + .build(); + + GetControlOperationResponse response = controlTowerClient.getControlOperation(request); + ControlOperationStatus status = response.controlOperation().status(); + + logger.info("Control operation status: {}", status); + return status; + + } catch (ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "ResourceNotFoundException": + logger.error("Control operation not found: {}", e.getMessage()); + break; + default: + logger.error("Error getting control operation status: {}", e.getMessage()); + } + throw e; + } catch (SdkException e) { + logger.error("SDK error getting control operation status: {}", e.getMessage()); + throw e; + } + } + // snippet-end:[controltower.java2.get_control_operation.main] + + // snippet-start:[controltower.java2.list_controls.main] + /** + * Lists all controls in the Control Tower control catalog. + * + * @param controlCatalogClient the Control Catalog client to use for the operation + * @return a list of controls + * @throws SdkException if a service-specific error occurs + */ + public static List listControls( + ControlCatalogClient controlCatalogClient) { + try { + List controls = new ArrayList<>(); + + ListControlsIterable paginator = controlCatalogClient.listControlsPaginator( + software.amazon.awssdk.services.controlcatalog.model.ListControlsRequest.builder().build()); + + paginator.stream() + .flatMap(response -> response.controls().stream()) + .forEach(controls::add); + + logger.info("Retrieved {} controls", controls.size()); + return controls; + + } catch (SdkException e) { + if (e.getMessage().contains("AccessDeniedException")) { + logger.error("Access denied. Please ensure you have the necessary permissions."); + } else { + logger.error("Couldn't list controls. Here's why: {}", e.getMessage()); + } + throw e; + } + } + // snippet-end:[controltower.java2.list_controls.main] + + // snippet-start:[controltower.java2.reset_enabled_baseline.main] + /** + * Resets an enabled baseline for a specific target. + * + * @param controlTowerClient the Control Tower client to use for the operation + * @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 static String resetEnabledBaseline(ControlTowerClient controlTowerClient, + String enabledBaselineIdentifier) { + try { + ResetEnabledBaselineRequest request = ResetEnabledBaselineRequest.builder() + .enabledBaselineIdentifier(enabledBaselineIdentifier) + .build(); + + ResetEnabledBaselineResponse response = controlTowerClient.resetEnabledBaseline(request); + String operationId = response.operationIdentifier(); + + logger.info("Reset enabled baseline with operation ID: {}", operationId); + return operationId; + + } catch (ControlTowerException e) { + String errorCode = e.awsErrorDetails().errorCode(); + switch (errorCode) { + case "ResourceNotFoundException": + logger.error("Target not found: {}", e.getMessage()); + break; + default: + logger.error("Couldn't reset enabled baseline. Here's why: {}", e.getMessage()); + } + throw e; + } catch (SdkException e) { + logger.error("SDK error resetting enabled baseline: {}", e.getMessage()); + throw e; + } + } + // 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/ControlTowerScenario.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java new file mode 100644 index 00000000000..89d75092781 --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java @@ -0,0 +1,159 @@ +// 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.DefaultCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.controltower.ControlTowerClient; +import software.amazon.awssdk.services.controltower.model.*; + +import java.util.List; +import java.util.Scanner; + +/** + * 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 performs the following tasks: + * + * 1. Check Control Tower setup and list landing zones + * 2. List available baselines for governance + * 3. List currently enabled baselines + * 4. Enable a baseline for a target organizational unit + * 5. List enabled controls for the target + * 6. Enable a control for a target organizational unit + * 7. Monitor operation status + * 8. Clean up by disabling controls and baselines + */ + +// snippet-start:[controltower.java2.controltower_scenario.main] +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.LandingZoneSummary; +import software.amazon.awssdk.services.controltower.model.BaselineSummary; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Scanner; + +public class ControlTowerScenario { + private static final Logger logger = LoggerFactory.getLogger(ControlTowerScenario.class); + public static final String DASHES = new String(new char[80]).replace("\0", "-"); + static Scanner scanner = new Scanner(System.in); + + public static void main(String[] args) { + // Suppress AWS SDK internal debug logs + System.setProperty("software.amazon.awssdk.logging.level", "WARN"); + + logger.info(DASHES); + logger.info("Welcome to the AWS Control Tower basics scenario!"); + logger.info(DASHES); + logger.info(""" + AWS Control Tower is a service that enables you to set up and govern + a secure, multi-account AWS environment based on best practices. + Control Tower orchestrates several other AWS services to build a landing zone. + + This scenario will guide you through the basic operations of + Control Tower, including managing baselines and controls for + governance and compliance. + """); + + // waitForInputToContinue(scanner); + + try { + + ControlTowerClient controlTowerClient = ControlTowerClient.builder() + .region(Region.US_EAST_1) + .credentialsProvider(DefaultCredentialsProvider.create()) + .httpClientBuilder(UrlConnectionHttpClient.builder()) // avoids Apache HTTP client + .build() ; + + runScenario(controlTowerClient); + + } catch (ControlTowerException e) { + logger.error("Control Tower service error: {}", e.awsErrorDetails().errorMessage()); + } catch (Exception e) { + logger.error("Unexpected error: {}", e.getMessage(), e); + } + } + + private static void runScenario(ControlTowerClient controlTowerClient) { + try { + // Step 1: Check Control Tower setup + logger.info(DASHES); + logger.info("Step 1: Checking Control Tower setup..."); + + /* + List landingZones = ControlTowerActions.listLandingZones(controlTowerClient); + if (landingZones.isEmpty()) { + logger.warn("⚠️ No landing zones found. Control Tower may not be set up."); + logger.warn("Please set up Control Tower in the AWS Console first."); + return; + } + + logger.info("Found {} landing zone(s):", landingZones.size()); + for (LandingZoneSummary landingZone : landingZones) { + logger.info(" - ARN: {}", landingZone.arn()); + } + + waitForInputToContinue(scanner); + + */ + + // Step 2: List available baselines + logger.info(DASHES); + logger.info("Step 2: Listing available baselines..."); + + List baselines = ControlTowerActions.listBaselines(controlTowerClient); + if (baselines.isEmpty()) { + logger.warn("No baselines available."); + } else { + logger.info("Available baselines:"); + for (BaselineSummary baseline : baselines) { + logger.info(" - Name: {}", baseline.name()); + logger.info(" ARN: {}", baseline.arn()); + logger.info(" Description: {}", baseline.description()); + } + } + + waitForInputToContinue(scanner); + /* + + logger.info(DASHES); + logger.info("🎉 Control Tower Basics scenario completed successfully!"); + + + */ + } catch (ControlTowerException e) { + logger.error("Control Tower service error: {}", e.awsErrorDetails().errorMessage()); + throw e; + } catch (Exception e) { + logger.error("Unexpected error: {}", e.getMessage(), e); + throw e; + } + } + + private static void waitForInputToContinue(Scanner scanner) { + while (true) { + System.out.println(""); + System.out.println("Enter 'c' followed by to continue:"); + String input = scanner.nextLine(); + + if (input.trim().equalsIgnoreCase("c")) { + logger.info("Continuing with the program...\n"); + break; + } else { + System.out.println("Invalid input. Please try again."); + } + } + } +} +// snippet-end:[controltower.java2.controltower_scenario.main] \ 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..aba26c4ee84 --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/HelloControlTower.java @@ -0,0 +1,78 @@ +// 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:[python.example_code.controltower.Hello] +public class HelloControlTower { + + public static void main(String[] args) { + try { + ControlTowerClient controlTowerClient = ControlTowerClient.builder() + .region(Region.US_EAST_1) + .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:[python.example_code.controltower.Hello] diff --git a/javav2/example_code/controltower/src/main/java/resources/config.properties b/javav2/example_code/controltower/src/main/java/resources/config.properties new file mode 100644 index 00000000000..c18495c2f4c --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/resources/config.properties @@ -0,0 +1,3 @@ +dataAccessRoleArn = +s3Uri = +documentClassifier = diff --git a/javav2/example_code/controltower/src/main/java/resources/log4j2.xml b/javav2/example_code/controltower/src/main/java/resources/log4j2.xml new file mode 100644 index 00000000000..fcef26f34b8 --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/resources/log4j2.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..c16e0049dc0 --- /dev/null +++ b/javav2/example_code/controltower/src/test/java/ControlTowerTest.java @@ -0,0 +1,51 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import com.example.controltower.ControlTowerActions; +import com.example.controltower.HelloControlTower; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.controltower.ControlTowerClient; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +@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) + .build(); + } + + @Test + @Order(1) + public void testHelloService() { + assertDoesNotThrow(() -> { + HelloControlTower.helloControlTower(controlTowerClient); + }); + System.out.println("Test 1 passed"); + } + + @Test + @Order(2) + public void testControlTowerActions() { + assertDoesNotThrow(() -> { + ControlTowerActions.listLandingZones(controlTowerClient); + ControlTowerActions.listBaselines(controlTowerClient); + ControlTowerActions.listEnabledBaselines(controlTowerClient); + + // Note: Enable/disable operations require valid ARNs and are not tested here + // to avoid modifying the actual Control Tower configuration + }); + System.out.println("Test 2 passed"); + } +} \ No newline at end of file From 57bc663956837d6a9c2ffd0336e138cdacc9a93e Mon Sep 17 00:00:00 2001 From: Scott Macdonald Date: Thu, 4 Dec 2025 11:17:36 -0500 Subject: [PATCH 2/9] Updated the Control Tower YAML file --- .doc_gen/metadata/controltower_metadata.yaml | 118 ++++++++++++++++++ .../controltower/HelloControlTower.java | 1 - 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/.doc_gen/metadata/controltower_metadata.yaml b/.doc_gen/metadata/controltower_metadata.yaml index 083079b4db1..1029f90fcc3 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: un 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/src/main/java/com/example/controltower/HelloControlTower.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/HelloControlTower.java index aba26c4ee84..5e948676491 100644 --- 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 @@ -3,7 +3,6 @@ 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; From e4099ba090b9123378fce8f3e3df527094f23018 Mon Sep 17 00:00:00 2001 From: Scott Macdonald Date: Thu, 4 Dec 2025 14:14:44 -0500 Subject: [PATCH 3/9] fixed a tag issue --- .../main/java/com/example/controltower/HelloControlTower.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index 5e948676491..d3c223dd182 100644 --- 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 @@ -25,7 +25,7 @@ * and config files. */ -// snippet-start:[python.example_code.controltower.Hello] +// snippet-start:[controltower.java2.hello.main] public class HelloControlTower { public static void main(String[] args) { @@ -74,4 +74,4 @@ public static void helloControlTower(ControlTowerClient controlTowerClient) { } } } -// snippet-end:[python.example_code.controltower.Hello] +// snippet-end:[controltower.java2.hello.main] From 84dcb641c06802c9e20667acd06f8a33016f97a0 Mon Sep 17 00:00:00 2001 From: Scott Macdonald Date: Fri, 5 Dec 2025 09:45:41 -0500 Subject: [PATCH 4/9] fixed a tag issue om YAML --- .doc_gen/metadata/controltower_metadata.yaml | 2 +- javav2/example_code/controltower/README.md | 135 +++++++++++-------- 2 files changed, 77 insertions(+), 60 deletions(-) diff --git a/.doc_gen/metadata/controltower_metadata.yaml b/.doc_gen/metadata/controltower_metadata.yaml index 1029f90fcc3..01f3fcfc2ab 100644 --- a/.doc_gen/metadata/controltower_metadata.yaml +++ b/.doc_gen/metadata/controltower_metadata.yaml @@ -386,7 +386,7 @@ controltower_Scenario: github: javav2/example_code/controltower sdkguide: excerpts: - - description: un an interactive scenario demonstrating &CTowerlong; features. + - description: Run an interactive scenario demonstrating &CTowerlong; features. snippet_tags: - controltower.java2.controltower_scenario.main - controltower.java2.controltower_actions.main diff --git a/javav2/example_code/controltower/README.md b/javav2/example_code/controltower/README.md index 400f3615ae3..09d4fcc4a63 100644 --- a/javav2/example_code/controltower/README.md +++ b/javav2/example_code/controltower/README.md @@ -2,101 +2,118 @@ ## Overview -This is a workspace where you can find the following AWS SDK for Java 2.x AWS Control Tower examples. +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. +* 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. +* 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 -### Actions +### Prerequisites + +For prerequisites, see the [README](../../README.md#Prerequisites) in the `javav2` folder. + -The following examples show you how to perform actions using the AWS SDK for Java 2.x. + + -* [List landing zones](src/main/java/com/example/controltower/ControlTowerActions.java) (`ListLandingZones`) -* [List baselines](src/main/java/com/example/controltower/ControlTowerActions.java) (`ListBaselines`) -* [List enabled baselines](src/main/java/com/example/controltower/ControlTowerActions.java) (`ListEnabledBaselines`) -* [Enable baseline](src/main/java/com/example/controltower/ControlTowerActions.java) (`EnableBaseline`) -* [Disable baseline](src/main/java/com/example/controltower/ControlTowerActions.java) (`DisableBaseline`) -* [Get baseline operation](src/main/java/com/example/controltower/ControlTowerActions.java) (`GetBaselineOperation`) -* [List enabled controls](src/main/java/com/example/controltower/ControlTowerActions.java) (`ListEnabledControls`) -* [Enable control](src/main/java/com/example/controltower/ControlTowerActions.java) (`EnableControl`) -* [Disable control](src/main/java/com/example/controltower/ControlTowerActions.java) (`DisableControl`) -* [Get control operation](src/main/java/com/example/controltower/ControlTowerActions.java) (`GetControlOperation`) +### Get started -### Scenarios +- [Hello AWS Control Tower](src/main/java/com/example/controltower/HelloControlTower.java#L28) (`ListBaselines`) -The following examples show you how to implement common scenarios. -* [Learn the basics](src/main/java/com/example/controltower/ControlTowerScenario.java) - Learn the basics by checking setup, managing baselines and controls. +### Basics -### Hello +Code examples that show you how to perform the essential operations within a service. -* [Hello Control Tower](src/main/java/com/example/controltower/HelloControlTower.java) - Get started with AWS Control Tower. +- [Learn the basics](src/main/java/com/example/controltower/ControlTowerActions.java) -## Prerequisites -- You must have an AWS account, and have your default credentials and AWS Region configured. -- Java 17 or later -- Maven 3.6 or later -- AWS Control Tower must be set up in your account +### Single actions -## Install +Code excerpts that show you how to call individual service functions. -To build and run the examples, navigate to the directory that contains a `pom.xml` file and run the following command: +- [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) -``` -mvn compile -``` + + + ## Run the examples ### Instructions -All examples can be run individually. For example: -``` -mvn exec:java -Dexec.mainClass="com.example.controltower.HelloControlTower" -Dexec.args="us-east-1" -``` + + -### Hello Control Tower +#### Hello AWS Control Tower This example shows you how to get started using AWS Control Tower. -``` -mvn exec:java -Dexec.mainClass="com.example.controltower.HelloControlTower" -Dexec.args="us-east-1" -``` -### Learn the basics +#### 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. + + + -This interactive scenario runs at a command prompt and shows you how to use AWS Control Tower to do the following: -1. Check Control Tower setup and list landing zones -2. List available baselines for governance -3. List currently enabled baselines -4. Enable baselines for organizational units -5. List and enable controls for compliance -6. Monitor operation status -7. Clean up resources + + -``` -mvn exec:java -Dexec.mainClass="com.example.controltower.ControlTowerScenario" -Dexec.args="us-east-1" -``` -## Run the tests +### Tests -Unit tests in this module use JUnit 5. To run all of the tests, -run the following in your [GitHub root]/javav2/example_code/controltower folder. +⚠ Running tests might result in charges to your AWS account. -``` -mvn test -``` + +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/) -- [AWS Control Tower API Reference](https://docs.aws.amazon.com/controltower/latest/APIReference/) -- [AWS SDK for Java 2.x (AWS Control Tower)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/controltower/package-summary.html) \ No newline at end of file +- [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 From 2acf0c6126a55133cd7d99731ca1bda9f8df3b1c Mon Sep 17 00:00:00 2001 From: Scott Macdonald Date: Tue, 9 Dec 2025 20:20:49 -0500 Subject: [PATCH 5/9] updated Java code --- javav2/example_code/controltower/pom.xml | 4 + .../controltower/ControlTowerActions.java | 144 +++---- .../controltower/ControlTowerScenario.java | 404 +++++++++++++----- .../controltower/HelloControlTower.java | 2 + .../src/main/java/resources/log4j2.xml | 32 -- .../src/main/java/resources/logback.xml | 18 + .../src/test/java/ControlTowerTest.java | 29 +- 7 files changed, 424 insertions(+), 209 deletions(-) delete mode 100644 javav2/example_code/controltower/src/main/java/resources/log4j2.xml create mode 100644 javav2/example_code/controltower/src/main/java/resources/logback.xml diff --git a/javav2/example_code/controltower/pom.xml b/javav2/example_code/controltower/pom.xml index 46e87718415..a7f0363c5a6 100644 --- a/javav2/example_code/controltower/pom.xml +++ b/javav2/example_code/controltower/pom.xml @@ -41,6 +41,10 @@ logback-classic 1.4.8 + + software.amazon.awssdk + organizations + software.amazon.awssdk diff --git a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java index 29b2f6db20b..a41a9c6ba46 100644 --- a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java @@ -33,8 +33,6 @@ // snippet-start:[controltower.java2.controltower_actions.main] public class ControlTowerActions { - private static final Logger logger = LoggerFactory.getLogger(ControlTowerActions.class); - // snippet-start:[controltower.java2.list_landing_zones.main] /** * Lists all landing zones using pagination to retrieve complete results. @@ -47,39 +45,39 @@ public class ControlTowerActions { public static List listLandingZones(ControlTowerClient controlTowerClient) { try { List landingZones = new ArrayList<>(); - + String nextToken = null; - + do { ListLandingZonesRequest.Builder requestBuilder = ListLandingZonesRequest.builder(); if (nextToken != null) { requestBuilder.nextToken(nextToken); } - + ListLandingZonesResponse response = controlTowerClient.listLandingZones(requestBuilder.build()); - + if (response.landingZones() != null) { landingZones.addAll(response.landingZones()); } - + nextToken = response.nextToken(); } while (nextToken != null); - - logger.info("Retrieved {} landing zones", landingZones.size()); + + System.out.format("Retrieved {} landing zones", landingZones.size()); return landingZones; } catch (ControlTowerException e) { String errorCode = e.awsErrorDetails().errorCode(); switch (errorCode) { case "AccessDeniedException": - logger.error("Access denied when listing landing zones: {}", e.getMessage()); + System.out.format("Access denied when listing landing zones: {}", e.getMessage()); break; default: - logger.error("Error listing landing zones: {}", e.getMessage()); + System.out.format("Error listing landing zones: {}", e.getMessage()); } throw e; } catch (SdkException e) { - logger.error("SDK error listing landing zones: {}", e.getMessage()); + System.out.format("SDK error listing landing zones: {}", e.getMessage()); throw e; } } @@ -115,21 +113,21 @@ public static List listBaselines(ControlTowerClient controlTowe nextToken = response.nextToken(); } while (nextToken != null); - logger.info("Retrieved {} baselines", baselines.size()); + System.out.format("Retrieved {} baselines", baselines.size()); return baselines; } catch (ControlTowerException e) { String errorCode = e.awsErrorDetails().errorCode(); switch (errorCode) { case "AccessDeniedException": - logger.error("Access denied when listing baselines: {}", e.getMessage()); + System.out.format("Access denied when listing baselines: {}", e.getMessage()); break; default: - logger.error("Error listing baselines: {}", e.getMessage()); + System.out.format("Error listing baselines: {}", e.getMessage()); } throw e; } catch (SdkException e) { - logger.error("SDK error listing baselines: {}", e.getMessage()); + System.out.format("SDK error listing baselines: {}", e.getMessage()); throw e; } } @@ -164,22 +162,22 @@ public static List listEnabledBaselines(ControlTowerClie nextToken = response.nextToken(); } while (nextToken != null); - - logger.info("Retrieved {} enabled baselines", enabledBaselines.size()); + + System.out.format("Retrieved {} enabled baselines", enabledBaselines.size()); return enabledBaselines; } catch (ControlTowerException e) { String errorCode = e.awsErrorDetails().errorCode(); switch (errorCode) { case "ResourceNotFoundException": - logger.error("Target not found when listing enabled baselines: {}", e.getMessage()); + System.out.format("Target not found when listing enabled baselines: {}", e.getMessage()); break; default: - logger.error("Error listing enabled baselines: {}", e.getMessage()); + System.out.format("Error listing enabled baselines: {}", e.getMessage()); } throw e; } catch (SdkException e) { - logger.error("SDK error listing enabled baselines: {}", e.getMessage()); + System.out.format("SDK error listing enabled baselines: {}", e.getMessage()); throw e; } } @@ -210,8 +208,8 @@ public static String enableBaseline(ControlTowerClient controlTowerClient, EnableBaselineResponse response = controlTowerClient.enableBaseline(request); String operationId = response.operationIdentifier(); - - logger.info("Enabled baseline with operation ID: {}", operationId); + + System.out.format("Enabled baseline with operation ID: {}", operationId); return operationId; } catch (ControlTowerException e) { @@ -219,20 +217,20 @@ public static String enableBaseline(ControlTowerClient controlTowerClient, switch (errorCode) { case "ValidationException": if (e.getMessage().contains("already enabled")) { - logger.info("Baseline is already enabled for this target"); + System.out.format("Baseline is already enabled for this target"); return null; } - logger.error("Validation error enabling baseline: {}", e.getMessage()); + System.out.format("Validation error enabling baseline: {}", e.getMessage()); break; case "ConflictException": - logger.error("Conflict enabling baseline: {}", e.getMessage()); + System.out.format("Conflict enabling baseline: {}", e.getMessage()); break; default: - logger.error("Error enabling baseline: {}", e.getMessage()); + System.out.format("Error enabling baseline: {}", e.getMessage()); } throw e; } catch (SdkException e) { - logger.error("SDK error enabling baseline: {}", e.getMessage()); + System.out.format("SDK error enabling baseline: {}", e.getMessage()); throw e; } } @@ -257,25 +255,25 @@ public static String disableBaseline(ControlTowerClient controlTowerClient, DisableBaselineResponse response = controlTowerClient.disableBaseline(request); String operationId = response.operationIdentifier(); - - logger.info("Disabled baseline with operation ID: {}", operationId); + + System.out.format("Disabled baseline with operation ID: {}", operationId); return operationId; } catch (ControlTowerException e) { String errorCode = e.awsErrorDetails().errorCode(); switch (errorCode) { case "ConflictException": - logger.error("Conflict disabling baseline: {}", e.getMessage()); + System.out.format("Conflict disabling baseline: {}", e.getMessage()); break; case "ResourceNotFoundException": - logger.error("Baseline not found for disabling: {}", e.getMessage()); + System.out.format("Baseline not found for disabling: {}", e.getMessage()); break; default: - logger.error("Error disabling baseline: {}", e.getMessage()); + System.out.format("Error disabling baseline: {}", e.getMessage()); } throw e; } catch (SdkException e) { - logger.error("SDK error disabling baseline: {}", e.getMessage()); + System.out.format("SDK error disabling baseline: {}", e.getMessage()); throw e; } } @@ -300,22 +298,22 @@ public static BaselineOperationStatus getBaselineOperation(ControlTowerClient co GetBaselineOperationResponse response = controlTowerClient.getBaselineOperation(request); BaselineOperationStatus status = response.baselineOperation().status(); - - logger.info("Baseline operation status: {}", status); + + System.out.format("Baseline operation status: {}", status); return status; } catch (ControlTowerException e) { String errorCode = e.awsErrorDetails().errorCode(); switch (errorCode) { case "ResourceNotFoundException": - logger.error("Baseline operation not found: {}", e.getMessage()); + System.out.format("Baseline operation not found: {}", e.getMessage()); break; default: - logger.error("Error getting baseline operation status: {}", e.getMessage()); + System.out.format("Error getting baseline operation status: {}", e.getMessage()); } throw e; } catch (SdkException e) { - logger.error("SDK error getting baseline operation status: {}", e.getMessage()); + System.out.format("SDK error getting baseline operation status: {}", e.getMessage()); throw e; } } @@ -346,29 +344,29 @@ public static List listEnabledControls(ControlTowerClient listIterable.stream() .flatMap(response -> response.enabledControls().stream()) .forEach(enabledControls::add); - - logger.info("Retrieved {} enabled controls for target {}", enabledControls.size(), targetIdentifier); + + System.out.format("Retrieved {} enabled controls for target {}", enabledControls.size(), targetIdentifier); return enabledControls; } catch (ControlTowerException e) { String errorCode = e.awsErrorDetails().errorCode(); switch (errorCode) { case "AccessDeniedException": - logger.error("Access denied when listing enabled controls: {}", e.getMessage()); + System.out.format("Access denied when listing enabled controls: {}", e.getMessage()); break; case "ResourceNotFoundException": if (e.getMessage().contains("not registered with AWS Control Tower")) { - logger.error("Control Tower must be enabled to work with controls"); + System.out.format("Control Tower must be enabled to work with controls"); } else { - logger.error("Target not found when listing enabled controls: {}", e.getMessage()); + System.out.format("Target not found when listing enabled controls: {}", e.getMessage()); } break; default: - logger.error("Error listing enabled controls: {}", e.getMessage()); + System.out.format("Error listing enabled controls: {}", e.getMessage()); } throw e; } catch (SdkException e) { - logger.error("SDK error listing enabled controls: {}", e.getMessage()); + System.out.format("SDK error listing enabled controls: {}", e.getMessage()); throw e; } } @@ -396,8 +394,8 @@ public static String enableControl(ControlTowerClient controlTowerClient, EnableControlResponse response = controlTowerClient.enableControl(request); String operationId = response.operationIdentifier(); - - logger.info("Enabled control with operation ID: {}", operationId); + + System.out.format("Enabled control with operation ID: {}", operationId); return operationId; } catch (ControlTowerException e) { @@ -405,24 +403,24 @@ public static String enableControl(ControlTowerClient controlTowerClient, switch (errorCode) { case "ValidationException": if (e.getMessage().contains("already enabled")) { - logger.info("Control is already enabled for this target"); + System.out.format("Control is already enabled for this target"); return null; } - logger.error("Validation error enabling control: {}", e.getMessage()); + System.out.format("Validation error enabling control: {}", e.getMessage()); break; case "ResourceNotFoundException": if (e.getMessage().contains("not registered with AWS Control Tower")) { - logger.error("Control Tower must be enabled to work with controls"); + System.out.format("Control Tower must be enabled to work with controls"); } else { - logger.error("Control not found: {}", e.getMessage()); + System.out.format("Control not found: {}", e.getMessage()); } break; default: - logger.error("Error enabling control: {}", e.getMessage()); + System.out.format("Error enabling control: {}", e.getMessage()); } throw e; } catch (SdkException e) { - logger.error("SDK error enabling control: {}", e.getMessage()); + System.out.format("SDK error enabling control: {}", e.getMessage()); throw e; } } @@ -450,22 +448,22 @@ public static String disableControl(ControlTowerClient controlTowerClient, DisableControlResponse response = controlTowerClient.disableControl(request); String operationId = response.operationIdentifier(); - - logger.info("Disabled control with operation ID: {}", operationId); + + System.out.format("Disabled control with operation ID: {}", operationId); return operationId; } catch (ControlTowerException e) { String errorCode = e.awsErrorDetails().errorCode(); switch (errorCode) { case "ResourceNotFoundException": - logger.error("Control not found for disabling: {}", e.getMessage()); + System.out.format("Control not found for disabling: {}", e.getMessage()); break; default: - logger.error("Error disabling control: {}", e.getMessage()); + System.out.format("Error disabling control: {}", e.getMessage()); } throw e; } catch (SdkException e) { - logger.error("SDK error disabling control: {}", e.getMessage()); + System.out.format("SDK error disabling control: {}", e.getMessage()); throw e; } } @@ -490,22 +488,22 @@ public static ControlOperationStatus getControlOperation(ControlTowerClient cont GetControlOperationResponse response = controlTowerClient.getControlOperation(request); ControlOperationStatus status = response.controlOperation().status(); - - logger.info("Control operation status: {}", status); + + System.out.format("Control operation status: {}", status); return status; } catch (ControlTowerException e) { String errorCode = e.awsErrorDetails().errorCode(); switch (errorCode) { case "ResourceNotFoundException": - logger.error("Control operation not found: {}", e.getMessage()); + System.out.format("Control operation not found: {}", e.getMessage()); break; default: - logger.error("Error getting control operation status: {}", e.getMessage()); + System.out.format("Error getting control operation status: {}", e.getMessage()); } throw e; } catch (SdkException e) { - logger.error("SDK error getting control operation status: {}", e.getMessage()); + System.out.format("SDK error getting control operation status: {}", e.getMessage()); throw e; } } @@ -530,15 +528,15 @@ public static List response.controls().stream()) .forEach(controls::add); - - logger.info("Retrieved {} controls", controls.size()); + + System.out.format("Retrieved {} controls", controls.size()); return controls; } catch (SdkException e) { if (e.getMessage().contains("AccessDeniedException")) { - logger.error("Access denied. Please ensure you have the necessary permissions."); + System.out.format("Access denied. Please ensure you have the necessary permissions."); } else { - logger.error("Couldn't list controls. Here's why: {}", e.getMessage()); + System.out.format("Couldn't list controls. Here's why: {}", e.getMessage()); } throw e; } @@ -564,22 +562,22 @@ public static String resetEnabledBaseline(ControlTowerClient controlTowerClient, ResetEnabledBaselineResponse response = controlTowerClient.resetEnabledBaseline(request); String operationId = response.operationIdentifier(); - - logger.info("Reset enabled baseline with operation ID: {}", operationId); + + System.out.format("Reset enabled baseline with operation ID: {}", operationId); return operationId; } catch (ControlTowerException e) { String errorCode = e.awsErrorDetails().errorCode(); switch (errorCode) { case "ResourceNotFoundException": - logger.error("Target not found: {}", e.getMessage()); + System.out.format("Target not found: {}", e.getMessage()); break; default: - logger.error("Couldn't reset enabled baseline. Here's why: {}", e.getMessage()); + System.out.format("Couldn't reset enabled baseline. Here's why: {}", e.getMessage()); } throw e; } catch (SdkException e) { - logger.error("SDK error resetting enabled baseline: {}", e.getMessage()); + System.out.format("SDK error resetting enabled baseline: {}", e.getMessage()); throw e; } } diff --git a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java index 89d75092781..c7b771d524c 100644 --- a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java @@ -3,13 +3,20 @@ package com.example.controltower; -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; +import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; +import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.controlcatalog.ControlCatalogClient; +import software.amazon.awssdk.services.controlcatalog.model.ControlSummary; +import software.amazon.awssdk.services.organizations.OrganizationsClient; +import software.amazon.awssdk.services.organizations.model.*; import software.amazon.awssdk.services.controltower.ControlTowerClient; import software.amazon.awssdk.services.controltower.model.*; - import java.util.List; import java.util.Scanner; +import java.util.Set; +import java.util.stream.Collectors; +import static java.lang.System.*; /** * Before running this Java V2 code example, set up your development @@ -19,141 +26,338 @@ * * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html * - * This Java code example performs the following tasks: - * - * 1. Check Control Tower setup and list landing zones - * 2. List available baselines for governance - * 3. List currently enabled baselines - * 4. Enable a baseline for a target organizational unit - * 5. List enabled controls for the target - * 6. Enable a control for a target organizational unit - * 7. Monitor operation status - * 8. Clean up by disabling controls and baselines */ -// snippet-start:[controltower.java2.controltower_scenario.main] -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.LandingZoneSummary; -import software.amazon.awssdk.services.controltower.model.BaselineSummary; -import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; -import java.util.Scanner; - public class ControlTowerScenario { - private static final Logger logger = LoggerFactory.getLogger(ControlTowerScenario.class); + public static final String DASHES = new String(new char[80]).replace("\0", "-"); - static Scanner scanner = new Scanner(System.in); + static Scanner scanner = new Scanner(in); + + private OrganizationsClient orgClient; + private ControlCatalogClient catClient; + private String ouArn; + private String stack = null; + private String ouId = null; + private String accountId = null; + private String landingZoneArn = null; + private boolean useLandingZone = false; public static void main(String[] args) { - // Suppress AWS SDK internal debug logs - System.setProperty("software.amazon.awssdk.logging.level", "WARN"); - - logger.info(DASHES); - logger.info("Welcome to the AWS Control Tower basics scenario!"); - logger.info(DASHES); - logger.info(""" - AWS Control Tower is a service that enables you to set up and govern - a secure, multi-account AWS environment based on best practices. - Control Tower orchestrates several other AWS services to build a landing zone. - - This scenario will guide you through the basic operations of - Control Tower, including managing baselines and controls for - governance and compliance. - """); - - // waitForInputToContinue(scanner); - try { + out.println(DASHES); + out.println("Welcome to the AWS Control Tower basics scenario!"); + out.println(DASHES); + try { ControlTowerClient controlTowerClient = ControlTowerClient.builder() - .region(Region.US_EAST_1) - .credentialsProvider(DefaultCredentialsProvider.create()) - .httpClientBuilder(UrlConnectionHttpClient.builder()) // avoids Apache HTTP client - .build() ; + .region(Region.US_EAST_1) + .credentialsProvider(ProfileCredentialsProvider.create("default")) + .build(); - runScenario(controlTowerClient); + OrganizationsClient orgClient = OrganizationsClient.builder() + .region(Region.AWS_GLOBAL) + .credentialsProvider(ProfileCredentialsProvider.create("default")) + .build(); + + ControlCatalogClient catClient = ControlCatalogClient.builder() + .region(Region.US_EAST_1) + .credentialsProvider(ProfileCredentialsProvider.create("default")) + .build(); + + ControlTowerScenario scenario = new ControlTowerScenario(orgClient, catClient); + scenario.runScenario(controlTowerClient); - } catch (ControlTowerException e) { - logger.error("Control Tower service error: {}", e.awsErrorDetails().errorMessage()); } catch (Exception e) { - logger.error("Unexpected error: {}", e.getMessage(), e); + e.printStackTrace(); } } - private static void runScenario(ControlTowerClient controlTowerClient) { + // ---------------------------------------------------------- + // Constructor: store orgClient for setupOrganization() + // ---------------------------------------------------------- + public ControlTowerScenario(OrganizationsClient orgClient, ControlCatalogClient catClient) { + this.orgClient = orgClient; + this.catClient = catClient; + } + + // ---------------------------------------------------------- + // Main scenario flow + // ---------------------------------------------------------- + private void runScenario(ControlTowerClient controlTowerClient) { try { - // Step 1: Check Control Tower setup - logger.info(DASHES); - logger.info("Step 1: Checking Control Tower setup..."); - - /* - List landingZones = ControlTowerActions.listLandingZones(controlTowerClient); - if (landingZones.isEmpty()) { - logger.warn("⚠️ No landing zones found. Control Tower may not be set up."); - logger.warn("Please set up Control Tower in the AWS Console first."); - return; + 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 + """); + + out.println("Step 1: Listing landing zones..."); + waitForInputToContinue(scanner); + + // CALL: ControlTowerActions.listLandingZones() + List landingZones = + ControlTowerActions.listLandingZones(controlTowerClient); + + if (!landingZones.isEmpty()) { + System.out.println("\nAvailable Landing Zones:"); + + for (int i = 0; i < landingZones.size(); i++) { + LandingZoneSummary lz = landingZones.get(i); + System.out.printf("%d %s)%n", i + 1, lz.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(); + + System.out.println("Using landing zone ID: " + landingZoneArn); + + // CALL: setupOrganization() + String sandboxOuId = setupOrganization(); + ouId = sandboxOuId; + + } else if (askYesNo( + "Do you want to use a different existing Landing Zone for this demo? (y/n): ")) { + + useLandingZone = true; + + System.out.print("Enter landing zone id: "); + landingZoneArn = scanner.nextLine().trim(); + + // CALL: setupOrganization() + String sandboxOuId = setupOrganization(); + ouId = sandboxOuId; + } + } + waitForInputToContinue(scanner); + + // ---------------------------------------------------------- + // CALL: ControlTowerActions.listBaselines() + // ---------------------------------------------------------- + out.println(DASHES); + out.println("Step 2: Listing available baselines..."); + + List baselines = + ControlTowerActions.listBaselines(controlTowerClient); + + baselines.forEach(b -> { + out.println("Baseline: " + b.name()); + out.println(" ARN: " + b.arn()); + }); + waitForInputToContinue(scanner); + + // ---------------------------------------------------------- + // CALL: ControlTowerActions.listControls() + // ---------------------------------------------------------- + out.println(DASHES); + out.println("Managing Controls:"); + + List controls = + ControlTowerActions.listControls(catClient); + + out.println("\nListing first 5 available Controls:"); + + for (int i = 0; i < Math.min(5, controls.size()); i++) { + ControlSummary c = controls.get(i); + out.println(String.format("%d. %s - %s", + (i + 1), c.name(), c.arn())); } - logger.info("Found {} landing zone(s):", landingZones.size()); - for (LandingZoneSummary landingZone : landingZones) { - logger.info(" - ARN: {}", landingZone.arn()); + if (useLandingZone) { + + String targetOu = ouArn; + waitForInputToContinue(scanner); + + // ---------------------------------------------------------- + // CALL: ControlTowerActions.listEnabledControls() + // ---------------------------------------------------------- + List enabledControls = + ControlTowerActions.listEnabledControls(controlTowerClient, targetOu); + + out.println("\nListing enabled controls:"); + + for (int i = 0; i < enabledControls.size(); i++) { + EnabledControlSummary ec = enabledControls.get(i); + out.println(String.format("%d. %s", + (i + 1), ec.controlIdentifier())); + } + + // Determine first non-enabled control + Set enabledControlArns = enabledControls.stream() + .map(EnabledControlSummary::arn) + .collect(Collectors.toSet()); + + String controlArnToEnable = controls.stream() + .map(ControlSummary::arn) + .filter(arn -> !enabledControlArns.contains(arn)) + .findFirst() + .orElse(null); + + waitForInputToContinue(scanner); + + // ---------------------------------------------------------- + // CALL: ControlTowerActions.enableControl() + // ---------------------------------------------------------- + if (controlArnToEnable != null && + askYesNo("Do you want to enable the control " + + controlArnToEnable + "? (y/n): ")) { + + out.println("\nEnabling control: " + controlArnToEnable); + + String operationId = + ControlTowerActions.enableControl( + controlTowerClient, controlArnToEnable, targetOu); + + if (operationId != null) { + out.println("Enabled control with operation id " + operationId); + } + } + waitForInputToContinue(scanner); + + // ---------------------------------------------------------- + // CALL: ControlTowerActions.disableControl() + // ---------------------------------------------------------- + if (controlArnToEnable != null && + askYesNo("Do you want to disable the control? (y/n): ")) { + + out.println("\nDisabling control..."); + + String operationId = + ControlTowerActions.disableControl( + controlTowerClient, controlArnToEnable, targetOu); + + out.println("Disable operation ID: " + operationId); + } } + // Final pause waitForInputToContinue(scanner); - */ + out.println("\nThis concludes the example scenario."); + out.println("Thanks for watching!"); + out.println(DASHES); + out.println(DASHES); + out.println("Scenario completed Successfully!"); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + public String setupOrganization() { + System.out.println("\nChecking organization status..."); + String orgId; + + // ------------------------------- + // 1. Describe or create organization + // ------------------------------- + try { + DescribeOrganizationResponse desc = orgClient.describeOrganization(); + orgId = desc.organization().id(); + System.out.println("Account is part of organization: " + orgId); + + } catch (AwsServiceException e) { + + if ("AWSOrganizationsNotInUseException".equals(e.awsErrorDetails().errorCode())) { - // Step 2: List available baselines - logger.info(DASHES); - logger.info("Step 2: Listing available baselines..."); + System.out.println("No organization found. Creating one..."); + + CreateOrganizationResponse create = + orgClient.createOrganization( + CreateOrganizationRequest.builder() + .featureSet("ALL") + .build() + ); + + orgId = create.organization().id(); + System.out.println("Created organization: " + orgId); + + // ✅ NO WAITERS — we simply proceed + // Organizations may take time to stabilize, + // but this method returns immediately. - List baselines = ControlTowerActions.listBaselines(controlTowerClient); - if (baselines.isEmpty()) { - logger.warn("No baselines available."); } else { - logger.info("Available baselines:"); - for (BaselineSummary baseline : baselines) { - logger.info(" - Name: {}", baseline.name()); - logger.info(" ARN: {}", baseline.arn()); - logger.info(" Description: {}", baseline.description()); + throw e; + } + } + + // ------------------------------- + // 2. Locate or create Sandbox OU + // ------------------------------- + String sandboxOuId = null; + + ListRootsResponse roots = orgClient.listRoots(); + String rootId = roots.roots().get(0).id(); + + System.out.println("Checking Sandbox OU..."); + + for (ListOrganizationalUnitsForParentResponse page : + orgClient.listOrganizationalUnitsForParentPaginator( + ListOrganizationalUnitsForParentRequest.builder() + .parentId(rootId) + .build() + )) { + + for (OrganizationalUnit ou : page.organizationalUnits()) { + if ("Sandbox".equals(ou.name())) { + sandboxOuId = ou.id(); + this.ouArn = ou.arn(); + System.out.println("Found Sandbox OU: " + sandboxOuId); + break; } } - waitForInputToContinue(scanner); - /* + if (sandboxOuId != null) { + break; + } + } - logger.info(DASHES); - logger.info("🎉 Control Tower Basics scenario completed successfully!"); + // ------------------------------- + // Create OU if missing + // ------------------------------- + if (sandboxOuId == null) { + System.out.println("Creating Sandbox OU..."); + CreateOrganizationalUnitResponse created = + orgClient.createOrganizationalUnit( + CreateOrganizationalUnitRequest.builder() + .parentId(rootId) + .name("Sandbox") + .build() + ); - */ - } catch (ControlTowerException e) { - logger.error("Control Tower service error: {}", e.awsErrorDetails().errorMessage()); - throw e; - } catch (Exception e) { - logger.error("Unexpected error: {}", e.getMessage(), e); - throw e; + sandboxOuId = created.organizationalUnit().id(); + this.ouArn = created.organizationalUnit().arn(); + + System.out.println("Created Sandbox OU: " + sandboxOuId); + + // ✅ NO WAITER — return immediately } + + return sandboxOuId; } - private static void waitForInputToContinue(Scanner scanner) { - while (true) { - System.out.println(""); - System.out.println("Enter 'c' followed by to continue:"); - String input = scanner.nextLine(); + // ---------------------------------------------------------- + // Utility methods + // ---------------------------------------------------------- + private static boolean askYesNo(String msg) { + out.print(msg); + return scanner.nextLine().trim().toLowerCase().startsWith("y"); + } - if (input.trim().equalsIgnoreCase("c")) { - logger.info("Continuing with the program...\n"); + private static void waitForInputToContinue(Scanner sc) { + out.println("\nEnter 'c' then to continue:"); + while (true) { + String in = sc.nextLine(); + if ("c".equalsIgnoreCase(in.trim())) { + out.println("Continuing..."); break; - } else { - System.out.println("Invalid input. Please try again."); } } } } -// snippet-end:[controltower.java2.controltower_scenario.main] \ 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 index d3c223dd182..b6478acf8a7 100644 --- 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 @@ -3,6 +3,7 @@ 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; @@ -32,6 +33,7 @@ public static void main(String[] args) { try { ControlTowerClient controlTowerClient = ControlTowerClient.builder() .region(Region.US_EAST_1) + .credentialsProvider(ProfileCredentialsProvider.create("default")) .build() ; helloControlTower(controlTowerClient); } catch (ControlTowerException e) { diff --git a/javav2/example_code/controltower/src/main/java/resources/log4j2.xml b/javav2/example_code/controltower/src/main/java/resources/log4j2.xml deleted file mode 100644 index fcef26f34b8..00000000000 --- a/javav2/example_code/controltower/src/main/java/resources/log4j2.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/javav2/example_code/controltower/src/main/java/resources/logback.xml b/javav2/example_code/controltower/src/main/java/resources/logback.xml new file mode 100644 index 00000000000..d705768de36 --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/resources/logback.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + diff --git a/javav2/example_code/controltower/src/test/java/ControlTowerTest.java b/javav2/example_code/controltower/src/test/java/ControlTowerTest.java index c16e0049dc0..3dfd0afcf98 100644 --- a/javav2/example_code/controltower/src/test/java/ControlTowerTest.java +++ b/javav2/example_code/controltower/src/test/java/ControlTowerTest.java @@ -9,8 +9,14 @@ 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.ControlCatalogClient; import software.amazon.awssdk.services.controltower.ControlTowerClient; +import software.amazon.awssdk.services.organizations.OrganizationsClient; +import software.amazon.awssdk.services.organizations.model.DescribeOrganizationResponse; +import software.amazon.awssdk.services.organizations.model.ListOrganizationalUnitsForParentRequest; +import software.amazon.awssdk.services.organizations.model.ListOrganizationalUnitsForParentResponse; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -18,12 +24,26 @@ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class ControlTowerTest { private static ControlTowerClient controlTowerClient; + private static OrganizationsClient orgClient; + private static ControlCatalogClient catClient ; @BeforeAll public static void setUp() { controlTowerClient = ControlTowerClient.builder() .region(Region.US_EAST_1) + .credentialsProvider(ProfileCredentialsProvider.create("default")) .build(); + + orgClient = OrganizationsClient.builder() + .region(Region.AWS_GLOBAL) + .credentialsProvider(ProfileCredentialsProvider.create("default")) + .build(); + + catClient = ControlCatalogClient.builder() + .region(Region.US_EAST_1) + .credentialsProvider(ProfileCredentialsProvider.create("default")) + .build(); + } @Test @@ -39,13 +59,14 @@ public void testHelloService() { @Order(2) public void testControlTowerActions() { assertDoesNotThrow(() -> { + // SAFE: read-only, no admin role required ControlTowerActions.listLandingZones(controlTowerClient); ControlTowerActions.listBaselines(controlTowerClient); - ControlTowerActions.listEnabledBaselines(controlTowerClient); - - // Note: Enable/disable operations require valid ARNs and are not tested here - // to avoid modifying the actual Control Tower configuration + ControlTowerActions.listControls(catClient); + }); + System.out.println("Test 2 passed"); } + } \ No newline at end of file From fba906226f8f7340cd25213781f795c08655fefa Mon Sep 17 00:00:00 2001 From: Scott Macdonald Date: Tue, 16 Dec 2025 09:33:47 -0500 Subject: [PATCH 6/9] updated Java files --- javav2/example_code/controltower/pom.xml | 10 + .../controltower/ControlTowerActions.java | 1263 +++++++++++------ .../controltower/ControlTowerScenario.java | 70 +- .../src/main/java/resources/config.properties | 3 - .../src/main/java/resources/log4j2.xml | 20 + .../src/main/java/resources/logback.xml | 18 - .../src/test/java/ControlTowerTest.java | 6 +- 7 files changed, 906 insertions(+), 484 deletions(-) delete mode 100644 javav2/example_code/controltower/src/main/java/resources/config.properties create mode 100644 javav2/example_code/controltower/src/main/java/resources/log4j2.xml delete mode 100644 javav2/example_code/controltower/src/main/java/resources/logback.xml diff --git a/javav2/example_code/controltower/pom.xml b/javav2/example_code/controltower/pom.xml index a7f0363c5a6..b7b4b759257 100644 --- a/javav2/example_code/controltower/pom.xml +++ b/javav2/example_code/controltower/pom.xml @@ -41,6 +41,16 @@ logback-classic 1.4.8 + + software.amazon.awssdk + netty-nio-client + + + software.amazon.awssdk + aws-crt-client + 2.25.35 + + software.amazon.awssdk organizations diff --git a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java index a41a9c6ba46..ea84c9c35db 100644 --- a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java @@ -3,11 +3,24 @@ package com.example.controltower; +import io.netty.handler.logging.LogLevel; +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.controltower.ControlTowerClient; +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.ListBaselinesIterable; +import software.amazon.awssdk.services.controltower.paginators.ListBaselinesPublisher; import software.amazon.awssdk.services.controltower.paginators.ListEnabledBaselinesIterable; +import software.amazon.awssdk.services.controltower.paginators.ListEnabledBaselinesPublisher; import software.amazon.awssdk.services.controltower.paginators.ListEnabledControlsIterable; +import software.amazon.awssdk.services.controltower.paginators.ListEnabledControlsPublisher; import software.amazon.awssdk.services.controltower.paginators.ListLandingZonesIterable; import software.amazon.awssdk.services.controlcatalog.ControlCatalogClient; import software.amazon.awssdk.services.controlcatalog.paginators.ListControlsIterable; @@ -15,71 +28,253 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - +import software.amazon.awssdk.services.controlcatalog.ControlCatalogAsyncClient; +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.core.exception.SdkException; +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.CreateOrganizationResponse; +import software.amazon.awssdk.services.organizations.model.DescribeOrganizationResponse; +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 software.amazon.awssdk.utils.Pair; + +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 final Logger logger = LoggerFactory.getLogger(ControlTowerActions.class); + + + 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) + .credentialsProvider(ProfileCredentialsProvider.create("default")) + .overrideConfiguration(overrideConfig) + .build(); + } + + return controlTowerAsyncClient; + } + + public record OrgSetupResult(String orgId, String sandboxOuArn) {} + + public CompletableFuture setupOrganizationAsync() { + logger.info("Starting organization setup…"); + + OrganizationsAsyncClient client = getAsyncOrgClient(); + + // Step 1: Describe or create organization + CompletableFuture orgFuture = client.describeOrganization() + .thenApply(desc -> { + logger.info("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())) { + logger.info("No organization found. Creating one…"); + return client.createOrganization(CreateOrganizationRequest.builder() + .featureSet(OrganizationFeatureSet.ALL) + .build()) + .thenApply(createResp -> { + logger.info("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(); + logger.info("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()); + logger.info("Found Sandbox OU: {}", ou.id()); + break; + } + } + }) + .thenApply(v -> { + String sandboxArn = sandboxOuArnRef.get(); + if (sandboxArn == null) { + logger.warn("Sandbox OU not found."); + } + return new OrgSetupResult(orgId, sandboxArn); + }); + }); + }).exceptionally(ex -> { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + logger.error("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. * - * @param controlTowerClient the Control Tower client to use for the operation * @return a list of all landing zones * @throws ControlTowerException if a service-specific error occurs - * @throws SdkException if an SDK error occurs + * @throws SdkException if an SDK error occurs */ - public static List listLandingZones(ControlTowerClient controlTowerClient) { - try { - List landingZones = new ArrayList<>(); - - String nextToken = null; - - do { - ListLandingZonesRequest.Builder requestBuilder = ListLandingZonesRequest.builder(); - if (nextToken != null) { - requestBuilder.nextToken(nextToken); - } - - ListLandingZonesResponse response = controlTowerClient.listLandingZones(requestBuilder.build()); - - if (response.landingZones() != null) { - landingZones.addAll(response.landingZones()); - } - - nextToken = response.nextToken(); - } while (nextToken != null); - - System.out.format("Retrieved {} landing zones", landingZones.size()); - return landingZones; - - } catch (ControlTowerException e) { - String errorCode = e.awsErrorDetails().errorCode(); - switch (errorCode) { - case "AccessDeniedException": - System.out.format("Access denied when listing landing zones: {}", e.getMessage()); - break; - default: - System.out.format("Error listing landing zones: {}", e.getMessage()); - } - throw e; - } catch (SdkException e) { - System.out.format("SDK error listing landing zones: {}", e.getMessage()); - throw e; - } + public CompletableFuture> listLandingZonesAsync() { + logger.info("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 -> { + logger.info("Landing zone ARN: {}", lz.arn()); + landingZones.add(lz); + }); + } else { + logger.info("Page contained no landing zones."); + } + }) + .thenRun(() -> logger.info("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] @@ -87,49 +282,59 @@ public static List listLandingZones(ControlTowerClient contr /** * Lists all available baselines using pagination to retrieve complete results. * - * @param controlTowerClient the Control Tower client to use for the operation * @return a list of all baselines * @throws ControlTowerException if a service-specific error occurs - * @throws SdkException if an SDK error occurs + * @throws SdkException if an SDK error occurs */ - public static List listBaselines(ControlTowerClient controlTowerClient) { - try { - List baselines = new ArrayList<>(); - - String nextToken = null; - - do { - ListBaselinesRequest.Builder requestBuilder = ListBaselinesRequest.builder(); - if (nextToken != null) { - requestBuilder.nextToken(nextToken); - } - - ListBaselinesResponse response = controlTowerClient.listBaselines(requestBuilder.build()); - - if (response.baselines() != null) { - baselines.addAll(response.baselines()); - } - - nextToken = response.nextToken(); - } while (nextToken != null); - - System.out.format("Retrieved {} baselines", baselines.size()); - return baselines; - - } catch (ControlTowerException e) { - String errorCode = e.awsErrorDetails().errorCode(); - switch (errorCode) { - case "AccessDeniedException": - System.out.format("Access denied when listing baselines: {}", e.getMessage()); - break; - default: - System.out.format("Error listing baselines: {}", e.getMessage()); - } - throw e; - } catch (SdkException e) { - System.out.format("SDK error listing baselines: {}", e.getMessage()); - throw e; - } + public CompletableFuture> listBaselinesAsync() { + logger.info("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 -> { + logger.info("Baseline: {}", baseline.name()); + baselines.add(baseline); + }); + } else { + logger.info("Page contained no baselines."); + } + }) + .thenRun(() -> + logger.info("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] @@ -137,49 +342,73 @@ public static List listBaselines(ControlTowerClient controlTowe /** * Lists all enabled baselines using pagination to retrieve complete results. * - * @param controlTowerClient the Control Tower client to use for the operation * @return a list of all enabled baselines * @throws ControlTowerException if a service-specific error occurs - * @throws SdkException if an SDK error occurs + * @throws SdkException if an SDK error occurs */ - public static List listEnabledBaselines(ControlTowerClient controlTowerClient) { - try { - List enabledBaselines = new ArrayList<>(); - - String nextToken = null; - - do { - ListEnabledBaselinesRequest.Builder requestBuilder = ListEnabledBaselinesRequest.builder(); - if (nextToken != null) { - requestBuilder.nextToken(nextToken); - } - - ListEnabledBaselinesResponse response = controlTowerClient.listEnabledBaselines(requestBuilder.build()); - - if (response.enabledBaselines() != null) { - enabledBaselines.addAll(response.enabledBaselines()); - } - - nextToken = response.nextToken(); - } while (nextToken != null); - - System.out.format("Retrieved {} enabled baselines", enabledBaselines.size()); - return enabledBaselines; - - } catch (ControlTowerException e) { - String errorCode = e.awsErrorDetails().errorCode(); - switch (errorCode) { - case "ResourceNotFoundException": - System.out.format("Target not found when listing enabled baselines: {}", e.getMessage()); - break; - default: - System.out.format("Error listing enabled baselines: {}", e.getMessage()); - } - throw e; - } catch (SdkException e) { - System.out.format("SDK error listing enabled baselines: {}", e.getMessage()); - throw e; - } + public CompletableFuture> listEnabledBaselinesAsync() { + logger.info("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 -> { + logger.info("Enabled baseline: {}", baseline.baselineIdentifier()); + enabledBaselines.add(baseline); + }); + } else { + logger.info("Page contained no enabled baselines."); + } + }) + .thenRun(() -> + logger.info( + "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] @@ -187,52 +416,80 @@ public static List listEnabledBaselines(ControlTowerClie /** * Enables a baseline for a specified target. * - * @param controlTowerClient the Control Tower client to use for the operation * @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) + * @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 + * @throws SdkException if an SDK error occurs */ - public static String enableBaseline(ControlTowerClient controlTowerClient, - String baselineIdentifier, - String baselineVersion, - String targetIdentifier) { - try { - EnableBaselineRequest request = EnableBaselineRequest.builder() - .baselineIdentifier(baselineIdentifier) - .baselineVersion(baselineVersion) - .targetIdentifier(targetIdentifier) - .build(); - - EnableBaselineResponse response = controlTowerClient.enableBaseline(request); - String operationId = response.operationIdentifier(); - - System.out.format("Enabled baseline with operation ID: {}", operationId); - return operationId; - - } catch (ControlTowerException e) { - String errorCode = e.awsErrorDetails().errorCode(); - switch (errorCode) { - case "ValidationException": - if (e.getMessage().contains("already enabled")) { - System.out.format("Baseline is already enabled for this target"); - return null; + public static CompletableFuture enableBaselineAsync( + ControlTowerAsyncClient controlTowerAsyncClient, + 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 + ); } - System.out.format("Validation error enabling baseline: {}", e.getMessage()); - break; - case "ConflictException": - System.out.format("Conflict enabling baseline: {}", e.getMessage()); - break; - default: - System.out.format("Error enabling baseline: {}", e.getMessage()); - } - throw e; - } catch (SdkException e) { - System.out.format("SDK error enabling baseline: {}", e.getMessage()); - throw e; - } + }) + .thenApply(response -> { + String operationId = response.operationIdentifier(); + return "Enabled baseline with operation ID: " + operationId; + }); } // snippet-end:[controltower.java2.enable_baseline.main] @@ -240,42 +497,71 @@ public static String enableBaseline(ControlTowerClient controlTowerClient, /** * Disables a baseline for a specified target. * - * @param controlTowerClient the Control Tower client to use for the operation * @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 + * @throws SdkException if an SDK error occurs */ - public static String disableBaseline(ControlTowerClient controlTowerClient, - String enabledBaselineIdentifier) { - try { - DisableBaselineRequest request = DisableBaselineRequest.builder() - .enabledBaselineIdentifier(enabledBaselineIdentifier) - .build(); - - DisableBaselineResponse response = controlTowerClient.disableBaseline(request); - String operationId = response.operationIdentifier(); - - System.out.format("Disabled baseline with operation ID: {}", operationId); - return operationId; - - } catch (ControlTowerException e) { - String errorCode = e.awsErrorDetails().errorCode(); - switch (errorCode) { - case "ConflictException": - System.out.format("Conflict disabling baseline: {}", e.getMessage()); - break; - case "ResourceNotFoundException": - System.out.format("Baseline not found for disabling: {}", e.getMessage()); - break; - default: - System.out.format("Error disabling baseline: {}", e.getMessage()); - } - throw e; - } catch (SdkException e) { - System.out.format("SDK error disabling baseline: {}", e.getMessage()); - throw e; - } + public CompletableFuture disableBaselineAsync( + ControlTowerAsyncClient controlTowerAsyncClient, + String enabledBaselineIdentifier) { + + DisableBaselineRequest request = DisableBaselineRequest.builder() + .enabledBaselineIdentifier(enabledBaselineIdentifier) + .build(); + + return controlTowerAsyncClient.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] @@ -283,39 +569,63 @@ public static String disableBaseline(ControlTowerClient controlTowerClient, /** * Gets the status of a baseline operation. * - * @param controlTowerClient the Control Tower client to use for the 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 + * @throws SdkException if an SDK error occurs */ - public static BaselineOperationStatus getBaselineOperation(ControlTowerClient controlTowerClient, - String operationIdentifier) { - try { - GetBaselineOperationRequest request = GetBaselineOperationRequest.builder() - .operationIdentifier(operationIdentifier) - .build(); - - GetBaselineOperationResponse response = controlTowerClient.getBaselineOperation(request); - BaselineOperationStatus status = response.baselineOperation().status(); - - System.out.format("Baseline operation status: {}", status); - return status; - - } catch (ControlTowerException e) { - String errorCode = e.awsErrorDetails().errorCode(); - switch (errorCode) { - case "ResourceNotFoundException": - System.out.format("Baseline operation not found: {}", e.getMessage()); - break; - default: - System.out.format("Error getting baseline operation status: {}", e.getMessage()); - } - throw e; - } catch (SdkException e) { - System.out.format("SDK error getting baseline operation status: {}", e.getMessage()); - throw e; - } + public CompletableFuture getBaselineOperationAsync( + ControlTowerAsyncClient controlTowerAsyncClient, + String operationIdentifier) { + + GetBaselineOperationRequest request = GetBaselineOperationRequest.builder() + .operationIdentifier(operationIdentifier) + .build(); + + return controlTowerAsyncClient.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] @@ -323,52 +633,70 @@ public static BaselineOperationStatus getBaselineOperation(ControlTowerClient co /** * Lists all enabled controls for a specific target using pagination. * - * @param controlTowerClient the Control Tower client to use for the operation * @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 + * @throws SdkException if an SDK error occurs */ - public static List listEnabledControls(ControlTowerClient controlTowerClient, - String targetIdentifier) { - try { - List enabledControls = new ArrayList<>(); - - // Use paginator to retrieve all results - ListEnabledControlsIterable listIterable = controlTowerClient.listEnabledControlsPaginator( - ListEnabledControlsRequest.builder() - .targetIdentifier(targetIdentifier) - .build() - ); - - listIterable.stream() - .flatMap(response -> response.enabledControls().stream()) - .forEach(enabledControls::add); - - System.out.format("Retrieved {} enabled controls for target {}", enabledControls.size(), targetIdentifier); - return enabledControls; - - } catch (ControlTowerException e) { - String errorCode = e.awsErrorDetails().errorCode(); - switch (errorCode) { - case "AccessDeniedException": - System.out.format("Access denied when listing enabled controls: {}", e.getMessage()); - break; - case "ResourceNotFoundException": - if (e.getMessage().contains("not registered with AWS Control Tower")) { - System.out.format("Control Tower must be enabled to work with controls"); + public CompletableFuture> listEnabledControlsAsync(String targetIdentifier) { + logger.info("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 -> { + logger.info("Enabled control: {}", control.controlIdentifier()); + enabledControls.add(control); + }); } else { - System.out.format("Target not found when listing enabled controls: {}", e.getMessage()); + logger.info("Page contained no enabled controls."); } - break; - default: - System.out.format("Error listing enabled controls: {}", e.getMessage()); - } - throw e; - } catch (SdkException e) { - System.out.format("SDK error listing enabled controls: {}", e.getMessage()); - throw e; - } + }) + .thenRun(() -> logger.info( + "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] @@ -376,210 +704,303 @@ public static List listEnabledControls(ControlTowerClient /** * Enables a control for a specified target. * - * @param controlTowerClient the Control Tower client to use for the operation * @param controlIdentifier the identifier of the control to enable - * @param targetIdentifier the identifier of the target (e.g., OU ARN) + * @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 + * @throws SdkException if an SDK error occurs */ - public static String enableControl(ControlTowerClient controlTowerClient, - String controlIdentifier, - String targetIdentifier) { - try { - EnableControlRequest request = EnableControlRequest.builder() - .controlIdentifier(controlIdentifier) - .targetIdentifier(targetIdentifier) - .build(); - - EnableControlResponse response = controlTowerClient.enableControl(request); - String operationId = response.operationIdentifier(); - - System.out.format("Enabled control with operation ID: {}", operationId); - return operationId; - - } catch (ControlTowerException e) { - String errorCode = e.awsErrorDetails().errorCode(); - switch (errorCode) { - case "ValidationException": - if (e.getMessage().contains("already enabled")) { - System.out.format("Control is already enabled for this target"); - return null; + public CompletableFuture enableControlAsync( + ControlTowerAsyncClient controlTowerAsyncClient, + 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 + ); } - System.out.format("Validation error enabling control: {}", e.getMessage()); - break; - case "ResourceNotFoundException": - if (e.getMessage().contains("not registered with AWS Control Tower")) { - System.out.format("Control Tower must be enabled to work with controls"); - } else { - System.out.format("Control not found: {}", e.getMessage()); - } - break; - default: - System.out.format("Error enabling control: {}", e.getMessage()); - } - throw e; - } catch (SdkException e) { - System.out.format("SDK error enabling control: {}", e.getMessage()); - throw e; - } + }) + .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 controlTowerClient the Control Tower client to use for the operation * @param controlIdentifier the identifier of the control to disable - * @param targetIdentifier the identifier of the target (e.g., OU ARN) + * @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 + * @throws SdkException if an SDK error occurs */ - public static String disableControl(ControlTowerClient controlTowerClient, - String controlIdentifier, - String targetIdentifier) { - try { - DisableControlRequest request = DisableControlRequest.builder() - .controlIdentifier(controlIdentifier) - .targetIdentifier(targetIdentifier) - .build(); - - DisableControlResponse response = controlTowerClient.disableControl(request); - String operationId = response.operationIdentifier(); - - System.out.format("Disabled control with operation ID: {}", operationId); - return operationId; - - } catch (ControlTowerException e) { - String errorCode = e.awsErrorDetails().errorCode(); - switch (errorCode) { - case "ResourceNotFoundException": - System.out.format("Control not found for disabling: {}", e.getMessage()); - break; - default: - System.out.format("Error disabling control: {}", e.getMessage()); - } - throw e; - } catch (SdkException e) { - System.out.format("SDK error disabling control: {}", e.getMessage()); - throw e; - } + public CompletableFuture disableControlAsync( + ControlTowerAsyncClient controlTowerAsyncClient, + String controlIdentifier, + String targetIdentifier) { + + DisableControlRequest request = DisableControlRequest.builder() + .controlIdentifier(controlIdentifier) + .targetIdentifier(targetIdentifier) + .build(); + + return controlTowerAsyncClient.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 controlTowerClient the Control Tower client to use for the 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 + * @throws SdkException if an SDK error occurs */ - public static ControlOperationStatus getControlOperation(ControlTowerClient controlTowerClient, - String operationIdentifier) { - try { - GetControlOperationRequest request = GetControlOperationRequest.builder() - .operationIdentifier(operationIdentifier) - .build(); - - GetControlOperationResponse response = controlTowerClient.getControlOperation(request); - ControlOperationStatus status = response.controlOperation().status(); - - System.out.format("Control operation status: {}", status); - return status; - - } catch (ControlTowerException e) { - String errorCode = e.awsErrorDetails().errorCode(); - switch (errorCode) { - case "ResourceNotFoundException": - System.out.format("Control operation not found: {}", e.getMessage()); - break; - default: - System.out.format("Error getting control operation status: {}", e.getMessage()); - } - throw e; - } catch (SdkException e) { - System.out.format("SDK error getting control operation status: {}", e.getMessage()); - throw e; - } + 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. * - * @param controlCatalogClient the Control Catalog client to use for the operation * @return a list of controls * @throws SdkException if a service-specific error occurs */ - public static List listControls( - ControlCatalogClient controlCatalogClient) { - try { - List controls = new ArrayList<>(); - - ListControlsIterable paginator = controlCatalogClient.listControlsPaginator( - software.amazon.awssdk.services.controlcatalog.model.ListControlsRequest.builder().build()); - - paginator.stream() - .flatMap(response -> response.controls().stream()) - .forEach(controls::add); - - System.out.format("Retrieved {} controls", controls.size()); - return controls; - - } catch (SdkException e) { - if (e.getMessage().contains("AccessDeniedException")) { - System.out.format("Access denied. Please ensure you have the necessary permissions."); - } else { - System.out.format("Couldn't list controls. Here's why: {}", e.getMessage()); - } - throw e; - } + public CompletableFuture> listControlsAsync() { + logger.info("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 -> { + logger.info("Control name: {}", control.name()); + controls.add(control); + }); + } else { + logger.info("Page contained no controls."); + } + }) + .thenRun(() -> logger.info("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 controlTowerClient the Control Tower client to use for the operation * @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 + * @throws SdkException if an SDK error occurs */ - public static String resetEnabledBaseline(ControlTowerClient controlTowerClient, - String enabledBaselineIdentifier) { - try { - ResetEnabledBaselineRequest request = ResetEnabledBaselineRequest.builder() - .enabledBaselineIdentifier(enabledBaselineIdentifier) - .build(); + public CompletableFuture resetEnabledBaselineAsync( + String enabledBaselineIdentifier) { + + logger.info("Starting reset of enabled baseline…"); + ResetEnabledBaselineRequest request = ResetEnabledBaselineRequest.builder() + .enabledBaselineIdentifier(enabledBaselineIdentifier) + .build(); + + return getAsyncClient().resetEnabledBaseline(request) + .thenApply(response -> { + String operationId = response.operationIdentifier(); + logger.info("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": + logger.error("Target not found: {}", e.getMessage()); + break; + default: + logger.error("Couldn't reset enabled baseline. Here's why: {}", e.getMessage()); + } + throw new CompletionException(e); + } - ResetEnabledBaselineResponse response = controlTowerClient.resetEnabledBaseline(request); - String operationId = response.operationIdentifier(); - - System.out.format("Reset enabled baseline with operation ID: {}", operationId); - return operationId; - - } catch (ControlTowerException e) { - String errorCode = e.awsErrorDetails().errorCode(); - switch (errorCode) { - case "ResourceNotFoundException": - System.out.format("Target not found: {}", e.getMessage()); - break; - default: - System.out.format("Couldn't reset enabled baseline. Here's why: {}", e.getMessage()); - } - throw e; - } catch (SdkException e) { - System.out.format("SDK error resetting enabled baseline: {}", e.getMessage()); - throw e; - } + if (cause instanceof SdkException sdkEx) { + logger.error("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] } diff --git a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java index c7b771d524c..c9fc26acfeb 100644 --- a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java @@ -30,6 +30,9 @@ public class ControlTowerScenario { + + + public static final String DASHES = new String(new char[80]).replace("\0", "-"); static Scanner scanner = new Scanner(in); @@ -42,51 +45,37 @@ public class ControlTowerScenario { private String landingZoneArn = null; private boolean useLandingZone = false; + static { + // Disable AWS CRT logging completely + System.setProperty("aws.crt.log.level", "OFF"); + } + public static void main(String[] args) { + + out.println(DASHES); out.println("Welcome to the AWS Control Tower basics scenario!"); out.println(DASHES); - try { - ControlTowerClient controlTowerClient = ControlTowerClient.builder() - .region(Region.US_EAST_1) - .credentialsProvider(ProfileCredentialsProvider.create("default")) - .build(); - - OrganizationsClient orgClient = OrganizationsClient.builder() - .region(Region.AWS_GLOBAL) - .credentialsProvider(ProfileCredentialsProvider.create("default")) - .build(); - ControlCatalogClient catClient = ControlCatalogClient.builder() - .region(Region.US_EAST_1) - .credentialsProvider(ProfileCredentialsProvider.create("default")) - .build(); - - ControlTowerScenario scenario = new ControlTowerScenario(orgClient, catClient); - scenario.runScenario(controlTowerClient); + try { + runScenario(); } catch (Exception e) { e.printStackTrace(); } } - // ---------------------------------------------------------- - // Constructor: store orgClient for setupOrganization() - // ---------------------------------------------------------- - public ControlTowerScenario(OrganizationsClient orgClient, ControlCatalogClient catClient) { - this.orgClient = orgClient; - this.catClient = catClient; - } // ---------------------------------------------------------- // Main scenario flow // ---------------------------------------------------------- - private void runScenario(ControlTowerClient controlTowerClient) { - try { - out.println(DASHES); - System.out.println(""" + private static void runScenario() { + ControlTowerActions actions = new ControlTowerActions(); + + 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, @@ -96,19 +85,19 @@ private void runScenario(ControlTowerClient controlTowerClient) { out.println("Step 1: Listing landing zones..."); waitForInputToContinue(scanner); - // CALL: ControlTowerActions.listLandingZones() - List landingZones = - ControlTowerActions.listLandingZones(controlTowerClient); - + List landingZones = actions.listLandingZonesAsync().join(); if (!landingZones.isEmpty()) { System.out.println("\nAvailable Landing Zones:"); - for (int i = 0; i < landingZones.size(); i++) { LandingZoneSummary lz = landingZones.get(i); - System.out.printf("%d %s)%n", i + 1, lz.arn()); + System.out.printf("%d) %s%n", i + 1, lz.arn()); } + } else { + System.out.println("No landing zones found."); + } - if (askYesNo( + /* + if (askYesNo( "Do you want to use the first landing zone in the list (" + landingZones.get(0).arn() + ")? (y/n): ")) { @@ -134,6 +123,7 @@ private void runScenario(ControlTowerClient controlTowerClient) { ouId = sandboxOuId; } } + */ waitForInputToContinue(scanner); // ---------------------------------------------------------- @@ -142,15 +132,14 @@ private void runScenario(ControlTowerClient controlTowerClient) { out.println(DASHES); out.println("Step 2: Listing available baselines..."); - List baselines = - ControlTowerActions.listBaselines(controlTowerClient); - + List baselines = actions.listBaselinesAsync().join(); baselines.forEach(b -> { out.println("Baseline: " + b.name()); out.println(" ARN: " + b.arn()); }); waitForInputToContinue(scanner); + /* // ---------------------------------------------------------- // CALL: ControlTowerActions.listControls() // ---------------------------------------------------------- @@ -233,7 +222,8 @@ private void runScenario(ControlTowerClient controlTowerClient) { out.println("Disable operation ID: " + operationId); } - } + + // Final pause waitForInputToContinue(scanner); @@ -247,6 +237,8 @@ private void runScenario(ControlTowerClient controlTowerClient) { } catch (Exception e) { e.printStackTrace(); } + + */ } public String setupOrganization() { diff --git a/javav2/example_code/controltower/src/main/java/resources/config.properties b/javav2/example_code/controltower/src/main/java/resources/config.properties deleted file mode 100644 index c18495c2f4c..00000000000 --- a/javav2/example_code/controltower/src/main/java/resources/config.properties +++ /dev/null @@ -1,3 +0,0 @@ -dataAccessRoleArn = -s3Uri = -documentClassifier = diff --git a/javav2/example_code/controltower/src/main/java/resources/log4j2.xml b/javav2/example_code/controltower/src/main/java/resources/log4j2.xml new file mode 100644 index 00000000000..158cc536c37 --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/resources/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/javav2/example_code/controltower/src/main/java/resources/logback.xml b/javav2/example_code/controltower/src/main/java/resources/logback.xml deleted file mode 100644 index d705768de36..00000000000 --- a/javav2/example_code/controltower/src/main/java/resources/logback.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - diff --git a/javav2/example_code/controltower/src/test/java/ControlTowerTest.java b/javav2/example_code/controltower/src/test/java/ControlTowerTest.java index 3dfd0afcf98..d9a7c288833 100644 --- a/javav2/example_code/controltower/src/test/java/ControlTowerTest.java +++ b/javav2/example_code/controltower/src/test/java/ControlTowerTest.java @@ -60,9 +60,9 @@ public void testHelloService() { public void testControlTowerActions() { assertDoesNotThrow(() -> { // SAFE: read-only, no admin role required - ControlTowerActions.listLandingZones(controlTowerClient); - ControlTowerActions.listBaselines(controlTowerClient); - ControlTowerActions.listControls(catClient); + // ControlTowerActions.listLandingZones(controlTowerClient); + // ControlTowerActions.listBaselines(controlTowerClient); + // ControlTowerActions.listControls(catClient); }); From 158dd7463a78b4f15a2c218df76fd00bb28996f9 Mon Sep 17 00:00:00 2001 From: Scott Macdonald Date: Tue, 16 Dec 2025 17:21:44 -0500 Subject: [PATCH 7/9] updated Scenario --- .../controltower/ControlTowerScenario.java | 378 +++++++----------- .../src/main/java/resources/log4j2.xml | 22 +- 2 files changed, 147 insertions(+), 253 deletions(-) diff --git a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java index c9fc26acfeb..068ceb4380c 100644 --- a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java @@ -3,19 +3,17 @@ package com.example.controltower; -import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; -import software.amazon.awssdk.awscore.exception.AwsServiceException; -import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.controlcatalog.ControlCatalogClient; import software.amazon.awssdk.services.controlcatalog.model.ControlSummary; -import software.amazon.awssdk.services.organizations.OrganizationsClient; -import software.amazon.awssdk.services.organizations.model.*; import software.amazon.awssdk.services.controltower.ControlTowerClient; import software.amazon.awssdk.services.controltower.model.*; +import software.amazon.awssdk.services.organizations.OrganizationsClient; +import software.amazon.awssdk.services.organizations.model.*; + import java.util.List; import java.util.Scanner; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.logging.LogManager; +import java.util.logging.Logger; import static java.lang.System.*; /** @@ -25,25 +23,24 @@ * For more information, see the following documentation topic: * * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html - * */ - public class ControlTowerScenario { - - - + //private static final Logger logger = LoggerFactory.getLogger(ControlTowerScenario.class); public static final String DASHES = new String(new char[80]).replace("\0", "-"); - static Scanner scanner = new Scanner(in); + + private static final Scanner scanner = new Scanner(in); private OrganizationsClient orgClient; private ControlCatalogClient catClient; - private String ouArn; + + private static String ouArn; + private static String ouId = null; + private static String landingZoneArn = null; + private static boolean useLandingZone = false; + private String stack = null; - private String ouId = null; private String accountId = null; - private String landingZoneArn = null; - private boolean useLandingZone = false; static { // Disable AWS CRT logging completely @@ -53,285 +50,194 @@ public class ControlTowerScenario { public static void main(String[] args) { + // ----------------------------- + // Your program logic here + // ----------------------------- + System.out.println("Hello! No AWS SDK logging should appear now."); + out.println(DASHES); out.println("Welcome to the AWS Control Tower basics scenario!"); out.println(DASHES); - try { - runScenario(); - + runScenario(); } catch (Exception e) { e.printStackTrace(); } } - // ---------------------------------------------------------- // Main scenario flow // ---------------------------------------------------------- private static void runScenario() { + ControlTowerActions actions = new ControlTowerActions(); 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 - """); - - out.println("Step 1: Listing landing zones..."); - waitForInputToContinue(scanner); - - List landingZones = actions.listLandingZonesAsync().join(); - if (!landingZones.isEmpty()) { - System.out.println("\nAvailable Landing Zones:"); - for (int i = 0; i < landingZones.size(); i++) { - LandingZoneSummary lz = landingZones.get(i); - System.out.printf("%d) %s%n", i + 1, lz.arn()); - } - } else { - System.out.println("No landing zones found."); - } - - /* - 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(); - - System.out.println("Using landing zone ID: " + landingZoneArn); - - // CALL: setupOrganization() - String sandboxOuId = setupOrganization(); - ouId = sandboxOuId; + 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 + """); - } else if (askYesNo( - "Do you want to use a different existing Landing Zone for this demo? (y/n): ")) { + out.println("Step 1: Listing landing zones..."); + waitForInputToContinue(scanner); - useLandingZone = true; + List landingZones = + actions.listLandingZonesAsync().join(); - System.out.print("Enter landing zone id: "); - landingZoneArn = scanner.nextLine().trim(); - - // CALL: setupOrganization() - String sandboxOuId = setupOrganization(); - ouId = sandboxOuId; - } - } - */ - waitForInputToContinue(scanner); - - // ---------------------------------------------------------- - // CALL: ControlTowerActions.listBaselines() - // ---------------------------------------------------------- - out.println(DASHES); - out.println("Step 2: Listing available baselines..."); - - List baselines = actions.listBaselinesAsync().join(); - baselines.forEach(b -> { - out.println("Baseline: " + b.name()); - out.println(" ARN: " + b.arn()); - }); + /* + * IMPORTANT: + * If no landing zones exist, skip all landing-zone logic + * and continue directly to Step 2. + */ + if (landingZones.isEmpty()) { + out.println("No landing zones found. Landing-zone-dependent steps will be skipped."); + useLandingZone = false; waitForInputToContinue(scanner); + } else { - /* - // ---------------------------------------------------------- - // CALL: ControlTowerActions.listControls() - // ---------------------------------------------------------- - out.println(DASHES); - out.println("Managing Controls:"); - - List controls = - ControlTowerActions.listControls(catClient); - - out.println("\nListing first 5 available Controls:"); - - for (int i = 0; i < Math.min(5, controls.size()); i++) { - ControlSummary c = controls.get(i); - out.println(String.format("%d. %s - %s", - (i + 1), c.name(), c.arn())); + // Display landing zones + 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 (useLandingZone) { + // Ask whether to use the first landing zone + if (askYesNo("Do you want to use the first landing zone in the list (" + + landingZones.get(0).arn() + ")? (y/n): ")) { - String targetOu = ouArn; - waitForInputToContinue(scanner); + useLandingZone = true; + landingZoneArn = landingZones.get(0).arn(); - // ---------------------------------------------------------- - // CALL: ControlTowerActions.listEnabledControls() - // ---------------------------------------------------------- - List enabledControls = - ControlTowerActions.listEnabledControls(controlTowerClient, targetOu); + } else if (askYesNo("Do you want to use a different existing Landing Zone for this demo? (y/n): ")) { - out.println("\nListing enabled controls:"); - - for (int i = 0; i < enabledControls.size(); i++) { - EnabledControlSummary ec = enabledControls.get(i); - out.println(String.format("%d. %s", - (i + 1), ec.controlIdentifier())); - } - - // Determine first non-enabled control - Set enabledControlArns = enabledControls.stream() - .map(EnabledControlSummary::arn) - .collect(Collectors.toSet()); - - String controlArnToEnable = controls.stream() - .map(ControlSummary::arn) - .filter(arn -> !enabledControlArns.contains(arn)) - .findFirst() - .orElse(null); + useLandingZone = true; + out.print("Enter landing zone ARN: "); + landingZoneArn = scanner.nextLine().trim(); + } else { + out.println("Proceeding without a landing zone."); + useLandingZone = false; waitForInputToContinue(scanner); + } + } - // ---------------------------------------------------------- - // CALL: ControlTowerActions.enableControl() - // ---------------------------------------------------------- - if (controlArnToEnable != null && - askYesNo("Do you want to enable the control " - + controlArnToEnable + "? (y/n): ")) { - out.println("\nEnabling control: " + controlArnToEnable); + /* + // Setup organization only if a landing zone is used + out.println("Using landing zone ARN: " + landingZoneArn); - String operationId = - ControlTowerActions.enableControl( - controlTowerClient, controlArnToEnable, targetOu); + ControlTowerActions.OrgSetupResult result = + actions.setupOrganizationAsync().join(); - if (operationId != null) { - out.println("Enabled control with operation id " + operationId); - } - } - waitForInputToContinue(scanner); + ouId = result.sandboxOuArn(); + logger.info("Organization ID: {}", result.orgId()); + logger.info("Using Sandbox OU ARN: {}", ouId); - // ---------------------------------------------------------- - // CALL: ControlTowerActions.disableControl() - // ---------------------------------------------------------- - if (controlArnToEnable != null && - askYesNo("Do you want to disable the control? (y/n): ")) { - out.println("\nDisabling control..."); + */ + // - String operationId = - ControlTowerActions.disableControl( - controlTowerClient, controlArnToEnable, targetOu); + // ---------------------------------------------------------- + // CALL: ControlTowerActions.listBaselines() + // ---------------------------------------------------------- + out.println(DASHES); + out.println("Step 2: Listing available baselines..."); + waitForInputToContinue(scanner); - out.println("Disable operation ID: " + operationId); - } + List baselines = actions.listBaselinesAsync().join(); + baselines.forEach(b -> { + out.println("Baseline: " + b.name()); + out.println(" ARN: " + b.arn()); + }); + waitForInputToContinue(scanner); - // Final pause - waitForInputToContinue(scanner); + // ---------------------------------------------------------- + // CALL: ControlTowerActions.listControls() + // ---------------------------------------------------------- + out.println(DASHES); + out.println("Step 3: Managing Controls:"); - out.println("\nThis concludes the example scenario."); - out.println("Thanks for watching!"); - out.println(DASHES); - out.println(DASHES); - out.println("Scenario completed Successfully!"); + List controls = actions.listControlsAsync().join(); + out.println("\nListing first 5 available Controls:"); - } catch (Exception e) { - e.printStackTrace(); + for (int i = 0; i < Math.min(5, controls.size()); i++) { + ControlSummary c = controls.get(i); + out.println(String.format("%d. %s - %s", + (i + 1), c.name(), c.arn())); } - */ - } + if (useLandingZone) { - public String setupOrganization() { - System.out.println("\nChecking organization status..."); - String orgId; + String targetOu = ouArn; + waitForInputToContinue(scanner); - // ------------------------------- - // 1. Describe or create organization - // ------------------------------- - try { - DescribeOrganizationResponse desc = orgClient.describeOrganization(); - orgId = desc.organization().id(); - System.out.println("Account is part of organization: " + orgId); + // ---------------------------------------------------------- + // CALL: ControlTowerActions.listEnabledControls() + // ---------------------------------------------------------- + // List enabledControls = + // ControlTowerActions.listEnabledControls(controlTowerClient, targetOu); - } catch (AwsServiceException e) { + out.println("Listing enabled controls:"); - if ("AWSOrganizationsNotInUseException".equals(e.awsErrorDetails().errorCode())) { + // for (int i = 0; i < enabledControls.size(); i++) { + // EnabledControlSummary ec = enabledControls.get(i); + // out.println(String.format("%d. %s", + // (i + 1), ec.controlIdentifier())); + // } - System.out.println("No organization found. Creating one..."); + // Determine first non-enabled control + // Set enabledControlArns = enabledControls.stream() + // .map(EnabledControlSummary::arn) + // .collect(Collectors.toSet()); - CreateOrganizationResponse create = - orgClient.createOrganization( - CreateOrganizationRequest.builder() - .featureSet("ALL") - .build() - ); + // String controlArnToEnable = controls.stream() + // .map(ControlSummary::arn) + // .filter(arn -> !enabledControlArns.contains(arn)) + // .findFirst() + // .orElse(null); - orgId = create.organization().id(); - System.out.println("Created organization: " + orgId); + waitForInputToContinue(scanner); - // ✅ NO WAITERS — we simply proceed - // Organizations may take time to stabilize, - // but this method returns immediately. + // ---------------------------------------------------------- + // CALL: ControlTowerActions.enableControl() + // ---------------------------------------------------------- + // if (controlArnToEnable != null && + // askYesNo("Do you want to enable the control " + // + controlArnToEnable + "? (y/n): ")) { - } else { - throw e; - } - } + // out.println("\nEnabling control: " + controlArnToEnable); - // ------------------------------- - // 2. Locate or create Sandbox OU - // ------------------------------- - String sandboxOuId = null; - - ListRootsResponse roots = orgClient.listRoots(); - String rootId = roots.roots().get(0).id(); - - System.out.println("Checking Sandbox OU..."); - - for (ListOrganizationalUnitsForParentResponse page : - orgClient.listOrganizationalUnitsForParentPaginator( - ListOrganizationalUnitsForParentRequest.builder() - .parentId(rootId) - .build() - )) { - - for (OrganizationalUnit ou : page.organizationalUnits()) { - if ("Sandbox".equals(ou.name())) { - sandboxOuId = ou.id(); - this.ouArn = ou.arn(); - System.out.println("Found Sandbox OU: " + sandboxOuId); - break; - } - } + // String operationId = + // ControlTowerActions.enableControl(controlTowerClient, + // controlArnToEnable, targetOu); - if (sandboxOuId != null) { - break; - } + // if (operationId != null) { + // out.println("Enabled control with operation id " + operationId); + // } } - // ------------------------------- - // Create OU if missing - // ------------------------------- - if (sandboxOuId == null) { - System.out.println("Creating Sandbox OU..."); + waitForInputToContinue(scanner); - CreateOrganizationalUnitResponse created = - orgClient.createOrganizationalUnit( - CreateOrganizationalUnitRequest.builder() - .parentId(rootId) - .name("Sandbox") - .build() - ); + // ---------------------------------------------------------- + // CALL: ControlTowerActions.disableControl() + // ---------------------------------------------------------- + // if (controlArnToEnable != null && + // askYesNo("Do you want to disable the control? (y/n): ")) { - sandboxOuId = created.organizationalUnit().id(); - this.ouArn = created.organizationalUnit().arn(); + // out.println("\nDisabling control..."); - System.out.println("Created Sandbox OU: " + sandboxOuId); - - // ✅ NO WAITER — return immediately - } + // String operationId = + // ControlTowerActions.disableControl(controlTowerClient, + // controlArnToEnable, targetOu); - return sandboxOuId; + // out.println("Disable operation ID: " + operationId); + // } } // ---------------------------------------------------------- @@ -345,8 +251,8 @@ private static boolean askYesNo(String msg) { private static void waitForInputToContinue(Scanner sc) { out.println("\nEnter 'c' then to continue:"); while (true) { - String in = sc.nextLine(); - if ("c".equalsIgnoreCase(in.trim())) { + String input = sc.nextLine(); + if ("c".equalsIgnoreCase(input.trim())) { out.println("Continuing..."); break; } diff --git a/javav2/example_code/controltower/src/main/java/resources/log4j2.xml b/javav2/example_code/controltower/src/main/java/resources/log4j2.xml index 158cc536c37..1d16360937a 100644 --- a/javav2/example_code/controltower/src/main/java/resources/log4j2.xml +++ b/javav2/example_code/controltower/src/main/java/resources/log4j2.xml @@ -1,20 +1,8 @@ - - - - - - - - + - - - - - - - - - + + + + From 133f023a8114c441ad3c9f6864f7239de3d82bbc Mon Sep 17 00:00:00 2001 From: Scott Macdonald Date: Wed, 17 Dec 2025 12:08:12 -0500 Subject: [PATCH 8/9] updated scenario --- javav2/example_code/controltower/pom.xml | 12 +- .../controltower/ControlTowerScenario.java | 261 ------------------ .../controltower/HelloControlTower.java | 2 - .../{ => scenario}/ControlTowerActions.java | 103 +++---- .../scenario/ControlTowerScenario.java | 261 ++++++++++++++++++ .../src/main/java/resources/log4j2.xml | 8 - .../src/test/java/ControlTowerTest.java | 85 ++++-- 7 files changed, 362 insertions(+), 370 deletions(-) delete mode 100644 javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java rename javav2/example_code/controltower/src/main/java/com/example/controltower/{ => scenario}/ControlTowerActions.java (90%) create mode 100644 javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerScenario.java delete mode 100644 javav2/example_code/controltower/src/main/java/resources/log4j2.xml diff --git a/javav2/example_code/controltower/pom.xml b/javav2/example_code/controltower/pom.xml index b7b4b759257..9fad0ea76a1 100644 --- a/javav2/example_code/controltower/pom.xml +++ b/javav2/example_code/controltower/pom.xml @@ -31,16 +31,6 @@ software.amazon.awssdk controlcatalog - - org.slf4j - slf4j-api - 2.0.7 - - - ch.qos.logback - logback-classic - 1.4.8 - software.amazon.awssdk netty-nio-client @@ -95,7 +85,7 @@ exec-maven-plugin 3.1.0 - com.example.controltower.ControlTowerScenario + com.example.controltower.scenario.ControlTowerScenario us-east-1 diff --git a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java deleted file mode 100644 index 068ceb4380c..00000000000 --- a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerScenario.java +++ /dev/null @@ -1,261 +0,0 @@ -// 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.controlcatalog.ControlCatalogClient; -import software.amazon.awssdk.services.controlcatalog.model.ControlSummary; -import software.amazon.awssdk.services.controltower.ControlTowerClient; -import software.amazon.awssdk.services.controltower.model.*; -import software.amazon.awssdk.services.organizations.OrganizationsClient; -import software.amazon.awssdk.services.organizations.model.*; - -import java.util.List; -import java.util.Scanner; -import java.util.logging.LogManager; -import java.util.logging.Logger; -import static java.lang.System.*; - -/** - * 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 - */ -public class ControlTowerScenario { - - //private static final Logger logger = LoggerFactory.getLogger(ControlTowerScenario.class); - public static final String DASHES = new String(new char[80]).replace("\0", "-"); - - private static final Scanner scanner = new Scanner(in); - - private OrganizationsClient orgClient; - private ControlCatalogClient catClient; - - private static String ouArn; - private static String ouId = null; - private static String landingZoneArn = null; - private static boolean useLandingZone = false; - - private String stack = null; - private String accountId = null; - - static { - // Disable AWS CRT logging completely - System.setProperty("aws.crt.log.level", "OFF"); - } - - public static void main(String[] args) { - - - // ----------------------------- - // Your program logic here - // ----------------------------- - System.out.println("Hello! No AWS SDK logging should appear now."); - - - out.println(DASHES); - out.println("Welcome to the AWS Control Tower basics scenario!"); - out.println(DASHES); - - try { - runScenario(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - // ---------------------------------------------------------- - // Main scenario flow - // ---------------------------------------------------------- - private static void runScenario() { - - ControlTowerActions actions = new ControlTowerActions(); - - 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(); - - /* - * IMPORTANT: - * If no landing zones exist, skip all landing-zone logic - * and continue directly to Step 2. - */ - if (landingZones.isEmpty()) { - out.println("No landing zones found. Landing-zone-dependent steps will be skipped."); - useLandingZone = false; - waitForInputToContinue(scanner); - } else { - - // Display landing zones - out.println("\nAvailable Landing Zones:"); - for (int i = 0; i < landingZones.size(); i++) { - out.printf("%d) %s%n", i + 1, landingZones.get(i).arn()); - } - - // Ask whether to use the first landing zone - 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 only if a landing zone is used - out.println("Using landing zone ARN: " + landingZoneArn); - - ControlTowerActions.OrgSetupResult result = - actions.setupOrganizationAsync().join(); - - ouId = result.sandboxOuArn(); - logger.info("Organization ID: {}", result.orgId()); - logger.info("Using Sandbox OU ARN: {}", ouId); - - - */ - // - - // ---------------------------------------------------------- - // CALL: ControlTowerActions.listBaselines() - // ---------------------------------------------------------- - out.println(DASHES); - out.println("Step 2: Listing available baselines..."); - waitForInputToContinue(scanner); - - - List baselines = actions.listBaselinesAsync().join(); - baselines.forEach(b -> { - out.println("Baseline: " + b.name()); - out.println(" ARN: " + b.arn()); - }); - - waitForInputToContinue(scanner); - - // ---------------------------------------------------------- - // CALL: ControlTowerActions.listControls() - // ---------------------------------------------------------- - out.println(DASHES); - out.println("Step 3: Managing Controls:"); - - 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); - out.println(String.format("%d. %s - %s", - (i + 1), c.name(), c.arn())); - } - - if (useLandingZone) { - - String targetOu = ouArn; - waitForInputToContinue(scanner); - - // ---------------------------------------------------------- - // CALL: ControlTowerActions.listEnabledControls() - // ---------------------------------------------------------- - // List enabledControls = - // ControlTowerActions.listEnabledControls(controlTowerClient, targetOu); - - out.println("Listing enabled controls:"); - - // for (int i = 0; i < enabledControls.size(); i++) { - // EnabledControlSummary ec = enabledControls.get(i); - // out.println(String.format("%d. %s", - // (i + 1), ec.controlIdentifier())); - // } - - // Determine first non-enabled control - // Set enabledControlArns = enabledControls.stream() - // .map(EnabledControlSummary::arn) - // .collect(Collectors.toSet()); - - // String controlArnToEnable = controls.stream() - // .map(ControlSummary::arn) - // .filter(arn -> !enabledControlArns.contains(arn)) - // .findFirst() - // .orElse(null); - - waitForInputToContinue(scanner); - - // ---------------------------------------------------------- - // CALL: ControlTowerActions.enableControl() - // ---------------------------------------------------------- - // if (controlArnToEnable != null && - // askYesNo("Do you want to enable the control " - // + controlArnToEnable + "? (y/n): ")) { - - // out.println("\nEnabling control: " + controlArnToEnable); - - // String operationId = - // ControlTowerActions.enableControl(controlTowerClient, - // controlArnToEnable, targetOu); - - // if (operationId != null) { - // out.println("Enabled control with operation id " + operationId); - // } - } - - waitForInputToContinue(scanner); - - // ---------------------------------------------------------- - // CALL: ControlTowerActions.disableControl() - // ---------------------------------------------------------- - // if (controlArnToEnable != null && - // askYesNo("Do you want to disable the control? (y/n): ")) { - - // out.println("\nDisabling control..."); - - // String operationId = - // ControlTowerActions.disableControl(controlTowerClient, - // controlArnToEnable, targetOu); - - // out.println("Disable operation ID: " + operationId); - // } - } - - // ---------------------------------------------------------- - // Utility methods - // ---------------------------------------------------------- - private static boolean askYesNo(String msg) { - out.print(msg); - return scanner.nextLine().trim().toLowerCase().startsWith("y"); - } - - 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; - } - } - } -} 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 index b6478acf8a7..ea3c4081e1e 100644 --- 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 @@ -32,8 +32,6 @@ public class HelloControlTower { public static void main(String[] args) { try { ControlTowerClient controlTowerClient = ControlTowerClient.builder() - .region(Region.US_EAST_1) - .credentialsProvider(ProfileCredentialsProvider.create("default")) .build() ; helloControlTower(controlTowerClient); } catch (ControlTowerException e) { diff --git a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java b/javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerActions.java similarity index 90% rename from javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java rename to javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerActions.java index ea84c9c35db..676f81bfdf5 100644 --- a/javav2/example_code/controltower/src/main/java/com/example/controltower/ControlTowerActions.java +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerActions.java @@ -1,9 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -package com.example.controltower; +package com.example.controltower.scenario; -import io.netty.handler.logging.LogLevel; import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; @@ -11,39 +10,24 @@ 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.controltower.ControlTowerClient; 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.ListBaselinesIterable; import software.amazon.awssdk.services.controltower.paginators.ListBaselinesPublisher; -import software.amazon.awssdk.services.controltower.paginators.ListEnabledBaselinesIterable; import software.amazon.awssdk.services.controltower.paginators.ListEnabledBaselinesPublisher; -import software.amazon.awssdk.services.controltower.paginators.ListEnabledControlsIterable; import software.amazon.awssdk.services.controltower.paginators.ListEnabledControlsPublisher; -import software.amazon.awssdk.services.controltower.paginators.ListLandingZonesIterable; -import software.amazon.awssdk.services.controlcatalog.ControlCatalogClient; -import software.amazon.awssdk.services.controlcatalog.paginators.ListControlsIterable; import software.amazon.awssdk.core.exception.SdkException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import software.amazon.awssdk.services.controlcatalog.ControlCatalogAsyncClient; 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.core.exception.SdkException; 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.CreateOrganizationResponse; -import software.amazon.awssdk.services.organizations.model.DescribeOrganizationResponse; 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 software.amazon.awssdk.utils.Pair; import java.time.Duration; import java.util.List; @@ -65,12 +49,9 @@ // snippet-start:[controltower.java2.controltower_actions.main] public class ControlTowerActions { - - private static ControlCatalogAsyncClient controlCatalogAsyncClient; private static ControlTowerAsyncClient controlTowerAsyncClient; private static OrganizationsAsyncClient orgAsyncClient; - private static final Logger logger = LoggerFactory.getLogger(ControlTowerActions.class); private static OrganizationsAsyncClient getAsyncOrgClient() { @@ -137,7 +118,6 @@ private static ControlTowerAsyncClient getAsyncClient() { controlTowerAsyncClient = ControlTowerAsyncClient.builder() .httpClient(httpClient) - .credentialsProvider(ProfileCredentialsProvider.create("default")) .overrideConfiguration(overrideConfig) .build(); } @@ -148,26 +128,26 @@ private static ControlTowerAsyncClient getAsyncClient() { public record OrgSetupResult(String orgId, String sandboxOuArn) {} public CompletableFuture setupOrganizationAsync() { - logger.info("Starting organization setup…"); + System.out.println("Starting organization setup…"); OrganizationsAsyncClient client = getAsyncOrgClient(); // Step 1: Describe or create organization CompletableFuture orgFuture = client.describeOrganization() .thenApply(desc -> { - logger.info("Organization exists: {}", desc.organization().id()); + 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())) { - logger.info("No organization found. Creating one…"); + System.out.println("No organization found. Creating one…"); return client.createOrganization(CreateOrganizationRequest.builder() .featureSet(OrganizationFeatureSet.ALL) .build()) .thenApply(createResp -> { - logger.info("Created organization: {}", createResp.organization().id()); + System.out.println("Created organization: {}" + createResp.organization().id()); return createResp.organization(); }); } @@ -179,7 +159,7 @@ public CompletableFuture setupOrganizationAsync() { // Step 2: Locate Sandbox OU return orgFuture.thenCompose(org -> { String orgId = org.id(); - logger.info("Organization ID: {}", orgId); + System.out.println("Organization ID: {}"+ orgId); return client.listRoots() .thenCompose(rootsResp -> { @@ -204,7 +184,7 @@ public CompletableFuture setupOrganizationAsync() { for (OrganizationalUnit ou : page.organizationalUnits()) { if ("Sandbox".equals(ou.name())) { sandboxOuArnRef.set(ou.arn()); - logger.info("Found Sandbox OU: {}", ou.id()); + System.out.println("Found Sandbox OU: " + ou.id()); break; } } @@ -212,14 +192,14 @@ public CompletableFuture setupOrganizationAsync() { .thenApply(v -> { String sandboxArn = sandboxOuArnRef.get(); if (sandboxArn == null) { - logger.warn("Sandbox OU not found."); + System.out.println("Sandbox OU not found."); } return new OrgSetupResult(orgId, sandboxArn); }); }); }).exceptionally(ex -> { Throwable cause = ex.getCause() != null ? ex.getCause() : ex; - logger.error("Failed to setup organization: {}", cause.getMessage()); + System.out.println("Failed to setup organization: {}" + cause.getMessage()); throw new CompletionException(cause); }); } @@ -235,7 +215,7 @@ public CompletableFuture setupOrganizationAsync() { * @throws SdkException if an SDK error occurs */ public CompletableFuture> listLandingZonesAsync() { - logger.info("Starting list landing zones paginator…"); + System.out.format("Starting list landing zones paginator…"); ListLandingZonesRequest request = ListLandingZonesRequest.builder().build(); ListLandingZonesPublisher paginator = getAsyncClient().listLandingZonesPaginator(request); @@ -244,14 +224,14 @@ public CompletableFuture> listLandingZonesAsync() { return paginator.subscribe(response -> { if (response.landingZones() != null && !response.landingZones().isEmpty()) { response.landingZones().forEach(lz -> { - logger.info("Landing zone ARN: {}", lz.arn()); + System.out.format("Landing zone ARN: {}", lz.arn()); landingZones.add(lz); }); } else { - logger.info("Page contained no landing zones."); + System.out.println("Page contained no landing zones."); } }) - .thenRun(() -> logger.info("Successfully retrieved {} landing zones.", landingZones.size())) + .thenRun(() -> System.out.println("Successfully retrieved {} landing zones."+ landingZones.size())) .thenApply(v -> landingZones) .exceptionally(ex -> { Throwable cause = ex.getCause() != null ? ex.getCause() : ex; @@ -287,7 +267,7 @@ public CompletableFuture> listLandingZonesAsync() { * @throws SdkException if an SDK error occurs */ public CompletableFuture> listBaselinesAsync() { - logger.info("Starting list baselines paginator…"); + System.out.format("Starting list baselines paginator…"); ListBaselinesRequest request = ListBaselinesRequest.builder().build(); ListBaselinesPublisher paginator = getAsyncClient().listBaselinesPaginator(request); @@ -296,15 +276,15 @@ public CompletableFuture> listBaselinesAsync() { return paginator.subscribe(response -> { if (response.baselines() != null && !response.baselines().isEmpty()) { response.baselines().forEach(baseline -> { - logger.info("Baseline: {}", baseline.name()); + System.out.format("Baseline: {}", baseline.name()); baselines.add(baseline); }); } else { - logger.info("Page contained no baselines."); + System.out.format("Page contained no baselines."); } }) .thenRun(() -> - logger.info("Successfully listed baselines. Total: {}", baselines.size()) + System.out.format("Successfully listed baselines. Total: {}", baselines.size()) ) .thenApply(v -> baselines) .exceptionally(ex -> { @@ -347,7 +327,7 @@ public CompletableFuture> listBaselinesAsync() { * @throws SdkException if an SDK error occurs */ public CompletableFuture> listEnabledBaselinesAsync() { - logger.info("Starting list enabled baselines paginator…"); + System.out.format("Starting list enabled baselines paginator…"); ListEnabledBaselinesRequest request = ListEnabledBaselinesRequest.builder().build(); @@ -361,16 +341,16 @@ public CompletableFuture> listEnabledBaselinesAsync && !response.enabledBaselines().isEmpty()) { response.enabledBaselines().forEach(baseline -> { - logger.info("Enabled baseline: {}", baseline.baselineIdentifier()); + System.out.format("Enabled baseline: {}", baseline.baselineIdentifier()); enabledBaselines.add(baseline); }); } else { - logger.info("Page contained no enabled baselines."); + System.out.format("Page contained no enabled baselines."); } }) .thenRun(() -> - logger.info( - "Successfully listed enabled baselines. Total: {}", + System.out.println( + "Successfully listed enabled baselines. Total: {}"+ enabledBaselines.size() ) ) @@ -423,8 +403,7 @@ public CompletableFuture> listEnabledBaselinesAsync * @throws ControlTowerException if a service-specific error occurs * @throws SdkException if an SDK error occurs */ - public static CompletableFuture enableBaselineAsync( - ControlTowerAsyncClient controlTowerAsyncClient, + public CompletableFuture enableBaselineAsync( String baselineIdentifier, String baselineVersion, String targetIdentifier) { @@ -503,14 +482,13 @@ public static CompletableFuture enableBaselineAsync( * @throws SdkException if an SDK error occurs */ public CompletableFuture disableBaselineAsync( - ControlTowerAsyncClient controlTowerAsyncClient, String enabledBaselineIdentifier) { DisableBaselineRequest request = DisableBaselineRequest.builder() .enabledBaselineIdentifier(enabledBaselineIdentifier) .build(); - return controlTowerAsyncClient.disableBaseline(request) + return getAsyncClient().disableBaseline(request) .whenComplete((response, exception) -> { if (exception != null) { Throwable cause = exception.getCause() != null @@ -575,14 +553,13 @@ public CompletableFuture disableBaselineAsync( * @throws SdkException if an SDK error occurs */ public CompletableFuture getBaselineOperationAsync( - ControlTowerAsyncClient controlTowerAsyncClient, String operationIdentifier) { GetBaselineOperationRequest request = GetBaselineOperationRequest.builder() .operationIdentifier(operationIdentifier) .build(); - return controlTowerAsyncClient.getBaselineOperation(request) + return getAsyncClient().getBaselineOperation(request) .whenComplete((response, exception) -> { if (exception != null) { Throwable cause = exception.getCause() != null @@ -639,8 +616,7 @@ public CompletableFuture getBaselineOperationAsync( * @throws SdkException if an SDK error occurs */ public CompletableFuture> listEnabledControlsAsync(String targetIdentifier) { - logger.info("Starting list enabled controls paginator for target {}…", targetIdentifier); - + System.out.format("Starting list enabled controls paginator for target {}…", targetIdentifier); ListEnabledControlsRequest request = ListEnabledControlsRequest.builder() .targetIdentifier(targetIdentifier) .build(); @@ -652,14 +628,14 @@ public CompletableFuture> listEnabledControlsAsync(S return paginator.subscribe(response -> { if (response.enabledControls() != null && !response.enabledControls().isEmpty()) { response.enabledControls().forEach(control -> { - logger.info("Enabled control: {}", control.controlIdentifier()); + System.out.println("Enabled control: {}"+ control.controlIdentifier()); enabledControls.add(control); }); } else { - logger.info("Page contained no enabled controls."); + System.out.println("Page contained no enabled controls."); } }) - .thenRun(() -> logger.info( + .thenRun(() -> System.out.format( "Successfully retrieved {} enabled controls for target {}", enabledControls.size(), targetIdentifier @@ -711,7 +687,6 @@ public CompletableFuture> listEnabledControlsAsync(S * @throws SdkException if an SDK error occurs */ public CompletableFuture enableControlAsync( - ControlTowerAsyncClient controlTowerAsyncClient, String controlIdentifier, String targetIdentifier) { @@ -802,7 +777,6 @@ public CompletableFuture enableControlAsync( * @throws SdkException if an SDK error occurs */ public CompletableFuture disableControlAsync( - ControlTowerAsyncClient controlTowerAsyncClient, String controlIdentifier, String targetIdentifier) { @@ -811,7 +785,7 @@ public CompletableFuture disableControlAsync( .targetIdentifier(targetIdentifier) .build(); - return controlTowerAsyncClient.disableControl(request) + return getAsyncClient().disableControl(request) .whenComplete((response, exception) -> { if (exception != null) { Throwable cause = exception.getCause() != null @@ -915,7 +889,7 @@ public CompletableFuture getControlOperationAsync( * @throws SdkException if a service-specific error occurs */ public CompletableFuture> listControlsAsync() { - logger.info("Starting list controls paginator…"); + System.out.println("Starting list controls paginator…"); ListControlsRequest request = ListControlsRequest.builder().build(); ListControlsPublisher paginator = getAsyncCatClient().listControlsPaginator(request); @@ -924,14 +898,13 @@ public CompletableFuture> listControlsAsync() { return paginator.subscribe(response -> { if (response.controls() != null && !response.controls().isEmpty()) { response.controls().forEach(control -> { - logger.info("Control name: {}", control.name()); controls.add(control); }); } else { - logger.info("Page contained no controls."); + System.out.println("Page contained no controls."); } }) - .thenRun(() -> logger.info("Successfully retrieved {} controls.", controls.size())) + .thenRun(() -> System.out.println("Successfully retrieved {} controls."+ controls.size())) .thenApply(v -> controls) .exceptionally(ex -> { Throwable cause = ex.getCause() != null ? ex.getCause() : ex; @@ -968,7 +941,7 @@ public CompletableFuture> listControlsAsync() { public CompletableFuture resetEnabledBaselineAsync( String enabledBaselineIdentifier) { - logger.info("Starting reset of enabled baseline…"); + System.out.println("Starting reset of enabled baseline…"); ResetEnabledBaselineRequest request = ResetEnabledBaselineRequest.builder() .enabledBaselineIdentifier(enabledBaselineIdentifier) .build(); @@ -976,7 +949,7 @@ public CompletableFuture resetEnabledBaselineAsync( return getAsyncClient().resetEnabledBaseline(request) .thenApply(response -> { String operationId = response.operationIdentifier(); - logger.info("Reset enabled baseline with operation ID: {}", operationId); + System.out.println("Reset enabled baseline with operation ID: {}" + operationId); return operationId; }) .exceptionally(ex -> { @@ -986,16 +959,16 @@ public CompletableFuture resetEnabledBaselineAsync( String errorCode = e.awsErrorDetails().errorCode(); switch (errorCode) { case "ResourceNotFoundException": - logger.error("Target not found: {}", e.getMessage()); + System.out.println("Target not found: {}"+ e.getMessage()); break; default: - logger.error("Couldn't reset enabled baseline. Here's why: {}", e.getMessage()); + System.out.println("Couldn't reset enabled baseline. Here's why: {}" + e.getMessage()); } throw new CompletionException(e); } if (cause instanceof SdkException sdkEx) { - logger.error("SDK error resetting enabled baseline: {}", sdkEx.getMessage()); + System.out.println("SDK error resetting enabled baseline: {}"+ sdkEx.getMessage()); throw new CompletionException(sdkEx); } 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..9416311f3b4 --- /dev/null +++ b/javav2/example_code/controltower/src/main/java/com/example/controltower/scenario/ControlTowerScenario.java @@ -0,0 +1,261 @@ +// 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; + +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; + } + } + } +} diff --git a/javav2/example_code/controltower/src/main/java/resources/log4j2.xml b/javav2/example_code/controltower/src/main/java/resources/log4j2.xml deleted file mode 100644 index 1d16360937a..00000000000 --- a/javav2/example_code/controltower/src/main/java/resources/log4j2.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/javav2/example_code/controltower/src/test/java/ControlTowerTest.java b/javav2/example_code/controltower/src/test/java/ControlTowerTest.java index d9a7c288833..1e8aa3cadd4 100644 --- a/javav2/example_code/controltower/src/test/java/ControlTowerTest.java +++ b/javav2/example_code/controltower/src/test/java/ControlTowerTest.java @@ -1,31 +1,36 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import com.example.controltower.ControlTowerActions; 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.ControlCatalogClient; +import software.amazon.awssdk.services.controlcatalog.model.ControlSummary; import software.amazon.awssdk.services.controltower.ControlTowerClient; -import software.amazon.awssdk.services.organizations.OrganizationsClient; -import software.amazon.awssdk.services.organizations.model.DescribeOrganizationResponse; -import software.amazon.awssdk.services.organizations.model.ListOrganizationalUnitsForParentRequest; -import software.amazon.awssdk.services.organizations.model.ListOrganizationalUnitsForParentResponse; +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; - private static OrganizationsClient orgClient; - private static ControlCatalogClient catClient ; @BeforeAll public static void setUp() { @@ -33,17 +38,6 @@ public static void setUp() { .region(Region.US_EAST_1) .credentialsProvider(ProfileCredentialsProvider.create("default")) .build(); - - orgClient = OrganizationsClient.builder() - .region(Region.AWS_GLOBAL) - .credentialsProvider(ProfileCredentialsProvider.create("default")) - .build(); - - catClient = ControlCatalogClient.builder() - .region(Region.US_EAST_1) - .credentialsProvider(ProfileCredentialsProvider.create("default")) - .build(); - } @Test @@ -57,16 +51,61 @@ public void testHelloService() { @Test @Order(2) - public void testControlTowerActions() { + public void testControlTowerActionsAsync() { assertDoesNotThrow(() -> { + // Create an instance of the async actions class + ControlTowerActions actions = new ControlTowerActions(); + // SAFE: read-only, no admin role required - // ControlTowerActions.listLandingZones(controlTowerClient); - // ControlTowerActions.listBaselines(controlTowerClient); - // ControlTowerActions.listControls(catClient); + 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 From d324ea96534ff5f0d11815cc546265e4fe9858f0 Mon Sep 17 00:00:00 2001 From: Scott Macdonald Date: Wed, 17 Dec 2025 12:30:51 -0500 Subject: [PATCH 9/9] updated scenario --- .../scenario/ControlTowerScenario.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 index 9416311f3b4..ad7aaa041ca 100644 --- 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 @@ -14,6 +14,22 @@ 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", "-"); @@ -259,3 +275,4 @@ private static void waitForInputToContinue(Scanner sc) { } } } +// snippet-end:[controltower.java2.controltower_scenario.main] \ No newline at end of file