public async Task TestPushPackage_Local_Directory_PackageSource(NuGetProtocolVersion protocolVersion) { await using var testPackageSrcDirectory = new DisposableDirectory(_baseFixture.WorkingDirectory, _snapFilesystem); var packageIdentity = new PackageIdentity("test", NuGetVersion.Parse("1.0.0")); var packageFilenameAbsolute = _snapFilesystem.PathCombine(testPackageSrcDirectory, $"{packageIdentity.Id}.{packageIdentity.Version.ToNormalizedString()}.nupkg"); await using (var packageOutputStream = _snapFilesystem.FileReadWrite(packageFilenameAbsolute)) { await using var nupkgStream = BuildNupkg(packageIdentity); await nupkgStream.CopyToAsync(packageOutputStream); } await using var publishDirectory = new DisposableDirectory(_baseFixture.WorkingDirectory, _snapFilesystem); var packageSource = new PackageSource(publishDirectory, "test", true) { ProtocolVersion = (int)protocolVersion }; var nuGetPackageSources = new NuGetInMemoryPackageSources(publishDirectory, packageSource); await _nugetService.PushAsync(packageFilenameAbsolute, nuGetPackageSources, packageSource, default); var dstFilename = _snapFilesystem.PathCombine(publishDirectory, _snapFilesystem.PathGetFileName(packageFilenameAbsolute)); using var packageArchiveRead = new PackageArchiveReader(dstFilename); Assert.Equal(packageArchiveRead.GetIdentity(), packageIdentity); Assert.Equal(new Uri(publishDirectory), packageSource.SourceUri); }
static Task PushPackageAsync([NotNull] INugetService nugetService, [NotNull] ISnapFilesystem filesystem, [NotNull] IDistributedMutex distributedMutex, [NotNull] INuGetPackageSources nugetSources, [NotNull] PackageSource packageSource, SnapChannel channel, [NotNull] string packageAbsolutePath, CancellationToken cancellationToken, [NotNull] ILog logger) { if (nugetService == null) { throw new ArgumentNullException(nameof(nugetService)); } if (filesystem == null) { throw new ArgumentNullException(nameof(filesystem)); } if (distributedMutex == null) { throw new ArgumentNullException(nameof(distributedMutex)); } if (nugetSources == null) { throw new ArgumentNullException(nameof(nugetSources)); } if (packageSource == null) { throw new ArgumentNullException(nameof(packageSource)); } if (packageAbsolutePath == null) { throw new ArgumentNullException(nameof(packageAbsolutePath)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (!filesystem.FileExists(packageAbsolutePath)) { throw new FileNotFoundException(packageAbsolutePath); } var packageName = filesystem.PathGetFileName(packageAbsolutePath); return(SnapUtility.RetryAsync(async() => { if (!distributedMutex.Acquired) { throw new Exception("Distributed mutex has expired. This is most likely due to intermittent internet connection issues " + "or another user is attempting to publish a new version. Please retry pack operation."); } logger.Info($"Pushing {packageName} to channel {channel.Name} using package source {packageSource.Name}"); var pushStopwatch = new Stopwatch(); pushStopwatch.Restart(); await nugetService.PushAsync(packageAbsolutePath, nugetSources, packageSource, null, cancellationToken: cancellationToken); logger.Info($"Pushed {packageName} to channel {channel.Name} using package source {packageSource.Name} in {pushStopwatch.Elapsed.TotalSeconds:0.0}s."); })); }
static async Task <int> CommandPackAsync([NotNull] PackOptions packOptions, [NotNull] ISnapFilesystem filesystem, [NotNull] ISnapAppReader snapAppReader, [NotNull] ISnapAppWriter snapAppWriter, [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] ISnapPack snapPack, [NotNull] INugetService nugetService, [NotNull] ISnapOs snapOs, [NotNull] ISnapxEmbeddedResources snapxEmbeddedResources, [NotNull] ISnapExtractor snapExtractor, [NotNull] ISnapPackageManager snapPackageManager, [NotNull] ICoreRunLib coreRunLib, [NotNull] ISnapNetworkTimeProvider snapNetworkTimeProvider, [NotNull] ILog logger, [NotNull] IDistributedMutexClient distributedMutexClient, [NotNull] string workingDirectory, CancellationToken cancellationToken) { if (packOptions == null) { throw new ArgumentNullException(nameof(packOptions)); } if (filesystem == null) { throw new ArgumentNullException(nameof(filesystem)); } if (snapAppReader == null) { throw new ArgumentNullException(nameof(snapAppReader)); } if (snapAppWriter == null) { throw new ArgumentNullException(nameof(snapAppWriter)); } if (nuGetPackageSources == null) { throw new ArgumentNullException(nameof(nuGetPackageSources)); } if (snapPack == null) { throw new ArgumentNullException(nameof(snapPack)); } if (nugetService == null) { throw new ArgumentNullException(nameof(nugetService)); } if (snapOs == null) { throw new ArgumentNullException(nameof(snapOs)); } if (snapxEmbeddedResources == null) { throw new ArgumentNullException(nameof(snapxEmbeddedResources)); } if (snapExtractor == null) { throw new ArgumentNullException(nameof(snapExtractor)); } if (snapPackageManager == null) { throw new ArgumentNullException(nameof(snapPackageManager)); } if (coreRunLib == null) { throw new ArgumentNullException(nameof(coreRunLib)); } if (snapNetworkTimeProvider == null) { throw new ArgumentNullException(nameof(snapNetworkTimeProvider)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (distributedMutexClient == null) { throw new ArgumentNullException(nameof(distributedMutexClient)); } if (workingDirectory == null) { throw new ArgumentNullException(nameof(workingDirectory)); } var stopwatch = new Stopwatch(); stopwatch.Restart(); var(snapApps, snapApp, error, snapsManifestAbsoluteFilename) = BuildSnapAppFromDirectory(filesystem, snapAppReader, nuGetPackageSources, packOptions.Id, packOptions.Rid, workingDirectory); if (snapApp == null) { if (!error) { logger.Error($"Snap with id {packOptions.Id} was not found in manifest: {snapsManifestAbsoluteFilename}"); } return(1); } if (!SemanticVersion.TryParse(packOptions.Version, out var semanticVersion)) { logger.Error($"Unable to parse semantic version (v2): {packOptions.Version}"); return(1); } snapApp.Version = semanticVersion; if (packOptions.ReleasesNotes != null) { snapApp.ReleaseNotes = packOptions.ReleasesNotes; } var artifactsDirectory = BuildArtifactsDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); var installersDirectory = BuildInstallersDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); var packagesDirectory = BuildPackagesDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); filesystem.DirectoryCreateIfNotExists(installersDirectory); filesystem.DirectoryCreateIfNotExists(packagesDirectory); var snapAppChannel = snapApp.GetDefaultChannelOrThrow(); MaybeOverrideLockToken(snapApps, logger, packOptions.Id, packOptions.LockToken); if (string.IsNullOrWhiteSpace(snapApps.Generic.Token)) { logger.Error("Please specify a token in your snapx.yml file. A random UUID is sufficient."); return(1); } await using var distributedMutex = WithDistributedMutex(distributedMutexClient, logger, snapApps.BuildLockKey(snapApp), cancellationToken); logger.Info($"Schema version: {snapApps.Schema}"); logger.Info($"Packages directory: {packagesDirectory}"); logger.Info($"Artifacts directory: {artifactsDirectory}"); logger.Info($"Installers directory: {installersDirectory}"); logger.Info($"Pack strategy: {snapApps.Generic.PackStrategy}"); logger.Info('-'.Repeat(TerminalBufferWidth)); logger.Info($"Id: {snapApp.Id}"); logger.Info($"Version: {snapApp.Version}"); logger.Info($"Channel: {snapAppChannel.Name}"); logger.Info($"Rid: {snapApp.Target.Rid}"); logger.Info($"OS: {snapApp.Target.Os.ToString().ToLowerInvariant()}"); var installersStr = !snapApp.Target.Installers.Any() ? "None" : string.Join(", ", snapApp.Target.Installers); logger.Info($"Installers: {installersStr}"); var shortcutsStr = !snapApp.Target.Shortcuts.Any() ? "None" : string.Join(", ", snapApp.Target.Shortcuts); logger.Info($"Shortcuts: {shortcutsStr}"); logger.Info('-'.Repeat(TerminalBufferWidth)); var tryAcquireRetries = packOptions.LockRetries == -1 ? int.MaxValue : packOptions.LockRetries; if (!await distributedMutex.TryAquireAsync(TimeSpan.FromSeconds(15), tryAcquireRetries)) { logger.Info('-'.Repeat(TerminalBufferWidth)); return(1); } logger.Info('-'.Repeat(TerminalBufferWidth)); var updateFeedPackageSource = await snapPackageManager.GetPackageSourceAsync(snapApp); logger.Info("Downloading releases nupkg."); var snapReleasesPackageDirectory = filesystem.DirectoryGetParent(packagesDirectory); filesystem.DirectoryCreateIfNotExists(snapReleasesPackageDirectory); var(snapAppsReleases, _, currentReleasesMemoryStream) = await snapPackageManager.GetSnapsReleasesAsync(snapApp, logger, cancellationToken); if (currentReleasesMemoryStream != null) { await currentReleasesMemoryStream.DisposeAsync(); } if (snapAppsReleases == null) { if (!logger.Prompt("y|yes", "Unable to find a previous release in any of your NuGet package sources. " + "Is this the first time you are publishing this application? " + "NB! The package may not yet be visible to due to upstream caching. [y/n]", infoOnly: packOptions.YesToAllPrompts) ) { return(1); } snapAppsReleases = new SnapAppsReleases(); } else { logger.Info($"Downloaded releases nupkg. Current version: {snapAppsReleases.Version}."); if (packOptions.Gc) { var releasesRemoved = snapAppsReleases.Gc(snapApp); logger.Info($"Garbage collected (removed) {releasesRemoved} releases."); } var snapAppChannelReleases = snapAppsReleases.GetReleases(snapApp, snapAppChannel); var restoreSummary = await snapPackageManager.RestoreAsync(packagesDirectory, snapAppChannelReleases, updateFeedPackageSource, SnapPackageManagerRestoreType.Pack, logger : logger, cancellationToken : cancellationToken); if (!restoreSummary.Success) { return(1); } if (snapAppChannelReleases.Any(x => x.Version >= snapApp.Version)) { logger.Error($"Version {snapApp.Version} is already published to feed: {updateFeedPackageSource.Name}."); return(1); } } var snapPackageDetails = new SnapPackageDetails { SnapApp = snapApp, NuspecBaseDirectory = artifactsDirectory, PackagesDirectory = packagesDirectory, SnapAppsReleases = snapAppsReleases }; logger.Info('-'.Repeat(TerminalBufferWidth)); logger.Info($"Building nupkg: {snapApp.Version}."); var pushPackages = new List <string>(); var(fullNupkgMemoryStream, fullSnapApp, fullSnapRelease, deltaNupkgMemorystream, deltaSnapApp, deltaSnapRelease) = await snapPack.BuildPackageAsync(snapPackageDetails, coreRunLib, cancellationToken); var fullNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, fullSnapRelease.Filename); await using (fullNupkgMemoryStream) await using (deltaNupkgMemorystream) { logger.Info($"Writing full nupkg to disk: {fullSnapRelease.Filename}. File size: {fullSnapRelease.FullFilesize.BytesAsHumanReadable()}"); await filesystem.FileWriteAsync(fullNupkgMemoryStream, fullNupkgAbsolutePath, default); if (!fullSnapRelease.IsGenesis) { var deltaNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, deltaSnapRelease.Filename); logger.Info( $"Writing delta nupkg to disk: {deltaSnapRelease.Filename}. File size: {deltaSnapRelease.DeltaFilesize.BytesAsHumanReadable()}"); await filesystem.FileWriteAsync(deltaNupkgMemorystream, deltaNupkgAbsolutePath, default); } } var fullOrDeltaSnapApp = deltaSnapApp ?? fullSnapApp; var fullOrDeltaNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, fullOrDeltaSnapApp.BuildNugetFilename()); pushPackages.Add(fullOrDeltaNupkgAbsolutePath); logger.Info('-'.Repeat(TerminalBufferWidth)); logger.Info($"Retrieving network time from: {snapNetworkTimeProvider}."); var nowUtc = await SnapUtility.RetryAsync(async() => await snapNetworkTimeProvider.NowUtcAsync(), 3); if (!nowUtc.HasValue) { logger.Error($"Unknown error retrieving network time from: {snapNetworkTimeProvider}"); return(1); } var localTimeStr = TimeZoneInfo .ConvertTimeFromUtc(nowUtc.Value, TimeZoneInfo.Local) .ToString("F", CultureInfo.CurrentCulture); logger.Info($"Successfully retrieved network time. Time is now: {localTimeStr}"); logger.Info('-'.Repeat(TerminalBufferWidth)); fullSnapRelease.CreatedDateUtc = nowUtc.Value; if (deltaSnapRelease != null) { deltaSnapRelease.CreatedDateUtc = nowUtc.Value; } snapAppsReleases.LastWriteAccessUtc = nowUtc.Value; int?forcedDbVersion = null; if (packOptions.DbVersion > 0) { if (packOptions.DbVersion <= snapAppsReleases.DbVersion) { logger.Error($"Unable to force database version because version is less than or equal to current database version. \n" + $"Forced version: {packOptions.DbVersion}.\n" + $"Current database version: {snapAppsReleases.DbVersion}."); return(1); } forcedDbVersion = packOptions.DbVersion; logger.Info($"Database version is forced because of '--db-version' option. Initial database version: {forcedDbVersion}."); } else if (fullOrDeltaSnapApp.IsGenesis && fullOrDeltaSnapApp.Version.Major > snapAppsReleases.Version.Major) { forcedDbVersion = fullOrDeltaSnapApp.Version.Major; logger.Info($"Database version is forced because genesis nupkg detected. Initial database version: {forcedDbVersion}"); } logger.Info($"Building releases nupkg. Current database version: {snapAppsReleases.Version}."); var releasesMemoryStream = snapPack.BuildReleasesPackage(fullOrDeltaSnapApp, snapAppsReleases, forcedDbVersion); var releasesNupkgAbsolutePath = snapOs.Filesystem.PathCombine(snapReleasesPackageDirectory, fullOrDeltaSnapApp.BuildNugetReleasesFilename()); var releasesNupkgFilename = filesystem.PathGetFileName(releasesNupkgAbsolutePath); await snapOs.Filesystem.FileWriteAsync(releasesMemoryStream, releasesNupkgAbsolutePath, cancellationToken); pushPackages.Add(releasesNupkgAbsolutePath); logger.Info("Finished building releases nupkg.\n" + $"Filename: {releasesNupkgFilename}.\n" + $"Size: {releasesMemoryStream.Length.BytesAsHumanReadable()}.\n" + $"New database version: {snapAppsReleases.Version}.\n" + $"Pack id: {snapAppsReleases.PackId:N}."); logger.Info('-'.Repeat(TerminalBufferWidth)); await using (releasesMemoryStream) { if (!packOptions.SkipInstallers && fullOrDeltaSnapApp.Target.Installers.Any()) { var channels = fullOrDeltaSnapApp.IsGenesis ? fullOrDeltaSnapApp.Channels : new List <SnapChannel> { snapAppChannel }; foreach (var channel in channels) { var snapAppInstaller = new SnapApp(fullOrDeltaSnapApp); snapAppInstaller.SetCurrentChannel(channel.Name); if (fullOrDeltaSnapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Offline))) { logger.Info('-'.Repeat(TerminalBufferWidth)); var(installerOfflineSuccess, canContinueIfError, installerOfflineExeAbsolutePath) = await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, installersDirectory, fullNupkgAbsolutePath, releasesNupkgAbsolutePath, true, cancellationToken); if (!installerOfflineSuccess) { if (!canContinueIfError || !logger.Prompt("y|yes", "Installer was not built. Do you still want to continue? (y|n)", infoOnly: packOptions.YesToAllPrompts)) { logger.Info('-'.Repeat(TerminalBufferWidth)); logger.Error("Unknown error building offline installer."); return(1); } } else { var installerOfflineExeStat = snapOs.Filesystem.FileStat(installerOfflineExeAbsolutePath); logger.Info($"Successfully built offline installer. File size: {installerOfflineExeStat.Length.BytesAsHumanReadable()}."); } } if (fullOrDeltaSnapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Web))) { logger.Info('-'.Repeat(TerminalBufferWidth)); var(installerWebSuccess, canContinueIfError, installerWebExeAbsolutePath) = await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, installersDirectory, null, releasesNupkgAbsolutePath, false, cancellationToken); if (!installerWebSuccess) { if (!canContinueIfError || !logger.Prompt("y|yes", "Installer was not built. Do you still want to continue? (y|n)", infoOnly: packOptions.YesToAllPrompts)) { logger.Info('-'.Repeat(TerminalBufferWidth)); logger.Error("Unknown error building offline installer."); return(1); } } else { var installerWebExeStat = snapOs.Filesystem.FileStat(installerWebExeAbsolutePath); logger.Info($"Successfully built web installer. File size: {installerWebExeStat.Length.BytesAsHumanReadable()}."); } } } } } if (snapApps.Generic.PackStrategy == SnapAppsPackStrategy.push) { await PushPackagesAsync(packOptions, logger, filesystem, nugetService, snapPackageManager, distributedMutex, snapAppsReleases, fullOrDeltaSnapApp, snapAppChannel, pushPackages, cancellationToken); } logger.Info($"Fetching releases overview from feed {updateFeedPackageSource.Name}."); await CommandListAsync(new ListOptions { Id = fullOrDeltaSnapApp.Id }, filesystem, snapAppReader, nuGetPackageSources, nugetService, snapExtractor, snapPackageManager, logger, workingDirectory, cancellationToken); logger.Info('-'.Repeat(TerminalBufferWidth)); logger.Info($"Pack completed in {stopwatch.Elapsed.TotalSeconds:F1}s."); return(0); }
static async Task <int> CommandDemoteAsync([NotNull] DemoteOptions options, [NotNull] ISnapFilesystem filesystem, [NotNull] ISnapAppReader snapAppReader, [NotNull] ISnapAppWriter snapAppWriter, [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] INugetService nugetService, [NotNull] IDistributedMutexClient distributedMutexClient, [NotNull] ISnapPackageManager snapPackageManager, [NotNull] ISnapPack snapPack, [NotNull] ISnapNetworkTimeProvider snapNetworkTimeProvider, [NotNull] ISnapExtractor snapExtractor, [NotNull] ISnapOs snapOs, [NotNull] ISnapxEmbeddedResources snapxEmbeddedResources, [NotNull] ICoreRunLib coreRunLib, [NotNull] ILog logger, [NotNull] string workingDirectory, CancellationToken cancellationToken) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (filesystem == null) { throw new ArgumentNullException(nameof(filesystem)); } if (snapAppReader == null) { throw new ArgumentNullException(nameof(snapAppReader)); } if (snapAppWriter == null) { throw new ArgumentNullException(nameof(snapAppWriter)); } if (nuGetPackageSources == null) { throw new ArgumentNullException(nameof(nuGetPackageSources)); } if (nugetService == null) { throw new ArgumentNullException(nameof(nugetService)); } if (distributedMutexClient == null) { throw new ArgumentNullException(nameof(distributedMutexClient)); } if (snapPackageManager == null) { throw new ArgumentNullException(nameof(snapPackageManager)); } if (snapPack == null) { throw new ArgumentNullException(nameof(snapPack)); } if (snapNetworkTimeProvider == null) { throw new ArgumentNullException(nameof(snapNetworkTimeProvider)); } if (snapExtractor == null) { throw new ArgumentNullException(nameof(snapExtractor)); } if (snapOs == null) { throw new ArgumentNullException(nameof(snapOs)); } if (snapxEmbeddedResources == null) { throw new ArgumentNullException(nameof(snapxEmbeddedResources)); } if (coreRunLib == null) { throw new ArgumentNullException(nameof(coreRunLib)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (workingDirectory == null) { throw new ArgumentNullException(nameof(workingDirectory)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } if (nugetService == null) { throw new ArgumentNullException(nameof(nugetService)); } var stopwatch = new Stopwatch(); stopwatch.Restart(); var anyRid = options.Rid == null; var anyVersion = options.FromVersion == null; SnapApp anyRidSnapApp = null; SemanticVersion fromVersion = null; var runtimeIdentifiers = new List <string>(); if (!anyVersion) { if (!SemanticVersion.TryParse(options.FromVersion, out fromVersion)) { Console.WriteLine($"Unable to parse from version: {options.FromVersion}"); return(1); } if (!options.RemoveAll) { Console.WriteLine("You must specify --remove-all if you want to demote releases newer than --from-version."); return(1); } } var snapApps = BuildSnapAppsFromDirectory(filesystem, snapAppReader, workingDirectory); if (!snapApps.Apps.Any()) { return(1); } foreach (var snapsApp in snapApps.Apps) { foreach (var target in snapsApp.Targets.Where(target => anyRid || string.Equals(options.Rid, target.Rid, StringComparison.OrdinalIgnoreCase))) { anyRidSnapApp = snapApps.BuildSnapApp(snapsApp.Id, target.Rid, nuGetPackageSources, filesystem); runtimeIdentifiers.AddRange(snapApps.GetRids(anyRidSnapApp)); break; } } if (anyRidSnapApp == null) { if (anyRid) { logger.Error($"Unable to find application with id: {options.Id}."); return(1); } logger.Error($"Unable to find application with id: {options.Id}. Rid: {options.Rid}"); return(1); } logger.Info('-'.Repeat(TerminalBufferWidth)); Console.WriteLine($"Demoting application with id: {anyRidSnapApp.Id}."); Console.WriteLine($"Runtime identifiers (RID): {string.Join(", ", runtimeIdentifiers)}"); if (anyRid) { if (!logger.Prompt("y|yes", "You have not specified a rid, all releases in listed runtime identifiers will be removed. " + "Do you want to continue? [y|n]") ) { return(1); } } MaybeOverrideLockToken(snapApps, logger, options.Id, options.LockToken); if (string.IsNullOrWhiteSpace(snapApps.Generic.Token)) { logger.Error("Please specify a token in your snapx.yml file. A random UUID is sufficient."); return(1); } logger.Info('-'.Repeat(TerminalBufferWidth)); var packagesDirectory = BuildPackagesDirectory(filesystem, workingDirectory); filesystem.DirectoryCreateIfNotExists(packagesDirectory); await using var distributedMutex = WithDistributedMutex(distributedMutexClient, logger, snapApps.BuildLockKey(anyRidSnapApp), cancellationToken); var tryAcquireRetries = options.LockRetries == -1 ? int.MaxValue : options.LockRetries; if (!await distributedMutex.TryAquireAsync(TimeSpan.FromSeconds(15), tryAcquireRetries)) { logger.Info('-'.Repeat(TerminalBufferWidth)); return(1); } logger.Info('-'.Repeat(TerminalBufferWidth)); logger.Info("Downloading releases nupkg."); var(snapAppsReleases, _, currentReleasesMemoryStream) = await snapPackageManager .GetSnapsReleasesAsync(anyRidSnapApp, logger, cancellationToken); if (currentReleasesMemoryStream != null) { await currentReleasesMemoryStream.DisposeAsync(); } if (snapAppsReleases == null) { return(1); } if (!snapAppsReleases.Any()) { logger.Error($"Releases nupkg does not contain application id: {anyRidSnapApp.Id}"); return(1); } logger.Info($"Downloaded releases nupkg. Current version: {snapAppsReleases.Version}."); var snapAppReleases = options.RemoveAll ? snapAppsReleases.GetReleases(anyRidSnapApp, x => { bool VersionFilter() { return(anyVersion || x.Version > fromVersion); } bool RidFilter() { return(anyRid || x.Target.Rid == anyRidSnapApp.Target.Rid); } return(RidFilter() && VersionFilter()); }) : snapAppsReleases.GetMostRecentReleases(anyRidSnapApp, x => anyRid || x.Target.Rid == anyRidSnapApp.Target.Rid); if (!snapAppReleases.Any()) { logger.Error("Unable to find any releases that matches demotion criterias."); return(1); } logger.Info('-'.Repeat(TerminalBufferWidth)); var consoleTable = new ConsoleTable("Rid", "Channels", "Version", "Count") { Header = $"Demote summary overview. Total releases: {snapAppReleases.Count()}." }; foreach (var(rid, releases) in snapAppReleases.ToDictionaryByKey(x => x.Target.Rid)) { var channels = releases.SelectMany(x => x.Channels).Distinct().ToList(); var releaseVersion = options.RemoveAll ? "All versions" : releases.First().Version.ToString(); consoleTable.AddRow(new object[] { rid, string.Join(", ", channels), releaseVersion, releases.Count.ToString() }); } consoleTable.Write(logger); if (!logger.Prompt("y|yes", "Ready to demote releases. Do you want to continue? [y|n]")) { return(1); } logger.Info('-'.Repeat(TerminalBufferWidth)); logger.Info($"Retrieving network time from: {snapNetworkTimeProvider}."); var nowUtc = await SnapUtility.RetryAsync(async() => await snapNetworkTimeProvider.NowUtcAsync(), 3); if (!nowUtc.HasValue) { logger.Error($"Unknown error retrieving network time from: {snapNetworkTimeProvider}"); return(1); } var localTimeStr = TimeZoneInfo .ConvertTimeFromUtc(nowUtc.Value, TimeZoneInfo.Local) .ToString("F", CultureInfo.CurrentCulture); logger.Info($"Successfully retrieved network time. Time is now: {localTimeStr}"); logger.Info('-'.Repeat(TerminalBufferWidth)); var snapAppsReleasesDemotedCount = snapAppsReleases.Demote(snapAppReleases); if (snapAppsReleasesDemotedCount != snapAppReleases.Count()) { logger.Error("Unknown error when removing demoted releases. " + $"Expected to remove {snapAppReleases.Count()} but only {snapAppsReleasesDemotedCount} was removed."); return(1); } logger.Info("Building releases nupkg. " + $"Current database version: {snapAppsReleases.Version}. " + $"Releases count: {snapAppsReleases.Count()}."); var releasesMemoryStream = !snapAppsReleases.Any() ? snapPack.BuildEmptyReleasesPackage(anyRidSnapApp, snapAppsReleases) : snapPack.BuildReleasesPackage(anyRidSnapApp, snapAppsReleases); var releasesNupkgAbsolutePath = snapOs.Filesystem.PathCombine(packagesDirectory, anyRidSnapApp.BuildNugetReleasesFilename()); var releasesNupkgFilename = filesystem.PathGetFileName(releasesNupkgAbsolutePath); await snapOs.Filesystem.FileWriteAsync(releasesMemoryStream, releasesNupkgAbsolutePath, cancellationToken); logger.Info("Finished building releases nupkg.\n" + $"Filename: {releasesNupkgFilename}.\n" + $"Size: {releasesMemoryStream.Length.BytesAsHumanReadable()}.\n" + $"New database version: {snapAppsReleases.Version}.\n" + $"Pack id: {snapAppsReleases.PackId:N}."); logger.Info('-'.Repeat(TerminalBufferWidth)); var anySnapTargetDefaultChannel = anyRidSnapApp.Channels.First(); var nugetSources = anyRidSnapApp.BuildNugetSources(filesystem.PathGetTempPath()); var packageSource = nugetSources.Items.Single(x => x.Name == anySnapTargetDefaultChannel.PushFeed.Name); await PushPackageAsync(nugetService, filesystem, distributedMutex, nuGetPackageSources, packageSource, anySnapTargetDefaultChannel, releasesNupkgAbsolutePath, cancellationToken, logger); await BlockUntilSnapUpdatedReleasesNupkgAsync(logger, snapPackageManager, snapAppsReleases, anyRidSnapApp, anySnapTargetDefaultChannel, TimeSpan.FromSeconds(15), cancellationToken); logger.Info('-'.Repeat(TerminalBufferWidth)); await CommandRestoreAsync(new RestoreOptions { Id = anyRidSnapApp.Id, Rid = anyRid ? null : anyRidSnapApp.Target.Rid, BuildInstallers = true }, filesystem, snapAppReader, snapAppWriter, nuGetPackageSources, snapPackageManager, snapOs, snapxEmbeddedResources, coreRunLib, snapPack, logger, workingDirectory, cancellationToken); logger.Info('-'.Repeat(TerminalBufferWidth)); logger.Info($"Fetching releases overview from feed: {anySnapTargetDefaultChannel.PushFeed.Name}"); await CommandListAsync(new ListOptions { Id = anyRidSnapApp.Id }, filesystem, snapAppReader, nuGetPackageSources, nugetService, snapExtractor, logger, workingDirectory, cancellationToken); logger.Info('-'.Repeat(TerminalBufferWidth)); logger.Info($"Demote completed in {stopwatch.Elapsed.TotalSeconds:F1}s."); return(0); }
static int Install([NotNull] ISnapInstallerEnvironment environment, [NotNull] ISnapInstallerEmbeddedResources snapInstallerEmbeddedResources, [NotNull] ISnapInstaller snapInstaller, [NotNull] ISnapFilesystem snapFilesystem, [NotNull] ISnapPack snapPack, [NotNull] ISnapOs snapOs, [NotNull] CoreRunLib coreRunLib, [NotNull] ISnapAppReader snapAppReader, [NotNull] ISnapAppWriter snapAppWriter, [NotNull] INugetService nugetService, [NotNull] ISnapPackageManager snapPackageManager, [NotNull] ISnapExtractor snapExtractor, [NotNull] ILog diskLogger, bool headless) { if (environment == null) { throw new ArgumentNullException(nameof(environment)); } if (snapInstallerEmbeddedResources == null) { throw new ArgumentNullException(nameof(snapInstallerEmbeddedResources)); } if (snapInstaller == null) { throw new ArgumentNullException(nameof(snapInstaller)); } if (snapFilesystem == null) { throw new ArgumentNullException(nameof(snapFilesystem)); } if (snapPack == null) { throw new ArgumentNullException(nameof(snapPack)); } if (snapOs == null) { throw new ArgumentNullException(nameof(snapOs)); } if (coreRunLib == null) { throw new ArgumentNullException(nameof(coreRunLib)); } if (snapAppReader == null) { throw new ArgumentNullException(nameof(snapAppReader)); } if (snapAppWriter == null) { throw new ArgumentNullException(nameof(snapAppWriter)); } if (nugetService == null) { throw new ArgumentNullException(nameof(nugetService)); } if (snapPackageManager == null) { throw new ArgumentNullException(nameof(snapPackageManager)); } if (snapExtractor == null) { throw new ArgumentNullException(nameof(snapExtractor)); } if (diskLogger == null) { throw new ArgumentNullException(nameof(diskLogger)); } // NB! All filesystem operations has to be readonly until check that verifies // current user is not elevated to root has run. var cancellationToken = environment.CancellationToken; var installerProgressSource = new SnapProgressSource(); var onFirstAnimationRenderedEvent = new ManualResetEventSlim(false); var exitCode = 1; // ReSharper disable once ImplicitlyCapturedClosure async Task InstallInBackgroundAsync(IMainWindowViewModel mainWindowViewModel) { if (mainWindowViewModel == null) { throw new ArgumentNullException(nameof(mainWindowViewModel)); } if (mainWindowViewModel.Headless) { diskLogger.Info("Headless install."); onFirstAnimationRenderedEvent.Dispose(); } else { diskLogger.Info("Waiting for main window to become visible."); onFirstAnimationRenderedEvent.Wait(cancellationToken); onFirstAnimationRenderedEvent.Dispose(); diskLogger.Info("Main window should now be visible."); } var mainWindowLogger = new LogForwarder(LogLevel.Info, diskLogger, (level, func, exception, parameters) => { var message = func?.Invoke(); if (message == null) { return; } SetStatusText(mainWindowViewModel, message); }); if (coreRunLib.IsElevated()) { var rootUserText = snapOs.OsPlatform == OSPlatform.Windows ? "Administrator" : "root"; mainWindowLogger.Error($"Error! Installer cannot run in an elevated user context: {rootUserText}"); goto done; } diskLogger.Debug($"{nameof(environment.Io.WorkingDirectory)}: {environment.Io.WorkingDirectory}"); diskLogger.Debug($"{nameof(environment.Io.ThisExeWorkingDirectory)}: {environment.Io.ThisExeWorkingDirectory}"); var snapAppDllAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, SnapConstants.SnapAppDllFilename); diskLogger.Debug($"{nameof(snapAppDllAbsolutePath)}: {snapAppDllAbsolutePath}."); if (!snapFilesystem.FileExists(snapAppDllAbsolutePath)) { mainWindowLogger.Info($"Unable to find: {snapFilesystem.PathGetFileName(snapAppDllAbsolutePath)}"); goto done; } SnapApp snapApp; SnapChannel snapChannel; try { snapApp = environment.Io.ThisExeWorkingDirectory.GetSnapAppFromDirectory(snapFilesystem, snapAppReader); snapChannel = snapApp.GetCurrentChannelOrThrow(); } catch (Exception ex) { mainWindowLogger.ErrorException($"Error reading {SnapConstants.SnapAppDllFilename}", ex); goto done; } var nupkgAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, "Setup.nupkg"); var nupkgReleasesAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, snapApp.BuildNugetReleasesFilename()); var offlineInstaller = false; using (var webInstallerDir = new DisposableDirectory(snapOs.SpecialFolders.NugetCacheDirectory, snapFilesystem)) { ISnapAppChannelReleases snapAppChannelReleases; SnapRelease snapReleaseToInstall; // Offline installer if (snapFilesystem.FileExists(nupkgAbsolutePath)) { mainWindowLogger.Info("Offline installer is loading releases nupkg"); try { var releasesFileStream = snapFilesystem.FileRead(nupkgReleasesAbsolutePath); using var packageArchiveReader = new PackageArchiveReader(releasesFileStream); var snapAppsReleases = await snapExtractor.GetSnapAppsReleasesAsync(packageArchiveReader, snapAppReader, cancellationToken); snapAppChannelReleases = snapAppsReleases.GetReleases(snapApp, snapChannel); var isGenesis = !snapAppChannelReleases.HasDeltaReleases(); snapReleaseToInstall = snapAppChannelReleases.GetMostRecentRelease().AsFullRelease(isGenesis); } catch (Exception e) { mainWindowLogger.ErrorException($"Error reading {nupkgAbsolutePath}", e); goto done; } offlineInstaller = true; } // Web installer else if (snapFilesystem.FileExists(snapAppDllAbsolutePath)) { mainWindowLogger.Info("Web installer is downloading releases nupkg"); try { var(snapAppsReleases, packageSource, releasesMemoryStream) = await snapPackageManager.GetSnapsReleasesAsync(snapApp, mainWindowLogger, cancellationToken); releasesMemoryStream?.Dispose(); if (snapAppsReleases == null) { mainWindowLogger.Error("Failed to download releases nupkg. Try rerunning the installer."); goto done; } var snapAppsReleasesBytes = snapAppWriter.ToSnapAppsReleases(snapAppsReleases); await snapFilesystem.FileWriteAsync(snapAppsReleasesBytes, nupkgReleasesAbsolutePath, cancellationToken); snapAppChannelReleases = snapAppsReleases.GetReleases(snapApp, snapApp.GetCurrentChannelOrThrow()); if (!snapAppChannelReleases.Any()) { mainWindowLogger.Error($"Unable to find any releases in channel: {snapAppChannelReleases.Channel.Name}."); goto done; } var isGenesis = snapAppChannelReleases.Count() == 1; snapReleaseToInstall = snapAppChannelReleases.GetMostRecentRelease().AsFullRelease(isGenesis); snapApp.Version = snapReleaseToInstall.Version; mainWindowLogger.Info($"Current version: {snapApp.Version}. Channel: {snapAppChannelReleases.Channel.Name}."); // ReSharper disable once MethodSupportsCancellation await Task.Delay(TimeSpan.FromSeconds(3)); mainWindowLogger.Info("Downloading required assets"); void UpdateProgress(string type, int totalPercentage, long releasesChecksummed = 0, long releasesToChecksum = 0, long releasesDownloaded = 0, long releasesToDownload = 0, long filesRestored = 0, long filesToRestore = 0, long totalBytesDownloaded = 0, long totalBytesToDownload = 0) { void SetProgressText(long current, long total, string defaultText, string pluralText) { var outputText = total > 1 ? pluralText : defaultText; switch (type) { case "Download": if (total > 1) { SetStatusText(mainWindowViewModel, $"{outputText} ({totalPercentage}%): {current} of {total}. " + $"Downloaded so far: {totalBytesDownloaded.BytesAsHumanReadable()}. " + $"Total: {totalBytesToDownload.BytesAsHumanReadable()}"); goto incrementProgress; } SetStatusText(mainWindowViewModel, $"{outputText} ({totalPercentage}%): " + $"Downloaded so far: {totalBytesDownloaded.BytesAsHumanReadable()}. " + $"Total: {totalBytesToDownload.BytesAsHumanReadable()}"); goto incrementProgress; default: if (total > 1) { SetStatusText(mainWindowViewModel, $"{outputText} ({totalPercentage}%): {current} of {total}."); goto incrementProgress; } SetStatusText(mainWindowViewModel, $"{outputText}: {totalPercentage}%"); goto incrementProgress; } incrementProgress: installerProgressSource.Raise(totalPercentage); } switch (type) { case "Checksum": SetProgressText(releasesChecksummed, releasesToChecksum, "Validating payload", "Validating payloads"); break; case "Download": SetProgressText(releasesDownloaded, releasesToDownload, "Downloading payload", "Downloading payloads"); break; case "Restore": SetProgressText(filesRestored, filesToRestore, "Restoring file", "Restoring files"); break; default: diskLogger.Warn($"Unknown progress type: {type}"); break; } } var snapPackageManagerProgressSource = new SnapPackageManagerProgressSource { ChecksumProgress = x => UpdateProgress("Checksum", x.progressPercentage, x.releasesChecksummed, x.releasesToChecksum), DownloadProgress = x => UpdateProgress("Download", x.progressPercentage, releasesDownloaded: x.releasesDownloaded, releasesToDownload: x.releasesToDownload, totalBytesDownloaded: x.totalBytesDownloaded, totalBytesToDownload: x.totalBytesToDownload), RestoreProgress = x => UpdateProgress("Restore", x.progressPercentage, filesRestored: x.filesRestored, filesToRestore: x.filesToRestore) }; var restoreSummary = await snapPackageManager.RestoreAsync(webInstallerDir.WorkingDirectory, snapAppChannelReleases, packageSource, SnapPackageManagerRestoreType.Default, snapPackageManagerProgressSource, diskLogger, cancellationToken); if (!restoreSummary.Success) { mainWindowLogger.Info("Unknown error while restoring assets."); goto done; } mainWindowLogger.Info("Preparing to install payload"); var setupNupkgAbsolutePath = snapOs.Filesystem.PathCombine(webInstallerDir.WorkingDirectory, snapReleaseToInstall.Filename); if (!snapFilesystem.FileExists(setupNupkgAbsolutePath)) { mainWindowLogger.Error($"Payload does not exist on disk: {setupNupkgAbsolutePath}."); goto done; } nupkgAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, "Setup.nupkg"); mainWindowLogger.Info("Copying payload to installer directory"); snapOs.Filesystem.FileDeleteIfExists(nupkgAbsolutePath); await snapOs.Filesystem.FileCopyAsync(setupNupkgAbsolutePath, nupkgAbsolutePath, cancellationToken); mainWindowLogger.Info("Successfully copied payload"); installerProgressSource.Reset(); } catch (Exception e) { mainWindowLogger.ErrorException("Unknown error while restoring assets", e); goto done; } } else { mainWindowLogger.Error("Unknown error. Could not find offline or web installer payload."); goto done; } diskLogger.Trace($"Offline installer: {offlineInstaller}"); diskLogger.Trace($"{nameof(nupkgAbsolutePath)}: {nupkgAbsolutePath}"); diskLogger.Trace($"{nameof(nupkgReleasesAbsolutePath)}: {nupkgReleasesAbsolutePath}"); if (!snapFilesystem.FileExists(nupkgAbsolutePath)) { mainWindowLogger.Error($"Unable to find installer payload: {snapFilesystem.PathGetFileName(nupkgAbsolutePath)}"); goto done; } mainWindowLogger.Info("Attempting to read payload release details"); if (!snapFilesystem.FileExists(nupkgReleasesAbsolutePath)) { mainWindowLogger.Error($"Unable to find releases nupkg: {snapFilesystem.PathGetFileName(nupkgReleasesAbsolutePath)}"); goto done; } var baseDirectory = snapFilesystem.PathCombine(snapOs.SpecialFolders.LocalApplicationData, snapApp.Id); mainWindowLogger.Info($"Installing {snapApp.Id}. Channel name: {snapChannel.Name}"); try { var snapAppInstalled = await snapInstaller.InstallAsync(nupkgAbsolutePath, baseDirectory, snapReleaseToInstall, snapChannel, installerProgressSource, mainWindowLogger, cancellationToken, offlineInstaller); if (snapAppInstalled == null) { goto done; } if (!offlineInstaller && snapAppChannelReleases.HasDeltaReleases()) { var snapReleasesToCopy = new List <SnapRelease> { snapAppChannelReleases.GetGenesisRelease() }; snapReleasesToCopy.AddRange(snapAppChannelReleases.GetDeltaReleases()); if (snapReleasesToCopy.Any()) { var totalSnapReleasesToCopyCount = snapReleasesToCopy.Count; mainWindowLogger.Info($"Copying 1 of {totalSnapReleasesToCopyCount} payloads to application directory."); var packagesDirectory = snapFilesystem.PathCombine(baseDirectory, "packages"); var snapReleasesCopied = 1; foreach (var snapRelease in snapReleasesToCopy) { var nupkgPackageWebInstallerDirectoryAbsolutePath = snapFilesystem.PathCombine( webInstallerDir.WorkingDirectory, snapRelease.Filename); var nupkgPackagePackagesDirectoryAbsolutePath = snapFilesystem.PathCombine( packagesDirectory, snapRelease.Filename); await snapFilesystem.FileCopyAsync( nupkgPackageWebInstallerDirectoryAbsolutePath, nupkgPackagePackagesDirectoryAbsolutePath, cancellationToken); mainWindowLogger.Info($"Copied {snapReleasesCopied} of {totalSnapReleasesToCopyCount} payloads to application directory."); ++snapReleasesCopied; } mainWindowLogger.Info("Successfully copied all payloads."); } snapFilesystem.FileDeleteIfExists(nupkgAbsolutePath); } mainWindowLogger.Info($"Successfully installed {snapApp.Id}."); exitCode = 0; } catch (Exception e) { mainWindowLogger.ErrorException("Unknown error during install", e); } } done: // Give user enough time to read final log message. Thread.Sleep(exitCode == 0 ? 3000 : 10000); environment.Shutdown(); } if (headless) { try { InstallInBackgroundAsync(new ConsoleMainViewModel()).Wait(cancellationToken); } catch (OperationCanceledException) { // ignore } return(exitCode); } var avaloniaApp = BuildAvaloniaApp <App>() .AfterSetup(builder => { MainWindow.Environment = environment; MainWindow.ViewModel = new AvaloniaMainWindowViewModel(snapInstallerEmbeddedResources, installerProgressSource, () => onFirstAnimationRenderedEvent.Set(), cancellationToken); Task.Factory.StartNew(() => InstallInBackgroundAsync(MainWindow.ViewModel), TaskCreationOptions.LongRunning); }); avaloniaApp.StartWithClassicDesktopLifetime(null); return(exitCode); }
static int CommandRcEdit([NotNull] RcEditOptions opts, [NotNull] ICoreRunLib coreRunLib, [NotNull] ISnapFilesystem snapFilesystem, [NotNull] ILog logger) { if (opts == null) { throw new ArgumentNullException(nameof(opts)); } if (coreRunLib == null) { throw new ArgumentNullException(nameof(coreRunLib)); } if (snapFilesystem == null) { throw new ArgumentNullException(nameof(snapFilesystem)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var exitCode = 1; if (!snapFilesystem.FileExists(opts.Filename)) { logger.Error($"Filename does not exist: {opts.Filename}."); goto done; } if (opts.ConvertSubSystemToWindowsGui) { logger.Info($"Attempting to change subsystem to Windows GUI for executable: {snapFilesystem.PathGetFileName(opts.Filename)}."); using (var srcStream = snapFilesystem.FileReadWrite(opts.Filename, false)) { if (!srcStream.ChangeSubsystemToWindowsGui(SnapLogger)) { goto done; } logger.Info("Subsystem has been successfully changed to Windows GUI."); } exitCode = 0; } if (opts.IconFilename != null) { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { logger.Error("Modifying executable icon is not supported on this OS platform."); goto done; } opts.IconFilename = snapFilesystem.PathGetFullPath(opts.IconFilename); if (!snapFilesystem.FileExists(opts.IconFilename)) { logger.Error($"Unable to find icon with filename: {opts.IconFilename}"); goto done; } if (!coreRunLib.SetIcon(opts.Filename, opts.IconFilename)) { logger.Error($"Unknown error setting icon for executable {opts.Filename}. Icon filename: {opts.Filename}."); goto done; } logger.Info("Icon has been successfully updated."); exitCode = 0; } done: return(exitCode); }