
io.fabric8.maven.docker.service.BuildXService Maven / Gradle / Ivy
The newest version!
package io.fabric8.maven.docker.service;
import io.fabric8.maven.docker.access.AuthConfig;
import io.fabric8.maven.docker.access.DockerAccess;
import io.fabric8.maven.docker.assembly.BuildDirs;
import io.fabric8.maven.docker.assembly.DockerAssemblyManager;
import io.fabric8.maven.docker.config.AttestationConfiguration;
import io.fabric8.maven.docker.config.BuildImageConfiguration;
import io.fabric8.maven.docker.config.BuildXConfiguration;
import io.fabric8.maven.docker.config.ImageConfiguration;
import io.fabric8.maven.docker.util.EnvUtil;
import io.fabric8.maven.docker.util.ImageName;
import io.fabric8.maven.docker.util.Logger;
import io.fabric8.maven.docker.util.ProjectPaths;
import org.apache.maven.plugin.MojoExecutionException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class BuildXService {
private final DockerAccess dockerAccess;
private final DockerAssemblyManager dockerAssemblyManager;
private final Logger logger;
private final Exec exec;
public BuildXService(DockerAccess dockerAccess, DockerAssemblyManager dockerAssemblyManager, Logger logger) {
this(dockerAccess, dockerAssemblyManager, logger, new DefaultExec(logger));
}
public BuildXService(DockerAccess dockerAccess, DockerAssemblyManager dockerAssemblyManager, Logger logger, Exec exec) {
this.dockerAccess = dockerAccess;
this.dockerAssemblyManager = dockerAssemblyManager;
this.logger = logger;
this.exec = exec;
}
public void build(ProjectPaths projectPaths, ImageConfiguration imageConfig, String configuredRegistry, AuthConfig authConfig, File buildArchive) throws MojoExecutionException {
useBuilder(projectPaths, imageConfig, configuredRegistry, authConfig, buildArchive, this::buildAndLoadSinglePlatform);
}
public void push(ProjectPaths projectPaths, ImageConfiguration imageConfig, String configuredRegistry, AuthConfig authConfig) throws MojoExecutionException {
BuildDirs buildDirs = new BuildDirs(projectPaths, imageConfig.getName());
File archive = new File(buildDirs.getTemporaryRootDirectory(), "docker-build.tar");
useBuilder(projectPaths, imageConfig, configuredRegistry, authConfig, archive, this::pushMultiPlatform);
}
protected void useBuilder(ProjectPaths projectPaths, ImageConfiguration imageConfig, String configuredRegistry, AuthConfig authConfig, C context, Builder builder) throws MojoExecutionException {
BuildDirs buildDirs = new BuildDirs(projectPaths, imageConfig.getName());
Path configPath = getDockerStateDir(imageConfig.getBuildConfiguration(), buildDirs);
List buildX = Arrays.asList("docker", "--config", configPath.toString(), "buildx");
String builderName = createBuilder(configPath, buildX, imageConfig, buildDirs);
Path configJson = configPath.resolve("config.json");
try {
createConfigJson(configJson, authConfig);
builder.useBuilder(buildX, builderName, buildDirs, imageConfig, configuredRegistry, context);
} finally {
removeConfigJson(configJson);
}
}
protected void createConfigJson(Path configJson, AuthConfig authConfig) throws MojoExecutionException {
try (BufferedWriter bufferedWriter = Files.newBufferedWriter(configJson, StandardCharsets.UTF_8,
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)
) {
bufferedWriter.write(authConfig != null ? authConfig.toJson() : "{}");
} catch (IOException e) {
throw new MojoExecutionException("Unable to create config.json", e);
}
}
protected void removeConfigJson(Path configJson) {
try {
Files.deleteIfExists(configJson);
} catch (IOException e) {
logger.warn("Unable to delete %s", configJson);
}
}
protected void buildAndLoadSinglePlatform(List buildX, String builderName, BuildDirs buildDirs, ImageConfiguration imageConfig, String configuredRegistry, File buildArchive) throws MojoExecutionException {
List platforms = imageConfig.getBuildConfiguration().getBuildX().getPlatforms();
// build and load the single-platform image by re-building, image should be cached and build should be quick
String nativePlatform = dockerAccess.getNativePlatform();
if (platforms.size() == 1) {
buildX(buildX, builderName, buildDirs, imageConfig, configuredRegistry, platforms, buildArchive, "--load");
} else if (platforms.contains(nativePlatform)) {
buildX(buildX, builderName, buildDirs, imageConfig, configuredRegistry, Collections.singletonList(nativePlatform), buildArchive, "--load");
} else {
logger.info("More than one platform specified not including native %s, no image built", nativePlatform);
}
}
protected void pushMultiPlatform(List buildX, String builderName, BuildDirs buildDirs, ImageConfiguration imageConfig, String configuredRegistry, File buildArchive) throws MojoExecutionException {
// build and push all images. The native platform may be re-built, image should be cached and build should be quick
buildX(buildX, builderName, buildDirs, imageConfig, configuredRegistry, imageConfig.getBuildConfiguration().getBuildX().getPlatforms(), buildArchive, "--push");
}
protected void buildX(List buildX, String builderName, BuildDirs buildDirs, ImageConfiguration imageConfig, String configuredRegistry, List platforms, File buildArchive, String extraParam)
throws MojoExecutionException {
BuildImageConfiguration buildConfiguration = imageConfig.getBuildConfiguration();
List cmdLine = new ArrayList<>(buildX);
append(cmdLine, "build", "--progress=plain", "--builder", builderName, "--platform",
String.join(",", platforms), "--tag",
new ImageName(imageConfig.getName()).getFullName(configuredRegistry));
buildConfiguration.getTags().forEach(t -> {
cmdLine.add("--tag");
cmdLine.add(new ImageName(imageConfig.getName(), t).getFullName(configuredRegistry));
});
Map args = buildConfiguration.getArgs();
if (args != null) {
args.forEach((key, value) -> {
cmdLine.add("--build-arg");
cmdLine.add(key + '=' + value);
});
}
AttestationConfiguration attestations = buildConfiguration.getBuildX().getAttestations();
if (attestations != null) {
if (Boolean.TRUE.equals(attestations.getSbom())) {
cmdLine.add("--sbom=true");
}
String provenance = attestations.getProvenance();
if (provenance != null) {
switch (provenance) {
case "min":
case "max":
cmdLine.add("--provenance=mode=" + provenance);
break;
case "false":
case "true":
cmdLine.add("--provenance=" + provenance);
break;
default:
logger.error("Unsupported provenance mode %s", provenance);
}
}
}
if (buildConfiguration.squash()) {
cmdLine.add("--squash");
}
File contextDir = buildConfiguration.getContextDir();
if (contextDir != null) {
Path destinationPath = getContextPath(buildArchive);
Path dockerFileRelativePath = contextDir.toPath().relativize(buildConfiguration.getDockerFile().toPath());
append(cmdLine, "--file=" + destinationPath.resolve(dockerFileRelativePath), destinationPath.toString());
} else {
cmdLine.add(buildDirs.getOutputDirectory().getAbsolutePath());
}
if (extraParam != null) {
cmdLine.add(extraParam);
}
int rc = exec.process(cmdLine);
if (rc != 0) {
throw new MojoExecutionException("Error status (" + rc + ") when building");
}
}
protected Path getContextPath(File buildArchive) throws MojoExecutionException {
String archiveName = buildArchive.getName();
String fileName = archiveName.substring(0, archiveName.indexOf('.'));
File destinationDirectory = new File(buildArchive.getParentFile(), fileName);
Path destinationPath = destinationDirectory.toPath();
try {
Files.createDirectories(destinationPath);
} catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
dockerAssemblyManager.extractDockerTarArchive(buildArchive, destinationDirectory);
return destinationPath;
}
protected Path getDockerStateDir(BuildImageConfiguration buildConfiguration, BuildDirs buildDirs) {
String stateDir = buildConfiguration.getBuildX().getDockerStateDir();
Path dockerStatePath = buildDirs.getBuildPath(stateDir != null ? EnvUtil.resolveHomeReference(stateDir) : "docker");
createDirectory(dockerStatePath);
return dockerStatePath;
}
protected void createDirectory(Path cachePath) {
try {
Files.createDirectories(cachePath);
} catch (IOException e) {
throw new IllegalArgumentException("Cannot create " + cachePath);
}
}
protected String createBuilder(Path configPath, List buildX, ImageConfiguration imageConfig, BuildDirs buildDirs) throws MojoExecutionException {
BuildXConfiguration buildXConfiguration = imageConfig.getBuildConfiguration().getBuildX();
String builderName = buildXConfiguration.getBuilderName();
if (builderName == null) {
builderName= "maven";
}
Path builderPath = configPath.resolve(Paths.get("buildx", "instances", builderName));
if(Files.notExists(builderPath)) {
List cmds = new ArrayList<>(buildX);
append(cmds, "create", "--driver", "docker-container", "--name", builderName);
String buildConfig = buildXConfiguration.getConfigFile();
if(buildConfig != null) {
append(cmds, "--config",
buildDirs.getProjectPath(EnvUtil.resolveHomeReference(buildConfig)).toString());
}
int rc = exec.process(cmds);
if (rc != 0) {
throw new MojoExecutionException("Error status (" + rc + ") while creating builder " + builderName);
}
}
return builderName;
}
public static List append(List collection, T... members) {
collection.addAll(Arrays.asList(members));
return collection;
}
interface Builder {
void useBuilder(List buildX, String builderName, BuildDirs buildDirs, ImageConfiguration imageConfig, String configuredRegistry, C context) throws MojoExecutionException;
}
public interface Exec {
int process(List cmdArgs) throws MojoExecutionException;
}
public static class DefaultExec implements Exec {
private final Logger logger;
public DefaultExec(Logger logger) {
this.logger = logger;
}
@Override public int process(List cmdArgs) throws MojoExecutionException {
try {
logger.info(String.join(" ", cmdArgs));
ProcessBuilder builder = new ProcessBuilder(cmdArgs);
Process process = builder.start();
pumpStream(process.getInputStream());
pumpStream(process.getErrorStream());
return process.waitFor();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new MojoExecutionException("Interrupted while executing " + cmdArgs, ex);
} catch (IOException ex) {
throw new MojoExecutionException("unable to execute " + cmdArgs, ex);
}
}
private void pumpStream(InputStream is) {
CompletableFuture.runAsync(() -> {
try (
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is))
) {
bufferedReader.lines().forEach(logger::info);
} catch (IOException e) {
logger.error("failed redirecting stream %s", e.getMessage());
}
});
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy