Skip to content

Commit

Permalink
Fixes 137. Add installer client factory, refreshing credentials provi…
Browse files Browse the repository at this point in the history
…der, and tests. (#142)

* Fixes 137. Add installer client factory, refreshing credentials provider, and tests.

This commit resolves issue 137 by adding a new AwsClientBuilder
Factory to the installer which automatically applies default
configurations: one of which being a new
RefreshingProfileDefaultCredentialsProvider, which acts just the same as
the DefaultCredentialsProvider, but automatically refreshing profile
credentials by recreating the underlying provider on each
resolveCredentials call.

* fix some checkstyle violations and reduce the allowed count in installer

* clean up unnecessary imports and log lines

* s/BoostAwsClientBuilderFactory/AwsClientBuilderFactory/, fix memory leak in ProfileUtils test, move IAM global region override to client factory

* upgrade installer aws-java-sdk to latest to fix TooManyRequests error in update

Co-authored-by: PoeppingT <[email protected]>
  • Loading branch information
PoeppingT and PoeppingT authored Oct 29, 2021
1 parent 2a59c12 commit 0380a12
Show file tree
Hide file tree
Showing 8 changed files with 632 additions and 62 deletions.
3 changes: 1 addition & 2 deletions installer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ limitations under the License.
</licenses>

<properties>
<aws.java.sdk.version>2.13.68</aws.java.sdk.version>
<checkstyle.maxAllowedViolations>135</checkstyle.maxAllowedViolations>
<checkstyle.maxAllowedViolations>138</checkstyle.maxAllowedViolations>
</properties>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,14 @@
*/
package com.amazon.aws.partners.saasfactory.saasboost;

import com.amazon.aws.partners.saasfactory.saasboost.clients.AwsClientBuilderFactory;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.core.SdkSystemSetting;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.core.exception.SdkServiceException;
import software.amazon.awssdk.core.internal.retry.SdkDefaultRetrySetting;
import software.amazon.awssdk.core.retry.RetryPolicy;
import software.amazon.awssdk.core.retry.backoff.BackoffStrategy;
import software.amazon.awssdk.core.retry.conditions.RetryCondition;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.apigateway.ApiGatewayClient;
Expand All @@ -54,7 +49,6 @@
import software.amazon.awssdk.services.s3.model.*;
import software.amazon.awssdk.services.ssm.SsmClient;
import software.amazon.awssdk.services.ssm.model.*;
import software.amazon.awssdk.services.sts.StsClient;

import java.io.*;
import java.nio.file.*;
Expand All @@ -71,15 +65,16 @@ public class SaaSBoostInstall {
private static final String OS = System.getProperty("os.name").toLowerCase();
private static final String VERSION = getVersionInfo();

private CloudFormationClient cfn;
private EcrClient ecr;
private IamClient iam;
private final AwsClientBuilderFactory awsClientBuilderFactory;
private final ApiGatewayClient apigw;
private final CloudFormationClient cfn;
private final EcrClient ecr;
private final IamClient iam;
// TODO do we need to reassign the quicksight client between getQuickSightUsername and setupQuicksight?
private QuickSightClient quickSight;
private S3Client s3;
private SsmClient ssm;
private StsClient sts;
private LambdaClient lambda;
private ApiGatewayClient apigw;
private final S3Client s3;
private final SsmClient ssm;
private final LambdaClient lambda;

private final String accountId;
private String envName;
Expand Down Expand Up @@ -133,46 +128,20 @@ public static ACTION ofChoice(int choice) {
}

public SaaSBoostInstall() {
this.s3 = S3Client.create();
this.ecr = EcrClient.create();
this.ssm = SsmClient.create();
this.sts = StsClient.create();
this.lambda = LambdaClient.create();
this.quickSight = QuickSightClient.create();
// IAM doesn't have a IamClient::create... because it's in the global region?
this.iam = IamClient.builder().region(Region.AWS_GLOBAL).build();
// API Gateway and CloudFormation throttle pretty aggressively on api deployments and describe stack calls,
// so we'll make sure we have a retry policy in place.
this.apigw = ApiGatewayClient.builder()
awsClientBuilderFactory = AwsClientBuilderFactory.builder()
.region(AWS_REGION)
.credentialsProvider(DefaultCredentialsProvider.create())
.overrideConfiguration(ClientOverrideConfiguration.builder()
.retryPolicy(RetryPolicy.builder()
.backoffStrategy(BackoffStrategy.defaultStrategy())
.throttlingBackoffStrategy(BackoffStrategy.defaultThrottlingStrategy())
.numRetries(SdkDefaultRetrySetting.defaultMaxAttempts())
.retryCondition(RetryCondition.defaultRetryCondition())
.build()
)
.build()
)
.build();
this.cfn = CloudFormationClient.builder()
.region(AWS_REGION)
.credentialsProvider(DefaultCredentialsProvider.create())
.overrideConfiguration(ClientOverrideConfiguration.builder()
.retryPolicy(RetryPolicy.builder()
.backoffStrategy(BackoffStrategy.defaultStrategy())
.throttlingBackoffStrategy(BackoffStrategy.defaultThrottlingStrategy())
.numRetries(SdkDefaultRetrySetting.defaultMaxAttempts())
.retryCondition(RetryCondition.defaultRetryCondition())
.build()
)
.build()
)
.build();

accountId = sts.getCallerIdentity().account();
apigw = awsClientBuilderFactory.apiGatewayBuilder().build();
cfn = awsClientBuilderFactory.cloudFormationBuilder().build();
ecr = awsClientBuilderFactory.ecrBuilder().build();
iam = awsClientBuilderFactory.iamBuilder().build();
lambda = awsClientBuilderFactory.lambdaBuilder().build();
quickSight = awsClientBuilderFactory.quickSightBuilder().build();
s3 = awsClientBuilderFactory.s3Builder().build();
ssm = awsClientBuilderFactory.ssmBuilder().build();

accountId = awsClientBuilderFactory.stsBuilder().build().getCallerIdentity().account();
}

public static void main(String[] args) {
Expand Down Expand Up @@ -746,6 +715,7 @@ protected static Path getWorkingDirectory() {

protected void getQuickSightUsername() {
Region quickSightRegion;
QuickSightClient oldClient = null;
while (true) {
System.out.print("Region where you registered for Amazon QuickSight (Press Enter for " + AWS_REGION.id() + "): ");
String quickSightAccountRegion = Keyboard.readString();
Expand All @@ -762,7 +732,8 @@ protected void getQuickSightUsername() {
if (quickSightRegion != null) {
// Update the SDK client for the proper AWS region if we need to
if (!AWS_REGION.equals(quickSightRegion)) {
quickSight = QuickSightClient.builder().region(quickSightRegion).build();
oldClient = quickSight;
quickSight = awsClientBuilderFactory.quickSightBuilder().region(quickSightRegion).build();
}
// See if there are QuickSight users in this account in this region
LinkedHashMap<String, User> quickSightUsers = getQuickSightUsers();
Expand All @@ -788,8 +759,8 @@ protected void getQuickSightUsername() {
}
}
// If we changed the QuickSight SDK client region to look up the username, put it back
if (!AWS_REGION.equals(quickSightRegion)) {
quickSight = QuickSightClient.create();
if (oldClient != null) {
quickSight = oldClient;
}
}

Expand Down Expand Up @@ -1499,8 +1470,6 @@ protected void createSaaSBoostStack(final String stackName, String adminEmail, B
boolean stackCompleted = false;
long sleepTime = 5L;
do {
// TODO how can we set this on the SDK client itself?
cfn = CloudFormationClient.create(); // refresh client due to timeouts
DescribeStacksResponse response = cfn.describeStacks(request -> request.stackName(stackName));
Stack stack = response.stacks().get(0);
if ("CREATE_COMPLETE".equalsIgnoreCase(stack.stackStatusAsString())) {
Expand Down Expand Up @@ -1544,7 +1513,6 @@ protected void updateCloudFormationStack(final String stackName, final Map<Strin
LOGGER.info("Waiting for update stack to complete for " + stackId);
long sleepTime = 3L;
while (true) {
cfn = CloudFormationClient.create(); // TODO why does the client timeout and can we change that setting?
DescribeStacksResponse response = cfn.describeStacks(request -> request.stackName(stackId));
Stack stack = response.stacks().get(0);
String stackStatus = stack.stackStatusAsString();
Expand Down Expand Up @@ -1655,7 +1623,6 @@ protected void deleteCloudFormationStack(final String stackName) {
cfn.deleteStack(request -> request.stackName(stackName));
long sleepTime = 5L;
while (true) {
//cfn = CloudFormationClient.create(); // TODO figure out how to extend the timeout in the client
try {
DescribeStacksResponse response = cfn.describeStacks(DescribeStacksRequest.builder()
.stackName(stackId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.amazon.aws.partners.saasfactory.saasboost.clients;

import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder;
import software.amazon.awssdk.core.SdkClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.apigateway.ApiGatewayClient;
import software.amazon.awssdk.services.apigateway.ApiGatewayClientBuilder;
import software.amazon.awssdk.services.cloudformation.CloudFormationClient;
import software.amazon.awssdk.services.cloudformation.CloudFormationClientBuilder;
import software.amazon.awssdk.services.ecr.EcrClient;
import software.amazon.awssdk.services.ecr.EcrClientBuilder;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.IamClientBuilder;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.LambdaClientBuilder;
import software.amazon.awssdk.services.quicksight.QuickSightClient;
import software.amazon.awssdk.services.quicksight.QuickSightClientBuilder;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3ClientBuilder;
import software.amazon.awssdk.services.ssm.SsmClient;
import software.amazon.awssdk.services.ssm.SsmClientBuilder;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.StsClientBuilder;

public class AwsClientBuilderFactory {

private static final AwsCredentialsProvider DEFAULT_CREDENTIALS_PROVIDER =
RefreshingProfileDefaultCredentialsProvider.builder().build();

private final Region awsRegion;
private final AwsCredentialsProvider credentialsProvider;

private AwsClientBuilderFactory(Builder builder) {
// passing no region or a null region to any of the AWS Client Builders
// leads to the default region from the configured profile being used
this.awsRegion = builder.defaultRegion;
this.credentialsProvider = builder.awsCredentialsProvider != null
? builder.awsCredentialsProvider
: DEFAULT_CREDENTIALS_PROVIDER;
}

// VisibleForTesting
<C extends SdkClient, B extends AwsClientBuilder<B, C>> B decorateBuilderWithDefaults(B builder) {
return builder
.credentialsProvider(credentialsProvider)
.region(awsRegion);
}

public ApiGatewayClientBuilder apiGatewayBuilder() {
return decorateBuilderWithDefaults(ApiGatewayClient.builder());
}

public CloudFormationClientBuilder cloudFormationBuilder() {
return decorateBuilderWithDefaults(CloudFormationClient.builder());
}

public EcrClientBuilder ecrBuilder() {
return decorateBuilderWithDefaults(EcrClient.builder());
}

public IamClientBuilder iamBuilder() {
// IAM is not regionalized: all endpoints except us-gov and aws-cn use the AWS_GLOBAL region
// ref: https://docs.aws.amazon.com/general/latest/gr/iam-service.html
return decorateBuilderWithDefaults(IamClient.builder()).region(Region.AWS_GLOBAL);
}

public LambdaClientBuilder lambdaBuilder() {
return decorateBuilderWithDefaults(LambdaClient.builder());
}

public QuickSightClientBuilder quickSightBuilder() {
return decorateBuilderWithDefaults(QuickSightClient.builder());
}

public S3ClientBuilder s3Builder() {
return decorateBuilderWithDefaults(S3Client.builder());
}

public SsmClientBuilder ssmBuilder() {
return decorateBuilderWithDefaults(SsmClient.builder());
}

public StsClientBuilder stsBuilder() {
return decorateBuilderWithDefaults(StsClient.builder());
}

public static Builder builder() {
return new Builder();
}

public static class Builder {
private Region defaultRegion;
private AwsCredentialsProvider awsCredentialsProvider;

private Builder() {

}

public Builder region(Region defaultRegion) {
this.defaultRegion = defaultRegion;
return this;
}

public Builder credentialsProvider(AwsCredentialsProvider awsCredentialsProvider) {
this.awsCredentialsProvider = awsCredentialsProvider;
return this;
}

public AwsClientBuilderFactory build() {
return new AwsClientBuilderFactory(this);
}
}
}
Loading

0 comments on commit 0380a12

Please sign in to comment.