diff --git a/Bicep.sln b/Bicep.sln index f97d853094a..885c206fae7 100644 --- a/Bicep.sln +++ b/Bicep.sln @@ -72,6 +72,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bicep.MSBuild", "src\Bicep. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bicep.Core.RegistryClient", "src\Bicep.Core.RegistryClient\Bicep.Core.RegistryClient.csproj", "{AA51BD66-BD30-432C-82B6-C783F232D989}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Containers.ContainerRegistry", "..\azure-sdk-for-net\sdk\containerregistry\Azure.Containers.ContainerRegistry\src\Azure.Containers.ContainerRegistry.csproj", "{3F04EBA8-F6BB-4A24-8AAE-9FE629F50ABE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -142,6 +144,10 @@ Global {AA51BD66-BD30-432C-82B6-C783F232D989}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA51BD66-BD30-432C-82B6-C783F232D989}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA51BD66-BD30-432C-82B6-C783F232D989}.Release|Any CPU.Build.0 = Release|Any CPU + {3F04EBA8-F6BB-4A24-8AAE-9FE629F50ABE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F04EBA8-F6BB-4A24-8AAE-9FE629F50ABE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F04EBA8-F6BB-4A24-8AAE-9FE629F50ABE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F04EBA8-F6BB-4A24-8AAE-9FE629F50ABE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -163,6 +169,7 @@ Global {F3AF01F6-24E8-4129-80B6-84AC070B5C7D} = {61643BA1-BE82-4430-A3D3-CB7A16E747E3} {61026689-70A0-403E-B616-DFAEEF259444} = {38A0A11F-72BD-4512-A4A9-AC953936C09F} {AA51BD66-BD30-432C-82B6-C783F232D989} = {FE323E78-E865-46E2-859A-E4F6FB312C0F} + {3F04EBA8-F6BB-4A24-8AAE-9FE629F50ABE} = {FE323E78-E865-46E2-859A-E4F6FB312C0F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {21F77282-91E7-4304-B1EF-FADFA4F39E37} diff --git a/src/Bicep.Core.RegistryClient/BicepRegistryBlobClient.cs b/src/Bicep.Core.RegistryClient/BicepRegistryBlobClient.cs index 39f8df0e3b9..bf0a8ffd031 100644 --- a/src/Bicep.Core.RegistryClient/BicepRegistryBlobClient.cs +++ b/src/Bicep.Core.RegistryClient/BicepRegistryBlobClient.cs @@ -1,105 +1,105 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Azure; -using Azure.Core; -using Azure.Core.Pipeline; -using System; -using System.IO; -using System.Threading.Tasks; -using System.Threading; -using Bicep.Core.Registry.Oci; -using Bicep.Core.RegistryClient.Models; - -namespace Bicep.Core.RegistryClient -{ - public class BicepRegistryBlobClient - { - private readonly Uri _endpoint; - private readonly string _registryName; - private readonly HttpPipeline _pipeline; - private readonly HttpPipeline _acrAuthPipeline; - private readonly ClientDiagnostics _clientDiagnostics; - private readonly ContainerRegistryRestClient _restClient; - private readonly AuthenticationRestClient _acrAuthClient; - private readonly ContainerRegistryBlobRestClient _blobRestClient; - private readonly string _repositoryName; - - public BicepRegistryBlobClient(Uri endpoint, TokenCredential credential, string repositoryName) : this(endpoint, credential, repositoryName, new ContainerRegistryClientOptions()) - { - } - - public BicepRegistryBlobClient(Uri endpoint, TokenCredential credential, string repositoryName, ContainerRegistryClientOptions options) - { - Argument.AssertNotNull(endpoint, nameof(endpoint)); - Argument.AssertNotNull(credential, nameof(credential)); - Argument.AssertNotNull(options, nameof(options)); - - _endpoint = endpoint; - _registryName = endpoint.Host.Split('.')[0]; - _repositoryName = repositoryName; - _clientDiagnostics = new ClientDiagnostics(options); - - _acrAuthPipeline = HttpPipelineBuilder.Build(options); - _acrAuthClient = new AuthenticationRestClient(_clientDiagnostics, _acrAuthPipeline, endpoint.AbsoluteUri); - - _pipeline = HttpPipelineBuilder.Build(options, new ContainerRegistryChallengeAuthenticationPolicy(credential, options.AuthenticationScope, _acrAuthClient)); - _restClient = new ContainerRegistryRestClient(_clientDiagnostics, _pipeline, _endpoint.AbsoluteUri); - _blobRestClient = new ContainerRegistryBlobRestClient(_clientDiagnostics, _pipeline, _endpoint.AbsoluteUri); - } - - // allows mocking - protected BicepRegistryBlobClient() - { - } - - public virtual async Task> UploadBlobAsync(Stream stream, CancellationToken cancellationToken = default) - { - string digest = DigestHelper.ComputeDigest(DigestHelper.AlgorithmIdentifierSha256, stream); - - ResponseWithHeaders startUploadResult = - await _blobRestClient.StartUploadAsync(_repositoryName, cancellationToken).ConfigureAwait(false); - - stream.Position = 0; - ResponseWithHeaders uploadChunkResult = - await _blobRestClient.UploadChunkAsync(startUploadResult.Headers.Location, stream, cancellationToken).ConfigureAwait(false); - - ResponseWithHeaders completeUploadResult = - await _blobRestClient.CompleteUploadAsync(digest, uploadChunkResult.Headers.Location, null, cancellationToken).ConfigureAwait(false); - - return Response.FromValue(new UploadBlobResult(), completeUploadResult.GetRawResponse()); - } - - public virtual async Task> UploadManifestAsync(Stream stream, UploadManifestOptions options = default, CancellationToken cancellationToken = default) - { - options ??= new UploadManifestOptions(); - - string reference = options.Tag ?? DigestHelper.ComputeDigest(DigestHelper.AlgorithmIdentifierSha256, stream); - stream.Position = 0; - ResponseWithHeaders response = await _restClient.CreateManifestAsync(_repositoryName, reference, options.MediaType, stream, cancellationToken).ConfigureAwait(false); - - return Response.FromValue(new UploadManifestResult(), response.GetRawResponse()); - } - - public virtual async Task> DownloadManifestAsync(string reference, DownloadManifestOptions options = default, CancellationToken cancellationToken = default) - { - options ??= new DownloadManifestOptions(); - - Response manifestWrapper = await _restClient.GetManifestAsync(_repositoryName, reference, options.MediaType.ToSerialString(), cancellationToken).ConfigureAwait(false); - - manifestWrapper.GetRawResponse().Headers.TryGetValue("Docker-Content-Digest", out var digest); - - Stream stream = manifestWrapper.GetRawResponse().ContentStream; - stream.Position = 0; - - return Response.FromValue(new DownloadManifestResult(digest, stream), manifestWrapper.GetRawResponse()); - } - - public virtual async Task> DownloadBlobAsync(string digest, DownloadBlobOptions options = default, CancellationToken cancellationToken = default) - { - options ??= new DownloadBlobOptions(); - ResponseWithHeaders blobResult = await _blobRestClient.GetBlobAsync(_repositoryName, digest, cancellationToken).ConfigureAwait(false); - return Response.FromValue(new DownloadBlobResult(digest, blobResult.Value), blobResult.GetRawResponse()); - } - } -} +//using Azure; +//using Azure.Core; +//using Azure.Core.Pipeline; +//using System; +//using System.IO; +//using System.Threading.Tasks; +//using System.Threading; +//using Bicep.Core.Registry.Oci; +//using Bicep.Core.RegistryClient.Models; + +//namespace Bicep.Core.RegistryClient +//{ +// public class BicepRegistryBlobClient +// { +// private readonly Uri _endpoint; +// private readonly string _registryName; +// private readonly HttpPipeline _pipeline; +// private readonly HttpPipeline _acrAuthPipeline; +// private readonly ClientDiagnostics _clientDiagnostics; +// private readonly ContainerRegistryRestClient _restClient; +// private readonly AuthenticationRestClient _acrAuthClient; +// private readonly ContainerRegistryBlobRestClient _blobRestClient; +// private readonly string _repositoryName; + +// public BicepRegistryBlobClient(Uri endpoint, TokenCredential credential, string repositoryName) : this(endpoint, credential, repositoryName, new ContainerRegistryClientOptions()) +// { +// } + +// public BicepRegistryBlobClient(Uri endpoint, TokenCredential credential, string repositoryName, ContainerRegistryClientOptions options) +// { +// Argument.AssertNotNull(endpoint, nameof(endpoint)); +// Argument.AssertNotNull(credential, nameof(credential)); +// Argument.AssertNotNull(options, nameof(options)); + +// _endpoint = endpoint; +// _registryName = endpoint.Host.Split('.')[0]; +// _repositoryName = repositoryName; +// _clientDiagnostics = new ClientDiagnostics(options); + +// _acrAuthPipeline = HttpPipelineBuilder.Build(options); +// _acrAuthClient = new AuthenticationRestClient(_clientDiagnostics, _acrAuthPipeline, endpoint.AbsoluteUri); + +// _pipeline = HttpPipelineBuilder.Build(options, new ContainerRegistryChallengeAuthenticationPolicy(credential, options.AuthenticationScope, _acrAuthClient)); +// _restClient = new ContainerRegistryRestClient(_clientDiagnostics, _pipeline, _endpoint.AbsoluteUri); +// _blobRestClient = new ContainerRegistryBlobRestClient(_clientDiagnostics, _pipeline, _endpoint.AbsoluteUri); +// } + +// // allows mocking +// protected BicepRegistryBlobClient() +// { +// } + +// public virtual async Task> UploadBlobAsync(Stream stream, CancellationToken cancellationToken = default) +// { +// string digest = DigestHelper.ComputeDigest(DigestHelper.AlgorithmIdentifierSha256, stream); + +// ResponseWithHeaders startUploadResult = +// await _blobRestClient.StartUploadAsync(_repositoryName, cancellationToken).ConfigureAwait(false); + +// stream.Position = 0; +// ResponseWithHeaders uploadChunkResult = +// await _blobRestClient.UploadChunkAsync(startUploadResult.Headers.Location, stream, cancellationToken).ConfigureAwait(false); + +// ResponseWithHeaders completeUploadResult = +// await _blobRestClient.CompleteUploadAsync(digest, uploadChunkResult.Headers.Location, null, cancellationToken).ConfigureAwait(false); + +// return Response.FromValue(new UploadBlobResult(), completeUploadResult.GetRawResponse()); +// } + +// public virtual async Task> UploadManifestAsync(Stream stream, UploadManifestOptions options = default, CancellationToken cancellationToken = default) +// { +// options ??= new UploadManifestOptions(); + +// string reference = options.Tag ?? DigestHelper.ComputeDigest(DigestHelper.AlgorithmIdentifierSha256, stream); +// stream.Position = 0; +// ResponseWithHeaders response = await _restClient.CreateManifestAsync(_repositoryName, reference, options.MediaType, stream, cancellationToken).ConfigureAwait(false); + +// return Response.FromValue(new UploadManifestResult(), response.GetRawResponse()); +// } + +// public virtual async Task> DownloadManifestAsync(string reference, DownloadManifestOptions options = default, CancellationToken cancellationToken = default) +// { +// options ??= new DownloadManifestOptions(); + +// Response manifestWrapper = await _restClient.GetManifestAsync(_repositoryName, reference, options.MediaType.ToSerialString(), cancellationToken).ConfigureAwait(false); + +// manifestWrapper.GetRawResponse().Headers.TryGetValue("Docker-Content-Digest", out var digest); + +// Stream stream = manifestWrapper.GetRawResponse().ContentStream; +// stream.Position = 0; + +// return Response.FromValue(new DownloadManifestResult(digest, stream), manifestWrapper.GetRawResponse()); +// } + +// public virtual async Task> DownloadBlobAsync(string digest, DownloadBlobOptions options = default, CancellationToken cancellationToken = default) +// { +// options ??= new DownloadBlobOptions(); +// ResponseWithHeaders blobResult = await _blobRestClient.GetBlobAsync(_repositoryName, digest, cancellationToken).ConfigureAwait(false); +// return Response.FromValue(new DownloadBlobResult(digest, blobResult.Value), blobResult.GetRawResponse()); +// } +// } +//} diff --git a/src/Bicep.Core/Bicep.Core.csproj b/src/Bicep.Core/Bicep.Core.csproj index c3435e43b01..5e03e3b3d9e 100644 --- a/src/Bicep.Core/Bicep.Core.csproj +++ b/src/Bicep.Core/Bicep.Core.csproj @@ -29,7 +29,7 @@ - + diff --git a/src/Bicep.Core/Registry/AzureContainerRegistryManager.cs b/src/Bicep.Core/Registry/AzureContainerRegistryManager.cs index 7bea5c536cd..579b4a92436 100644 --- a/src/Bicep.Core/Registry/AzureContainerRegistryManager.cs +++ b/src/Bicep.Core/Registry/AzureContainerRegistryManager.cs @@ -5,14 +5,14 @@ using Azure.Core; using Bicep.Core.Modules; using Bicep.Core.Registry.Oci; -using Bicep.Core.RegistryClient; -using Bicep.Core.RegistryClient.Models; +using Azure.Containers.ContainerRegistry.Specialized; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using UploadManifestOptions = Bicep.Core.RegistryClient.UploadManifestOptions; +using Azure.Containers.ContainerRegistry; +using Azure.Identity; namespace Bicep.Core.Registry { @@ -20,7 +20,6 @@ public class AzureContainerRegistryManager { // media types are case-insensitive (they are lowercase by convention only) private const StringComparison MediaTypeComparison = StringComparison.OrdinalIgnoreCase; - private const StringComparison DigestComparison = StringComparison.Ordinal; private readonly string artifactCachePath; private readonly TokenCredential tokenCredential; @@ -33,7 +32,7 @@ public AzureContainerRegistryManager(string artifactCachePath, TokenCredential t this.clientFactory = clientFactory; } - public async Task PullArtifactsync(OciArtifactModuleReference moduleReference) + public async Task PullArtifactAsync(OciArtifactModuleReference moduleReference) { try { @@ -61,30 +60,28 @@ public async Task PushArtifactAsync(OciArtifactModuleReference moduleReference, var blobClient = this.CreateBlobClient(moduleReference); + var manifest = new OciManifest(); config.ResetStream(); var configDescriptor = DescriptorFactory.CreateDescriptor(algorithmIdentifier, config); config.ResetStream(); var configUploadResult = await blobClient.UploadBlobAsync(config.Stream); + manifest.Config = configDescriptor; - var layerDescriptors = new List(layers.Length); + var layerDescriptors = new List(layers.Length); foreach (var layer in layers) { layer.ResetStream(); var layerDescriptor = DescriptorFactory.CreateDescriptor(algorithmIdentifier, layer); - layerDescriptors.Add(layerDescriptor); layer.ResetStream(); var layerUploadResult = await blobClient.UploadBlobAsync(layer.Stream); - } - var manifest = new OciManifest(2, configDescriptor, layerDescriptors); - using var manifestStream = new MemoryStream(); - OciManifestSerialization.SerializeManifest(manifestStream, manifest); + manifest.Layers.Add(layerDescriptor); + } - manifestStream.Position = 0; // BUG: the client closes the stream :( - var manifestUploadResult = await blobClient.UploadManifestAsync(manifestStream, new UploadManifestOptions(ContentType.ApplicationVndOciImageManifestV1Json, moduleReference.Tag)); + var manifestUploadResult = await blobClient.UploadManifestAsync(manifest, new UploadManifestOptions() { Tag = moduleReference.Tag }); } public string GetLocalPackageDirectory(OciArtifactModuleReference reference) @@ -108,7 +105,7 @@ public string GetLocalPackageDirectory(OciArtifactModuleReference reference) private static Uri GetRegistryUri(OciArtifactModuleReference moduleReference) => new Uri($"https://{moduleReference.Registry}"); - private BicepRegistryBlobClient CreateBlobClient(OciArtifactModuleReference moduleReference) => this.clientFactory.CreateBlobClient(GetRegistryUri(moduleReference), moduleReference.Repository, this.tokenCredential); + private ContainerRegistryBlobClient CreateBlobClient(OciArtifactModuleReference moduleReference) => this.clientFactory.CreateBlobClient(GetRegistryUri(moduleReference), moduleReference.Repository, this.tokenCredential); private static void CreateModuleDirectory(string modulePath) { @@ -135,12 +132,14 @@ private async Task PullArtifactInternalAsync(OciArtifactModuleReference moduleRe await ProcessManifest(client, manifest, modulePath); } - private static async Task DownloadManifestAsync(OciArtifactModuleReference moduleReference, BicepRegistryBlobClient client) + private static async Task DownloadManifestAsync(OciArtifactModuleReference moduleReference, ContainerRegistryBlobClient client) { Response manifestResponse; try { - manifestResponse = await client.DownloadManifestAsync(moduleReference.Tag, new DownloadManifestOptions(ContentType.ApplicationVndOciImageManifestV1Json)); + ContainerRegistryClient registryClient = new ContainerRegistryClient(new Uri("example.azurecr.io"), new DefaultAzureCredential()); + var tagProperties = registryClient.GetArtifact(moduleReference.Repository, moduleReference.Tag).GetTagProperties(moduleReference.Tag); + manifestResponse = await client.DownloadManifestAsync(tagProperties.Value.Digest); } catch(RequestFailedException exception) when (exception.Status == 404) { @@ -148,35 +147,13 @@ private static async Task DownloadManifestAsync(OciArtifactModuleRe throw new AcrManagerException("The module does not exist in the registry.", exception); } - ValidateManifestResponse(manifestResponse); - - // the SDK doesn't expose all the manifest properties we need - // so we need to deserialize the manifest ourselves to get everything - var stream = manifestResponse.Value.Content; - stream.Position = 0; - return DeserializeManifest(stream); - } - - private static void ValidateManifestResponse(Response manifestResponse) - { - var digestFromRegistry = manifestResponse.Value.Digest; - - var stream = manifestResponse.Value.Content; - stream.Position = 0; - - // TODO: The registry may use a different digest algorithm - we need to handle that - string digestFromContent = DigestHelper.ComputeDigest(DigestHelper.AlgorithmIdentifierSha256, stream); - - if (!string.Equals(digestFromRegistry, digestFromContent, DigestComparison)) - { - throw new AcrManagerException($"There is a mismatch in the manifest digests. Received content digest = {digestFromContent}, Digest in registry response = {digestFromRegistry}"); - } + return manifestResponse.Value.Manifest; } - private static async Task ProcessManifest(BicepRegistryBlobClient client, OciManifest manifest, string modulePath) + private static async Task ProcessManifest(ContainerRegistryBlobClient client, OciManifest manifest, string modulePath) { ProcessConfig(manifest.Config); - if (manifest.Layers.Length != 1) + if (manifest.Layers.Count != 1) { throw new InvalidModuleException("Expected a single layer in the OCI artifact."); } @@ -186,26 +163,7 @@ private static async Task ProcessManifest(BicepRegistryBlobClient client, OciMan await ProcessLayer(client, layer, modulePath); } - private static void ValidateBlobResponse(Response blobResponse, OciDescriptor descriptor) - { - var stream = blobResponse.Value.Content; - - if(descriptor.Size != stream.Length) - { - throw new InvalidModuleException($"Expected blob size of {descriptor.Size} bytes but received {stream.Length} bytes from the registry."); - } - - stream.Position = 0; - string digestFromContents = DigestHelper.ComputeDigest(DigestHelper.AlgorithmIdentifierSha256, stream); - stream.Position = 0; - - if(!string.Equals(descriptor.Digest, digestFromContents, DigestComparison)) - { - throw new InvalidModuleException($"There is a mismatch in the layer digests. Received content digest = {digestFromContents}, Requested digest = {descriptor.Digest}"); - } - } - - private static async Task ProcessLayer(BicepRegistryBlobClient client, OciDescriptor layer, string modulePath) + private static async Task ProcessLayer(ContainerRegistryBlobClient client, OciBlobDescriptor layer, string modulePath) { if(!string.Equals(layer.MediaType, BicepMediaTypes.BicepModuleLayerV1Json, MediaTypeComparison)) { @@ -222,15 +180,13 @@ private static async Task ProcessLayer(BicepRegistryBlobClient client, OciDescri throw new InvalidModuleException($"Module manifest refers to a non-existent blob with digest \"{layer.Digest}\".", exception); } - ValidateBlobResponse(blobResult, layer); - var layerPath = Path.Combine(modulePath, "main.json"); using var fileStream = new FileStream(layerPath, FileMode.Create); await blobResult.Value.Content.CopyToAsync(fileStream); } - private static void ProcessConfig(OciDescriptor config) + private static void ProcessConfig(OciBlobDescriptor config) { // media types are case insensitive if(!string.Equals(config.MediaType, BicepMediaTypes.BicepModuleConfigV1, MediaTypeComparison)) @@ -244,18 +200,18 @@ private static void ProcessConfig(OciDescriptor config) } } - private static OciManifest DeserializeManifest(Stream stream) - { - try - { - - return OciManifestSerialization.DeserializeManifest(stream); - } - catch(Exception exception) - { - throw new InvalidModuleException("Unable to deserialize the module manifest.", exception); - } - } + //private static OciManifest DeserializeManifest(Stream stream) + //{ + // try + // { + + // return OciManifestSerialization.DeserializeManifest(stream); + // } + // catch(Exception exception) + // { + // throw new InvalidModuleException("Unable to deserialize the module manifest.", exception); + // } + //} private class AcrManagerException : Exception { diff --git a/src/Bicep.Core/Registry/ContainerRegistryClientFactory.cs b/src/Bicep.Core/Registry/ContainerRegistryClientFactory.cs index 333b3c789e5..91be3b2de81 100644 --- a/src/Bicep.Core/Registry/ContainerRegistryClientFactory.cs +++ b/src/Bicep.Core/Registry/ContainerRegistryClientFactory.cs @@ -1,15 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using Azure.Containers.ContainerRegistry; +using Azure.Containers.ContainerRegistry.Specialized; using Azure.Core; -using Bicep.Core.RegistryClient; using System; namespace Bicep.Core.Registry { public class ContainerRegistryClientFactory : IContainerRegistryClientFactory { - public BicepRegistryBlobClient CreateBlobClient(Uri registryUri, string repository, TokenCredential credential) + public ContainerRegistryBlobClient CreateBlobClient(Uri registryUri, string repository, TokenCredential credential) { var options = new ContainerRegistryClientOptions(); diff --git a/src/Bicep.Core/Registry/IContainerRegistryClientFactory.cs b/src/Bicep.Core/Registry/IContainerRegistryClientFactory.cs index 2127f715c5a..c84538d14c9 100644 --- a/src/Bicep.Core/Registry/IContainerRegistryClientFactory.cs +++ b/src/Bicep.Core/Registry/IContainerRegistryClientFactory.cs @@ -2,7 +2,7 @@ // Licensed under the MIT License. using Azure.Core; -using Bicep.Core.RegistryClient; +using Azure.Containers.ContainerRegistry.Specialized; using System; namespace Bicep.Core.Registry @@ -13,6 +13,6 @@ namespace Bicep.Core.Registry /// This exists because we need to inject mock clients in integration tests and because the real client constructor requires parameters. public interface IContainerRegistryClientFactory { - BicepRegistryBlobClient CreateBlobClient(Uri registryUri, string repository, TokenCredential credential); + ContainerRegistryBlobClient CreateBlobClient(Uri registryUri, string repository, TokenCredential credential); } } diff --git a/src/Bicep.Core/Registry/Oci/DescriptorFactory.cs b/src/Bicep.Core/Registry/Oci/DescriptorFactory.cs index 6036f162b84..6c69caabcf4 100644 --- a/src/Bicep.Core/Registry/Oci/DescriptorFactory.cs +++ b/src/Bicep.Core/Registry/Oci/DescriptorFactory.cs @@ -5,6 +5,7 @@ using System.Security.Cryptography; using System; using System.Text; +using Azure.Containers.ContainerRegistry.Specialized; namespace Bicep.Core.Registry.Oci { @@ -13,14 +14,19 @@ public static class DescriptorFactory public const string AlgorithmIdentifierSha256 = "sha256"; public const string AlgorithmIdentifierSha512 = "sha512"; - public static OciDescriptor CreateDescriptor(string algorithmIdentifier, StreamDescriptor streamDescriptor) + public static OciBlobDescriptor CreateDescriptor(string algorithmIdentifier, StreamDescriptor streamDescriptor) { var digest = ComputeDigest(algorithmIdentifier, streamDescriptor.Stream); - return new OciDescriptor(streamDescriptor.MediaType, digest, streamDescriptor.Stream.Length, streamDescriptor.Annotations); + return new OciBlobDescriptor() + { + MediaType = streamDescriptor.MediaType, + Digest = digest, + Size = streamDescriptor.Stream.Length + }; } - public static string ComputeDigest(string algorithmIdentifier, Stream stream) + private static string ComputeDigest(string algorithmIdentifier, Stream stream) { using var algorithm = CreateHashAlgorithm(algorithmIdentifier); var hashValue = algorithm.ComputeHash(stream); diff --git a/src/Bicep.Core/Registry/Oci/OciDescriptor.cs b/src/Bicep.Core/Registry/Oci/OciDescriptor.cs deleted file mode 100644 index 7a061443476..00000000000 --- a/src/Bicep.Core/Registry/Oci/OciDescriptor.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace Bicep.Core.Registry.Oci -{ - public class OciDescriptor - { - public OciDescriptor(string mediaType, string digest, long size, IDictionary? annotations) - { - this.MediaType = mediaType; - this.Digest = digest; - this.Size = size; - this.Annotations = annotations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty; - } - - public string MediaType { get; } - - public string Digest { get; } - - public long Size { get; } - - // TODO: Skip serialization for empty annotations - public ImmutableDictionary Annotations { get; } - } -} diff --git a/src/Bicep.Core/Registry/Oci/OciManifest.cs b/src/Bicep.Core/Registry/Oci/OciManifest.cs deleted file mode 100644 index bf39f246468..00000000000 --- a/src/Bicep.Core/Registry/Oci/OciManifest.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Collections.Generic; -using System.Collections.Immutable; - -namespace Bicep.Core.Registry.Oci -{ - public class OciManifest - { - // TODO: Add top-level annotations - public OciManifest(int schemaVersion, OciDescriptor config, IEnumerable layers) - { - this.SchemaVersion = schemaVersion; - this.Config = config; - this.Layers = layers.ToImmutableArray(); - } - - public int SchemaVersion { get; } - - public OciDescriptor Config { get; } - - public ImmutableArray Layers { get; } - } -} diff --git a/src/Bicep.Core/Registry/Oci/OciManifestSerialization.cs b/src/Bicep.Core/Registry/Oci/OciManifestSerialization.cs deleted file mode 100644 index 73f377185d9..00000000000 --- a/src/Bicep.Core/Registry/Oci/OciManifestSerialization.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Newtonsoft.Json.Serialization; -using Newtonsoft.Json; -using System; -using System.IO; -using System.Text; - -namespace Bicep.Core.Registry.Oci -{ - public class OciManifestSerialization - { - private static readonly Encoding ManifestEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); - - public static OciManifest DeserializeManifest(Stream stream) - { - using var streamReader = new StreamReader(stream, ManifestEncoding, detectEncodingFromByteOrderMarks: true, bufferSize: -1, leaveOpen: true); - using var reader = new JsonTextReader(streamReader); - - var serializer = CreateSerializer(); - var manifest = serializer.Deserialize(reader); - - return manifest ?? throw new InvalidOperationException("Manifest is null"); - } - - public static void SerializeManifest(Stream stream, OciManifest manifest) - { - using var streamWriter = new StreamWriter(stream, ManifestEncoding, bufferSize: -1, leaveOpen: true); - using var writer = new JsonTextWriter(streamWriter); - - var serializer = CreateSerializer(); - serializer.Serialize(writer, manifest); - } - - private static JsonSerializer CreateSerializer() - { - return JsonSerializer.Create(new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - NullValueHandling = NullValueHandling.Ignore, - TypeNameHandling = TypeNameHandling.None - }); - } - } -} diff --git a/src/Bicep.Core/Registry/Oci/StreamDescriptor.cs b/src/Bicep.Core/Registry/Oci/StreamDescriptor.cs index d490ccc4983..23fa9536a31 100644 --- a/src/Bicep.Core/Registry/Oci/StreamDescriptor.cs +++ b/src/Bicep.Core/Registry/Oci/StreamDescriptor.cs @@ -9,19 +9,16 @@ namespace Bicep.Core.Registry.Oci { public class StreamDescriptor { - public StreamDescriptor(Stream stream, string mediaType, IDictionary? annotations = null) + public StreamDescriptor(Stream stream, string mediaType) { Stream = stream; MediaType = mediaType; - Annotations = annotations?.ToImmutableDictionary() ?? ImmutableDictionary.Empty; } public Stream Stream { get; } public string MediaType { get; } - public ImmutableDictionary Annotations { get; } - public void ResetStream() => this.Stream.Position = 0; } } diff --git a/src/Bicep.Core/Registry/OciModuleRegistry.cs b/src/Bicep.Core/Registry/OciModuleRegistry.cs index f5cc983394b..44da70c8114 100644 --- a/src/Bicep.Core/Registry/OciModuleRegistry.cs +++ b/src/Bicep.Core/Registry/OciModuleRegistry.cs @@ -57,7 +57,7 @@ public bool IsModuleRestoreRequired(ModuleReference reference) { using (var timer = new ExecutionTimer($"Restore module {reference.FullyQualifiedReference}")) { - var result = await this.client.PullArtifactsync(reference); + var result = await this.client.PullArtifactAsync(reference); if (!result.Success) {