using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Amazon.CloudFormation;
using Amazon.CloudFormation.Model;
using ThirdParty.Json.LitJson;
using Amazon.Common.DotNetCli.Tools;
using Amazon.Common.DotNetCli.Tools.Options;
using Amazon.Lambda.Tools.TemplateProcessor;
using Newtonsoft.Json.Linq;
namespace Amazon.Lambda.Tools.Commands
{
///
/// Deployment command that uses an AWS Serverless CloudFormation template to drive the creation of the resources
/// for an AWS Serverless application.
///
public class DeployServerlessCommand : LambdaBaseCommand
{
public const string COMMAND_NAME = "deploy-serverless";
public const string COMMAND_DESCRIPTION = "Command to deploy an AWS Serverless application";
public const string COMMAND_ARGUMENTS = " The name of the CloudFormation stack used to deploy the AWS Serverless application";
// CloudFormation statuses for when the stack is in transition all end with IN_PROGRESS
const string IN_PROGRESS_SUFFIX = "IN_PROGRESS";
public static readonly IList DeployServerlessCommandOptions = BuildLineOptions(new List
{
CommonDefinedCommandOptions.ARGUMENT_CONFIGURATION,
CommonDefinedCommandOptions.ARGUMENT_FRAMEWORK,
CommonDefinedCommandOptions.ARGUMENT_MSBUILD_PARAMETERS,
LambdaDefinedCommandOptions.ARGUMENT_PACKAGE,
LambdaDefinedCommandOptions.ARGUMENT_RESOLVE_S3,
LambdaDefinedCommandOptions.ARGUMENT_S3_BUCKET,
LambdaDefinedCommandOptions.ARGUMENT_S3_PREFIX,
LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE,
LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE_PARAMETER,
LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE_SUBSTITUTIONS,
LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_ROLE,
LambdaDefinedCommandOptions.ARGUMENT_STACK_NAME,
LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_DISABLE_CAPABILITIES,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_TAGS,
LambdaDefinedCommandOptions.ARGUMENT_STACK_WAIT,
LambdaDefinedCommandOptions.ARGUMENT_DISABLE_VERSION_CHECK
});
public string Configuration { get; set; }
public string TargetFramework { get; set; }
public string Package { get; set; }
public string MSBuildParameters { get; set; }
public bool? ResolveS3 { get; set; }
public string S3Bucket { get; set; }
public string S3Prefix { get; set; }
public string CloudFormationTemplate { get; set; }
public string StackName { get; set; }
public bool? WaitForStackToComplete { get; set; }
public string CloudFormationRole { get; set; }
public Dictionary TemplateParameters { get; set; }
public Dictionary TemplateSubstitutions { get; set; }
public Dictionary Tags { get; set; }
public bool? DisableVersionCheck { get; set; }
public string[] DisabledCapabilities { get; set; }
///
/// Parse the CommandOptions into the Properties on the command.
///
///
protected override void ParseCommandArguments(CommandOptions values)
{
base.ParseCommandArguments(values);
if (values.Arguments.Count > 0)
{
this.StackName = values.Arguments[0];
}
Tuple tuple;
if ((tuple = values.FindCommandOption(CommonDefinedCommandOptions.ARGUMENT_CONFIGURATION.Switch)) != null)
this.Configuration = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(CommonDefinedCommandOptions.ARGUMENT_FRAMEWORK.Switch)) != null)
this.TargetFramework = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_PACKAGE.Switch)) != null)
this.Package = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_RESOLVE_S3.Switch)) != null)
this.ResolveS3 = tuple.Item2.BoolValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_S3_BUCKET.Switch)) != null)
this.S3Bucket = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_S3_PREFIX.Switch)) != null)
this.S3Prefix = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_STACK_NAME.Switch)) != null)
this.StackName = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE.Switch)) != null)
this.CloudFormationTemplate = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_STACK_WAIT.Switch)) != null)
this.WaitForStackToComplete = tuple.Item2.BoolValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE_PARAMETER.Switch)) != null)
this.TemplateParameters = tuple.Item2.KeyValuePairs;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE_SUBSTITUTIONS.Switch)) != null)
this.TemplateSubstitutions = tuple.Item2.KeyValuePairs;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_ROLE.Switch)) != null)
this.CloudFormationRole = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_DISABLE_VERSION_CHECK.Switch)) != null)
this.DisableVersionCheck = tuple.Item2.BoolValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_TAGS.Switch)) != null)
this.Tags = tuple.Item2.KeyValuePairs;
if ((tuple = values.FindCommandOption(CommonDefinedCommandOptions.ARGUMENT_MSBUILD_PARAMETERS.Switch)) != null)
this.MSBuildParameters = tuple.Item2.StringValue;
if (!string.IsNullOrEmpty(values.MSBuildParameters))
{
if (this.MSBuildParameters == null)
this.MSBuildParameters = values.MSBuildParameters;
else
this.MSBuildParameters += " " + values.MSBuildParameters;
}
}
public DeployServerlessCommand(IToolLogger logger, string workingDirectory, string[] args)
: base(logger, workingDirectory, DeployServerlessCommandOptions, args)
{
}
protected override async Task PerformActionAsync()
{
string projectLocation = this.GetStringValueOrDefault(this.ProjectLocation, CommonDefinedCommandOptions.ARGUMENT_PROJECT_LOCATION, false);
string stackName = this.GetStringValueOrDefault(this.StackName, LambdaDefinedCommandOptions.ARGUMENT_STACK_NAME, true);
string s3Prefix = this.GetStringValueOrDefault(this.S3Prefix, LambdaDefinedCommandOptions.ARGUMENT_S3_PREFIX, false);
string templatePath = this.GetStringValueOrDefault(this.CloudFormationTemplate, LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE, true);
string s3Bucket = await DetermineS3Bucket();
await Utilities.ValidateBucketRegionAsync(this.S3Client, s3Bucket);
if (!Path.IsPathRooted(templatePath))
{
templatePath = Path.Combine(Utilities.DetermineProjectLocation(this.WorkingDirectory, projectLocation), templatePath);
}
if (!File.Exists(templatePath))
throw new LambdaToolsException($"Template file {templatePath} cannot be found.", LambdaToolsException.LambdaErrorCode.ServerlessTemplateNotFound);
// Read in the serverless template and update all the locations for Lambda functions to point to the app bundle that was just uploaded.
string templateBody = File.ReadAllText(templatePath);
// Process any template substitutions
templateBody = LambdaUtilities.ProcessTemplateSubstitions(this.Logger, templateBody, this.GetKeyValuePairOrDefault(this.TemplateSubstitutions, LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE_SUBSTITUTIONS, false), Utilities.DetermineProjectLocation(this.WorkingDirectory, projectLocation));
var options = new DefaultLocationOption
{
Configuration = this.GetStringValueOrDefault(this.Configuration, CommonDefinedCommandOptions.ARGUMENT_CONFIGURATION, false),
TargetFramework = this.GetStringValueOrDefault(this.TargetFramework, CommonDefinedCommandOptions.ARGUMENT_FRAMEWORK, false),
MSBuildParameters = this.GetStringValueOrDefault(this.MSBuildParameters, CommonDefinedCommandOptions.ARGUMENT_MSBUILD_PARAMETERS, false),
DisableVersionCheck = this.GetBoolValueOrDefault(this.DisableVersionCheck, LambdaDefinedCommandOptions.ARGUMENT_DISABLE_VERSION_CHECK, false).GetValueOrDefault(),
Package = this.GetStringValueOrDefault(this.Package, LambdaDefinedCommandOptions.ARGUMENT_PACKAGE, false)
};
var templateProcessor = new TemplateProcessorManager(this, s3Bucket, s3Prefix, options);
templateBody = await templateProcessor.TransformTemplateAsync(templatePath, templateBody, OriginalCommandLineArguments);
// Upload the template to S3 instead of sending it straight to CloudFormation to avoid the size limitation
string s3KeyTemplate;
using (var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(templateBody)))
{
s3KeyTemplate = await Utilities.UploadToS3Async(this.Logger, this.S3Client, s3Bucket, s3Prefix, stackName + "-" + Path.GetFileName(templatePath), stream);
}
var existingStack = await GetExistingStackAsync(stackName);
this.Logger.WriteLine("Found existing stack: " + (existingStack != null));
var changeSetName = "Lambda-Tools-" + DateTime.Now.Ticks;
List tagList = null;
{
var tags = this.GetKeyValuePairOrDefault(this.Tags, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_TAGS, false);
if(tags != null)
{
tagList = new List();
foreach(var kvp in tags)
{
tagList.Add(new Tag { Key = kvp.Key, Value = kvp.Value });
}
}
}
// Determine if the stack is in a good state to be updated.
ChangeSetType changeSetType;
if (existingStack == null || existingStack.StackStatus == StackStatus.REVIEW_IN_PROGRESS || existingStack.StackStatus == StackStatus.DELETE_COMPLETE)
{
changeSetType = ChangeSetType.CREATE;
}
// If the status was ROLLBACK_COMPLETE that means the stack failed on initial creation
// and the resources were cleaned up. It is safe to delete the stack so we can recreate it.
else if (existingStack.StackStatus == StackStatus.ROLLBACK_COMPLETE)
{
await DeleteRollbackCompleteStackAsync(existingStack);
changeSetType = ChangeSetType.CREATE;
}
// If the status was ROLLBACK_IN_PROGRESS that means the initial creation is failing.
// Wait to see if it goes into ROLLBACK_COMPLETE status meaning everything got cleaned up and then delete it.
else if (existingStack.StackStatus == StackStatus.ROLLBACK_IN_PROGRESS)
{
existingStack = await WaitForNoLongerInProgress(existingStack.StackName);
if (existingStack != null && existingStack.StackStatus == StackStatus.ROLLBACK_COMPLETE)
await DeleteRollbackCompleteStackAsync(existingStack);
changeSetType = ChangeSetType.CREATE;
}
// If the status was DELETE_IN_PROGRESS then just wait for delete to complete
else if (existingStack.StackStatus == StackStatus.DELETE_IN_PROGRESS)
{
await WaitForNoLongerInProgress(existingStack.StackName);
changeSetType = ChangeSetType.CREATE;
}
// The Stack state is in a normal state and ready to be updated.
else if (existingStack.StackStatus == StackStatus.CREATE_COMPLETE ||
existingStack.StackStatus == StackStatus.UPDATE_COMPLETE ||
existingStack.StackStatus == StackStatus.UPDATE_ROLLBACK_COMPLETE)
{
changeSetType = ChangeSetType.UPDATE;
if(tagList == null)
{
tagList = existingStack.Tags;
}
}
// All other states means the Stack is in an inconsistent state.
else
{
throw new LambdaToolsException($"The stack's current state of {existingStack.StackStatus} is invalid for updating", LambdaToolsException.LambdaErrorCode.InvalidCloudFormationStackState);
}
CreateChangeSetResponse changeSetResponse;
try
{
var definedParameters = LambdaUtilities.GetTemplateDefinedParameters(templateBody);
var templateParameters = GetTemplateParameters(changeSetType == ChangeSetType.UPDATE ? existingStack : null, definedParameters);
if (templateParameters != null && templateParameters.Any())
{
var setParameters = templateParameters.Where(x => !x.UsePreviousValue);
// ReSharper disable once PossibleMultipleEnumeration
if (setParameters.Any())
{
this.Logger.WriteLine("Template Parameters Applied:");
foreach (var parameter in setParameters)
{
Tuple dp = null;
if (definedParameters != null)
{
dp = definedParameters.FirstOrDefault(x => string.Equals(x.Item1, parameter.ParameterKey));
}
if (dp != null && dp.Item2)
{
this.Logger.WriteLine($"\t{parameter.ParameterKey}: ****");
}
else
{
this.Logger.WriteLine($"\t{parameter.ParameterKey}: {parameter.ParameterValue}");
}
}
}
}
var capabilities = new List();
var disabledCapabilties = GetStringValuesOrDefault(this.DisabledCapabilities, LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_DISABLE_CAPABILITIES, false);
if (disabledCapabilties?.FirstOrDefault(x => string.Equals(x, "CAPABILITY_IAM", StringComparison.OrdinalIgnoreCase)) == null)
{
capabilities.Add("CAPABILITY_IAM");
}
if (disabledCapabilties?.FirstOrDefault(x => string.Equals(x, "CAPABILITY_NAMED_IAM", StringComparison.OrdinalIgnoreCase)) == null)
{
capabilities.Add("CAPABILITY_NAMED_IAM");
}
if (disabledCapabilties?.FirstOrDefault(x => string.Equals(x, "CAPABILITY_AUTO_EXPAND", StringComparison.OrdinalIgnoreCase)) == null)
{
capabilities.Add("CAPABILITY_AUTO_EXPAND");
}
if (tagList == null)
{
tagList = new List();
}
if(tagList.FirstOrDefault(x => string.Equals(x.Key, LambdaConstants.SERVERLESS_TAG_NAME)) == null)
{
tagList.Add(new Tag { Key = LambdaConstants.SERVERLESS_TAG_NAME, Value = "true" });
}
var changeSetRequest = new CreateChangeSetRequest
{
StackName = stackName,
Parameters = templateParameters,
ChangeSetName = changeSetName,
ChangeSetType = changeSetType,
Capabilities = capabilities,
RoleARN = this.GetStringValueOrDefault(this.CloudFormationRole, LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_ROLE, false),
Tags = tagList
};
if(templateBody.Length < LambdaConstants.MAX_TEMPLATE_BODY_IN_REQUEST_SIZE)
{
changeSetRequest.TemplateBody = templateBody;
}
else
{
changeSetRequest.TemplateURL = this.S3Client.GetPreSignedURL(new S3.Model.GetPreSignedUrlRequest { BucketName = s3Bucket, Key = s3KeyTemplate, Expires = DateTime.Now.AddHours(1) });
}
// Create the change set which performs the transformation on the Serverless resources in the template.
changeSetResponse = await this.CloudFormationClient.CreateChangeSetAsync(changeSetRequest);
this.Logger.WriteLine("CloudFormation change set created");
}
catch(LambdaToolsException)
{
throw;
}
catch (Exception e)
{
throw new LambdaToolsException($"Error creating CloudFormation change set: {e.Message}", LambdaToolsException.LambdaErrorCode.CloudFormationCreateStack, e);
}
// The change set can take a few seconds to be reviewed and be ready to be executed.
await WaitForChangeSetBeingAvailableAsync(changeSetResponse.Id);
var executeChangeSetRequest = new ExecuteChangeSetRequest
{
StackName = stackName,
ChangeSetName = changeSetResponse.Id
};
// Execute the change set.
DateTime timeChangeSetExecuted = DateTime.Now;
try
{
await this.CloudFormationClient.ExecuteChangeSetAsync(executeChangeSetRequest);
if (changeSetType == ChangeSetType.CREATE)
this.Logger.WriteLine($"Created CloudFormation stack {stackName}");
else
this.Logger.WriteLine($"Initiated CloudFormation stack update on {stackName}");
}
catch (Exception e)
{
throw new LambdaToolsException($"Error executing CloudFormation change set: {e.Message}", LambdaToolsException.LambdaErrorCode.CloudFormationCreateChangeSet, e);
}
// Wait for the stack to finish unless the user opts out of waiting. The VS Toolkit opts out and
// instead shows the stack view in the IDE, enabling the user to view progress.
var shouldWait = GetBoolValueOrDefault(this.WaitForStackToComplete, LambdaDefinedCommandOptions.ARGUMENT_STACK_WAIT, false);
if (!shouldWait.HasValue || shouldWait.Value)
{
var updatedStack = await WaitStackToCompleteAsync(stackName, timeChangeSetExecuted);
if (updatedStack.StackStatus == StackStatus.CREATE_COMPLETE || updatedStack.StackStatus == StackStatus.UPDATE_COMPLETE)
{
this.Logger.WriteLine($"Stack finished updating with status: {updatedStack.StackStatus}");
// Display the output parameters.
DisplayOutputs(updatedStack);
}
else
{
throw new LambdaToolsException($"Stack update failed with status: {updatedStack.StackStatus} ({updatedStack.StackStatusReason})", LambdaToolsException.LambdaErrorCode.FailedLambdaCreateOrUpdate);
}
}
return true;
}
private void DisplayOutputs(Stack stack)
{
if (stack.Outputs.Count == 0)
return;
const int OUTPUT_NAME_WIDTH = 30;
const int OUTPUT_VALUE_WIDTH = 50;
this.Logger.WriteLine(" ");
this.Logger.WriteLine("Output Name".PadRight(OUTPUT_NAME_WIDTH) + " " + "Value".PadRight(OUTPUT_VALUE_WIDTH));
this.Logger.WriteLine($"{new string('-', OUTPUT_NAME_WIDTH)} {new string('-', OUTPUT_VALUE_WIDTH)}");
foreach (var output in stack.Outputs)
{
string line = output.OutputKey.PadRight(OUTPUT_NAME_WIDTH) + " " + output.OutputValue?.PadRight(OUTPUT_VALUE_WIDTH);
this.Logger.WriteLine(line);
}
}
static readonly TimeSpan POLLING_PERIOD = TimeSpan.FromSeconds(3);
private async Task WaitStackToCompleteAsync(string stackName, DateTime mintimeStampForEvents)
{
const int TIMESTAMP_WIDTH = 20;
const int LOGICAL_RESOURCE_WIDTH = 40;
const int RESOURCE_STATUS = 40;
string mostRecentEventId = "";
// Write header for the status table.
this.Logger.WriteLine(" ");
this.Logger.WriteLine(
"Timestamp".PadRight(TIMESTAMP_WIDTH) + " " +
"Logical Resource Id".PadRight(LOGICAL_RESOURCE_WIDTH) + " " +
"Status".PadRight(RESOURCE_STATUS) + " ");
this.Logger.WriteLine(
new string('-', TIMESTAMP_WIDTH) + " " +
new string('-', LOGICAL_RESOURCE_WIDTH) + " " +
new string('-', RESOURCE_STATUS) + " ");
Stack stack;
do
{
Thread.Sleep(POLLING_PERIOD);
stack = await GetExistingStackAsync(stackName);
var events = await GetLatestEventsAsync(stackName, mintimeStampForEvents, mostRecentEventId);
if (events.Count > 0)
mostRecentEventId = events[0].EventId;
for (int i = events.Count - 1; i >= 0; i--)
{
string line =
events[i].Timestamp.ToString("g").PadRight(TIMESTAMP_WIDTH) + " " +
events[i].LogicalResourceId.PadRight(LOGICAL_RESOURCE_WIDTH) + " " +
events[i].ResourceStatus.ToString().PadRight(RESOURCE_STATUS);
// To save screen space only show error messages.
if (!events[i].ResourceStatus.ToString().EndsWith(IN_PROGRESS_SUFFIX) && !string.IsNullOrEmpty(events[i].ResourceStatusReason))
line += " " + events[i].ResourceStatusReason;
this.Logger.WriteLine(line);
}
} while (stack.StackStatus.ToString().EndsWith(IN_PROGRESS_SUFFIX));
return stack;
}
private async Task> GetLatestEventsAsync(string stackName, DateTime mintimeStampForEvents, string mostRecentEventId)
{
bool noNewEvents = false;
List events = new List();
DescribeStackEventsResponse response = null;
do
{
var request = new DescribeStackEventsRequest() { StackName = stackName };
if (response != null)
request.NextToken = response.NextToken;
try
{
response = await this.CloudFormationClient.DescribeStackEventsAsync(request);
}
catch (Exception e)
{
throw new LambdaToolsException($"Error getting events for stack: {e.Message}", LambdaToolsException.LambdaErrorCode.CloudFormationDescribeStackEvents, e);
}
foreach (var evnt in response.StackEvents)
{
if (string.Equals(evnt.EventId, mostRecentEventId) || evnt.Timestamp < mintimeStampForEvents)
{
noNewEvents = true;
break;
}
events.Add(evnt);
}
} while (!noNewEvents && !string.IsNullOrEmpty(response.NextToken));
return events;
}
private async Task DeleteRollbackCompleteStackAsync(Stack stack)
{
try
{
if (stack.StackStatus == StackStatus.ROLLBACK_COMPLETE)
await this.CloudFormationClient.DeleteStackAsync(new DeleteStackRequest { StackName = stack.StackName });
await WaitForNoLongerInProgress(stack.StackName);
}
catch (Exception e)
{
throw new LambdaToolsException($"Error removing previous failed stack creation {stack.StackName}: {e.Message}", LambdaToolsException.LambdaErrorCode.CloudFormationDeleteStack, e);
}
}
private async Task WaitForNoLongerInProgress(string stackName)
{
try
{
long start = DateTime.Now.Ticks;
Stack currentStack = null;
do
{
if (currentStack != null)
this.Logger.WriteLine($"... Waiting for stack's state to change from {currentStack.StackStatus}: {TimeSpan.FromTicks(DateTime.Now.Ticks - start).TotalSeconds.ToString("0").PadLeft(3)} secs");
Thread.Sleep(POLLING_PERIOD);
currentStack = await GetExistingStackAsync(stackName);
} while (currentStack != null && currentStack.StackStatus.ToString().EndsWith(IN_PROGRESS_SUFFIX));
return currentStack;
}
catch (Exception e)
{
throw new LambdaToolsException($"Error waiting for stack state change: {e.Message}", LambdaToolsException.LambdaErrorCode.WaitingForStackError, e);
}
}
private async Task WaitForChangeSetBeingAvailableAsync(string changeSetId)
{
try
{
var request = new DescribeChangeSetRequest
{
ChangeSetName = changeSetId
};
this.Logger.WriteLine("... Waiting for change set to be reviewed");
DescribeChangeSetResponse response;
do
{
Thread.Sleep(POLLING_PERIOD);
response = await this.CloudFormationClient.DescribeChangeSetAsync(request);
} while (response.Status == ChangeSetStatus.CREATE_IN_PROGRESS || response.Status == ChangeSetStatus.CREATE_PENDING);
if (response.Status == ChangeSetStatus.FAILED)
{
throw new LambdaToolsException($"Failed to create CloudFormation change set: {response.StatusReason}", LambdaToolsException.LambdaErrorCode.FailedToCreateChangeSet);
}
}
catch (Exception e)
{
throw new LambdaToolsException($"Error getting status of change set: {e.Message}", LambdaToolsException.LambdaErrorCode.CloudFormationDescribeChangeSet, e);
}
}
public async Task GetExistingStackAsync(string stackName)
{
try
{
var response = await this.CloudFormationClient.DescribeStacksAsync(new DescribeStacksRequest { StackName = stackName });
if (response.Stacks.Count != 1)
return null;
return response.Stacks[0];
}
catch (AmazonCloudFormationException)
{
return null;
}
}
private List GetTemplateParameters(Stack stack, List> definedParameters)
{
var parameters = new List();
var map = GetKeyValuePairOrDefault(this.TemplateParameters, LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE_PARAMETER, false);
if (map != null)
{
foreach (var kvp in map)
{
Tuple dp = null;
if(definedParameters != null)
{
dp = definedParameters.FirstOrDefault(x => string.Equals(x.Item1, kvp.Key));
}
if (dp == null)
{
this.Logger.WriteLine($"Skipping passed in template parameter {kvp.Key} because the template does not define that parameter");
}
else
{
parameters.Add(new Parameter { ParameterKey = kvp.Key, ParameterValue = kvp.Value ?? "" });
}
}
}
if (stack != null)
{
foreach (var existingParameter in stack.Parameters)
{
if ((definedParameters == null || definedParameters.FirstOrDefault(x => string.Equals(x.Item1, existingParameter.ParameterKey)) != null) &&
!parameters.Any(x => string.Equals(x.ParameterKey, existingParameter.ParameterKey)))
{
parameters.Add(new Parameter { ParameterKey = existingParameter.ParameterKey, UsePreviousValue = true });
}
}
}
return parameters;
}
private async Task DetermineS3Bucket()
{
string s3Bucket = this.GetStringValueOrDefault(this.S3Bucket, LambdaDefinedCommandOptions.ARGUMENT_S3_BUCKET, false);
bool? resolveS3 = this.GetBoolValueOrDefault(this.ResolveS3, LambdaDefinedCommandOptions.ARGUMENT_RESOLVE_S3, false);
if (string.IsNullOrEmpty(s3Bucket))
{
if (resolveS3 == true)
{
s3Bucket = await LambdaUtilities.ResolveDefaultS3Bucket(this.Logger, this.S3Client, this.STSClient);
}
else
{
// Since a bucket wasn't explicitly passed in nor was resolve s3 passed in then ask the user for the S3 bucket.
s3Bucket = this.GetStringValueOrDefault(this.S3Bucket, LambdaDefinedCommandOptions.ARGUMENT_S3_BUCKET, true);
}
}
return s3Bucket;
}
protected override void SaveConfigFile(JsonData data)
{
data.SetIfNotNull(CommonDefinedCommandOptions.ARGUMENT_CONFIGURATION.ConfigFileKey, this.GetStringValueOrDefault(this.Configuration, CommonDefinedCommandOptions.ARGUMENT_CONFIGURATION, false));
data.SetIfNotNull(CommonDefinedCommandOptions.ARGUMENT_FRAMEWORK.ConfigFileKey, this.GetStringValueOrDefault(this.TargetFramework, CommonDefinedCommandOptions.ARGUMENT_FRAMEWORK, false));
data.SetIfNotNull(CommonDefinedCommandOptions.ARGUMENT_MSBUILD_PARAMETERS.ConfigFileKey, this.GetStringValueOrDefault(this.MSBuildParameters, CommonDefinedCommandOptions.ARGUMENT_MSBUILD_PARAMETERS, false));
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_RESOLVE_S3.ConfigFileKey, this.GetBoolValueOrDefault(this.ResolveS3, LambdaDefinedCommandOptions.ARGUMENT_RESOLVE_S3, false));
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_S3_BUCKET.ConfigFileKey, this.GetStringValueOrDefault(this.S3Bucket, LambdaDefinedCommandOptions.ARGUMENT_S3_BUCKET, false));
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_S3_PREFIX.ConfigFileKey, this.GetStringValueOrDefault(this.S3Prefix, LambdaDefinedCommandOptions.ARGUMENT_S3_PREFIX, false));
var template = this.GetStringValueOrDefault(this.CloudFormationTemplate, LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE, false);
if(Path.IsPathRooted(template))
{
string projectLocation = this.GetStringValueOrDefault(this.ProjectLocation, CommonDefinedCommandOptions.ARGUMENT_PROJECT_LOCATION, false);
var projectRoot = Utilities.DetermineProjectLocation(this.WorkingDirectory, projectLocation);
if(template.StartsWith(projectRoot))
{
template = template.Substring(projectRoot.Length + 1);
}
}
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE.ConfigFileKey, template);
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE_PARAMETER.ConfigFileKey, LambdaToolsDefaults.FormatKeyValue(this.GetKeyValuePairOrDefault(this.TemplateParameters, LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_TEMPLATE_PARAMETER, false)));
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_STACK_NAME.ConfigFileKey, this.GetStringValueOrDefault(this.StackName, LambdaDefinedCommandOptions.ARGUMENT_STACK_NAME, false));
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_DISABLE_CAPABILITIES.ConfigFileKey, LambdaToolsDefaults.FormatCommaDelimitedList(this.GetStringValuesOrDefault(this.DisabledCapabilities, LambdaDefinedCommandOptions.ARGUMENT_CLOUDFORMATION_DISABLE_CAPABILITIES, false)));
}
}
}