Java tutorial
/* * Copyright 2019 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.materials.git.GitMaterialConfig; import com.thoughtworks.go.config.validation.GoConfigValidity; import com.thoughtworks.go.domain.materials.MaterialConfig; import com.thoughtworks.go.domain.packagerepository.PackageDefinition; import com.thoughtworks.go.domain.packagerepository.PackageRepository; import com.thoughtworks.go.helper.ConfigFileFixture; import com.thoughtworks.go.security.CryptoException; import com.thoughtworks.go.security.GoCipher; import com.thoughtworks.go.security.ResetCipher; import com.thoughtworks.go.server.service.GoConfigService; import com.thoughtworks.go.serverhealth.ServerHealthService; import com.thoughtworks.go.service.ConfigRepository; import com.thoughtworks.go.util.ConfigElementImplementationRegistryMother; import com.thoughtworks.go.util.GoConfigFileHelper; import com.thoughtworks.go.util.SystemEnvironment; import com.thoughtworks.go.util.TimeProvider; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.jdom2.Document; import org.jdom2.filter.ElementFilter; import org.jdom2.input.SAXBuilder; 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 org.xmlunit.assertj.XmlAssert; import java.io.File; import java.io.StringReader; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static com.thoughtworks.go.config.PipelineConfig.LOCK_VALUE_LOCK_ON_FAILURE; import static com.thoughtworks.go.config.PipelineConfig.LOCK_VALUE_NONE; import static com.thoughtworks.go.helper.ConfigFileFixture.configWithArtifactSourceAs; import static com.thoughtworks.go.helper.ConfigFileFixture.pipelineWithAttributes; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:WEB-INF/applicationContext-global.xml", "classpath:WEB-INF/applicationContext-dataLocalAccess.xml", "classpath:testPropertyConfigurer.xml", "classpath:WEB-INF/spring-all-servlet.xml", }) public class GoConfigMigrationIntegrationTest { private File configFile; ConfigRepository configRepository; @Autowired private SystemEnvironment systemEnvironment; @Autowired private ConfigCache configCache; @Autowired private GoConfigService goConfigService; @Autowired private ServerHealthService serverHealthService; @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); @Rule public ResetCipher resetCipher = new ResetCipher(); @Rule public ExpectedException thrown = ExpectedException.none(); private MagicalGoConfigXmlLoader loader; @Before public void setUp() throws Exception { File file = temporaryFolder.newFolder(); configFile = new File(file, "cruise-config.xml"); new SystemEnvironment().setProperty(SystemEnvironment.CONFIG_FILE_PROPERTY, configFile.getAbsolutePath()); GoConfigFileHelper.clearConfigVersions(); configRepository = new ConfigRepository(systemEnvironment); configRepository.initialize(); serverHealthService.removeAllLogs(); loader = new MagicalGoConfigXmlLoader(new ConfigCache(), ConfigElementImplementationRegistryMother.withNoPlugins()); resetCipher.setupDESCipherFile(); resetCipher.setupAESCipherFile(); } @After public void tearDown() throws Exception { GoConfigFileHelper.clearConfigVersions(); configFile.delete(); serverHealthService.removeAllLogs(); } @Test public void shouldMigrateConfigContentAsAString() { String newContent = new GoConfigMigration(new TimeProvider(), ConfigElementImplementationRegistryMother.withNoPlugins()) .upgradeIfNecessary(ConfigFileFixture.VERSION_0); assertThat(newContent).contains("schemaVersion=\"" + GoConfigSchema.currentSchemaVersion() + "\""); } @Test public void shouldNotMigrateConfigContentAsAStringWhenAlreadyUpToDate() { GoConfigMigration configMigration = new GoConfigMigration(new TimeProvider(), ConfigElementImplementationRegistryMother.withNoPlugins()); String newContent = configMigration.upgradeIfNecessary(ConfigFileFixture.VERSION_0); assertThat(newContent).isEqualTo(configMigration.upgradeIfNecessary(newContent)); } @Test public void shouldMigrateToRevision22() throws Exception { final String content = IOUtils.toString( getClass().getResourceAsStream("cruise-config-escaping-migration-test-fixture.xml"), UTF_8); String migratedContent = migrateXmlString(content, 21, 22); String expected = content.replaceAll("(?<!do_not_sub_)#", "##").replace("<cruise schemaVersion=\"21\">", "<cruise schemaVersion=\"22\">"); assertStringsIgnoringCarriageReturnAreEqual(expected, migratedContent); } @Test public void shouldMigrateToRevision28() throws Exception { final String content = IOUtils .toString(getClass().getResourceAsStream("no-tracking-tool-group-holder-config.xml"), UTF_8); String migratedContent = migrateXmlString(content, 27); assertThat(migratedContent).contains("\"http://foo.bar/baz/${ID}\""); assertThat(migratedContent).contains("\"http://hello.world/${ID}/hello\""); } @Test public void shouldMigrateToRevision34() throws Exception { final String content = IOUtils .toString(getClass().getResourceAsStream("svn-p4-with-parameterized-passwords.xml"), UTF_8); String migratedContent = migrateXmlString(content, 22, 34); String expected = content.replaceAll("#\\{jez_passwd\\}", "badger") .replace("<cruise schemaVersion=\"22\">", "<cruise schemaVersion=\"34\">").replaceAll("##", "#"); assertStringsIgnoringCarriageReturnAreEqual(expected, migratedContent); } @Test public void shouldMigrateToRevision35_escapeHash() throws Exception { final String content = IOUtils .toString(getClass().getResourceAsStream("escape_param_for_nant_p4.xml"), UTF_8).trim(); String migratedContent = migrateXmlString(content, 22, 35); String expected = content.replace("<cruise schemaVersion=\"22\">", "<cruise schemaVersion=\"35\">") .replace("<view>##foo#</view>", "<view>####foo##</view>") .replace("nantpath=\"#foo##\"", "nantpath=\"##foo####\""); assertStringsIgnoringCarriageReturnAreEqual(expected, migratedContent); } @Test public void shouldMigrateToRevision58_deleteVMMS() throws Exception { String migratedContent = migrateXmlString(ConfigFileFixture.WITH_VMMS_CONFIG, 50, 58); assertThat(migratedContent.contains("vmms")).isFalse(); } @Test public void shouldMigrateExecTaskArgValueToTextNode() throws Exception { String migratedContent = migrateXmlString(ConfigFileFixture.VALID_XML_3169, 14); assertThat(migratedContent).contains("<arg>test</arg>"); } @Test public void shouldMigrateToRevision23_IsLockedIsFalseByDefault() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { final String content = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"22\">\n" + " <server artifactsdir=\"artifacts\"/>\n" + " <pipelines>" + " <pipeline name=\"in_env\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"some_stage\">" + " <jobs>" + " <job name=\"some_job\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " <pipeline name=\"not_in_env\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"some_stage\">" + " <jobs>" + " <job name=\"some_job\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " <pipeline name=\"in_env_unLocked\" isLocked=\"false\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"some_stage\">" + " <jobs>" + " <job name=\"some_job\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </pipelines>" + " <environments>" + " <environment name=\"some_env\">" + " <pipelines>" + " <pipeline name=\"in_env\"/>" + " <pipeline name=\"in_env_unLocked\"/>" + " </pipelines>" + " </environment>" + " </environments>" + " </cruise>"; String migratedContent = migrateXmlString(content, 22, 23); assertThat(migratedContent).contains("<pipeline isLocked=\"true\" name=\"in_env\">"); assertThat(migratedContent).contains("<pipeline isLocked=\"false\" name=\"in_env_unLocked\">"); assertThat(migratedContent).contains("<pipeline name=\"not_in_env\">"); } @Test public void shouldSetServerId_toARandomUUID_ifServerTagDoesntExist() { GoConfigService.XmlPartialSaver fileSaver = goConfigService.fileSaver(true); GoConfigValidity configValidity = fileSaver.saveXml("<cruise schemaVersion='" + 53 + "'>\n" + "</cruise>", goConfigService.configFileMd5()); assertThat(configValidity.isValid()).as("Has no error").isTrue(); CruiseConfig config = goConfigService.getCurrentConfig(); ServerConfig server = config.server(); assertThat(server.getServerId().matches("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")) .isTrue(); } @Test public void shouldSetServerId_toARandomUUID_ifOneDoesntExist() { GoConfigService.XmlPartialSaver fileSaver = goConfigService.fileSaver(true); GoConfigValidity configValidity = fileSaver.saveXml("<cruise schemaVersion='" + 55 + "'>\n" + "<server artifactsdir=\"logs\" siteUrl=\"http://go-server-site-url:8153\" secureSiteUrl=\"https://go-server-site-url:8154\" jobTimeout=\"60\">\n" + " </server>" + "</cruise>", goConfigService.configFileMd5()); assertThat(configValidity.isValid()).as("Has no error").isTrue(); CruiseConfig config = goConfigService.getCurrentConfig(); ServerConfig server = config.server(); assertThat(server.getServerId().matches("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")) .isTrue(); } @Test public void shouldLoadServerId_ifOneExists() { GoConfigService.XmlPartialSaver fileSaver = goConfigService.fileSaver(true); GoConfigValidity configValidity = fileSaver.saveXml("<cruise schemaVersion='" + 55 + "'>\n" + "<server artifactsdir=\"logs\" siteUrl=\"http://go-server-site-url:8153\" secureSiteUrl=\"https://go-server-site-url:8154\" jobTimeout=\"60\" serverId=\"foo\">\n" + " </server>" + "</cruise>", goConfigService.configFileMd5()); assertThat(configValidity.isValid()).as("Has no error").isTrue(); CruiseConfig config = goConfigService.getCurrentConfig(); ServerConfig server = config.server(); assertThat(server.getServerId()).isEqualTo("foo"); } @Test public void shouldRemoveAllLuauConfigurationFromConfig() throws Exception { String configString = "<cruise schemaVersion='66'>" + "<server siteUrl='https://hostname'>" + "<security>" + " <luau url='https://luau.url.com' clientKey='0d010cf97ec505ee3788a9b5b8cf71d482c394ae88d32f0333' authState='authorized' />" + " <ldap uri='ldap' managerDn='managerDn' encryptedManagerPassword='+XhtUNvVAxJdHGF4qQGnWw==' searchFilter='(sAMAccountName={0})'>" + " <bases>" + " <base value='ou=Enterprise,ou=Principal,dc=corporate,dc=thoughtworks,dc=com' />" + " </bases>" + " </ldap>" + " <roles>" + " <role name='luau-role'><groups><luauGroup>luau-group</luauGroup></groups></role>" + " <role name='ldap-role'><users><user>some-user</user></users></role>" + "</roles>" + "</security>" + "</server>" + "</cruise>"; String migratedContent = migrateXmlString(configString, 66); Document document = new SAXBuilder().build(new StringReader(migratedContent)); assertThat(document.getDescendants(new ElementFilter("luau")).hasNext()).isFalse(); assertThat(document.getDescendants(new ElementFilter("groups")).hasNext()).isFalse(); } @Test public void shouldAddAttributeAutoUpdateOnPackage_AsPartOfMigration68() throws Exception { String configString = "<cruise schemaVersion='67'>" + "<repositories>" + " <repository id='2ef830d7-dd66-42d6-b393-64a84646e557' name='GoYumRepo'>" + " <pluginConfiguration id='yum' version='1' />" + " <configuration>" + " <property>" + " <key>REPO_URL</key>" + " <value>http://random-yum-repo/go/yum/no-arch</value>" + " </property>" + " </configuration>" + " <packages>" + " <package id='88a3beca-cbe2-4c4d-9744-aa0cda3f371c' name='1'>" + " <configuration>" + " <property>" + " <key>REPO_URL</key>" + " <value>http://random-yum-repo/go/yum/no-arch</value>" + " </property>" + " </configuration>" + " </package>" + " </packages>" + " </repository>" + "</repositories>" + "</cruise>"; String migratedContent = migrateXmlString(configString, 67); GoConfigHolder holder = loader.loadConfigHolder(migratedContent); PackageRepository packageRepository = holder.config.getPackageRepositories() .find("2ef830d7-dd66-42d6-b393-64a84646e557"); PackageDefinition aPackage = packageRepository.findPackage("88a3beca-cbe2-4c4d-9744-aa0cda3f371c"); assertThat(aPackage.isAutoUpdate()).isTrue(); } @Test public void shouldAllowAuthorizationUnderEachTemplate_asPartOfMigration69() throws Exception { String configString = "<cruise schemaVersion='69'>" + " <templates>" + " <pipeline name='template-name'>" + " <authorization>" + " <admins>" + " <user>admin1</user>" + " <user>admin2</user>" + " </admins>" + " </authorization>" + " <stage name='stage-name'>" + " <jobs>" + " <job name='job-name'/>" + " </jobs>" + " </stage>" + " </pipeline>" + " </templates>" + "</cruise>"; String migratedContent = migrateXmlString(configString, 69); assertThat(migratedContent).contains("<authorization>"); CruiseConfig configForEdit = loader.loadConfigHolder(migratedContent).configForEdit; PipelineTemplateConfig template = configForEdit .getTemplateByName(new CaseInsensitiveString("template-name")); Authorization authorization = template.getAuthorization(); assertThat(authorization).isNotNull(); assertThat(authorization.hasAdminsDefined()).isTrue(); assertThat(authorization.getAdminsConfig().getUsers()).contains( new AdminUser(new CaseInsensitiveString("admin1")), new AdminUser(new CaseInsensitiveString("admin2"))); } @Test public void shouldRemoveLicenseSection_asPartOfMigration72() throws Exception { String licenseUser = "Go UAT ThoughtWorks"; String configWithLicenseSection = "<cruise schemaVersion='71'>" + "<server artifactsdir=\"logs\" commandRepositoryLocation=\"default\" serverId=\"dev-id\">" + " <license user=\"" + licenseUser + "\">kTr+1ZBEr/5EiWlADIM6gUMtedtaLKPh6WRGp/2qISy1QczZpqJP5vmfydvx\n" + " Hq6o5X+nrb69sGOaBAvmjJ4cZBaIq+/4Yb+ufQCUM2DkacG/BjdEDpIoPHRA\n" + " fUnmjddxMnVKh2CW7gn7ZnmZUyasS9621UH2uNsfms3gfIK/1PRfbdrFuu5d\n" + " 6xQEiEhjRVhKGFH4Uq2Cb0BVYCnQ+9eJ7WNwcV4pZCt1AoaMAxo4dox4NLpS\n" + " pKtgCp1Is/7ui+MGzKEyLCuO/LLMt0ChxWSN62vXiwdW3jl2HCEsLpb70FYR\n" + " Gj8eif3vuIB2rkOSvLkiAXqDFdEBEmb+GNV3nA4qOw==" + "</license>\n" + " </server>" + "</cruise>"; String migratedContent = migrateXmlString(configWithLicenseSection, 71); assertThat(migratedContent).doesNotContain("license"); assertThat(migratedContent).doesNotContain(licenseUser); } @Test public void shouldPerformNOOPWhenNoLicenseIsPresent_asPartOfMigration72() throws Exception { String licenseUser = "Go UAT ThoughtWorks"; String configWithLicenseSection = "<cruise schemaVersion='71'>" + "<server artifactsdir=\"logs\" commandRepositoryLocation=\"default\" serverId=\"dev-id\">" + " </server>" + "</cruise>"; String migratedContent = migrateXmlString(configWithLicenseSection, 71); assertThat(migratedContent).doesNotContain("license"); assertThat(migratedContent).doesNotContain(licenseUser); } @Test public void shouldNotRemoveNonEmptyUserTags_asPartOfMigration78() throws Exception { String configXml = "<cruise schemaVersion='77'>" + " <pipelines group='first'>" + " <authorization>" + " <view>" + " <user>abc</user>" + " </view>" + " </authorization>" + " <pipeline name='Test' template='test_template'>" + " <materials>" + " <hg url='../manual-testing/ant_hg/dummy' />" + " </materials>" + " </pipeline>" + " </pipelines>" + "</cruise>"; String migratedXml = migrateXmlString(configXml, 77); assertThat(migratedXml).contains("<user>"); } @Test public void shouldRemoveEmptyTags_asPartOfMigration78() throws Exception { String configXml = "<cruise schemaVersion='77'>" + " <pipelines group='first'>" + " <authorization>" + " <view>" + " <user>foo</user>" + " <user />" + " <user> </user>" + " </view>" + " <operate>" + " <user></user>" + " </operate>" + " </authorization>" + " <pipeline name='Test' template='test_template'>" + " <materials>" + " <hg url='../manual-testing/ant_hg/dummy' />" + " </materials>" + " </pipeline>" + " </pipelines>" + "</cruise>"; String migratedXml = migrateXmlString(configXml, 77); assertThat(StringUtils.countMatches(migratedXml, "<user>")).isEqualTo(1); } @Test public void shouldRemoveEmptyTagsRecursively_asPartOfMigration78() throws Exception { String configXml = "<cruise schemaVersion='77'>" + " <pipelines group='first'>" + " <authorization>" + " <view>" + " <user></user>" + " </view>" + " </authorization>" + " <pipeline name='Test' template='test_template'>" + " <materials>" + " <hg url='../manual-testing/ant_hg/dummy' />" + " </materials>" + " </pipeline>" + " </pipelines>" + "</cruise>"; String migratedXml = migrateXmlString(configXml, 77); assertThat(migratedXml).doesNotContain("<user>"); assertThat(migratedXml).doesNotContain("<view>"); assertThat(migratedXml).doesNotContain("<authorization>"); } @Test public void shouldAddIdOnConfigRepoAsPartOfMigration94() throws Exception { String configXml = "<cruise schemaVersion='93'>" + "<config-repos>\n" + " <config-repo plugin=\"json.config.plugin\">\n" + " <git url=\"https://github.com/tomzo/gocd-json-config-example.git\" />\n" + " </config-repo>\n" + "</config-repos>" + "</cruise>"; String migratedContent = migrateXmlString(configXml, 93); assertThat(migratedContent).contains("id="); } @Test public void shouldConvertPluginToPluginIdOnConfigRepoAsPartOfMigration95() throws Exception { String configXml = "<cruise schemaVersion='94'>" + "<config-repos>\n" + " <config-repo plugin=\"json.config.plugin\" id=\"config-repo-1\">\n" + " <git url=\"https://github.com/tomzo/gocd-json-config-example.git\" />\n" + " </config-repo>\n" + "</config-repos>" + "</cruise>"; assertThat(configXml).doesNotContain("pluginId=\"json.config.plugin\""); String migratedContent = migrateXmlString(configXml, 94); assertThat(migratedContent).contains("pluginId=\"json.config.plugin\""); } @Test public void shouldConvertIsLockedAttributeToATristateNamedLockBehavior() throws Exception { String defaultPipeline = pipelineWithAttributes("name=\"default1\"", 97); String lockedPipeline = pipelineWithAttributes("name=\"locked1\" isLocked=\"true\"", 97); String unLockedPipeline = pipelineWithAttributes("name=\"unlocked1\" isLocked=\"false\"", 97); String defaultPipelineAfterMigration = pipelineWithAttributes("name=\"default1\"", 98); String lockedPipelineAfterMigration = pipelineWithAttributes( "name=\"locked1\" lockBehavior=\"" + LOCK_VALUE_LOCK_ON_FAILURE + "\"", 98); String unLockedPipelineAfterMigration = pipelineWithAttributes( "name=\"unlocked1\" lockBehavior=\"" + LOCK_VALUE_NONE + "\"", 98); assertStringsIgnoringCarriageReturnAreEqual(defaultPipelineAfterMigration, migrateXmlString(defaultPipeline, 97, 98)); assertStringsIgnoringCarriageReturnAreEqual(lockedPipelineAfterMigration, migrateXmlString(lockedPipeline, 97, 98)); assertStringsIgnoringCarriageReturnAreEqual(unLockedPipelineAfterMigration, migrateXmlString(unLockedPipeline, 97, 98)); } @Test public void shouldNotSupportedUncesseryMaterialFieldsAsPartOfMigration99() throws Exception { String configXml = "<cruise schemaVersion='99'>" + "<config-repos>\n" + " <config-repo pluginId=\"json.config.plugin\" id=\"config-repo-1\">\n" + " <git url=\"https://github.com/tomzo/gocd-json-config-example.git\" dest=\"dest\"/>\n" + " </config-repo>\n" + "</config-repos>" + "</cruise>"; String message = "Attribute 'dest' is not allowed to appear in element 'git'."; try { migrateXmlString(configXml, 99); fail(String.format( "Expected a failure. Reason: Cruise config file with version 98 is invalid. Unable to upgrade. Message:%s", message)); } catch (InvocationTargetException e) { assertThat(e.getTargetException().getCause().getMessage()).isEqualTo(message); } } @Test public void migration99_shouldMigrateGitMaterialsUnderConfigRepoAndRetainOnlyTheMinimalRequiredAttributes() throws Exception { String configXml = "<cruise schemaVersion='98'>" + "<config-repos>\n" + " <config-repo pluginId=\"json.config.plugin\" id=\"config-repo-1\">\n" + " <git url=\"test-repo\" dest='dest' shallowClone='true' autoUpdate='true' invertFilter='true' materialName=\"foo\">\n" + " <filter>\n" + " <ignore pattern=\"asdsd\" />\n" + " </filter>\n" + " </git>" + " </config-repo>\n" + "</config-repos>" + "</cruise>"; assertThat(configXml).contains("<filter>"); assertThat(configXml).contains("dest='dest'"); assertThat(configXml).contains("autoUpdate='true'"); assertThat(configXml).contains("invertFilter='true'"); assertThat(configXml).contains("shallowClone='true'"); String migratedContent = migrateXmlString(configXml, 98); CruiseConfig cruiseConfig = loader.deserializeConfig(migratedContent); GitMaterialConfig materialConfig = (GitMaterialConfig) cruiseConfig.getConfigRepos() .getConfigRepo("config-repo-1").getMaterialConfig(); assertThat(migratedContent).doesNotContain("<filter>"); assertThat(migratedContent).doesNotContain("dest='dest'"); assertThat(migratedContent).doesNotContain("invertFilter='true'"); assertThat(migratedContent).doesNotContain("shallowClone='true'"); assertThat(materialConfig.getFolder()).isNull(); assertThat(materialConfig.filter().size()).isEqualTo(0); assertThat(materialConfig.isAutoUpdate()).isTrue(); assertThat(materialConfig.isInvertFilter()).isFalse(); assertThat(materialConfig.isShallowClone()).isFalse(); } @Test public void migration99_shouldMigrateSvnMaterialsUnderConfigRepoAndRetainOnlyTheMinimalRequiredAttributes() throws Exception { String configXml = "<cruise schemaVersion='98'>" + "<config-repos>\n" + " <config-repo pluginId=\"json.config.plugin\" id=\"config-repo-1\">\n" + " <svn url=\"test-repo\" dest='dest' autoUpdate='true' checkexternals='false' invertFilter='true' materialName=\"foo\">\n" + " <filter>\n" + " <ignore pattern=\"asdsd\" />\n" + " </filter>\n" + " </svn>" + " </config-repo>\n" + "</config-repos>" + "</cruise>"; assertThat(configXml).contains("<filter>"); assertThat(configXml).contains("dest='dest'"); assertThat(configXml).contains("autoUpdate='true'"); assertThat(configXml).contains("invertFilter='true'"); String migratedContent = migrateXmlString(configXml, 98); CruiseConfig cruiseConfig = loader.deserializeConfig(migratedContent); MaterialConfig materialConfig = cruiseConfig.getConfigRepos().getConfigRepo("config-repo-1") .getMaterialConfig(); assertThat(migratedContent).doesNotContain("<filter>"); assertThat(migratedContent).doesNotContain("dest='dest'"); assertThat(migratedContent).doesNotContain("invertFilter='true'"); assertThat(materialConfig.getFolder()).isNull(); assertThat(materialConfig.filter().size()).isEqualTo(0); assertThat(materialConfig.isAutoUpdate()).isTrue(); assertThat(materialConfig.isInvertFilter()).isFalse(); } @Test public void migration99_shouldMigrateP4MaterialsUnderConfigRepoAndRetainOnlyTheMinimalRequiredAttributes() throws Exception { String configXml = "<cruise schemaVersion='98'>" + "<config-repos>\n" + " <config-repo pluginId=\"json.config.plugin\" id=\"config-repo-1\">\n" + " <p4 port=\"10.18.3.241:9999\" username=\"cruise\" password=\"password\" autoUpdate='true' invertFilter='true' dest=\"dest\">\n" + " <view><![CDATA[//depot/dev/... //lumberjack/...]]></view>\n" + " <filter>\n" + " <ignore pattern=\"asdsd\" />\n" + " </filter>\n" + " </p4>" + " </config-repo>\n" + "</config-repos>" + "</cruise>"; assertThat(configXml).contains("dest=\"dest\""); assertThat(configXml).contains("<filter>"); assertThat(configXml).contains("autoUpdate='true'"); assertThat(configXml).contains("invertFilter='true'"); String migratedContent = migrateXmlString(configXml, 98); CruiseConfig cruiseConfig = loader.deserializeConfig(migratedContent); MaterialConfig materialConfig = cruiseConfig.getConfigRepos().getConfigRepo("config-repo-1") .getMaterialConfig(); assertThat(migratedContent).doesNotContain("dest=\"dest\""); assertThat(migratedContent).doesNotContain("<filter>"); assertThat(migratedContent).doesNotContain("invertFilter='true'"); assertThat(materialConfig.getFolder()).isNull(); assertThat(materialConfig.filter().size()).isEqualTo(0); assertThat(materialConfig.isAutoUpdate()).isTrue(); assertThat(materialConfig.isInvertFilter()).isFalse(); } @Test public void migration99_shouldMigrateHgMaterialsUnderConfigRepoAndRetainOnlyTheMinimalRequiredAttributes() throws Exception { String configXml = "<cruise schemaVersion='98'>" + "<config-repos>\n" + " <config-repo pluginId=\"json.config.plugin\" id=\"config-repo-1\">\n" + " <hg url=\"test-repo\" dest='dest' autoUpdate='true' invertFilter='true' materialName=\"foo\">\n" + " <filter>\n" + " <ignore pattern=\"asdsd\" />\n" + " </filter>\n" + " </hg>" + " </config-repo>\n" + "</config-repos>" + "</cruise>"; assertThat(configXml).contains("<filter>"); assertThat(configXml).contains("dest='dest'"); assertThat(configXml).contains("autoUpdate='true'"); assertThat(configXml).contains("invertFilter='true'"); String migratedContent = migrateXmlString(configXml, 98); CruiseConfig cruiseConfig = loader.deserializeConfig(migratedContent); MaterialConfig materialConfig = cruiseConfig.getConfigRepos().getConfigRepo("config-repo-1") .getMaterialConfig(); assertThat(migratedContent).doesNotContain("<filter>"); assertThat(migratedContent).doesNotContain("dest='dest'"); assertThat(migratedContent).doesNotContain("invertFilter='true'"); assertThat(materialConfig.getFolder()).isNull(); assertThat(materialConfig.filter().size()).isEqualTo(0); assertThat(materialConfig.isAutoUpdate()).isTrue(); assertThat(materialConfig.isInvertFilter()).isFalse(); } @Test public void migration99_shouldMigrateTfsMaterialsUnderConfigRepoAndRetainOnlyTheMinimalRequiredAttributes() throws Exception { String configXml = "<cruise schemaVersion='98'>" + "<config-repos>\n" + " <config-repo pluginId=\"json.config.plugin\" id=\"config-repo-1\">\n" + " <tfs url='tfsurl' dest='dest' autoUpdate='true' invertFilter='true' username='foo' password='bar' projectPath='project-path'>\n" + " <filter>\n" + " <ignore pattern=\"asdsd\" />\n" + " </filter>\n" + " </tfs>" + " </config-repo>\n" + "</config-repos>" + "</cruise>"; assertThat(configXml).contains("<filter>"); assertThat(configXml).contains("dest='dest'"); assertThat(configXml).contains("autoUpdate='true'"); assertThat(configXml).contains("invertFilter='true'"); String migratedContent = migrateXmlString(configXml, 98); CruiseConfig cruiseConfig = loader.deserializeConfig(migratedContent); MaterialConfig materialConfig = cruiseConfig.getConfigRepos().getConfigRepo("config-repo-1") .getMaterialConfig(); assertThat(migratedContent).doesNotContain("<filter>"); assertThat(migratedContent).doesNotContain("dest='dest'"); assertThat(migratedContent).doesNotContain("invertFilter='true'"); assertThat(materialConfig.getFolder()).isNull(); assertThat(materialConfig.filter().size()).isEqualTo(0); assertThat(materialConfig.isAutoUpdate()).isTrue(); assertThat(materialConfig.isInvertFilter()).isFalse(); } @Test public void migration99_shouldMigrateScmMaterialsUnderConfigRepoAndRetainOnlyTheMinimalRequiredAttributes() throws Exception { String configXml = "<cruise schemaVersion='98'>" + "<config-repos>\n" + " <config-repo pluginId=\"json.config.plugin\" id=\"config-repo-1\">\n" + " <scm ref='some-ref' dest='dest'>\n" + " <filter>\n" + " <ignore pattern=\"asdsd\" />\n" + " </filter>\n" + " </scm>" + " </config-repo>\n" + "</config-repos>" + "</cruise>"; assertThat(configXml).contains("<filter>"); assertThat(configXml).contains("dest='dest'"); String migratedContent = migrateXmlString(configXml, 98); CruiseConfig cruiseConfig = loader.deserializeConfig(migratedContent); MaterialConfig materialConfig = cruiseConfig.getConfigRepos().getConfigRepo("config-repo-1") .getMaterialConfig(); assertThat(migratedContent).doesNotContain("<filter>"); assertThat(migratedContent).doesNotContain("dest='dest'"); assertThat(materialConfig.getFolder()).isNull(); assertThat(materialConfig.filter().size()).isEqualTo(0); } @Test public void shouldRemoveAgentWithDuplicateElasticAgentId_asPartOf102To103Migration() throws Exception { String configXml = "<cruise schemaVersion='102'>" + "<agents>\n" + " <agent hostname=\"hostname\" ipaddress=\"127.0.0.1\" uuid=\"c46a08a7-921c-4e77-b748-6128975a3e7d\" elasticAgentId=\"16649813-4cb3-4682-8702-8e202824dd73\" elasticPluginId=\"elastic-plugin-id\" />\n" + " <agent hostname=\"hostname\" ipaddress=\"127.0.0.1\" uuid=\"c46a08a7-921c-4e77-b748-6128975a3e7e\" elasticAgentId=\"16649813-4cb3-4682-8702-8e202824dd73\" elasticPluginId=\"elastic-plugin-id\" />\n" + " <agent hostname=\"hostname\" ipaddress=\"127.0.0.1\" uuid=\"537d36f9-bf4b-48b2-8d09-5d20357d4f16\" elasticAgentId=\"a38d2559-0703-4e69-a30d-a21245d740af\" elasticPluginId=\"elastic-plugin-id\" />\n" + " <agent hostname=\"hostname\" ipaddress=\"127.0.0.1\" uuid=\"c46a08a7-921c-4e77-b748-6128975a3e7f\" elasticAgentId=\"16649813-4cb3-4682-8702-8e202824dd73\" elasticPluginId=\"elastic-plugin-id\" />\n" + " <agent hostname=\"hostname\" ipaddress=\"127.0.0.1\" uuid=\"537d36f9-bf4b-48b2-8d09-5d20357d4f17\" elasticAgentId=\"a38d2559-0703-4e69-a30d-a21245d740af\" elasticPluginId=\"elastic-plugin-id\" />\n" + " </agents>" + "</cruise>"; //before migration should contain 5 elastic agents 3 duplicates assertThat(configXml).contains("c46a08a7-921c-4e77-b748-6128975a3e7d"); assertThat(configXml).contains("537d36f9-bf4b-48b2-8d09-5d20357d4f16"); assertThat(configXml).contains("c46a08a7-921c-4e77-b748-6128975a3e7e"); assertThat(configXml).contains("c46a08a7-921c-4e77-b748-6128975a3e7f"); assertThat(configXml).contains("537d36f9-bf4b-48b2-8d09-5d20357d4f17"); String migratedContent = migrateXmlString(configXml, 102); //after migration should contain 2 unique elastic agents assertThat(configXml).contains("c46a08a7-921c-4e77-b748-6128975a3e7d"); assertThat(configXml).contains("537d36f9-bf4b-48b2-8d09-5d20357d4f16"); //after migration should remove 3 duplicate elastic agents assertThat(migratedContent).doesNotContain("c46a08a7-921c-4e77-b748-6128975a3e7e"); assertThat(migratedContent).doesNotContain("c46a08a7-921c-4e77-b748-6128975a3e7f"); assertThat(migratedContent).doesNotContain("537d36f9-bf4b-48b2-8d09-5d20357d4f17"); } @Test public void shouldIntroduceTypeOnBuildArtifacts_asPartOf106Migration() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"105\">\n" + " <server artifactsdir=\"artifacts\"/>\n" + " <pipelines>" + " <pipeline name=\"foo\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"some_stage\">" + " <jobs>" + " <job name=\"some_job\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " <artifacts>" + " <artifact src='foo.txt' dest='cruise-output' />" + " <artifact src='dir/**' dest='dir' />" + " <artifact src='build' />" + " </artifacts>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </pipelines>" + "</cruise>"; String migratedContent = migrateXmlString(configXml, 105); assertThat(migratedContent).contains("<artifact type=\"build\" src=\"foo.txt\" dest=\"cruise-output\"/>"); assertThat(migratedContent).contains("<artifact type=\"build\" src=\"dir/**\" dest=\"dir\"/>"); assertThat(migratedContent).contains("<artifact type=\"build\" src=\"build\"/>"); } @Test public void shouldConvertTestTagToArtifactWithTypeOnTestArtifacts_asPartOf106Migration() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"105\">\n" + " <server artifactsdir=\"artifacts\"/>\n" + " <pipelines>" + " <pipeline name=\"foo\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"some_stage\">" + " <jobs>" + " <job name=\"some_job\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " <artifacts>" + " <test src='foo.txt' dest='cruise-output' />" + " <test src='dir/**' dest='dir' />" + " <test src='build' />" + " </artifacts>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </pipelines>" + "</cruise>"; String migratedContent = migrateXmlString(configXml, 105); assertThat(migratedContent).contains("<artifact type=\"test\" src=\"foo.txt\" dest=\"cruise-output\"/>"); assertThat(migratedContent).contains("<artifact type=\"test\" src=\"dir/**\" dest=\"dir\"/>"); assertThat(migratedContent).contains("<artifact type=\"test\" src=\"build\"/>"); } @Test public void shouldConvertPluggableArtifactTagToArtifactWithTypeOnPluggableArtifacts_asPartOf106Migration() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"105\">\n" + " <server artifactsdir=\"artifacts\"/>\n" + " <artifactStores>\n" + " <artifactStore id=\"foo\" pluginId=\"cd.go.artifact.docker.registry\">\n" + " <property>\n" + " <key>RegistryURL</key>\n" + " <value>http://foo</value>\n" + " </property>\n" + " </artifactStore>\n" + " </artifactStores>" + " <pipelines>" + " <pipeline name=\"foo\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"some_stage\">" + " <jobs>" + " <job name=\"some_job\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " <artifacts>" + " <pluggableArtifact id='artifactId1' storeId='foo' />" + " <pluggableArtifact id='artifactId2' storeId='foo'>" + " <property>" + " <key>BuildFile</key>" + " <value>foo.json</value>" + " </property>" + " </pluggableArtifact>" + " <pluggableArtifact id='artifactId3' storeId='foo'>" + " <property>" + " <key>SecureProperty</key>" + " <encryptedValue>trMHp15AjUE=</encryptedValue>" + " </property>" + " </pluggableArtifact>" + " </artifacts>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </pipelines>" + "</cruise>"; String migratedContent = migrateXmlString(configXml, 105, 106); String artifactId2 = "<artifact type=\"external\" id=\"artifactId2\" storeId=\"foo\">" + " <property>" + " <key>BuildFile</key>" + " <value>foo.json</value>" + " </property>" + " </artifact>"; String artifactId3 = "<artifact type=\"external\" id=\"artifactId3\" storeId=\"foo\">" + " <property>" + " <key>SecureProperty</key>" + " <encryptedValue>trMHp15AjUE=</encryptedValue>" + " </property>" + " </artifact>"; assertThat(migratedContent).contains("<artifact type=\"external\" id=\"artifactId1\" storeId=\"foo\"/>"); assertThat(migratedContent).contains(artifactId2); assertThat(migratedContent).contains(artifactId3); } @Test public void shouldAddTypeAttributeOnFetchArtifactTag_asPartOf107Migration() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"106\">\n" + " <server artifactsdir=\"artifacts\"/>\n" + " <pipelines>" + " <pipeline name=\"foo\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"stage1\">" + " <jobs>" + " <job name=\"job1\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " <artifacts>" + " <artifact type='build' src='foo/**' dest='cruise-output' />" + " </artifacts>" + " </job>" + " </jobs>" + " </stage>" + " <stage name=\"stage2\">" + " <jobs>" + " <job name=\"job2\">" + " <tasks>" + " <exec command=\"ls\"/>" + " <fetchartifact pipeline='foo' stage='stage1' job='job1' srcfile='foo/foo.txt'/>" + " <fetchartifact pipeline='foo' stage='stage1' job='job1' srcdir='foo'/>" + " <fetchartifact stage='stage1' job='job1' srcdir='foo' dest='dest_on_agent'/>" + " </tasks>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </pipelines>" + "</cruise>"; String migratedContent = migrateXmlString(configXml, 106); assertThat(migratedContent).contains( "<fetchartifact artifactOrigin=\"gocd\" pipeline=\"foo\" stage=\"stage1\" job=\"job1\" srcfile=\"foo/foo.txt\""); assertThat(migratedContent).contains( "<fetchartifact artifactOrigin=\"gocd\" pipeline=\"foo\" stage=\"stage1\" job=\"job1\" srcdir=\"foo\""); assertThat(migratedContent).contains( "<fetchartifact artifactOrigin=\"gocd\" stage=\"stage1\" job=\"job1\" srcdir=\"foo\" dest=\"dest_on_agent\""); } @Test public void shouldConvertFetchPluggableArtifactToFetchArtifactTagWithType_asPartOf107Migration() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, CryptoException { String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"106\">\n" + " <server artifactsdir=\"artifacts\"/>\n" + " <artifactStores>\n" + " <artifactStore id=\"foobar\" pluginId=\"cd.go.artifact.docker.registry\">\n" + " <property>\n" + " <key>RegistryURL</key>\n" + " <value>http://foo</value>\n" + " </property>\n" + " </artifactStore>\n" + " </artifactStores>" + " <pipelines>" + " <pipeline name=\"foo\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"stage1\">" + " <jobs>" + " <job name=\"job1\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " <artifacts>" + " <artifact type='external' id='artifactId1' storeId='foobar' />" + " <artifact type='external' id='artifactId2' storeId='foobar' />" + " <artifact type='external' id='artifactId3' storeId='foobar' />" + " </artifacts>" + " </job>" + " </jobs>" + " </stage>" + " <stage name=\"stage2\">" + " <jobs>" + " <job name=\"job2\">" + " <tasks>" + " <exec command=\"ls\"/>" + " <fetchPluggableArtifact pipeline='foo' stage='stage1' job='job1' artifactId='artifactId1'/>" + " <fetchPluggableArtifact pipeline='foo' stage='stage1' job='job1' artifactId='artifactId2'>" + " <configuration>" + " <property>" + " <key>dest</key>" + " <value>destination</value>" + " </property>" + " </configuration>" + " </fetchPluggableArtifact>" + " <fetchPluggableArtifact pipeline='foo' stage='stage1' job='job1' artifactId='artifactId3'>" + " <configuration>" + " <property>" + " <key>SomeSecureProperty</key>" + " <encryptedValue>trMHp15AjUE=</encryptedValue>" + " </property>" + " </configuration>" + " </fetchPluggableArtifact>" + " </tasks>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </pipelines>" + "</cruise>"; String migratedContent = migrateXmlString(configXml, 106); String artifactId2 = "<fetchartifact artifactOrigin=\"external\" pipeline=\"foo\" stage=\"stage1\" job=\"job1\" artifactId=\"artifactId2\">" + " <configuration>" + " <property>" + " <key>dest</key>" + " <value>destination</value>" + " </property>" + " </configuration>" + " </fetchartifact>"; String artifactId3 = "<fetchartifact artifactOrigin=\"external\" pipeline=\"foo\" stage=\"stage1\" job=\"job1\" artifactId=\"artifactId3\">" + " <configuration>" + " <property>" + " <key>SomeSecureProperty</key>" + " <encryptedValue>" + new GoCipher().encrypt("abcd") + "</encryptedValue>" + " </property>" + " </configuration>" + " </fetchartifact>"; assertThat(migratedContent).contains( "<fetchartifact artifactOrigin=\"external\" pipeline=\"foo\" stage=\"stage1\" job=\"job1\" artifactId=\"artifactId1\""); assertThat(migratedContent).contains(artifactId2); assertThat(migratedContent).contains(artifactId3); } @Test public void shouldAddTheConfigurationSubTagOnExternalArtifacts_asPartOf108Migration() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"107\">\n" + " <server artifactsdir=\"artifacts\"/>\n" + " <artifactStores>\n" + " <artifactStore id=\"foobar\" pluginId=\"cd.go.artifact.docker.registry\">\n" + " <property>\n" + " <key>RegistryURL</key>\n" + " <value>http://foo</value>\n" + " </property>\n" + " </artifactStore>\n" + " </artifactStores>" + " <pipelines>" + " <pipeline name=\"p1\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"s1\">" + " <jobs>" + " <job name=\"j1\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " <artifacts>" + " <artifact type=\"external\" id=\"artifactId1\" storeId=\"foobar\" />" + " <artifact type=\"external\" id=\"artifactId2\" storeId=\"foobar\">" + " <property>" + " <key>BuildFile</key>" + " <value>foo.json</value>" + " </property>" + " </artifact>" + " </artifacts>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </pipelines>" + "</cruise>"; String migratedContent = migrateXmlString(configXml, 107); String migratedArtifact1 = "<artifact type=\"external\" id=\"artifactId1\" storeId=\"foobar\"/>"; String migratedArtifact2 = "<artifact type=\"external\" id=\"artifactId2\" storeId=\"foobar\"><configuration>" + " <property>" + " <key>BuildFile</key>" + " <value>foo.json</value>" + " </property>" + " </configuration></artifact>"; assertThat(migratedContent).contains(migratedArtifact1); assertThat(migratedContent).contains(migratedArtifact2); } @Test public void shouldOnlyUpdateSchemaVersionForMigration114() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { String configContent = "<pipelines>" + " <pipeline name=\"p1\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"s1\">" + " <jobs>" + " <job name=\"j1\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </pipelines>"; String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"113\">\n" + configContent + "</cruise>"; String migratedContent = migrateXmlString(configXml, 113, 114); assertThat(migratedContent).contains("<cruise schemaVersion=\"114\""); assertThat(migratedContent).contains(configContent); } @Test public void shouldRenameOriginAttributeOnFetchArtifactToArtifactOrigin_AsPartOf110To111Migration() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"110\">\n" + " <server artifactsdir=\"artifacts\"/>\n" + " <artifactStores>\n" + " <artifactStore id=\"foobar\" pluginId=\"cd.go.artifact.docker.registry\">\n" + " <property>\n" + " <key>RegistryURL</key>\n" + " <value>http://foo</value>\n" + " </property>\n" + " </artifactStore>\n" + " </artifactStores>" + " <pipelines>" + " <pipeline name=\"foo\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"stage1\">" + " <jobs>" + " <job name=\"job1\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " <artifacts>" + " <artifact type='build' src='foo' dest='bar'/>" + " <artifact type='external' id='artifactId1' storeId='foobar' />" + " </artifacts>" + " </job>" + " </jobs>" + " </stage>" + " <stage name=\"stage2\">" + " <jobs>" + " <job name=\"job2\">" + " <tasks>" + " <exec command=\"ls\"/>" + " <fetchartifact origin='gocd' pipeline='foo' stage='stage1' job='job1' srcdir='dist/zip' dest='target'/>" + " <fetchartifact origin='external' pipeline='foo' stage='stage1' job='job1' artifactId='artifactId1'>" + " <configuration>" + " <property>" + " <key>dest</key>" + " <value>destination</value>" + " </property>" + " </configuration>" + " </fetchartifact>" + " </tasks>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </pipelines>" + "</cruise>"; String migratedContent = migrateXmlString(configXml, 110); assertThat(migratedContent).contains( "<fetchartifact artifactOrigin=\"gocd\" pipeline=\"foo\" stage=\"stage1\" job=\"job1\" srcdir=\"dist/zip\" dest=\"target\"/> "); assertThat(migratedContent).contains( "<fetchartifact artifactOrigin=\"external\" pipeline=\"foo\" stage=\"stage1\" job=\"job1\" artifactId=\"artifactId1\">"); } @Test public void shouldRemoveMaterialNameFromConfigRepos_AsPartOf114To115Migration() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion='114'>" + "<config-repos>\n" + " <config-repo pluginId=\"yaml.config.plugin\" id=\"test\">\n" + " <git url=\"test\" branch=\"test\" materialName=\"test\" />\n" + " </config-repo>\n" + " <config-repo pluginId=\"yaml.config.plugin\" id=\"test1\">\n" + " <svn url=\"test\" username=\"\" materialName=\"test\" />\n" + " </config-repo>\n" + " <config-repo pluginId=\"yaml.config.plugin\" id=\"test2\">\n" + " <hg url=\"test\" materialName=\"test\" />\n" + " </config-repo>\n" + " <config-repo pluginId=\"yaml.config.plugin\" id=\"asd\">\n" + " <tfs url=\"test\" username=\"admin\" domain=\"test\" encryptedPassword=\"AES:09M8nDpEgOgRGVVWAnEiMQ==:7lAsVu5nZ6iYhoZ4Alwc5g==\" projectPath=\"test\" materialName=\"test\" />\n" + " </config-repo>\n" + " <config-repo pluginId=\"yaml.config.plugin\" id=\"asdasd\">\n" + " <p4 port=\"test\" username=\"admin\" encryptedPassword=\"AES:A7h8pqjGyz372Kogx5xX/w==:tG1WNNd680UyqOUM1BVrfQ==\" materialName=\"test\">\n" + " <view><![CDATA[<h1>test</h1>]]></view>\n" + " </p4>\n" + " </config-repo>\n" + " </config-repos>\n" + "</cruise>"; String migratedContent = migrateXmlString(configXml, 114); assertStringContainsIgnoringCarriageReturn(migratedContent, "<config-repos>\n" + " <config-repo pluginId=\"yaml.config.plugin\" id=\"test\">\n" + " <git url=\"test\" branch=\"test\"/>\n" + " </config-repo>\n" + " <config-repo pluginId=\"yaml.config.plugin\" id=\"test1\">\n" + " <svn url=\"test\" username=\"\"/>\n" + " </config-repo>\n" + " <config-repo pluginId=\"yaml.config.plugin\" id=\"test2\">\n" + " <hg url=\"test\"/>\n" + " </config-repo>\n" + " <config-repo pluginId=\"yaml.config.plugin\" id=\"asd\">\n" + " <tfs url=\"test\" username=\"admin\" domain=\"test\" encryptedPassword=\"AES:09M8nDpEgOgRGVVWAnEiMQ==:7lAsVu5nZ6iYhoZ4Alwc5g==\" projectPath=\"test\"/>\n" + " </config-repo>\n" + " <config-repo pluginId=\"yaml.config.plugin\" id=\"asdasd\">\n" + " <p4 port=\"test\" username=\"admin\" encryptedPassword=\"AES:A7h8pqjGyz372Kogx5xX/w==:tG1WNNd680UyqOUM1BVrfQ==\">\n" + " <view><h1>test</h1></view>\n" + " </p4>\n" + " </config-repo>\n" + " </config-repos>"); } @Test public void shouldOnlyUpdateSchemaVersionForMigration116() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { String configContent = "<pipelines>" + " <pipeline name=\"p1\">" + " <materials> " + " <hg url=\"blah\"/>" + " </materials> " + " <stage name=\"s1\">" + " <jobs>" + " <job name=\"j1\">" + " <tasks>" + " <exec command=\"ls\"/>" + " </tasks>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </pipelines>"; String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"115\">\n" + configContent + "</cruise>"; String migratedContent = migrateXmlString(configXml, 115, 116); assertThat(migratedContent).contains("<cruise schemaVersion=\"116\""); assertThat(migratedContent).contains(configContent); } @Test public void shouldRenameProfilesToAgentProfilesAsPartOfMigration120() throws Exception { String configContent = "<elastic jobStarvationTimeout=\"1\">\n" + " <profiles>\n" + " <profile clusterProfileId=\"4ca85ebb-3fad-45f6-a4fc-0894f714ecdc\" id=\"ecs-gocd-dev-build-dind\" pluginId=\"com.thoughtworks.gocd.elastic-agent.ecs\">\n" + " <property>\n" + " <key>Image</key>\n" + " <value>docker.gocd.io/gocddev/gocd-dev-build:centos-7-v2.0.67</value>\n" + " </property>\n" + " </profile>\n" + " <profile clusterProfileId=\"4ca85ebb-3fad-45f6-a4fc-0894f714ecdc\" id=\"ecs-gocd-dev-build-dind-docker-compose\" pluginId=\"com.thoughtworks.gocd.elastic-agent.ecs\">\n" + " <property>\n" + " <key>Image</key>\n" + " <value>docker.gocd.io/gocddev/gocd-dev-build:centos-7-v2.0.67</value>\n" + " </property>\n" + " </profile>\n" + " </profiles>\n" + " <clusterProfiles>\n" + " <clusterProfile id=\"no-op-cluster-for-cd.go.contrib.elasticagent.kubernetes\" pluginId=\"cd.go.contrib.elasticagent.kubernetes\"/>\n" + " <clusterProfile id=\"4ca85ebb-3fad-45f6-a4fc-0894f714ecdc\" pluginId=\"com.thoughtworks.gocd.elastic-agent.ecs\">\n" + " <property>\n" + " <key>GoServerUrl</key>\n" + " <value>https://build.gocd.io:8154/go</value>\n" + " </property>\n" + " </clusterProfile>\n" + " </clusterProfiles>\n" + " </elastic>"; String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"119\">\n" + configContent + "</cruise>"; String migratedContent = migrateXmlString(configXml, 119); assertThat(migratedContent).doesNotContain("<profiles"); assertThat(migratedContent).doesNotContain("<profile"); assertThat(migratedContent).contains("<agentProfiles"); assertThat(migratedContent).contains("<agentProfile"); CruiseConfig cruiseConfig = loader.deserializeConfig(migratedContent); assertThat(cruiseConfig.getElasticConfig().getJobStarvationTimeout()).isEqualTo(1 * 60 * 1000); assertThat(cruiseConfig.getElasticConfig().getProfiles()).hasSize(2); assertThat(cruiseConfig.getElasticConfig().getProfiles().find("ecs-gocd-dev-build-dind")).isNotNull(); assertThat(cruiseConfig.getElasticConfig().getProfiles().find("ecs-gocd-dev-build-dind-docker-compose")) .isNotNull(); } @Test public void shouldRemovePluginIdFromAgentProfilesMigration122() throws Exception { String configContent = "<elastic jobStarvationTimeout=\"1\">\n" + " <agentProfiles>\n" + " <agentProfile clusterProfileId=\"4ca85ebb-3fad-45f6-a4fc-0894f714ecdc\" id=\"ecs-gocd-dev-build-dind\" pluginId=\"com.thoughtworks.gocd.elastic-agent.ecs\">\n" + " <property>\n" + " <key>Image</key>\n" + " <value>docker.gocd.io/gocddev/gocd-dev-build:centos-7-v2.0.67</value>\n" + " </property>\n" + " </agentProfile>\n" + " <agentProfile clusterProfileId=\"4ca85ebb-3fad-45f6-a4fc-0894f714ecdc\" id=\"ecs-gocd-dev-build-dind-docker-compose\" pluginId=\"com.thoughtworks.gocd.elastic-agent.ecs\">\n" + " <property>\n" + " <key>Image</key>\n" + " <value>docker.gocd.io/gocddev/gocd-dev-build:centos-7-v2.0.67</value>\n" + " </property>\n" + " </agentProfile>\n" + " </agentProfiles>\n" + " </elastic>"; String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"121\">\n" + configContent + "</cruise>"; String migratedContent = migrateXmlString(configXml, 121); assertThat(migratedContent).doesNotContain("pluginId=\"com.thoughtworks.gocd.elastic-agent.ecs\""); CruiseConfig cruiseConfig = loader.deserializeConfig(migratedContent); assertThat(cruiseConfig.getElasticConfig().getJobStarvationTimeout()).isEqualTo(1 * 60 * 1000); assertThat(cruiseConfig.getElasticConfig().getProfiles()).hasSize(2); assertThat(cruiseConfig.getElasticConfig().getProfiles().find("ecs-gocd-dev-build-dind")).isNotNull(); assertThat(cruiseConfig.getElasticConfig().getProfiles().find("ecs-gocd-dev-build-dind-docker-compose")) .isNotNull(); } @Test public void shouldMigrateEverythingAsItIs_Migration120To121() throws Exception { String originalConfig = "<pipelines group=\"first\">" + " <pipeline name=\"Test\" template=\"test_template\">" + " <materials>" + " <git url=\"http://\" dest=\"dest_dir14\" />" + " </materials>" + " </pipeline>" + " </pipelines>" + " <templates>" + " <pipeline name=\"test_template\">" + " <stage name=\"Functional\">" + " <jobs>" + " <job name=\"Functional\">" + " <tasks>" + " <exec command=\"echo\" args=\"Hello World!!!\" />" + " </tasks>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </templates>"; String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"120\">" + originalConfig + "</cruise>"; String expectedConfig = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"121\">" + originalConfig + "</cruise>"; final String migratedXml = migrateXmlString(configXml, 120, 121); XmlAssert.assertThat(migratedXml).and(expectedConfig).areIdentical(); } @Test public void shouldMigrateEverythingAsItIs_Migration122To123() throws Exception { String originalConfig = "<pipelines group=\"first\">" + " <pipeline name=\"Test\" template=\"test_template\">" + " <materials>" + " <git url=\"http://\" dest=\"dest_dir14\" />" + " </materials>" + " </pipeline>" + " </pipelines>" + " <templates>" + " <pipeline name=\"test_template\">" + " <stage name=\"Functional\">" + " <jobs>" + " <job name=\"Functional\">" + " <tasks>" + " <exec command=\"echo\" args=\"Hello World!!!\" />" + " </tasks>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </templates>"; String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"122\">" + originalConfig + "</cruise>"; String expectedConfig = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"123\">" + originalConfig + "</cruise>"; final String migratedXml = migrateXmlString(configXml, 122, 123); XmlAssert.assertThat(migratedXml).and(expectedConfig).areIdentical(); } @Test public void shouldMigrateEverythingAsItIs_Migration123To124() throws Exception { String originalConfig = "<pipelines group=\"first\">" + " <pipeline name=\"Test\" template=\"test_template\">" + " <materials>" + " <git url=\"http://\" dest=\"dest_dir14\" />" + " </materials>" + " </pipeline>" + " </pipelines>" + " <templates>" + " <pipeline name=\"test_template\">" + " <stage name=\"Functional\">" + " <jobs>" + " <job name=\"Functional\">" + " <tasks>" + " <exec command=\"echo\" args=\"Hello World!!!\" />" + " </tasks>" + " </job>" + " </jobs>" + " </stage>" + " </pipeline>" + " </templates>"; String configXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"123\">" + originalConfig + "</cruise>"; String expectedConfig = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<cruise schemaVersion=\"124\">" + originalConfig + "</cruise>"; final String migratedXml = migrateXmlString(configXml, 123, 124); XmlAssert.assertThat(migratedXml).and(expectedConfig).areIdentical(); } private void assertStringsIgnoringCarriageReturnAreEqual(String expected, String actual) { assertThat(actual.replaceAll("\\r", "").trim()).isEqualTo(expected.replaceAll("\\r", "").trim()); } @Test public void shouldMigrateAnEmptyArtifactSourceToStar() throws Exception { String migratedContent = migrateXmlString(configWithArtifactSourceAs(""), 28); CruiseConfig cruiseConfig = loader.deserializeConfig(migratedContent); JobConfig plan = cruiseConfig.jobConfigByName("pipeline", "stage", "job", true); assertThat(plan.artifactConfigs().getBuiltInArtifactConfigs().get(0).getSource()).isEqualTo("*"); } @Test public void shouldMigrateAnArtifactSourceWithJustWhitespaceToStar() throws Exception { String migratedContent = migrateXmlString(configWithArtifactSourceAs(" \t "), 28); CruiseConfig cruiseConfig = loader.deserializeConfig(migratedContent); JobConfig plan = cruiseConfig.jobConfigByName("pipeline", "stage", "job", true); assertThat(plan.artifactConfigs().getBuiltInArtifactConfigs().get(0).getSource()).isEqualTo("*"); } private void assertStringContainsIgnoringCarriageReturn(String actual, String substring) { assertThat(actual.replaceAll("\\r", "")).contains(substring.replaceAll("\\r", "")); } private String migrateXmlString(String content, int fromVersion) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { return migrateXmlString(content, fromVersion, GoConfigSchema.currentSchemaVersion()); } private String migrateXmlString(String content, int fromVersion, int toVersion) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { GoConfigMigration upgrader = new GoConfigMigration(new TimeProvider(), ConfigElementImplementationRegistryMother.withNoPlugins()); Method upgrade = upgrader.getClass().getDeclaredMethod("upgrade", String.class, Integer.TYPE, Integer.TYPE); upgrade.setAccessible(true); return (String) upgrade.invoke(upgrader, content, fromVersion, toVersion); } }