Add support for publishing docker images to a registry
This commit adds options to the Maven and Gradle plugins to publish to a Docker registry the image generated by the image-building goal and task. The Docker registry auth configuration added in an earlier commit was modified to accept separate auth configs for the builder/run image and the generated image, since it is likely these images will be stored in separate registries or repositories with distinct auth required for each. Fixes gh-21001
This commit is contained in:
@@ -178,6 +178,15 @@ class BootBuildImageIntegrationTests {
|
||||
.containsPattern("example/Invalid-Image-Name");
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void failsWithPublishMissingPublishRegistry() {
|
||||
writeMainClass();
|
||||
writeLongNameResource();
|
||||
BuildResult result = this.gradleBuild.buildAndFail("bootBuildImage", "--publishImage");
|
||||
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.FAILED);
|
||||
assertThat(result.getOutput()).contains("requires docker.publishRegistry");
|
||||
}
|
||||
|
||||
private void writeMainClass() {
|
||||
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
|
||||
examplePackage.mkdirs();
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.gradle.tasks.bundling;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.gradle.testkit.runner.BuildResult;
|
||||
import org.gradle.testkit.runner.TaskOutcome;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.TestTemplate;
|
||||
import org.testcontainers.containers.GenericContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.DockerApi;
|
||||
import org.springframework.boot.buildpack.platform.docker.UpdateListener;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.Image;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ImageReference;
|
||||
import org.springframework.boot.gradle.junit.GradleCompatibility;
|
||||
import org.springframework.boot.gradle.testkit.GradleBuild;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link BootBuildImage} tasks requiring a Docker image registry.
|
||||
*
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@GradleCompatibility
|
||||
@Testcontainers(disabledWithoutDocker = true)
|
||||
public class BootBuildImageRegistryIntegrationTests {
|
||||
|
||||
@Container
|
||||
static final RegistryContainer registry = new RegistryContainer().withStartupAttempts(5)
|
||||
.withStartupTimeout(Duration.ofMinutes(3));
|
||||
|
||||
String registryAddress;
|
||||
|
||||
GradleBuild gradleBuild;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
assertThat(registry.isRunning());
|
||||
this.registryAddress = registry.getHost() + ":" + registry.getFirstMappedPort();
|
||||
}
|
||||
|
||||
@TestTemplate
|
||||
void buildsImageAndPublishesToRegistry() throws IOException, InterruptedException {
|
||||
writeMainClass();
|
||||
String repoName = "test-image";
|
||||
String imageName = this.registryAddress + "/" + repoName;
|
||||
BuildResult result = this.gradleBuild.build("bootBuildImage", "--imageName=" + imageName);
|
||||
assertThat(result.task(":bootBuildImage").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
|
||||
assertThat(result.getOutput()).contains("Building image").contains("Successfully built image")
|
||||
.contains("Pushing image '" + imageName + ":latest" + "'")
|
||||
.contains("Pushed image '" + imageName + ":latest" + "'");
|
||||
ImageReference imageReference = ImageReference.of(imageName);
|
||||
Image pulledImage = new DockerApi().image().pull(imageReference, UpdateListener.none());
|
||||
assertThat(pulledImage).isNotNull();
|
||||
new DockerApi().image().remove(imageReference, false);
|
||||
}
|
||||
|
||||
private void writeMainClass() {
|
||||
File examplePackage = new File(this.gradleBuild.getProjectDir(), "src/main/java/example");
|
||||
examplePackage.mkdirs();
|
||||
File main = new File(examplePackage, "Main.java");
|
||||
try (PrintWriter writer = new PrintWriter(new FileWriter(main))) {
|
||||
writer.println("package example;");
|
||||
writer.println();
|
||||
writer.println("import java.io.IOException;");
|
||||
writer.println();
|
||||
writer.println("public class Main {");
|
||||
writer.println();
|
||||
writer.println(" public static void main(String[] args) {");
|
||||
writer.println(" }");
|
||||
writer.println();
|
||||
writer.println("}");
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RegistryContainer extends GenericContainer<RegistryContainer> {
|
||||
|
||||
RegistryContainer() {
|
||||
super("registry:2.7.1");
|
||||
addExposedPorts(5000);
|
||||
addEnv("SERVER_NAME", "localhost");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.JavaVersion;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.testfixtures.ProjectBuilder;
|
||||
@@ -30,6 +31,7 @@ import org.springframework.boot.buildpack.platform.build.BuildRequest;
|
||||
import org.springframework.boot.buildpack.platform.build.PullPolicy;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link BootBuildImage}.
|
||||
@@ -174,6 +176,18 @@ class BootBuildImageTests {
|
||||
assertThat(this.buildImage.createRequest().isCleanCache()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenUsingDefaultConfigurationThenRequestHasPublishDisabled() {
|
||||
assertThat(this.buildImage.createRequest().isPublish()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenPublishIsEnabledWithoutPublishRegistryThenExceptionIsThrown() {
|
||||
this.buildImage.setPublish(true);
|
||||
assertThatExceptionOfType(GradleException.class).isThrownBy(this.buildImage::createRequest)
|
||||
.withMessageContaining("Publishing an image requires docker.publishRegistry to be configured");
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoBuilderIsConfiguredThenRequestHasDefaultBuilder() {
|
||||
assertThat(this.buildImage.createRequest().getBuilder().getName()).isEqualTo("paketo-buildpacks/builder");
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
|
||||
import org.springframework.boot.buildpack.platform.docker.configuration.DockerHost;
|
||||
import org.springframework.boot.buildpack.platform.docker.configuration.DockerRegistryAuthentication;
|
||||
import org.springframework.util.Base64Utils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -39,7 +38,8 @@ public class DockerSpecTests {
|
||||
void asDockerConfigurationWithDefaults() {
|
||||
DockerSpec dockerSpec = new DockerSpec();
|
||||
assertThat(dockerSpec.asDockerConfiguration().getHost()).isNull();
|
||||
assertThat(dockerSpec.asDockerConfiguration().getRegistryAuthentication()).isNull();
|
||||
assertThat(dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull();
|
||||
assertThat(dockerSpec.asDockerConfiguration().getPublishRegistryAuthentication()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -53,7 +53,8 @@ public class DockerSpecTests {
|
||||
assertThat(host.getAddress()).isEqualTo("docker.example.com");
|
||||
assertThat(host.isSecure()).isEqualTo(true);
|
||||
assertThat(host.getCertificatePath()).isEqualTo("/tmp/ca-cert");
|
||||
assertThat(dockerSpec.asDockerConfiguration().getRegistryAuthentication()).isNull();
|
||||
assertThat(dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull();
|
||||
assertThat(dockerSpec.asDockerConfiguration().getPublishRegistryAuthentication()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -65,59 +66,71 @@ public class DockerSpecTests {
|
||||
assertThat(host.getAddress()).isEqualTo("docker.example.com");
|
||||
assertThat(host.isSecure()).isEqualTo(false);
|
||||
assertThat(host.getCertificatePath()).isNull();
|
||||
assertThat(dockerSpec.asDockerConfiguration().getRegistryAuthentication()).isNull();
|
||||
assertThat(dockerSpec.asDockerConfiguration().getBuilderRegistryAuthentication()).isNull();
|
||||
assertThat(dockerSpec.asDockerConfiguration().getPublishRegistryAuthentication()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void asDockerConfigurationWithUserAuth() {
|
||||
DockerSpec.DockerRegistrySpec dockerRegistry = new DockerSpec.DockerRegistrySpec();
|
||||
dockerRegistry.setUsername("user");
|
||||
dockerRegistry.setPassword("secret");
|
||||
dockerRegistry.setUrl("https://docker.example.com");
|
||||
dockerRegistry.setEmail("docker@example.com");
|
||||
DockerSpec dockerSpec = new DockerSpec(dockerRegistry);
|
||||
DockerSpec dockerSpec = new DockerSpec(
|
||||
new DockerSpec.DockerRegistrySpec("user1", "secret1", "https://docker1.example.com",
|
||||
"docker1@example.com"),
|
||||
new DockerSpec.DockerRegistrySpec("user2", "secret2", "https://docker2.example.com",
|
||||
"docker2@example.com"));
|
||||
DockerConfiguration dockerConfiguration = dockerSpec.asDockerConfiguration();
|
||||
DockerRegistryAuthentication registryAuthentication = dockerConfiguration.getRegistryAuthentication();
|
||||
assertThat(registryAuthentication).isNotNull();
|
||||
assertThat(new String(Base64Utils.decodeFromString(registryAuthentication.createAuthHeader())))
|
||||
.contains("\"username\" : \"user\"").contains("\"password\" : \"secret\"")
|
||||
.contains("\"email\" : \"docker@example.com\"")
|
||||
.contains("\"serveraddress\" : \"https://docker.example.com\"");
|
||||
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))
|
||||
.contains("\"username\" : \"user1\"").contains("\"password\" : \"secret1\"")
|
||||
.contains("\"email\" : \"docker1@example.com\"")
|
||||
.contains("\"serveraddress\" : \"https://docker1.example.com\"");
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
|
||||
.contains("\"username\" : \"user2\"").contains("\"password\" : \"secret2\"")
|
||||
.contains("\"email\" : \"docker2@example.com\"")
|
||||
.contains("\"serveraddress\" : \"https://docker2.example.com\"");
|
||||
assertThat(dockerSpec.asDockerConfiguration().getHost()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void asDockerConfigurationWithIncompleteUserAuthFails() {
|
||||
DockerSpec.DockerRegistrySpec dockerRegistry = new DockerSpec.DockerRegistrySpec();
|
||||
dockerRegistry.setUsername("user");
|
||||
dockerRegistry.setUrl("https://docker.example.com");
|
||||
dockerRegistry.setEmail("docker@example.com");
|
||||
DockerSpec dockerSpec = new DockerSpec(dockerRegistry);
|
||||
void asDockerConfigurationWithIncompleteBuilderUserAuthFails() {
|
||||
DockerSpec.DockerRegistrySpec builderRegistry = new DockerSpec.DockerRegistrySpec("user", null,
|
||||
"https://docker.example.com", "docker@example.com");
|
||||
DockerSpec dockerSpec = new DockerSpec(builderRegistry, null);
|
||||
assertThatExceptionOfType(GradleException.class).isThrownBy(dockerSpec::asDockerConfiguration)
|
||||
.withMessageContaining("Invalid Docker registry configuration");
|
||||
.withMessageContaining("Invalid Docker builder registry configuration");
|
||||
}
|
||||
|
||||
@Test
|
||||
void asDockerConfigurationWithIncompletePublishUserAuthFails() {
|
||||
DockerSpec.DockerRegistrySpec publishRegistry = new DockerSpec.DockerRegistrySpec("user2", null,
|
||||
"https://docker2.example.com", "docker2@example.com");
|
||||
DockerSpec dockerSpec = new DockerSpec(null, publishRegistry);
|
||||
assertThatExceptionOfType(GradleException.class).isThrownBy(dockerSpec::asDockerConfiguration)
|
||||
.withMessageContaining("Invalid Docker publish registry configuration");
|
||||
}
|
||||
|
||||
@Test
|
||||
void asDockerConfigurationWithTokenAuth() {
|
||||
DockerSpec.DockerRegistrySpec dockerRegistry = new DockerSpec.DockerRegistrySpec();
|
||||
dockerRegistry.setToken("token");
|
||||
DockerSpec dockerSpec = new DockerSpec(dockerRegistry);
|
||||
DockerSpec dockerSpec = new DockerSpec(new DockerSpec.DockerRegistrySpec("token1"),
|
||||
new DockerSpec.DockerRegistrySpec("token2"));
|
||||
DockerConfiguration dockerConfiguration = dockerSpec.asDockerConfiguration();
|
||||
DockerRegistryAuthentication registryAuthentication = dockerConfiguration.getRegistryAuthentication();
|
||||
assertThat(registryAuthentication).isNotNull();
|
||||
assertThat(new String(Base64Utils.decodeFromString(registryAuthentication.createAuthHeader())))
|
||||
.contains("\"identitytoken\" : \"token\"");
|
||||
assertThat(decoded(dockerConfiguration.getBuilderRegistryAuthentication().getAuthHeader()))
|
||||
.contains("\"identitytoken\" : \"token1\"");
|
||||
assertThat(decoded(dockerConfiguration.getPublishRegistryAuthentication().getAuthHeader()))
|
||||
.contains("\"identitytoken\" : \"token2\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void asDockerConfigurationWithUserAndTokenAuthFails() {
|
||||
DockerSpec.DockerRegistrySpec dockerRegistry = new DockerSpec.DockerRegistrySpec();
|
||||
dockerRegistry.setUsername("user");
|
||||
dockerRegistry.setPassword("secret");
|
||||
dockerRegistry.setToken("token");
|
||||
DockerSpec dockerSpec = new DockerSpec(dockerRegistry);
|
||||
DockerSpec.DockerRegistrySpec builderRegistry = new DockerSpec.DockerRegistrySpec();
|
||||
builderRegistry.setUsername("user");
|
||||
builderRegistry.setPassword("secret");
|
||||
builderRegistry.setToken("token");
|
||||
DockerSpec dockerSpec = new DockerSpec(builderRegistry, null);
|
||||
assertThatExceptionOfType(GradleException.class).isThrownBy(dockerSpec::asDockerConfiguration)
|
||||
.withMessageContaining("Invalid Docker registry configuration");
|
||||
.withMessageContaining("Invalid Docker builder registry configuration");
|
||||
}
|
||||
|
||||
String decoded(String value) {
|
||||
return new String(Base64Utils.decodeFromString(value));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user