diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/config/tasks/CheckIfApplicationExistsTaskConfig.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/config/tasks/CheckIfApplicationExistsTaskConfig.java index a29c5082cf..98f67011f9 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/config/tasks/CheckIfApplicationExistsTaskConfig.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/config/tasks/CheckIfApplicationExistsTaskConfig.java @@ -23,6 +23,9 @@ public class CheckIfApplicationExistsTaskConfig { // controls whether clouddriver should be queried for an application or not. Defaults to true boolean checkClouddriver = true; + // controls whether the task should fail or simply log a warning + boolean auditModeEnabled = true; + // front50 specific retry config. This is only applicable when services.front50.enabled: true private RetryConfig front50Retries = new RetryConfig(); diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/AbstractCheckIfApplicationExistsTask.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/AbstractCheckIfApplicationExistsTask.java index 1bff447a09..816549cd2d 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/AbstractCheckIfApplicationExistsTask.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/AbstractCheckIfApplicationExistsTask.java @@ -46,10 +46,12 @@ *

If the application doesn't exist, the task fails. * *

The motivation for adding such a task is to prevent creation of any ad-hoc applications in - * amazon deployment pipeline stages. Depending on what is the application value set in the moniker - * and/or the cluster keys in such stages, any application that didn't previously exist can be - * created on demand. This can have an adverse effect on the security of such applications since - * these applications aren't created via a controlled process. + * amazon and kubernetes deployment pipeline stages. + * + *

Depending on what is the application value set in the moniker and/or the cluster keys in such + * stages, any application that isn't known to front50 can be created by clouddriver on demand. This + * can have an adverse effect on the security of such applications since these applications aren't + * created via a controlled process. */ @Slf4j @Component @@ -90,13 +92,27 @@ public TaskResult execute(@Nonnull StageExecution stage) { log.info("querying clouddriver for application: {}", applicationName); fetchedApplication = getApplicationFromClouddriver(applicationName); if (fetchedApplication == null) { - errorMessage += " and in clouddriver."; + errorMessage += " and in clouddriver"; } } } if (fetchedApplication == null) { - log.error(errorMessage); - throw new NotFoundException(errorMessage); + if (this.config.isAuditModeEnabled()) { + String pipelineName = "unknown"; + if (stage.getParent() != null) { + pipelineName = stage.getParent().getName(); + } + log.warn( + "Warning: stage: {}, pipeline: {}, message: {}. " + + "This will be a terminal failure in the near future.", + errorMessage, + stage.getName(), + pipelineName); + outputs.put("checkIfApplicationExistsWarning", errorMessage); + } else { + log.error(errorMessage); + throw new NotFoundException(errorMessage); + } } return TaskResult.builder(ExecutionStatus.SUCCEEDED).outputs(outputs).build(); } diff --git a/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/servergroup/CheckIfApplicationExistsForServerGroupTaskTest.java b/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/servergroup/CheckIfApplicationExistsForServerGroupTaskTest.java index c123c4a144..aeb05311f7 100644 --- a/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/servergroup/CheckIfApplicationExistsForServerGroupTaskTest.java +++ b/orca-clouddriver/src/test/java/com/netflix/spinnaker/orca/clouddriver/tasks/servergroup/CheckIfApplicationExistsForServerGroupTaskTest.java @@ -46,7 +46,6 @@ import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.springframework.http.HttpStatus; @@ -135,10 +134,14 @@ public void testSuccessfulRetrievalOfApplicationFromClouddriverIfFront50IsDisabl assertEquals(result.getStatus(), ExecutionStatus.SUCCEEDED); } - @Test - public void testIfApplicationCannotBeRetrievedFromFront50AndCheckClouddriverIsFalse() { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testIfApplicationCannotBeRetrievedFromFront50AndCheckClouddriverIsFalse( + boolean auditModeEnabled) { TaskConfigurationProperties configurationProperties = new TaskConfigurationProperties(); configurationProperties.getCheckIfApplicationExistsTask().setCheckClouddriver(false); + configurationProperties.getCheckIfApplicationExistsTask().setAuditModeEnabled(auditModeEnabled); + final String expectedErrorMessage = "did not find application: testapp in front50"; // setup: task = new CheckIfApplicationExistsForServerGroupTask( @@ -147,22 +150,34 @@ public void testIfApplicationCannotBeRetrievedFromFront50AndCheckClouddriverIsFa stageExecution.setContext(getStageContext("application")); // then - NotFoundException thrown = - assertThrows(NotFoundException.class, () -> task.execute(stageExecution)); - - assertThat(thrown.getMessage()).contains("did not find application: testapp in front50"); + if (auditModeEnabled) { + TaskResult result = task.execute(stageExecution); + assertEquals(result.getStatus(), ExecutionStatus.SUCCEEDED); + assertEquals( + expectedErrorMessage, result.getOutputs().get("checkIfApplicationExistsWarning")); + } else { + NotFoundException thrown = + assertThrows(NotFoundException.class, () -> task.execute(stageExecution)); + + assertThat(thrown.getMessage()).contains(expectedErrorMessage); + } verifyNoInteractions(front50Service); verifyNoInteractions(oortService); } - @Test - public void testAnApplicationWhichDoesNotExistInBothFront50AndClouddriver() { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testAnApplicationWhichDoesNotExistInBothFront50AndClouddriver( + boolean auditModeEnabled) { // setup: task = new CheckIfApplicationExistsForServerGroupTask( null, oortService, objectMapper, retrySupport, configurationProperties); + configurationProperties.getCheckIfApplicationExistsTask().setAuditModeEnabled(auditModeEnabled); + final String expectedErrorMessage = + "did not find application: invalid app in front50 and in clouddriver"; when(oortService.getApplication("invalid app")) .thenReturn( new Response( @@ -175,13 +190,19 @@ public void testAnApplicationWhichDoesNotExistInBothFront50AndClouddriver() { Map stageContext = new HashMap<>(); stageContext.put("application", "invalid app"); stageExecution.setContext(stageContext); - // then - NotFoundException thrown = - assertThrows(NotFoundException.class, () -> task.execute(stageExecution)); - - assertThat(thrown.getMessage()) - .contains("did not find application: invalid app in front50 and in clouddriver."); + if (auditModeEnabled) { + TaskResult result = task.execute(stageExecution); + assertEquals(result.getStatus(), ExecutionStatus.SUCCEEDED); + assertEquals( + expectedErrorMessage, result.getOutputs().get("checkIfApplicationExistsWarning")); + } else { + // then + NotFoundException thrown = + assertThrows(NotFoundException.class, () -> task.execute(stageExecution)); + + assertThat(thrown.getMessage()).contains(expectedErrorMessage); + } verifyNoInteractions(front50Service); verify(oortService).getApplication("invalid app"); }