public void GivenManagedInstallItCanInstallManifestVersion()
        {
            var(_, installer, nugetDownloader) = GetTestInstaller(manifestDownload: true);
            var featureBand     = new SdkFeatureBand("6.0.100");
            var manifestId      = new ManifestId("test-manifest-1");
            var manifestVersion = new ManifestVersion("5.0.0");

            var manifestUpdate = new ManifestVersionUpdate(manifestId, null, null, manifestVersion, featureBand.ToString());

            CliTransaction.RunNew(context => installer.InstallWorkloadManifest(manifestUpdate, context));

            var mockNugetInstaller = nugetDownloader as MockNuGetPackageDownloader;

            mockNugetInstaller.DownloadCallParams.Count.Should().Be(1);
            mockNugetInstaller.DownloadCallParams[0].ShouldBeEquivalentTo((new PackageId($"{manifestId}.manifest-{featureBand}"),
                                                                           new NuGetVersion(manifestVersion.ToString()), null as DirectoryPath?, null as PackageSourceLocation));
        }
Beispiel #2
0
        public void GivenWorkloadManifestUpdateItCanCalculateUpdates()
        {
            var testDir                 = _testAssetsManager.CreateTestDirectory().Path;
            var featureBand             = "6.0.100";
            var dotnetRoot              = Path.Combine(testDir, "dotnet");
            var expectedManifestUpdates = new ManifestVersionUpdate[] {
                new ManifestVersionUpdate(new ManifestId("test-manifest-1"), new ManifestVersion("5.0.0"), featureBand, new ManifestVersion("7.0.0"), featureBand),
                new ManifestVersionUpdate(new ManifestId("test-manifest-2"), new ManifestVersion("3.0.0"), featureBand, new ManifestVersion("4.0.0"), featureBand)
            };
            var expectedManifestNotUpdated = new ManifestId[] { new ManifestId("test-manifest-3"), new ManifestId("test-manifest-4") };

            // Write mock manifests
            var installedManifestDir = Path.Combine(testDir, "dotnet", "sdk-manifests", featureBand);
            var adManifestDir        = Path.Combine(testDir, ".dotnet", "sdk-advertising", featureBand);

            Directory.CreateDirectory(installedManifestDir);
            Directory.CreateDirectory(adManifestDir);
            foreach (ManifestVersionUpdate manifestUpdate in expectedManifestUpdates)
            {
                Directory.CreateDirectory(Path.Combine(installedManifestDir, manifestUpdate.ManifestId.ToString()));
                File.WriteAllText(Path.Combine(installedManifestDir, manifestUpdate.ManifestId.ToString(), _manifestFileName), GetManifestContent(manifestUpdate.ExistingVersion));
                Directory.CreateDirectory(Path.Combine(adManifestDir, manifestUpdate.ManifestId.ToString()));
                File.WriteAllText(Path.Combine(adManifestDir, manifestUpdate.ManifestId.ToString(), _manifestFileName), GetManifestContent(manifestUpdate.NewVersion));
            }
            foreach (var manifest in expectedManifestNotUpdated)
            {
                Directory.CreateDirectory(Path.Combine(installedManifestDir, manifest.ToString()));
                File.WriteAllText(Path.Combine(installedManifestDir, manifest.ToString(), _manifestFileName), GetManifestContent(new ManifestVersion("5.0.0")));
                Directory.CreateDirectory(Path.Combine(adManifestDir, manifest.ToString()));
                File.WriteAllText(Path.Combine(adManifestDir, manifest.ToString(), _manifestFileName), GetManifestContent(new ManifestVersion("5.0.0")));
            }

            var manifestDirs = expectedManifestUpdates.Select(manifest => manifest.ManifestId)
                               .Concat(expectedManifestNotUpdated)
                               .Select(manifest => Path.Combine(installedManifestDir, manifest.ToString(), _manifestFileName))
                               .ToArray();
            var workloadManifestProvider = new MockManifestProvider(manifestDirs);
            var nugetDownloader          = new MockNuGetPackageDownloader(dotnetRoot);
            var workloadResolver         = WorkloadResolver.CreateForTests(workloadManifestProvider, dotnetRoot);
            var installationRepo         = new MockInstallationRecordRepository();
            var manifestUpdater          = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, userProfileDir: Path.Combine(testDir, ".dotnet"), testDir, installationRepo);

            var manifestUpdates = manifestUpdater.CalculateManifestUpdates().Select(m => m.manifestUpdate);

            manifestUpdates.Should().BeEquivalentTo(expectedManifestUpdates);
        }
Beispiel #3
0
        public void GivenWorkloadManifestRollbackItCanCalculateUpdates()
        {
            var testDir                 = _testAssetsManager.CreateTestDirectory().Path;
            var currentFeatureBand      = "6.0.100";
            var dotnetRoot              = Path.Combine(testDir, "dotnet");
            var expectedManifestUpdates = new ManifestVersionUpdate[] {
                new ManifestVersionUpdate(new ManifestId("test-manifest-1"), new ManifestVersion("5.0.0"), currentFeatureBand, new ManifestVersion("4.0.0"), currentFeatureBand),
                new ManifestVersionUpdate(new ManifestId("test-manifest-2"), new ManifestVersion("3.0.0"), currentFeatureBand, new ManifestVersion("2.0.0"), currentFeatureBand)
            };

            // Write mock manifests
            var installedManifestDir = Path.Combine(testDir, "dotnet", "sdk-manifests", currentFeatureBand);
            var adManifestDir        = Path.Combine(testDir, ".dotnet", "sdk-advertising", currentFeatureBand);

            Directory.CreateDirectory(installedManifestDir);
            Directory.CreateDirectory(adManifestDir);
            foreach (var manifestUpdate in expectedManifestUpdates)
            {
                Directory.CreateDirectory(Path.Combine(installedManifestDir, manifestUpdate.ManifestId.ToString()));
                File.WriteAllText(Path.Combine(installedManifestDir, manifestUpdate.ManifestId.ToString(), _manifestFileName), GetManifestContent(manifestUpdate.ExistingVersion));
            }

            var rollbackDefContent = JsonSerializer.Serialize(new Dictionary <string, string>()
            {
                { "test-manifest-1", "4.0.0" }, { "test-manifest-2", "2.0.0" }
            });
            var rollbackDefPath = Path.Combine(testDir, "testRollbackDef.txt");

            File.WriteAllText(rollbackDefPath, rollbackDefContent);

            var manifestDirs = expectedManifestUpdates.Select(manifest => manifest.ManifestId)
                               .Select(manifest => Path.Combine(installedManifestDir, manifest.ToString(), _manifestFileName))
                               .ToArray();
            var workloadManifestProvider = new MockManifestProvider(manifestDirs);
            var nugetDownloader          = new MockNuGetPackageDownloader(dotnetRoot);
            var workloadResolver         = WorkloadResolver.CreateForTests(workloadManifestProvider, dotnetRoot);
            var installationRepo         = new MockInstallationRecordRepository();
            var manifestUpdater          = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, testDir, testDir, installationRepo);

            var manifestUpdates = manifestUpdater.CalculateManifestRollbacks(rollbackDefPath);

            manifestUpdates.Should().BeEquivalentTo(expectedManifestUpdates);
        }
Beispiel #4
0
 public void InstallWorkloadManifest(ManifestVersionUpdate manifestUpdate, ITransactionContext transactionContext, DirectoryPath?offlineCache = null, bool isRollback = false)
 {
     try
     {
         transactionContext.Run(
             action: () =>
         {
             InstallWorkloadManifestImplementation(manifestUpdate, offlineCache, isRollback);
         },
             rollback: () =>
         {
             InstallWorkloadManifestImplementation(manifestUpdate.Reverse(), offlineCache: null, isRollback: true);
         });
     }
     catch (Exception e)
     {
         LogException(e);
         throw;
     }
 }
Beispiel #5
0
        void InstallWorkloadManifestImplementation(ManifestVersionUpdate manifestUpdate, DirectoryPath?offlineCache = null, bool isRollback = false)
        {
            ReportPendingReboot();

            // Rolling back a manifest update after a successful install is essentially a downgrade, which is blocked so we have to
            // treat it as a special case and is different from the install failing and rolling that back, though depending where the install
            // failed, it may have removed the old product already.
            Log?.LogMessage($"Installing manifest: Id: {manifestUpdate.ManifestId}, version: {manifestUpdate.NewVersion}, feature band: {manifestUpdate.NewFeatureBand}, rollback: {isRollback}.");

            // Resolve the package ID for the manifest payload package
            string msiPackageId      = WorkloadManifestUpdater.GetManifestPackageId(new SdkFeatureBand(manifestUpdate.NewFeatureBand), manifestUpdate.ManifestId, InstallType.Msi).ToString();
            string msiPackageVersion = $"{manifestUpdate.NewVersion}";

            Log?.LogMessage($"Resolving {manifestUpdate.ManifestId} ({manifestUpdate.NewVersion}) to {msiPackageId} ({msiPackageVersion}).");

            // Retrieve the payload from the MSI package cache.
            MsiPayload msi = GetCachedMsiPayload(msiPackageId, msiPackageVersion, offlineCache);

            VerifyPackage(msi);
            DetectState   state         = DetectPackage(msi.ProductCode, out Version installedVersion);
            InstallAction plannedAction = PlanPackage(msi, state, InstallAction.Install, installedVersion, out IEnumerable <string> relatedProducts);

            // If we've detected a downgrade, it's possible we might be doing a rollback after the manifests were updated,
            // but another error occurred. In this case we need to try and uninstall the upgrade and then install the lower
            // version of the MSI. The downgrade can also be a deliberate rollback.
            if (plannedAction == InstallAction.Downgrade && isRollback && state == DetectState.Absent)
            {
                Log?.LogMessage($"Rolling back manifest update.");

                // The provider keys for manifest packages are stable across feature bands so we retain dependents during upgrades.
                DependencyProvider depProvider = new DependencyProvider(msi.Manifest.ProviderKeyName);

                // Try and remove the SDK dependency, but ignore any remaining dependencies since
                // we want to force the removal of the old version. The remaining dependencies and the provider
                // key won't be removed.
                UpdateDependent(InstallRequestType.RemoveDependent, msi.Manifest.ProviderKeyName, _dependent);

                // Since we don't have records for manifests, we need to try and retrieve the ProductCode of
                // the newer MSI that's installed that we want to remove using its dependency provider.
                string productCode = depProvider.ProductCode;

                if (string.IsNullOrWhiteSpace(productCode))
                {
                    // We don't know the MSI package that wrote this provider key, so if the ProductCode is missing
                    // we can't do anything else.
                    Log?.LogMessage($"Failed to retrieve the ProductCode for provider: {depProvider.ProviderKeyName}.");
                    return;
                }

                Log?.LogMessage($"Found ProductCode {productCode} registered against provider, {depProvider.ProviderKeyName}.");

                // This is a best effort. If for some reason the manifest installers were fixed, for example, manually
                // adding additional upgrade paths to work around previous faulty authoring, we may have multiple related
                // products. The best we can do is to check for at least one match and remove it and then try the rollback.
                if (!relatedProducts.Contains(productCode, StringComparer.OrdinalIgnoreCase))
                {
                    Log?.LogMessage($"Cannot rollback manifest. ProductCode does not match any detected related products.");
                    return;
                }

                string logFile = GetMsiLogName(productCode, InstallAction.Uninstall);
                uint   error   = UninstallMsi(productCode, logFile, ignoreDependencies: true);

                ExitOnError(error, "Failed to uninstall manifest package.");

                // Detect the package again and fall through to the original execution. If that fails, then there's nothing
                // we could have done.
                Log?.LogMessage("Replanning manifest package.");
                state         = DetectPackage(msi, out Version _);
                plannedAction = PlanPackage(msi, state, InstallAction.Install, installedVersion, out IEnumerable <string> _);
            }

            ExecutePackage(msi, plannedAction);

            // Update the reference count against the MSI.
            UpdateDependent(InstallRequestType.AddDependent, msi.Manifest.ProviderKeyName, _dependent);
        }
Beispiel #6
0
        public void InstallWorkloadManifest(ManifestVersionUpdate manifestUpdate, ITransactionContext transactionContext, DirectoryPath?offlineCache = null, bool isRollback = false)
        {
            string packagePath       = null;
            string tempExtractionDir = null;
            string tempBackupDir     = null;
            string rootInstallDir    = WorkloadFileBasedInstall.IsUserLocal(_dotnetDir, _sdkFeatureBand.ToString()) ? _userProfileDir : _dotnetDir;
            var    newManifestPath   = Path.Combine(rootInstallDir, "sdk-manifests", manifestUpdate.NewFeatureBand, manifestUpdate.ManifestId.ToString());

            _reporter.WriteLine(string.Format(LocalizableStrings.InstallingWorkloadManifest, manifestUpdate.ManifestId, manifestUpdate.NewVersion));

            try
            {
                transactionContext.Run(
                    action: () =>
                {
                    var newManifestPackageId = WorkloadManifestUpdater.GetManifestPackageId(new SdkFeatureBand(manifestUpdate.NewFeatureBand), manifestUpdate.ManifestId);
                    if (offlineCache == null || !offlineCache.HasValue)
                    {
                        packagePath = _nugetPackageDownloader.DownloadPackageAsync(newManifestPackageId,
                                                                                   new NuGetVersion(manifestUpdate.NewVersion.ToString()), _packageSourceLocation).GetAwaiter().GetResult();
                    }
                    else
                    {
                        packagePath = Path.Combine(offlineCache.Value.Value, $"{newManifestPackageId}.{manifestUpdate.NewVersion}.nupkg");
                        if (!File.Exists(packagePath))
                        {
                            throw new Exception(string.Format(LocalizableStrings.CacheMissingPackage, newManifestPackageId, manifestUpdate.NewVersion, offlineCache));
                        }
                    }
                    tempExtractionDir = Path.Combine(_tempPackagesDir.Value, $"{newManifestPackageId}-{manifestUpdate.NewVersion}-extracted");
                    Directory.CreateDirectory(tempExtractionDir);
                    var manifestFiles = _nugetPackageDownloader.ExtractPackageAsync(packagePath, new DirectoryPath(tempExtractionDir)).GetAwaiter().GetResult();

                    if (Directory.Exists(newManifestPath) && Directory.GetFileSystemEntries(newManifestPath).Any())
                    {
                        // Backup existing manifest data for roll back purposes
                        tempBackupDir = Path.Combine(_tempPackagesDir.Value, $"{manifestUpdate.ManifestId}-{manifestUpdate.ExistingVersion}-backup");
                        if (Directory.Exists(tempBackupDir))
                        {
                            Directory.Delete(tempBackupDir, true);
                        }
                        FileAccessRetrier.RetryOnMoveAccessFailure(() => DirectoryPath.MoveDirectory(newManifestPath, tempBackupDir));
                    }
                    Directory.CreateDirectory(Path.GetDirectoryName(newManifestPath));
                    FileAccessRetrier.RetryOnMoveAccessFailure(() => DirectoryPath.MoveDirectory(Path.Combine(tempExtractionDir, "data"), newManifestPath));
                },
                    rollback: () =>
                {
                    if (!string.IsNullOrEmpty(tempBackupDir) && Directory.Exists(tempBackupDir))
                    {
                        FileAccessRetrier.RetryOnMoveAccessFailure(() => DirectoryPath.MoveDirectory(tempBackupDir, newManifestPath));
                    }
                },
                    cleanup: () =>
                {
                    // Delete leftover dirs and files
                    if (!string.IsNullOrEmpty(packagePath) && File.Exists(packagePath) && (offlineCache == null || !offlineCache.HasValue))
                    {
                        File.Delete(packagePath);
                    }

                    var versionDir = Path.GetDirectoryName(packagePath);
                    if (Directory.Exists(versionDir) && !Directory.GetFileSystemEntries(versionDir).Any())
                    {
                        Directory.Delete(versionDir);
                        var idDir = Path.GetDirectoryName(versionDir);
                        if (Directory.Exists(idDir) && !Directory.GetFileSystemEntries(idDir).Any())
                        {
                            Directory.Delete(idDir);
                        }
                    }

                    if (!string.IsNullOrEmpty(tempExtractionDir) && Directory.Exists(tempExtractionDir))
                    {
                        Directory.Delete(tempExtractionDir, true);
                    }

                    if (!string.IsNullOrEmpty(tempBackupDir) && Directory.Exists(tempBackupDir))
                    {
                        Directory.Delete(tempBackupDir, true);
                    }
                });
            }
            catch (Exception e)
            {
                throw new Exception(string.Format(LocalizableStrings.FailedToInstallWorkloadManifest, manifestUpdate.ManifestId, manifestUpdate.NewVersion, e.Message), e);
            }
        }