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

fix: deployment workflow fails if there is no default vpc #672

Merged
merged 1 commit into from
Aug 24, 2022
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
2 changes: 2 additions & 0 deletions src/AWS.Deploy.CLI/Commands/DeployCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ private async Task<Recommendation> GetSelectedRecommendationFromPreviousDeployme
else
previousSettings = await _deployedApplicationQueryer.GetPreviousSettings(deployedApplication);

await orchestrator.ApplyAllReplacementTokens(selectedRecommendation, deployedApplication.Name);

selectedRecommendation = await orchestrator.ApplyRecommendationPreviousSettings(selectedRecommendation, previousSettings);

var header = $"Loading {deployedApplication.DisplayName} settings:";
Expand Down
190 changes: 190 additions & 0 deletions src/AWS.Deploy.CLI/Commands/TypeHints/ElasticBeanstalkVpcCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon.EC2.Model;
using Amazon.ECS.Model;
using AWS.Deploy.CLI.Extensions;
using AWS.Deploy.CLI.TypeHintResponses;
using AWS.Deploy.Common;
using AWS.Deploy.Common.Data;
using AWS.Deploy.Common.Recipes;
using AWS.Deploy.Common.TypeHintData;
using AWS.Deploy.Orchestration;
using AWS.Deploy.Orchestration.Data;
using Newtonsoft.Json;

namespace AWS.Deploy.CLI.Commands.TypeHints
{
/// <summary>
/// The <see cref="ElasticBeanstalkVpcCommand"/> type hint orchestrates the VPC object in Elastic Beanstalk environments.
/// </summary>
public class ElasticBeanstalkVpcCommand : ITypeHintCommand
philasmar marked this conversation as resolved.
Show resolved Hide resolved
{
private readonly IAWSResourceQueryer _awsResourceQueryer;
private readonly IConsoleUtilities _consoleUtilities;
private readonly IToolInteractiveService _toolInteractiveService;
private readonly IOptionSettingHandler _optionSettingHandler;

public ElasticBeanstalkVpcCommand(IAWSResourceQueryer awsResourceQueryer, IConsoleUtilities consoleUtilities, IToolInteractiveService toolInteractiveService, IOptionSettingHandler optionSettingHandler)
{
_awsResourceQueryer = awsResourceQueryer;
_consoleUtilities = consoleUtilities;
_toolInteractiveService = toolInteractiveService;
_optionSettingHandler = optionSettingHandler;
}

private async Task<List<Vpc>> GetData()
{
return await _awsResourceQueryer.GetListOfVpcs();
}

public async Task<TypeHintResourceTable> GetResources(Recommendation recommendation, OptionSettingItem optionSetting)
{
var vpcs = await GetData();
var resourceTable = new TypeHintResourceTable();

resourceTable.Rows = vpcs.ToDictionary(x => x.VpcId, x => x.GetDisplayableVpc()).Select(x => new TypeHintResource(x.Key, x.Value)).ToList();

return resourceTable;
}

public async Task<object> Execute(Recommendation recommendation, OptionSettingItem optionSetting)
philasmar marked this conversation as resolved.
Show resolved Hide resolved
{
_toolInteractiveService.WriteLine();

// Ask user if they want to Use a VPC
var useVpcOptionSetting = optionSetting.ChildOptionSettings.First(x => x.Id.Equals("UseVPC"));
var useVpcValue = _optionSettingHandler.GetOptionSettingValue<string>(recommendation, useVpcOptionSetting) ?? "false";
var useVpcAnswer = _consoleUtilities.AskYesNoQuestion(useVpcOptionSetting.Description, useVpcValue);
var useVpc = useVpcAnswer == YesNo.Yes;

// If user doesn't want to Use VPC, no need to continue
if (!useVpc)
return new ElasticBeanstalkVpcTypeHintResponse()
{
UseVPC = false
};

// Retrieve all the available VPCs
var vpcs = await GetData();

// If there are no VPCs, create a new one
if (!vpcs.Any())
{
_toolInteractiveService.WriteLine();
_toolInteractiveService.WriteLine("There are no VPCs in the selected account. The only option is to create a new one.");
return new ElasticBeanstalkVpcTypeHintResponse
{
UseVPC = true,
CreateNew = true
};
philasmar marked this conversation as resolved.
Show resolved Hide resolved
}

// Ask user to select a VPC from the available ones
_toolInteractiveService.WriteLine();
var currentVpcTypeHintResponse = optionSetting.GetTypeHintData<ElasticBeanstalkVpcTypeHintResponse>();
var vpcOptionSetting = optionSetting.ChildOptionSettings.First(x => x.Id.Equals("VpcId"));
var currentVpcValue = _optionSettingHandler.GetOptionSettingValue(recommendation, vpcOptionSetting).ToString();
var userInputConfigurationVPCs = new UserInputConfiguration<Vpc>(
idSelector: vpc => vpc.VpcId,
displaySelector: vpc => vpc.GetDisplayableVpc(),
defaultSelector: vpc =>
!string.IsNullOrEmpty(currentVpcTypeHintResponse?.VpcId)
? vpc.VpcId == currentVpcTypeHintResponse.VpcId
: vpc.IsDefault)
{
CanBeEmpty = false,
CreateNew = true
};
var vpc = _consoleUtilities.AskUserToChooseOrCreateNew<Vpc>(vpcs, "Select a VPC:", userInputConfigurationVPCs);

// Create a new VPC if the user wants to do that
if (vpc.CreateNew)
return new ElasticBeanstalkVpcTypeHintResponse
{
UseVPC = true,
CreateNew = true
};

// If for some reason an option was not selected, don't use a VPC
if (vpc.SelectedOption == null)
return new ElasticBeanstalkVpcTypeHintResponse
{
UseVPC = false
};

// Retrieve available Subnets based on the selected VPC
var availableSubnets = (await _awsResourceQueryer.DescribeSubnets(vpc.SelectedOption.VpcId)).OrderBy(x => x.SubnetId).ToList();

// If there are no subnets, don't use a VPC
if (!availableSubnets.Any())
philasmar marked this conversation as resolved.
Show resolved Hide resolved
{
_toolInteractiveService.WriteLine();
_toolInteractiveService.WriteLine("The selected VPC does not have any Subnets. Please select a VPC with Subnets.");
return new ElasticBeanstalkVpcTypeHintResponse
{
UseVPC = false
};
}

// Ask user to select subnets based on the selected VPC
var userInputConfigurationSubnets = new UserInputConfiguration<Subnet>(
idSelector: subnet => subnet.SubnetId,
displaySelector: subnet => $"{subnet.SubnetId.PadRight(24)} | {subnet.VpcId.PadRight(21)} | {subnet.AvailabilityZone}",
defaultSelector: subnet => false)
{
CanBeEmpty = false,
CreateNew = false
};
var subnetsOptionSetting = optionSetting.ChildOptionSettings.First(x => x.Id.Equals("Subnets"));
_toolInteractiveService.WriteLine($"{subnetsOptionSetting.Id}:");
_toolInteractiveService.WriteLine(subnetsOptionSetting.Description);
var subnets = _consoleUtilities.AskUserForList<Subnet>(userInputConfigurationSubnets, availableSubnets, subnetsOptionSetting, recommendation);

// Retrieve available security groups based on the selected VPC
var availableSecurityGroups = (await _awsResourceQueryer.DescribeSecurityGroups(vpc.SelectedOption.VpcId)).OrderBy(x => x.VpcId).ToList();
if (!availableSecurityGroups.Any())
return new ElasticBeanstalkVpcTypeHintResponse
{
UseVPC = true,
CreateNew = false,
VpcId = vpc.SelectedOption.VpcId,
Subnets = subnets
};

// Get the length of the longest group name to do padding when displaying the security groups
var groupNamePadding = 0;
availableSecurityGroups.ForEach(x =>
{
if (x.GroupName.Length > groupNamePadding)
groupNamePadding = x.GroupName.Length;
});

// Ask user to select security groups
var userInputConfigurationSecurityGroups = new UserInputConfiguration<SecurityGroup>(
idSelector: securityGroup => securityGroup.GroupId,
displaySelector: securityGroup => $"{securityGroup.GroupName.PadRight(groupNamePadding)} | {securityGroup.GroupId.PadRight(20)} | {securityGroup.VpcId}",
defaultSelector: securityGroup => false)
{
CanBeEmpty = false,
CreateNew = false
};
var securityGroupsOptionSetting = optionSetting.ChildOptionSettings.First(x => x.Id.Equals("SecurityGroups"));
_toolInteractiveService.WriteLine($"{securityGroupsOptionSetting.Id}:");
_toolInteractiveService.WriteLine(securityGroupsOptionSetting.Description);
var securityGroups = _consoleUtilities.AskUserForList<SecurityGroup>(userInputConfigurationSecurityGroups, availableSecurityGroups, securityGroupsOptionSetting, recommendation);

return new ElasticBeanstalkVpcTypeHintResponse
{
UseVPC = true,
CreateNew = false,
VpcId = vpc.SelectedOption.VpcId,
Subnets = subnets,
SecurityGroups = securityGroups
};
}
}
}
36 changes: 6 additions & 30 deletions src/AWS.Deploy.CLI/Commands/TypeHints/ExistingVpcCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Threading.Tasks;
using Amazon.EC2.Model;
using AWS.Deploy.CLI.Extensions;
using AWS.Deploy.Common;
using AWS.Deploy.Common.Data;
using AWS.Deploy.Common.Recipes;
Expand All @@ -13,6 +14,9 @@

namespace AWS.Deploy.CLI.Commands.TypeHints
{
/// <summary>
/// The <see cref="ExistingVpcCommand"/> type hint lists existing VPC in an account for an option setting of type <see cref="OptionSettingValueType.String"/>.
/// </summary>
public class ExistingVpcCommand : ITypeHintCommand
{
private readonly IAWSResourceQueryer _awsResourceQueryer;
Expand All @@ -37,21 +41,7 @@ public async Task<TypeHintResourceTable> GetResources(Recommendation recommendat

var resourceTable = new TypeHintResourceTable();

resourceTable.Rows = vpcs.ToDictionary(x => x.VpcId, x =>
{
var name = x.Tags?.FirstOrDefault(x => x.Key == "Name")?.Value ?? string.Empty;
var namePart =
string.IsNullOrEmpty(name)
? ""
: $" ({name}) ";
var isDefaultPart =
x.IsDefault
? " *** Account Default VPC ***"
: "";
return $"{x.VpcId}{namePart}{isDefaultPart}";
}).Select(x => new TypeHintResource(x.Key, x.Value)).ToList();
resourceTable.Rows = vpcs.ToDictionary(x => x.VpcId, x => x.GetDisplayableVpc()).Select(x => new TypeHintResource(x.Key, x.Value)).ToList();

return resourceTable;
}
Expand All @@ -63,21 +53,7 @@ public async Task<object> Execute(Recommendation recommendation, OptionSettingIt

var userInputConfig = new UserInputConfiguration<Vpc>(
idSelector: vpc => vpc.VpcId,
displaySelector: vpc =>
{
var name = vpc.Tags?.FirstOrDefault(x => x.Key == "Name")?.Value ?? string.Empty;
var namePart =
string.IsNullOrEmpty(name)
? ""
: $" ({name}) ";
var isDefaultPart =
vpc.IsDefault
? " *** Account Default VPC ***"
: "";
return $"{vpc.VpcId}{namePart}{isDefaultPart}";
},
displaySelector: vpc => vpc.GetDisplayableVpc(),
defaultSelector: vpc =>
!string.IsNullOrEmpty(currentVpcValue)
? vpc.VpcId == currentVpcValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public TypeHintCommandFactory(IServiceProvider serviceProvider, IToolInteractive
{ OptionSettingTypeHint.ExistingSecurityGroups, ActivatorUtilities.CreateInstance<ExistingSecurityGroupsCommand>(serviceProvider) },
{ OptionSettingTypeHint.VPCConnector, ActivatorUtilities.CreateInstance<VPCConnectorCommand>(serviceProvider) },
{ OptionSettingTypeHint.FilePath, ActivatorUtilities.CreateInstance<FilePathCommand>(serviceProvider) },
{ OptionSettingTypeHint.ElasticBeanstalkVpc, ActivatorUtilities.CreateInstance<ElasticBeanstalkVpcCommand>(serviceProvider) },
};
}

Expand Down
26 changes: 25 additions & 1 deletion src/AWS.Deploy.CLI/Commands/TypeHints/VPCConnectorCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,30 @@ public async Task<object> Execute(Recommendation recommendation, OptionSettingIt
_toolInteractiveService.WriteLine();
if (createNewVPCConnector)
{
var availableVpcs = await _awsResourceQueryer.GetListOfVpcs();
if (!availableVpcs.Any())
{
_toolInteractiveService.WriteLine("Your account does not contain a VPC, so we will create one for you and assign its Subnets and Security Groups to the VPC Connector.");
return new VPCConnectorTypeHintResponse
{
UseVPCConnector = true,
CreateNew = true,
CreateNewVpc = true
};
}

var createNewVpcOptionSetting = optionSetting.ChildOptionSettings.First(x => x.Id.Equals("CreateNewVpc"));
var createNewVpcOptionSettingValue = _optionSettingHandler.GetOptionSettingValue<string>(recommendation, createNewVpcOptionSetting) ?? "false";
var createNewVpcAnswer = _consoleUtilities.AskYesNoQuestion(createNewVpcOptionSetting.Description, createNewVpcOptionSettingValue);
var createNewVpc = createNewVpcAnswer == YesNo.Yes;
if (createNewVpc)
return new VPCConnectorTypeHintResponse
{
UseVPCConnector = true,
CreateNew = true,
CreateNewVpc = true
};

_toolInteractiveService.WriteLine("In order to create a new VPC Connector, you need to select 1 or more Subnets as well as 1 or more Security groups.");
_toolInteractiveService.WriteLine();

Expand All @@ -89,7 +113,6 @@ public async Task<object> Execute(Recommendation recommendation, OptionSettingIt
CanBeEmpty = false,
CreateNew = false
};
var availableVpcs = await _awsResourceQueryer.GetListOfVpcs();
var vpc = _consoleUtilities.AskUserToChooseOrCreateNew<Vpc>(availableVpcs, "Select a VPC:", userInputConfigurationVPCs);

if (vpc.SelectedOption == null)
Expand Down Expand Up @@ -136,6 +159,7 @@ public async Task<object> Execute(Recommendation recommendation, OptionSettingIt
{
UseVPCConnector = true,
CreateNew = true,
CreateNewVpc = false,
VpcId = vpc.SelectedOption.VpcId,
Subnets = subnets,
SecurityGroups = securityGroups
Expand Down
Loading