using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Amazon.Common.DotNetCli.Tools;
using Amazon.Common.DotNetCli.Tools.Options;
using Amazon.Lambda;
using Amazon.Lambda.Model;
using ThirdParty.Json.LitJson;
namespace Amazon.Lambda.Tools.Commands
{
///
/// Updates the configuration for an existing function. To avoid any accidental changes to the function
/// only fields that were explicitly set are changed and defaults are ignored.
///
public class UpdateFunctionConfigCommand : LambdaBaseCommand
{
public const string COMMAND_NAME = "update-function-config";
public const string COMMAND_DESCRIPTION = "Command to update the runtime configuration for a Lambda function";
public const string COMMAND_ARGUMENTS = " The name of the function to be updated";
public static readonly IList UpdateCommandOptions = BuildLineOptions(new List
{
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_NAME,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_DESCRIPTION,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_PUBLISH,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_MEMORY_SIZE,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_ROLE,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_TIMEOUT,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_RUNTIME,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_HANDLER,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_LAYERS,
LambdaDefinedCommandOptions.ARGUMENT_EPHEMERAL_STORAGE_SIZE,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_ENABLE,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_AUTH,
LambdaDefinedCommandOptions.ARGUMENT_IMAGE_ENTRYPOINT,
LambdaDefinedCommandOptions.ARGUMENT_IMAGE_COMMAND,
LambdaDefinedCommandOptions.ARGUMENT_IMAGE_WORKING_DIRECTORY,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_TAGS,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SUBNETS,
LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SECURITY_GROUPS,
LambdaDefinedCommandOptions.ARGUMENT_DEADLETTER_TARGET_ARN,
LambdaDefinedCommandOptions.ARGUMENT_TRACING_MODE,
LambdaDefinedCommandOptions.ARGUMENT_ENVIRONMENT_VARIABLES,
LambdaDefinedCommandOptions.ARGUMENT_APPEND_ENVIRONMENT_VARIABLES,
LambdaDefinedCommandOptions.ARGUMENT_KMS_KEY_ARN,
LambdaDefinedCommandOptions.ARGUMENT_APPLY_DEFAULTS_FOR_UPDATE_OBSOLETE
});
public string FunctionName { get; set; }
public string Description { get; set; }
public bool? Publish { get; set; }
public string Handler { get; set; }
public int? MemorySize { get; set; }
public string Role { get; set; }
public int? Timeout { get; set; }
public string[] LayerVersionArns { get; set; }
public string[] SubnetIds { get; set; }
public string[] SecurityGroupIds { get; set; }
public Runtime Runtime { get; set; }
public Dictionary EnvironmentVariables { get; set; }
public Dictionary AppendEnvironmentVariables { get; set; }
public Dictionary Tags { get; set; }
public string KMSKeyArn { get; set; }
public string DeadLetterTargetArn { get; set; }
public string TracingMode { get; set; }
public string[] ImageEntryPoint { get; set; }
public string[] ImageCommand { get; set; }
public string ImageWorkingDirectory { get; set; }
public string PackageType { get; set; }
public int? EphemeralStorageSize { get; set; }
public bool? FunctionUrlEnable { get; set; }
public string FunctionUrlAuthType { get; set; }
public string FunctionUrlLink { get; private set; }
public UpdateFunctionConfigCommand(IToolLogger logger, string workingDirectory, string[] args)
: base(logger, workingDirectory, UpdateCommandOptions, args)
{
}
protected UpdateFunctionConfigCommand(IToolLogger logger, string workingDirectory, IList possibleOptions, string[] args)
: base(logger, workingDirectory, possibleOptions, args)
{
}
///
/// Parse the CommandOptions into the Properties on the command.
///
///
protected override void ParseCommandArguments(CommandOptions values)
{
base.ParseCommandArguments(values);
if (values.Arguments.Count > 0)
{
this.FunctionName = values.Arguments[0];
}
Tuple tuple;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_NAME.Switch)) != null)
this.FunctionName = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_DESCRIPTION.Switch)) != null)
this.Description = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_PUBLISH.Switch)) != null)
this.Publish = tuple.Item2.BoolValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_HANDLER.Switch)) != null)
this.Handler = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_MEMORY_SIZE.Switch)) != null)
this.MemorySize = tuple.Item2.IntValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_ROLE.Switch)) != null)
this.Role = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_TIMEOUT.Switch)) != null)
this.Timeout = tuple.Item2.IntValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_RUNTIME.Switch)) != null)
this.Runtime = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_LAYERS.Switch)) != null)
this.LayerVersionArns = tuple.Item2.StringValues;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_TAGS.Switch)) != null)
this.Tags = tuple.Item2.KeyValuePairs;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SUBNETS.Switch)) != null)
this.SubnetIds = tuple.Item2.StringValues;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SECURITY_GROUPS.Switch)) != null)
this.SecurityGroupIds = tuple.Item2.StringValues;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_DEADLETTER_TARGET_ARN.Switch)) != null)
this.DeadLetterTargetArn = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_TRACING_MODE.Switch)) != null)
this.TracingMode = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_ENVIRONMENT_VARIABLES.Switch)) != null)
this.EnvironmentVariables = tuple.Item2.KeyValuePairs;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_APPEND_ENVIRONMENT_VARIABLES.Switch)) != null)
this.AppendEnvironmentVariables = tuple.Item2.KeyValuePairs;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_KMS_KEY_ARN.Switch)) != null)
this.KMSKeyArn = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_IMAGE_ENTRYPOINT.Switch)) != null)
this.ImageEntryPoint = tuple.Item2.StringValues;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_IMAGE_COMMAND.Switch)) != null)
this.ImageCommand = tuple.Item2.StringValues;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_IMAGE_WORKING_DIRECTORY.Switch)) != null)
this.ImageWorkingDirectory = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_EPHEMERAL_STORAGE_SIZE.Switch)) != null)
this.EphemeralStorageSize = tuple.Item2.IntValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_ENABLE.Switch)) != null)
this.FunctionUrlEnable = tuple.Item2.BoolValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_AUTH.Switch)) != null)
this.FunctionUrlAuthType = tuple.Item2.StringValue;
}
protected override async Task PerformActionAsync()
{
var currentConfiguration = await GetFunctionConfigurationAsync();
if(currentConfiguration == null)
{
this.Logger.WriteLine($"Could not find existing Lambda function {this.GetStringValueOrDefault(this.FunctionName, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_NAME, true)}");
return false;
}
var layerVersionArns = this.GetStringValuesOrDefault(this.LayerVersionArns, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_LAYERS, false);
var layerPackageInfo = await LambdaUtilities.LoadLayerPackageInfos(this.Logger, this.LambdaClient, this.S3Client, layerVersionArns);
await UpdateConfigAsync(currentConfiguration, layerPackageInfo.GenerateDotnetSharedStoreValue());
await ApplyTags(currentConfiguration.FunctionArn);
var publish = this.GetBoolValueOrDefault(this.Publish, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_PUBLISH, false).GetValueOrDefault();
if (publish)
{
await PublishFunctionAsync(currentConfiguration.FunctionName);
}
return true;
}
protected async Task PublishFunctionAsync(string functionName)
{
try
{
await LambdaUtilities.WaitTillFunctionAvailableAsync(this.Logger, this.LambdaClient, functionName);
var response = await this.LambdaClient.PublishVersionAsync(new PublishVersionRequest
{
FunctionName = functionName
});
this.Logger.WriteLine("Published new Lambda function version: " + response.Version);
}
catch (Exception e)
{
throw new LambdaToolsException($"Error publishing Lambda function: {e.Message}", LambdaToolsException.LambdaErrorCode.LambdaPublishFunction, e);
}
}
protected async Task ApplyTags(string functionArn)
{
try
{
var tags = this.GetKeyValuePairOrDefault(this.Tags, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_TAGS, false);
if (tags == null || tags.Count == 0)
return;
var tagRequest = new TagResourceRequest
{
Resource = functionArn,
Tags = tags
};
await this.LambdaClient.TagResourceAsync(tagRequest);
this.Logger?.WriteLine($"Applying {tags.Count} tag(s) to function");
}
catch (Exception e)
{
throw new LambdaToolsException($"Error tagging Lambda function: {e.Message}", LambdaToolsException.LambdaErrorCode.LambdaTaggingFunction, e);
}
}
///
/// Reverts the lambda configuration to an earlier point, which is needed if updating the lambda function code failed.
///
///
protected async Task AttemptRevertConfigAsync(GetFunctionConfigurationResponse existingConfiguration)
{
try
{
var request = CreateRevertConfigurationRequest(existingConfiguration);
await LambdaUtilities.WaitTillFunctionAvailableAsync(Logger, this.LambdaClient, request.FunctionName);
this.Logger.WriteLine($"Reverting runtime configuration for function {this.GetStringValueOrDefault(this.FunctionName, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_NAME, true)}");
await this.LambdaClient.UpdateFunctionConfigurationAsync(request);
}
catch (Exception e)
{
this.Logger.WriteLine($"Error reverting configuration for Lambda function: {e.Message}");
}
}
protected async Task UpdateConfigAsync(GetFunctionConfigurationResponse existingConfiguration, string dotnetSharedStoreValue)
{
var configUpdated = false;
var request = CreateConfigurationRequestIfDifferent(existingConfiguration, dotnetSharedStoreValue);
if (request != null)
{
await LambdaUtilities.WaitTillFunctionAvailableAsync(Logger, this.LambdaClient, request.FunctionName);
this.Logger.WriteLine($"Updating runtime configuration for function {this.GetStringValueOrDefault(this.FunctionName, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_NAME, true)}");
try
{
await this.LambdaClient.UpdateFunctionConfigurationAsync(request);
configUpdated = true;
}
catch (Exception e)
{
throw new LambdaToolsException($"Error updating configuration for Lambda function: {e.Message}", LambdaToolsException.LambdaErrorCode.LambdaUpdateFunctionConfiguration, e);
}
}
// only attempt to modify function url if the user has explicitly opted-in to use FunctionUrl
if (GetBoolValueOrDefault(FunctionUrlEnable, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_ENABLE, false).HasValue)
{
var urlConfig = await this.GetFunctionUrlConfig(existingConfiguration.FunctionName);
// To determine what is the state of the function url check to see if the user explicitly set a value. If they did set a value then use that
// to either add or remove the url config. If the user didn't set a value check to see if there is an existing config to make sure we don't remove
// the config url if the user didn't set a value.
bool enableUrlConfig = this.GetBoolValueOrDefault(this.FunctionUrlEnable, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_ENABLE, false).HasValue ?
this.GetBoolValueOrDefault(this.FunctionUrlEnable, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_ENABLE, false).Value : urlConfig != null;
var authType = this.GetStringValueOrDefault(this.FunctionUrlAuthType, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_AUTH, false);
if (urlConfig != null)
{
this.FunctionUrlLink = urlConfig.FunctionUrl;
}
if (urlConfig != null && !enableUrlConfig)
{
await this.DeleteFunctionUrlConfig(existingConfiguration.FunctionName, urlConfig.AuthType);
this.Logger.WriteLine("Removing function url config");
}
else if(urlConfig == null && enableUrlConfig)
{
await this.CreateFunctionUrlConfig(existingConfiguration.FunctionName, authType);
this.Logger.WriteLine($"Creating function url config: {this.FunctionUrlLink}");
}
else if (urlConfig != null && enableUrlConfig &&
!string.Equals(authType, urlConfig.AuthType.Value, StringComparison.Ordinal))
{
await this.UpdateFunctionUrlConfig(existingConfiguration.FunctionName, urlConfig.AuthType, authType);
this.Logger.WriteLine($"Updating function url config: {this.FunctionUrlLink}");
}
}
return configUpdated;
}
public async Task GetFunctionConfigurationAsync()
{
var request = new GetFunctionConfigurationRequest
{
FunctionName = this.GetStringValueOrDefault(this.FunctionName, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_NAME, true)
};
try
{
var response = await this.LambdaClient.GetFunctionConfigurationAsync(request);
return response;
}
catch (ResourceNotFoundException)
{
return null;
}
catch(Exception e)
{
throw new LambdaToolsException($"Error retrieving configuration for function {request.FunctionName}: {e.Message}", LambdaToolsException.LambdaErrorCode.LambdaGetConfiguration, e);
}
}
///
/// Create an UpdateFunctionConfigurationRequest for the current configuration to be used to revert a failed configuration update.
///
///
///
private UpdateFunctionConfigurationRequest CreateRevertConfigurationRequest(GetFunctionConfigurationResponse existingConfiguration)
{
var request = new UpdateFunctionConfigurationRequest
{
FunctionName = existingConfiguration.FunctionName,
Description = existingConfiguration.Description,
Role = existingConfiguration.Role,
MemorySize = existingConfiguration.MemorySize,
EphemeralStorage = existingConfiguration.EphemeralStorage,
Timeout = existingConfiguration.Timeout,
Layers = existingConfiguration.Layers?.Select(x => x.Arn).ToList(),
DeadLetterConfig = existingConfiguration.DeadLetterConfig,
KMSKeyArn = existingConfiguration.KMSKeyArn
};
if (existingConfiguration.VpcConfig != null)
{
request.VpcConfig = new VpcConfig
{
IsSecurityGroupIdsSet = existingConfiguration.VpcConfig.SecurityGroupIds?.Any() ?? false,
IsSubnetIdsSet = existingConfiguration.VpcConfig.SubnetIds?.Any() ?? false,
SecurityGroupIds = existingConfiguration.VpcConfig.SecurityGroupIds,
SubnetIds = existingConfiguration.VpcConfig.SubnetIds
};
}
if (existingConfiguration.TracingConfig != null)
{
request.TracingConfig = new TracingConfig
{
Mode = existingConfiguration.TracingConfig.Mode
};
}
if (existingConfiguration.Environment != null)
{
request.Environment = new Model.Environment
{
IsVariablesSet = existingConfiguration.Environment.Variables?.Any() ?? false,
Variables = existingConfiguration.Environment.Variables
};
}
if (existingConfiguration.PackageType == Lambda.PackageType.Zip)
{
request.Handler = existingConfiguration.Handler;
request.Runtime = existingConfiguration.Runtime;
}
else if (existingConfiguration.PackageType == Lambda.PackageType.Image)
{
if (existingConfiguration.ImageConfigResponse != null)
{
request.ImageConfig = new ImageConfig
{
Command = existingConfiguration.ImageConfigResponse.ImageConfig?.Command,
EntryPoint = existingConfiguration.ImageConfigResponse.ImageConfig?.EntryPoint,
IsCommandSet = existingConfiguration.ImageConfigResponse.ImageConfig?.Command?.Any() ?? false,
IsEntryPointSet = existingConfiguration.ImageConfigResponse.ImageConfig?.EntryPoint?.Any() ?? false,
WorkingDirectory = existingConfiguration.ImageConfigResponse.ImageConfig?.WorkingDirectory
};
}
}
return request;
}
///
/// Create an UpdateFunctionConfigurationRequest if any fields have changed. Otherwise it returns back null causing the Update
/// to skip.
///
///
///
private UpdateFunctionConfigurationRequest CreateConfigurationRequestIfDifferent(GetFunctionConfigurationResponse existingConfiguration, string dotnetSharedStoreValue)
{
bool different = false;
var request = new UpdateFunctionConfigurationRequest
{
FunctionName = this.GetStringValueOrDefault(this.FunctionName, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_NAME, true)
};
var description = this.GetStringValueOrDefault(this.Description, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_DESCRIPTION, false);
if (!string.IsNullOrEmpty(description) && !string.Equals(description, existingConfiguration.Description, StringComparison.Ordinal))
{
request.Description = description;
different = true;
}
var role = this.GetStringValueOrDefault(this.Role, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_ROLE, false);
if (!string.IsNullOrEmpty(role))
{
string fullRole;
if (role.StartsWith(LambdaConstants.IAM_ARN_PREFIX))
fullRole = role;
else
fullRole = RoleHelper.ExpandRoleName(this.IAMClient, role);
if (!string.Equals(fullRole, existingConfiguration.Role, StringComparison.Ordinal))
{
request.Role = fullRole;
different = true;
}
}
var memorySize = this.GetIntValueOrDefault(this.MemorySize, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_MEMORY_SIZE, false);
if(memorySize.HasValue && memorySize.Value != existingConfiguration.MemorySize)
{
request.MemorySize = memorySize.Value;
different = true;
}
var ephemeralSize = this.GetIntValueOrDefault(this.EphemeralStorageSize, LambdaDefinedCommandOptions.ARGUMENT_EPHEMERAL_STORAGE_SIZE, false);
if (ephemeralSize.HasValue && ephemeralSize.Value != existingConfiguration.EphemeralStorage?.Size)
{
request.EphemeralStorage = new EphemeralStorage { Size = ephemeralSize.Value };
}
var timeout = this.GetIntValueOrDefault(this.Timeout, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_TIMEOUT, false);
if (timeout.HasValue && timeout.Value != existingConfiguration.Timeout)
{
request.Timeout = timeout.Value;
different = true;
}
var layerVersionArns = this.GetStringValuesOrDefault(this.LayerVersionArns, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_LAYERS, false);
if(layerVersionArns != null && AreDifferent(layerVersionArns, existingConfiguration.Layers?.Select(x => x.Arn)))
{
request.Layers = layerVersionArns.ToList();
request.IsLayersSet = true;
different = true;
}
var subnetIds = this.GetStringValuesOrDefault(this.SubnetIds, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SUBNETS, false);
if (subnetIds != null)
{
if (request.VpcConfig == null)
{
request.VpcConfig = new VpcConfig();
}
request.VpcConfig.SubnetIds = subnetIds.ToList();
request.VpcConfig.IsSubnetIdsSet = true;
if (existingConfiguration.VpcConfig == null || AreDifferent(subnetIds, existingConfiguration.VpcConfig.SubnetIds))
{
different = true;
}
}
var securityGroupIds = this.GetStringValuesOrDefault(this.SecurityGroupIds, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SECURITY_GROUPS, false);
if (securityGroupIds != null)
{
if (request.VpcConfig == null)
{
request.VpcConfig = new VpcConfig();
}
request.VpcConfig.SecurityGroupIds = securityGroupIds.ToList();
request.VpcConfig.IsSecurityGroupIdsSet = true;
if (existingConfiguration.VpcConfig == null || AreDifferent(securityGroupIds, existingConfiguration.VpcConfig.SecurityGroupIds))
{
different = true;
}
}
var deadLetterTargetArn = this.GetStringValueOrDefault(this.DeadLetterTargetArn, LambdaDefinedCommandOptions.ARGUMENT_DEADLETTER_TARGET_ARN, false);
if (deadLetterTargetArn != null)
{
if (!string.IsNullOrEmpty(deadLetterTargetArn) && !string.Equals(deadLetterTargetArn, existingConfiguration.DeadLetterConfig?.TargetArn, StringComparison.Ordinal))
{
request.DeadLetterConfig = existingConfiguration.DeadLetterConfig ?? new DeadLetterConfig();
request.DeadLetterConfig.TargetArn = deadLetterTargetArn;
different = true;
}
else if (string.IsNullOrEmpty(deadLetterTargetArn) && !string.IsNullOrEmpty(existingConfiguration.DeadLetterConfig?.TargetArn))
{
request.DeadLetterConfig = null;
request.DeadLetterConfig = existingConfiguration.DeadLetterConfig ?? new DeadLetterConfig();
request.DeadLetterConfig.TargetArn = string.Empty;
different = true;
}
}
var tracingMode = this.GetStringValueOrDefault(this.TracingMode, LambdaDefinedCommandOptions.ARGUMENT_TRACING_MODE, false);
if (tracingMode != null)
{
var eTraceMode = !string.Equals(tracingMode, string.Empty) ? Amazon.Lambda.TracingMode.FindValue(tracingMode) : null;
if (eTraceMode != existingConfiguration.TracingConfig?.Mode)
{
request.TracingConfig = new TracingConfig();
request.TracingConfig.Mode = eTraceMode;
different = true;
}
}
var kmsKeyArn = this.GetStringValueOrDefault(this.KMSKeyArn, LambdaDefinedCommandOptions.ARGUMENT_KMS_KEY_ARN, false);
if (!string.IsNullOrEmpty(kmsKeyArn) && !string.Equals(kmsKeyArn, existingConfiguration.KMSKeyArn, StringComparison.Ordinal))
{
request.KMSKeyArn = kmsKeyArn;
different = true;
}
var environmentVariables = GetEnvironmentVariables(existingConfiguration?.Environment?.Variables);
// If runtime package store layers were set, then set the environment variable to tell the .NET Core runtime
// to look for assemblies in the folder where the layer will be expanded.
if(!string.IsNullOrEmpty(dotnetSharedStoreValue))
{
if(environmentVariables == null)
{
environmentVariables = new Dictionary();
}
environmentVariables[LambdaConstants.ENV_DOTNET_SHARED_STORE] = dotnetSharedStoreValue;
}
if (environmentVariables != null && AreDifferent(environmentVariables, existingConfiguration?.Environment?.Variables))
{
request.Environment = new Model.Environment { Variables = environmentVariables };
request.Environment.IsVariablesSet = true;
different = true;
}
if (existingConfiguration.PackageType == Lambda.PackageType.Zip)
{
var handler = this.GetStringValueOrDefault(this.Handler, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_HANDLER, false);
if (!string.IsNullOrEmpty(handler) && !string.Equals(handler, existingConfiguration.Handler, StringComparison.Ordinal))
{
request.Handler = handler;
different = true;
}
var runtime = this.GetStringValueOrDefault(this.Runtime, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_RUNTIME, false);
if (runtime != null && runtime != existingConfiguration.Runtime)
{
request.Runtime = runtime;
different = true;
}
}
else if (existingConfiguration.PackageType == Lambda.PackageType.Image)
{
{
var imageEntryPoints = this.GetStringValuesOrDefault(this.ImageEntryPoint, LambdaDefinedCommandOptions.ARGUMENT_IMAGE_ENTRYPOINT, false);
if (imageEntryPoints != null)
{
if (AreDifferent(imageEntryPoints, existingConfiguration.ImageConfigResponse?.ImageConfig?.EntryPoint))
{
if (request.ImageConfig == null)
{
request.ImageConfig = new ImageConfig();
}
request.ImageConfig.EntryPoint = imageEntryPoints.ToList();
request.ImageConfig.IsEntryPointSet = true;
different = true;
}
}
}
{
var imageCommands = this.GetStringValuesOrDefault(this.ImageCommand, LambdaDefinedCommandOptions.ARGUMENT_IMAGE_COMMAND, false);
if (imageCommands != null)
{
if (AreDifferent(imageCommands, existingConfiguration.ImageConfigResponse?.ImageConfig?.Command))
{
if (request.ImageConfig == null)
{
request.ImageConfig = new ImageConfig();
}
request.ImageConfig.Command = imageCommands.ToList();
request.ImageConfig.IsCommandSet = true;
different = true;
}
}
}
var imageWorkingDirectory = this.GetStringValueOrDefault(this.ImageWorkingDirectory, LambdaDefinedCommandOptions.ARGUMENT_IMAGE_WORKING_DIRECTORY, false);
if (imageWorkingDirectory != null)
{
if (request.ImageConfig == null)
{
request.ImageConfig = new ImageConfig();
}
if(!string.Equals(imageWorkingDirectory, existingConfiguration.ImageConfigResponse?.ImageConfig?.WorkingDirectory, StringComparison.Ordinal))
{
request.ImageConfig.WorkingDirectory = imageWorkingDirectory;
different = true;
}
}
}
if (!different)
return null;
return request;
}
public Dictionary GetEnvironmentVariables(Dictionary existingEnvironmentVariables)
{
var specifiedEnvironmentVariables = this.GetKeyValuePairOrDefault(this.EnvironmentVariables, LambdaDefinedCommandOptions.ARGUMENT_ENVIRONMENT_VARIABLES, false);
var appendEnvironmentVariables = this.GetKeyValuePairOrDefault(this.AppendEnvironmentVariables, LambdaDefinedCommandOptions.ARGUMENT_APPEND_ENVIRONMENT_VARIABLES, false);
if (appendEnvironmentVariables == null)
{
return specifiedEnvironmentVariables;
}
var combineSet = specifiedEnvironmentVariables ?? existingEnvironmentVariables;
if (combineSet == null)
{
combineSet = appendEnvironmentVariables;
}
else
{
foreach (var kvp in appendEnvironmentVariables)
{
combineSet[kvp.Key] = kvp.Value;
}
}
return combineSet;
}
private bool AreDifferent(IDictionary source, IDictionary target)
{
if (target == null)
target = new Dictionary();
if (source.Count != target.Count)
return true;
foreach(var kvp in source)
{
string value;
if (!target.TryGetValue(kvp.Key, out value))
return true;
if (!string.Equals(kvp.Value, value, StringComparison.Ordinal))
return true;
}
foreach (var kvp in target)
{
string value;
if (!source.TryGetValue(kvp.Key, out value))
return true;
if (!string.Equals(kvp.Value, value, StringComparison.Ordinal))
return true;
}
return false;
}
private bool AreDifferent(IEnumerable source, IEnumerable target)
{
if (source == null && target == null)
return false;
if(source?.Count() != target?.Count())
return true;
foreach(var item in source)
{
if (!target.Contains(item))
return true;
}
foreach (var item in target)
{
if (!source.Contains(item))
return true;
}
return false;
}
protected async Task GetFunctionUrlConfig(string functionName)
{
var request = new GetFunctionUrlConfigRequest
{
FunctionName = functionName
};
try
{
var response = await this.LambdaClient.GetFunctionUrlConfigAsync(request);
return response;
}
catch (ResourceNotFoundException)
{
return null;
}
catch (Exception e)
{
throw new LambdaToolsException($"Error creating function url config for function {request.FunctionName}: {e.Message}", LambdaToolsException.LambdaErrorCode.LambdaFunctionUrlGet, e);
}
}
protected async Task CreateFunctionUrlConfig(string functionName, FunctionUrlAuthType authType)
{
if (authType == null)
authType = Amazon.Lambda.FunctionUrlAuthType.NONE;
var request = new CreateFunctionUrlConfigRequest
{
FunctionName = functionName,
AuthType = authType
};
try
{
this.FunctionUrlLink = (await this.LambdaClient.CreateFunctionUrlConfigAsync(request)).FunctionUrl;
if(authType == Amazon.Lambda.FunctionUrlAuthType.NONE)
{
await AddFunctionUrlPublicPermissionStatement(functionName);
}
}
catch (Exception e)
{
throw new LambdaToolsException($"Error creating function url config for function {request.FunctionName}: {e.Message}", LambdaToolsException.LambdaErrorCode.LambdaFunctionUrlCreate, e);
}
}
protected async Task UpdateFunctionUrlConfig(string functionName, FunctionUrlAuthType oldAuthType, FunctionUrlAuthType newAuthType)
{
if (newAuthType == null)
newAuthType = Amazon.Lambda.FunctionUrlAuthType.NONE;
var request = new UpdateFunctionUrlConfigRequest
{
FunctionName = functionName,
AuthType = newAuthType
};
try
{
this.FunctionUrlLink = (await this.LambdaClient.UpdateFunctionUrlConfigAsync(request)).FunctionUrl;
if(oldAuthType != newAuthType)
{
if(newAuthType == Amazon.Lambda.FunctionUrlAuthType.NONE)
{
await AddFunctionUrlPublicPermissionStatement(functionName);
}
else
{
await RemoveFunctionUrlPublicPermissionStatement(functionName);
}
}
}
catch (Exception e)
{
throw new LambdaToolsException($"Error updating function url config for function {request.FunctionName}: {e.Message}", LambdaToolsException.LambdaErrorCode.LambdaFunctionUrlUpdate, e);
}
}
protected async Task DeleteFunctionUrlConfig(string functionName, FunctionUrlAuthType oldAuthType)
{
var request = new DeleteFunctionUrlConfigRequest
{
FunctionName = functionName
};
try
{
await this.LambdaClient.DeleteFunctionUrlConfigAsync(request);
if (oldAuthType == Lambda.FunctionUrlAuthType.NONE)
{
await RemoveFunctionUrlPublicPermissionStatement(functionName);
}
this.FunctionUrlLink = null;
}
catch (Exception e)
{
throw new LambdaToolsException($"Error deleting function url config for function {request.FunctionName}: {e.Message}", LambdaToolsException.LambdaErrorCode.LambdaFunctionUrlDelete, e);
}
}
private async Task AddFunctionUrlPublicPermissionStatement(string functionName)
{
var request = new AddPermissionRequest
{
StatementId = LambdaConstants.FUNCTION_URL_PUBLIC_PERMISSION_STATEMENT_ID,
FunctionName = functionName,
Principal = "*",
Action = "lambda:InvokeFunctionUrl",
FunctionUrlAuthType = Amazon.Lambda.FunctionUrlAuthType.NONE
};
try
{
this.Logger.WriteLine("Adding Lambda permission statement to public access for Function Url");
await LambdaClient.AddPermissionAsync(request);
}
catch(Amazon.Lambda.Model.ResourceConflictException)
{
this.Logger.WriteLine($"Lambda permission with statement id {LambdaConstants.FUNCTION_URL_PUBLIC_PERMISSION_STATEMENT_ID} for public access already exists");
}
}
private async Task RemoveFunctionUrlPublicPermissionStatement(string functionName)
{
var request = new RemovePermissionRequest
{
FunctionName = functionName,
StatementId = LambdaConstants.FUNCTION_URL_PUBLIC_PERMISSION_STATEMENT_ID
};
try
{
this.Logger.WriteLine("Removing Lambda permission statement to allow public access for Function Url");
await LambdaClient.RemovePermissionAsync(request);
}
catch(Amazon.Lambda.Model.ResourceNotFoundException)
{
this.Logger.WriteLine($"Lambda permission with statement id {LambdaConstants.FUNCTION_URL_PUBLIC_PERMISSION_STATEMENT_ID} for public access not found to be removed");
}
}
protected override void SaveConfigFile(JsonData data)
{
}
}
}