/* * 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.rits.cloning.Cloner; import com.thoughtworks.go.config.exceptions.GoConfigInvalidException; import com.thoughtworks.go.config.materials.dependency.DependencyMaterialConfig; import com.thoughtworks.go.config.parts.XmlPartialConfigProvider; import com.thoughtworks.go.config.registry.ConfigElementImplementationRegistry; import com.thoughtworks.go.config.remote.ConfigRepoConfig; import com.thoughtworks.go.config.remote.PartialConfig; import com.thoughtworks.go.config.remote.RepoConfigOrigin; import com.thoughtworks.go.helper.MaterialConfigsMother; import com.thoughtworks.go.helper.PartialConfigMother; import com.thoughtworks.go.helper.PipelineConfigMother; import com.thoughtworks.go.server.service.GoConfigService; import com.thoughtworks.go.util.*; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.Charset; import java.util.UUID; import static com.thoughtworks.go.helper.ConfigFileFixture.DEFAULT_XML_WITH_2_AGENTS; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:WEB-INF/applicationContext-global.xml", "classpath:WEB-INF/applicationContext-dataLocalAccess.xml", "classpath:WEB-INF/applicationContext-acegi-security.xml" }) public class GoFileConfigDataSourceIntegrationTest { private final String DEFAULT_CHARSET = "defaultCharset"; private final SystemEnvironment systemEnvironment = new SystemEnvironment(); @Autowired private GoFileConfigDataSource dataSource; @Autowired private GoConfigService configService; @Autowired private GoPartialConfig goPartialConfig; @Autowired private GoConfigDao goConfigDao; @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); private GoConfigFileHelper configHelper; @Autowired private GoConfigService goConfigService; @Autowired private GoConfigWatchList configWatchList; private ConfigRepoConfig configRepo; @Autowired private CachedGoPartials cachedGoPartials; @Autowired private ConfigCache configCache; @Autowired private ConfigElementImplementationRegistry configElementImplementationRegistry; private final String remoteDownstream = "remote_downstream"; private PartialConfig partialConfig; private PipelineConfig upstreamPipeline; @Before public void setUp() throws Exception { File configDir = temporaryFolder.newFolder(); String absolutePath = new File(configDir, "cruise-config.xml").getAbsolutePath(); systemEnvironment.setProperty(SystemEnvironment.CONFIG_FILE_PROPERTY, absolutePath); configHelper = new GoConfigFileHelper(DEFAULT_XML_WITH_2_AGENTS); configHelper.usingCruiseConfigDao(goConfigDao).initializeConfigFile(); configHelper.onSetUp(); configHelper.addConfigRepo(new ConfigRepoConfig(MaterialConfigsMother.gitMaterialConfig("url"), XmlPartialConfigProvider.providerName)); configHelper.addPipeline("upstream", "upstream_stage_original"); goConfigService.forceNotifyListeners(); cachedGoPartials.clear(); configRepo = configWatchList.getCurrentConfigRepos().get(0); upstreamPipeline = goConfigService.pipelineConfigNamed(new CaseInsensitiveString("upstream")); partialConfig = PartialConfigMother.pipelineWithDependencyMaterial(remoteDownstream, upstreamPipeline, new RepoConfigOrigin(configRepo, "r1")); goPartialConfig.onSuccessPartialConfig(configRepo, partialConfig); } @After public void tearDown() throws Exception { cachedGoPartials.clear(); dataSource.reloadIfModified(); configHelper.onTearDown(); ReflectionUtil.setStaticField(Charset.class, DEFAULT_CHARSET, null); systemEnvironment.clearProperty(SystemEnvironment.CONFIG_FILE_PROPERTY); } @Test public void shouldConvertToUTF8BeforeSavingConfigToFileSystem() throws IOException { ReflectionUtil.setStaticField(Charset.class, DEFAULT_CHARSET, Charset.forName("windows-1252")); GoFileConfigDataSource.GoConfigSaveResult result = dataSource.writeWithLock(new UpdateConfigCommand() { @Override public CruiseConfig update(CruiseConfig cruiseConfig) throws Exception { PipelineConfig pipelineConfig = PipelineConfigMother.pipelineConfig(UUID.randomUUID().toString()); JobConfig job = new JobConfig("job"); ExecTask task = new ExecTask(); task.setCommand("powershell"); task.setArgs("Get-ChildItem -Path . –Recurse"); job.addTask(task); pipelineConfig.first().getJobs().add(job); cruiseConfig.addPipeline(UUID.randomUUID().toString(), pipelineConfig); return cruiseConfig; } }, new GoConfigHolder(goConfigService.currentCruiseConfig(), goConfigService.getConfigForEditing())); assertThat(result.getConfigSaveState(), is(ConfigSaveState.UPDATED)); FileInputStream inputStream = new FileInputStream(dataSource.fileLocation()); String newMd5 = CachedDigestUtils.md5Hex(inputStream); assertThat(newMd5, is(result.getConfigHolder().config.getMd5())); } @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void shouldValidateMergedConfigForConfigChangesThroughFileSystem() throws Exception { assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(new CaseInsensitiveString(remoteDownstream)), is(true)); updateConfigOnFileSystem(new UpdateConfig() { @Override public void update(CruiseConfig cruiseConfig) { PipelineConfig updatedUpstream = cruiseConfig.getPipelineConfigByName(upstreamPipeline.name()); updatedUpstream.getFirstStageConfig().setName(new CaseInsensitiveString("upstream_stage_renamed")); } }); thrown.expect(org.hamcrest.Matchers.any(GoConfigInvalidException.class)); thrown.expectMessage("Stage with name 'upstream_stage_original' does not exist on pipeline 'upstream', it is being referred to from pipeline 'remote_downstream' (url at r1)"); dataSource.forceLoad(new File(systemEnvironment.getCruiseConfigFile())); } @Test public void shouldFallbackToValidPartialsForConfigChangesThroughFileSystem() throws Exception { assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(new CaseInsensitiveString(remoteDownstream)), is(true)); String remoteInvalidPipeline = "remote_invalid_pipeline"; PartialConfig invalidPartial = PartialConfigMother.invalidPartial(remoteInvalidPipeline, new RepoConfigOrigin(configRepo, "r2")); goPartialConfig.onSuccessPartialConfig(configRepo, invalidPartial); assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(new CaseInsensitiveString(remoteInvalidPipeline)), is(false)); final String newArtifactLocation = "some_random_change_to_config"; updateConfigOnFileSystem(new UpdateConfig() { @Override public void update(CruiseConfig cruiseConfig) { cruiseConfig.server().setArtifactsDir(newArtifactLocation); } }); GoConfigHolder goConfigHolder = dataSource.forceLoad(new File(systemEnvironment.getCruiseConfigFile())); assertThat(goConfigHolder.config.server().artifactsDir(), is(newArtifactLocation)); assertThat(goConfigHolder.config.getAllPipelineNames().contains(new CaseInsensitiveString(remoteDownstream)), is(true)); assertThat(goConfigHolder.config.getAllPipelineNames().contains(new CaseInsensitiveString(remoteInvalidPipeline)), is(false)); } @Test public void shouldSaveWithKnownPartialsWhenValidationPassesForConfigChangesThroughFileSystem() throws Exception { assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(new CaseInsensitiveString(remoteDownstream)), is(true)); //Introducing a change to make the latest version of remote pipeline invalid PipelineConfig remoteDownstreamPipeline = partialConfig.getGroups().first().getPipelines().get(0); DependencyMaterialConfig dependencyMaterial = remoteDownstreamPipeline.materialConfigs().findDependencyMaterial(upstreamPipeline.name()); dependencyMaterial.setStageName(new CaseInsensitiveString("upstream_stage_renamed")); goPartialConfig.onSuccessPartialConfig(configRepo, partialConfig); DependencyMaterialConfig dependencyMaterialForRemotePipelineInConfigCache = goConfigService.getCurrentConfig().getPipelineConfigByName(new CaseInsensitiveString(remoteDownstream)).materialConfigs().findDependencyMaterial(upstreamPipeline.name()); assertThat(dependencyMaterialForRemotePipelineInConfigCache.getStageName(), is(new CaseInsensitiveString("upstream_stage_original"))); final CaseInsensitiveString upstreamStageRenamed = new CaseInsensitiveString("upstream_stage_renamed"); updateConfigOnFileSystem(new UpdateConfig() { @Override public void update(CruiseConfig cruiseConfig) { cruiseConfig.getPipelineConfigByName(upstreamPipeline.name()).first().setName(upstreamStageRenamed); } }); GoConfigHolder goConfigHolder = dataSource.forceLoad(new File(systemEnvironment.getCruiseConfigFile())); assertThat(goConfigHolder.config.getAllPipelineNames().contains(new CaseInsensitiveString(remoteDownstream)), is(true)); assertThat(goConfigHolder.config.getPipelineConfigByName(remoteDownstreamPipeline.name()).materialConfigs().findDependencyMaterial(upstreamPipeline.name()).getStageName(), is(upstreamStageRenamed)); assertThat(goConfigHolder.config.getPipelineConfigByName(upstreamPipeline.name()).getFirstStageConfig().name(), is(upstreamStageRenamed)); } private interface UpdateConfig { void update(CruiseConfig cruiseConfig); } private void updateConfigOnFileSystem(UpdateConfig updateConfig) throws Exception { String cruiseConfigFile = systemEnvironment.getCruiseConfigFile(); CruiseConfig updatedConfig = new Cloner().deepClone(goConfigService.getConfigForEditing()); updateConfig.update(updatedConfig); File configFile = new File(cruiseConfigFile); FileOutputStream outputStream = new FileOutputStream(configFile); new MagicalGoConfigXmlWriter(configCache, configElementImplementationRegistry).write(updatedConfig, outputStream, true); } }