4
* Copyright (c) 2004-2011, Sun Microsystems, Inc., Kohsuke Kawaguchi,
5
* Erik Ramfelt, Koichi Fujikawa, Red Hat, Inc., Seiji Sogabe,
6
* Stephen Connolly, Tom Huybrechts, Yahoo! Inc., Alan Harder, CloudBees, Inc.,
9
* Permission is hereby granted, free of charge, to any person obtaining a copy
10
* of this software and associated documentation files (the "Software"), to deal
11
* in the Software without restriction, including without limitation the rights
12
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
* copies of the Software, and to permit persons to whom the Software is
14
* furnished to do so, subject to the following conditions:
16
* The above copyright notice and this permission notice shall be included in
17
* all copies or substantial portions of the Software.
19
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
package jenkins.model;
29
import com.google.common.collect.Lists;
30
import com.google.inject.Injector;
31
import hudson.ExtensionComponent;
32
import hudson.ExtensionFinder;
33
import hudson.model.LoadStatistics;
34
import hudson.model.Messages;
35
import hudson.model.Node;
36
import hudson.model.AbstractCIBase;
37
import hudson.model.AbstractProject;
38
import hudson.model.Action;
39
import hudson.model.AdministrativeMonitor;
40
import hudson.model.AllView;
41
import hudson.model.Api;
42
import hudson.model.Computer;
43
import hudson.model.ComputerSet;
44
import hudson.model.DependencyGraph;
45
import hudson.model.Describable;
46
import hudson.model.Descriptor;
47
import hudson.model.DescriptorByNameOwner;
48
import hudson.model.DirectoryBrowserSupport;
49
import hudson.model.Failure;
50
import hudson.model.Fingerprint;
51
import hudson.model.FingerprintCleanupThread;
52
import hudson.model.FingerprintMap;
53
import hudson.model.FullDuplexHttpChannel;
54
import hudson.model.Hudson;
55
import hudson.model.Item;
56
import hudson.model.ItemGroup;
57
import hudson.model.ItemGroupMixIn;
58
import hudson.model.Items;
59
import hudson.model.JDK;
60
import hudson.model.Job;
61
import hudson.model.JobPropertyDescriptor;
62
import hudson.model.Label;
63
import hudson.model.ListView;
64
import hudson.model.LoadBalancer;
65
import hudson.model.ManagementLink;
66
import hudson.model.NoFingerprintMatch;
67
import hudson.model.OverallLoadStatistics;
68
import hudson.model.Project;
69
import hudson.model.RestartListener;
70
import hudson.model.RootAction;
71
import hudson.model.Slave;
72
import hudson.model.TaskListener;
73
import hudson.model.TopLevelItem;
74
import hudson.model.TopLevelItemDescriptor;
75
import hudson.model.UnprotectedRootAction;
76
import hudson.model.UpdateCenter;
77
import hudson.model.User;
78
import hudson.model.View;
79
import hudson.model.ViewGroup;
80
import hudson.model.ViewGroupMixIn;
81
import hudson.model.Descriptor.FormException;
82
import hudson.model.labels.LabelAtom;
83
import hudson.model.listeners.ItemListener;
84
import hudson.model.listeners.SCMListener;
85
import hudson.model.listeners.SaveableListener;
86
import hudson.model.Queue;
87
import hudson.model.WorkspaceCleanupThread;
89
import antlr.ANTLRException;
90
import com.google.common.collect.ImmutableMap;
91
import com.thoughtworks.xstream.XStream;
92
import hudson.BulkChange;
93
import hudson.DNSMultiCast;
94
import hudson.DescriptorExtensionList;
95
import hudson.Extension;
96
import hudson.ExtensionList;
97
import hudson.ExtensionPoint;
98
import hudson.FilePath;
99
import hudson.Functions;
100
import hudson.Launcher;
101
import hudson.Launcher.LocalLauncher;
102
import hudson.LocalPluginManager;
103
import hudson.Lookup;
104
import hudson.markup.MarkupFormatter;
105
import hudson.Plugin;
106
import hudson.PluginManager;
107
import hudson.PluginWrapper;
108
import hudson.ProxyConfiguration;
109
import hudson.TcpSlaveAgentListener;
110
import hudson.UDPBroadcastThread;
112
import static hudson.Util.fixEmpty;
113
import static hudson.Util.fixNull;
114
import hudson.WebAppMain;
115
import hudson.XmlFile;
116
import hudson.cli.CLICommand;
117
import hudson.cli.CliEntryPoint;
118
import hudson.cli.CliManagerImpl;
119
import hudson.cli.declarative.CLIMethod;
120
import hudson.cli.declarative.CLIResolver;
121
import hudson.init.InitMilestone;
122
import hudson.init.InitStrategy;
123
import hudson.lifecycle.Lifecycle;
124
import hudson.logging.LogRecorderManager;
125
import hudson.lifecycle.RestartNotSupportedException;
126
import hudson.markup.RawHtmlMarkupFormatter;
127
import hudson.remoting.Channel;
128
import hudson.remoting.LocalChannel;
129
import hudson.remoting.VirtualChannel;
130
import hudson.scm.RepositoryBrowser;
131
import hudson.scm.SCM;
132
import hudson.search.CollectionSearchIndex;
133
import hudson.search.SearchIndexBuilder;
134
import hudson.search.SearchItem;
135
import hudson.security.ACL;
136
import hudson.security.AccessControlled;
137
import hudson.security.AuthorizationStrategy;
138
import hudson.security.FederatedLoginService;
139
import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
140
import hudson.security.HudsonFilter;
141
import hudson.security.LegacyAuthorizationStrategy;
142
import hudson.security.LegacySecurityRealm;
143
import hudson.security.Permission;
144
import hudson.security.PermissionGroup;
145
import hudson.security.PermissionScope;
146
import hudson.security.SecurityMode;
147
import hudson.security.SecurityRealm;
148
import hudson.security.csrf.CrumbIssuer;
149
import hudson.slaves.Cloud;
150
import hudson.slaves.ComputerListener;
151
import hudson.slaves.DumbSlave;
152
import hudson.slaves.EphemeralNode;
153
import hudson.slaves.NodeDescriptor;
154
import hudson.slaves.NodeList;
155
import hudson.slaves.NodeProperty;
156
import hudson.slaves.NodePropertyDescriptor;
157
import hudson.slaves.NodeProvisioner;
158
import hudson.slaves.OfflineCause;
159
import hudson.slaves.RetentionStrategy;
160
import hudson.tasks.BuildWrapper;
161
import hudson.tasks.Builder;
162
import hudson.tasks.Publisher;
163
import hudson.triggers.SafeTimerTask;
164
import hudson.triggers.Trigger;
165
import hudson.triggers.TriggerDescriptor;
166
import hudson.util.AdministrativeError;
167
import hudson.util.CaseInsensitiveComparator;
168
import hudson.util.ClockDifference;
169
import hudson.util.CopyOnWriteList;
170
import hudson.util.CopyOnWriteMap;
171
import hudson.util.DaemonThreadFactory;
172
import hudson.util.DescribableList;
173
import hudson.util.FormApply;
174
import hudson.util.FormValidation;
175
import hudson.util.Futures;
176
import hudson.util.HudsonIsLoading;
177
import hudson.util.HudsonIsRestarting;
178
import hudson.util.Iterators;
179
import hudson.util.JenkinsReloadFailed;
180
import hudson.util.Memoizer;
181
import hudson.util.MultipartFormDataParser;
182
import hudson.util.RemotingDiagnostics;
183
import hudson.util.RemotingDiagnostics.HeapDump;
184
import hudson.util.StreamTaskListener;
185
import hudson.util.TextFile;
186
import hudson.util.TimeUnit2;
187
import hudson.util.VersionNumber;
188
import hudson.util.XStream2;
189
import hudson.views.DefaultMyViewsTabBar;
190
import hudson.views.DefaultViewsTabBar;
191
import hudson.views.MyViewsTabBar;
192
import hudson.views.ViewsTabBar;
193
import hudson.widgets.Widget;
194
import jenkins.ExtensionComponentSet;
195
import jenkins.ExtensionRefreshException;
196
import jenkins.InitReactorRunner;
197
import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy;
198
import jenkins.security.ConfidentialKey;
199
import jenkins.security.ConfidentialStore;
200
import jenkins.slaves.WorkspaceLocator;
201
import jenkins.util.io.FileBoolean;
202
import net.sf.json.JSONObject;
203
import org.acegisecurity.AccessDeniedException;
204
import org.acegisecurity.AcegiSecurityException;
205
import org.acegisecurity.Authentication;
206
import org.acegisecurity.GrantedAuthority;
207
import org.acegisecurity.GrantedAuthorityImpl;
208
import org.acegisecurity.context.SecurityContextHolder;
209
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
210
import org.acegisecurity.ui.AbstractProcessingFilter;
211
import org.apache.commons.jelly.JellyException;
212
import org.apache.commons.jelly.Script;
213
import org.apache.commons.logging.LogFactory;
214
import org.jvnet.hudson.reactor.Executable;
215
import org.jvnet.hudson.reactor.ReactorException;
216
import org.jvnet.hudson.reactor.Task;
217
import org.jvnet.hudson.reactor.TaskBuilder;
218
import org.jvnet.hudson.reactor.TaskGraphBuilder;
219
import org.jvnet.hudson.reactor.Reactor;
220
import org.jvnet.hudson.reactor.TaskGraphBuilder.Handle;
221
import org.kohsuke.accmod.Restricted;
222
import org.kohsuke.accmod.restrictions.NoExternalUse;
223
import org.kohsuke.args4j.Argument;
224
import org.kohsuke.args4j.Option;
225
import org.kohsuke.stapler.Ancestor;
226
import org.kohsuke.stapler.HttpRedirect;
227
import org.kohsuke.stapler.HttpResponse;
228
import org.kohsuke.stapler.HttpResponses;
229
import org.kohsuke.stapler.MetaClass;
230
import org.kohsuke.stapler.QueryParameter;
231
import org.kohsuke.stapler.Stapler;
232
import org.kohsuke.stapler.StaplerFallback;
233
import org.kohsuke.stapler.StaplerProxy;
234
import org.kohsuke.stapler.StaplerRequest;
235
import org.kohsuke.stapler.StaplerResponse;
236
import org.kohsuke.stapler.WebApp;
237
import org.kohsuke.stapler.export.Exported;
238
import org.kohsuke.stapler.export.ExportedBean;
239
import org.kohsuke.stapler.framework.adjunct.AdjunctManager;
240
import org.kohsuke.stapler.interceptor.RequirePOST;
241
import org.kohsuke.stapler.jelly.JellyClassLoaderTearOff;
242
import org.kohsuke.stapler.jelly.JellyRequestDispatcher;
243
import org.xml.sax.InputSource;
245
import javax.crypto.SecretKey;
246
import javax.servlet.RequestDispatcher;
247
import javax.servlet.ServletContext;
248
import javax.servlet.ServletException;
249
import javax.servlet.http.Cookie;
250
import javax.servlet.http.HttpServletResponse;
252
import static hudson.init.InitMilestone.*;
253
import hudson.security.BasicAuthenticationFilter;
254
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
255
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
257
import java.io.FileFilter;
258
import java.io.IOException;
259
import java.io.InputStream;
260
import java.io.PrintWriter;
261
import java.io.StringWriter;
262
import java.net.BindException;
263
import java.net.HttpURLConnection;
265
import java.nio.charset.Charset;
266
import java.security.SecureRandom;
267
import java.text.Collator;
268
import java.util.ArrayList;
269
import java.util.Arrays;
270
import java.util.Collection;
271
import java.util.Collections;
272
import java.util.Comparator;
273
import java.util.HashMap;
274
import java.util.HashSet;
275
import java.util.Iterator;
276
import java.util.List;
277
import java.util.Map;
278
import java.util.Map.Entry;
279
import java.util.Properties;
280
import java.util.Set;
281
import java.util.Stack;
282
import java.util.StringTokenizer;
283
import java.util.Timer;
284
import java.util.TreeSet;
285
import java.util.UUID;
286
import java.util.concurrent.ConcurrentHashMap;
287
import java.util.concurrent.CopyOnWriteArrayList;
288
import java.util.concurrent.ExecutionException;
289
import java.util.concurrent.ExecutorService;
290
import java.util.concurrent.Future;
291
import java.util.concurrent.LinkedBlockingQueue;
292
import java.util.concurrent.ThreadPoolExecutor;
293
import java.util.concurrent.TimeUnit;
294
import java.util.concurrent.TimeoutException;
295
import java.util.logging.Level;
296
import static java.util.logging.Level.SEVERE;
297
import java.util.logging.LogRecord;
298
import java.util.logging.Logger;
299
import java.util.regex.Pattern;
300
import javax.annotation.CheckForNull;
301
import javax.annotation.Nonnull;
302
import javax.annotation.Nullable;
305
* Root object of the system.
307
* @author Kohsuke Kawaguchi
310
public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGroup, StaplerProxy, StaplerFallback, ViewGroup, AccessControlled, DescriptorByNameOwner, ModelObjectWithContextMenu {
311
private transient final Queue queue;
314
* Stores various objects scoped to {@link Jenkins}.
316
public transient final Lookup lookup = new Lookup();
319
* We update this field to the current version of Hudson whenever we save {@code config.xml}.
320
* This can be used to detect when an upgrade happens from one version to next.
323
* Since this field is introduced starting 1.301, "1.0" is used to represent every version
324
* up to 1.300. This value may also include non-standard versions like "1.301-SNAPSHOT" or
325
* "?", etc., so parsing needs to be done with a care.
329
// this field needs to be at the very top so that other components can look at this value even during unmarshalling
330
private String version = "1.0";
333
* Number of executors of the master node.
335
private int numExecutors = 2;
338
* Job allocation strategy.
340
private Mode mode = Mode.NORMAL;
343
* False to enable anyone to do anything.
344
* Left as a field so that we can still read old data that uses this flag.
346
* @see #authorizationStrategy
347
* @see #securityRealm
349
private Boolean useSecurity;
353
* <a href="http://en.wikipedia.org/wiki/Authorization">authorization</a>
354
* is handled in Hudson.
356
* This ultimately controls who has access to what.
360
private volatile AuthorizationStrategy authorizationStrategy = AuthorizationStrategy.UNSECURED;
363
* Controls a part of the
364
* <a href="http://en.wikipedia.org/wiki/Authentication">authentication</a>
365
* handling in Hudson.
367
* Intuitively, this corresponds to the user database.
369
* See {@link HudsonFilter} for the concrete authentication protocol.
371
* Never null. Always use {@link #setSecurityRealm(SecurityRealm)} to
374
* @see #getSecurity()
375
* @see #setSecurityRealm(SecurityRealm)
377
private volatile SecurityRealm securityRealm = SecurityRealm.NO_AUTHENTICATION;
380
* The project naming strategy defines/restricts the names which can be given to a project/job. e.g. does the name have to follow a naming convention?
382
private ProjectNamingStrategy projectNamingStrategy = DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
385
* Root directory for the workspaces.
386
* This value will be variable-expanded as per {@link #expandVariablesForDirectory}.
387
* @see #getWorkspaceFor(TopLevelItem)
389
private String workspaceDir = "${ITEM_ROOTDIR}/"+WORKSPACE_DIRNAME;
392
* Root directory for the builds.
393
* This value will be variable-expanded as per {@link #expandVariablesForDirectory}.
394
* @see #getBuildDirFor(Job)
396
private String buildsDir = "${ITEM_ROOTDIR}/builds";
399
* Message displayed in the top page.
401
private String systemMessage;
403
private MarkupFormatter markupFormatter;
406
* Root directory of the system.
408
public transient final File root;
411
* Where are we in the initialization?
413
private transient volatile InitMilestone initLevel = InitMilestone.STARTED;
416
* All {@link Item}s keyed by their {@link Item#getName() name}s.
418
/*package*/ transient final Map<String,TopLevelItem> items = new CopyOnWriteMap.Tree<String,TopLevelItem>(CaseInsensitiveComparator.INSTANCE);
423
private static Jenkins theInstance;
425
private transient volatile boolean isQuietingDown;
426
private transient volatile boolean terminating;
428
private List<JDK> jdks = new ArrayList<JDK>();
430
private transient volatile DependencyGraph dependencyGraph;
433
* Currently active Views tab bar.
435
private volatile ViewsTabBar viewsTabBar = new DefaultViewsTabBar();
438
* Currently active My Views tab bar.
440
private volatile MyViewsTabBar myViewsTabBar = new DefaultMyViewsTabBar();
443
* All {@link ExtensionList} keyed by their {@link ExtensionList#extensionType}.
445
private transient final Memoizer<Class,ExtensionList> extensionLists = new Memoizer<Class,ExtensionList>() {
446
public ExtensionList compute(Class key) {
447
return ExtensionList.create(Jenkins.this,key);
452
* All {@link DescriptorExtensionList} keyed by their {@link DescriptorExtensionList#describableType}.
454
private transient final Memoizer<Class,DescriptorExtensionList> descriptorLists = new Memoizer<Class,DescriptorExtensionList>() {
455
public DescriptorExtensionList compute(Class key) {
456
return DescriptorExtensionList.createDescriptorList(Jenkins.this,key);
461
* {@link Computer}s in this Hudson system. Read-only.
463
protected transient final Map<Node,Computer> computers = new CopyOnWriteMap.Hash<Node,Computer>();
466
* Active {@link Cloud}s.
468
public final Hudson.CloudList clouds = new Hudson.CloudList(this);
470
public static class CloudList extends DescribableList<Cloud,Descriptor<Cloud>> {
471
public CloudList(Jenkins h) {
475
public CloudList() {// needed for XStream deserialization
478
public Cloud getByName(String name) {
480
if (c.name.equals(name))
486
protected void onModified() throws IOException {
488
Jenkins.getInstance().trimLabels();
493
* Set of installed cluster nodes.
495
* We use this field with copy-on-write semantics.
496
* This field has mutable list (to keep the serialization look clean),
497
* but it shall never be modified. Only new completely populated slave
498
* list can be set here.
500
* The field name should be really {@code nodes}, but again the backward compatibility
501
* prevents us from renaming.
503
protected volatile NodeList slaves;
508
* This is {@link Integer} so that we can initialize it to '5' for upgrading users.
510
/*package*/ Integer quietPeriod;
513
* Global default for {@link AbstractProject#getScmCheckoutRetryCount()}
515
/*package*/ int scmCheckoutRetryCount;
520
private final CopyOnWriteArrayList<View> views = new CopyOnWriteArrayList<View>();
523
* Name of the primary view.
525
* Start with null, so that we can upgrade pre-1.269 data well.
528
private volatile String primaryView;
530
private transient final ViewGroupMixIn viewGroupMixIn = new ViewGroupMixIn(this) {
531
protected List<View> views() { return views; }
532
protected String primaryView() { return primaryView; }
533
protected void primaryView(String name) { primaryView=name; }
537
private transient final FingerprintMap fingerprintMap = new FingerprintMap();
542
public transient final PluginManager pluginManager;
544
public transient volatile TcpSlaveAgentListener tcpSlaveAgentListener;
546
private transient UDPBroadcastThread udpBroadcastThread;
548
private transient DNSMultiCast dnsMultiCast;
551
* List of registered {@link SCMListener}s.
553
private transient final CopyOnWriteList<SCMListener> scmListeners = new CopyOnWriteList<SCMListener>();
556
* TCP slave agent port.
557
* 0 for random, -1 to disable.
559
private int slaveAgentPort =0;
562
* Whitespace-separated labels assigned to the master as a {@link Node}.
564
private String label="";
567
* {@link hudson.security.csrf.CrumbIssuer}
569
private volatile CrumbIssuer crumbIssuer;
572
* All labels known to Jenkins. This allows us to reuse the same label instances
573
* as much as possible, even though that's not a strict requirement.
575
private transient final ConcurrentHashMap<String,Label> labels = new ConcurrentHashMap<String,Label>();
578
* Load statistics of the entire system.
580
* This includes every executor and every job in the system.
583
public transient final OverallLoadStatistics overallLoad = new OverallLoadStatistics();
586
* Load statistics of the free roaming jobs and slaves.
588
* This includes all executors on {@link Mode#NORMAL} nodes and jobs that do not have any assigned nodes.
593
public transient final LoadStatistics unlabeledLoad = new UnlabeledLoadStatistics();
596
* {@link NodeProvisioner} that reacts to {@link #unlabeledLoad}.
599
public transient final NodeProvisioner unlabeledNodeProvisioner = new NodeProvisioner(null,unlabeledLoad);
602
* @deprecated as of 1.467
603
* Use {@link #unlabeledNodeProvisioner}.
604
* This was broken because it was tracking all the executors in the system, but it was only tracking
605
* free-roaming jobs in the queue. So {@link Cloud} fails to launch nodes when you have some exclusive
606
* slaves and free-roaming jobs in the queue.
608
@Restricted(NoExternalUse.class)
609
public transient final NodeProvisioner overallNodeProvisioner = unlabeledNodeProvisioner;
612
public transient final ServletContext servletContext;
615
* Transient action list. Useful for adding navigation items to the navigation bar
618
private transient final List<Action> actions = new CopyOnWriteArrayList<Action>();
621
* List of master node properties
623
private DescribableList<NodeProperty<?>,NodePropertyDescriptor> nodeProperties = new DescribableList<NodeProperty<?>,NodePropertyDescriptor>(this);
626
* List of global properties
628
private DescribableList<NodeProperty<?>,NodePropertyDescriptor> globalNodeProperties = new DescribableList<NodeProperty<?>,NodePropertyDescriptor>(this);
631
* {@link AdministrativeMonitor}s installed on this system.
633
* @see AdministrativeMonitor
635
public transient final List<AdministrativeMonitor> administrativeMonitors = getExtensionList(AdministrativeMonitor.class);
640
private transient final List<Widget> widgets = getExtensionList(Widget.class);
643
* {@link AdjunctManager}
645
private transient final AdjunctManager adjuncts;
648
* Code that handles {@link ItemGroup} work.
650
private transient final ItemGroupMixIn itemGroupMixIn = new ItemGroupMixIn(this,this) {
652
protected void add(TopLevelItem item) {
653
items.put(item.getName(),item);
657
protected File getRootDirFor(String name) {
658
return Jenkins.this.getRootDirFor(name);
662
* Send the browser to the config page.
663
* use View to trim view/{default-view} from URL if possible
666
protected String redirectAfterCreateItem(StaplerRequest req, TopLevelItem result) throws IOException {
667
String redirect = result.getUrl()+"configure";
668
List<Ancestor> ancestors = req.getAncestors();
669
for (int i = ancestors.size() - 1; i >= 0; i--) {
670
Object o = ancestors.get(i).getObject();
671
if (o instanceof View) {
672
redirect = req.getContextPath() + '/' + ((View)o).getUrl() + redirect;
682
* Hook for a test harness to intercept Jenkins.getInstance()
684
* Do not use in the production code as the signature may change.
686
public interface JenkinsHolder {
687
Jenkins getInstance();
690
static JenkinsHolder HOLDER = new JenkinsHolder() {
691
public Jenkins getInstance() {
697
public static Jenkins getInstance() {
698
return HOLDER.getInstance();
702
* Secret key generated once and used for a long time, beyond
703
* container start/stop. Persisted outside <tt>config.xml</tt> to avoid
704
* accidental exposure.
706
private transient final String secretKey;
708
private transient final UpdateCenter updateCenter = new UpdateCenter();
711
* True if the user opted out from the statistics tracking. We'll never send anything if this is true.
713
private Boolean noUsageStatistics;
716
* HTTP proxy configuration.
718
public transient volatile ProxyConfiguration proxy;
723
private transient final LogRecorderManager log = new LogRecorderManager();
725
protected Jenkins(File root, ServletContext context) throws IOException, InterruptedException, ReactorException {
726
this(root,context,null);
730
* @param pluginManager
731
* If non-null, use existing plugin manager. create a new one.
733
@edu.umd.cs.findbugs.annotations.SuppressWarnings({
734
"SC_START_IN_CTOR", // bug in FindBugs. It flags UDPBroadcastThread.start() call but that's for another class
735
"ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD" // Trigger.timer
737
protected Jenkins(File root, ServletContext context, PluginManager pluginManager) throws IOException, InterruptedException, ReactorException {
738
long start = System.currentTimeMillis();
740
// As Jenkins is starting, grant this process full control
741
ACL.impersonate(ACL.SYSTEM);
744
this.servletContext = context;
745
computeVersion(context);
746
if(theInstance!=null)
747
throw new IllegalStateException("second instance");
750
if (!new File(root,"jobs").exists()) {
751
// if this is a fresh install, use more modern default layout that's consistent with slaves
752
workspaceDir = "${JENKINS_HOME}/workspace/${ITEM_FULLNAME}";
755
// doing this early allows InitStrategy to set environment upfront
756
final InitStrategy is = InitStrategy.get(Thread.currentThread().getContextClassLoader());
758
Trigger.timer = new Timer("Jenkins cron thread");
759
queue = new Queue(LoadBalancer.CONSISTENT_HASH);
762
dependencyGraph = DependencyGraph.EMPTY;
763
} catch (InternalError e) {
764
if(e.getMessage().contains("window server")) {
765
throw new Error("Looks like the server runs without X. Please specify -Djava.awt.headless=true as JVM option",e);
770
// get or create the secret
771
TextFile secretFile = new TextFile(new File(getRootDir(),"secret.key"));
772
if(secretFile.exists()) {
773
secretKey = secretFile.readTrim();
775
SecureRandom sr = new SecureRandom();
776
byte[] random = new byte[32];
777
sr.nextBytes(random);
778
secretKey = Util.toHexString(random);
779
secretFile.write(secretKey);
781
// this marker indicates that the secret.key is generated by the version of Jenkins post SECURITY-49.
782
// this indicates that there's no need to rewrite secrets on disk
783
new FileBoolean(new File(root,"secret.key.not-so-secret")).on();
787
proxy = ProxyConfiguration.load();
788
} catch (IOException e) {
789
LOGGER.log(SEVERE, "Failed to load proxy configuration", e);
792
if (pluginManager==null)
793
pluginManager = new LocalPluginManager(this);
794
this.pluginManager = pluginManager;
795
// JSON binding needs to be able to see all the classes from all the plugins
796
WebApp.get(servletContext).setClassLoader(pluginManager.uberClassLoader);
798
adjuncts = new AdjunctManager(servletContext, pluginManager.uberClassLoader,"adjuncts/"+SESSION_HASH, TimeUnit2.DAYS.toMillis(365));
800
// initialization consists of ...
802
pluginManager.initTasks(is), // loading and preparing plugins
803
loadTasks(), // load jobs
804
InitMilestone.ordering() // forced ordering among key milestones
810
if(slaveAgentPort!=-1) {
812
tcpSlaveAgentListener = new TcpSlaveAgentListener(slaveAgentPort);
813
} catch (BindException e) {
814
new AdministrativeError(getClass().getName()+".tcpBind",
815
"Failed to listen to incoming slave connection",
816
"Failed to listen to incoming slave connection. <a href='configure'>Change the port number</a> to solve the problem.",e);
819
tcpSlaveAgentListener = null;
822
udpBroadcastThread = new UDPBroadcastThread(this);
823
udpBroadcastThread.start();
824
} catch (IOException e) {
825
LOGGER.log(Level.WARNING, "Failed to broadcast over UDP",e);
827
dnsMultiCast = new DNSMultiCast(this);
829
Timer timer = Trigger.timer;
831
timer.scheduleAtFixedRate(new SafeTimerTask() {
833
protected void doRun() throws Exception {
836
}, TimeUnit2.MINUTES.toMillis(5), TimeUnit2.MINUTES.toMillis(5));
839
updateComputerList();
841
{// master is online now
842
Computer c = toComputer();
844
for (ComputerListener cl : ComputerListener.all())
845
cl.onOnline(c,StreamTaskListener.fromStdout());
848
for (ItemListener l : ItemListener.all()) {
849
long itemListenerStart = System.currentTimeMillis();
851
if (LOG_STARTUP_PERFORMANCE)
852
LOGGER.info(String.format("Took %dms for item listener %s startup",
853
System.currentTimeMillis()-itemListenerStart,l.getClass().getName()));
856
if (LOG_STARTUP_PERFORMANCE)
857
LOGGER.info(String.format("Took %dms for complete Jenkins startup",
858
System.currentTimeMillis()-start));
860
SecurityContextHolder.clearContext();
865
* Executes a reactor.
868
* If non-null, this can be consulted for ignoring some tasks. Only used during the initialization of Hudson.
870
private void executeReactor(final InitStrategy is, TaskBuilder... builders) throws IOException, InterruptedException, ReactorException {
871
Reactor reactor = new Reactor(builders) {
873
* Sets the thread name to the task for better diagnostics.
876
protected void runTask(Task task) throws Exception {
877
if (is!=null && is.skipInitTask(task)) return;
879
ACL.impersonate(ACL.SYSTEM); // full access in the initialization thread
880
String taskName = task.getDisplayName();
882
Thread t = Thread.currentThread();
883
String name = t.getName();
887
long start = System.currentTimeMillis();
889
if(LOG_STARTUP_PERFORMANCE)
890
LOGGER.info(String.format("Took %dms for %s by %s",
891
System.currentTimeMillis()-start, taskName, name));
894
SecurityContextHolder.clearContext();
899
new InitReactorRunner() {
901
protected void onInitMilestoneAttained(InitMilestone milestone) {
902
initLevel = milestone;
908
public TcpSlaveAgentListener getTcpSlaveAgentListener() {
909
return tcpSlaveAgentListener;
913
* Makes {@link AdjunctManager} URL-bound.
914
* The dummy parameter allows us to use different URLs for the same adjunct,
915
* for proper cache handling.
917
public AdjunctManager getAdjuncts(String dummy) {
922
public int getSlaveAgentPort() {
923
return slaveAgentPort;
928
* 0 to indicate random available TCP port. -1 to disable this service.
930
public void setSlaveAgentPort(int port) throws IOException {
931
this.slaveAgentPort = port;
933
// relaunch the agent
934
if(tcpSlaveAgentListener==null) {
935
if(slaveAgentPort!=-1)
936
tcpSlaveAgentListener = new TcpSlaveAgentListener(slaveAgentPort);
938
if(tcpSlaveAgentListener.configuredPort!=slaveAgentPort) {
939
tcpSlaveAgentListener.shutdown();
940
tcpSlaveAgentListener = null;
941
if(slaveAgentPort!=-1)
942
tcpSlaveAgentListener = new TcpSlaveAgentListener(slaveAgentPort);
947
public void setNodeName(String name) {
948
throw new UnsupportedOperationException(); // not allowed
951
public String getNodeDescription() {
952
return Messages.Hudson_NodeDescription();
956
public String getDescription() {
957
return systemMessage;
960
public PluginManager getPluginManager() {
961
return pluginManager;
964
public UpdateCenter getUpdateCenter() {
968
public boolean isUsageStatisticsCollected() {
969
return noUsageStatistics==null || !noUsageStatistics;
972
public void setNoUsageStatistics(Boolean noUsageStatistics) throws IOException {
973
this.noUsageStatistics = noUsageStatistics;
977
public View.People getPeople() {
978
return new View.People(this);
984
public View.AsynchPeople getAsynchPeople() {
985
return new View.AsynchPeople(this);
989
* Does this {@link View} has any associated user information recorded?
990
* @deprecated Potentially very expensive call; do not use from Jelly views.
992
public boolean hasPeople() {
993
return View.People.isApplicable(items.values());
996
public Api getApi() {
997
return new Api(this);
1001
* Returns a secret key that survives across container start/stop.
1003
* This value is useful for implementing some of the security features.
1006
* Due to the past security advisory, this value should not be used any more to protect sensitive information.
1007
* See {@link ConfidentialStore} and {@link ConfidentialKey} for how to store secrets.
1009
public String getSecretKey() {
1014
* Gets {@linkplain #getSecretKey() the secret key} as a key for AES-128.
1017
* See {@link #getSecretKey()}.
1019
public SecretKey getSecretKeyAsAES128() {
1020
return Util.toAes128Key(secretKey);
1024
* Returns the unique identifier of this Jenkins that has been historically used to identify
1025
* this Jenkins to the outside world.
1028
* This form of identifier is weak in that it can be impersonated by others. See
1029
* https://wiki.jenkins-ci.org/display/JENKINS/Instance+Identity for more modern form of instance ID
1030
* that can be challenged and verified.
1034
@SuppressWarnings("deprecation")
1035
public String getLegacyInstanceId() {
1036
return Util.getDigestOf(getSecretKey());
1040
* Gets the SCM descriptor by name. Primarily used for making them web-visible.
1042
public Descriptor<SCM> getScm(String shortClassName) {
1043
return findDescriptor(shortClassName,SCM.all());
1047
* Gets the repository browser descriptor by name. Primarily used for making them web-visible.
1049
public Descriptor<RepositoryBrowser<?>> getRepositoryBrowser(String shortClassName) {
1050
return findDescriptor(shortClassName,RepositoryBrowser.all());
1054
* Gets the builder descriptor by name. Primarily used for making them web-visible.
1056
public Descriptor<Builder> getBuilder(String shortClassName) {
1057
return findDescriptor(shortClassName, Builder.all());
1061
* Gets the build wrapper descriptor by name. Primarily used for making them web-visible.
1063
public Descriptor<BuildWrapper> getBuildWrapper(String shortClassName) {
1064
return findDescriptor(shortClassName, BuildWrapper.all());
1068
* Gets the publisher descriptor by name. Primarily used for making them web-visible.
1070
public Descriptor<Publisher> getPublisher(String shortClassName) {
1071
return findDescriptor(shortClassName, Publisher.all());
1075
* Gets the trigger descriptor by name. Primarily used for making them web-visible.
1077
public TriggerDescriptor getTrigger(String shortClassName) {
1078
return (TriggerDescriptor) findDescriptor(shortClassName, Trigger.all());
1082
* Gets the retention strategy descriptor by name. Primarily used for making them web-visible.
1084
public Descriptor<RetentionStrategy<?>> getRetentionStrategy(String shortClassName) {
1085
return findDescriptor(shortClassName, RetentionStrategy.all());
1089
* Gets the {@link JobPropertyDescriptor} by name. Primarily used for making them web-visible.
1091
public JobPropertyDescriptor getJobProperty(String shortClassName) {
1092
// combining these two lines triggers javac bug. See issue #610.
1093
Descriptor d = findDescriptor(shortClassName, JobPropertyDescriptor.all());
1094
return (JobPropertyDescriptor) d;
1099
* UI method. Not meant to be used programatically.
1101
public ComputerSet getComputer() {
1102
return new ComputerSet();
1106
* Exposes {@link Descriptor} by its name to URL.
1108
* After doing all the {@code getXXX(shortClassName)} methods, I finally realized that
1109
* this just doesn't scale.
1112
* Either {@link Descriptor#getId()} (recommended) or the short name of a {@link Describable} subtype (for compatibility)
1113
* @throws IllegalArgumentException if a short name was passed which matches multiple IDs (fail fast)
1115
@SuppressWarnings({"unchecked", "rawtypes"}) // too late to fix
1116
public Descriptor getDescriptor(String id) {
1117
// legacy descriptors that are reigstered manually doesn't show up in getExtensionList, so check them explicitly.
1118
Iterable<Descriptor> descriptors = Iterators.sequence(getExtensionList(Descriptor.class), DescriptorExtensionList.listLegacyInstances());
1119
for (Descriptor d : descriptors) {
1120
if (d.getId().equals(id)) {
1124
Descriptor candidate = null;
1125
for (Descriptor d : descriptors) {
1126
String name = d.getId();
1127
if (name.substring(name.lastIndexOf('.') + 1).equals(id)) {
1128
if (candidate == null) {
1131
throw new IllegalArgumentException(id + " is ambiguous; matches both " + name + " and " + candidate.getId());
1139
* Alias for {@link #getDescriptor(String)}.
1141
public Descriptor getDescriptorByName(String id) {
1142
return getDescriptor(id);
1146
* Gets the {@link Descriptor} that corresponds to the given {@link Describable} type.
1148
* If you have an instance of {@code type} and call {@link Describable#getDescriptor()},
1149
* you'll get the same instance that this method returns.
1151
public Descriptor getDescriptor(Class<? extends Describable> type) {
1152
for( Descriptor d : getExtensionList(Descriptor.class) )
1159
* Works just like {@link #getDescriptor(Class)} but don't take no for an answer.
1161
* @throws AssertionError
1162
* If the descriptor is missing.
1165
public Descriptor getDescriptorOrDie(Class<? extends Describable> type) {
1166
Descriptor d = getDescriptor(type);
1168
throw new AssertionError(type+" is missing its descriptor");
1173
* Gets the {@link Descriptor} instance in the current Hudson by its type.
1175
public <T extends Descriptor> T getDescriptorByType(Class<T> type) {
1176
for( Descriptor d : getExtensionList(Descriptor.class) )
1177
if(d.getClass()==type)
1178
return type.cast(d);
1183
* Gets the {@link SecurityRealm} descriptors by name. Primarily used for making them web-visible.
1185
public Descriptor<SecurityRealm> getSecurityRealms(String shortClassName) {
1186
return findDescriptor(shortClassName,SecurityRealm.all());
1190
* Finds a descriptor that has the specified name.
1192
private <T extends Describable<T>>
1193
Descriptor<T> findDescriptor(String shortClassName, Collection<? extends Descriptor<T>> descriptors) {
1194
String name = '.'+shortClassName;
1195
for (Descriptor<T> d : descriptors) {
1196
if(d.clazz.getName().endsWith(name))
1202
protected void updateComputerList() throws IOException {
1203
updateComputerList(AUTOMATIC_SLAVE_LAUNCH);
1207
* Gets all the installed {@link SCMListener}s.
1209
public CopyOnWriteList<SCMListener> getSCMListeners() {
1210
return scmListeners;
1214
* Gets the plugin object from its short name.
1217
* This allows URL <tt>hudson/plugin/ID</tt> to be served by the views
1218
* of the plugin class.
1220
public Plugin getPlugin(String shortName) {
1221
PluginWrapper p = pluginManager.getPlugin(shortName);
1222
if(p==null) return null;
1223
return p.getPlugin();
1227
* Gets the plugin object from its class.
1230
* This allows easy storage of plugin information in the plugin singleton without
1231
* every plugin reimplementing the singleton pattern.
1233
* @param clazz The plugin class (beware class-loader fun, this will probably only work
1234
* from within the jpi that defines the plugin class, it may or may not work in other cases)
1236
* @return The plugin instance.
1238
@SuppressWarnings("unchecked")
1239
public <P extends Plugin> P getPlugin(Class<P> clazz) {
1240
PluginWrapper p = pluginManager.getPlugin(clazz);
1241
if(p==null) return null;
1242
return (P) p.getPlugin();
1246
* Gets the plugin objects from their super-class.
1248
* @param clazz The plugin class (beware class-loader fun)
1250
* @return The plugin instances.
1252
public <P extends Plugin> List<P> getPlugins(Class<P> clazz) {
1253
List<P> result = new ArrayList<P>();
1254
for (PluginWrapper w: pluginManager.getPlugins(clazz)) {
1255
result.add((P)w.getPlugin());
1257
return Collections.unmodifiableList(result);
1261
* Synonym for {@link #getDescription}.
1263
public String getSystemMessage() {
1264
return systemMessage;
1268
* Gets the markup formatter used in the system.
1274
public MarkupFormatter getMarkupFormatter() {
1275
return markupFormatter!=null ? markupFormatter : RawHtmlMarkupFormatter.INSTANCE;
1279
* Sets the markup formatter used in the system globally.
1283
public void setMarkupFormatter(MarkupFormatter f) {
1284
this.markupFormatter = f;
1288
* Sets the system message.
1290
public void setSystemMessage(String message) throws IOException {
1291
this.systemMessage = message;
1295
public FederatedLoginService getFederatedLoginService(String name) {
1296
for (FederatedLoginService fls : FederatedLoginService.all()) {
1297
if (fls.getUrlName().equals(name))
1303
public List<FederatedLoginService> getFederatedLoginServices() {
1304
return FederatedLoginService.all();
1307
public Launcher createLauncher(TaskListener listener) {
1308
return new LocalLauncher(listener).decorateFor(this);
1312
public String getFullName() {
1316
public String getFullDisplayName() {
1321
* Returns the transient {@link Action}s associated with the top page.
1324
* Adding {@link Action} is primarily useful for plugins to contribute
1325
* an item to the navigation bar of the top page. See existing {@link Action}
1326
* implementation for it affects the GUI.
1329
* To register an {@link Action}, implement {@link RootAction} extension point, or write code like
1330
* {@code Hudson.getInstance().getActions().add(...)}.
1333
* Live list where the changes can be made. Can be empty but never null.
1336
public List<Action> getActions() {
1341
* Gets just the immediate children of {@link Jenkins}.
1343
* @see #getAllItems(Class)
1345
@Exported(name="jobs")
1346
public List<TopLevelItem> getItems() {
1347
if (authorizationStrategy instanceof AuthorizationStrategy.Unsecured ||
1348
authorizationStrategy instanceof FullControlOnceLoggedInAuthorizationStrategy) {
1349
return new ArrayList(items.values());
1352
List<TopLevelItem> viewableItems = new ArrayList<TopLevelItem>();
1353
for (TopLevelItem item : items.values()) {
1354
if (item.hasPermission(Item.READ))
1355
viewableItems.add(item);
1358
return viewableItems;
1362
* Returns the read-only view of all the {@link TopLevelItem}s keyed by their names.
1364
* This method is efficient, as it doesn't involve any copying.
1368
public Map<String,TopLevelItem> getItemMap() {
1369
return Collections.unmodifiableMap(items);
1373
* Gets just the immediate children of {@link Jenkins} but of the given type.
1375
public <T> List<T> getItems(Class<T> type) {
1376
List<T> r = new ArrayList<T>();
1377
for (TopLevelItem i : getItems())
1378
if (type.isInstance(i))
1379
r.add(type.cast(i));
1384
* Gets all the {@link Item}s recursively in the {@link ItemGroup} tree
1385
* and filter them by the given type.
1387
public <T extends Item> List<T> getAllItems(Class<T> type) {
1388
List<T> r = new ArrayList<T>();
1390
Stack<ItemGroup> q = new Stack<ItemGroup>();
1393
while(!q.isEmpty()) {
1394
ItemGroup<?> parent = q.pop();
1395
for (Item i : parent.getItems()) {
1396
if(type.isInstance(i)) {
1397
if (i.hasPermission(Item.READ))
1398
r.add(type.cast(i));
1400
if(i instanceof ItemGroup)
1401
q.push((ItemGroup)i);
1409
* Gets all the items recursively.
1413
public List<Item> getAllItems() {
1414
return getAllItems(Item.class);
1418
* Gets a list of simple top-level projects.
1419
* @deprecated This method will ignore Maven and matrix projects, as well as projects inside containers such as folders.
1420
* You may prefer to call {@link #getAllItems(Class)} on {@link AbstractProject},
1421
* perhaps also using {@link Util#createSubList} to consider only {@link TopLevelItem}s.
1422
* (That will also consider the caller's permissions.)
1423
* If you really want to get just {@link Project}s at top level, ignoring permissions,
1424
* you can filter the values from {@link #getItemMap} using {@link Util#createSubList}.
1427
public List<Project> getProjects() {
1428
return Util.createSubList(items.values(),Project.class);
1432
* Gets the names of all the {@link Job}s.
1434
public Collection<String> getJobNames() {
1435
List<String> names = new ArrayList<String>();
1436
for (Job j : getAllItems(Job.class))
1437
names.add(j.getFullName());
1441
public List<Action> getViewActions() {
1442
return getActions();
1446
* Gets the names of all the {@link TopLevelItem}s.
1448
public Collection<String> getTopLevelItemNames() {
1449
List<String> names = new ArrayList<String>();
1450
for (TopLevelItem j : items.values())
1451
names.add(j.getName());
1455
public View getView(String name) {
1456
return viewGroupMixIn.getView(name);
1460
* Gets the read-only list of all {@link View}s.
1463
public Collection<View> getViews() {
1464
return viewGroupMixIn.getViews();
1467
public void addView(View v) throws IOException {
1468
viewGroupMixIn.addView(v);
1471
public boolean canDelete(View view) {
1472
return viewGroupMixIn.canDelete(view);
1475
public synchronized void deleteView(View view) throws IOException {
1476
viewGroupMixIn.deleteView(view);
1479
public void onViewRenamed(View view, String oldName, String newName) {
1480
viewGroupMixIn.onViewRenamed(view,oldName,newName);
1484
* Returns the primary {@link View} that renders the top-page of Hudson.
1487
public View getPrimaryView() {
1488
return viewGroupMixIn.getPrimaryView();
1491
public void setPrimaryView(View v) {
1492
this.primaryView = v.getViewName();
1495
public ViewsTabBar getViewsTabBar() {
1499
public void setViewsTabBar(ViewsTabBar viewsTabBar) {
1500
this.viewsTabBar = viewsTabBar;
1503
public Jenkins getItemGroup() {
1507
public MyViewsTabBar getMyViewsTabBar() {
1508
return myViewsTabBar;
1511
public void setMyViewsTabBar(MyViewsTabBar myViewsTabBar) {
1512
this.myViewsTabBar = myViewsTabBar;
1516
* Returns true if the current running Jenkins is upgraded from a version earlier than the specified version.
1519
* This method continues to return true until the system configuration is saved, at which point
1520
* {@link #version} will be overwritten and Hudson forgets the upgrade history.
1523
* To handle SNAPSHOTS correctly, pass in "1.N.*" to test if it's upgrading from the version
1524
* equal or younger than N. So say if you implement a feature in 1.301 and you want to check
1525
* if the installation upgraded from pre-1.301, pass in "1.300.*"
1529
public boolean isUpgradedFromBefore(VersionNumber v) {
1531
return new VersionNumber(version).isOlderThan(v);
1532
} catch (IllegalArgumentException e) {
1533
// fail to parse this version number
1539
* Gets the read-only list of all {@link Computer}s.
1541
public Computer[] getComputers() {
1542
Computer[] r = computers.values().toArray(new Computer[computers.size()]);
1543
Arrays.sort(r,new Comparator<Computer>() {
1544
final Collator collator = Collator.getInstance();
1545
public int compare(Computer lhs, Computer rhs) {
1546
if(lhs.getNode()==Jenkins.this) return -1;
1547
if(rhs.getNode()==Jenkins.this) return 1;
1548
return collator.compare(lhs.getDisplayName(), rhs.getDisplayName());
1555
public Computer getComputer(@Argument(required=true,metaVar="NAME",usage="Node name") String name) {
1556
if(name.equals("(master)"))
1559
for (Computer c : computers.values()) {
1560
if(c.getName().equals(name))
1567
* Gets the label that exists on this system by the name.
1569
* @return null if name is null.
1570
* @see Label#parseExpression(String) (String)
1572
public Label getLabel(String expr) {
1573
if(expr==null) return null;
1574
expr = hudson.util.QuotedStringTokenizer.unquote(expr);
1576
Label l = labels.get(expr);
1582
labels.putIfAbsent(expr,Label.parseExpression(expr));
1583
} catch (ANTLRException e) {
1584
// laxly accept it as a single label atom for backward compatibility
1585
return getLabelAtom(expr);
1591
* Returns the label atom of the given name.
1592
* @return non-null iff name is non-null
1594
public @Nullable LabelAtom getLabelAtom(@CheckForNull String name) {
1595
if (name==null) return null;
1598
Label l = labels.get(name);
1600
return (LabelAtom)l;
1603
LabelAtom la = new LabelAtom(name);
1604
if (labels.putIfAbsent(name, la)==null)
1610
* Gets all the active labels in the current system.
1612
public Set<Label> getLabels() {
1613
Set<Label> r = new TreeSet<Label>();
1614
for (Label l : labels.values()) {
1621
public Set<LabelAtom> getLabelAtoms() {
1622
Set<LabelAtom> r = new TreeSet<LabelAtom>();
1623
for (Label l : labels.values()) {
1624
if(!l.isEmpty() && l instanceof LabelAtom)
1625
r.add((LabelAtom)l);
1630
public Queue getQueue() {
1635
public String getDisplayName() {
1636
return Messages.Hudson_DisplayName();
1639
public List<JDK> getJDKs() {
1641
jdks = new ArrayList<JDK>();
1646
* Gets the JDK installation of the given name, or returns null.
1648
public JDK getJDK(String name) {
1650
// if only one JDK is configured, "default JDK" should mean that JDK.
1651
List<JDK> jdks = getJDKs();
1652
if(jdks.size()==1) return jdks.get(0);
1655
for (JDK j : getJDKs()) {
1656
if(j.getName().equals(name))
1665
* Gets the slave node of the give name, hooked under this Hudson.
1667
public @CheckForNull Node getNode(String name) {
1668
return slaves.getNode(name);
1672
* Gets a {@link Cloud} by {@link Cloud#name its name}, or null.
1674
public Cloud getCloud(String name) {
1675
return clouds.getByName(name);
1678
protected Map<Node,Computer> getComputerMap() {
1683
* Returns all {@link Node}s in the system, excluding {@link Jenkins} instance itself which
1684
* represents the master.
1686
public List<Node> getNodes() {
1691
* Adds one more {@link Node} to Hudson.
1693
public synchronized void addNode(Node n) throws IOException {
1694
if(n==null) throw new IllegalArgumentException();
1695
ArrayList<Node> nl = new ArrayList<Node>(this.slaves);
1696
if(!nl.contains(n)) // defensive check
1702
* Removes a {@link Node} from Hudson.
1704
public synchronized void removeNode(@Nonnull Node n) throws IOException {
1705
Computer c = n.toComputer();
1707
c.disconnect(OfflineCause.create(Messages._Hudson_NodeBeingRemoved()));
1709
ArrayList<Node> nl = new ArrayList<Node>(this.slaves);
1714
public void setNodes(List<? extends Node> nodes) throws IOException {
1715
this.slaves = new NodeList(nodes);
1716
updateComputerList();
1721
public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties() {
1722
return nodeProperties;
1725
public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getGlobalNodeProperties() {
1726
return globalNodeProperties;
1730
* Resets all labels and remove invalid ones.
1732
* This should be called when the assumptions behind label cache computation changes,
1733
* but we also call this periodically to self-heal any data out-of-sync issue.
1735
private void trimLabels() {
1736
for (Iterator<Label> itr = labels.values().iterator(); itr.hasNext();) {
1737
Label l = itr.next();
1745
* Binds {@link AdministrativeMonitor}s to URL.
1747
public AdministrativeMonitor getAdministrativeMonitor(String id) {
1748
for (AdministrativeMonitor m : administrativeMonitors)
1754
public NodeDescriptor getDescriptor() {
1755
return DescriptorImpl.INSTANCE;
1758
public static final class DescriptorImpl extends NodeDescriptor {
1760
public static final DescriptorImpl INSTANCE = new DescriptorImpl();
1762
public String getDisplayName() {
1767
public boolean isInstantiable() {
1771
public FormValidation doCheckNumExecutors(@QueryParameter String value) {
1772
return FormValidation.validateNonNegativeInteger(value);
1775
public FormValidation doCheckRawBuildsDir(@QueryParameter String value) {
1776
if (!value.contains("${")) {
1777
File d = new File(value);
1778
if (!d.isDirectory() && (d.getParentFile() == null || !d.getParentFile().canWrite())) {
1779
return FormValidation.error(value + " does not exist and probably cannot be created");
1781
// XXX failure to use either ITEM_* variable might be an error too?
1783
return FormValidation.ok(); // XXX assumes it will be OK after substitution, but can we be sure?
1786
// to route /descriptor/FQCN/xxx to getDescriptor(FQCN).xxx
1787
public Object getDynamic(String token) {
1788
return Jenkins.getInstance().getDescriptor(token);
1793
* Gets the system default quiet period.
1795
public int getQuietPeriod() {
1796
return quietPeriod!=null ? quietPeriod : 5;
1800
* Sets the global quiet period.
1802
* @param quietPeriod
1803
* null to the default value.
1805
public void setQuietPeriod(Integer quietPeriod) throws IOException {
1806
this.quietPeriod = quietPeriod;
1811
* Gets the global SCM check out retry count.
1813
public int getScmCheckoutRetryCount() {
1814
return scmCheckoutRetryCount;
1817
public void setScmCheckoutRetryCount(int scmCheckoutRetryCount) throws IOException {
1818
this.scmCheckoutRetryCount = scmCheckoutRetryCount;
1823
public String getSearchUrl() {
1828
public SearchIndexBuilder makeSearchIndex() {
1829
return super.makeSearchIndex()
1830
.add("configure", "config","configure")
1833
.add(new CollectionSearchIndex<TopLevelItem>() {
1834
protected SearchItem get(String key) { return getItem(key); }
1835
protected Collection<TopLevelItem> all() { return getItems(); }
1837
.add(getPrimaryView().makeSearchIndex())
1838
.add(new CollectionSearchIndex() {// for computers
1839
protected Computer get(String key) { return getComputer(key); }
1840
protected Collection<Computer> all() { return computers.values(); }
1842
.add(new CollectionSearchIndex() {// for users
1843
protected User get(String key) { return User.get(key,false); }
1844
protected Collection<User> all() { return User.getAll(); }
1846
.add(new CollectionSearchIndex() {// for views
1847
protected View get(String key) { return getView(key); }
1848
protected Collection<View> all() { return views; }
1852
public String getUrlChildPrefix() {
1857
* Gets the absolute URL of Jenkins,
1858
* such as "http://localhost/jenkins/".
1861
* This method first tries to use the manually configured value, then
1862
* fall back to {@link StaplerRequest#getRootPath()}.
1863
* It is done in this order so that it can work correctly even in the face
1864
* of a reverse proxy.
1867
* This method returns null if this parameter is not configured by the user.
1868
* The caller must gracefully deal with this situation.
1869
* The returned URL will always have the trailing '/'.
1871
* @see Descriptor#getCheckUrl(String)
1872
* @see #getRootUrlFromRequest()
1873
* @see <a href="https://wiki.jenkins-ci.org/display/JENKINS/Hyperlinks+in+HTML">Hyperlinks in HTML</a>
1875
public String getRootUrl() {
1876
String url = JenkinsLocationConfiguration.get().getUrl();
1878
return Util.ensureEndsWith(url,"/");
1880
StaplerRequest req = Stapler.getCurrentRequest();
1882
return getRootUrlFromRequest();
1887
* Is Jenkins running in HTTPS?
1889
* Note that we can't really trust {@link StaplerRequest#isSecure()} because HTTPS might be terminated
1890
* in the reverse proxy.
1892
public boolean isRootUrlSecure() {
1893
String url = getRootUrl();
1894
return url!=null && url.startsWith("https");
1898
* Gets the absolute URL of Hudson top page, such as "http://localhost/hudson/".
1901
* Unlike {@link #getRootUrl()}, which uses the manually configured value,
1902
* this one uses the current request to reconstruct the URL. The benefit is
1903
* that this is immune to the configuration mistake (users often fail to set the root URL
1904
* correctly, especially when a migration is involved), but the downside
1905
* is that unless you are processing a request, this method doesn't work.
1907
* Please note that this will not work in all cases if Jenkins is running behind a
1908
* reverse proxy (e.g. when user has switched off ProxyPreserveHost, which is
1909
* default setup or the actual url uses https) and you should use getRootUrl if
1910
* you want to be sure you reflect user setup.
1911
* See https://wiki.jenkins-ci.org/display/JENKINS/Running+Jenkins+behind+Apache
1915
public String getRootUrlFromRequest() {
1916
StaplerRequest req = Stapler.getCurrentRequest();
1917
StringBuilder buf = new StringBuilder();
1918
buf.append(req.getScheme()+"://");
1919
buf.append(req.getServerName());
1920
if(req.getServerPort()!=80)
1921
buf.append(':').append(req.getServerPort());
1922
buf.append(req.getContextPath()).append('/');
1923
return buf.toString();
1926
public File getRootDir() {
1930
public FilePath getWorkspaceFor(TopLevelItem item) {
1931
for (WorkspaceLocator l : WorkspaceLocator.all()) {
1932
FilePath workspace = l.locate(item, this);
1933
if (workspace != null) {
1938
return new FilePath(expandVariablesForDirectory(workspaceDir, item));
1941
public File getBuildDirFor(Job job) {
1942
return expandVariablesForDirectory(buildsDir, job);
1945
private File expandVariablesForDirectory(String base, Item item) {
1946
return new File(Util.replaceMacro(base, ImmutableMap.of(
1947
"JENKINS_HOME", getRootDir().getPath(),
1948
"ITEM_ROOTDIR", item.getRootDir().getPath(),
1949
"ITEM_FULLNAME", item.getFullName(), // legacy, deprecated
1950
"ITEM_FULL_NAME", item.getFullName().replace(':','$')))); // safe, see JENKINS-12251
1953
public String getRawWorkspaceDir() {
1954
return workspaceDir;
1957
public String getRawBuildsDir() {
1961
public FilePath getRootPath() {
1962
return new FilePath(getRootDir());
1966
public FilePath createPath(String absolutePath) {
1967
return new FilePath((VirtualChannel)null,absolutePath);
1970
public ClockDifference getClockDifference() {
1971
return ClockDifference.ZERO;
1975
* For binding {@link LogRecorderManager} to "/log".
1976
* Everything below here is admin-only, so do the check here.
1978
public LogRecorderManager getLog() {
1979
checkPermission(ADMINISTER);
1984
* A convenience method to check if there's some security
1985
* restrictions in place.
1988
public boolean isUseSecurity() {
1989
return securityRealm!=SecurityRealm.NO_AUTHENTICATION || authorizationStrategy!=AuthorizationStrategy.UNSECURED;
1992
public boolean isUseProjectNamingStrategy(){
1993
return projectNamingStrategy != DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
1997
* If true, all the POST requests to Hudson would have to have crumb in it to protect
1998
* Hudson from CSRF vulnerabilities.
2001
public boolean isUseCrumbs() {
2002
return crumbIssuer!=null;
2006
* Returns the constant that captures the three basic security modes
2009
public SecurityMode getSecurity() {
2010
// fix the variable so that this code works under concurrent modification to securityRealm.
2011
SecurityRealm realm = securityRealm;
2013
if(realm==SecurityRealm.NO_AUTHENTICATION)
2014
return SecurityMode.UNSECURED;
2015
if(realm instanceof LegacySecurityRealm)
2016
return SecurityMode.LEGACY;
2017
return SecurityMode.SECURED;
2024
public SecurityRealm getSecurityRealm() {
2025
return securityRealm;
2028
public void setSecurityRealm(SecurityRealm securityRealm) {
2029
if(securityRealm==null)
2030
securityRealm= SecurityRealm.NO_AUTHENTICATION;
2031
this.useSecurity = true;
2032
this.securityRealm = securityRealm;
2033
// reset the filters and proxies for the new SecurityRealm
2035
HudsonFilter filter = HudsonFilter.get(servletContext);
2036
if (filter == null) {
2037
// Fix for #3069: This filter is not necessarily initialized before the servlets.
2038
// when HudsonFilter does come back, it'll initialize itself.
2039
LOGGER.fine("HudsonFilter has not yet been initialized: Can't perform security setup for now");
2041
LOGGER.fine("HudsonFilter has been previously initialized: Setting security up");
2042
filter.reset(securityRealm);
2043
LOGGER.fine("Security is now fully set up");
2045
} catch (ServletException e) {
2046
// for binary compatibility, this method cannot throw a checked exception
2047
throw new AcegiSecurityException("Failed to configure filter",e) {};
2051
public void setAuthorizationStrategy(AuthorizationStrategy a) {
2053
a = AuthorizationStrategy.UNSECURED;
2055
authorizationStrategy = a;
2058
public void disableSecurity() {
2060
setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
2061
authorizationStrategy = AuthorizationStrategy.UNSECURED;
2062
markupFormatter = null;
2065
public void setProjectNamingStrategy(ProjectNamingStrategy ns) {
2067
ns = DefaultProjectNamingStrategy.DEFAULT_NAMING_STRATEGY;
2069
projectNamingStrategy = ns;
2072
public Lifecycle getLifecycle() {
2073
return Lifecycle.get();
2077
* Gets the dependency injection container that hosts all the extension implementations and other
2078
* components in Jenkins.
2082
public Injector getInjector() {
2083
return lookup(Injector.class);
2087
* Returns {@link ExtensionList} that retains the discovered instances for the given extension type.
2089
* @param extensionType
2090
* The base type that represents the extension point. Normally {@link ExtensionPoint} subtype
2091
* but that's not a hard requirement.
2093
* Can be an empty list but never null.
2095
@SuppressWarnings({"unchecked"})
2096
public <T> ExtensionList<T> getExtensionList(Class<T> extensionType) {
2097
return extensionLists.get(extensionType);
2101
* Used to bind {@link ExtensionList}s to URLs.
2105
public ExtensionList getExtensionList(String extensionType) throws ClassNotFoundException {
2106
return getExtensionList(pluginManager.uberClassLoader.loadClass(extensionType));
2110
* Returns {@link ExtensionList} that retains the discovered {@link Descriptor} instances for the given
2111
* kind of {@link Describable}.
2114
* Can be an empty list but never null.
2116
@SuppressWarnings({"unchecked"})
2117
public <T extends Describable<T>,D extends Descriptor<T>> DescriptorExtensionList<T,D> getDescriptorList(Class<T> type) {
2118
return descriptorLists.get(type);
2122
* Refresh {@link ExtensionList}s by adding all the newly discovered extensions.
2124
* Exposed only for {@link PluginManager#dynamicLoad(File)}.
2126
public void refreshExtensions() throws ExtensionRefreshException {
2127
ExtensionList<ExtensionFinder> finders = getExtensionList(ExtensionFinder.class);
2128
for (ExtensionFinder ef : finders) {
2129
if (!ef.isRefreshable())
2130
throw new ExtensionRefreshException(ef+" doesn't support refresh");
2133
List<ExtensionComponentSet> fragments = Lists.newArrayList();
2134
for (ExtensionFinder ef : finders) {
2135
fragments.add(ef.refresh());
2137
ExtensionComponentSet delta = ExtensionComponentSet.union(fragments).filtered();
2139
// if we find a new ExtensionFinder, we need it to list up all the extension points as well
2140
List<ExtensionComponent<ExtensionFinder>> newFinders = Lists.newArrayList(delta.find(ExtensionFinder.class));
2141
while (!newFinders.isEmpty()) {
2142
ExtensionFinder f = newFinders.remove(newFinders.size()-1).getInstance();
2144
ExtensionComponentSet ecs = ExtensionComponentSet.allOf(f).filtered();
2145
newFinders.addAll(ecs.find(ExtensionFinder.class));
2146
delta = ExtensionComponentSet.union(delta, ecs);
2149
for (ExtensionList el : extensionLists.values()) {
2152
for (ExtensionList el : descriptorLists.values()) {
2156
// TODO: we need some generalization here so that extension points can be notified when a refresh happens?
2157
for (ExtensionComponent<RootAction> ea : delta.find(RootAction.class)) {
2158
Action a = ea.getInstance();
2159
if (!actions.contains(a)) actions.add(a);
2164
* Returns the root {@link ACL}.
2166
* @see AuthorizationStrategy#getRootACL()
2169
public ACL getACL() {
2170
return authorizationStrategy.getRootACL();
2177
public AuthorizationStrategy getAuthorizationStrategy() {
2178
return authorizationStrategy;
2182
* The strategy used to check the project names.
2183
* @return never <code>null</code>
2185
public ProjectNamingStrategy getProjectNamingStrategy() {
2186
return projectNamingStrategy == null ? ProjectNamingStrategy.DEFAULT_NAMING_STRATEGY : projectNamingStrategy;
2190
* Returns true if Hudson is quieting down.
2192
* No further jobs will be executed unless it
2193
* can be finished while other current pending builds
2194
* are still in progress.
2197
public boolean isQuietingDown() {
2198
return isQuietingDown;
2202
* Returns true if the container initiated the termination of the web application.
2204
public boolean isTerminating() {
2209
* Gets the initialization milestone that we've already reached.
2212
* {@link InitMilestone#STARTED} even if the initialization hasn't been started, so that this method
2213
* never returns null.
2215
public InitMilestone getInitLevel() {
2219
public void setNumExecutors(int n) throws IOException {
2220
this.numExecutors = n;
2229
* Note that the look up is case-insensitive.
2231
public TopLevelItem getItem(String name) {
2232
if (name==null) return null;
2233
TopLevelItem item = items.get(name);
2236
if (!item.hasPermission(Item.READ)) {
2237
if (item.hasPermission(Item.DISCOVER)) {
2238
throw new AccessDeniedException("Please login to access job " + name);
2246
* Gets the item by its path name from the given context
2248
* <h2>Path Names</h2>
2250
* If the name starts from '/', like "/foo/bar/zot", then it's interpreted as absolute.
2251
* Otherwise, the name should be something like "foo/bar" and it's interpreted like
2252
* relative path name in the file system is, against the given context.
2255
* null is interpreted as {@link Jenkins}. Base 'directory' of the interpretation.
2258
public Item getItem(String pathName, ItemGroup context) {
2259
if (context==null) context = this;
2260
if (pathName==null) return null;
2262
if (pathName.startsWith("/")) // absolute
2263
return getItemByFullName(pathName);
2265
Object/*Item|ItemGroup*/ ctx = context;
2267
StringTokenizer tokens = new StringTokenizer(pathName,"/");
2268
while (tokens.hasMoreTokens()) {
2269
String s = tokens.nextToken();
2270
if (s.equals("..")) {
2271
if (ctx instanceof Item) {
2272
ctx = ((Item)ctx).getParent();
2276
ctx=null; // can't go up further
2279
if (s.equals(".")) {
2283
if (ctx instanceof ItemGroup) {
2284
ItemGroup g = (ItemGroup) ctx;
2285
Item i = g.getItem(s);
2286
if (i==null || !i.hasPermission(Item.READ)) { // XXX consider DISCOVER
2287
ctx=null; // can't go up further
2296
if (ctx instanceof Item)
2299
// fall back to the classic interpretation
2300
return getItemByFullName(pathName);
2303
public final Item getItem(String pathName, Item context) {
2304
return getItem(pathName,context!=null?context.getParent():null);
2307
public final <T extends Item> T getItem(String pathName, ItemGroup context, Class<T> type) {
2308
Item r = getItem(pathName, context);
2309
if (type.isInstance(r))
2310
return type.cast(r);
2314
public final <T extends Item> T getItem(String pathName, Item context, Class<T> type) {
2315
return getItem(pathName,context!=null?context.getParent():null,type);
2318
public File getRootDirFor(TopLevelItem child) {
2319
return getRootDirFor(child.getName());
2322
private File getRootDirFor(String name) {
2323
return new File(new File(getRootDir(),"jobs"), name);
2327
* Gets the {@link Item} object by its full name.
2328
* Full names are like path names, where each name of {@link Item} is
2332
* null if either such {@link Item} doesn't exist under the given full name,
2333
* or it exists but it's no an instance of the given type.
2335
public @CheckForNull <T extends Item> T getItemByFullName(String fullName, Class<T> type) {
2336
StringTokenizer tokens = new StringTokenizer(fullName,"/");
2337
ItemGroup parent = this;
2339
if(!tokens.hasMoreTokens()) return null; // for example, empty full name.
2342
Item item = parent.getItem(tokens.nextToken());
2343
if(!tokens.hasMoreTokens()) {
2344
if(type.isInstance(item))
2345
return type.cast(item);
2350
if(!(item instanceof ItemGroup))
2351
return null; // this item can't have any children
2353
if (!item.hasPermission(Item.READ))
2354
return null; // XXX consider DISCOVER
2356
parent = (ItemGroup) item;
2360
public @CheckForNull Item getItemByFullName(String fullName) {
2361
return getItemByFullName(fullName,Item.class);
2365
* Gets the user of the given name.
2367
* @return the user of the given name, if that person exists or the invoker {@link #hasPermission} on {@link #ADMINISTER}; else null
2368
* @see User#get(String,boolean)
2370
public @CheckForNull User getUser(String name) {
2371
return User.get(name,hasPermission(ADMINISTER));
2374
public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name ) throws IOException {
2375
return createProject(type, name, true);
2378
public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify ) throws IOException {
2379
return itemGroupMixIn.createProject(type,name,notify);
2383
* Overwrites the existing item by new one.
2386
* This is a short cut for deleting an existing job and adding a new one.
2388
public synchronized void putItem(TopLevelItem item) throws IOException, InterruptedException {
2389
String name = item.getName();
2390
TopLevelItem old = items.get(name);
2391
if (old ==item) return; // noop
2393
checkPermission(Item.CREATE);
2396
items.put(name,item);
2397
ItemListener.fireOnCreated(item);
2401
* Creates a new job.
2404
* This version infers the descriptor from the type of the top-level item.
2406
* @throws IllegalArgumentException
2407
* if the project of the given name already exists.
2409
public synchronized <T extends TopLevelItem> T createProject( Class<T> type, String name ) throws IOException {
2410
return type.cast(createProject((TopLevelItemDescriptor)getDescriptor(type),name));
2414
* Called by {@link Job#renameTo(String)} to update relevant data structure.
2415
* assumed to be synchronized on Hudson by the caller.
2417
public void onRenamed(TopLevelItem job, String oldName, String newName) throws IOException {
2418
items.remove(oldName);
2419
items.put(newName,job);
2421
for (View v : views)
2422
v.onJobRenamed(job, oldName, newName);
2427
* Called in response to {@link Job#doDoDelete(StaplerRequest, StaplerResponse)}
2429
public void onDeleted(TopLevelItem item) throws IOException {
2430
for (ItemListener l : ItemListener.all())
2433
items.remove(item.getName());
2434
for (View v : views)
2435
v.onJobRenamed(item, item.getName(), null);
2439
public FingerprintMap getFingerprintMap() {
2440
return fingerprintMap;
2443
// if no finger print matches, display "not found page".
2444
public Object getFingerprint( String md5sum ) throws IOException {
2445
Fingerprint r = fingerprintMap.get(md5sum);
2446
if(r==null) return new NoFingerprintMatch(md5sum);
2451
* Gets a {@link Fingerprint} object if it exists.
2454
public Fingerprint _getFingerprint( String md5sum ) throws IOException {
2455
return fingerprintMap.get(md5sum);
2459
* The file we save our configuration.
2461
private XmlFile getConfigFile() {
2462
return new XmlFile(XSTREAM, new File(root,"config.xml"));
2465
public int getNumExecutors() {
2466
return numExecutors;
2469
public Mode getMode() {
2473
public void setMode(Mode m) throws IOException {
2478
public String getLabelString() {
2479
return fixNull(label).trim();
2483
public void setLabelString(String label) throws IOException {
2489
public LabelAtom getSelfLabel() {
2490
return getLabelAtom("master");
2493
public Computer createComputer() {
2494
return new Hudson.MasterComputer();
2497
private synchronized TaskBuilder loadTasks() throws IOException {
2498
File projectsDir = new File(root,"jobs");
2499
if(!projectsDir.getCanonicalFile().isDirectory() && !projectsDir.mkdirs()) {
2500
if(projectsDir.exists())
2501
throw new IOException(projectsDir+" is not a directory");
2502
throw new IOException("Unable to create "+projectsDir+"\nPermission issue? Please create this directory manually.");
2504
File[] subdirs = projectsDir.listFiles(new FileFilter() {
2505
public boolean accept(File child) {
2506
return child.isDirectory() && Items.getConfigFile(child).exists();
2510
final Set<String> loadedNames = Collections.synchronizedSet(new HashSet<String>());
2512
TaskGraphBuilder g = new TaskGraphBuilder();
2513
Handle loadHudson = g.requires(EXTENSIONS_AUGMENTED).attains(JOB_LOADED).add("Loading global config", new Executable() {
2514
public void run(Reactor session) throws Exception {
2515
// JENKINS-8043: some slaves (eg. swarm slaves) are not saved into the config file
2516
// and will get overwritten when reloading. Make a backup copy now, and re-add them later
2517
NodeList oldSlaves = slaves;
2519
XmlFile cfg = getConfigFile();
2521
// reset some data that may not exist in the disk file
2522
// so that we can take a proper compensation action later.
2527
cfg.unmarshal(Jenkins.this);
2530
// if we are loading old data that doesn't have this field
2531
if (slaves == null) slaves = new NodeList();
2533
clouds.setOwner(Jenkins.this);
2535
// JENKINS-8043: re-add the slaves which were not saved into the config file
2536
// and are now missing, but still connected.
2537
if (oldSlaves != null) {
2538
ArrayList<Node> newSlaves = new ArrayList<Node>(slaves);
2539
for (Node n: oldSlaves) {
2540
if (n instanceof EphemeralNode) {
2541
if(!newSlaves.contains(n)) {
2546
setNodes(newSlaves);
2551
for (final File subdir : subdirs) {
2552
g.requires(loadHudson).attains(JOB_LOADED).notFatal().add("Loading job "+subdir.getName(),new Executable() {
2553
public void run(Reactor session) throws Exception {
2554
TopLevelItem item = (TopLevelItem) Items.load(Jenkins.this, subdir);
2555
items.put(item.getName(), item);
2556
loadedNames.add(item.getName());
2561
g.requires(JOB_LOADED).add("Cleaning up old builds",new Executable() {
2562
public void run(Reactor reactor) throws Exception {
2563
// anything we didn't load from disk, throw them away.
2564
// doing this after loading from disk allows newly loaded items
2565
// to inspect what already existed in memory (in case of reloading)
2567
// retainAll doesn't work well because of CopyOnWriteMap implementation, so remove one by one
2568
// hopefully there shouldn't be too many of them.
2569
for (String name : items.keySet()) {
2570
if (!loadedNames.contains(name))
2576
g.requires(JOB_LOADED).add("Finalizing set up",new Executable() {
2577
public void run(Reactor session) throws Exception {
2578
rebuildDependencyGraph();
2580
{// recompute label objects - populates the labels mapping.
2581
for (Node slave : slaves)
2582
// Note that not all labels are visible until the slaves have connected.
2583
slave.getAssignedLabels();
2584
getAssignedLabels();
2587
// initialize views by inserting the default view if necessary
2588
// this is both for clean Hudson and for backward compatibility.
2589
if(views.size()==0 || primaryView==null) {
2590
View v = new AllView(Messages.Hudson_ViewName());
2593
primaryView = v.getViewName();
2596
// read in old data that doesn't have the security field set
2597
if(authorizationStrategy==null) {
2598
if(useSecurity==null || !useSecurity)
2599
authorizationStrategy = AuthorizationStrategy.UNSECURED;
2601
authorizationStrategy = new LegacyAuthorizationStrategy();
2603
if(securityRealm==null) {
2604
if(useSecurity==null || !useSecurity)
2605
setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
2607
setSecurityRealm(new LegacySecurityRealm());
2609
// force the set to proxy
2610
setSecurityRealm(securityRealm);
2613
if(useSecurity!=null && !useSecurity) {
2614
// forced reset to the unsecure mode.
2615
// this works as an escape hatch for people who locked themselves out.
2616
authorizationStrategy = AuthorizationStrategy.UNSECURED;
2617
setSecurityRealm(SecurityRealm.NO_AUTHENTICATION);
2620
// Initialize the filter with the crumb issuer
2621
setCrumbIssuer(crumbIssuer);
2623
// auto register root actions
2624
for (Action a : getExtensionList(RootAction.class))
2625
if (!actions.contains(a)) actions.add(a);
2633
* Save the settings to a file.
2635
public synchronized void save() throws IOException {
2636
if(BulkChange.contains(this)) return;
2637
getConfigFile().write(this);
2638
SaveableListener.fireOnChange(this, getConfigFile());
2643
* Called to shut down the system.
2645
@edu.umd.cs.findbugs.annotations.SuppressWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
2646
public void cleanUp() {
2647
for (ItemListener l : ItemListener.all())
2648
l.onBeforeShutdown();
2650
Set<Future<?>> pending = new HashSet<Future<?>>();
2652
for( Computer c : computers.values() ) {
2655
pending.add(c.disconnect(null));
2657
if(udpBroadcastThread!=null)
2658
udpBroadcastThread.shutdown();
2659
if(dnsMultiCast!=null)
2660
dnsMultiCast.close();
2661
interruptReloadThread();
2662
Timer timer = Trigger.timer;
2663
if (timer != null) {
2666
// TODO: how to wait for the completion of the last job?
2667
Trigger.timer = null;
2668
if(tcpSlaveAgentListener!=null)
2669
tcpSlaveAgentListener.shutdown();
2671
if(pluginManager!=null) // be defensive. there could be some ugly timing related issues
2672
pluginManager.stop();
2674
if(getRootDir().exists())
2675
// if we are aborting because we failed to create JENKINS_HOME,
2676
// don't try to save. Issue #536
2679
threadPoolForLoad.shutdown();
2680
for (Future<?> f : pending)
2682
f.get(10, TimeUnit.SECONDS); // if clean up operation didn't complete in time, we fail the test
2683
} catch (InterruptedException e) {
2684
Thread.currentThread().interrupt();
2685
break; // someone wants us to die now. quick!
2686
} catch (ExecutionException e) {
2687
LOGGER.log(Level.WARNING, "Failed to shut down properly",e);
2688
} catch (TimeoutException e) {
2689
LOGGER.log(Level.WARNING, "Failed to shut down properly",e);
2692
LogFactory.releaseAll();
2697
public Object getDynamic(String token) {
2698
for (Action a : getActions()) {
2699
String url = a.getUrlName();
2700
if (url==null) continue;
2701
if (url.equals(token) || url.equals('/' + token))
2704
for (Action a : getManagementLinks())
2705
if(a.getUrlName().equals(token))
2717
* Accepts submission from the configuration page.
2719
public synchronized void doConfigSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
2720
BulkChange bc = new BulkChange(this);
2722
checkPermission(ADMINISTER);
2724
JSONObject json = req.getSubmittedForm();
2726
workspaceDir = json.getString("rawWorkspaceDir");
2727
buildsDir = json.getString("rawBuildsDir");
2729
systemMessage = Util.nullify(req.getParameter("system_message"));
2732
jdks.addAll(req.bindJSONToList(JDK.class,json.get("jdks")));
2734
boolean result = true;
2735
for (Descriptor<?> d : Functions.getSortedDescriptorsForGlobalConfigUnclassified())
2736
result &= configureDescriptor(req,json,d);
2741
updateComputerList();
2743
FormApply.success(req.getContextPath()+'/').generateResponse(req, rsp, null);
2745
FormApply.success("configure").generateResponse(req, rsp, null); // back to config
2752
* Gets the {@link CrumbIssuer} currently in use.
2754
* @return null if none is in use.
2756
public CrumbIssuer getCrumbIssuer() {
2760
public void setCrumbIssuer(CrumbIssuer issuer) {
2761
crumbIssuer = issuer;
2764
public synchronized void doTestPost( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
2765
rsp.sendRedirect("foo");
2768
private boolean configureDescriptor(StaplerRequest req, JSONObject json, Descriptor<?> d) throws FormException {
2769
// collapse the structure to remain backward compatible with the JSON structure before 1.
2770
String name = d.getJsonSafeClassName();
2771
JSONObject js = json.has(name) ? json.getJSONObject(name) : new JSONObject(); // if it doesn't have the property, the method returns invalid null object.
2773
return d.configure(req, js);
2777
* Accepts submission from the node configuration page.
2779
public synchronized void doConfigExecutorsSubmit( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
2780
checkPermission(ADMINISTER);
2782
BulkChange bc = new BulkChange(this);
2784
JSONObject json = req.getSubmittedForm();
2786
MasterBuildConfiguration mbc = MasterBuildConfiguration.all().get(MasterBuildConfiguration.class);
2788
mbc.configure(req,json);
2790
getNodeProperties().rebuild(req, json.optJSONObject("nodeProperties"), NodeProperty.all());
2795
rsp.sendRedirect(req.getContextPath()+'/'+toComputer().getUrl()); // back to the computer page
2799
* Accepts the new description.
2801
public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
2802
getPrimaryView().doSubmitDescription(req, rsp);
2805
public synchronized HttpRedirect doQuietDown() throws IOException {
2807
return doQuietDown(false,0);
2808
} catch (InterruptedException e) {
2809
throw new AssertionError(); // impossible
2813
@CLIMethod(name="quiet-down")
2814
public HttpRedirect doQuietDown(
2815
@Option(name="-block",usage="Block until the system really quiets down and no builds are running") @QueryParameter boolean block,
2816
@Option(name="-timeout",usage="If non-zero, only block up to the specified number of milliseconds") @QueryParameter int timeout) throws InterruptedException, IOException {
2817
synchronized (this) {
2818
checkPermission(ADMINISTER);
2819
isQuietingDown = true;
2822
if (timeout > 0) timeout += System.currentTimeMillis();
2823
while (isQuietingDown
2824
&& (timeout <= 0 || System.currentTimeMillis() < timeout)
2825
&& !RestartListener.isAllReady()) {
2829
return new HttpRedirect(".");
2832
@CLIMethod(name="cancel-quiet-down")
2833
public synchronized HttpRedirect doCancelQuietDown() {
2834
checkPermission(ADMINISTER);
2835
isQuietingDown = false;
2836
getQueue().scheduleMaintenance();
2837
return new HttpRedirect(".");
2841
* Backward compatibility. Redirect to the thread dump.
2843
public void doClassicThreadDump(StaplerResponse rsp) throws IOException, ServletException {
2844
rsp.sendRedirect2("threadDump");
2848
* Obtains the thread dump of all slaves (including the master.)
2851
* Since this is for diagnostics, it has a built-in precautionary measure against hang slaves.
2853
public Map<String,Map<String,String>> getAllThreadDumps() throws IOException, InterruptedException {
2854
checkPermission(ADMINISTER);
2856
// issue the requests all at once
2857
Map<String,Future<Map<String,String>>> future = new HashMap<String, Future<Map<String, String>>>();
2859
for (Computer c : getComputers()) {
2861
future.put(c.getName(), RemotingDiagnostics.getThreadDumpAsync(c.getChannel()));
2862
} catch(Exception e) {
2863
LOGGER.info("Failed to get thread dump for node " + c.getName() + ": " + e.getMessage());
2866
if (toComputer() == null) {
2867
future.put("master", RemotingDiagnostics.getThreadDumpAsync(MasterComputer.localChannel));
2870
// if the result isn't available in 5 sec, ignore that.
2871
// this is a precaution against hang nodes
2872
long endTime = System.currentTimeMillis() + 5000;
2874
Map<String,Map<String,String>> r = new HashMap<String, Map<String, String>>();
2875
for (Entry<String, Future<Map<String, String>>> e : future.entrySet()) {
2877
r.put(e.getKey(), e.getValue().get(endTime-System.currentTimeMillis(), TimeUnit.MILLISECONDS));
2878
} catch (Exception x) {
2879
StringWriter sw = new StringWriter();
2880
x.printStackTrace(new PrintWriter(sw,true));
2881
r.put(e.getKey(), Collections.singletonMap("Failed to retrieve thread dump",sw.toString()));
2887
public synchronized TopLevelItem doCreateItem( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
2888
return itemGroupMixIn.createTopLevelItem(req, rsp);
2894
public TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException {
2895
return itemGroupMixIn.createProjectFromXML(name, xml);
2899
@SuppressWarnings({"unchecked"})
2900
public <T extends TopLevelItem> T copy(T src, String name) throws IOException {
2901
return itemGroupMixIn.copy(src, name);
2904
// a little more convenient overloading that assumes the caller gives us the right type
2905
// (or else it will fail with ClassCastException)
2906
public <T extends AbstractProject<?,?>> T copy(T src, String name) throws IOException {
2907
return (T)copy((TopLevelItem)src,name);
2910
public synchronized void doCreateView( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, FormException {
2911
checkPermission(View.CREATE);
2912
addView(View.create(req,rsp, this));
2916
* Check if the given name is suitable as a name
2917
* for job, view, etc.
2920
* if the given name is not good
2922
public static void checkGoodName(String name) throws Failure {
2923
if(name==null || name.length()==0)
2924
throw new Failure(Messages.Hudson_NoName());
2926
if("..".equals(name.trim()))
2927
throw new Failure(Messages.Jenkins_NotAllowedName(".."));
2928
for( int i=0; i<name.length(); i++ ) {
2929
char ch = name.charAt(i);
2930
if(Character.isISOControl(ch)) {
2931
throw new Failure(Messages.Hudson_ControlCodeNotAllowed(toPrintableName(name)));
2933
if("?*/\\%!@#$^&|<>[]:;".indexOf(ch)!=-1)
2934
throw new Failure(Messages.Hudson_UnsafeChar(ch));
2941
* Makes sure that the given name is good as a job name.
2942
* @return trimmed name if valid; throws Failure if not
2944
private String checkJobName(String name) throws Failure {
2945
checkGoodName(name);
2947
projectNamingStrategy.checkName(name);
2948
if(getItem(name)!=null)
2949
throw new Failure(Messages.Hudson_JobAlreadyExists(name));
2954
private static String toPrintableName(String name) {
2955
StringBuilder printableName = new StringBuilder();
2956
for( int i=0; i<name.length(); i++ ) {
2957
char ch = name.charAt(i);
2958
if(Character.isISOControl(ch))
2959
printableName.append("\\u").append((int)ch).append(';');
2961
printableName.append(ch);
2963
return printableName.toString();
2967
* Checks if the user was successfully authenticated.
2969
* @see BasicAuthenticationFilter
2971
public void doSecured( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
2972
if(req.getUserPrincipal()==null) {
2973
// authentication must have failed
2974
rsp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
2978
// the user is now authenticated, so send him back to the target
2979
String path = req.getContextPath()+req.getOriginalRestOfPath();
2980
String q = req.getQueryString();
2984
rsp.sendRedirect2(path);
2988
* Called once the user logs in. Just forward to the top page.
2990
public void doLoginEntry( StaplerRequest req, StaplerResponse rsp ) throws IOException {
2991
if(req.getUserPrincipal()==null) {
2992
rsp.sendRedirect2("noPrincipal");
2996
String from = req.getParameter("from");
2997
if(from!=null && from.startsWith("/") && !from.equals("/loginError")) {
2998
rsp.sendRedirect2(from); // I'm bit uncomfortable letting users redircted to other sites, make sure the URL falls into this domain
3002
String url = AbstractProcessingFilter.obtainFullRequestUrl(req);
3004
// if the login redirect is initiated by Acegi
3005
// this should send the user back to where s/he was from.
3006
rsp.sendRedirect2(url);
3010
rsp.sendRedirect2(".");
3014
* Logs out the user.
3016
public void doLogout( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
3017
securityRealm.doLogout(req, rsp);
3021
* Serves jar files for JNLP slave agents.
3023
public Slave.JnlpJar getJnlpJars(String fileName) {
3024
return new Slave.JnlpJar(fileName);
3027
public Slave.JnlpJar doJnlpJars(StaplerRequest req) {
3028
return new Slave.JnlpJar(req.getRestOfPath().substring(1));
3032
* Reloads the configuration.
3034
@CLIMethod(name="reload-configuration")
3036
public synchronized HttpResponse doReload() throws IOException {
3037
checkPermission(ADMINISTER);
3039
// engage "loading ..." UI and then run the actual task in a separate thread
3040
servletContext.setAttribute("app", new HudsonIsLoading());
3042
new Thread("Jenkins config reload thread") {
3046
ACL.impersonate(ACL.SYSTEM);
3048
} catch (Exception e) {
3049
LOGGER.log(SEVERE,"Failed to reload Jenkins config",e);
3050
WebApp.get(servletContext).setApp(new JenkinsReloadFailed(e));
3055
return HttpResponses.redirectViaContextPath("/");
3059
* Reloads the configuration synchronously.
3061
public void reload() throws IOException, InterruptedException, ReactorException {
3062
executeReactor(null, loadTasks());
3064
servletContext.setAttribute("app", this);
3068
* Do a finger-print check.
3070
public void doDoFingerprintCheck( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
3071
// Parse the request
3072
MultipartFormDataParser p = new MultipartFormDataParser(req);
3073
if(isUseCrumbs() && !getCrumbIssuer().validateCrumb(req, p)) {
3074
rsp.sendError(HttpServletResponse.SC_FORBIDDEN,"No crumb found");
3077
rsp.sendRedirect2(req.getContextPath()+"/fingerprint/"+
3078
Util.getDigestOf(p.getFileItem("name").getInputStream())+'/');
3085
* For debugging. Expose URL to perform GC.
3087
@edu.umd.cs.findbugs.annotations.SuppressWarnings("DM_GC")
3088
public void doGc(StaplerResponse rsp) throws IOException {
3089
checkPermission(Jenkins.ADMINISTER);
3091
rsp.setStatus(HttpServletResponse.SC_OK);
3092
rsp.setContentType("text/plain");
3093
rsp.getWriter().println("GCed");
3097
* End point that intentionally throws an exception to test the error behaviour.
3099
public void doException() {
3100
throw new RuntimeException();
3103
public ContextMenu doContextMenu(StaplerRequest request, StaplerResponse response) throws IOException, JellyException {
3104
ContextMenu menu = new ContextMenu().from(this, request, response);
3105
for (MenuItem i : menu.items) {
3106
if (i.url.equals(request.getContextPath() + "/manage")) {
3107
// add "Manage Jenkins" subitems
3108
i.subMenu = new ContextMenu().from(this, request, response, "manage");
3115
* Obtains the heap dump.
3117
public HeapDump getHeapDump() throws IOException {
3118
return new HeapDump(this,MasterComputer.localChannel);
3122
* Simulates OutOfMemoryError.
3123
* Useful to make sure OutOfMemoryHeapDump setting.
3125
public void doSimulateOutOfMemory() throws IOException {
3126
checkPermission(ADMINISTER);
3128
System.out.println("Creating artificial OutOfMemoryError situation");
3129
List<Object> args = new ArrayList<Object>();
3131
args.add(new byte[1024*1024]);
3134
private transient final Map<UUID,FullDuplexHttpChannel> duplexChannels = new HashMap<UUID, FullDuplexHttpChannel>();
3137
* Handles HTTP requests for duplex channels for CLI.
3139
public void doCli(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, InterruptedException {
3140
if (!"POST".equals(req.getMethod())) {
3141
// for GET request, serve _cli.jelly, assuming this is a browser
3142
checkPermission(READ);
3143
req.getView(this,"_cli.jelly").forward(req,rsp);
3147
// do not require any permission to establish a CLI connection
3148
// the actual authentication for the connecting Channel is done by CLICommand
3150
UUID uuid = UUID.fromString(req.getHeader("Session"));
3151
rsp.setHeader("Hudson-Duplex",""); // set the header so that the client would know
3153
FullDuplexHttpChannel server;
3154
if(req.getHeader("Side").equals("download")) {
3155
duplexChannels.put(uuid,server=new FullDuplexHttpChannel(uuid, !hasPermission(ADMINISTER)) {
3156
protected void main(Channel channel) throws IOException, InterruptedException {
3157
// capture the identity given by the transport, since this can be useful for SecurityRealm.createCliAuthenticator()
3158
channel.setProperty(CLICommand.TRANSPORT_AUTHENTICATION,getAuthentication());
3159
channel.setProperty(CliEntryPoint.class.getName(),new CliManagerImpl(channel));
3163
server.download(req,rsp);
3165
duplexChannels.remove(uuid);
3168
duplexChannels.get(uuid).upload(req,rsp);
3173
* Binds /userContent/... to $JENKINS_HOME/userContent.
3175
public DirectoryBrowserSupport doUserContent() {
3176
return new DirectoryBrowserSupport(this,getRootPath().child("userContent"),"User content","folder.png",true);
3180
* Perform a restart of Hudson, if we can.
3182
* This first replaces "app" to {@link HudsonIsRestarting}
3184
@CLIMethod(name="restart")
3185
public void doRestart(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, RestartNotSupportedException {
3186
checkPermission(ADMINISTER);
3187
if (req != null && req.getMethod().equals("GET")) {
3188
req.getView(this,"_restart.jelly").forward(req,rsp);
3194
if (rsp != null) // null for CLI
3195
rsp.sendRedirect2(".");
3199
* Queues up a restart of Hudson for when there are no builds running, if we can.
3201
* This first replaces "app" to {@link HudsonIsRestarting}
3205
@CLIMethod(name="safe-restart")
3206
public HttpResponse doSafeRestart(StaplerRequest req) throws IOException, ServletException, RestartNotSupportedException {
3207
checkPermission(ADMINISTER);
3208
if (req != null && req.getMethod().equals("GET"))
3209
return HttpResponses.forwardToView(this,"_safeRestart.jelly");
3213
return HttpResponses.redirectToDot();
3217
* Performs a restart.
3219
public void restart() throws RestartNotSupportedException {
3220
final Lifecycle lifecycle = Lifecycle.get();
3221
lifecycle.verifyRestartable(); // verify that Hudson is restartable
3222
servletContext.setAttribute("app", new HudsonIsRestarting());
3224
new Thread("restart thread") {
3225
final String exitUser = getAuthentication().getName();
3229
ACL.impersonate(ACL.SYSTEM);
3231
// give some time for the browser to load the "reloading" page
3233
LOGGER.severe(String.format("Restarting VM as requested by %s",exitUser));
3234
for (RestartListener listener : RestartListener.all())
3235
listener.onRestart();
3236
lifecycle.restart();
3237
} catch (InterruptedException e) {
3238
LOGGER.log(Level.WARNING, "Failed to restart Hudson",e);
3239
} catch (IOException e) {
3240
LOGGER.log(Level.WARNING, "Failed to restart Hudson",e);
3247
* Queues up a restart to be performed once there are no builds currently running.
3250
public void safeRestart() throws RestartNotSupportedException {
3251
final Lifecycle lifecycle = Lifecycle.get();
3252
lifecycle.verifyRestartable(); // verify that Hudson is restartable
3253
// Quiet down so that we won't launch new builds.
3254
isQuietingDown = true;
3256
new Thread("safe-restart thread") {
3257
final String exitUser = getAuthentication().getName();
3261
ACL.impersonate(ACL.SYSTEM);
3263
// Wait 'til we have no active executors.
3264
doQuietDown(true, 0);
3266
// Make sure isQuietingDown is still true.
3267
if (isQuietingDown) {
3268
servletContext.setAttribute("app",new HudsonIsRestarting());
3269
// give some time for the browser to load the "reloading" page
3270
LOGGER.info("Restart in 10 seconds");
3271
Thread.sleep(10000);
3272
LOGGER.severe(String.format("Restarting VM as requested by %s",exitUser));
3273
for (RestartListener listener : RestartListener.all())
3274
listener.onRestart();
3275
lifecycle.restart();
3277
LOGGER.info("Safe-restart mode cancelled");
3279
} catch (InterruptedException e) {
3280
LOGGER.log(Level.WARNING, "Failed to restart Hudson",e);
3281
} catch (IOException e) {
3282
LOGGER.log(Level.WARNING, "Failed to restart Hudson",e);
3289
* Shutdown the system.
3292
@CLIMethod(name="shutdown")
3293
public void doExit( StaplerRequest req, StaplerResponse rsp ) throws IOException {
3294
checkPermission(ADMINISTER);
3295
LOGGER.severe(String.format("Shutting down VM as requested by %s from %s",
3296
getAuthentication().getName(), req!=null?req.getRemoteAddr():"???"));
3298
rsp.setStatus(HttpServletResponse.SC_OK);
3299
rsp.setContentType("text/plain");
3300
PrintWriter w = rsp.getWriter();
3301
w.println("Shutting down");
3310
* Shutdown the system safely.
3313
@CLIMethod(name="safe-shutdown")
3314
public HttpResponse doSafeExit(StaplerRequest req) throws IOException {
3315
checkPermission(ADMINISTER);
3316
isQuietingDown = true;
3317
final String exitUser = getAuthentication().getName();
3318
final String exitAddr = req!=null ? req.getRemoteAddr() : "unknown";
3319
new Thread("safe-exit thread") {
3323
ACL.impersonate(ACL.SYSTEM);
3324
LOGGER.severe(String.format("Shutting down VM as requested by %s from %s",
3325
exitUser, exitAddr));
3326
// Wait 'til we have no active executors.
3327
while (isQuietingDown
3328
&& (overallLoad.computeTotalExecutors() > overallLoad.computeIdleExecutors())) {
3331
// Make sure isQuietingDown is still true.
3332
if (isQuietingDown) {
3336
} catch (InterruptedException e) {
3337
LOGGER.log(Level.WARNING, "Failed to shutdown Hudson",e);
3342
return HttpResponses.plainText("Shutting down as soon as all jobs are complete");
3346
* Gets the {@link Authentication} object that represents the user
3347
* associated with the current request.
3349
public static Authentication getAuthentication() {
3350
Authentication a = SecurityContextHolder.getContext().getAuthentication();
3351
// on Tomcat while serving the login page, this is null despite the fact
3352
// that we have filters. Looking at the stack trace, Tomcat doesn't seem to
3353
// run the request through filters when this is the login request.
3354
// see http://www.nabble.com/Matrix-authorization-problem-tp14602081p14886312.html
3361
* For system diagnostics.
3362
* Run arbitrary Groovy script.
3364
public void doScript(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
3365
_doScript(req, rsp, req.getView(this, "_script.jelly"), MasterComputer.localChannel, getACL());
3369
* Run arbitrary Groovy script and return result as plain text.
3371
public void doScriptText(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
3372
_doScript(req, rsp, req.getView(this, "_scriptText.jelly"), MasterComputer.localChannel, getACL());
3378
public static void _doScript(StaplerRequest req, StaplerResponse rsp, RequestDispatcher view, VirtualChannel channel, ACL acl) throws IOException, ServletException {
3379
// ability to run arbitrary script is dangerous
3380
acl.checkPermission(RUN_SCRIPTS);
3382
String text = req.getParameter("script");
3384
if (!"POST".equals(req.getMethod())) {
3385
throw HttpResponses.error(HttpURLConnection.HTTP_BAD_METHOD, "requires POST");
3388
req.setAttribute("output",
3389
RemotingDiagnostics.executeGroovy(text, channel));
3390
} catch (InterruptedException e) {
3391
throw new ServletException(e);
3395
view.forward(req, rsp);
3399
* Evaluates the Jelly script submitted by the client.
3401
* This is useful for system administration as well as unit testing.
3404
public void doEval(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
3405
checkPermission(RUN_SCRIPTS);
3408
MetaClass mc = WebApp.getCurrent().getMetaClass(getClass());
3409
Script script = mc.classLoader.loadTearOff(JellyClassLoaderTearOff.class).createContext().compileScript(new InputSource(req.getReader()));
3410
new JellyRequestDispatcher(this,script).forward(req,rsp);
3411
} catch (JellyException e) {
3412
throw new ServletException(e);
3417
* Sign up for the user account.
3419
public void doSignup( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
3420
req.getView(getSecurityRealm(), "signup.jelly").forward(req, rsp);
3424
* Changes the icon size by changing the cookie
3426
public void doIconSize( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
3427
String qs = req.getQueryString();
3428
if(qs==null || !ICON_SIZE.matcher(qs).matches())
3429
throw new ServletException();
3430
Cookie cookie = new Cookie("iconSize", qs);
3431
cookie.setMaxAge(/* ~4 mo. */9999999); // #762
3432
rsp.addCookie(cookie);
3433
String ref = req.getHeader("Referer");
3434
if(ref==null) ref=".";
3435
rsp.sendRedirect2(ref);
3438
public void doFingerprintCleanup(StaplerResponse rsp) throws IOException {
3439
FingerprintCleanupThread.invoke();
3440
rsp.setStatus(HttpServletResponse.SC_OK);
3441
rsp.setContentType("text/plain");
3442
rsp.getWriter().println("Invoked");
3445
public void doWorkspaceCleanup(StaplerResponse rsp) throws IOException {
3446
WorkspaceCleanupThread.invoke();
3447
rsp.setStatus(HttpServletResponse.SC_OK);
3448
rsp.setContentType("text/plain");
3449
rsp.getWriter().println("Invoked");
3453
* If the user chose the default JDK, make sure we got 'java' in PATH.
3455
public FormValidation doDefaultJDKCheck(StaplerRequest request, @QueryParameter String value) {
3456
if(!value.equals("(Default)"))
3457
// assume the user configured named ones properly in system config ---
3458
// or else system config should have reported form field validation errors.
3459
return FormValidation.ok();
3461
// default JDK selected. Does such java really exist?
3462
if(JDK.isDefaultJDKValid(Jenkins.this))
3463
return FormValidation.ok();
3465
return FormValidation.errorWithMarkup(Messages.Hudson_NoJavaInPath(request.getContextPath()));
3469
* Makes sure that the given name is good as a job name.
3471
public FormValidation doCheckJobName(@QueryParameter String value) {
3472
// this method can be used to check if a file exists anywhere in the file system,
3473
// so it should be protected.
3474
checkPermission(Item.CREATE);
3476
if(fixEmpty(value)==null)
3477
return FormValidation.ok();
3480
checkJobName(value);
3481
return FormValidation.ok();
3482
} catch (Failure e) {
3483
return FormValidation.error(e.getMessage());
3488
* Checks if a top-level view with the given name exists.
3490
public FormValidation doViewExistsCheck(@QueryParameter String value) {
3491
checkPermission(View.CREATE);
3493
String view = fixEmpty(value);
3494
if(view==null) return FormValidation.ok();
3496
if(getView(view)==null)
3497
return FormValidation.ok();
3499
return FormValidation.error(Messages.Hudson_ViewAlreadyExists(view));
3503
* Serves static resources placed along with Jelly view files.
3505
* This method can serve a lot of files, so care needs to be taken
3506
* to make this method secure. It's not clear to me what's the best
3507
* strategy here, though the current implementation is based on
3510
public void doResources(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
3511
String path = req.getRestOfPath();
3512
// cut off the "..." portion of /resources/.../path/to/file
3513
// as this is only used to make path unique (which in turn
3514
// allows us to set a long expiration date
3515
path = path.substring(path.indexOf('/',1)+1);
3517
int idx = path.lastIndexOf('.');
3518
String extension = path.substring(idx+1);
3519
if(ALLOWED_RESOURCE_EXTENSIONS.contains(extension)) {
3520
URL url = pluginManager.uberClassLoader.getResource(path);
3522
long expires = MetaClass.NO_CACHE ? 0 : 365L * 24 * 60 * 60 * 1000; /*1 year*/
3523
rsp.serveFile(req,url,expires);
3527
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
3531
* Extension list that {@link #doResources(StaplerRequest, StaplerResponse)} can serve.
3532
* This set is mutable to allow plugins to add additional extensions.
3534
public static final Set<String> ALLOWED_RESOURCE_EXTENSIONS = new HashSet<String>(Arrays.asList(
3535
"js|css|jpeg|jpg|png|gif|html|htm".split("\\|")
3539
* Checks if container uses UTF-8 to decode URLs. See
3540
* http://wiki.jenkins-ci.org/display/JENKINS/Tomcat#Tomcat-i18n
3542
public FormValidation doCheckURIEncoding(StaplerRequest request) throws IOException {
3543
// expected is non-ASCII String
3544
final String expected = "\u57f7\u4e8b";
3545
final String value = fixEmpty(request.getParameter("value"));
3546
if (!expected.equals(value))
3547
return FormValidation.warningWithMarkup(Messages.Hudson_NotUsesUTF8ToDecodeURL());
3548
return FormValidation.ok();
3552
* Does not check when system default encoding is "ISO-8859-1".
3554
public static boolean isCheckURIEncodingEnabled() {
3555
return !"ISO-8859-1".equalsIgnoreCase(System.getProperty("file.encoding"));
3559
* Rebuilds the dependency map.
3561
public void rebuildDependencyGraph() {
3562
DependencyGraph graph = new DependencyGraph();
3564
// volatile acts a as a memory barrier here and therefore guarantees
3565
// that graph is fully build, before it's visible to other threads
3566
dependencyGraph = graph;
3569
public DependencyGraph getDependencyGraph() {
3570
return dependencyGraph;
3574
public List<ManagementLink> getManagementLinks() {
3575
return ManagementLink.all();
3579
* Exposes the current user to <tt>/me</tt> URL.
3581
public User getMe() {
3582
User u = User.current();
3584
throw new AccessDeniedException("/me is not available when not logged in");
3589
* Gets the {@link Widget}s registered on this object.
3592
* Plugins who wish to contribute boxes on the side panel can add widgets
3593
* by {@code getWidgets().add(new MyWidget())} from {@link Plugin#start()}.
3595
public List<Widget> getWidgets() {
3599
public Object getTarget() {
3601
checkPermission(READ);
3602
} catch (AccessDeniedException e) {
3603
String rest = Stapler.getCurrentRequest().getRestOfPath();
3604
if(rest.startsWith("/login")
3605
|| rest.startsWith("/logout")
3606
|| rest.startsWith("/accessDenied")
3607
|| rest.startsWith("/adjuncts/")
3608
|| rest.startsWith("/signup")
3609
|| rest.startsWith("/tcpSlaveAgentListener")
3610
// XXX SlaveComputer.doSlaveAgentJnlp; there should be an annotation to request unprotected access
3611
|| rest.matches("/computer/[^/]+/slave-agent[.]jnlp") && "true".equals(Stapler.getCurrentRequest().getParameter("encrypt"))
3612
|| rest.startsWith("/cli")
3613
|| rest.startsWith("/federatedLoginService/")
3614
|| rest.startsWith("/securityRealm"))
3615
return this; // URLs that are always visible without READ permission
3617
for (String name : getUnprotectedRootActions()) {
3618
if (rest.startsWith("/" + name + "/") || rest.equals("/" + name)) {
3629
* Gets a list of unprotected root actions.
3630
* These URL prefixes should be exempted from access control checks by container-managed security.
3631
* Ideally would be synchronized with {@link #getTarget}.
3632
* @return a list of {@linkplain Action#getUrlName URL names}
3635
public Collection<String> getUnprotectedRootActions() {
3636
Set<String> names = new TreeSet<String>();
3637
names.add("jnlpJars"); // XXX cleaner to refactor doJnlpJars into a URA
3638
// XXX consider caching (expiring cache when actions changes)
3639
for (Action a : getActions()) {
3640
if (a instanceof UnprotectedRootAction) {
3641
names.add(a.getUrlName());
3648
* Fallback to the primary view.
3650
public View getStaplerFallback() {
3651
return getPrimaryView();
3655
* This method checks all existing jobs to see if displayName is
3656
* unique. It does not check the displayName against the displayName of the
3657
* job that the user is configuring though to prevent a validation warning
3658
* if the user sets the displayName to what it currently is.
3659
* @param displayName
3660
* @param currentJobName
3663
boolean isDisplayNameUnique(String displayName, String currentJobName) {
3664
Collection<TopLevelItem> itemCollection = items.values();
3666
// if there are a lot of projects, we'll have to store their
3667
// display names in a HashSet or something for a quick check
3668
for(TopLevelItem item : itemCollection) {
3669
if(item.getName().equals(currentJobName)) {
3670
// we won't compare the candidate displayName against the current
3671
// item. This is to prevent an validation warning if the user
3672
// sets the displayName to what the existing display name is
3675
else if(displayName.equals(item.getDisplayName())) {
3684
* True if there is no item in Jenkins that has this name
3685
* @param name The name to test
3686
* @param currentJobName The name of the job that the user is configuring
3689
boolean isNameUnique(String name, String currentJobName) {
3690
Item item = getItem(name);
3693
// the candidate name didn't return any items so the name is unique
3696
else if(item.getName().equals(currentJobName)) {
3697
// the candidate name returned an item, but the item is the item
3698
// that the user is configuring so this is ok
3702
// the candidate name returned an item, so it is not unique
3708
* Checks to see if the candidate displayName collides with any
3709
* existing display names or project names
3710
* @param displayName The display name to test
3711
* @param jobName The name of the job the user is configuring
3714
public FormValidation doCheckDisplayName(@QueryParameter String displayName,
3715
@QueryParameter String jobName) {
3716
displayName = displayName.trim();
3718
if(LOGGER.isLoggable(Level.FINE)) {
3719
LOGGER.log(Level.FINE, "Current job name is " + jobName);
3722
if(!isNameUnique(displayName, jobName)) {
3723
return FormValidation.warning(Messages.Jenkins_CheckDisplayName_NameNotUniqueWarning(displayName));
3725
else if(!isDisplayNameUnique(displayName, jobName)){
3726
return FormValidation.warning(Messages.Jenkins_CheckDisplayName_DisplayNameNotUniqueWarning(displayName));
3729
return FormValidation.ok();
3733
public static class MasterComputer extends Computer {
3734
protected MasterComputer() {
3735
super(Jenkins.getInstance());
3739
* Returns "" to match with {@link Jenkins#getNodeName()}.
3742
public String getName() {
3747
public boolean isConnecting() {
3752
public String getDisplayName() {
3753
return Messages.Hudson_Computer_DisplayName();
3757
public String getCaption() {
3758
return Messages.Hudson_Computer_Caption();
3762
public String getUrl() {
3763
return "computer/(master)/";
3766
public RetentionStrategy getRetentionStrategy() {
3767
return RetentionStrategy.NOOP;
3774
public HttpResponse doDoDelete() throws IOException {
3775
throw HttpResponses.status(SC_BAD_REQUEST);
3779
public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException {
3780
Jenkins.getInstance().doConfigExecutorsSubmit(req, rsp);
3784
public boolean hasPermission(Permission permission) {
3785
// no one should be allowed to delete the master.
3786
// this hides the "delete" link from the /computer/(master) page.
3787
if(permission==Computer.DELETE)
3789
// Configuration of master node requires ADMINISTER permission
3790
return super.hasPermission(permission==Computer.CONFIGURE ? Jenkins.ADMINISTER : permission);
3794
public VirtualChannel getChannel() {
3795
return localChannel;
3799
public Charset getDefaultCharset() {
3800
return Charset.defaultCharset();
3803
public List<LogRecord> getLogRecords() throws IOException, InterruptedException {
3807
public void doLaunchSlaveAgent(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
3808
// this computer never returns null from channel, so
3809
// this method shall never be invoked.
3810
rsp.sendError(SC_NOT_FOUND);
3813
protected Future<?> _connect(boolean forceReconnect) {
3814
return Futures.precomputed(null);
3818
* {@link LocalChannel} instance that can be used to execute programs locally.
3820
public static final LocalChannel localChannel = new LocalChannel(threadPoolForRemoting);
3824
* Shortcut for {@code Hudson.getInstance().lookup.get(type)}
3826
public static <T> T lookup(Class<T> type) {
3827
return Jenkins.getInstance().lookup.get(type);
3831
* Live view of recent {@link LogRecord}s produced by Hudson.
3833
public static List<LogRecord> logRecords = Collections.emptyList(); // initialized to dummy value to avoid NPE
3836
* Thread-safe reusable {@link XStream}.
3838
public static final XStream XSTREAM = new XStream2();
3841
* Alias to {@link #XSTREAM} so that one can access additional methods on {@link XStream2} more easily.
3843
public static final XStream2 XSTREAM2 = (XStream2)XSTREAM;
3845
private static final int TWICE_CPU_NUM = Math.max(4, Runtime.getRuntime().availableProcessors() * 2);
3848
* Thread pool used to load configuration in parallel, to improve the start up time.
3850
* The idea here is to overlap the CPU and I/O, so we want more threads than CPU numbers.
3852
/*package*/ transient final ExecutorService threadPoolForLoad = new ThreadPoolExecutor(
3853
TWICE_CPU_NUM, TWICE_CPU_NUM,
3854
5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new DaemonThreadFactory());
3857
private static void computeVersion(ServletContext context) {
3859
Properties props = new Properties();
3861
InputStream is = Jenkins.class.getResourceAsStream("jenkins-version.properties");
3864
} catch (IOException e) {
3865
e.printStackTrace(); // if the version properties is missing, that's OK.
3867
String ver = props.getProperty("version");
3868
if(ver==null) ver="?";
3870
context.setAttribute("version",ver);
3872
VERSION_HASH = Util.getDigestOf(ver).substring(0, 8);
3873
SESSION_HASH = Util.getDigestOf(ver+System.currentTimeMillis()).substring(0, 8);
3875
if(ver.equals("?") || Boolean.getBoolean("hudson.script.noCache"))
3878
RESOURCE_PATH = "/static/"+SESSION_HASH;
3880
VIEW_RESOURCE_PATH = "/resources/"+ SESSION_HASH;
3884
* Version number of this Hudson.
3886
public static String VERSION="?";
3889
* Parses {@link #VERSION} into {@link VersionNumber}, or null if it's not parseable as a version number
3890
* (such as when Hudson is run with "mvn hudson-dev:run")
3892
public static VersionNumber getVersion() {
3894
return new VersionNumber(VERSION);
3895
} catch (NumberFormatException e) {
3897
// for non-released version of Hudson, this looks like "1.345 (private-foobar), so try to approximate.
3898
int idx = VERSION.indexOf(' ');
3900
return new VersionNumber(VERSION.substring(0,idx));
3901
} catch (NumberFormatException _) {
3905
// totally unparseable
3907
} catch (IllegalArgumentException e) {
3908
// totally unparseable
3914
* Hash of {@link #VERSION}.
3916
public static String VERSION_HASH;
3919
* Unique random token that identifies the current session.
3920
* Used to make {@link #RESOURCE_PATH} unique so that we can set long "Expires" header.
3922
* We used to use {@link #VERSION_HASH}, but making this session local allows us to
3923
* reuse the same {@link #RESOURCE_PATH} for static resources in plugins.
3925
public static String SESSION_HASH;
3928
* Prefix to static resources like images and javascripts in the war file.
3929
* Either "" or strings like "/static/VERSION", which avoids Hudson to pick up
3930
* stale cache when the user upgrades to a different version.
3932
* Value computed in {@link WebAppMain}.
3934
public static String RESOURCE_PATH = "";
3937
* Prefix to resources alongside view scripts.
3938
* Strings like "/resources/VERSION", which avoids Hudson to pick up
3939
* stale cache when the user upgrades to a different version.
3941
* Value computed in {@link WebAppMain}.
3943
public static String VIEW_RESOURCE_PATH = "/resources/TBD";
3945
public static boolean PARALLEL_LOAD = Configuration.getBooleanConfigParameter("parallelLoad", true);
3946
public static boolean KILL_AFTER_LOAD = Configuration.getBooleanConfigParameter("killAfterLoad", false);
3948
* Enabled by default as of 1.337. Will keep it for a while just in case we have some serious problems.
3950
public static boolean FLYWEIGHT_SUPPORT = Configuration.getBooleanConfigParameter("flyweightSupport", true);
3953
* Tentative switch to activate the concurrent build behavior.
3954
* When we merge this back to the trunk, this allows us to keep
3955
* this feature hidden for a while until we iron out the kinks.
3956
* @see AbstractProject#isConcurrentBuild()
3957
* @deprecated as of 1.464
3958
* This flag will have no effect.
3960
@Restricted(NoExternalUse.class)
3961
public static boolean CONCURRENT_BUILD = true;
3964
* Switch to enable people to use a shorter workspace name.
3966
private static final String WORKSPACE_DIRNAME = Configuration.getStringConfigParameter("workspaceDirName", "workspace");
3969
* Automatically try to launch a slave when Jenkins is initialized or a new slave is created.
3971
public static boolean AUTOMATIC_SLAVE_LAUNCH = true;
3973
private static final Logger LOGGER = Logger.getLogger(Jenkins.class.getName());
3975
private static final Pattern ICON_SIZE = Pattern.compile("\\d+x\\d+");
3977
public static final PermissionGroup PERMISSIONS = Permission.HUDSON_PERMISSIONS;
3978
public static final Permission ADMINISTER = Permission.HUDSON_ADMINISTER;
3979
public static final Permission READ = new Permission(PERMISSIONS,"Read",Messages._Hudson_ReadPermission_Description(),Permission.READ,PermissionScope.JENKINS);
3980
public static final Permission RUN_SCRIPTS = new Permission(PERMISSIONS, "RunScripts", Messages._Hudson_RunScriptsPermission_Description(),ADMINISTER,PermissionScope.JENKINS);
3983
* {@link Authentication} object that represents the anonymous user.
3984
* Because Acegi creates its own {@link AnonymousAuthenticationToken} instances, the code must not
3985
* expect the singleton semantics. This is just a convenient instance.
3989
public static final Authentication ANONYMOUS = new AnonymousAuthenticationToken(
3990
"anonymous","anonymous",new GrantedAuthority[]{new GrantedAuthorityImpl("anonymous")});
3993
XSTREAM.alias("jenkins",Jenkins.class);
3994
XSTREAM.alias("slave", DumbSlave.class);
3995
XSTREAM.alias("jdk",JDK.class);
3996
// for backward compatibility with <1.75, recognize the tag name "view" as well.
3997
XSTREAM.alias("view", ListView.class);
3998
XSTREAM.alias("listView", ListView.class);
3999
// this seems to be necessary to force registration of converter early enough
4000
Mode.class.getEnumConstants();
4002
// double check that initialization order didn't do any harm
4003
assert PERMISSIONS!=null;
4004
assert ADMINISTER!=null;