1010import software .amazon .awssdk .http .async .SdkAsyncHttpClient ;
1111import software .amazon .awssdk .http .nio .netty .NettyNioAsyncHttpClient ;
1212import software .amazon .awssdk .services .cloudformation .CloudFormationAsyncClient ;
13- import software .amazon .awssdk .services .cloudformation .model .Capability ;
14- import software .amazon .awssdk .services .cloudformation .model .CloudFormationException ;
15- import software .amazon .awssdk .services .cloudformation .model .DescribeStacksRequest ;
16- import software .amazon .awssdk .services .cloudformation .model .DescribeStacksResponse ;
17- import software .amazon .awssdk .services .cloudformation .model .Output ;
13+ import software .amazon .awssdk .services .cloudformation .model .*;
1814import software .amazon .awssdk .services .cloudformation .model .Stack ;
1915import software .amazon .awssdk .services .cloudformation .waiters .CloudFormationAsyncWaiter ;
2016import software .amazon .awssdk .services .s3 .S3AsyncClient ;
2622import java .nio .file .Path ;
2723import java .nio .file .Paths ;
2824import java .time .Duration ;
29- import java .util .HashMap ;
30- import java .util .List ;
31- import java .util .Map ;
25+ import java .util .*;
3226import java .util .concurrent .CompletableFuture ;
3327import java .util .stream .Collectors ;
3428
@@ -39,150 +33,187 @@ public class CloudFormationHelper {
3933 private static CloudFormationAsyncClient cloudFormationClient ;
4034
4135 public static void main (String [] args ) {
36+ if (args .length < 1 ) {
37+ logger .error ("Usage: CloudFormationHelper <bucketName>" );
38+ return ;
39+ }
4240 emptyS3Bucket (args [0 ]);
4341 }
4442
4543 private static CloudFormationAsyncClient getCloudFormationClient () {
4644 if (cloudFormationClient == null ) {
4745 SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient .builder ()
48- .maxConcurrency (100 )
49- .connectionTimeout (Duration .ofSeconds (60 ))
50- .readTimeout (Duration .ofSeconds (60 ))
51- .writeTimeout (Duration .ofSeconds (60 ))
52- .build ();
46+ .maxConcurrency (100 )
47+ .connectionTimeout (Duration .ofSeconds (60 ))
48+ .readTimeout (Duration .ofSeconds (60 ))
49+ .writeTimeout (Duration .ofSeconds (60 ))
50+ .build ();
5351
5452 ClientOverrideConfiguration overrideConfig = ClientOverrideConfiguration .builder ()
55- .apiCallTimeout (Duration .ofMinutes (2 ))
56- .apiCallAttemptTimeout (Duration .ofSeconds (90 ))
57- .retryStrategy (RetryMode .STANDARD )
58- .build ();
53+ .apiCallTimeout (Duration .ofMinutes (2 ))
54+ .apiCallAttemptTimeout (Duration .ofSeconds (90 ))
55+ .retryStrategy (RetryMode .STANDARD )
56+ .build ();
5957
6058 cloudFormationClient = CloudFormationAsyncClient .builder ()
61- .httpClient (httpClient )
62- .overrideConfiguration (overrideConfig )
63- .build ();
59+ .httpClient (httpClient )
60+ .overrideConfiguration (overrideConfig )
61+ .build ();
6462 }
6563 return cloudFormationClient ;
6664 }
6765
6866 public static void deployCloudFormationStack (String stackName ) {
69- String templateBody ;
67+ logger . info ( "Deploying CloudFormation stack: {}" , stackName ) ;
7068 boolean doesExist = describeStack (stackName );
7169 if (!doesExist ) {
70+ String templateBody ;
7271 try {
7372 ClassLoader classLoader = Thread .currentThread ().getContextClassLoader ();
74- Path filePath = Paths .get (classLoader .getResource (CFN_TEMPLATE ).toURI ());
73+ Path filePath = Paths .get (Objects . requireNonNull ( classLoader .getResource (CFN_TEMPLATE ) ).toURI ());
7574 templateBody = Files .readString (filePath );
7675 } catch (IOException | URISyntaxException e ) {
76+ logger .error ("Failed to read CloudFormation template" , e );
7777 throw new RuntimeException (e );
7878 }
7979
8080 getCloudFormationClient ().createStack (b -> b .stackName (stackName )
81- .templateBody (templateBody )
82- .capabilities (Capability .CAPABILITY_IAM ))
83- .whenComplete ((csr , t ) -> {
84- if (csr != null ) {
85- System .out .println ("Stack creation requested, ARN is " + csr .stackId ());
81+ .templateBody (templateBody )
82+ .capabilities (Capability .CAPABILITY_IAM ))
83+ .whenComplete ((csr , t ) -> {
84+ if (t != null ) {
85+ logger .error ("Error creating stack {}" , stackName , t );
86+ throw new RuntimeException ("Stack creation failed" , t );
87+ }
88+
89+ logger .info ("Stack creation requested. ARN: {}" , csr .stackId ());
90+
8691 try (CloudFormationAsyncWaiter waiter = getCloudFormationClient ().waiter ()) {
8792 waiter .waitUntilStackCreateComplete (request -> request .stackName (stackName ))
88- .whenComplete ((dsr , th ) -> {
89- if (th != null ) {
90- System .out .println ("Error waiting for stack creation: " + th .getMessage ());
91- } else {
92- dsr .matched ().response ().orElseThrow (() -> new RuntimeException ("Failed to deploy" ));
93- System .out .println ("Stack created successfully" );
94- }
95- }).join ();
93+ .whenComplete ((dsr , th ) -> {
94+ if (th != null ) {
95+ logger .error ("Error waiting for stack creation: {}" , stackName , th );
96+ } else {
97+ dsr .matched ().response ()
98+ .orElseThrow (() -> new RuntimeException ("Stack creation failed for " + stackName ));
99+ logger .info ("Stack {} created successfully." , stackName );
100+
101+ // Print outputs immediately
102+ getStackOutputsAsync (stackName ).whenComplete ((outputs , outEx ) -> {
103+ if (outEx != null ) {
104+ logger .error ("Failed to fetch stack outputs" , outEx );
105+ } else {
106+ logger .info ("Stack Outputs for {}:" , stackName );
107+ outputs .forEach ((k , v ) -> logger .info (" {} = {}" , k , v ));
108+ }
109+ }).join ();
110+ }
111+ }).join ();
96112 }
97- } else {
98- System .out .format ("Error creating stack: " + t .getMessage (), t );
99- throw new RuntimeException (t .getCause ().getMessage (), t );
100- }
101- }).join ();
113+ }).join ();
102114 } else {
103- logger .info ("{} stack already exists" , stackName );
115+ logger .info ("Stack {} already exists, skipping creation. " , stackName );
104116 }
105117 }
106118
107119 // Check to see if the Stack exists before deploying it
108120 public static Boolean describeStack (String stackName ) {
109121 try {
110- CompletableFuture <? > future = getCloudFormationClient ().describeStacks ();
111- DescribeStacksResponse stacksResponse = ( DescribeStacksResponse ) future .join ();
112- List < Stack > stacks = stacksResponse .stacks ();
113- for ( Stack myStack : stacks ) {
114- if ( myStack . stackName (). compareTo ( stackName ) == 0 ) {
122+ CompletableFuture <DescribeStacksResponse > future = getCloudFormationClient ().describeStacks ();
123+ DescribeStacksResponse stacksResponse = future .join ();
124+ for ( Stack myStack : stacksResponse .stacks ()) {
125+ if ( myStack . stackName (). equals ( stackName ) ) {
126+ logger . info ( "Stack {} exists already." , stackName );
115127 return true ;
116128 }
117129 }
118130 } catch (CloudFormationException e ) {
119- System . err . println ( e . getMessage () );
131+ logger . error ( "Error describing stack {}" , stackName , e );
120132 }
121133 return false ;
122134 }
123135
124136 public static void destroyCloudFormationStack (String stackName ) {
137+ logger .info ("Deleting CloudFormation stack: {}" , stackName );
125138 getCloudFormationClient ().deleteStack (b -> b .stackName (stackName ))
126- .whenComplete ((dsr , t ) -> {
127- if (dsr != null ) {
128- System .out .println ("Delete stack requested ...." );
139+ .whenComplete ((dsr , t ) -> {
140+ if (t != null ) {
141+ logger .error ("Error deleting stack {}" , stackName , t );
142+ throw new RuntimeException ("Delete failed" , t );
143+ }
144+
145+ logger .info ("Delete stack requested: {}" , stackName );
129146 try (CloudFormationAsyncWaiter waiter = getCloudFormationClient ().waiter ()) {
130147 waiter .waitUntilStackDeleteComplete (request -> request .stackName (stackName ))
131- .whenComplete ((waiterResponse , throwable ) ->
132- System .out .println ("Stack deleted successfully." ))
133- .join ();
148+ .whenComplete ((waiterResponse , throwable ) -> {
149+ if (throwable != null ) {
150+ logger .error ("Error waiting for stack deletion {}" , stackName , throwable );
151+ } else {
152+ logger .info ("Stack {} deleted successfully." , stackName );
153+ }
154+ }).join ();
134155 }
135- } else {
136- System .out .format ("Error deleting stack: " + t .getMessage (), t );
137- throw new RuntimeException (t .getCause ().getMessage (), t );
138- }
139- }).join ();
156+ }).join ();
140157 }
141158
142159 public static CompletableFuture <Map <String , String >> getStackOutputsAsync (String stackName ) {
143- CloudFormationAsyncClient cloudFormationAsyncClient = getCloudFormationClient ( );
160+ logger . info ( "Fetching stack outputs for {}" , stackName );
144161
145162 DescribeStacksRequest describeStacksRequest = DescribeStacksRequest .builder ()
146- .stackName (stackName )
147- .build ();
148-
149- return cloudFormationAsyncClient .describeStacks (describeStacksRequest )
150- .handle ((describeStacksResponse , throwable ) -> {
151- if (throwable != null ) {
152- throw new RuntimeException ("Failed to get stack outputs for: " + stackName , throwable );
153- }
163+ .stackName (stackName )
164+ .build ();
154165
155- // Process the result
156- if (describeStacksResponse .stacks ().isEmpty ()) {
157- throw new RuntimeException ("Stack not found: " + stackName );
158- }
166+ return getCloudFormationClient ().describeStacks (describeStacksRequest )
167+ .handle ((describeStacksResponse , throwable ) -> {
168+ if (throwable != null ) {
169+ logger .error ("Failed to get stack outputs for {}" , stackName , throwable );
170+ throw new RuntimeException ("Failed to get stack outputs for: " + stackName , throwable );
171+ }
159172
160- Stack stack = describeStacksResponse .stacks ().get (0 );
161- Map <String , String > outputs = new HashMap <>();
162- for (Output output : stack .outputs ()) {
163- outputs .put (output .outputKey (), output .outputValue ());
164- }
173+ if (describeStacksResponse .stacks ().isEmpty ()) {
174+ throw new RuntimeException ("Stack not found: " + stackName );
175+ }
165176
166- return outputs ;
167- });
177+ Stack stack = describeStacksResponse .stacks ().get (0 );
178+ Map <String , String > outputs = new HashMap <>();
179+ for (Output output : stack .outputs ()) {
180+ outputs .put (output .outputKey (), output .outputValue ());
181+ }
182+ return outputs ;
183+ });
168184 }
169185
170186 public static void emptyS3Bucket (String bucketName ) {
187+ logger .info ("Emptying S3 bucket: {}" , bucketName );
171188 S3AsyncClient s3Client = S3AsyncClient .builder ().build ();
172189
173190 s3Client .listObjectsV2 (req -> req .bucket (bucketName ))
174- .thenCompose (response -> {
175- List <CompletableFuture <DeleteObjectResponse >> deleteFutures = response .contents ().stream ()
176- .map (s3Object -> s3Client .deleteObject (req -> req
177- .bucket (bucketName )
178- .key (s3Object .key ())))
179- .collect (Collectors .toList ());
191+ .thenCompose (response -> {
192+ if (response .contents ().isEmpty ()) {
193+ logger .info ("Bucket {} is already empty." , bucketName );
194+ return CompletableFuture .completedFuture (null );
195+ }
180196
181- return CompletableFuture .allOf (deleteFutures .toArray (new CompletableFuture [0 ]));
182- })
183- .join ();
197+ List <CompletableFuture <DeleteObjectResponse >> deleteFutures = response .contents ().stream ()
198+ .map (s3Object -> {
199+ logger .info ("Deleting object: {}" , s3Object .key ());
200+ return s3Client .deleteObject (req -> req
201+ .bucket (bucketName )
202+ .key (s3Object .key ()));
203+ })
204+ .collect (Collectors .toList ());
205+
206+ return CompletableFuture .allOf (deleteFutures .toArray (new CompletableFuture [0 ]));
207+ })
208+ .whenComplete ((res , ex ) -> {
209+ if (ex != null ) {
210+ logger .error ("Failed to empty bucket {}" , bucketName , ex );
211+ } else {
212+ logger .info ("Bucket {} emptied successfully." , bucketName );
213+ }
214+ })
215+ .join ();
184216
185217 s3Client .close ();
186218 }
187219}
188-
0 commit comments