Example #1
0
        public async Task BackupAsync(string path, string id, string version, string hash)
        {
            var backupBlob = BackupsContainer.GetBlockBlobReference(
                StorageHelpers.GetPackageBackupBlobName(id, version, hash));

            if (!await backupBlob.ExistsAsync())
            {
                await backupBlob.UploadFromFileAsync(path);
            }
        }
        private async Task ApplyEdit(PackageEdit edit)
        {
            // Download the original file
            string originalPath = null;

            try
            {
                string directory = Path.Combine(TempDirectory, edit.Id, edit.Version);
                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }
                originalPath = Path.Combine(directory, "original.nupkg");
                var sourceBlob = SourceContainer.GetBlockBlobReference(
                    StorageHelpers.GetPackageBlobName(edit.Id, edit.Version));
                Log.DownloadingOriginal(edit.Id, edit.Version);
                await sourceBlob.DownloadToFileAsync(originalPath, FileMode.Create);

                Log.DownloadedOriginal(edit.Id, edit.Version);

                // Check that a backup exists
                var backupBlob = BackupsContainer.GetBlockBlobReference(
                    StorageHelpers.GetPackageBackupBlobName(edit.Id, edit.Version, edit.Hash));
                if (!WhatIf && !await backupBlob.ExistsAsync())
                {
                    Log.BackingUpOriginal(edit.Id, edit.Version);
                    await backupBlob.UploadFromFileAsync(originalPath, FileMode.Open);

                    Log.BackedUpOriginal(edit.Id, edit.Version);
                }

                // Load the zip file and find the manifest
                using (var originalStream = File.Open(originalPath, FileMode.Open, FileAccess.ReadWrite))
                    using (var archive = new ZipArchive(originalStream, ZipArchiveMode.Update))
                    {
                        // Find the nuspec
                        var nuspecEntries = archive.Entries.Where(e => ManifestSelector.IsMatch(e.FullName)).ToArray();
                        if (nuspecEntries.Length == 0)
                        {
                            throw new InvalidDataException(String.Format(
                                                               CultureInfo.CurrentCulture,
                                                               Strings.HandlePackageEditsJob_MissingManifest,
                                                               edit.Id,
                                                               edit.Version,
                                                               backupBlob.Uri.AbsoluteUri));
                        }
                        else if (nuspecEntries.Length > 1)
                        {
                            throw new InvalidDataException(String.Format(
                                                               CultureInfo.CurrentCulture,
                                                               Strings.HandlePackageEditsJob_MultipleManifests,
                                                               edit.Id,
                                                               edit.Version,
                                                               backupBlob.Uri.AbsoluteUri));
                        }

                        // We now have the nuspec
                        var manifestEntry = nuspecEntries.Single();

                        // Load the manifest with a constrained stream
                        Log.RewritingPackage(edit.Id, edit.Version);
                        Manifest manifest;
                        using (var manifestStream = manifestEntry.Open())
                        {
                            manifest = Manifest.ReadFrom(manifestStream, validateSchema: false);

                            // Modify the manifest as per the edit
                            edit.ApplyTo(manifest.Metadata);

                            // Save the manifest back
                            manifestStream.Seek(0, SeekOrigin.Begin);
                            manifestStream.SetLength(0);
                            manifest.Save(manifestStream);
                        }
                        Log.RewrotePackage(edit.Id, edit.Version);
                    }

                // Snapshot the original blob
                Log.SnapshottingBlob(edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri);
                var sourceSnapshot = await sourceBlob.CreateSnapshotAsync();

                Log.SnapshottedBlob(edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri, sourceSnapshot.Uri.AbsoluteUri);

                // Upload the updated file
                Log.UploadingModifiedPackage(edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri);
                await sourceBlob.UploadFromFileAsync(originalPath, FileMode.Open);

                Log.UploadedModifiedPackage(edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri);

                // Calculate new size and hash
                string hash;
                long   size;
                using (var originalStream = File.OpenRead(originalPath))
                {
                    size = originalStream.Length;

                    var hashAlgorithm = HashAlgorithm.Create(HashAlgorithmName);
                    hash = Convert.ToBase64String(
                        hashAlgorithm.ComputeHash(originalStream));
                }

                // Update the database
                try
                {
                    Log.UpdatingDatabase(edit.Id, edit.Version);
                    using (var connection = await PackageDatabase.ConnectTo())
                    {
                        var parameters = new DynamicParameters(new
                        {
                            edit.Authors,
                            edit.Copyright,
                            edit.Description,
                            edit.IconUrl,
                            edit.LicenseUrl,
                            edit.ProjectUrl,
                            edit.ReleaseNotes,
                            edit.RequiresLicenseAcceptance,
                            edit.Summary,
                            edit.Title,
                            edit.Tags,
                            edit.Key,
                            edit.PackageKey,
                            edit.UserKey,
                            PackageFileSize = size,
                            Hash            = hash,
                            HashAlgorithm   = HashAlgorithmName
                        });

                        // Prep SQL for merging in authors
                        StringBuilder loadAuthorsSql = new StringBuilder();
                        var           authors        = edit.Authors.Split(',');
                        for (int i = 0; i < authors.Length; i++)
                        {
                            loadAuthorsSql.Append("INSERT INTO [PackageAuthors]([PackageKey],[Name]) VALUES(@PackageKey, @Author" + i.ToString() + ")");
                            parameters.Add("Author" + i.ToString(), authors[i]);
                        }

                        await connection.QueryAsync <int>(@"
                            BEGIN TRANSACTION
                                -- Form a comma-separated list of authors
                                DECLARE @existingAuthors nvarchar(MAX)
                                SELECT @existingAuthors = COALESCE(@existingAuthors + ',', '') + Name
                                FROM PackageAuthors
                                WHERE PackageKey = @PackageKey

                                -- Copy packages data to package history table
                                INSERT INTO [PackageHistories]
                                SELECT      [Key] AS PackageKey,
                                            @UserKey AS UserKey,
                                            GETUTCDATE() AS Timestamp,
                                            Title,
                                            @existingAuthors AS Authors,
                                            Copyright,
                                            Description,
                                            IconUrl,
                                            LicenseUrl,
                                            ProjectUrl,
                                            ReleaseNotes,
                                            RequiresLicenseAcceptance,
                                            Summary,
                                            Tags,
                                            Hash,
                                            HashAlgorithm,
                                            PackageFileSize,
                                            LastUpdated,
                                            Published
                                FROM        [Packages]
                                WHERE       [Key] = @PackageKey

                                -- Update the packages table
                                UPDATE  [Packages]
                                SET     Copyright = @Copyright,
                                        Description = @Description,
                                        IconUrl = @IconUrl,
                                        LicenseUrl = @LicenseUrl,
                                        ProjectUrl = @ProjectUrl,
                                        ReleaseNotes = @ReleaseNotes,
                                        RequiresLicenseAcceptance = @RequiresLicenseAcceptance,
                                        Summary = @Summary,
                                        Title = @Title,
                                        Tags = @Tags,
                                        LastEdited = GETUTCDATE(),
                                        LastUpdated = GETUTCDATE(),
                                        UserKey = @UserKey,
                                        Hash = @Hash,
                                        HashAlgorithm = @HashAlgorithm,
                                        PackageFileSize = @PackageFileSize,
                                        FlattenedAuthors = @Authors
                                WHERE   [Key] = @PackageKey

                                -- Update Authors
                                DELETE FROM [PackageAuthors] 
                                WHERE PackageKey = @PackageKey

                                " + loadAuthorsSql.ToString() + @"
                            
                                -- Clean this edit and all previous edits.
                                DELETE FROM [PackageEdits]
                                WHERE [PackageKey] = @PackageKey
                                AND [Key] <= @Key
                            " + (WhatIf ? "ROLLBACK TRANSACTION" : "COMMIT TRANSACTION"),
                                                          parameters);
                    }
                    Log.UpdatedDatabase(edit.Id, edit.Version);
                }
                catch (Exception)
                {
                    // Error occurred while updaing database, roll back the blob to the snapshot
                    // Can't do "await" in a catch block, but this should be pretty quick since it just starts the copy
                    Log.RollingBackBlob(edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri, sourceBlob.Uri.AbsoluteUri);
                    sourceBlob.StartCopyFromBlob(sourceSnapshot);
                    Log.RolledBackBlob(edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri, sourceBlob.Uri.AbsoluteUri);
                    throw;
                }

                Log.DeletingSnapshot(edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri);
                await sourceSnapshot.DeleteAsync();

                Log.DeletedSnapshot(edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri);
            }
            finally
            {
                if (!String.IsNullOrEmpty(originalPath) && File.Exists(originalPath))
                {
                    File.Delete(originalPath);
                }
            }
        }
Example #3
0
        private async Task ApplyEdit(PackageEdit edit)
        {
            string originalPath         = null;
            string originalReadMeMDPath = null;

            try
            {
                TempDirectory = Path.Combine(Path.GetTempPath(), "NuGetService", "HandlePackageEdits");
                var directory = Path.Combine(TempDirectory, edit.Id, edit.Version);
                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }

                originalPath = Path.Combine(directory, "original.nupkg");
                Logger.LogInformation("Downloading nupkg to {OriginalPath}", originalPath);

                var sourceBlob = SourceContainer.GetBlockBlobReference(
                    StorageHelpers.GetPackageBlobName(edit.Id, edit.Version));
                Logger.LogInformation("Name is {SourceBlobName}, storage uri is {StorageUri}", sourceBlob.Name, sourceBlob.StorageUri);

                // Download the original file
                Logger.LogInformation("Downloading original copy of {EditId} {EditVersion}", edit.Id, edit.Version);
                await sourceBlob.DownloadToFileAsync(originalPath, FileMode.Create);

                Logger.LogInformation("Downloaded original copy of {EditId} {EditVersion}", edit.Id, edit.Version);

                // Check that a backup exists
                var backupBlob = BackupsContainer.GetBlockBlobReference(
                    StorageHelpers.GetPackageBackupBlobName(edit.Id, edit.Version, edit.Hash));
                if (!await backupBlob.ExistsAsync())
                {
                    Logger.LogInformation("Backing up original copy of {EditId} {EditVersion}", edit.Id, edit.Version);
                    await backupBlob.UploadFromFileAsync(originalPath);

                    Logger.LogInformation("Backed up original copy of {EditId} {EditVersion}", edit.Id, edit.Version);
                }

                // Update the nupkg manifest with the new metadata
                using (var originalStream = File.Open(originalPath, FileMode.Open, FileAccess.ReadWrite))
                {
                    Logger.LogInformation("Rewriting package file for {EditId} {EditVersion}", edit.Id, edit.Version);

                    var editActionList = edit.GetEditsAsActionList();
                    NupkgRewriter.RewriteNupkgManifest(originalStream, editActionList);

                    Logger.LogInformation("Rewrote package file for {EditId} {EditVersion}", edit.Id, edit.Version);
                }

                // Snapshot the original blob
                Logger.LogInformation("Snapshotting original blob for {EditId} {EditVersion} ({BlobUri}).", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri);
                var sourceSnapshot = await sourceBlob.CreateSnapshotAsync();

                Logger.LogInformation("Snapshotted original blob for {EditId} {EditVersion} ({BlobUri}).", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri);

                // Upload the updated file
                Logger.LogInformation("Uploading modified package file for {EditId} {EditVersion} to {BlobUri}", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri);
                await sourceBlob.UploadFromFileAsync(originalPath);

                Logger.LogInformation("Uploaded modified package file for {EditId} {EditVersion} to {BlobUri}", edit.Id, edit.Version, sourceBlob.Uri.AbsoluteUri);

                // Calculate new size and hash
                string hash;
                long   size;
                using (var originalStream = File.OpenRead(originalPath))
                {
                    size = originalStream.Length;

                    var hashAlgorithm = HashAlgorithm.Create(HashAlgorithmName);
                    hash = Convert.ToBase64String(
                        hashAlgorithm.ComputeHash(originalStream));
                }

                // ReadMe update
                var readMeMDBlob = await UpdateReadMeAsync(edit, directory, originalReadMeMDPath, MarkdownExtension);

                try
                {
                    Logger.LogInformation("Updating package record for {EditId} {EditVersion}", edit.Id, edit.Version);
                    await UpdateDatabaseWithEdit(edit, hash, size);

                    Logger.LogInformation("Updated package record for {EditId} {EditVersion}", edit.Id, edit.Version);
                }
                catch (Exception exception)
                {
                    // Error occurred while updaing database, roll back the blob to the snapshot
                    Logger.LogError("Failed to update database! {Exception}", exception);
                    Logger.LogWarning(
                        "Rolling back updated blob for {EditId} {EditVersion}. Copying snapshot {SnapshotUri} to {BlobUri}",
                        edit.Id, edit.Version, sourceSnapshot.Uri.AbsoluteUri, sourceBlob.Uri.AbsoluteUri);
                    await sourceBlob.StartCopyAsync(sourceSnapshot);

                    while (sourceBlob.CopyState.Status != CopyStatus.Success)
                    {
                        await Task.Delay(1000);
                    }
                    Logger.LogWarning(
                        "Rolled back updated blob for {EditId} {EditVersion}. Copying snapshot {SnapshotUri} to {BlobUri}",
                        sourceSnapshot.Uri.AbsoluteUri, sourceBlob.Uri.AbsoluteUri);

                    await RollBackReadMeAsync(edit, directory, originalReadMeMDPath, readMeMDBlob.activeSnapshot, readMeMDBlob.activeBlob);

                    throw;
                }

                if (edit.ReadMeState == ReadMeChanged)
                {
                    // Delete pending ReadMes
                    Logger.LogInformation("Deleting pending ReadMe for {EditId} {EditVersion} from {PendingReadMeMdUri}", edit.Id, edit.Version, readMeMDBlob.pendingBlob.Uri.AbsoluteUri);
                    await readMeMDBlob.pendingBlob.DeleteIfExistsAsync();

                    Logger.LogInformation($"Deleted pending ReadMe for {edit.Id} {edit.Version} from {readMeMDBlob.pendingBlob.Uri.AbsoluteUri}");
                }

                Logger.LogInformation("Deleting snapshot blob {SnapshotUri} for {EditId} {EditVersion}.", sourceSnapshot.Uri.AbsoluteUri, edit.Id, edit.Version);
                await sourceSnapshot.DeleteAsync();

                Logger.LogInformation("Deleted snapshot blob {SnapshotUri} for {EditId} {EditVersion}.", sourceSnapshot.Uri.AbsoluteUri, edit.Id, edit.Version);

                if (readMeMDBlob.activeSnapshot != null)
                {
                    Logger.LogInformation("Deleting snapshot ReadMe Md {ActiveReadMeMdUri} for {EditId} {EditVersion}.", readMeMDBlob.activeSnapshot.Uri.AbsoluteUri, edit.Id, edit.Version);
                    await readMeMDBlob.activeSnapshot.DeleteAsync();

                    Logger.LogInformation("Deleted snapshot ReadMe Md {ActiveReadMeMdUri} for {EditId} {EditVersion}.", readMeMDBlob.activeSnapshot.Uri.AbsoluteUri, edit.Id, edit.Version);
                }
            }
            finally
            {
                if (!string.IsNullOrEmpty(originalPath) && File.Exists(originalPath))
                {
                    File.Delete(originalPath);
                }
            }
        }