/*
* 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.merge;
import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.remote.ConfigOrigin;
import com.thoughtworks.go.domain.BaseCollection;
import com.thoughtworks.go.domain.ConfigErrors;
import com.thoughtworks.go.domain.EnvironmentPipelineMatcher;
import com.thoughtworks.go.util.command.EnvironmentVariableContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static com.thoughtworks.go.util.ExceptionUtils.bomb;
/**
* Composite of many EnvironmentConfig instances. Hides elementary environment configurations.
*/
public class MergeEnvironmentConfig extends BaseCollection<EnvironmentConfig> implements EnvironmentConfig {
private final ConfigErrors configErrors = new ConfigErrors();
public static final String CONSISTENT_KV = "ConsistentEnvVariables";
public MergeEnvironmentConfig(EnvironmentConfig... configs) {
CaseInsensitiveString name = configs[0].name();
for (EnvironmentConfig part : configs) {
if (!part.name().equals(name))
throw new IllegalArgumentException(
"partial environment configs must all have the same name");
this.add(part);
}
}
public MergeEnvironmentConfig(List<EnvironmentConfig> configs) {
CaseInsensitiveString name = configs.get(0).name();
for (EnvironmentConfig part : configs) {
if (!part.name().equals(name))
throw new IllegalArgumentException(
"partial environment configs must all have the same name");
this.add(part);
}
}
public EnvironmentConfig getFirstEditablePartOrNull() {
for (EnvironmentConfig part : this) {
if (isEditable(part))
return part;
}
return null;
}
private boolean isEditable(EnvironmentConfig part) {
return part.getOrigin() == null || part.getOrigin().canEdit();
}
public EnvironmentConfig getFirstEditablePart() {
EnvironmentConfig found = getFirstEditablePartOrNull();
if (found == null)
throw bomb("No editable configuration part");
return found;
}
@Override
public void validate(ValidationContext validationContext) {
validateDuplicateEnvironmentVariables(validationContext);
validateDuplicatePipelines(validationContext);
validateDuplicateAgents(validationContext);
}
private void validateDuplicateAgents(ValidationContext validationContext) {
ArrayList<String> allAgents = new ArrayList<>();
for (EnvironmentConfig part : this) {
for (EnvironmentAgentConfig agent : part.getAgents()) {
if (allAgents.contains(agent.getUuid())) {
String message = String.format("Environment agent '%s' is defined more than once.", agent.getUuid());
configErrors.add("agent", message);
} else {
allAgents.add(agent.getUuid());
}
}
}
}
private void validateDuplicateEnvironmentVariables(ValidationContext validationContext) {
EnvironmentVariablesConfig allVariables = new EnvironmentVariablesConfig();
for (EnvironmentConfig part : this) {
for (EnvironmentVariableConfig partVariable : part.getVariables()) {
if (!allVariables.hasVariable(partVariable.getName())) {
allVariables.add(partVariable);
} else {
//then it must be equal
if (!allVariables.contains(partVariable))
configErrors.add(CONSISTENT_KV, String.format(
"Environment variable '%s' is defined more than once with different values",
partVariable.getName()));
}
}
}
}
private void validateDuplicatePipelines(ValidationContext validationContext) {
ArrayList<CaseInsensitiveString> allPipelines = new ArrayList<>();
for (EnvironmentConfig part : this) {
for (CaseInsensitiveString pipelineName : part.getPipelineNames()) {
if (allPipelines.contains(pipelineName)) {
String message = String.format("Environment pipeline '%s' is defined more than once.", pipelineName);
configErrors.add("pipeline", message);
} else {
allPipelines.add(pipelineName);
}
}
}
}
@Override
public ConfigErrors errors() {
return configErrors;
}
@Override
public void addError(String fieldName, String message) {
configErrors.add(fieldName, message);
}
@Override
public EnvironmentPipelineMatcher createMatcher() {
return new EnvironmentPipelineMatcher(this.name(), this.getAgents().getUuids(), this.getPipelines());
}
@Override
public boolean hasAgent(String uuid) {
for (EnvironmentConfig part : this) {
if (part.hasAgent(uuid))
return true;
}
return false;
}
@Override
public boolean validateContainsOnlyUuids(Set<String> uuids) {
boolean isValid = true;
for (EnvironmentAgentConfig agent : this.getAgents()) {
isValid = agent.validateUuidPresent(this.name(), uuids) && isValid;
}
return isValid;
}
@Override
public void validateContainsOnlyPipelines(List<CaseInsensitiveString> pipelineNames) {
this.getPipelines().validateContainsOnlyPipelines(this.name(), pipelineNames);
}
@Override
public boolean containsPipeline(CaseInsensitiveString pipelineName) {
for (EnvironmentConfig part : this) {
if (part.containsPipeline(pipelineName))
return true;
}
return false;
}
@Override
public void setConfigAttributes(Object attributes) {
if (attributes == null) {
return;
}
this.getFirstEditablePart().setConfigAttributes(attributes);
}
@Override
public void addEnvironmentVariable(String name, String value) {
this.getFirstEditablePart().addEnvironmentVariable(name, value);
}
@Override
public void addEnvironmentVariable(EnvironmentVariableConfig variableConfig) {
this.getFirstEditablePart().addEnvironmentVariable(variableConfig);
}
@Override
public void addAgent(String uuid) {
this.getFirstEditablePart().addAgent(uuid);
}
@Override
public void addAgentIfNew(String uuid) {
for (EnvironmentConfig part : this) {
if (part.hasAgent(uuid)) {
return;
}
}
this.getFirstEditablePart().addAgentIfNew(uuid);
}
@Override
public void addPipeline(CaseInsensitiveString pipelineName) {
this.getFirstEditablePart().addPipeline(pipelineName);
}
@Override
public void removePipeline(CaseInsensitiveString pipelineName) {
this.getFirstEditablePart().removePipeline(pipelineName);
}
@Override
public void removeAgent(String uuid) {
for (EnvironmentConfig part : this) {
if (part.hasAgent(uuid)) {
if (isEditable(part))
part.removeAgent(uuid);
else
throw bomb("cannot remove agent defined in non-editable source");
}
}
}
@Override
public boolean hasName(CaseInsensitiveString environmentName) {
return this.name().equals(environmentName);
}
@Override
public boolean hasVariable(String variableName) {
for (EnvironmentConfig part : this) {
if (part.hasVariable(variableName))
return true;
}
return false;
}
@Override
public boolean hasSamePipelinesAs(EnvironmentConfig other) {
for (CaseInsensitiveString pipeline : getPipelineNames()) {
for (CaseInsensitiveString name : other.getPipelineNames()) {
if (name.equals(pipeline))
return true;
}
}
return false;
}
@Override
public boolean contains(String pipelineName) {
for (EnvironmentConfig part : this) {
if (part.contains(pipelineName))
return true;
}
return false;
}
@Override
public CaseInsensitiveString name() {
return this.first().name();
}
@Override
public EnvironmentAgentsConfig getAgents() {
EnvironmentAgentsConfig allAgents = new EnvironmentAgentsConfig();
for (EnvironmentConfig part : this) {
for (EnvironmentAgentConfig partAgent : part.getAgents()) {
if (!allAgents.contains(partAgent))
allAgents.add(partAgent);
}
}
return allAgents;
}
@Override
public EnvironmentVariableContext createEnvironmentContext() {
EnvironmentVariableContext context = new EnvironmentVariableContext(
EnvironmentVariableContext.GO_ENVIRONMENT_NAME, CaseInsensitiveString.str(this.name()));
this.getVariables().addTo(context);
return context;
}
@Override
public List<CaseInsensitiveString> getPipelineNames() {
List<CaseInsensitiveString> allNames = new ArrayList<>();
for (EnvironmentConfig part : this) {
for (CaseInsensitiveString pipe : part.getPipelineNames()) {
if (!allNames.contains(pipe))
allNames.add(pipe);
}
}
return allNames;
}
@Override
public EnvironmentPipelinesConfig getPipelines() {
EnvironmentPipelinesConfig allPipelines = new EnvironmentPipelinesConfig();
for (EnvironmentConfig part : this) {
EnvironmentPipelinesConfig partPipes = part.getPipelines();
for (EnvironmentPipelineConfig partPipe : partPipes) {
if (!allPipelines.containsPipelineNamed(partPipe.getName()))
allPipelines.add(partPipe);
}
}
return allPipelines;
}
@Override
public EnvironmentVariablesConfig getVariables() {
EnvironmentVariablesConfig allVariables = new EnvironmentVariablesConfig();
for (EnvironmentConfig part : this) {
for (EnvironmentVariableConfig partVariable : part.getVariables()) {
if (!allVariables.contains(partVariable))
allVariables.add(partVariable);
}
}
return allVariables;
}
@Override
public EnvironmentVariablesConfig getPlainTextVariables() {
EnvironmentVariablesConfig allVariables = new EnvironmentVariablesConfig();
for (EnvironmentConfig part : this) {
for (EnvironmentVariableConfig partVariable : part.getPlainTextVariables()) {
if (!allVariables.contains(partVariable))
allVariables.add(partVariable);
}
}
return allVariables;
}
@Override
public EnvironmentVariablesConfig getSecureVariables() {
EnvironmentVariablesConfig allVariables = new EnvironmentVariablesConfig();
for (EnvironmentConfig part : this) {
for (EnvironmentVariableConfig partVariable : part.getSecureVariables()) {
if (!allVariables.contains(partVariable))
allVariables.add(partVariable);
}
}
return allVariables;
}
@Override
public EnvironmentConfig getLocal() {
for (EnvironmentConfig part : this) {
if (part.isLocal())
return part;
}
return null;
}
@Override
public boolean isLocal() {
for (EnvironmentConfig part : this) {
if (!part.isLocal())
return false;
}
return true;
}
@Override
public boolean isEnvironmentEmpty() {
for (EnvironmentConfig part : this) {
if (!part.isEnvironmentEmpty())
return false;
}
return true;
}
@Override
public EnvironmentPipelinesConfig getRemotePipelines() {
EnvironmentPipelinesConfig remotes = new EnvironmentPipelinesConfig();
for (EnvironmentConfig part : this) {
remotes.addAll(part.getRemotePipelines());
}
return remotes;
}
@Override
public EnvironmentAgentsConfig getLocalAgents() {
EnvironmentAgentsConfig locals = new EnvironmentAgentsConfig();
for (EnvironmentConfig part : this) {
locals.addAll(part.getLocalAgents());
}
return locals;
}
@Override
public boolean containsPipelineRemotely(CaseInsensitiveString pipelineName) {
for (EnvironmentConfig part : this) {
if (part.containsPipelineRemotely(pipelineName))
return true;
}
return false;
}
@Override
public boolean containsAgentRemotely(String uuid) {
for (EnvironmentConfig part : this) {
if (part.containsAgentRemotely(uuid)) {
return true;
}
}
return false;
}
@Override
public boolean containsEnvironmentVariableRemotely(String variableName) {
for (EnvironmentConfig part : this) {
if (part.containsEnvironmentVariableRemotely(variableName)) {
return true;
}
}
return false;
}
public ConfigOrigin getOriginForPipeline(CaseInsensitiveString pipelineName) {
for (EnvironmentConfig part : this) {
if (part.containsPipeline(pipelineName)) {
return part.getOrigin();
}
}
return null;
}
public ConfigOrigin getOriginForAgent(String agentUUID) {
for (EnvironmentConfig part : this) {
if (part.hasAgent(agentUUID)) {
return part.getOrigin();
}
}
return null;
}
public ConfigOrigin getOriginForEnvironmentVariable(String variableName) {
for (EnvironmentConfig part : this) {
if (part.getVariables().hasVariable(variableName)) {
return part.getOrigin();
}
}
return null;
}
@Override
public boolean validateTree(ConfigSaveValidationContext validationContext, CruiseConfig preprocessedConfig) {
validate(validationContext);
boolean isValid = ErrorCollector.getAllErrors(this).isEmpty();
for (EnvironmentConfig part : this) {
isValid = isValid && part.validateTree(validationContext, preprocessedConfig);
}
return isValid;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
EnvironmentConfig that = as(EnvironmentConfig.class, o);
if (that == null)
return false;
if (this.getAgents() != null ? !this.getAgents().equals(that.getAgents()) : that.getAgents() != null) {
return false;
}
if (this.name() != null ? !this.name().equals(that.name()) : that.name() != null) {
return false;
}
if (this.getPipelines() != null ? !this.getPipelines().equals(that.getPipelines()) : that.getPipelines() != null) {
return false;
}
if (this.getVariables() != null ? !this.getVariables().equals(that.getVariables()) : that.getVariables() != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = (this.name() != null ? this.name().hashCode() : 0);
result = 31 * result + (this.getAgents() != null ? this.getAgents().hashCode() : 0);
result = 31 * result + (this.getPipelines() != null ? this.getPipelines().hashCode() : 0);
result = 31 * result + (this.getVariables() != null ? this.getVariables().hashCode() : 0);
return result;
}
private static <T> T as(Class<T> clazz, Object o) {
if (clazz.isInstance(o)) {
return clazz.cast(o);
}
return null;
}
@Override
public ConfigOrigin getOrigin() {
MergeConfigOrigin mergeConfigOrigin = new MergeConfigOrigin();
for (EnvironmentConfig part : this) {
mergeConfigOrigin.add(part.getOrigin());
}
return mergeConfigOrigin;
}
@Override
public void setOrigins(ConfigOrigin origins) {
throw bomb("Cannot set origins on merged config");
}
}