Skip to content

Commit

Permalink
Re-enable cross-repository blob mounts (#1793)
Browse files Browse the repository at this point in the history
* Nothing uses RegistryCredentials

* Enable cross-repository blob mounts

* move authorization check from PushBlobStep to RegistryClient; renames

* add missing @nullable

* add java-jwt dependency

* Add CHANGELOG

* Review notes:
  - be more explicit for JWT payload source
  - use JsonTemplateMapper rather than third-party library for parsing JWT
  - simplify RegistryClient factory use

* review comments

* Move the docker-token related checks into RegistryClient

* canMountBlobs -> canAttemptBlobMount, fix embarrassing inverted test

* Add ability to disable blob-mounts with `jib.blobMounts` flag

* whitespace
  • Loading branch information
briandealwis authored Jun 25, 2019
1 parent 3e0280b commit 740c2ac
Show file tree
Hide file tree
Showing 15 changed files with 442 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,16 @@ public BlobDescriptor call() throws IOException, RegistryException {
return blobDescriptor;
}

// todo: leverage cross-repository mounts
registryClient.pushBlob(blobDescriptor.getDigest(), blob, null, throttledProgressReporter);
// If base and target images are in the same registry, then use mount/from to try mounting the
// BLOB from the base image repository to the target image repository and possibly avoid
// having to push the BLOB. See
// https://docs.docker.com/registry/spec/api/#cross-repository-blob-mount for details.
String baseRegistry = buildConfiguration.getBaseImageConfiguration().getImageRegistry();
String baseRepository = buildConfiguration.getBaseImageConfiguration().getImageRepository();
String targetRegistry = buildConfiguration.getTargetImageConfiguration().getImageRegistry();
String sourceRepository = targetRegistry.equals(baseRegistry) ? baseRepository : null;
registryClient.pushBlob(
blobDescriptor.getDigest(), blob, sourceRepository, throttledProgressReporter);

return blobDescriptor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,18 @@ public RegistryClient.Factory newBaseImageRegistryClientFactory() {
* @return a new {@link RegistryClient.Factory}
*/
public RegistryClient.Factory newTargetImageRegistryClientFactory() {
// if base and target are on the same registry, try enabling cross-repository mounts
if (baseImageConfiguration
.getImageRegistry()
.equals(targetImageConfiguration.getImageRegistry())) {
return RegistryClient.factory(
getEventHandlers(),
targetImageConfiguration.getImageRegistry(),
targetImageConfiguration.getImageRepository(),
baseImageConfiguration.getImageRepository())
.setAllowInsecureRegistries(getAllowInsecureRegistries())
.setUserAgentSuffix(getToolName());
}
return newRegistryClientFactory(targetImageConfiguration);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public class JibSystemProperties {

@VisibleForTesting public static final String HTTP_TIMEOUT = "jib.httpTimeout";

@VisibleForTesting static final String CROSS_REPOSITORY_BLOB_MOUNTS = "jib.blobMounts";

@VisibleForTesting
public static final String SEND_CREDENTIALS_OVER_HTTP = "sendCredentialsOverHttp";

Expand All @@ -46,6 +48,18 @@ public static int getHttpTimeout() {
return Integer.getInteger(HTTP_TIMEOUT);
}

/**
* Gets whether or not to use <em>cross-repository blob mounts</em> when uploading image layers
* ({@code mount/from}). This is defined by the {@code jib.blobMounts} system property.
*
* @return {@code true} if {@code mount/from} should be used, {@code false} if not, defaulting to
* {@code true}
*/
public static boolean useCrossRepositoryBlobMounts() {
return System.getProperty(CROSS_REPOSITORY_BLOB_MOUNTS) == null
|| Boolean.getBoolean(CROSS_REPOSITORY_BLOB_MOUNTS);
}

/**
* Gets whether or not to serialize Jib's execution. This is defined by the {@code jibSerialize}
* system property.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,6 @@
*/
public class Authorization {

/**
* @param token the token
* @return an {@link Authorization} with a {@code Bearer} token
*/
public static Authorization fromBearerToken(String token) {
return new Authorization("Bearer", token);
}

/**
* @param username the username
* @param secret the secret
Expand All @@ -56,6 +48,14 @@ public static Authorization fromBasicToken(String token) {
return new Authorization("Basic", token);
}

/**
* @param token the token
* @return an {@link Authorization} with a {@code Bearer} token
*/
public static Authorization fromBearerToken(String token) {
return new Authorization("Bearer", token);
}

private final String scheme;
private final String token;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@ public static <T extends JsonTemplate> T readJson(String jsonString, Class<T> te
return objectMapper.readValue(jsonString, templateClass);
}

/**
* Deserializes a JSON object from a JSON byte array.
*
* @param <T> child type of {@link JsonTemplate}
* @param jsonBytes a JSON byte array
* @param templateClass the template to deserialize the string to
* @return the template filled with the values parsed from {@code jsonBytes}
* @throws IOException if an error occurred during parsing the JSON
*/
public static <T extends JsonTemplate> T readJson(byte[] jsonBytes, Class<T> templateClass)
throws IOException {
return objectMapper.readValue(jsonBytes, templateClass);
}

/**
* Deserializes a JSON object list from a JSON string.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@
import com.google.cloud.tools.jib.json.JsonTemplateMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.CharStreams;
import com.google.common.net.MediaType;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -180,26 +183,30 @@ public Authorization authenticatePush(@Nullable Credential credential)
}

@VisibleForTesting
String getServiceScopeRequestParameters(String scope) {
return "service="
+ service
+ "&scope=repository:"
+ registryEndpointRequestProperties.getImageName()
+ ":"
+ scope;
String getServiceScopeRequestParameters(Map<String, String> repositoryScopes) {
StringBuilder parameters = new StringBuilder("service=").append(service);
for (Entry<String, String> pair : repositoryScopes.entrySet()) {
parameters
.append("&scope=repository:")
.append(pair.getKey())
.append(":")
.append(pair.getValue());
}
return parameters.toString();
}

@VisibleForTesting
URL getAuthenticationUrl(@Nullable Credential credential, String scope)
URL getAuthenticationUrl(@Nullable Credential credential, Map<String, String> repositoryScopes)
throws MalformedURLException {
return isOAuth2Auth(credential)
? new URL(realm) // Required parameters will be sent via POST .
: new URL(realm + "?" + getServiceScopeRequestParameters(scope));
: new URL(realm + "?" + getServiceScopeRequestParameters(repositoryScopes));
}

@VisibleForTesting
String getAuthRequestParameters(@Nullable Credential credential, String scope) {
String serviceScope = getServiceScopeRequestParameters(scope);
String getAuthRequestParameters(
@Nullable Credential credential, Map<String, String> repositoryScopes) {
String serviceScope = getServiceScopeRequestParameters(repositoryScopes);
return isOAuth2Auth(credential)
? serviceScope
// https://github.com/GoogleContainerTools/jib/pull/1545
Expand Down Expand Up @@ -227,15 +234,43 @@ boolean isOAuth2Auth(@Nullable Credential credential) {
*/
private Authorization authenticate(@Nullable Credential credential, String scope)
throws RegistryAuthenticationFailedException {
// try authorizing against both the main repository and the source repository too
// to enable cross-repository mounts on pushes
if (registryEndpointRequestProperties.getSourceImageName() != null) {
try {
Map<String, String> scopes =
ImmutableMap.of(
registryEndpointRequestProperties.getImageName(),
scope,
registryEndpointRequestProperties.getSourceImageName(),
"pull");
Authorization auth = authenticate(credential, scopes);
if (auth != null) {
return auth;
}
} catch (RegistryAuthenticationFailedException ex) {
// Unable to obtain authorization with source image: fallthrough and try without
}
}
Map<String, String> repositoryScopes =
ImmutableMap.of(registryEndpointRequestProperties.getImageName(), scope);
Authorization auth = authenticate(credential, repositoryScopes);
return auth;
}

private Authorization authenticate(
@Nullable Credential credential, Map<String, String> repositoryScopes)
throws RegistryAuthenticationFailedException {
try (Connection connection =
Connection.getConnectionFactory().apply(getAuthenticationUrl(credential, scope))) {
Connection.getConnectionFactory()
.apply(getAuthenticationUrl(credential, repositoryScopes))) {
Request.Builder requestBuilder =
Request.builder()
.setHttpTimeout(JibSystemProperties.getHttpTimeout())
.setUserAgent(userAgent);

if (isOAuth2Auth(credential)) {
String parameters = getAuthRequestParameters(credential, scope);
String parameters = getAuthRequestParameters(credential, repositoryScopes);
requestBuilder.setBody(
new BlobHttpContent(Blobs.from(parameters), MediaType.FORM_DATA.toString()));
} else if (credential != null) {
Expand All @@ -257,9 +292,9 @@ private Authorization authenticate(@Nullable Credential credential, String scope
registryEndpointRequestProperties.getServerUrl(),
registryEndpointRequestProperties.getImageName(),
"Did not get token in authentication response from "
+ getAuthenticationUrl(credential, scope)
+ getAuthenticationUrl(credential, repositoryScopes)
+ "; parameters: "
+ getAuthRequestParameters(credential, scope));
+ getAuthRequestParameters(credential, repositoryScopes));
}
return Authorization.fromBearerToken(responseJson.getToken());

Expand Down
Loading

0 comments on commit 740c2ac

Please sign in to comment.