Skip to content

Commit

Permalink
fix(core): handle null when invoking pipelines with stages that are m…
Browse files Browse the repository at this point in the history
…issing a "type"

so callers see 400 with a helpful mesage instead of 500.

There's also code here in PipelineBuilder.withStages to throw an IllegalArgumentException
for null stages.  Due to [this change](https://github.com/spinnaker/orca/pull/4102/files#diff-88a2bb8f8eddf44a88866f3d27f096d67577c6c5f651acd88fdb3da32ed88d0aR211),
PipelineBuilder.withStages isn't called with null even if stages is null in the incoming
pipeline.  #4182 (Nov 30, 2021, version 1.28.0) was
a behavior change from a NullPointerException/500 to succeed (but do nothing) for a
pipeline with null stages.  #4102 (Jan 3, 2022) was
also part of version 1.28.0.  Changing the behavior to explictly reject null stages /
respond with 400 instead of accepting them and doing nothing feels better, but I'll leave
that for a separate PR as it's potentially more controversial.
  • Loading branch information
dbyron-sf committed Feb 23, 2024
1 parent ef18954 commit f8f213b
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,30 @@ public PipelineBuilder withStage(String type) {
}

public PipelineBuilder withStages(List<Map<String, Object>> stages) {
if (stages != null) {
stages.forEach(
it -> {
String type = it.remove("type").toString();
String name = it.containsKey("name") ? it.remove("name").toString() : null;
withStage(type, name != null ? name : type, it);
});
if (stages == null) {
throw new IllegalArgumentException(
"null stages in pipeline '"
+ pipeline.getName()
+ "' in application '"
+ pipeline.getApplication()
+ "'");
}
stages.forEach(
it -> {
String name = it.containsKey("name") ? it.remove("name").toString() : null;
if (!it.containsKey("type")) {
throw new IllegalArgumentException(
"type missing from pipeline '"
+ pipeline.getName()
+ "' in application '"
+ pipeline.getApplication()
+ "', stage name: '"
+ name
+ "'");
}
String type = it.remove("type").toString();
withStage(type, name != null ? name : type, it);
});
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,16 @@ class PipelineBuilderTest {
@Test
void withStagesChecksForNull() {
PipelineBuilder pipelineBuilder = new PipelineBuilder("my-application");
// There's no exception, but also no indication that the caller is likely
// doing something wrong by passing null.
pipelineBuilder.withStages(null);
assertThrows(IllegalArgumentException.class, () -> pipelineBuilder.withStages(null));
}

@Test
void withStagesChecksForType() {
PipelineBuilder pipelineBuilder = new PipelineBuilder("my-application");
Map<String, Object> stageWithoutType = new HashMap<>();
stageWithoutType.put("name", "my-pipeline-stage");
// NullPointerException is unfortunate since the input is bad. It
// translates to a 500 http response code, so callers retry.
assertThrows(
NullPointerException.class, () -> pipelineBuilder.withStages(List.of(stageWithoutType)));
IllegalArgumentException.class,
() -> pipelineBuilder.withStages(List.of(stageWithoutType)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ void orchestrateTreatsMissingTypeAsBadRequest() throws Exception {
.characterEncoding(StandardCharsets.UTF_8.toString())
.content(objectMapper.writeValueAsString(pipelineWithStageWithoutType)))
.andDo(print())
// NB: this is a bug. Expect a bad request so callers don't retry.
.andExpect(status().isInternalServerError());
.andExpect(status().isBadRequest());
}
}

0 comments on commit f8f213b

Please sign in to comment.