using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Text; using System.Text.RegularExpressions; using Microsoft.DotNet.Cli.Build.Framework; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; using static Microsoft.DotNet.Cli.Build.Framework.BuildHelpers; namespace Microsoft.DotNet.Cli.Build { public static class PublishTargets { private static AzurePublisher AzurePublisherTool { get; set; } private static DebRepoPublisher DebRepoPublisherTool { get; set; } private static string Channel { get; set; } private static string CliVersion { get; set; } private static string CliNuGetVersion { get; set; } private static string SharedFrameworkNugetVersion { get; set; } [Target] public static BuildTargetResult InitPublish(BuildTargetContext c) { AzurePublisherTool = new AzurePublisher(); DebRepoPublisherTool = new DebRepoPublisher(Dirs.Packages); CliVersion = c.BuildContext.Get("BuildVersion").SimpleVersion; CliNuGetVersion = c.BuildContext.Get("BuildVersion").NuGetVersion; SharedFrameworkNugetVersion = DependencyVersions.SharedFrameworkVersion; Channel = c.BuildContext.Get("Channel"); return c.Success(); } [Target(nameof(PrepareTargets.Init), nameof(PublishTargets.InitPublish), nameof(PublishTargets.PublishArtifacts), nameof(PublishTargets.TriggerDockerHubBuilds), nameof(PublishTargets.FinalizeBuild))] [Environment("PUBLISH_TO_AZURE_BLOB", "1", "true")] // This is set by CI systems public static BuildTargetResult Publish(BuildTargetContext c) { return c.Success(); } [Target] public static BuildTargetResult FinalizeBuild(BuildTargetContext c) { if (CheckIfAllBuildsHavePublished()) { string targetContainer = $"{Channel}/Binaries/Latest/"; string targetVersionFile = $"{targetContainer}{CliNuGetVersion}"; string semaphoreBlob = $"{Channel}/Binaries/publishSemaphore"; AzurePublisherTool.CreateBlobIfNotExists(semaphoreBlob); string leaseId = AzurePublisherTool.AcquireLeaseOnBlob(semaphoreBlob); // Prevent race conditions by dropping a version hint of what version this is. If we see this file // and it is the same as our version then we know that a race happened where two+ builds finished // at the same time and someone already took care of publishing and we have no work to do. if (AzurePublisherTool.IsLatestSpecifiedVersion(targetVersionFile)) { AzurePublisherTool.ReleaseLeaseOnBlob(semaphoreBlob, leaseId); return c.Success(); } else { // This is an old drop of latest so remove all old files to ensure a clean state AzurePublisherTool.ListBlobs($"{targetContainer}") .Select(s => s.Replace("/dotnet/", "")) .ToList() .ForEach(f => AzurePublisherTool.TryDeleteBlob(f)); // Drop the version file signaling such for any race-condition builds (see above comment). AzurePublisherTool.DropLatestSpecifiedVersion(targetVersionFile); } try { // Copy the latest CLI bits CopyBlobs($"{Channel}/Binaries/{CliNuGetVersion}/", targetContainer); // Copy the latest installer files CopyBlobs($"{Channel}/Installers/{CliNuGetVersion}/", $"{Channel}/Installers/Latest/"); // Generate the SDK Version text files List versionFiles = new List() { "win.x86.version", "win.x64.version", "ubuntu.x64.version", "rhel.x64.version", "osx.x64.version", "debian.x64.version", "centos.x64.version" }; string cliVersion = Utils.GetCliVersionFileContent(c); foreach (string version in versionFiles) { AzurePublisherTool.PublishStringToBlob($"{Channel}/dnvm/latest.{version}", cliVersion); } } finally { AzurePublisherTool.ReleaseLeaseOnBlob(semaphoreBlob, leaseId); } } return c.Success(); } private static void CopyBlobs(string sourceFolder, string destinationFolder) { foreach (string blob in AzurePublisherTool.ListBlobs(sourceFolder)) { string source = blob.Replace("/dotnet/", ""); string targetName = Path.GetFileName(blob) .Replace(CliNuGetVersion, "latest"); string target = $"{destinationFolder}{targetName}"; AzurePublisherTool.CopyBlob(source, target); } } private static bool CheckIfAllBuildsHavePublished() { Dictionary badges = new Dictionary() { { "Windows_x86", false }, { "Windows_x64", false }, { "Ubuntu_x64", false }, { "RHEL_x64", false }, { "OSX_x64", false }, { "Debian_x64", false }, { "CentOS_x64", false } }; List blobs = new List(AzurePublisherTool.ListBlobs($"{Channel}/Binaries/{CliNuGetVersion}/")); var versionBadgeName = $"{CurrentPlatform.Current}_{CurrentArchitecture.Current}"; if (badges.ContainsKey(versionBadgeName) == false) { throw new ArgumentException("A new OS build was added without adding the moniker to the {nameof(badges)} lookup"); } foreach (string file in blobs) { string name = Path.GetFileName(file); string key = string.Empty; foreach (string img in badges.Keys) { if ((name.StartsWith($"{img}")) && (name.EndsWith(".svg"))) { key = img; break; } } if (string.IsNullOrEmpty(key) == false) { badges[key] = true; } } return badges.Keys.All(key => badges[key]); } [Target( nameof(PublishTargets.PublishInstallerFilesToAzure), nameof(PublishTargets.PublishArchivesToAzure), /*nameof(PublishTargets.PublishDebFilesToDebianRepo),*/ //https://github.com/dotnet/cli/issues/2973 nameof(PublishTargets.PublishCliVersionBadge))] public static BuildTargetResult PublishArtifacts(BuildTargetContext c) => c.Success(); [Target( nameof(PublishTargets.PublishSdkInstallerFileToAzure), nameof(PublishTargets.PublishCombinedFrameworkSDKHostInstallerFileToAzure))] public static BuildTargetResult PublishInstallerFilesToAzure(BuildTargetContext c) => c.Success(); [Target( nameof(PublishTargets.PublishCombinedHostFrameworkSdkArchiveToAzure), nameof(PublishTargets.PublishCombinedFrameworkSDKArchiveToAzure), nameof(PublishTargets.PublishSDKSymbolsArchiveToAzure))] public static BuildTargetResult PublishArchivesToAzure(BuildTargetContext c) => c.Success(); [Target( nameof(PublishSdkDebToDebianRepo))] [BuildPlatforms(BuildPlatform.Ubuntu)] public static BuildTargetResult PublishDebFilesToDebianRepo(BuildTargetContext c) { return c.Success(); } [Target] public static BuildTargetResult PublishCliVersionBadge(BuildTargetContext c) { var versionBadge = c.BuildContext.Get("VersionBadge"); var versionBadgeBlob = $"{Channel}/Binaries/{CliNuGetVersion}/{Path.GetFileName(versionBadge)}"; AzurePublisherTool.PublishFile(versionBadgeBlob, versionBadge); return c.Success(); } [Target] [BuildPlatforms(BuildPlatform.Ubuntu)] public static BuildTargetResult PublishSdkInstallerFileToAzure(BuildTargetContext c) { var version = CliNuGetVersion; var installerFile = c.BuildContext.Get("SdkInstallerFile"); AzurePublisherTool.PublishInstallerFile(installerFile, Channel, version); return c.Success(); } [Target] [BuildPlatforms(BuildPlatform.Windows, BuildPlatform.OSX)] public static BuildTargetResult PublishCombinedFrameworkSDKHostInstallerFileToAzure(BuildTargetContext c) { var version = CliNuGetVersion; var installerFile = c.BuildContext.Get("CombinedFrameworkSDKHostInstallerFile"); AzurePublisherTool.PublishInstallerFile(installerFile, Channel, version); return c.Success(); } [Target] [BuildPlatforms(BuildPlatform.Windows)] public static BuildTargetResult PublishCombinedFrameworkSDKArchiveToAzure(BuildTargetContext c) { var version = CliNuGetVersion; var archiveFile = c.BuildContext.Get("CombinedFrameworkSDKCompressedFile"); AzurePublisherTool.PublishArchive(archiveFile, Channel, version); return c.Success(); } [Target] public static BuildTargetResult PublishCombinedHostFrameworkSdkArchiveToAzure(BuildTargetContext c) { var version = CliNuGetVersion; var archiveFile = c.BuildContext.Get("CombinedFrameworkSDKHostCompressedFile"); AzurePublisherTool.PublishArchive(archiveFile, Channel, version); return c.Success(); } [Target] public static BuildTargetResult PublishSDKSymbolsArchiveToAzure(BuildTargetContext c) { var version = CliNuGetVersion; var archiveFile = c.BuildContext.Get("SdkSymbolsCompressedFile"); AzurePublisherTool.PublishArchive(archiveFile, Channel, version); return c.Success(); } [Target] [BuildPlatforms(BuildPlatform.Ubuntu)] public static BuildTargetResult PublishSdkDebToDebianRepo(BuildTargetContext c) { var version = CliNuGetVersion; var packageName = Monikers.GetSdkDebianPackageName(c); var installerFile = c.BuildContext.Get("SdkInstallerFile"); var uploadUrl = AzurePublisherTool.CalculateInstallerUploadUrl(installerFile, Channel, version); DebRepoPublisherTool.PublishDebFileToDebianRepo( packageName, version, uploadUrl); return c.Success(); } [Target] [Environment("DOCKER_HUB_REPO")] [Environment("DOCKER_HUB_TRIGGER_TOKEN")] public static BuildTargetResult TriggerDockerHubBuilds(BuildTargetContext c) { string dockerHubRepo = Environment.GetEnvironmentVariable("DOCKER_HUB_REPO"); string dockerHubTriggerToken = Environment.GetEnvironmentVariable("DOCKER_HUB_TRIGGER_TOKEN"); Uri baseDockerHubUri = new Uri("https://registry.hub.docker.com/u/"); Uri dockerHubTriggerUri; if (!Uri.TryCreate(baseDockerHubUri, $"{dockerHubRepo}/trigger/{dockerHubTriggerToken}/", out dockerHubTriggerUri)) { return c.Failed("Invalid DOCKER_HUB_REPO and/or DOCKER_HUB_TRIGGER_TOKEN"); } c.Info($"Triggering automated DockerHub builds for {dockerHubRepo}"); using (HttpClient client = new HttpClient()) { StringContent requestContent = new StringContent("{\"build\": true}", Encoding.UTF8, "application/json"); try { HttpResponseMessage response = client.PostAsync(dockerHubTriggerUri, requestContent).Result; if (!response.IsSuccessStatusCode) { StringBuilder sb = new StringBuilder(); sb.AppendLine($"HTTP request to {dockerHubTriggerUri.ToString()} was unsuccessful."); sb.AppendLine($"Response status code: {response.StatusCode}. Reason phrase: {response.ReasonPhrase}."); sb.Append($"Respone content: {response.Content.ReadAsStringAsync().Result}"); return c.Failed(sb.ToString()); } } catch (AggregateException e) { return c.Failed($"HTTP request to {dockerHubTriggerUri.ToString()} failed. {e.ToString()}"); } } return c.Success(); } [Target(nameof(PrepareTargets.Init))] public static BuildTargetResult UpdateVersionsRepo(BuildTargetContext c) { string githubAuthToken = EnvVars.EnsureVariable("GITHUB_PASSWORD"); string nupkgFilePath = EnvVars.EnsureVariable("NUPKG_FILE_PATH"); string versionsRepoPath = EnvVars.EnsureVariable("VERSIONS_REPO_PATH"); VersionRepoUpdater repoUpdater = new VersionRepoUpdater(githubAuthToken); repoUpdater.UpdatePublishedVersions(nupkgFilePath, versionsRepoPath).Wait(); return c.Success(); } } }