package org.jenkinsci.plugins.ghprb; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.CredentialsStore; import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.SystemCredentialsProvider; import com.cloudbees.plugins.credentials.common.StandardCredentials; import com.cloudbees.plugins.credentials.domains.*; import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; import hudson.Util; import hudson.model.*; import hudson.security.ACL; import hudson.triggers.Trigger; import hudson.util.DescribableList; import hudson.util.Secret; import jenkins.model.ParameterizedJobMixIn; import org.apache.commons.collections.Predicate; import org.apache.commons.collections.PredicateUtils; import org.apache.commons.collections.functors.InstanceofPredicate; import org.apache.commons.lang.StringUtils; import org.jenkinsci.plugins.ghprb.extensions.GhprbExtension; import org.jenkinsci.plugins.ghprb.extensions.GhprbExtensionDescriptor; import org.jenkinsci.plugins.ghprb.extensions.GhprbProjectExtension; import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl; import org.kohsuke.github.GHCommitState; import org.kohsuke.github.GHIssue; import org.kohsuke.github.GHUser; import java.net.URI; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; /** * @author janinko */ public class Ghprb { private static final Logger logger = Logger.getLogger(Ghprb.class.getName()); public static final Pattern githubUserRepoPattern = Pattern.compile("^(http[s]?://[^/]*)/([^/]*/[^/]*).*"); private final GhprbTrigger trigger; public Ghprb(GhprbTrigger trigger) { this.trigger = trigger; } public void addWhitelist(String author) { logger.log(Level.INFO, "Adding {0} to whitelist", author); trigger.addWhitelist(author); } public boolean isProjectDisabled() { return !trigger.isActive(); } public GhprbBuilds getBuilds() { return trigger.getBuilds(); } public GhprbTrigger getTrigger() { return trigger; } public GhprbGitHub getGitHub() { return trigger.getGhprbGitHub(); } public static Pattern compilePattern(String regex) { try { return Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); } catch (Exception e) { logger.log(Level.SEVERE, "Failed to compile pattern "+regex, e); return null; } } private static boolean checkPattern(Pattern pattern, String comment) { return pattern != null && pattern.matcher(comment).matches(); } // These used to be stored on the object in the constructor. // But because the object is only instantiated once per PR, configuration would go stale. // Some optimization could be done around re-compiling regex/hash sets, but beyond that we still have to re-pull the text. private Pattern retestPhrasePattern() { return compilePattern(trigger.getDescriptor().getRetestPhrase()); } /** * Returns skip build phrases from Jenkins global configuration * * @return skip build phrases */ public Set<String> getSkipBuildPhrases() { return new HashSet<String>(Arrays.asList(getTrigger().getSkipBuildPhrase().split("[\\r\\n]+"))); } /** * Checks for skip build phrase in pull request title and body. If present it updates shouldRun as false. * * @param issue The GitHub issue * @return the skip phrase or null if should not skip */ public String checkSkipBuild(GHIssue issue) { // check in title String pullRequestTitle = issue.getTitle(); String skipBuildPhrase = checkSkipBuildInString(pullRequestTitle); if (StringUtils.isNotBlank(skipBuildPhrase)) { return skipBuildPhrase; } // not found in title, check in body String pullRequestBody = issue.getBody(); skipBuildPhrase = checkSkipBuildInString(pullRequestBody); if (StringUtils.isNotBlank(skipBuildPhrase)) { return skipBuildPhrase; } return null; } /** * Checks for skip build phrase in the passed string * * @param string The string we're looking for the phrase in * @return the skip phrase or null if we don't find it */ private String checkSkipBuildInString( String string ) { // check for skip build phrase in the passed string if (StringUtils.isNotBlank(string)) { string = string.trim(); Set<String> skipBuildPhrases = getSkipBuildPhrases(); skipBuildPhrases.remove(""); for (String skipBuildPhrase : skipBuildPhrases) { skipBuildPhrase = skipBuildPhrase.trim(); Pattern skipBuildPhrasePattern = compilePattern(skipBuildPhrase); if (skipBuildPhrasePattern != null && skipBuildPhrasePattern.matcher(string).matches()) { return skipBuildPhrase; } } } return null; } public Set<String> getBlackListLabels() { return spiltLabels(getTrigger().getBlackListLabels()); } public Set<String> getWhiteListLabels() { return spiltLabels(getTrigger().getWhiteListLabels()); } private Set<String> spiltLabels(String labelsField) { Set<String> labels = new HashSet<String>(); if (labelsField != null && !labelsField.trim().isEmpty()) { String[] split = labelsField.split("\\n+"); for (int i = 0; i < split.length; i++) { split[i] = split[i].trim(); } Collections.addAll(labels, split); } return labels; } private Pattern whitelistPhrasePattern() { return compilePattern(trigger.getDescriptor().getWhitelistPhrase()); } private Pattern oktotestPhrasePattern() { return compilePattern(trigger.getDescriptor().getOkToTestPhrase()); } private Pattern triggerPhrase() { return compilePattern(trigger.getTriggerPhrase()); } private HashSet<String> admins() { HashSet<String> adminList; adminList = new HashSet<String>(Arrays.asList(trigger.getAdminlist().toLowerCase().split("\\s+"))); adminList.remove(""); return adminList; } private HashSet<String> whitelisted() { HashSet<String> whitelistedList; whitelistedList = new HashSet<String>(Arrays.asList(trigger.getWhitelist().toLowerCase().split("\\s+"))); whitelistedList.remove(""); return whitelistedList; } private HashSet<String> organisations() { HashSet<String> organisationsList; organisationsList = new HashSet<String>(Arrays.asList(trigger.getOrgslist().split("\\s+"))); organisationsList.remove(""); return organisationsList; } public boolean isRetestPhrase(String comment) { return checkPattern(retestPhrasePattern(), comment); } public boolean isWhitelistPhrase(String comment) { return checkPattern(whitelistPhrasePattern(), comment); } public boolean isOktotestPhrase(String comment) { return checkPattern(oktotestPhrasePattern(), comment); } public boolean isTriggerPhrase(String comment) { return checkPattern(triggerPhrase(), comment); } public boolean ifOnlyTriggerPhrase() { return trigger.getOnlyTriggerPhrase(); } public boolean isWhitelisted(GHUser user) { return trigger.getPermitAll() || whitelisted().contains(user.getLogin().toLowerCase()) || admins().contains(user.getLogin().toLowerCase()) || isInWhitelistedOrganisation(user); } public boolean isAdmin(GHUser user) { return admins().contains(user.getLogin().toLowerCase()) || (trigger.getAllowMembersOfWhitelistedOrgsAsAdmin() && isInWhitelistedOrganisation(user)); } public boolean isBotUser(GHUser user) { return user != null && user.getLogin().equals(getGitHub().getBotUserLogin()); } private boolean isInWhitelistedOrganisation(GHUser user) { for (String organisation : organisations()) { if (getGitHub().isUserMemberOfOrganization(organisation, user)) { return true; } } return false; } List<GhprbBranch> getBlackListTargetBranches() { return trigger.getBlackListTargetBranches(); } List<GhprbBranch> getWhiteListTargetBranches() { return trigger.getWhiteListTargetBranches(); } public static String replaceMacros(Run<?, ?> build, TaskListener listener, String inputString) { String returnString = inputString; if (build != null && inputString != null) { try { Map<String, String> messageEnvVars = getEnvVars(build, listener); returnString = Util.replaceMacro(inputString, messageEnvVars); } catch (Exception e) { logger.log(Level.SEVERE, "Couldn't replace macros in message: ", e); } } return returnString; } public static Map<String, String> getEnvVars(Run<?, ?> build, TaskListener listener) { Map<String, String> messageEnvVars = new HashMap<String, String>(); if (build != null) { messageEnvVars.putAll(build.getCharacteristicEnvVars()); if (build instanceof AbstractBuild) { messageEnvVars.putAll( ((AbstractBuild) build).getBuildVariables()); } try { messageEnvVars.putAll(build.getEnvironment(listener)); } catch (Exception e) { logger.log(Level.SEVERE, "Couldn't get Env Variables: ", e); } } return messageEnvVars; } public static String replaceMacros(Job<?, ?> project, String inputString) { String returnString = inputString; if (project != null && inputString != null) { try { Map<String, String> messageEnvVars = new HashMap<String, String>(); messageEnvVars.putAll(project.getCharacteristicEnvVars()); returnString = Util.replaceMacro(inputString, messageEnvVars); } catch (Exception e) { logger.log(Level.SEVERE, "Couldn't replace macros in message: ", e); } } return returnString; } public static GHCommitState getState(Run<?, ?> build) { GHCommitState state; if (build.getResult() == Result.SUCCESS) { state = GHCommitState.SUCCESS; } else if (build.getResult() == Result.UNSTABLE) { state = GhprbTrigger.getDscp().getUnstableAs(); } else { state = GHCommitState.FAILURE; } return state; } public static Set<String> createSet(String list) { String listString = list == null ? "" : list; List<String> listList = Arrays.asList(listString.split("\\s+")); Set<String> listSet = new HashSet<String>(listList); listSet.remove(""); return listSet; } public static GhprbCause getCause(Run<?, ?> build) { Cause cause = build.getCause(GhprbCause.class); if (cause == null || (!(cause instanceof GhprbCause))) { return null; } return (GhprbCause) cause; } public static GhprbTrigger extractTrigger(Run<?, ?> build) { return extractTrigger(build.getParent()); } public static GhprbTrigger extractTrigger(Job<?, ?> p) { ParameterizedJobMixIn.ParameterizedJob pJob = (ParameterizedJobMixIn.ParameterizedJob) p; GhprbTrigger ghprbTrigger = null; if (p instanceof ParameterizedJobMixIn.ParameterizedJob) { for (Trigger trigger : pJob.getTriggers().values()) { if (trigger instanceof GhprbTrigger) { ghprbTrigger = (GhprbTrigger) trigger; break; } } } return ghprbTrigger; } private static List<Predicate> createPredicate(Class<?> ...types) { List<Predicate> predicates = new ArrayList<Predicate>(types.length); for (Class<?> type : types) { predicates.add(InstanceofPredicate.getInstance(type)); } return predicates; } public static void filterList(DescribableList<GhprbExtension, GhprbExtensionDescriptor> descriptors, Predicate predicate) { for (GhprbExtension descriptor : descriptors) { if (!predicate.evaluate(descriptor)) { descriptors.remove(descriptor); } } } private static DescribableList<GhprbExtension, GhprbExtensionDescriptor> copyExtensions(DescribableList<GhprbExtension, GhprbExtensionDescriptor> ...extensionsList){ DescribableList<GhprbExtension, GhprbExtensionDescriptor> copiedList = new DescribableList<GhprbExtension, GhprbExtensionDescriptor>(Saveable.NOOP); for (DescribableList<GhprbExtension, GhprbExtensionDescriptor> extensions: extensionsList) { copiedList.addAll(extensions); } return copiedList; } @SuppressWarnings("unchecked") public static DescribableList<GhprbExtension, GhprbExtensionDescriptor> getJobExtensions(GhprbTrigger trigger, Class<?> ...types) { // First get all global extensions DescribableList<GhprbExtension, GhprbExtensionDescriptor> copied = copyExtensions(trigger.getDescriptor().getExtensions()); // Remove extensions that are specified by job filterList(copied, PredicateUtils.notPredicate(InstanceofPredicate.getInstance(GhprbProjectExtension.class))); // Then get the rest of the extensions from the job copied = copyExtensions(copied, trigger.getExtensions()); // Filter extensions by desired interface filterList(copied, PredicateUtils.anyPredicate(createPredicate(types))); return copied; } public static DescribableList<GhprbExtension, GhprbExtensionDescriptor> matchesAll(DescribableList<GhprbExtension, GhprbExtensionDescriptor> extensions, Class<?> ...types) { Predicate predicate = PredicateUtils.allPredicate(createPredicate(types)); DescribableList<GhprbExtension, GhprbExtensionDescriptor> copyExtensions = new DescribableList<GhprbExtension, GhprbExtensionDescriptor>(Saveable.NOOP); copyExtensions.addAll(extensions); filterList(copyExtensions, predicate); return copyExtensions; } public static DescribableList<GhprbExtension, GhprbExtensionDescriptor> matchesSome(DescribableList<GhprbExtension, GhprbExtensionDescriptor> extensions, Class<?> ...types) { Predicate predicate = PredicateUtils.anyPredicate(createPredicate(types)); DescribableList<GhprbExtension, GhprbExtensionDescriptor> copyExtensions = new DescribableList<GhprbExtension, GhprbExtensionDescriptor>(Saveable.NOOP); copyExtensions.addAll(extensions); filterList(copyExtensions, predicate); return copyExtensions; } public static DescribableList<GhprbExtension, GhprbExtensionDescriptor> onlyOneEntry(DescribableList<GhprbExtension, GhprbExtensionDescriptor> extensions, Class<?> ...types) { DescribableList<GhprbExtension, GhprbExtensionDescriptor> copyExtensions = new DescribableList<GhprbExtension, GhprbExtensionDescriptor>(Saveable.NOOP); Set<Class<?>> extSet = new HashSet<Class<?>>(types.length); List<Predicate> predicates = createPredicate(types); for (GhprbExtension extension: extensions) { if (addExtension(extension, predicates, extSet)) { copyExtensions.add(extension); } } return copyExtensions; } private static boolean addExtension(GhprbExtension extension, List<Predicate> predicates, Set<Class<?>> extSet) { for (Predicate predicate: predicates) { if (predicate.evaluate(extension)) { Class<?> clazz = ((InstanceofPredicate)predicate).getType(); if (extSet.contains(clazz)) { return false; } else { extSet.add(clazz); return true; } } } return true; } public static void addIfMissing(DescribableList<GhprbExtension, GhprbExtensionDescriptor> extensions, GhprbExtension ext, Class<?> type) { if (ext == null) { return; } Predicate predicate = InstanceofPredicate.getInstance(type); for (GhprbExtension extension : extensions) { if (predicate.evaluate(extension)){ return; } } extensions.add(ext); } public static StandardCredentials lookupCredentials(Item context, String credentialId, String uri) { String contextName = "(Jenkins.instance)"; if (context != null) { contextName = context.getFullName(); } logger.log(Level.FINE, "Looking up credentials for {0}, using context {1} for url {2}", new Object[] { credentialId, contextName, uri }); List<StandardCredentials> credentials; logger.log(Level.FINE, "Using null context because of issues not getting all credentials"); credentials = CredentialsProvider.lookupCredentials(StandardCredentials.class, (Item) null, ACL.SYSTEM, URIRequirementBuilder.fromUri(uri).build()); logger.log(Level.FINE, "Found {0} credentials", new Object[]{credentials.size()}); return (credentialId == null) ? null : CredentialsMatchers.firstOrNull(credentials, CredentialsMatchers.withId(credentialId)); } public static String createCredentials(String serverAPIUrl, String token) throws Exception { String description = serverAPIUrl + " GitHub auto generated token credentials"; StringCredentialsImpl credentials = new StringCredentialsImpl( CredentialsScope.GLOBAL, UUID.randomUUID().toString(), description, Secret.fromString(token)); return createCredentials(serverAPIUrl, credentials); } public static String createCredentials(String serverAPIUrl, String username, String password) throws Exception { String description = serverAPIUrl + " GitHub auto generated Username password credentials"; UsernamePasswordCredentialsImpl credentials = new UsernamePasswordCredentialsImpl( CredentialsScope.GLOBAL, UUID.randomUUID().toString(), description, username, password); return createCredentials(serverAPIUrl, credentials); } private static String createCredentials(String serverAPIUrl, StandardCredentials credentials) throws Exception { List<DomainSpecification> specifications = new ArrayList<DomainSpecification>(2); URI serverUri = new URI(serverAPIUrl); if (serverUri.getPort() > 0) { specifications.add(new HostnamePortSpecification(serverUri.getHost() + ":" + serverUri.getPort(), null)); } else { specifications.add(new HostnameSpecification(serverUri.getHost(), null)); } specifications.add(new SchemeSpecification(serverUri.getScheme())); String path = serverUri.getPath(); if (StringUtils.isEmpty(path)) { path = "/"; } specifications.add(new PathSpecification(path, null, false)); Domain domain = new Domain(serverUri.getHost(), "Auto generated credentials domain", specifications); CredentialsStore provider = new SystemCredentialsProvider.StoreImpl(); provider.addDomain(domain, credentials); return credentials.getId(); } public static <T extends GhprbExtension> T getGlobal(Class<T> clazz) { DescribableList<GhprbExtension, GhprbExtensionDescriptor> copyExtensions = new DescribableList<GhprbExtension, GhprbExtensionDescriptor>(Saveable.NOOP); copyExtensions.addAll(GhprbTrigger.DESCRIPTOR.getExtensions()); filterList(copyExtensions, InstanceofPredicate.getInstance(clazz)); return copyExtensions.get(clazz); } @SuppressWarnings({ "unchecked", "rawtypes" }) public static <T, S extends GhprbExtension> T getDefaultValue(S local, Class<S> globalClass, String methodName) { T toReturn = null; S global = getGlobal(globalClass); if (local == null && global == null) { return null; } try { if (local == null) { return (T) global.getClass().getMethod(methodName).invoke(global); } else if (global == null) { return (T) local.getClass().getMethod(methodName).invoke(local); } T localValue = (T) local.getClass().getMethod(methodName).invoke(local); T globalValue = (T) global.getClass().getMethod(methodName).invoke(global); if (localValue instanceof String) { if (StringUtils.isEmpty((String) localValue)) { return globalValue; } } else if (localValue instanceof List) { if (((List) localValue).isEmpty()) { return globalValue; } } return localValue; } catch (Exception e) { e.printStackTrace(); } return toReturn; } }