/* * 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.server.service; import com.thoughtworks.go.config.*; import com.thoughtworks.go.config.materials.MaterialConfigs; import com.thoughtworks.go.config.materials.PackageMaterialConfig; import com.thoughtworks.go.config.materials.PluggableSCMMaterialConfig; import com.thoughtworks.go.config.materials.dependency.DependencyMaterialConfig; import com.thoughtworks.go.config.materials.git.GitMaterialConfig; 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.domain.config.*; import com.thoughtworks.go.domain.scm.SCM; import com.thoughtworks.go.helper.*; import com.thoughtworks.go.i18n.Localizer; import com.thoughtworks.go.listener.EntityConfigChangedListener; import com.thoughtworks.go.presentation.TriStateSelection; import com.thoughtworks.go.security.GoCipher; import com.thoughtworks.go.server.dao.DatabaseAccessHelper; import com.thoughtworks.go.server.domain.Username; import com.thoughtworks.go.server.service.result.DefaultLocalizedOperationResult; import com.thoughtworks.go.server.service.result.HttpLocalizedOperationResult; import com.thoughtworks.go.serverhealth.HealthStateScope; import com.thoughtworks.go.serverhealth.ServerHealthService; import com.thoughtworks.go.service.ConfigRepository; import com.thoughtworks.go.util.CachedDigestUtils; import com.thoughtworks.go.util.GoConfigFileHelper; import com.thoughtworks.go.util.GoConstants; import com.thoughtworks.go.util.SystemEnvironment; import org.eclipse.jgit.api.errors.GitAPIException; import org.junit.*; import org.junit.rules.ExpectedException; 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.ByteArrayOutputStream; import java.util.UUID; import static com.thoughtworks.go.util.TestUtils.contains; import static java.util.Arrays.asList; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.assertFalse; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.IsNull.nullValue; 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 PipelineConfigServiceIntegrationTest { static { new SystemEnvironment().setProperty(GoConstants.USE_COMPRESSED_JAVASCRIPT, "false"); } @Autowired private PipelineConfigService pipelineConfigService; @Autowired private GoConfigService goConfigService; @Autowired private GoConfigDao goConfigDao; @Autowired private DatabaseAccessHelper dbHelper; @Autowired private ConfigRepository configRepository; @Autowired private ConfigCache configCache; @Autowired private ConfigElementImplementationRegistry registry; @Autowired private GoPartialConfig goPartialConfig; @Autowired private CachedGoPartials cachedGoPartials; @Autowired private Localizer localizer; @Autowired private EntityHashingService entityHashingService; @Autowired private ServerHealthService serverHealthService; private GoConfigFileHelper configHelper; private PipelineConfig pipelineConfig; private Username user; private String headCommitBeforeUpdate; private HttpLocalizedOperationResult result; private String groupName = "jumbo"; private ConfigRepoConfig repoConfig1; private PartialConfig partialConfig; @Rule public ExpectedException thrown = ExpectedException.none(); private String remoteDownstreamPipelineName; private ConfigRepoConfig repoConfig2; @Before public void setup() throws Exception { cachedGoPartials.clear(); configHelper = new GoConfigFileHelper(); dbHelper.onSetUp(); configHelper.usingCruiseConfigDao(goConfigDao).initializeConfigFile(); configHelper.onSetUp(); goConfigService.forceNotifyListeners(); user = new Username(new CaseInsensitiveString("current")); pipelineConfig = GoConfigMother.createPipelineConfigWithMaterialConfig(UUID.randomUUID().toString(), new GitMaterialConfig("FOO")); goConfigService.addPipeline(pipelineConfig, groupName); repoConfig1 = new ConfigRepoConfig(MaterialConfigsMother.gitMaterialConfig("url"), XmlPartialConfigProvider.providerName); repoConfig2 = new ConfigRepoConfig(MaterialConfigsMother.gitMaterialConfig("url2"), XmlPartialConfigProvider.providerName); goConfigService.updateConfig(new UpdateConfigCommand() { @Override public CruiseConfig update(CruiseConfig cruiseConfig) throws Exception { cruiseConfig.getConfigRepos().add(repoConfig1); cruiseConfig.getConfigRepos().add(repoConfig2); return cruiseConfig; } }); GoCipher goCipher = new GoCipher(); goConfigService.updateServerConfig(new MailHost(goCipher), new LdapConfig(goCipher), new PasswordFileConfig("path"), false, goConfigService.configFileMd5(), "artifacts", null, null, "0", null, null, "foo"); UpdateConfigCommand command = goConfigService.modifyAdminPrivilegesCommand(asList(user.getUsername().toString()), new TriStateSelection(Admin.GO_SYSTEM_ADMIN, TriStateSelection.Action.add)); goConfigService.updateConfig(command); remoteDownstreamPipelineName = "remote-downstream"; partialConfig = PartialConfigMother.pipelineWithDependencyMaterial(remoteDownstreamPipelineName, pipelineConfig, new RepoConfigOrigin(repoConfig1, "repo1_r1")); goPartialConfig.onSuccessPartialConfig(repoConfig1, partialConfig); PartialConfig partialConfigFromRepo2 = PartialConfigMother.withPipeline("independent-pipeline", new RepoConfigOrigin(repoConfig2, "repo2_r1")); goPartialConfig.onSuccessPartialConfig(repoConfig2, partialConfigFromRepo2); result = new HttpLocalizedOperationResult(); headCommitBeforeUpdate = configRepository.getCurrentRevCommit().name(); } @After public void tearDown() throws Exception { for (PartialConfig partial : cachedGoPartials.lastValidPartials()) { assertThat(ErrorCollector.getAllErrors(partial).isEmpty(), is(true)); } for (PartialConfig partial : cachedGoPartials.lastKnownPartials()) { assertThat(ErrorCollector.getAllErrors(partial).isEmpty(), is(true)); } cachedGoPartials.clear(); configHelper.onTearDown(); dbHelper.onTearDown(); } @Test public void shouldCreatePipelineConfigWhenPipelineGroupExists() throws GitAPIException { GoConfigHolder goConfigHolderBeforeUpdate = goConfigDao.loadConfigHolder(); pipelineConfig = GoConfigMother.createPipelineConfigWithMaterialConfig(UUID.randomUUID().toString(), new GitMaterialConfig("FOO")); pipelineConfigService.createPipelineConfig(user, pipelineConfig, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(true)); assertThat(goConfigDao.loadConfigHolder(), is(not(goConfigHolderBeforeUpdate))); PipelineConfig savedPipelineConfig = goConfigDao.loadForEditing().getPipelineConfigByName(pipelineConfig.name()); assertThat(savedPipelineConfig, is(pipelineConfig)); assertThat(configRepository.getCurrentRevCommit().name(), is(not(headCommitBeforeUpdate))); assertThat(configRepository.getCurrentRevision().getUsername(), is(user.getDisplayName())); assertThat(configRepository.getCurrentRevision().getMd5(), is(not(goConfigHolderBeforeUpdate.config.getMd5()))); assertThat(configRepository.getCurrentRevision().getMd5(), is(goConfigDao.loadConfigHolder().config.getMd5())); assertThat(configRepository.getCurrentRevision().getMd5(), is(goConfigDao.loadConfigHolder().configForEdit.getMd5())); } @Test public void shouldCreatePipelineConfigWhenPipelineGroupDoesNotExist() throws GitAPIException { GoConfigHolder goConfigHolderBeforeUpdate = goConfigDao.loadConfigHolder(); PipelineConfig downstream = GoConfigMother.createPipelineConfigWithMaterialConfig(UUID.randomUUID().toString(), new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); pipelineConfigService.createPipelineConfig(user, downstream, result, "does-not-exist"); assertThat(result.toString(), result.isSuccessful(), is(true)); assertThat(goConfigDao.loadConfigHolder(), is(not(goConfigHolderBeforeUpdate))); PipelineConfig savedPipelineConfig = goConfigDao.loadForEditing().getPipelineConfigByName(downstream.name()); assertThat(savedPipelineConfig, is(downstream)); assertThat(configRepository.getCurrentRevCommit().name(), is(not(headCommitBeforeUpdate))); assertThat(configRepository.getCurrentRevision().getUsername(), is(user.getDisplayName())); } @Test public void shouldUpdatePipelineConfigWhenDependencyMaterialHasTemplateDefined() throws Exception { CaseInsensitiveString templateName = new CaseInsensitiveString("template_with_param"); saveTemplateWithParamToConfig(templateName); pipelineConfig.clear(); pipelineConfig.setTemplateName(templateName); pipelineConfig.addParam(new ParamConfig("SOME_PARAM", "SOME_VALUE")); CruiseConfig cruiseConfig = goConfigDao.loadConfigHolder().configForEdit; cruiseConfig.update(groupName, pipelineConfig.name().toString(), pipelineConfig); saveConfig(cruiseConfig); PipelineConfig downstream = GoConfigMother.createPipelineConfigWithMaterialConfig("downstream", new DependencyMaterialConfig(pipelineConfig.name(), new CaseInsensitiveString("stage"))); pipelineConfigService.createPipelineConfig(user, downstream, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(true)); assertTrue(downstream.materialConfigs().first().errors().isEmpty()); } @Test public void shouldUpdatePipelineConfigWithDependencyMaterialWhenUpstreamPipelineHasTemplateDefinedANDUpstreamPipelineIsCreatedUsingCreatePipelineFlow() throws Exception { CaseInsensitiveString templateName = new CaseInsensitiveString("template_with_param"); saveTemplateWithParamToConfig(templateName); MaterialConfigs materialConfigs = new MaterialConfigs(); materialConfigs.add(new DependencyMaterialConfig(pipelineConfig.name(), new CaseInsensitiveString("stage"))); PipelineConfig upstream = new PipelineConfig(new CaseInsensitiveString("upstream"), materialConfigs); upstream.setTemplateName(templateName); upstream.addParam(new ParamConfig("SOME_PARAM", "SOME_VALUE")); pipelineConfigService.createPipelineConfig(user, upstream, result, groupName); PipelineConfig downstream = GoConfigMother.createPipelineConfigWithMaterialConfig("downstream", new DependencyMaterialConfig(upstream.name(), new CaseInsensitiveString("stage"))); pipelineConfigService.createPipelineConfig(user, downstream, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(true)); assertTrue(downstream.materialConfigs().first().errors().isEmpty()); } @Test public void shouldUpdatePipelineConfigWhenFetchTaskFromUpstreamHasPipelineWithTemplateDefined() throws Exception { CaseInsensitiveString templateName = new CaseInsensitiveString("template_with_param"); saveTemplateWithParamToConfig(templateName); pipelineConfig.clear(); pipelineConfig.setTemplateName(templateName); pipelineConfig.addParam(new ParamConfig("SOME_PARAM", "SOME_VALUE")); CaseInsensitiveString stage = new CaseInsensitiveString("stage"); CaseInsensitiveString job = new CaseInsensitiveString("job"); CruiseConfig cruiseConfig = goConfigDao.loadConfigHolder().configForEdit; cruiseConfig.update(groupName, pipelineConfig.name().toString(), pipelineConfig); saveConfig(cruiseConfig); PipelineConfig downstream = GoConfigMother.createPipelineConfigWithMaterialConfig("downstream", new DependencyMaterialConfig(pipelineConfig.name(), stage)); downstream.getStage(stage).getJobs().first().addTask(new FetchTask(pipelineConfig.name(), stage, job, "src", "dest")); pipelineConfigService.createPipelineConfig(user, downstream, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(true)); assertTrue(downstream.materialConfigs().first().errors().isEmpty()); } @Test public void shouldNotCreatePipelineConfigWhenAPipelineBySameNameAlreadyExists() throws GitAPIException { GoConfigHolder goConfigHolderBeforeUpdate = goConfigDao.loadConfigHolder(); PipelineConfig pipelineBeingCreated = GoConfigMother.createPipelineConfigWithMaterialConfig(pipelineConfig.name().toLower(), new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); pipelineConfigService.createPipelineConfig(user, pipelineBeingCreated, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); assertFalse(pipelineBeingCreated.errors().isEmpty()); assertThat(pipelineBeingCreated.errors().on(PipelineConfig.NAME), is(String.format("You have defined multiple pipelines named '%s'. Pipeline names must be unique. Source(s): [cruise-config.xml]", pipelineConfig.name()))); assertThat(goConfigDao.loadConfigHolder(), is(goConfigHolderBeforeUpdate)); assertThat(configRepository.getCurrentRevCommit().name(), is(headCommitBeforeUpdate)); } @Test public void shouldNotCreatePipelineConfigWhenInvalidGroupNameIsPassed() throws GitAPIException { GoConfigHolder goConfigHolderBeforeUpdate = goConfigDao.loadConfigHolder(); PipelineConfig pipelineBeingCreated = GoConfigMother.createPipelineConfigWithMaterialConfig(pipelineConfig.name().toLower(), new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); pipelineConfigService.createPipelineConfig(user, pipelineBeingCreated, result, "%$-with-invalid-characters"); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); assertFalse(pipelineBeingCreated.errors().isEmpty()); assertThat(pipelineBeingCreated.errors().on(PipelineConfigs.GROUP), contains("Invalid group name '%$-with-invalid-characters'")); assertThat(goConfigDao.loadConfigHolder(), is(goConfigHolderBeforeUpdate)); assertThat(configRepository.getCurrentRevCommit().name(), is(headCommitBeforeUpdate)); } @Test public void shouldShowThePipelineConfigErrorMessageWhenPipelineBeingCreatedHasErrors() throws GitAPIException { ExecTask execTask = new ExecTask("ls", "-al", "#{foo}"); FetchTask fetchTask = new FetchTask(pipelineConfig.name(), new CaseInsensitiveString("stage"), new CaseInsensitiveString("job"), "srcfile", "/usr/dest"); JobConfig job = new JobConfig("default-job"); job.addTask(execTask); job.addTask(fetchTask); StageConfig stage = new StageConfig(new CaseInsensitiveString("default-stage"), new JobConfigs(job)); PipelineConfig pipeline = GoConfigMother.createPipelineConfigWithMaterialConfig(UUID.randomUUID().toString(), new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); pipeline.addParam(new ParamConfig("foo", ".")); pipeline.addStageWithoutValidityAssertion(stage); pipelineConfigService.createPipelineConfig(user, pipeline, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); String expectedError = String.format("Task of job 'default-job' in stage 'default-stage' of pipeline '%s' has dest path '/usr/dest' which is outside the working directory.", pipeline.name()); assertThat(fetchTask.errors().on("dest"), is(expectedError)); } @Test public void shouldShowThePipelineConfigErrorMessageWhenPipelineBeingCreatedHasErrorsOnEnvironmentVariables() throws GitAPIException { PipelineConfig pipeline = GoConfigMother.createPipelineConfigWithMaterialConfig(UUID.randomUUID().toString(), new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); pipeline.addEnvironmentVariable("", "PipelineEnvVar"); EnvironmentVariablesConfig stageVariables = new EnvironmentVariablesConfig(); EnvironmentVariableConfig stageVar = new EnvironmentVariableConfig("", "StageEnvVar"); stageVariables.add(stageVar); EnvironmentVariablesConfig jobVariables = new EnvironmentVariablesConfig(); EnvironmentVariableConfig jobVar = new EnvironmentVariableConfig("", "JobEnvVar"); jobVariables.add(jobVar); StageConfig stageConfig = pipeline.get(0); stageConfig.setVariables(stageVariables); JobConfig jobConfig = stageConfig.getJobs().get(0); jobConfig.setVariables(jobVariables); pipelineConfigService.createPipelineConfig(user, pipeline, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); assertThat(pipeline.getVariables().get(0).errors().firstError(), is(String.format("Environment Variable cannot have an empty name for pipeline '" + pipeline.name() + "'.", pipeline.name()))); assertThat(stageVar.errors().firstError(), is(String.format("Environment Variable cannot have an empty name for stage 'stage'.", pipeline.name()))); assertThat(jobVar.errors().firstError(), is(String.format("Environment Variable cannot have an empty name for job 'job'.", pipeline.name()))); } @Test public void shouldShowThePipelineConfigErrorMessageWhenPipelineBeingCreatedHasErrorsOnParameters() throws GitAPIException { PipelineConfig pipeline = GoConfigMother.createPipelineConfigWithMaterialConfig(UUID.randomUUID().toString(), new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); ParamConfig param = new ParamConfig("", "Foo"); pipeline.addParam(param); pipelineConfigService.createPipelineConfig(user, pipeline, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); assertThat(param.errors().firstError(), is(String.format("Parameter cannot have an empty name for pipeline '" + pipeline.name() + "'.", pipeline.name()))); } @Test public void shouldShowThePipelineConfigErrorMessageWhenPipelineBeingCreatedHasErrorsOnTrackingTool() throws GitAPIException { PipelineConfig pipeline = GoConfigMother.createPipelineConfigWithMaterialConfig(UUID.randomUUID().toString(), new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); TrackingTool trackingTool = new TrackingTool(); pipeline.setTrackingTool(trackingTool); pipelineConfigService.createPipelineConfig(user, pipeline, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); assertThat(trackingTool.errors().firstError(), is(String.format("Regex should be populated", pipeline.name()))); } @Test public void shouldShowThePipelineConfigErrorMessageWhenPipelineBeingCreatedHasErrorsOnArtifactPlans() throws GitAPIException { PipelineConfig pipeline = GoConfigMother.createPipelineConfigWithMaterialConfig(UUID.randomUUID().toString(), new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); JobConfig jobConfig = pipeline.get(0).getJobs().get(0); ArtifactPlans artifactPlans = new ArtifactPlans(); ArtifactPlan artifactPlan = new ArtifactPlan("", "/foo"); artifactPlans.add(artifactPlan); jobConfig.setArtifactPlans(artifactPlans); pipelineConfigService.createPipelineConfig(user, pipeline, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); assertThat(artifactPlan.errors().firstError(), is(String.format("Job 'job' has an artifact with an empty source", pipeline.name()))); } @Test public void shouldShowThePipelineConfigErrorMessageWhenPipelineBeingCreatedHasErrorsOnTimer() throws GitAPIException { PipelineConfig pipeline = GoConfigMother.createPipelineConfigWithMaterialConfig(UUID.randomUUID().toString(), new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); TimerConfig timer = new TimerConfig(null, true); pipeline.setTimer(timer); pipelineConfigService.createPipelineConfig(user, pipeline, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); assertThat(timer.errors().firstError(), is("Timer Spec can not be null.")); } @Test public void shouldShowThePipelineConfigErrorMessageWhenPipelineBeingCreatedHasErrorsOnProperties() throws GitAPIException { PipelineConfig pipeline = GoConfigMother.createPipelineConfigWithMaterialConfig(UUID.randomUUID().toString(), new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); JobConfig jobConfig = pipeline.get(0).getJobs().get(0); ArtifactPropertiesGenerators properties = new ArtifactPropertiesGenerators(); ArtifactPropertiesGenerator artifactPropertiesGenerator = new ArtifactPropertiesGenerator(); properties.add(artifactPropertiesGenerator); jobConfig.setProperties(properties); pipelineConfigService.createPipelineConfig(user, pipeline, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); assertThat(artifactPropertiesGenerator.errors().firstError(), is(String.format("Invalid property name 'null'. This must be alphanumeric and can contain underscores and periods (however, it cannot start with a period). The maximum allowed length is 255 characters.", pipeline.name()))); } @Test public void shouldShowThePipelineConfigErrorMessageWhenPipelineBeingCreatedHasErrorsOnTabs() throws GitAPIException { PipelineConfig pipeline = GoConfigMother.createPipelineConfigWithMaterialConfig(UUID.randomUUID().toString(), new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); JobConfig jobConfig = pipeline.get(0).getJobs().get(0); jobConfig.addTab("", "/foo"); pipelineConfigService.createPipelineConfig(user, pipeline, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); assertThat(jobConfig.getTabs().first().errors().firstError(), is(String.format("Tab name '' is invalid. This must be alphanumeric and can contain underscores and periods.", pipeline.name()))); } @Test public void shouldShowThePipelineConfigErrorMessageWhenPipelineBeingCreatedFromTemplateHasErrors() throws GitAPIException { JobConfigs jobConfigs = new JobConfigs(); jobConfigs.add(new JobConfig(new CaseInsensitiveString("Job"))); StageConfig stage = new StageConfig(new CaseInsensitiveString("Stage-1"), jobConfigs); final PipelineTemplateConfig templateConfig = new PipelineTemplateConfig(new CaseInsensitiveString("foo"), stage); goConfigDao.updateConfig(new UpdateConfigCommand() { @Override public CruiseConfig update(CruiseConfig cruiseConfig) throws Exception { cruiseConfig.addTemplate(templateConfig); return cruiseConfig; } }); PipelineConfig pipeline = GoConfigMother.createPipelineConfigWithMaterialConfig(); pipeline.templatize(templateConfig.name()); DependencyMaterialConfig material = new DependencyMaterialConfig(new CaseInsensitiveString("Invalid-pipeline"), new CaseInsensitiveString("Stage")); pipeline.addMaterialConfig(material); pipelineConfigService.createPipelineConfig(user, pipeline, result, groupName); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); assertThat(material.errors().firstError(), is(String.format("Pipeline with name 'Invalid-pipeline' does not exist, it is defined as a dependency for pipeline 'pipeline' (cruise-config.xml)", pipeline.name()))); } @Test public void shouldShowThePipelineConfigErrorMessageWhenPipelineBeingUpdatedHasErrors() throws GitAPIException { ExecTask execTask = new ExecTask("ls", "-al", "#{foo}"); FetchTask fetchTask = new FetchTask(pipelineConfig.name(), new CaseInsensitiveString("stage"), new CaseInsensitiveString("job"), "srcfile", "/usr/dest"); JobConfig job = new JobConfig("default-job"); job.addTask(execTask); job.addTask(fetchTask); StageConfig stage = new StageConfig(new CaseInsensitiveString("default-stage"), new JobConfigs(job)); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.add(stage); pipelineConfig.addParam(new ParamConfig("foo", ".")); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); String expectedError = String.format("Task of job 'default-job' in stage 'default-stage' of pipeline '%s' has dest path '/usr/dest' which is outside the working directory.", pipelineConfig.name()); assertThat(fetchTask.errors().on("dest"), is(expectedError)); } @Test public void shouldUpdatePipelineConfig() throws GitAPIException { GoConfigHolder goConfigHolderBeforeUpdate = goConfigDao.loadConfigHolder(); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.add(new StageConfig(new CaseInsensitiveString("additional_stage"), new JobConfigs(new JobConfig(new CaseInsensitiveString("addtn_job"))))); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.toString(), result.isSuccessful(), is(true)); assertThat(goConfigDao.loadConfigHolder(), is(not(goConfigHolderBeforeUpdate))); StageConfig newlyAddedStage = goConfigDao.loadForEditing().getPipelineConfigByName(pipelineConfig.name()).getStage(new CaseInsensitiveString("additional_stage")); assertThat(newlyAddedStage, is(not(nullValue()))); assertThat(newlyAddedStage.getJobs().isEmpty(), is(false)); assertThat(newlyAddedStage.getJobs().first().name().toString(), is("addtn_job")); assertThat(configRepository.getCurrentRevCommit().name(), is(not(headCommitBeforeUpdate))); assertThat(configRepository.getCurrentRevision().getUsername(), is(user.getDisplayName())); } @Test public void shouldNotUpdatePipelineConfigInCaseOfValidationErrors() throws GitAPIException { GoConfigHolder goConfigHolder = goConfigDao.loadConfigHolder(); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.setLabelTemplate("LABEL"); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.httpCode(), is(422)); assertThat(pipelineConfig.errors().on(PipelineConfig.LABEL_TEMPLATE), contains("Invalid label")); assertThat(configRepository.getCurrentRevCommit().name(), is(headCommitBeforeUpdate)); assertThat(goConfigDao.loadConfigHolder().configForEdit, is(goConfigHolder.configForEdit)); assertThat(goConfigDao.loadConfigHolder().config, is(goConfigHolder.config)); } @Test public void shouldNotUpdatePipelineWhenPreprocessingFails() throws Exception { CaseInsensitiveString templateName = new CaseInsensitiveString("template_with_param"); saveTemplateWithParamToConfig(templateName); GoConfigHolder goConfigHolder = goConfigDao.loadConfigHolder(); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.clear(); pipelineConfig.setTemplateName(templateName); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.toString(), result.toString().contains("Parameter 'SOME_PARAM' is not defined"), is(true)); assertThat(configRepository.getCurrentRevCommit().name(), is(headCommitBeforeUpdate)); assertThat(goConfigDao.loadConfigHolder().configForEdit, is(goConfigHolder.configForEdit)); assertThat(goConfigDao.loadConfigHolder().config, is(goConfigHolder.config)); } @Test public void shouldNotUpdatePipelineWhenPipelineIsAssociatedWithTemplateAsWellAsHasStagesDefinedLocally() throws Exception { CaseInsensitiveString templateName = new CaseInsensitiveString("template_with_param"); saveTemplateWithParamToConfig(templateName); GoConfigHolder goConfigHolder = goConfigDao.loadConfigHolder(); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.clear(); pipelineConfig.setTemplateName(templateName); pipelineConfig.addStageWithoutValidityAssertion(StageConfigMother.stageConfig("local-stage")); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(pipelineConfig.errors().on("stages"), is(String.format("Cannot add stages to pipeline '%s' which already references template '%s'", pipelineConfig.name(), templateName))); assertThat(pipelineConfig.errors().on("template"), is(String.format("Cannot set template '%s' on pipeline '%s' because it already has stages defined", templateName, pipelineConfig.name()))); assertThat(configRepository.getCurrentRevCommit().name(), is(headCommitBeforeUpdate)); assertThat(goConfigDao.loadConfigHolder().configForEdit, is(goConfigHolder.configForEdit)); assertThat(goConfigDao.loadConfigHolder().config, is(goConfigHolder.config)); } @Test public void shouldCheckForUserPermissionBeforeUpdatingPipelineConfig() throws Exception { CaseInsensitiveString templateName = new CaseInsensitiveString("template_with_param"); saveTemplateWithParamToConfig(templateName); GoConfigHolder goConfigHolderBeforeUpdate = goConfigDao.loadConfigHolder(); pipelineConfigService.updatePipelineConfig(new Username(new CaseInsensitiveString("unauthorized_user")), pipelineConfig, null, result); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.toString(), result.httpCode(), is(401)); assertThat(result.toString(), result.toString().contains("UNAUTHORIZED_TO_EDIT_PIPELINE"), is(true)); assertThat(configRepository.getCurrentRevCommit().name(), is(headCommitBeforeUpdate)); assertThat(goConfigDao.loadConfigHolder().configForEdit, is(goConfigHolderBeforeUpdate.configForEdit)); assertThat(goConfigDao.loadConfigHolder().config, is(goConfigHolderBeforeUpdate.config)); } @Test public void shouldMapErrorsBackToScmMaterials() throws Exception { GoConfigHolder goConfigHolder = goConfigDao.loadConfigHolder(); String scmid = "scmid"; saveScmMaterialToConfig(scmid); PluggableSCMMaterialConfig scmMaterialConfig = new PluggableSCMMaterialConfig(scmid); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.materialConfigs().add(scmMaterialConfig); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(scmMaterialConfig.errors().on(PluggableSCMMaterialConfig.FOLDER), is("Destination directory is required when specifying multiple scm materials")); assertThat(scmMaterialConfig.errors().on(PluggableSCMMaterialConfig.SCM_ID), is("Could not find plugin for scm-id: [scmid].")); assertThat(configRepository.getCurrentRevCommit().name(), is(headCommitBeforeUpdate)); assertThat(goConfigDao.loadConfigHolder().configForEdit, is(goConfigHolder.configForEdit)); assertThat(goConfigDao.loadConfigHolder().config, is(goConfigHolder.config)); } @Test public void shouldMapErrorsBackToPackageMaterials() throws Exception { GoConfigHolder goConfigHolder = goConfigDao.loadConfigHolder(); String packageid = "packageid"; saveScmMaterialToConfig(packageid); PackageMaterialConfig packageMaterialConfig = new PackageMaterialConfig(packageid); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.materialConfigs().add(packageMaterialConfig); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.toString(), result.isSuccessful(), is(false)); assertThat(result.message(localizer), is(String.format("Validations failed for pipeline '%s'. Error(s): [Validation failed.]. Please correct and resubmit.", pipelineConfig.name()))); assertThat(packageMaterialConfig.errors().on(PackageMaterialConfig.PACKAGE_ID), is("Could not find repository for given package id:[packageid]")); assertThat(configRepository.getCurrentRevCommit().name(), is(headCommitBeforeUpdate)); assertThat(goConfigDao.loadConfigHolder().configForEdit, is(goConfigHolder.configForEdit)); assertThat(goConfigDao.loadConfigHolder().config, is(goConfigHolder.config)); } @Test public void shouldDeletePipelineConfig() throws Exception { PipelineConfig pipeline = PipelineConfigMother.createPipelineConfigWithStages(UUID.randomUUID().toString(), "stage"); goConfigService.addPipeline(pipeline, "default"); assertTrue(goConfigService.hasPipelineNamed(pipeline.name())); int pipelineCountBefore = goConfigService.getAllPipelineConfigs().size(); pipelineConfigService.deletePipelineConfig(user, pipeline, result); assertTrue(result.isSuccessful()); int pipelineCountAfter = goConfigService.getAllPipelineConfigs().size(); assertThat(pipelineCountBefore - pipelineCountAfter, is(1)); assertFalse(goConfigService.hasPipelineNamed(pipeline.name())); } @Test public void shouldNotDeleteThePipelineForUnauthorizedUsers() throws Exception { int pipelineCountBefore = goConfigService.getAllPipelineConfigs().size(); assertTrue(goConfigService.hasPipelineNamed(pipelineConfig.name())); pipelineConfigService.deletePipelineConfig(new Username(new CaseInsensitiveString("unauthorized-user")), pipelineConfig, result); assertFalse(result.isSuccessful()); assertThat(result.toString(), result.toString().contains("UNAUTHORIZED_TO_DELETE_PIPELINE"), is(true)); assertThat(result.httpCode(), is(401)); int pipelineCountAfter = goConfigService.getAllPipelineConfigs().size(); assertThat(pipelineCountAfter, is(pipelineCountBefore)); assertTrue(goConfigService.hasPipelineNamed(pipelineConfig.name())); } @Test public void shouldNotDeletePipelineConfigWhenItIsUsedInAnEnvironment() throws Exception { BasicEnvironmentConfig env = new BasicEnvironmentConfig(new CaseInsensitiveString("Dev")); PipelineConfig pipeline = PipelineConfigMother.createPipelineConfigWithStages(UUID.randomUUID().toString(), "stage"); goConfigService.addPipeline(pipeline, "default"); env.addPipeline(pipeline.name()); goConfigService.addEnvironment(env); int pipelineCountBefore = goConfigService.getAllPipelineConfigs().size(); assertTrue(goConfigService.hasPipelineNamed(pipeline.name())); pipelineConfigService.deletePipelineConfig(user, pipeline, result); assertFalse(result.isSuccessful()); assertThat(result.toString(), result.toString().contains("CANNOT_DELETE_PIPELINE_IN_ENVIRONMENT"), is(true)); assertThat(result.httpCode(), is(422)); int pipelineCountAfter = goConfigService.getAllPipelineConfigs().size(); assertThat(pipelineCountAfter, is(pipelineCountBefore)); assertTrue(goConfigService.hasPipelineNamed(pipeline.name())); } @Test public void shouldNotDeletePipelineConfigWhenItHasDownstreamDependencies() throws Exception { PipelineConfig dependency = GoConfigMother.createPipelineConfigWithMaterialConfig(new DependencyMaterialConfig(pipelineConfig.name(), pipelineConfig.first().name())); goConfigService.addPipeline(dependency, groupName); int pipelineCountBefore = goConfigService.getAllPipelineConfigs().size(); assertTrue(goConfigService.hasPipelineNamed(pipelineConfig.name())); pipelineConfigService.deletePipelineConfig(user, pipelineConfig, result); assertFalse(result.isSuccessful()); assertThat(result.toString(), result.toString().contains("CANNOT_DELETE_PIPELINE_USED_AS_MATERIALS"), is(true)); assertThat(result.httpCode(), is(422)); int pipelineCountAfter = goConfigService.getAllPipelineConfigs().size(); assertThat(pipelineCountAfter, is(pipelineCountBefore)); assertTrue(goConfigService.hasPipelineNamed(pipelineConfig.name())); } @Test public void shouldNotifyListenersWithPreprocessedConfigUponSuccessfulUpdate() { final String pipelineName = UUID.randomUUID().toString(); final String templateName = UUID.randomUUID().toString(); final boolean[] listenerInvoked = {false}; setupPipelineWithTemplate(pipelineName, templateName); PipelineConfig pipelineConfig1 = goConfigService.pipelineConfigNamed(new CaseInsensitiveString(pipelineName)); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig1); String md5 = CachedDigestUtils.md5Hex(xml); EntityConfigChangedListener<PipelineConfig> pipelineConfigChangedListener = new EntityConfigChangedListener<PipelineConfig>() { @Override public void onConfigChange(CruiseConfig newCruiseConfig) { } @Override public void onEntityConfigChange(PipelineConfig pipelineConfig) { listenerInvoked[0] = true; assertThat(pipelineConfig.first(), is(goConfigService.cruiseConfig().getTemplateByName(new CaseInsensitiveString(templateName)).first())); } }; goConfigService.register(pipelineConfigChangedListener); PipelineConfig pipeline = PipelineConfigMother.pipelineConfigWithTemplate(pipelineName, templateName); pipeline.setVariables(new EnvironmentVariablesConfig()); pipelineConfigService.updatePipelineConfig(user, pipeline, md5, new DefaultLocalizedOperationResult()); assertThat(listenerInvoked[0], is(true)); } @Test public void shouldNotifyListenersWithPreprocessedConfigUponSuccessfulCreate() { final String pipelineName = UUID.randomUUID().toString(); final String templateName = UUID.randomUUID().toString(); final boolean[] listenerInvoked = {false}; setupPipelineWithTemplate(pipelineName, templateName); EntityConfigChangedListener<PipelineConfig> pipelineConfigChangedListener = new EntityConfigChangedListener<PipelineConfig>() { @Override public void onConfigChange(CruiseConfig newCruiseConfig) { } @Override public void onEntityConfigChange(PipelineConfig pipelineConfig) { listenerInvoked[0] = true; assertThat(pipelineConfig.first(), is(goConfigService.cruiseConfig().getTemplateByName(new CaseInsensitiveString(templateName)).first())); } }; goConfigService.register(pipelineConfigChangedListener); PipelineConfig pipeline = PipelineConfigMother.pipelineConfigWithTemplate(UUID.randomUUID().toString(), templateName); pipeline.setVariables(new EnvironmentVariablesConfig()); pipelineConfigService.createPipelineConfig(user, pipeline, new DefaultLocalizedOperationResult(), "group1"); assertThat(listenerInvoked[0], is(true)); } private void setupPipelineWithTemplate(final String pipelineName, final String templateName) { goConfigService.updateConfig(new UpdateConfigCommand() { @Override public CruiseConfig update(CruiseConfig cruiseConfig) throws Exception { PipelineTemplateConfig template = PipelineTemplateConfigMother.createTemplate(templateName); PipelineConfig pipeline = PipelineConfigMother.pipelineConfigWithTemplate(pipelineName, template.name().toString()); cruiseConfig.addTemplate(template); cruiseConfig.addPipeline("group", pipeline); return cruiseConfig; } }); } @Test public void shouldValidateMergedConfigForConfigChanges() throws Exception { assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(new CaseInsensitiveString(remoteDownstreamPipelineName)), is(true)); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.getFirstStageConfig().setName(new CaseInsensitiveString("upstream_stage_renamed")); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.isSuccessful(), is(false)); assertThat(pipelineConfig.errors().on("base"), is(String.format("Stage with name 'stage' does not exist on pipeline '%s', it is being referred to from pipeline 'remote-downstream' (url at repo1_r1)", pipelineConfig.name()))); assertThat(result.message(localizer), is(String.format("Validations failed for pipeline '%s'. Error(s): [Validation failed.]. Please correct and resubmit.", pipelineConfig.name()))); } @Test public void shouldFallbackToValidPartialsForConfigChanges() throws Exception { assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(new CaseInsensitiveString(remoteDownstreamPipelineName)), is(true)); String remoteInvalidPipeline = "remote_invalid_pipeline"; PartialConfig invalidPartial = PartialConfigMother.invalidPartial(remoteInvalidPipeline, new RepoConfigOrigin(repoConfig1, "repo1_r2")); goPartialConfig.onSuccessPartialConfig(repoConfig1, invalidPartial); assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(new CaseInsensitiveString(remoteInvalidPipeline)), is(false)); assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(new CaseInsensitiveString(remoteDownstreamPipelineName)), is(true)); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.getFirstStageConfig().getJobs().first().addTask(new ExecTask("executable", new Arguments(new Argument("foo")), "working")); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.isSuccessful(), is(true)); CruiseConfig currentConfig = goConfigService.getCurrentConfig(); assertThat(currentConfig.getAllPipelineNames().contains(new CaseInsensitiveString(remoteDownstreamPipelineName)), is(true)); assertThat(currentConfig.getAllPipelineNames().contains(new CaseInsensitiveString(remoteInvalidPipeline)), is(false)); } @Test public void shouldSaveWhenKnownPartialListIsTheSameAsValidPartialsAndValidationPassesForConfigChanges() throws Exception { PipelineConfig remoteDownstreamPipeline = partialConfig.getGroups().first().getPipelines().get(0); assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).isEmpty(), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); DependencyMaterialConfig dependencyMaterialForRemotePipelineInConfigCache = goConfigService.getCurrentConfig().getPipelineConfigByName(remoteDownstreamPipeline.name()).materialConfigs().findDependencyMaterial(pipelineConfig.name()); assertThat(dependencyMaterialForRemotePipelineInConfigCache.getStageName(), is(new CaseInsensitiveString("stage"))); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.setVariables(new EnvironmentVariablesConfig(asList(new EnvironmentVariableConfig("key", "value")))); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.isSuccessful(), is(true)); CruiseConfig currentConfig = goConfigService.getCurrentConfig(); assertThat(currentConfig.getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); assertThat(currentConfig.getPipelineConfigByName(pipelineConfig.name()).getVariables().contains(new EnvironmentVariableConfig("key", "value")), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).isEmpty(), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); } @Test public void shouldNotSaveWhenKnownPartialsListIsTheSameAsValidPartialsAndPipelineValidationFailsForConfigChanges() throws Exception { PipelineConfig remoteDownstreamPipeline = partialConfig.getGroups().first().getPipelines().get(0); assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).isEmpty(), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); DependencyMaterialConfig dependencyMaterialForRemotePipelineInConfigCache = goConfigService.getCurrentConfig().getPipelineConfigByName(remoteDownstreamPipeline.name()).materialConfigs().findDependencyMaterial(pipelineConfig.name()); assertThat(dependencyMaterialForRemotePipelineInConfigCache.getStageName(), is(new CaseInsensitiveString("stage"))); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.getFirstStageConfig().setName(new CaseInsensitiveString("new_name")); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.isSuccessful(), is(false)); assertThat(result.message(localizer), is(String.format("Validations failed for pipeline '%s'. Error(s): [Validation failed.]. Please correct and resubmit.", pipelineConfig.name()))); CruiseConfig currentConfig = goConfigService.getCurrentConfig(); assertThat(currentConfig.getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); assertThat(cachedGoPartials.lastValidPartials().contains(partialConfig), is(true)); assertThat(cachedGoPartials.lastKnownPartials().contains(partialConfig), is(true)); assertThat(cachedGoPartials.lastKnownPartials().equals(cachedGoPartials.lastValidPartials()), is(true)); assertThat(currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).materialConfigs().findDependencyMaterial(pipelineConfig.name()).getStageName(), is(new CaseInsensitiveString("stage"))); assertThat(currentConfig.getPipelineConfigByName(pipelineConfig.name()).getFirstStageConfig().name(), is(new CaseInsensitiveString("stage"))); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).isEmpty(), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); } @Test public void shouldSaveWhenKnownNotEqualsValidPartialsAndPipelineValidationPassesWhenValidPartialsAreMergedToMain() throws Exception { PipelineConfig remoteDownstreamPipeline = partialConfig.getGroups().first().getPipelines().get(0); assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).isEmpty(), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); partialConfig = PartialConfigMother.invalidPartial(remoteDownstreamPipeline.name().toString(), new RepoConfigOrigin(repoConfig1, "repo1_r2")); goPartialConfig.onSuccessPartialConfig(repoConfig1, partialConfig); CruiseConfig currentConfig = goConfigService.getCurrentConfig(); assertThat(currentConfig.getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); assertThat(((RepoConfigOrigin) currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getValid(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getKnown(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r2")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).size(), is(1)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getMessage(), is("Invalid Merged Configuration")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getDescription(), is("1+ errors :: Invalid stage name ''. This must be alphanumeric and can contain underscores and periods (however, it cannot start with a period). The maximum allowed length is 255 characters.;; - Config-Repo: url at repo1_r2")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.setVariables(new EnvironmentVariablesConfig(asList(new EnvironmentVariableConfig("key", "value")))); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.isSuccessful(), is(true)); currentConfig = goConfigService.getCurrentConfig(); assertThat(currentConfig.getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); assertThat(currentConfig.getPipelineConfigByName(pipelineConfig.name()).getVariables().contains(new EnvironmentVariableConfig("key", "value")), is(true)); assertThat(((RepoConfigOrigin) currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getValid(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getKnown(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r2")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).size(), is(1)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getMessage(), is("Invalid Merged Configuration")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getDescription(), is("1+ errors :: Invalid stage name ''. This must be alphanumeric and can contain underscores and periods (however, it cannot start with a period). The maximum allowed length is 255 characters.;; - Config-Repo: url at repo1_r2")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); } @Test public void shouldSaveWhenKnownNotEqualsValidPartialsAndPipelineValidationFailsWithValidPartialsButPassesWhenKnownPartialsAreMergedToMain() throws Exception { PipelineConfig remoteDownstreamPipeline = partialConfig.getGroups().first().getPipelines().get(0); assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).isEmpty(), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); final CaseInsensitiveString upstreamStageRenamed = new CaseInsensitiveString("upstream_stage_renamed"); partialConfig = PartialConfigMother.pipelineWithDependencyMaterial("remote-downstream", new PipelineConfig(pipelineConfig.name(), pipelineConfig.materialConfigs(), new StageConfig(upstreamStageRenamed, new JobConfigs())), new RepoConfigOrigin(repoConfig1, "repo1_r2")); goPartialConfig.onSuccessPartialConfig(repoConfig1, partialConfig); CruiseConfig currentConfig = goConfigService.getCurrentConfig(); DependencyMaterialConfig dependencyMaterialForRemotePipelineInConfigCache = currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).materialConfigs().findDependencyMaterial(pipelineConfig.name()); assertThat(dependencyMaterialForRemotePipelineInConfigCache.getStageName(), is(new CaseInsensitiveString("stage"))); assertThat(((RepoConfigOrigin) currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getValid(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getKnown(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r2")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).size(), is(1)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getMessage(), is("Invalid Merged Configuration")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getDescription(), is(String.format("1+ errors :: Stage with name 'upstream_stage_renamed' does not exist on pipeline '%s', it is being referred to from pipeline 'remote-downstream' (url at repo1_r2);; - Config-Repo: url at repo1_r2", pipelineConfig.name()))); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.getFirstStageConfig().setName(new CaseInsensitiveString("upstream_stage_renamed")); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.isSuccessful(), is(true)); currentConfig = goConfigService.getCurrentConfig(); assertThat(currentConfig.getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); assertThat(cachedGoPartials.lastValidPartials().contains(partialConfig), is(true)); assertThat(cachedGoPartials.lastKnownPartials().contains(partialConfig), is(true)); assertThat(cachedGoPartials.lastKnownPartials().equals(cachedGoPartials.lastValidPartials()), is(true)); assertThat(currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).materialConfigs().findDependencyMaterial(pipelineConfig.name()).getStageName(), is(upstreamStageRenamed)); assertThat(currentConfig.getPipelineConfigByName(pipelineConfig.name()).getFirstStageConfig().name(), is(upstreamStageRenamed)); assertThat(((RepoConfigOrigin) currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).getOrigin()).getRevision(), is("repo1_r2")); assertThat(((RepoConfigOrigin) cachedGoPartials.getValid(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r2")); assertThat(((RepoConfigOrigin) cachedGoPartials.getKnown(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r2")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).isEmpty(), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); } @Test public void shouldPerformFullValidationNotJustEntitySpecificIfMergingKnownPartialsAsOtherAspectsOfAKnownPartialMightBeInvalid() throws Exception { PipelineConfig remoteDownstreamPipeline = partialConfig.getGroups().first().getPipelines().get(0); assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); String independentRemotePipeline = "independent-pipeline"; assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(new CaseInsensitiveString(independentRemotePipeline)), is(true)); //introduce an invalid change in the independent partial PartialConfig invalidIndependentPartial = PartialConfigMother.invalidPartial(independentRemotePipeline, new RepoConfigOrigin(repoConfig2, "repo2_r2")); goPartialConfig.onSuccessPartialConfig(repoConfig2, invalidIndependentPartial); assertThat(((RepoConfigOrigin) cachedGoPartials.getValid(repoConfig2.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo2_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getKnown(repoConfig2.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo2_r2")); assertThat(((RepoConfigOrigin) goConfigService.getCurrentConfig().getPipelineConfigByName(new CaseInsensitiveString(independentRemotePipeline)).getOrigin()).getRevision(), is("repo2_r1")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).isEmpty(), is(true)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).size(), is(1)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).get(0).getMessage(), is("Invalid Merged Configuration")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).get(0).getDescription(), is("1+ errors :: Invalid stage name ''. This must be alphanumeric and can contain underscores and periods (however, it cannot start with a period). The maximum allowed length is 255 characters.;; - Config-Repo: url2 at repo2_r2")); final CaseInsensitiveString upstreamStageRenamed = new CaseInsensitiveString("upstream_stage_renamed"); partialConfig = PartialConfigMother.pipelineWithDependencyMaterial("remote-downstream", new PipelineConfig(pipelineConfig.name(), pipelineConfig.materialConfigs(), new StageConfig(upstreamStageRenamed, new JobConfigs())), new RepoConfigOrigin(repoConfig1, "repo1_r2")); goPartialConfig.onSuccessPartialConfig(repoConfig1, partialConfig); CruiseConfig currentConfig = goConfigService.getCurrentConfig(); DependencyMaterialConfig dependencyMaterialForRemotePipelineInConfigCache = currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).materialConfigs().findDependencyMaterial(pipelineConfig.name()); assertThat(dependencyMaterialForRemotePipelineInConfigCache.getStageName(), is(new CaseInsensitiveString("stage"))); assertThat(((RepoConfigOrigin) currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getValid(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getKnown(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r2")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).size(), is(1)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getMessage(), is("Invalid Merged Configuration")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getDescription(), is(String.format("1+ errors :: Stage with name 'upstream_stage_renamed' does not exist on pipeline '%s', it is being referred to from pipeline 'remote-downstream' (url at repo1_r2);; - Config-Repo: url at repo1_r2", pipelineConfig.name()))); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).size(), is(1)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).get(0).getMessage(), is("Invalid Merged Configuration")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).get(0).getDescription(), is("1+ errors :: Invalid stage name ''. This must be alphanumeric and can contain underscores and periods (however, it cannot start with a period). The maximum allowed length is 255 characters.;; - Config-Repo: url2 at repo2_r2")); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.getFirstStageConfig().setName(new CaseInsensitiveString("upstream_stage_renamed")); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.isSuccessful(), is(false)); assertThat(result.message(localizer), is(String.format("Validations failed for pipeline '%s'. " + "Error(s): [Merged update operation failed on VALID 2 partials. Falling back to using LAST KNOWN 2 partials. " + "Exception message was: [Validation failed. Stage with name 'stage' does not exist on pipeline '%s', " + "it is being referred to from pipeline 'remote-downstream' (url at repo1_r1)]" + System.lineSeparator() + "Merged config update operation failed using fallback LAST KNOWN 2 partials. " + "Exception message was: 1+ errors :: Invalid stage name ''. This must be alphanumeric and can contain underscores and periods " + "(however, it cannot start with a period). The maximum allowed length is 255 characters.;; ]. Please correct and resubmit.", pipelineConfig.name(), pipelineConfig.name()))); assertThat(ErrorCollector.getAllErrors(pipelineConfig).isEmpty(), is(true)); currentConfig = goConfigService.getCurrentConfig(); assertThat(currentConfig.getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); assertThat(cachedGoPartials.lastKnownPartials().equals(cachedGoPartials.lastValidPartials()), is(false)); assertThat(currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).materialConfigs().findDependencyMaterial(pipelineConfig.name()).getStageName(), is(new CaseInsensitiveString("stage"))); assertThat(currentConfig.getPipelineConfigByName(pipelineConfig.name()).getFirstStageConfig().name(), is(new CaseInsensitiveString("stage"))); assertThat(((RepoConfigOrigin) currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getValid(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getKnown(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r2")); assertThat(((RepoConfigOrigin) currentConfig.getPipelineConfigByName(new CaseInsensitiveString(independentRemotePipeline)).getOrigin()).getRevision(), is("repo2_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getValid(repoConfig2.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo2_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getKnown(repoConfig2.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo2_r2")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).size(), is(1)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getMessage(), is("Invalid Merged Configuration")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getDescription(), is(String.format("1+ errors :: Stage with name 'upstream_stage_renamed' does not exist on pipeline '%s', it is being referred to from pipeline 'remote-downstream' (url at repo1_r2);; - Config-Repo: url at repo1_r2", pipelineConfig.name()))); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).size(), is(1)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).get(0).getMessage(), is("Invalid Merged Configuration")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).get(0).getDescription(), is("1+ errors :: Invalid stage name ''. This must be alphanumeric and can contain underscores and periods (however, it cannot start with a period). The maximum allowed length is 255 characters.;; - Config-Repo: url2 at repo2_r2")); } @Test public void shouldNotSaveWhenKnownNotEqualsValidPartialsAndPipelineValidationFailsWithValidPartialsAsWellAsKnownPartialsMergedToMain() throws Exception { PipelineConfig remoteDownstreamPipeline = partialConfig.getGroups().first().getPipelines().get(0); assertThat(goConfigService.getCurrentConfig().getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); final CaseInsensitiveString upstreamStageRenamed = new CaseInsensitiveString("upstream_stage_renamed"); partialConfig = PartialConfigMother.pipelineWithDependencyMaterial("remote-downstream", new PipelineConfig(pipelineConfig.name(), pipelineConfig.materialConfigs(), new StageConfig(upstreamStageRenamed, new JobConfigs())), new RepoConfigOrigin(repoConfig1, "repo1_r2")); goPartialConfig.onSuccessPartialConfig(repoConfig1, partialConfig); CruiseConfig currentConfig = goConfigService.getCurrentConfig(); DependencyMaterialConfig dependencyMaterialForRemotePipelineInConfigCache = currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).materialConfigs().findDependencyMaterial(pipelineConfig.name()); assertThat(dependencyMaterialForRemotePipelineInConfigCache.getStageName(), is(new CaseInsensitiveString("stage"))); assertThat(((RepoConfigOrigin) currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getValid(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getKnown(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r2")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).size(), is(1)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getMessage(), is("Invalid Merged Configuration")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getDescription(), is(String.format("1+ errors :: Stage with name 'upstream_stage_renamed' does not exist on pipeline '%s', it is being referred to from pipeline 'remote-downstream' (url at repo1_r2);; - Config-Repo: url at repo1_r2", pipelineConfig.name()))); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig); String md5 = CachedDigestUtils.md5Hex(xml); pipelineConfig.getFirstStageConfig().setName(new CaseInsensitiveString("new_name")); pipelineConfigService.updatePipelineConfig(user, pipelineConfig, md5, result); assertThat(result.isSuccessful(), is(false)); assertThat(result.message(localizer), is(String.format("Validations failed for pipeline '%s'. " + "Error(s): [Merged update operation failed on VALID 2 partials. Falling back to using LAST KNOWN 2 partials. " + "Exception message was: [Validation failed. Stage with name 'stage' does not exist on pipeline '%s', " + "it is being referred to from pipeline 'remote-downstream' (url at repo1_r1)]" + System.lineSeparator() + "Merged config update operation failed using fallback LAST KNOWN 2 partials. " + "Exception message was: 1+ errors :: Stage with name 'upstream_stage_renamed' does not exist on pipeline " + "'%s', it is being referred to from pipeline 'remote-downstream' (url at repo1_r2);; ]. " + "Please correct and resubmit.", pipelineConfig.name(), pipelineConfig.name(), pipelineConfig.name()))); currentConfig = goConfigService.getCurrentConfig(); assertThat(currentConfig.getAllPipelineNames().contains(remoteDownstreamPipeline.name()), is(true)); assertThat(((RepoConfigOrigin) currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getValid(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.getKnown(repoConfig1.getMaterialConfig().getFingerprint()).getOrigin()).getRevision(), is("repo1_r2")); assertThat(currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).materialConfigs().findDependencyMaterial(pipelineConfig.name()).getStageName(), is(new CaseInsensitiveString("stage"))); assertThat(currentConfig.getPipelineConfigByName(pipelineConfig.name()).getFirstStageConfig().name(), is(new CaseInsensitiveString("stage"))); assertThat(((RepoConfigOrigin) currentConfig.getPipelineConfigByName(remoteDownstreamPipeline.name()).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.lastValidPartials().get(0).getOrigin()).getRevision(), is("repo1_r1")); assertThat(((RepoConfigOrigin) cachedGoPartials.lastKnownPartials().get(0).getOrigin()).getRevision(), is("repo1_r2")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).size(), is(1)); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getMessage(), is("Invalid Merged Configuration")); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig1)).get(0).getDescription(), is(String.format("1+ errors :: Stage with name 'upstream_stage_renamed' does not exist on pipeline '%s', it is being referred to from pipeline 'remote-downstream' (url at repo1_r2);; - Config-Repo: url at repo1_r2", pipelineConfig.name()))); assertThat(serverHealthService.filterByScope(HealthStateScope.forPartialConfigRepo(repoConfig2)).isEmpty(), is(true)); } private void saveTemplateWithParamToConfig(CaseInsensitiveString templateName) throws Exception { JobConfig jobConfig = new JobConfig(new CaseInsensitiveString("job")); ExecTask task = new ExecTask(); task.setCommand("ls"); jobConfig.addTask(task); jobConfig.addVariable("ENV_VAR", "#{SOME_PARAM}"); final PipelineTemplateConfig template = new PipelineTemplateConfig(templateName, new StageConfig(new CaseInsensitiveString("stage"), new JobConfigs(jobConfig))); CruiseConfig cruiseConfig = goConfigDao.loadConfigHolder().configForEdit; cruiseConfig.addTemplate(template); saveConfig(cruiseConfig); } private void saveScmMaterialToConfig(String id) throws Exception { SCM scm = new SCM(id, new PluginConfiguration(id, "1.0"), new Configuration(new ConfigurationProperty(new ConfigurationKey("key"), new ConfigurationValue("value")))); scm.setName(id); CruiseConfig cruiseConfig = goConfigDao.loadConfigHolder().configForEdit; cruiseConfig.getSCMs().add(scm); saveConfig(cruiseConfig); } private void saveConfig(CruiseConfig cruiseConfig) throws Exception { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); new MagicalGoConfigXmlWriter(configCache, registry).write(cruiseConfig, buffer, false); } }