Home / Class/ BuildImageMojo Class — spring-boot Architecture

BuildImageMojo Class — spring-boot Architecture

Architecture documentation for the BuildImageMojo class in BuildImageMojo.java from the spring-boot codebase.

Entity Profile

Relationship Graph

Source Code

build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java lines 64–474

public abstract class BuildImageMojo extends AbstractPackagerMojo {

	static {
		System.setProperty("org.slf4j.simpleLogger.log.org.apache.http.wire", "ERROR");
	}

	/**
	 * Directory containing the source archive.
	 * @since 2.3.0
	 */
	@Parameter(defaultValue = "${project.build.directory}", required = true)
	@SuppressWarnings("NullAway.Init")
	private File sourceDirectory;

	/**
	 * Name of the source archive.
	 * @since 2.3.0
	 */
	@Parameter(defaultValue = "${project.build.finalName}", readonly = true, required = true)
	@SuppressWarnings("NullAway.Init")
	private String finalName;

	/**
	 * Skip the execution.
	 * @since 2.3.0
	 */
	@Parameter(property = "spring-boot.build-image.skip", defaultValue = "false")
	private boolean skip;

	/**
	 * Classifier used when finding the source archive.
	 * @since 2.3.0
	 */
	@Parameter
	private @Nullable String classifier;

	/**
	 * Image configuration, with {@code builder}, {@code runImage}, {@code name},
	 * {@code env}, {@code cleanCache}, {@code verboseLogging}, {@code pullPolicy}, and
	 * {@code publish} options.
	 * @since 2.3.0
	 */
	@Parameter
	private @Nullable Image image;

	/**
	 * Alias for {@link Image#name} to support configuration through command-line
	 * property.
	 * @since 2.3.0
	 */
	@Parameter(property = "spring-boot.build-image.imageName")
	@Nullable String imageName;

	/**
	 * Alias for {@link Image#builder} to support configuration through command-line
	 * property.
	 * @since 2.3.0
	 */
	@Parameter(property = "spring-boot.build-image.builder")
	@Nullable String imageBuilder;

	/**
	 * Alias for {@link Image#trustBuilder} to support configuration through command-line
	 * property.
	 */
	@Parameter(property = "spring-boot.build-image.trustBuilder")
	@Nullable Boolean trustBuilder;

	/**
	 * Alias for {@link Image#runImage} to support configuration through command-line
	 * property.
	 * @since 2.3.1
	 */
	@Parameter(property = "spring-boot.build-image.runImage")
	@Nullable String runImage;

	/**
	 * Alias for {@link Image#cleanCache} to support configuration through command-line
	 * property.
	 * @since 2.4.0
	 */
	@Parameter(property = "spring-boot.build-image.cleanCache")
	@Nullable Boolean cleanCache;

	/**
	 * Alias for {@link Image#pullPolicy} to support configuration through command-line
	 * property.
	 */
	@Parameter(property = "spring-boot.build-image.pullPolicy")
	@Nullable PullPolicy pullPolicy;

	/**
	 * Alias for {@link Image#publish} to support configuration through command-line
	 * property.
	 */
	@Parameter(property = "spring-boot.build-image.publish")
	@Nullable Boolean publish;

	/**
	 * Alias for {@link Image#network} to support configuration through command-line
	 * property.
	 * @since 2.6.0
	 */
	@Parameter(property = "spring-boot.build-image.network")
	@Nullable String network;

	/**
	 * Alias for {@link Image#createdDate} to support configuration through command-line
	 * property.
	 * @since 3.1.0
	 */
	@Parameter(property = "spring-boot.build-image.createdDate")
	@Nullable String createdDate;

	/**
	 * Alias for {@link Image#applicationDirectory} to support configuration through
	 * command-line property.
	 * @since 3.1.0
	 */
	@Parameter(property = "spring-boot.build-image.applicationDirectory")
	@Nullable String applicationDirectory;

	/**
	 * Alias for {@link Image#imagePlatform} to support configuration through command-line
	 * property.
	 * @since 3.4.0
	 */
	@Parameter(property = "spring-boot.build-image.imagePlatform")
	@Nullable String imagePlatform;

	/**
	 * Docker configuration options.
	 * @since 2.4.0
	 */
	@Parameter
	private @Nullable Docker docker;

	/**
	 * The type of archive (which corresponds to how the dependencies are laid out inside
	 * it). Possible values are {@code JAR}, {@code WAR}, {@code ZIP}, {@code DIR},
	 * {@code NONE}. Defaults to a guess based on the archive type.
	 * @since 2.3.11
	 */
	@Parameter
	private @Nullable LayoutType layout;

	/**
	 * The layout factory that will be used to create the executable archive if no
	 * explicit layout is set. Alternative layouts implementations can be provided by 3rd
	 * parties.
	 * @since 2.3.11
	 */
	@Parameter
	private @Nullable LayoutFactory layoutFactory;

	protected BuildImageMojo(MavenProjectHelper projectHelper) {
		super(projectHelper);
	}

	/**
	 * Return the type of archive that should be used when building the image.
	 * @return the value of the {@code layout} parameter, or {@code null} if the parameter
	 * is not provided
	 */
	@Override
	protected @Nullable LayoutType getLayout() {
		return this.layout;
	}

	/**
	 * Return the layout factory that will be used to determine the
	 * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set.
	 * @return the value of the {@code layoutFactory} parameter, or {@code null} if the
	 * parameter is not provided
	 */
	@Override
	protected @Nullable LayoutFactory getLayoutFactory() {
		return this.layoutFactory;
	}

	@Override
	public void execute() throws MojoExecutionException {
		if (this.project.getPackaging().equals("pom")) {
			getLog().debug("build-image goal could not be applied to pom project.");
			return;
		}
		if (this.skip) {
			getLog().debug("skipping build-image as per configuration.");
			return;
		}
		buildImage();
	}

	private void buildImage() throws MojoExecutionException {
		Libraries libraries = getLibraries(Collections.emptySet());
		try {
			BuildRequest request = getBuildRequest(libraries);
			Docker docker = (this.docker != null) ? this.docker : new Docker();
			BuilderDockerConfiguration dockerConfiguration = docker.asDockerConfiguration(getLog(),
					request.isPublish());
			Builder builder = new Builder(new MojoBuildLog(this::getLog), dockerConfiguration);
			builder.build(request);
		}
		catch (IOException ex) {
			throw new MojoExecutionException(ex.getMessage(), ex);
		}
	}

	private BuildRequest getBuildRequest(Libraries libraries) {
		ImagePackager imagePackager = new ImagePackager(getArchiveFile(), getBackupFile());
		Function<Owner, TarArchive> content = (owner) -> getApplicationContent(owner, libraries, imagePackager);
		Image image = (this.image != null) ? this.image : new Image();
		if (image.name == null && this.imageName != null) {
			image.setName(this.imageName);
		}
		if (image.builder == null && this.imageBuilder != null) {
			image.setBuilder(this.imageBuilder);
		}
		if (image.trustBuilder == null && this.trustBuilder != null) {
			image.setTrustBuilder(this.trustBuilder);
		}
		if (image.runImage == null && this.runImage != null) {
			image.setRunImage(this.runImage);
		}
		if (image.cleanCache == null && this.cleanCache != null) {
			image.setCleanCache(this.cleanCache);
		}
		if (image.pullPolicy == null && this.pullPolicy != null) {
			image.setPullPolicy(this.pullPolicy);
		}
		if (image.publish == null && this.publish != null) {
			image.setPublish(this.publish);
		}
		if (image.network == null && this.network != null) {
			image.setNetwork(this.network);
		}
		if (image.createdDate == null && this.createdDate != null) {
			image.setCreatedDate(this.createdDate);
		}
		if (image.applicationDirectory == null && this.applicationDirectory != null) {
			image.setApplicationDirectory(this.applicationDirectory);
		}
		if (image.imagePlatform == null && this.imagePlatform != null) {
			image.setImagePlatform(this.imagePlatform);
		}
		return customize(image.getBuildRequest(this.project.getArtifact(), content));
	}

	private TarArchive getApplicationContent(Owner owner, Libraries libraries, ImagePackager imagePackager) {
		ImagePackager packager = getConfiguredPackager(() -> imagePackager);
		return new PackagedTarArchive(owner, libraries, packager);
	}

	private File getArchiveFile() {
		// We can't use 'project.getArtifact().getFile()' because package can be done in a
		// forked lifecycle and will be null
		File archiveFile = getTargetFile(this.finalName, this.classifier, this.sourceDirectory);
		if (!archiveFile.exists()) {
			archiveFile = getSourceArtifact(this.classifier).getFile();
		}
		if (!archiveFile.exists()) {
			throw new IllegalStateException("A jar or war file is required for building image");
		}
		return archiveFile;
	}

	/**
	 * Return the {@link File} to use to back up the original source.
	 * @return the file to use to back up the original source
	 */
	private @Nullable File getBackupFile() {
		// We can't use 'project.getAttachedArtifacts()' because package can be done in a
		// forked lifecycle and will be null
		if (this.classifier != null) {
			File backupFile = getTargetFile(this.finalName, null, this.sourceDirectory);
			if (backupFile.exists()) {
				return backupFile;
			}
			Artifact source = getSourceArtifact(null);
			if (!this.classifier.equals(source.getClassifier())) {
				return source.getFile();
			}
		}
		return null;
	}

	private BuildRequest customize(BuildRequest request) {
		request = customizeCreator(request);
		return request;
	}

	private BuildRequest customizeCreator(BuildRequest request) {
		String springBootVersion = VersionExtractor.forClass(BuildImageMojo.class);
		if (StringUtils.hasText(springBootVersion)) {
			request = request.withCreator(Creator.withVersion(springBootVersion));
		}
		return request;
	}

	/**
	 * {@link BuildLog} backed by Mojo logging.
	 */
	private static class MojoBuildLog extends AbstractBuildLog {

		private static final long THRESHOLD = Duration.ofSeconds(2).toMillis();

		private final Supplier<Log> log;

		MojoBuildLog(Supplier<Log> log) {
			this.log = log;
		}

		@Override
		protected void log(String message) {
			this.log.get().info(message);
		}

		@Override
		protected Consumer<TotalProgressEvent> getProgressConsumer(String message) {
			return new ProgressLog(message);
		}

		private class ProgressLog implements Consumer<TotalProgressEvent> {

			private final String message;

			private long last;

			ProgressLog(String message) {
				this.message = message;
				this.last = System.currentTimeMillis();
			}

			@Override
			public void accept(TotalProgressEvent progress) {
				log(progress.getPercent());
			}

			private void log(int percent) {
				if (percent == 100 || (System.currentTimeMillis() - this.last) > THRESHOLD) {
					MojoBuildLog.this.log.get().info(this.message + " " + percent + "%");
					this.last = System.currentTimeMillis();
				}
			}

		}

	}

	/**
	 * Adapter class to expose the packaged jar as a {@link TarArchive}.
	 */
	static class PackagedTarArchive implements TarArchive {

		static final long NORMALIZED_MOD_TIME = TarArchive.NORMALIZED_TIME.toEpochMilli();

		private final Owner owner;

		private final Libraries libraries;

		private final ImagePackager packager;

		PackagedTarArchive(Owner owner, Libraries libraries, ImagePackager packager) {
			this.owner = owner;
			this.libraries = libraries;
			this.packager = packager;
		}

		@Override
		public void writeTo(OutputStream outputStream) throws IOException {
			TarArchiveOutputStream tar = new TarArchiveOutputStream(outputStream);
			tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
			try {
				this.packager.packageImage(this.libraries, (entry, entryWriter) -> write(entry, entryWriter, tar));
			}
			catch (RuntimeException ex) {
				outputStream.close();
				throw new RuntimeException("Error packaging archive for image", ex);
			}
		}

		private void write(ZipEntry jarEntry, @Nullable EntryWriter entryWriter, TarArchiveOutputStream tar) {
			try {
				TarArchiveEntry tarEntry = convert(jarEntry);
				tar.putArchiveEntry(tarEntry);
				if (tarEntry.isFile()) {
					Assert.state(entryWriter != null, "'entryWriter' must not be null");
					entryWriter.write(tar);
				}
				tar.closeArchiveEntry();
			}
			catch (IOException ex) {
				throw new IllegalStateException(ex);
			}
		}

		private TarArchiveEntry convert(ZipEntry entry) {
			byte linkFlag = (entry.isDirectory()) ? TarConstants.LF_DIR : TarConstants.LF_NORMAL;
			TarArchiveEntry tarEntry = new TarArchiveEntry(entry.getName(), linkFlag, true);
			tarEntry.setUserId(this.owner.getUid());
			tarEntry.setGroupId(this.owner.getGid());
			tarEntry.setModTime(NORMALIZED_MOD_TIME);
			if (!entry.isDirectory()) {
				tarEntry.setSize(entry.getSize());
			}
			return tarEntry;
		}

	}

}

Domain

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free