Пример #1
0
        private void ProcessPackageEdits(int packageKey)
        {
            // Create a fresh entities context so that we work in isolation
            var entitiesContext = new EntitiesContext(ConnectionString.ConnectionString, readOnly: false);

            // Get the list of edits for this package
            // Do edit with a 'most recent edit to this package wins - other edits are deleted' strategy.
            var editsForThisPackage = entitiesContext.Set <PackageEdit>()
                                      .Where(pe => pe.PackageKey == packageKey && pe.TriedCount < 3)
                                      .Include(pe => pe.Package)
                                      .Include(pe => pe.Package.PackageRegistration)
                                      .Include(pe => pe.User)
                                      .OrderByDescending(pe => pe.Timestamp)
                                      .ToList();

            // List of Work to do:
            // 1) Backup old blob, if the original has not been backed up yet
            // 2) Downloads blob, create new NUPKG locally
            // 3) Upload blob
            // 4) Update the database
            PackageEdit edit = editsForThisPackage.First();

            var blobClient        = StorageAccount.CreateCloudBlobClient();
            var packagesContainer = Util.GetPackagesBlobContainer(blobClient);

            var latestPackageFileName   = Util.GetPackageFileName(edit.Package.PackageRegistration.Id, edit.Package.Version);
            var originalPackageFileName = Util.GetBackupOfOriginalPackageFileName(edit.Package.PackageRegistration.Id, edit.Package.Version);

            var originalPackageBackupBlob = packagesContainer.GetBlockBlobReference(originalPackageFileName);
            var latestPackageBlob         = packagesContainer.GetBlockBlobReference(latestPackageFileName);

            var edits = new List <Action <ManifestMetadata> >
            {
                (m) => { m.Authors = edit.Authors; },
                (m) => { m.Copyright = edit.Copyright; },
                (m) => { m.Description = edit.Description; },
                (m) => { m.IconUrl = edit.IconUrl; },
                (m) => { m.LicenseUrl = edit.LicenseUrl; },
                (m) => { m.ProjectUrl = edit.ProjectUrl; },
                (m) => { m.ReleaseNotes = edit.ReleaseNotes; },
                (m) => { m.RequireLicenseAcceptance = edit.RequiresLicenseAcceptance; },
                (m) => { m.Summary = edit.Summary; },
                (m) => { m.Title = edit.Title; },
                (m) => { m.Tags = edit.Tags; },
            };

            Log.Info(
                "Processing Edit Key={0}, PackageId={1}, Version={2}, User={3}",
                edit.Key,
                edit.Package.PackageRegistration.Id,
                edit.Package.Version,
                edit.User.Username);

            if (!WhatIf)
            {
                edit.TriedCount += 1;
                int nr = entitiesContext.SaveChanges();
                if (nr != 1)
                {
                    throw new Exception(
                              String.Format("Something went terribly wrong, only one entity should be updated but actually {0} entities were updated", nr));
                }
            }

            try
            {
                ArchiveOriginalPackageBlob(originalPackageBackupBlob, latestPackageBlob);
                using (var readWriteStream = new MemoryStream())
                {
                    // Download to memory
                    CloudBlockBlob downloadSourceBlob = WhatIf ? latestPackageBlob : originalPackageBackupBlob;
                    Log.Info("Downloading original package blob to memory {0}", downloadSourceBlob.Name);
                    downloadSourceBlob.DownloadToStream(readWriteStream);

                    // Rewrite in memory
                    Log.Info("Rewriting nupkg package in memory", downloadSourceBlob.Name);
                    NupkgRewriter.RewriteNupkgManifest(readWriteStream, edits);

                    // Get updated hash code, and file size
                    Log.Info("Computing updated hash code of memory stream");
                    var    newPackageFileSize = readWriteStream.Length;
                    var    hashAlgorithm      = HashAlgorithm.Create("SHA512");
                    byte[] hashBytes          = hashAlgorithm.ComputeHash(readWriteStream.GetBuffer());
                    var    newHash            = Convert.ToBase64String(hashBytes);

                    if (!WhatIf)
                    {
                        // Snapshot the blob
                        var blobSnapshot = latestPackageBlob.CreateSnapshot();

                        // Build up the changes in the entities context
                        edit.Apply(hashAlgorithm: "SHA512", hash: newHash, packageFileSize: newPackageFileSize);
                        foreach (var eachEdit in editsForThisPackage)
                        {
                            entitiesContext.DeleteOnCommit(eachEdit);
                        }

                        // Upload the blob before doing SaveChanges(). If blob update fails, we won't do SaveChanges() and the edit can be retried.
                        // If SaveChanges() fails we can undo the blob upload.
                        try
                        {
                            Log.Info("Uploading blob from memory {0}", latestPackageBlob.Name);
                            readWriteStream.Position = 0;
                            latestPackageBlob.UploadFromStream(readWriteStream);
                        }
                        catch (Exception e)
                        {
                            Log.Error("(error) - package edit blob update failed.");
                            Log.ErrorException("(exception)", e);
                            Log.Error("(note) - blob snapshot URL = " + blobSnapshot.Uri);
                            throw; // To handler block that will record error in DB
                        }

                        try
                        {
                            // SaveChanges tries to commit changes to DB
                            entitiesContext.SaveChanges();
                        }
                        catch (Exception e)
                        {
                            // Commit changes to DB probably failed.
                            // Since our blob update wasn't part of the transaction (and doesn't AFAIK have a 'commit()' operator we can utilize for the type of blobs we are using)
                            // try, (single attempt) to roll back the blob update by restoring the previous snapshot.
                            Log.Error("(error) - package edit DB update failed. Trying to roll back the blob to its previous snapshot.");
                            Log.ErrorException("(exception)", e);
                            Log.Error("(note) - blob snapshot URL = " + blobSnapshot.Uri);
                            try
                            {
                                latestPackageBlob.StartCopyFromBlob(blobSnapshot);
                            }
                            catch (Exception e2)
                            {
                                // If blob rollback fails it is not be the end of the world
                                // - the package metadata mismatches the edit now,
                                // but there should still an edit in the queue, waiting to be rerun and put everything back in synch.
                                Log.Error("(error) - rolling back the package blob to its previous snapshot failed.");
                                Log.ErrorException("(exception)", e2);
                                Log.Error("(note) - blob snapshot URL = " + blobSnapshot.Uri);
                            }

                            throw; // To handler block that will record error in DB
                        }
                    }
                }
            }
            catch (Exception e)
            {
                if (!WhatIf)
                {
                    try
                    {
                        Log.Info("Storing the error on package edit with key {0}", edit.Key);

                        // Try to record the error into the PackageEdit database record
                        // so that we can actually diagnose failures.
                        // This must be done on a fresh context to ensure no conflicts.
                        var errorContext = new EntitiesContext(ConnectionString.ConnectionString, readOnly: false);
                        var errorEdit    = errorContext.Set <PackageEdit>().Where(pe => pe.Key == edit.Key).FirstOrDefault();

                        if (errorEdit != null)
                        {
                            errorEdit.LastError = string.Format("{0} : {1}", e.GetType(), e);
                            errorContext.SaveChanges();
                        }
                        else
                        {
                            Log.Info("The package edit with key {0} couldn't be found. It was likely canceled and deleted.", edit.Key);
                        }
                    }
                    catch (Exception errorException)
                    {
                        Log.ErrorException("(error) - couldn't save the last error on the edit that was being applied.", errorException);
                    }
                }
            }
        }
Пример #2
0
        private void ProcessPackageEdits(IEnumerable <PackageEdit> editsForThisPackage, EntitiesContext entitiesContext)
        {
            // List of Work to do:
            // 1) Backup old blob, if the original has not been backed up yet
            // 2) Downloads blob, create new NUPKG locally
            // 3) Upload blob
            // 4) Update the database
            PackageEdit edit = editsForThisPackage.OrderByDescending(pe => pe.Timestamp).First();

            var blobClient        = StorageAccount.CreateCloudBlobClient();
            var packagesContainer = Util.GetPackagesBlobContainer(blobClient);

            var latestPackageFileName   = Util.GetPackageFileName(edit.Package.PackageRegistration.Id, edit.Package.Version);
            var originalPackageFileName = Util.GetBackupOfOriginalPackageFileName(edit.Package.PackageRegistration.Id, edit.Package.Version);

            var originalPackageBackupBlob = packagesContainer.GetBlockBlobReference(originalPackageFileName);
            var latestPackageBlob         = packagesContainer.GetBlockBlobReference(latestPackageFileName);

            var edits = new List <Action <ManifestMetadata> >
            {
                (m) => { m.Authors = edit.Authors; },
                (m) => { m.Copyright = edit.Copyright; },
                (m) => { m.Description = edit.Description; },
                (m) => { m.IconUrl = edit.IconUrl; },
                (m) => { m.LicenseUrl = edit.LicenseUrl; },
                (m) => { m.ProjectUrl = edit.ProjectUrl; },
                (m) => { m.ReleaseNotes = edit.ReleaseNotes; },
                (m) => { m.RequireLicenseAcceptance = edit.RequiresLicenseAcceptance; },
                (m) => { m.Summary = edit.Summary; },
                (m) => { m.Title = edit.Title; },
                (m) => { m.Tags = edit.Tags; },
            };

            Log.Info(
                "Processing Edit Key={0}, PackageId={1}, Version={2}",
                edit.Key,
                edit.Package.PackageRegistration.Id,
                edit.Package.Version);

            if (!WhatIf)
            {
                edit.TriedCount += 1;
                int nr = entitiesContext.SaveChanges();
                if (nr != 1)
                {
                    throw new ApplicationException(
                              String.Format("Something went terribly wrong, only one entity should be updated but actually {0} entities were updated", nr));
                }
            }

            ArchiveOriginalPackageBlob(originalPackageBackupBlob, latestPackageBlob);
            using (var readWriteStream = new MemoryStream())
            {
                // Download to memory
                CloudBlockBlob downloadSourceBlob = WhatIf ? latestPackageBlob : originalPackageBackupBlob;
                Log.Info("Downloading original package blob to memory {0}", downloadSourceBlob.Name);
                downloadSourceBlob.DownloadToStream(readWriteStream);

                // Rewrite in memory
                Log.Info("Rewriting nupkg package in memory", downloadSourceBlob.Name);
                NupkgRewriter.RewriteNupkgManifest(readWriteStream, edits);

                // Get updated hash code, and file size
                Log.Info("Computing updated hash code of memory stream");
                var    newPackageFileSize = readWriteStream.Length;
                var    hashAlgorithm      = HashAlgorithm.Create("SHA512");
                byte[] hashBytes          = hashAlgorithm.ComputeHash(readWriteStream.GetBuffer());
                var    newHash            = Convert.ToBase64String(hashBytes);

                if (!WhatIf)
                {
                    // Snapshot the blob
                    var blobSnapshot = latestPackageBlob.CreateSnapshot();

                    // Start Transaction: Complete the edit in the gallery DB.
                    // Use explicit SQL transactions instead of EF operation-grouping
                    // so that we can manually roll the transaction back on a blob related failure.
                    ObjectContext objectContext = (entitiesContext as IObjectContextAdapter).ObjectContext;
                    ((objectContext.Connection) as EntityConnection).Open(); // must open in order to begin transaction
                    using (EntityTransaction transaction = ((objectContext.Connection) as EntityConnection).BeginTransaction())
                    {
                        edit.Apply(hashAlgorithm: "SHA512", hash: newHash, packageFileSize: newPackageFileSize);

                        // Add to transaction: delete all the pending edits of this package.
                        foreach (var eachEdit in editsForThisPackage)
                        {
                            entitiesContext.DeleteOnCommit(eachEdit);
                        }

                        entitiesContext.SaveChanges(); // (transaction is still not committed, but do some EF legwork up-front of modifying the blob)
                        try
                        {
                            // Reupload blob
                            Log.Info("Uploading blob from memory {0}", latestPackageBlob.Name);
                            readWriteStream.Position = 0;
                            latestPackageBlob.UploadFromStream(readWriteStream);
                        }
                        catch (Exception e)
                        {
                            // Uploading the updated nupkg failed.
                            // Rollback the transaction, which restores the Edit to PackageEdits so it can be attempted again.
                            Log.Error("(error) - package edit blob update failed. Rolling back the DB transaction.");
                            Log.ErrorException("(exception", e);
                            Log.Error("(note) - blob snapshot URL = " + blobSnapshot.Uri);
                            transaction.Rollback();
                            return;
                        }

                        try
                        {
                            transaction.Commit();
                        }
                        catch (Exception e)
                        {
                            // Commit changes to DB failed.
                            // Since our blob update wasn't part of the transaction (and doesn't AFAIK have a 'commit()' operator we can utilize for the type of blobs we are using)
                            // try, (single attempt) to roll back the blob update by restoring the previous snapshot.
                            Log.Error("(error) - package edit DB update failed. Trying to roll back the blob to its previous snapshot.");
                            Log.ErrorException("(exception", e);
                            Log.Error("(note) - blob snapshot URL = " + blobSnapshot.Uri);
                            try
                            {
                                latestPackageBlob.StartCopyFromBlob(blobSnapshot);
                            }
                            catch (Exception e2)
                            {
                                // In this case it may not be the end of the world - the package metadata mismatches the edit now,
                                // but there's still an edit in the queue, waiting to be rerun and put everything back in synch.
                                Log.Error("(error) - rolling back the package blob to its previous snapshot failed.");
                                Log.ErrorException("(exception", e2);
                                Log.Error("(note) - blob snapshot URL = " + blobSnapshot.Uri);
                            }
                        }
                    }
                }
            }
        }
Пример #3
0
        private async Task ApplyEdit(PackageEdit edit)
        {
            string originalPath = 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");

                var sourceItem = FileStorage.GetFile(edit.Id, edit.Version);
                Trace.TraceInformation($"Name is {sourceItem.Name}, storage uri is {sourceItem.Uri}");

                // Download the original file
                Trace.TraceInformation($"Downloading original copy of {edit.Id} {edit.Version}");
                await FileStorage.DownloadAsync(sourceItem, originalPath);

                Trace.TraceInformation($"Downloaded original copy of {edit.Id} {edit.Version}");

                // Check that a backup exists
                Trace.TraceInformation($"Backing up original copy of {edit.Id} {edit.Version}");
                await FileStorage.BackupAsync(originalPath, edit.Id, edit.Version, edit.Hash);

                Trace.TraceInformation($"Backed up original copy of {edit.Id} {edit.Version}");

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

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

                    Trace.TraceInformation($"Rewrote package file for {edit.Id} {edit.Version}");
                }

                // Snapshot the original blob
                Trace.TraceInformation($"Snapshotting original blob for {edit.Id} {edit.Version} ({sourceItem.Uri.AbsoluteUri}).");
                var snapshot = await FileStorage.SnapshotService.CreateSnapshot(sourceItem);

                Trace.TraceInformation($"Snapshotted original blob for {edit.Id} {edit.Version} ({sourceItem.Uri.AbsoluteUri}).");

                // Upload the updated file
                Trace.TraceInformation($"Uploading modified package file for {edit.Id} {edit.Version} to {sourceItem.Uri.AbsoluteUri}");
                await FileStorage.UploadAsync(sourceItem, originalPath);

                Trace.TraceInformation($"Uploaded modified package file for {edit.Id} {edit.Version} to {sourceItem.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
                {
                    Trace.TraceInformation($"Updating package record for {edit.Id} {edit.Version}");
                    await UpdateDatabaseWithEdit(edit, hash, size);

                    Trace.TraceInformation($"Updated package record for {edit.Id} {edit.Version}");
                }
                catch (Exception 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
                    Trace.TraceError($"Failed to update database! {exception}");
                    Trace.TraceWarning(
                        $"Rolling back updated blob for {edit.Id} {edit.Version}. Copying snapshot {snapshot.Uri.AbsoluteUri} to {sourceItem.Uri.AbsoluteUri}");
                    FileStorage.SnapshotService.RestoreSnapshot(sourceItem, snapshot);
                    Trace.TraceWarning(
                        $"Rolled back updated blob for {edit.Id} {edit.Version}. Copying snapshot {snapshot.Uri.AbsoluteUri} to {sourceItem.Uri.AbsoluteUri}");

                    throw;
                }

                Trace.TraceInformation("Deleting snapshot blob {2} for {0} {1}.", edit.Id, edit.Version, snapshot.Uri.AbsoluteUri);
                await FileStorage.SnapshotService.DeleteSnapshotAsync(snapshot);

                Trace.TraceInformation("Deleted snapshot blob {2} for {0} {1}.", edit.Id, edit.Version, snapshot.Uri.AbsoluteUri);
            }
            finally
            {
                if (!string.IsNullOrEmpty(originalPath) && File.Exists(originalPath))
                {
                    File.Delete(originalPath);
                }
            }
        }
Пример #4
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);
                }
            }
        }