/* * 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.server.service.materials; import com.thoughtworks.go.config.ConfigTag; import com.thoughtworks.go.config.CruiseConfig; import com.thoughtworks.go.config.Validatable; import com.thoughtworks.go.config.commands.EntityConfigUpdateCommand; import com.thoughtworks.go.config.exceptions.GoConfigInvalidException; import com.thoughtworks.go.config.update.*; import com.thoughtworks.go.domain.ConfigErrors; import com.thoughtworks.go.domain.config.Configuration; import com.thoughtworks.go.domain.config.ConfigurationProperty; import com.thoughtworks.go.domain.config.PluginConfiguration; import com.thoughtworks.go.domain.packagerepository.PackageRepositories; import com.thoughtworks.go.domain.packagerepository.PackageRepository; import com.thoughtworks.go.i18n.LocalizedMessage; import com.thoughtworks.go.i18n.Localizer; import com.thoughtworks.go.plugin.access.packagematerial.PackageRepositoryExtension; import com.thoughtworks.go.plugin.access.packagematerial.PackageConfiguration; import com.thoughtworks.go.plugin.access.packagematerial.RepositoryMetadataStore; import com.thoughtworks.go.plugin.api.material.packagerepository.PackageMaterialProperty; import com.thoughtworks.go.plugin.api.material.packagerepository.RepositoryConfiguration; import com.thoughtworks.go.plugin.api.response.Result; import com.thoughtworks.go.plugin.api.response.validation.ValidationError; import com.thoughtworks.go.plugin.api.response.validation.ValidationResult; import com.thoughtworks.go.plugin.infra.PluginManager; import com.thoughtworks.go.plugin.infra.plugininfo.GoPluginDescriptor; import com.thoughtworks.go.server.domain.Username; import com.thoughtworks.go.server.service.EntityHashingService; import com.thoughtworks.go.server.service.GoConfigService; import com.thoughtworks.go.server.service.SecurityService; import com.thoughtworks.go.server.service.result.HttpLocalizedOperationResult; import com.thoughtworks.go.server.service.result.LocalizedOperationResult; import com.thoughtworks.go.util.StringUtil; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import static com.thoughtworks.go.config.update.ErrorCollector.collectFieldErrors; import static com.thoughtworks.go.config.update.ErrorCollector.collectGlobalErrors; import static org.apache.commons.lang.StringUtils.isEmpty; @Service public class PackageRepositoryService { private PluginManager pluginManager; private GoConfigService goConfigService; private SecurityService securityService; private EntityHashingService entityHashingService; private final Localizer localizer; private RepositoryMetadataStore repositoryMetadataStore; private PackageRepositoryExtension packageRepositoryExtension; public static final Logger LOGGER = Logger.getLogger(PackageRepositoryService.class); @Autowired public PackageRepositoryService(PluginManager pluginManager, PackageRepositoryExtension packageRepositoryExtension, GoConfigService goConfigService, SecurityService securityService, EntityHashingService entityHashingService, Localizer localizer) { this.pluginManager = pluginManager; this.packageRepositoryExtension = packageRepositoryExtension; this.goConfigService = goConfigService; this.securityService = securityService; this.entityHashingService = entityHashingService; this.localizer = localizer; repositoryMetadataStore = RepositoryMetadataStore.getInstance(); } public ConfigUpdateAjaxResponse savePackageRepositoryToConfig(PackageRepository packageRepository, final String md5, Username username) { performPluginValidationsFor(packageRepository); UpdateConfigFromUI updateCommand = getPackageRepositoryUpdateCommand(packageRepository, username); HttpLocalizedOperationResult result = new HttpLocalizedOperationResult(); ConfigUpdateResponse configUpdateResponse = goConfigService.updateConfigFromUI(updateCommand, md5, username, result); if (result.isSuccessful()) { ConfigUpdateAjaxResponse response = ConfigUpdateAjaxResponse.success(packageRepository.getId(), result.httpCode(), configUpdateResponse.wasMerged() ? localizer.localize("CONFIG_MERGED") : localizer.localize("SAVED_CONFIGURATION_SUCCESSFULLY")); return response; } else { List<String> globalErrors = globalErrors(configUpdateResponse.getCruiseConfig().getAllErrorsExceptFor(configUpdateResponse.getSubject())); HashMap<String, List<String>> fieldErrors = fieldErrors(configUpdateResponse.getSubject(), "package_repository"); String message = result.message(localizer); ConfigUpdateAjaxResponse response = ConfigUpdateAjaxResponse.failure(packageRepository.getId(), result.httpCode(), message, fieldErrors, globalErrors); return response; } } public void checkConnection(final PackageRepository packageRepository, final LocalizedOperationResult result) { try { Result checkConnectionResult = packageRepositoryExtension.checkConnectionToRepository(packageRepository.getPluginConfiguration().getId(), populateConfiguration(packageRepository.getConfiguration())); String messages = checkConnectionResult.getMessagesForDisplay(); if (!checkConnectionResult.isSuccessful()) { result.connectionError(LocalizedMessage.string("CHECK_CONNECTION_FAILED", "package repository",messages)); return; } result.setMessage(LocalizedMessage.string("CONNECTION_OK", messages)); return; } catch (Exception e) { result.internalServerError(LocalizedMessage.string("CHECK_CONNECTION_FAILED", "package repository", e.getMessage())); } } void performPluginValidationsFor(final PackageRepository packageRepository) { if (!validatePluginId(packageRepository)) { return; } for (ConfigurationProperty configurationProperty : packageRepository.getConfiguration()) { String key = configurationProperty.getConfigurationKey().getName(); String pluginId = packageRepository.getPluginConfiguration().getId(); if (repositoryMetadataStore.hasOption(pluginId, key, PackageConfiguration.REQUIRED)) { if (configurationProperty.getValue().isEmpty()) { configurationProperty.addErrorAgainstConfigurationValue(localizer.localize("MANDATORY_CONFIGURATION_FIELD")); } } } ValidationResult validationResult = packageRepositoryExtension.isRepositoryConfigurationValid(packageRepository.getPluginConfiguration().getId(), populateConfiguration(packageRepository.getConfiguration())); for (ValidationError error : validationResult.getErrors()) { packageRepository.addConfigurationErrorFor(error.getKey(), error.getMessage()); } } public boolean validateRepositoryConfiguration(final PackageRepository packageRepository) { if (!packageRepository.doesPluginExist()) { throw new RuntimeException(String.format("Plugin with id '%s' is not found.", packageRepository.getPluginConfiguration().getId())); } ValidationResult validationResult = packageRepositoryExtension.isRepositoryConfigurationValid(packageRepository.getPluginConfiguration().getId(), populateConfiguration(packageRepository.getConfiguration())); addErrorsToConfiguration(validationResult, packageRepository); return validationResult.isSuccessful(); } private void addErrorsToConfiguration(ValidationResult validationResult, PackageRepository packageRepository) { for (ValidationError validationError : validationResult.getErrors()) { ConfigurationProperty property = packageRepository.getConfiguration().getProperty(validationError.getKey()); if (property != null) { property.addError(validationError.getKey(), validationError.getMessage()); } else { String validationErrorKey = StringUtil.isBlank(validationError.getKey()) ? PackageRepository.CONFIGURATION : validationError.getKey(); packageRepository.addError(validationErrorKey, validationError.getMessage()); } } } public boolean validatePluginId(PackageRepository packageRepository) { String pluginId = packageRepository.getPluginConfiguration().getId(); if (isEmpty(pluginId)) { packageRepository.getPluginConfiguration().errors().add(PluginConfiguration.ID, localizer.localize("PLUGIN_ID_REQUIRED")); return false; } for (String currentPluginId : repositoryMetadataStore.getPlugins()) { if (currentPluginId.equals(pluginId)) { GoPluginDescriptor pluginDescriptor = pluginManager.getPluginDescriptorFor(pluginId); packageRepository.getPluginConfiguration().setVersion(pluginDescriptor.version()); return true; } } packageRepository.getPluginConfiguration().errors().add(PluginConfiguration.ID, localizer.localize("PLUGIN_ID_INVALID")); return false; } UpdateConfigFromUI getPackageRepositoryUpdateCommand(final PackageRepository packageRepository, final Username username) { return new UpdateConfigFromUI() { @Override public void checkPermission(CruiseConfig cruiseConfig, LocalizedOperationResult result) { if (!securityService.canViewAdminPage(username)) { result.unauthorized(LocalizedMessage.string("UNAUTHORIZED_TO_ADMINISTER"), null); } } @Override public Validatable node(CruiseConfig cruiseConfig) { return cruiseConfig; } @Override public Validatable updatedNode(CruiseConfig cruiseConfig) { return cruiseConfig; } @Override public void update(Validatable node) { ((CruiseConfig) node).savePackageRepository(packageRepository); } @Override public Validatable subject(Validatable node) { return ((CruiseConfig) node).getPackageRepositories().find(packageRepository.getRepoId()); } @Override public Validatable updatedSubject(Validatable updatedNode) { return ((CruiseConfig) updatedNode).getPackageRepositories().find(packageRepository.getRepoId()); } }; } private HashMap<String, List<String>> fieldErrors(Validatable subject, String filedErrorPrefix) { HashMap<String, List<String>> filedErrors = new HashMap<>(); //TODO; Ideally subject should not be null, but when xsd validations fails subject comes as null hence this fix if (subject != null) { collectFieldErrors(filedErrors, filedErrorPrefix, subject); } return filedErrors; } private List<String> globalErrors(List<ConfigErrors> allErrorsExceptSubject) { ArrayList<String> globalErrors = new ArrayList<>(); collectGlobalErrors(globalErrors, allErrorsExceptSubject); return globalErrors; } public PackageRepository getPackageRepository(String repoId) { return goConfigService.getPackageRepository(repoId); } public PackageRepositories getPackageRepositories() { return goConfigService.getPackageRepositories(); } private void update(Username username, HttpLocalizedOperationResult result, EntityConfigUpdateCommand command, PackageRepository repository) { try { goConfigService.updateConfig(command, username); } catch (Exception e) { if (e instanceof GoConfigInvalidException && !result.hasMessage()) { result.unprocessableEntity(LocalizedMessage.string("ENTITY_CONFIG_VALIDATION_FAILED", repository.getClass().getAnnotation(ConfigTag.class).value(), repository.getId(), e.getMessage())); } else { if (!result.hasMessage()) { LOGGER.error(e.getMessage(), e); result.internalServerError(LocalizedMessage.string("SAVE_FAILED_WITH_REASON", "An error occurred while saving the package repository config. Please check the logs for more information.")); } } } } //Used only in tests public void setPluginManager(PluginManager pluginManager) { this.pluginManager = pluginManager; } public void deleteRepository(Username username, PackageRepository repository, HttpLocalizedOperationResult result) { DeletePackageRepositoryCommand command = new DeletePackageRepositoryCommand(goConfigService, repository, username, result); update(username, result, command, repository); if (result.isSuccessful()) { result.setMessage(LocalizedMessage.string("RESOURCE_DELETE_SUCCESSFUL", "package repository", repository.getId())); } } public void createPackageRepository(PackageRepository repository, Username username, HttpLocalizedOperationResult result){ CreatePackageRepositoryCommand command = new CreatePackageRepositoryCommand(goConfigService, this, repository, username, result); update(username, result, command, repository); } public void updatePackageRepository(PackageRepository newRepo, Username username, String md5, HttpLocalizedOperationResult result, String oldRepoId){ UpdatePackageRepositoryCommand command = new UpdatePackageRepositoryCommand(goConfigService, this, newRepo, username, md5, entityHashingService, result, oldRepoId); update(username,result, command, newRepo); } private RepositoryConfiguration populateConfiguration(Configuration configuration) { RepositoryConfiguration repositoryConfiguration = new RepositoryConfiguration(); for (ConfigurationProperty configurationProperty : configuration) { String value = configurationProperty.getValue(); repositoryConfiguration.add(new PackageMaterialProperty(configurationProperty.getConfigurationKey().getName(), value)); } return repositoryConfiguration; } }