/// <summary>
        /// Upload an artifact blob.
        /// </summary>
        /// <param name="stream">The stream containing the blob data.</param>
        /// <param name="cancellationToken"> The cancellation token to use. </param>
        /// <returns></returns>
        public virtual async Task <Response <UploadBlobResult> > UploadBlobAsync(Stream stream, CancellationToken cancellationToken = default)
        {
            Argument.AssertNotNull(stream, nameof(stream));

            using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(ContainerRegistryBlobClient)}.{nameof(UploadBlob)}");
            scope.Start();
            try
            {
                string digest = OciBlobDescriptor.ComputeDigest(stream);

                ResponseWithHeaders <ContainerRegistryBlobStartUploadHeaders> startUploadResult =
                    await _blobRestClient.StartUploadAsync(_repositoryName, cancellationToken).ConfigureAwait(false);

                ResponseWithHeaders <ContainerRegistryBlobUploadChunkHeaders> uploadChunkResult =
                    await _blobRestClient.UploadChunkAsync(startUploadResult.Headers.Location, stream, cancellationToken).ConfigureAwait(false);

                ResponseWithHeaders <ContainerRegistryBlobCompleteUploadHeaders> completeUploadResult =
                    await _blobRestClient.CompleteUploadAsync(digest, uploadChunkResult.Headers.Location, null, cancellationToken).ConfigureAwait(false);

                return(Response.FromValue(new UploadBlobResult(completeUploadResult.Headers.DockerContentDigest), completeUploadResult.GetRawResponse()));
            }
            catch (Exception e)
            {
                scope.Failed(e);
                throw;
            }
        }
        /// <summary>
        /// Uploads a manifest for an OCI artifact.
        /// </summary>
        /// <param name="manifestStream">The <see cref="Stream"/> manifest to upload.</param>
        /// <param name="options">Options for configuring the upload operation.</param>
        /// <param name="cancellationToken"> The cancellation token to use. </param>
        /// <returns></returns>
        public virtual async Task <Response <UploadManifestResult> > UploadManifestAsync(Stream manifestStream, UploadManifestOptions options = default, CancellationToken cancellationToken = default)
        {
            Argument.AssertNotNull(manifestStream, nameof(manifestStream));

            options ??= new UploadManifestOptions();

            using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(ContainerRegistryBlobClient)}.{nameof(UploadManifest)}");
            scope.Start();
            try
            {
                using Stream stream = new MemoryStream();
                await manifestStream.CopyToAsync(stream).ConfigureAwait(false);

                manifestStream.Position = 0;
                stream.Position         = 0;

                string tagOrDigest = options.Tag ?? OciBlobDescriptor.ComputeDigest(manifestStream);
                ResponseWithHeaders <ContainerRegistryCreateManifestHeaders> response = await _restClient.CreateManifestAsync(_repositoryName, tagOrDigest, manifestStream, ManifestMediaType.OciManifest.ToString(), cancellationToken).ConfigureAwait(false);

                if (!ValidateDigest(stream, response.Headers.DockerContentDigest))
                {
                    throw _clientDiagnostics.CreateRequestFailedException(response,
                                                                          new ResponseError(null, "The digest in the response does not match the digest of the uploaded manifest."));
                }

                return(Response.FromValue(new UploadManifestResult(response.Headers.DockerContentDigest), response.GetRawResponse()));
            }
            catch (Exception e)
            {
                scope.Failed(e);
                throw;
            }
        }
        /// <summary>
        /// Uploads a manifest for an OCI Artifact.
        /// </summary>
        /// <param name="manifest">The manifest to upload.</param>
        /// <param name="options">Options for configuring the upload operation.</param>
        /// <param name="cancellationToken"> The cancellation token to use. </param>
        /// <returns></returns>
        public virtual Response <UploadManifestResult> UploadManifest(OciManifest manifest, UploadManifestOptions options = default, CancellationToken cancellationToken = default)
        {
            Argument.AssertNotNull(manifest, nameof(manifest));

            options ??= new UploadManifestOptions();

            using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(ContainerRegistryBlobClient)}.{nameof(UploadManifest)}");
            scope.Start();
            try
            {
                Stream manifestStream = SerializeManifest(manifest);
                string manifestDigest = OciBlobDescriptor.ComputeDigest(manifestStream);
                string tagOrDigest    = options.Tag ?? manifestDigest;

                ResponseWithHeaders <ContainerRegistryCreateManifestHeaders> response = _restClient.CreateManifest(_repositoryName, tagOrDigest, manifestStream, ManifestMediaType.OciManifest.ToString(), cancellationToken);

                if (!manifestDigest.Equals(response.Headers.DockerContentDigest, StringComparison.Ordinal))
                {
                    throw _clientDiagnostics.CreateRequestFailedException(response,
                                                                          new ResponseError(null, "The digest in the response does not match the digest of the uploaded manifest."));
                }

                return(Response.FromValue(new UploadManifestResult(response.Headers.DockerContentDigest), response.GetRawResponse()));
            }
            catch (Exception e)
            {
                scope.Failed(e);
                throw;
            }
        }
Example #4
0
 internal OciManifest(OciBlobDescriptor config, IList <OciBlobDescriptor> layers, OciAnnotations annotations, int?schemaVersion)
 {
     Config        = config;
     Layers        = layers;
     Annotations   = annotations;
     SchemaVersion = schemaVersion;
 }
Example #5
0
        internal static OciManifest DeserializeOciManifest(JsonElement element)
        {
            Optional <OciBlobDescriptor>          config      = default;
            Optional <IList <OciBlobDescriptor> > layers      = default;
            Optional <OciAnnotations>             annotations = default;
            Optional <int> schemaVersion = default;

            foreach (var property in element.EnumerateObject())
            {
                if (property.NameEquals("config"))
                {
                    if (property.Value.ValueKind == JsonValueKind.Null)
                    {
                        property.ThrowNonNullablePropertyIsNull();
                        continue;
                    }
                    config = OciBlobDescriptor.DeserializeOciBlobDescriptor(property.Value);
                    continue;
                }
                if (property.NameEquals("layers"))
                {
                    if (property.Value.ValueKind == JsonValueKind.Null)
                    {
                        property.ThrowNonNullablePropertyIsNull();
                        continue;
                    }
                    List <OciBlobDescriptor> array = new List <OciBlobDescriptor>();
                    foreach (var item in property.Value.EnumerateArray())
                    {
                        array.Add(OciBlobDescriptor.DeserializeOciBlobDescriptor(item));
                    }
                    layers = array;
                    continue;
                }
                if (property.NameEquals("annotations"))
                {
                    if (property.Value.ValueKind == JsonValueKind.Null)
                    {
                        annotations = null;
                        continue;
                    }
                    annotations = OciAnnotations.DeserializeOciAnnotations(property.Value);
                    continue;
                }
                if (property.NameEquals("schemaVersion"))
                {
                    if (property.Value.ValueKind == JsonValueKind.Null)
                    {
                        property.ThrowNonNullablePropertyIsNull();
                        continue;
                    }
                    schemaVersion = property.Value.GetInt32();
                    continue;
                }
            }
            return(new OciManifest(config.Value, Optional.ToList(layers), annotations.Value, Optional.ToNullable(schemaVersion)));
        }
        private static bool ValidateDigest(Stream content, string digest)
        {
            // Validate that the file content did not change in transmission from the registry.

            // TODO: The registry may use a different digest algorithm - we may need to handle that
            string contentDigest = OciBlobDescriptor.ComputeDigest(content);

            content.Position = 0;
            return(digest.Equals(contentDigest, StringComparison.OrdinalIgnoreCase));
        }