Skip to content

Commit

Permalink
Merge pull request #12 from Flagbit/add-credentials-management
Browse files Browse the repository at this point in the history
[JENKINS-32574] Add credentials management
  • Loading branch information
Antonio Mansilla committed Mar 3, 2016
2 parents d73ec91 + a90f66e commit faae72c
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 68 deletions.
66 changes: 54 additions & 12 deletions README.md
Expand Up @@ -10,11 +10,16 @@ you will be able to know when your build is passing right within the Bitbucket U
* Build finish

## Dependencies
This plugin depends on other Jenkins plugin the [Git Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin).
Please install it before if it's still not installed on your Jenkins server.
This plugin depends on other Jenkins plugins:

* [Git Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin)
* [Credentials Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Credentials+Plugin)

Please install them before if they are still not installed on your Jenkins server.

## Instructions

### Create a OAuth Consumer
First you need to get a OAuth consumer key/secret from Bitbucket.

1. Login into your Bitbucket account.
Expand All @@ -28,16 +33,53 @@ First you need to get a OAuth consumer key/secret from Bitbucket.
3. Add **Read** and **Write** permissions to **Repositories**.
4. Click **Save** button and a **Key** and **Secret** will be automatically generated.

Second, you need to configure Jenkins:

1. Open Jenkins **Configure System** page.
2. Set correct URL to **Jenkins URL**.
3. Go to the Job you want notifies the builds to Bitbucket.
4. Click **Configure**.
5. Click **Add post-build action**.
6. Select **Bitbucket notify build status**.
7. Set the **Key** and **Secret** you previously created.
8. Choose whether you want to notify the build status on Jenkins to Bitbucket.
### Ensure Jenkins URL is set
Second, ensure that Jenkins URL is properly set:

1. Open Jenkins **Manage Jenkins** page.
2. Click **Configure System** page.
3. Got to the section **Jenkins Location**.
4. Set correct URL to **Jenkins URL**.
5. Click **Save** button.

### Add OAuth Credentials to Jenkins
Third, you need to add the Bitbucket OAuth Consumer credentials. You have two ways to configure it globally or locally:

#### Global

1. Open Jenkins **Manage Jenkins** page.
2. Click **Configure System**.
3. Go to the section **Bitbucket Build Status Notifier plugin**
4. If you still don't have stored the credentials click **Add**, otherwise you can skip this step.
1. Select **Username with password**.
2. Set the the OAuth consumer **key** in **Username**.
3. Set the the OAuth consumer **secret** in **Password**.
4. Click **Add** button.
5. Select the desired credentials.
6. Click **Save** button.

#### Local

1. Go to the Job you want notifies the builds to Bitbucket.
2. Click **Configure**.
3. Click **Add post-build action**.
4. Select **Bitbucket notify build status**.
5. Click **Advanced** button.
6. If you still don't have stored the credentials click **Add**, otherwise you can skip this step.
1. Select **Username with password**.
2. Set the the OAuth consumer **key** in **Username**.
3. Set the the OAuth consumer **secret** in **Password**.
4. Click **Add** button.
7. Select the desired credentials.

### Configure Jenkins to notify Bitbucket

Once you have configured the credentials, configure jenkins to notify Bitbucket.

1. Go to the Job you want notifies the builds to Bitbucket.
2. Click **Configure**.
3. Select **Bitbucket notify build status**.
4. Choose whether you want to notify the build status on Jenkins to Bitbucket.

## Contributions

Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -76,6 +76,11 @@
<artifactId>scribe</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>credentials</artifactId>
<version>1.22</version>
</dependency>
</dependencies>

</project>
@@ -1,12 +1,20 @@
package org.jenkinsci.plugins.bitbucket;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardUsernameListBoxModel;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.Result;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.util.BuildData;
Expand All @@ -17,46 +25,40 @@
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.jenkinsci.plugins.bitbucket.api.*;
import org.jenkinsci.plugins.bitbucket.model.BitbucketBuildStatus;
import org.jenkinsci.plugins.bitbucket.model.BitbucketBuildStatusResource;
import org.jenkinsci.plugins.bitbucket.model.BitbucketBuildStatusSerializer;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.scribe.model.*;

public class BitbucketBuildStatusNotifier extends Notifier {

private static final Logger logger = Logger.getLogger(BitbucketBuildStatusNotifier.class.getName());

private String apiKey;
private String apiSecret;
private boolean notifyStart;
private boolean notifyFinish;
private String credentialsId;

@DataBoundConstructor
public BitbucketBuildStatusNotifier(final String apiKey, final String apiSecret, final boolean notifyStart,
final boolean notifyFinish) {
public BitbucketBuildStatusNotifier(final boolean notifyStart, final boolean notifyFinish,
final String credentialsId) {
super();
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.notifyStart = notifyStart;
this.notifyFinish = notifyFinish;
}

public String getApiKey() {
return this.apiKey;
}

public String getApiSecret() {
return this.apiSecret;
this.credentialsId = credentialsId;
}

public boolean getNotifyStart() {
Expand All @@ -67,6 +69,10 @@ public boolean getNotifyFinish() {
return this.notifyFinish;
}

public String getCredentialsId() {
return this.credentialsId;
}

@Override
public boolean prebuild(AbstractBuild<?, ?> build, BuildListener listener) {

Expand All @@ -76,10 +82,7 @@ public boolean prebuild(AbstractBuild<?, ?> build, BuildListener listener) {
logger.info("Bitbucket notify on start");

try {
BitbucketBuildStatusResource buildStatusResource = this.createBuildStatusResourceFromBuild(build);
BitbucketBuildStatus buildStatus = this.createBitbucketBuildStatusFromBuild(build);
this.notifyBuildStatus(buildStatusResource, buildStatus);
listener.getLogger().println("Sending build status " + buildStatus.getState() + " for commit " + buildStatusResource.getCommitId() + " to BitBucket is done!");
this.notifyBuildStatus(build, listener);
} catch (Exception e) {
logger.log(Level.INFO, "Bitbucket notify on start failed: " + e.getMessage(), e);
listener.getLogger().println("Bitbucket notify on start failed: " + e.getMessage());
Expand All @@ -100,10 +103,7 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
logger.info("Bitbucket notify on finish");

try {
BitbucketBuildStatusResource buildStatusResource = this.createBuildStatusResourceFromBuild(build);
BitbucketBuildStatus buildStatus = this.createBitbucketBuildStatusFromBuild(build);
this.notifyBuildStatus(buildStatusResource, buildStatus);
listener.getLogger().println("Sending build status " + buildStatus.getState() + " for commit " + buildStatusResource.getCommitId() + " to BitBucket is done!");
this.notifyBuildStatus(build, listener);
} catch (Exception e) {
logger.log(Level.INFO, "Bitbucket notify on finish failed: " + e.getMessage(), e);
listener.getLogger().println("Bitbucket notify on finish failed: " + e.getMessage());
Expand All @@ -115,6 +115,18 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
return true;
}

public static StandardUsernamePasswordCredentials getCredentials(String credentialsId, Job<?,?> owner) {
if (credentialsId != null) {
for (StandardUsernamePasswordCredentials c : CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, owner, null, URIRequirementBuilder.fromUri(BitbucketApi.OAUTH_ENDPOINT).build())) {
if (c.getId().equals(credentialsId)) {
return c;
}
}
}

return null;
}

@Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
Expand Down Expand Up @@ -197,9 +209,21 @@ private BitbucketBuildStatusResource createBuildStatusResourceFromBuild(final Ab
return new BitbucketBuildStatusResource(userName, repoName, commitId);
}

private void notifyBuildStatus(final BitbucketBuildStatusResource buildStatusResource, final BitbucketBuildStatus buildStatus) throws Exception {
private void notifyBuildStatus(final AbstractBuild build, final BuildListener listener) throws Exception {

UsernamePasswordCredentials credentials = this.getCredentials(this.getCredentialsId(), build.getProject());
BitbucketBuildStatusResource buildStatusResource = this.createBuildStatusResourceFromBuild(build);
BitbucketBuildStatus buildStatus = this.createBitbucketBuildStatusFromBuild(build);

if (credentials == null) {
Job job = null;
credentials = this.getCredentials(this.getDescriptor().getGlobalCredentialsId(), job);
}
if (credentials == null) {
throw new Exception("Credentials could not be found!");
}

OAuthConfig config = new OAuthConfig(this.apiKey, this.apiSecret);
OAuthConfig config = new OAuthConfig(credentials.getUsername(), credentials.getPassword().getPlainText());
BitbucketApiService apiService = (BitbucketApiService) new BitbucketApi().createService(config);

GsonBuilder gsonBuilder = new GsonBuilder();
Expand All @@ -217,6 +241,7 @@ private void notifyBuildStatus(final BitbucketBuildStatusResource buildStatusRes

Response response = request.send();
logger.info("This response was received:" + response.getBody());
listener.getLogger().println("Sending build status " + buildStatus.getState() + " for commit " + buildStatusResource.getCommitId() + " to BitBucket is done!");
}

private BitbucketBuildStatus createBitbucketBuildStatusFromBuild(AbstractBuild build) {
Expand Down Expand Up @@ -289,6 +314,16 @@ public String findCurrentCommitId() throws Exception {
@Extension // This indicates to Jenkins that this is an implementation of an extension point.
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {

private String globalCredentialsId;

public String getGlobalCredentialsId() {
return globalCredentialsId;
}

public void setGlobalCredentialsId(String globalCredentialsId) {
this.globalCredentialsId = globalCredentialsId;
}

@Override
public String getDisplayName() {
return "Bitbucket notify build status";
Expand All @@ -303,23 +338,73 @@ public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}

@Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
req.bindJSON(this, formData.getJSONObject("bitbucket-build-status-notifier"));
save();

public FormValidation doCheckApiKey(@QueryParameter final String apiKey, @QueryParameter final String apiSecret) throws FormException {
return true;
}

if (apiKey.isEmpty() || apiSecret.isEmpty()) {
return FormValidation.error("Please enter Bitbucket OAuth credentials");
public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Job<?,?> owner) {
if (owner == null || !owner.hasPermission(Item.CONFIGURE)) {
return new ListBoxModel();
}
List<DomainRequirement> apiEndpoint = URIRequirementBuilder.fromUri(BitbucketApi.OAUTH_ENDPOINT).build();

return new StandardUsernameListBoxModel()
.withEmptySelection()
.withAll(CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, owner, null, apiEndpoint));
}

public FormValidation doCheckCredentialsId(@QueryParameter final String credentialsId,
@AncestorInPath final Job<?,?> owner) {
String globalCredentialsId = this.getGlobalCredentialsId();

if (credentialsId == null || credentialsId.isEmpty()) {
if (globalCredentialsId == null || globalCredentialsId.isEmpty()) {
return FormValidation.error("Please enter Bitbucket OAuth credentials");
} else {
return this.doCheckGlobalCredentialsId(this.getGlobalCredentialsId());
}
}

UsernamePasswordCredentials credentials = BitbucketBuildStatusNotifier.getCredentials(credentialsId, owner);

return this.checkCredentials(credentials);
}

public ListBoxModel doFillGlobalCredentialsIdItems() {
Job owner = null;
List<DomainRequirement> apiEndpoint = URIRequirementBuilder.fromUri(BitbucketApi.OAUTH_ENDPOINT).build();

return new StandardUsernameListBoxModel()
.withEmptySelection()
.withAll(CredentialsProvider.lookupCredentials(StandardUsernamePasswordCredentials.class, owner, null, apiEndpoint));
}

public FormValidation doCheckGlobalCredentialsId(@QueryParameter final String globalCredentialsId) {
if (globalCredentialsId.isEmpty()) {
return FormValidation.ok();
}

Job owner = null;
UsernamePasswordCredentials credentials = BitbucketBuildStatusNotifier.getCredentials(globalCredentialsId, owner);

return this.checkCredentials(credentials);
}

private FormValidation checkCredentials(UsernamePasswordCredentials credentials) {

try {
OAuthConfig config = new OAuthConfig(apiKey, apiSecret);
OAuthConfig config = new OAuthConfig(credentials.getUsername(), credentials.getPassword().getPlainText());
BitbucketApiService apiService = (BitbucketApiService) new BitbucketApi().createService(config);
Verifier verifier = null;
Token token = apiService.getAccessToken(OAuthConstants.EMPTY_TOKEN, verifier);

if (token.isEmpty()) {
FormValidation.error("Invalid Bitbucket OAuth credentials");
return FormValidation.error("Invalid Bitbucket OAuth credentials");
}

} catch (Exception e) {
return FormValidation.error(e.getClass() + e.getMessage());
}
Expand Down
Expand Up @@ -9,7 +9,7 @@

public class BitbucketApi extends DefaultApi20 {

private static final String OAUTH_ENDPOINT = "https://bitbucket.org/site/oauth2/";
public static final String OAUTH_ENDPOINT = "https://bitbucket.org/site/oauth2/";

@Override
public String getAccessTokenEndpoint() {
Expand Down
@@ -1,15 +1,14 @@
<?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 title="${%Key}" field="apiKey">
<f:textbox />
</f:entry>
<f:entry title="${%Secret}" field="apiSecret">
<f:textbox />
</f:entry>
<f:entry title="Notify build start" field="notifyStart">
<f:checkbox />
</f:entry>
<f:entry title="Notify build finish" field="notifyFinish">
<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" xmlns:c="/lib/credentials">
<f:entry title="${%Notify build start}" field="notifyStart">
<f:checkbox />
</f:entry>
</f:entry>
<f:entry title="${%Notify build finish}" field="notifyFinish">
<f:checkbox />
</f:entry>
<f:advanced>
<f:entry title="${%Credentials}" field="credentialsId">
<c:select />
</f:entry>
</f:advanced>
</j:jelly>

0 comments on commit faae72c

Please sign in to comment.