PageRenderTime 34ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/com/atlassian/bamboo/plugins/git/GitCacheHandler.java

https://bitbucket.org/atlassian/bamboo-git-plugin
Java | 341 lines | 271 code | 41 blank | 29 comment | 17 complexity | dc44846808fecc808e410c00a001feb8 MD5 | raw file
Possible License(s): Apache-2.0
  1. package com.atlassian.bamboo.plugins.git;
  2. import com.atlassian.bamboo.build.fileserver.BuildDirectoryManager;
  3. import com.atlassian.bamboo.buildqueue.manager.AgentManager;
  4. import com.atlassian.bamboo.plan.PlanHelper;
  5. import com.atlassian.bamboo.plan.cache.CachedPlanManager;
  6. import com.atlassian.bamboo.plan.cache.ImmutableChain;
  7. import com.atlassian.bamboo.plan.cache.ImmutablePlan;
  8. import com.atlassian.bamboo.plugins.git.messages.DeleteSpecifiedGitCacheDirectoriesOnAgentMessage;
  9. import com.atlassian.bamboo.plugins.git.messages.DeleteUnusedGitCacheDirectoriesOnAgentMessage;
  10. import com.atlassian.bamboo.repository.CacheDescription;
  11. import com.atlassian.bamboo.repository.Repository;
  12. import com.atlassian.bamboo.repository.RepositoryDefinition;
  13. import com.atlassian.bamboo.utils.BambooPredicates;
  14. import com.atlassian.bamboo.v2.build.agent.AgentCommandSender;
  15. import com.atlassian.bamboo.v2.build.agent.BuildAgent;
  16. import com.atlassian.bamboo.v2.build.agent.LocalBuildAgent;
  17. import com.atlassian.bamboo.v2.build.agent.messages.RemoteBambooMessage;
  18. import com.atlassian.sal.api.message.I18nResolver;
  19. import com.google.common.base.Function;
  20. import com.google.common.base.Preconditions;
  21. import com.google.common.base.Predicates;
  22. import com.google.common.collect.Collections2;
  23. import com.google.common.collect.HashMultimap;
  24. import com.google.common.collect.ImmutableSet;
  25. import com.google.common.collect.Iterables;
  26. import com.google.common.collect.Lists;
  27. import com.google.common.collect.Maps;
  28. import com.google.common.collect.Multimap;
  29. import com.google.common.collect.Sets;
  30. import com.opensymphony.xwork.ValidationAware;
  31. import org.apache.commons.io.FileUtils;
  32. import org.apache.commons.io.filefilter.DirectoryFileFilter;
  33. import org.apache.commons.lang.ArrayUtils;
  34. import org.apache.commons.lang.StringUtils;
  35. import org.jetbrains.annotations.NotNull;
  36. import org.jetbrains.annotations.Nullable;
  37. import java.io.File;
  38. import java.io.FileFilter;
  39. import java.io.IOException;
  40. import java.util.ArrayList;
  41. import java.util.Collection;
  42. import java.util.Collections;
  43. import java.util.Map;
  44. import java.util.Set;
  45. /**
  46. * Implementation of a cache handler for git. This caters for data displayed on the Repository Settings admin page for
  47. * Git repositories.
  48. *
  49. * @since 2.6
  50. * @see com.atlassian.bamboo.repository.CacheHandler
  51. */
  52. @SuppressWarnings ({ "JavaDoc" })
  53. public class GitCacheHandler
  54. {
  55. private CachedPlanManager cachedPlanManager;
  56. private BuildDirectoryManager buildDirectoryManager;
  57. private GitCacheDirectoryUtils gitCacheDirectoryUtils;
  58. private I18nResolver i18nResolver;
  59. private AgentManager agentManager;
  60. private AgentCommandSender agentCommandSender;
  61. /**
  62. * Retrieves git repositories of one plan
  63. */
  64. private static Function<ImmutablePlan, Iterable<GitRepository>> GIT_REPOSITORIES_OF_PLAN = new Function<ImmutablePlan, Iterable<GitRepository>>()
  65. {
  66. @Override
  67. public Iterable<GitRepository> apply(@Nullable final ImmutablePlan input)
  68. {
  69. final Iterable<Repository> allRepositories = Iterables.transform(PlanHelper.getRepositoryDefinitions(input), new Function<RepositoryDefinition, Repository>()
  70. {
  71. @Override
  72. public Repository apply(@Nullable final RepositoryDefinition input)
  73. {
  74. return input.getRepository();
  75. }
  76. });
  77. return Iterables.transform(Iterables.filter(allRepositories, Predicates.or(Predicates.instanceOf(GitRepository.class), Predicates.instanceOf(GitHubRepository.class))),
  78. new Function<Repository, GitRepository>()
  79. {
  80. @Override
  81. public GitRepository apply(@Nullable final Repository input)
  82. {
  83. if (input instanceof GitHubRepository)
  84. {
  85. return ((GitHubRepository) input).getGitRepository();
  86. }
  87. else
  88. {
  89. return (GitRepository) input;
  90. }
  91. }
  92. });
  93. }
  94. };
  95. /**
  96. * Handles both Git and GitHub repositories.
  97. * @see com.atlassian.bamboo.repository.CacheHandler#getCacheDescriptions()
  98. */
  99. @NotNull
  100. public Collection<CacheDescription> getCacheDescriptions()
  101. {
  102. final Collection<CacheDescription> cacheDescriptions = Lists.newArrayList();
  103. final Multimap<File, ImmutablePlan> plans = HashMultimap.create();
  104. final Map<File, GitRepository> repositories = Maps.newHashMap();
  105. for (ImmutablePlan plan : cachedPlanManager.getPlans(ImmutableChain.class))
  106. {
  107. for (GitRepository gitRepository : GIT_REPOSITORIES_OF_PLAN.apply(plan))
  108. {
  109. File cacheDir = gitRepository.getCacheDirectory();
  110. plans.put(cacheDir, plan);
  111. if (!repositories.containsKey(cacheDir))
  112. {
  113. repositories.put(cacheDir, gitRepository);
  114. }
  115. }
  116. }
  117. // add the used caches:
  118. for (File cacheDir : repositories.keySet())
  119. {
  120. cacheDescriptions.add(createCacheDescription(repositories.get(cacheDir), cacheDir, plans.get(cacheDir)));
  121. }
  122. // add sparse info on unused caches:
  123. final Set<File> unusedDirs = findUnusedCaches(plans.keySet());
  124. for (File unusedDir : unusedDirs)
  125. {
  126. final String description = "Descriptions for unused caches is unsupported";
  127. final CacheDescription cacheDescription = new CacheDescription.FileBased(unusedDir, description, Collections.<ImmutablePlan>emptyList());
  128. cacheDescriptions.add(cacheDescription);
  129. }
  130. return cacheDescriptions;
  131. }
  132. @NotNull
  133. private static CacheDescription createCacheDescription(@NotNull GitRepository repository, @NotNull File cacheDir, @NotNull Collection<ImmutablePlan> usingPlans)
  134. {
  135. final GitRepositoryAccessData accessData = repository.getSubstitutedAccessData();
  136. final StringBuilder sb = new StringBuilder();
  137. sb.append("URL: '").append(accessData.getRepositoryUrl()).append('\'');
  138. if (accessData.getUsername() != null)
  139. {
  140. sb.append(", Username: '").append(accessData.getUsername()).append('\'');
  141. }
  142. final Collection<String> features = new ArrayList<String>(2);
  143. if (accessData.isUseRemoteAgentCache())
  144. {
  145. features.add("remote agent caching");
  146. }
  147. if (!features.isEmpty())
  148. {
  149. sb.append(" (").append(StringUtils.join(features, ", ")).append(")");
  150. }
  151. final String description = sb.toString();
  152. return new CacheDescription.FileBased(cacheDir, description, usingPlans);
  153. }
  154. /**
  155. * Handles both Git and GitHub repositories.
  156. *
  157. * @see com.atlassian.bamboo.repository.CacheHandler#deleteCaches(java.util.Collection,
  158. * com.opensymphony.xwork.ValidationAware)
  159. */
  160. public void deleteCaches(@NotNull Collection<String> keys, @NotNull ValidationAware feedback)
  161. {
  162. if (keys.isEmpty())
  163. {
  164. feedback.addActionMessage(i18nResolver.getText("manageCaches.delete.git.nothingToDelete"));
  165. return;
  166. }
  167. final File cacheRootDir = getCacheRootDir();
  168. for (final String key : keys)
  169. {
  170. Preconditions.checkArgument(StringUtils.isAlphanumeric(key), "directory traversal blocked");
  171. final File cacheCandidate = new File(cacheRootDir, key);
  172. if (cacheCandidate.exists())
  173. {
  174. try
  175. {
  176. FileUtils.deleteDirectory(cacheCandidate);
  177. feedback.addActionMessage(i18nResolver.getText("manageCaches.delete.git.success", key));
  178. }
  179. catch (IOException e)
  180. {
  181. feedback.addActionError(i18nResolver.getText("manageCaches.delete.git.failed", key, e.getLocalizedMessage()));
  182. }
  183. }
  184. else
  185. {
  186. feedback.addActionMessage(i18nResolver.getText("manageCaches.delete.git.skipped", key));
  187. }
  188. }
  189. final RemoteBambooMessage message = new DeleteSpecifiedGitCacheDirectoriesOnAgentMessage(keys, gitCacheDirectoryUtils);
  190. final Collection<String> agentNames = sendMessageToRemoteAgents(message);
  191. if (!agentNames.isEmpty())
  192. {
  193. String names = StringUtils.join(agentNames, ", ");
  194. feedback.addActionMessage(i18nResolver.getText("manageCaches.delete.git.scheduling.deleteSpecific", names));
  195. }
  196. }
  197. /**
  198. * Handles both Git and GitHub repositories.
  199. * @see com.atlassian.bamboo.repository.CacheHandler#deleteUnusedCaches(com.opensymphony.xwork.ValidationAware)
  200. */
  201. public void deleteUnusedCaches(@NotNull final ValidationAware feedback)
  202. {
  203. // find all git repositories, (there will be duplicates in here):
  204. final Iterable<GitRepository> gitRepositories = Iterables.concat(Iterables.transform(cachedPlanManager.getPlans(ImmutableChain.class), GIT_REPOSITORIES_OF_PLAN));
  205. // get the cache directories for these repositories:
  206. final Iterable<File> cacheDirectories = Iterables.transform(gitRepositories, new Function<GitRepository, File>()
  207. {
  208. @Override
  209. public File apply(@Nullable final GitRepository input)
  210. {
  211. return input.getCacheDirectory();
  212. }
  213. });
  214. // iterate and remove duplicates:
  215. final Set<File> usedCaches = ImmutableSet.copyOf(cacheDirectories);
  216. final Set<File> unusedCacheDirs = findUnusedCaches(usedCaches);
  217. for (File unusedCacheDir : unusedCacheDirs)
  218. {
  219. final String sha = unusedCacheDir.getName();
  220. try
  221. {
  222. FileUtils.deleteDirectory(unusedCacheDir);
  223. feedback.addActionMessage(i18nResolver.getText("manageCaches.delete.git.unused.success", sha));
  224. }
  225. catch (IOException e)
  226. {
  227. feedback.addActionError(i18nResolver.getText("manageCaches.delete.git.unused.failed", sha, e.getLocalizedMessage()));
  228. }
  229. }
  230. final Collection<String> usedSHAs = Collections2.transform(usedCaches, new Function<File, String>()
  231. {
  232. public String apply(File from)
  233. {
  234. return from.getName();
  235. }
  236. });
  237. final RemoteBambooMessage message = new DeleteUnusedGitCacheDirectoriesOnAgentMessage(usedSHAs, gitCacheDirectoryUtils);
  238. final Collection<String> agentNames = sendMessageToRemoteAgents(message);
  239. if (!agentNames.isEmpty())
  240. {
  241. final String names = StringUtils.join(agentNames, ", ");
  242. feedback.addActionMessage(i18nResolver.getText("manageCaches.delete.git.scheduling.deleteUnused", names));
  243. }
  244. }
  245. @NotNull
  246. private Collection<String> sendMessageToRemoteAgents(final RemoteBambooMessage message)
  247. {
  248. final Collection<String> agentNames = Lists.newArrayList();
  249. for (final BuildAgent buildAgent : Iterables.filter(agentManager.getAllAgents(), BambooPredicates.buildAgentIsActive()))
  250. {
  251. buildAgent.accept(new BuildAgent.BuildAgentVisitor()
  252. {
  253. public void visitLocal(final LocalBuildAgent localBuildAgent)
  254. {
  255. }
  256. public void visitRemote(final BuildAgent remoteBuildAgent)
  257. {
  258. agentNames.add(buildAgent.getName());
  259. agentCommandSender.send(message, buildAgent.getId());
  260. }
  261. });
  262. }
  263. return agentNames;
  264. }
  265. @NotNull
  266. private Set<File> findUnusedCaches(@NotNull Set<File> usedCaches)
  267. {
  268. final File[] cacheDirs = getCacheRootDir().listFiles((FileFilter) DirectoryFileFilter.DIRECTORY); // will be null if cacheRootDir does not exist
  269. return ArrayUtils.isEmpty(cacheDirs) ? Collections.<File>emptySet() : Sets.difference(ImmutableSet.copyOf(cacheDirs), usedCaches);
  270. }
  271. public void setCachedPlanManager(CachedPlanManager cachedPlanManager)
  272. {
  273. this.cachedPlanManager = cachedPlanManager;
  274. }
  275. public void setBuildDirectoryManager(BuildDirectoryManager buildDirectoryManager)
  276. {
  277. this.buildDirectoryManager = buildDirectoryManager;
  278. }
  279. public void setI18nResolver(I18nResolver i18nResolver)
  280. {
  281. this.i18nResolver = i18nResolver;
  282. }
  283. public void setAgentManager(AgentManager agentManager)
  284. {
  285. this.agentManager = agentManager;
  286. }
  287. public void setAgentCommandSender(AgentCommandSender agentCommandSender)
  288. {
  289. this.agentCommandSender = agentCommandSender;
  290. }
  291. public void setGitCacheDirectoryUtils(final GitCacheDirectoryUtils gitCacheDirectoryUtils)
  292. {
  293. this.gitCacheDirectoryUtils = gitCacheDirectoryUtils;
  294. }
  295. @NotNull
  296. private File getCacheRootDir()
  297. {
  298. return GitCacheDirectory.getCacheDirectoryRoot(buildDirectoryManager.getBaseBuildWorkingDirectory());
  299. }
  300. }