/* * 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.config; import com.rits.cloning.Cloner; import com.thoughtworks.go.config.materials.dependency.DependencyMaterialConfig; import com.thoughtworks.go.domain.ConfigErrors; import com.thoughtworks.go.domain.Task; import com.thoughtworks.go.domain.materials.MaterialConfig; import com.thoughtworks.go.util.*; import java.util.ArrayList; import java.util.List; public class PipelineConfigTreeValidator { private final PipelineConfig pipelineConfig; public PipelineConfigTreeValidator(PipelineConfig pipelineConfig) { this.pipelineConfig = pipelineConfig; } public boolean validate(PipelineConfigSaveValidationContext validationContext) { pipelineConfig.validate(validationContext); pipelineCreationSpecificValidations(validationContext); validateDependencies(validationContext); boolean isValid = pipelineConfig.errors().isEmpty(); PipelineConfigSaveValidationContext contextForChildren = validationContext.withParent(pipelineConfig); for (StageConfig stageConfig : pipelineConfig.getStages()) { isValid = stageConfig.validateTree(contextForChildren) && isValid; if (pipelineConfig.hasTemplateApplied()) { final List<ConfigErrors> allErrors = new ArrayList<>(); new GoConfigGraphWalker(stageConfig).walk(new ErrorCollectingHandler(allErrors) { @Override public void handleValidation(Validatable validatable, ValidationContext context) { } }); for (ConfigErrors error : allErrors) { pipelineConfig.errors().add("template", ListUtil.join(error.getAll())); } } } validateCyclicDependencies(validationContext); isValid = pipelineConfig.materialConfigs().validateTree(contextForChildren) && isValid; isValid = pipelineConfig.getParams().validateTree(contextForChildren) && isValid; isValid = pipelineConfig.getVariables().validateTree(contextForChildren) && isValid; if (pipelineConfig.getTrackingTool() != null) isValid = pipelineConfig.getTrackingTool().validateTree(contextForChildren) && isValid; if (pipelineConfig.getMingleConfig() != null) isValid = pipelineConfig.getMingleConfig().validateTree(contextForChildren) && isValid; if (pipelineConfig.getTimer() != null) isValid = pipelineConfig.getTimer().validateTree(contextForChildren) && isValid; return isValid; } private void pipelineCreationSpecificValidations(PipelineConfigSaveValidationContext validationContext) { if (validationContext.isPipelineBeingCreated()) { validationContext.getGroups().validatePipelineNameUniqueness(); PipelineConfigs group = validationContext.getPipelineGroup(); group.validateGroupNameAndAddErrorsTo(pipelineConfig.errors()); } } private void validateCyclicDependencies(PipelineConfigSaveValidationContext validationContext) { final DFSCycleDetector dfsCycleDetector = new DFSCycleDetector(); try { dfsCycleDetector.topoSort(pipelineConfig.name(), new PipelineConfigValidationContextDependencyState(pipelineConfig, validationContext)); } catch (Exception e) { pipelineConfig.materialConfigs().addError("base", e.getMessage()); } } void validateDependencies(PipelineConfigSaveValidationContext validationContext) { if (validationContext.isPipelineBeingCreated()) return; for (CaseInsensitiveString selected : validationContext.getPipelinesWithDependencyMaterials()) { if (selected.equals(pipelineConfig.name())) continue; PipelineConfig selectedPipeline = validationContext.getPipelineConfigByName(selected); validateDependencyMaterialsForDownstreams(validationContext, selected, selectedPipeline); validateFetchTasksForOtherPipelines(validationContext, selectedPipeline); } } private void validateDependencyMaterialsForDownstreams(PipelineConfigSaveValidationContext validationContext, CaseInsensitiveString selected, PipelineConfig downstreamPipeline) { Node dependenciesOfSelectedPipeline = validationContext.getDependencyMaterialsFor(selected); for (Node.DependencyNode dependencyNode : dependenciesOfSelectedPipeline.getDependencies()) { if (dependencyNode.getPipelineName().equals(pipelineConfig.name())) { for (MaterialConfig materialConfig : downstreamPipeline.materialConfigs()) { if (materialConfig instanceof DependencyMaterialConfig) { DependencyMaterialConfig dependencyMaterialConfig = new Cloner().deepClone((DependencyMaterialConfig) materialConfig); dependencyMaterialConfig.validate(validationContext.withParent(downstreamPipeline)); List<String> allErrors = dependencyMaterialConfig.errors().getAll(); for (String error : allErrors) { pipelineConfig.errors().add("base", error); } } } } } } private void validateFetchTasksForOtherPipelines(PipelineConfigSaveValidationContext validationContext, PipelineConfig downstreamPipeline) { for (StageConfig stageConfig : downstreamPipeline.getStages()) { for (JobConfig jobConfig : stageConfig.getJobs()) { for (Task task : jobConfig.getTasks()) { if (task instanceof FetchTask) { FetchTask fetchTask = (FetchTask) task; if (fetchTask.getPipelineNamePathFromAncestor() != null && !StringUtil.isBlank(CaseInsensitiveString.str(fetchTask.getPipelineNamePathFromAncestor().getPath())) && fetchTask.getPipelineNamePathFromAncestor().pathIncludingAncestor().contains(pipelineConfig.name())) { fetchTask = new Cloner().deepClone(fetchTask); fetchTask.validateTask(validationContext.withParent(downstreamPipeline).withParent(stageConfig).withParent(jobConfig)); List<String> allErrors = fetchTask.errors().getAll(); for (String error : allErrors) { pipelineConfig.errors().add("base", error); } } } } } } } private class PipelineConfigValidationContextDependencyState implements PipelineDependencyState { private PipelineConfig pipelineConfig; private PipelineConfigSaveValidationContext validationContext; public PipelineConfigValidationContextDependencyState(PipelineConfig pipelineConfig, PipelineConfigSaveValidationContext validationContext) { this.pipelineConfig = pipelineConfig; this.validationContext = validationContext; } @Override public boolean hasPipeline(CaseInsensitiveString key) { return validationContext.getPipelineConfigByName(key) != null; } @Override public Node getDependencyMaterials(CaseInsensitiveString pipelineName) { if (pipelineConfig.name().equals(pipelineName)) return pipelineConfig.getDependenciesAsNode(); return validationContext.getDependencyMaterialsFor(pipelineName); } } }