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

Add registry mirror support in Jib build plugins #3011

Merged
merged 44 commits into from
Jan 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b1286b6
wip
chanseokoh Jan 8, 2021
33c4292
Merge remote-tracking branch 'origin/master' into docker-mirror-pull
chanseokoh Jan 8, 2021
211387c
wip
chanseokoh Jan 8, 2021
a454011
Remove unnecessary file lock
chanseokoh Jan 8, 2021
211aa63
Merge remote-tracking branch 'origin/master' into file-sync-cleanup
chanseokoh Jan 12, 2021
db873bb
Merge remote-tracking branch 'origin/master' into file-sync-cleanup
chanseokoh Jan 12, 2021
3412f54
wip
chanseokoh Jan 12, 2021
a2a202a
Add tests
chanseokoh Jan 12, 2021
1a482f8
Add registry mirror configs
chanseokoh Jan 12, 2021
196d077
wip
chanseokoh Jan 12, 2021
382d0f8
Clean up
chanseokoh Jan 13, 2021
1dcd766
Merge remote-tracking branch 'origin/master' into refactor-global-jib…
chanseokoh Jan 13, 2021
f6ce23b
Merge branch 'refactor-global-jib-config' into add-mirror-configs
chanseokoh Jan 13, 2021
d28eb8a
wip
chanseokoh Jan 13, 2021
84ed59e
wip
chanseokoh Jan 14, 2021
15f6ea5
fix
chanseokoh Jan 14, 2021
24ce1b7
wip
chanseokoh Jan 14, 2021
1b7e9f1
refactor
chanseokoh Jan 14, 2021
264adbd
fix
chanseokoh Jan 14, 2021
146cd76
Add Javadocs
chanseokoh Jan 15, 2021
0d3fb90
Merge remote-tracking branch 'origin/master' into add-mirror-configs
chanseokoh Jan 15, 2021
9345bfa
Use ListMultimap
chanseokoh Jan 15, 2021
b59ca3a
more Multimap
chanseokoh Jan 15, 2021
babbc4a
Fix test
chanseokoh Jan 20, 2021
df45f26
Merge branch 'docker-mirror-pull' into docker-mirror-final
chanseokoh Jan 20, 2021
8a4ce6d
wire mirror config
chanseokoh Jan 20, 2021
04d6d67
Rename to addRegistryMirrors()
chanseokoh Jan 21, 2021
2c0bff0
Merge remote-tracking branch 'origin/master' into add-mirror-configs
chanseokoh Jan 21, 2021
e365444
Refactor
chanseokoh Jan 21, 2021
8b3cc74
Merge branch 'add-mirror-configs' into docker-mirror-final
chanseokoh Jan 21, 2021
d6b5bee
wip
chanseokoh Jan 21, 2021
8a1d4e1
Fix progress dispatcher
chanseokoh Jan 21, 2021
35ebd19
fix
chanseokoh Jan 21, 2021
7f1b1ae
fix
chanseokoh Jan 21, 2021
36aa636
Merge branch 'fix-progress' into docker-mirror-final
chanseokoh Jan 21, 2021
3246ddb
fix progress
chanseokoh Jan 21, 2021
3fedfa0
working
chanseokoh Jan 21, 2021
b6981e0
Merge remote-tracking branch 'origin/master' into docker-mirror-final
chanseokoh Jan 21, 2021
1d2b74d
Add tests
chanseokoh Jan 22, 2021
67d6e00
Merge branch 'master' into docker-mirror-final
chanseokoh Jan 28, 2021
4cc7575
Update Javadoc
chanseokoh Jan 28, 2021
1f77d64
Add failover test
chanseokoh Jan 28, 2021
afe5855
Format code
chanseokoh Jan 28, 2021
c68d320
Move log message
chanseokoh Jan 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
Expand Down Expand Up @@ -96,7 +98,7 @@ public ImagesAndRegistryClient call()
CacheCorruptedException, CredentialRetrievalException {
EventHandlers eventHandlers = buildContext.getEventHandlers();
try (ProgressEventDispatcher progressDispatcher =
progressDispatcherFactory.create("pulling base image manifest", 3);
progressDispatcherFactory.create("pulling base image manifest", 4);
TimerEventDispatcher ignored1 = new TimerEventDispatcher(eventHandlers, DESCRIPTION)) {

// Skip this step if this is a scratch image
Expand Down Expand Up @@ -138,6 +140,12 @@ public ImagesAndRegistryClient call()
}
}

Optional<ImagesAndRegistryClient> mirrorPull =
tryMirrors(buildContext, progressDispatcher.newChildProducer());
if (mirrorPull.isPresent()) {
chanseokoh marked this conversation as resolved.
Show resolved Hide resolved
return mirrorPull.get();
}

try {
// First, try with no credentials. This works with public GCR images (but not Docker Hub).
// TODO: investigate if we should just pass credentials up front. However, this involves
Expand Down Expand Up @@ -199,6 +207,60 @@ public ImagesAndRegistryClient call()
}
}

@VisibleForTesting
Optional<ImagesAndRegistryClient> tryMirrors(
BuildContext buildContext, ProgressEventDispatcher.Factory progressDispatcherFactory)
throws LayerCountMismatchException, BadContainerConfigurationFormatException {
EventHandlers eventHandlers = buildContext.getEventHandlers();

Collection<Map.Entry<String, String>> mirrorEntries =
buildContext.getRegistryMirrors().entries();
try (ProgressEventDispatcher progressDispatcher1 =
progressDispatcherFactory.create("trying mirrors", mirrorEntries.size());
TimerEventDispatcher ignored1 = new TimerEventDispatcher(eventHandlers, "trying mirrors")) {
for (Map.Entry<String, String> entry : mirrorEntries) {
String registry = entry.getKey();
String mirror = entry.getValue();
eventHandlers.dispatch(LogEvent.debug("mirror config: " + registry + " --> " + mirror));

if (!buildContext.getBaseImageConfiguration().getImageRegistry().equals(registry)) {
progressDispatcher1.dispatchProgress(1);
continue;
}

eventHandlers.dispatch(LogEvent.info("trying mirror " + mirror + " for the base image"));
try (ProgressEventDispatcher progressDispatcher2 =
progressDispatcher1.newChildProducer().create("trying mirror " + mirror, 2)) {
// First, try with no credentials. This works with public GCR images.
RegistryClient registryClient =
buildContext.newBaseImageRegistryClientFactory(mirror).newRegistryClient();
try {
List<Image> images =
pullBaseImages(registryClient, progressDispatcher2.newChildProducer());
eventHandlers.dispatch(LogEvent.info("pulled manifest from mirror " + mirror));
return Optional.of(new ImagesAndRegistryClient(images, registryClient));

} catch (RegistryUnauthorizedException ex) {
// in case if a mirror requires bearer auth
eventHandlers.dispatch(LogEvent.debug("mirror " + mirror + " requires auth"));
registryClient.doPullBearerAuth();
List<Image> images =
pullBaseImages(registryClient, progressDispatcher2.newChildProducer());
eventHandlers.dispatch(LogEvent.info("pulled manifest from mirror " + mirror));
return Optional.of(new ImagesAndRegistryClient(images, registryClient));
}

} catch (IOException | RegistryException ex) {
// Ignore errors from this mirror and continue.
eventHandlers.dispatch(
LogEvent.debug(
"failed to get manifest from mirror " + mirror + ": " + ex.getMessage()));
}
}
return Optional.empty();
}
}

/**
* Pulls the base images specified in the platforms list.
*
Expand Down Expand Up @@ -350,6 +412,7 @@ private ContainerConfigurationTemplate pullContainerConfigJson(
new ThrottledProgressEventDispatcherWrapper(
progressDispatcherFactory,
"pull container configuration " + manifest.getContainerConfiguration().getDigest())) {

String containerConfigString =
Blobs.writeToString(
registryClient.pullBlob(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,12 +527,26 @@ public ImmutableListMultimap<String, String> getRegistryMirrors() {

/**
* Creates a new {@link RegistryClient.Factory} for the base image with fields from the build
* configuration.
* configuration. The server URL is derived from the base {@link ImageConfiguration}.
*
* @return a new {@link RegistryClient.Factory}
*/
public RegistryClient.Factory newBaseImageRegistryClientFactory() {
return newRegistryClientFactory(baseImageConfiguration);
return newBaseImageRegistryClientFactory(baseImageConfiguration.getImageRegistry());
chanseokoh marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Creates a new {@link RegistryClient.Factory} for the base image repository on the registry
* {@code serverUrl}. Compared to @link #newBaseImageRegistryClientFactory()), this method is
* useful to try a mirror.
*
* @param serverUrl the server URL for the registry (for example, {@code gcr.io})
* @return a new {@link RegistryClient.Factory}
*/
public RegistryClient.Factory newBaseImageRegistryClientFactory(String serverUrl) {
return RegistryClient.factory(
getEventHandlers(), serverUrl, baseImageConfiguration.getImageRepository(), httpClient)
.setUserAgent(makeUserAgent());
}

/**
Expand All @@ -554,14 +568,10 @@ public RegistryClient.Factory newTargetImageRegistryClientFactory() {
httpClient)
.setUserAgent(makeUserAgent());
}
return newRegistryClientFactory(targetImageConfiguration);
}

private RegistryClient.Factory newRegistryClientFactory(ImageConfiguration imageConfiguration) {
return RegistryClient.factory(
getEventHandlers(),
imageConfiguration.getImageRegistry(),
imageConfiguration.getImageRepository(),
targetImageConfiguration.getImageRegistry(),
targetImageConfiguration.getImageRepository(),
httpClient)
.setUserAgent(makeUserAgent());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public interface BuildableManifestTemplate extends ManifestTemplate {
* @see <a href="https://github.com/opencontainers/image-spec/blob/master/descriptor.md">OCI
* Content Descriptors</a>
*/
@VisibleForTesting
class ContentDescriptorTemplate implements JsonTemplate {

@SuppressWarnings("unused")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.api.ImageReference;
import com.google.cloud.tools.jib.api.InvalidImageReferenceException;
import com.google.cloud.tools.jib.api.LogEvent;
import com.google.cloud.tools.jib.api.RegistryException;
import com.google.cloud.tools.jib.api.buildplan.Platform;
import com.google.cloud.tools.jib.blob.Blobs;
import com.google.cloud.tools.jib.builder.ProgressEventDispatcher;
import com.google.cloud.tools.jib.builder.steps.PullBaseImageStep.ImagesAndRegistryClient;
import com.google.cloud.tools.jib.cache.Cache;
Expand All @@ -41,18 +43,22 @@
import com.google.cloud.tools.jib.image.json.V22ManifestListTemplate;
import com.google.cloud.tools.jib.image.json.V22ManifestTemplate;
import com.google.cloud.tools.jib.json.JsonTemplateMapper;
import com.google.cloud.tools.jib.registry.ManifestAndDigest;
import com.google.cloud.tools.jib.registry.RegistryClient;
import com.google.cloud.tools.jib.registry.credentials.CredentialRetrievalException;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.security.DigestException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
Expand All @@ -62,8 +68,10 @@
public class PullBaseImageStepTest {
chanseokoh marked this conversation as resolved.
Show resolved Hide resolved

@Mock private ProgressEventDispatcher.Factory progressDispatcherFactory;
@Mock private ProgressEventDispatcher progressDispatcher;
@Mock private BuildContext buildContext;
@Mock private RegistryClient registryClient;
@Mock private RegistryClient.Factory registryClientFactory;
@Mock private ImageConfiguration imageConfiguration;
@Mock private ContainerConfiguration containerConfig;
@Mock private Cache cache;
Expand All @@ -76,13 +84,15 @@ public void setUp() {
Mockito.when(buildContext.getBaseImageConfiguration()).thenReturn(imageConfiguration);
Mockito.when(buildContext.getEventHandlers()).thenReturn(eventHandlers);
Mockito.when(buildContext.getBaseImageLayersCache()).thenReturn(cache);
RegistryClient.Factory registryClientFactory = Mockito.mock(RegistryClient.Factory.class);
Mockito.when(buildContext.newBaseImageRegistryClientFactory())
.thenReturn(registryClientFactory);
Mockito.when(registryClientFactory.newRegistryClient()).thenReturn(registryClient);
Mockito.when(buildContext.getContainerConfiguration()).thenReturn(containerConfig);
Mockito.when(containerConfig.getPlatforms())
.thenReturn(ImmutableSet.of(new Platform("slim arch", "fat system")));
Mockito.when(progressDispatcherFactory.create(Mockito.anyString(), Mockito.anyLong()))
.thenReturn(progressDispatcher);
Mockito.when(progressDispatcher.newChildProducer()).thenReturn(progressDispatcherFactory);

pullBaseImageStep = new PullBaseImageStep(buildContext, progressDispatcherFactory);
}
Expand Down Expand Up @@ -409,4 +419,142 @@ public void testGetCachedBaseImages_v22ManifestListCached_onlyPlatforms()
Assert.assertEquals(1, images.size());
Assert.assertEquals("target-user", images.get(0).getUser());
}

@Test
public void testTryMirrors_noMatchingMirrors()
throws LayerCountMismatchException, BadContainerConfigurationFormatException {
Mockito.when(imageConfiguration.getImageRegistry()).thenReturn("registry");
Mockito.when(buildContext.getRegistryMirrors())
.thenReturn(ImmutableListMultimap.of("unmatched1", "mirror1", "unmatched2", "mirror2"));

Optional<ImagesAndRegistryClient> result =
pullBaseImageStep.tryMirrors(buildContext, progressDispatcherFactory);
Assert.assertEquals(Optional.empty(), result);

InOrder inOrder = Mockito.inOrder(eventHandlers);
inOrder.verify(eventHandlers).dispatch(LogEvent.debug("mirror config: unmatched1 --> mirror1"));
inOrder.verify(eventHandlers).dispatch(LogEvent.debug("mirror config: unmatched2 --> mirror2"));
Mockito.verify(buildContext, Mockito.never()).newBaseImageRegistryClientFactory(Mockito.any());
}

@Test
public void testTryMirrors_mirrorIoError()
throws LayerCountMismatchException, BadContainerConfigurationFormatException, IOException,
RegistryException {
Mockito.when(imageConfiguration.getImageRegistry()).thenReturn("registry");
Mockito.when(buildContext.getRegistryMirrors())
.thenReturn(ImmutableListMultimap.of("registry", "gcr.io"));
Mockito.when(buildContext.newBaseImageRegistryClientFactory("gcr.io"))
.thenReturn(registryClientFactory);
Mockito.when(registryClient.pullManifest(Mockito.any()))
.thenThrow(new IOException("test exception"));

Optional<ImagesAndRegistryClient> result =
pullBaseImageStep.tryMirrors(buildContext, progressDispatcherFactory);
Assert.assertEquals(Optional.empty(), result);

InOrder inOrder = Mockito.inOrder(eventHandlers);
inOrder
.verify(eventHandlers)
.dispatch(LogEvent.info("trying mirror gcr.io for the base image"));
inOrder
.verify(eventHandlers)
.dispatch(LogEvent.debug("failed to get manifest from mirror gcr.io: test exception"));
}

@Test
public void testTryMirrors_multipleMirrors()
throws LayerCountMismatchException, BadContainerConfigurationFormatException, IOException,
RegistryException, InvalidImageReferenceException {
Mockito.when(imageConfiguration.getImage()).thenReturn(ImageReference.parse("registry/repo"));
Mockito.when(imageConfiguration.getImageRegistry()).thenReturn("registry");
Mockito.when(buildContext.getRegistryMirrors())
.thenReturn(ImmutableListMultimap.of("registry", "quay.io", "registry", "gcr.io"));

Mockito.when(buildContext.newBaseImageRegistryClientFactory("quay.io"))
.thenReturn(registryClientFactory);
Mockito.when(registryClient.pullManifest(Mockito.any()))
.thenThrow(new RegistryException("not found"));

RegistryClient.Factory gcrRegistryClientFactory = setUpWorkingRegistryClientFactory();
Mockito.when(buildContext.newBaseImageRegistryClientFactory("gcr.io"))
.thenReturn(gcrRegistryClientFactory);

Optional<ImagesAndRegistryClient> result =
pullBaseImageStep.tryMirrors(buildContext, progressDispatcherFactory);
Assert.assertTrue(result.isPresent());
Assert.assertEquals(gcrRegistryClientFactory.newRegistryClient(), result.get().registryClient);

InOrder inOrder = Mockito.inOrder(eventHandlers);
inOrder
.verify(eventHandlers)
.dispatch(LogEvent.info("trying mirror quay.io for the base image"));
inOrder
.verify(eventHandlers)
.dispatch(LogEvent.debug("failed to get manifest from mirror quay.io: not found"));
inOrder
.verify(eventHandlers)
.dispatch(LogEvent.info("trying mirror gcr.io for the base image"));
inOrder.verify(eventHandlers).dispatch(LogEvent.info("pulled manifest from mirror gcr.io"));
}

@Test
public void testCall_allMirrorsFail()
throws InvalidImageReferenceException, IOException, RegistryException,
LayerPropertyNotFoundException, LayerCountMismatchException,
BadContainerConfigurationFormatException, CacheCorruptedException,
CredentialRetrievalException {
Mockito.when(imageConfiguration.getImage()).thenReturn(ImageReference.parse("registry/repo"));
Mockito.when(imageConfiguration.getImageRegistry()).thenReturn("registry");
Mockito.when(buildContext.getRegistryMirrors())
.thenReturn(ImmutableListMultimap.of("registry", "quay.io", "registry", "gcr.io"));

Mockito.when(buildContext.newBaseImageRegistryClientFactory(Mockito.any()))
.thenReturn(registryClientFactory);
Mockito.when(registryClient.pullManifest(Mockito.any()))
.thenThrow(new RegistryException("not found"));

RegistryClient.Factory dockerHubRegistryClientFactory = setUpWorkingRegistryClientFactory();
Mockito.when(buildContext.newBaseImageRegistryClientFactory())
.thenReturn(dockerHubRegistryClientFactory);

ImagesAndRegistryClient result = pullBaseImageStep.call();
Assert.assertEquals(dockerHubRegistryClientFactory.newRegistryClient(), result.registryClient);

InOrder inOrder = Mockito.inOrder(eventHandlers);
inOrder
.verify(eventHandlers)
.dispatch(LogEvent.info("trying mirror quay.io for the base image"));
inOrder
.verify(eventHandlers)
.dispatch(LogEvent.debug("failed to get manifest from mirror quay.io: not found"));
chanseokoh marked this conversation as resolved.
Show resolved Hide resolved
inOrder
.verify(eventHandlers)
.dispatch(LogEvent.info("trying mirror gcr.io for the base image"));
inOrder
.verify(eventHandlers)
.dispatch(LogEvent.debug("failed to get manifest from mirror gcr.io: not found"));
}

private static RegistryClient.Factory setUpWorkingRegistryClientFactory()
throws IOException, RegistryException {
DescriptorDigest digest = Mockito.mock(DescriptorDigest.class);
V22ManifestTemplate manifest = new V22ManifestTemplate();
manifest.setContainerConfiguration(1234, digest);

RegistryClient.Factory clientFactory = Mockito.mock(RegistryClient.Factory.class);
RegistryClient client = Mockito.mock(RegistryClient.class);
Mockito.when(clientFactory.newRegistryClient()).thenReturn(client);
Mockito.when(client.pullManifest(Mockito.any()))
.thenReturn(new ManifestAndDigest<>(manifest, digest));
// mocking pulling container config json
Mockito.when(client.pullBlob(Mockito.any(), Mockito.any(), Mockito.any()))
.then(
invocation -> {
Consumer<Long> blobSizeListener = invocation.getArgument(1);
blobSizeListener.accept(1L);
return Blobs.from("{}");
});
return clientFactory;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ public void buildDocker()
new GradleRawConfiguration(jibExtension),
ignored -> java.util.Optional.empty(),
projectProperties,
globalConfig,
new GradleHelpfulSuggestions(HELPFUL_SUGGESTIONS_PREFIX))
.runBuild();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public void buildTar()
new GradleRawConfiguration(jibExtension),
ignored -> Optional.empty(),
projectProperties,
globalConfig,
new GradleHelpfulSuggestions(HELPFUL_SUGGESTIONS_PREFIX))
.runBuild();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,15 @@ public void execute() throws MojoExecutionException, MojoFailureException {

Future<Optional<String>> updateCheckFuture = Futures.immediateFuture(Optional.empty());
try {
updateCheckFuture =
MojoCommon.newUpdateChecker(projectProperties, GlobalConfig.readConfig(), getLog());
GlobalConfig globalConfig = GlobalConfig.readConfig();
updateCheckFuture = MojoCommon.newUpdateChecker(projectProperties, globalConfig, getLog());

PluginConfigurationProcessor.createJibBuildRunnerForDockerDaemonImage(
new MavenRawConfiguration(this),
new MavenSettingsServerCredentials(
getSession().getSettings(), getSettingsDecrypter()),
projectProperties,
globalConfig,
new MavenHelpfulSuggestions(HELPFUL_SUGGESTIONS_PREFIX))
.runBuild();

Expand Down
Loading