/* * 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; import com.rits.cloning.Cloner; import com.thoughtworks.go.config.remote.ConfigRepoConfig; import com.thoughtworks.go.config.remote.ConfigReposConfig; import com.thoughtworks.go.config.remote.PartialConfig; import com.thoughtworks.go.config.remote.RepoConfigOrigin; import com.thoughtworks.go.config.validation.GoConfigValidity; import com.thoughtworks.go.server.service.GoConfigService; import com.thoughtworks.go.serverhealth.HealthStateScope; import com.thoughtworks.go.serverhealth.HealthStateType; import com.thoughtworks.go.serverhealth.ServerHealthService; import com.thoughtworks.go.serverhealth.ServerHealthState; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; import java.util.Set; /** * @understands current state of configuration part. * <p/> * Provides partial configurations. */ @Component public class GoPartialConfig implements PartialConfigUpdateCompletedListener, ChangedRepoConfigWatchListListener{ private static final Logger LOGGER = Logger.getLogger(GoPartialConfig.class); public static final String INVALID_CRUISE_CONFIG_MERGE = "Invalid Merged Configuration"; private GoRepoConfigDataSource repoConfigDataSource; private GoConfigWatchList configWatchList; private final GoConfigService goConfigService; private final CachedGoPartials cachedGoPartials; private final ServerHealthService serverHealthService; @Autowired public GoPartialConfig(GoRepoConfigDataSource repoConfigDataSource, GoConfigWatchList configWatchList, GoConfigService goConfigService, CachedGoPartials cachedGoPartials, ServerHealthService serverHealthService) { this.repoConfigDataSource = repoConfigDataSource; this.configWatchList = configWatchList; this.goConfigService = goConfigService; this.cachedGoPartials = cachedGoPartials; this.serverHealthService = serverHealthService; this.configWatchList.registerListener(this); this.repoConfigDataSource.registerListener(this); } public List<PartialConfig> lastPartials() { return cachedGoPartials.lastValidPartials(); } @Override public void onFailedPartialConfig(ConfigRepoConfig repoConfig, Exception ex) { // do nothing here, we keep previous version of part. // As an addition we should stop scheduling pipelines defined in that old part. } @Override public void onSuccessPartialConfig(ConfigRepoConfig repoConfig, PartialConfig newPart) { String fingerprint = repoConfig.getMaterialConfig().getFingerprint(); if (this.configWatchList.hasConfigRepoWithFingerprint(fingerprint)) { //TODO maybe validate new part without context of other partials or main config // put latest known cachedGoPartials.addOrUpdate(fingerprint, newPart); if (updateConfig(newPart, fingerprint, repoConfig)) { cachedGoPartials.markAsValid(fingerprint, newPart); } } } public class GoPartialUpdateCommand implements UpdateConfigCommand { private final PartialConfig newPart; private final String fingerprint; public GoPartialUpdateCommand(final PartialConfig newPart, final String fingerprint) { this.newPart = newPart; this.fingerprint = fingerprint; } @Override public CruiseConfig update(CruiseConfig cruiseConfig) throws Exception { if (newPart != null && fingerprint != null) { cruiseConfig.getPartials().remove(findMatchingPartial(cruiseConfig, fingerprint)); cruiseConfig.getPartials().add(new Cloner().deepClone(newPart)); for (PartialConfig partial : cruiseConfig.getPartials()) { for (EnvironmentConfig environmentConfig : partial.getEnvironments()) { if (!cruiseConfig.getEnvironments().hasEnvironmentNamed(environmentConfig.name())) { cruiseConfig.addEnvironment(new BasicEnvironmentConfig(environmentConfig.name())); } } for (PipelineConfigs pipelineConfigs : partial.getGroups()) { if (!cruiseConfig.getGroups().hasGroup(pipelineConfigs.getGroup())) { cruiseConfig.getGroups().add(new BasicPipelineConfigs(pipelineConfigs.getGroup(), new Authorization())); } } } } return cruiseConfig; } } private boolean updateConfig(final PartialConfig newPart, final String fingerprint, ConfigRepoConfig repoConfig) { try { goConfigService.updateConfig(new GoPartialUpdateCommand(newPart, fingerprint)); return true; } catch (Exception e) { if (repoConfig != null) { String description = String.format("%s - Config-Repo: %s", GoConfigValidity.invalid(e).errorMessage(), newPart.getOrigin().displayName()); ServerHealthState state = ServerHealthState.error(INVALID_CRUISE_CONFIG_MERGE, description, HealthStateType.general(HealthStateScope.forPartialConfigRepo(repoConfig))); serverHealthService.update(state); } return false; } } private PartialConfig findMatchingPartial(CruiseConfig cruiseConfig, String fingerprint) { PartialConfig matchingPartial = findMatchingPartial(cruiseConfig, fingerprint, cachedGoPartials.getValid(fingerprint)); if (matchingPartial == null) { matchingPartial = findMatchingPartial(cruiseConfig, fingerprint, cachedGoPartials.getKnown(fingerprint)); } return matchingPartial; } private PartialConfig findMatchingPartial(CruiseConfig cruiseConfig, String fingerprint, PartialConfig partial) { PartialConfig matching = null; if (partial != null) { for (PartialConfig partialConfig : cruiseConfig.getPartials()) { if (partialConfig.getOrigin() instanceof RepoConfigOrigin && (((RepoConfigOrigin) partialConfig.getOrigin()).getMaterial().getFingerprint().equals(fingerprint))) { matching = partialConfig; break; } } } return matching; } @Override public void onChangedRepoConfigWatchList(ConfigReposConfig newConfigRepos) { // remove partial configs from map which are no longer on the list Set<String> known = cachedGoPartials.getFingerprintToLatestKnownConfigMap().keySet(); for (String fingerprint : known) { if (!newConfigRepos.hasMaterialWithFingerprint(fingerprint)) { cachedGoPartials.removeKnown(fingerprint); } } Set<String> valid = cachedGoPartials.getFingerprintToLatestValidConfigMap().keySet(); for (String fingerprint : valid) { if (!newConfigRepos.hasMaterialWithFingerprint(fingerprint)) { cachedGoPartials.removeValid(fingerprint); } } } }