提交 c508620b 编写于 作者: D Daniel Beck
......@@ -2,6 +2,6 @@
<extension>
<groupId>io.jenkins.tools.incrementals</groupId>
<artifactId>git-changelist-maven-extension</artifactId>
<version>1.0-beta-3</version>
<version>1.0-beta-4</version>
</extension>
</extensions>
......@@ -535,7 +535,7 @@ THE SOFTWARE.
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.2.1</version>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.kohsuke</groupId>
......
......@@ -166,6 +166,8 @@ public abstract class Launcher {
@CheckForNull
protected OutputStream stdout = NULL_OUTPUT_STREAM, stderr;
@CheckForNull
private TaskListener stdoutListener;
@CheckForNull
protected InputStream stdin = NULL_INPUT_STREAM;
@CheckForNull
protected String[] envs = null;
......@@ -285,17 +287,20 @@ public abstract class Launcher {
*/
public ProcStarter stdout(@CheckForNull OutputStream out) {
this.stdout = out;
stdoutListener = null;
return this;
}
/**
* Sends the stdout to the given {@link TaskListener}.
*
* @param out Task listener
* @param out Task listener (must be safely remotable)
* @return {@code this}
*/
public ProcStarter stdout(@Nonnull TaskListener out) {
return stdout(out.getLogger());
stdout = out.getLogger();
stdoutListener = out;
return this;
}
/**
......@@ -490,6 +495,7 @@ public abstract class Launcher {
@Nonnull
public ProcStarter copy() {
ProcStarter rhs = new ProcStarter().cmds(commands).pwd(pwd).masks(masks).stdin(stdin).stdout(stdout).stderr(stderr).envs(envs).quiet(quiet);
rhs.stdoutListener = stdoutListener;
rhs.reverseStdin = this.reverseStdin;
rhs.reverseStderr = this.reverseStderr;
rhs.reverseStdout = this.reverseStdout;
......@@ -1041,7 +1047,7 @@ public abstract class Launcher {
}
public Proc launch(ProcStarter ps) throws IOException {
final OutputStream out = ps.stdout == null ? null : new RemoteOutputStream(new CloseProofOutputStream(ps.stdout));
final OutputStream out = ps.stdout == null || ps.stdoutListener != null ? null : new RemoteOutputStream(new CloseProofOutputStream(ps.stdout));
final OutputStream err = ps.stderr==null ? null : new RemoteOutputStream(new CloseProofOutputStream(ps.stderr));
final InputStream in = (ps.stdin==null || ps.stdin==NULL_INPUT_STREAM) ? null : new RemoteInputStream(ps.stdin,false);
......@@ -1049,7 +1055,7 @@ public abstract class Launcher {
final String workDir = psPwd==null ? null : psPwd.getRemote();
try {
return new ProcImpl(getChannel().call(new RemoteLaunchCallable(ps.commands, ps.masks, ps.envs, in, ps.reverseStdin, out, ps.reverseStdout, err, ps.reverseStderr, ps.quiet, workDir, listener)));
return new ProcImpl(getChannel().call(new RemoteLaunchCallable(ps.commands, ps.masks, ps.envs, in, ps.reverseStdin, out, ps.reverseStdout, err, ps.reverseStderr, ps.quiet, workDir, listener, ps.stdoutListener)));
} catch (InterruptedException e) {
throw (IOException)new InterruptedIOException().initCause(e);
}
......@@ -1265,6 +1271,7 @@ public abstract class Launcher {
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;
......@@ -1272,7 +1279,7 @@ public abstract class Launcher {
@CheckForNull InputStream in, boolean reverseStdin,
@CheckForNull OutputStream out, boolean reverseStdout,
@CheckForNull OutputStream err, boolean reverseStderr,
boolean quiet, @CheckForNull String workDir, @Nonnull TaskListener listener) {
boolean quiet, @CheckForNull String workDir, @Nonnull TaskListener listener, @CheckForNull TaskListener stdoutListener) {
this.cmd = new ArrayList<>(cmd);
this.masks = masks;
this.env = env;
......@@ -1281,6 +1288,7 @@ public abstract class Launcher {
this.err = err;
this.workDir = workDir;
this.listener = listener;
this.stdoutListener = stdoutListener;
this.reverseStdin = reverseStdin;
this.reverseStdout = reverseStdout;
this.reverseStderr = reverseStderr;
......@@ -1290,7 +1298,12 @@ public abstract class Launcher {
public RemoteProcess call() throws IOException {
final Channel channel = getOpenChannelOrFail();
Launcher.ProcStarter ps = new LocalLauncher(listener).launch();
ps.cmds(cmd).masks(masks).envs(env).stdin(in).stdout(out).stderr(err).quiet(quiet);
ps.cmds(cmd).masks(masks).envs(env).stdin(in).stderr(err).quiet(quiet);
if (stdoutListener != null) {
ps.stdout(stdoutListener.getLogger());
} else {
ps.stdout(out);
}
if(workDir!=null) ps.pwd(workDir);
if (reverseStdin) ps.writeStdin();
if (reverseStdout) ps.readStdout();
......
......@@ -36,7 +36,6 @@ import jenkins.model.Jenkins;
import hudson.model.UpdateCenter;
import hudson.model.UpdateSite;
import hudson.util.VersionNumber;
import org.jvnet.localizer.ResourceBundleHolder;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
......@@ -54,7 +53,6 @@ import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
......@@ -64,7 +62,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
......@@ -600,7 +597,7 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
} else {
VersionNumber actualVersion = Jenkins.getVersion();
if (actualVersion.isOlderThan(new VersionNumber(requiredCoreVersion))) {
dependencyErrors.put(Messages.PluginWrapper_obsoleteCore(Jenkins.getVersion().toString(), requiredCoreVersion), false);
versionDependencyError(Messages.PluginWrapper_obsoleteCore(Jenkins.getVersion().toString(), requiredCoreVersion), Jenkins.getVersion().toString(), requiredCoreVersion);
}
}
}
......@@ -618,11 +615,11 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
} else {
if (dependency.isActive()) {
if (isDependencyObsolete(d, dependency)) {
dependencyErrors.put(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version), false);
versionDependencyError(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version), dependency.getVersion(), d.version);
}
} else {
if (isDependencyObsolete(d, dependency)) {
dependencyErrors.put(Messages.PluginWrapper_disabledAndObsolete(dependency.getLongName(), dependency.getVersion(), d.version), false);
versionDependencyError(Messages.PluginWrapper_disabledAndObsolete(dependency.getLongName(), dependency.getVersion(), d.version), dependency.getVersion(), d.version);
} else {
dependencyErrors.put(Messages.PluginWrapper_disabled(dependency.getLongName()), false);
}
......@@ -635,7 +632,7 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
PluginWrapper dependency = parent.getPlugin(d.shortName);
if (dependency != null && dependency.isActive()) {
if (isDependencyObsolete(d, dependency)) {
dependencyErrors.put(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version), false);
versionDependencyError(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version), dependency.getVersion(), d.version);
} else {
dependencies.add(d);
}
......@@ -660,6 +657,26 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
return ENABLE_PLUGIN_DEPENDENCIES_VERSION_CHECK && dependency.getVersionNumber().isOlderThan(new VersionNumber(d.version));
}
/**
* Called when there appears to be a core or plugin version which is too old for a stated dependency.
* Normally records an error in {@link #dependencyErrors}.
* But if one or both versions {@link #isSnapshot}, just issue a warning (JENKINS-52665).
*/
private void versionDependencyError(String message, String actual, String minimum) {
if (isSnapshot(actual) || isSnapshot(minimum)) {
LOGGER.log(WARNING, "Suppressing dependency error in {0} v{1}: {2}", new Object[] {getLongName(), getVersion(), message});
} else {
dependencyErrors.put(message, false);
}
}
/**
* Similar to {@code org.apache.maven.artifact.ArtifactUtils.isSnapshot}.
*/
static boolean isSnapshot(@Nonnull String version) {
return version.contains("-SNAPSHOT") || version.matches(".+-[0-9]{8}.[0-9]{6}-[0-9]+");
}
/**
* If the plugin has {@link #getUpdateInfo() an update},
* returns the {@link hudson.model.UpdateSite.Plugin} object.
......
/*
* The MIT License
*
* Copyright (c) 2018 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 hudson.cli;
import hudson.Extension;
import hudson.PluginManager;
import hudson.PluginWrapper;
import jenkins.model.Jenkins;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import java.io.IOException;
import java.util.List;
/**
* Enables one or more installed plugins. The listed plugins must already be installed along with its dependencies.
* Any listed plugin with disabled dependencies will have its dependencies enabled transitively. Note that enabling an
* already enabled plugin does nothing.
*
* @since TODO
*/
@Extension
public class EnablePluginCommand extends CLICommand {
@Argument(required = true, usage = "Enables the plugins with the given short names and their dependencies.")
private List<String> pluginNames;
@Option(name = "-restart", usage = "Restart Jenkins after enabling plugins.")
private boolean restart;
@Override
public String getShortDescription() {
return Messages.EnablePluginCommand_ShortDescription();
}
@Override
protected int run() throws Exception {
Jenkins jenkins = Jenkins.get();
jenkins.checkPermission(Jenkins.ADMINISTER);
PluginManager manager = jenkins.getPluginManager();
for (String pluginName : pluginNames) {
enablePlugin(manager, pluginName);
}
if (restart) {
jenkins.safeRestart();
}
return 0;
}
private void enablePlugin(PluginManager manager, String shortName) throws IOException {
PluginWrapper plugin = manager.getPlugin(shortName);
if (plugin == null) {
throw new IllegalArgumentException(Messages.EnablePluginCommand_NoSuchPlugin(shortName));
}
if (plugin.isEnabled()) {
return;
}
stdout.println(String.format("Enabling plugin `%s' (%s)", plugin.getShortName(), plugin.getVersion()));
enableDependencies(manager, plugin);
plugin.enable();
stdout.println(String.format("Plugin `%s' was enabled.", plugin.getShortName()));
}
private void enableDependencies(PluginManager manager, PluginWrapper plugin) throws IOException {
for (PluginWrapper.Dependency dep : plugin.getDependencies()) {
PluginWrapper dependency = manager.getPlugin(dep.shortName);
if (dependency == null) {
throw new IllegalArgumentException(Messages.EnablePluginCommand_MissingDependencies(plugin.getShortName(), dep));
}
if (!dependency.isEnabled()) {
enableDependencies(manager, dependency);
stdout.println(String.format("Enabling plugin dependency `%s' (%s) for `%s'", dependency.getShortName(), dependency.getVersion(), plugin.getShortName()));
dependency.enable();
}
}
}
}
......@@ -1470,7 +1470,7 @@ public class Queue extends ResourceController implements Saveable {
{// update parked (and identify any pending items whose executor has disappeared)
List<BuildableItem> lostPendings = new ArrayList<BuildableItem>(pendings);
for (Computer c : jenkins.getComputers()) {
for (Executor e : c.getExecutors()) {
for (Executor e : c.getAllExecutors()) {
if (e.isInterrupted()) {
// JENKINS-28840 we will deadlock if we try to touch this executor while interrupt flag set
// we need to clear lost pendings as we cannot know what work unit was on this executor
......
......@@ -25,6 +25,7 @@ package hudson.model;
import hudson.console.ConsoleNote;
import hudson.console.HyperlinkNote;
import hudson.remoting.Channel;
import hudson.util.NullStream;
import hudson.util.StreamTaskListener;
......@@ -59,7 +60,9 @@ import org.kohsuke.accmod.restrictions.ProtectedExternally;
*
* <p>
* {@link StreamTaskListener} is the most typical implementation of this interface.
* All the {@link TaskListener} implementations passed to plugins from Hudson core are remotable.
*
* <p>
* Implementations are generally expected to be remotable via {@link Channel}.
*
* @author Kohsuke Kawaguchi
*/
......
......@@ -39,6 +39,9 @@ import org.kohsuke.stapler.Stapler;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* {@link NodeProperty} that sets additional environment variables.
......@@ -65,6 +68,14 @@ public class EnvironmentVariablesNodeProperty extends NodeProperty<Node> {
return envVars;
}
/**
* @return environment variables using same data type as constructor parameter.
* @since TODO
*/
public List<Entry> getEnv() {
return envVars.entrySet().stream().map(Entry::new).collect(Collectors.toList());
}
@Override
public Environment setUp(AbstractBuild build, Launcher launcher,
BuildListener listener) throws IOException, InterruptedException {
......@@ -100,6 +111,10 @@ public class EnvironmentVariablesNodeProperty extends NodeProperty<Node> {
public static class Entry {
public String key, value;
private Entry(Map.Entry<String,String> e) {
this(e.getKey(), e.getValue());
}
@DataBoundConstructor
public Entry(String key, String value) {
this.key = key;
......
......@@ -42,12 +42,14 @@ import org.kohsuke.stapler.DataBoundConstructor;
/**
* An object which can ensure that a generic {@link ToolInstallation} in fact exists on a node.
* The properties can be added to {@link ToolInstallation} using the {@link InstallSourceProperty}.
*
* The subclass should have a {@link ToolInstallerDescriptor}.
* A {@code config.jelly} should be provided to customize specific fields;
* {@code <t:label xmlns:t="/hudson/tools"/>} to customize {@code label}.
* @see <a href="http://wiki.jenkins-ci.org/display/JENKINS/Tool+Auto-Installation">Tool Auto-Installation</a>
* @since 1.305
* @see InstallSourceProperty
*/
public abstract class ToolInstaller implements Describable<ToolInstaller>, ExtensionPoint {
......
......@@ -100,7 +100,7 @@ public class LegacyApiTokenAdministrativeMonitor extends AdministrativeMonitor {
@Restricted(NoExternalUse.class)
public @Nullable ApiTokenProperty.TokenInfoAndStats getLegacyStatsOf(@Nonnull User user, @Nullable ApiTokenStore.HashedToken legacyToken) {
ApiTokenProperty apiTokenProperty = user.getProperty(ApiTokenProperty.class);
if(legacyToken != null){
if (legacyToken != null) {
ApiTokenStats.SingleTokenStats legacyStats = apiTokenProperty.getTokenStats().findTokenStatsById(legacyToken.getUuid());
ApiTokenProperty.TokenInfoAndStats tokenInfoAndStats = new ApiTokenProperty.TokenInfoAndStats(legacyToken, legacyStats);
return tokenInfoAndStats;
......@@ -116,7 +116,7 @@ public class LegacyApiTokenAdministrativeMonitor extends AdministrativeMonitor {
// used by Jelly view
@Restricted(NoExternalUse.class)
public boolean hasFreshToken(@Nonnull User user, @Nullable ApiTokenProperty.TokenInfoAndStats legacyStats) {
if(legacyStats == null){
if (legacyStats == null) {
return false;
}
......@@ -140,12 +140,12 @@ public class LegacyApiTokenAdministrativeMonitor extends AdministrativeMonitor {
// used by Jelly view
@Restricted(NoExternalUse.class)
public boolean hasMoreRecentlyUsedToken(@Nonnull User user, @Nullable ApiTokenProperty.TokenInfoAndStats legacyStats) {
if(legacyStats == null){
if (legacyStats == null) {
return false;
}
ApiTokenProperty apiTokenProperty = user.getProperty(ApiTokenProperty.class);
return apiTokenProperty.getTokenList().stream()
.filter(token -> !token.isLegacy)
.anyMatch(token -> {
......@@ -161,18 +161,22 @@ public class LegacyApiTokenAdministrativeMonitor extends AdministrativeMonitor {
@RequirePOST
public HttpResponse doRevokeAllSelected(@JsonBody RevokeAllSelectedModel content) throws IOException {
for (RevokeAllSelectedUserAndUuid value : content.values) {
if (value.userId == null) {
// special case not managed by JSONObject
value.userId = "null";
}
User user = User.getById(value.userId, false);
if (user == null) {
LOGGER.log(Level.INFO, "User not found id={0}", value.userId);
} else {
ApiTokenProperty apiTokenProperty = user.getProperty(ApiTokenProperty.class);
if(apiTokenProperty == null){
if (apiTokenProperty == null) {
LOGGER.log(Level.INFO, "User without apiTokenProperty found id={0}", value.userId);
}else{
} else {
ApiTokenStore.HashedToken revokedToken = apiTokenProperty.getTokenStore().revokeToken(value.uuid);
if(revokedToken == null){
if (revokedToken == null) {
LOGGER.log(Level.INFO, "User without selected token id={0}, tokenUuid={1}", new Object[]{value.userId, value.uuid});
}else{
} else {
apiTokenProperty.deleteApiToken();
user.save();
LOGGER.log(Level.INFO, "Revocation success for user id={0}, tokenUuid={1}", new Object[]{value.userId, value.uuid});
......
......@@ -16,6 +16,8 @@ import org.kohsuke.stapler.StaplerProxy;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.interceptor.RequirePOST;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
......@@ -52,12 +54,10 @@ public class AdminWhitelistRule implements StaplerProxy {
*/
public final FilePathRuleConfig filePathRules;
private final Jenkins jenkins;
private boolean masterKillSwitch;
public AdminWhitelistRule() throws IOException, InterruptedException {
this.jenkins = Jenkins.getInstance();
final Jenkins jenkins = Jenkins.get();
// while this file is not a secret, write access to this file is dangerous,
// so put this in the better-protected part of $JENKINS_HOME, which is in secrets/
......@@ -79,17 +79,21 @@ public class AdminWhitelistRule implements StaplerProxy {
whitelisted);
this.filePathRules = new FilePathRuleConfig(
new File(jenkins.getRootDir(),"secrets/filepath-filters.d/50-gui.conf"));
this.masterKillSwitch = loadMasterKillSwitchFile();
File f = getMasterKillSwitchFile(jenkins);
this.masterKillSwitch = loadMasterKillSwitchFile(f);
}
/**
* Reads the master kill switch.
* Reads the master kill switch from a file.
*
* Instead of {@link FileBoolean}, we use a text file so that the admin can prevent Jenkins from
* writing this to file.
* @param f File to load
* @return {@code true} if the file was loaded, {@code false} otherwise
*/
private boolean loadMasterKillSwitchFile() {
File f = getMasterKillSwitchFile();
@CheckReturnValue
private boolean loadMasterKillSwitchFile(@Nonnull File f) {
try {
if (!f.exists()) return true;
return Boolean.parseBoolean(FileUtils.readFileToString(f).trim());
......@@ -99,7 +103,8 @@ public class AdminWhitelistRule implements StaplerProxy {
}
}
private File getMasterKillSwitchFile() {
@Nonnull
private File getMasterKillSwitchFile(@Nonnull Jenkins jenkins) {
return new File(jenkins.getRootDir(),"secrets/slave-to-master-security-kill-switch");
}
......@@ -155,7 +160,7 @@ public class AdminWhitelistRule implements StaplerProxy {
@RequirePOST
public HttpResponse doSubmit(StaplerRequest req) throws IOException {
jenkins.checkPermission(Jenkins.RUN_SCRIPTS);
Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS);
String whitelist = Util.fixNull(req.getParameter("whitelist"));
if (!whitelist.endsWith("\n"))
......@@ -206,11 +211,13 @@ public class AdminWhitelistRule implements StaplerProxy {
}
public void setMasterKillSwitch(boolean state) {
final Jenkins jenkins = Jenkins.get();
try {
jenkins.checkPermission(Jenkins.RUN_SCRIPTS);
FileUtils.writeStringToFile(getMasterKillSwitchFile(),Boolean.toString(state));
File f = getMasterKillSwitchFile(jenkins);
FileUtils.writeStringToFile(f, Boolean.toString(state));
// treat the file as the canonical source of information in case write fails
masterKillSwitch = loadMasterKillSwitchFile();
masterKillSwitch = loadMasterKillSwitchFile(f);
} catch (IOException e) {
LOGGER.log(WARNING, "Failed to write master kill switch", e);
}
......@@ -221,7 +228,7 @@ public class AdminWhitelistRule implements StaplerProxy {
*/
@Override
public Object getTarget() {
jenkins.checkPermission(Jenkins.RUN_SCRIPTS);
Jenkins.get().checkPermission(Jenkins.RUN_SCRIPTS);
return this;
}
......
......@@ -7,6 +7,9 @@ InstallPluginCommand.NoUpdateCenterDefined=Note that no update center is defined
InstallPluginCommand.NoUpdateDataRetrieved=No update center data is retrieved yet from: {0}
InstallPluginCommand.NotAValidSourceName={0} is neither a valid file, URL, nor a plugin artifact name in the update center
EnablePluginCommand.NoSuchPlugin=No such plugin found with the name {0}
EnablePluginCommand.MissingDependencies=Cannot enable plugin {0} as it is missing the dependency {1}
AddJobToViewCommand.ShortDescription=\
Adds jobs to view.
BuildCommand.ShortDescription=\
......@@ -25,6 +28,8 @@ DeleteViewCommand.ShortDescription=\
Deletes view(s).
DeleteJobCommand.ShortDescription=\
Deletes job(s).
EnablePluginCommand.ShortDescription=\
Enables one or more installed plugins transitively.
GroovyCommand.ShortDescription=\
Executes the specified Groovy script.
GroovyshCommand.ShortDescription=\
......
......@@ -21,6 +21,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
You\ have\ data\ stored\ in\ an\ older\ format\ and/or\ unreadable\ data.=\u60A8\u6709\u8CC7\u6599\u683C\u5F0F\u904E\u820A\u6216\u662F\u7121\u6CD5\u8B80\u53D6\u3002
You\ have\ data\ stored\ in\ an\ older\ format\ and/or\ unreadable\ data.=\u60A8\u7684\u5B58\u50A8\u4E2D\u6709\u65E0\u6CD5\u8BFB\u53D6\u6216\u8005\u65E7\u7684\u6570\u636E\u683C\u5F0F\u3002
Manage=\u7BA1\u7406
Dismiss=\u4E0D\u518D\u663E\u793A
......@@ -25,7 +25,7 @@ THE SOFTWARE.
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define"
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%List of variables}" help="${descriptor.getHelpPage()}">
<f:repeatable var="env" items="${instance.envVars.entrySet()}">
<f:repeatable var="env" items="${instance.env}">
<table width="100%">
<f:entry title="${%Name}">
<f:textbox name="env.key" value="${env.key}" />
......
......@@ -15,9 +15,8 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;
import org.jvnet.hudson.test.Issue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
......@@ -180,4 +179,16 @@ public class PluginWrapperTest {
);
}
}
@Issue("JENKINS-52665")
@Test
public void isSnapshot() {
assertFalse(PluginWrapper.isSnapshot("1.0"));
assertFalse(PluginWrapper.isSnapshot("1.0-alpha-1"));
assertFalse(PluginWrapper.isSnapshot("1.0-rc9999.abc123def456"));
assertTrue(PluginWrapper.isSnapshot("1.0-SNAPSHOT"));
assertTrue(PluginWrapper.isSnapshot("1.0-20180719.153600-1"));
assertTrue(PluginWrapper.isSnapshot("1.0-SNAPSHOT (private-abcd1234-jqhacker)"));
}
}
......@@ -28,7 +28,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci</groupId>
<artifactId>jenkins</artifactId>
<version>1.46</version>
<version>1.48</version>
<relativePath />
</parent>
......@@ -75,7 +75,7 @@ THE SOFTWARE.
</issueManagement>
<properties>
<revision>2.136</revision>
<revision>2.138</revision>
<changelist>-SNAPSHOT</changelist>
<!-- *.html files are in UTF-8, and *.properties are in iso-8859-1, so this configuration is actually incorrect,
......
......@@ -23,8 +23,10 @@
*/
package hudson;
import hudson.console.LineTransformationOutputStream;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Node;
......@@ -32,15 +34,27 @@ import hudson.model.ParametersDefinitionProperty;
import hudson.model.Slave;
import hudson.model.StringParameterDefinition;
import hudson.model.TaskListener;
import hudson.remoting.Channel;
import hudson.tasks.BatchFile;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.tasks.CommandInterpreter;
import hudson.tasks.Shell;
import hudson.util.StreamTaskListener;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.junit.Assume.*;
import org.junit.Rule;
import org.junit.Test;
......@@ -153,4 +167,110 @@ public class LauncherTest {
}
}
@Issue("JENKINS-52729")
@Test public void remotable() throws Exception {
File log = new File(rule.jenkins.root, "log");
TaskListener listener = new RemotableBuildListener(log);
Launcher.ProcStarter ps = rule.createOnlineSlave().createLauncher(listener).launch();
if (Functions.isWindows()) {
ps.cmds("cmd", "/c", "echo", "hello");
} else {
ps.cmds("echo", "hello");
}
assertEquals(0, ps.stdout(listener).join());
assertThat(FileUtils.readFileToString(log, StandardCharsets.UTF_8).replace("\r\n", "\n"),
containsString("[master → slave0] $ " + (Functions.isWindows() ? "cmd /c " : "") + "echo hello\n" +
"[master → slave0] hello"));
}
private static class RemotableBuildListener implements BuildListener {
private static final long serialVersionUID = 1;
/** location of log file streamed to by multiple sources */
private final File logFile;
/** records allocation & deserialization history; e.g., {@code master → agentName} */
private final String id;
private transient PrintStream logger;
RemotableBuildListener(File logFile) {
this(logFile, "master");
}
private RemotableBuildListener(File logFile, String id) {
this.logFile = logFile;
this.id = id;
}
@Override public PrintStream getLogger() {
if (logger == null) {
final OutputStream fos;
try {
fos = new FileOutputStream(logFile, true);
logger = new PrintStream(new LineTransformationOutputStream() {
@Override protected void eol(byte[] b, int len) throws IOException {
fos.write(("[" + id + "] ").getBytes(StandardCharsets.UTF_8));
fos.write(b, 0, len);
}
}, true, "UTF-8");
} catch (IOException x) {
throw new AssertionError(x);
}
}
return logger;
}
private Object writeReplace() {
Thread.dumpStack();
String name = Channel.current().getName();
return new RemotableBuildListener(logFile, id + " → " + name);
}
}
@Issue("JENKINS-52729")
@Test public void multipleStdioCalls() throws Exception {
Node master = rule.jenkins;
Node agent = rule.createOnlineSlave();
for (Node node : new Node[] {master, agent}) {
assertMultipleStdioCalls("first TaskListener then OutputStream", node, false, (ps, os1, os2, os2Listener) -> {
ps.stdout(os2Listener).stdout(os1);
assertEquals(os1, ps.stdout());
}, false);
assertMultipleStdioCalls("first OutputStream then TaskListener", node, false, (ps, os1, os2, os2Listener) -> {
ps.stdout(os1).stdout(os2Listener);
assertEquals(os2Listener.getLogger(), ps.stdout());
}, true);
assertMultipleStdioCalls("stdout then stderr", node, true, (ps, os1, os2, os2Listener) -> {
ps.stdout(os1).stderr(os2);
assertEquals(os1, ps.stdout());
assertEquals(os2, ps.stderr());
}, true);
assertMultipleStdioCalls("stderr then stdout", node, true, (ps, os1, os2, os2Listener) -> {
ps.stdout(os1).stderr(os2);
assertEquals(os1, ps.stdout());
assertEquals(os2, ps.stderr());
}, true);
}
}
@FunctionalInterface
private interface ProcStarterCustomizer {
void run(Launcher.ProcStarter ps, OutputStream os1, OutputStream os2, TaskListener os2Listener) throws Exception;
}
private void assertMultipleStdioCalls(String message, Node node, boolean emitStderr, ProcStarterCustomizer psCustomizer, boolean outputIn2) throws Exception {
message = node.getDisplayName() + ": " + message;
Launcher launcher = node.createLauncher(StreamTaskListener.fromStderr());
Launcher.ProcStarter ps = launcher.launch();
assumeFalse("should not be platform-dependent, not bothering for now", Functions.isWindows());
if (emitStderr) {
ps.cmds("sh", "-c", "echo hello >&2").quiet(true);
} else {
ps.cmds("echo", "hello");
}
ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
TaskListener listener = new StreamTaskListener(baos2);
psCustomizer.run(ps, baos1, baos2, listener);
assertEquals(message, 0, ps.join());
if (outputIn2) {
assertThat(message, baos2.toString(), containsString("hello"));
assertThat(message, baos1.toString(), isEmptyString());
} else {
assertThat(message, baos1.toString(), containsString("hello"));
assertThat(message, baos2.toString(), isEmptyString());
}
}
}
/*
* The MIT License
*
* Copyright (c) 2018 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 hudson.cli;
import hudson.PluginManager;
import hudson.PluginWrapper;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import java.io.IOException;
import static hudson.cli.CLICommandInvoker.Matcher.failedWith;
import static hudson.cli.CLICommandInvoker.Matcher.succeeded;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class EnablePluginCommandTest {
@Rule
public JenkinsRule j = new JenkinsRule();
private CLICommandInvoker.Result installTestPlugin(String name) {
return new CLICommandInvoker(j, new InstallPluginCommand())
.withStdin(EnablePluginCommandTest.class.getResourceAsStream("/plugins/" + name + ".hpi"))
.invokeWithArgs("-name", name, "-deploy", "=");
}
private CLICommandInvoker.Result enablePlugins(String... names) {
return new CLICommandInvoker(j, new EnablePluginCommand()).invokeWithArgs(names);
}
@Test
public void enableSinglePlugin() throws IOException {
String name = "token-macro";
PluginManager m = j.getPluginManager();
assertThat(m.getPlugin(name), is(nullValue()));
assertThat(installTestPlugin("token-macro"), succeeded());
PluginWrapper wrapper = m.getPlugin(name);
assertThat(wrapper, is(notNullValue()));
assertTrue(wrapper.isEnabled());
wrapper.disable();
assertFalse(wrapper.isEnabled());
assertThat(enablePlugins(name), succeeded());
assertTrue(wrapper.isEnabled());
}
@Test
public void enableInvalidPluginFails() {
assertThat(
new CLICommandInvoker(j, "enable-plugin").invokeWithArgs("foobar"), failedWith(3)
);
}
@Test
public void enableDependerEnablesDependee() throws IOException {
installTestPlugin("dependee");
installTestPlugin("depender");
PluginManager m = j.getPluginManager();
PluginWrapper depender = m.getPlugin("depender");
assertThat(depender, is(notNullValue()));
PluginWrapper dependee = m.getPlugin("dependee");
assertThat(dependee, is(notNullValue()));
depender.disable();
dependee.disable();
assertThat(enablePlugins("depender"), succeeded());
assertTrue(depender.isEnabled());
assertTrue(dependee.isEnabled());
}
}
......@@ -123,6 +123,7 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.*;
import org.junit.Ignore;
......@@ -458,6 +459,31 @@ public class QueueTest {
assert task.exec instanceof OneOffExecutor : task.exec;
}
@Issue("JENKINS-41127")
@Test public void flyweightTasksUnwantedConcurrency() throws Exception {
Label label = r.jenkins.getSelfLabel();
AtomicInteger cnt = new AtomicInteger();
TestFlyweightTask task1 = new TestFlyweightTask(cnt, label);
TestFlyweightTask task2 = new TestFlyweightTask(cnt, label);
assertFalse(task1.isConcurrentBuild());
assertFalse(task2.isConcurrentBuild());
// We need to call Queue#maintain without any interleaving Queue modification to reproduce the issue.
Queue.withLock(() -> {
r.jenkins.getQueue().schedule2(task1, 0);
r.jenkins.getQueue().maintain();
Queue.Item item1 = r.jenkins.getQueue().getItem(task1);
assertThat(r.jenkins.getQueue().getPendingItems(), contains(item1));
r.jenkins.getQueue().schedule2(task2, 0);
r.jenkins.getQueue().maintain();
Queue.Item item2 = r.jenkins.getQueue().getItem(task2);
// Before the fix, item1 would no longer be present in the pending items (but would
// still be assigned to a live executor), and item2 would not be blocked, which would
// allow the tasks to execute concurrently.
assertThat(r.jenkins.getQueue().getPendingItems(), contains(item1));
assertTrue(item2.isBlocked());
});
}
@Issue("JENKINS-27256")
@Test public void inQueueTaskLookupByAPI() throws Exception {
FreeStyleProject p = r.createFreeStyleProject();
......
......@@ -38,6 +38,7 @@ import org.apache.commons.lang.StringUtils;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import static org.junit.Assert.assertEquals;
......@@ -51,6 +52,18 @@ public class LegacyApiTokenAdministrativeMonitorTest {
@Rule
public JenkinsRule j = new JenkinsRule();
private enum SelectFilter {
ALL(0),
ONLY_FRESH(1),
ONLY_RECENT(2);
int index;
SelectFilter(int index) {
this.index = index;
}
}
@Test
public void isActive() throws Exception {
ApiTokenPropertyConfiguration config = ApiTokenPropertyConfiguration.get();
......@@ -76,6 +89,40 @@ public class LegacyApiTokenAdministrativeMonitorTest {
assertTrue(monitor.isActivated());
}
@Test
@Issue("JENKINS-52441")
public void takeCareOfUserWithIdNull() throws Exception {
ApiTokenPropertyConfiguration config = ApiTokenPropertyConfiguration.get();
config.setCreationOfLegacyTokenEnabled(true);
config.setTokenGenerationOnCreationEnabled(false);
// user created without legacy token
User user = User.getById("null", true);
ApiTokenProperty apiTokenProperty = user.getProperty(ApiTokenProperty.class);
assertFalse(apiTokenProperty.hasLegacyToken());
LegacyApiTokenAdministrativeMonitor monitor = j.jenkins.getExtensionList(AdministrativeMonitor.class).get(LegacyApiTokenAdministrativeMonitor.class);
assertFalse(monitor.isActivated());
apiTokenProperty.changeApiToken();
assertTrue(monitor.isActivated());
{//revoke the legacy token
JenkinsRule.WebClient wc = j.createWebClient();
HtmlPage page = wc.goTo(monitor.getUrl() + "/manage");
{// select all (only one user normally)
HtmlAnchor filterAll = getFilterByIndex(page, SelectFilter.ALL);
HtmlElementUtil.click(filterAll);
}
// revoke them
HtmlButton revokeSelected = getRevokeSelected(page);
HtmlElementUtil.click(revokeSelected);
}
assertFalse(monitor.isActivated());
}
@Test
public void listOfUserWithLegacyTokenIsCorrect() throws Exception {
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
......@@ -254,7 +301,7 @@ public class LegacyApiTokenAdministrativeMonitorTest {
checkUserWithLegacyTokenListHasSizeOf(page, 1 + 2 + 3 + 4, 2 + 4, 3 + 4);
{// select 2
HtmlAnchor filterOnlyFresh = getFilterByIndex(page, 1);
HtmlAnchor filterOnlyFresh = getFilterByIndex(page, SelectFilter.ONLY_FRESH);
HtmlElementUtil.click(filterOnlyFresh);
}
// revoke them
......@@ -265,7 +312,7 @@ public class LegacyApiTokenAdministrativeMonitorTest {
assertTrue(monitor.isActivated());
{// select 1 + 3
HtmlAnchor filterAll = getFilterByIndex(newPage, 0);
HtmlAnchor filterAll = getFilterByIndex(newPage, SelectFilter.ALL);
HtmlElementUtil.click(filterAll);
}
// revoke them
......@@ -275,13 +322,13 @@ public class LegacyApiTokenAdministrativeMonitorTest {
assertFalse(monitor.isActivated());
}
private HtmlAnchor getFilterByIndex(HtmlPage page, int index) {
private HtmlAnchor getFilterByIndex(HtmlPage page, SelectFilter selectFilter) {
HtmlElement document = page.getDocumentElement();
HtmlDivision filterDiv = document.getOneHtmlElementByAttribute("div", "class", "selection-panel");
DomNodeList<HtmlElement> filters = filterDiv.getElementsByTagName("a");
assertEquals(3, filters.size());
HtmlAnchor filter = (HtmlAnchor) filters.get(index);
HtmlAnchor filter = (HtmlAnchor) filters.get(selectFilter.index);
assertNotNull(filter);
return filter;
}
......@@ -341,7 +388,7 @@ public class LegacyApiTokenAdministrativeMonitorTest {
private void createUserWithToken(boolean legacy, boolean fresh, boolean recent) throws Exception {
User user = User.getById(String.format("user %b %b %b %d", legacy, fresh, recent, nextId++), true);
if (!legacy) {
return ;
return;
}
ApiTokenProperty apiTokenProperty = user.getProperty(ApiTokenProperty.class);
......
......@@ -1067,6 +1067,7 @@ table.parameters > tbody:hover {
}
.build-row.multi-line .build-row-cell .pane.build-name.block {
padding-right: 20px;
width: 100%;
}
.build-row-cell .pane.build-controls.block {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册