diff --git a/build-plugin/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java b/build-plugin/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java index 78bcb7f8f4e..b9103ce53ae 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java +++ b/build-plugin/spring-boot-gradle-plugin/src/dockerTest/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageIntegrationTests.java @@ -473,8 +473,7 @@ class BootBuildImageIntegrationTests { void buildsImageWithMultipleCommandLineEnvironments() throws IOException { writeMainClass(); writeLongNameResource(); - BuildResult result = this.gradleBuild.build("bootBuildImage", - "--environment", "BP_LIVE_RELOAD_ENABLED=true", + BuildResult result = this.gradleBuild.build("bootBuildImage", "--environment", "BP_LIVE_RELOAD_ENABLED=true", "--environment", "MY_CUSTOM_VAR=hello_world"); BuildTask task = result.task(":bootBuildImage"); assertThat(task).isNotNull(); diff --git a/build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc b/build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc index 139eeda2adb..8e3b4bbfade 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc +++ b/build-plugin/spring-boot-gradle-plugin/src/docs/antora/modules/gradle-plugin/pages/packaging-oci-image.adoc @@ -162,7 +162,7 @@ Acceptable values are `ALWAYS`, `NEVER`, and `IF_NOT_PRESENT`. | `ALWAYS` | `environment` -| +| `--environment` | Environment variables that should be passed to the builder. | Empty. @@ -346,6 +346,15 @@ include::example$packaging/boot-build-image-env.gradle.kts[tags=env] ---- ====== +Environment variables can also be specified on the command line, as shown in the following example: + +[source,shell] +---- +$ gradle bootBuildImage --environment BP_JVM_VERSION=17 +---- + +`--environment` can be used multiple times to specify multiple environment variables. + If there is a network proxy between the Docker daemon the builder runs in and network locations that buildpacks download artifacts from, you will need to configure the builder to use the proxy. When using the Paketo builder, this can be accomplished by setting the `HTTPS_PROXY` and/or `HTTP_PROXY` environment variables as show in the following example: diff --git a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java index 102e6ea40d1..078180fafae 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java +++ b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImage.java @@ -17,12 +17,14 @@ package org.springframework.boot.gradle.tasks.bundling; import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.gradle.api.Action; import org.gradle.api.DefaultTask; -import org.gradle.api.GradleException; +import org.gradle.api.InvalidUserDataException; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.file.RegularFileProperty; @@ -104,6 +106,23 @@ public abstract class BootBuildImage extends DefaultTask { this.docker = getProject().getObjects().newInstance(DockerSpec.class); this.pullPolicy = getProject().getObjects().property(PullPolicy.class); getSecurityOptions().convention((Iterable) null); + getEffectiveEnvironment().putAll(getEnvironment()); + getEffectiveEnvironment().putAll(getEnvironmentFromCommandLine().map(BootBuildImage::asMap)); + } + + private static Map asMap(List variables) { + Map environment = new LinkedHashMap<>(); + for (String variable : variables) { + int index = variable.indexOf('='); + if (index <= 0) { + throw new InvalidUserDataException( + "Invalid value for option '--environment'. Expected 'NAME=VALUE' but got '" + variable + "'."); + } + String name = variable.substring(0, index); + String value = variable.substring(index + 1); + environment.put(name, value); + } + return environment; } /** @@ -159,22 +178,21 @@ public abstract class BootBuildImage extends DefaultTask { * Returns the environment that will be used when building the image. * @return the environment */ - @Input + @Internal public abstract MapProperty getEnvironment(); /** - * Returns environment variables contributed from the command line. - * Each entry must be in the form NAME=VALUE. + * Returns environment variables contributed from the command line. Each entry must be + * in the form NAME=VALUE. * @return the environment variables from the command line */ @Internal - public abstract ListProperty getEnvironmentFromCommandLine(); + @Option(option = "environment", description = "Environment variable that will be used when building the image " + + "(NAME=VALUE). Can be specified multiple times.") + abstract ListProperty getEnvironmentFromCommandLine(); - @Option(option = "environment", - description = "Environment variable to set for the build (NAME=VALUE). Can be specified multiple times.") - public void environment(List environment) { - getEnvironmentFromCommandLine().addAll(environment); - } + @Input + abstract MapProperty getEffectiveEnvironment(); /** * Returns whether caches should be cleaned before packaging. @@ -429,7 +447,7 @@ public abstract class BootBuildImage extends DefaultTask { } private BuildRequest customizeEnvironment(BuildRequest request) { - Map environment = getEffectiveEnvironment(); + Map environment = getEffectiveEnvironment().getOrElse(Collections.emptyMap()); if (!environment.isEmpty()) { request = request.withEnv(environment); } @@ -518,31 +536,4 @@ public abstract class BootBuildImage extends DefaultTask { return request; } - private Map getEffectiveEnvironment() { - Map environment = new java.util.LinkedHashMap<>(); - Map configured = getEnvironment().getOrNull(); - if (!CollectionUtils.isEmpty(configured)) { - environment.putAll(configured); - } - List fromCli = getEnvironmentFromCommandLine().getOrNull(); - if (!CollectionUtils.isEmpty(fromCli)) { - for (String entry : fromCli) { - Map.Entry parsed = parseEnvironmentEntry(entry); - environment.put(parsed.getKey(), parsed.getValue()); - } - } - return environment; - } - - private Map.Entry parseEnvironmentEntry(String entry) { - int index = entry.indexOf('='); - if (index <= 0) { - throw new GradleException( - "Invalid value for option '--environment'. Expected 'NAME=VALUE' but got '" + entry + "'."); - } - String name = entry.substring(0, index); - String value = entry.substring(index + 1); - return Map.entry(name, value); - } - } diff --git a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java index 0eef7843ef3..9a917d02382 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java +++ b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootBuildImageTests.java @@ -144,6 +144,25 @@ class BootBuildImageTests { .hasSize(2); } + @Test + void whenEnvironmentVariablesAreSetOnTheCommandLineTheyAreIncludedInTheRequest() { + this.buildImage.getEnvironmentFromCommandLine().add("ALPHA=a"); + this.buildImage.getEnvironmentFromCommandLine().add("BRAVO=b"); + assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "a") + .containsEntry("BRAVO", "b") + .hasSize(2); + } + + @Test + void environmentVariablesFromTheCommandLineOverrideThoseInTheBuildScript() { + this.buildImage.getEnvironment().put("ALPHA", "a"); + this.buildImage.getEnvironmentFromCommandLine().add("ALPHA=apple"); + this.buildImage.getEnvironmentFromCommandLine().add("BRAVO=banana"); + assertThat(this.buildImage.createRequest().getEnv()).containsEntry("ALPHA", "apple") + .containsEntry("BRAVO", "banana") + .hasSize(2); + } + @Test void whenUsingDefaultConfigurationThenRequestHasVerboseLoggingDisabled() { assertThat(this.buildImage.createRequest().isVerboseLogging()).isFalse();