Skip to content

Commit

Permalink
Update logic to read the region when reading non default profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
gcbeattyAWS committed Nov 12, 2024
1 parent cc33364 commit ed86700
Show file tree
Hide file tree
Showing 15 changed files with 1,092 additions and 20 deletions.
11 changes: 11 additions & 0 deletions .autover/changes/ebd7f49a-72cd-4c5b-83f4-2790a2560e94.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"Projects": [
{
"Name": "AWS.Deploy.CLI",
"Type": "Minor",
"ChangelogMessages": [
"Read region value for non default profiles"
]
}
]
}
17 changes: 17 additions & 0 deletions src/AWS.Deploy.CLI/AWSCredentialsFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime;

namespace AWS.Deploy.CLI
{
/// <inheritdoc />
public class AWSCredentialsFactory : IAWSCredentialsFactory
{
/// <inheritdoc />
public AWSCredentials Create()
{
return FallbackCredentialsFactory.GetCredentials();
}
}
}
53 changes: 39 additions & 14 deletions src/AWS.Deploy.CLI/AWSUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Linq;
using System.Threading.Tasks;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using AWS.Deploy.CLI.Utilities;
using AWS.Deploy.Common;
using AWS.Deploy.Common.IO;
Expand All @@ -17,7 +16,7 @@ namespace AWS.Deploy.CLI
{
public interface IAWSUtilities
{
Task<AWSCredentials> ResolveAWSCredentials(string? profileName);
Task<Tuple<AWSCredentials, string?>> ResolveAWSCredentials(string? profileName);
string ResolveAWSRegion(string? region, string? lastRegionUsed = null);
}

Expand All @@ -28,35 +27,54 @@ public class AWSUtilities : IAWSUtilities
private readonly IDirectoryManager _directoryManager;
private readonly IOptionSettingHandler _optionSettingHandler;
private readonly IServiceProvider _serviceProvider;
private readonly ICredentialProfileStoreChainFactory _credentialChainFactory;
private readonly ISharedCredentialsFileFactory _sharedCredentialsFileFactory;
private readonly IAWSCredentialsFactory _awsCredentialsFactory;

public AWSUtilities(
IServiceProvider serviceProvider,
IToolInteractiveService toolInteractiveService,
IConsoleUtilities consoleUtilities,
IDirectoryManager directoryManager,
IOptionSettingHandler optionSettingHandler)
IOptionSettingHandler optionSettingHandler,
ICredentialProfileStoreChainFactory credentialChainFactory,
ISharedCredentialsFileFactory sharedCredentialsFileFactory,
IAWSCredentialsFactory awsCredentialsFactory)
{
_serviceProvider = serviceProvider;
_toolInteractiveService = toolInteractiveService;
_consoleUtilities = consoleUtilities;
_directoryManager = directoryManager;
_optionSettingHandler = optionSettingHandler;
_credentialChainFactory = credentialChainFactory;
_sharedCredentialsFileFactory = sharedCredentialsFileFactory;
_awsCredentialsFactory = awsCredentialsFactory;
}

public async Task<AWSCredentials> ResolveAWSCredentials(string? profileName)

/// <summary>
/// At a high level there are 2 possible return values for this function:
/// 1. <Credentials, regionName> In this case, both the credentials and region were able to be read from the profile.
/// 2. <Credentials, null>: In this case, the region was not able to be read from the profile, so we return null for it. The null case will be handled later on by <see cref="ResolveAWSRegion">.
/// </summary>
public async Task<Tuple<AWSCredentials, string?>> ResolveAWSCredentials(string? profileName)
{
async Task<AWSCredentials> Resolve()
async Task<Tuple<AWSCredentials, string?>> Resolve()
{
var chain = new CredentialProfileStoreChain();
var chain = _credentialChainFactory.Create();

// Use provided profile to read credentials
if (!string.IsNullOrEmpty(profileName))
{
if (chain.TryGetAWSCredentials(profileName, out var profileCredentials) &&
// Skip checking CanLoadCredentials for AssumeRoleAWSCredentials because it might require an MFA token and the callback hasn't been setup yet.
(profileCredentials is AssumeRoleAWSCredentials || await CanLoadCredentials(profileCredentials)))
{
_toolInteractiveService.WriteLine($"Configuring AWS Credentials from Profile {profileName}.");
return profileCredentials;
chain.TryGetProfile(profileName, out var profile);
// Return the credentials since they must be found at this point.
// For region, we try to read it from the profile. If it's not found in the profile, then return null and region selection will be handled later on by ResolveAWSRegion.
return Tuple.Create<AWSCredentials, string?>(profileCredentials, profile.Region?.SystemName);
}
else
{
Expand All @@ -65,14 +83,17 @@ async Task<AWSCredentials> Resolve()
}
}

// Use default credentials
try
{
var fallbackCredentials = FallbackCredentialsFactory.GetCredentials();
var fallbackCredentials = _awsCredentialsFactory.Create();

if (await CanLoadCredentials(fallbackCredentials))
{
// Always return the credentials since they must be found at this point.
// For region, we return null here, since it will read from default region in ResolveAWSRegion
_toolInteractiveService.WriteLine("Configuring AWS Credentials using AWS SDK credential search.");
return fallbackCredentials;
return Tuple.Create<AWSCredentials, string?>(fallbackCredentials, null);
}
}
catch (AmazonServiceException ex)
Expand All @@ -82,7 +103,8 @@ async Task<AWSCredentials> Resolve()
_toolInteractiveService.WriteDebugLine(ex.PrettyPrint());
}

var sharedCredentials = new SharedCredentialsFile();
// Use Shared Credentials
var sharedCredentials = _sharedCredentialsFileFactory.Create();
if (sharedCredentials.ListProfileNames().Count == 0)
{
throw new NoAWSCredentialsFoundException(DeployToolErrorCode.UnableToResolveAWSCredentials, "Unable to resolve AWS credentials to access AWS.");
Expand All @@ -93,21 +115,24 @@ async Task<AWSCredentials> Resolve()
if (chain.TryGetAWSCredentials(selectedProfileName, out var selectedProfileCredentials) &&
(await CanLoadCredentials(selectedProfileCredentials)))
{
return selectedProfileCredentials;
// Return the credentials since they must be found at this point.
// For region, we try to read it from the profile. If it's not found in the profile, then return null and region selection will be handled later on by ResolveAWSRegion.
chain.TryGetProfile(selectedProfileName, out var profile);
return Tuple.Create<AWSCredentials, string?>(selectedProfileCredentials, profile.Region?.SystemName);
}

throw new NoAWSCredentialsFoundException(DeployToolErrorCode.UnableToCreateAWSCredentials, $"Unable to create AWS credentials for profile {selectedProfileName}.");
}

var credentials = await Resolve();
var credentialsAndRegion = await Resolve();

if (credentials is AssumeRoleAWSCredentials assumeRoleAWSCredentials)
if (credentialsAndRegion.Item1 is AssumeRoleAWSCredentials assumeRoleAWSCredentials)
{
var assumeOptions = assumeRoleAWSCredentials.Options;
assumeOptions.MfaTokenCodeCallback = ActivatorUtilities.CreateInstance<AssumeRoleMfaTokenCodeCallback>(_serviceProvider, assumeOptions).Execute;
}

return credentials;
return credentialsAndRegion;
}

private async Task<bool> CanLoadCredentials(AWSCredentials credentials)
Expand Down
12 changes: 6 additions & 6 deletions src/AWS.Deploy.CLI/Commands/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ private Command BuildDeployCommand()
deploymentSettings = await _deploymentSettingsHandler.ReadSettings(applyPath);
}

var awsCredentials = await _awsUtilities.ResolveAWSCredentials(input.Profile ?? deploymentSettings?.AWSProfile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? deploymentSettings?.AWSRegion);
var (awsCredentials, regionFromProfile) = await _awsUtilities.ResolveAWSCredentials(input.Profile ?? deploymentSettings?.AWSProfile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? deploymentSettings?.AWSRegion ?? regionFromProfile);

_commandLineWrapper.RegisterAWSContext(awsCredentials, awsRegion);
_awsClientFactory.RegisterAWSContext(awsCredentials, awsRegion);
Expand Down Expand Up @@ -318,8 +318,8 @@ private Command BuildDeleteCommand()
_toolInteractiveService.Diagnostics = input.Diagnostics;
_toolInteractiveService.DisableInteractive = input.Silent;

var awsCredentials = await _awsUtilities.ResolveAWSCredentials(input.Profile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region);
var (awsCredentials, regionFromProfile) = await _awsUtilities.ResolveAWSCredentials(input.Profile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? regionFromProfile);

_awsClientFactory.ConfigureAWSOptions(awsOption =>
{
Expand Down Expand Up @@ -401,8 +401,8 @@ private Command BuildListCommand()
{
_toolInteractiveService.Diagnostics = input.Diagnostics;

var awsCredentials = await _awsUtilities.ResolveAWSCredentials(input.Profile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region);
var (awsCredentials, regionFromProfile) = await _awsUtilities.ResolveAWSCredentials(input.Profile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? regionFromProfile);

_awsClientFactory.ConfigureAWSOptions(awsOptions =>
{
Expand Down
17 changes: 17 additions & 0 deletions src/AWS.Deploy.CLI/CredentialProfileStoreChainFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;

namespace AWS.Deploy.CLI
{
/// <inheritdoc />
public class CredentialProfileStoreChainFactory : ICredentialProfileStoreChainFactory
{
/// <inheritdoc />
public CredentialProfileStoreChain Create()
{
return new CredentialProfileStoreChain();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ public static void AddCustomServices(this IServiceCollection serviceCollection,
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IEnvironmentVariableManager), typeof(EnvironmentVariableManager), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDeployToolWorkspaceMetadata), typeof(DeployToolWorkspaceMetadata), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDeploymentSettingsHandler), typeof(DeploymentSettingsHandler), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICredentialProfileStoreChainFactory), typeof(CredentialProfileStoreChainFactory), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ISharedCredentialsFileFactory), typeof(SharedCredentialsFileFactory), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IAWSCredentialsFactory), typeof(AWSCredentialsFactory), lifetime));

var packageJsonTemplate = typeof(PackageJsonGenerator).Assembly.ReadEmbeddedFile(PackageJsonGenerator.TemplateIdentifier);
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IPackageJsonGenerator), (serviceProvider) => new PackageJsonGenerator(packageJsonTemplate), lifetime));
Expand Down
18 changes: 18 additions & 0 deletions src/AWS.Deploy.CLI/IAWSCredentialsFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime;

namespace AWS.Deploy.CLI
{
/// <summary>
/// Represents a factory for creating <see cref="AWSCredentials">
/// </summary>
public interface IAWSCredentialsFactory
{
/// <summary>
/// Creates <see cref="AWSCredentials">
/// </summary>
AWSCredentials Create();
}
}
18 changes: 18 additions & 0 deletions src/AWS.Deploy.CLI/ICredentialProfileStoreChainFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;

namespace AWS.Deploy.CLI
{
/// <summary>
/// Represents a factory for creating <see cref="CredentialProfileStoreChain">
/// </summary>
public interface ICredentialProfileStoreChainFactory
{
/// <summary>
/// Creates a <see cref="CredentialProfileStoreChain">
/// </summary>
CredentialProfileStoreChain Create();
}
}
18 changes: 18 additions & 0 deletions src/AWS.Deploy.CLI/ISharedCredentialsFileFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;

namespace AWS.Deploy.CLI
{
/// <summary>
/// Represents a factory for creating <see cref="SharedCredentialsFile">
/// </summary>
public interface ISharedCredentialsFileFactory
{
/// <summary>
/// Creates <see cref="SharedCredentialsFile">
/// </summary>
SharedCredentialsFile Create();
}
}
17 changes: 17 additions & 0 deletions src/AWS.Deploy.CLI/SharedCredentialsFileFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;

namespace AWS.Deploy.CLI
{
/// <inheritdoc />
public class SharedCredentialsFileFactory : ISharedCredentialsFileFactory
{
/// <inheritdoc />
public SharedCredentialsFile Create()
{
return new SharedCredentialsFile();
}
}
}
25 changes: 25 additions & 0 deletions test/AWS.Deploy.CLI.UnitTests/AWSCredentialsFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime;
using Xunit;

namespace AWS.Deploy.CLI.UnitTests
{
public class AWSCredentialsFactoryTests
{
[Fact]
public void Create_ReturnsAWSCredentialsInstance()
{
// Arrange
var factory = new AWSCredentialsFactory();

// Act
var result = factory.Create();

// Assert
Assert.NotNull(result);
Assert.IsAssignableFrom<AWSCredentials>(result);
}
}
}
Loading

0 comments on commit ed86700

Please sign in to comment.