in src/Amazon.Lambda.Tools/Commands/DeployFunctionCommand.cs [193:535]
protected override async Task<bool> PerformActionAsync()
{
string projectLocation = Utilities.DetermineProjectLocation(this.WorkingDirectory, this.GetStringValueOrDefault(this.ProjectLocation, CommonDefinedCommandOptions.ARGUMENT_PROJECT_LOCATION, false));
string zipArchivePath = null;
string package = this.GetStringValueOrDefault(this.Package, LambdaDefinedCommandOptions.ARGUMENT_PACKAGE, false);
var layerVersionArns = this.GetStringValuesOrDefault(this.LayerVersionArns, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_LAYERS, false);
var layerPackageInfo = await LambdaUtilities.LoadLayerPackageInfos(this.Logger, this.LambdaClient, this.S3Client, layerVersionArns);
var architecture = this.GetStringValueOrDefault(this.Architecture, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_ARCHITECTURE, false);
Lambda.PackageType packageType = DeterminePackageType();
string ecrImageUri = null;
if (packageType == Lambda.PackageType.Image)
{
var pushResults = await PushLambdaImageAsync();
if (!pushResults.Success)
{
if (pushResults.LastException != null)
throw pushResults.LastException;
throw new LambdaToolsException("Failed to push container image to ECR.", LambdaToolsException.LambdaErrorCode.FailedToPushImage);
}
ecrImageUri = pushResults.ImageUri;
}
else
{
if (string.IsNullOrEmpty(package))
{
EnsureInProjectDirectory();
// Release will be the default configuration if nothing set.
string configuration = this.GetStringValueOrDefault(this.Configuration, CommonDefinedCommandOptions.ARGUMENT_CONFIGURATION, false);
string msbuildParameters = this.GetStringValueOrDefault(this.MSBuildParameters, CommonDefinedCommandOptions.ARGUMENT_MSBUILD_PARAMETERS, false);
var targetFramework = this.GetStringValueOrDefault(this.TargetFramework, CommonDefinedCommandOptions.ARGUMENT_FRAMEWORK, false);
if (string.IsNullOrEmpty(targetFramework))
{
targetFramework = Utilities.LookupTargetFrameworkFromProjectFile(projectLocation, msbuildParameters);
// If we still don't know what the target framework is ask the user what targetframework to use.
// This is common when a project is using multi targeting.
if(string.IsNullOrEmpty(targetFramework))
{
targetFramework = this.GetStringValueOrDefault(this.TargetFramework, CommonDefinedCommandOptions.ARGUMENT_FRAMEWORK, true);
}
}
bool isNativeAot = Utilities.LookPublishAotFlag(projectLocation, this.MSBuildParameters);
ValidateTargetFrameworkAndLambdaRuntime(targetFramework);
bool disableVersionCheck = this.GetBoolValueOrDefault(this.DisableVersionCheck, LambdaDefinedCommandOptions.ARGUMENT_DISABLE_VERSION_CHECK, false).GetValueOrDefault();
string publishLocation;
LambdaPackager.CreateApplicationBundle(defaults: this.DefaultConfig,
logger: this.Logger,
workingDirectory: this.WorkingDirectory,
projectLocation: projectLocation,
configuration: configuration,
targetFramework: targetFramework,
msbuildParameters: msbuildParameters,
architecture: architecture,
disableVersionCheck: disableVersionCheck,
layerPackageInfo: layerPackageInfo,
isNativeAot: isNativeAot,
useContainerForBuild: GetBoolValueOrDefault(this.UseContainerForBuild, LambdaDefinedCommandOptions.ARGUMENT_USE_CONTAINER_FOR_BUILD, false),
containerImageForBuild: GetStringValueOrDefault(this.ContainerImageForBuild, LambdaDefinedCommandOptions.ARGUMENT_CONTAINER_IMAGE_FOR_BUILD, false),
codeMountDirectory: GetStringValueOrDefault(this.CodeMountDirectory, LambdaDefinedCommandOptions.ARGUMENT_CODE_MOUNT_DIRECTORY, false),
publishLocation: out publishLocation,
zipArchivePath: ref zipArchivePath
);
if (string.IsNullOrEmpty(zipArchivePath))
throw new LambdaToolsException("Failed to create Lambda deployment bundle.", ToolsException.CommonErrorCode.DotnetPublishFailed);
}
else
{
if (!File.Exists(package))
throw new LambdaToolsException($"Package {package} does not exist", LambdaToolsException.LambdaErrorCode.InvalidPackage);
if (!string.Equals(Path.GetExtension(package), ".zip", StringComparison.OrdinalIgnoreCase))
throw new LambdaToolsException($"Package {package} must be a zip file", LambdaToolsException.LambdaErrorCode.InvalidPackage);
this.Logger.WriteLine($"Skipping compilation and using precompiled package {package}");
zipArchivePath = package;
}
}
MemoryStream lambdaZipArchiveStream = null;
if(zipArchivePath != null)
{
lambdaZipArchiveStream = new MemoryStream(File.ReadAllBytes(zipArchivePath));
}
try
{
var s3Bucket = this.GetStringValueOrDefault(this.S3Bucket, LambdaDefinedCommandOptions.ARGUMENT_S3_BUCKET, false);
bool? resolveS3 = this.GetBoolValueOrDefault(this.ResolveS3, LambdaDefinedCommandOptions.ARGUMENT_RESOLVE_S3, false);
string s3Key = null;
if (zipArchivePath != null && (resolveS3 == true || !string.IsNullOrEmpty(s3Bucket)))
{
if(string.IsNullOrEmpty(s3Bucket))
{
s3Bucket = await LambdaUtilities.ResolveDefaultS3Bucket(this.Logger, this.S3Client, this.STSClient);
}
else
{
await Utilities.ValidateBucketRegionAsync(this.S3Client, s3Bucket);
}
var functionName = this.GetStringValueOrDefault(this.FunctionName, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_NAME, true);
var s3Prefix = this.GetStringValueOrDefault(this.S3Prefix, LambdaDefinedCommandOptions.ARGUMENT_S3_PREFIX, false);
s3Key = await Utilities.UploadToS3Async(this.Logger, this.S3Client, s3Bucket, s3Prefix, functionName, lambdaZipArchiveStream);
}
var currentConfiguration = await GetFunctionConfigurationAsync();
if (currentConfiguration == null)
{
this.Logger.WriteLine($"Creating new Lambda function {this.FunctionName}");
var createRequest = new CreateFunctionRequest
{
PackageType = packageType,
FunctionName = this.GetStringValueOrDefault(this.FunctionName, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_NAME, true),
Description = this.GetStringValueOrDefault(this.Description, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_DESCRIPTION, false),
Role = this.GetRoleValueOrDefault(this.Role, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_ROLE,
Constants.LAMBDA_PRINCIPAL, LambdaConstants.AWS_LAMBDA_MANAGED_POLICY_PREFIX,
LambdaConstants.KNOWN_MANAGED_POLICY_DESCRIPTIONS, true),
Publish = this.GetBoolValueOrDefault(this.Publish, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_PUBLISH, false).GetValueOrDefault(),
MemorySize = this.GetIntValueOrDefault(this.MemorySize, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_MEMORY_SIZE, true).GetValueOrDefault(),
Timeout = this.GetIntValueOrDefault(this.Timeout, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_TIMEOUT, true).GetValueOrDefault(),
KMSKeyArn = this.GetStringValueOrDefault(this.KMSKeyArn, LambdaDefinedCommandOptions.ARGUMENT_KMS_KEY_ARN, false),
VpcConfig = new VpcConfig
{
SubnetIds = this.GetStringValuesOrDefault(this.SubnetIds, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SUBNETS, false)?.ToList(),
SecurityGroupIds = this.GetStringValuesOrDefault(this.SecurityGroupIds, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SECURITY_GROUPS, false)?.ToList()
},
LoggingConfig = new LoggingConfig
{
LogFormat = this.GetStringValueOrDefault(this.LogFormat, LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT, false),
ApplicationLogLevel = this.GetStringValueOrDefault(this.LogApplicationLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL, false),
SystemLogLevel = this.GetStringValueOrDefault(this.LogSystemLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL, false),
LogGroup = this.GetStringValueOrDefault(this.LogGroup, LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP, false),
}
};
var ephemeralSize = this.GetIntValueOrDefault(this.EphemeralStorageSize, LambdaDefinedCommandOptions.ARGUMENT_EPHEMERAL_STORAGE_SIZE, false);
if(ephemeralSize.HasValue)
{
createRequest.EphemeralStorage = new EphemeralStorage
{
Size = ephemeralSize.Value
};
}
if (!string.IsNullOrEmpty(architecture))
{
createRequest.Architectures = new List<string> { architecture };
}
if(packageType == Lambda.PackageType.Zip)
{
createRequest.Handler = this.GetStringValueOrDefault(this.Handler, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_HANDLER, true);
createRequest.Runtime = this.GetStringValueOrDefault(this.Runtime, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_RUNTIME, true);
createRequest.Layers = layerVersionArns?.ToList();
if (s3Bucket != null)
{
createRequest.Code = new FunctionCode
{
S3Bucket = s3Bucket,
S3Key = s3Key
};
}
else
{
createRequest.Code = new FunctionCode
{
ZipFile = lambdaZipArchiveStream
};
}
}
else if(packageType == Lambda.PackageType.Image)
{
createRequest.Code = new FunctionCode
{
ImageUri = ecrImageUri
};
createRequest.ImageConfig = new ImageConfig
{
Command = this.GetStringValuesOrDefault(this.ImageCommand, LambdaDefinedCommandOptions.ARGUMENT_IMAGE_COMMAND, false)?.ToList(),
EntryPoint = this.GetStringValuesOrDefault(this.ImageEntryPoint, LambdaDefinedCommandOptions.ARGUMENT_IMAGE_ENTRYPOINT, false)?.ToList(),
WorkingDirectory = this.GetStringValueOrDefault(this.ImageWorkingDirectory, LambdaDefinedCommandOptions.ARGUMENT_IMAGE_WORKING_DIRECTORY, false)
};
}
var environmentVariables = GetEnvironmentVariables(null);
var dotnetShareStoreVal = layerPackageInfo.GenerateDotnetSharedStoreValue();
if(!string.IsNullOrEmpty(dotnetShareStoreVal))
{
if(environmentVariables == null)
{
environmentVariables = new Dictionary<string, string>();
}
environmentVariables[LambdaConstants.ENV_DOTNET_SHARED_STORE] = dotnetShareStoreVal;
}
if (environmentVariables != null && environmentVariables.Count > 0)
{
createRequest.Environment = new Model.Environment
{
Variables = environmentVariables
};
}
var tags = this.GetKeyValuePairOrDefault(this.Tags, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_TAGS, false);
if(tags != null && tags.Count > 0)
{
createRequest.Tags = tags;
}
var deadLetterQueue = this.GetStringValueOrDefault(this.DeadLetterTargetArn, LambdaDefinedCommandOptions.ARGUMENT_DEADLETTER_TARGET_ARN, false);
if(!string.IsNullOrEmpty(deadLetterQueue))
{
createRequest.DeadLetterConfig = new DeadLetterConfig {TargetArn = deadLetterQueue };
}
var tracingMode = this.GetStringValueOrDefault(this.TracingMode, LambdaDefinedCommandOptions.ARGUMENT_TRACING_MODE, false);
if(!string.IsNullOrEmpty(tracingMode))
{
createRequest.TracingConfig = new TracingConfig { Mode = tracingMode };
}
var snapStartApplyOn = this.GetStringValueOrDefault(this.SnapStartApplyOn, LambdaDefinedCommandOptions.ARGUMENT_SNAP_START_APPLY_ON, false);
if (!string.IsNullOrEmpty(snapStartApplyOn))
{
createRequest.SnapStart = new SnapStart {ApplyOn = Amazon.Lambda.SnapStartApplyOn.FindValue(snapStartApplyOn)};
}
try
{
await this.LambdaClient.CreateFunctionAsync(createRequest);
this.Logger.WriteLine("New Lambda function created");
}
catch (Exception e)
{
throw new LambdaToolsException($"Error creating Lambda function: {e.Message}", LambdaToolsException.LambdaErrorCode.LambdaCreateFunction, e);
}
if(this.GetBoolValueOrDefault(this.FunctionUrlEnable, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_ENABLE, false).GetValueOrDefault())
{
var authType = this.GetStringValueOrDefault(this.FunctionUrlAuthType, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_AUTH, false);
await base.CreateFunctionUrlConfig(createRequest.FunctionName, authType);
this.Logger.WriteLine($"Function url config created: {this.FunctionUrlLink}");
}
}
else
{
this.Logger.WriteLine($"Updating code for existing function {this.FunctionName}");
var updateCodeRequest = new UpdateFunctionCodeRequest
{
FunctionName = this.GetStringValueOrDefault(this.FunctionName, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_NAME, true)
};
if (!string.IsNullOrEmpty(architecture))
{
updateCodeRequest.Architectures = new List<string> { architecture };
}
// In case the function is currently being updated from previous deployment wait till it available
// to be updated.
if (currentConfiguration.LastUpdateStatus == LastUpdateStatus.InProgress)
{
await LambdaUtilities.WaitTillFunctionAvailableAsync(Logger, this.LambdaClient, updateCodeRequest.FunctionName);
}
if (packageType == Lambda.PackageType.Zip)
{
if (s3Bucket != null)
{
updateCodeRequest.S3Bucket = s3Bucket;
updateCodeRequest.S3Key = s3Key;
}
else
{
updateCodeRequest.ZipFile = lambdaZipArchiveStream;
}
}
else if (packageType == Lambda.PackageType.Image)
{
updateCodeRequest.ImageUri = ecrImageUri;
}
var configUpdated = false;
try
{
// Update config should run before updating the function code to avoid a situation such as
// upgrading from an EOL .NET version to a supported version where the update would fail
// since lambda thinks we are updating an EOL version instead of upgrading.
configUpdated = await base.UpdateConfigAsync(currentConfiguration, layerPackageInfo.GenerateDotnetSharedStoreValue());
}
catch (Exception e)
{
throw new LambdaToolsException($"Error updating configuration for Lambda function: {e.Message}", LambdaToolsException.LambdaErrorCode.LambdaUpdateFunctionConfiguration, e);
}
try
{
await LambdaUtilities.WaitTillFunctionAvailableAsync(Logger, this.LambdaClient, updateCodeRequest.FunctionName);
await this.LambdaClient.UpdateFunctionCodeAsync(updateCodeRequest);
}
catch (Exception e)
{
if (configUpdated)
{
await base.AttemptRevertConfigAsync(currentConfiguration);
}
throw new LambdaToolsException($"Error updating code for Lambda function: {e.Message}", LambdaToolsException.LambdaErrorCode.LambdaUpdateFunctionCode, e);
}
await base.ApplyTags(currentConfiguration.FunctionArn);
var publish = this.GetBoolValueOrDefault(this.Publish, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_PUBLISH, false).GetValueOrDefault();
if(publish)
{
await base.PublishFunctionAsync(updateCodeRequest.FunctionName);
}
}
}
finally
{
lambdaZipArchiveStream?.Dispose();
}
return true;
}