/*
* 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.server.service;
import com.rits.cloning.Cloner;
import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.commands.EntityConfigUpdateCommand;
import com.thoughtworks.go.config.exceptions.GoConfigInvalidException;
import com.thoughtworks.go.config.exceptions.NoSuchEnvironmentException;
import com.thoughtworks.go.config.update.AddEnvironmentCommand;
import com.thoughtworks.go.config.update.DeleteEnvironmentCommand;
import com.thoughtworks.go.config.update.PatchEnvironmentCommand;
import com.thoughtworks.go.config.update.UpdateEnvironmentCommand;
import com.thoughtworks.go.domain.*;
import com.thoughtworks.go.i18n.Localizable;
import com.thoughtworks.go.i18n.LocalizedMessage;
import com.thoughtworks.go.listener.ConfigChangedListener;
import com.thoughtworks.go.listener.EntityConfigChangedListener;
import com.thoughtworks.go.presentation.TriStateSelection;
import com.thoughtworks.go.presentation.environment.EnvironmentPipelineModel;
import com.thoughtworks.go.remote.work.BuildAssignment;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.service.result.HttpLocalizedOperationResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* @understands grouping of agents and pipelines within an environment
*/
@Service
public class EnvironmentConfigService implements ConfigChangedListener {
public final GoConfigService goConfigService;
private final SecurityService securityService;
private EntityHashingService entityHashingService;
private EnvironmentsConfig environments;
private EnvironmentPipelineMatchers matchers;
private static final Cloner cloner = new Cloner();
@Autowired
public EnvironmentConfigService(GoConfigService goConfigService, SecurityService securityService, EntityHashingService entityHashingService) {
this.goConfigService = goConfigService;
this.securityService = securityService;
this.entityHashingService = entityHashingService;
}
public void initialize() {
goConfigService.register(this);
goConfigService.register(new EntityConfigChangedListener<Agents>() {
@Override
public void onEntityConfigChange(Agents entity) {
sync(goConfigService.getEnvironments());
}
});
goConfigService.register(new EntityConfigChangedListener<EnvironmentConfig>() {
@Override
public void onEntityConfigChange(EnvironmentConfig entity) {
sync(goConfigService.getEnvironments());
}
});
}
public void sync(EnvironmentsConfig environments) {
matchers = environments.matchers();
this.environments = environments;
}
public void onConfigChange(CruiseConfig newCruiseConfig) {
sync(newCruiseConfig.getEnvironments());
}
public List<JobPlan> filterJobsByAgent(List<JobPlan> jobPlans, String agentUuid) {
ArrayList<JobPlan> plans = new ArrayList<>();
for (JobPlan jobPlan : jobPlans) {
if (matchers.match(jobPlan.getPipelineName(), agentUuid)) {
plans.add(jobPlan);
}
}
return plans;
}
public String envForPipeline(String pipelineName) {
for (EnvironmentPipelineMatcher matcher : matchers) {
if (matcher.hasPipeline(pipelineName)) {
return CaseInsensitiveString.str(matcher.name());
}
}
return null;
}
public void enhanceEnvironmentVariables(BuildAssignment assignment) {
EnvironmentConfig environment = environments.findEnvironmentForPipeline(new CaseInsensitiveString(assignment.getPlan().getPipelineName()));
if (environment != null) {
assignment.enhanceEnvironmentVariables(environment.createEnvironmentContext());
}
}
public Agents agentsForPipeline(final CaseInsensitiveString pipelineName) {
Agents configs = new Agents();
if (environments.isPipelineAssociatedWithAnyEnvironment(pipelineName)) {
EnvironmentConfig forPipeline = environments.findEnvironmentForPipeline(pipelineName);
for (EnvironmentAgentConfig environmentAgentConfig : forPipeline.getAgents()) {
configs.add(goConfigService.agentByUuid(environmentAgentConfig.getUuid()));
}
} else {
for (AgentConfig agentConfig : goConfigService.agents()) {
if (!environments.isAgentUnderEnvironment(agentConfig.getUuid())) {
configs.add(agentConfig);
}
}
}
return configs;
}
public List<CaseInsensitiveString> pipelinesFor(final CaseInsensitiveString environmentName) throws NoSuchEnvironmentException {
return environments.named(environmentName).getPipelineNames();
}
public List<CaseInsensitiveString> environmentNames() {
return environments.names();
}
public Set<EnvironmentConfig> getEnvironments() {
Set<EnvironmentConfig> environmentConfigs = new HashSet<>();
environmentConfigs.addAll(environments);
return environmentConfigs;
}
public Set<String> environmentsFor(String uuid) {
return environments.environmentsForAgent(uuid);
}
public void modifyEnvironments(List<AgentInstance> agents, List<TriStateSelection> selections) {
goConfigService.modifyEnvironments(agents, selections);
}
public EnvironmentConfig named(String environmentName) throws NoSuchEnvironmentException {
return environments.named(new CaseInsensitiveString(environmentName));
}
public EnvironmentConfig getEnvironmentConfig(String environmentName) throws NoSuchEnvironmentException {
return environments.named(new CaseInsensitiveString(environmentName));
}
public EnvironmentConfig getEnvironmentForEdit(String environmentName) {
return cloner.deepClone(goConfigService.getConfigForEditing().getEnvironments().find(new CaseInsensitiveString(environmentName)));
}
public ConfigElementForEdit<EnvironmentConfig> getMergedEnvironmentforDisplay(String environmentName, HttpLocalizedOperationResult result) {
ConfigElementForEdit<EnvironmentConfig> edit = null;
try {
CruiseConfig config = goConfigService.getMergedConfigForEditing();
EnvironmentConfig env = environments.named(new CaseInsensitiveString(environmentName));
edit = new ConfigElementForEdit<>(cloner.deepClone(env), config.getMd5());
} catch (NoSuchEnvironmentException e) {
result.badRequest(LocalizedMessage.string("RESOURCE_NOT_FOUND", "Environment", environmentName));
}
return edit;
}
public void createEnvironment(final BasicEnvironmentConfig environmentConfig, final Username user, final HttpLocalizedOperationResult result) {
Localizable.CurryableLocalizable actionFailed = LocalizedMessage.string("ENV_ADD_FAILED");
AddEnvironmentCommand addEnvironmentCommand = new AddEnvironmentCommand(goConfigService, environmentConfig, user, actionFailed, result);
update(addEnvironmentCommand, environmentConfig, user, result, actionFailed);
}
public List<EnvironmentPipelineModel> getAllLocalPipelinesForUser(Username user) {
List<PipelineConfig> pipelineConfigs = goConfigService.getAllLocalPipelineConfigs();
return getAllPipelinesForUser(user, pipelineConfigs);
}
public List<EnvironmentPipelineModel> getAllRemotePipelinesForUserInEnvironment(Username user, EnvironmentConfig environment) {
List<EnvironmentPipelineModel> pipelines = new ArrayList<>();
for (EnvironmentPipelineConfig pipelineConfig : environment.getRemotePipelines()) {
String pipelineName = CaseInsensitiveString.str(pipelineConfig.getName());
if (securityService.hasViewPermissionForPipeline(user, pipelineName)) {
pipelines.add(new EnvironmentPipelineModel(pipelineName, CaseInsensitiveString.str(environment.name())));
}
}
Collections.sort(pipelines);
return pipelines;
}
private List<EnvironmentPipelineModel> getAllPipelinesForUser(Username user, List<PipelineConfig> pipelineConfigs) {
List<EnvironmentPipelineModel> pipelines = new ArrayList<>();
for (PipelineConfig pipelineConfig : pipelineConfigs) {
String pipelineName = CaseInsensitiveString.str(pipelineConfig.name());
if (securityService.hasViewPermissionForPipeline(user, pipelineName)) {
EnvironmentConfig environment = environments.findEnvironmentForPipeline(new CaseInsensitiveString(pipelineName));
if (environment != null) {
pipelines.add(new EnvironmentPipelineModel(pipelineName, CaseInsensitiveString.str(environment.name())));
} else {
pipelines.add(new EnvironmentPipelineModel(pipelineName));
}
}
}
Collections.sort(pipelines);
return pipelines;
}
public void updateEnvironment(final String oldEnvironmentConfigName, final EnvironmentConfig newEnvironmentConfig, final Username username, String md5, final HttpLocalizedOperationResult result) {
Localizable.CurryableLocalizable actionFailed = LocalizedMessage.string("ENV_UPDATE_FAILED", oldEnvironmentConfigName);
UpdateEnvironmentCommand updateEnvironmentCommand = new UpdateEnvironmentCommand(goConfigService, oldEnvironmentConfigName, newEnvironmentConfig, username, actionFailed, md5, entityHashingService, result);
update(updateEnvironmentCommand, newEnvironmentConfig, username, result, actionFailed);
if (result.isSuccessful()) {
result.setMessage(LocalizedMessage.string("UPDATE_ENVIRONMENT_SUCCESS", oldEnvironmentConfigName));
}
}
public void patchEnvironment(final EnvironmentConfig environmentConfig, List<String> pipelinesToAdd, List<String> pipelinesToRemove, List<String> agentsToAdd, List<String> agentsToRemove, List<EnvironmentVariableConfig> envVarsToAdd, List<String> envVarsToRemove, final Username username, final HttpLocalizedOperationResult result) {
Localizable.CurryableLocalizable actionFailed = LocalizedMessage.string("ENV_UPDATE_FAILED", environmentConfig.name());
PatchEnvironmentCommand patchEnvironmentCommand = new PatchEnvironmentCommand(goConfigService, environmentConfig, pipelinesToAdd, pipelinesToRemove, agentsToAdd, agentsToRemove, envVarsToAdd, envVarsToRemove, username, actionFailed, result);
update(patchEnvironmentCommand, environmentConfig, username, result, actionFailed);
if (result.isSuccessful()) {
result.setMessage(LocalizedMessage.string("UPDATE_ENVIRONMENT_SUCCESS", environmentConfig.name()));
}
}
public void deleteEnvironment(final EnvironmentConfig environmentConfig, final Username username, final HttpLocalizedOperationResult result) {
String environmentName = environmentConfig.name().toString();
Localizable.CurryableLocalizable actionFailed = LocalizedMessage.string("ENV_DELETE_FAILED", environmentName);
DeleteEnvironmentCommand deleteEnvironmentCommand = new DeleteEnvironmentCommand(goConfigService, environmentConfig, username, actionFailed, result);
update(deleteEnvironmentCommand, environmentConfig, username, result, actionFailed);
if (result.isSuccessful()) {
result.setMessage(LocalizedMessage.string("RESOURCE_DELETE_SUCCESSFUL", "environment", environmentName));
}
}
private void update(EntityConfigUpdateCommand command, EnvironmentConfig config, Username currentUser, HttpLocalizedOperationResult result, Localizable.CurryableLocalizable actionFailed) {
try {
goConfigService.updateConfig(command, currentUser);
} catch (Exception e) {
if ((e instanceof GoConfigInvalidException) && !result.hasMessage()) {
result.unprocessableEntity(LocalizedMessage.string("ENTITY_CONFIG_VALIDATION_FAILED", config.getClass().getAnnotation(ConfigTag.class).value(), config.name(), e.getMessage()));
} else if (!result.hasMessage()) {
result.badRequest(actionFailed.addParam(e.getMessage()));
}
}
}
}