build/BuildSteps.cs (676 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using Colors.Net;
using Colors.Net.StringColorExtensions;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Newtonsoft.Json;
namespace Build
{
public static class BuildSteps
{
private static readonly string _wwwroot = Environment.ExpandEnvironmentVariables(@"%HOME%\site\wwwroot");
private static IntegrationTestBuildManifest _integrationManifest;
public static void Clean()
{
Directory.Delete(Settings.OutputDir, recursive: true);
}
public static void RestorePackages()
{
// This will use the sources from the nuget.config file in the repo root
Shell.Run("dotnet", $"restore");
}
public static void UpdatePackageVersionForIntegrationTests()
{
if (string.IsNullOrEmpty(Settings.IntegrationBuildNumber))
{
throw new Exception($"Environment variable 'integrationBuildNumber' cannot be null or empty for an integration build.");
}
const string AzureFunctionsPreReleaseFeedName = "https://azfunc.pkgs.visualstudio.com/e6a70c92-4128-439f-8012-382fe78d6396/_packaging/AzureFunctionsPreRelease/nuget/v3/index.json";
var packagesToUpdate = GetV3PackageList();
string currentDirectory = null;
Dictionary<string, string> buildPackages = new Dictionary<string, string>();
_integrationManifest = new IntegrationTestBuildManifest();
try
{
currentDirectory = Directory.GetCurrentDirectory();
var projectFolder = Path.GetFullPath(Settings.SrcProjectPath);
Directory.SetCurrentDirectory(projectFolder);
foreach (var package in packagesToUpdate)
{
var packageInfo = GetLatestPackageInfo(name: package.Name, majorVersion: package.MajorVersion, source: AzureFunctionsPreReleaseFeedName);
Shell.Run("dotnet", $"add package {packageInfo.Name} -v {packageInfo.Version} -s {AzureFunctionsPreReleaseFeedName} --no-restore");
buildPackages.Add(packageInfo.Name, packageInfo.Version);
}
}
finally
{
if (buildPackages.Count > 0)
{
_integrationManifest.Packages = buildPackages;
}
Directory.SetCurrentDirectory(currentDirectory);
}
}
public static void ReplaceTelemetryInstrumentationKey()
{
var instrumentationKey = Settings.TelemetryInstrumentationKey;
if (!string.IsNullOrEmpty(instrumentationKey))
{
// Given the small size of the file, it should be ok to load it in the memory
var constantsFileText = File.ReadAllText(Settings.ConstantsFile);
if (Regex.Matches(constantsFileText, Settings.TelemetryKeyToReplace).Count != 1)
{
throw new Exception($"Could not find exactly one {Settings.TelemetryKeyToReplace} in {Settings.ConstantsFile} to replace.");
}
constantsFileText = constantsFileText.Replace(Settings.TelemetryKeyToReplace, instrumentationKey);
File.WriteAllText(Settings.ConstantsFile, constantsFileText);
}
}
private static string GetRuntimeId(string runtime)
{
if (runtime.StartsWith(Settings.MinifiedVersionPrefix))
{
return runtime.Substring(Settings.MinifiedVersionPrefix.Length);
}
return runtime;
}
public static void DotnetPack()
{
var outputPath = Path.Combine(Settings.OutputDir);
Shell.Run("dotnet", $"pack {Settings.SrcProjectPath} " +
$"/p:BuildNumber=\"{Settings.BuildNumber}\" " +
$"/p:NoWorkers=\"true\" " +
$"/p:TargetFramework=net8.0 " + // without TargetFramework, the generated nuspec has incorrect path for the copy files operation.
$"/p:CommitHash=\"{Settings.CommitId}\" " +
(string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") +
$"-o {outputPath} -c Release --no-build");
}
public static void DotnetPublishForZips()
{
foreach (var runtime in Settings.TargetRuntimes)
{
var isMinVersion = runtime.StartsWith(Settings.MinifiedVersionPrefix);
var outputPath = Path.Combine(Settings.OutputDir, runtime);
var rid = GetRuntimeId(runtime);
ExecuteDotnetPublish(outputPath, rid, "net8.0");
if (isMinVersion)
{
RemoveLanguageWorkers(outputPath);
CreateMinConfigurationFile(outputPath);
}
}
if (!string.IsNullOrEmpty(Settings.IntegrationBuildNumber) && (_integrationManifest != null))
{
_integrationManifest.CommitId = Settings.CommitId;
}
}
private static void ExecuteDotnetPublish(string outputPath, string rid, string targetFramework)
{
Shell.Run("dotnet", $"publish {Settings.ProjectFile} " +
$"/p:BuildNumber=\"{Settings.BuildNumber}\" " +
$"/p:CommitHash=\"{Settings.CommitId}\" " +
$"/p:ContinuousIntegrationBuild=\"true\" " +
(string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") +
$"-o {outputPath} -c Release -f {targetFramework} --self-contained" +
(string.IsNullOrEmpty(rid) ? string.Empty : $" -r {rid}"));
}
public static void FilterPowershellRuntimes()
{
var minifiedRuntimes = Settings.TargetRuntimes.Where(r => r.StartsWith(Settings.MinifiedVersionPrefix));
foreach (var runtime in Settings.TargetRuntimes.Except(minifiedRuntimes))
{
var powershellWorkerRootPath = Path.Combine(Settings.OutputDir, runtime, "workers", "powershell");
var allPowershellWorkerPaths = Directory.GetDirectories(powershellWorkerRootPath);
foreach (var powershellWorkerPath in allPowershellWorkerPaths)
{
var powerShellVersion = Path.GetFileName(powershellWorkerPath);
var powershellRuntimePath = Path.Combine(powershellWorkerPath, "runtimes");
var allFoundPowershellRuntimes = Directory.GetDirectories(powershellRuntimePath).Select(Path.GetFileName).ToList();
var powershellRuntimesForCurrentToolsRuntime = Settings.ToolsRuntimeToPowershellRuntimes[powerShellVersion][runtime];
// Check to make sure all the expected runtimes are available
if (allFoundPowershellRuntimes.All(powershellRuntimesForCurrentToolsRuntime.Contains))
{
throw new Exception($"Expected PowerShell runtimes not found for Powershell v{powerShellVersion}. Expected runtimes are {string.Join(", ", powershellRuntimesForCurrentToolsRuntime)}." +
$"{Environment.NewLine}Found runtimes are {string.Join(", ", allFoundPowershellRuntimes)}");
}
// Delete all the runtimes that should not belong to the current artifactDirectory
allFoundPowershellRuntimes.Except(powershellRuntimesForCurrentToolsRuntime).ToList().ForEach(r => Directory.Delete(Path.Combine(powershellRuntimePath, r), recursive: true));
}
}
// Small test to ensure we have all the right runtimes at the right places
foreach (var runtime in Settings.TargetRuntimes.Except(minifiedRuntimes))
{
var powershellWorkerRootPath = Path.Combine(Settings.OutputDir, runtime, "workers", "powershell");
var allPowershellWorkerPaths = Directory.GetDirectories(powershellWorkerRootPath);
foreach (var powershellWorkerPath in allPowershellWorkerPaths)
{
var powerShellVersion = Path.GetFileName(powershellWorkerPath);
var powershellRuntimePath = Path.Combine(powershellWorkerPath, "runtimes");
var currentPowershellRuntimes = Directory.GetDirectories(powershellRuntimePath).Select(Path.GetFileName).ToList();
var requiredPowershellRuntimes = Settings.ToolsRuntimeToPowershellRuntimes[powerShellVersion][runtime].Distinct().ToList();
if (currentPowershellRuntimes.Count != requiredPowershellRuntimes.Count() || !requiredPowershellRuntimes.All(currentPowershellRuntimes.Contains))
{
throw new Exception($"Mismatch between Expected Powershell runtimes ({string.Join(", ", requiredPowershellRuntimes)}) and Found Powershell runtimes " +
$"({string.Join(", ", currentPowershellRuntimes)}) in the path {powershellRuntimePath}");
}
}
}
// No action needed for the "_net8.0" versions of these artifacts as they have an empty "workers" directory.
}
public static void FilterPythonRuntimes()
{
var minifiedRuntimes = Settings.TargetRuntimes.Where(r => r.StartsWith(Settings.MinifiedVersionPrefix));
foreach (var runtime in Settings.TargetRuntimes.Except(minifiedRuntimes))
{
var pythonWorkerPath = Path.Combine(Settings.OutputDir, runtime, "workers", "python");
var allPythonVersions = Directory.GetDirectories(pythonWorkerPath);
foreach (var pyVersionPath in allPythonVersions)
{
var allOs = Directory.GetDirectories(pyVersionPath).Select(Path.GetFileName).ToList();
bool atLeastOne = false;
foreach (var os in allOs)
{
if (!string.Equals(Settings.RuntimesToOS[runtime], os, StringComparison.OrdinalIgnoreCase))
{
Directory.Delete(Path.Combine(pyVersionPath, os), recursive: true);
}
else
{
atLeastOne = true;
}
}
if (!atLeastOne)
{
throw new Exception($"No Python worker matched the OS '{Settings.RuntimesToOS[runtime]}' for artifactDirectory '{runtime}'. " +
$"Something went wrong.");
}
}
}
// No action needed for the "_net8.0" versions of these artifacts as they have an empty "workers" directory.
}
public static void AddDistLib()
{
var distLibDir = Path.Combine(Settings.OutputDir, "distlib");
var distLibZip = Path.Combine(Settings.OutputDir, "distlib.zip");
using (var client = new WebClient())
{
client.DownloadFile(Settings.DistLibUrl, distLibZip);
}
ZipFile.ExtractToDirectory(distLibZip, distLibDir);
foreach (var runtime in Settings.TargetRuntimes)
{
var dist = Path.Combine(Settings.OutputDir, runtime, "tools", "python", "packapp", "distlib");
Directory.CreateDirectory(dist);
FileHelpers.RecursiveCopy(Path.Combine(distLibDir, Directory.GetDirectories(distLibDir).First(), "distlib"), dist);
}
File.Delete(distLibZip);
Directory.Delete(distLibDir, recursive: true);
// No action needed for the "_net8.0" versions of these artifacts as we don't ship workers for them.
}
public static void AddTemplatesNupkgs()
{
var templatesPath = Path.Combine(Settings.OutputDir, "nupkg-templates");
var isolatedTemplatesPath = Path.Combine(templatesPath, "net-isolated");
Directory.CreateDirectory(templatesPath);
Directory.CreateDirectory(isolatedTemplatesPath);
using (var client = new WebClient())
{
// If any of these names / paths change, we need to make sure our tooling partners (in particular VS and VS Mac) are notified
// and we are sure it doesn't break them.
client.DownloadFile(Settings.DotnetIsolatedItemTemplates,
Path.Combine(isolatedTemplatesPath, $"itemTemplates.{Settings.DotnetIsolatedItemTemplatesVersion}.nupkg"));
client.DownloadFile(Settings.DotnetIsolatedProjectTemplates,
Path.Combine(isolatedTemplatesPath, $"projectTemplates.{Settings.DotnetIsolatedProjectTemplatesVersion}.nupkg"));
client.DownloadFile(Settings.DotnetItemTemplates,
Path.Combine(templatesPath, $"itemTemplates.{Settings.DotnetItemTemplatesVersion}.nupkg"));
client.DownloadFile(Settings.DotnetProjectTemplates,
Path.Combine(templatesPath, $"projectTemplates.{Settings.DotnetProjectTemplatesVersion}.nupkg"));
}
foreach (var runtime in Settings.TargetRuntimes)
{
FileHelpers.RecursiveCopy(templatesPath, Path.Combine(Settings.OutputDir, runtime, "templates"));
}
Directory.Delete(templatesPath, recursive: true);
}
public static void AddTemplatesJson()
{
var tempDirectoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
using (var client = new WebClient())
{
FileHelpers.EnsureDirectoryExists(tempDirectoryPath);
var zipFilePath = Path.Combine(tempDirectoryPath, "templates.zip");
client.DownloadFile(Settings.TemplatesJsonZip, zipFilePath);
FileHelpers.ExtractZipToDirectory(zipFilePath, tempDirectoryPath);
}
string templatesJsonPath = Path.Combine(tempDirectoryPath, "templates", "templates.json");
if (File.Exists(templatesJsonPath))
{
foreach (var runtime in Settings.TargetRuntimes)
{
File.Copy(templatesJsonPath, Path.Combine(Settings.OutputDir, runtime, "templates", "templates.json"));
}
}
string templatesv2JsonPath = Path.Combine(tempDirectoryPath, "templates-v2", "templates.json");
string userPromptsv2JsonPath = Path.Combine(tempDirectoryPath, "bindings-v2", "userPrompts.json");
if (File.Exists(templatesv2JsonPath) && File.Exists(userPromptsv2JsonPath))
{
foreach (var runtime in Settings.TargetRuntimes)
{
File.Copy(templatesv2JsonPath, Path.Combine(Settings.OutputDir, runtime, "templates-v2", "templates.json"));
File.Copy(userPromptsv2JsonPath, Path.Combine(Settings.OutputDir, runtime, "templates-v2", "userPrompts.json"));
}
}
}
public static void Test()
{
var funcPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? Path.Combine(Settings.OutputDir, "win-x86", "func.exe")
: Path.Combine(Settings.OutputDir, "linux-x64", "func");
Environment.SetEnvironmentVariable("FUNC_PATH", funcPath);
string durableStorageConnectionVar = "DURABLE_STORAGE_CONNECTION";
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(durableStorageConnectionVar)))
{
Environment.SetEnvironmentVariable(durableStorageConnectionVar, "UseDevelopmentStorage=true");
}
Environment.SetEnvironmentVariable("DURABLE_FUNCTION_PATH", Settings.DurableFolder);
Shell.Run("dotnet", $"test {Settings.TestProjectFile} -f net8.0 --logger trx");
}
public static void TestNewE2EProject()
{
var funcPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? Path.Combine(Settings.OutputDir, "win-x86", "func.exe")
: Path.Combine(Settings.OutputDir, "linux-x64", "func");
Environment.SetEnvironmentVariable("FUNC_PATH", funcPath);
string durableStorageConnectionVar = "DURABLE_STORAGE_CONNECTION";
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(durableStorageConnectionVar)))
{
Environment.SetEnvironmentVariable(durableStorageConnectionVar, "UseDevelopmentStorage=true");
}
Environment.SetEnvironmentVariable("DURABLE_FUNCTION_PATH", Settings.DurableFolder);
Shell.Run("dotnet", $"test {Settings.NewTestProjectFile} -f net8.0 --blame-hang-timeout 10m --logger \"console;verbosity=detailed\"");
}
public static void TestNewE2EProjectDotnetInProc()
{
var funcPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? Path.Combine(Settings.OutputDir, "win-x86", "func.exe")
: Path.Combine(Settings.OutputDir, "linux-x64", "func");
Environment.SetEnvironmentVariable("FUNC_PATH", funcPath);
string durableStorageConnectionVar = "DURABLE_STORAGE_CONNECTION";
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(durableStorageConnectionVar)))
{
Environment.SetEnvironmentVariable(durableStorageConnectionVar, "UseDevelopmentStorage=true");
}
Environment.SetEnvironmentVariable("DURABLE_FUNCTION_PATH", Settings.DurableFolder);
Shell.Run("dotnet", $"test {Settings.NewTestProjectFile} -f net8.0 --logger trx --settings {Settings.RuntimeSettings} --blame-hang-timeout 10m");
}
public static void CopyBinariesToSign()
{
string toSignDirPath = Path.Combine(Settings.OutputDir, Settings.SignInfo.ToSignDir);
string authentiCodeDirectory = Path.Combine(toSignDirPath, Settings.SignInfo.ToAuthenticodeSign);
string thirdPartyDirectory = Path.Combine(toSignDirPath, Settings.SignInfo.ToThirdPartySign);
string macDirectory = Path.Combine(toSignDirPath, Settings.SignInfo.ToMacSign);
Directory.CreateDirectory(authentiCodeDirectory);
Directory.CreateDirectory(thirdPartyDirectory);
Directory.CreateDirectory(macDirectory);
var combinedRuntimesToSign = GetAllRuntimesToSign();
foreach (var supportedRuntime in combinedRuntimesToSign)
{
var sourceDir = Path.Combine(Settings.OutputDir, supportedRuntime);
var dirName = $"Azure.Functions.Cli.{supportedRuntime}.{CurrentVersion}";
if (supportedRuntime.StartsWith("osx"))
{
var toSignMacFiles = Settings.SignInfo.macBinaries.Select(el => Path.Combine(sourceDir, el)).ToList();
var targetMacDirectory = Path.Combine(macDirectory, dirName);
toSignMacFiles.ForEach(f => FileHelpers.CopyFileRelativeToBase(f, targetMacDirectory, sourceDir));
// mac signing requires the files to be in a zip
var zipPath = Path.Combine(macDirectory, $"{dirName}.zip");
ColoredConsole.WriteLine($"Creating {zipPath}");
ZipFile.CreateFromDirectory(targetMacDirectory, zipPath, CompressionLevel.Optimal, includeBaseDirectory: false);
Directory.Delete(targetMacDirectory, recursive: true);
}
else
{
var toSignPaths = Settings.SignInfo.authentiCodeBinaries.Select(el => Path.Combine(sourceDir, el));
// Grab all the files and filter the extensions not to be signed
var toAuthenticodeSignFiles = FileHelpers.GetAllFilesFromFilesAndDirs(FileHelpers.ExpandFileWildCardEntries(toSignPaths))
.Where(file => !Settings.SignInfo.FilterExtensionsSign.Any(ext => file.EndsWith(ext))).ToList();
string targetAuthenticodeDirectory = Path.Combine(authentiCodeDirectory, dirName);
toAuthenticodeSignFiles.ForEach(f => FileHelpers.CopyFileRelativeToBase(f, targetAuthenticodeDirectory, sourceDir));
var toSignThirdPartyPaths = Settings.SignInfo.thirdPartyBinaries.Select(el => Path.Combine(sourceDir, el));
// Grab all the files and filter the extensions not to be signed
var toSignThirdPartyFiles = FileHelpers.GetAllFilesFromFilesAndDirs(FileHelpers.ExpandFileWildCardEntries(toSignThirdPartyPaths))
.Where(file => !Settings.SignInfo.FilterExtensionsSign.Any(ext => file.EndsWith(ext))).ToList();
string targetThirdPartyDirectory = Path.Combine(thirdPartyDirectory, dirName);
toSignThirdPartyFiles.ForEach(f => FileHelpers.CopyFileRelativeToBase(f, targetThirdPartyDirectory, sourceDir));
}
}
// binaries we know are unsigned via sigcheck.exe
var unSignedBinaries = GetUnsignedBinaries(toSignDirPath);
// binaries to be signed via signed tool
var allFiles = Directory.GetFiles(toSignDirPath, "*.*", new EnumerationOptions() { RecurseSubdirectories = true }).ToList();
// These assemblies are currently signed, but with an invalid root cert.
// Until that is resolved, we are explicity signing the AppService.Middleware packages
unSignedBinaries = unSignedBinaries.Concat(allFiles
.Where(f => f.Contains("Microsoft.Azure.AppService.Middleware") || f.Contains("Microsoft.Azure.AppService.Proxy"))).ToList();
// remove all entries for binaries that are actually unsigned (checked via sigcheck.exe)
unSignedBinaries.ForEach(f => allFiles.RemoveAll(n => n.Equals(f, StringComparison.OrdinalIgnoreCase)));
// all the files that are remaining are signed files, delete the signed files since they don't need to be signed again
allFiles.ForEach(f => File.Delete(f));
}
public static void TestPreSignedArtifacts()
{
var filterExtensionsSignSet = new HashSet<string>(Settings.SignInfo.FilterExtensionsSign);
var combinedRuntimesToSign = GetAllRuntimesToSign();
foreach (var supportedRuntime in combinedRuntimesToSign)
{
if (supportedRuntime.StartsWith("osx"))
{
// sigcheck.exe does not work for mac signatures
continue;
}
var sourceDir = Path.Combine(Settings.OutputDir, supportedRuntime);
var targetDir = Path.Combine(Settings.OutputDir, Settings.PreSignTestDir, supportedRuntime);
Directory.CreateDirectory(targetDir);
FileHelpers.RecursiveCopy(sourceDir, targetDir);
var inProc8Directory = Path.Combine(targetDir, "in-proc8");
var inProc8DirectoryExists = Directory.Exists(inProc8Directory);
var toSignPathsForInProc8 = inProc8DirectoryExists
? Settings.SignInfo.authentiCodeBinaries.Select(el => Path.Combine(inProc8Directory, el))
: Enumerable.Empty<string>();
var toSignPaths = Settings.SignInfo.authentiCodeBinaries.Select(el => Path.Combine(targetDir, el)).Concat(toSignPathsForInProc8);
var toSignThirdPartyPathsForInProc8 = inProc8DirectoryExists
? Settings.SignInfo.thirdPartyBinaries.Select(el => Path.Combine(inProc8Directory, el))
: Enumerable.Empty<string>();
var toSignThirdPartyPaths = Settings.SignInfo.thirdPartyBinaries.Select(el => Path.Combine(targetDir, el)).Concat(toSignThirdPartyPathsForInProc8);
var unSignedFiles = FileHelpers.GetAllFilesFromFilesAndDirs(FileHelpers.ExpandFileWildCardEntries(toSignPaths))
.Where(file => !filterExtensionsSignSet.Any(ext => file.EndsWith(ext))).ToList();
unSignedFiles.AddRange(FileHelpers.GetAllFilesFromFilesAndDirs(FileHelpers.ExpandFileWildCardEntries(toSignThirdPartyPaths))
.Where(file => !filterExtensionsSignSet.Any(ext => file.EndsWith(ext))));
unSignedFiles.ForEach(filePath => File.Delete(filePath));
var unSignedPackages = GetUnsignedBinaries(targetDir);
if (unSignedPackages.Count() != 0)
{
var missingSignature = string.Join($",{Environment.NewLine}", unSignedPackages);
ColoredConsole.Error.WriteLine($"This files are missing valid signatures: {Environment.NewLine}{missingSignature}");
throw new Exception($"sigcheck.exe test failed. Following files are unsigned: {Environment.NewLine}{missingSignature}");
}
}
}
public static void TestSignedArtifacts()
{
string[] zipFiles = Directory.GetFiles(Settings.OutputDir, "*.zip");
foreach (string zipFilePath in zipFiles)
{
if (zipFilePath.Contains("osx"))
{
// sigcheck.exe does not work for mac signatures
continue;
}
bool isSignedRuntime = Settings.SignInfo.RuntimesToSign.Any(r => zipFilePath.Contains(r));
if (isSignedRuntime)
{
string targetDir = Path.Combine(Settings.OutputDir, "PostSignTest", Path.GetFileNameWithoutExtension(zipFilePath));
Directory.CreateDirectory(targetDir);
ZipFile.ExtractToDirectory(zipFilePath, targetDir);
var unSignedPackages = GetUnsignedBinaries(targetDir);
if (unSignedPackages.Count() != 0)
{
var missingSignature = string.Join($",{Environment.NewLine}", unSignedPackages);
ColoredConsole.Error.WriteLine($"This files are missing valid signatures: {Environment.NewLine}{missingSignature}");
throw new Exception($"sigcheck.exe test failed. Following files are unsigned: {Environment.NewLine}{missingSignature}");
}
}
}
}
public static List<string> GetUnsignedBinaries(string targetDir)
{
// Download sigcheck.exe
var sigcheckPath = Path.Combine(Settings.OutputDir, "sigcheck.exe");
if (!File.Exists(sigcheckPath))
{
using (var client = new WebClient())
{
client.DownloadFile(Settings.SignInfo.SigcheckDownloadURL, sigcheckPath);
}
}
// https://peter.hahndorf.eu/blog/post/2010/03/07/WorkAroundSysinternalsLicensePopups
// Can't use sigcheck without signing the License Agreement
Console.WriteLine("Signing EULA");
Console.WriteLine(Shell.GetOutput("reg.exe", "ADD HKCU\\Software\\Sysinternals /v EulaAccepted /t REG_DWORD /d 1 /f"));
Console.WriteLine(Shell.GetOutput("reg.exe", "ADD HKU\\.DEFAULT\\Software\\Sysinternals /v EulaAccepted /t REG_DWORD /d 1 /f"));
// sigcheck.exe will exit with error codes if unsigned binaries present
var csvOutputLines = Shell.GetOutput(sigcheckPath, $" -s -u -c -q {targetDir}", ignoreExitCode: true).Split(Environment.NewLine);
// CSV separators can differ between languages and regions.
var csvSep = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator;
var unSignedPackages = new List<string>();
foreach (var line in csvOutputLines)
{
// Some lines contain sigcheck header info and we filter them out by making sure
// there's at least six commas in each valid line.
if (line.Split(csvSep).Length - 1 > 6)
{
// Package name is the first element in each line.
var fileName = line.Split(csvSep)[0].Trim('"');
unSignedPackages.Add(fileName);
}
}
if (unSignedPackages.Count() < 1)
{
throw new Exception("Something went wrong while testing for signed packages. There must be a few unsigned allowed binaries");
}
// The first element is simply the column heading
unSignedPackages = unSignedPackages.Skip(1).ToList();
// Filter out the extensions we didn't want to sign
unSignedPackages = unSignedPackages.Where(file => !Settings.SignInfo.FilterExtensionsSign.Any(ext => file.EndsWith(ext))).ToList();
// Filter out files we don't want to verify
unSignedPackages = unSignedPackages.Where(file => !Settings.SignInfo.SkipSigcheckTest.Any(ext => file.EndsWith(ext))).ToList();
return unSignedPackages;
}
private static void CreateZipFromArtifact(string artifactSourcePath, string zipPath)
{
if (!Directory.Exists(artifactSourcePath))
{
throw new Exception($"Artifact source path {artifactSourcePath} does not exist.");
}
ColoredConsole.WriteLine($"Creating {zipPath}");
ZipFile.CreateFromDirectory(artifactSourcePath, zipPath, CompressionLevel.Optimal, includeBaseDirectory: false);
}
public static void Zip()
{
var version = CurrentVersion;
foreach (var runtime in Settings.TargetRuntimes)
{
var isMinVersion = runtime.StartsWith(Settings.MinifiedVersionPrefix);
var artifactPath = Path.Combine(Settings.OutputDir, runtime);
var zipPath = Path.Combine(Settings.OutputDir, $"Azure.Functions.Cli.{runtime}.{version}.zip");
CreateZipFromArtifact(artifactPath, zipPath);
// We leave the folders beginning with 'win' to generate the .msi files. They will be deleted in
// the ./generateMsiFiles.ps1 script
if (!runtime.StartsWith("win"))
{
try
{
Directory.Delete(artifactPath, recursive: true);
}
catch (Exception ex)
{
ColoredConsole.Error.WriteLine($"Error deleting artifact for runtime {runtime}. Exception: {ex}");
}
}
ColoredConsole.WriteLine();
}
}
private static string _version;
private static string CurrentVersion
{
get
{
if (string.IsNullOrEmpty(_version))
{
var funcPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? Path.Combine(Settings.OutputDir, "win-x86", "func.exe")
: Path.Combine(Settings.OutputDir, "linux-x64", "func");
_version = Shell.GetOutput(funcPath, "--version");
}
return _version;
}
}
public static void GenerateSBOMManifestForZips()
{
Directory.CreateDirectory(Settings.SBOMManifestTelemetryDir);
// Generate the SBOM manifest for each artifactDirectory
var allArtifactDirectories = Settings.TargetRuntimes.Concat(Settings.TargetRuntimes);
foreach (var artifactDirectory in allArtifactDirectories)
{
var packageName = $"Azure.Functions.Cli.{artifactDirectory}.{CurrentVersion}";
var artifactDirectoryFullPath = Path.Combine(Settings.OutputDir, artifactDirectory);
var manifestFolderPath = Path.Combine(artifactDirectoryFullPath, "_manifest");
var telemetryFilePath = Path.Combine(Settings.SBOMManifestTelemetryDir, Guid.NewGuid().ToString() + ".json");
// Delete the manifest folder if it exists
if (Directory.Exists(manifestFolderPath))
{
Directory.Delete(manifestFolderPath, recursive: true);
}
// Generate the SBOM manifest
Shell.Run("dotnet",
$"{Settings.SBOMManifestToolPath} generate -PackageName {packageName} -BuildDropPath {artifactDirectoryFullPath}"
+ $" -BuildComponentPath {artifactDirectoryFullPath} -Verbosity Information -t {telemetryFilePath}");
}
}
public static void DotnetPublishForNupkg()
{
// By default, this publishes to the /bin/Release/$targetFramework$/publish
Shell.Run("dotnet", $"publish {Settings.ProjectFile} " +
$"/p:BuildNumber=\"{Settings.BuildNumber}\" " +
$"/p:NoWorkers=\"true\" " +
$"/p:TargetFramework=net8.0 " +
$"/p:CommitHash=\"{Settings.CommitId}\" " +
(string.IsNullOrEmpty(Settings.IntegrationBuildNumber) ? string.Empty : $"/p:IntegrationBuildNumber=\"{Settings.IntegrationBuildNumber}\" ") +
$"-c Release -f net8.0");
}
public static void GenerateSBOMManifestForNupkg()
{
Directory.CreateDirectory(Settings.SBOMManifestTelemetryDir);
var packageName = $"Microsoft.Azure.Functions.CoreTools";
var buildPath = Settings.NupkgPublishDir;
var manifestFolderPath = Path.Combine(buildPath, "_manifest");
var telemetryFilePath = Path.Combine(Settings.SBOMManifestTelemetryDir, Guid.NewGuid().ToString() + ".json");
// Delete the manifest folder if it exists
if (Directory.Exists(manifestFolderPath))
{
Directory.Delete(manifestFolderPath, recursive: true);
}
Shell.Run("dotnet",
$"{Settings.SBOMManifestToolPath} generate -PackageName {packageName} -BuildDropPath {buildPath}"
+ $" -BuildComponentPath {buildPath} -Verbosity Information -t {telemetryFilePath}");
}
public static void DeleteSBOMTelemetryFolder()
{
Directory.Delete(Settings.SBOMManifestTelemetryDir, recursive: true);
}
public static void LogIntoAzure()
{
var directoryId = Environment.GetEnvironmentVariable("AZURE_DIRECTORY_ID");
var appId = Environment.GetEnvironmentVariable("AZURE_SERVICE_PRINCIPAL_ID");
var key = Environment.GetEnvironmentVariable("AZURE_SERVICE_PRINCIPAL_KEY");
if (!string.IsNullOrEmpty(directoryId) &&
!string.IsNullOrEmpty(appId) &&
!string.IsNullOrEmpty(key))
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Shell.Run("cmd", $"/c az login --service-principal -u {appId} -p \"{key}\" --tenant {directoryId}", silent: true);
}
else
{
Shell.Run("az", $"login --service-principal -u {appId} -p \"{key}\" --tenant {directoryId}", silent: true);
}
}
}
/// <summary>
/// Returns all target runtimes and their net8.0 versions.
/// </summary>
private static IEnumerable<string> GetAllTargetRuntimes()
{
var targetRuntimes = Settings.TargetRuntimes;
return targetRuntimes;
}
private static IEnumerable<string> GetAllRuntimesToSign()
{
var runtimeToSign = Settings.SignInfo.RuntimesToSign;
return runtimeToSign;
}
public static void AddGoZip()
{
var runtimeToGoEnv = new Dictionary<string, (string GOOS, string GOARCH)>
{
{ "win-x86", ("windows", "386") },
{ "win-arm64", ("windows", "arm64") },
{ "win-x64", ("windows", "amd64") },
{ "linux-x64", ("linux", "amd64") },
{ "osx-arm64", ("darwin", "arm64") },
{ "osx-x64", ("darwin", "amd64") }
};
var combinedRuntimesToSign = GetAllTargetRuntimes();
foreach (var runtime in combinedRuntimesToSign)
{
var runtimeId = GetRuntimeId(runtime);
if (runtimeToGoEnv.TryGetValue(runtimeId, out var goEnv))
{
Environment.SetEnvironmentVariable("CGO_ENABLED", "0");
Environment.SetEnvironmentVariable("GOOS", goEnv.GOOS);
Environment.SetEnvironmentVariable("GOARCH", goEnv.GOARCH);
var outputPath = Path.Combine(Settings.OutputDir, runtime, "gozip");
var output = runtimeId.Contains("win") ? $"{outputPath}.exe" : outputPath;
var goFile = Path.GetFullPath("../tools/go/gozip/main.go");
Shell.Run("go", $"build -o {output} {goFile}");
}
else
{
throw new Exception($"Unsupported runtime: {runtime}");
}
}
}
public static void CreateIntegrationTestsBuildManifest()
{
if (!string.IsNullOrEmpty(Settings.IntegrationBuildNumber) && (_integrationManifest != null))
{
_integrationManifest.CoreToolsVersion = _version;
_integrationManifest.Build = Settings.IntegrationBuildNumber;
var json = JsonConvert.SerializeObject(_integrationManifest, Formatting.Indented);
var manifestFilePath = Path.Combine(Settings.OutputDir, "integrationTestsBuildManifest.json");
File.WriteAllText(manifestFilePath, json);
}
}
private static List<Package> GetV3PackageList()
{
const string CoreToolsBuildPackageList = "https://raw.githubusercontent.com/Azure/azure-functions-integration-tests/main/integrationTestsBuild/V4/CoreToolsBuild.json";
Uri address = new Uri(CoreToolsBuildPackageList);
string content = null;
using (var client = new WebClient())
{
content = client.DownloadString(address);
}
if (string.IsNullOrEmpty(content))
{
throw new Exception($"Failed to download package list from {CoreToolsBuildPackageList}");
}
var packageList = JsonConvert.DeserializeObject<List<Package>>(content);
return packageList;
}
private static void RemoveLanguageWorkers(string outputPath)
{
foreach (var languageWorker in Settings.LanguageWorkers)
{
var path = Path.Combine(outputPath, "workers", languageWorker);
if (Directory.Exists(path))
{
Directory.Delete(path, recursive: true);
}
}
}
private static void CreateMinConfigurationFile(string outputPath)
{
var filePath = Path.Combine(outputPath, "artifactsconfig.json");
string artifactsJsonContent = "{\"minifiedVersion\": true}";
File.WriteAllTextAsync(filePath, artifactsJsonContent).GetAwaiter().GetResult();
}
private static PackageInfo GetLatestPackageInfo(string name, string majorVersion, string source)
{
string includeAllVersion = !string.IsNullOrWhiteSpace(majorVersion) ? "-AllVersions" : string.Empty;
string packageInfo = Shell.GetOutput("NuGet", $"list {name} -Source {source} -prerelease {includeAllVersion}");
if (packageInfo.Contains("No packages found", StringComparison.OrdinalIgnoreCase))
{
throw new Exception($"Package name {name} not found in {source}.");
}
if (!string.IsNullOrWhiteSpace(majorVersion))
{
foreach (var package in packageInfo.Split(Environment.NewLine))
{
var thisPackage = NewPackageInfo(package);
if (thisPackage.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && thisPackage.Version.StartsWith(majorVersion))
{
return thisPackage;
}
}
throw new Exception($"Failed to find {name} package for major version {majorVersion}.");
}
return NewPackageInfo(packageInfo);
}
private static PackageInfo NewPackageInfo(string packageInfo)
{
var parts = packageInfo.Split(" ");
if (parts.Length > 2)
{
throw new Exception($"Invalid package format. The string should only contain 'name<space>version'. Current value: '{packageInfo}'");
}
return new PackageInfo(Name: parts[0], Version: parts[1]);
}
}
}