/* | |
* Copyright 2011 gitblit.com. | |
* | |
* 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 com.gitblit.utils; | |
import java.lang.reflect.Type; | |
import java.text.MessageFormat; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import javax.servlet.http.HttpServletResponse; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import com.gitblit.Constants; | |
import com.gitblit.Constants.FederationProposalResult; | |
import com.gitblit.Constants.FederationRequest; | |
import com.gitblit.Constants.FederationToken; | |
import com.gitblit.IStoredSettings; | |
import com.gitblit.Keys; | |
import com.gitblit.models.FederationModel; | |
import com.gitblit.models.FederationProposal; | |
import com.gitblit.models.RepositoryModel; | |
import com.gitblit.models.TeamModel; | |
import com.gitblit.models.UserModel; | |
import com.google.gson.reflect.TypeToken; | |
/** | |
* Utility methods for federation functions. | |
* | |
* @author James Moger | |
* | |
*/ | |
public class FederationUtils { | |
private static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() { | |
}.getType(); | |
private static final Type SETTINGS_TYPE = new TypeToken<Map<String, String>>() { | |
}.getType(); | |
private static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() { | |
}.getType(); | |
private static final Type TEAMS_TYPE = new TypeToken<Collection<TeamModel>>() { | |
}.getType(); | |
private static final Logger LOGGER = LoggerFactory.getLogger(FederationUtils.class); | |
/** | |
* Returns an url to this servlet for the specified parameters. | |
* | |
* @param sourceURL | |
* the url of the source gitblit instance | |
* @param token | |
* the federation token of the source gitblit instance | |
* @param req | |
* the pull type request | |
*/ | |
public static String asLink(String sourceURL, String token, FederationRequest req) { | |
return asLink(sourceURL, null, token, req, null); | |
} | |
/** | |
* | |
* @param remoteURL | |
* the url of the remote gitblit instance | |
* @param tokenType | |
* the type of federation token of a gitblit instance | |
* @param token | |
* the federation token of a gitblit instance | |
* @param req | |
* the pull type request | |
* @param myURL | |
* the url of this gitblit instance | |
* @return | |
*/ | |
public static String asLink(String remoteURL, FederationToken tokenType, String token, | |
FederationRequest req, String myURL) { | |
if (remoteURL.length() > 0 && remoteURL.charAt(remoteURL.length() - 1) == '/') { | |
remoteURL = remoteURL.substring(0, remoteURL.length() - 1); | |
} | |
if (req == null) { | |
req = FederationRequest.PULL_REPOSITORIES; | |
} | |
return remoteURL + Constants.FEDERATION_PATH + "?req=" + req.name().toLowerCase() | |
+ (token == null ? "" : ("&token=" + token)) | |
+ (tokenType == null ? "" : ("&tokenType=" + tokenType.name().toLowerCase())) | |
+ (myURL == null ? "" : ("&url=" + StringUtils.encodeURL(myURL))); | |
} | |
/** | |
* Returns the list of federated gitblit instances that this instance will | |
* try to pull. | |
* | |
* @return list of registered gitblit instances | |
*/ | |
public static List<FederationModel> getFederationRegistrations(IStoredSettings settings) { | |
List<FederationModel> federationRegistrations = new ArrayList<FederationModel>(); | |
List<String> keys = settings.getAllKeys(Keys.federation._ROOT); | |
keys.remove(Keys.federation.name); | |
keys.remove(Keys.federation.passphrase); | |
keys.remove(Keys.federation.allowProposals); | |
keys.remove(Keys.federation.proposalsFolder); | |
keys.remove(Keys.federation.defaultFrequency); | |
keys.remove(Keys.federation.sets); | |
Collections.sort(keys); | |
Map<String, FederationModel> federatedModels = new HashMap<String, FederationModel>(); | |
for (String key : keys) { | |
String value = key.substring(Keys.federation._ROOT.length() + 1); | |
List<String> values = StringUtils.getStringsFromValue(value, "\\."); | |
String server = values.get(0); | |
if (!federatedModels.containsKey(server)) { | |
federatedModels.put(server, new FederationModel(server)); | |
} | |
String setting = values.get(1); | |
if (setting.equals("url")) { | |
// url of the origin Gitblit instance | |
federatedModels.get(server).url = settings.getString(key, ""); | |
} else if (setting.equals("token")) { | |
// token for the origin Gitblit instance | |
federatedModels.get(server).token = settings.getString(key, ""); | |
} else if (setting.equals("frequency")) { | |
// frequency of the pull operation | |
federatedModels.get(server).frequency = settings.getString(key, ""); | |
} else if (setting.equals("folder")) { | |
// destination folder of the pull operation | |
federatedModels.get(server).folder = settings.getString(key, ""); | |
} else if (setting.equals("bare")) { | |
// whether pulled repositories should be bare | |
federatedModels.get(server).bare = settings.getBoolean(key, true); | |
} else if (setting.equals("mirror")) { | |
// are the repositories to be true mirrors of the origin | |
federatedModels.get(server).mirror = settings.getBoolean(key, true); | |
} else if (setting.equals("mergeAccounts")) { | |
// merge remote accounts into local accounts | |
federatedModels.get(server).mergeAccounts = settings.getBoolean(key, false); | |
} else if (setting.equals("sendStatus")) { | |
// send a status acknowledgment to source Gitblit instance | |
// at end of git pull | |
federatedModels.get(server).sendStatus = settings.getBoolean(key, false); | |
} else if (setting.equals("notifyOnError")) { | |
// notify administrators on federation pull failures | |
federatedModels.get(server).notifyOnError = settings.getBoolean(key, false); | |
} else if (setting.equals("exclude")) { | |
// excluded repositories | |
federatedModels.get(server).exclusions = settings.getStrings(key); | |
} else if (setting.equals("include")) { | |
// included repositories | |
federatedModels.get(server).inclusions = settings.getStrings(key); | |
} | |
} | |
// verify that registrations have a url and a token | |
for (FederationModel model : federatedModels.values()) { | |
if (StringUtils.isEmpty(model.url)) { | |
LOGGER.warn(MessageFormat.format( | |
"Dropping federation registration {0}. Missing url.", model.name)); | |
continue; | |
} | |
if (StringUtils.isEmpty(model.token)) { | |
LOGGER.warn(MessageFormat.format( | |
"Dropping federation registration {0}. Missing token.", model.name)); | |
continue; | |
} | |
// set default frequency if unspecified | |
if (StringUtils.isEmpty(model.frequency)) { | |
model.frequency = settings.getString(Keys.federation.defaultFrequency, "60 mins"); | |
} | |
federationRegistrations.add(model); | |
} | |
return federationRegistrations; | |
} | |
/** | |
* Sends a federation poke to the Gitblit instance at remoteUrl. Pokes are | |
* sent by an pulling Gitblit instance to an origin Gitblit instance as part | |
* of the proposal process. This is to ensure that the pulling Gitblit | |
* instance has an IP route to the origin instance. | |
* | |
* @param remoteUrl | |
* the remote Gitblit instance to send a federation proposal to | |
* @param proposal | |
* a complete federation proposal | |
* @return true if there is a route to the remoteUrl | |
*/ | |
public static boolean poke(String remoteUrl) throws Exception { | |
String url = asLink(remoteUrl, null, FederationRequest.POKE); | |
String json = JsonUtils.toJsonString("POKE"); | |
int status = JsonUtils.sendJsonString(url, json); | |
return status == HttpServletResponse.SC_OK; | |
} | |
/** | |
* Sends a federation proposal to the Gitblit instance at remoteUrl | |
* | |
* @param remoteUrl | |
* the remote Gitblit instance to send a federation proposal to | |
* @param proposal | |
* a complete federation proposal | |
* @return the federation proposal result code | |
*/ | |
public static FederationProposalResult propose(String remoteUrl, FederationProposal proposal) | |
throws Exception { | |
String url = asLink(remoteUrl, null, FederationRequest.PROPOSAL); | |
String json = JsonUtils.toJsonString(proposal); | |
int status = JsonUtils.sendJsonString(url, json); | |
switch (status) { | |
case HttpServletResponse.SC_FORBIDDEN: | |
// remote Gitblit Federation disabled | |
return FederationProposalResult.FEDERATION_DISABLED; | |
case HttpServletResponse.SC_BAD_REQUEST: | |
// remote Gitblit did not receive any JSON data | |
return FederationProposalResult.MISSING_DATA; | |
case HttpServletResponse.SC_METHOD_NOT_ALLOWED: | |
// remote Gitblit not accepting proposals | |
return FederationProposalResult.NO_PROPOSALS; | |
case HttpServletResponse.SC_NOT_ACCEPTABLE: | |
// remote Gitblit failed to poke this Gitblit instance | |
return FederationProposalResult.NO_POKE; | |
case HttpServletResponse.SC_OK: | |
// received | |
return FederationProposalResult.ACCEPTED; | |
default: | |
return FederationProposalResult.ERROR; | |
} | |
} | |
/** | |
* Retrieves a map of the repositories at the remote gitblit instance keyed | |
* by the repository clone url. | |
* | |
* @param registration | |
* @param checkExclusions | |
* should returned repositories remove registration exclusions | |
* @return a map of cloneable repositories | |
* @throws Exception | |
*/ | |
public static Map<String, RepositoryModel> getRepositories(FederationModel registration, | |
boolean checkExclusions) throws Exception { | |
String url = asLink(registration.url, registration.token, | |
FederationRequest.PULL_REPOSITORIES); | |
Map<String, RepositoryModel> models = JsonUtils.retrieveJson(url, REPOSITORIES_TYPE); | |
if (checkExclusions) { | |
Map<String, RepositoryModel> includedModels = new HashMap<String, RepositoryModel>(); | |
for (Map.Entry<String, RepositoryModel> entry : models.entrySet()) { | |
if (registration.isIncluded(entry.getValue())) { | |
includedModels.put(entry.getKey(), entry.getValue()); | |
} | |
} | |
return includedModels; | |
} | |
return models; | |
} | |
/** | |
* Tries to pull the gitblit user accounts from the remote gitblit instance. | |
* | |
* @param registration | |
* @return a collection of UserModel objects | |
* @throws Exception | |
*/ | |
public static List<UserModel> getUsers(FederationModel registration) throws Exception { | |
String url = asLink(registration.url, registration.token, FederationRequest.PULL_USERS); | |
Collection<UserModel> models = JsonUtils.retrieveJson(url, USERS_TYPE); | |
List<UserModel> list = new ArrayList<UserModel>(models); | |
return list; | |
} | |
/** | |
* Tries to pull the gitblit team definitions from the remote gitblit | |
* instance. | |
* | |
* @param registration | |
* @return a collection of TeamModel objects | |
* @throws Exception | |
*/ | |
public static List<TeamModel> getTeams(FederationModel registration) throws Exception { | |
String url = asLink(registration.url, registration.token, FederationRequest.PULL_TEAMS); | |
Collection<TeamModel> models = JsonUtils.retrieveJson(url, TEAMS_TYPE); | |
List<TeamModel> list = new ArrayList<TeamModel>(models); | |
return list; | |
} | |
/** | |
* Tries to pull the gitblit server settings from the remote gitblit | |
* instance. | |
* | |
* @param registration | |
* @return a map of the remote gitblit settings | |
* @throws Exception | |
*/ | |
public static Map<String, String> getSettings(FederationModel registration) throws Exception { | |
String url = asLink(registration.url, registration.token, FederationRequest.PULL_SETTINGS); | |
Map<String, String> settings = JsonUtils.retrieveJson(url, SETTINGS_TYPE); | |
return settings; | |
} | |
/** | |
* Tries to pull the referenced scripts from the remote gitblit instance. | |
* | |
* @param registration | |
* @return a map of the remote gitblit scripts by script name | |
* @throws Exception | |
*/ | |
public static Map<String, String> getScripts(FederationModel registration) throws Exception { | |
String url = asLink(registration.url, registration.token, FederationRequest.PULL_SCRIPTS); | |
Map<String, String> scripts = JsonUtils.retrieveJson(url, SETTINGS_TYPE); | |
return scripts; | |
} | |
/** | |
* Send an status acknowledgment to the remote Gitblit server. | |
* | |
* @param identification | |
* identification of this pulling instance | |
* @param registration | |
* the source Gitblit instance to receive an acknowledgment | |
* @param results | |
* the results of your pull operation | |
* @return true, if the remote Gitblit instance acknowledged your results | |
* @throws Exception | |
*/ | |
public static boolean acknowledgeStatus(String identification, FederationModel registration) | |
throws Exception { | |
String url = asLink(registration.url, null, registration.token, FederationRequest.STATUS, | |
identification); | |
String json = JsonUtils.toJsonString(registration); | |
int status = JsonUtils.sendJsonString(url, json); | |
return status == HttpServletResponse.SC_OK; | |
} | |
} |