package com.hubspot.blazar.discovery; import static com.hubspot.blazar.util.ModuleDiscoveryValidations.getDuplicateModules; import static com.hubspot.blazar.util.ModuleDiscoveryValidations.getDuplicateModulesMalformedFile; import static com.hubspot.blazar.util.ModuleDiscoveryValidations.preDiscoveryBranchValidation; import java.io.IOException; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Optional; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.hubspot.blazar.base.CommitInfo; import com.hubspot.blazar.base.DiscoveredModule; import com.hubspot.blazar.base.DiscoveryResult; import com.hubspot.blazar.base.GitInfo; import com.hubspot.blazar.base.MalformedFile; import com.hubspot.blazar.base.Module; import com.hubspot.blazar.base.ModuleDiscoveryResult; import com.hubspot.blazar.data.service.DependenciesService; import com.hubspot.blazar.data.service.ModuleDiscoveryService; import com.hubspot.blazar.data.service.ModuleService; @Singleton public class ModuleDiscoveryHandler { private static final Logger LOG = LoggerFactory.getLogger(ModuleDiscoveryHandler.class); private final Set<ModuleDiscovery> moduleDiscoveryPlugins; private final BuildConfigDiscovery buildConfigDiscovery; private final BuildConfigurationResolver buildConfigurationResolver; private final ModuleDiscoveryService moduleDiscoveryService; private final ModuleService moduleService; private final DependenciesService dependenciesService; @Inject public ModuleDiscoveryHandler(Set<ModuleDiscovery> moduleDiscoveryPlugins, BuildConfigDiscovery buildConfigDiscovery, BuildConfigurationResolver buildConfigurationResolver, ModuleDiscoveryService moduleDiscoveryService, ModuleService moduleService, DependenciesService dependenciesService) { this.moduleDiscoveryPlugins = moduleDiscoveryPlugins; this.buildConfigDiscovery = buildConfigDiscovery; this.buildConfigurationResolver = buildConfigurationResolver; this.moduleDiscoveryService = moduleDiscoveryService; this.moduleService = moduleService; this.dependenciesService = dependenciesService; } public ModuleDiscoveryResult updateModules(GitInfo branch, boolean persistUpdatedModules) throws IOException { return updateModules(branch, Optional.absent(), persistUpdatedModules); } public ModuleDiscoveryResult updateModules(GitInfo branch, CommitInfo commitInfo, boolean persistUpdatedModules) throws IOException { return updateModules(branch, Optional.of(commitInfo), persistUpdatedModules); } private ModuleDiscoveryResult updateModules(GitInfo branch, Optional<CommitInfo> commitInfo, boolean persistUpdatedModules) throws IOException { Multimap<String, Module> discoveredModulesByFolder = ArrayListMultimap.create(); Set<MalformedFile> malformedFiles = new HashSet<>(); // check if branch name is malformed Optional<MalformedFile> malformedBranchFile = preDiscoveryBranchValidation(branch); if (malformedBranchFile.isPresent()) { ModuleDiscoveryResult moduleDiscoveryResult = new ModuleDiscoveryResult(ImmutableSet.of(), ImmutableSet.of(malformedBranchFile.get())); if (persistUpdatedModules) { moduleDiscoveryService.persistDiscoveryResult(branch, moduleDiscoveryResult); } return moduleDiscoveryResult; } // apply the available plugins to discover modules inside the branch applyModuleDiscoveryPlugins(branch, commitInfo, discoveredModulesByFolder, malformedFiles); // We will now create a combined map of the (re)discovered modules plus the modules that have been registered before // and have not been deleted during this discovery iteration. Multimap<String, Module> allActiveModules = ArrayListMultimap.create(discoveredModulesByFolder); Set<Module> alreadyRegisteredModules = moduleService.getByBranch(branch.getId().get()); Set<String> rediscoveredModuleTypes = discoveredModulesByFolder.values().stream().map(Module::getType).collect(Collectors.toSet()); alreadyRegisteredModules.forEach(module -> { boolean isRediscovered = isRediscovered(module, discoveredModulesByFolder.get(module.getFolder())); // if it was not discovered this time but its type is among the types we have rediscovered then it was deleted // and we will not add it in the list if (module.isActive() && !isRediscovered && !rediscoveredModuleTypes.contains(module.getType())) { allActiveModules.put(module.getFolder(), module); } }); // check if modules use the same module name checkForDuplicateModuleNames(branch, allActiveModules, malformedFiles); // Resolve the build configuration for each active module. // It is possible that there is no plugin available and users directly specify building instructions // in .blazar.yaml files. // Another option is that users use .blazar.yaml files to disable module building or override // the auto-discovered configurations buildConfigurationResolver.findAndResolveBuildConfigurations(branch, allActiveModules, malformedFiles); ModuleDiscoveryResult moduleDiscoveryResult = new ModuleDiscoveryResult(ImmutableSet.copyOf(allActiveModules.values()), ImmutableSet.copyOf(malformedFiles)); if (persistUpdatedModules) { moduleDiscoveryService.persistDiscoveryResult(branch, moduleDiscoveryResult); } return moduleDiscoveryResult; } private boolean isRediscovered(Module existingModule, Collection<Module> discoveredModulesInSameFolder) { return discoveredModulesInSameFolder.stream().anyMatch(discoveredModuleInSameFolder -> discoveredModuleInSameFolder.getName().equals(existingModule) && discoveredModuleInSameFolder.getType().equals(existingModule.getType())); } /* * check if we have duplicate names in the auto-discovered modules * If we find any we will remove them from the list of discovered modules and will create a MalformedFile instance * to surface the error to the UI */ private void checkForDuplicateModuleNames(GitInfo branch, Multimap<String, Module> modulesByPath, Set<MalformedFile> malformedFiles) { List<Module> modules = ImmutableList.copyOf(modulesByPath.values()); List<Module> duplicateModules = getDuplicateModules(modules); if (!duplicateModules.isEmpty()) { duplicateModules.forEach(module -> modulesByPath.remove(module.getFolder(), module)); MalformedFile duplicateModulesMalFormedFile = getDuplicateModulesMalformedFile(branch, duplicateModules); malformedFiles.add(duplicateModulesMalFormedFile); } } /* * use the discovery plugins to discover modules */ private void applyModuleDiscoveryPlugins(GitInfo branch, Optional<CommitInfo> commitInfo, Multimap<String, Module> discoveredModulesByFolder, Set<MalformedFile> malformedFiles) throws IOException { // if the dependency source is missing in existing dependency entries we rediscover the modules so the entries will // be updated with the source info boolean dependencySourceIsMissingInBranchModules = dependenciesService.getCountOfDependenciesWithoutSourceByBranchId(branch.getId().get()) > 0 || dependenciesService.getCountOfProvidedDependenciesWithoutSourceByBranchId(branch.getId().get()) > 0; String fullBranchName = String.format("%s/%s",branch.getFullRepositoryName(), branch.getBranch()); LOG.debug("Dependency source is missing for modules in branch {}: {}", fullBranchName, dependencySourceIsMissingInBranchModules); LOG.debug("Commit info was provided for module discovery in branch {}: {}", fullBranchName, commitInfo.isPresent()); LOG.debug("Commit info for branch {} is truncated: {}", fullBranchName, commitInfo.isPresent() && commitInfo.get().isTruncated()); boolean rediscoverAllModules = !commitInfo.isPresent() || commitInfo.get().isTruncated() || dependencySourceIsMissingInBranchModules; LOG.debug("Modules for branch {} will be rediscovered: {}", fullBranchName, rediscoverAllModules); Set<ModuleDiscovery> moduleDiscoveryPluginsToUse = new HashSet<>(); for (ModuleDiscovery moduleDiscoveryPlugin : moduleDiscoveryPlugins) { if (moduleDiscoveryPlugin.isEnabled(branch) && (rediscoverAllModules || (commitInfo.isPresent() && moduleDiscoveryPlugin.shouldRediscover(branch, commitInfo.get())))) { moduleDiscoveryPluginsToUse.add(moduleDiscoveryPlugin); } } for (ModuleDiscovery moduleDiscoveryPluginToUse : moduleDiscoveryPluginsToUse) { DiscoveryResult result = moduleDiscoveryPluginToUse.discover(branch); malformedFiles.addAll(result.getMalformedFiles()); for (DiscoveredModule module : result.getModules()) { discoveredModulesByFolder.put(module.getFolder(), module); } } } }