Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #144 from stephenc/jenkins-45323
[FIXED JENKINS-45323] Add methods to manipulate the list of servers
  • Loading branch information
stephenc committed Jul 5, 2017
2 parents ab97be9 + 8ffb1c3 commit f69fb42
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 11 deletions.
Expand Up @@ -25,6 +25,9 @@
package org.jenkinsci.plugins.github_branch_source;

import com.fasterxml.jackson.core.JsonParseException;
import com.google.common.net.InternetDomainName;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.Util;
import hudson.model.AbstractDescribableImpl;
Expand All @@ -33,11 +36,16 @@

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Locale;
import java.util.logging.Logger;
import java.util.logging.Level;

import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.github.GitHub;
Expand All @@ -48,19 +56,84 @@
* @author Stephen Connolly
*/
public class Endpoint extends AbstractDescribableImpl<Endpoint> {
/**
* Common prefixes that we should remove when inferring a display name.
*/
private static final String[] COMMON_PREFIX_HOSTNAMES = {
"git.",
"github.",
"vcs.",
"scm.",
"source."
};

private final String name;
private final String apiUri;

/**
* Makes best effort to guess a "sensible" display name from the hostname in the apiUri.
*
* @param apiUri the apiUri.
* @return the display name or {@code null}
* @throws LinkageError if Guava changes their API that we have depended on.
*/
@CheckForNull
/*package*/ static String inferDisplayName(@CheckForNull String apiUri) throws LinkageError {
if (apiUri == null) {
return apiUri;
}
String hostName;
try {
URI serverUri = new URI(apiUri);
hostName = serverUri.getHost();
if (hostName != null) {
// let's see if we can make this more "friendly"
InternetDomainName host = InternetDomainName.from(hostName);
if (host.hasPublicSuffix()) {
String publicName = host.publicSuffix().name();
hostName = StringUtils.removeEnd(StringUtils.removeEnd(host.name(), publicName), ".")
.toLowerCase(Locale.ENGLISH);
} else {
hostName = StringUtils.removeEnd(host.name(), ".").toLowerCase(Locale.ENGLISH);
}
for (String prefix : COMMON_PREFIX_HOSTNAMES) {
if (hostName.startsWith(prefix)) {
hostName = hostName.substring(prefix.length());
break;
}
}
}
} catch (URISyntaxException e) {
// ignore, best effort
hostName = null;
}
return hostName;
}

@DataBoundConstructor
public Endpoint(String apiUri, String name) {
this.apiUri = Util.fixEmptyAndTrim(apiUri);
this.name = Util.fixEmptyAndTrim(name);
this.apiUri = GitHubConfiguration.normalizeApiUri(Util.fixEmptyAndTrim(apiUri));
name = Util.fixEmptyAndTrim(name);
if (name == null) {
this.name = inferDisplayName(apiUri);
} else {
this.name = name;
}
}

private Object readResolve() throws ObjectStreamException {
if (!apiUri.equals(GitHubConfiguration.normalizeApiUri(apiUri))) {
return new Endpoint(apiUri, name);
}
return this;
}

@NonNull
public String getApiUri() {
return apiUri;
}

@CheckForNull
public String getName() {
return name;
}
Expand Down Expand Up @@ -144,7 +217,7 @@ public FormValidation doCheckApiUri(@QueryParameter String apiUri) {
@Restricted(NoExternalUse.class)
public FormValidation doCheckName(@QueryParameter String name) {
if (Util.fixEmptyAndTrim(name) == null) {
return FormValidation.warning("You must specify the name");
return FormValidation.warning("A name is recommended to help differentiate similar endpoints");
}
return FormValidation.ok();
}
Expand Down
Expand Up @@ -27,14 +27,18 @@
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import jenkins.model.GlobalConfiguration;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.StaplerRequest;

@Extension public class GitHubConfiguration extends GlobalConfiguration {
Expand All @@ -55,17 +59,59 @@ public GitHubConfiguration() {
}

@NonNull
public List<Endpoint> getEndpoints() {
public synchronized List<Endpoint> getEndpoints() {
return endpoints == null ? Collections.<Endpoint>emptyList() : Collections.unmodifiableList(endpoints);
}

public void setEndpoints(@CheckForNull List<Endpoint> endpoints) {
/**
* Fix an apiUri.
*
* @param apiUri the api URI.
* @return the normalized api URI.
*/
@CheckForNull
public static String normalizeApiUri(@CheckForNull String apiUri) {
if (apiUri == null) {
return null;
}
try {
URI uri = new URI(apiUri).normalize();
String scheme = uri.getScheme();
if ("http".equals(scheme) || "https".equals(scheme)) {
// we only expect http / https, but also these are the only ones where we know the authority
// is server based, i.e. [userinfo@]server[:port]
// DNS names must be US-ASCII and are case insensitive, so we force all to lowercase

String host = uri.getHost() == null ? null : uri.getHost().toLowerCase(Locale.ENGLISH);
int port = uri.getPort();
if ("http".equals(scheme) && port == 80) {
port = -1;
} else if ("https".equals(scheme) && port == 443) {
port = -1;
}
apiUri = new URI(
scheme,
uri.getUserInfo(),
host,
port,
uri.getPath(),
uri.getQuery(),
uri.getFragment()
).toASCIIString();
}
} catch (URISyntaxException e) {
// ignore, this was a best effort tidy-up
}
return apiUri.replaceAll("/$", "");
}

public synchronized void setEndpoints(@CheckForNull List<Endpoint> endpoints) {
endpoints = new ArrayList<Endpoint>(endpoints == null ? Collections.<Endpoint>emptyList() : endpoints);
// remove duplicates and empty urls
Set<String> apiUris = new HashSet<String>();
for (Iterator<Endpoint> iterator = endpoints.iterator(); iterator.hasNext(); ) {
Endpoint endpoint = iterator.next();
if (endpoint.getApiUri() == null || apiUris.contains(endpoint.getApiUri())) {
if (StringUtils.isBlank(endpoint.getApiUri()) || apiUris.contains(endpoint.getApiUri())) {
iterator.remove();
}
apiUris.add(endpoint.getApiUri());
Expand All @@ -74,4 +120,97 @@ public void setEndpoints(@CheckForNull List<Endpoint> endpoints) {
save();
}

/**
* Adds an endpoint.
*
* @param endpoint the endpoint to add.
* @return {@code true} if the list of endpoints was modified
*/
public synchronized boolean addEndpoint(@NonNull Endpoint endpoint) {
if (StringUtils.isBlank(endpoint.getApiUri())) {
return false;
}
List<Endpoint> endpoints = new ArrayList<>(getEndpoints());
for (Endpoint ep : endpoints) {
if (StringUtils.equals(ep.getApiUri(), endpoint.getApiUri())) {
return false;
}
}
endpoints.add(endpoint);
setEndpoints(endpoints);
return true;
}

/**
* Updates an existing endpoint (or adds if missing).
*
* @param endpoint the endpoint to update.
*/
public synchronized void updateEndpoint(@NonNull Endpoint endpoint) {
if (StringUtils.isBlank(endpoint.getApiUri())) {
return;
}
List<Endpoint> endpoints = new ArrayList<>(getEndpoints());
boolean found = false;
for (int i = 0; i < endpoints.size(); i++) {
Endpoint ep = endpoints.get(i);
if (StringUtils.equals(ep.getApiUri(), endpoint.getApiUri())) {
endpoints.set(i, endpoint);
found = true;
break;
}
}
if (!found) {
endpoints.add(endpoint);
}
setEndpoints(endpoints);
}

/**
* Removes an endpoint.
*
* @param endpoint the endpoint to remove.
* @return {@code true} if the list of endpoints was modified
*/
public boolean removeEndpoint(@NonNull Endpoint endpoint) {
return removeEndpoint(endpoint.getApiUri());
}

/**
* Removes an endpoint.
*
* @param apiUri the API URI to remove.
* @return {@code true} if the list of endpoints was modified
*/
public synchronized boolean removeEndpoint(@CheckForNull String apiUri) {
apiUri = normalizeApiUri(apiUri);
boolean modified = false;
List<Endpoint> endpoints = new ArrayList<>(getEndpoints());
for (Iterator<Endpoint> iterator = endpoints.iterator(); iterator.hasNext(); ) {
if (StringUtils.equals(apiUri, iterator.next().getApiUri())) {
iterator.remove();
modified = true;
}
}
setEndpoints(endpoints);
return modified;
}

/**
* Checks to see if the supplied server URL is defined in the global configuration.
*
* @param apiUri the server url to check.
* @return the global configuration for the specified server url or {@code null} if not defined.
*/
@CheckForNull
public synchronized Endpoint findEndpoint(@CheckForNull String apiUri) {
apiUri = normalizeApiUri(apiUri);
for (Endpoint endpoint : getEndpoints()) {
if (StringUtils.equals(apiUri, endpoint.getApiUri())) {
return endpoint;
}
}
return null;
}

}
Expand Up @@ -71,7 +71,6 @@
import org.kohsuke.github.GHUser;
import org.kohsuke.github.GitHub;
import org.kohsuke.github.HttpException;
import org.kohsuke.github.PagedIterable;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
Expand Down Expand Up @@ -113,7 +112,7 @@ public GitHubSCMNavigator(String apiUri, String repoOwner, String scanCredential
this.repoOwner = repoOwner;
this.scanCredentialsId = Util.fixEmpty(scanCredentialsId);
this.checkoutCredentialsId = checkoutCredentialsId;
this.apiUri = Util.fixEmpty(apiUri);
this.apiUri = GitHubConfiguration.normalizeApiUri(Util.fixEmpty(apiUri));
}

/** Use defaults for old settings. */
Expand All @@ -137,6 +136,19 @@ private Object readResolve() {
if (buildForkPRHead == null) {
buildForkPRHead = DescriptorImpl.defaultBuildForkPRHead;
}
if (!StringUtils.equals(apiUri, GitHubConfiguration.normalizeApiUri(apiUri))) {
GitHubSCMNavigator that = new GitHubSCMNavigator(apiUri, repoOwner, scanCredentialsId, checkoutCredentialsId);
that.pattern = this.pattern;
that.includes = this.includes;
that.excludes = this.excludes;
that.buildOriginBranch = this.buildOriginBranch;
that.buildOriginBranchWithPR = this.buildOriginBranchWithPR;
that.buildOriginPRHead = this.buildOriginPRHead;
that.buildOriginPRMerge = this.buildOriginPRMerge;
that.buildForkPRHead = this.buildForkPRHead;
that.buildForkPRMerge = this.buildForkPRMerge;
return that;
}
return this;
}

Expand Down Expand Up @@ -677,7 +689,8 @@ public ListBoxModel doFillApiUriItems() {
ListBoxModel result = new ListBoxModel();
result.add("GitHub", "");
for (Endpoint e : GitHubConfiguration.get().getEndpoints()) {
result.add(e.getName() == null ? e.getApiUri() : e.getName(), e.getApiUri());
result.add(e.getName() == null ? e.getApiUri() : e.getName() + " (" + e.getApiUri() + ")",
e.getApiUri());
}
return result;
}
Expand Down
Expand Up @@ -202,7 +202,7 @@ public class GitHubSCMSource extends AbstractGitSCMSource {
@DataBoundConstructor
public GitHubSCMSource(String id, String apiUri, String checkoutCredentialsId, String scanCredentialsId, String repoOwner, String repository) {
super(id);
this.apiUri = Util.fixEmpty(apiUri);
this.apiUri = GitHubConfiguration.normalizeApiUri(Util.fixEmpty(apiUri));
this.repoOwner = repoOwner;
this.repository = repository;
this.scanCredentialsId = Util.fixEmpty(scanCredentialsId);
Expand Down Expand Up @@ -238,6 +238,20 @@ private Object readResolve() {
if (pullRequestContributorCache == null) {
pullRequestContributorCache = new ConcurrentHashMap<>();
}
if (!StringUtils.equals(apiUri, GitHubConfiguration.normalizeApiUri(apiUri))) {
GitHubSCMSource that = new GitHubSCMSource(
getId(), apiUri, checkoutCredentialsId, scanCredentialsId, repoOwner, repository
);
that.includes = this.includes;
that.excludes = this.excludes;
that.buildOriginBranch = this.buildOriginBranch;
that.buildOriginBranchWithPR = this.buildOriginBranchWithPR;
that.buildOriginPRHead = this.buildOriginPRHead;
that.buildOriginPRMerge = this.buildOriginPRMerge;
that.buildForkPRHead = this.buildForkPRHead;
that.buildForkPRMerge = this.buildForkPRMerge;
return that;
}
return this;
}

Expand Down Expand Up @@ -1311,7 +1325,8 @@ public ListBoxModel doFillApiUriItems() {
ListBoxModel result = new ListBoxModel();
result.add("GitHub", "");
for (Endpoint e : GitHubConfiguration.get().getEndpoints()) {
result.add(e.getName() == null ? e.getApiUri() : e.getName(), e.getApiUri());
result.add(e.getName() == null ? e.getApiUri() : e.getName() + " (" + e.getApiUri() + ")",
e.getApiUri());
}
return result;
}
Expand Down

0 comments on commit f69fb42

Please sign in to comment.