/*
* 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.preprocessor.ClassAttributeCache;
import com.thoughtworks.go.config.preprocessor.ParamReferenceCollectorFactory;
import com.thoughtworks.go.config.preprocessor.ParamResolver;
import com.thoughtworks.go.config.preprocessor.SkipParameterResolution;
import com.thoughtworks.go.config.validation.NameTypeValidator;
import com.thoughtworks.go.domain.BaseCollection;
import com.thoughtworks.go.domain.ConfigErrors;
import com.thoughtworks.go.domain.Task;
import com.thoughtworks.go.domain.config.Admin;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.thoughtworks.go.config.Authorization.ALLOW_GROUP_ADMINS;
/**
* @understands abstracting a pipeline definition
*/
@ConfigTag("pipeline")
@ConfigCollection(value = StageConfig.class)
public class PipelineTemplateConfig extends BaseCollection<StageConfig> implements Validatable, ParamsAttributeAware {
private static final ClassAttributeCache.FieldCache FIELD_CACHE = new ClassAttributeCache.FieldCache();
private static final Cloner CLONER = new Cloner();
public static final String NAME = "name";
@ConfigAttribute(optional = false, value = "name")
private CaseInsensitiveString name;
public static final String AUTHORIZATION = "authorization";
@ConfigSubtag @SkipParameterResolution
private Authorization authorization = new Authorization();
private final ConfigErrors configErrors = new ConfigErrors();
public PipelineTemplateConfig() {
}
public PipelineTemplateConfig(CaseInsensitiveString name, StageConfig... items) {
super(items);
this.name = name;
}
public PipelineTemplateConfig(CaseInsensitiveString name, Authorization authorization, StageConfig... items) {
this(name, items);
this.authorization = authorization;
}
public CaseInsensitiveString name() {
return name;
}
public void validateTree(ValidationContext validationContext, CruiseConfig preprocessedConfig, boolean isTemplateBeingCreated) {
validate(validationContext);
if (!isTemplateBeingCreated) {
validateDependencies(preprocessedConfig);
}
}
private void validateDependencies(CruiseConfig preprocessedConfig) {
List<CaseInsensitiveString> pipelineNames = preprocessedConfig.pipelinesAssociatedWithTemplate(this.name());
ParamsConfig paramsConfig = this.referredParams();
for (CaseInsensitiveString pipelineName : pipelineNames) {
PipelineConfig pipelineConfig = preprocessedConfig.getPipelineConfigByName(pipelineName);
PipelineConfigSaveValidationContext contextForStages = PipelineConfigSaveValidationContext.forChain(false, "", preprocessedConfig, pipelineConfig);
validateParams(pipelineConfig, paramsConfig);
validateFetchTasksAndElasticProfileId(pipelineConfig, contextForStages);
validateDependenciesOfDownstreams(pipelineConfig, contextForStages);
}
}
private void validateDependenciesOfDownstreams(PipelineConfig pipelineConfig, PipelineConfigSaveValidationContext contextForStages) {
PipelineConfigTreeValidator pipelineConfigTreeValidator = new PipelineConfigTreeValidator(pipelineConfig);
pipelineConfigTreeValidator.validateDependencies(contextForStages);
this.errors().addAll(pipelineConfig.errors());
}
private void validateFetchTasksAndElasticProfileId(PipelineConfig pipelineConfig, PipelineConfigSaveValidationContext contextForStages) {
for (StageConfig stageConfig : pipelineConfig.getStages()) {
PipelineConfigSaveValidationContext contextForJobs = contextForStages.withParent(stageConfig);
for (JobConfig jobConfig : stageConfig.getJobs()) {
PipelineConfigSaveValidationContext contextForTasks = contextForJobs.withParent(jobConfig);
validateFetchTasks(jobConfig, contextForTasks);
validateElasticProfileId(jobConfig, contextForTasks);
}
}
}
private void validateElasticProfileId(JobConfig jobConfig, PipelineConfigSaveValidationContext preprocessedConfig) {
String elasticProfileId = jobConfig.getElasticProfileId();
if(elasticProfileId != null && !preprocessedConfig.isValidProfileId(elasticProfileId)){
String message = String.format("No profile defined corresponding to profile_id '%s'", elasticProfileId);
jobConfig.addError("elasticProfileId", message);
this.errors().addAll(jobConfig.errors());
}
}
private void validateFetchTasks(JobConfig jobConfig, PipelineConfigSaveValidationContext contextForTasks) {
for (Task task : jobConfig.getTasks()) {
if (task instanceof FetchTask) {
task.validate(contextForTasks);
this.errors().addAll(task.errors());
}
}
}
private void validateParams(PipelineConfig pipelineConfig, ParamsConfig paramsConfig) {
for (ParamConfig paramConfig : paramsConfig) {
if (!pipelineConfig.getParams().hasParamNamed(paramConfig.getName())) {
this.addError("params", String.format("The param '%s' is not defined in pipeline '%s'", paramConfig.getName(), pipelineConfig.getName()));
}
}
}
public void validate(ValidationContext validationContext) {
validateTemplateName();
validateStageNameUniqueness();
validateTemplateAuth(new DelegatingValidationContext(validationContext) {
@Override
public boolean shouldNotCheckRole() {
return false;
}
});
validateStageConfig(validationContext);
}
private void validateTemplateAuth(DelegatingValidationContext validationContextWhichChecksForRole) {
for (Admin admin : getAuthorization().getAdminsConfig()) {
admin.validate(validationContextWhichChecksForRole);
}
for (Admin admin : getAuthorization().getViewConfig()) {
admin.validate(validationContextWhichChecksForRole);
}
}
public void validateStageConfig(ValidationContext validationContext) {
ValidationContext contextForChildren = validationContext.withParent(this);
for(StageConfig stageConfig : this) {
stageConfig.validateTree(contextForChildren);
}
}
private void validateStageNameUniqueness() {
Map<String, StageConfig> stageNameMap = new HashMap<>();
for (StageConfig stageConfig : this) {
stageConfig.validateNameUniqueness(stageNameMap);
}
}
private void validateTemplateName() {
if (!new NameTypeValidator().isNameValid(name)) {
errors().add(NAME, NameTypeValidator.errorMessage("template", name));
}
}
public ConfigErrors errors() {
return configErrors;
}
public void addError(String fieldName, String message) {
configErrors.add(fieldName, message);
}
public StageConfig getStage(final CaseInsensitiveString stageName) {
return findBy(stageName);
}
public List<StageConfig> getStages() {
return this;
}
public void setName(String name) {
setName(new CaseInsensitiveString(name));
}
public void setName(CaseInsensitiveString name) {
this.name = name;
}
public StageConfig findBy(final CaseInsensitiveString stageName) {
for (StageConfig stageConfig : this) {
if (stageConfig.name().equals(stageName)) {
return stageConfig;
}
}
return null;
}
public boolean addStageWithoutValidityAssertion(StageConfig stageConfig) {
return super.add(stageConfig);
}
public void incrementIndex(StageConfig stageToBeMoved) {
moveStage(stageToBeMoved, 1);
}
public void decrementIndex(StageConfig stageToBeMoved) {
moveStage(stageToBeMoved, -1);
}
private void moveStage(StageConfig moveMeStage, int moveBy) {
int current = this.indexOf(moveMeStage);
if (current == -1) {
throw new RuntimeException(String.format("Cannot find the stage '%s' in pipeline '%s'", moveMeStage.name(), name()));
}
this.remove(moveMeStage);
this.add(current + moveBy, moveMeStage);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
PipelineTemplateConfig config = (PipelineTemplateConfig) o;
if (name != null ? !name.equals(config.name) : config.name != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
public boolean matches(CaseInsensitiveString templateName) {
return this.name.equals(templateName);
}
public void setConfigAttributes(Object attributes) {
Map attributeMap = (Map) attributes;
if (attributeMap.containsKey(NAME)) {
String strName = (String) attributeMap.get(NAME);
name = new CaseInsensitiveString(strName);
}
if (attributeMap.containsKey(AUTHORIZATION)) {
this.authorization = new Authorization();
this.authorization.setConfigAttributes(attributeMap.get(AUTHORIZATION));
}
else {
this.authorization = new Authorization();
}
if(attributeMap.containsKey(ALLOW_GROUP_ADMINS)) {
this.authorization.setAllowGroupAdmins("true".equals(attributeMap.get(ALLOW_GROUP_ADMINS)));
}
}
public void addDefaultStage() {
add(new StageConfig(new CaseInsensitiveString(StageConfig.DEFAULT_NAME), new JobConfigs(new JobConfig(JobConfig.DEFAULT_NAME))));
}
public boolean isAllowGroupAdmins() {
return this.getAuthorization().isAllowGroupAdmins();
}
public void validateNameUniquness(Map<String, PipelineTemplateConfig> templateMap) {
String currentName = name.toLower();
PipelineTemplateConfig templateWithSameName = templateMap.get(currentName);
if (templateWithSameName == null) {
templateMap.put(currentName, this);
} else {
templateWithSameName.addError(NAME, String.format("Template name '%s' is not unique", templateWithSameName.name()));
this.addError(NAME, String.format("Template name '%s' is not unique", name));
}
}
public ParamsConfig referredParams() {
ParamReferenceCollectorFactory paramHandlerFactory = new ParamReferenceCollectorFactory();
new ParamResolver(paramHandlerFactory, FIELD_CACHE).resolve(CLONER.deepClone(this));
ParamsConfig paramsConfig = new ParamsConfig();
for (String param : paramHandlerFactory.referredParams()) {
paramsConfig.add(new ParamConfig(param, null));
}
return paramsConfig;
}
public void copyStages(PipelineConfig pipeline) {
if (pipeline != null) {
addAll(pipeline);
}
}
public Authorization getAuthorization() {
return authorization;
}
public void setAuthorization(Authorization authorization) {
this.authorization = authorization;
}
public List<ConfigErrors> getAllErrors() {
return ErrorCollector.getAllErrors(this);
}
public void cleanupAllUsagesOfRole(Role roleToDelete) {
for (StageConfig stage : getStages()) {
stage.cleanupAllUsagesOfRole(roleToDelete);
}
}
}