public string GetBestMatch(IEnumerable <string> packageVersions) { var versions = packageVersions.Select(UniversalPackageVersion.Parse).OrderByDescending(v => v).ToList(); if (string.Equals(this.Value, "latest", StringComparison.OrdinalIgnoreCase)) { return(versions.FirstOrDefault()?.ToString()); } else if (this.Major != null && this.Minor != null) { return(versions.FirstOrDefault(v => v.Major == this.Major && v.Minor == this.Minor)?.ToString()); } else if (this.Major != null && this.Minor == null) { return(versions.FirstOrDefault(v => v.Major == this.Major)?.ToString()); } else { var semver = UniversalPackageVersion.Parse(this.Value); return(versions.FirstOrDefault(v => v == semver)?.ToString()); } }
public static async Task DeployAsync(IOperationExecutionContext context, IProGetPackageInstallTemplate template, ILogSink log, string installationReason, bool recordDeployment, Action <OperationProgress> setProgress = null) { var fileOps = await context.Agent.GetServiceAsync <IFileOperationsExecuter>().ConfigureAwait(false); var client = new ProGetClient(template.FeedUrl, template.FeedName, template.UserName, template.Password, log, context.CancellationToken); try { var packageId = PackageName.Parse(template.PackageName); log.LogInformation($"Connecting to {template.FeedUrl} to get metadata for {template.PackageName}..."); var packageInfo = await client.GetPackageInfoAsync(packageId).ConfigureAwait(false); string version; if (string.Equals(template.PackageVersion, "latest-stable", StringComparison.OrdinalIgnoreCase)) { var stableVersions = packageInfo.versions .Select(v => UniversalPackageVersion.TryParse(v)) .Where(v => string.IsNullOrEmpty(v?.Prerelease)); if (!stableVersions.Any()) { log.LogError($"Package {template.PackageName} does not have any stable versions."); return; } version = stableVersions.Max().ToString(); log.LogInformation($"Latest stable version of {template.PackageName} is {version}."); } else if (!string.IsNullOrEmpty(template.PackageVersion) && !string.Equals(template.PackageVersion, "latest", StringComparison.OrdinalIgnoreCase)) { if (!packageInfo.versions.Contains(template.PackageVersion, StringComparer.OrdinalIgnoreCase)) { log.LogError($"Package {template.PackageName} does not have a version {template.PackageVersion}."); return; } version = template.PackageVersion; } else { version = packageInfo.latestVersion; log.LogInformation($"Latest version of {template.PackageName} is {version}."); } var deployInfo = recordDeployment ? PackageDeploymentData.Create(context, log, $"Deployed by {installationReason} operation. See the URL for more info.") : null; var targetRootPath = context.ResolvePath(template.TargetDirectory); log.LogDebug("Target path: " + targetRootPath); log.LogInformation("Downloading package..."); using (var content = await client.DownloadPackageContentAsync(packageId, version, deployInfo, (position, length) => setProgress?.Invoke(new OperationProgress(length == 0 ? null : (int?)(100 * position / length), "downloading package"))).ConfigureAwait(false)) { var tempDirectoryName = fileOps.CombinePath(await fileOps.GetBaseWorkingDirectoryAsync().ConfigureAwait(false), Guid.NewGuid().ToString("N")); // ensure directory exists on server await fileOps.CreateDirectoryAsync(tempDirectoryName); var tempZipFileName = tempDirectoryName + ".zip"; try { setProgress?.Invoke(new OperationProgress(0, "copying package to agent")); using (var remote = await fileOps.OpenFileAsync(tempZipFileName, FileMode.CreateNew, FileAccess.Write).ConfigureAwait(false)) { await content.CopyToAsync(remote, 81920, context.CancellationToken, position => setProgress?.Invoke(new OperationProgress((int)(100 * position / content.Length), "copying package to agent"))).ConfigureAwait(false); } setProgress?.Invoke(new OperationProgress("extracting package to temporary directory")); await fileOps.ExtractZipFileAsync(tempZipFileName, tempDirectoryName, IO.FileCreationOptions.Overwrite).ConfigureAwait(false); var expectedFiles = new HashSet <string>(StringComparer.OrdinalIgnoreCase); var expectedDirectories = new HashSet <string>(StringComparer.OrdinalIgnoreCase); content.Position = 0; using (var zip = new ZipArchive(content, ZipArchiveMode.Read, true)) { foreach (var entry in zip.Entries) { // TODO: use AH.ReadZip when it is available in Otter. var fullName = entry.FullName.Replace('\\', '/'); if (!fullName.StartsWith("package/", StringComparison.OrdinalIgnoreCase) || fullName.Length <= "package/".Length) { continue; } if (entry.IsDirectory()) { expectedDirectories.Add(fullName.Substring("package/".Length).Trim('/')); } else { expectedFiles.Add(fullName.Substring("package/".Length)); var parts = fullName.Substring("package/".Length).Split('/'); for (int i = 1; i < parts.Length; i++) { // Add directories that are not explicitly in the zip file. expectedDirectories.Add(string.Join("/", parts.Take(i))); } } } } var jobExec = await context.Agent.TryGetServiceAsync <IRemoteJobExecuter>().ConfigureAwait(false); if (jobExec != null) { var job = new PackageDeploymentJob { DeleteExtra = template.DeleteExtra, TargetRootPath = targetRootPath, TempDirectoryName = tempDirectoryName, ExpectedDirectories = expectedDirectories.ToArray(), ExpectedFiles = expectedFiles.ToArray() }; job.MessageLogged += (s, e) => log.Log(e.Level, e.Message); job.ProgressChanged += (s, e) => setProgress?.Invoke(e); setProgress?.Invoke(new OperationProgress("starting remote job on agent")); await jobExec.ExecuteJobAsync(job, context.CancellationToken).ConfigureAwait(false); } else { setProgress?.Invoke(new OperationProgress("ensuring target directory exists")); await fileOps.CreateDirectoryAsync(targetRootPath).ConfigureAwait(false); int index = 0; if (template.DeleteExtra) { setProgress?.Invoke(new OperationProgress("checking existing files")); var remoteFileList = await fileOps.GetFileSystemInfosAsync(targetRootPath, MaskingContext.IncludeAll).ConfigureAwait(false); foreach (var file in remoteFileList) { index++; setProgress?.Invoke(new OperationProgress(100 * index / remoteFileList.Count, "checking existing files")); var relativeName = file.FullName.Substring(targetRootPath.Length).Replace('\\', '/').Trim('/'); if (file is SlimDirectoryInfo) { if (!expectedDirectories.Contains(relativeName)) { log.LogDebug("Deleting extra directory: " + relativeName); await fileOps.DeleteDirectoryAsync(file.FullName).ConfigureAwait(false); } } else { if (!expectedFiles.Contains(relativeName)) { log.LogDebug($"Deleting extra file: " + relativeName); await fileOps.DeleteFileAsync(file.FullName).ConfigureAwait(false); } } } } index = 0; foreach (var relativeName in expectedDirectories) { index++; setProgress?.Invoke(new OperationProgress(100 * index / expectedDirectories.Count, "ensuring target subdirectories exist")); await fileOps.CreateDirectoryAsync(fileOps.CombinePath(targetRootPath, relativeName)).ConfigureAwait(false); } index = 0; foreach (var relativeName in expectedFiles) { var sourcePath = fileOps.CombinePath(tempDirectoryName, "package", relativeName); var targetPath = fileOps.CombinePath(targetRootPath, relativeName); index++; setProgress?.Invoke(new OperationProgress(100 * index / expectedFiles.Count, "moving files to target directory")); await fileOps.MoveFileAsync(sourcePath, targetPath, true).ConfigureAwait(false); } } setProgress?.Invoke(new OperationProgress("cleaning temporary files")); } finally { await Task.WhenAll( fileOps.DeleteFileAsync(tempZipFileName), fileOps.DeleteDirectoryAsync(tempDirectoryName) ).ConfigureAwait(false); } } setProgress?.Invoke(new OperationProgress("recording package installation in machine registry")); using (var registry = await PackageRegistry.GetRegistryAsync(context.Agent, false).ConfigureAwait(false)) { var package = new RegisteredPackage { Group = packageId.Group, Name = packageId.Name, Version = version, InstallPath = targetRootPath, FeedUrl = template.FeedUrl, InstallationDate = DateTimeOffset.Now.ToString("o"), InstallationReason = installationReason, InstalledUsing = $"{SDK.ProductName}/{SDK.ProductVersion} (InedoCore/{Extension.Version})" }; try { using (var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10))) using (context.CancellationToken.Register(() => cancellationTokenSource.Cancel())) { await registry.LockAsync(cancellationTokenSource.Token).ConfigureAwait(false); await registry.RegisterPackageAsync(package, context.CancellationToken).ConfigureAwait(false); // doesn't need to be in a finally because dispose will unlock if necessary, but prefer doing it asynchronously await registry.UnlockAsync().ConfigureAwait(false); } } catch (TaskCanceledException) { log.LogWarning("Registering the package in the machine package registry timed out."); } } } catch (ProGetException ex) { log.LogError(ex.FullMessage); return; } setProgress?.Invoke(null); log.LogInformation("Package deployed!"); }
public override async Task ExecuteAsync(IOperationExecutionContext context) { if (string.IsNullOrWhiteSpace(this.PackageFile)) { if (string.IsNullOrWhiteSpace(this.FeedUrl)) { this.LogError("FeedUrl is required if PackageFile is not specified."); return; } if (string.IsNullOrWhiteSpace(this.PackageName)) { this.LogError("Name is required if PackageFile is not specified."); return; } var endpoint = new UniversalFeedEndpoint(new Uri(this.FeedUrl), this.UserName, this.Password); this.LogInformation($"Getting package information for {this.PackageName} from {endpoint}..."); var client = new UniversalFeedClient(endpoint); var versions = await client.ListPackageVersionsAsync(UniversalPackageId.Parse(this.PackageName)); this.LogDebug($"Server return info for {versions.Count} packages."); RemoteUniversalPackageVersion package; if (!string.IsNullOrWhiteSpace(this.PackageVersion)) { this.LogDebug($"Checking for {this.PackageVersion} in result set..."); var parsedVersion = UniversalPackageVersion.Parse(this.PackageVersion); package = versions.FirstOrDefault(p => p.Version == parsedVersion); if (package != null) { this.LogInformation($"Package {this.PackageName} {this.PackageVersion} found."); } else { this.LogInformation($"Package {this.PackageName} {this.PackageVersion} not found."); } } else { if (versions.Count > 0) { this.LogDebug($"Determining latest version of {this.PackageName}..."); package = versions.Aggregate((p1, p2) => p1.Version >= p2.Version ? p1 : p2); this.LogInformation($"Latest version of {this.PackageName} is {package.Version}."); } else { this.LogInformation($"Package {this.PackageName} not found."); package = null; } } if (package != null) { this.Exists = true; this.Metadata = this.Convert(package.AllProperties).AsDictionary(); } else { this.Exists = false; } } else { if (!string.IsNullOrWhiteSpace(this.FeedUrl)) { this.LogWarning("FeedUrl is ignored when PackageFile is specified."); } if (!string.IsNullOrWhiteSpace(this.PackageName)) { this.LogError("Name is ignored when PackageFile is specified."); } if (!string.IsNullOrWhiteSpace(this.PackageVersion)) { this.LogError("Version is ignored when PackageFile is specified."); } var fileOps = await context.Agent.GetServiceAsync <IFileOperationsExecuter>(); var fullPath = context.ResolvePath(this.PackageFile); this.LogInformation($"Reading {fullPath}..."); if (await fileOps.FileExistsAsync(fullPath)) { this.LogDebug("Package file exists; reading metadata..."); UniversalPackageMetadata metadata; try { using (var stream = await fileOps.OpenFileAsync(fullPath, FileMode.Open, FileAccess.Read)) using (var package = new UniversalPackage(stream)) { metadata = package.GetFullMetadata(); } } catch (Exception ex) { this.LogError("Error reading package: " + ex); return; } this.Exists = true; this.Metadata = this.Convert(metadata).AsDictionary(); } else { this.LogInformation("Package file not found."); this.Exists = false; } } }
protected override async Task <object> RemoteExecuteAsync(IRemoteOperationExecutionContext context) { var fullPath = context.ResolvePath(this.FileName); this.LogInformation($"Changing \"{fullPath}\" package version to {AH.CoalesceString(this.NewVersion, "remove pre-release label")}..."); var tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("n")); try { DirectoryEx.Create(tempPath); UniversalPackageMetadata currentMetadata; using (var upack = new UniversalPackage(fullPath)) { currentMetadata = upack.GetFullMetadata(); await upack.ExtractAllItemsAsync(tempPath, context.CancellationToken); FileEx.Delete(PathEx.Combine(tempPath, "upack.json")); } var newMetadata = currentMetadata.Clone(); if (string.IsNullOrEmpty(this.NewVersion)) { newMetadata.Version = new UniversalPackageVersion(currentMetadata.Version.Major, currentMetadata.Version.Minor, currentMetadata.Version.Patch); } else { newMetadata.Version = UniversalPackageVersion.Parse(this.NewVersion); } if (currentMetadata.Version == newMetadata.Version) { this.LogWarning($"Current package version {currentMetadata.Version} and the new version {newMetadata.Version} are the same; nothing to do."); return(null); } this.LogInformation("New version: " + newMetadata.Version); this.LogDebug("Adding repacking entry..."); newMetadata.RepackageHistory.Add( new RepackageHistoryEntry { Id = new UniversalPackageId(currentMetadata.Group, currentMetadata.Name) + ":" + currentMetadata.Version, Date = DateTimeOffset.Now, Using = SDK.ProductName + "/" + SDK.ProductVersion, Reason = this.Reason } ); using (var builder = new UniversalPackageBuilder(fullPath, newMetadata)) { await builder.AddRawContentsAsync(tempPath, string.Empty, true, c => true, context.CancellationToken); } this.LogInformation("Package version changed."); return(null); } finally { try { this.LogDebug($"Deleting temporary files from {tempPath}..."); DirectoryEx.Clear(tempPath); DirectoryEx.Delete(tempPath); } catch (Exception ex) { this.LogWarning("Unable to delete temporary files: " + ex.Message); } } }