Skip to content

Commit

Permalink
fix (jkube-kit/build/service/jib) : JibServiceUtil should push additi…
Browse files Browse the repository at this point in the history
…onal tags in single request (#2293)

We are doing separate push requests for each additional tag provided in
build configuration. Use only single push request and rely on in built
jib's Containerizer.withAdditionalTag method

Signed-off-by: Rohan Kumar <rohaan@redhat.com>
  • Loading branch information
rohanKanojia authored and manusa committed Aug 9, 2023
1 parent ba86499 commit 7b51ea7
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Usage:
* Fix #2224: Quarkus native base image read from properties (configurable)
* Fix #2228: Quarkus native base image uses UBI 8.7
* Fix #2290: JKube is not picking docker credentials from `~/.docker/config.json` file
* Fix #2293: JibServiceUtil pushes separate images for additional tags specified in build configuration
* Fix #2299: Gradle v8.x compatibility
* Fix #2302 Bump Kubernetes Client version to 6.8.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,11 @@ public static String getFullImageName(ImageConfiguration imageConfiguration, Str
* @param log Logger
*/
public static void jibPush(ImageConfiguration imageConfiguration, Credential pushCredentials, File tarArchive, KitLogger log) {
BuildConfiguration buildImageConfiguration = imageConfiguration.getBuildConfiguration();
String imageName = getFullImageName(imageConfiguration, null);
List<String> additionalTags = imageConfiguration.getBuildConfiguration().getTags();
try {
for (String tag : getAllImageTags(buildImageConfiguration.getTags(), imageName)) {
String imageNameWithTag = getFullImageName(imageConfiguration, tag);
log.info("Pushing image: %s", imageNameWithTag);
pushImage(TarImage.at(tarArchive.toPath()), imageNameWithTag, pushCredentials, log);
}
log.info("Pushing image: %s", imageName);
pushImage(TarImage.at(tarArchive.toPath()), additionalTags, imageName, pushCredentials, log);
} catch (IllegalStateException e) {
log.error("Exception occurred while pushing the image: %s", imageConfiguration.getName());
throw new IllegalStateException(e.getMessage(), e);
Expand All @@ -158,12 +155,12 @@ public static void jibPush(ImageConfiguration imageConfiguration, Credential pus
}
}

private static void pushImage(TarImage baseImage, String targetImageName, Credential credential, KitLogger logger)
private static void pushImage(TarImage baseImage, List<String> additionalTags, String targetImageName, Credential credential, KitLogger logger)
throws InterruptedException {

final ExecutorService jibBuildExecutor = Executors.newCachedThreadPool();
try {
submitPushToJib(baseImage, getRegistryImage(targetImageName, credential), jibBuildExecutor, logger);
submitPushToJib(baseImage, getRegistryImage(targetImageName, credential), additionalTags, jibBuildExecutor, logger);
} catch (RegistryException | CacheDirectoryCreationException | InvalidImageReferenceException | IOException | ExecutionException e) {
logger.error("Exception occurred while pushing the image: %s, %s", targetImageName, e.getMessage());
throw new IllegalStateException(e.getMessage(), e);
Expand Down Expand Up @@ -220,13 +217,21 @@ static Set<String> getAllImageTags(List<String> tags, String imageName) {
return tagSet;
}

private static void submitPushToJib(TarImage baseImage, RegistryImage targetImage, ExecutorService jibBuildExecutor, KitLogger logger) throws InterruptedException, ExecutionException, RegistryException, CacheDirectoryCreationException, IOException {
Jib.from(baseImage).setCreationTime(Instant.now()).containerize(Containerizer.to(targetImage)
private static void submitPushToJib(TarImage baseImage, RegistryImage targetImage, List<String> additionalTags, ExecutorService jibBuildExecutor, KitLogger logger) throws InterruptedException, ExecutionException, RegistryException, CacheDirectoryCreationException, IOException {
Jib.from(baseImage).setCreationTime(Instant.now()).containerize(createJibContainerizer(targetImage, additionalTags, jibBuildExecutor, logger));
logUpdateFinished();
}

private static Containerizer createJibContainerizer(RegistryImage targetImage, List<String> additionalTags, ExecutorService jibBuildExecutor, KitLogger logger) {
Containerizer containerizer = Containerizer.to(targetImage)
.setAllowInsecureRegistries(true)
.setExecutorService(jibBuildExecutor)
.addEventHandler(LogEvent.class, log(logger))
.addEventHandler(ProgressEvent.class, new ProgressEventHandler(logUpdate())));
logUpdateFinished();
.addEventHandler(ProgressEvent.class, new ProgressEventHandler(logUpdate()));
if (additionalTags != null) {
additionalTags.forEach(containerizer::withAdditionalTag);
}
return containerizer;
}

private static RegistryImage getRegistryImage(String targetImage, Credential credential) throws InvalidImageReferenceException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,24 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;

import com.google.cloud.tools.jib.api.CacheDirectoryCreationException;
import com.google.cloud.tools.jib.api.Containerizer;
import com.google.cloud.tools.jib.api.Credential;
import com.google.cloud.tools.jib.api.Jib;
import com.google.cloud.tools.jib.api.RegistryException;
import com.google.cloud.tools.jib.api.RegistryImage;
import com.google.cloud.tools.jib.api.TarImage;
import org.eclipse.jkube.kit.build.api.assembly.BuildDirs;
import org.eclipse.jkube.kit.common.Assembly;
import org.eclipse.jkube.kit.common.AssemblyConfiguration;
import org.eclipse.jkube.kit.common.AssemblyFile;
import org.eclipse.jkube.kit.common.AssemblyFileEntry;
import org.eclipse.jkube.kit.common.JKubeConfiguration;
import org.eclipse.jkube.kit.common.JavaProject;
import org.eclipse.jkube.kit.common.KitLogger;
import org.eclipse.jkube.kit.config.image.ImageConfiguration;
import org.eclipse.jkube.kit.common.Arguments;
import org.eclipse.jkube.kit.config.image.build.BuildConfiguration;
Expand All @@ -39,18 +49,32 @@
import com.google.cloud.tools.jib.api.buildplan.FileEntriesLayer;
import com.google.cloud.tools.jib.api.buildplan.ImageFormat;
import com.google.cloud.tools.jib.api.buildplan.Port;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.eclipse.jkube.kit.service.jib.JibServiceUtil.containerFromImageConfiguration;
import static org.mockito.Answers.RETURNS_SELF;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockConstructionWithAnswer;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class JibServiceUtilTest {
private KitLogger kitLogger;

@BeforeEach
void setUp() {
kitLogger = new KitLogger.SilentLogger();
}

@Test
void testGetBaseImageWithNullBuildConfig() {
Expand Down Expand Up @@ -172,6 +196,121 @@ void layers_withMultipleLayers_shouldReturnTransformedLayers(@TempDir File tempo
.containsExactly("layer-1", "", "jkube-generated-layer-final-artifact");
}

@Test
void buildContainer_whenBuildSuccessful_thenDelegateToJibContainerize() throws InterruptedException, CacheDirectoryCreationException, IOException, ExecutionException, RegistryException {
try (MockedStatic<Containerizer> containerizerMockedStatic = mockStatic(Containerizer.class)) {
// Given
JibContainerBuilder jibContainerBuilder = mock(JibContainerBuilder.class, RETURNS_SELF);
Containerizer containerizer = mock(Containerizer.class, RETURNS_SELF);
TarImage tarImage = TarImage.at(new File("docker-build.tar").toPath());
containerizerMockedStatic.when(() -> Containerizer.to(tarImage)).thenReturn(containerizer);

// When
JibServiceUtil.buildContainer(jibContainerBuilder, tarImage, kitLogger);

// Then
verify(containerizer).setAllowInsecureRegistries(true);
verify(containerizer).setExecutorService(any(ExecutorService.class));
verify(containerizer, times(2)).addEventHandler(any(), any());
verify(jibContainerBuilder).containerize(containerizer);
}
}

@Test
void buildContainer_whenBuildFailure_thenThrowException() throws InterruptedException, CacheDirectoryCreationException, IOException, ExecutionException, RegistryException {
try (MockedStatic<Containerizer> containerizerMockedStatic = mockStatic(Containerizer.class)) {
// Given
JibContainerBuilder jibContainerBuilder = mock(JibContainerBuilder.class, RETURNS_SELF);
Containerizer containerizer = mock(Containerizer.class, RETURNS_SELF);
TarImage tarImage = TarImage.at(new File("docker-build.tar").toPath());
containerizerMockedStatic.when(() -> Containerizer.to(tarImage)).thenReturn(containerizer);
when(jibContainerBuilder.containerize(containerizer)).thenThrow(new RegistryException("Unable to pull base image"));

// When
assertThatIllegalStateException()
.isThrownBy(() -> JibServiceUtil.buildContainer(jibContainerBuilder, tarImage, kitLogger))
.withMessageContaining("Unable to pull base image");

// Then
verify(containerizer).setAllowInsecureRegistries(true);
verify(containerizer).setExecutorService(any(ExecutorService.class));
verify(containerizer, times(2)).addEventHandler(any(), any());
verify(jibContainerBuilder).containerize(containerizer);
}
}

@Test
void jibPush_whenPushFailed_thenThrowException() throws CacheDirectoryCreationException, IOException, ExecutionException, InterruptedException, RegistryException {
try (MockedStatic<Containerizer> containerizerMockedStatic = mockStatic(Containerizer.class);
MockedStatic<Jib> jibMockedStatic = mockStatic(Jib.class)) {
// Given
JibContainerBuilder jibContainerBuilder = mock(JibContainerBuilder.class, RETURNS_SELF);
Containerizer containerizer = mock(Containerizer.class, RETURNS_SELF);
jibMockedStatic.when(() -> Jib.from(any(TarImage.class))).thenReturn(jibContainerBuilder);
containerizerMockedStatic.when(() -> Containerizer.to(any(RegistryImage.class))).thenReturn(containerizer);
when(jibContainerBuilder.containerize(containerizer)).thenThrow(new RegistryException("Unauthorized"));
ImageConfiguration imageConfiguration = getSampleImageConfiguration();
Credential credential = Credential.from("testuser", "secret");
File tarArchive = new File("docker-build.tar");

// When + Then
assertThatIllegalStateException()
.isThrownBy(() -> JibServiceUtil.jibPush(imageConfiguration, credential, tarArchive, kitLogger))
.withMessage("Unauthorized");
}
}

@Test
void jibPush_whenNoTagsInBuildConfig_thenNoAdditionalTagsAddedToContainerizer() throws CacheDirectoryCreationException, IOException, ExecutionException, InterruptedException, RegistryException {
try (MockedStatic<Containerizer> containerizerMockedStatic = mockStatic(Containerizer.class);
MockedStatic<Jib> jibMockedStatic = mockStatic(Jib.class)) {
// Given
JibContainerBuilder jibContainerBuilder = mock(JibContainerBuilder.class, RETURNS_SELF);
Containerizer containerizer = mock(Containerizer.class, RETURNS_SELF);
jibMockedStatic.when(() -> Jib.from(any(TarImage.class))).thenReturn(jibContainerBuilder);
containerizerMockedStatic.when(() -> Containerizer.to(any(RegistryImage.class))).thenReturn(containerizer);
ImageConfiguration imageConfiguration = getSampleImageConfiguration();
Credential credential = Credential.from("testuser", "secret");
File tarArchive = new File("docker-build.tar");

// When
JibServiceUtil.jibPush(imageConfiguration, credential, tarArchive, kitLogger);

// Then
verify(containerizer, times(0)).withAdditionalTag(anyString());
verify(jibContainerBuilder).containerize(containerizer);
}
}

@Test
void jibPush_whenAdditionalTagsInBuildConfig_thenAdditionalTagsAddedToContainerizer() throws CacheDirectoryCreationException, IOException, ExecutionException, InterruptedException, RegistryException {
try (MockedStatic<Containerizer> containerizerMockedStatic = mockStatic(Containerizer.class);
MockedStatic<Jib> jibMockedStatic = mockStatic(Jib.class)) {
// Given
JibContainerBuilder jibContainerBuilder = mock(JibContainerBuilder.class, RETURNS_SELF);
Containerizer containerizer = mock(Containerizer.class, RETURNS_SELF);
jibMockedStatic.when(() -> Jib.from(any(TarImage.class))).thenReturn(jibContainerBuilder);
containerizerMockedStatic.when(() -> Containerizer.to(any(RegistryImage.class))).thenReturn(containerizer);
ImageConfiguration imageConfiguration = getSampleImageConfiguration();
imageConfiguration = imageConfiguration.toBuilder()
.build(imageConfiguration.getBuild().toBuilder()
.tags(Arrays.asList("t1", "t2", "t3"))
.build())
.build();
Credential credential = Credential.from("testuser", "secret");
File tarArchive = new File("docker-build.tar");

// When
JibServiceUtil.jibPush(imageConfiguration, credential, tarArchive, kitLogger);

// Then
verify(containerizer).withAdditionalTag("t1");
verify(containerizer).withAdditionalTag("t2");
verify(containerizer).withAdditionalTag("t3");
verify(jibContainerBuilder).containerize(containerizer);
}
}

private ImageConfiguration getSampleImageConfiguration() {
return ImageConfiguration.builder()
.name("test/test-project")
Expand Down

0 comments on commit 7b51ea7

Please sign in to comment.