{
@Override
public void onStarted(WorkflowRun workflowRun, TaskListener listener) {
WorkflowJob parent = workflowRun.getParent();
JiraSite site = JiraSite.get(parent);
if (site != null) {
try {
setAction(parent, site);
} catch (IOException | RestClientException e) {
LOGGER.log(Level.WARNING, "Could not set JiraJobAction for <" + parent.getFullName() + ">", e);
listener.getLogger().println(e.getMessage());
}
}
}
}
}
================================================
FILE: src/main/java/hudson/plugins/jira/JiraMailAddressResolver.java
================================================
package hudson.plugins.jira;
import hudson.Extension;
import hudson.model.Job;
import hudson.model.User;
import hudson.tasks.MailAddressResolver;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest2;
/**
* Resolve user email by searching his userId as username in Jira.
*
* @author Honza Brázdil jbrazdil@redhat.com
*/
@Extension
public class JiraMailAddressResolver extends MailAddressResolver {
private static final Logger LOGGER = Logger.getLogger(JiraMailAddressResolver.class.getName());
/**
* Boolean to disable the Jira mail address resolver.
*
* To disable set the System property "-Dhudson.plugins.jira.JiraMailAddressResolver.disabled=true"
*/
public static boolean disabled = Boolean.getBoolean(JiraMailAddressResolver.class.getName() + ".disabled");
@Override
public String findMailAddressFor(User u) {
if (disabled) {
return null;
}
String username = u.getId();
Job, ?> job = null;
StaplerRequest2 req = Stapler.getCurrentRequest2();
if (req != null) {
job = req.findAncestorObject(Job.class);
}
List sites = job == null ? JiraGlobalConfiguration.get().getSites() : JiraSite.getJiraSites(job);
for (JiraSite site : sites) {
JiraSession session = site.getSession(job);
if (session == null) {
continue;
}
com.atlassian.jira.rest.client.api.domain.User user = session.service.getUser(username);
if (user != null) {
String email = user.getEmailAddress();
if (email != null) {
email = unmaskEmail(email);
return email;
}
}
}
return null;
}
private static final String PRE = "[( \\[<_{\"=]+";
private static final String POST = "[) \\]>_}\"=]+";
private static final Pattern AT = Pattern.compile(PRE + "[aA][tT]" + POST);
private static final Pattern DOT = Pattern.compile(PRE + "[dD][oO0][tT]" + POST);
// unmask emails like "john dot doe at example dot com" to john.doe@example.com
static String unmaskEmail(String email) {
email = AT.matcher(email).replaceAll("@");
email = DOT.matcher(email).replaceAll(".");
return email;
}
}
================================================
FILE: src/main/java/hudson/plugins/jira/JiraProjectProperty.java
================================================
package hudson.plugins.jira;
import com.cloudbees.hudson.plugins.folder.AbstractFolder;
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.Extension;
import hudson.Util;
import hudson.init.InitMilestone;
import hudson.init.Initializer;
import hudson.model.Job;
import hudson.model.JobProperty;
import hudson.model.JobPropertyDescriptor;
import hudson.util.ListBoxModel;
import java.util.List;
import java.util.stream.Stream;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
/**
* Associates {@link Job} with {@link JiraSite}.
*
* @author Kohsuke Kawaguchi
*/
public class JiraProjectProperty extends JobProperty> {
/**
* Used to find {@link JiraSite}. Matches {@link JiraSite#getName()}. Always
* non-null (but beware that this value might become stale if the system
* config is changed.)
*/
public final String siteName;
@DataBoundConstructor
public JiraProjectProperty(String siteName) {
siteName = Util.fixEmptyAndTrim(siteName);
if (siteName == null) {
// defaults to the first one
List sites = JiraGlobalConfiguration.get().getSites();
if (!sites.isEmpty()) {
siteName = sites.get(0).getName();
}
}
this.siteName = siteName;
}
/**
* Gets the {@link JiraSite} that this project belongs to.
*
* @return null if the configuration becomes out of sync.
*/
@Nullable
public JiraSite getSite() {
List sites = JiraGlobalConfiguration.get().getSites();
if (siteName == null && sites.size() > 0) {
// default
return sites.get(0);
}
Stream streams = sites.stream();
if (owner != null) {
Stream stream2 = JiraFolderProperty.getSitesFromFolders(owner.getParent()).stream();
streams = Stream.concat(streams, stream2).parallel();
}
return streams.filter(jiraSite -> jiraSite.getName().equals(siteName))
.findFirst()
.orElse(null);
}
@Extension
public static final class DescriptorImpl extends JobPropertyDescriptor {
@Deprecated
protected transient List sites;
@Override
@SuppressWarnings("unchecked")
public boolean isApplicable(Class extends Job> jobType) {
return Job.class.isAssignableFrom(jobType);
}
@Override
public String getDisplayName() {
return Messages.JiraProjectProperty_DisplayName();
}
/**
* @param site the Jira site
*
* @deprecated use {@link JiraGlobalConfiguration#setSites(List)} instead
*/
@Deprecated
public void setSites(JiraSite site) {
JiraGlobalConfiguration.get().getSites().add(site);
}
/**
* @return array of sites
*
* @deprecated use {@link JiraGlobalConfiguration#getSites()} instead
*/
@Deprecated
public JiraSite[] getSites() {
return JiraGlobalConfiguration.get().getSites().toArray(new JiraSite[0]);
}
@SuppressWarnings("unused") // Used by stapler
public ListBoxModel doFillSiteNameItems(@AncestorInPath AbstractFolder> folder) {
ListBoxModel items = new ListBoxModel();
for (JiraSite site : JiraGlobalConfiguration.get().getSites()) {
items.add(site.getName());
}
if (folder != null) {
List sitesFromFolder = JiraFolderProperty.getSitesFromFolders(folder);
sitesFromFolder.stream().map(JiraSite::getName).forEach(items::add);
}
return items;
}
@SuppressWarnings("unused") // Used to start migration after all extensions are loaded
@Initializer(after = InitMilestone.EXTENSIONS_AUGMENTED)
public void migrate() {
DescriptorImpl descriptor = (DescriptorImpl) Jenkins.getInstance().getDescriptor(JiraProjectProperty.class);
if (descriptor != null) {
descriptor.load(); // force readResolve without registering descriptor as configurable
}
}
@SuppressWarnings("deprecation") // Migrate configuration
protected Object readResolve() {
if (sites != null) {
JiraGlobalConfiguration jiraGlobalConfiguration = (JiraGlobalConfiguration)
Jenkins.getInstance().getDescriptorOrDie(JiraGlobalConfiguration.class);
jiraGlobalConfiguration.load();
jiraGlobalConfiguration.getSites().addAll(sites);
jiraGlobalConfiguration.save();
sites = null;
DescriptorImpl oldDescriptor =
(DescriptorImpl) Jenkins.getInstance().getDescriptor(JiraProjectProperty.class);
if (oldDescriptor != null) {
oldDescriptor.save();
}
}
return this;
}
}
}
================================================
FILE: src/main/java/hudson/plugins/jira/JiraReleaseVersionUpdater.java
================================================
package hudson.plugins.jira;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Notifier;
import hudson.tasks.Publisher;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest2;
/**
* Task which releases the jira version specified in the parameters when the build completes.
*
* @author Justen Walker justen.walker@gmail.com
* @deprecated Replaced by {@link JiraReleaseVersionUpdaterBuilder} which can be used as a PostBuild step with conditional triggering.
* Kept for backward compatibility.
*/
@Deprecated
public class JiraReleaseVersionUpdater extends Notifier {
private static final long serialVersionUID = 699563338312232811L;
private String jiraProjectKey;
private String jiraRelease;
private String jiraDescription;
@Deprecated
public JiraReleaseVersionUpdater(String jiraProjectKey, String jiraRelease) {
this.jiraRelease = jiraRelease;
this.jiraProjectKey = jiraProjectKey;
}
@DataBoundConstructor
public JiraReleaseVersionUpdater(String jiraProjectKey, String jiraRelease, String jiraDescription) {
this.jiraRelease = jiraRelease;
this.jiraProjectKey = jiraProjectKey;
this.jiraDescription = jiraDescription;
}
public String getJiraRelease() {
return jiraRelease;
}
public void setJiraRelease(String jiraRelease) {
this.jiraRelease = jiraRelease;
}
public String getJiraProjectKey() {
return jiraProjectKey;
}
public void setJiraProjectKey(String jiraProjectKey) {
this.jiraProjectKey = jiraProjectKey;
}
public String getJiraDescription() {
return jiraDescription;
}
public void setJiraDescription(String jiraDescription) {
this.jiraDescription = jiraDescription;
}
@Override
public BuildStepDescriptor getDescriptor() {
return DESCRIPTOR;
}
@Extension
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
@Override
public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) {
return new VersionReleaser()
.perform(build.getProject(), jiraProjectKey, jiraRelease, jiraDescription, build, listener);
}
@Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}
public static class DescriptorImpl extends BuildStepDescriptor {
public DescriptorImpl() {
super(JiraReleaseVersionUpdater.class);
}
@Override
public JiraReleaseVersionUpdater newInstance(StaplerRequest2 req, JSONObject formData) throws FormException {
return req.bindJSON(JiraReleaseVersionUpdater.class, formData);
}
@Override
public boolean isApplicable(Class extends AbstractProject> jobType) {
return true;
}
@Override
public String getDisplayName() {
return Messages.JiraReleaseVersionBuilder_DisplayName();
}
@Override
public String getHelpFile() {
return "/plugin/jira/help-release.html";
}
}
}
================================================
FILE: src/main/java/hudson/plugins/jira/JiraReleaseVersionUpdaterBuilder.java
================================================
package hudson.plugins.jira;
import hudson.EnvVars;
import hudson.Extension;
import hudson.model.AbstractProject;
import hudson.model.Descriptor;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import jenkins.tasks.SimpleBuildStep;
import net.sf.json.JSONObject;
import org.jenkinsci.Symbol;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest2;
/**
* Created by Reda on 18/12/2014.
*/
public class JiraReleaseVersionUpdaterBuilder extends Builder implements SimpleBuildStep {
private String jiraProjectKey;
private String jiraRelease;
private String jiraDescription;
@Extension
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
@Deprecated
public JiraReleaseVersionUpdaterBuilder(String jiraProjectKey, String jiraRelease) {
this.jiraRelease = jiraRelease;
this.jiraProjectKey = jiraProjectKey;
}
@DataBoundConstructor
public JiraReleaseVersionUpdaterBuilder(String jiraProjectKey, String jiraRelease, String jiraDescription) {
this.jiraRelease = jiraRelease;
this.jiraProjectKey = jiraProjectKey;
this.jiraDescription = jiraDescription;
}
public String getJiraRelease() {
return jiraRelease;
}
public void setJiraRelease(String jiraRelease) {
this.jiraRelease = jiraRelease;
}
public String getJiraProjectKey() {
return jiraProjectKey;
}
public void setJiraProjectKey(String jiraProjectKey) {
this.jiraProjectKey = jiraProjectKey;
}
public String getJiraDescription() {
return jiraDescription;
}
public void setJiraDescription(String jiraDescription) {
this.jiraDescription = jiraDescription;
}
@Override
public void perform(Run, ?> run, EnvVars env, TaskListener listener) {
new VersionReleaser().perform(run.getParent(), jiraProjectKey, jiraRelease, jiraDescription, run, listener);
}
@Override
public boolean requiresWorkspace() {
return false;
}
@Override
public Descriptor getDescriptor() {
return DESCRIPTOR;
}
@Symbol("jiraMarkVersionReleased")
public static final class DescriptorImpl extends BuildStepDescriptor {
private DescriptorImpl() {
super(JiraReleaseVersionUpdaterBuilder.class);
}
@Override
public boolean isApplicable(Class extends AbstractProject> jobType) {
return true;
}
@Override
public String getDisplayName() {
// Placed in the build settings section
return Messages.JiraReleaseVersionBuilder_DisplayName();
}
@Override
public String getHelpFile() {
return "/plugin/jira/help.html";
}
@Override
public JiraReleaseVersionUpdaterBuilder newInstance(StaplerRequest2 req, JSONObject formData)
throws FormException {
return req.bindJSON(JiraReleaseVersionUpdaterBuilder.class, formData);
}
}
}
================================================
FILE: src/main/java/hudson/plugins/jira/JiraRestService.java
================================================
/*
* Copyright 2015 Hao Cheng Lee
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package hudson.plugins.jira;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import com.atlassian.jira.rest.client.api.RestClientException;
import com.atlassian.jira.rest.client.api.domain.BasicIssue;
import com.atlassian.jira.rest.client.api.domain.BasicProject;
import com.atlassian.jira.rest.client.api.domain.BasicUser;
import com.atlassian.jira.rest.client.api.domain.Comment;
import com.atlassian.jira.rest.client.api.domain.Component;
import com.atlassian.jira.rest.client.api.domain.Issue;
import com.atlassian.jira.rest.client.api.domain.IssueFieldId;
import com.atlassian.jira.rest.client.api.domain.IssueType;
import com.atlassian.jira.rest.client.api.domain.Permissions;
import com.atlassian.jira.rest.client.api.domain.Priority;
import com.atlassian.jira.rest.client.api.domain.SearchResult;
import com.atlassian.jira.rest.client.api.domain.Status;
import com.atlassian.jira.rest.client.api.domain.Transition;
import com.atlassian.jira.rest.client.api.domain.User;
import com.atlassian.jira.rest.client.api.domain.Version;
import com.atlassian.jira.rest.client.api.domain.input.ComplexIssueInputFieldValue;
import com.atlassian.jira.rest.client.api.domain.input.FieldInput;
import com.atlassian.jira.rest.client.api.domain.input.IssueInput;
import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder;
import com.atlassian.jira.rest.client.api.domain.input.TransitionInput;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.ProxyConfiguration;
import hudson.plugins.jira.extension.ExtendedJiraRestClient;
import hudson.plugins.jira.extension.ExtendedVersion;
import hudson.plugins.jira.extension.ExtendedVersionInput;
import hudson.plugins.jira.model.JiraIssueField;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import jenkins.model.Jenkins;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.client.fluent.Content;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.utils.URIBuilder;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
public class JiraRestService {
private static final Logger LOGGER = Logger.getLogger(JiraRestService.class.getName());
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd");
/**
* Base URI path for a REST API call. It must be relative to site's base
* URI.
*/
public static final String BASE_API_PATH = "rest/api/2";
static final long BUG_ISSUE_TYPE_ID = 1L;
private final URI uri;
private final ExtendedJiraRestClient jiraRestClient;
private final ObjectMapper objectMapper;
private final String authHeader;
private final String baseApiPath;
private final int timeout;
@Deprecated
public JiraRestService(URI uri, ExtendedJiraRestClient jiraRestClient, String username, String password) {
this(uri, jiraRestClient, username, password, JiraSite.DEFAULT_TIMEOUT);
}
public JiraRestService(
URI uri, ExtendedJiraRestClient jiraRestClient, String username, String password, int timeout) {
this.uri = uri;
this.objectMapper = new ObjectMapper();
this.timeout = timeout;
final String login = username + ":" + password;
try {
byte[] encodeBase64 = Base64.encodeBase64(login.getBytes("UTF-8"));
this.authHeader = "Basic " + new String(encodeBase64, "UTF-8");
} catch (UnsupportedEncodingException e) {
LOGGER.warning("Jira REST encode username:password error. cause: " + e.getMessage());
throw new RuntimeException("failed to encode username:password using Base64");
}
this.jiraRestClient = jiraRestClient;
baseApiPath = buildBaseApiPath(uri);
}
public JiraRestService(URI uri, ExtendedJiraRestClient jiraRestClient, String token, int timeout) {
this.uri = uri;
this.objectMapper = new ObjectMapper();
this.timeout = timeout;
this.authHeader = "Bearer " + token;
this.jiraRestClient = jiraRestClient;
baseApiPath = buildBaseApiPath(uri);
}
private String buildBaseApiPath(URI uri) {
final StringBuilder builder = new StringBuilder();
if (uri.getPath() != null) {
builder.append(uri.getPath());
if (!uri.getPath().endsWith("/")) {
builder.append('/');
}
} else {
builder.append('/');
}
builder.append(BASE_API_PATH);
return builder.toString();
}
public void addComment(String issueId, String commentBody, String groupVisibility, String roleVisibility) {
final URIBuilder builder =
new URIBuilder(uri).setPath(String.format("%s/issue/%s/comment", baseApiPath, issueId));
final Comment comment;
if (StringUtils.isNotBlank(groupVisibility)) {
comment = Comment.createWithGroupLevel(commentBody, groupVisibility);
} else if (StringUtils.isNotBlank(roleVisibility)) {
comment = Comment.createWithRoleLevel(commentBody, roleVisibility);
} else {
comment = Comment.valueOf(commentBody);
}
try {
jiraRestClient.getIssueClient().addComment(builder.build(), comment).get(timeout, TimeUnit.SECONDS);
} catch (RestClientException
| URISyntaxException
| InterruptedException
| ExecutionException
| TimeoutException e) {
LOGGER.log(WARNING, "Jira REST client add comment error. cause: " + e.getMessage(), e);
throw new RestClientException(
"[Jira] Jira REST client add comment error. cause: " + e.getMessage(), e.getCause());
}
}
public Issue getIssue(String issueKey) {
LOGGER.log(FINE, "[Jira] Fetching issue {0}", issueKey);
try {
return jiraRestClient.getIssueClient().getIssue(issueKey).get(timeout, TimeUnit.SECONDS);
} catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {
if (e.getCause() != null
&& e.getCause() instanceof RestClientException
&& ((RestClientException) e.getCause()).getStatusCode().isPresent()
&& ((RestClientException) e.getCause()).getStatusCode().get() == 404) {
LOGGER.log(INFO, "Issue '" + issueKey + "' not found in Jira.");
throw new RestClientException("[Jira] Issue '" + issueKey + "' not found in Jira.", e.getCause());
} else {
LOGGER.log(WARNING, "Jira REST client get issue error. cause: " + e.getMessage(), e);
throw new RestClientException(
"[Jira] Jira REST client get issue error. cause: " + e.getMessage(), e.getCause());
}
}
}
public List getIssueTypes() {
try {
return StreamSupport.stream(
jiraRestClient
.getMetadataClient()
.getIssueTypes()
.get(timeout, TimeUnit.SECONDS)
.spliterator(),
false)
.collect(Collectors.toList());
} catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {
LOGGER.log(WARNING, "Jira REST client get issue types error. cause: " + e.getMessage(), e);
throw new RestClientException(
"[Jira] Jira REST client get issue types error. cause: " + e.getMessage(), e.getCause());
}
}
public List getPriorities() {
try {
return StreamSupport.stream(
jiraRestClient
.getMetadataClient()
.getPriorities()
.get(timeout, TimeUnit.SECONDS)
.spliterator(),
false)
.collect(Collectors.toList());
} catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {
LOGGER.log(WARNING, "Jira REST client get priorities error. cause: " + e.getMessage(), e);
throw new RestClientException(
"[Jira] Jira REST client get priorities error. cause: " + e.getMessage(), e.getCause());
}
}
public List getProjectsKeys() {
Iterable projects = Collections.emptyList();
try {
projects = jiraRestClient.getProjectClient().getAllProjects().get(timeout, TimeUnit.SECONDS);
} catch (RestClientException | InterruptedException | ExecutionException | TimeoutException e) {
LOGGER.log(WARNING, "Jira REST client get project keys error. cause: " + e.getMessage(), e);
throw new RestClientException(
"[Jira] Jira REST client get project keys error. cause: " + e.getMessage(), e.getCause());
}
final List keys = new ArrayList<>();
for (BasicProject project : projects) {
keys.add(project.getKey());
}
return keys;
}
public List getIssuesFromJqlSearch(String jqlSearch, Integer maxResults) throws RestClientException {
LOGGER.log(FINE, "[Jira] Executing JQL: {0}", jqlSearch);
try {
Set neededFields = new HashSet<>(
Arrays.asList("summary", "issuetype", "created", "updated", "project", "status", "fixVersions"));
final SearchResult searchResult = jiraRestClient
.getSearchClient()
.searchJql(jqlSearch, maxResults, 0, neededFields)
.get(timeout, TimeUnit.SECONDS);
return StreamSupport.stream(searchResult.getIssues().spliterator(), false)
.collect(Collectors.toList());
} catch (RestClientException
| TimeoutException
| CancellationException
| ExecutionException
| InterruptedException e) {
LOGGER.log(WARNING, "Jira REST client get issue from jql search error. cause: " + e.getMessage(), e);
throw new RestClientException(
"[Jira] Jira REST client get issue from jql search error. cause: " + e.getMessage(), e.getCause());
}
}
public List getVersions(String projectKey) {
final URIBuilder builder =
new URIBuilder(uri).setPath(String.format("%s/project/%s/versions", baseApiPath, projectKey));
List