提交 f69080ea 编写于 作者: G gusreiber

merging 2.0

......@@ -54,12 +54,21 @@ Upcoming changes</a>
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class="bug">
Under some conditions Jenkins startup could fail because of incorrectly linked extensions; now recovering more gracefully.
(<a href-"https://issues.jenkins-ci.org/browse/JENKINS-25440">issue 25440</a>)
</ul>
</div><!--=TRUNK-END=-->
<h3><a name=v1.651>What's new in 1.651</a> (2016/02/28)</h3>
<ul class=image>
<li class="rfe">
Move periodic task log files from <code>JENKINS_HOME/*.log</code> to <code>JENKINS_HOME/logs/tasks/*.log</code> and rotate them periodically rather than overwrite every execution
Move periodic task log files from <code>JENKINS_HOME/*.log</code> to <code>JENKINS_HOME/logs/tasks/*.log</code> and rotate them periodically rather than overwrite every execution.
(<a href-"https://issues.jenkins-ci.org/browse/JENKINS-33068">issue 33068</a>)
<li class="bug">
Fix documentation of proxy configuration.
(<a href="https://github.com/jenkinsci/jenkins/pull/2060">pull 2060</a>)
</ul>
</div><!--=TRUNK-END=-->
<h3><a name=v1.650>What's new in 1.650</a> (2016/02/24)</h3>
<ul class=image>
<li class="major bug">
......
......@@ -39,7 +39,7 @@ THE SOFTWARE.
<properties>
<staplerFork>true</staplerFork>
<stapler.version>1.239</stapler.version>
<stapler.version>1.240</stapler.version>
<spring.version>2.5.6.SEC03</spring.version>
<groovy.version>1.8.9</groovy.version>
</properties>
......
......@@ -282,7 +282,6 @@ public abstract class ExtensionFinder implements ExtensionPoint {
LOGGER.log(Level.SEVERE, "Failed to create Guice container from all the plugins",e);
// failing to load all bindings are disastrous, so recover by creating minimum that works
// by just including the core
// TODO this recovery is pretty much useless; startup crashes anyway
container = Guice.createInjector(new SezpozModule(loadSezpozIndices(Jenkins.class.getClassLoader())));
}
......@@ -479,7 +478,11 @@ public abstract class ExtensionFinder implements ExtensionPoint {
m.invoke(ecl, c);
c.getConstructors();
c.getMethods();
c.getFields();
for (Field f : c.getFields()) {
if (f.getAnnotation(javax.inject.Inject.class) != null || f.getAnnotation(com.google.inject.Inject.class) != null) {
resolve(f.getType());
}
}
LOGGER.log(Level.FINER, "{0} looks OK", c);
while (c != Object.class) {
c.getGenericSuperclass();
......
......@@ -1189,7 +1189,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
break;
}
updateCenter.persistInstallStatus();
jenkins.setInstallState(InstallState.INITIAL_PLUGINS_INSTALLED);
jenkins.setInstallState(InstallState.INITIAL_PLUGINS_INSTALLING.getNextState());
InstallUtil.saveLastExecVersion();
}
}.start();
......
......@@ -57,7 +57,8 @@ import jenkins.install.InstallState;
import jenkins.install.InstallUtil;
import jenkins.model.Jenkins;
import jenkins.util.io.OnMaster;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.acegisecurity.context.SecurityContext;
import org.apache.commons.codec.binary.Base64;
......@@ -73,7 +74,6 @@ import javax.annotation.Nonnull;
import javax.net.ssl.SSLHandshakeException;
import javax.servlet.ServletException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
......@@ -81,12 +81,12 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.security.DigestInputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
......@@ -131,7 +131,7 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
*/
@ExportedBean
public class UpdateCenter extends AbstractModelObject implements Saveable, OnMaster {
private static final String UPDATE_CENTER_URL = System.getProperty(UpdateCenter.class.getName()+".updateCenterUrl","http://updates.jenkins-ci.org/");
/**
......@@ -142,7 +142,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
@Restricted(NoExternalUse.class)
public static final String ID_UPLOAD = "_upload";
/**
* {@link ExecutorService} that performs installation.
* @since 1.501
......@@ -155,7 +155,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
*/
protected final ExecutorService updateService = Executors.newCachedThreadPool(
new NamingThreadFactory(new DaemonThreadFactory(), "Update site data downloader"));
/**
* List of created {@link UpdateCenterJob}s. Access needs to be synchronized.
*/
......@@ -302,6 +302,27 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
}
}
if (checkJob != null) {
boolean isOffline = false;
for (ConnectionStatus status : checkJob.connectionStates.values()) {
if(ConnectionStatus.FAILED.equals(status)) {
isOffline = true;
break;
}
}
if (isOffline) {
// retry connection states if determined to be offline
checkJob.run();
isOffline = false;
for (ConnectionStatus status : checkJob.connectionStates.values()) {
if(ConnectionStatus.FAILED.equals(status)) {
isOffline = true;
break;
}
}
if(!isOffline) { // also need to download the metadata
updateAllSites();
}
}
return HttpResponses.okJSON(checkJob.connectionStates);
} else {
return HttpResponses.errorJSON(String.format("Unknown site '%s'.", siteId));
......@@ -311,29 +332,16 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
}
}
/**
* Called to bypass install wizard
*/
@Restricted(DoNotUse.class) // WebOnly
public HttpResponse doCompleteInstall() {
if(isRestartRequiredForCompletion()) {
Jenkins.getActiveInstance().setInstallState(InstallState.RESTART);
}
InstallUtil.saveLastExecVersion();
Jenkins.getActiveInstance().setInstallState(InstallState.INITIAL_PLUGINS_INSTALLED);
return HttpResponses.okJSON();
}
/**
* Called to determine if there was an incomplete installation, what the statuses of the plugins are
*/
@Restricted(DoNotUse.class) // WebOnly
public HttpResponse doIncompleteInstallStatus() {
try {
Map<String,String> jobs = InstallUtil.getPersistedInstallStatus();
if(jobs == null) {
jobs = Collections.emptyMap();
}
Map<String,String> jobs = InstallUtil.getPersistedInstallStatus();
if(jobs == null) {
jobs = Collections.emptyMap();
}
return HttpResponses.okJSON(jobs);
} catch (Exception e) {
return HttpResponses.errorJSON(String.format("ERROR: %s", e.getMessage()));
......@@ -353,16 +361,16 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
if (job instanceof InstallationJob) {
InstallationJob installationJob = (InstallationJob) job;
if(!installationJob.status.isSuccess()) {
activeInstalls = true;
activeInstalls = true;
}
}
}
if(activeInstalls) {
InstallUtil.persistInstallStatus(jobs); // save this info
InstallUtil.persistInstallStatus(jobs); // save this info
}
else {
InstallUtil.clearInstallStatus(); // clear this info
InstallUtil.clearInstallStatus(); // clear this info
}
}
......@@ -379,7 +387,10 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
public HttpResponse doInstallStatus(StaplerRequest request) {
try {
String correlationId = request.getParameter("correlationId");
Map<String,Object> response = new HashMap<>();
response.put("state", Jenkins.getInstance().getInstallState().name());
List<Map<String, String>> installStates = new ArrayList<>();
response.put("jobs", installStates);
List<UpdateCenterJob> jobCopy = getJobs();
for (UpdateCenterJob job : jobCopy) {
......@@ -400,7 +411,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
}
}
}
return HttpResponses.okJSON(JSONArray.fromObject(installStates));
return HttpResponses.okJSON(JSONObject.fromObject(response));
} catch (Exception e) {
return HttpResponses.errorJSON(String.format("ERROR: %s", e.getMessage()));
}
......@@ -558,7 +569,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
}
response.sendRedirect2(".");
}
/**
* Cancel all scheduled jenkins restarts
*/
......@@ -845,16 +856,16 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
return new ArrayList<Plugin>(pluginMap.values());
}
/**
* Ensure that all UpdateSites are up to date, without requiring a user to
* browse to the instance.
*
*
* @return a list of {@link FormValidation} for each updated Update Site
* @throws ExecutionException
* @throws InterruptedException
* @throws ExecutionException
* @throws InterruptedException
* @since 1.501
*
*
*/
public List<FormValidation> updateAllSites() throws InterruptedException, ExecutionException {
List <Future<FormValidation>> futures = new ArrayList<Future<FormValidation>>();
......@@ -864,8 +875,8 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
futures.add(future);
}
}
List<FormValidation> results = new ArrayList<FormValidation>();
List<FormValidation> results = new ArrayList<FormValidation>();
for (Future<FormValidation> f : futures) {
results.add(f.get());
}
......@@ -1211,7 +1222,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
public String getErrorMessage() {
return error != null ? error.getMessage() : null;
}
public Throwable getError() {
return error;
}
......@@ -1226,10 +1237,10 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
*/
@Exported(inline=true)
public volatile RestartJenkinsJobStatus status = new Pending();
/**
* Cancel job
*/
*/
public synchronized boolean cancel() {
if (status instanceof Pending) {
status = new Canceled();
......@@ -1237,7 +1248,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
}
return false;
}
public RestartJenkinsJob(UpdateSite site) {
super(site);
}
......@@ -1260,26 +1271,26 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
public abstract class RestartJenkinsJobStatus {
@Exported
public final int id = iota.incrementAndGet();
}
public class Pending extends RestartJenkinsJobStatus {
@Exported
public String getType() {
return getClass().getSimpleName();
}
}
public class Running extends RestartJenkinsJobStatus {
}
public class Failure extends RestartJenkinsJobStatus {
}
public class Canceled extends RestartJenkinsJobStatus {
}
}
......@@ -1603,7 +1614,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
File baseDir = pm.rootDir;
return new File(baseDir, plugin.name + ".jpi");
}
private File getLegacyDestination() {
File baseDir = pm.rootDir;
return new File(baseDir, plugin.name + ".hpi");
......@@ -1649,7 +1660,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
public String toString() {
return super.toString()+"[plugin="+plugin.title+"]";
}
/**
* Called when the download is completed to overwrite
* the old file with the new file.
......@@ -1704,7 +1715,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
File baseDir = pm.rootDir;
final File legacy = new File(baseDir, plugin.name + ".hpi");
if(legacy.exists()){
return legacy;
return legacy;
}
return new File(baseDir, plugin.name + ".jpi");
}
......
......@@ -27,6 +27,7 @@ import hudson.Extension;
import hudson.ExtensionList;
import hudson.FilePath;
import hudson.Util;
import hudson.slaves.WorkspaceList;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
......@@ -89,6 +90,7 @@ public class WorkspaceCleanupThread extends AsyncPeriodicWork {
listener.getLogger().println("Deleting " + ws + " on " + node.getDisplayName());
try {
ws.deleteRecursive();
WorkspaceList.tempDir(ws).deleteRecursive();
} catch (IOException x) {
x.printStackTrace(listener.error("Failed to delete " + ws + " on " + node.getDisplayName()));
} catch (InterruptedException x) {
......
......@@ -29,6 +29,7 @@ import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import javax.inject.Inject;
import java.util.Collections;
......@@ -36,30 +37,52 @@ import java.util.List;
/**
* {@link AuthorizationStrategy} that grants full-control to authenticated user
* (other than anonymous users.)
* and optionally read access to anonymous users
*
* @author Kohsuke Kawaguchi
*/
public class FullControlOnceLoggedInAuthorizationStrategy extends AuthorizationStrategy {
/**
* Whether to allow anonymous read access, for backward compatibility
* default is to allow it
*/
private boolean denyAnonymousReadAccess = false;
@DataBoundConstructor
public FullControlOnceLoggedInAuthorizationStrategy() {
}
@Override
public ACL getRootACL() {
return THE_ACL;
return denyAnonymousReadAccess ? AUTHENTICATED_READ : ANONYMOUS_READ;
}
public List<String> getGroups() {
return Collections.emptyList();
}
/**
* If true, anonymous read access will be allowed
*/
public boolean isAllowAnonymousRead() {
return !denyAnonymousReadAccess;
}
@DataBoundSetter
public void setAllowAnonymousRead(boolean allowAnonymousRead) {
this.denyAnonymousReadAccess = !allowAnonymousRead;
}
private static final SparseACL THE_ACL = new SparseACL(null);
private static final SparseACL AUTHENTICATED_READ = new SparseACL(null);
private static final SparseACL ANONYMOUS_READ = new SparseACL(null);
static {
THE_ACL.add(ACL.EVERYONE, Jenkins.ADMINISTER,true);
THE_ACL.add(ACL.ANONYMOUS, Jenkins.ADMINISTER,false);
THE_ACL.add(ACL.ANONYMOUS,Permission.READ,true);
ANONYMOUS_READ.add(ACL.EVERYONE, Jenkins.ADMINISTER,true);
ANONYMOUS_READ.add(ACL.ANONYMOUS, Jenkins.ADMINISTER,false);
ANONYMOUS_READ.add(ACL.ANONYMOUS, Permission.READ,true);
AUTHENTICATED_READ.add(ACL.EVERYONE, Jenkins.ADMINISTER, true);
AUTHENTICATED_READ.add(ACL.ANONYMOUS, Jenkins.ADMINISTER, false);
}
/**
......
/*
* The MIT License
*
*
* Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, David Calavera, Seiji Sogabe
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
......@@ -29,6 +29,8 @@ import hudson.ExtensionList;
import hudson.Util;
import hudson.diagnosis.OldDataMonitor;
import hudson.model.Descriptor;
import jenkins.install.InstallState;
import jenkins.install.SetupWizard;
import jenkins.model.Jenkins;
import hudson.model.ManagementLink;
import hudson.model.ModelObject;
......@@ -97,7 +99,7 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
/**
* If true, sign up is not allowed.
* <p>
* This is a negative switch so that the default value 'false' remains compatible with older installations.
* This is a negative switch so that the default value 'false' remains compatible with older installations.
*/
private final boolean disableSignup;
......@@ -279,14 +281,36 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
* This can be run by anyone, but only to create the very first user account.
*/
public void doCreateFirstAccount(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
if(hasSomeUser()) {
boolean inSetup = !Jenkins.getInstance().getInstallState().isSetupComplete();
if(!inSetup && hasSomeUser()) {
rsp.sendError(SC_UNAUTHORIZED,"First user was already created");
return;
}
User u = createAccount(req, rsp, false, "firstUser.jelly");
if (u!=null) {
tryToMakeAdmin(u);
loginAndTakeBack(req, rsp, u);
User admin = null;
try {
String view = "firstUser.jelly";
if(inSetup) {
admin = getUser(SetupWizard.initialSetupAdminUserName);
if(admin != null) {
admin.delete(); // assume the new user may well be 'admin'
}
view = "setupWizardFirstUser.jelly";
}
User u = createAccount(req, rsp, false, view);
if (u!=null) {
tryToMakeAdmin(u);
if(admin != null) {
admin = null;
}
Jenkins.getInstance().setInstallState(InstallState.CREATE_ADMIN_USER.getNextState());
loginAndTakeBack(req, rsp, u);
}
} finally {
if(admin != null) {
admin.save(); // recreate this initial user if something failed
}
}
}
......
......@@ -26,6 +26,7 @@ package hudson.slaves;
import hudson.FilePath;
import hudson.Functions;
import hudson.model.Computer;
import hudson.model.DirectoryBrowserSupport;
import java.io.Closeable;
import java.util.Date;
......@@ -283,6 +284,25 @@ public final class WorkspaceList {
};
}
/**
* Locates a conventional temporary directory to be associated with a workspace.
* <p>This directory is suitable for temporary files to be deleted later in the course of a build,
* or caches and local repositories which should persist across builds done in the same workspace.
* (If multiple workspaces are present for a single job built concurrently, via {@link #allocate(FilePath)}, each will get its own temporary directory.)
* <p>It may also be used for security-sensitive files which {@link DirectoryBrowserSupport} ought not serve,
* acknowledging that these will be readable by builds of other jobs done on the same node.
* <p>Each plugin using this directory is responsible for specifying sufficiently unique subdirectory/file names.
* {@link FilePath#createTempFile} may be used for this purpose if desired.
* <p>The resulting directory may not exist; you may call {@link FilePath#mkdirs} on it if you need it to.
* It may be deleted alongside the workspace itself during cleanup actions.
* @param ws a directory such as a build workspace
* @return a sibling directory, for example {@code …/something@tmp} for {@code …/something}
* @since 1.652
*/
public static FilePath tempDir(FilePath ws) {
return ws.sibling(ws.getName() + COMBINATOR + "tmp");
}
private static final Logger LOGGER = Logger.getLogger(WorkspaceList.class.getName());
/**
......
......@@ -34,32 +34,63 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
@Restricted(NoExternalUse.class)
public enum InstallState {
/**
* New Jenkins install.
* The initial set up has been completed
*/
INITIAL_SETUP_COMPLETED(true, null),
/**
* Creating an admin user for an initial Jenkins install.
*/
NEW,
CREATE_ADMIN_USER(false, INITIAL_SETUP_COMPLETED),
/**
* New Jenkins install. The user has kicked off the process of installing an
* initial set of plugins (via the install wizard).
*/
INITIAL_PLUGINS_INSTALLING,
INITIAL_PLUGINS_INSTALLING(false, CREATE_ADMIN_USER),
/**
* New Jenkins install. The initial set of plugins are now installed.
* New Jenkins install.
*/
INITIAL_PLUGINS_INSTALLED,
NEW(false, INITIAL_PLUGINS_INSTALLING),
/**
* Restart of an existing Jenkins install.
*/
RESTART,
RESTART(true, INITIAL_SETUP_COMPLETED),
/**
* Upgrade of an existing Jenkins install.
*/
UPGRADE,
UPGRADE(true, INITIAL_SETUP_COMPLETED),
/**
* Downgrade of an existing Jenkins install.
*/
DOWNGRADE,
DOWNGRADE(true, INITIAL_SETUP_COMPLETED),
/**
* Jenkins started in test mode (JenkinsRule).
*/
TEST
TEST(true, INITIAL_SETUP_COMPLETED),
/**
* Jenkins started in development mode: Bolean.getBoolean("hudson.Main.development").
* Can be run normally with the -Djenkins.install.runSetupWizard=true
*/
DEVELOPMENT(true, INITIAL_SETUP_COMPLETED);
private final boolean isSetupComplete;
private final InstallState nextState;
private InstallState(boolean isSetupComplete, InstallState nextState) {
this.isSetupComplete = isSetupComplete;
this.nextState = nextState;
}
/**
* Indicates the initial setup is complete
*/
public boolean isSetupComplete() {
return isSetupComplete;
}
/**
* Gets the next state
*/
public InstallState getNextState() {
return nextState;
}
}
......@@ -61,6 +61,7 @@ public class InstallUtil {
private static final Logger LOGGER = Logger.getLogger(InstallUtil.class.getName());
// tests need this to be 1.0
private static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0");
/**
......@@ -68,8 +69,15 @@ public class InstallUtil {
* @return The type of "startup" currently under way in Jenkins.
*/
public static InstallState getInstallState() {
if (Functions.getIsUnitTest()) {
return InstallState.TEST;
// install wizard will always run if environment specified
if (!Boolean.getBoolean("jenkins.install.runSetupWizard")) {
if (Functions.getIsUnitTest()) {
return InstallState.TEST;
}
if (Boolean.getBoolean("hudson.Main.development")) {
return InstallState.DEVELOPMENT;
}
}
VersionNumber lastRunVersion = new VersionNumber(getLastExecVersion());
......@@ -77,6 +85,12 @@ public class InstallUtil {
// Neither the top level config or the lastExecVersionFile have a version
// stored in them, which means it's a new install.
if (lastRunVersion.compareTo(NEW_INSTALL_VERSION) == 0) {
// Edge case: used Jenkins 1 but did not save the system config page,
// the version is not persisted and returns 1.0, so try to check if
// they actually did anything
if (!Jenkins.getInstance().getItemMap().isEmpty()) {
return InstallState.UPGRADE;
}
return InstallState.NEW;
}
......
package jenkins.install;
import java.io.IOException;
import java.util.Locale;
import java.util.UUID;
import java.util.logging.Logger;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import hudson.BulkChange;
import hudson.ExtensionList;
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
import hudson.security.HudsonPrivateSecurityRealm;
import hudson.security.PermissionAdder;
import hudson.security.SecurityRealm;
import hudson.security.csrf.DefaultCrumbIssuer;
import hudson.util.HttpResponses;
import hudson.util.PluginServletFilter;
import jenkins.model.Jenkins;
import jenkins.security.s2m.AdminWhitelistRule;
/**
* A Jenkins instance used during first-run to provide a limited set of services while
* initial installation is in progress
*/
public class SetupWizard {
/**
* The security token parameter name
*/
public static String initialSetupAdminUserName = "admin";
private final Logger LOGGER = Logger.getLogger(SetupWizard.class.getName());
public SetupWizard(Jenkins j) throws IOException {
User admin;
// Create an admin user by default with a
// difficult password
if(j.getSecurityRealm() == null || j.getSecurityRealm() == SecurityRealm.NO_AUTHENTICATION) { // this seems very fragile
BulkChange bc = new BulkChange(j);
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(false, false, null);
j.setSecurityRealm(securityRealm);
String randomUUID = UUID.randomUUID().toString().replace("-", "").toLowerCase(Locale.ENGLISH);
admin = securityRealm.createAccount(SetupWizard.initialSetupAdminUserName, randomUUID);
admin.addProperty(new SetupWizard.AuthenticationKey(randomUUID));
// Lock Jenkins down:
FullControlOnceLoggedInAuthorizationStrategy authStrategy = new FullControlOnceLoggedInAuthorizationStrategy();
authStrategy.setAllowAnonymousRead(false);
j.setAuthorizationStrategy(authStrategy);
// Shut down all the ports we can by default:
j.setSlaveAgentPort(-1); // -1 to disable
// require a crumb issuer
j.setCrumbIssuer(new DefaultCrumbIssuer(false));
// set master -> slave security:
j.getInjector().getInstance(AdminWhitelistRule.class)
.setMasterKillSwitch(false);
try{
j.save(); // !!
} finally {
bc.commit();
}
}
else {
admin = j.getUser(SetupWizard.initialSetupAdminUserName);
}
String setupKey = null;
if(admin != null && admin.getProperty(SetupWizard.AuthenticationKey.class) != null) {
setupKey = admin.getProperty(SetupWizard.AuthenticationKey.class).getKey();
}
if(setupKey != null) {
LOGGER.info("\n\n*************************************************************\n"
+ "*************************************************************\n"
+ "*************************************************************\n"
+ "\n"
+ "Jenkins initial setup is required. A security token is required to proceed. \n"
+ "Please use the following security token to proceed to installation: \n"
+ "\n"
+ "" + setupKey + "\n"
+ "\n"
+ "*************************************************************\n"
+ "*************************************************************\n"
+ "*************************************************************\n");
}
try {
PluginServletFilter.addFilter(FORCE_SETUP_WIZARD_FILTER);
} catch (ServletException e) {
throw new AssertionError(e);
}
}
/**
* Remove the setupWizard filter, ensure all updates are written to disk, etc
*/
public HttpResponse doCompleteInstall(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
Jenkins j = Jenkins.getActiveInstance();
j.setInstallState(InstallState.INITIAL_SETUP_COMPLETED);
InstallUtil.saveLastExecVersion();
PluginServletFilter.removeFilter(FORCE_SETUP_WIZARD_FILTER);
// Also, clean up the setup wizard if it's completed
j.setSetupWizard(null);
return HttpResponses.okJSON();
}
// Stores a user property for the authentication key, which is really the auto-generated user's password
public static class AuthenticationKey extends UserProperty {
String key;
public AuthenticationKey() {
}
public AuthenticationKey(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
/**
* This filter will validate that the security token is provided
*/
private final Filter FORCE_SETUP_WIZARD_FILTER = new Filter() {
@Override
public void init(FilterConfig cfg) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// As an extra measure of security, the install wizard generates a security token, and
// requires the user to enter it before proceeding through the installation. Once set
// we'll set a cookie so the subsequent operations succeed
if (request instanceof HttpServletRequest) {
HttpServletRequest req = (HttpServletRequest)request;
//if (!Pattern.compile(".*[.](css|ttf|gif|woff|eot|png|js)").matcher(req.getRequestURI()).matches()) {
// Allow js & css requests through
if((req.getContextPath() + "/").equals(req.getRequestURI())) {
chain.doFilter(new HttpServletRequestWrapper(req) {
public String getRequestURI() {
return getContextPath() + "/setupWizard/";
}
}, response);
return;
}
// fall through to handling the request normally
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
};
}
......@@ -194,6 +194,7 @@ import jenkins.ExtensionRefreshException;
import jenkins.InitReactorRunner;
import jenkins.install.InstallState;
import jenkins.install.InstallUtil;
import jenkins.install.SetupWizard;
import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy;
import jenkins.security.ConfidentialKey;
import jenkins.security.ConfidentialStore;
......@@ -333,7 +334,13 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
/**
* The Jenkins instance startup type i.e. NEW, UPGRADE etc
*/
private InstallState installState;
private transient InstallState installState = InstallState.NEW;
/**
* If we're in the process of an initial setup,
* this will be set
*/
private transient SetupWizard setupWizard;
/**
* Number of executors of the master node.
......@@ -759,7 +766,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
protected Jenkins(File root, ServletContext context, PluginManager pluginManager) throws IOException, InterruptedException, ReactorException {
long start = System.currentTimeMillis();
// As Jenkins is starting, grant this process full control
// As Jenkins is starting, grant this process full control
ACL.impersonate(ACL.SYSTEM);
try {
this.root = root;
......@@ -773,7 +780,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
if (installState == InstallState.RESTART || installState == InstallState.DOWNGRADE) {
InstallUtil.saveLastExecVersion();
}
if (!new File(root,"jobs").exists()) {
// if this is a fresh install, use more modern default layout that's consistent with agents
workspaceDir = "${JENKINS_HOME}/workspace/${ITEM_FULLNAME}";
......@@ -834,6 +841,11 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
if(KILL_AFTER_LOAD)
System.exit(0);
if(!installState.isSetupComplete()) {
// Start immediately with the setup wizard for new installs
setupWizard = new SetupWizard(this);
}
launchTcpSlaveAgentListener();
if (UDPBroadcastThread.PORT != -1) {
......@@ -913,6 +925,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* Get the Jenkins {@link jenkins.install.InstallState install state}.
* @return The Jenkins {@link jenkins.install.InstallState install state}.
*/
@Nonnull
@Restricted(NoExternalUse.class)
public InstallState getInstallState() {
return installState;
......@@ -1437,10 +1450,10 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
@Exported(name="jobs")
public List<TopLevelItem> getItems() {
if (authorizationStrategy instanceof AuthorizationStrategy.Unsecured ||
authorizationStrategy instanceof FullControlOnceLoggedInAuthorizationStrategy) {
return new ArrayList(items.values());
}
if (authorizationStrategy instanceof AuthorizationStrategy.Unsecured ||
authorizationStrategy instanceof FullControlOnceLoggedInAuthorizationStrategy) {
return new ArrayList(items.values());
}
List<TopLevelItem> viewableItems = new ArrayList<TopLevelItem>();
for (TopLevelItem item : items.values()) {
......@@ -1815,11 +1828,11 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties() {
return nodeProperties;
return nodeProperties;
}
public DescribableList<NodeProperty<?>, NodePropertyDescriptor> getGlobalNodeProperties() {
return globalNodeProperties;
return globalNodeProperties;
}
/**
......@@ -2428,7 +2441,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
*/
@Override public TopLevelItem getItem(String name) throws AccessDeniedException {
if (name==null) return null;
TopLevelItem item = items.get(name);
TopLevelItem item = items.get(name);
if (item==null)
return null;
if (!item.hasPermission(Item.READ)) {
......@@ -3079,10 +3092,10 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
}
public HttpResponse doToggleCollapse() throws ServletException, IOException {
final StaplerRequest request = Stapler.getCurrentRequest();
final String paneId = request.getParameter("paneId");
final StaplerRequest request = Stapler.getCurrentRequest();
final String paneId = request.getParameter("paneId");
PaneStatusProperties.forCurrentUser().toggleCollapsed(paneId);
PaneStatusProperties.forCurrentUser().toggleCollapsed(paneId);
return HttpResponses.forwardToPreviousPage();
}
......@@ -3113,9 +3126,9 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
LOGGER.info("Failed to get thread dump for node " + c.getName() + ": " + e.getMessage());
}
}
if (toComputer() == null) {
future.put("master", RemotingDiagnostics.getThreadDumpAsync(FilePath.localChannel));
}
if (toComputer() == null) {
future.put("master", RemotingDiagnostics.getThreadDumpAsync(FilePath.localChannel));
}
// if the result isn't available in 5 sec, ignore that.
// this is a precaution against hang nodes
......@@ -3891,6 +3904,20 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
public List<ManagementLink> getManagementLinks() {
return ManagementLink.all();
}
/**
* If set, a currently active setup wizard - e.g. installation
*/
public SetupWizard getSetupWizard() {
return setupWizard;
}
/**
* Sets the setup wizard
*/
public void setSetupWizard(SetupWizard setupWizard) {
this.setupWizard = setupWizard;
}
/**
* Exposes the current user to <tt>/me</tt> URL.
......
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson"
xmlns:f="/lib/form">
<f:entry field="allowAnonymousRead">
<f:checkbox default="true" title="${%Allow anonymous read access}"/>
</f:entry>
</j:jelly>
<div>
If checked, this will allow users who are not authenticated to access Jenkins in a read-only mode.
</div>
......@@ -25,64 +25,43 @@ THE SOFTWARE.
<!-- tag file sed by both signup.jelly and addUser.jelly -->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:layout norefresh="true" title="${%Sign up}">
<l:header>
<style>
<!-- match width with captcha image -->
INPUT {
width:200px;
}
</style>
</l:header>
<l:hasPermission permission="${app.READ}" it="${host}">
<st:include page="sidepanel.jelly" it="${host}" />
</l:hasPermission>
<l:main-panel>
<h1>${title}</h1>
<div style="margin: 2em;">
<j:if test="${data.errorMessage!=null}">
<div class="error" style="margin-bottom:1em">
${data.errorMessage}
</div>
</j:if>
<form action="${rootURL}/securityRealm/${action}" method="post" style="text-size:smaller">
<table>
<tr>
<td>${%Username}:</td>
<td><input type="text" name="username" id="username" value="${data.username}" /></td>
</tr>
<tr>
<td>${%Password}:</td>
<td><input type="password" name="password1" value="${data.password1}" /></td>
</tr>
<tr>
<td>${%Confirm password}:</td>
<td><input type="password" name="password2" value="${data.password2}" /></td>
</tr>
<tr>
<td>${%Full name}:</td>
<td><input type="text" name="fullname" value="${data.fullname}" /></td>
</tr>
<tr>
<td>${%E-mail address}:</td>
<td><input type="text" name="email" value="${data.email}" /></td>
</tr>
<j:if test="${captcha}">
<tr>
<td>${%Enter text as shown}:</td>
<td>
<input type="text" name="captcha" autocomplete="off" /><br />
<img src="${rootURL}/securityRealm/captcha" alt="[captcha]"/>
</td>
</tr>
</j:if>
</table>
<f:submit value="${title}" />
<script>
$('username').focus();
</script>
</form>
<h1>${title}</h1>
<div style="margin: 2em;">
<j:if test="${data.errorMessage!=null}">
<div class="error" style="margin-bottom:1em">
${data.errorMessage}
</div>
</l:main-panel>
</l:layout>
</j:if>
<table>
<tr>
<td>${%Username}:</td>
<td><input type="text" name="username" id="username" value="${data.username}" /></td>
</tr>
<tr>
<td>${%Password}:</td>
<td><input type="password" name="password1" value="${data.password1}" /></td>
</tr>
<tr>
<td>${%Confirm password}:</td>
<td><input type="password" name="password2" value="${data.password2}" /></td>
</tr>
<tr>
<td>${%Full name}:</td>
<td><input type="text" name="fullname" value="${data.fullname}" /></td>
</tr>
<tr>
<td>${%E-mail address}:</td>
<td><input type="text" name="email" value="${data.email}" /></td>
</tr>
<j:if test="${captcha}">
<tr>
<td>${%Enter text as shown}:</td>
<td>
<input type="text" name="captcha" autocomplete="off" /><br />
<img src="${rootURL}/securityRealm/captcha" alt="[captcha]"/>
</td>
</tr>
</j:if>
</table>
</div>
</j:jelly>
<!--
The MIT License
Copyright (c) 2004-2016, Sun Microsystems, Inc., CloudBees, Inc., Kohsuke Kawaguchi, Seiji Sogabe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<!-- tag file used by both signup.jelly and addUser.jelly -->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:layout norefresh="true" title="${title}">
<l:header>
<style>
<!-- match width with captcha image -->
INPUT {
width:200px;
}
</style>
</l:header>
<l:hasPermission permission="${app.READ}" it="${host}">
<st:include page="sidepanel.jelly" it="${host}" />
</l:hasPermission>
<l:main-panel>
<form action="${rootURL}/securityRealm/${action}" method="post" style="text-size:smaller">
<local:_entryForm title="${title}" action="${action}" captcha="${captcha}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
<f:submit value="${title}" />
<script>
$('username').focus();
</script>
</form>
</l:main-panel>
</l:layout>
</j:jelly>
......@@ -27,5 +27,5 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<local:_entryForm host="${it}" title="${%Create User}" action="createAccountByAdmin" captcha="${false}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
<local:_entryFormPage host="${it}" title="${%Create User}" action="createAccountByAdmin" captcha="${false}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
</j:jelly>
\ No newline at end of file
......@@ -27,5 +27,5 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<local:_entryForm host="${it}" title="${%Create First Admin User}" action="createFirstAccount" captcha="${false}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
<local:_entryFormPage host="${it}" title="${%Create First Admin User}" action="createFirstAccount" captcha="${false}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
</j:jelly>
\ No newline at end of file
<!--
This is used to create the first user.
-->
<?jelly escape-by-default='true'?>
<l:html norefresh="true" title="${it.displayName}" xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:main-panel>
<style type="text/css">
@import url(https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css);
@import url(https://fonts.googleapis.com/css?family=Roboto:400,300,500,900,700);
#main-panel {
margin: 0;
padding: 0;
}
tr td {
padding-bottom: 2px;
}
body {
padding: 20px 20px 20px 100px;
font-family: 'roboto';
}
form > div {
margin: 0 !important;
}
h1 {
font-family: 'roboto', sans-serif;
font-size: 48px;
margin-top: 30px;
font-weight: 500;
}
h1 img {
position: absolute;
right: -80px;
top: 38px;
}
tr td, input {
line-height: 25px;
margin-bottom: 6px;
}
input[type=text], input[type=password] {
border: 1px solid #ddd;
border-radius: 2px;
padding: 1px 8px;
}
</style>
<form action="${rootURL}/securityRealm/${action}" method="post">
<local:_entryForm host="${app.securityRealm}" title="${%Create First Admin User}" action="createFirstAccount" captcha="${false}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
<script>
$('username').focus();
</script>
</form>
</l:main-panel>
</l:html>
......@@ -27,5 +27,5 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<local:_entryForm host="${app}" title="${%Sign up}" action="createAccount" captcha="${it.isEnableCaptcha()}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
<local:_entryFormPage host="${app}" title="${%Sign up}" action="createAccount" captcha="${it.isEnableCaptcha()}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
</j:jelly>
......@@ -27,5 +27,5 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<local:_entryForm host="${app}" title="${%Sign up}" action="createAccountWithFederatedIdentity" captcha="${true}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
<local:_entryFormPage host="${app}" title="${%Sign up}" action="createAccountWithFederatedIdentity" captcha="${true}" xmlns:local="/hudson/security/HudsonPrivateSecurityRealm" />
</j:jelly>
\ No newline at end of file
<?jelly escape-by-default='true'?>
<l:html norefresh="true" title="${app.instance.displayName}" xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:main-panel>
<form action="${app.instance.securityRealm.authenticationGatewayUrl}" method="POST">
<div class="plugin-setup-wizard bootstrap-3">
<div class="modal fade in" style="display: block;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">${%Security Token}</h4>
</div>
<div class="modal-body">
<i class="water-mark icon-service"></i>
<div class="jumbotron welcome-panel offline">
<h1>${%Security Token}</h1>
<p>${%As an extra measure of security, please enter the setup security token to proceed.}</p>
<p>${%It can be found in the logs for this Jenkins instance.}</p>
<j:if test="${error}">
<div class="alert alert-danger">
<strong>${%ERROR:} </strong>
${%There is a problem with your security token, please check the server logs for the correct token}
</div>
</j:if>
<div class="form-group ${error ? 'has-error' : ''}">
<label class="control-label" for="security-token">${%Security token}</label>
<input name="j_username" value="${j.setupWizard.initialSetupAdminUserName}" type="hidden"/>
<input id="security-token" class="form-control" name="j_password"/>
<link rel="stylesheet" href="${j.installWizardPath}.css" type="text/css" />
</div>
</div>
</div>
<div class="modal-footer">
<input type="submit" class="btn btn-primary set-security-key" value="${%Continue}"/>
</div>
</div>
</div>
</div>
</div>
</form>
</l:main-panel>
</l:html>
<?jelly escape-by-default='true'?>
<l:html norefresh="true" title="${it.displayName}" xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:main-panel>
<div class="plugin-setup-wizard-container"></div>
<script src="${resURL}/${j.installWizardPath}.js" type="text/javascript"/>
<link rel="stylesheet" href="${resURL}/${j.installWizardPath}.css" type="text/css" />
</l:main-panel>
</l:html>
<?jelly escape-by-default='true'?>
<l:html norefresh="true" title="${it.displayName}" xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:main-panel>
<h1>${%HTTP Proxy Configuration}</h1>
<f:form method="post" action="/pluginManager/proxyConfigure" name="proxyConfigure">
<j:scope>
<j:set var="instance" value="${app.proxy}"/>
<j:set var="descriptor" value="${app.pluginManager.proxyDescriptor}"/>
<st:include from="${descriptor}" page="${descriptor.configPage}" />
</j:scope>
</f:form>
</l:main-panel>
</l:html>
installWizard_welcomePanel_title=Getting Started
installWizard_welcomePanel_banner=Initial Jenkins Setup
installWizard_welcomePanel_message=Choose plugins. This will add features to your Jenkins environment.
installWizard_welcomePanel_recommendedActionTitle=Start with recommended plugins
installWizard_welcomePanel_recommendedActionDetails=Install the set of plugins the community finds most useful
installWizard_welcomePanel_customizeActionTitle=Customize your plugins
installWizard_welcomePanel_customizeActionDetails=Select from a community approved list of plugins
installWizard_welcomePanel_banner=Customize Jenkins
installWizard_welcomePanel_message=Plugins extend Jenkins with additional features to support many different needs.
installWizard_welcomePanel_recommendedActionTitle=Install suggested plugins
installWizard_welcomePanel_recommendedActionDetails=Install plugins the Jenkins community finds most useful.
installWizard_welcomePanel_customizeActionTitle=Select plugins to install
installWizard_welcomePanel_customizeActionDetails=Select and install plugins most suitable for your needs.
installWizard_offline_title=Offline
installWizard_offline_message=This Jenkins instance appears to be offline. \
<p style="font-size:18px; margin-top: 6%"> \
For information about installing Jenkins without an internet connection, see the \
<a href="https://wiki.jenkins-ci.org/display/JENKINS/Offline+Jenkins+Installation" target="_blank">Offline Jenkins Installation Documentation</a>. <br/><br/> \
If you need to configure a proxy, you may close this wizard and use the Plugin Manager. \
You may choose to continue by configuring a proxy or skipping plugin installation. \
</p>
installWizard_error_header=An error occurred
installWizard_error_message=An error occurred during installation:
......@@ -28,12 +28,23 @@ installWizard_installing_title=Installing...
installWizard_installing_detailsLink=Details...
installWizard_installComplete_title=Installed
installWizard_installComplete_banner=Jenkins is ready!
installWizard_installComplete_message=Your plugin installations are complete.
installWizard_installComplete_finishButtonLabel=Get Started
installWizard_pluginsInstalled_message=Your plugin installations are complete.
installWizard_installComplete_message=Your Jenkins setup is complete.
installWizard_installComplete_finishButtonLabel=Start using Jenkins
installWizard_installComplete_restartRequiredMessage=Some plugins require Jenkins to be restarted.
installWizard_installComplete_restartLabel=Restart
installWizard_installIncomplete_title=Resume Installation
installWizard_installIncomplete_banner=Resume Installation
installWizard_installIncomplete_message=Jenknins was restarted during installation and some plugins didn't seem to get installed.
installWizard_installIncomplete_message=Jenkins was restarted during installation and some plugins didn't seem to get installed.
installWizard_installIncomplete_resumeInstallationButtonLabel=Resume
installWizard_saveFirstUser=Save and Finish
installWizard_skipFirstUser=Skip
installWizard_firstUserSkippedMessage=<div class="alert alert-warning fade in">\
You've skipped creating an admin user. To log in, use the username: 'admin' and \
the security token you used to access the setup wizard.\
</div>
installWizard_addFirstUser_title=Create an Admin User
installWizard_configureProxy_label=Configure Proxy
installWizard_configureProxy_save=Save and Continue
installWizard_skipPluginInstallations=Skip Plugin Installations
installWizard_installIncomplete_dependenciesLabel=Dependencies
......@@ -24,6 +24,10 @@ THE SOFTWARE.
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:if test="${it.setupWizard != null}">
<st:include it="${it.setupWizard}" page="authenticate-security-token"/>
</j:if>
<j:if test="${it.setupWizard == null}">
<l:layout norefresh="true">
<l:hasPermission permission="${app.READ}">
<st:include page="sidepanel.jelly" />
......@@ -70,4 +74,5 @@ THE SOFTWARE.
</div>
</l:main-panel>
</l:layout>
</j:if>
</j:jelly>
......@@ -27,6 +27,10 @@ THE SOFTWARE.
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:choose>
<j:new var="h" className="hudson.Functions" />
<j:when test="${app.setupWizard != null}">
<j:set var="error" value="true"/>
<st:include it="${app.setupWizard}" page="authenticate-security-token"/>
</j:when>
<j:when test="${app.isUseSecurity() and h.isAnonymous()}">
<!--
The only time the error message makes sense is when Jenkins is protected and the user failed to authenticate.
......
......@@ -44,6 +44,9 @@ THE SOFTWARE.
<st:attribute name="onclick" />
<st:attribute name="class" />
<st:attribute name="negative" />
<st:attribute name="readonly">
If set to true, this will take precedence over the onclick attribute and prevent the state of the checkbox from being changed.
</st:attribute>
<st:attribute name="field">
Used for databinding. TBD.
</st:attribute>
......@@ -63,7 +66,7 @@ THE SOFTWARE.
name="${name}"
value="${attrs.value}"
title="${attrs.tooltip}"
onclick="${attrs.onclick}" id="${attrs.id}" class="${attrs.class} ${attrs.negative!=null ? 'negative' : null} ${attrs.checkUrl!=null?'validated':''}"
onclick="${attrs.readonly=='true' ? 'return false;' : attrs.onclick}" id="${attrs.id}" class="${attrs.class} ${attrs.negative!=null ? 'negative' : null} ${attrs.checkUrl!=null?'validated':''}"
checkUrl="${attrs.checkUrl}" checkDependsOn="${attrs.checkDependsOn}" json="${attrs.json}"
checked="${value ? 'true' : null}"/>
<j:if test="${attrs.title!=null}">
......
......@@ -44,7 +44,11 @@ Behaviour.specify("SELECT.select", 'select', 1000, function(e) {
function hasChanged(selectEl, originalValue) {
// seems like a race condition allows this to fire before the 'selectEl' is defined. If that happens, exit..
<<<<<<< HEAD
if(!selectEl || !selectEl.options || !selectEl.options[0])
=======
if(!selectEl || !selectEl.options || !selectEl[0])
>>>>>>> 219481a2926a6a6e2d86753f250449ba73f198ba
return false;
var firstValue = selectEl.options[0].value;
var selectedValue = selectEl.value;
......
<!--
The MIT License
Copyright (c) 2004-2016, Sun Microsystems, Inc., Kohsuke Kawaguchi,
Daniel Dyer, Seiji Sogabe, Tom Huybrechts, Manufacture Francaise des Pneumatiques
Michelin, Romain Seguy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:i="jelly:fmt" xmlns:x="jelly:xml">
<st:documentation>
Outer-most tag for a normal (non-AJAX) HTML rendering.
This is used with nested &lt;header>, &lt;side-panel>, and &lt;main-panel>
to form Jenkins's basic HTML layout.
<st:attribute name="title" use="required">
Title of the HTML page. Rendered into &lt;title> tag.
</st:attribute>
<st:attribute name="norefresh">
If non-null and not "false", auto refresh is disabled on this page.
This is necessary for pages that include forms.
</st:attribute>
<st:attribute name="css" deprecated="true">
specify path that starts from "/" for loading additional CSS stylesheet.
path is interprted as relative to the context root. e.g.,
{noformat}&lt;l:layout css="/plugin/mysuperplugin/css/myneatstyle.css">{noformat}
This was originally added to allow plugins to load their stylesheets, but
*the use of thie attribute is discouraged now.*
plugins should now do so by inserting &lt;style> elements and/or &lt;script> elements
in &lt;l:header/> tag.
</st:attribute>
<st:attribute name="permission">
If given, this page is only made available to users that has the specified permission.
(The permission will be checked against the "it" object.)
</st:attribute>
</st:documentation>
<st:setHeader name="Expires" value="0" />
<st:setHeader name="Cache-Control" value="no-cache,no-store,must-revalidate" />
<st:setHeader name="X-Hudson-Theme" value="default" />
<st:contentType value="text/html;charset=UTF-8" />
<j:new var="h" className="hudson.Functions" /><!-- instead of JSP functions -->
${h.initPageVariables(context)}
<!--
depending on what tags are used, we can later end up discovering that we needed a session,
but it's too late because the headers are already committed. so ensure we always have a session.
this also allows us to configure HttpSessionContextIntegrationFilter not to create sessions,
which I suspect can end up creating sessions for wrong resource types (such as static resources.)
-->
<j:set var="isMSIE" value="${userAgent.contains('MSIE')}"/>
<j:set var="_" value="${request.getSession()}"/>
<j:set var="_" value="${h.configureAutoRefresh(request, response, attrs.norefresh!=null and !attrs.norefresh.equals(false))}"/>
<j:set var="extensionsAvailable" value="${h.extensionsAvailable}"/>
<j:if test="${request.servletPath=='/' || request.servletPath==''}">
${h.advertiseHeaders(response)}
<j:if test="${extensionsAvailable}">
<j:forEach var="pd" items="${h.pageDecorators}">
<st:include it="${pd}" page="httpHeaders.jelly" optional="true"/>
</j:forEach>
</j:if>
</j:if>
<x:doctype name="html" />
<html>
<head data-rooturl="${rootURL}" data-resurl="${resURL}" resURL="${resURL}">
${h.checkPermission(it,permission)}
<j:if test="${isMSIE}">
<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
</j:if>
<title>${h.appendIfNotNull(title, ' [Jenkins]', 'Jenkins')}</title>
<link rel="stylesheet" href="${resURL}/css/style.css" type="text/css" />
<link rel="stylesheet" href="${resURL}/css/color.css" type="text/css" />
<link rel="stylesheet" href="${resURL}/css/responsive-grid.css" type="text/css" />
<j:if test="${attrs.css!=null}">
<link rel="stylesheet" href="${resURL}${attrs.css}" type="text/css" />
</j:if>
<link rel="shortcut icon" href="${resURL}/favicon.ico" type="image/vnd.microsoft.icon" />
<link rel="mask-icon" href="${rootURL}/images/mask-icon.svg" color="black" />
<!-- are we running as an unit test? -->
<script>var isRunAsTest=${h.isUnitTest}; var rootURL="${rootURL}"; var resURL="${resURL}";</script>
<script src="${resURL}/scripts/prototype.js" type="text/javascript"/>
<script src="${resURL}/scripts/behavior.js" type="text/javascript"/>
<!-- we include our own prototype.js, so don't let stapler pull in another. -->
<st:adjunct assumes="org.kohsuke.stapler.framework.prototype.prototype"
includes="org.kohsuke.stapler.bind"/>
<!-- To use the debug version of YUI, set the system property 'debug.YUI' to true -->
<j:set var="yuiSuffix" value="${h.yuiSuffix}" />
<l:yui module="yahoo" />
<l:yui module="dom" />
<l:yui module="event" />
<j:if test="${h.yuiSuffix=='debug'}">
<l:yui module="logger" />
</j:if>
<l:yui module="animation" />
<l:yui module="dragdrop" />
<l:yui module="container" />
<l:yui module="connection" />
<l:yui module="datasource" />
<l:yui module="autocomplete" />
<l:yui module="menu" />
<l:yui module="element" />
<l:yui module="button" />
<l:yui module="storage" />
<!--l:yui module="editor" suffix="-beta" /-->
<script src="${resURL}/scripts/hudson-behavior.js" type="text/javascript"></script>
<script src="${resURL}/scripts/sortable.js" type="text/javascript"/>
<j:if test="${extensionsAvailable}">
<script>
crumb.init("${h.getCrumbRequestField()}", "${h.getCrumb(request)}");
</script>
</j:if>
<link rel="stylesheet" href="${resURL}/scripts/yui/container/assets/container.css" type="text/css"/>
<link rel="stylesheet" href="${resURL}/scripts/yui/assets/skins/sam/skin.css" type="text/css" />
<link rel="stylesheet" href="${resURL}/scripts/yui/container/assets/skins/sam/container.css" type="text/css"/>
<link rel="stylesheet" href="${resURL}/scripts/yui/button/assets/skins/sam/button.css" type="text/css" />
<link rel="stylesheet" href="${resURL}/scripts/yui/menu/assets/skins/sam/menu.css" type="text/css" />
<!--link rel="stylesheet" href="${resURL}/scripts/yui/editor/assets/skins/sam/editor.css" type="text/css" /-->
<l:hasPermission permission="${app.READ}">
<link rel="search" type="application/opensearchdescription+xml" href="${rootURL}/opensearch.xml" title="Jenkins" />
</l:hasPermission>
<meta name="ROBOTS" content="INDEX,NOFOLLOW" />
<j:set var="mode" value="header" />
<d:invokeBody />
<j:if test="${extensionsAvailable}">
<j:forEach var="pd" items="${h.pageDecorators}">
<st:include it="${pd}" page="header.jelly" optional="true" />
</j:forEach>
</j:if>
<j:invokeStatic var="j" className="jenkins.model.Jenkins" method="getActiveInstance" />
<j:set var="installState" value="${j.installState.name()}" />
<j:if test="${installState == 'NEW' || installState == 'INITIAL_PLUGINS_INSTALLING'}">
<script src="${resURL}/${j.installWizardPath}.js" type="text/javascript"/>
<link rel="stylesheet" href="${resURL}/${j.installWizardPath}.css" type="text/css" />
</j:if>
<j:if test="${isMSIE}">
<script src="${resURL}/scripts/msie.js" type="text/javascript"/>
</j:if>
</head>
<body id="jenkins" class="yui-skin-sam jenkins-${h.version}" data-version="jenkins-${h.version}" data-model-type="${it.class.name}">
<div id="main-panel" style="margin-left: 0;">
<j:set var="mode" value="main-panel" />
<d:invokeBody />
</div>
</body>
</html>
</j:jelly>
......@@ -166,10 +166,6 @@ ${h.initPageVariables(context)}
<j:invokeStatic var="j" className="jenkins.model.Jenkins" method="getActiveInstance" />
<j:set var="installState" value="${j.installState.name()}" />
<j:if test="${installState == 'NEW' || installState == 'INITIAL_PLUGINS_INSTALLING'}">
<script src="${resURL}/${j.installWizardPath}.js" type="text/javascript"/>
<link rel="stylesheet" href="${resURL}/${j.installWizardPath}.css" type="text/css" />
</j:if>
<j:if test="${isMSIE}">
<script src="${resURL}/scripts/msie.js" type="text/javascript"/>
......
......@@ -58,7 +58,7 @@ THE SOFTWARE.
<connection>scm:git:git://github.com/jenkinsci/jenkins.git</connection>
<developerConnection>scm:git:ssh://git@github.com/jenkinsci/jenkins.git</developerConnection>
<url>https://github.com/jenkinsci/jenkins</url>
<tag>HEAD</tag>
<tag>jenkins-1.652</tag>
</scm>
<distributionManagement>
......
......@@ -65,6 +65,12 @@ THE SOFTWARE.
<artifactId>jenkins-test-harness</artifactId>
<version>2.5</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>${project.groupId}</groupId>
<artifactId>jenkins-war</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
......
......@@ -31,6 +31,7 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.JenkinsRule.WebClient;
import java.util.List;
......@@ -47,11 +48,15 @@ public class ManagementLinkTest {
*/
@Test
public void links() throws Exception {
HtmlPage page = j.createWebClient().goTo("manage");
List<?> anchors = DomNodeUtil.selectNodes(page, "id('management-links')//*[@class='link']/a[not(@onclick)]");
assertTrue(anchors.size()>=8);
for(HtmlAnchor e : (List<HtmlAnchor>) anchors) {
e.click();
WebClient wc = j.createWebClient();
for (int i=0; ; i++) {
HtmlPage page = wc.goTo("manage");
List<?> anchors = DomNodeUtil.selectNodes(page, "id('management-links')//*[@class='link']/a[not(@onclick)]");
assertTrue(anchors.size()>=8);
if (i==anchors.size()) return; // done
((HtmlAnchor)anchors.get(i)).click();
}
}
}
......@@ -75,7 +75,8 @@ public class UpdateCenterPluginInstallTest {
String correlationId = data.getString("correlationId");
JSONObject installStatus = jenkinsRule.getJSON("updateCenter/installStatus?correlationId=" + correlationId).getJSONObject();
Assert.assertEquals("ok", json.get("status"));
JSONArray states = installStatus.getJSONArray("data");
JSONObject status = installStatus.getJSONObject("data");
JSONArray states = status.getJSONArray("jobs");
Assert.assertEquals(2, states.size());
JSONObject pluginInstallState = states.getJSONObject(0);
......
......@@ -28,6 +28,7 @@ import hudson.FilePath;
import hudson.remoting.VirtualChannel;
import hudson.scm.NullSCM;
import hudson.slaves.DumbSlave;
import hudson.slaves.WorkspaceList;
import hudson.util.StreamTaskListener;
import java.io.File;
......@@ -180,6 +181,19 @@ public class WorkspaceCleanupThreadTest {
assertFalse(ws.exists());
}
@Issue("JENKINS-27152")
@Test
public void deleteTemporaryDirectory() throws Exception {
FreeStyleProject p = r.createFreeStyleProject();
FilePath ws = createOldWorkspaceOn(r.jenkins, p);
FilePath tmp = WorkspaceList.tempDir(ws);
tmp.child("stuff").write("content", null);
createOldWorkspaceOn(r.createOnlineSlave(), p);
performCleanup();
assertFalse(ws.exists());
assertFalse("temporary directory should be cleaned up as well", tmp.exists());
}
private FilePath createOldWorkspaceOn(Node slave, FreeStyleProject p) throws Exception {
p.setAssignedNode(slave);
FreeStyleBuild b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
......
......@@ -149,7 +149,7 @@ exports.incompleteInstallStatus = function(handler, correlationId) {
* Call this to complete the installation without installing anything
*/
exports.completeInstall = function(handler) {
jenkins.get('/updateCenter/completeInstall', function() {
jenkins.get('/setupWizard/completeInstall', function() {
handler.call({ isError: false });
}, {
timeout: pluginManagerErrorTimeoutMillis,
......
......@@ -8,17 +8,27 @@
// away from these.
//
exports.recommendedPlugins = [
"ant",
"antisamy-markup-formatter",
"credentials",
"build-monitor-plugin",
"build-timeout",
"cloudbees-folder",
"credentials-binding",
"email-ext",
"git",
"github-branch-source",
"junit",
"gradle",
"ldap",
"mailer",
"matrix-auth",
"script-security",
// "matrix-auth",
"pam-auth",
"pipeline-stage-view",
"ssh-slaves",
"subversion",
"translation",
"timestamper",
"workflow-aggregator",
"workflow-multibranch"
"workflow-multibranch",
"ws-cleanup"
];
//
......@@ -27,50 +37,84 @@ exports.recommendedPlugins = [
//
exports.availablePlugins = [
{
"category": "General",
"description": "(a collection of things I cannot think of a better name for)",
"category":"Organization and Administration",
"plugins": [
{ "name": "external-monitor-job" },
{ "name": "translation" }
// { "name": "dashboard-view" },
{ "name": "build-monitor-plugin" },
{ "name": "cloudbees-folder" },
{ "name": "antisamy-markup-formatter" }
]
},
{
"category":"Organization and Administration",
"category":"Build Features",
"description":"Add general purpose features to your jobs",
"plugins": [
{ "name": "antisamy-markup-formatter" }
{ "name": "ansicolor" },
// { "name": "build-name-setter" },
{ "name": "build-timeout" },
{ "name": "config-file-provider" },
{ "name": "credentials-binding" },
{ "name": "rebuild" },
{ "name": "ssh-agent" },
// { "name": "throttle-concurrents" },
{ "name": "timestamper" }
// { "name": "ws-cleanup" }
]
},
{
"category":"Build Tools",
"plugins": [
{ "name": "ant" },
{ "name": "maven-plugin" }
{ "name": "gradle" },
{ "name": "msbuild" },
{ "name": "nodejs" }
]
},
{
"category":"Build Analysis and Reporting",
"plugins": [
{ "name": "javadoc" },
{ "name": "junit" }
// { "name": "checkstyle" },
// { "name": "cobertura" },
{ "name": "htmlpublisher" },
{ "name": "junit" },
// { "name": "sonar" },
// { "name": "warnings" },
{ "name": "xunit" }
]
},
{
"category":"Pipelines and Continuous Delivery",
"plugins": [
{ "name": "workflow-aggregator" },
{ "name": "workflow-multibranch" },
{ "name": "github-branch-source" },
{ "name": "workflow-multibranch" }
{ "name": "pipeline-stage-view" },
{ "name": "build-pipeline-plugin" },
// { "name": "conditional-buildstep" },
// { "name": "jenkins-multijob-plugin" },
{ "name": "parameterized-trigger" },
{ "name": "copyartifact" }
]
},
{
"category":"SCM",
"category":"Source Code Management",
"plugins": [
{ "name": "bitbucket" },
{ "name": "clearcase" },
{ "name": "cvs" },
{ "name": "subversion" }
{ "name": "git" },
{ "name": "git-parameter" },
{ "name": "github" },
{ "name": "gitlab-plugin" },
{ "name": "p4" },
{ "name": "repo" },
{ "name": "subversion" },
{ "name": "teamconcert" },
{ "name": "tfs" }
]
},
{
"category":"Distributed Builds and Containers",
"category":"Distributed Builds",
"plugins": [
{ "name": "matrix-project" },
{ "name": "ssh-slaves" },
......@@ -80,18 +124,22 @@ exports.availablePlugins = [
{
"category":"User Management and Security",
"plugins": [
{ "name": "credentials" },
{ "name": "ldap" },
{ "name": "matrix-auth" },
// { "name": "matrix-auth" },
{ "name": "pam-auth" },
{ "name": "script-security" },
{ "name": "ssh-credentials" }
{ "name": "ldap" },
// { "name": "role-strategy" },
{ "name": "active-directory" }
]
},
{
"category":"Notifications and Publishing",
"plugins": [
{ "name": "mailer" }
{ "name": "email-ext" },
{ "name": "emailext-template" },
{ "name": "mailer" },
{ "name": "publish-over-ssh" },
{ "name": "slack" },
{ "name": "ssh" }
]
}
];
\ No newline at end of file
/**
* Provides a wrapper to interact with the security configuration
*/
var jenkins = require('../util/jenkins');
var jquery = require('jquery-detached');
var wh = require('window-handle');
/**
* Calls a stapler post method to save the first user settings
*/
exports.saveFirstUser = function($form, success, error) {
jenkins.staplerPost(
'/securityRealm/createFirstAccount',
$form,
success, {
dataType: 'html',
error: error
});
};
/**
* Calls a stapler post method to save the first user settings
*/
exports.saveProxy = function($form, success, error) {
jenkins.staplerPost(
'/pluginManager/proxyConfigure',
$form,
success, {
dataType: 'html',
error: error
});
};
......@@ -6,5 +6,10 @@ var pluginSetupWizard = require('./pluginSetupWizardGui');
// This entry point for the bundle only bootstraps the main module in a browser
$(function() {
pluginSetupWizard.init();
$('.plugin-setup-wizard-container').each(function() {
var $container = $(this);
if($container.children().length === 0) { // this may get double-initialized
pluginSetupWizard.init($container);
}
});
});
......@@ -7,9 +7,13 @@ var jquery = require('jquery-detached');
var bootstrap = require('bootstrap-detached');
var jenkins = require('./util/jenkins');
var pluginManager = require('./api/pluginManager');
var securityConfig = require('./api/securityConfig');
var wh = require('window-handle');
window.zq = jquery.getJQuery();
// Setup the dialog, exported
var createPluginSetupWizard = function() {
var createPluginSetupWizard = function(appendTarget) {
// call getJQuery / getBootstrap within the main function so it will work with tests -- if getJQuery etc is called in the main
var $ = jquery.getJQuery();
var $bs = bootstrap.getBootstrap();
......@@ -109,6 +113,9 @@ var createPluginSetupWizard = function() {
var progressPanel = require('./templates/progressPanel.hbs');
var pluginSelectionPanel = require('./templates/pluginSelectionPanel.hbs');
var successPanel = require('./templates/successPanel.hbs');
var setupCompletePanel = require('./templates/setupCompletePanel.hbs');
var proxyConfigPanel = require('./templates/proxyConfigPanel.hbs');
var firstUserPanel = require('./templates/firstUserPanel.hbs');
var offlinePanel = require('./templates/offlinePanel.hbs');
var pluginSetupWizard = require('./templates/pluginSetupWizard.hbs');
var incompleteInstallationPanel = require('./templates/incompleteInstallationPanel.hbs');
......@@ -133,16 +140,16 @@ var createPluginSetupWizard = function() {
// state variables for plugin data, selected plugins, etc.:
var pluginList = pluginManager.plugins();
var allPluginNames = pluginManager.pluginNames();
var selectedPluginNames = pluginManager.recommendedPluginNames();
var allPluginNames = pluginManager.pluginNames();
var selectedPluginNames = pluginManager.recommendedPluginNames();
var visibleDependencies = {};
var categories = [];
var availablePlugins = {};
var categorizedPlugins = {};
var categories = [];
var availablePlugins = {};
var categorizedPlugins = {};
// Instantiate the wizard panel
var $wizard = $(pluginSetupWizard());
$wizard.appendTo('body');
$wizard.appendTo(appendTarget);
var $container = $wizard.find('.modal-content');
var currentPanel;
......@@ -165,10 +172,10 @@ var createPluginSetupWizard = function() {
var translations = {};
var decorations = [
function() {
function() {
// any decorations after DOM replacement go here
}
];
}
];
// call this to set the panel in the app, this performs some additional things & adds common transitions
var setPanel = function(panel, data, oncomplete) {
......@@ -177,7 +184,7 @@ var createPluginSetupWizard = function() {
decorations[i]($base);
}
};
var html = panel($.extend({translations: translations}, data));
var html = panel($.extend({translations: translations, baseUrl: jenkins.baseUrl}, data));
if(panel === currentPanel) { // just replace id-marked elements
var $upd = $(html);
$upd.find('*[id]').each(function() {
......@@ -200,6 +207,12 @@ var createPluginSetupWizard = function() {
currentPanel = panel;
$container.append(html);
decorate($container);
var $modalHeader = $container.find('.modal-header');
if($modalHeader.length > 0) {
$modalHeader.prepend(
'<button type="button" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button>');
}
if(oncomplete) {
oncomplete();
......@@ -255,25 +268,25 @@ var createPluginSetupWizard = function() {
// Initializes the set of installing plugins with pending statuses
var initInstallingPluginList = function() {
installingPlugins = [];
installingPlugins.names = [];
installingPlugins.names = [];
for (var i = 0; i < selectedPluginNames.length; i++) {
var pluginName = selectedPluginNames[i];
var p = availablePlugins[ pluginName];
var pluginName = selectedPluginNames[i];
var p = availablePlugins[ pluginName];
if (p) {
var plug = $.extend({
installStatus : 'pending'
}, p);
installingPlugins.push(plug);
installingPlugins[plug.name] = plug;
installingPlugins.names.push(pluginName);
installingPlugins.names.push(pluginName);
}
}
};
// call this to go install the selected set of plugins
var installPlugins = function(plugins) {
var installPlugins = function(plugins) {
pluginManager.installPlugins(plugins, handleGenericError(function() {
showInstallProgress();
showInstallProgress();
}));
setPanel(progressPanel, { installingPlugins : installingPlugins });
......@@ -306,13 +319,22 @@ var createPluginSetupWizard = function() {
};
// Define actions
var showInstallProgress = function() {
initInstallingPluginList();
var showInstallProgress = function(state) {
if(state) {
if(/CREATE_ADMIN_USER/.test(state)) {
setupFirstUser();
return;
}
}
initInstallingPluginList();
setPanel(progressPanel, { installingPlugins : installingPlugins });
// call to the installStatus, update progress bar & plugin details; transition on complete
var updateStatus = function() {
pluginManager.installStatus(handleGenericError(function(jobs) {
pluginManager.installStatus(handleGenericError(function(data) {
var jobs = data.jobs;
var i, j;
var complete = 0;
var total = 0;
......@@ -402,10 +424,7 @@ var createPluginSetupWizard = function() {
else {
// mark complete
$('.progress-bar').css({width: '100%'});
setPanel(successPanel, {
installingPlugins : installingPlugins,
restartRequired: restartRequired
});
setupFirstUser();
}
}));
};
......@@ -416,7 +435,7 @@ var createPluginSetupWizard = function() {
// Called to complete the installation
var finishInstallation = function() {
jenkins.goTo('/');
closeInstaller();
};
// load the plugin data, callback
......@@ -514,28 +533,28 @@ var createPluginSetupWizard = function() {
});
// walk the elements and search for the text
var walk = function(elements, element, text, xform) {
var i, child, n= element.childNodes.length;
for (i = 0; i<n; i++) {
child = element.childNodes[i];
if (child.nodeType===3 && xform(child.data).indexOf(text)!==-1) {
elements.push(element);
break;
}
}
for (i = 0; i<n; i++) {
child = element.childNodes[i];
if (child.nodeType === 1) {
walk(elements, child, text, xform);
}
}
};
// find elements matching the given text, optionally transforming the text before match (e.g. you can .toLowerCase() it)
var walk = function(elements, element, text, xform) {
var i, child, n= element.childNodes.length;
for (i = 0; i<n; i++) {
child = element.childNodes[i];
if (child.nodeType===3 && xform(child.data).indexOf(text)!==-1) {
elements.push(element);
break;
}
}
for (i = 0; i<n; i++) {
child = element.childNodes[i];
if (child.nodeType === 1) {
walk(elements, child, text, xform);
}
}
};
// find elements matching the given text, optionally transforming the text before match (e.g. you can .toLowerCase() it)
var findElementsWithText = function(ancestor, text, xform) {
var elements= [];
walk(elements, ancestor, text, xform ? xform : function(d){ return d; });
return elements;
var elements= [];
walk(elements, ancestor, text, xform ? xform : function(d){ return d; });
return elements;
};
// search UI vars
......@@ -646,7 +665,64 @@ var createPluginSetupWizard = function() {
$c.slideDown();
}
};
var enableButtonsAfterFrameLoad = function() {
$('iframe[src]').load(function() {
var location = $(this).contents().get(0).location.href;
$('button').prop({disabled:false});
});
};
var setupFirstUser = function() {
setPanel(firstUserPanel, {}, enableButtonsAfterFrameLoad);
};
// call to submit the firstuser
var saveFirstUser = function() {
$('button').prop({disabled:true});
var handleSubmit = function(data) {
if(data.status && data.status > 200) {
// Nothing we can really do here
setPanel(errorPanel, { errorMessage: data.statusText });
return;
}
// we get 200 OK
var $page = $(data);
var $errors = $page.find('.error');
if($errors.length > 0) {
var $main = $page.find('#main-panel').detach();
if($main.length > 0) {
data = data.replace(/body([^>]*)[>](.|[\r\n])+[<][/]body/,'body$1>'+$main.html()+'</body');
}
var doc = $('iframe[src]').contents()[0];
doc.open();
doc.write(data);
doc.close();
}
else {
setPanel(setupCompletePanel);
}
};
securityConfig.saveFirstUser($('iframe[src]').contents().find('form:not(.no-json)'), handleSubmit, handleSubmit);
};
var skipFirstUser = function() {
$('button').prop({disabled:true});
setPanel(setupCompletePanel, {message: translations.installWizard_firstUserSkippedMessage});
};
// call to setup the proxy
var setupProxy = function() {
setPanel(proxyConfigPanel, {}, enableButtonsAfterFrameLoad);
};
// Save the proxy config
var saveProxyConfig = function() {
securityConfig.saveProxy($('iframe[src]').contents().find('form:not(.no-json)'), function() {
jenkins.goTo('/'); // this will re-run connectivity test
});
};
// Call this to resume an installation after restart
var resumeInstallation = function() {
// don't re-initialize installing plugins
......@@ -716,7 +792,12 @@ var createPluginSetupWizard = function() {
'.select-category': selectCategory,
'.close': closeInstaller,
'.resume-installation': resumeInstallation,
'.install-done-restart': restartJenkins
'.install-done-restart': restartJenkins,
'.save-first-user:not([disabled])': saveFirstUser,
'.skip-first-user': skipFirstUser,
'.show-proxy-config': setupProxy,
'.save-proxy-config': saveProxyConfig,
'.skip-plugin-installs': function() { installPlugins([]); }
};
for(var cls in actions) {
bindClickHandler(cls, actions[cls]);
......@@ -737,26 +818,28 @@ var createPluginSetupWizard = function() {
}
// check for updates when first loaded...
pluginManager.installStatus(handleGenericError(function(jobs) {
pluginManager.installStatus(handleGenericError(function(data) {
var jobs = data.jobs;
if(jobs.length > 0) {
if (installingPlugins.length === 0) {
// This can happen on a page reload if we are in the middle of
// an install. So, lets get a list of plugins being installed at the
// moment and use that as the "selectedPlugins" list.
selectedPluginNames = [];
loadPluginData(handleGenericError(function() {
for (var i = 0; i < jobs.length; i++) {
// If the job does not have a 'correlationId', then it was not selected
// by the user for install i.e. it's probably a dependency plugin.
if (jobs[i].correlationId) {
selectedPluginNames.push(jobs[i].name);
}
}
showInstallProgress();
}));
} else {
showInstallProgress();
}
if (installingPlugins.length === 0) {
// This can happen on a page reload if we are in the middle of
// an install. So, lets get a list of plugins being installed at the
// moment and use that as the "selectedPlugins" list.
selectedPluginNames = [];
loadPluginData(handleGenericError(function() {
for (var i = 0; i < jobs.length; i++) {
// If the job does not have a 'correlationId', then it was not selected
// by the user for install i.e. it's probably a dependency plugin.
if (jobs[i].correlationId) {
selectedPluginNames.push(jobs[i].name);
}
}
showInstallProgress(data.state);
}));
} else {
showInstallProgress(data.state);
}
return;
}
......@@ -774,12 +857,12 @@ var createPluginSetupWizard = function() {
for(var plugName in incompleteStatus) {
var j = installingPlugins[plugName];
if (!j) {
console.warn('Plugin "' + plugName + '" not found in the list of installing plugins.');
console.warn('\tInstalling plugins: ' + installingPlugins.names);
continue;
}
if (!j) {
console.warn('Plugin "' + plugName + '" not found in the list of installing plugins.');
console.warn('\tInstalling plugins: ' + installingPlugins.names);
continue;
}
var txt = false;
var state = false;
......
<div class="modal-header">
<h4 class="modal-title">{{translations.installWizard_addFirstUser_title}}</h4>
</div>
<div class="modal-body">
<div class="jumbotron welcome-panel security-panel">
<iframe src="{{baseUrl}}/securityRealm/setupWizardFirstUser"></iframe>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-link skip-first-user" disabled>
{{translations.installWizard_skipFirstUser}}
</button>
<button type="button" class="btn btn-primary save-first-user" disabled>
{{translations.installWizard_saveFirstUser}}
</button>
</div>
<div class="modal-header">
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{translations.installWizard_installIncomplete_title}}</h4>
</div>
<div class="modal-body">
......
<div class="modal-header">
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{translations.installWizard_offline_title}}</h4>
<h4 class="modal-title">{{translations.installWizard_offline_title}}</h4>
</div>
<div class="modal-body">
<div class="jumbotron welcome-panel offline">
<h1>{{translations.installWizard_offline_title}}</h1>
<p>{{{translations.installWizard_offline_message}}}</p>
</div>
<div class="jumbotron welcome-panel offline">
<h1>{{translations.installWizard_offline_title}}</h1>
<p>{{{translations.installWizard_offline_message}}}</p>
<p>
<button type="button" class="btn btn-primary show-proxy-config">{{translations.installWizard_configureProxy_label}}</button>
<button type="button" class="btn btn-primary skip-plugin-installs">{{translations.installWizard_skipPluginInstallations}}</button>
</p>
</div>
</div>
<div class="modal-header">
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{translations.installWizard_installCustom_title}}</h4>
</div>
<div class="modal-body plugin-selector">
......
<div class="modal-header">
<h4 class="modal-title">{{translations.installWizard_configureProxy_label}}</h4>
</div>
<div class="modal-body">
<div class="jumbotron welcome-panel security-panel">
<iframe src="{{baseUrl}}/setupWizard/proxy-configuration"></iframe>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-link install-home">
{{translations.installWizard_goBack}}
</button>
<button type="button" class="btn btn-primary save-proxy-config" disabled>
{{translations.installWizard_configureProxy_save}}
</button>
</div>
<div class="modal-header">
<h4 class="modal-title">{{translations.installWizard_installComplete_title}}</h4>
</div>
<div class="modal-body">
<div class="jumbotron welcome-panel success-panel">
<h1>{{translations.installWizard_installComplete_banner}}</h1>
{{{message}}}
{{#if restartRequired}}
<p>{{translations.installWizard_installComplete_message}} {{translations.installWizard_installComplete_restartRequiredMessage}}</p>
<button type="button" class="btn btn-primary install-done-restart">
{{translations.installWizard_installComplete_restartLabel}}
</button>
{{else}}
<p>{{translations.installWizard_installComplete_message}}</p>
<button type="button" class="btn btn-primary install-done">
{{translations.installWizard_installComplete_finishButtonLabel}}
</button>
{{/if}}
</div>
</div>
<div class="modal-header">
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{translations.installWizard_installComplete_title}}</h4>
<h4 class="modal-title">{{translations.installWizard_pluginsInstalled_title}}</h4>
</div>
<div class="modal-body">
<div class="jumbotron welcome-panel success-panel">
<h1>{{translations.installWizard_installComplete_banner}}</h1>
<h1>{{translations.installWizard_pluginsInstalled_banner}}</h1>
{{#if restartRequired}}
<p>{{translations.installWizard_installComplete_message}} {{translations.installWizard_installComplete_restartRequiredMessage}}</p>
......
<div class="modal-header">
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{translations.installWizard_welcomePanel_title}}</h4>
</div>
<div class="modal-body">
......
......@@ -28,15 +28,15 @@ exports.stringify = function(o) {
h: Hash.prototype.toJSON,
s: String.prototype.toJSON
};
try {
delete Array.prototype.toJSON;
try {
delete Array.prototype.toJSON;
delete Object.prototype.toJSON;
delete Hash.prototype.toJSON;
delete String.prototype.toJSON;
delete Hash.prototype.toJSON;
delete String.prototype.toJSON;
return JSON.stringify(o);
}
finally {
}
finally {
if(protoJSON.a) {
Array.prototype.toJSON = protoJSON.a;
}
......@@ -49,7 +49,7 @@ exports.stringify = function(o) {
if(protoJSON.s) {
String.prototype.toJSON = protoJSON.s;
}
}
}
}
else {
return JSON.stringify(o);
......@@ -67,7 +67,7 @@ exports.idIfy = function(str) {
* redirect
*/
exports.goTo = function(url) {
wh.getWindow().location.replace(exports.baseUrl() + url);
wh.getWindow().location.replace(exports.baseUrl() + url);
};
/**
......@@ -76,13 +76,13 @@ exports.goTo = function(url) {
*/
exports.get = function(url, success, options) {
if(debug) {
console.log('get: ' + url);
}
console.log('get: ' + url);
}
var $ = jquery.getJQuery();
var args = {
url: exports.baseUrl() + url,
type: 'GET',
cache: false,
cache: false,
dataType: 'json',
success: success
};
......@@ -98,22 +98,49 @@ exports.get = function(url, success, options) {
*/
exports.post = function(url, data, success, options) {
if(debug) {
console.log('post: ' + url);
}
console.log('post: ' + url);
}
var $ = jquery.getJQuery();
// handle crumbs
var headers = {};
var wnd = wh.getWindow();
var crumb;
if('crumb' in options) {
crumb = options.crumb;
}
else if('crumb' in wnd) {
crumb = wnd.crumb;
}
if(crumb) {
headers[crumb.fieldName] = crumb.value;
}
var formBody = data;
if(formBody instanceof Object) {
if(crumb) {
formBody = $.extend({}, formBody);
formBody[crumb.fieldName] = crumb.value;
}
formBody = exports.stringify(formBody);
}
var args = {
url: exports.baseUrl() + url,
type: 'POST',
cache: false,
cache: false,
dataType: 'json',
data: exports.stringify(data),
contentType: "application/json",
success: success
data: formBody,
contentType: "application/json",
success: success,
headers: headers
};
if(options instanceof Object) {
$.extend(args, options);
}
$.ajax(args);
$.ajax(args);
};
/**
......@@ -124,20 +151,20 @@ exports.initHandlebars = function() {
Handlebars.registerHelper('ifeq', function(o1, o2, options) {
if(o1 === o2) {
return options.fn();
}
return options.fn();
}
});
Handlebars.registerHelper('ifneq', function(o1, o2, options) {
if(o1 !== o2) {
return options.fn();
}
return options.fn();
}
});
Handlebars.registerHelper('in-array', function(arr, val, options) {
if(arr.indexOf(val) >= 0) {
return options.fn();
}
return options.fn();
}
});
Handlebars.registerHelper('id', exports.idIfy);
......@@ -186,3 +213,64 @@ exports.testConnectivity = function(handler) {
};
testConnectivity();
};
/**
* gets the window containing a form, taking in to account top-level iframes
*/
exports.getWindow = function($form) {
var $ = jquery.getJQuery();
$form = $($form);
var wnd = wh.getWindow();
$(top.document).find('iframe').each(function() {
var windowFrame = this.contentWindow;
var $f = $(this).contents().find('form');
if($f.length > 0 && $form[0] === $f[0]) {
wnd = windowFrame;
}
});
return wnd;
};
/**
* Builds a stapler form post
*/
exports.buildFormPost = function($form) {
var $ = jquery.getJQuery();
$form = $($form);
var wnd = exports.getWindow($form);
var form = $form[0];
if(wnd.buildFormTree(form)) {
return $form.serialize() +
'&core:apply=&Submit=Save&json=' + $form.find('input[name=json]').val();
}
return '';
};
/**
* Gets the crumb, if crumbs are enabled
*/
exports.getFormCrumb = function($form) {
var $ = jquery.getJQuery();
$form = $($form);
var wnd = exports.getWindow($form);
return wnd.crumb;
};
/**
* Jenkins Stapler JSON POST callback
* If last parameter is an object, will be extended to jQuery options (e.g. pass { error: function() ... } to handle errors)
*/
exports.staplerPost = function(url, $form, success, options) {
var $ = jquery.getJQuery();
$form = $($form);
var postBody = exports.buildFormPost($form);
var crumb = exports.getFormCrumb($form);
exports.post(
url,
postBody,
success, $.extend({
processData: false,
contentType: 'application/x-www-form-urlencoded',
crumb: crumb
}, options));
};
......@@ -15,20 +15,20 @@ pluginList.recommendedPlugins = ['subversion'];
// Iterates through all responses until the end and returns the last response repeatedly
var LastResponse = function(responses) {
var counter = 0;
this.next = function() {
if(counter < responses.length) {
try {
return responses[counter];
} finally {
counter++;
}
}
if(responses.length > 0) {
return responses[counter-1];
}
return { status: 'fail' };
};
var counter = 0;
this.next = function() {
if(counter < responses.length) {
try {
return responses[counter];
} finally {
counter++;
}
}
if(responses.length > 0) {
return responses[counter-1];
}
return { status: 'fail' };
};
};
// common mocks for jQuery $.ajax
......@@ -48,17 +48,23 @@ var ajaxMocks = function(responseMappings) {
},
'/jenkins/updateCenter/installStatus': new LastResponse([{
status: 'ok',
data: [] // first, return nothing by default, no ongoing install
data: { // first, return nothing by default, no ongoing install
state: 'NEW',
jobs: []
}
},
{
status: 'ok',
data: [
{
name: 'subversion',
type: 'InstallJob',
installStatus: 'Success'
}
]
data: {
state: 'INSTALLING_PLUGINS',
jobs: [
{
name: 'subversion',
type: 'InstallJob',
installStatus: 'Success'
}
]
}
}]),
'/jenkins/pluginManager/plugins': {
status: 'ok',
......@@ -92,8 +98,8 @@ var ajaxMocks = function(responseMappings) {
}
},
'/jenkins/pluginManager/installPlugins': {
status: 'ok',
data: 'RANDOM_UUID_1234'
status: 'ok',
data: 'RANDOM_UUID_1234'
}
};
......@@ -114,7 +120,7 @@ var ajaxMocks = function(responseMappings) {
throw 'No data mapping provided for AJAX call: ' + call.url;
}
if(response instanceof LastResponse) {
response = response.next();
response = response.next();
}
call.success(response);
};
......@@ -122,7 +128,7 @@ var ajaxMocks = function(responseMappings) {
// call this for each test, it will provide a new wizard, jquery to the caller
var test = function(test, ajaxMappings) {
jsTest.onPage(function() {
jsTest.onPage(function() {
// deps
var $ = getJQuery();
......@@ -133,41 +139,41 @@ var test = function(test, ajaxMappings) {
var pluginSetupWizard = jsTest.requireSrcModule('pluginSetupWizardGui');
// exported init
pluginSetupWizard.init();
pluginSetupWizard.init('body');
test($, pluginSetupWizard);
});
});
};
// helper to validate the appropriate plugins were installed
var validatePlugins = function(plugins, complete) {
var jenkins = jsTest.requireSrcModule('util/jenkins');
if(!jenkins.originalPost) {
jenkins.originalPost = jenkins.post;
jenkins.originalPost = jenkins.post;
}
jenkins.post = function(url, data, cb) {
expect(url).toBe('/pluginManager/installPlugins');
var allMatch = true;
for(var i = 0; i < plugins.length; i++) {
if(data.plugins.indexOf(plugins[i]) < 0) {
allMatch = false;
break;
}
if(data.plugins.indexOf(plugins[i]) < 0) {
allMatch = false;
break;
}
}
if(!allMatch) {
expect(JSON.stringify(plugins)).toBe('In: ' + JSON.stringify(data.plugins));
}
// return status
cb({status:'ok',data:{correlationId:1}});
if(complete) {
complete();
}
if(complete) {
complete();
}
};
};
describe("pluginSetupWizard.js", function () {
it("wizard shows", function (done) {
test(function($) {
it("wizard shows", function (done) {
test(function($) {
// Make sure the dialog was shown
var $wizard = $('.plugin-setup-wizard');
expect($wizard.size()).toBe(1);
......@@ -176,49 +182,49 @@ describe("pluginSetupWizard.js", function () {
});
});
it("offline shows", function (done) {
jsTest.onPage(function() {
// deps
var jenkins = jsTest.requireSrcModule('./util/jenkins');
it("offline shows", function (done) {
jsTest.onPage(function() {
// deps
var jenkins = jsTest.requireSrcModule('./util/jenkins');
var $ = getJQuery();
$.ajax = ajaxMocks();
var $ = getJQuery();
$.ajax = ajaxMocks();
var get = jenkins.get;
try {
// Respond with failure
jenkins.get = function(url, cb) {
if (debug) {
var get = jenkins.get;
try {
// Respond with failure
jenkins.get = function(url, cb) {
if (debug) {
console.log('Jenkins.GET: ' + url);
}
if(url === '/updateCenter/connectionStatus?siteId=default') {
cb({
status: 'ok',
data: {
updatesite: 'ERROR',
internet: 'ERROR'
}
});
}
else {
get(url, cb);
}
};
// load the module
var pluginSetupWizard = jsTest.requireSrcModule('pluginSetupWizardGui');
// exported init
pluginSetupWizard.init();
expect($('.welcome-panel h1').text()).toBe('Offline');
done();
} finally {
jenkins.get = get;
}
});
if(url === '/updateCenter/connectionStatus?siteId=default') {
cb({
status: 'ok',
data: {
updatesite: 'ERROR',
internet: 'ERROR'
}
});
}
else {
get(url, cb);
}
};
// load the module
var pluginSetupWizard = jsTest.requireSrcModule('pluginSetupWizardGui');
// exported init
pluginSetupWizard.init('body');
expect($('.welcome-panel h1').text()).toBe('Offline');
done();
} finally {
jenkins.get = get;
}
});
});
it("install defaults", function (done) {
......@@ -240,16 +246,16 @@ describe("pluginSetupWizard.js", function () {
});
var doit = function($, sel, trigger) {
var $el = $(sel);
if($el.length !== 1) {
console.log('Not found! ' + sel);
var $el = $(sel);
if($el.length !== 1) {
console.log('Not found! ' + sel);
console.log(new Error().stack);
}
if(trigger === 'check') {
$el.prop('checked', true);
trigger = 'change';
}
$el.trigger(trigger);
}
if(trigger === 'check') {
$el.prop('checked', true);
trigger = 'change';
}
$el.trigger(trigger);
};
it("install custom", function (done) {
......@@ -258,8 +264,8 @@ describe("pluginSetupWizard.js", function () {
// validate a call to installPlugins with our defaults
validatePlugins(['junit','mailer'], function() {
// install a specific, other 'set' of plugins
$('input[name=searchbox]').val('junit');
// install a specific, other 'set' of plugins
$('input[name=searchbox]').val('junit');
done();
});
......@@ -277,7 +283,7 @@ describe("pluginSetupWizard.js", function () {
it("resume install", function (done) {
var ajaxMappings = {
'/jenkins/updateCenter/incompleteInstallStatus': {
'/jenkins/updateCenter/incompleteInstallStatus': {
status: 'ok',
data: {
'junit': 'Success',
......@@ -288,27 +294,27 @@ describe("pluginSetupWizard.js", function () {
}
};
test(function($) {
expect($('.modal-title').text()).toBe('Resume Installation');
expect($('*[data-name="junit"]').is('.success')).toBe(true);
done();
expect($('.modal-title').text()).toBe('Resume Installation');
expect($('*[data-name="junit"]').is('.success')).toBe(true);
done();
}, ajaxMappings);
});
it("error conditions", function (done) {
var ajaxMappings = {
'/jenkins/updateCenter/incompleteInstallStatus': {
'/jenkins/updateCenter/incompleteInstallStatus': {
status: 'error',
data: {
'junit': 'Success',
'subversion': 'Pending',
'other': 'Failed',
'mailer': 'Success'
'junit': 'Success',
'subversion': 'Pending',
'other': 'Failed',
'mailer': 'Success'
}
}
};
test(function($) {
expect($('.error-container h1').html()).toBe('Error');
done();
expect($('.error-container h1').html()).toBe('Error');
done();
}, ajaxMappings);
});
......@@ -321,18 +327,24 @@ describe("pluginSetupWizard.js", function () {
var ajaxMappings = {
'/jenkins/updateCenter/installStatus': new LastResponse([{
status: 'ok',
data: [] // first, return nothing by default, no ongoing install
data: { // first, return nothing by default, no ongoing install
state: 'NEW',
jobs: []
}
},
{
status: 'ok',
data: [
{
name: 'subversion',
type: 'InstallJob',
installStatus: 'Success',
requiresRestart: 'true' // a string...
}
]
data: {
state: 'INSTALLING_PLUGINS',
jobs: [
{
name: 'subversion',
type: 'InstallJob',
installStatus: 'Success',
requiresRestart: 'true' // a string...
}
]
}
}])
};
test(function($) {
......@@ -340,12 +352,13 @@ describe("pluginSetupWizard.js", function () {
expect(goButton.size()).toBe(1);
// validate a call to installPlugins with our defaults
setTimeout(function() {
setTimeout(function() {
expect($('.install-done').is(':visible')).toBe(false);
expect($('.install-done-restart').is(':visible')).toBe(true);
expect($('.save-first-user').is(':visible')).toBe(true);
done();
}, 500);
}, 500);
goButton.click();
}, ajaxMappings);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册