diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f19f559a..b6a41ef1fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,10 @@ All notable changes to this project will be documented in this file. - Simple example `helloworld` project under `examples/` ([#62](https://github.com/google/jib/pull/62)) - Better error messages when pushing an image manifest ([#63](https://github.com/google/jib/pull/63)) - Validates target image configuration ([#63](https://github.com/google/jib/pull/63)) +- Configure multiple credential helpers with `credHelpers` ([#68](https://github.com/google/jib/pull/68)) +- Configure registry credentials with Maven settings ([#81](https://github.com/google/jib/pull/81)) ### Changed -- Configure multiple credential helpers with `credHelpers` ([#68](https://github.com/google/jib/pull/68)) - Removed configuration `credentialHelperName` ([#68](https://github.com/google/jib/pull/68)) ### Fixed @@ -23,3 +24,4 @@ All notable changes to this project will be documented in this file. - Infers common credential helper names (for GCR and ECR) ([#64](https://github.com/google/jib/pull/64)) - Cannot use private base image ([#68](https://github.com/google/jib/pull/68)) - Building applications with no resources ([#73](https://github.com/google/jib/pull/73)) +- Pushing to registries like Docker Hub and ACR ([#75](https://github.com/google/jib/issues/75)) diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/builder/BuildImageStepsIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/builder/BuildImageStepsIntegrationTest.java index 3258f4439a..4af10e95a9 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/builder/BuildImageStepsIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/builder/BuildImageStepsIntegrationTest.java @@ -42,11 +42,11 @@ public void testSteps() throws Exception { SourceFilesConfiguration sourceFilesConfiguration = new TestSourceFilesConfiguration(); BuildConfiguration buildConfiguration = BuildConfiguration.builder() - .setBaseImageServerUrl("gcr.io") - .setBaseImageName("distroless/java") + .setBaseImageRegistry("gcr.io") + .setBaseImageRepository("distroless/java") .setBaseImageTag("latest") - .setTargetServerUrl("localhost:5000") - .setTargetImageName("testimage") + .setTargetRegistry("localhost:5000") + .setTargetRepository("testimage") .setTargetTag("testtag") .setMainClass("HelloWorld") .setBuildLogger(new TestBuildLogger()) diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverIntegrationTest.java index f9818537ca..e6dc2195cb 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverIntegrationTest.java @@ -29,7 +29,7 @@ public void testGetRegistryAuthenticator() RegistryClient registryClient = new RegistryClient(null, "registry.hub.docker.com", "library/busybox"); RegistryAuthenticator registryAuthenticator = registryClient.getRegistryAuthenticator(); - Authorization authorization = registryAuthenticator.authenticate(); + Authorization authorization = registryAuthenticator.authenticatePull(); RegistryClient authorizedRegistryClient = new RegistryClient(authorization, "registry.hub.docker.com", "library/busybox"); diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorIntegrationTest.java index 9338b60dd3..dec85e914c 100644 --- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorIntegrationTest.java +++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorIntegrationTest.java @@ -27,7 +27,7 @@ public class RegistryAuthenticatorIntegrationTest { public void testAuthenticate() throws RegistryAuthenticationFailedException { RegistryAuthenticator registryAuthenticator = RegistryAuthenticators.forDockerHub("library/busybox"); - Authorization authorization = registryAuthenticator.authenticate(); + Authorization authorization = registryAuthenticator.authenticatePull(); // Checks that some token was received. Assert.assertTrue(0 < authorization.getToken().length()); diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/AuthenticatePullStep.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/AuthenticatePullStep.java index c3c2fb05e2..bed7005302 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/AuthenticatePullStep.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/AuthenticatePullStep.java @@ -19,67 +19,52 @@ import com.google.cloud.tools.jib.Timer; import com.google.cloud.tools.jib.http.Authorization; import com.google.cloud.tools.jib.registry.RegistryAuthenticationFailedException; +import com.google.cloud.tools.jib.registry.RegistryAuthenticator; import com.google.cloud.tools.jib.registry.RegistryAuthenticators; import com.google.cloud.tools.jib.registry.RegistryException; -import com.google.cloud.tools.jib.registry.credentials.RegistryCredentials; import com.google.common.util.concurrent.ListenableFuture; import java.io.IOException; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import javax.annotation.Nullable; -/** Retrieves credentials to pull from the base image registry. */ +/** + * Authenticates pull from the base image registry using Docker Token Authentication. + * + * @see https://docs.docker.com/registry/spec/auth/token/ + */ class AuthenticatePullStep implements Callable { - private static final String DESCRIPTION = "Authenticating with base image registry"; + private static final String DESCRIPTION = "Authenticating pull from %s"; private final BuildConfiguration buildConfiguration; - private final ListenableFuture registryCredentialsFuture; + private final ListenableFuture registryCredentialsFuture; AuthenticatePullStep( BuildConfiguration buildConfiguration, - ListenableFuture registryCredentialsFuture) { + ListenableFuture registryCredentialsFuture) { this.buildConfiguration = buildConfiguration; this.registryCredentialsFuture = registryCredentialsFuture; } - /** Depends on nothing. */ + /** Depends on {@link RetrieveRegistryCredentialsStep}. */ @Override public Authorization call() throws RegistryAuthenticationFailedException, IOException, RegistryException, ExecutionException, InterruptedException { - try (Timer ignored = new Timer(buildConfiguration.getBuildLogger(), DESCRIPTION)) { - return RegistryAuthenticators.forOther( - buildConfiguration.getBaseImageServerUrl(), buildConfiguration.getBaseImageName()) - .setAuthorization(getBaseImageAuthorization()) - .authenticate(); + try (Timer ignored = + new Timer( + buildConfiguration.getBuildLogger(), + String.format(DESCRIPTION, buildConfiguration.getBaseImageRegistry()))) { + Authorization registryCredentials = NonBlockingFutures.get(registryCredentialsFuture); + RegistryAuthenticator registryAuthenticator = + RegistryAuthenticators.forOther( + buildConfiguration.getBaseImageRegistry(), + buildConfiguration.getBaseImageRepository()); + if (registryAuthenticator == null) { + return registryCredentials; + } + return registryAuthenticator.setAuthorization(registryCredentials).authenticatePull(); } } - - /** Attempts to retrieve authorization for pulling the base image. */ - @Nullable - private Authorization getBaseImageAuthorization() - throws ExecutionException, InterruptedException { - RegistryCredentials registryCredentials = NonBlockingFutures.get(registryCredentialsFuture); - String registry = buildConfiguration.getBaseImageServerUrl(); - - String credentialHelperSuffix = registryCredentials.getCredentialHelperUsed(registry); - Authorization authorization = registryCredentials.getAuthorization(registry); - if (credentialHelperSuffix == null || authorization == null) { - /* - * If no credentials found, give an info (not warning because in most cases, the base image is - * public and does not need extra credentials) and return null. - */ - buildConfiguration - .getBuildLogger() - .info("No credentials could be retrieved for registry " + registry); - return null; - } - - buildConfiguration - .getBuildLogger() - .info( - "Using docker-credential-" + credentialHelperSuffix + " for pulling from " + registry); - return authorization; - } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/AuthenticatePushStep.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/AuthenticatePushStep.java index 2444dbf77c..15e5e4e3c7 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/AuthenticatePushStep.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/AuthenticatePushStep.java @@ -18,52 +18,56 @@ import com.google.cloud.tools.jib.Timer; import com.google.cloud.tools.jib.http.Authorization; -import com.google.cloud.tools.jib.registry.credentials.RegistryCredentials; +import com.google.cloud.tools.jib.registry.RegistryAuthenticationFailedException; +import com.google.cloud.tools.jib.registry.RegistryAuthenticator; +import com.google.cloud.tools.jib.registry.RegistryAuthenticators; +import com.google.cloud.tools.jib.registry.RegistryException; import com.google.common.util.concurrent.ListenableFuture; +import java.io.IOException; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; -/** Retrieves credentials to push to a target registry. */ +/** + * Authenticates push to a target registry using Docker Token Authentication. + * + * @see https://docs.docker.com/registry/spec/auth/token/ + */ class AuthenticatePushStep implements Callable { private static final String DESCRIPTION = "Authenticating with push to %s"; private final BuildConfiguration buildConfiguration; - private final ListenableFuture registryCredentialsFuture; + private final ListenableFuture registryCredentialsFuture; AuthenticatePushStep( BuildConfiguration buildConfiguration, - ListenableFuture registryCredentialsFuture) { + ListenableFuture registryCredentialsFuture) { this.buildConfiguration = buildConfiguration; this.registryCredentialsFuture = registryCredentialsFuture; } - /** Depends on nothing. */ + /** Depends on {@link RetrieveRegistryCredentialsStep}. */ @Override @Nullable - public Authorization call() throws ExecutionException, InterruptedException { + public Authorization call() + throws ExecutionException, InterruptedException, RegistryAuthenticationFailedException, + IOException, RegistryException { try (Timer ignored = new Timer( buildConfiguration.getBuildLogger(), - String.format(DESCRIPTION, buildConfiguration.getTargetServerUrl()))) { - RegistryCredentials registryCredentials = NonBlockingFutures.get(registryCredentialsFuture); - String registry = buildConfiguration.getTargetServerUrl(); - - String credentialHelperSuffix = registryCredentials.getCredentialHelperUsed(registry); - Authorization authorization = registryCredentials.getAuthorization(registry); - if (credentialHelperSuffix == null || authorization == null) { - // If no credentials found, give a warning and return null. - buildConfiguration - .getBuildLogger() - .warn("No credentials could be retrieved for registry " + registry); - return null; + String.format(DESCRIPTION, buildConfiguration.getTargetRegistry()))) { + Authorization registryCredentials = NonBlockingFutures.get(registryCredentialsFuture); + RegistryAuthenticator registryAuthenticator = + RegistryAuthenticators.forOther( + buildConfiguration.getTargetRegistry(), buildConfiguration.getTargetRepository()); + if (registryAuthenticator == null) { + return registryCredentials; } - buildConfiguration - .getBuildLogger() - .info( - "Using docker-credential-" + credentialHelperSuffix + " for pushing to " + registry); - return authorization; + return registryAuthenticator + .setAuthorization(NonBlockingFutures.get(registryCredentialsFuture)) + .authenticatePush(); } } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildAndPushContainerConfigurationStep.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildAndPushContainerConfigurationStep.java index 1249212f15..ec6a1eb70d 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildAndPushContainerConfigurationStep.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildAndPushContainerConfigurationStep.java @@ -90,8 +90,8 @@ private BlobDescriptor afterBaseImageLayerFuturesFuture() RegistryClient registryClient = new RegistryClient( NonBlockingFutures.get(pushAuthorizationFuture), - buildConfiguration.getTargetServerUrl(), - buildConfiguration.getTargetImageName()) + buildConfiguration.getTargetRegistry(), + buildConfiguration.getTargetRepository()) .setTimer(timer); // Constructs the image. diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildConfiguration.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildConfiguration.java index 2a3a050f9e..18c456c3f1 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildConfiguration.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildConfiguration.java @@ -31,21 +31,23 @@ public class BuildConfiguration { @VisibleForTesting enum Fields { /** The server URL of the registry to pull the base image from. */ - BASE_IMAGE_SERVER_URL(true), + BASE_IMAGE_REGISTRY(true), /** The image name/repository of the base image (also known as the registry namespace). */ - BASE_IMAGE_NAME(true), + BASE_IMAGE_REPOSITORY(true), /** The base image tag. */ BASE_IMAGE_TAG(true), /** The server URL of the registry to push the built image to. */ - TARGET_SERVER_URL(true), + TARGET_REGISTRY(true), /** The image name/repository of the built image (also known as the registry namespace). */ - TARGET_IMAGE_NAME(true), + TARGET_REPOSITORY(true), /** The image tag of the built image (the part after the colon). */ TARGET_TAG(true), /** The credential helper names used by {@link RegistryCredentials}. */ CREDENTIAL_HELPER_NAMES(false), + /** Known registry credentials to fallback on. */ + KNOWN_REGISTRY_CREDENTIALS(false), /** The main class to use when running the application. */ MAIN_CLASS(true), @@ -74,11 +76,11 @@ public static class Builder { static final Map FIELD_DESCRIPTIONS = new EnumMap(Fields.class) { { - put(Fields.BASE_IMAGE_SERVER_URL, "base image registry server URL"); - put(Fields.BASE_IMAGE_NAME, "base image name"); + put(Fields.BASE_IMAGE_REGISTRY, "base image registry server URL"); + put(Fields.BASE_IMAGE_REPOSITORY, "base image name"); put(Fields.BASE_IMAGE_TAG, "base image tag"); - put(Fields.TARGET_SERVER_URL, "target registry server URL"); - put(Fields.TARGET_IMAGE_NAME, "target image name"); + put(Fields.TARGET_REGISTRY, "target registry server URL"); + put(Fields.TARGET_REPOSITORY, "target image name"); put(Fields.TARGET_TAG, "target image tag"); put(Fields.CREDENTIAL_HELPER_NAMES, "credential helper names"); put(Fields.MAIN_CLASS, "main class"); @@ -96,6 +98,7 @@ public static class Builder { private Builder() { // Sets default empty values. values.put(Fields.CREDENTIAL_HELPER_NAMES, Collections.emptyList()); + values.put(Fields.KNOWN_REGISTRY_CREDENTIALS, RegistryCredentials.none()); values.put(Fields.JVM_FLAGS, Collections.emptyList()); values.put(Fields.ENVIRONMENT, Collections.emptyMap()); } @@ -105,13 +108,13 @@ public Builder setBuildLogger(BuildLogger buildLogger) { return this; } - public Builder setBaseImageServerUrl(String baseImageServerUrl) { - values.put(Fields.BASE_IMAGE_SERVER_URL, baseImageServerUrl); + public Builder setBaseImageRegistry(String baseImageServerUrl) { + values.put(Fields.BASE_IMAGE_REGISTRY, baseImageServerUrl); return this; } - public Builder setBaseImageName(String baseImageName) { - values.put(Fields.BASE_IMAGE_NAME, baseImageName); + public Builder setBaseImageRepository(String baseImageName) { + values.put(Fields.BASE_IMAGE_REPOSITORY, baseImageName); return this; } @@ -120,13 +123,13 @@ public Builder setBaseImageTag(String baseImageTag) { return this; } - public Builder setTargetServerUrl(String targetServerUrl) { - values.put(Fields.TARGET_SERVER_URL, targetServerUrl); + public Builder setTargetRegistry(String targetServerUrl) { + values.put(Fields.TARGET_REGISTRY, targetServerUrl); return this; } - public Builder setTargetImageName(String targetImageName) { - values.put(Fields.TARGET_IMAGE_NAME, targetImageName); + public Builder setTargetRepository(String targetImageName) { + values.put(Fields.TARGET_REPOSITORY, targetImageName); return this; } @@ -142,6 +145,13 @@ public Builder setCredentialHelperNames(List credentialHelperNames) { return this; } + public Builder setKnownRegistryCredentials(RegistryCredentials knownRegistryCredentials) { + if (knownRegistryCredentials != null) { + values.put(Fields.KNOWN_REGISTRY_CREDENTIALS, knownRegistryCredentials); + } + return this; + } + public Builder setMainClass(String mainClass) { values.put(Fields.MAIN_CLASS, mainClass); return this; @@ -220,30 +230,34 @@ public BuildLogger getBuildLogger() { return buildLogger; } - public String getBaseImageServerUrl() { - return getFieldValue(Fields.BASE_IMAGE_SERVER_URL); + public String getBaseImageRegistry() { + return getFieldValue(Fields.BASE_IMAGE_REGISTRY); } - public String getBaseImageName() { - return getFieldValue(Fields.BASE_IMAGE_NAME); + public String getBaseImageRepository() { + return getFieldValue(Fields.BASE_IMAGE_REPOSITORY); } public String getBaseImageTag() { return getFieldValue(Fields.BASE_IMAGE_TAG); } - public String getTargetServerUrl() { - return getFieldValue(Fields.TARGET_SERVER_URL); + public String getTargetRegistry() { + return getFieldValue(Fields.TARGET_REGISTRY); } - public String getTargetImageName() { - return getFieldValue(Fields.TARGET_IMAGE_NAME); + public String getTargetRepository() { + return getFieldValue(Fields.TARGET_REPOSITORY); } public String getTargetTag() { return getFieldValue(Fields.TARGET_TAG); } + public RegistryCredentials getKnownRegistryCredentials() { + return getFieldValue(Fields.KNOWN_REGISTRY_CREDENTIALS); + } + public List getCredentialHelperNames() { return getFieldValue(Fields.CREDENTIAL_HELPER_NAMES); } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildImageSteps.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildImageSteps.java index b588f006b6..66b56798f1 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildImageSteps.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/BuildImageSteps.java @@ -23,7 +23,6 @@ import com.google.cloud.tools.jib.cache.CachedLayer; import com.google.cloud.tools.jib.http.Authorization; import com.google.cloud.tools.jib.image.Image; -import com.google.cloud.tools.jib.registry.credentials.RegistryCredentials; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -68,26 +67,31 @@ public void run() try (Cache cache = Cache.init(cacheDirectory)) { timer2.lap("Setting up credential retrieval"); - ListenableFuture retrieveRegistryCredentialsFuture = + ListenableFuture retrieveTargetRegistryCredentialsFuture = listeningExecutorService.submit( - new RetrieveRegistryCredentialsStep(buildConfiguration)); + new RetrieveRegistryCredentialsStep( + buildConfiguration, buildConfiguration.getTargetRegistry())); + ListenableFuture retrieveBaseImageRegistryCredentialsFuture = + listeningExecutorService.submit( + new RetrieveRegistryCredentialsStep( + buildConfiguration, buildConfiguration.getBaseImageRegistry())); timer2.lap("Setting up image push authentication"); // Authenticates push. ListenableFuture authenticatePushFuture = - Futures.whenAllSucceed(retrieveRegistryCredentialsFuture) + Futures.whenAllSucceed(retrieveTargetRegistryCredentialsFuture) .call( new AuthenticatePushStep( - buildConfiguration, retrieveRegistryCredentialsFuture), + buildConfiguration, retrieveTargetRegistryCredentialsFuture), listeningExecutorService); timer2.lap("Setting up image pull authentication"); // Authenticates base image pull. ListenableFuture authenticatePullFuture = - Futures.whenAllSucceed(retrieveRegistryCredentialsFuture) + Futures.whenAllSucceed(retrieveBaseImageRegistryCredentialsFuture) .call( new AuthenticatePullStep( - buildConfiguration, retrieveRegistryCredentialsFuture), + buildConfiguration, retrieveBaseImageRegistryCredentialsFuture), listeningExecutorService); timer2.lap("Setting up base image pull"); diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PullAndCacheBaseImageLayerStep.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PullAndCacheBaseImageLayerStep.java index 09fb63468d..55eb9d8e8f 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PullAndCacheBaseImageLayerStep.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PullAndCacheBaseImageLayerStep.java @@ -64,8 +64,8 @@ public CachedLayer call() RegistryClient registryClient = new RegistryClient( pullAuthorizationFuture.get(), - buildConfiguration.getBaseImageServerUrl(), - buildConfiguration.getBaseImageName()); + buildConfiguration.getBaseImageRegistry(), + buildConfiguration.getBaseImageRepository()); // Checks if the layer already exists in the cache. CachedLayer cachedLayer = new CacheChecker(cache).getLayer(layerDigest); diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PullBaseImageStep.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PullBaseImageStep.java index 010f605482..af4bf33eef 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PullBaseImageStep.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PullBaseImageStep.java @@ -61,8 +61,8 @@ public Image call() RegistryClient registryClient = new RegistryClient( NonBlockingFutures.get(pullAuthorizationFuture), - buildConfiguration.getBaseImageServerUrl(), - buildConfiguration.getBaseImageName()); + buildConfiguration.getBaseImageRegistry(), + buildConfiguration.getBaseImageRepository()); ManifestTemplate manifestTemplate = registryClient.pullManifest(buildConfiguration.getBaseImageTag()); diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PushBlobStep.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PushBlobStep.java index 44f7ffe8e0..7a781632d6 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PushBlobStep.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PushBlobStep.java @@ -56,8 +56,8 @@ public Void call() RegistryClient registryClient = new RegistryClient( pushAuthorizationFuture.get(), - buildConfiguration.getTargetServerUrl(), - buildConfiguration.getTargetImageName()) + buildConfiguration.getTargetRegistry(), + buildConfiguration.getTargetRepository()) .setTimer(timer); if (registryClient.checkBlob(layerDigest) != null) { diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PushImageStep.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PushImageStep.java index 28a2a48670..457391cc3a 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PushImageStep.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/PushImageStep.java @@ -104,8 +104,8 @@ private Void afterPushBaseImageLayerFuturesFuture() RegistryClient registryClient = new RegistryClient( NonBlockingFutures.get(pushAuthorizationFuture), - buildConfiguration.getTargetServerUrl(), - buildConfiguration.getTargetImageName()); + buildConfiguration.getTargetRegistry(), + buildConfiguration.getTargetRepository()); // TODO: Consolidate with BuildAndPushContainerConfigurationStep. // Constructs the image. diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/RetrieveRegistryCredentialsStep.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/RetrieveRegistryCredentialsStep.java index db3ddbcfff..e3ceb9270d 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/RetrieveRegistryCredentialsStep.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/RetrieveRegistryCredentialsStep.java @@ -16,29 +16,69 @@ package com.google.cloud.tools.jib.builder; +import com.google.cloud.tools.jib.Timer; +import com.google.cloud.tools.jib.http.Authorization; +import com.google.cloud.tools.jib.registry.DockerCredentialRetriever; import com.google.cloud.tools.jib.registry.NonexistentDockerCredentialHelperException; -import com.google.cloud.tools.jib.registry.credentials.RegistryCredentials; +import com.google.cloud.tools.jib.registry.NonexistentServerUrlDockerCredentialHelperException; import java.io.IOException; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.Callable; /** Attempts to retrieve registry credentials. */ -class RetrieveRegistryCredentialsStep implements Callable { +class RetrieveRegistryCredentialsStep implements Callable { - private static final String DESCRIPTION = "Retrieving registry credentials"; + private static final String DESCRIPTION = "Retrieving registry credentials for %s"; private final BuildConfiguration buildConfiguration; + private final String registry; - RetrieveRegistryCredentialsStep(BuildConfiguration buildConfiguration) { + RetrieveRegistryCredentialsStep(BuildConfiguration buildConfiguration, String registry) { this.buildConfiguration = buildConfiguration; + this.registry = registry; } @Override - public RegistryCredentials call() throws IOException, NonexistentDockerCredentialHelperException { - List registries = - Arrays.asList( - buildConfiguration.getBaseImageServerUrl(), buildConfiguration.getTargetServerUrl()); - return RegistryCredentials.from(buildConfiguration.getCredentialHelperNames(), registries); + public Authorization call() throws IOException, NonexistentDockerCredentialHelperException { + try (Timer ignored = + new Timer( + buildConfiguration.getBuildLogger(), + String.format(DESCRIPTION, buildConfiguration.getTargetRegistry()))) { + // Tries to get registry credentials from Docker credential helpers. + for (String credentialHelperSuffix : buildConfiguration.getCredentialHelperNames()) { + // Attempts to retrieve authorization for the registry using + // docker-credential-[credentialSource]. + try { + Authorization authorization = + new DockerCredentialRetriever(registry, credentialHelperSuffix).retrieve(); + logGotCredentialsFrom("docker-credential-" + credentialHelperSuffix); + return authorization; + + } catch (NonexistentServerUrlDockerCredentialHelperException ex) { + // No authorization is found, so continues on to the next credential helper. + } + } + + // Tries to get registry credentials from known registry credentials. + if (buildConfiguration.getKnownRegistryCredentials().has(registry)) { + logGotCredentialsFrom( + buildConfiguration.getKnownRegistryCredentials().getCredentialSource(registry)); + return buildConfiguration.getKnownRegistryCredentials().getAuthorization(registry); + } + + /* + * If no credentials found, give an info (not warning because in most cases, the base image is + * public and does not need extra credentials) and return null. + */ + buildConfiguration + .getBuildLogger() + .info("No credentials could be retrieved for registry " + registry); + return null; + } + } + + private void logGotCredentialsFrom(String credentialSource) { + buildConfiguration + .getBuildLogger() + .info("Using " + credentialSource + " for pulling from " + registry); } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetriever.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetriever.java index 1d603464f3..5d26a30820 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetriever.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetriever.java @@ -44,10 +44,10 @@ public List getAccept() { } @Override - public RegistryAuthenticator handleResponse(Response response) throws RegistryErrorException { - throw new RegistryErrorExceptionBuilder(getActionDescription()) - .addReason("Did not receive '401 Unauthorized' response") - .build(); + @Nullable + public RegistryAuthenticator handleResponse(Response response) { + // The registry does not require authentication. + return null; } @Override @@ -66,6 +66,7 @@ public String getActionDescription() { } @Override + @Nullable public RegistryAuthenticator handleHttpResponseException( HttpResponseException httpResponseException) throws HttpResponseException, RegistryErrorException { diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java index 405c400112..b39875b255 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java @@ -34,7 +34,7 @@ import javax.annotation.Nullable; /** - * Authenticates pull access with a registry service. + * Authenticates push/pull access with a registry service. * * @see https://docs.docker.com/registry/spec/auth/token/ @@ -50,9 +50,15 @@ public class RegistryAuthenticator { * @see https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate */ + @Nullable static RegistryAuthenticator fromAuthenticationMethod( String authenticationMethod, String repository) throws RegistryAuthenticationFailedException, MalformedURLException { + // If the authentication method starts with 'Basic ', no registry authentication is needed. + if (authenticationMethod.matches("^Basic .*")) { + return null; + } + // Checks that the authentication method starts with 'Bearer '. if (!authenticationMethod.matches("^Bearer .*")) { throw newRegistryAuthenticationFailedException(authenticationMethod, "Bearer"); @@ -91,13 +97,12 @@ private static class AuthenticationResponseTemplate extends JsonTemplate { private String token; } - private final URL authenticationUrl; + private final String authenticationUrlBase; @Nullable private Authorization authorization; RegistryAuthenticator(String realm, String service, String repository) throws MalformedURLException { - authenticationUrl = - new URL(realm + "?service=" + service + "&scope=repository:" + repository + ":pull"); + authenticationUrlBase = realm + "?service=" + service + "&scope=repository:" + repository + ":"; } /** Sets an {@code Authorization} header to authenticate with. */ @@ -106,9 +111,30 @@ public RegistryAuthenticator setAuthorization(@Nullable Authorization authorizat return this; } - /** Sends the authentication request and retrieves the Bearer authorization token. */ - public Authorization authenticate() throws RegistryAuthenticationFailedException { - try (Connection connection = new Connection(authenticationUrl)) { + /** Authenticates permissions to pull. */ + public Authorization authenticatePull() throws RegistryAuthenticationFailedException { + return authenticate("pull"); + } + + /** Authenticates permission to pull and push. */ + public Authorization authenticatePush() throws RegistryAuthenticationFailedException { + return authenticate("pull,push"); + } + + @VisibleForTesting + URL getAuthenticationUrl(String scope) throws MalformedURLException { + return new URL(authenticationUrlBase + scope); + } + + /** + * Sends the authentication request and retrieves the Bearer authorization token. + * + * @param scope the scope of permissions to authenticate for + * @see https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate + */ + private Authorization authenticate(String scope) throws RegistryAuthenticationFailedException { + try (Connection connection = new Connection(getAuthenticationUrl(scope))) { Request.Builder requestBuilder = Request.builder(); if (authorization != null) { requestBuilder.setAuthorization(authorization); @@ -124,9 +150,4 @@ public Authorization authenticate() throws RegistryAuthenticationFailedException throw new RegistryAuthenticationFailedException(ex); } } - - @VisibleForTesting - URL getAuthenticationUrl() { - return authenticationUrl; - } } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticators.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticators.java index e853662bcb..54c3ed3678 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticators.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticators.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.net.MalformedURLException; +import javax.annotation.Nullable; /** Static initializers for {@link RegistryAuthenticator}. */ public abstract class RegistryAuthenticators { @@ -33,6 +34,7 @@ public static RegistryAuthenticator forDockerHub(String repository) } } + @Nullable public static RegistryAuthenticator forOther(String serverUrl, String repository) throws RegistryAuthenticationFailedException, IOException, RegistryException { try { diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java index f7513650d5..4a6b9d9113 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java @@ -101,13 +101,15 @@ public RegistryClient(@Nullable Authorization authorization, String serverUrl, S this.registryEndpointProperties = new RegistryEndpointProperties(serverUrl, imageName); } - /** Gets the {@link RegistryAuthenticator} to authenticate pulls from the registry. */ + /** + * @return the {@link RegistryAuthenticator} to authenticate pulls/pushes with the registry, or + * {@code null} if no token authentication is necessary + */ + @Nullable public RegistryAuthenticator getRegistryAuthenticator() throws IOException, RegistryException { // Gets the WWW-Authenticate header (eg. 'WWW-Authenticate: Bearer // realm="https://gcr.io/v2/token",service="gcr.io"') - AuthenticationMethodRetriever authenticationMethodRetriever = - new AuthenticationMethodRetriever(registryEndpointProperties); - return callRegistryEndpoint(authenticationMethodRetriever); + return callRegistryEndpoint(new AuthenticationMethodRetriever(registryEndpointProperties)); } /** diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointProvider.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointProvider.java index 1d1708fbb1..dd14f2e1a5 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointProvider.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointProvider.java @@ -49,6 +49,7 @@ interface RegistryEndpointProvider { List getAccept(); /** Handles the response specific to the registry action. */ + @Nullable T handleResponse(Response response) throws IOException, RegistryException; /** @@ -58,6 +59,7 @@ interface RegistryEndpointProvider { * @throws HttpResponseException {@code httpResponseException} if {@code httpResponseException} * could not be handled */ + @Nullable default T handleHttpResponseException(HttpResponseException httpResponseException) throws HttpResponseException, RegistryErrorException { throw httpResponseException; diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryUnauthorizedException.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryUnauthorizedException.java index d14b016634..68dfc3b5b3 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryUnauthorizedException.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryUnauthorizedException.java @@ -24,7 +24,7 @@ public class RegistryUnauthorizedException extends RegistryException { private final String imageReference; /** @param imageReference identifies the image registry and repository that denied access */ - RegistryUnauthorizedException(String imageReference, HttpResponseException cause) { + public RegistryUnauthorizedException(String imageReference, HttpResponseException cause) { super(cause); this.imageReference = imageReference; } diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/RegistryCredentials.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/RegistryCredentials.java index 7e2f5bca89..d98dd55957 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/RegistryCredentials.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/credentials/RegistryCredentials.java @@ -33,6 +33,17 @@ */ public class RegistryCredentials { + /** Instantiates with no credentials. */ + public static RegistryCredentials none() { + return new RegistryCredentials(); + } + + /** Instantiates with credentials for a single registry. */ + public static RegistryCredentials of( + String registry, String credentialSource, Authorization authorization) { + return new RegistryCredentials().store(registry, credentialSource, authorization); + } + /** * Retrieves credentials for {@code registries} using the credential helpers referred to by {@code * credentialHelperSuffixes}. @@ -48,11 +59,16 @@ public static RegistryCredentials from( // TODO: These can be done in parallel. for (String registry : registries) { for (String credentialHelperSuffix : credentialHelperSuffixes) { - Authorization authorization = retrieveCredentials(registry, credentialHelperSuffix); - if (authorization != null) { - - registryCredentials.store(registry, credentialHelperSuffix, authorization); - break; + // Attempts to retrieve authorization for the registry using + // docker-credential-[credentialSource]. + try { + registryCredentials.store( + registry, + "docker-credential-" + credentialHelperSuffix, + new DockerCredentialRetriever(registry, credentialHelperSuffix).retrieve()); + + } catch (NonexistentServerUrlDockerCredentialHelperException ex) { + // No authorization is found, so continues on to the next credential helper. } } } @@ -60,48 +76,54 @@ public static RegistryCredentials from( } /** - * Attempts to retrieve authorization for {@code registry} using docker-credential-{@code - * credentialHelperSuffix}. + * Instantiates from a credential source and a map of registry credentials. * - * @return the retrieved credentials, or {@code null} if not found + * @param credentialSource the source of the credentials, useful for informing users where the + * credentials came from + * @param registryCredentialMap a map from registries to their respective credentials */ - @Nullable - private static Authorization retrieveCredentials(String registry, String credentialHelperSuffix) - throws IOException, NonexistentDockerCredentialHelperException { - try { - DockerCredentialRetriever dockerCredentialRetriever = - new DockerCredentialRetriever(registry, credentialHelperSuffix); - - return dockerCredentialRetriever.retrieve(); - - } catch (NonexistentServerUrlDockerCredentialHelperException ex) { - // Returns null if no authorization is found. - return null; + public static RegistryCredentials from( + String credentialSource, Map registryCredentialMap) { + RegistryCredentials registryCredentials = new RegistryCredentials(); + for (Map.Entry registryCredential : registryCredentialMap.entrySet()) { + registryCredentials.store( + registryCredential.getKey(), credentialSource, registryCredential.getValue()); } + return registryCredentials; } - /** Pair of (Docker credential helper name, {@link Authorization}). */ - private static class CredentialHelperAuthorizationPair { + /** Pair of (source of credentials, {@link Authorization}). */ + private static class AuthorizationSourcePair { + + /** + * A string representation of where the credentials were retrieved from. This is useful for + * letting the user know which credentials were used. + */ + private final String credentialSource; - private final String credentialHelperSuffix; private final Authorization authorization; - private CredentialHelperAuthorizationPair( - String credentialHelperSuffix, Authorization authorization) { - this.credentialHelperSuffix = credentialHelperSuffix; + private AuthorizationSourcePair(String credentialSource, Authorization authorization) { + this.credentialSource = credentialSource; this.authorization = authorization; } } /** Maps from registry to the credentials for that registry. */ - private final Map credentials = new HashMap<>(); + private final Map credentials = new HashMap<>(); /** Instantiate using {@link #from}. */ private RegistryCredentials() {}; - private void store(String registry, String credentialHelperSuffix, Authorization authorization) { - credentials.put( - registry, new CredentialHelperAuthorizationPair(credentialHelperSuffix, authorization)); + private RegistryCredentials store( + String registry, String credentialSource, Authorization authorization) { + credentials.put(registry, new AuthorizationSourcePair(credentialSource, authorization)); + return this; + } + + /** @return {@code true} if there are credentials for {@code registry}; {@code false} otherwise */ + public boolean has(String registry) { + return credentials.containsKey(registry); } /** @@ -110,7 +132,7 @@ private void store(String registry, String credentialHelperSuffix, Authorization */ @Nullable public Authorization getAuthorization(String registry) { - if (!credentials.containsKey(registry)) { + if (!has(registry)) { return null; } return credentials.get(registry).authorization; @@ -121,10 +143,10 @@ public Authorization getAuthorization(String registry) { * registry}, or {@code null} if none exists */ @Nullable - public String getCredentialHelperUsed(String registry) { - if (!credentials.containsKey(registry)) { + public String getCredentialSource(String registry) { + if (!has(registry)) { return null; } - return credentials.get(registry).credentialHelperSuffix; + return credentials.get(registry).credentialSource; } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/BuildConfigurationTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/BuildConfigurationTest.java index b8ae08bbf8..50c95cb895 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/BuildConfigurationTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/BuildConfigurationTest.java @@ -41,21 +41,21 @@ public void testBuilder() { BuildConfiguration buildConfiguration = BuildConfiguration.builder() - .setBaseImageServerUrl(expectedBaseImageServerUrl) - .setBaseImageName(expectedBaseImageName) + .setBaseImageRegistry(expectedBaseImageServerUrl) + .setBaseImageRepository(expectedBaseImageName) .setBaseImageTag(expectedBaseImageTag) - .setTargetServerUrl(expectedTargetServerUrl) - .setTargetImageName(expectedTargetImageName) + .setTargetRegistry(expectedTargetServerUrl) + .setTargetRepository(expectedTargetImageName) .setTargetTag(expectedTargetTag) .setCredentialHelperNames(expectedCredentialHelperNames) .setMainClass(expectedMainClass) .setJvmFlags(expectedJvmFlags) .build(); - Assert.assertEquals(expectedBaseImageServerUrl, buildConfiguration.getBaseImageServerUrl()); - Assert.assertEquals(expectedBaseImageName, buildConfiguration.getBaseImageName()); + Assert.assertEquals(expectedBaseImageServerUrl, buildConfiguration.getBaseImageRegistry()); + Assert.assertEquals(expectedBaseImageName, buildConfiguration.getBaseImageRepository()); Assert.assertEquals(expectedBaseImageTag, buildConfiguration.getBaseImageTag()); - Assert.assertEquals(expectedTargetServerUrl, buildConfiguration.getTargetServerUrl()); - Assert.assertEquals(expectedTargetImageName, buildConfiguration.getTargetImageName()); + Assert.assertEquals(expectedTargetServerUrl, buildConfiguration.getTargetRegistry()); + Assert.assertEquals(expectedTargetImageName, buildConfiguration.getTargetRepository()); Assert.assertEquals(expectedTargetTag, buildConfiguration.getTargetTag()); Assert.assertEquals( expectedCredentialHelperNames, buildConfiguration.getCredentialHelperNames()); diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverTest.java index d0b1b570b3..65b18696a7 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverTest.java @@ -55,15 +55,8 @@ public void testGetAccept() { @Test public void testHandleResponse() { - try { - testAuthenticationMethodRetriever.handleResponse(Mockito.mock(Response.class)); - Assert.fail("Authentication method retriever should only handle HTTP error responses"); - - } catch (RegistryErrorException ex) { - Assert.assertThat( - ex.getMessage(), - CoreMatchers.containsString("Did not receive '401 Unauthorized' response")); - } + Assert.assertNull( + testAuthenticationMethodRetriever.handleResponse(Mockito.mock(Response.class))); } @Test @@ -155,7 +148,7 @@ public void testHandleHttpResponseException_pass() testAuthenticationMethodRetriever.handleHttpResponseException(mockHttpResponseException); Assert.assertEquals( - new URL("https://somerealm?service=someservice&scope=repository:someImageName:pull"), - registryAuthenticator.getAuthenticationUrl()); + new URL("https://somerealm?service=someservice&scope=repository:someImageName:someScope"), + registryAuthenticator.getAuthenticationUrl("someScope")); } } diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java index 74a7a166ab..7a2e894070 100644 --- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java @@ -25,15 +25,24 @@ public class RegistryAuthenticatorTest { @Test - public void testFromAuthenticationMethod() + public void testFromAuthenticationMethod_bearer() throws MalformedURLException, RegistryAuthenticationFailedException { RegistryAuthenticator registryAuthenticator = RegistryAuthenticator.fromAuthenticationMethod( "Bearer realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"", "someimage"); Assert.assertEquals( - new URL("https://somerealm?service=someservice&scope=repository:someimage:pull"), - registryAuthenticator.getAuthenticationUrl()); + new URL("https://somerealm?service=someservice&scope=repository:someimage:scope"), + registryAuthenticator.getAuthenticationUrl("scope")); + } + + @Test + public void testFromAuthenticationMethod_basic() + throws MalformedURLException, RegistryAuthenticationFailedException { + Assert.assertNull( + RegistryAuthenticator.fromAuthenticationMethod( + "Basic realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"", + "someimage")); } @Test @@ -41,7 +50,7 @@ public void testFromAuthenticationMethod_noBearer() throws MalformedURLException try { RegistryAuthenticator.fromAuthenticationMethod( "realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"", "someimage"); - Assert.fail("Authentication method without 'Bearer ' should fail"); + Assert.fail("Authentication method without 'Bearer ' or 'Basic ' should fail"); } catch (RegistryAuthenticationFailedException ex) { Assert.assertEquals( diff --git a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java index b4bf4c98ef..c4395b1f38 100644 --- a/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java +++ b/jib-maven-plugin/src/main/java/com/google/cloud/tools/jib/maven/BuildImageMojo.java @@ -16,15 +16,20 @@ package com.google.cloud.tools.jib.maven; +import com.google.api.client.http.HttpResponseException; import com.google.api.client.http.HttpStatusCodes; import com.google.cloud.tools.jib.builder.BuildConfiguration; import com.google.cloud.tools.jib.builder.BuildImageSteps; import com.google.cloud.tools.jib.builder.SourceFilesConfiguration; import com.google.cloud.tools.jib.cache.CacheMetadataCorruptedException; +import com.google.cloud.tools.jib.http.Authorization; +import com.google.cloud.tools.jib.http.Authorizations; import com.google.cloud.tools.jib.image.ImageReference; import com.google.cloud.tools.jib.image.InvalidImageReferenceException; +import com.google.cloud.tools.jib.registry.RegistryAuthenticationFailedException; import com.google.cloud.tools.jib.registry.RegistryClient; import com.google.cloud.tools.jib.registry.RegistryUnauthorizedException; +import com.google.cloud.tools.jib.registry.credentials.RegistryCredentials; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.io.IOException; @@ -32,11 +37,13 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; import org.apache.http.conn.HttpHostConnectException; +import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Plugin; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -45,6 +52,7 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; +import org.apache.maven.settings.Server; import org.codehaus.plexus.util.xml.Xpp3Dom; /** Builds a container image. */ @@ -67,6 +75,9 @@ public class BuildImageMojo extends AbstractMojo { @Parameter(defaultValue = "${project}", readonly = true) private MavenProject project; + @Parameter(defaultValue = "${session}", readonly = true) + private MavenSession session; + @Parameter(defaultValue = "gcr.io/distroless/java", required = true) private String from; @@ -108,10 +119,27 @@ public void execute() throws MojoExecutionException, MojoFailureException { SourceFilesConfiguration sourceFilesConfiguration = getSourceFilesConfiguration(); - // Parse 'from' into image reference. + // Parses 'from' into image reference. ImageReference baseImage = getBaseImageReference(); - // Infer common credential helper names if credHelpers is not set. + // Checks Maven settings for registry credentials. + session.getSettings().getServer(baseImage.getRegistry()); + Map registryCredentials = new HashMap<>(2); + // Retrieves credentials for the base image registry. + Authorization baseImageRegistryCredentials = + getRegistryCredentialsFromSettings(baseImage.getRegistry()); + if (baseImageRegistryCredentials != null) { + registryCredentials.put(baseImage.getRegistry(), baseImageRegistryCredentials); + } + // Retrieves credentials for the target registry. + Authorization targetRegistryCredentials = getRegistryCredentialsFromSettings(registry); + if (targetRegistryCredentials != null) { + registryCredentials.put(registry, targetRegistryCredentials); + } + RegistryCredentials mavenSettingsCredentials = + RegistryCredentials.from("Maven settings", registryCredentials); + + // Infers common credential helper names if credHelpers is not set. if (credHelpers == null) { credHelpers = new ArrayList<>(2); String baseImageRegistryCredHelper = inferCredHelper(baseImage.getRegistry()); @@ -127,13 +155,14 @@ public void execute() throws MojoExecutionException, MojoFailureException { BuildConfiguration buildConfiguration = BuildConfiguration.builder() .setBuildLogger(new MavenBuildLogger(getLog())) - .setBaseImageServerUrl(baseImage.getRegistry()) - .setBaseImageName(baseImage.getRepository()) + .setBaseImageRegistry(baseImage.getRegistry()) + .setBaseImageRepository(baseImage.getRepository()) .setBaseImageTag(baseImage.getTag()) - .setTargetServerUrl(registry) - .setTargetImageName(repository) + .setTargetRegistry(registry) + .setTargetRepository(repository) .setTargetTag(tag) .setCredentialHelperNames(credHelpers) + .setKnownRegistryCredentials(mavenSettingsCredentials) .setMainClass(mainClass) .setJvmFlags(jvmFlags) .setEnvironment(environment) @@ -179,6 +208,8 @@ void buildImage(BuildImageSteps buildImageSteps) throws MojoExecutionException { cacheMetadataCorruptedException, "run 'mvn clean' to clear the cache"); } catch (ExecutionException executionException) { + BuildConfiguration buildConfiguration = buildImageSteps.getBuildConfiguration(); + if (executionException.getCause() instanceof HttpHostConnectException) { // Failed to connect to registry. throwMojoExecutionExceptionWithHelpMessage( @@ -186,34 +217,18 @@ void buildImage(BuildImageSteps buildImageSteps) throws MojoExecutionException { "make sure your Internet is up and that the registry you are pushing to exists"); } else if (executionException.getCause() instanceof RegistryUnauthorizedException) { - BuildConfiguration buildConfiguration = buildImageSteps.getBuildConfiguration(); - - RegistryUnauthorizedException registryUnauthorizedException = - (RegistryUnauthorizedException) executionException.getCause(); - if (registryUnauthorizedException.getHttpResponseException().getStatusCode() - == HttpStatusCodes.STATUS_CODE_FORBIDDEN) { - // No permissions for registry/repository. - throwMojoExecutionExceptionWithHelpMessage( - registryUnauthorizedException, - "make sure your have permissions for " - + registryUnauthorizedException.getImageReference()); - - } else if (buildConfiguration.getCredentialHelperNames() == null - || buildConfiguration.getCredentialHelperNames().isEmpty()) { - // No credential helpers not defined. - throwMojoExecutionExceptionWithHelpMessage( - registryUnauthorizedException, - "set a credential helper name with the configuration 'credHelpers'"); - - } else { - // Credential helper probably was not configured correctly or did not have the necessary - // credentials. - throwMojoExecutionExceptionWithHelpMessage( - registryUnauthorizedException, - "make sure your credential helper for '" - + registryUnauthorizedException.getImageReference() - + "' is set up correctly"); - } + handleRegistryUnauthorizedException( + (RegistryUnauthorizedException) executionException.getCause(), buildConfiguration); + + } else if (executionException.getCause() instanceof RegistryAuthenticationFailedException + && executionException.getCause().getCause() instanceof HttpResponseException) { + handleRegistryUnauthorizedException( + new RegistryUnauthorizedException( + buildConfiguration.getTargetRegistry() + + "/" + + buildConfiguration.getTargetRepository(), + (HttpResponseException) executionException.getCause().getCause()), + buildConfiguration); } else { throwMojoExecutionExceptionWithHelpMessage(executionException.getCause(), null); @@ -246,6 +261,17 @@ String inferCredHelper(String registry) { return null; } + /** Attempts to retrieve credentials for {@code registry} from Maven settings. */ + @Nullable + private Authorization getRegistryCredentialsFromSettings(String registry) { + Server registryServerSettings = session.getSettings().getServer(registry); + if (registryServerSettings == null) { + return null; + } + return Authorizations.withBasicCredentials( + registryServerSettings.getUsername(), registryServerSettings.getPassword()); + } + /** Checks validity of plugin parameters. */ private void validateParameters() throws MojoFailureException { // Validates 'registry'. @@ -338,6 +364,40 @@ private ImageReference getBaseImageReference() throws MojoFailureException { } } + private void handleRegistryUnauthorizedException( + RegistryUnauthorizedException registryUnauthorizedException, + BuildConfiguration buildConfiguration) + throws MojoExecutionException { + if (registryUnauthorizedException.getHttpResponseException().getStatusCode() + == HttpStatusCodes.STATUS_CODE_FORBIDDEN) { + // No permissions for registry/repository. + throwMojoExecutionExceptionWithHelpMessage( + registryUnauthorizedException, + "make sure your have permissions for " + + registryUnauthorizedException.getImageReference()); + + } else if (buildConfiguration.getCredentialHelperNames() == null + || buildConfiguration.getCredentialHelperNames().isEmpty() + || buildConfiguration.getKnownRegistryCredentials() == null) { + // No credential helpers not defined. + throwMojoExecutionExceptionWithHelpMessage( + registryUnauthorizedException, + "set a credential helper name with the configuration 'credHelpers' or " + + "set credentials for '" + + registryUnauthorizedException.getImageReference() + + "' in your Maven settings"); + + } else { + // Credential helper probably was not configured correctly or did not have the necessary + // credentials. + throwMojoExecutionExceptionWithHelpMessage( + registryUnauthorizedException, + "make sure your credentials for '" + + registryUnauthorizedException.getImageReference() + + "' is set up correctly"); + } + } + /** * Wraps an exception in a {@link MojoExecutionException} and provides a suggestion on how to fix * the error. diff --git a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildImageMojoTest.java b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildImageMojoTest.java index 605be6fc4a..99f14a1579 100644 --- a/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildImageMojoTest.java +++ b/jib-maven-plugin/src/test/java/com/google/cloud/tools/jib/maven/BuildImageMojoTest.java @@ -22,6 +22,7 @@ import com.google.cloud.tools.jib.builder.BuildImageSteps; import com.google.cloud.tools.jib.cache.CacheMetadataCorruptedException; import com.google.cloud.tools.jib.registry.RegistryUnauthorizedException; +import com.google.cloud.tools.jib.registry.credentials.RegistryCredentials; import java.io.IOException; import java.util.Collections; import java.util.concurrent.ExecutionException; @@ -140,6 +141,8 @@ public void testBuildImage_executionException_registryUnauthorizedException_noCr HttpResponseException mockHttpResponseException = Mockito.mock(HttpResponseException.class); Mockito.when(mockRegistryUnauthorizedException.getHttpResponseException()) .thenReturn(mockHttpResponseException); + Mockito.when(mockRegistryUnauthorizedException.getImageReference()) + .thenReturn("someregistry/somerepository"); Mockito.when(mockHttpResponseException.getStatusCode()).thenReturn(-1); // Unknown Mockito.when(mockBuildImageSteps.getBuildConfiguration()) @@ -155,7 +158,7 @@ public void testBuildImage_executionException_registryUnauthorizedException_noCr } catch (MojoExecutionException ex) { Assert.assertEquals( - "Build image failed, perhaps you should set a credential helper name with the configuration 'credHelpers'", + "Build image failed, perhaps you should set a credential helper name with the configuration 'credHelpers' or set credentials for 'someregistry/somerepository' in your Maven settings", ex.getMessage()); Assert.assertEquals(mockRegistryUnauthorizedException, ex.getCause()); } @@ -181,6 +184,8 @@ public void testBuildImage_executionException_registryUnauthorizedException_othe BuildConfiguration mockBuildConfiguration = Mockito.mock(BuildConfiguration.class); Mockito.when(mockBuildConfiguration.getCredentialHelperNames()) .thenReturn(Collections.singletonList("some-credential-helper")); + Mockito.when(mockBuildConfiguration.getKnownRegistryCredentials()) + .thenReturn(Mockito.mock(RegistryCredentials.class)); Mockito.when(mockBuildImageSteps.getBuildConfiguration()).thenReturn(mockBuildConfiguration); try { @@ -189,7 +194,7 @@ public void testBuildImage_executionException_registryUnauthorizedException_othe } catch (MojoExecutionException ex) { Assert.assertEquals( - "Build image failed, perhaps you should make sure your credential helper for 'someregistry/somerepository' is set up correctly", + "Build image failed, perhaps you should make sure your credentials for 'someregistry/somerepository' is set up correctly", ex.getMessage()); Assert.assertEquals(mockRegistryUnauthorizedException, ex.getCause()); } diff --git a/jib-maven-plugin/src/test/resources/project/pom.xml b/jib-maven-plugin/src/test/resources/project/pom.xml index f6c5aa8d7d..8651c645a9 100644 --- a/jib-maven-plugin/src/test/resources/project/pom.xml +++ b/jib-maven-plugin/src/test/resources/project/pom.xml @@ -41,9 +41,9 @@ gcr.io qingyangc-sandbox/jibtestimage built-with-jib - - gcr - + + gcr + com.test.HelloWorld