コード例 #1
0
        /**
         * Retrieves the cached base image.
         *
         * @return the cached image
         * @throws IOException when an I/O exception occurs
         * @throws CacheCorruptedException if the cache is corrupted
         * @throws LayerPropertyNotFoundException if adding image layers fails
         * @throws BadContainerConfigurationFormatException if the container configuration is in a bad
         *     format
         */
        private Image PullBaseImageOffline()
        {
            IImageReference           baseImage = buildConfiguration.GetBaseImageConfiguration().GetImage();
            Maybe <ManifestAndConfig> metadata  =
                buildConfiguration.GetBaseImageLayersCache().RetrieveMetadata(baseImage);

            if (!metadata.IsPresent())
            {
                throw new IOException(
                          "Cannot run Fib in offline mode; " + baseImage + " not found in local Fib cache");
            }

            IManifestTemplate manifestTemplate = metadata.Get().GetManifest();

            if (manifestTemplate is V21ManifestTemplate v21ManifestTemplate)
            {
                return(JsonToImageTranslator.ToImage(v21ManifestTemplate));
            }

            ContainerConfigurationTemplate configurationTemplate =
                metadata.Get().GetConfig().OrElseThrow(() => new InvalidOperationException());

            return(JsonToImageTranslator.ToImage(
                       (IBuildableManifestTemplate)manifestTemplate, configurationTemplate));
        }
コード例 #2
0
        public void TestToJson()
        {
            // Loads the expected JSON string.
            SystemPath jsonFile     = Paths.Get(TestResources.GetResource("core/json/containerconfig.json").ToURI());
            string     expectedJson = Encoding.UTF8.GetString(Files.ReadAllBytes(jsonFile));

            // Creates the JSON object to serialize.
            ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate
            {
                Created      = "1970-01-01T00:00:20Z",
                Architecture = "wasm",
                Os           = "js"
            };

            containerConfigJson.SetContainerEnvironment(new[] { "VAR1=VAL1", "VAR2=VAL2" });
            containerConfigJson.SetContainerEntrypoint(new[] { "some", "entrypoint", "command" });
            containerConfigJson.SetContainerCmd(new[] { "arg1", "arg2" });
            containerConfigJson.SetContainerHealthCheckTest(new[] { "CMD-SHELL", "/checkhealth" });
            containerConfigJson.SetContainerHealthCheckInterval(3000000000L);
            containerConfigJson.SetContainerHealthCheckTimeout(1000000000L);
            containerConfigJson.SetContainerHealthCheckStartPeriod(2000000000L);
            containerConfigJson.SetContainerHealthCheckRetries(3);
            containerConfigJson.SetContainerExposedPorts(
                new Dictionary <string, IDictionary <object, object> >
            {
                ["1000/tcp"] =
                    ImmutableDictionary.Create <object, object>(),
                ["2000/tcp"] =
                    ImmutableDictionary.Create <object, object>(),
                ["3000/udp"] =
                    ImmutableDictionary.Create <object, object>()
            }.ToImmutableSortedDictionary());
            containerConfigJson.SetContainerLabels(ImmutableDic.Of("key1", "value1", "key2", "value2"));
            containerConfigJson.SetContainerVolumes(
                ImmutableDic.Of <string, IDictionary <object, object> >(
                    "/var/job-result-data", ImmutableDictionary.Create <object, object>(), "/var/log/my-app-logs", ImmutableDictionary.Create <object, object>()));
            containerConfigJson.SetContainerWorkingDir("/some/workspace");
            containerConfigJson.SetContainerUser("tomcat");

            containerConfigJson.AddLayerDiffId(
                DescriptorDigest.FromDigest(
                    "sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad"));
            containerConfigJson.AddHistoryEntry(
                HistoryEntry.CreateBuilder()
                .SetCreationTimestamp(Instant.FromUnixTimeSeconds(0))
                .SetAuthor("Bazel")
                .SetCreatedBy("bazel build ...")
                .SetEmptyLayer(true)
                .Build());
            containerConfigJson.AddHistoryEntry(
                HistoryEntry.CreateBuilder()
                .SetCreationTimestamp(Instant.FromUnixTimeSeconds(20))
                .SetAuthor("Fib")
                .SetCreatedBy("fib")
                .Build());

            // Serializes the JSON object.
            Assert.AreEqual(expectedJson, JsonTemplateMapper.ToUtf8String(containerConfigJson));
        }
コード例 #3
0
 /**
  * Saves a manifest and container configuration for a V2.2 or OCI image.
  *
  * @param imageReference the image reference to save the manifest and container configuration for
  * @param manifestTemplate the V2.2 or OCI manifest
  * @param containerConfigurationTemplate the container configuration
  * @throws IOException if an I/O exception occurs
  */
 public async Task WriteMetadataAsync(
     IImageReference imageReference,
     IBuildableManifestTemplate manifestTemplate,
     ContainerConfigurationTemplate containerConfigurationTemplate)
 {
     await cacheStorageWriter.WriteMetadataAsync(
         imageReference, manifestTemplate, containerConfigurationTemplate).ConfigureAwait(false);
 }
コード例 #4
0
        public void TestFromJson()
        {
            // Loads the JSON string.
            SystemPath jsonFile = Paths.Get(TestResources.GetResource("core/json/containerconfig.json").ToURI());

            // Deserializes into a manifest JSON object.
            ContainerConfigurationTemplate containerConfigJson =
                JsonTemplateMapper.ReadJsonFromFile <ContainerConfigurationTemplate>(jsonFile);

            Assert.AreEqual("1970-01-01T00:00:20Z", containerConfigJson.Created);
            Assert.AreEqual("wasm", containerConfigJson.Architecture);
            Assert.AreEqual("js", containerConfigJson.Os);
            Assert.AreEqual(
                new[] { "VAR1=VAL1", "VAR2=VAL2" }, containerConfigJson.GetContainerEnvironment());
            Assert.AreEqual(
                new[] { "some", "entrypoint", "command" },
                containerConfigJson.GetContainerEntrypoint());
            Assert.AreEqual(new[] { "arg1", "arg2" }, containerConfigJson.GetContainerCmd());

            Assert.AreEqual(
                new[] { "CMD-SHELL", "/checkhealth" }, containerConfigJson.GetContainerHealthTest());
            Assert.IsNotNull(containerConfigJson.GetContainerHealthInterval());
            Assert.AreEqual(3000000000L, containerConfigJson.GetContainerHealthInterval().GetValueOrDefault());
            Assert.IsNotNull(containerConfigJson.GetContainerHealthTimeout());
            Assert.AreEqual(1000000000L, containerConfigJson.GetContainerHealthTimeout().GetValueOrDefault());
            Assert.IsNotNull(containerConfigJson.GetContainerHealthStartPeriod());
            Assert.AreEqual(
                2000000000L, containerConfigJson.GetContainerHealthStartPeriod().GetValueOrDefault());
            Assert.IsNotNull(containerConfigJson.GetContainerHealthRetries());
            Assert.AreEqual(3, containerConfigJson.GetContainerHealthRetries().GetValueOrDefault());

            Assert.AreEqual(
                ImmutableDic.Of("key1", "value1", "key2", "value2"),
                containerConfigJson.GetContainerLabels());
            Assert.AreEqual("/some/workspace", containerConfigJson.GetContainerWorkingDir());
            Assert.AreEqual(
                DescriptorDigest.FromDigest(
                    "sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad"),
                containerConfigJson.GetLayerDiffId(0));
            Assert.AreEqual(
                ImmutableArray.Create(
                    HistoryEntry.CreateBuilder()
                    .SetCreationTimestamp(Instant.FromUnixTimeSeconds(0))
                    .SetAuthor("Bazel")
                    .SetCreatedBy("bazel build ...")
                    .SetEmptyLayer(true)
                    .Build(),
                    HistoryEntry.CreateBuilder()
                    .SetCreationTimestamp(Instant.FromUnixTimeSeconds(20))
                    .SetAuthor("Fib")
                    .SetCreatedBy("fib")
                    .Build()),
                containerConfigJson.History);
        }
コード例 #5
0
        public void TestGetContainerConfiguration()
        {
            SetUp(ManifestFormat.V22);

            // Loads the expected JSON string.
            SystemPath jsonFile     = Paths.Get(TestResources.GetResource("core/json/containerconfig.json").ToURI());
            string     expectedJson = Encoding.UTF8.GetString(Files.ReadAllBytes(jsonFile));

            // Translates the image to the container configuration and writes the JSON string.
            ContainerConfigurationTemplate containerConfiguration = imageToJsonTranslator.GetContainerConfiguration();

            Assert.AreEqual(expectedJson, JsonTemplateMapper.ToUtf8String(containerConfiguration));
        }
コード例 #6
0
        /** Tests translation of image to {@link BuildableManifestTemplate}. */
        private async Task TestGetManifestAsync(
            ManifestFormat manifestTemplateClass, string translatedJsonFilename)
        {
            // Loads the expected JSON string.
            SystemPath jsonFile     = Paths.Get(TestResources.GetResource(translatedJsonFilename).ToURI());
            string     expectedJson = Encoding.UTF8.GetString(Files.ReadAllBytes(jsonFile));

            // Translates the image to the manifest and writes the JSON string.
            ContainerConfigurationTemplate containerConfiguration = imageToJsonTranslator.GetContainerConfiguration();
            BlobDescriptor blobDescriptor = await Digests.ComputeJsonDescriptorAsync(containerConfiguration).ConfigureAwait(false);

            IBuildableManifestTemplate manifestTemplate =
                imageToJsonTranslator.GetManifestTemplate(manifestTemplateClass, blobDescriptor);

            Assert.AreEqual(expectedJson, JsonTemplateMapper.ToUtf8String(manifestTemplate));
        }
コード例 #7
0
        /**
         * Gets a {@link BuildResult} from an {@link Image}.
         *
         * @param image the image
         * @param targetFormat the target format of the image
         * @return a new {@link BuildResult} with the image's digest and id
         * @throws IOException if writing the digest or container configuration fails
         */
        public static async Task <BuildResult> FromImageAsync(Image image, ManifestFormat targetFormat)
        {
            ImageToJsonTranslator          imageToJsonTranslator = new ImageToJsonTranslator(image);
            ContainerConfigurationTemplate configurationTemplate = imageToJsonTranslator.GetContainerConfiguration();
            BlobDescriptor containerConfigurationBlobDescriptor  =
                await Digests.ComputeJsonDescriptorAsync(configurationTemplate).ConfigureAwait(false);

            IBuildableManifestTemplate manifestTemplate =
                imageToJsonTranslator.GetManifestTemplate(
                    targetFormat, containerConfigurationBlobDescriptor);
            DescriptorDigest imageDigest =
                await Digests.ComputeJsonDigestAsync(manifestTemplate).ConfigureAwait(false);

            DescriptorDigest imageId = containerConfigurationBlobDescriptor.GetDigest();

            return(new BuildResult(imageDigest, imageId));
        }
コード例 #8
0
        public void TestRetrieveContainerConfiguration()
        {
            SystemPath cacheDirectory = temporaryFolder.NewFolder().ToPath();

            SetupCachedMetadataV22(cacheDirectory);

            CacheStorageFiles  cacheStorageFiles  = new CacheStorageFiles(cacheDirectory);
            CacheStorageReader cacheStorageReader = new CacheStorageReader(cacheStorageFiles);

            ContainerConfigurationTemplate configurationTemplate =
                cacheStorageReader
                .RetrieveMetadata(ImageReference.Of("test", "image", "tag"))
                .Get()
                .GetConfig()
                .Get();

            Assert.AreEqual("wasm", configurationTemplate.Architecture);
            Assert.AreEqual("js", configurationTemplate.Os);
        }
コード例 #9
0
ファイル: CacheStorageWriter.cs プロジェクト: tiaotiao97/jib
        /**
         * Saves the manifest and container configuration for a V2.2 or OCI image.
         *
         * @param imageReference the image reference to store the metadata for
         * @param manifestTemplate the manifest
         * @param containerConfiguration the container configuration
         */
        public async Task WriteMetadataAsync(
            IImageReference imageReference,
            IBuildableManifestTemplate manifestTemplate,
            ContainerConfigurationTemplate containerConfiguration)
        {
            manifestTemplate = manifestTemplate ?? throw new ArgumentNullException(nameof(manifestTemplate));
            Preconditions.CheckNotNull(manifestTemplate.GetContainerConfiguration());
            Preconditions.CheckNotNull(manifestTemplate.GetContainerConfiguration().Digest);

            SystemPath imageDirectory = cacheStorageFiles.GetImageDirectory(imageReference);

            Files.CreateDirectories(imageDirectory);

            using (LockFile ignored1 = LockFile.Create(imageDirectory.Resolve("lock")))
            {
                await WriteMetadataAsync(manifestTemplate, imageDirectory.Resolve("manifest.json")).ConfigureAwait(false);
                await WriteMetadataAsync(containerConfiguration, imageDirectory.Resolve("config.json")).ConfigureAwait(false);
            }
        }
コード例 #10
0
        public void TestFromJson()
        {
            // Loads the JSON string.
            SystemPath jsonFile = Paths.Get(TestResources.GetResource("core/json/v21manifest.json").ToURI());

            // Deserializes into a manifest JSON object.
            V21ManifestTemplate manifestJson =
                JsonTemplateMapper.ReadJsonFromFile <V21ManifestTemplate>(jsonFile);

            Assert.AreEqual(
                DescriptorDigest.FromDigest(
                    "sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad"),
                manifestJson.FsLayers[0].GetDigest());

            ContainerConfigurationTemplate containerConfiguration =
                manifestJson.GetContainerConfiguration().OrElse(null);

            Assert.AreEqual(
                new[] { "JAVA_HOME=/opt/openjdk", "PATH=/opt/openjdk/bin" },
                containerConfiguration.GetContainerEnvironment());
            Assert.AreEqual(
                new[] { "/opt/openjdk/bin/java" }, containerConfiguration.GetContainerEntrypoint());
        }
コード例 #11
0
        public async Task TestWriteMetadata_v22Async()
        {
            SystemPath containerConfigurationJsonFile =
                Paths.Get(
                    TestResources.GetResource("core/json/containerconfig.json").ToURI());
            ContainerConfigurationTemplate containerConfigurationTemplate =
                JsonTemplateMapper.ReadJsonFromFile <ContainerConfigurationTemplate>(
                    containerConfigurationJsonFile);
            SystemPath manifestJsonFile =
                Paths.Get(TestResources.GetResource("core/json/v22manifest.json").ToURI());
            IBuildableManifestTemplate manifestTemplate =
                JsonTemplateMapper.ReadJsonFromFile <V22ManifestTemplate>(manifestJsonFile);
            ImageReference imageReference = ImageReference.Parse("image.reference/project/thing:tag");

            await new CacheStorageWriter(cacheStorageFiles)
            .WriteMetadataAsync(imageReference, manifestTemplate, containerConfigurationTemplate).ConfigureAwait(false);

            SystemPath savedManifestPath =
                cacheRoot.Resolve("images/image.reference/project/thing!tag/manifest.json");
            SystemPath savedConfigPath =
                cacheRoot.Resolve("images/image.reference/project/thing!tag/config.json");

            Assert.IsTrue(Files.Exists(savedManifestPath));
            Assert.IsTrue(Files.Exists(savedConfigPath));

            V22ManifestTemplate savedManifest =
                JsonTemplateMapper.ReadJsonFromFile <V22ManifestTemplate>(savedManifestPath);

            Assert.AreEqual(
                "8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad",
                savedManifest.GetContainerConfiguration().Digest.GetHash());

            ContainerConfigurationTemplate savedContainerConfig =
                JsonTemplateMapper.ReadJsonFromFile <ContainerConfigurationTemplate>(savedConfigPath);

            Assert.AreEqual("wasm", savedContainerConfig.Architecture);
        }
コード例 #12
0
        /**
         * Pulls the base image.
         *
         * @param registryAuthorization authentication credentials to possibly use
         * @param progressEventDispatcher the {@link ProgressEventDispatcher} for emitting {@link
         *     ProgressEvent}s
         * @return the pulled image
         * @throws IOException when an I/O exception occurs during the pulling
         * @throws RegistryException if communicating with the registry caused a known error
         * @throws LayerCountMismatchException if the manifest and configuration contain conflicting layer
         *     information
         * @throws LayerPropertyNotFoundException if adding image layers fails
         * @throws BadContainerConfigurationFormatException if the container configuration is in a bad
         *     format
         */
        private async Task <Image> PullBaseImageAsync(
            Authorization registryAuthorization,
            ProgressEventDispatcher progressEventDispatcher)
        {
            RegistryClient registryClient =
                buildConfiguration
                .NewBaseImageRegistryClientFactory()
                .SetAuthorization(registryAuthorization)
                .NewRegistryClient();

            IManifestTemplate manifestTemplate =
                await registryClient.PullManifestAsync(buildConfiguration.GetBaseImageConfiguration().GetImageTag()).ConfigureAwait(false);

            // TODO: Make schema version be enum.
            switch (manifestTemplate.SchemaVersion)
            {
            case 1:
                V21ManifestTemplate v21ManifestTemplate = (V21ManifestTemplate)manifestTemplate;
                await buildConfiguration
                .GetBaseImageLayersCache()
                .WriteMetadataAsync(
                    buildConfiguration.GetBaseImageConfiguration().GetImage(), v21ManifestTemplate).ConfigureAwait(false);

                return(JsonToImageTranslator.ToImage(v21ManifestTemplate));

            case 2:
                IBuildableManifestTemplate buildableManifestTemplate =
                    (IBuildableManifestTemplate)manifestTemplate;
                if (buildableManifestTemplate.GetContainerConfiguration() == null ||
                    buildableManifestTemplate.GetContainerConfiguration().Digest == null)
                {
                    throw new UnknownManifestFormatException(
                              "Invalid container configuration in Docker V2.2/OCI manifest: \n"
                              + JsonTemplateMapper.ToUtf8String(buildableManifestTemplate));
                }

                DescriptorDigest containerConfigurationDigest =
                    buildableManifestTemplate.GetContainerConfiguration().Digest;

                using (ThrottledProgressEventDispatcherWrapper progressEventDispatcherWrapper =
                           new ThrottledProgressEventDispatcherWrapper(
                               progressEventDispatcher.NewChildProducer(),
                               "pull container configuration " + containerConfigurationDigest))
                {
                    string containerConfigurationString =
                        await Blobs.WriteToStringAsync(
                            registryClient.PullBlob(
                                containerConfigurationDigest,
                                progressEventDispatcherWrapper.SetProgressTarget,
                                progressEventDispatcherWrapper.DispatchProgress)).ConfigureAwait(false);

                    ContainerConfigurationTemplate containerConfigurationTemplate =
                        JsonTemplateMapper.ReadJson <ContainerConfigurationTemplate>(
                            containerConfigurationString);
                    await buildConfiguration
                    .GetBaseImageLayersCache()
                    .WriteMetadataAsync(
                        buildConfiguration.GetBaseImageConfiguration().GetImage(),
                        buildableManifestTemplate,
                        containerConfigurationTemplate).ConfigureAwait(false);

                    return(JsonToImageTranslator.ToImage(
                               buildableManifestTemplate, containerConfigurationTemplate));
                }
            }

            throw new InvalidOperationException(Resources.PullBaseImageStepUnknownManifestErrorMessage);
        }
コード例 #13
0
ファイル: CacheStorageReader.cs プロジェクト: tiaotiao97/jib
        /**
         * Retrieves the cached manifest and container configuration for an image reference.
         *
         * @param imageReference the image reference
         * @return the manifest and container configuration for the image reference, if found
         * @throws IOException if an I/O exception occurs
         * @throws CacheCorruptedException if the cache is corrupted
         */
        public Maybe <ManifestAndConfig> RetrieveMetadata(IImageReference imageReference)
        {
            SystemPath imageDirectory = cacheStorageFiles.GetImageDirectory(imageReference);
            SystemPath manifestPath   = imageDirectory.Resolve("manifest.json");

            if (!Files.Exists(manifestPath))
            {
                return(Maybe.Empty <ManifestAndConfig>());
            }

            // TODO: Consolidate with ManifestPuller
            JToken token;

            using (JsonTextReader reader = new JsonTextReader(File.OpenText(manifestPath)))
            {
                token = JToken.ReadFrom(reader);
            }
            if (!(token is JObject node))
            {
                throw new CacheCorruptedException(
                          cacheStorageFiles.GetCacheDirectory(),
                          Resources.CacheStorageReaderNotJsonExecpetionMessage);
            }
            if (!node.ContainsKey("schemaVersion"))
            {
                throw new CacheCorruptedException(
                          cacheStorageFiles.GetCacheDirectory(),
                          Resources.CacheStorageReaderSchemaVersionMissingExecpetionMessage);
            }

            int schemaVersion = node["schemaVersion"].Value <int>();

            if (schemaVersion == -1)
            {
                throw new CacheCorruptedException(
                          cacheStorageFiles.GetCacheDirectory(),
                          Resources.CacheStorageReaderInvalidSchemaVersionExecpetionMessageFormat);
            }

            if (schemaVersion == 1)
            {
                return(Maybe.Of(
                           new ManifestAndConfig(
                               JsonTemplateMapper.ReadJsonFromFile <V21ManifestTemplate>(manifestPath),
                               null)));
            }
            if (schemaVersion == 2)
            {
                // 'schemaVersion' of 2 can be either Docker V2.2 or OCI.
                string mediaType = node["mediaType"].Value <string>();

                IManifestTemplate manifestTemplate;
                if (V22ManifestTemplate.ManifestMediaType == mediaType)
                {
                    manifestTemplate =
                        JsonTemplateMapper.ReadJsonFromFile <V22ManifestTemplate>(manifestPath);
                }
                else if (OCIManifestTemplate.ManifestMediaType == mediaType)
                {
                    manifestTemplate =
                        JsonTemplateMapper.ReadJsonFromFile <OCIManifestTemplate>(manifestPath);
                }
                else
                {
                    throw new CacheCorruptedException(
                              cacheStorageFiles.GetCacheDirectory(),
                              string.Format(
                                  CultureInfo.CurrentCulture,
                                  Resources.CacheStorageReaderUnknownMediaTypeExecpetionMessageFormat, mediaType));
                }

                SystemPath configPath = imageDirectory.Resolve("config.json");
                if (!Files.Exists(configPath))
                {
                    throw new CacheCorruptedException(
                              cacheStorageFiles.GetCacheDirectory(),
                              Resources.CacheStorageReaderContainerConfigurationMissingExecpetionMessage);
                }
                ContainerConfigurationTemplate config =
                    JsonTemplateMapper.ReadJsonFromFile <ContainerConfigurationTemplate>(configPath);

                return(Maybe.Of(new ManifestAndConfig(manifestTemplate, config)));
            }
            throw new CacheCorruptedException(
                      cacheStorageFiles.GetCacheDirectory(),
                      string.Format(
                          CultureInfo.CurrentCulture,
                          Resources.CacheStorageReaderInvalidSchemaVersionExecpetionMessageFormat, schemaVersion));
        }
コード例 #14
0
        private void TestToImage_buildable <T>(
            string jsonFilename) where T : IBuildableManifestTemplate
        {
            // Loads the container configuration JSON.
            SystemPath containerConfigurationJsonFile =
                Paths.Get(
                    TestResources.GetResource("core/json/containerconfig.json").ToURI());
            ContainerConfigurationTemplate containerConfigurationTemplate =
                JsonTemplateMapper.ReadJsonFromFile <ContainerConfigurationTemplate>(
                    containerConfigurationJsonFile);

            // Loads the manifest JSON.
            SystemPath manifestJsonFile =
                Paths.Get(TestResources.GetResource(jsonFilename).ToURI());
            T manifestTemplate =
                JsonTemplateMapper.ReadJsonFromFile <T>(manifestJsonFile);

            Image image = JsonToImageTranslator.ToImage(manifestTemplate, containerConfigurationTemplate);

            IList <ILayer> layers = image.GetLayers();

            Assert.AreEqual(1, layers.Count);
            Assert.AreEqual(
                new BlobDescriptor(
                    1000000,
                    DescriptorDigest.FromDigest(
                        "sha256:4945ba5011739b0b98c4a41afe224e417f47c7c99b2ce76830999c9a0861b236")),
                layers[0].GetBlobDescriptor());
            Assert.AreEqual(
                DescriptorDigest.FromDigest(
                    "sha256:8c662931926fa990b41da3c9f42663a537ccd498130030f9149173a0493832ad"),
                layers[0].GetDiffId());
            CollectionAssert.AreEqual(
                ImmutableArray.Create(
                    HistoryEntry.CreateBuilder()
                    .SetCreationTimestamp(Instant.FromUnixTimeSeconds(0))
                    .SetAuthor("Bazel")
                    .SetCreatedBy("bazel build ...")
                    .SetEmptyLayer(true)
                    .Build(),
                    HistoryEntry.CreateBuilder()
                    .SetCreationTimestamp(Instant.FromUnixTimeSeconds(20))
                    .SetAuthor("Fib")
                    .SetCreatedBy("fib")
                    .Build()),
                image.GetHistory());
            Assert.AreEqual(Instant.FromUnixTimeSeconds(20), image.GetCreated());
            Assert.AreEqual(new[] { "some", "entrypoint", "command" }, image.GetEntrypoint());
            Assert.AreEqual(ImmutableDic.Of("VAR1", "VAL1", "VAR2", "VAL2"), image.GetEnvironment());
            Assert.AreEqual("/some/workspace", image.GetWorkingDirectory());
            Assert.AreEqual(
                ImmutableHashSet.Create(Port.Tcp(1000), Port.Tcp(2000), Port.Udp(3000)), image.GetExposedPorts());
            Assert.AreEqual(
                ImmutableHashSet.Create(
                    AbsoluteUnixPath.Get("/var/job-result-data"),
                    AbsoluteUnixPath.Get("/var/log/my-app-logs")),
                image.GetVolumes());
            Assert.AreEqual("tomcat", image.GetUser());
            Assert.AreEqual("value1", image.GetLabels()["key1"]);
            Assert.AreEqual("value2", image.GetLabels()["key2"]);
            Assert.AreEqual(2, image.GetLabels().Count);
        }