/* * 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; import com.thoughtworks.go.config.registry.ConfigElementImplementationRegistry; import com.thoughtworks.go.config.update.FullConfigUpdateCommand; import com.thoughtworks.go.domain.GoConfigRevision; 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 com.thoughtworks.go.service.ConfigRepository; import com.thoughtworks.go.util.SystemEnvironment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MarkerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.File; import java.util.ArrayList; /* GoConfigMigrator is responsible to migrate the config xml to the latest version, this is called at server startup. */ @Component public class GoConfigMigrator { private final GoConfigMigration goConfigMigration; private final SystemEnvironment systemEnvironment; private FullConfigSaveNormalFlow fullConfigSaveNormalFlow; private MagicalGoConfigXmlLoader loader; private GoConfigFileReader goConfigFileReader; private ConfigRepository configRepository; private ServerHealthService serverHealthService; private static final Logger LOGGER = LoggerFactory.getLogger(GoConfigMigrator.class.getName()); @Autowired public GoConfigMigrator(GoConfigMigration goConfigMigration, SystemEnvironment systemEnvironment, ConfigCache configCache, ConfigElementImplementationRegistry registry, FullConfigSaveNormalFlow fullConfigSaveNormalFlow, ConfigRepository configRepository, ServerHealthService serverHealthService) { this(goConfigMigration, systemEnvironment, fullConfigSaveNormalFlow, new MagicalGoConfigXmlLoader(configCache, registry), new GoConfigFileReader(systemEnvironment), configRepository, serverHealthService); } public GoConfigMigrator(GoConfigMigration goConfigMigration, SystemEnvironment systemEnvironment, FullConfigSaveNormalFlow fullConfigSaveNormalFlow, MagicalGoConfigXmlLoader loader, GoConfigFileReader goConfigFileReader, ConfigRepository configRepository, ServerHealthService serverHealthService) { this.goConfigMigration = goConfigMigration; this.systemEnvironment = systemEnvironment; this.fullConfigSaveNormalFlow = fullConfigSaveNormalFlow; this.loader = loader; this.goConfigFileReader = goConfigFileReader; this.configRepository = configRepository; this.serverHealthService = serverHealthService; } public GoConfigHolder migrate() throws Exception { try { return upgrade(); } catch (Exception e) { e.printStackTrace(); System.err.println( "There are errors in the Cruise config file. Please read the error message and correct the errors.\n" + "Once fixed, please restart Cruise.\nError: " + e.getMessage()); LOGGER.error(MarkerFactory.getMarker("FATAL"), "There are errors in the Cruise config file. Please read the error message and correct the errors.\n" + "Once fixed, please restart Cruise.\nError: " + e.getMessage()); // Send exit signal in a separate thread otherwise it will deadlock jetty new Thread(new Runnable() { public void run() { System.exit(1); } }).start(); } return null; } private GoConfigHolder upgrade() throws Exception { try { return upgradeConfigFile(); } catch (Exception e) { LOGGER.warn("Error upgrading config file, trying to upgrade using the versioned config file."); return upgradeVersionedConfigFile(e); } } private GoConfigHolder upgradeConfigFile() throws Exception { String upgradedXml = this.goConfigMigration.upgradeIfNecessary(this.goConfigFileReader.configXml()); LOGGER.info("[Config Save] Starting Config Save post upgrade using FullConfigSaveNormalFlow"); CruiseConfig cruiseConfig = this.loader.deserializeConfig(upgradedXml); return fullConfigSaveNormalFlow.execute(new FullConfigUpdateCommand(cruiseConfig, null), new ArrayList<>(), "Upgrade"); } private GoConfigHolder upgradeVersionedConfigFile(Exception originalException) throws Exception { GoConfigRevision currentConfigRevision = configRepository.getCurrentRevision(); if(currentConfigRevision == null) { LOGGER.warn("There is no versioned configuration to fallback for migration."); throw originalException; } try { File backupFile = this.goConfigMigration.revertFileToVersion(fileLocation(), currentConfigRevision); logException(backupFile.getAbsolutePath(), originalException.getMessage()); return upgradeConfigFile(); } catch (Exception e) { LOGGER.warn("The versioned config file could be invalid or migrating the versioned config resulted in an invalid configuration"); throw e; } } private void logException(String backupFileLocation, String exceptionMessage) { String invalidConfigMessage = String.format("Go encountered an invalid configuration file while starting up. " + "The invalid configuration file has been renamed to ā€˜%sā€™ and a new configuration file has been automatically " + "created using the last good configuration. Cause: '%s'", backupFileLocation, exceptionMessage); serverHealthService.update(ServerHealthState.warning("Invalid Configuration", invalidConfigMessage, HealthStateType.general(HealthStateScope.forInvalidConfig()))); LOGGER.warn(invalidConfigMessage); } public File fileLocation() { return new File(systemEnvironment.getCruiseConfigFile()); } }