提交 f69080ea 编写于 作者: G gusreiber

merging 2.0

...@@ -54,12 +54,21 @@ Upcoming changes</a> ...@@ -54,12 +54,21 @@ Upcoming changes</a>
<!-- Record your changes in the trunk here. --> <!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=--> <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> <ul class=image>
<li class="rfe"> <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>) (<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> </ul>
</div><!--=TRUNK-END=-->
<h3><a name=v1.650>What's new in 1.650</a> (2016/02/24)</h3> <h3><a name=v1.650>What's new in 1.650</a> (2016/02/24)</h3>
<ul class=image> <ul class=image>
<li class="major bug"> <li class="major bug">
......
...@@ -39,7 +39,7 @@ THE SOFTWARE. ...@@ -39,7 +39,7 @@ THE SOFTWARE.
<properties> <properties>
<staplerFork>true</staplerFork> <staplerFork>true</staplerFork>
<stapler.version>1.239</stapler.version> <stapler.version>1.240</stapler.version>
<spring.version>2.5.6.SEC03</spring.version> <spring.version>2.5.6.SEC03</spring.version>
<groovy.version>1.8.9</groovy.version> <groovy.version>1.8.9</groovy.version>
</properties> </properties>
......
...@@ -282,7 +282,6 @@ public abstract class ExtensionFinder implements ExtensionPoint { ...@@ -282,7 +282,6 @@ public abstract class ExtensionFinder implements ExtensionPoint {
LOGGER.log(Level.SEVERE, "Failed to create Guice container from all the plugins",e); 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 // failing to load all bindings are disastrous, so recover by creating minimum that works
// by just including the core // by just including the core
// TODO this recovery is pretty much useless; startup crashes anyway
container = Guice.createInjector(new SezpozModule(loadSezpozIndices(Jenkins.class.getClassLoader()))); container = Guice.createInjector(new SezpozModule(loadSezpozIndices(Jenkins.class.getClassLoader())));
} }
...@@ -479,7 +478,11 @@ public abstract class ExtensionFinder implements ExtensionPoint { ...@@ -479,7 +478,11 @@ public abstract class ExtensionFinder implements ExtensionPoint {
m.invoke(ecl, c); m.invoke(ecl, c);
c.getConstructors(); c.getConstructors();
c.getMethods(); 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); LOGGER.log(Level.FINER, "{0} looks OK", c);
while (c != Object.class) { while (c != Object.class) {
c.getGenericSuperclass(); c.getGenericSuperclass();
......
...@@ -1189,7 +1189,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas ...@@ -1189,7 +1189,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
break; break;
} }
updateCenter.persistInstallStatus(); updateCenter.persistInstallStatus();
jenkins.setInstallState(InstallState.INITIAL_PLUGINS_INSTALLED); jenkins.setInstallState(InstallState.INITIAL_PLUGINS_INSTALLING.getNextState());
InstallUtil.saveLastExecVersion(); InstallUtil.saveLastExecVersion();
} }
}.start(); }.start();
......
...@@ -57,7 +57,8 @@ import jenkins.install.InstallState; ...@@ -57,7 +57,8 @@ import jenkins.install.InstallState;
import jenkins.install.InstallUtil; import jenkins.install.InstallUtil;
import jenkins.model.Jenkins; import jenkins.model.Jenkins;
import jenkins.util.io.OnMaster; import jenkins.util.io.OnMaster;
import net.sf.json.JSONArray; import net.sf.json.JSONObject;
import org.acegisecurity.Authentication; import org.acegisecurity.Authentication;
import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContext;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
...@@ -73,7 +74,6 @@ import javax.annotation.Nonnull; ...@@ -73,7 +74,6 @@ import javax.annotation.Nonnull;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
...@@ -81,12 +81,12 @@ import java.net.MalformedURLException; ...@@ -81,12 +81,12 @@ import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.DigestInputStream;
import java.security.DigestOutputStream; import java.security.DigestOutputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
...@@ -302,6 +302,27 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas ...@@ -302,6 +302,27 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
} }
} }
if (checkJob != null) { 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); return HttpResponses.okJSON(checkJob.connectionStates);
} else { } else {
return HttpResponses.errorJSON(String.format("Unknown site '%s'.", siteId)); return HttpResponses.errorJSON(String.format("Unknown site '%s'.", siteId));
...@@ -311,19 +332,6 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas ...@@ -311,19 +332,6 @@ 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 * Called to determine if there was an incomplete installation, what the statuses of the plugins are
*/ */
...@@ -379,7 +387,10 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas ...@@ -379,7 +387,10 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas
public HttpResponse doInstallStatus(StaplerRequest request) { public HttpResponse doInstallStatus(StaplerRequest request) {
try { try {
String correlationId = request.getParameter("correlationId"); 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<>(); List<Map<String, String>> installStates = new ArrayList<>();
response.put("jobs", installStates);
List<UpdateCenterJob> jobCopy = getJobs(); List<UpdateCenterJob> jobCopy = getJobs();
for (UpdateCenterJob job : jobCopy) { for (UpdateCenterJob job : jobCopy) {
...@@ -400,7 +411,7 @@ public class UpdateCenter extends AbstractModelObject implements Saveable, OnMas ...@@ -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) { } catch (Exception e) {
return HttpResponses.errorJSON(String.format("ERROR: %s", e.getMessage())); return HttpResponses.errorJSON(String.format("ERROR: %s", e.getMessage()));
} }
......
...@@ -27,6 +27,7 @@ import hudson.Extension; ...@@ -27,6 +27,7 @@ import hudson.Extension;
import hudson.ExtensionList; import hudson.ExtensionList;
import hudson.FilePath; import hudson.FilePath;
import hudson.Util; import hudson.Util;
import hudson.slaves.WorkspaceList;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
...@@ -89,6 +90,7 @@ public class WorkspaceCleanupThread extends AsyncPeriodicWork { ...@@ -89,6 +90,7 @@ public class WorkspaceCleanupThread extends AsyncPeriodicWork {
listener.getLogger().println("Deleting " + ws + " on " + node.getDisplayName()); listener.getLogger().println("Deleting " + ws + " on " + node.getDisplayName());
try { try {
ws.deleteRecursive(); ws.deleteRecursive();
WorkspaceList.tempDir(ws).deleteRecursive();
} catch (IOException x) { } catch (IOException x) {
x.printStackTrace(listener.error("Failed to delete " + ws + " on " + node.getDisplayName())); x.printStackTrace(listener.error("Failed to delete " + ws + " on " + node.getDisplayName()));
} catch (InterruptedException x) { } catch (InterruptedException x) {
......
...@@ -29,6 +29,7 @@ import jenkins.model.Jenkins; ...@@ -29,6 +29,7 @@ import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Collections; import java.util.Collections;
...@@ -36,30 +37,52 @@ import java.util.List; ...@@ -36,30 +37,52 @@ import java.util.List;
/** /**
* {@link AuthorizationStrategy} that grants full-control to authenticated user * {@link AuthorizationStrategy} that grants full-control to authenticated user
* (other than anonymous users.) * and optionally read access to anonymous users
* *
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
public class FullControlOnceLoggedInAuthorizationStrategy extends AuthorizationStrategy { public class FullControlOnceLoggedInAuthorizationStrategy extends AuthorizationStrategy {
/**
* Whether to allow anonymous read access, for backward compatibility
* default is to allow it
*/
private boolean denyAnonymousReadAccess = false;
@DataBoundConstructor @DataBoundConstructor
public FullControlOnceLoggedInAuthorizationStrategy() { public FullControlOnceLoggedInAuthorizationStrategy() {
} }
@Override @Override
public ACL getRootACL() { public ACL getRootACL() {
return THE_ACL; return denyAnonymousReadAccess ? AUTHENTICATED_READ : ANONYMOUS_READ;
} }
public List<String> getGroups() { public List<String> getGroups() {
return Collections.emptyList(); return Collections.emptyList();
} }
private static final SparseACL THE_ACL = new SparseACL(null); /**
* 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 AUTHENTICATED_READ = new SparseACL(null);
private static final SparseACL ANONYMOUS_READ = new SparseACL(null);
static { static {
THE_ACL.add(ACL.EVERYONE, Jenkins.ADMINISTER,true); ANONYMOUS_READ.add(ACL.EVERYONE, Jenkins.ADMINISTER,true);
THE_ACL.add(ACL.ANONYMOUS, Jenkins.ADMINISTER,false); ANONYMOUS_READ.add(ACL.ANONYMOUS, Jenkins.ADMINISTER,false);
THE_ACL.add(ACL.ANONYMOUS,Permission.READ,true); ANONYMOUS_READ.add(ACL.ANONYMOUS, Permission.READ,true);
AUTHENTICATED_READ.add(ACL.EVERYONE, Jenkins.ADMINISTER, true);
AUTHENTICATED_READ.add(ACL.ANONYMOUS, Jenkins.ADMINISTER, false);
} }
/** /**
......
...@@ -29,6 +29,8 @@ import hudson.ExtensionList; ...@@ -29,6 +29,8 @@ import hudson.ExtensionList;
import hudson.Util; import hudson.Util;
import hudson.diagnosis.OldDataMonitor; import hudson.diagnosis.OldDataMonitor;
import hudson.model.Descriptor; import hudson.model.Descriptor;
import jenkins.install.InstallState;
import jenkins.install.SetupWizard;
import jenkins.model.Jenkins; import jenkins.model.Jenkins;
import hudson.model.ManagementLink; import hudson.model.ManagementLink;
import hudson.model.ModelObject; import hudson.model.ModelObject;
...@@ -279,15 +281,37 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea ...@@ -279,15 +281,37 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea
* This can be run by anyone, but only to create the very first user account. * 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 { 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"); rsp.sendError(SC_UNAUTHORIZED,"First user was already created");
return; return;
} }
User u = createAccount(req, rsp, false, "firstUser.jelly");
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) { if (u!=null) {
tryToMakeAdmin(u); tryToMakeAdmin(u);
if(admin != null) {
admin = null;
}
Jenkins.getInstance().setInstallState(InstallState.CREATE_ADMIN_USER.getNextState());
loginAndTakeBack(req, rsp, u); loginAndTakeBack(req, rsp, u);
} }
} finally {
if(admin != null) {
admin.save(); // recreate this initial user if something failed
}
}
} }
/** /**
......
...@@ -26,6 +26,7 @@ package hudson.slaves; ...@@ -26,6 +26,7 @@ package hudson.slaves;
import hudson.FilePath; import hudson.FilePath;
import hudson.Functions; import hudson.Functions;
import hudson.model.Computer; import hudson.model.Computer;
import hudson.model.DirectoryBrowserSupport;
import java.io.Closeable; import java.io.Closeable;
import java.util.Date; import java.util.Date;
...@@ -283,6 +284,25 @@ public final class WorkspaceList { ...@@ -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()); private static final Logger LOGGER = Logger.getLogger(WorkspaceList.class.getName());
/** /**
......
...@@ -34,32 +34,63 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; ...@@ -34,32 +34,63 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
@Restricted(NoExternalUse.class) @Restricted(NoExternalUse.class)
public enum InstallState { 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 * New Jenkins install. The user has kicked off the process of installing an
* initial set of plugins (via the install wizard). * 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 of an existing Jenkins install.
*/ */
RESTART, RESTART(true, INITIAL_SETUP_COMPLETED),
/** /**
* Upgrade of an existing Jenkins install. * Upgrade of an existing Jenkins install.
*/ */
UPGRADE, UPGRADE(true, INITIAL_SETUP_COMPLETED),
/** /**
* Downgrade of an existing Jenkins install. * Downgrade of an existing Jenkins install.
*/ */
DOWNGRADE, DOWNGRADE(true, INITIAL_SETUP_COMPLETED),
/** /**
* Jenkins started in test mode (JenkinsRule). * 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 { ...@@ -61,6 +61,7 @@ public class InstallUtil {
private static final Logger LOGGER = Logger.getLogger(InstallUtil.class.getName()); 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"); private static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0");
/** /**
...@@ -68,15 +69,28 @@ public class InstallUtil { ...@@ -68,15 +69,28 @@ public class InstallUtil {
* @return The type of "startup" currently under way in Jenkins. * @return The type of "startup" currently under way in Jenkins.
*/ */
public static InstallState getInstallState() { public static InstallState getInstallState() {
// install wizard will always run if environment specified
if (!Boolean.getBoolean("jenkins.install.runSetupWizard")) {
if (Functions.getIsUnitTest()) { if (Functions.getIsUnitTest()) {
return InstallState.TEST; return InstallState.TEST;
} }
if (Boolean.getBoolean("hudson.Main.development")) {
return InstallState.DEVELOPMENT;
}
}
VersionNumber lastRunVersion = new VersionNumber(getLastExecVersion()); VersionNumber lastRunVersion = new VersionNumber(getLastExecVersion());
// Neither the top level config or the lastExecVersionFile have a version // Neither the top level config or the lastExecVersionFile have a version
// stored in them, which means it's a new install. // stored in them, which means it's a new install.
if (lastRunVersion.compareTo(NEW_INSTALL_VERSION) == 0) { 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; 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; ...@@ -194,6 +194,7 @@ import jenkins.ExtensionRefreshException;
import jenkins.InitReactorRunner; import jenkins.InitReactorRunner;
import jenkins.install.InstallState; import jenkins.install.InstallState;
import jenkins.install.InstallUtil; import jenkins.install.InstallUtil;
import jenkins.install.SetupWizard;
import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy; import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy;
import jenkins.security.ConfidentialKey; import jenkins.security.ConfidentialKey;
import jenkins.security.ConfidentialStore; import jenkins.security.ConfidentialStore;
...@@ -333,7 +334,13 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve ...@@ -333,7 +334,13 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
/** /**
* The Jenkins instance startup type i.e. NEW, UPGRADE etc * 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. * Number of executors of the master node.
...@@ -834,6 +841,11 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve ...@@ -834,6 +841,11 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
if(KILL_AFTER_LOAD) if(KILL_AFTER_LOAD)
System.exit(0); System.exit(0);
if(!installState.isSetupComplete()) {
// Start immediately with the setup wizard for new installs
setupWizard = new SetupWizard(this);
}
launchTcpSlaveAgentListener(); launchTcpSlaveAgentListener();
if (UDPBroadcastThread.PORT != -1) { if (UDPBroadcastThread.PORT != -1) {
...@@ -913,6 +925,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve ...@@ -913,6 +925,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
* Get the Jenkins {@link jenkins.install.InstallState install state}. * Get the Jenkins {@link jenkins.install.InstallState install state}.
* @return The Jenkins {@link jenkins.install.InstallState install state}. * @return The Jenkins {@link jenkins.install.InstallState install state}.
*/ */
@Nonnull
@Restricted(NoExternalUse.class) @Restricted(NoExternalUse.class)
public InstallState getInstallState() { public InstallState getInstallState() {
return installState; return installState;
...@@ -3892,6 +3905,20 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve ...@@ -3892,6 +3905,20 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
return ManagementLink.all(); 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. * 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,19 +25,6 @@ THE SOFTWARE. ...@@ -25,19 +25,6 @@ THE SOFTWARE.
<!-- tag file sed by both signup.jelly and addUser.jelly --> <!-- tag file sed by both signup.jelly and addUser.jelly -->
<?jelly escape-by-default='true'?> <?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: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> <h1>${title}</h1>
<div style="margin: 2em;"> <div style="margin: 2em;">
<j:if test="${data.errorMessage!=null}"> <j:if test="${data.errorMessage!=null}">
...@@ -45,7 +32,6 @@ THE SOFTWARE. ...@@ -45,7 +32,6 @@ THE SOFTWARE.
${data.errorMessage} ${data.errorMessage}
</div> </div>
</j:if> </j:if>
<form action="${rootURL}/securityRealm/${action}" method="post" style="text-size:smaller">
<table> <table>
<tr> <tr>
<td>${%Username}:</td> <td>${%Username}:</td>
...@@ -77,12 +63,5 @@ THE SOFTWARE. ...@@ -77,12 +63,5 @@ THE SOFTWARE.
</tr> </tr>
</j:if> </j:if>
</table> </table>
<f:submit value="${title}" />
<script>
$('username').focus();
</script>
</form>
</div> </div>
</l:main-panel>
</l:layout>
</j:jelly> </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. ...@@ -27,5 +27,5 @@ THE SOFTWARE.
--> -->
<?jelly escape-by-default='true'?> <?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: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> </j:jelly>
\ No newline at end of file
...@@ -27,5 +27,5 @@ THE SOFTWARE. ...@@ -27,5 +27,5 @@ THE SOFTWARE.
--> -->
<?jelly escape-by-default='true'?> <?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: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> </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. ...@@ -27,5 +27,5 @@ THE SOFTWARE.
--> -->
<?jelly escape-by-default='true'?> <?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: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> </j:jelly>
...@@ -27,5 +27,5 @@ THE SOFTWARE. ...@@ -27,5 +27,5 @@ THE SOFTWARE.
--> -->
<?jelly escape-by-default='true'?> <?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: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> </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_title=Getting Started
installWizard_welcomePanel_banner=Initial Jenkins Setup installWizard_welcomePanel_banner=Customize Jenkins
installWizard_welcomePanel_message=Choose plugins. This will add features to your Jenkins environment. installWizard_welcomePanel_message=Plugins extend Jenkins with additional features to support many different needs.
installWizard_welcomePanel_recommendedActionTitle=Start with recommended plugins installWizard_welcomePanel_recommendedActionTitle=Install suggested plugins
installWizard_welcomePanel_recommendedActionDetails=Install the set of plugins the community finds most useful installWizard_welcomePanel_recommendedActionDetails=Install plugins the Jenkins community finds most useful.
installWizard_welcomePanel_customizeActionTitle=Customize your plugins installWizard_welcomePanel_customizeActionTitle=Select plugins to install
installWizard_welcomePanel_customizeActionDetails=Select from a community approved list of plugins installWizard_welcomePanel_customizeActionDetails=Select and install plugins most suitable for your needs.
installWizard_offline_title=Offline installWizard_offline_title=Offline
installWizard_offline_message=This Jenkins instance appears to be offline. \ installWizard_offline_message=This Jenkins instance appears to be offline. \
<p style="font-size:18px; margin-top: 6%"> \ <p style="font-size:18px; margin-top: 6%"> \
For information about installing Jenkins without an internet connection, see the \ 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/> \ <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> </p>
installWizard_error_header=An error occurred installWizard_error_header=An error occurred
installWizard_error_message=An error occurred during installation: installWizard_error_message=An error occurred during installation:
...@@ -28,12 +28,23 @@ installWizard_installing_title=Installing... ...@@ -28,12 +28,23 @@ installWizard_installing_title=Installing...
installWizard_installing_detailsLink=Details... installWizard_installing_detailsLink=Details...
installWizard_installComplete_title=Installed installWizard_installComplete_title=Installed
installWizard_installComplete_banner=Jenkins is ready! installWizard_installComplete_banner=Jenkins is ready!
installWizard_installComplete_message=Your plugin installations are complete. installWizard_pluginsInstalled_message=Your plugin installations are complete.
installWizard_installComplete_finishButtonLabel=Get Started 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_restartRequiredMessage=Some plugins require Jenkins to be restarted.
installWizard_installComplete_restartLabel=Restart installWizard_installComplete_restartLabel=Restart
installWizard_installIncomplete_title=Resume Installation installWizard_installIncomplete_title=Resume Installation
installWizard_installIncomplete_banner=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_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 installWizard_installIncomplete_dependenciesLabel=Dependencies
...@@ -24,6 +24,10 @@ THE SOFTWARE. ...@@ -24,6 +24,10 @@ THE SOFTWARE.
<?jelly escape-by-default='true'?> <?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: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:layout norefresh="true">
<l:hasPermission permission="${app.READ}"> <l:hasPermission permission="${app.READ}">
<st:include page="sidepanel.jelly" /> <st:include page="sidepanel.jelly" />
...@@ -70,4 +74,5 @@ THE SOFTWARE. ...@@ -70,4 +74,5 @@ THE SOFTWARE.
</div> </div>
</l:main-panel> </l:main-panel>
</l:layout> </l:layout>
</j:if>
</j:jelly> </j:jelly>
...@@ -27,6 +27,10 @@ THE SOFTWARE. ...@@ -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: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:choose>
<j:new var="h" className="hudson.Functions" /> <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()}"> <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. 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. ...@@ -44,6 +44,9 @@ THE SOFTWARE.
<st:attribute name="onclick" /> <st:attribute name="onclick" />
<st:attribute name="class" /> <st:attribute name="class" />
<st:attribute name="negative" /> <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"> <st:attribute name="field">
Used for databinding. TBD. Used for databinding. TBD.
</st:attribute> </st:attribute>
...@@ -63,7 +66,7 @@ THE SOFTWARE. ...@@ -63,7 +66,7 @@ THE SOFTWARE.
name="${name}" name="${name}"
value="${attrs.value}" value="${attrs.value}"
title="${attrs.tooltip}" 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}" checkUrl="${attrs.checkUrl}" checkDependsOn="${attrs.checkDependsOn}" json="${attrs.json}"
checked="${value ? 'true' : null}"/> checked="${value ? 'true' : null}"/>
<j:if test="${attrs.title!=null}"> <j:if test="${attrs.title!=null}">
......
...@@ -44,7 +44,11 @@ Behaviour.specify("SELECT.select", 'select', 1000, function(e) { ...@@ -44,7 +44,11 @@ Behaviour.specify("SELECT.select", 'select', 1000, function(e) {
function hasChanged(selectEl, originalValue) { function hasChanged(selectEl, originalValue) {
// seems like a race condition allows this to fire before the 'selectEl' is defined. If that happens, exit.. // 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.options[0])
=======
if(!selectEl || !selectEl.options || !selectEl[0])
>>>>>>> 219481a2926a6a6e2d86753f250449ba73f198ba
return false; return false;
var firstValue = selectEl.options[0].value; var firstValue = selectEl.options[0].value;
var selectedValue = selectEl.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)} ...@@ -166,10 +166,6 @@ ${h.initPageVariables(context)}
<j:invokeStatic var="j" className="jenkins.model.Jenkins" method="getActiveInstance" /> <j:invokeStatic var="j" className="jenkins.model.Jenkins" method="getActiveInstance" />
<j:set var="installState" value="${j.installState.name()}" /> <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}"> <j:if test="${isMSIE}">
<script src="${resURL}/scripts/msie.js" type="text/javascript"/> <script src="${resURL}/scripts/msie.js" type="text/javascript"/>
......
...@@ -58,7 +58,7 @@ THE SOFTWARE. ...@@ -58,7 +58,7 @@ THE SOFTWARE.
<connection>scm:git:git://github.com/jenkinsci/jenkins.git</connection> <connection>scm:git:git://github.com/jenkinsci/jenkins.git</connection>
<developerConnection>scm:git:ssh://git@github.com/jenkinsci/jenkins.git</developerConnection> <developerConnection>scm:git:ssh://git@github.com/jenkinsci/jenkins.git</developerConnection>
<url>https://github.com/jenkinsci/jenkins</url> <url>https://github.com/jenkinsci/jenkins</url>
<tag>HEAD</tag> <tag>jenkins-1.652</tag>
</scm> </scm>
<distributionManagement> <distributionManagement>
......
...@@ -65,6 +65,12 @@ THE SOFTWARE. ...@@ -65,6 +65,12 @@ THE SOFTWARE.
<artifactId>jenkins-test-harness</artifactId> <artifactId>jenkins-test-harness</artifactId>
<version>2.5</version> <version>2.5</version>
<scope>test</scope> <scope>test</scope>
<exclusions>
<exclusion>
<groupId>${project.groupId}</groupId>
<artifactId>jenkins-war</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>${project.groupId}</groupId> <groupId>${project.groupId}</groupId>
......
...@@ -31,6 +31,7 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage; ...@@ -31,6 +31,7 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.JenkinsRule.WebClient;
import java.util.List; import java.util.List;
...@@ -47,11 +48,15 @@ public class ManagementLinkTest { ...@@ -47,11 +48,15 @@ public class ManagementLinkTest {
*/ */
@Test @Test
public void links() throws Exception { public void links() throws Exception {
HtmlPage page = j.createWebClient().goTo("manage"); 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)]"); List<?> anchors = DomNodeUtil.selectNodes(page, "id('management-links')//*[@class='link']/a[not(@onclick)]");
assertTrue(anchors.size()>=8); assertTrue(anchors.size()>=8);
for(HtmlAnchor e : (List<HtmlAnchor>) anchors) { if (i==anchors.size()) return; // done
e.click();
((HtmlAnchor)anchors.get(i)).click();
} }
} }
} }
...@@ -75,7 +75,8 @@ public class UpdateCenterPluginInstallTest { ...@@ -75,7 +75,8 @@ public class UpdateCenterPluginInstallTest {
String correlationId = data.getString("correlationId"); String correlationId = data.getString("correlationId");
JSONObject installStatus = jenkinsRule.getJSON("updateCenter/installStatus?correlationId=" + correlationId).getJSONObject(); JSONObject installStatus = jenkinsRule.getJSON("updateCenter/installStatus?correlationId=" + correlationId).getJSONObject();
Assert.assertEquals("ok", json.get("status")); 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()); Assert.assertEquals(2, states.size());
JSONObject pluginInstallState = states.getJSONObject(0); JSONObject pluginInstallState = states.getJSONObject(0);
......
...@@ -28,6 +28,7 @@ import hudson.FilePath; ...@@ -28,6 +28,7 @@ import hudson.FilePath;
import hudson.remoting.VirtualChannel; import hudson.remoting.VirtualChannel;
import hudson.scm.NullSCM; import hudson.scm.NullSCM;
import hudson.slaves.DumbSlave; import hudson.slaves.DumbSlave;
import hudson.slaves.WorkspaceList;
import hudson.util.StreamTaskListener; import hudson.util.StreamTaskListener;
import java.io.File; import java.io.File;
...@@ -180,6 +181,19 @@ public class WorkspaceCleanupThreadTest { ...@@ -180,6 +181,19 @@ public class WorkspaceCleanupThreadTest {
assertFalse(ws.exists()); 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 { private FilePath createOldWorkspaceOn(Node slave, FreeStyleProject p) throws Exception {
p.setAssignedNode(slave); p.setAssignedNode(slave);
FreeStyleBuild b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); FreeStyleBuild b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0));
......
...@@ -149,7 +149,7 @@ exports.incompleteInstallStatus = function(handler, correlationId) { ...@@ -149,7 +149,7 @@ exports.incompleteInstallStatus = function(handler, correlationId) {
* Call this to complete the installation without installing anything * Call this to complete the installation without installing anything
*/ */
exports.completeInstall = function(handler) { exports.completeInstall = function(handler) {
jenkins.get('/updateCenter/completeInstall', function() { jenkins.get('/setupWizard/completeInstall', function() {
handler.call({ isError: false }); handler.call({ isError: false });
}, { }, {
timeout: pluginManagerErrorTimeoutMillis, timeout: pluginManagerErrorTimeoutMillis,
......
...@@ -8,17 +8,27 @@ ...@@ -8,17 +8,27 @@
// away from these. // away from these.
// //
exports.recommendedPlugins = [ exports.recommendedPlugins = [
"ant",
"antisamy-markup-formatter", "antisamy-markup-formatter",
"credentials", "build-monitor-plugin",
"build-timeout",
"cloudbees-folder",
"credentials-binding",
"email-ext",
"git",
"github-branch-source", "github-branch-source",
"junit", "gradle",
"ldap",
"mailer", "mailer",
"matrix-auth", // "matrix-auth",
"script-security", "pam-auth",
"pipeline-stage-view",
"ssh-slaves",
"subversion", "subversion",
"translation", "timestamper",
"workflow-aggregator", "workflow-aggregator",
"workflow-multibranch" "workflow-multibranch",
"ws-cleanup"
]; ];
// //
...@@ -27,50 +37,84 @@ exports.recommendedPlugins = [ ...@@ -27,50 +37,84 @@ exports.recommendedPlugins = [
// //
exports.availablePlugins = [ exports.availablePlugins = [
{ {
"category": "General", "category":"Organization and Administration",
"description": "(a collection of things I cannot think of a better name for)",
"plugins": [ "plugins": [
{ "name": "external-monitor-job" }, // { "name": "dashboard-view" },
{ "name": "translation" } { "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": [ "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", "category":"Build Tools",
"plugins": [ "plugins": [
{ "name": "ant" }, { "name": "ant" },
{ "name": "maven-plugin" } { "name": "gradle" },
{ "name": "msbuild" },
{ "name": "nodejs" }
] ]
}, },
{ {
"category":"Build Analysis and Reporting", "category":"Build Analysis and Reporting",
"plugins": [ "plugins": [
{ "name": "javadoc" }, // { "name": "checkstyle" },
{ "name": "junit" } // { "name": "cobertura" },
{ "name": "htmlpublisher" },
{ "name": "junit" },
// { "name": "sonar" },
// { "name": "warnings" },
{ "name": "xunit" }
] ]
}, },
{ {
"category":"Pipelines and Continuous Delivery", "category":"Pipelines and Continuous Delivery",
"plugins": [ "plugins": [
{ "name": "workflow-aggregator" }, { "name": "workflow-aggregator" },
{ "name": "workflow-multibranch" },
{ "name": "github-branch-source" }, { "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": [ "plugins": [
{ "name": "bitbucket" },
{ "name": "clearcase" },
{ "name": "cvs" }, { "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": [ "plugins": [
{ "name": "matrix-project" }, { "name": "matrix-project" },
{ "name": "ssh-slaves" }, { "name": "ssh-slaves" },
...@@ -80,18 +124,22 @@ exports.availablePlugins = [ ...@@ -80,18 +124,22 @@ exports.availablePlugins = [
{ {
"category":"User Management and Security", "category":"User Management and Security",
"plugins": [ "plugins": [
{ "name": "credentials" }, // { "name": "matrix-auth" },
{ "name": "ldap" },
{ "name": "matrix-auth" },
{ "name": "pam-auth" }, { "name": "pam-auth" },
{ "name": "script-security" }, { "name": "ldap" },
{ "name": "ssh-credentials" } // { "name": "role-strategy" },
{ "name": "active-directory" }
] ]
}, },
{ {
"category":"Notifications and Publishing", "category":"Notifications and Publishing",
"plugins": [ "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'); ...@@ -6,5 +6,10 @@ var pluginSetupWizard = require('./pluginSetupWizardGui');
// This entry point for the bundle only bootstraps the main module in a browser // This entry point for the bundle only bootstraps the main module in a browser
$(function() { $(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'); ...@@ -7,9 +7,13 @@ var jquery = require('jquery-detached');
var bootstrap = require('bootstrap-detached'); var bootstrap = require('bootstrap-detached');
var jenkins = require('./util/jenkins'); var jenkins = require('./util/jenkins');
var pluginManager = require('./api/pluginManager'); var pluginManager = require('./api/pluginManager');
var securityConfig = require('./api/securityConfig');
var wh = require('window-handle');
window.zq = jquery.getJQuery();
// Setup the dialog, exported // 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 // 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 $ = jquery.getJQuery();
var $bs = bootstrap.getBootstrap(); var $bs = bootstrap.getBootstrap();
...@@ -109,6 +113,9 @@ var createPluginSetupWizard = function() { ...@@ -109,6 +113,9 @@ var createPluginSetupWizard = function() {
var progressPanel = require('./templates/progressPanel.hbs'); var progressPanel = require('./templates/progressPanel.hbs');
var pluginSelectionPanel = require('./templates/pluginSelectionPanel.hbs'); var pluginSelectionPanel = require('./templates/pluginSelectionPanel.hbs');
var successPanel = require('./templates/successPanel.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 offlinePanel = require('./templates/offlinePanel.hbs');
var pluginSetupWizard = require('./templates/pluginSetupWizard.hbs'); var pluginSetupWizard = require('./templates/pluginSetupWizard.hbs');
var incompleteInstallationPanel = require('./templates/incompleteInstallationPanel.hbs'); var incompleteInstallationPanel = require('./templates/incompleteInstallationPanel.hbs');
...@@ -142,7 +149,7 @@ var createPluginSetupWizard = function() { ...@@ -142,7 +149,7 @@ var createPluginSetupWizard = function() {
// Instantiate the wizard panel // Instantiate the wizard panel
var $wizard = $(pluginSetupWizard()); var $wizard = $(pluginSetupWizard());
$wizard.appendTo('body'); $wizard.appendTo(appendTarget);
var $container = $wizard.find('.modal-content'); var $container = $wizard.find('.modal-content');
var currentPanel; var currentPanel;
...@@ -177,7 +184,7 @@ var createPluginSetupWizard = function() { ...@@ -177,7 +184,7 @@ var createPluginSetupWizard = function() {
decorations[i]($base); 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 if(panel === currentPanel) { // just replace id-marked elements
var $upd = $(html); var $upd = $(html);
$upd.find('*[id]').each(function() { $upd.find('*[id]').each(function() {
...@@ -201,6 +208,12 @@ var createPluginSetupWizard = function() { ...@@ -201,6 +208,12 @@ var createPluginSetupWizard = function() {
$container.append(html); $container.append(html);
decorate($container); 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) { if(oncomplete) {
oncomplete(); oncomplete();
} }
...@@ -306,13 +319,22 @@ var createPluginSetupWizard = function() { ...@@ -306,13 +319,22 @@ var createPluginSetupWizard = function() {
}; };
// Define actions // Define actions
var showInstallProgress = function() { var showInstallProgress = function(state) {
if(state) {
if(/CREATE_ADMIN_USER/.test(state)) {
setupFirstUser();
return;
}
}
initInstallingPluginList(); initInstallingPluginList();
setPanel(progressPanel, { installingPlugins : installingPlugins }); setPanel(progressPanel, { installingPlugins : installingPlugins });
// call to the installStatus, update progress bar & plugin details; transition on complete // call to the installStatus, update progress bar & plugin details; transition on complete
var updateStatus = function() { var updateStatus = function() {
pluginManager.installStatus(handleGenericError(function(jobs) { pluginManager.installStatus(handleGenericError(function(data) {
var jobs = data.jobs;
var i, j; var i, j;
var complete = 0; var complete = 0;
var total = 0; var total = 0;
...@@ -402,10 +424,7 @@ var createPluginSetupWizard = function() { ...@@ -402,10 +424,7 @@ var createPluginSetupWizard = function() {
else { else {
// mark complete // mark complete
$('.progress-bar').css({width: '100%'}); $('.progress-bar').css({width: '100%'});
setPanel(successPanel, { setupFirstUser();
installingPlugins : installingPlugins,
restartRequired: restartRequired
});
} }
})); }));
}; };
...@@ -416,7 +435,7 @@ var createPluginSetupWizard = function() { ...@@ -416,7 +435,7 @@ var createPluginSetupWizard = function() {
// Called to complete the installation // Called to complete the installation
var finishInstallation = function() { var finishInstallation = function() {
jenkins.goTo('/'); closeInstaller();
}; };
// load the plugin data, callback // load the plugin data, callback
...@@ -647,6 +666,63 @@ var createPluginSetupWizard = function() { ...@@ -647,6 +666,63 @@ var createPluginSetupWizard = function() {
} }
}; };
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 // Call this to resume an installation after restart
var resumeInstallation = function() { var resumeInstallation = function() {
// don't re-initialize installing plugins // don't re-initialize installing plugins
...@@ -716,7 +792,12 @@ var createPluginSetupWizard = function() { ...@@ -716,7 +792,12 @@ var createPluginSetupWizard = function() {
'.select-category': selectCategory, '.select-category': selectCategory,
'.close': closeInstaller, '.close': closeInstaller,
'.resume-installation': resumeInstallation, '.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) { for(var cls in actions) {
bindClickHandler(cls, actions[cls]); bindClickHandler(cls, actions[cls]);
...@@ -737,7 +818,9 @@ var createPluginSetupWizard = function() { ...@@ -737,7 +818,9 @@ var createPluginSetupWizard = function() {
} }
// check for updates when first loaded... // 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(jobs.length > 0) {
if (installingPlugins.length === 0) { if (installingPlugins.length === 0) {
// This can happen on a page reload if we are in the middle of // This can happen on a page reload if we are in the middle of
...@@ -752,10 +835,10 @@ var createPluginSetupWizard = function() { ...@@ -752,10 +835,10 @@ var createPluginSetupWizard = function() {
selectedPluginNames.push(jobs[i].name); selectedPluginNames.push(jobs[i].name);
} }
} }
showInstallProgress(); showInstallProgress(data.state);
})); }));
} else { } else {
showInstallProgress(); showInstallProgress(data.state);
} }
return; return;
} }
......
<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"> <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> <h4 class="modal-title">{{translations.installWizard_installIncomplete_title}}</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
......
<div class="modal-header"> <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>
<div class="modal-body"> <div class="modal-body">
<div class="jumbotron welcome-panel offline"> <div class="jumbotron welcome-panel offline">
<h1>{{translations.installWizard_offline_title}}</h1> <h1>{{translations.installWizard_offline_title}}</h1>
<p>{{{translations.installWizard_offline_message}}}</p> <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> </div>
<div class="modal-header"> <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> <h4 class="modal-title">{{translations.installWizard_installCustom_title}}</h4>
</div> </div>
<div class="modal-body plugin-selector"> <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"> <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_pluginsInstalled_title}}</h4>
<h4 class="modal-title">{{translations.installWizard_installComplete_title}}</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="jumbotron welcome-panel success-panel"> <div class="jumbotron welcome-panel success-panel">
<h1>{{translations.installWizard_installComplete_banner}}</h1> <h1>{{translations.installWizard_pluginsInstalled_banner}}</h1>
{{#if restartRequired}} {{#if restartRequired}}
<p>{{translations.installWizard_installComplete_message}} {{translations.installWizard_installComplete_restartRequiredMessage}}</p> <p>{{translations.installWizard_installComplete_message}} {{translations.installWizard_installComplete_restartRequiredMessage}}</p>
......
<div class="modal-header"> <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> <h4 class="modal-title">{{translations.installWizard_welcomePanel_title}}</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
......
...@@ -100,15 +100,42 @@ exports.post = function(url, data, success, options) { ...@@ -100,15 +100,42 @@ exports.post = function(url, data, success, options) {
if(debug) { if(debug) {
console.log('post: ' + url); console.log('post: ' + url);
} }
var $ = jquery.getJQuery(); 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 = { var args = {
url: exports.baseUrl() + url, url: exports.baseUrl() + url,
type: 'POST', type: 'POST',
cache: false, cache: false,
dataType: 'json', dataType: 'json',
data: exports.stringify(data), data: formBody,
contentType: "application/json", contentType: "application/json",
success: success success: success,
headers: headers
}; };
if(options instanceof Object) { if(options instanceof Object) {
$.extend(args, options); $.extend(args, options);
...@@ -186,3 +213,64 @@ exports.testConnectivity = function(handler) { ...@@ -186,3 +213,64 @@ exports.testConnectivity = function(handler) {
}; };
testConnectivity(); 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));
};
...@@ -55,12 +55,16 @@ ...@@ -55,12 +55,16 @@
animation-name: none; animation-name: none;
} }
.modal {
padding: 20px 0;
}
.modal-dialog { .modal-dialog {
width: 90%; width: 90%;
height: 90%; height: 100%;
padding: 0; padding: 0;
position: relative; position: relative;
margin: 3% auto; margin: 0 auto;
max-width:992px; max-width:992px;
font-family:'roboto',sans-serif; font-family:'roboto',sans-serif;
} }
...@@ -715,6 +719,21 @@ ...@@ -715,6 +719,21 @@
} }
} }
} }
.security-panel {
&.security-panel, > iframe {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
border: 0;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
}
} }
@-ms-keyframes spin { @-ms-keyframes spin {
......
...@@ -48,17 +48,23 @@ var ajaxMocks = function(responseMappings) { ...@@ -48,17 +48,23 @@ var ajaxMocks = function(responseMappings) {
}, },
'/jenkins/updateCenter/installStatus': new LastResponse([{ '/jenkins/updateCenter/installStatus': new LastResponse([{
status: 'ok', 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', status: 'ok',
data: [ data: {
state: 'INSTALLING_PLUGINS',
jobs: [
{ {
name: 'subversion', name: 'subversion',
type: 'InstallJob', type: 'InstallJob',
installStatus: 'Success' installStatus: 'Success'
} }
] ]
}
}]), }]),
'/jenkins/pluginManager/plugins': { '/jenkins/pluginManager/plugins': {
status: 'ok', status: 'ok',
...@@ -133,7 +139,7 @@ var test = function(test, ajaxMappings) { ...@@ -133,7 +139,7 @@ var test = function(test, ajaxMappings) {
var pluginSetupWizard = jsTest.requireSrcModule('pluginSetupWizardGui'); var pluginSetupWizard = jsTest.requireSrcModule('pluginSetupWizardGui');
// exported init // exported init
pluginSetupWizard.init(); pluginSetupWizard.init('body');
test($, pluginSetupWizard); test($, pluginSetupWizard);
}); });
...@@ -210,7 +216,7 @@ describe("pluginSetupWizard.js", function () { ...@@ -210,7 +216,7 @@ describe("pluginSetupWizard.js", function () {
var pluginSetupWizard = jsTest.requireSrcModule('pluginSetupWizardGui'); var pluginSetupWizard = jsTest.requireSrcModule('pluginSetupWizardGui');
// exported init // exported init
pluginSetupWizard.init(); pluginSetupWizard.init('body');
expect($('.welcome-panel h1').text()).toBe('Offline'); expect($('.welcome-panel h1').text()).toBe('Offline');
...@@ -321,11 +327,16 @@ describe("pluginSetupWizard.js", function () { ...@@ -321,11 +327,16 @@ describe("pluginSetupWizard.js", function () {
var ajaxMappings = { var ajaxMappings = {
'/jenkins/updateCenter/installStatus': new LastResponse([{ '/jenkins/updateCenter/installStatus': new LastResponse([{
status: 'ok', 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', status: 'ok',
data: [ data: {
state: 'INSTALLING_PLUGINS',
jobs: [
{ {
name: 'subversion', name: 'subversion',
type: 'InstallJob', type: 'InstallJob',
...@@ -333,6 +344,7 @@ describe("pluginSetupWizard.js", function () { ...@@ -333,6 +344,7 @@ describe("pluginSetupWizard.js", function () {
requiresRestart: 'true' // a string... requiresRestart: 'true' // a string...
} }
] ]
}
}]) }])
}; };
test(function($) { test(function($) {
...@@ -342,7 +354,8 @@ describe("pluginSetupWizard.js", function () { ...@@ -342,7 +354,8 @@ describe("pluginSetupWizard.js", function () {
// validate a call to installPlugins with our defaults // validate a call to installPlugins with our defaults
setTimeout(function() { setTimeout(function() {
expect($('.install-done').is(':visible')).toBe(false); expect($('.install-done').is(':visible')).toBe(false);
expect($('.install-done-restart').is(':visible')).toBe(true);
expect($('.save-first-user').is(':visible')).toBe(true);
done(); done();
}, 500); }, 500);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册