Skip to content

Commit 234164a

Browse files
author
Wei Li
committed
✨ Fixed #JENKINS-27013. add support for managing credentials
1 parent e2a0fe9 commit 234164a

File tree

7 files changed

+590
-15
lines changed

7 files changed

+590
-15
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.offbytwo.jenkins.integration;
2+
3+
import com.offbytwo.jenkins.model.credentials.Credential;
4+
import com.offbytwo.jenkins.model.credentials.UsernamePasswordCredential;
5+
import org.apache.commons.lang.RandomStringUtils;
6+
import org.testng.annotations.Test;
7+
8+
import java.io.IOException;
9+
import java.util.Map;
10+
11+
import static org.testng.Assert.*;
12+
13+
/**
14+
* Created by weili on 31/05/2017.
15+
*/
16+
@Test(groups = { Groups.NO_EXECUTOR_GROUP} )
17+
public class NoExecutorStartedManageCredentialsIT extends AbstractJenkinsIntegrationCase {
18+
19+
@Test
20+
public void credentialCRUDL() throws IOException {
21+
String credentialId = "credentialIntegrationTest-" + RandomStringUtils.randomAlphanumeric(24);
22+
String testUsername = "testusername";
23+
String testPassword = "testpassword";
24+
UsernamePasswordCredential testCredential = new UsernamePasswordCredential();
25+
testCredential.setId(credentialId);
26+
testCredential.setUsername(testUsername);
27+
testCredential.setPassword(testPassword);
28+
//create the credential
29+
jenkinsServer.createCredential(testCredential, false);
30+
31+
//check if has been created by listing
32+
Map<String, Credential> credentials = jenkinsServer.listCredentials();
33+
Credential found = credentials.get(credentialId);
34+
assertNotNull(found);
35+
assertTrue(found instanceof UsernamePasswordCredential);
36+
37+
//compare fields
38+
assertEquals(credentialId, found.getId());
39+
assertEquals(testUsername, ((UsernamePasswordCredential)found).getUsername());
40+
assertNotNull(found.getDisplayName());
41+
42+
//update the credential
43+
String updateUsername = "updatedTestusername";
44+
testCredential.setUsername(updateUsername);
45+
jenkinsServer.updateCredential(credentialId, testCredential, false);
46+
47+
//verify it is updated
48+
credentials = jenkinsServer.listCredentials();
49+
found = credentials.get(credentialId);
50+
assertEquals(updateUsername, ((UsernamePasswordCredential)found).getUsername());
51+
52+
//delete the credential
53+
jenkinsServer.deleteCredential(credentialId, false);
54+
credentials = jenkinsServer.listCredentials();
55+
assertFalse(credentials.containsKey(credentialId));
56+
}
57+
}

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

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,20 @@
88

99
import java.io.IOException;
1010
import java.net.URI;
11+
import java.rmi.server.ExportException;
12+
import java.util.Collections;
1113
import java.util.List;
1214
import java.util.Map;
1315

1416
import javax.xml.bind.JAXBException;
1517

18+
import com.google.common.collect.Collections2;
19+
import com.google.common.collect.Lists;
20+
import com.offbytwo.jenkins.model.*;
21+
import com.offbytwo.jenkins.model.credentials.Credential;
22+
import com.offbytwo.jenkins.model.credentials.CredentialManager;
23+
import org.apache.commons.collections.CollectionUtils;
24+
import org.apache.commons.collections.Predicate;
1625
import org.apache.http.HttpStatus;
1726
import org.apache.http.client.HttpResponseException;
1827
import org.apache.http.entity.ContentType;
@@ -27,21 +36,6 @@
2736
import com.offbytwo.jenkins.client.JenkinsHttpClient;
2837
import com.offbytwo.jenkins.client.util.EncodingUtils;
2938
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;
4539

4640
/**
4741
* The main starting point for interacting with a Jenkins server.
@@ -51,6 +45,8 @@ public class JenkinsServer {
5145

5246
private final JenkinsHttpClient client;
5347

48+
private CredentialManager credentialManager;
49+
5450
/**
5551
* Create a new Jenkins server reference given only the server address
5652
*
@@ -917,4 +913,83 @@ private String toViewBaseUrl(FolderJob folder, String name) {
917913
return toBaseUrl(folder) + "view/" + EncodingUtils.encode(name);
918914
}
919915

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

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,59 @@ public void post_form(String path, Map<String, String> data, boolean crumbFlag)
312312
}
313313
}
314314

315+
/**
316+
* Perform a POST request using form url encoding.
317+
*
318+
* This method was added for the purposes of creating credentials, but may be
319+
* useful for other API calls as well.
320+
*
321+
* Unlike post and post_xml, the path is *not* modified by adding
322+
* "/api/json". Additionally, the params in data are provided as both
323+
* request parameters including a json parameter, *and* in the
324+
* JSON-formatted StringEntity, because this is what the folder creation
325+
* call required. It is unclear if any other jenkins APIs operate in this
326+
* fashion.
327+
*
328+
* @param path path to request, can be relative or absolute
329+
* @param data data to post
330+
* @param crumbFlag true / false.
331+
* @throws IOException in case of an error.
332+
*/
333+
public void post_form_json(String path, Map<String, Object> data, boolean crumbFlag) throws IOException {
334+
HttpPost request;
335+
if (data != null) {
336+
// https://gist.github.com/stuart-warren/7786892 was slightly
337+
// helpful here
338+
List<String> queryParams = Lists.newArrayList();
339+
340+
queryParams.add("json=" + EncodingUtils.encodeParam(JSONObject.fromObject(data).toString()));
341+
String value = mapper.writeValueAsString(data);
342+
StringEntity stringEntity = new StringEntity(value, ContentType.APPLICATION_FORM_URLENCODED);
343+
request = new HttpPost(noapi(path) + StringUtils.join(queryParams, "&"));
344+
request.setEntity(stringEntity);
345+
} else {
346+
request = new HttpPost(noapi(path));
347+
}
348+
349+
if (crumbFlag == true) {
350+
Crumb crumb = get("/crumbIssuer", Crumb.class);
351+
if (crumb != null) {
352+
request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb()));
353+
}
354+
}
355+
356+
HttpResponse response = client.execute(request, localContext);
357+
getJenkinsVersionFromHeader(response);
358+
359+
try {
360+
httpResponseValidator.validateResponse(response);
361+
} finally {
362+
EntityUtils.consume(response.getEntity());
363+
releaseConnection(request);
364+
}
365+
}
366+
367+
315368
/**
316369
* Perform a POST request of XML (instead of using json mapper) and return a
317370
* string rendering of the response entity.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.offbytwo.jenkins.model.credentials;
2+
3+
4+
import com.fasterxml.jackson.annotation.JsonSubTypes;
5+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
6+
import com.offbytwo.jenkins.model.BaseModel;
7+
8+
import java.util.Map;
9+
10+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "typeName")
11+
@JsonSubTypes({@JsonSubTypes.Type(value = UsernamePasswordCredential.class, name = "Username with password")})
12+
/**
13+
* Base class for credentials. Should not be instantiated directly.
14+
*/
15+
public abstract class Credential extends BaseModel {
16+
17+
protected static final String SCOPE_GLOBAL = "GLOBAL";
18+
private static final String BASE_URL = "/credentials/store/system/domain/_/";
19+
20+
private String id = "";
21+
private String scope = SCOPE_GLOBAL;
22+
private String description = "";
23+
private String fullName = "";
24+
private String displayName = "";
25+
26+
private String typeName = "";
27+
28+
protected String $class;
29+
30+
public String getScope() {
31+
return scope;
32+
}
33+
34+
/**
35+
* Set the scope of the credential. It is "GLOBAL" by default. Should not be changed.
36+
* @param scope
37+
*/
38+
public void setScope(String scope) {
39+
this.scope = scope;
40+
}
41+
42+
public String getDescription() {
43+
return description;
44+
}
45+
46+
public void setDescription(String description) {
47+
this.description = description;
48+
}
49+
50+
public String getId() {
51+
return this.id;
52+
}
53+
54+
/**
55+
* Set the id of the credential. When creating a new credential, if this is not provided, a random id will be generated by Jenkins.
56+
* Required for update and delete operations
57+
* @param id the id of the credential.
58+
*/
59+
public void setId(String id) {
60+
this.id = id;
61+
}
62+
63+
public String getFullName() {
64+
return fullName;
65+
}
66+
67+
/**
68+
* Should not be used. The value is set by Jenkins.
69+
* @param fullName
70+
*/
71+
public void setFullName(String fullName) {
72+
this.fullName = fullName;
73+
}
74+
75+
public String getTypeName() {
76+
return typeName;
77+
}
78+
79+
/**
80+
* Should not be used. The value is set by Jenkins.
81+
* @param typeName
82+
*/
83+
public void setTypeName(String typeName) {
84+
this.typeName = typeName;
85+
}
86+
87+
public String getDisplayName() {
88+
return this.displayName;
89+
}
90+
91+
/**
92+
* Should not be used. The value is set by Jenkins
93+
* @param displayName
94+
*/
95+
public void setDisplayName(String displayName) {
96+
this.displayName = displayName;
97+
}
98+
99+
public abstract Map<String, Object> dataForCreate();
100+
101+
public abstract Map<String, String> dataForUpdate();
102+
}

0 commit comments

Comments
 (0)