Skip to content

Commit

Permalink
Introducing ControllerToAgentCallable and `ControllerToAgentFileCal…
Browse files Browse the repository at this point in the history
…lable` (#9921)

Co-authored-by: Vincent Latombe <[email protected]>
  • Loading branch information
jglick and Vlatombe authored Nov 9, 2024
1 parent 0405a44 commit abef2cc
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 96 deletions.
27 changes: 3 additions & 24 deletions core/src/main/java/hudson/FilePath.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import jenkins.MasterToSlaveFileCallable;
import jenkins.SlaveToMasterFileCallable;
import jenkins.agents.ControllerToAgentFileCallable;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import jenkins.util.ContextResettingExecutorService;
Expand Down Expand Up @@ -520,30 +520,14 @@ public int archive(final ArchiverFactory factory, OutputStream os, final DirScan
return act(new Archive(factory, out, scanner, verificationRoot, openOptions));
}

private static class Archive extends MasterToSlaveFileCallable<Integer> {
private final ArchiverFactory factory;
private final OutputStream out;
private final DirScanner scanner;
private final String verificationRoot;
private OpenOption[] openOptions;

Archive(ArchiverFactory factory, OutputStream out, DirScanner scanner, String verificationRoot, OpenOption... openOptions) {
this.factory = factory;
this.out = out;
this.scanner = scanner;
this.verificationRoot = verificationRoot;
this.openOptions = openOptions;
}

private record Archive(ArchiverFactory factory, OutputStream out, DirScanner scanner, String verificationRoot, OpenOption... openOptions) implements ControllerToAgentFileCallable<Integer> {
@Override
public Integer invoke(File f, VirtualChannel channel) throws IOException {
try (Archiver a = factory.create(out)) {
scanner.scan(f, ignoringTmpDirs(ignoringSymlinks(a, verificationRoot, openOptions), verificationRoot, openOptions));
return a.countEntries();
}
}

private static final long serialVersionUID = 1L;
}

public int archive(final ArchiverFactory factory, OutputStream os, final FileFilter filter) throws IOException, InterruptedException {
Expand Down Expand Up @@ -1185,12 +1169,7 @@ public void copyFrom(org.apache.commons.fileupload.FileItem file) throws IOExcep
/**
* Code that gets executed on the machine where the {@link FilePath} is local.
* Used to act on {@link FilePath}.
* <strong>Warning:</strong> implementations must be serializable, so prefer a static nested class to an inner class.
*
* <p>
* Subtypes would likely want to extend from either {@link MasterToSlaveCallable}
* or {@link SlaveToMasterFileCallable}.
*
* A typical implementation would be a {@code record} implementing {@link ControllerToAgentFileCallable}.
* @see FilePath#act(FileCallable)
*/
public interface FileCallable<T> extends Serializable, RoleSensitive {
Expand Down
47 changes: 6 additions & 41 deletions core/src/main/java/hudson/Launcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.agents.ControllerToAgentCallable;
import jenkins.model.Jenkins;
import jenkins.security.MasterToSlaveCallable;
import jenkins.tasks.filters.EnvVarsFilterLocalRule;
Expand Down Expand Up @@ -1114,8 +1115,7 @@ public Proc launch(ProcStarter ps) throws IOException {
final String workDir = psPwd == null ? null : psPwd.getRemote();

try {
RemoteLaunchCallable remote = new RemoteLaunchCallable(ps.commands, ps.masks, ps.envs, in, ps.reverseStdin, out, ps.reverseStdout, err, ps.reverseStderr, ps.quiet, workDir, listener, ps.stdoutListener);
remote.setEnvVarsFilterRuleWrapper(envVarsFilterRuleWrapper);
RemoteLaunchCallable remote = new RemoteLaunchCallable(ps.commands, ps.masks, ps.envs, in, ps.reverseStdin, out, ps.reverseStdout, err, ps.reverseStderr, ps.quiet, workDir, listener, ps.stdoutListener, envVarsFilterRuleWrapper);
// reset the rules to prevent build step without rules configuration to re-use those
envVarsFilterRuleWrapper = null;
return new ProcImpl(getChannel().call(remote));
Expand Down Expand Up @@ -1334,46 +1334,13 @@ public interface RemoteProcess {
IOTriplet getIOtriplet();
}

private static class RemoteLaunchCallable extends MasterToSlaveCallable<RemoteProcess, IOException> {
private final @NonNull List<String> cmd;
private final @CheckForNull boolean[] masks;
private final @CheckForNull String[] env;
private final @CheckForNull InputStream in;
private final @CheckForNull OutputStream out;
private final @CheckForNull OutputStream err;
private final @CheckForNull String workDir;
private final @NonNull TaskListener listener;
private final @CheckForNull TaskListener stdoutListener;
private final boolean reverseStdin, reverseStdout, reverseStderr;
private final boolean quiet;

private EnvVarsFilterRuleWrapper envVarsFilterRuleWrapper;

RemoteLaunchCallable(@NonNull List<String> cmd, @CheckForNull boolean[] masks, @CheckForNull String[] env,
private record RemoteLaunchCallable(@NonNull List<String> cmd, @CheckForNull boolean[] masks, @CheckForNull String[] env,
@CheckForNull InputStream in, boolean reverseStdin,
@CheckForNull OutputStream out, boolean reverseStdout,
@CheckForNull OutputStream err, boolean reverseStderr,
boolean quiet, @CheckForNull String workDir, @NonNull TaskListener listener, @CheckForNull TaskListener stdoutListener) {
this.cmd = new ArrayList<>(cmd);
this.masks = masks;
this.env = env;
this.in = in;
this.out = out;
this.err = err;
this.workDir = workDir;
this.listener = listener;
this.stdoutListener = stdoutListener;
this.reverseStdin = reverseStdin;
this.reverseStdout = reverseStdout;
this.reverseStderr = reverseStderr;
this.quiet = quiet;
}

@Restricted(NoExternalUse.class)
public void setEnvVarsFilterRuleWrapper(EnvVarsFilterRuleWrapper envVarsFilterRuleWrapper) {
this.envVarsFilterRuleWrapper = envVarsFilterRuleWrapper;
}

boolean quiet, @CheckForNull String workDir,
@NonNull TaskListener listener, @CheckForNull TaskListener stdoutListener,
@CheckForNull EnvVarsFilterRuleWrapper envVarsFilterRuleWrapper) implements ControllerToAgentCallable<RemoteProcess, IOException> {
@Override
public RemoteProcess call() throws IOException {
final Channel channel = getOpenChannelOrFail();
Expand Down Expand Up @@ -1433,8 +1400,6 @@ public IOTriplet getIOtriplet() {
}
});
}

private static final long serialVersionUID = 1L;
}

private static class RemoteChannelLaunchCallable extends MasterToSlaveCallable<OutputStream, IOException> {
Expand Down
22 changes: 6 additions & 16 deletions core/src/main/java/jenkins/MasterToSlaveFileCallable.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
package jenkins;

import hudson.FilePath.FileCallable;
import hudson.remoting.VirtualChannel;
import java.io.File;
import jenkins.security.Roles;
import jenkins.slaves.RemotingVersionInfo;
import org.jenkinsci.remoting.RoleChecker;
import jenkins.agents.ControllerToAgentFileCallable;

/**
* {@link FileCallable}s that are meant to be only used on the master.
*
* Note that the logic within {@link #invoke(File, VirtualChannel)} should use API of a minimum supported Remoting version.
* See {@link RemotingVersionInfo#getMinimumSupportedVersion()}.
*
* {@link FileCallable}s that could run on an agent.
* For new code, implement {@link ControllerToAgentFileCallable}
* which has the advantage that it can be used on {@code record}s.
* @since 1.587 / 1.580.1
* @param <T> the return type; note that this must either be defined in your plugin or included in the stock JEP-200 whitelist
* @param <T> the return type
*/
public abstract class MasterToSlaveFileCallable<T> implements FileCallable<T> {
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
checker.check(this, Roles.SLAVE);
}
public abstract class MasterToSlaveFileCallable<T> implements ControllerToAgentFileCallable<T> {

private static final long serialVersionUID = 1L;
}
48 changes: 48 additions & 0 deletions core/src/main/java/jenkins/agents/ControllerToAgentCallable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* The MIT License
*
* Copyright 2024 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package jenkins.agents;

import hudson.remoting.Callable;
import jenkins.security.Roles;
import jenkins.slaves.RemotingVersionInfo;
import org.jenkinsci.remoting.RoleChecker;

/**
* {@link Callable} meant to be serialized then run on an agent.
* A typical implementation will be a {@link Record}
* since instance state merely transfers a set of parameters to an agent JVM.
* <p>Note that the logic within {@link #call} may not use Remoting APIs
* newer than {@link RemotingVersionInfo#getMinimumSupportedVersion}.
* (Core and plugin APIs will be identical to those run inside the controller.)
* @param <V> the return type; note that this must either be defined in your plugin or included in the stock JEP-200 whitelist
* @since TODO
*/
public interface ControllerToAgentCallable<V, T extends Throwable> extends Callable<V, T> {

@Override
default void checkRoles(RoleChecker checker) throws SecurityException {
checker.check(this, Roles.SLAVE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* The MIT License
*
* Copyright 2024 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package jenkins.agents;

import hudson.FilePath;
import jenkins.security.Roles;
import org.jenkinsci.remoting.RoleChecker;

/**
* {@link FilePath.FileCallable} meant to be serialized then run on an agent.
* Like {@link ControllerToAgentCallable} this will typically be a {@link Record}.
* @param <T> the return type; note that this must either be defined in your plugin or included in the stock JEP-200 whitelist
* @since TODO
*/
public interface ControllerToAgentFileCallable<T> extends FilePath.FileCallable<T> {

@Override
default void checkRoles(RoleChecker checker) throws SecurityException {
checker.check(this, Roles.SLAVE);
}
}
20 changes: 6 additions & 14 deletions core/src/main/java/jenkins/security/MasterToSlaveCallable.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
package jenkins.security;

import hudson.remoting.Callable;
import jenkins.slaves.RemotingVersionInfo;
import org.jenkinsci.remoting.RoleChecker;
import jenkins.agents.ControllerToAgentCallable;

/**
* Convenient {@link Callable} meant to be run on agent.
*
* Note that the logic within {@link #call()} should use API of a minimum supported Remoting version.
* See {@link RemotingVersionInfo#getMinimumSupportedVersion()}.
*
* {@link Callable} meant to be run on agent.
* For new code, implement {@link ControllerToAgentCallable}
* which has the advantage that it can be used on {@code record}s.
* @author Kohsuke Kawaguchi
* @since 1.587 / 1.580.1
* @param <V> the return type; note that this must either be defined in your plugin or included in the stock JEP-200 whitelist
* @param <V> the return type
*/
public abstract class MasterToSlaveCallable<V, T extends Throwable> implements Callable<V, T> {
public abstract class MasterToSlaveCallable<V, T extends Throwable> implements ControllerToAgentCallable<V, T> {

private static final long serialVersionUID = 1L;

@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
checker.check(this, Roles.SLAVE);
}
}
3 changes: 2 additions & 1 deletion core/src/main/java/jenkins/slaves/RemotingVersionInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.agents.ControllerToAgentCallable;

/**
* Provides information about Remoting versions used within the core.
Expand Down Expand Up @@ -100,7 +101,7 @@ public static VersionNumber getEmbeddedVersion() {

/**
* Gets Remoting version which is supported by the core.
* Jenkins core and plugins make invoke operations on agents (e.g. {@link jenkins.security.MasterToSlaveCallable})
* Jenkins core and plugins make invoke operations on agents (e.g. {@link ControllerToAgentCallable})
* and use Remoting-internal API within them.
* In such case this API should be present on the remote side.
* This method defines a minimum expected version, so that all calls should use a compatible API.
Expand Down

0 comments on commit abef2cc

Please sign in to comment.