Exemplo n.º 1
0
        public async Task CompareLocalPackageToFeedPackageShouldCorrectlyInterpretFeedResponse(
            string feedResponseContentName,
            HttpStatusCode feedResponseStatusCode,
            PackageFeedStatus expectedResult)
        {
            var localPackagePath  = TestInputs.GetFullPath(Path.Combine("Nupkgs", "test-package-a.zip"));
            var packageContentUrl = $"https://fakefeed.azure.com/nuget/v3/{feedResponseContentName}.nupkg";
            var taskLoggingHelper = new Microsoft.Build.Utilities.TaskLoggingHelper(new StubTask());
            var retryHandler      = new MockRetryHandler();

            var response = new HttpResponseMessage(feedResponseStatusCode);

            if (!string.IsNullOrEmpty(feedResponseContentName))
            {
                var content = TestInputs.ReadAllBytes(Path.Combine("Nupkgs", $"{feedResponseContentName}.zip"));
                response.Content = new ByteArrayContent(content);
            }
            ;

            var httpClient = FakeHttpClient.WithResponses(response);

            var result = await GeneralUtils.CompareLocalPackageToFeedPackage(
                localPackagePath,
                packageContentUrl,
                httpClient,
                taskLoggingHelper,
                retryHandler);

            result.Should().Be(expectedResult);
        }
Exemplo n.º 2
0
        /// <summary>
        ///     Determine whether a local package is the same as a package on an AzDO feed.
        /// </summary>
        /// <param name="localPackageFullPath"></param>
        /// <param name="packageContentUrl"></param>
        /// <param name="client"></param>
        /// <returns></returns>
        /// <remarks>
        ///     Open a stream to the local file and an http request to the package. There are a couple possibilities:
        ///     - The returned headers includes a content MD5 header, in which case we can
        ///       hash the local file and just compare those.
        ///     - No content MD5 hash, and the streams must be compared in blocks. This is a bit trickier to do efficiently,
        ///       since we do not necessarily want to read all bytes if we can help it. Thus, we should compare in blocks.  However,
        ///       the streams make no gaurantee that they will return a full block each time when read operations are performed, so we
        ///       must be sure to only compare the minimum number of bytes returned.
        /// </remarks>
        public static async Task <PackageFeedStatus> CompareLocalPackageToFeedPackage(string localPackageFullPath, string packageContentUrl, HttpClient client, TaskLoggingHelper log)
        {
            log.LogMessage($"Getting package content from {packageContentUrl} and comparing to {localPackageFullPath}");

            PackageFeedStatus result = PackageFeedStatus.Unknown;

            ExponentialRetry RetryHandler = new ExponentialRetry
            {
                MaxAttempts = MaxRetries
            };

            bool success = await RetryHandler.RunAsync(async attempt =>
            {
                try
                {
                    using (Stream localFileStream = File.OpenRead(localPackageFullPath))
                        using (HttpResponseMessage response = await client.GetAsync(packageContentUrl))
                        {
                            response.EnsureSuccessStatusCode();

                            // Check the headers for content length and md5
                            bool md5HeaderAvailable    = response.Headers.TryGetValues("Content-MD5", out var md5);
                            bool lengthHeaderAvailable = response.Headers.TryGetValues("Content-Length", out var contentLength);

                            if (lengthHeaderAvailable && long.Parse(contentLength.Single()) != localFileStream.Length)
                            {
                                log.LogMessage(MessageImportance.Low, $"Package '{localPackageFullPath}' has different length than remote package '{packageContentUrl}'.");
                                result = PackageFeedStatus.ExistsAndDifferent;
                                return(true);
                            }

                            if (md5HeaderAvailable)
                            {
                                var localMD5 = AzureStorageUtils.CalculateMD5(localPackageFullPath);
                                if (!localMD5.Equals(md5.Single(), StringComparison.OrdinalIgnoreCase))
                                {
                                    log.LogMessage(MessageImportance.Low, $"Package '{localPackageFullPath}' has different MD5 hash than remote package '{packageContentUrl}'.");
                                }

                                result = PackageFeedStatus.ExistsAndDifferent;
                                return(true);
                            }

                            const int BufferSize = 64 * 1024;

                            // Otherwise, compare the streams
                            var remoteStream = await response.Content.ReadAsStreamAsync();
                            var streamsMatch = await GeneralUtils.CompareStreamsAsync(localFileStream, remoteStream, BufferSize);
                            result           = streamsMatch ? PackageFeedStatus.ExistsAndIdenticalToLocal : PackageFeedStatus.ExistsAndDifferent;
                            return(true);
                        }
                }
                // String based comparison because the status code isn't exposed in HttpRequestException
                // see here: https://github.com/dotnet/runtime/issues/23648
                catch (HttpRequestException e)
                {
                    if (e.Message.Contains("404 (Not Found)"))
                    {
                        result = PackageFeedStatus.DoesNotExist;
                        return(true);
                    }

                    // Retry this. Could be an http client timeout, 500, etc.
                    return(false);
                }
            });

            return(result);
        }