Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Staging branch #25

Merged
merged 12 commits into from Feb 26, 2016
29 changes: 28 additions & 1 deletion README.md
Expand Up @@ -7,13 +7,18 @@ The CloudTest Jenkins plugin provides the ability to:
* play CloudTest compositions and include the output in the build's test results

### Pre-requisites
The CloudTest plug-in requires Jenkins 1.447 or later.
The CloudTest plug-in requires Jenkins 1.580.1 or later.

### Future development on the plugin
* mvn hpi:run -Djetty.port=8081

### Global Configuration Options

Before using the plug-in, you will need to provide the CloudTest server information, in the "Manage Jenkins" -> "Configure System" page.
This includes the CloudTest URL and a set of credentials. _We recommend creating a dedicated CloudTest account for Jenkins to use._

If you wish to deploy CloudTest test environments we also suggest you setup a CloudTest Server that points to CloudTest Manager, at https://cloudtestmanager.soasta.com/concerto/ . You will not need to create a dedicated CTM account, the Jenkins plugin will not log you out.

The CloudTest password is encrypted before saving it to disk. It is also masked (e.g. "\*\*\*\*") in all output generated during builds.

### Build Steps
Expand All @@ -29,6 +34,28 @@ Silently installs an IPA file on one or more attached iOS devices. This ensures
##### Play Composition
Executes a CloudTest composition, and saves the output in the build's test results. You can include this build step multiple times if there are multiple compositions in your test suite.

##### Start Grid
Starts the grid with the specified CloudTest server, and name, and ensures that the object reaches a ready status. In the event that the grid fails to reach a ready status, it will be torn down and the build step will fail.

##### Stop Grid
Terminates the grid, and ensures that the grid reaches a terminated status. In the event that the grid fails to reach a terminated status, the build step will fail.

##### Start RSDB
Starts the RSDB with the specified CloudTest server, and name, and ensures that the RSDB reaches a ready status. In the event that the RSDB fails to reach a ready status, it will be torn down and the build step will fail.

##### Stop RSDB
Terminates the RSDB, and ensures that the RSDB reaches a terminated status. In the event that the grid fails to reach a terminated status, the build step will fail.

##### Test Environments
Note that for test environments you will need to set up a CloudTest Server to point to CloudTest Manager https://cloudtestmanager.soasta.com/concerto/.

##### Start Test Environment
Starts the test environment with the specified CloudTest server (Note this MUST be CTM), and name, and ensures that the object reaches a ready status. In the event that the test environment fails to reach a ready status, it will be torn down and the build step will fail.

##### Stop Test Environment
Terminates the test environment, and ensures that the test environment reaches a terminated status. In the event that the test environment fails to reach a terminated status, the build step will fail.


##### Wake Up iOS Device
Wakes up one or more attached iOS devices, and opens Mobile Safari to the most recently-viewed page (e.g. TouchTest Agent). This can optionally be used at the beginning of a build, to "prep" the devices for testing. _NOTE: there is no Android version of this build step, because the Android SDK already provides this functionality._

Expand Down
8 changes: 8 additions & 0 deletions pom.xml
Expand Up @@ -38,6 +38,14 @@
<url>http://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>junit</artifactId>
<version>1.5</version>
</dependency>
</dependencies>

<pluginRepositories>
<pluginRepository>
Expand Down
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2012-2013, CloudBees, Inc., SOASTA, Inc.
* All Rights Reserved.
*/
package com.soasta.jenkins;

import hudson.FilePath;
import hudson.model.AbstractProject;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;

import javax.inject.Inject;
import java.io.IOException;

/**
* @author Kohsuke Kawaguchi
*/
public abstract class AbstractCloudTestPostBuildDescriptor extends BuildStepDescriptor<Publisher> {
@Inject
CloudTestServer.DescriptorImpl serverDescriptor;

@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}

public ListBoxModel doFillCloudTestServerIDItems() {
ListBoxModel r = new ListBoxModel();
for (CloudTestServer s : serverDescriptor.getServers()) {
r.add(s.getName(), s.getId());
}
return r;
}

protected FormValidation validateFileMask(AbstractProject project, String value) throws IOException {
if (value.contains("${")) {
// if the value contains a variable reference, bail out from the check because we can end up
// warning a file that actually resolves correctly at the runtime
// the same change is made in FilePath.validateFileMask independently, and in the future
// we can remove this check from here
return FormValidation.ok();
}

// Make sure the file exists.
return FilePath.validateFileMask(project.getSomeWorkspace(), value);
}
}
16 changes: 1 addition & 15 deletions src/main/java/com/soasta/jenkins/AbstractSCommandBuilder.java
Expand Up @@ -69,21 +69,7 @@ protected ArgumentListBuilder getSCommandArgs(AbstractBuild<?, ?> build, BuildLi
// Extract the destination CloudTest host.
String host = new URL(s.getUrl()).getHost();

// Check if the proxy applies for this destination host.
// This code is more or less copied from ProxyConfiguration.createProxy() :-(.
boolean isNonProxyHost = false;
for (Pattern p : proxyConfig.getNoProxyHostPatterns()) {
if (p.matcher(host).matches()) {
// It's a match.
// Don't use the proxy.
isNonProxyHost = true;

// No need to continue checking the list.
break;
}
}

if (!isNonProxyHost) {
if (ProxyChecker.useProxy(host, proxyConfig)) {
// Add the SCommand proxy parameters.
args.add("httpproxyhost=" + proxyConfig.name)
.add("httpproxyport=" + proxyConfig.port);
Expand Down
67 changes: 43 additions & 24 deletions src/main/java/com/soasta/jenkins/CloudTestServer.java
Expand Up @@ -15,12 +15,12 @@
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
Expand Down Expand Up @@ -55,12 +55,16 @@ public class CloudTestServer extends AbstractDescribableImpl<CloudTestServer> {
* URL like "http://touchtestlite.soasta.com/concerto/"
*/
private final String url;

private static final int CONNECTION_TIMEOUT = Integer.parseInt(System.getProperty("com.soasta.Jenkins.ConnectionTimeout", "15000"));

private final String username;
private final Secret password;

private final String id;
private final String name;

private static final String REPOSITORY_SERVICE_BASE_URL = "/services/rest/RepositoryService/v1/Tokens";

private transient boolean generatedIdOrName;

Expand All @@ -78,7 +82,7 @@ public CloudTestServer(String url, String username, Secret password, String id,
url+="concerto/";
this.url = url;
}

if (username == null || username.isEmpty()) {
this.username = "";
}
Expand Down Expand Up @@ -172,27 +176,34 @@ public Object readResolve() throws IOException {
public FormValidation validate() throws IOException {
HttpClient hc = createClient();

PostMethod post = new PostMethod(url + "Login");
post.addParameter("userName",getUsername());
// to validate the credentials we will request a token from the repository.

JSONObject obj = new JSONObject();
obj.put("userName", username);
obj.put("password", password.getPlainText());

PutMethod put = new PutMethod(url + REPOSITORY_SERVICE_BASE_URL);

if (getPassword() != null) {
post.addParameter("password",getPassword().getPlainText());
} else {
post.addParameter("password","");
StringRequestEntity requestEntity = new StringRequestEntity(
obj.toString(),
"application/json",
"UTF-8");

put.setRequestEntity(requestEntity);
int statusCode = hc.executeMethod(put);
LOGGER.info("Status code got back for URL: " + (url + REPOSITORY_SERVICE_BASE_URL) + " STATUS : " + statusCode );

switch (statusCode)
{
case 200:
return FormValidation.ok("Success!");
case 404:
return FormValidation.error("[404] Could not find the server");
case 401:
return FormValidation.error("[401] Invalid Credentials");
default:
return FormValidation.error("Unknown error, Http Code " + statusCode);
}

hc.executeMethod(post);

// if the login succeeds, we'll see a redirect
Header loc = post.getResponseHeader("Location");
if (loc!=null && loc.getValue().endsWith("/Central"))
return FormValidation.ok("Success!");

if (!post.getResponseBodyAsString().contains("SOASTA"))
return FormValidation.error(getUrl()+" doesn't look like a CloudTest server");

// if it fails, the server responds with 200!
return FormValidation.error("Invalid credentials.");
}

/**
Expand Down Expand Up @@ -251,11 +262,19 @@ public void startElement(String uri, String localName, String qName, Attributes
this.getDescriptor().getDisplayName() + "\': <" + url + ">.");
}

private HttpClient createClient() {
private HttpClient createClient() throws IOException {
HttpClient hc = new HttpClient();

hc.getParams().setParameter("http.socket.timeout", CONNECTION_TIMEOUT);
hc.getParams().setParameter("http.connection.timeout", CONNECTION_TIMEOUT);
// get the actual host name to compare in the proxy checker.
String host = new URL(url).getHost();

Jenkins j = Jenkins.getInstance();
ProxyConfiguration jpc = j!=null ? j.proxy : null;
if(jpc != null) {

if(jpc != null && jpc.name != null && ProxyChecker.useProxy(host, jpc))
{
hc.getHostConfiguration().setProxy(jpc.name, jpc.port);
if(jpc.getUserName() != null)
hc.getState().setProxyCredentials(AuthScope.ANY,new UsernamePasswordCredentials(jpc.getUserName(),jpc.getPassword()));
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/soasta/jenkins/CommonInstaller.java
Expand Up @@ -36,6 +36,7 @@
import org.apache.commons.io.input.CountingInputStream;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.jenkinsci.remoting.RoleChecker;
/*******************************************************************************************************************************
* END (Jenkins User-Agent change related imports)
*******************************************************************************************************************************/
Expand Down Expand Up @@ -138,6 +139,12 @@ private void process(File f) {
}
}
}
@Override
public void checkRoles(RoleChecker arg0) throws SecurityException
{
// TODO Auto-generated method stub

}
}

/**
Expand Down Expand Up @@ -312,6 +319,12 @@ static private void unzip(File dir, File zipFile) throws IOException {
zip.close();
}
}
@Override
public void checkRoles(RoleChecker arg0) throws SecurityException
{
// TODO Auto-generated method stub

}
}
/*******************************************************************************************************************************
* END (Jenkins User-Agent related code changes)
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/com/soasta/jenkins/ProxyChecker.java
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2012-2016, CloudBees, Inc., SOASTA, Inc.
* All Rights Reserved.
*/
package com.soasta.jenkins;

import java.util.regex.Pattern;

import hudson.ProxyConfiguration;

public class ProxyChecker
{
public static boolean useProxy(String host, ProxyConfiguration proxyConfig)
{
// Check if the proxy applies for this destination host.
// This code is more or less copied from ProxyConfiguration.createProxy() :-(.
if (proxyConfig != null && proxyConfig.name != null)
{
for (Pattern p : proxyConfig.getNoProxyHostPatterns())
{
if (p.matcher(host).matches())
{
// It's a match.
// Don't use the proxy.
return false;
}
}
// we have checked, and the proxy host pattern doesn't match our whitelist, So use the proxy.
return true;
}
else
{
// jenkins is not configured to use a proxy.
return false;
}
}
}