Beispiel #1
0
        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!");
        }
Beispiel #3
0
        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;
                }
            }
        }
Beispiel #4
0
        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);
                }
            }
        }