package hudson.plugins.jira; import hudson.Extension; import hudson.model.Action; import hudson.model.Job; import hudson.model.TaskListener; import hudson.model.listeners.RunListener; import hudson.plugins.jira.model.JiraIssue; import jenkins.branch.MultiBranchProject; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; import java.net.URL; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * JiraJobAction is to store a reference to the {@link JiraIssue} that represents work * being done for a {@link WorkflowJob} (branch or PR) belonging to a {@link MultiBranchProject} * * Any branches with the whole key in the name or after a prefix will have this action attached. * e.g. "JENKINS-1234" and "feature/JENKINS-1234" will have this action with the issue JENKINS-1234 referenced */ @ExportedBean public class JiraJobAction implements Action { private static final Logger LOGGER = Logger.getLogger(JiraJobAction.class.getName()); private final Job<?, ?> owner; private final JiraIssue issue; @DataBoundConstructor public JiraJobAction(Job<?, ?> owner, JiraIssue issue) { this.owner = owner; this.issue = issue; } /** * @return issue representing the job */ @Exported public JiraIssue getIssue() { return issue; } /** * @return url of the JIRA server */ @Exported @Nullable public String getServerURL() { JiraSite jiraSite = JiraSite.get(owner); URL url = jiraSite != null ? jiraSite.getUrl() : null; return url != null ? url.toString() : null; } /** * Adds a {@link JiraJobAction} to a {@link WorkflowJob} if it belongs to a {@link MultiBranchProject} * and its name contains an JIRA issue key * @param job to add the property to * @param site to fetch issue data * @throws IOException if something goes wrong fetching the JIRA issue */ public static void setAction(@Nonnull Job<?, ?> job, @Nonnull JiraSite site) throws IOException { // If there is already a action set then skip if (job.getAction(JiraJobAction.class) != null) { return; } // Exclude all non-multibranch workflow jobs if (!(job.getParent() instanceof MultiBranchProject)) { return; } // Find the first JIRA issue key in the branch name // If it exists, create the action and set it String issueKey = null; for (String part : StringUtils.split(job.getName(), '/')) { Pattern pattern = site.getIssuePattern(); Matcher matcher = pattern.matcher(part); if (matcher.matches() && matcher.groupCount() > 0) { issueKey = matcher.group(); } } if (issueKey != null) { JiraIssue issue = site.getIssue(issueKey); job.addAction(new JiraJobAction(job, issue)); job.save(); } } @Override public String getIconFileName() { return null; } @Override public String getDisplayName() { return "JIRA"; } @Override public String getUrlName() { return "jira"; } @Extension public static final class RunListenerImpl extends RunListener<WorkflowRun> { @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 e) { LOGGER.log(Level.WARNING, "Could not set JiraJobAction for <" + parent.getFullName() + ">", e); } } } } }