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)); }
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); }
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); }
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; } }
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); }
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); } }