protected override async Task PerformActionAsync()

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