Skip to content

Commit

Permalink
Fix eclipse-jkube#364: jkube.watch.postExec property/parameter/config…
Browse files Browse the repository at this point in the history
…uration is ignored

Added support for copying changed files and executing a command provided in postExec option after copying files
to the application pod.
  • Loading branch information
rohanKanojia committed Sep 25, 2020
1 parent e36a9b0 commit 59fa718
Show file tree
Hide file tree
Showing 11 changed files with 433 additions and 40 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Usage:
* Fix #381: Remove root as default user in AssemblyConfigurationUtils#getAssemblyConfigurationOrCreateDefault
* Fix #358: Prometheus is enabled by default, opt-out via AB_PROMETHEUS_OFF required to disable (like in FMP)
* Fix #384: Enricher defined Container environment variables get merged with vars defined in Image Build Configuration
* Fix #364: jkube.watch.postExec property/parameter/configuration is ignored

### 1.0.0 (2020-09-09)
* Fix #351: Fix AutoTLSEnricher - add annotation + volume config to resource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,8 @@ public File createChangedFilesArchive(
File archiveDir = createArchiveDir(dirs);
for (AssemblyFileEntry entry : entries) {
File dest = prepareChangedFilesArchivePath(archiveDir, entry.getDest(), assemblyDirectory);
Files.copy(Paths.get(entry.getSource().getAbsolutePath()), Paths.get(dest.getAbsolutePath()));
Files.createDirectories(dest.getParentFile().toPath());
Files.copy(Paths.get(entry.getSource().getAbsolutePath()), Paths.get(dest.getAbsolutePath()), StandardCopyOption.REPLACE_EXISTING);
}
return JKubeTarArchiver.createTarBallOfDirectory(archive, archiveDir, ArchiveCompression.none);
} catch (IOException exp) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public class ServiceHub {
runService = new RunService(dockerAccess, queryService, containerTracker, logSpecFactory, logger);
buildService = new BuildService(dockerAccess, queryService, registryService, archiveService, logger);
volumeService = new VolumeService(dockerAccess);
watchService = new WatchService(archiveService, buildService, dockerAccess, queryService, runService, logger);
watchService = new WatchService(archiveService, buildService, queryService, runService, logger);
waitService = new WaitService(dockerAccess, queryService, logger);
} else {
queryService = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Predicate;

import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -36,9 +38,6 @@
import org.eclipse.jkube.kit.config.image.WaitConfiguration;
import org.eclipse.jkube.kit.config.image.build.JKubeConfiguration;
import org.eclipse.jkube.kit.build.api.assembly.AssemblyFiles;
import org.eclipse.jkube.kit.build.service.docker.access.DockerAccess;
import org.eclipse.jkube.kit.build.service.docker.access.DockerAccessException;
import org.eclipse.jkube.kit.build.service.docker.access.ExecException;
import org.eclipse.jkube.kit.build.service.docker.access.PortMapping;
import org.eclipse.jkube.kit.build.service.docker.access.log.LogDispatcher;
import org.eclipse.jkube.kit.config.image.WatchImageConfiguration;
Expand All @@ -55,16 +54,14 @@ public class WatchService {

private final ArchiveService archiveService;
private final BuildService buildService;
private final DockerAccess dockerAccess;
private final QueryService queryService;
private final RunService runService;
private final KitLogger log;

public WatchService(ArchiveService archiveService, BuildService buildService, DockerAccess dockerAccess, QueryService queryService, RunService
public WatchService(ArchiveService archiveService, BuildService buildService, QueryService queryService, RunService
runService, KitLogger log) {
this.archiveService = archiveService;
this.buildService = buildService;
this.dockerAccess = dockerAccess;
this.queryService = queryService;
this.runService = runService;
this.log = log;
Expand Down Expand Up @@ -94,8 +91,7 @@ public synchronized void watch(WatchContext context, JKubeConfiguration buildCon
if (imageConfig.getBuildConfiguration() != null &&
imageConfig.getBuildConfiguration().getAssembly() != null) {
if (watcher.isCopy()) {
String containerBaseDir = imageConfig.getBuildConfiguration().getAssembly().getTargetDir();
schedule(executor, createCopyWatchTask(watcher, context.getBuildContext(), containerBaseDir), interval);
schedule(executor, createCopyWatchTask(watcher, context.getBuildContext()), interval);
tasks.add("copying artifacts");
}

Expand Down Expand Up @@ -134,7 +130,7 @@ private void schedule(ScheduledExecutorService executor, Runnable runnable, long
}

private Runnable createCopyWatchTask(final ImageWatcher watcher,
final JKubeConfiguration jKubeConfiguration, final String containerBaseDir) throws IOException {
final JKubeConfiguration jKubeConfiguration) throws IOException {
final ImageConfiguration imageConfig = watcher.getImageConfiguration();

final AssemblyFiles files = archiveService.getAssemblyFiles(imageConfig, jKubeConfiguration);
Expand All @@ -148,9 +144,9 @@ public void run() {

File changedFilesArchive = archiveService.createChangedFilesArchive(entries, files.getAssemblyDirectory(),
imageConfig.getName(), jKubeConfiguration);
dockerAccess.copyArchive(watcher.getContainerId(), changedFilesArchive, containerBaseDir);
copyFilesToContainer(changedFilesArchive, watcher);
callPostExec(watcher);
} catch (IOException | ExecException e) {
} catch (Exception e) {
log.error("%s: Error when copying files to container %s: %s",
imageConfig.getDescription(), watcher.getContainerId(), e.getMessage());
}
Expand All @@ -159,11 +155,31 @@ public void run() {
};
}

private void callPostExec(ImageWatcher watcher) throws DockerAccessException, ExecException {
void copyFilesToContainer(File changedFilesArchive, ImageWatcher watcher) {
Predicate<File> copyTask = watcher.getWatchContext().getContainerCopyTask();
if (copyTask != null) {
boolean copyStatus = copyTask.test(changedFilesArchive);
if (!copyStatus) {
log.warn("Unable to copy files into container");
return;
}
log.info("Files successfully coped to the container..");
} else {
log.warn("No copy task found for copy mode. Ignoring..");
}
}


String callPostExec(ImageWatcher watcher) throws Exception {
if (watcher.getPostExec() != null) {
String containerId = watcher.getContainerId();
runService.execInContainer(containerId, watcher.getPostExec(), watcher.getImageConfiguration());
Function<ImageWatcher, String> execTask = watcher.getWatchContext().getContainerCommandExecutor();
if (execTask != null) {
String execOutput = execTask.apply(watcher);
log.info("jkube.watch.postExec: " + execOutput);
return execOutput;
}
}
return null;
}

private Runnable createBuildWatchTask(final ImageWatcher watcher,
Expand Down Expand Up @@ -282,7 +298,7 @@ private String getPreStopCommand(ImageConfiguration imageConfig) {
// ===============================================================================================================

// Helper class for holding state and parameter when watching images
public class ImageWatcher {
public static class ImageWatcher {

private final ImageConfiguration imageConfig;
private final WatchContext watchContext;
Expand Down Expand Up @@ -408,6 +424,8 @@ public static class WatchContext implements Serializable {
private boolean autoCreateCustomNetworks;
private Task<ImageConfiguration> imageCustomizer;
private Task<ImageWatcher> containerRestarter;
private Function<ImageWatcher, String> containerCommandExecutor;
private Predicate<File> containerCopyTask;

private transient ServiceHub hub;
private transient ServiceHubFactory serviceHubFactory;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* Copyright (c) 2019 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at:
*
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.jkube.kit.build.service.docker;

import mockit.Mocked;
import org.eclipse.jkube.kit.common.KitLogger;
import org.eclipse.jkube.kit.config.image.ImageConfiguration;
import org.eclipse.jkube.kit.config.image.WatchImageConfiguration;
import org.eclipse.jkube.kit.config.image.WatchMode;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class WatchServiceTest {
@Mocked
ArchiveService archiveService;

@Mocked
BuildService buildService;

@Mocked
QueryService queryService;

@Mocked
RunService runService;

@Mocked
KitLogger logger;

ImageConfiguration imageConfiguration;

@Before
public void setUp() {
imageConfiguration = ImageConfiguration.builder()
.name("test-app")
.watch(WatchImageConfiguration.builder()
.postExec("ls -lt /deployments")
.build())
.build();
}

@Test
public void testCopyFilesToContainer() throws IOException {
// Given
AtomicBoolean fileCopied = new AtomicBoolean(false);
WatchService.WatchContext watchContext = WatchService.WatchContext.builder()
.watchMode(WatchMode.copy)
// Override Copy task to set this value to goal executed
.containerCopyTask(f -> fileCopied.compareAndSet(false,true))
.build();
File fileToCopy = Files.createTempFile("test-changed-files", "tar").toFile();
WatchService.ImageWatcher imageWatcher = new WatchService.ImageWatcher(imageConfiguration, watchContext, "test-img", "efe1234");
WatchService watchService = new WatchService(archiveService, buildService, queryService, runService, logger);

// When
watchService.copyFilesToContainer(fileToCopy, imageWatcher);

// Then
assertTrue(fileCopied.get());
}

@Test
public void testCallPostExec() throws Exception {
// Given
AtomicBoolean postExecCommandExecuted = new AtomicBoolean(false);
WatchService.WatchContext watchContext = WatchService.WatchContext.builder()
.watchMode(WatchMode.copy)
// Override PostExec task to set this value to goal executed
.containerCommandExecutor(imageWatcher -> {
postExecCommandExecuted.set(true);
return "Some Output";
})
.build();
WatchService.ImageWatcher imageWatcher = new WatchService.ImageWatcher(imageConfiguration, watchContext, "test-img", "efe1234");
WatchService watchService = new WatchService(archiveService, buildService, queryService, runService, logger);

// When
String output = watchService.callPostExec(imageWatcher);

// Then
assertTrue(postExecCommandExecuted.get());
assertEquals("Some Output", output);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package org.eclipse.jkube.kit.common;

import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;

import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
Expand All @@ -24,6 +25,7 @@
* @since 24.10.18
*/
public class TimeUtil {
private TimeUtil() { }

/**
* Calculate the duration between now and the given time
Expand Down Expand Up @@ -66,4 +68,19 @@ public static String formatDurationTill(long start) {

return res.toString();
}

/**
* Waits until a condition is satisfied upto a certain amount of time.
*
* @param condition {@link BooleanSupplier} for condition to check
* @param timeOutInMillis Max time out in milliseconds.
*/
public static void waitUntilCondition(BooleanSupplier condition, int timeOutInMillis) {
long start = System.currentTimeMillis();
while (!condition.getAsBoolean()) {
if (System.currentTimeMillis() - start > timeOutInMillis) {
break;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
import io.fabric8.kubernetes.api.model.batch.Job;
import io.fabric8.kubernetes.api.model.batch.JobSpec;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
Expand Down Expand Up @@ -888,5 +889,15 @@ public static Map<File, String> getCustomResourcesFileToNameMap(
}
return fileToCrdGroupMap;
}

public static String getNewestApplicationPodName(KubernetesClient client, String namespace, Set<HasMetadata> resources) {
LabelSelector selector = KubernetesHelper.getPodLabelSelector(resources);
PodList pods = client.pods().inNamespace(namespace).withLabelSelector(selector).list();
Pod newestPod = KubernetesHelper.getNewestPod(pods.getItems());
if (newestPod != null) {
return newestPod.getMetadata().getName();
}
return null;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2019 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at:
*
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.jkube.kit.common.util;

import org.eclipse.jkube.kit.common.TimeUtil;
import org.junit.Test;

import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.Assert.assertTrue;

public class TimeUtilTest {
@Test
public void testWaitUntilCondition() {
long timeBeforeWait = System.currentTimeMillis();
AtomicBoolean value = new AtomicBoolean(false);
new Thread(() -> value.set(true)).start();

TimeUtil.waitUntilCondition(value::get, 200);
long timeAfterWait = System.currentTimeMillis();
assertTrue(timeAfterWait - timeBeforeWait < 200);
}
}
Loading

0 comments on commit 59fa718

Please sign in to comment.