private static async Task RunDockerBuild( string imageName, DockerBuildUnwrap build, string[]?cacheFrom, Resource logResource, string?target = null) { // Prepare the build arguments. var buildArgs = new List <string>(new[] { "build" }); if (!string.IsNullOrEmpty(build.Dockerfile)) { buildArgs.AddRange(new[] { "-f", build.Dockerfile }); // add a custom Dockerfile location. } if (build.Args != null) { foreach (var arg in build.Args.Keys) { buildArgs.AddRange(new[] { "--build-arg", $"{arg}={build.Args[arg]}" }); } } if (build.Target != null) { buildArgs.AddRange(new[] { "--target", build.Target }); } if (build.CacheFrom != null) { var cacheFromImages = cacheFrom; if (cacheFromImages != null && cacheFromImages.Length > 0) { buildArgs.AddRange(new[] { "--cache-from", string.Join("", cacheFromImages) }); } } if (build.ExtraOptions != null) { buildArgs.AddRange(build.ExtraOptions); } buildArgs.Add(build.Context !); // push the docker build context onto the path. buildArgs.AddRange(new[] { "-t", imageName }); // tag the image with the chosen name. if (target != null) { buildArgs.AddRange(new[] { "--target", target }); } await RunCommandThatMustSucceed("docker", buildArgs.ToArray(), logResource, reportFullCommandLine : true, stdin : null, build.Env).ConfigureAwait(false); }
private static async Task <(string imageId, string[] stages)> BuildImageAsync( string imageName, Union <string, DockerBuildUnwrap> pathOrBuild, Resource logResource, string[]?cacheFrom) { DockerBuildUnwrap build = pathOrBuild.IsT0 ? new DockerBuildUnwrap { Context = pathOrBuild.AsT0 } : pathOrBuild.AsT1; // If the build context is missing, default it to the working directory. if (build.Context == null) { build.Context = "."; } Log.Info( $"Building container image '{imageName}': context={build.Context}" + (build.Dockerfile != null ? $", dockerfile={build.Dockerfile}" : "") + (build.Args != null ? $", args={JsonSerializer.Serialize(build.Args)}" : "") + (build.Target != null ? $", target={build.Target}" : ""), logResource, ephemeral: true); // If the container build specified build stages to cache, build each in turn. var stages = new List <string>(); if (build.CacheFrom?.Stages != null) { foreach (var stage in build.CacheFrom.Stages) { await RunDockerBuild(LocalStageImageName(imageName, stage), build, cacheFrom, logResource, stage).ConfigureAwait(false); stages.Add(stage); } } //// Invoke Docker CLI commands to build. await RunDockerBuild(imageName, build, cacheFrom, logResource).ConfigureAwait(false); // Finally, inspect the image so we can return the SHA digest. Do not forward the output of this // command this to the CLI to show the user. var inspectResult = await RunCommandThatMustSucceed( "docker", new[] { "image", "inspect", "-f", "{{.Id}}", imageName }, logResource).ConfigureAwait(false); if (string.IsNullOrEmpty(inspectResult)) { throw new ResourceException($"No digest available for image {imageName}", logResource); } // From https://docs.docker.com/registry/spec/api/#content-digests // // the image id will be a "algorithm:hex" pair. We don't care about the algorithm part. All we // want is the unique portion we can use elsewhere. Since we are also going to place this in an // image tag, we also don't want the colon, as that's not legal there. So simply grab the hex // portion after the colon and return that. var imageId = inspectResult.Trim(); var colonIndex = imageId.LastIndexOf(":"); imageId = colonIndex < 0 ? imageId : imageId.Substring(colonIndex + 1); return(imageId, stages.ToArray()); }