/*
* Copyright 2017 ThoughtWorks, Inc.
*
* 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.thoughtworks.go.server.service;
import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.materials.dependency.DependencyMaterial;
import com.thoughtworks.go.domain.MaterialInstance;
import com.thoughtworks.go.domain.MaterialRevision;
import com.thoughtworks.go.domain.buildcause.BuildCause;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.domain.materials.MaterialConfig;
import com.thoughtworks.go.domain.materials.Modification;
import com.thoughtworks.go.domain.materials.dependency.DependencyMaterialRevision;
import com.thoughtworks.go.domain.valuestreammap.*;
import com.thoughtworks.go.i18n.LocalizedMessage;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.persistence.MaterialRepository;
import com.thoughtworks.go.server.presentation.models.ValueStreamMapPresentationModel;
import com.thoughtworks.go.server.service.result.LocalizedOperationResult;
import com.thoughtworks.go.server.valuestreammap.DownstreamInstancePopulator;
import com.thoughtworks.go.server.valuestreammap.RunStagesPopulator;
import com.thoughtworks.go.server.valuestreammap.UnrunStagesPopulator;
import com.thoughtworks.go.serverhealth.HealthStateScope;
import com.thoughtworks.go.serverhealth.HealthStateType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class ValueStreamMapService {
private final PipelineService pipelineService;
private final MaterialRepository materialRepository;
private final GoConfigService goConfigService;
private final DownstreamInstancePopulator downstreamInstancePopulator;
private final RunStagesPopulator runStagesPopulator;
private final UnrunStagesPopulator unrunStagePopulator;
private SecurityService securityService;
private static final org.apache.log4j.Logger LOGGER = org.apache.log4j.Logger.getLogger(ValueStreamMapService.class);
@Autowired
public ValueStreamMapService(PipelineService pipelineService, MaterialRepository materialRepository, GoConfigService goConfigService, DownstreamInstancePopulator downstreamInstancePopulator,
RunStagesPopulator runStagesPopulator, UnrunStagesPopulator unrunStagePopulator, SecurityService securityService) {
this.pipelineService = pipelineService;
this.materialRepository = materialRepository;
this.goConfigService = goConfigService;
this.downstreamInstancePopulator = downstreamInstancePopulator;
this.runStagesPopulator = runStagesPopulator;
this.unrunStagePopulator = unrunStagePopulator;
this.securityService = securityService;
}
public ValueStreamMapPresentationModel getValueStreamMap(String pipelineName, int counter, Username username, LocalizedOperationResult result) {
try {
if (!securityService.hasViewPermissionForPipeline(username, pipelineName)) {
result.unauthorized(LocalizedMessage.cannotViewPipeline(pipelineName), HealthStateType.general(HealthStateScope.forPipeline(pipelineName)));
return null;
}
ValueStreamMap valueStreamMap = buildValueStreamMap(pipelineName,counter, username, result);
if (valueStreamMap == null) {
return null;
}
return valueStreamMap.presentationModel();
} catch (Exception e) {
result.internalServerError(LocalizedMessage.string("VSM_INTERNAL_SERVER_ERROR", pipelineName, counter));
LOGGER.error(String.format("[Value Stream Map] Pipeline %s with counter %s could not be rendered.", pipelineName, counter), e);
return null;
}
}
private ValueStreamMap buildValueStreamMap(String pipelineName, int counter, Username username, LocalizedOperationResult result) {
CruiseConfig cruiseConfig = goConfigService.currentCruiseConfig();
BuildCause buildCauseForPipeline;
try {
pipelineName = pipelineNameWithSameCaseAsConfig(pipelineName, cruiseConfig);
buildCauseForPipeline = pipelineService.buildCauseFor(pipelineName, counter);
} catch (PipelineNotFoundException e) {
result.notFound(LocalizedMessage.string("PIPELINE_WITH_COUNTER_NOT_FOUND", pipelineName, counter), HealthStateType.general(HealthStateScope.forPipeline(pipelineName)));
return null;
}
String label = pipelineService.findPipelineByCounterOrLabel(pipelineName, String.valueOf(counter)).getLabel();
ValueStreamMap valueStreamMap = new ValueStreamMap(pipelineName, new PipelineRevision(pipelineName, counter, label));
Map<String, List<PipelineConfig>> pipelineToDownstreamMap = cruiseConfig.generatePipelineVsDownstreamMap();
traverseDownstream(pipelineName, pipelineToDownstreamMap, valueStreamMap, new ArrayList<>());
traverseUpstream(pipelineName, buildCauseForPipeline, valueStreamMap, new ArrayList<>());
if (valueStreamMap.hasCycle()) {
result.notImplemented(LocalizedMessage.string("VSM_CYCLIC_DEPENDENCY",pipelineName,counter));
LOGGER.error(String.format("[Value Stream Map] Cyclic dependency for pipeline %s with counter %s. Graph is %s", pipelineName, counter, valueStreamMap));
return null;
}
addInstanceInformationToTheGraph(valueStreamMap);
removeRevisionsBasedOnPermissionAndCurrentConfig(valueStreamMap, username);
valueStreamMap.addWarningIfBuiltFromInCompatibleRevisions();
return valueStreamMap;
}
public ValueStreamMapPresentationModel getValueStreamMap(String materialFingerprint, String revision, Username username, LocalizedOperationResult result) {
try {
MaterialConfig materialConfig = null;
boolean hasViewPermissionForMaterial = false;
List<PipelineConfig> downstreamPipelines = new ArrayList<>();
for (PipelineConfigs pipelineGroup : goConfigService.groups()) {
boolean hasViewPermissionForGroup = securityService.hasViewPermissionForGroup(CaseInsensitiveString.str(username.getUsername()), pipelineGroup.getGroup());
for (PipelineConfig pipelineConfig : pipelineGroup) {
for (MaterialConfig currentMaterialConfig : pipelineConfig.materialConfigs()) {
if (currentMaterialConfig.getFingerprint().equals(materialFingerprint)) {
materialConfig = currentMaterialConfig;
if (hasViewPermissionForGroup) {
hasViewPermissionForMaterial = true;
}
downstreamPipelines.add(pipelineConfig);
}
}
}
}
if (materialConfig == null) {
result.notFound(LocalizedMessage.string("MATERIAL_CONFIG_WITH_FINGERPRINT_NOT_FOUND", materialFingerprint), HealthStateType.general(HealthStateScope.GLOBAL));
return null;
}
if (!hasViewPermissionForMaterial) {
result.unauthorized(LocalizedMessage.cannotViewMaterial(materialFingerprint), HealthStateType.general(HealthStateScope.forMaterialConfig(materialConfig)));
return null;
}
MaterialInstance materialInstance = materialRepository.findMaterialInstance(materialConfig);
if (materialInstance == null) {
result.notFound(LocalizedMessage.string("MATERIAL_INSTANCE_WITH_FINGERPRINT_NOT_FOUND", materialFingerprint), HealthStateType.general(HealthStateScope.forMaterialConfig(materialConfig)));
return null;
}
Material material = new MaterialConfigConverter().toMaterial(materialConfig);
Modification modification = materialRepository.findModificationWithRevision(material, revision);
if (modification == null) {
result.notFound(LocalizedMessage.string("MATERIAL_MODIFICATION_NOT_FOUND", materialFingerprint, revision), HealthStateType.general(HealthStateScope.forMaterialConfig(materialConfig)));
return null;
}
ValueStreamMap valueStreamMap = buildValueStreamMap(material, materialInstance, modification, downstreamPipelines, username);
if (valueStreamMap == null) {
return null;
}
return valueStreamMap.presentationModel();
} catch (Exception e) {
result.internalServerError(LocalizedMessage.string("VSM_INTERNAL_SERVER_ERROR_FOR_MATERIAL", materialFingerprint, revision));
LOGGER.error(String.format("[Value Stream Map] Material %s with revision %s could not be rendered.", materialFingerprint, revision), e);
return null;
}
}
private ValueStreamMap buildValueStreamMap(Material material, MaterialInstance materialInstance, Modification modification, List<PipelineConfig> downstreamPipelines, Username username) {
CruiseConfig cruiseConfig = goConfigService.currentCruiseConfig();
ValueStreamMap valueStreamMap = new ValueStreamMap(material, materialInstance, modification);
Map<String, List<PipelineConfig>> pipelineToDownstreamMap = cruiseConfig.generatePipelineVsDownstreamMap();
traverseDownstream(material.getFingerprint(), downstreamPipelines, pipelineToDownstreamMap, valueStreamMap, new ArrayList<>());
addInstanceInformationToTheGraph(valueStreamMap);
removeRevisionsBasedOnPermissionAndCurrentConfig(valueStreamMap, username);
return valueStreamMap;
}
private String pipelineNameWithSameCaseAsConfig(String pipelineName, CruiseConfig cruiseConfig) {
return cruiseConfig.pipelineConfigByName(new CaseInsensitiveString(pipelineName)).name().toString();
}
private void removeRevisionsBasedOnPermissionAndCurrentConfig(ValueStreamMap valueStreamMap, Username username) {
for (Node node : valueStreamMap.allNodes()) {
if (node instanceof PipelineDependencyNode) {
String pipelineName = node.getName();
PipelineDependencyNode pipelineDependencyNode = (PipelineDependencyNode) node;
if (!goConfigService.hasPipelineNamed(new CaseInsensitiveString(pipelineName))) {
pipelineDependencyNode.setDeleted();
} else if (!securityService.hasViewPermissionForPipeline(username, pipelineName)) {
pipelineDependencyNode.setNoPermission();
}
}
}
}
private void traverseUpstream(String pipelineName, BuildCause buildCause, ValueStreamMap graph, List<MaterialRevision> visitedNodes) {
for (MaterialRevision materialRevision : buildCause.getMaterialRevisions()) {
Material material = materialRevision.getMaterial();
if (material instanceof DependencyMaterial) {
String upstreamPipeline = ((DependencyMaterial) material).getPipelineName().toString();
DependencyMaterialRevision revision = (DependencyMaterialRevision) materialRevision.getRevision();
graph.addUpstreamNode(new PipelineDependencyNode(upstreamPipeline, upstreamPipeline), new PipelineRevision(revision.getPipelineName(), revision.getPipelineCounter(), revision.getPipelineLabel()),
pipelineName);
if (visitedNodes.contains(materialRevision)) {
continue;
}
visitedNodes.add(materialRevision);
DependencyMaterialRevision dmrOfUpstreamPipeline = buildCause.getMaterialRevisions().findDependencyMaterialRevision(upstreamPipeline);
BuildCause buildCauseForUpstreamPipeline = pipelineService.buildCauseFor(dmrOfUpstreamPipeline.getPipelineName(), dmrOfUpstreamPipeline.getPipelineCounter());
traverseUpstream(upstreamPipeline, buildCauseForUpstreamPipeline, graph, visitedNodes);
} else {
graph.addUpstreamMaterialNode(new SCMDependencyNode(material.getFingerprint(), material.getUriForDisplay(), materialRevision.getMaterialType()), material.getName(),
pipelineName, materialRevision);
}
}
}
private void traverseDownstream(String upstreamPipelineName, Map<String, List<PipelineConfig>> pipelineToDownstreamMap, ValueStreamMap graph, List<PipelineConfig> visitedNodes) {
List<PipelineConfig> downstreamPipelines = pipelineToDownstreamMap.get(upstreamPipelineName);
traverseDownstream(upstreamPipelineName, downstreamPipelines, pipelineToDownstreamMap, graph, visitedNodes);
}
private void traverseDownstream(String materialId, List<PipelineConfig> downstreamPipelines, Map<String, List<PipelineConfig>> pipelineToDownstreamMap, ValueStreamMap graph, List<PipelineConfig> visitedNodes) {
for (PipelineConfig downstreamPipeline : downstreamPipelines) {
String downstreamPipelineName = downstreamPipeline.name().toString();
graph.addDownstreamNode(new PipelineDependencyNode(downstreamPipelineName, downstreamPipelineName), materialId);
if (visitedNodes.contains(downstreamPipeline)) {
continue;
}
visitedNodes.add(downstreamPipeline);
traverseDownstream(downstreamPipelineName, pipelineToDownstreamMap, graph, visitedNodes);
}
}
private void addInstanceInformationToTheGraph(ValueStreamMap valueStreamMap) {
downstreamInstancePopulator.apply(valueStreamMap);
runStagesPopulator.apply(valueStreamMap);
unrunStagePopulator.apply(valueStreamMap);
}
}