/// <inheritdoc/> public override async Task AddAsync(DeveloperDisk disk, CancellationToken cancellationToken) { if (disk == null) { throw new ArgumentNullException(nameof(disk)); } var factory = new DeveloperDiskRegistryImageFactory(); (var manifest, var configStream, var layerStream) = await factory.CreateRegistryImageAsync(disk, cancellationToken).ConfigureAwait(false); using (layerStream) using (configStream) using (Stream manifestStream = new MemoryStream()) using (var client = await this.clientFactory.CreateAsync(cancellationToken).ConfigureAwait(false)) { // Send over the base layer Descriptor layerDescriptor = await Descriptor.CreateAsync(layerStream, "application/vnd.oci.image.layer.nondistributable.v1.tar", cancellationToken); await client.PushBlobAsync(this.repositoryName, layerStream, layerDescriptor.Digest, cancellationToken).ConfigureAwait(false); // Prepare and send the configuration var configDescriptor = await Descriptor.CreateAsync(configStream, "application/vnd.oci.image.config.v1+json", cancellationToken).ConfigureAwait(false); await client.PushBlobAsync(this.repositoryName, configStream, configDescriptor.Digest, cancellationToken).ConfigureAwait(false); // Prepare and send the manifest manifestStream.Write(JsonSerializer.SerializeToUtf8Bytes(manifest)); await client.PushManifestAsync(this.repositoryName, disk.Version.ProductVersion.ToString(), manifestStream, cancellationToken); } }
public void Dispose_Works() { var disk = new DeveloperDisk(); disk.Dispose(); Assert.True(disk.IsDisposed); disk = new DeveloperDisk(); disk.Image = new MemoryStream(); disk.Dispose(); Assert.Throws <ObjectDisposedException>(() => disk.Image.Length); }
private static async Task <Stream> CreateLayerAsync(DeveloperDisk developerDisk, CancellationToken cancellationToken) { MemoryStream stream = new MemoryStream(); var writer = new TarWriter(stream); var versionBytes = Encoding.UTF8.GetBytes(developerDisk.Version.ToDictionary().ToXmlPropertyList()); var fileMode = LinuxFileMode.S_IFREG | LinuxFileMode.S_IRUSR | LinuxFileMode.S_IRGRP | LinuxFileMode.S_IROTH; await writer.AddFileAsync("SystemVersion.plist", fileMode, developerDisk.CreationTime, new MemoryStream(versionBytes), cancellationToken).ConfigureAwait(false); await writer.AddFileAsync("DeveloperDiskImage.dmg", fileMode, developerDisk.CreationTime, developerDisk.Image, cancellationToken).ConfigureAwait(false); await writer.AddFileAsync("DeveloperDiskImage.dmg.signature", fileMode, developerDisk.CreationTime, new MemoryStream(developerDisk.Signature), cancellationToken).ConfigureAwait(false); await writer.WriteTrailerAsync(cancellationToken).ConfigureAwait(false); stream.Seek(0, SeekOrigin.Begin); return(stream); }
public async Task AddAsync_Works_Async() { var disk = new DeveloperDisk() { Image = new MemoryStream(Encoding.UTF8.GetBytes("Hello, world!")), Signature = new byte[] { 1, 2, 3, 4 }, Version = new SystemVersion() { BuildID = new Guid("5abf1921-e3e3-4bfc-94c5-6c6805f23815"), ProductBuildVersion = new AppleVersion(1, 'A', 1), ProductCopyright = "Quamotion bv", ProductName = "Kaponata", ProductVersion = new Version(1, 0), }, CreationTime = new DateTimeOffset(2000, 1, 1, 0, 0, 0, 0, TimeSpan.Zero), }; var registryClient = new Mock <ImageRegistryClient>(MockBehavior.Strict); registryClient .Setup(c => c.PushBlobAsync("devimg", It.IsAny <Stream>(), "sha256:caad51da051d60541b2544a5984bdfc44ebceb894069007ad63b2ec073c911d0", default)) .Returns(Task.FromResult(new Uri("http://localhost:5000/v2/devimg/blobs/sha256:caad51da051d60541b2544a5984bdfc44ebceb894069007ad63b2ec073c911d0"))) .Verifiable(); registryClient .Setup(c => c.PushBlobAsync("devimg", It.IsAny <Stream>(), "sha256:30c5c9a3aeb6eb237abf743b7657657580657e3548e82f6d2ce6e6e4660a9878", default)) .Returns(Task.FromResult(new Uri("http://localhost:5000/v2/devimg/blobs/sha256:caad51da051d60541b2544a5984bdfc44ebceb894069007ad63b2ec073c911d0"))) .Verifiable(); registryClient .Setup(c => c.PushManifestAsync("devimg", "1.0", It.IsAny <Stream>(), default)) .Returns(Task.FromResult(new Uri("http://localhost:5000/v2/devimg/manifests/1.0"))) .Verifiable(); var factory = new Mock <ImageRegistryClientFactory>(MockBehavior.Strict); factory.Setup(f => f.CreateAsync(default)).ReturnsAsync(registryClient.Object);
public async Task Create_Works_Async() { var disk = new DeveloperDisk() { Image = new MemoryStream(Encoding.UTF8.GetBytes("Hello, world!")), Signature = new byte[] { 1, 2, 3, 4 }, Version = new SystemVersion() { BuildID = new Guid("5abf1921-e3e3-4bfc-94c5-6c6805f23815"), ProductBuildVersion = new AppleVersion(1, 'A', 1), ProductCopyright = "Quamotion bv", ProductName = "Kaponata", ProductVersion = new Version(1, 0), }, CreationTime = new DateTimeOffset(2000, 1, 1, 0, 0, 0, 0, TimeSpan.Zero), }; var factory = new DeveloperDiskRegistryImageFactory(); (var manifest, var configStream, var layerStream) = await factory.CreateRegistryImageAsync(disk, default).ConfigureAwait(false); Assert.NotNull(manifest); Assert.NotNull(configStream); Assert.Equal(0, configStream.Position); Assert.NotEqual(0, configStream.Length); Assert.NotNull(layerStream); Assert.Equal(0, layerStream.Position); Assert.NotEqual(0, layerStream.Length); var config = await JsonSerializer.DeserializeAsync <Image>(configStream, default).ConfigureAwait(false); // The RootFS is a Tar archive which contains the developer disk image, signature and a copy of the SystemVersion.plist file var reader = new TarReader(layerStream); (TarHeader? header, Stream entryStream) = await reader.ReadAsync(default).ConfigureAwait(false);
/// <summary> /// Asynchronously creates a registry image which contains a <see cref="DeveloperDisk"/>. /// </summary> /// <param name="developerDisk"> /// The <see cref="DeveloperDisk"/> for which to create the image. /// </param> /// <param name="cancellationToken"> /// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation. /// </param> /// <returns> /// A <see cref="Task"/> which represents the asynchronous operation, and, when completed, returns the image manifest, /// image configuration and RootFS layer for the registry image containing the devleoper disk image. /// </returns> public async Task <(Manifest manifest, Stream configStream, Stream layer)> CreateRegistryImageAsync(DeveloperDisk developerDisk, CancellationToken cancellationToken) { if (developerDisk == null) { throw new ArgumentNullException(nameof(developerDisk)); } var layer = await CreateLayerAsync(developerDisk, cancellationToken); var layerDescriptor = await Descriptor.CreateAsync(layer, "application/vnd.oci.image.layer.nondistributable.v1.tar", cancellationToken).ConfigureAwait(false); var config = CreateConfig(layerDescriptor.Digest); Stream configStream = new MemoryStream(); await JsonSerializer.SerializeAsync(configStream, config, cancellationToken : cancellationToken).ConfigureAwait(false); configStream.Seek(0, SeekOrigin.Begin); var configDescriptor = await Descriptor.CreateAsync(configStream, "application/vnd.oci.image.config.v1+json", cancellationToken).ConfigureAwait(false); var manifest = CreateManifest(configDescriptor, layerDescriptor, developerDisk); return(manifest, configStream, layer); }
private static Manifest CreateManifest(Descriptor configDescriptor, Descriptor layerDescriptor, DeveloperDisk developerDisk) { return(new Manifest() { SchemaVersion = 2, Config = configDescriptor, Layers = new Descriptor[] { layerDescriptor, }, Annotations = new Dictionary <string, string>() { { nameof(SystemVersion.BuildID), developerDisk.Version.BuildID !.ToString() },
/// <inheritdoc/> public override async Task <DeveloperDisk?> GetAsync(Version version, CancellationToken cancellationToken) { if (version == null) { throw new ArgumentNullException(nameof(version)); } using (var client = await this.clientFactory.CreateAsync(cancellationToken).ConfigureAwait(false)) { var manifest = await client.GetManifestAsync(this.repositoryName, version.ToString(), cancellationToken).ConfigureAwait(false); if (manifest == null) { return(null); } // We should perform some sanity checks here. // Skip the config for now; although it is stored as a regular blob. // var config = await this.client.GetBlobAsync using (var layer = await client.GetBlobAsync(this.repositoryName, manifest.Layers[0].Digest, cancellationToken).ConfigureAwait(false)) { var disk = new DeveloperDisk(); TarReader reader = new TarReader(layer); TarHeader?header; Stream? entryStream; while (((header, entryStream) = await reader.ReadAsync(cancellationToken).ConfigureAwait(false)).header != null) { var fileName = header !.Value.FileName; switch (fileName) { case "DeveloperDiskImage.dmg": disk.Image = new MemoryStream(); disk.CreationTime = header.Value.LastModified; await entryStream !.CopyToAsync(disk.Image, cancellationToken).ConfigureAwait(false); disk.Image.Seek(0, SeekOrigin.Begin); break; case "DeveloperDiskImage.dmg.signature": disk.Signature = new byte[header.Value.FileSize]; await entryStream !.ReadBlockAsync(disk.Signature, cancellationToken).ConfigureAwait(false); break; case "SystemVersion.plist": var data = new byte[header.Value.FileSize]; await entryStream !.ReadBlockAsync(data, cancellationToken).ConfigureAwait(false); var plist = (NSDictionary)PropertyListParser.Parse(data); disk.Version = new SystemVersion(); disk.Version.FromDictionary(plist); break; case "": break; default: throw new InvalidDataException(); } } return(disk); } } }