internal static async Task <Stream> GetPythonDeploymentPackage(IEnumerable <string> files, string functionAppRoot, bool buildNativeDeps, BuildOption buildOption, string additionalPackages) { var reqTxtFile = Path.Combine(functionAppRoot, Constants.RequirementsTxt); if (!FileSystemHelpers.FileExists(reqTxtFile)) { throw new CliException($"{Constants.RequirementsTxt} is not found. " + $"{Constants.RequirementsTxt} is required for python function apps. Please make sure to generate one before publishing."); } var packagesLocation = Path.Combine(functionAppRoot, Constants.ExternalPythonPackages); if (FileSystemHelpers.DirectoryExists(packagesLocation)) { // Only update packages if checksum of requirements.txt does not match // If build option is remote, we don't need to verify if packages are in sync, as we need to delete them regardless if (buildOption != BuildOption.Remote && await ArePackagesInSync(reqTxtFile, packagesLocation)) { ColoredConsole.WriteLine(Yellow($"Directory {Constants.ExternalPythonPackages} already in sync with {Constants.RequirementsTxt}. Skipping restoring dependencies...")); return(await ZipHelper.CreateZip(files.Union(FileSystemHelpers.GetFiles(packagesLocation)), functionAppRoot)); } ColoredConsole.WriteLine($"Deleting the old {Constants.ExternalPythonPackages} directory"); FileSystemHelpers.DeleteDirectorySafe(packagesLocation); } FileSystemHelpers.EnsureDirectory(packagesLocation); // Only one of the remote build or build-native-deps flag can be chosen if (buildNativeDeps) { if (CommandChecker.CommandExists("docker") && await DockerHelpers.VerifyDockerAccess()) { await RestorePythonRequirementsDocker(functionAppRoot, packagesLocation, additionalPackages); } else { throw new CliException("Docker is required to build native dependencies for python function apps"); } } else if (buildOption == BuildOption.Remote) { // No-ops, python packages will be resolved on the server side } else { await RestorePythonRequirementsPackapp(functionAppRoot, packagesLocation); } // No need to generate and compare .md5 when using remote build if (buildOption != BuildOption.Remote) { // Store a checksum of requirements.txt var md5FilePath = Path.Combine(packagesLocation, $"{Constants.RequirementsTxt}.md5"); await FileSystemHelpers.WriteAllTextToFileAsync(md5FilePath, SecurityHelpers.CalculateMd5(reqTxtFile)); } return(await ZipHelper.CreateZip(files.Union(FileSystemHelpers.GetFiles(packagesLocation)), functionAppRoot)); }
private static async Task <bool> ArePackagesInSync(string requirementsTxt, string pythonPackages) { var md5File = Path.Combine(pythonPackages, $"{Constants.RequirementsTxt}.md5"); if (!FileSystemHelpers.FileExists(md5File)) { return(false); } var packagesMd5 = await FileSystemHelpers.ReadAllTextFromFileAsync(md5File); var requirementsTxtMd5 = SecurityHelpers.CalculateMd5(requirementsTxt); return(packagesMd5 == requirementsTxtMd5); }
internal static async Task <Stream> GetPythonDeploymentPackage(IEnumerable <string> files, string functionAppRoot, bool buildNativeDeps, string additionalPackages) { var reqTxtFile = Path.Combine(functionAppRoot, Constants.RequirementsTxt); if (!FileSystemHelpers.FileExists(reqTxtFile)) { throw new CliException($"{Constants.RequirementsTxt} is not found. " + $"{Constants.RequirementsTxt} is required for python function apps. Please make sure to generate one before publishing."); } var packagesLocation = Path.Combine(functionAppRoot, Constants.ExternalPythonPackages); if (FileSystemHelpers.DirectoryExists(packagesLocation)) { // Only update packages if checksum of requirements.txt does not match or a sync is forced if (await ArePackagesInSync(reqTxtFile, packagesLocation)) { ColoredConsole.WriteLine(Yellow($"Directory {Constants.ExternalPythonPackages} already in sync with {Constants.RequirementsTxt}. Skipping restoring dependencies...")); return(ZipHelper.CreateZip(files.Union(FileSystemHelpers.GetFiles(packagesLocation)), functionAppRoot)); } ColoredConsole.WriteLine($"Deleting the old {Constants.ExternalPythonPackages} directory"); FileSystemHelpers.DeleteDirectorySafe(Path.Combine(functionAppRoot, Constants.ExternalPythonPackages)); } FileSystemHelpers.EnsureDirectory(packagesLocation); if (buildNativeDeps) { if (CommandChecker.CommandExists("docker") && await DockerHelpers.VerifyDockerAccess()) { await RestorePythonRequirementsDocker(functionAppRoot, packagesLocation, additionalPackages); } else { throw new CliException("Docker is required to build native dependencies for python function apps"); } } else { await RestorePythonRequirementsPackapp(functionAppRoot, packagesLocation); } // Store a checksum of requirements.txt var md5FilePath = Path.Combine(packagesLocation, $"{Constants.RequirementsTxt}.md5"); await FileSystemHelpers.WriteAllTextToFileAsync(md5FilePath, SecurityHelpers.CalculateMd5(reqTxtFile)); return(ZipHelper.CreateZip(files.Union(FileSystemHelpers.GetFiles(packagesLocation)), functionAppRoot)); }