Skip to content

Commit

Permalink
Merge pull request #141 from jenkinsci/feature/docker-pipeline-plugin
Browse files Browse the repository at this point in the history
Add additional hint if using paths outside workspace
  • Loading branch information
repolevedavaj authored Jan 24, 2023
2 parents 5361606 + 0be8e8e commit 7ba3fb1
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 96 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ cache(maxCacheSize: 250, defaultBranch: 'develop', caches: [
}
```

#### Note about using within Docker containers
If you use the plugin within a Docker container through the [Docker Pipeline plugin](https://plugins.jenkins.io/docker-workflow/), the path to cache must be located within the workspace. Everything outside is not visible to the plugin and therefore not cacheable.

## Contributing

See [contribution guidelines](https://github.com/jenkinsci/.github/blob/master/CONTRIBUTING.md)
Expand Down
29 changes: 29 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<testcontainers.version>1.17.6</testcontainers.version>
<zstd-jni.version>1.5.2-5</zstd-jni.version>
<jackson2-api.version>2.14.1-313.v504cdd45c18b</jackson2-api.version>
<docker-workflow.version>563.vd5d2e5c4007f</docker-workflow.version>
</properties>

<name>Jenkins Job Cacher plugin</name>
Expand Down Expand Up @@ -135,6 +136,11 @@
<artifactId>workflow-basic-steps</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkinsci.plugins</groupId>
<artifactId>pipeline-model-definition</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
Expand All @@ -147,6 +153,29 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>docker-workflow</artifactId>
<version>${docker-workflow.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>scm-api</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>git</artifactId>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/jenkins/plugins/jobcacher/ArbitraryFileCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import hudson.model.Job;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.remoting.LocalChannel;
import hudson.util.ListBoxModel;
import jenkins.plugins.itemstorage.GlobalItemStorage;
import jenkins.plugins.itemstorage.ObjectPath;
Expand Down Expand Up @@ -342,6 +343,9 @@ public long calculateSize(ObjectPath objectPath, Run<?, ?> build, FilePath works
public void save(ObjectPath cachesRoot, Run<?, ?> build, FilePath workspace, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
if (!resolvedPath.exists()) {
logMessage("Cannot create cache as the path does not exist", listener);
if (isPathOutsideWorkspace(workspace) && isMaybeInsideDockerContainer(workspace)) {
logMessage("Note that paths outside the workspace while using the Docker Pipeline plugin are not supported", listener);
}
return;
}

Expand Down Expand Up @@ -371,6 +375,14 @@ public void save(ObjectPath cachesRoot, Run<?, ?> build, FilePath workspace, Lau
logMessage("Cache created in " + Duration.ofNanos(cacheCreationEndTime - cacheCreationStartTime).toMillis() + "ms", listener);
}

private boolean isPathOutsideWorkspace(FilePath workspace) {
return !StringUtils.startsWith(resolvedPath.getRemote(), workspace.getRemote());
}

private boolean isMaybeInsideDockerContainer(FilePath workspace) {
return workspace.getChannel() == null || workspace.getChannel() instanceof LocalChannel;
}

private void updateSkipCacheTriggerFileHash(ObjectPath cachesRoot, FilePath workspace) throws IOException, InterruptedException {
try (TempFile tempFile = WorkspaceHelper.createTempFile(workspace, CACHE_VALIDITY_DECIDING_FILE_HASH_FILE_EXTENSION)) {
tempFile.get().write(getCurrentCacheValidityDecidingFileHash(workspace), StandardCharsets.UTF_8.displayName());
Expand Down
107 changes: 11 additions & 96 deletions src/main/java/jenkins/plugins/jobcacher/pipeline/CacheStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,21 @@
package jenkins.plugins.jobcacher.pipeline;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.Run;
import hudson.model.TaskListener;
import jenkins.model.Jenkins;
import jenkins.plugins.itemstorage.GlobalItemStorage;
import jenkins.plugins.jobcacher.Cache;
import jenkins.plugins.jobcacher.CacheDescriptor;
import jenkins.plugins.jobcacher.CacheManager;
import jenkins.plugins.jobcacher.Messages;
import org.jenkinsci.plugins.workflow.steps.*;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
* Wrapping workflow step that automatically seeds the specified path with the previous run and on exit of the
Expand Down Expand Up @@ -90,96 +86,15 @@ public List<Cache> getCaches() {

@Override
public StepExecution start(StepContext context) throws Exception {
return new ExecutionImpl(context, maxCacheSize, caches, defaultBranch);
}

public static class ExecutionImpl extends GeneralNonBlockingStepExecution {

private static final long serialVersionUID = 1L;

private final Long maxCacheSize;
private final List<Cache> caches;
private final String defaultBranch;

protected ExecutionImpl(StepContext context, Long maxCacheSize, List<Cache> caches, String defaultBranch) {
super(context);

this.maxCacheSize = maxCacheSize;
this.caches = caches;
this.defaultBranch = defaultBranch;
}

@Override
public boolean start() throws Exception {
run(this::execute);
return false;
}

private void execute() throws Exception {
StepContext context = getContext();

Run<?, ?> run = context.get(Run.class);
FilePath workspace = context.get(FilePath.class);
Launcher launcher = context.get(Launcher.class);
TaskListener listener = context.get(TaskListener.class);
EnvVars initialEnvironment = context.get(EnvVars.class);

List<Cache.Saver> cacheSavers = CacheManager.cache(GlobalItemStorage.get().getStorage(), run, workspace, launcher, listener, initialEnvironment, caches, defaultBranch);

context.newBodyInvoker()
.withContext(context)
.withCallback(new ExecutionCallback(maxCacheSize, caches, cacheSavers))
.start();
}

}

public static class ExecutionCallback extends BodyExecutionCallback {

private static final long serialVersionUID = 1L;

private final Long maxCacheSize;
private final List<Cache> caches;
private final List<Cache.Saver> cacheSavers;

public ExecutionCallback(Long maxCacheSize, List<Cache> caches, List<Cache.Saver> cacheSavers) {
this.maxCacheSize = maxCacheSize;
this.caches = caches;
this.cacheSavers = cacheSavers;
}

@Override
public void onSuccess(StepContext context, Object result) {
try {
complete(context);

context.onSuccess(result);
} catch (Throwable t) {
context.onFailure(t);
}
}

@Override
public void onFailure(StepContext context, Throwable t) {
context.onFailure(t);
}

public void complete(StepContext context) throws IOException, InterruptedException {
Run<?, ?> run = context.get(Run.class);
FilePath workspace = context.get(FilePath.class);
Launcher launcher = context.get(Launcher.class);
TaskListener listener = context.get(TaskListener.class);

CacheManager.save(GlobalItemStorage.get().getStorage(), run, workspace, launcher, listener, maxCacheSize, caches, cacheSavers);
}
return new CacheStepExecution(context, maxCacheSize, caches, defaultBranch);
}

@Extension(optional = true)
public static class DescriptorImpl extends AbstractStepDescriptorImpl {
public static class DescriptorImpl extends StepDescriptor {

@SuppressWarnings("unused")
public DescriptorImpl() {
super(ExecutionImpl.class);
@Override
public Set<? extends Class<?>> getRequiredContext() {
return Collections.emptySet();
}

@Override
Expand All @@ -198,7 +113,6 @@ public boolean takesImplicitBlockArgument() {
return true;
}


@SuppressWarnings("unused")
public List<CacheDescriptor> getCacheDescriptors() {
Jenkins jenkins = Jenkins.getInstanceOrNull();
Expand All @@ -208,5 +122,6 @@ public List<CacheDescriptor> getCacheDescriptors() {
return Collections.emptyList();
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package jenkins.plugins.jobcacher.pipeline;

import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.Run;
import hudson.model.TaskListener;
import jenkins.plugins.itemstorage.GlobalItemStorage;
import jenkins.plugins.jobcacher.Cache;
import jenkins.plugins.jobcacher.CacheManager;
import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback;
import org.jenkinsci.plugins.workflow.steps.GeneralNonBlockingStepExecution;
import org.jenkinsci.plugins.workflow.steps.StepContext;

import java.io.IOException;
import java.util.List;

public class CacheStepExecution extends GeneralNonBlockingStepExecution {

private static final long serialVersionUID = 1L;

private final Long maxCacheSize;
private final List<Cache> caches;
private final String defaultBranch;

protected CacheStepExecution(StepContext context, Long maxCacheSize, List<Cache> caches, String defaultBranch) {
super(context);

this.maxCacheSize = maxCacheSize;
this.caches = caches;
this.defaultBranch = defaultBranch;
}

@Override
public boolean start() throws Exception {
run(this::execute);
return false;
}

private void execute() throws Exception {
StepContext context = getContext();

Run<?, ?> run = context.get(Run.class);
FilePath workspace = context.get(FilePath.class);
Launcher launcher = context.get(Launcher.class);
TaskListener listener = context.get(TaskListener.class);
EnvVars initialEnvironment = context.get(EnvVars.class);

List<Cache.Saver> cacheSavers = CacheManager.cache(GlobalItemStorage.get().getStorage(), run, workspace, launcher, listener, initialEnvironment, caches, defaultBranch);

context.newBodyInvoker()
.withContext(context)
.withCallback(new ExecutionCallback(maxCacheSize, caches, cacheSavers))
.start();
}

private static class ExecutionCallback extends BodyExecutionCallback {

private static final long serialVersionUID = 1L;

private final Long maxCacheSize;
private final List<Cache> caches;
private final List<Cache.Saver> cacheSavers;

public ExecutionCallback(Long maxCacheSize, List<Cache> caches, List<Cache.Saver> cacheSavers) {
this.maxCacheSize = maxCacheSize;
this.caches = caches;
this.cacheSavers = cacheSavers;
}

@Override
public void onSuccess(StepContext context, Object result) {
try {
complete(context);

context.onSuccess(result);
} catch (Throwable t) {
context.onFailure(t);
}
}

@Override
public void onFailure(StepContext context, Throwable t) {
context.onFailure(t);
}

private void complete(StepContext context) throws IOException, InterruptedException {
Run<?, ?> run = context.get(Run.class);
FilePath workspace = context.get(FilePath.class);
Launcher launcher = context.get(Launcher.class);
TaskListener listener = context.get(TaskListener.class);

CacheManager.save(GlobalItemStorage.get().getStorage(), run, workspace, launcher, listener, maxCacheSize, caches, cacheSavers);
}
}

}
Loading

0 comments on commit 7ba3fb1

Please sign in to comment.