Package com.gitblit.service

Source Code of com.gitblit.service.FederationPullService

package com.gitblit.service;

import static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitblit.ConfigUserService;
import com.gitblit.Constants;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.FederationPullStatus;
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.GitBlitException.ForbiddenException;
import com.gitblit.IUserService;
import com.gitblit.Keys;
import com.gitblit.manager.IGitblit;
import com.gitblit.models.FederationModel;
import com.gitblit.models.RefModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.FileUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JGitUtils.CloneResult;
import com.gitblit.utils.StringUtils;

public abstract class FederationPullService implements Runnable {

  final Logger logger = LoggerFactory.getLogger(getClass());

  final IGitblit gitblit;

  private final List<FederationModel> registrations;

  /**
   * Constructor for specifying a single federation registration. This
   * constructor is used to schedule the next pull execution.
   *
   * @param provider
   * @param registration
   */
  public FederationPullService(IGitblit gitblit, FederationModel registration) {
    this(gitblit, Arrays.asList(registration));
  }

  /**
   * Constructor to specify a group of federation registrations. This is
   * normally used at startup to pull and then schedule the next update based
   * on each registrations frequency setting.
   *
   * @param provider
   * @param registrations
   * @param isDaemon
   *            if true, registrations are rescheduled in perpetuity. if
   *            false, the federation pull operation is executed once.
   */
  public FederationPullService(IGitblit gitblit, List<FederationModel> registrations) {
    this.gitblit = gitblit;
    this.registrations = registrations;
  }

  public abstract void reschedule(FederationModel registration);

  /**
   * Run method for this pull service.
   */
  @Override
  public void run() {
    for (FederationModel registration : registrations) {
      FederationPullStatus was = registration.getLowestStatus();
      try {
        Date now = new Date(System.currentTimeMillis());
        pull(registration);
        sendStatusAcknowledgment(registration);
        registration.lastPull = now;
        FederationPullStatus is = registration.getLowestStatus();
        if (is.ordinal() < was.ordinal()) {
          // the status for this registration has downgraded
          logger.warn("Federation pull status of {0} is now {1}", registration.name,
              is.name());
          if (registration.notifyOnError) {
            String message = "Federation pull of " + registration.name + " @ "
                + registration.url + " is now at " + is.name();
            gitblit.sendMailToAdministrators(
                "Pull Status of " + registration.name + " is " + is.name(),
                message);
          }
        }
      } catch (Throwable t) {
        logger.error(MessageFormat.format(
            "Failed to pull from federated gitblit ({0} @ {1})", registration.name,
            registration.url), t);
      } finally {
        reschedule(registration);
      }
    }
  }

  /**
   * Mirrors a repository and, optionally, the server's users, and/or
   * configuration settings from a origin Gitblit instance.
   *
   * @param registration
   * @throws Exception
   */
  private void pull(FederationModel registration) throws Exception {
    Map<String, RepositoryModel> repositories = FederationUtils.getRepositories(registration,
        true);
    String registrationFolder = registration.folder.toLowerCase().trim();
    // confirm valid characters in server alias
    Character c = StringUtils.findInvalidCharacter(registrationFolder);
    if (c != null) {
      logger.error(MessageFormat
          .format("Illegal character ''{0}'' in folder name ''{1}'' of federation registration {2}!",
              c, registrationFolder, registration.name));
      return;
    }
    File repositoriesFolder = gitblit.getRepositoriesFolder();
    File registrationFolderFile = new File(repositoriesFolder, registrationFolder);
    registrationFolderFile.mkdirs();

    // Clone/Pull the repository
    for (Map.Entry<String, RepositoryModel> entry : repositories.entrySet()) {
      String cloneUrl = entry.getKey();
      RepositoryModel repository = entry.getValue();
      if (!repository.hasCommits) {
        logger.warn(MessageFormat.format(
            "Skipping federated repository {0} from {1} @ {2}. Repository is EMPTY.",
            repository.name, registration.name, registration.url));
        registration.updateStatus(repository, FederationPullStatus.SKIPPED);
        continue;
      }

      // Determine local repository name
      String repositoryName;
      if (StringUtils.isEmpty(registrationFolder)) {
        repositoryName = repository.name;
      } else {
        repositoryName = registrationFolder + "/" + repository.name;
      }

      if (registration.bare) {
        // bare repository, ensure .git suffix
        if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
          repositoryName += DOT_GIT_EXT;
        }
      } else {
        // normal repository, strip .git suffix
        if (repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
          repositoryName = repositoryName.substring(0,
              repositoryName.indexOf(DOT_GIT_EXT));
        }
      }

      // confirm that the origin of any pre-existing repository matches
      // the clone url
      String fetchHead = null;
      Repository existingRepository = gitblit.getRepository(repositoryName);

      if (existingRepository == null && gitblit.isCollectingGarbage(repositoryName)) {
        logger.warn(MessageFormat.format("Skipping local repository {0}, busy collecting garbage", repositoryName));
        continue;
      }

      if (existingRepository != null) {
        StoredConfig config = existingRepository.getConfig();
        config.load();
        String origin = config.getString("remote", "origin", "url");
        RevCommit commit = JGitUtils.getCommit(existingRepository,
            org.eclipse.jgit.lib.Constants.FETCH_HEAD);
        if (commit != null) {
          fetchHead = commit.getName();
        }
        existingRepository.close();
        if (!origin.startsWith(registration.url)) {
          logger.warn(MessageFormat
              .format("Skipping federated repository {0} from {1} @ {2}. Origin does not match, consider EXCLUDING.",
                  repository.name, registration.name, registration.url));
          registration.updateStatus(repository, FederationPullStatus.SKIPPED);
          continue;
        }
      }

      // clone/pull this repository
      CredentialsProvider credentials = new UsernamePasswordCredentialsProvider(
          Constants.FEDERATION_USER, registration.token);
      logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}",
          repository.name, registration.name, registration.url));

      CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name,
          cloneUrl, registration.bare, credentials);
      Repository r = gitblit.getRepository(repositoryName);
      RepositoryModel rm = gitblit.getRepositoryModel(repositoryName);
      repository.isFrozen = registration.mirror;
      if (result.createdRepository) {
        // default local settings
        repository.federationStrategy = FederationStrategy.EXCLUDE;
        repository.isFrozen = registration.mirror;
        repository.showRemoteBranches = !registration.mirror;
        logger.info(MessageFormat.format("     cloning {0}", repository.name));
        registration.updateStatus(repository, FederationPullStatus.MIRRORED);
      } else {
        // fetch and update
        boolean fetched = false;
        RevCommit commit = JGitUtils.getCommit(r, org.eclipse.jgit.lib.Constants.FETCH_HEAD);
        String newFetchHead = commit.getName();
        fetched = fetchHead == null || !fetchHead.equals(newFetchHead);

        if (registration.mirror) {
          // mirror
          if (fetched) {
            // update local branches to match the remote tracking branches
            for (RefModel ref : JGitUtils.getRemoteBranches(r, false, -1)) {
              if (ref.displayName.startsWith("origin/")) {
                String branch = org.eclipse.jgit.lib.Constants.R_HEADS
                    + ref.displayName.substring(ref.displayName.indexOf('/') + 1);
                String hash = ref.getReferencedObjectId().getName();

                JGitUtils.setBranchRef(r, branch, hash);
                logger.info(MessageFormat.format("     resetting {0} of {1} to {2}", branch,
                    repository.name, hash));
              }
            }

            String newHead;
            if (StringUtils.isEmpty(repository.HEAD)) {
              newHead = newFetchHead;
            } else {
              newHead = repository.HEAD;
            }
            JGitUtils.setHEADtoRef(r, newHead);
            logger.info(MessageFormat.format("     resetting HEAD of {0} to {1}",
                repository.name, newHead));
            registration.updateStatus(repository, FederationPullStatus.MIRRORED);
          } else {
            // indicate no commits pulled
            registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
          }
        } else {
          // non-mirror
          if (fetched) {
            // indicate commits pulled to origin/master
            registration.updateStatus(repository, FederationPullStatus.PULLED);
          } else {
            // indicate no commits pulled
            registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
          }
        }

        // preserve local settings
        repository.isFrozen = rm.isFrozen;
        repository.federationStrategy = rm.federationStrategy;

        // merge federation sets
        Set<String> federationSets = new HashSet<String>();
        if (rm.federationSets != null) {
          federationSets.addAll(rm.federationSets);
        }
        if (repository.federationSets != null) {
          federationSets.addAll(repository.federationSets);
        }
        repository.federationSets = new ArrayList<String>(federationSets);

        // merge indexed branches
        Set<String> indexedBranches = new HashSet<String>();
        if (rm.indexedBranches != null) {
          indexedBranches.addAll(rm.indexedBranches);
        }
        if (repository.indexedBranches != null) {
          indexedBranches.addAll(repository.indexedBranches);
        }
        repository.indexedBranches = new ArrayList<String>(indexedBranches);

      }
      // only repositories that are actually _cloned_ from the origin
      // Gitblit repository are marked as federated. If the origin
      // is from somewhere else, these repositories are not considered
      // "federated" repositories.
      repository.isFederated = cloneUrl.startsWith(registration.url);

      gitblit.updateConfiguration(r, repository);
      r.close();
    }

    IUserService userService = null;

    try {
      // Pull USERS
      // TeamModels are automatically pulled because they are contained
      // within the UserModel. The UserService creates unknown teams
      // and updates existing teams.
      Collection<UserModel> users = FederationUtils.getUsers(registration);
      if (users != null && users.size() > 0) {
        File realmFile = new File(registrationFolderFile, registration.name + "_users.conf");
        realmFile.delete();
        userService = new ConfigUserService(realmFile);
        for (UserModel user : users) {
          userService.updateUserModel(user.username, user);

          // merge the origin permissions and origin accounts into
          // the user accounts of this Gitblit instance
          if (registration.mergeAccounts) {
            // reparent all repository permissions if the local
            // repositories are stored within subfolders
            if (!StringUtils.isEmpty(registrationFolder)) {
              if (user.permissions != null) {
                // pulling from >= 1.2 version
                Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
                user.permissions.clear();
                for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
                  user.setRepositoryPermission(registrationFolder + "/" + entry.getKey(), entry.getValue());
                }
              } else {
                // pulling from <= 1.1 version
                List<String> permissions = new ArrayList<String>(user.repositories);
                user.repositories.clear();
                for (String permission : permissions) {
                  user.addRepositoryPermission(registrationFolder + "/" + permission);
                }
              }
            }

            // insert new user or update local user
            UserModel localUser = gitblit.getUserModel(user.username);
            if (localUser == null) {
              // create new local user
              gitblit.addUser(user);
            } else {
              // update repository permissions of local user
              if (user.permissions != null) {
                // pulling from >= 1.2 version
                Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
                for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
                  localUser.setRepositoryPermission(entry.getKey(), entry.getValue());
                }
              } else {
                // pulling from <= 1.1 version
                for (String repository : user.repositories) {
                  localUser.addRepositoryPermission(repository);
                }
              }
              localUser.password = user.password;
              localUser.canAdmin = user.canAdmin;
              gitblit.reviseUser(localUser.username, localUser);
            }

            for (String teamname : gitblit.getAllTeamNames()) {
              TeamModel team = gitblit.getTeamModel(teamname);
              if (user.isTeamMember(teamname) && !team.hasUser(user.username)) {
                // new team member
                team.addUser(user.username);
                gitblit.updateTeamModel(teamname, team);
              } else if (!user.isTeamMember(teamname) && team.hasUser(user.username)) {
                // remove team member
                team.removeUser(user.username);
                gitblit.updateTeamModel(teamname, team);
              }

              // update team repositories
              TeamModel remoteTeam = user.getTeam(teamname);
              if (remoteTeam != null) {
                if (remoteTeam.permissions != null) {
                  // pulling from >= 1.2
                  for (Map.Entry<String, AccessPermission> entry : remoteTeam.permissions.entrySet()){
                    team.setRepositoryPermission(entry.getKey(), entry.getValue());
                  }
                  gitblit.updateTeamModel(teamname, team);
                } else if (!ArrayUtils.isEmpty(remoteTeam.repositories)) {
                  // pulling from <= 1.1
                  team.addRepositoryPermissions(remoteTeam.repositories);
                  gitblit.updateTeamModel(teamname, team);
                }
              }
            }
          }
        }
      }
    } catch (ForbiddenException e) {
      // ignore forbidden exceptions
    } catch (IOException e) {
      logger.warn(MessageFormat.format(
          "Failed to retrieve USERS from federated gitblit ({0} @ {1})",
          registration.name, registration.url), e);
    }

    try {
      // Pull TEAMS
      // We explicitly pull these even though they are embedded in
      // UserModels because it is possible to use teams to specify
      // mailing lists or push scripts without specifying users.
      if (userService != null) {
        Collection<TeamModel> teams = FederationUtils.getTeams(registration);
        if (teams != null && teams.size() > 0) {
          for (TeamModel team : teams) {
            userService.updateTeamModel(team);
          }
        }
      }
    } catch (ForbiddenException e) {
      // ignore forbidden exceptions
    } catch (IOException e) {
      logger.warn(MessageFormat.format(
          "Failed to retrieve TEAMS from federated gitblit ({0} @ {1})",
          registration.name, registration.url), e);
    }

    try {
      // Pull SETTINGS
      Map<String, String> settings = FederationUtils.getSettings(registration);
      if (settings != null && settings.size() > 0) {
        Properties properties = new Properties();
        properties.putAll(settings);
        FileOutputStream os = new FileOutputStream(new File(registrationFolderFile,
            registration.name + "_" + Constants.PROPERTIES_FILE));
        properties.store(os, null);
        os.close();
      }
    } catch (ForbiddenException e) {
      // ignore forbidden exceptions
    } catch (IOException e) {
      logger.warn(MessageFormat.format(
          "Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})",
          registration.name, registration.url), e);
    }

    try {
      // Pull SCRIPTS
      Map<String, String> scripts = FederationUtils.getScripts(registration);
      if (scripts != null && scripts.size() > 0) {
        for (Map.Entry<String, String> script : scripts.entrySet()) {
          String scriptName = script.getKey();
          if (scriptName.endsWith(".groovy")) {
            scriptName = scriptName.substring(0, scriptName.indexOf(".groovy"));
          }
          File file = new File(registrationFolderFile, registration.name + "_"
              + scriptName + ".groovy");
          FileUtils.writeContent(file, script.getValue());
        }
      }
    } catch (ForbiddenException e) {
      // ignore forbidden exceptions
    } catch (IOException e) {
      logger.warn(MessageFormat.format(
          "Failed to retrieve SCRIPTS from federated gitblit ({0} @ {1})",
          registration.name, registration.url), e);
    }
  }

  /**
   * Sends a status acknowledgment to the origin Gitblit instance. This
   * includes the results of the federated pull.
   *
   * @param registration
   * @throws Exception
   */
  private void sendStatusAcknowledgment(FederationModel registration) throws Exception {
    if (!registration.sendStatus) {
      // skip status acknowledgment
      return;
    }
    InetAddress addr = InetAddress.getLocalHost();
    String federationName = gitblit.getSettings().getString(Keys.federation.name, null);
    if (StringUtils.isEmpty(federationName)) {
      federationName = addr.getHostName();
    }
    FederationUtils.acknowledgeStatus(addr.getHostAddress(), registration);
    logger.info(MessageFormat.format("Pull status sent to {0}", registration.url));
  }
}
TOP

Related Classes of com.gitblit.service.FederationPullService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.