Skip to content

Commit faf92c9

Browse files
author
Wei Li
committed
✨ Fixed #JENKINS-44586. add support for managing credentials
1 parent 198f4be commit faf92c9

File tree

10 files changed

+1034
-29
lines changed

10 files changed

+1034
-29
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package com.offbytwo.jenkins.integration;
2+
3+
import com.offbytwo.jenkins.JenkinsServer;
4+
import com.offbytwo.jenkins.model.Plugin;
5+
import com.offbytwo.jenkins.model.credentials.*;
6+
import org.apache.commons.lang.RandomStringUtils;
7+
import org.testng.SkipException;
8+
import org.testng.annotations.Test;
9+
10+
import java.io.IOException;
11+
import java.util.List;
12+
import java.util.Map;
13+
14+
import static org.testng.Assert.*;
15+
16+
@Test(groups = { Groups.NO_EXECUTOR_GROUP} )
17+
public class NoExecutorStartedManageCredentialsIT extends AbstractJenkinsIntegrationCase {
18+
19+
@Test
20+
public void credentialCRUDL() throws IOException {
21+
List<Plugin> plugins = jenkinsServer.getPluginManager().getPlugins();
22+
Plugin credentialPlugin = jenkinsServer.findPluginWithName("credentials");
23+
if (credentialPlugin == null) {
24+
throw new SkipException("No credentials plugin found. Skip Test");
25+
}
26+
27+
String pluginVersion = credentialPlugin.getVersion();
28+
if (pluginVersion.startsWith("1.")) {
29+
runTest(jenkinsServer);
30+
31+
//test CertificateCredential with upload cert file. The 2.x version may throw exceptions.
32+
CertificateCredential certificateCredential = new CertificateCredential();
33+
certificateCredential.setId("certficateTest-" + RandomStringUtils.randomAlphanumeric(24));
34+
certificateCredential.setCertificateSourceType(CertificateCredential.CERTIFICATE_SOURCE_TYPES.UPLOAD_CERT_FILE);
35+
certificateCredential.setCertificateContent("testcert".getBytes());
36+
certificateCredential.setPassword("testpasssword");
37+
38+
credentialOperations(jenkinsServer, certificateCredential);
39+
40+
} else {
41+
runTest(jenkinsServer);
42+
43+
//test SecretTextCredential, this is v2 only
44+
SecretTextCredential secretText = new SecretTextCredential();
45+
secretText.setId("secrettextcredentialTest-" + RandomStringUtils.randomAlphanumeric(24));
46+
secretText.setSecret("testsecrettext");
47+
48+
credentialOperations(jenkinsServer, secretText);
49+
}
50+
}
51+
52+
private void runTest(JenkinsServer jenkinsServer) throws IOException {
53+
String testUsername = "testusername";
54+
String testPassword = "testpassword";
55+
String credentialDescription = "testDescription";
56+
//test UsernamePasswordCredential
57+
UsernamePasswordCredential testUPCredential = new UsernamePasswordCredential();
58+
testUPCredential.setId("usernamepasswordcredentialTest-" + RandomStringUtils.randomAlphanumeric(24));
59+
testUPCredential.setUsername(testUsername);
60+
testUPCredential.setPassword(testPassword);
61+
testUPCredential.setDescription(credentialDescription);
62+
63+
credentialOperations(jenkinsServer, testUPCredential);
64+
65+
//test SSHKeyCredential
66+
SSHKeyCredential sshCredential = new SSHKeyCredential();
67+
sshCredential.setId("sshusercredentialTest-" + RandomStringUtils.randomAlphanumeric(24));
68+
sshCredential.setUsername(testUsername);
69+
sshCredential.setPassphrase(testPassword);
70+
sshCredential.setPrivateKeyType(SSHKeyCredential.PRIVATE_KEY_TYPES.DIRECT_ENTRY);
71+
sshCredential.setPrivateKeyValue("testPrivateKeyContent");
72+
73+
credentialOperations(jenkinsServer, sshCredential);
74+
75+
//test credential
76+
CertificateCredential certificateCredential = new CertificateCredential();
77+
certificateCredential.setId("certficateTest-" + RandomStringUtils.randomAlphanumeric(24));
78+
certificateCredential.setCertificateSourceType(CertificateCredential.CERTIFICATE_SOURCE_TYPES.FILE_ON_MASTER);
79+
certificateCredential.setCertificatePath("/tmp/test");
80+
certificateCredential.setPassword("testpasssword");
81+
82+
credentialOperations(jenkinsServer, certificateCredential);
83+
84+
}
85+
86+
private void credentialOperations(JenkinsServer jenkinsServer, Credential credential) throws IOException {
87+
//create the credential
88+
String credentialId = credential.getId();
89+
jenkinsServer.createCredential(credential, false);
90+
91+
//check if has been created by listing
92+
Map<String, Credential> credentials = jenkinsServer.listCredentials();
93+
Credential found = credentials.get(credentialId);
94+
assertNotNull(found);
95+
assertEquals(credential.getTypeName(), found.getTypeName());
96+
97+
//compare fields
98+
assertEquals(credentialId, found.getId());
99+
assertNotNull(found.getDisplayName());
100+
101+
//update the credential
102+
String updateDescription = "updatedDescription";
103+
credential.setDescription(updateDescription);
104+
jenkinsServer.updateCredential(credentialId, credential, false);
105+
106+
//verify it is updated
107+
credentials = jenkinsServer.listCredentials();
108+
found = credentials.get(credentialId);
109+
assertEquals(updateDescription, found.getDescription());
110+
111+
//delete the credential
112+
jenkinsServer.deleteCredential(credentialId, false);
113+
credentials = jenkinsServer.listCredentials();
114+
assertFalse(credentials.containsKey(credentialId));
115+
}
116+
}

jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsServer.java

Lines changed: 99 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,31 @@
66

77
package com.offbytwo.jenkins;
88

9-
import java.io.IOException;
10-
import java.net.URI;
11-
import java.util.List;
12-
import java.util.Map;
13-
14-
import javax.xml.bind.JAXBException;
15-
16-
import org.apache.http.HttpStatus;
17-
import org.apache.http.client.HttpResponseException;
18-
import org.apache.http.entity.ContentType;
19-
import org.dom4j.DocumentException;
20-
import org.slf4j.Logger;
21-
import org.slf4j.LoggerFactory;
22-
239
import com.google.common.base.Function;
2410
import com.google.common.base.Optional;
2511
import com.google.common.collect.ImmutableMap;
2612
import com.google.common.collect.Maps;
2713
import com.offbytwo.jenkins.client.JenkinsHttpClient;
2814
import com.offbytwo.jenkins.client.util.EncodingUtils;
2915
import com.offbytwo.jenkins.helper.JenkinsVersion;
30-
import com.offbytwo.jenkins.model.Build;
31-
import com.offbytwo.jenkins.model.Computer;
32-
import com.offbytwo.jenkins.model.ComputerSet;
33-
import com.offbytwo.jenkins.model.FolderJob;
34-
import com.offbytwo.jenkins.model.Job;
35-
import com.offbytwo.jenkins.model.JobConfiguration;
36-
import com.offbytwo.jenkins.model.JobWithDetails;
37-
import com.offbytwo.jenkins.model.LabelWithDetails;
38-
import com.offbytwo.jenkins.model.MainView;
39-
import com.offbytwo.jenkins.model.MavenJobWithDetails;
40-
import com.offbytwo.jenkins.model.PluginManager;
41-
import com.offbytwo.jenkins.model.Queue;
42-
import com.offbytwo.jenkins.model.QueueItem;
43-
import com.offbytwo.jenkins.model.QueueReference;
44-
import com.offbytwo.jenkins.model.View;
16+
import com.offbytwo.jenkins.model.*;
17+
import com.offbytwo.jenkins.model.credentials.Credential;
18+
import com.offbytwo.jenkins.model.credentials.CredentialManager;
19+
import org.apache.commons.collections.CollectionUtils;
20+
import org.apache.commons.collections.Predicate;
21+
import org.apache.http.HttpStatus;
22+
import org.apache.http.client.HttpResponseException;
23+
import org.apache.http.entity.ContentType;
24+
import org.dom4j.DocumentException;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
28+
import javax.xml.bind.JAXBException;
29+
import java.io.IOException;
30+
import java.net.URI;
31+
import java.rmi.server.ExportException;
32+
import java.util.List;
33+
import java.util.Map;
4534

4635
/**
4736
* The main starting point for interacting with a Jenkins server.
@@ -51,6 +40,8 @@ public class JenkinsServer {
5140

5241
private final JenkinsHttpClient client;
5342

43+
private CredentialManager credentialManager;
44+
5445
/**
5546
* Create a new Jenkins server reference given only the server address
5647
*
@@ -917,4 +908,83 @@ private String toViewBaseUrl(FolderJob folder, String name) {
917908
return toBaseUrl(folder) + "view/" + EncodingUtils.encode(name);
918909
}
919910

911+
/**
912+
* List the credentials from the Jenkins server.
913+
* @return a hash map of the credentials. The key is the id of each credential.
914+
* @throws IOException
915+
*/
916+
public Map<String, Credential> listCredentials() throws IOException {
917+
return this.getCredentialManager().listCredentials();
918+
}
919+
920+
/**
921+
* Create the given credential
922+
* @param credential a credential instance
923+
* @param crumbFlag
924+
* @throws IOException
925+
*/
926+
public void createCredential(Credential credential, boolean crumbFlag) throws IOException {
927+
this.getCredentialManager().createCredential(credential, crumbFlag);
928+
}
929+
930+
/**
931+
* Update an existing credential
932+
* @param credentialId the id of the credential
933+
* @param credential the updated credential instance
934+
* @param crumbFlag
935+
* @throws IOException
936+
*/
937+
public void updateCredential(String credentialId, Credential credential, boolean crumbFlag) throws IOException {
938+
this.getCredentialManager().updateCredential(credentialId, credential, crumbFlag);
939+
}
940+
941+
/**
942+
* Delete an existing credential
943+
* @param credentialId the id of the credential to delete
944+
* @param crumbFlag
945+
* @throws IOException
946+
*/
947+
public void deleteCredential(String credentialId, boolean crumbFlag) throws IOException {
948+
this.getCredentialManager().deleteCredential(credentialId, crumbFlag);
949+
}
950+
951+
/**
952+
* Return the credentialManager instance. Will initialise it if it's never used before.
953+
* @return the credentialManager instance
954+
* @throws IOException
955+
*/
956+
private CredentialManager getCredentialManager() throws IOException {
957+
if (this.credentialManager == null) {
958+
Plugin credentialPlugin = findPluginWithName("credentials");
959+
if (credentialPlugin == null) {
960+
throw new ExportException("credential plugin is not installed");
961+
}
962+
String version = credentialPlugin.getVersion();
963+
this.credentialManager = new CredentialManager(version, this.client);
964+
}
965+
return this.credentialManager;
966+
}
967+
968+
/**
969+
* Find a plugin that matches the given short name
970+
* @param pluginShortName the short name of the plugin to find
971+
* @return the pluin object that is found. Can be null if no match found.
972+
* @throws IOException
973+
*/
974+
public Plugin findPluginWithName(final String pluginShortName) throws IOException {
975+
List<Plugin> plugins = this.getPluginManager().getPlugins();
976+
Object foundPlugin = CollectionUtils.find(plugins, new Predicate() {
977+
@Override
978+
public boolean evaluate(Object o) {
979+
Plugin p = (Plugin) o;
980+
if (p.getShortName().equalsIgnoreCase(pluginShortName)) {
981+
return true;
982+
} else {
983+
return false;
984+
}
985+
}
986+
});
987+
988+
return foundPlugin == null ? null : (Plugin) foundPlugin;
989+
}
920990
}

jenkins-client/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,58 @@ public HttpResponse post_form_with_result(String path, List<NameValuePair> data,
346346
return response;
347347
}
348348

349+
/**
350+
* Perform a POST request using form url encoding.
351+
*
352+
* This method was added for the purposes of creating credentials, but may be
353+
* useful for other API calls as well.
354+
*
355+
* Unlike post and post_xml, the path is *not* modified by adding
356+
* "/api/json". Additionally, the params in data are provided as both
357+
* request parameters including a json parameter, *and* in the
358+
* JSON-formatted StringEntity, because this is what the folder creation
359+
* call required. It is unclear if any other jenkins APIs operate in this
360+
* fashion.
361+
*
362+
* @param path path to request, can be relative or absolute
363+
* @param data data to post
364+
* @param crumbFlag true / false.
365+
* @throws IOException in case of an error.
366+
*/
367+
public void post_form_json(String path, Map<String, Object> data, boolean crumbFlag) throws IOException {
368+
HttpPost request;
369+
if (data != null) {
370+
// https://gist.github.com/stuart-warren/7786892 was slightly
371+
// helpful here
372+
List<String> queryParams = Lists.newArrayList();
373+
queryParams.add("json=" + EncodingUtils.encodeParam(JSONObject.fromObject(data).toString()));
374+
String value = mapper.writeValueAsString(data);
375+
StringEntity stringEntity = new StringEntity(value, ContentType.APPLICATION_FORM_URLENCODED);
376+
request = new HttpPost(noapi(path) + StringUtils.join(queryParams, "&"));
377+
request.setEntity(stringEntity);
378+
} else {
379+
request = new HttpPost(noapi(path));
380+
}
381+
382+
if (crumbFlag == true) {
383+
Crumb crumb = get("/crumbIssuer", Crumb.class);
384+
if (crumb != null) {
385+
request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb()));
386+
}
387+
}
388+
389+
HttpResponse response = client.execute(request, localContext);
390+
getJenkinsVersionFromHeader(response);
391+
392+
try {
393+
httpResponseValidator.validateResponse(response);
394+
} finally {
395+
EntityUtils.consume(response.getEntity());
396+
releaseConnection(request);
397+
}
398+
}
399+
400+
349401
/**
350402
* Perform a POST request of XML (instead of using json mapper) and return a
351403
* string rendering of the response entity.

0 commit comments

Comments
 (0)