/// <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; } }
internal OciManifest(OciBlobDescriptor config, IList <OciBlobDescriptor> layers, OciAnnotations annotations, int?schemaVersion) { Config = config; Layers = layers; Annotations = annotations; SchemaVersion = schemaVersion; }
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)); }