/*
* Copyright 2016 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.update;
import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.commands.EntityConfigUpdateCommand;
import com.thoughtworks.go.config.exceptions.ElasticAgentsResourceUpdateException;
import com.thoughtworks.go.config.exceptions.InvalidPendingAgentOperationException;
import com.thoughtworks.go.config.exceptions.NoSuchAgentException;
import com.thoughtworks.go.config.exceptions.NoSuchEnvironmentException;
import com.thoughtworks.go.domain.AgentInstance;
import com.thoughtworks.go.i18n.LocalizedMessage;
import com.thoughtworks.go.server.domain.AgentInstances;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.service.GoConfigService;
import com.thoughtworks.go.server.service.result.LocalizedOperationResult;
import com.thoughtworks.go.serverhealth.HealthStateType;
import com.thoughtworks.go.util.TriState;
import com.thoughtworks.go.validation.AgentConfigsUpdateValidator;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class AgentsEntityConfigUpdateCommand implements EntityConfigUpdateCommand<Agents> {
private AgentInstances agentInstances;
private final Username username;
private final LocalizedOperationResult result;
private final List<String> uuids;
private final List<String> environmentsToAdd;
private final List<String> environmentsToRemove;
private final TriState state;
private final List<String> resourcesToAdd;
private final List<String> resourcesToRemove;
private GoConfigService goConfigService;
public Agents agents;
public AgentsEntityConfigUpdateCommand(AgentInstances agentInstances, Username username, LocalizedOperationResult result, List<String> uuids, List<String> environmentsToAdd, List<String> environmentsToRemove, TriState state, List<String> resourcesToAdd, List<String> resourcesToRemove, GoConfigService goConfigService) {
this.agentInstances = agentInstances;
this.username = username;
this.result = result;
this.uuids = uuids;
this.environmentsToAdd = environmentsToAdd;
this.environmentsToRemove = environmentsToRemove;
this.state = state;
this.resourcesToAdd = resourcesToAdd;
this.resourcesToRemove = resourcesToRemove;
this.goConfigService = goConfigService;
}
@Override
public boolean canContinue(CruiseConfig cruiseConfig) {
if (!isAuthorized()) {
return false;
}
if (isAnyOperationPerformedOnAgents()) {
return true;
}
result.badRequest(LocalizedMessage.string("NO_OPERATION_PERFORMED_ON_AGENTS"));
return false;
}
@Override
public void update(CruiseConfig modifiedConfig) throws Exception {
Set<CaseInsensitiveString> allEnvironmentNames = new HashSet<>(goConfigService.getEnvironments().names());
// validate all inputs
validatePresenceOfEnvironments(allEnvironmentNames, environmentsToAdd);
validatePresenceOfEnvironments(allEnvironmentNames, environmentsToRemove);
validatePresenceOfAgentUuidsInConfig();
checkIfResourcesAreBeingUpdatedOnElasticAgents();
List<AgentConfig> pendingAgents = findPendingAgents();
validateOperationOnPendingAgents(pendingAgents);
// add pending agents to the config
modifiedConfig.agents().addAll(pendingAgents);
// update all agents specified by uuids
Agents agents = modifiedConfig.agents().filter(uuids);
for (AgentConfig agentConfig : agents) {
if (state.isFalse()) {
agentConfig.disable();
}
if (state.isTrue()) {
agentConfig.enable();
}
for (String r : resourcesToAdd) {
agentConfig.addResource(new Resource(r));
}
for (String r : resourcesToRemove) {
agentConfig.removeResource(new Resource(r));
}
for (String environment : environmentsToAdd) {
EnvironmentConfig environmentConfig = modifiedConfig.getEnvironments().find(new CaseInsensitiveString(environment));
if (environmentConfig != null) {
environmentConfig.addAgentIfNew(agentConfig.getUuid());
}
}
for (String environment : environmentsToRemove) {
EnvironmentConfig environmentConfig = modifiedConfig.getEnvironments().find(new CaseInsensitiveString(environment));
if (environmentConfig != null) {
environmentConfig.removeAgent(agentConfig.getUuid());
}
}
}
}
private void validatePresenceOfAgentUuidsInConfig() throws NoSuchAgentException {
List<String> unknownUUIDs = new ArrayList<>();
for (String uuid : uuids) {
if (agentInstances.findAgent(uuid).isNullAgent()) {
unknownUUIDs.add(uuid);
}
}
if (!unknownUUIDs.isEmpty()) {
result.badRequest(LocalizedMessage.string("RESOURCE_NOT_FOUND", "Agents", unknownUUIDs));
throw new NoSuchAgentException(unknownUUIDs);
}
}
private void validatePresenceOfEnvironments(Set<CaseInsensitiveString> allEnvironmentNames, List<String> environmentsToOperate) throws NoSuchEnvironmentException {
for (String environment : environmentsToOperate) {
CaseInsensitiveString environmentName = new CaseInsensitiveString(environment);
if (!allEnvironmentNames.contains(environmentName)) {
result.badRequest(LocalizedMessage.string("RESOURCE_NOT_FOUND", "Environment", environmentName));
throw new NoSuchEnvironmentException(environmentName);
}
}
}
private boolean isAuthorized() {
if (goConfigService.isAdministrator(username.getUsername())) {
return true;
}
result.unauthorized(LocalizedMessage.string("UNAUTHORIZED_TO_EDIT"), HealthStateType.unauthorised());
return false;
}
private boolean isAnyOperationPerformedOnAgents() {
return !resourcesToAdd.isEmpty() || !resourcesToRemove.isEmpty() || !environmentsToAdd.isEmpty()
|| !environmentsToRemove.isEmpty() || state.isTrue() || state.isFalse();
}
private List<AgentConfig> findPendingAgents() {
List<AgentConfig> pendingAgents = new ArrayList<>();
for (String uuid : uuids) {
AgentInstance agent = agentInstances.findAgent(uuid);
if (agent.isPending()) {
pendingAgents.add(agent.agentConfig().deepClone());
}
}
return pendingAgents;
}
private void validateOperationOnPendingAgents(List<AgentConfig> pendingAgents) throws InvalidPendingAgentOperationException {
if (pendingAgents.isEmpty()) {
return;
}
List<String> pendingAgentUuids = getPendingAgentUuids(pendingAgents);
if (!(state.isTrue() || state.isFalse())) {
result.badRequest(LocalizedMessage.string("PENDING_AGENT_INVALID_OPERATION", pendingAgentUuids));
throw new InvalidPendingAgentOperationException(pendingAgentUuids);
}
}
private List<String> getPendingAgentUuids(List<AgentConfig> pendingAgents) {
List<String> pendingAgentUuids = new ArrayList<>();
for (AgentConfig pendingAgent : pendingAgents) {
pendingAgentUuids.add(pendingAgent.getUuid());
}
return pendingAgentUuids;
}
private void checkIfResourcesAreBeingUpdatedOnElasticAgents() throws ElasticAgentsResourceUpdateException {
if (resourcesToAdd.isEmpty() && resourcesToRemove.isEmpty()) {
return;
}
List<String> elasticAgentUUIDs = findAllElasticAgentUuids(uuids);
if (elasticAgentUUIDs.isEmpty()) {
return;
}
result.badRequest(LocalizedMessage.string("CAN_NOT_UPDATE_RESOURCES_ON_ELASTIC_AGENT", elasticAgentUUIDs));
throw new ElasticAgentsResourceUpdateException(elasticAgentUUIDs);
}
private List<String> findAllElasticAgentUuids(List<String> uuids) {
ArrayList<String> elasticAgentUUIDs = new ArrayList<>();
for (String uuid : uuids) {
if (agentInstances.findAgent(uuid).isElastic()) {
elasticAgentUUIDs.add(uuid);
}
}
return elasticAgentUUIDs;
}
@Override
public boolean isValid(CruiseConfig preprocessedConfig) {
agents = preprocessedConfig.agents();
AgentConfigsUpdateValidator validator = new AgentConfigsUpdateValidator(uuids);
boolean isValid = validator.isValid(preprocessedConfig);
if (!isValid) {
result.unprocessableEntity(LocalizedMessage.string("BULK_AGENT_UPDATE_FAILED", agents.getAllErrors()));
}
return isValid;
}
@Override
public void clearErrors() {
BasicCruiseConfig.clearErrors(agents);
}
@Override
public Agents getPreprocessedEntityConfig() {
return agents;
}
}