/// <summary>
        /// Determine the action to be done for the local path, like building a .NET Core package, then uploading the
        /// package to S3. The S3 key is returned to be updated in the template.
        /// </summary>
        /// <param name="templateDirectory"></param>
        /// <param name="field"></param>
        /// <returns></returns>
        /// <exception cref="LambdaToolsException"></exception>
        private async Task <UpdateResourceResults> ProcessUpdatableResourceAsync(string templateDirectory, IUpdateResourceField field, string[] args)
        {
            UpdateResourceResults results;
            var localPath = field.GetLocalPath();

            if (!Path.IsPathRooted(localPath))
            {
                localPath = Path.Combine(templateDirectory, localPath);
            }

            bool deleteArchiveAfterUploaded = false;

            // Uploading a single file as the code for the resource. If the single file is not a zip file then zip the file first.
            if (File.Exists(localPath))
            {
                if (field.IsCode && !string.Equals(Path.GetExtension(localPath), ".zip", StringComparison.OrdinalIgnoreCase))
                {
                    this.Logger.WriteLine($"Creating zip archive for {localPath} file");
                    results = new UpdateResourceResults {
                        ZipArchivePath = GenerateOutputZipFilename(field)
                    };
                    LambdaPackager.BundleFiles(results.ZipArchivePath, Path.GetDirectoryName(localPath), new string[] { localPath }, this.Logger);
                }
                else
                {
                    results = new UpdateResourceResults {
                        ZipArchivePath = localPath
                    };
                }
            }
            // If IsCode is false then the local path needs to point to a file and not a directory. When IsCode is true
            // it can point either to a file or a directory.
            else if (!field.IsCode && !File.Exists(localPath))
            {
                throw new LambdaToolsException($"File that the field {field.Resource.Name}/{field.Name} is pointing to doesn't exist", LambdaToolsException.LambdaErrorCode.ServerlessTemplateMissingLocalPath);
            }
            else if (!Directory.Exists(localPath))
            {
                throw new LambdaToolsException($"Directory that the field {field.Resource.Name}/{field.Name} is pointing doesn't exist", LambdaToolsException.LambdaErrorCode.ServerlessTemplateMissingLocalPath);
            }
            // To maintain compatibility if the field is point to current directory or not set at all but a prepackaged zip archive is given
            // then use it as the package source.
            else if (IsCurrentDirectory(field.GetLocalPath()) && !string.IsNullOrEmpty(this.DefaultOptions.Package))
            {
                results = new UpdateResourceResults {
                    ZipArchivePath = this.DefaultOptions.Package
                };
            }
            else if (field.IsCode)
            {
                // If the function is image upload then run the .NET tools to handle running
                // docker build even if the current folder is not a .NET project. The .NET
                // could be in a sub folder or be a self contained Docker build.
                if (IsDotnetProjectDirectory(localPath) || field.Resource.UploadType == CodeUploadType.Image)
                {
                    results = await PackageDotnetProjectAsync(field, localPath, args);
                }
                else
                {
                    results = new UpdateResourceResults {
                        ZipArchivePath = GenerateOutputZipFilename(field)
                    };
                    LambdaPackager.BundleDirectory(results.ZipArchivePath, localPath, false, this.Logger);
                }
                deleteArchiveAfterUploaded = true;
            }
            else
            {
                throw new LambdaToolsException($"Unable to determine package action for the field {field.Resource.Name}/{field.Name}", LambdaToolsException.LambdaErrorCode.ServerlessTemplateUnknownActionForLocalPath);
            }

            if (!string.IsNullOrEmpty(results.ZipArchivePath))
            {
                string s3Key;
                using (var stream = File.OpenRead(results.ZipArchivePath))
                {
                    s3Key = await Utilities.UploadToS3Async(this.Logger, this.S3Client, this.S3Bucket, this.S3Prefix, Path.GetFileName(results.ZipArchivePath), stream);

                    results.S3Key = s3Key;
                }

                // Now that the temp zip file is uploaded to S3 clean up by deleting the temp file.
                if (deleteArchiveAfterUploaded)
                {
                    try
                    {
                        File.Delete(results.ZipArchivePath);
                    }
                    catch (Exception e)
                    {
                        this.Logger?.WriteLine($"Warning: Unable to delete temporary archive, {results.ZipArchivePath}, after uploading to S3: {e.Message}");
                    }
                }
            }

            return(results);
        }
示例#2
0
        public static int Execute(CreateLocalLayerOptions opts)
        {
            Console.WriteLine($"Creating runtime package store from manifest: {opts.Manifest}");

            if (opts.EnableOptimization)
            {
                if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
                {
                    throw new Exception($"Package optimization is only possible on Amazon Linux. To use this feature execute the command in an Amazon Linux environment.");
                }
                else
                {
                    Console.WriteLine("Warning: Package optimization has been enabled. Be sure to run this on an Amazon Linux environment or the optimization might not be compatbile with the Lambda runtime.");
                }
            }

            if (!File.Exists(opts.Manifest))
            {
                throw new Exception($"Can not find package manifest {opts.Manifest}. Make sure to point to a file not a directory.");
            }

            var tempDirectoryName = $"{opts.StoreName}-{DateTime.UtcNow.Ticks}".ToLower();

            var tempRootPath         = Path.Combine(Path.GetTempPath(), tempDirectoryName);
            var storeOutputDirectory = Path.Combine(tempRootPath, Common.Constants.DEFAULT_LAYER_OPT_DIRECTORY);

            var convertResult = ManifestUtilities.ConvertManifestToSdkManifest(opts.Manifest);

            if (convertResult.ShouldDelete)
            {
                Console.WriteLine("Converted ASP.NET Core project file to temporary package manifest file.");
            }

            var cliWrapper  = new LambdaDotNetCLIWrapper(Directory.GetCurrentDirectory());
            var storeResult = cliWrapper.Store(
                !string.IsNullOrEmpty(opts.ProjectLocation) ? opts.ProjectLocation : Directory.GetCurrentDirectory(),
                storeOutputDirectory,
                opts.TargetFramework,
                convertResult.PackageManifest,
                opts.EnableOptimization);

            if (storeResult.exitCode != 0)
            {
                throw new Exception($"Error executing the 'dotnet store' command");
            }

            var manifest = ProjectUtilities.FindArtifactOutput(storeResult.filePath);

            //string[] files = Directory.GetFiles(storeResult.filePath, "artifact.xml", SearchOption.AllDirectories);

            var updatedContent = File.ReadAllText(manifest);
            var manifestPath   = Path.Combine(Directory.GetCurrentDirectory(), $"{opts.StoreName}.xml");

            File.WriteAllText(manifestPath, updatedContent);

            Console.WriteLine($"Created package manifest file ({manifestPath})");

            if (convertResult.ShouldDelete)
            {
                File.Delete(convertResult.PackageManifest);
            }

            var zipPath = Path.Combine(Directory.GetCurrentDirectory(), $"{opts.StoreName}.zip");

            if (File.Exists(zipPath))
            {
                File.Delete(zipPath);
            }

            LambdaPackager.BundleDirectory(zipPath, tempRootPath, false);

            return(0);
        }
        private async Task <CreateLayerZipFileResult> CreateRuntimePackageStoreLayerZipFile(string layerName, string s3Prefix)
        {
            var targetFramework    = this.GetStringValueOrDefault(this.TargetFramework, CommonDefinedCommandOptions.ARGUMENT_FRAMEWORK, true);
            var projectLocation    = this.GetStringValueOrDefault(this.ProjectLocation, CommonDefinedCommandOptions.ARGUMENT_PROJECT_LOCATION, false);
            var enableOptimization = this.GetBoolValueOrDefault(this.EnablePackageOptimization, LambdaDefinedCommandOptions.ARGUMENT_ENABLE_PACKAGE_OPTIMIZATION, false).GetValueOrDefault();

            if (string.Equals(targetFramework, "netcoreapp3.1"))
            {
                var version = DotNetCLIWrapper.GetSdkVersion();

                // .NET SDK 3.1 versions less then 3.1.400 have an issue throwing NullReferenceExceptions when pruning packages out with the manifest.
                // https://github.com/dotnet/sdk/issues/10973
                if (version < Version.Parse("3.1.400"))
                {
                    var message = $"Publishing runtime package store layers targeting .NET Core 3.1 requires at least version 3.1.400 of the .NET SDK. Current version installed is {version}.";
                    throw new LambdaToolsException(message, LambdaToolsException.LambdaErrorCode.DisabledSupportForNET31Layers);
                }
            }

#if NETCORE
            if (enableOptimization)
            {
                if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
                {
                    throw new LambdaToolsException($"Package optimization is only possible on Amazon Linux. To use this feature execute the command in an Amazon Linux environment.", LambdaToolsException.LambdaErrorCode.UnsupportedOptimizationPlatform);
                }
                else
                {
                    this.Logger.WriteLine("Warning: Package optimization has been enabled. Be sure to run this on an Amazon Linux environment or the optimization might not be compatbile with the Lambda runtime.");
                }
            }
#else
            // This is the case the code is run in the AWS Toolkit for Visual Studio which will never run on Amazon Linux.
            enableOptimization = false;
#endif

            // This is the manifest that list the NuGet packages via <PackageReference> elements in the msbuild project file.
            var packageManifest = this.GetStringValueOrDefault(this.PackageManifest, LambdaDefinedCommandOptions.ARGUMENT_PACKAGE_MANIFEST, false);
            // If this is null attempt to use the current directory. This is likely if the intent is to make a
            // layer from the current Lambda project.
            if (string.IsNullOrEmpty(packageManifest))
            {
                packageManifest = Utilities.DetermineProjectLocation(this.WorkingDirectory, projectLocation);
            }

            // If this is a directory look to see if there is a single csproj of fsproj in the directory in use that.
            // This is to make it easy to make a layer in the current directory of a Lambda function.
            if (Directory.Exists(packageManifest))
            {
                var files = Directory.GetFiles(packageManifest, "*.csproj");
                if (files.Length == 1)
                {
                    packageManifest = Path.Combine(packageManifest, files[0]);
                }
                else if (files.Length == 0)
                {
                    files = Directory.GetFiles(packageManifest, "*.fsproj");
                    if (files.Length == 1)
                    {
                        packageManifest = Path.Combine(packageManifest, files[0]);
                    }
                }
            }

            if (!File.Exists(packageManifest))
            {
                throw new LambdaToolsException($"Can not find package manifest {packageManifest}. Make sure to point to a file not a directory.", LambdaToolsException.LambdaErrorCode.LayerPackageManifestNotFound);
            }

            // Create second subdirectory so that when the directory is zipped the sub directory is retained in the zip file.
            // The sub directory will be created in the /opt directory in the Lambda environment.
            var tempDirectoryName = $"{layerName}-{DateTime.UtcNow.Ticks}".ToLower();
            var optDirectory      = this.GetStringValueOrDefault(this.OptDirectory, LambdaDefinedCommandOptions.ARGUMENT_OPT_DIRECTORY, false);
            if (string.IsNullOrEmpty(optDirectory))
            {
                optDirectory = LambdaConstants.DEFAULT_LAYER_OPT_DIRECTORY;
            }
            var tempRootPath         = Path.Combine(Path.GetTempPath(), tempDirectoryName);
            var storeOutputDirectory = Path.Combine(tempRootPath, optDirectory);

            {
                var convertResult = LambdaUtilities.ConvertManifestToSdkManifest(targetFramework, packageManifest);
                if (convertResult.ShouldDelete)
                {
                    this.Logger?.WriteLine("Converted ASP.NET Core project file to temporary package manifest file.");
                }
                var architecture = this.GetStringValueOrDefault(this.Architecture, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_ARCHITECTURE, false);
                var cliWrapper   = new LambdaDotNetCLIWrapper(this.Logger, this.WorkingDirectory);
                if (cliWrapper.Store(defaults: this.DefaultConfig,
                                     projectLocation: projectLocation,
                                     outputLocation: storeOutputDirectory,
                                     targetFramework: targetFramework,
                                     packageManifest: convertResult.PackageManifest,
                                     architecture: architecture,
                                     enableOptimization: enableOptimization) != 0)
                {
                    throw new LambdaToolsException($"Error executing the 'dotnet store' command", LambdaToolsException.LambdaErrorCode.StoreCommandError);
                }

                if (convertResult.ShouldDelete)
                {
                    File.Delete(convertResult.PackageManifest);
                }
            }

            // The artifact.xml file is generated by the "dotnet store" command that lists the packages that were added to the store.
            // It is required during a "dotnet publish" so the NuGet packages in the store will be filtered out.
            var artifactXmlPath = Path.Combine(storeOutputDirectory, "x64", targetFramework, "artifact.xml");
            if (!File.Exists(artifactXmlPath))
            {
                throw new LambdaToolsException($"Failed to find artifact.xml file in created local store.", LambdaToolsException.LambdaErrorCode.FailedToFindArtifactZip);
            }
            this.Logger.WriteLine($"Uploading runtime package store manifest to S3");
            var s3Key = await UploadFile(artifactXmlPath, $"{s3Prefix}artifact.xml");

            this.Logger.WriteLine($"Create zip file of runtime package store directory");
            var zipPath = Path.Combine(Path.GetTempPath(), $"{layerName}-{DateTime.UtcNow.Ticks}.zip");
            if (File.Exists(zipPath))
            {
                File.Delete(zipPath);
            }
            LambdaPackager.BundleDirectory(zipPath, tempRootPath, false, this.Logger);

            var result = new CreateLayerZipFileResult
            {
                ZipFile        = zipPath,
                LayerDirectory = optDirectory
            };

            var s3Bucket = this.GetStringValueOrDefault(this.S3Bucket, LambdaDefinedCommandOptions.ARGUMENT_S3_BUCKET, true);

            // Set the description field to the our JSON layer manifest file so when the tooling is used
            // to create a package of a Lambda function in the future the artifact.xml file can be used during "dotnet publish".
            result.Description = GeneratorRuntimePackageManifestLayerDescription(optDirectory, s3Bucket, s3Key, enableOptimization);

            var compatibleRuntime = LambdaUtilities.DetermineLambdaRuntimeFromTargetFramework(targetFramework);
            if (!string.IsNullOrEmpty(compatibleRuntime))
            {
                result.CompatibleRuntimes = new List <string>()
                {
                    compatibleRuntime
                };
            }

            return(result);
        }
示例#4
0
        /// <summary>
        /// Determine the action to be done for the local path, like building a .NET Core package, then uploading the
        /// package to S3. The S3 key is returned to be updated in the template.
        /// </summary>
        /// <param name="templateDirectory"></param>
        /// <param name="field"></param>
        /// <returns></returns>
        /// <exception cref="LambdaToolsException"></exception>
        private async Task <string> ProcessUpdatableResourceAsync(string templateDirectory, IUpdateResourceField field)
        {
            var localPath = field.GetLocalPath();

            if (!Path.IsPathRooted(localPath))
            {
                localPath = Path.Combine(templateDirectory, localPath);
            }

            bool   deleteArchiveAfterUploaded = false;
            string zipArchivePath;

            if (File.Exists(localPath))
            {
                if (field.IsCode && !string.Equals(Path.GetExtension(localPath), ".zip", StringComparison.OrdinalIgnoreCase))
                {
                    this.Logger.WriteLine($"Creating zip archive for {localPath} file");
                    zipArchivePath = GenerateOutputZipFilename(field);
                    LambdaPackager.BundleFiles(zipArchivePath, Path.GetDirectoryName(localPath), new string[] { localPath }, this.Logger);
                }
                else
                {
                    zipArchivePath = localPath;
                }
            }
            // If IsCode is false then the local path needs to point to a file and not a directory. When IsCode is true
            // it can point either to a file or a directory.
            else if (!field.IsCode && !File.Exists(localPath))
            {
                throw new LambdaToolsException($"File that the field {field.Resource.Name}/{field.Name} is pointing to doesn't exist", LambdaToolsException.LambdaErrorCode.ServerlessTemplateMissingLocalPath);
            }
            else if (!Directory.Exists(localPath))
            {
                throw new LambdaToolsException($"Directory that the field {field.Resource.Name}/{field.Name} is pointing doesn't exist", LambdaToolsException.LambdaErrorCode.ServerlessTemplateMissingLocalPath);
            }
            // To maintain compatibility if the field is point to current directory or not set at all but a prepackaged zip archive is given
            // then use it as the package source.
            else if (IsCurrentDirectory(field.GetLocalPath()) && !string.IsNullOrEmpty(this.DefaultOptions.Package))
            {
                zipArchivePath = this.DefaultOptions.Package;
            }
            else if (field.IsCode)
            {
                if (IsDotnetProjectDirectory(localPath))
                {
                    zipArchivePath = await PackageDotnetProjectAsync(field, localPath);
                }
                else
                {
                    zipArchivePath = GenerateOutputZipFilename(field);
                    LambdaPackager.BundleDirectory(zipArchivePath, localPath, false, this.Logger);
                }
                deleteArchiveAfterUploaded = true;
            }
            else
            {
                throw new LambdaToolsException($"Unable to determine package action for the field {field.Resource.Name}/{field.Name}", LambdaToolsException.LambdaErrorCode.ServerlessTemplateUnknownActionForLocalPath);
            }

            string s3Key;

            using (var stream = File.OpenRead(zipArchivePath))
            {
                s3Key = await Utilities.UploadToS3Async(this.Logger, this.S3Client, this.S3Bucket, this.S3Prefix, Path.GetFileName(zipArchivePath), stream);
            }

            // Now that the temp zip file is uploaded to S3 clean up by deleting the temp file.
            if (deleteArchiveAfterUploaded)
            {
                try
                {
                    File.Delete(zipArchivePath);
                }
                catch (Exception e)
                {
                    this.Logger?.WriteLine($"Warning: Unable to delete temporary archive, {zipArchivePath}, after uploading to S3: {e.Message}");
                }
            }

            return(s3Key);
        }