Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read region value for non default profiles #880

Merged
merged 1 commit into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
gcbeattyAWS marked this conversation as resolved.
Show resolved Hide resolved
{
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;
gcbeattyAWS marked this conversation as resolved.
Show resolved Hide resolved
}

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);
gcbeattyAWS marked this conversation as resolved.
Show resolved Hide resolved
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
Loading