/* * 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.commands.CheckedUpdateCommand; import com.thoughtworks.go.config.commands.EntityConfigUpdateCommand; import com.thoughtworks.go.config.update.ConfigUpdateCheckFailedException; import com.thoughtworks.go.config.update.FullConfigUpdateCommand; import com.thoughtworks.go.config.validation.GoConfigValidity; import com.thoughtworks.go.listener.ConfigChangedListener; import com.thoughtworks.go.presentation.TriStateSelection; import com.thoughtworks.go.server.domain.Username; import com.thoughtworks.go.server.util.UserHelper; import com.thoughtworks.go.util.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @understands how to modify the cruise config sources */ @Component public class GoConfigDao { private static final Logger LOGGER = LoggerFactory.getLogger(GoConfigDao.class); private CachedGoConfig cachedConfigService; private Cloner cloner = new Cloner(); @Autowired public GoConfigDao(CachedGoConfig cachedConfigService) { this.cachedConfigService = cachedConfigService; } public String fileLocation() { return cachedConfigService.getFileLocation(); } public void updateMailHost(MailHost mailHost) { updateConfig(mailHostUpdater(mailHost)); } public void addPipeline(PipelineConfig pipelineConfig, String groupName) { updateConfig(pipelineAdder(pipelineConfig, groupName)); } public void addEnvironment(final EnvironmentConfig environmentConfig) { updateConfig(new UpdateConfigCommand() { public CruiseConfig update(CruiseConfig cruiseConfig) { cruiseConfig.getEnvironments().add(environmentConfig); return cruiseConfig; } }); } public CruiseConfig loadForEditing() { return cachedConfigService.loadForEditing(); } public CruiseConfig loadMergedForEditing() { return cachedConfigService.loadMergedForEditing(); } public CruiseConfig load() { return cachedConfigService.currentConfig(); } public String md5OfConfigFile() { return cachedConfigService.currentConfig().getMd5(); } public void updateConfig(EntityConfigUpdateCommand command, Username currentUser) { LOGGER.info("Config update for pipeline request by {} is in queue - {}", currentUser, command); synchronized (GoConfigWriteLock.class) { LOGGER.info("Config update for pipeline request by {} is being processed", currentUser); if (!command.canContinue(cachedConfigService.currentConfig())) { throw new ConfigUpdateCheckFailedException(); } cachedConfigService.writeEntityWithLock(command, currentUser); } } public ConfigSaveState updateConfig(UpdateConfigCommand command) { ConfigSaveState configSaveState = null; LOGGER.info("Config update request by {} is in queue - {}", UserHelper.getUserName().getUsername(), command); synchronized (GoConfigWriteLock.class) { try { LOGGER.info("Config update request {} by {} is being processed", command, UserHelper.getUserName().getUsername()); if (command instanceof CheckedUpdateCommand) { CheckedUpdateCommand checkedCommand = (CheckedUpdateCommand) command; if (!checkedCommand.canContinue(cachedConfigService.currentConfig())) { throw new ConfigUpdateCheckFailedException(); } } configSaveState = cachedConfigService.writeWithLock(command); } finally { if (command instanceof ConfigAwareUpdate) { ((ConfigAwareUpdate) command).afterUpdate(clonedConfig()); } LOGGER.info("Config update request by {} is completed", UserHelper.getUserName().getUsername()); } } return configSaveState; } public ConfigSaveState updateFullConfig(FullConfigUpdateCommand command) { ConfigSaveState configSaveState; LOGGER.info("Config update request by {} is in queue - {}", UserHelper.getUserName().getUsername(), command); synchronized (GoConfigWriteLock.class) { LOGGER.info("Config update request by {} is being processed", UserHelper.getUserName().getUsername()); configSaveState = cachedConfigService.writeFullConfigWithLock(command); LOGGER.info("Config update request by {} is completed", UserHelper.getUserName().getUsername()); } return configSaveState; } private CruiseConfig clonedConfig() { return cloner.deepClone(cachedConfigService.currentConfig()); } public GoConfigValidity checkConfigFileValid() { return cachedConfigService.checkConfigFileValid(); } public void registerListener(ConfigChangedListener listener) { cachedConfigService.registerListener(listener); } /** * @deprecated Used only in tests */ public void reloadListeners() { cachedConfigService.reloadListeners(); } /** * @deprecated Used only in tests */ public void forceReload() { cachedConfigService.forceReload(); } public static class CompositeConfigCommand implements UpdateConfigCommand { private List<UpdateConfigCommand> commands = new ArrayList<>(); public CompositeConfigCommand(UpdateConfigCommand... commands) { this.commands.addAll(Arrays.asList(commands)); } public void addCommand(UpdateConfigCommand command) { commands.add(command); } public List<UpdateConfigCommand> getCommands() { return commands; } public CruiseConfig update(CruiseConfig cruiseConfig) throws Exception { for (UpdateConfigCommand command : commands) { cruiseConfig = command.update(cruiseConfig); } return cruiseConfig; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } CompositeConfigCommand command = (CompositeConfigCommand) o; if (commands != null ? !commands.equals(command.commands) : command.commands != null) { return false; } return true; } @Override public int hashCode() { return commands != null ? commands.hashCode() : 0; } @Override public String toString() { return "CompositeConfigCommand{" + "commands=" + commands + '}'; } } public static class NoOverwriteCompositeConfigCommand extends CompositeConfigCommand implements NoOverwriteUpdateConfigCommand { private final String md5; public NoOverwriteCompositeConfigCommand(String md5, UpdateConfigCommand... commands) { super(commands); this.md5 = md5; } public String unmodifiedMd5() { return md5; } } private UpdateConfigCommand pipelineAdder(final PipelineConfig pipelineConfig, final String groupName) { return new UpdateConfigCommand() { public CruiseConfig update(CruiseConfig cruiseConfig) { cruiseConfig.addPipeline(groupName, pipelineConfig); return cruiseConfig; } }; } public UpdateConfigCommand mailHostUpdater(final MailHost mailHost) { return new UpdateConfigCommand() { public CruiseConfig update(CruiseConfig cruiseConfig) { cruiseConfig.server().updateMailHost(mailHost); return cruiseConfig; } }; } public static class ModifyRoleCommand implements UpdateConfigCommand { private String user; private TriStateSelection roleSelection; public ModifyRoleCommand(String user, TriStateSelection roleSelection) { this.user = user; this.roleSelection = roleSelection; } public CruiseConfig update(CruiseConfig cruiseConfig) { RolesConfig rolesConfig = cruiseConfig.server().security().getRoles(); String roleName = roleSelection.getValue(); Role role = rolesConfig.findByName(new CaseInsensitiveString(roleName)); switch (roleSelection.getAction()) { case add: if (role == null) { role = new RoleConfig(new CaseInsensitiveString(roleName), new Users()); rolesConfig.add(role); } if (!role.hasMember(new CaseInsensitiveString(user))) { role.addUser(new RoleUser(new CaseInsensitiveString(user))); } break; case remove: if (role != null) { role.removeUser(new RoleUser(new CaseInsensitiveString(user))); } break; case nochange: break; default: throw ExceptionUtils.bomb("unrecognized Action: " + roleSelection.getAction()); } return cruiseConfig; } } public static class ModifyAdminPrivilegeCommand implements UpdateConfigCommand { private String user; private TriStateSelection adminPrivilegeSelection; public static final UserRoleMatcher ALWAYS_FALSE_MATCHER = new UserRoleMatcher() { public boolean match(CaseInsensitiveString userName, CaseInsensitiveString roleName) { return false; } }; public ModifyAdminPrivilegeCommand(String user, TriStateSelection adminPrivilege) { this.user = user; this.adminPrivilegeSelection = adminPrivilege; } public CruiseConfig update(CruiseConfig cruiseConfig) throws Exception { final AdminsConfig adminsConfig = cruiseConfig.server().security().adminsConfig(); switch (adminPrivilegeSelection.getAction()) { case add: if (!adminsConfig.hasUser(new CaseInsensitiveString(user), ALWAYS_FALSE_MATCHER)) { adminsConfig.add(new AdminUser(new CaseInsensitiveString(user))); } break; case remove: adminsConfig.remove(new AdminUser(new CaseInsensitiveString(user))); break; } return cruiseConfig; } } public GoConfigHolder loadConfigHolder() { return cachedConfigService.loadConfigHolder(); } }