EnvironmentComponentEventListener.java
package com.capitalone.dashboard.event;
import com.capitalone.dashboard.model.BaseModel;
import com.capitalone.dashboard.model.BinaryArtifact;
import com.capitalone.dashboard.model.Build;
import com.capitalone.dashboard.model.Collector;
import com.capitalone.dashboard.model.CollectorItem;
import com.capitalone.dashboard.model.CollectorType;
import com.capitalone.dashboard.model.Component;
import com.capitalone.dashboard.model.Dashboard;
import com.capitalone.dashboard.model.EnvironmentComponent;
import com.capitalone.dashboard.model.EnvironmentStage;
import com.capitalone.dashboard.model.Pipeline;
import com.capitalone.dashboard.model.PipelineCommit;
import com.capitalone.dashboard.model.PipelineStage;
import com.capitalone.dashboard.model.SCM;
import com.capitalone.dashboard.repository.BinaryArtifactRepository;
import com.capitalone.dashboard.repository.BuildRepository;
import com.capitalone.dashboard.repository.CollectorItemRepository;
import com.capitalone.dashboard.repository.CollectorRepository;
import com.capitalone.dashboard.repository.CommitRepository;
import com.capitalone.dashboard.repository.ComponentRepository;
import com.capitalone.dashboard.repository.DashboardRepository;
import com.capitalone.dashboard.repository.JobRepository;
import com.capitalone.dashboard.repository.PipelineRepository;
import com.capitalone.dashboard.util.PipelineUtils;
import com.google.common.base.Function;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@org.springframework.stereotype.Component
public class EnvironmentComponentEventListener extends HygieiaMongoEventListener<EnvironmentComponent> {
private static final Logger LOGGER = LoggerFactory.getLogger(EnvironmentComponentEventListener.class);
private final DashboardRepository dashboardRepository;
private final ComponentRepository componentRepository;
private final BinaryArtifactRepository binaryArtifactRepository;
private final BuildRepository buildRepository;
private final JobRepository<?> jobRepository;
private final CommitRepository commitRepository;
@Autowired
public EnvironmentComponentEventListener(DashboardRepository dashboardRepository,
CollectorItemRepository collectorItemRepository,
ComponentRepository componentRepository,
BinaryArtifactRepository binaryArtifactRepository,
PipelineRepository pipelineRepository,
CollectorRepository collectorRepository,
BuildRepository buildRepository,
JobRepository<?> jobRepository, CommitRepository commitRepository) {
super(collectorItemRepository, pipelineRepository, collectorRepository);
this.dashboardRepository = dashboardRepository;
this.componentRepository = componentRepository;
this.binaryArtifactRepository = binaryArtifactRepository;
this.buildRepository = buildRepository;
this.jobRepository = jobRepository;
this.commitRepository = commitRepository;
}
@Override
public void onAfterSave(AfterSaveEvent<EnvironmentComponent> event) {
super.onAfterSave(event);
EnvironmentComponent environmentComponent = event.getSource();
if(!environmentComponent.isDeployed()){
return;
}
processEnvironmentComponent(environmentComponent);
}
/**
* For the environment component, find all team dashboards related to the environment component and add the
* commits to the proper stage
* @param environmentComponent
*/
private void processEnvironmentComponent(EnvironmentComponent environmentComponent) {
List<Dashboard> dashboards = findTeamDashboardsForEnvironmentComponent(environmentComponent);
for (Dashboard dashboard : dashboards) {
Pipeline pipeline = getOrCreatePipeline(dashboard);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Attempting to update pipeline " + pipeline.getId());
}
addCommitsToEnvironmentStage(environmentComponent, pipeline);
pipelineRepository.save(pipeline);
}
}
/**
* Must first start by finding all artifacts that relate to an environment component based on the name, and potentially
* the timestamp of the last time an artifact came through the environment.
*
* Multiple artifacts could have been built but never deployed.
* @param environmentComponent
* @param pipeline
*/
@SuppressWarnings("PMD.NPathComplexity")
private void addCommitsToEnvironmentStage(EnvironmentComponent environmentComponent, Pipeline pipeline){
EnvironmentStage currentStage = getOrCreateEnvironmentStage(pipeline, environmentComponent.getEnvironmentName());
String pseudoEnvName = environmentComponent.getEnvironmentName();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Attempting to find new artifacts to process for environment '" + environmentComponent.getEnvironmentName() + "'");
}
String artifactName = environmentComponent.getComponentName();
String artifactExtension = null;
int dotIdx = artifactName.lastIndexOf('.');
if (dotIdx > 0) {
// If idx is 0 starts with a dot... in which case not an extension
artifactExtension = artifactName.substring(dotIdx + 1);
artifactName = artifactName.substring(0, dotIdx);
}
List<BinaryArtifact> artifacts = new ArrayList<>();
BinaryArtifact oldLastArtifact = currentStage.getLastArtifact();
if(oldLastArtifact != null){
Long lastArtifactTimestamp = oldLastArtifact != null ? oldLastArtifact.getTimestamp() : null;
artifacts.addAll(Lists.newArrayList(binaryArtifactRepository.findByArtifactNameAndArtifactExtensionAndTimestampGreaterThan(artifactName, artifactExtension, lastArtifactTimestamp)));
// Backwards compatibility
if (artifactExtension != null) {
// In the past the extension was saved as part of the artifact name
artifacts.addAll(Lists.newArrayList(binaryArtifactRepository.findByArtifactNameAndArtifactExtensionAndTimestampGreaterThan(environmentComponent.getComponentName(), null, lastArtifactTimestamp)));
}
}
else {
Map<String, Object> attributes = new HashMap<>();
attributes.put(BinaryArtifactRepository.ARTIFACT_NAME, artifactName);
attributes.put(BinaryArtifactRepository.ARTIFACT_EXTENSION, artifactExtension);
artifacts.addAll(Lists.newArrayList(binaryArtifactRepository.findByAttributes(attributes)));
// Backwards compatibility
if (artifactExtension != null) {
// In the past the extension was saved as part of the artifact name
attributes.clear();
attributes.put(BinaryArtifactRepository.ARTIFACT_NAME, environmentComponent.getComponentName());
attributes.put(BinaryArtifactRepository.ARTIFACT_EXTENSION, null);
artifacts.addAll(Lists.newArrayList(binaryArtifactRepository.findByAttributes(attributes)));
}
}
/**
* Sort the artifacts by timestamp and iterate through each artifact, getting their changesets and adding them to the bucket
*/
List<BinaryArtifact> sortedArtifacts = Lists.newArrayList(artifacts);
Collections.sort(sortedArtifacts, BinaryArtifact.TIMESTAMP_COMPARATOR);
for(BinaryArtifact artifact : sortedArtifacts){
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Processing artifact " + artifact.getArtifactGroupId() + ":" + artifact.getArtifactName() + ":" + artifact.getArtifactVersion());
}
Build build = artifact.getBuildInfo();
if (build == null) {
// Attempt to get the build based on the artifact metadata information if possible
build = getBuildByMetadata(artifact);
}
if (build != null) {
for (SCM scm : build.getSourceChangeSet()) {
PipelineCommit commit = new PipelineCommit(scm, environmentComponent.getAsOfDate());
pipeline.addCommit(environmentComponent.getEnvironmentName(), commit);
}
}
PipelineUtils.processPreviousFailedBuilds(build, pipeline);
/**
* If some build events are missed, here is an attempt to move commits to the build stage
* This also takes care of the problem with Jenkins first build change set being empty.
*
* Logic:
* If the build start time is after the scm commit, move the commit to build stage. Match the repo at the very least.
*/
Map<String, PipelineCommit> commitStageCommits = pipeline.getCommitsByEnvironmentName(PipelineStage.COMMIT.getName());
Map<String, PipelineCommit> envStageCommits = pipeline.getCommitsByEnvironmentName(pseudoEnvName);
for (String rev : commitStageCommits.keySet()) {
PipelineCommit commit = commitStageCommits.get(rev);
if ((commit.getScmCommitTimestamp() < build.getStartTime()) && !envStageCommits.containsKey(rev) && PipelineUtils.isMoveCommitToBuild(build, commit, commitRepository)) {
pipeline.addCommit(pseudoEnvName, commit);
}
}
pipelineRepository.save(pipeline);
}
/**
* Update last artifact on the pipeline
*/
if(sortedArtifacts != null && !sortedArtifacts.isEmpty()){
BinaryArtifact lastArtifact = sortedArtifacts.get(sortedArtifacts.size() - 1);
currentStage.setLastArtifact(lastArtifact);
}
}
/**
* Attempts to find the build for the artifact based on the artifacts build metadata information.
*
* @param artifact
* @return
*/
private Build getBuildByMetadata(BinaryArtifact artifact) {
Build build = null;
// Note: in order to work properly both the artifact and the build must exist when this is run
// This shouldn't be a problem as they would exist by the time the component is deployed so
// long as the collector frequency allowed the information to be picked up
String jobName = null;
String buildNumber = null;
String instanceUrl = null;
if (artifact.getMetadata() != null) {
jobName = artifact.getJobName();
buildNumber = artifact.getBuildNumber();
instanceUrl = artifact.getInstanceUrl();
}
if (jobName != null && buildNumber != null && instanceUrl != null) {
List<Collector> buildCollectors = collectorRepository.findByCollectorType(CollectorType.Build);
List<ObjectId> collectorIds = Lists.newArrayList(Iterables.transform(buildCollectors, new ToCollectorId()));
// Just in case more build collectors are added in the future that run together...
for (ObjectId buildCollectorId : collectorIds) {
CollectorItem jobCollectorItem = jobRepository.findJob(buildCollectorId, instanceUrl, jobName);
if (jobCollectorItem == null) {
continue;
}
build = buildRepository.findByCollectorItemIdAndNumber(jobCollectorItem.getId(), buildNumber);
if (build != null) {
break;
}
}
} else {
LOGGER.debug("Artifact " + artifact.getId() + " is missing build information.");
}
if (build == null) {
LOGGER.debug("Artifact " + artifact.getId() + " references build " + buildNumber + " in '" + instanceUrl + "' but no build with that information was found.");
}
return build;
}
/**
* Finds team dashboards for a given environment componentby way of the deploy collector item
* @param environmentComponent
* @return
*/
private List<Dashboard> findTeamDashboardsForEnvironmentComponent(EnvironmentComponent environmentComponent){
CollectorItem deploymentCollectorItem = collectorItemRepository.findOne(environmentComponent.getCollectorItemId());
List<Component> components = componentRepository.findByDeployCollectorItemId(deploymentCollectorItem.getId());
List<ObjectId> componentIds = components.stream().map(BaseModel::getId).collect(Collectors.toList());
return dashboardRepository.findByApplicationComponentIdsIn(componentIds);
}
private static class ToCollectorId implements Function<Collector, ObjectId> {
@Override
public ObjectId apply(Collector input) {
return input.getId();
}
}
}