Skip to content

Commit

Permalink
fix: deployment workflow fails if there is no default vpc
Browse files Browse the repository at this point in the history
  • Loading branch information
philasmar committed Jul 12, 2022
1 parent 8e21755 commit 7821440
Show file tree
Hide file tree
Showing 34 changed files with 610 additions and 74 deletions.
192 changes: 192 additions & 0 deletions src/AWS.Deploy.CLI/Commands/TypeHints/ElasticBeanstalkVpcCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// 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.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
{
public class ElasticBeanstalkVpcCommand : ITypeHintCommand
{
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 => {
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();

return resourceTable;
}

public async Task<object> Execute(Recommendation recommendation, OptionSettingItem optionSetting)
{
_toolInteractiveService.WriteLine();
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 (!useVpc)
return new ElasticBeanstalkVpcTypeHintResponse()
{
UseVPC = false
};

var currentVpcTypeHintResponse = optionSetting.GetTypeHintData<ElasticBeanstalkVpcTypeHintResponse>();

var vpcs = await GetData();

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
};
}

_toolInteractiveService.WriteLine();
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 =>
{
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}";
},
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);
if (vpc.CreateNew)
return new ElasticBeanstalkVpcTypeHintResponse
{
UseVPC = true,
CreateNew = true
};
if (vpc.SelectedOption == null)
return new ElasticBeanstalkVpcTypeHintResponse
{
UseVPC = false
};

var availableSubnets = (await _awsResourceQueryer.DescribeSubnets(vpc.SelectedOption.VpcId)).OrderBy(x => x.SubnetId).ToList();
if (!availableSubnets.Any())
return new ElasticBeanstalkVpcTypeHintResponse
{
UseVPC = true,
CreateNew = false,
VpcId = vpc.SelectedOption.VpcId
};
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);

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
};
var groupNamePadding = 0;
availableSecurityGroups.ForEach(x =>
{
if (x.GroupName.Length > groupNamePadding)
groupNamePadding = x.GroupName.Length;
});
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
};
}
}
}
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 it's 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
11 changes: 10 additions & 1 deletion src/AWS.Deploy.CLI/Commands/TypeHints/VpcCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ public class VpcCommand : ITypeHintCommand
{
private readonly IAWSResourceQueryer _awsResourceQueryer;
private readonly IConsoleUtilities _consoleUtilities;
private readonly IToolInteractiveService _toolInteractiveService;

public VpcCommand(IAWSResourceQueryer awsResourceQueryer, IConsoleUtilities consoleUtilities)
public VpcCommand(IAWSResourceQueryer awsResourceQueryer, IConsoleUtilities consoleUtilities, IToolInteractiveService toolInteractiveService)
{
_awsResourceQueryer = awsResourceQueryer;
_consoleUtilities = consoleUtilities;
_toolInteractiveService = toolInteractiveService;
}

private async Task<List<Vpc>> GetData()
Expand Down Expand Up @@ -62,6 +64,13 @@ public async Task<object> Execute(Recommendation recommendation, OptionSettingIt

var vpcs = await GetData();

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 VpcTypeHintResponse(false, true, string.Empty);
}

var userInputConfig = new UserInputConfiguration<Vpc>(
idSelector: vpc => vpc.VpcId,
displaySelector: vpc =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\r
// SPDX-License-Identifier: Apache-2.0

using System.Collections.Generic;
using AWS.Deploy.Common.Recipes;

namespace AWS.Deploy.CLI.TypeHintResponses
{
/// <summary>
/// <see cref="OptionSettingTypeHint.Vpc"/> type hint response
/// </summary>
public class ElasticBeanstalkVpcTypeHintResponse : IDisplayable
{
public bool UseVPC { get; set; }
public bool CreateNew { get; set; }
public string? VpcId { get; set; }
public SortedSet<string> Subnets { get; set; } = new SortedSet<string>();
public SortedSet<string> SecurityGroups { get; set; } = new SortedSet<string>();

public string? ToDisplayString() => null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class VPCConnectorTypeHintResponse : IDisplayable
public string? VpcConnectorId { get; set; }
public bool UseVPCConnector { get; set; }
public bool CreateNew { get; set; }
public bool CreateNewVpc { get; set; }
public string? VpcId { get; set; }
public SortedSet<string> Subnets { get; set; } = new SortedSet<string>();
public SortedSet<string> SecurityGroups { get; set; } = new SortedSet<string>();
Expand Down
32 changes: 24 additions & 8 deletions src/AWS.Deploy.Common/Recipes/OptionSettingItem.ValueOverride.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ namespace AWS.Deploy.Common.Recipes
/// <see cref="GetValue{T}"/>, <see cref="GetValue"/> and <see cref="SetValueOverride"/> methods
public partial class OptionSettingItem
{
public T? GetValue<T>(IDictionary<string, string> replacementTokens, IDictionary<string, bool>? displayableOptionSettings = null)
public T? GetValue<T>(IDictionary<string, object> replacementTokens, IDictionary<string, bool>? displayableOptionSettings = null)
{
var value = GetValue(replacementTokens, displayableOptionSettings);

return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(value));
}

public object GetValue(IDictionary<string, string> replacementTokens, IDictionary<string, bool>? displayableOptionSettings = null)
public object GetValue(IDictionary<string, object> replacementTokens, IDictionary<string, bool>? displayableOptionSettings = null)
{
if (_value != null)
{
Expand Down Expand Up @@ -60,7 +60,7 @@ public object GetValue(IDictionary<string, string> replacementTokens, IDictionar
return DefaultValue;
}

public T? GetDefaultValue<T>(IDictionary<string, string> replacementTokens)
public T? GetDefaultValue<T>(IDictionary<string, object> replacementTokens)
{
var value = GetDefaultValue(replacementTokens);
if (value == null)
Expand All @@ -71,7 +71,7 @@ public object GetValue(IDictionary<string, string> replacementTokens, IDictionar
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(value));
}

public object? GetDefaultValue(IDictionary<string, string> replacementTokens)
public object? GetDefaultValue(IDictionary<string, object> replacementTokens)
{
if (DefaultValue == null)
{
Expand Down Expand Up @@ -167,14 +167,30 @@ public async Task SetValue(IOptionSettingHandler optionSettingHandler, object va
}
}

private string ApplyReplacementTokens(IDictionary<string, string> replacementTokens, string defaultValue)
private object ApplyReplacementTokens(IDictionary<string, object> replacementTokens, object defaultValue)
{
foreach (var token in replacementTokens)
var defaultValueString = defaultValue?.ToString();
if (!string.IsNullOrEmpty(defaultValueString))
{
defaultValue = defaultValue.Replace(token.Key, token.Value);
if (Type != OptionSettingValueType.String)
{
foreach (var token in replacementTokens)
{
if (defaultValueString.Equals(token.Key))
defaultValue = token.Value;
}
}
else
{
foreach (var token in replacementTokens)
{
defaultValueString = defaultValueString.Replace(token.Key, token.Value.ToString());
}
defaultValue = defaultValueString;
}
}

return defaultValue;
return defaultValue ?? string.Empty;
}
}
}
Loading

0 comments on commit 7821440

Please sign in to comment.