Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-enable cross-repository blob mounts #1793

Merged
merged 12 commits into from
Jun 25, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,16 @@ public BlobDescriptor call() throws IOException, RegistryException, ExecutionExc
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 @@ -449,6 +449,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()) {
loosebazooka marked this conversation as resolved.
Show resolved Hide resolved
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