diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/LocalBaseImageSteps.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/LocalBaseImageSteps.java index 2070790de1..4cd9f09bf8 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/LocalBaseImageSteps.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/LocalBaseImageSteps.java @@ -111,6 +111,8 @@ static Callable retrieveDockerDaemonLayersStep( Optional cachedImage = getCachedDockerImage(buildContext.getBaseImageLayersCache(), dockerImageDetails); if (cachedImage.isPresent()) { + PlatformChecker.checkManifestPlatform( + buildContext, cachedImage.get().configurationTemplate); return cachedImage.get(); } @@ -125,11 +127,14 @@ static Callable retrieveDockerDaemonLayersStep( dockerClient.save(imageReference, tarPath, throttledProgressReporter); } - return cacheDockerImageTar( - buildContext, - tarPath, - progressEventDispatcher.newChildProducer(), - tempDirectoryProvider); + LocalImage localImage = + cacheDockerImageTar( + buildContext, + tarPath, + progressEventDispatcher.newChildProducer(), + tempDirectoryProvider); + PlatformChecker.checkManifestPlatform(buildContext, localImage.configurationTemplate); + return localImage; } }; } @@ -139,9 +144,13 @@ static Callable retrieveTarLayersStep( ProgressEventDispatcher.Factory progressEventDispatcherFactory, Path tarPath, TempDirectoryProvider tempDirectoryProvider) { - return () -> - cacheDockerImageTar( - buildContext, tarPath, progressEventDispatcherFactory, tempDirectoryProvider); + return () -> { + LocalImage localImage = + cacheDockerImageTar( + buildContext, tarPath, progressEventDispatcherFactory, tempDirectoryProvider); + PlatformChecker.checkManifestPlatform(buildContext, localImage.configurationTemplate); + return localImage; + }; } static Callable returnImageAndRegistryClientStep( diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PlatformChecker.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PlatformChecker.java new file mode 100644 index 0000000000..8348ef72e2 --- /dev/null +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PlatformChecker.java @@ -0,0 +1,75 @@ +/* + * Copyright 2020 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.builder.steps; + +import com.google.cloud.tools.jib.api.LogEvent; +import com.google.cloud.tools.jib.api.buildplan.Platform; +import com.google.cloud.tools.jib.configuration.BuildContext; +import com.google.cloud.tools.jib.event.EventHandlers; +import com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate; +import com.google.common.base.Verify; +import java.util.Set; + +/** Provides helper methods to check platforms. */ +public class PlatformChecker { + + /** + * Assuming the base image is not a manifest list, checks and warns misconfigured platforms. + * + * @param buildContext the {@link BuildContext} + * @param containerConfig container configuration JSON of the base image + */ + static void checkManifestPlatform( + BuildContext buildContext, ContainerConfigurationTemplate containerConfig) { + EventHandlers eventHandlers = buildContext.getEventHandlers(); + String baseImageName = + buildContext.getBaseImageConfiguration().getTarPath().isPresent() + ? buildContext.getBaseImageConfiguration().getTarPath().get().toString() + : buildContext.getBaseImageConfiguration().getImage().toString(); + + Set platforms = buildContext.getContainerConfiguration().getPlatforms(); + Verify.verify(!platforms.isEmpty()); + + if (platforms.size() != 1) { + eventHandlers.dispatch( + LogEvent.warn( + "platforms configured, but '" + baseImageName + "' is not a manifest list")); + } else { + Platform platform = platforms.iterator().next(); + if (!platform.getArchitecture().equals(containerConfig.getArchitecture()) + || !platform.getOs().equals(containerConfig.getOs())) { + + // Unfortunately, "platforms" has amd64/linux by default even if the user didn't explicitly + // configure it. Skip reporting to suppress false alarm. + if (!(platform.getArchitecture().equals("amd64") && platform.getOs().equals("linux"))) { + String warning = + "the configured platform (%s/%s) doesn't match the platform (%s/%s) of the base " + + "image (%s)"; + eventHandlers.dispatch( + LogEvent.warn( + String.format( + warning, + platform.getArchitecture(), + platform.getOs(), + containerConfig.getArchitecture(), + containerConfig.getOs(), + baseImageName))); + } + } + } + } +} diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStep.java b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStep.java index 8aed86f8ed..52ab6daa67 100644 --- a/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStep.java +++ b/jib-core/src/main/java/com/google/cloud/tools/jib/builder/steps/PullBaseImageStep.java @@ -233,6 +233,7 @@ private List pullBaseImages( BuildableManifestTemplate imageManifest = (BuildableManifestTemplate) manifestTemplate; ContainerConfigurationTemplate containerConfig = pullContainerConfigJson(manifestAndDigest, registryClient, progressEventDispatcher); + PlatformChecker.checkManifestPlatform(buildContext, containerConfig); cache.writeMetadata(baseImageConfig.getImage(), imageManifest, containerConfig); return Collections.singletonList( JsonToImageTranslator.toImage(imageManifest, containerConfig)); @@ -379,10 +380,14 @@ List getCachedBaseImages() return Collections.singletonList( JsonToImageTranslator.toImage((V21ManifestTemplate) manifest)); } + + ContainerConfigurationTemplate containerConfig = + Verify.verifyNotNull(manifestsAndConfigs.get(0).getConfig()); + PlatformChecker.checkManifestPlatform(buildContext, containerConfig); + return Collections.singletonList( JsonToImageTranslator.toImage( - (BuildableManifestTemplate) Verify.verifyNotNull(manifest), - Verify.verifyNotNull(manifestsAndConfigs.get(0).getConfig()))); + (BuildableManifestTemplate) Verify.verifyNotNull(manifest), containerConfig)); } // Manifest list cached. Identify matching platforms and check if all of them are cached. diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PlatformCheckerTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PlatformCheckerTest.java new file mode 100644 index 0000000000..2dd77d731a --- /dev/null +++ b/jib-core/src/test/java/com/google/cloud/tools/jib/builder/steps/PlatformCheckerTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2020 Google LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.cloud.tools.jib.builder.steps; + +import com.google.cloud.tools.jib.api.ImageReference; +import com.google.cloud.tools.jib.api.LogEvent; +import com.google.cloud.tools.jib.api.buildplan.Platform; +import com.google.cloud.tools.jib.builder.ProgressEventDispatcher; +import com.google.cloud.tools.jib.configuration.BuildContext; +import com.google.cloud.tools.jib.configuration.ContainerConfiguration; +import com.google.cloud.tools.jib.configuration.ImageConfiguration; +import com.google.cloud.tools.jib.event.EventHandlers; +import com.google.cloud.tools.jib.image.json.ContainerConfigurationTemplate; +import com.google.cloud.tools.jib.registry.RegistryClient; +import com.google.common.collect.ImmutableSet; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +/** Tests for {@link PlatformChecker}. */ +@RunWith(MockitoJUnitRunner.class) +public class PlatformCheckerTest { + + @Mock private ProgressEventDispatcher.Factory progressDispatcherFactory; + @Mock private BuildContext buildContext; + @Mock private RegistryClient registryClient; + @Mock private ImageConfiguration imageConfiguration; + @Mock private ContainerConfiguration containerConfig; + @Mock private EventHandlers eventHandlers; + + @Before + public void setUp() { + Mockito.when(buildContext.getBaseImageConfiguration()) + .thenReturn(ImageConfiguration.builder(ImageReference.scratch()).build()); + Mockito.when(buildContext.getEventHandlers()).thenReturn(eventHandlers); + Mockito.when(buildContext.getContainerConfiguration()).thenReturn(containerConfig); + } + + @Test + public void testCheckManifestPlatform_mismatch() { + Mockito.when(containerConfig.getPlatforms()) + .thenReturn(ImmutableSet.of(new Platform("configured arch", "configured OS"))); + + ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate(); + containerConfigJson.setArchitecture("actual arch"); + containerConfigJson.setOs("actual OS"); + + PlatformChecker.checkManifestPlatform(buildContext, containerConfigJson); + + Mockito.verify(eventHandlers) + .dispatch( + LogEvent.warn( + "the configured platform (configured arch/configured OS) doesn't match the " + + "platform (actual arch/actual OS) of the base image (scratch)")); + } + + @Test + public void testCheckManifestPlatform_noWarningIfDefaultAmd64Linux() { + Mockito.when(containerConfig.getPlatforms()) + .thenReturn(ImmutableSet.of(new Platform("amd64", "linux"))); + + ContainerConfigurationTemplate containerConfigJson = new ContainerConfigurationTemplate(); + containerConfigJson.setArchitecture("actual arch"); + containerConfigJson.setOs("actual OS"); + + PlatformChecker.checkManifestPlatform(buildContext, containerConfigJson); + + Mockito.verifyNoInteractions(eventHandlers); + } + + @Test + public void testCheckManifestPlatform_multiplePlatformsConfigured() { + Mockito.when(containerConfig.getPlatforms()) + .thenReturn(ImmutableSet.of(new Platform("amd64", "linux"), new Platform("arch", "os"))); + + PlatformChecker.checkManifestPlatform(buildContext, new ContainerConfigurationTemplate()); + + Mockito.verify(eventHandlers) + .dispatch(LogEvent.warn("platforms configured, but 'scratch' is not a manifest list")); + } + + @Test + public void testCheckManifestPlatform_tarBaseImage() { + Path tar = Paths.get("/foo/bar.tar"); + Mockito.when(buildContext.getBaseImageConfiguration()) + .thenReturn(ImageConfiguration.builder(ImageReference.scratch()).setTarPath(tar).build()); + Mockito.when(containerConfig.getPlatforms()) + .thenReturn(ImmutableSet.of(new Platform("amd64", "linux"), new Platform("arch", "os"))); + + PlatformChecker.checkManifestPlatform(buildContext, new ContainerConfigurationTemplate()); + + Mockito.verify(eventHandlers) + .dispatch( + LogEvent.warn( + "platforms configured, but '" + tar.toString() + "' is not a manifest list")); + } +}