diff --git a/build.gradle b/build.gradle index b150794f..b5522e38 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ dependencies { compile group: 'com.google.code.gson', name: 'gson', version: '2.6.2' compile group: 'commons-io', name: 'commons-io', version: '2.4' compile group: 'org.apache.ant', name: 'ant', version: '1.7.1' - compile group: 'com.esotericsoftware.yamlbeans', name: 'yamlbeans', version: '1.09' + compile group: 'com.github.sanjusoftware', name: 'yamlbeans', version: '1.11' testCompile group: 'junit', name: 'junit', version: '4.11' testCompile group: 'org.mockito', name: 'mockito-core', version: '1.10.19' testCompile group: 'org.hamcrest', name: 'hamcrest-core', version: '1.3' diff --git a/src/main/java/cd/go/plugin/config/yaml/YamlFileParser.java b/src/main/java/cd/go/plugin/config/yaml/YamlFileParser.java index bc91bf4f..dfdbdda9 100644 --- a/src/main/java/cd/go/plugin/config/yaml/YamlFileParser.java +++ b/src/main/java/cd/go/plugin/config/yaml/YamlFileParser.java @@ -1,6 +1,7 @@ package cd.go.plugin.config.yaml; import cd.go.plugin.config.yaml.transforms.RootTransform; +import com.esotericsoftware.yamlbeans.YamlConfig; import com.esotericsoftware.yamlbeans.YamlReader; import java.io.*; @@ -22,10 +23,14 @@ public JsonConfigCollection parseFiles(File baseDir, String[] files) { InputStream inputStream = null; try { inputStream = new FileInputStream(new File(baseDir, file)); - YamlReader reader = new YamlReader(new InputStreamReader(inputStream)); + YamlConfig config = new YamlConfig(); + config.setAllowDuplicates(false); + YamlReader reader = new YamlReader(new InputStreamReader(inputStream), config); Object rootObject = reader.read(); JsonConfigCollection filePart = rootTransform.transform(rootObject, file); collection.append(filePart); + } catch (YamlReader.YamlReaderException ex) { + collection.addError("YAML contains duplicate keys: " + ex.getMessage() , file); } catch (FileNotFoundException ex) { collection.addError("File matching Go YAML pattern disappeared", file); } catch (IOException ex) { diff --git a/src/test/java/cd/go/plugin/config/yaml/TestUtils.java b/src/test/java/cd/go/plugin/config/yaml/TestUtils.java index 9d5b4900..dbfea3f3 100644 --- a/src/test/java/cd/go/plugin/config/yaml/TestUtils.java +++ b/src/test/java/cd/go/plugin/config/yaml/TestUtils.java @@ -1,5 +1,6 @@ package cd.go.plugin.config.yaml; +import com.esotericsoftware.yamlbeans.YamlConfig; import com.esotericsoftware.yamlbeans.YamlReader; import com.google.gson.JsonElement; import com.google.gson.JsonParser; @@ -17,7 +18,9 @@ public static JsonElement readJsonObject(String path) throws IOException { } public static Object readYamlObject(String path) throws IOException { - YamlReader reader = new YamlReader(TestUtils.createReader(path)); + YamlConfig config = new YamlConfig(); + config.setAllowDuplicates(false); + YamlReader reader = new YamlReader(TestUtils.createReader(path), config); return reader.read(); } diff --git a/src/test/java/cd/go/plugin/config/yaml/YamlConfigPluginIntegrationTest.java b/src/test/java/cd/go/plugin/config/yaml/YamlConfigPluginIntegrationTest.java index b3e07170..0f2ebbf3 100644 --- a/src/test/java/cd/go/plugin/config/yaml/YamlConfigPluginIntegrationTest.java +++ b/src/test/java/cd/go/plugin/config/yaml/YamlConfigPluginIntegrationTest.java @@ -169,6 +169,27 @@ public void shouldRespondSuccessWithErrorMessagesToParseDirectoryRequestWhenSimp assertThat(errors.get(0).getAsJsonObject().getAsJsonPrimitive("location").getAsString(), is("simple-invalid.gocd.yaml")); } + @Test + public void shouldRespondSuccessWithErrorMessagesToParseDirectoryRequestWhenDuplicateKeysCaseFile() throws UnhandledRequestTypeException, IOException { + setupCase("simpleInvalidCase", "duplicate-materials"); + + DefaultGoPluginApiRequest parseDirectoryRequest = new DefaultGoPluginApiRequest("configrepo", "1.0", "parse-directory"); + String requestBody = "{\n" + + " \"directory\":\"simpleInvalidCase\",\n" + + " \"configurations\":[]\n" + + "}"; + parseDirectoryRequest.setRequestBody(requestBody); + + GoPluginApiResponse response = plugin.handle(parseDirectoryRequest); + assertThat(response.responseCode(), is(DefaultGoPluginApiResponse.SUCCESS_RESPONSE_CODE)); + JsonObject responseJsonObject = getJsonObjectFromResponse(response); + JsonArray errors = (JsonArray) responseJsonObject.get("errors"); + JsonArray pipelines = responseJsonObject.get("pipelines").getAsJsonArray(); + assertThat(pipelines.size(), is(0)); + assertThat(errors.get(0).getAsJsonObject().getAsJsonPrimitive("message").getAsString(), is("YAML contains duplicate keys: Line 9, column 20: Duplicate key found 'upstream'")); + assertThat(errors.get(0).getAsJsonObject().getAsJsonPrimitive("location").getAsString(), is("duplicate-materials.gocd.yaml")); + } + private void setupCase(String folder, String caseName) throws IOException { File caseFolder = new File(folder); FileUtils.deleteDirectory(caseFolder); diff --git a/src/test/java/cd/go/plugin/config/yaml/transforms/RootTransformTest.java b/src/test/java/cd/go/plugin/config/yaml/transforms/RootTransformTest.java index edcfeb88..63392f33 100644 --- a/src/test/java/cd/go/plugin/config/yaml/transforms/RootTransformTest.java +++ b/src/test/java/cd/go/plugin/config/yaml/transforms/RootTransformTest.java @@ -1,6 +1,8 @@ package cd.go.plugin.config.yaml.transforms; import cd.go.plugin.config.yaml.JsonConfigCollection; +import cd.go.plugin.config.yaml.YamlConfigException; +import com.esotericsoftware.yamlbeans.YamlReader; import org.junit.Before; import org.junit.Test; @@ -9,6 +11,7 @@ import static cd.go.plugin.config.yaml.TestUtils.readYamlObject; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; public class RootTransformTest { @@ -40,6 +43,12 @@ public void shouldTransformRootWhen2PipelinesAnd2Environments() throws IOExcepti assertThat(collection.getJsonObject().get("pipelines").getAsJsonArray().size(), is(2)); } + @Test(expected = YamlReader.YamlReaderException.class) + public void shouldNotTransformRootWhenYAMLHasDuplicateKeys() throws IOException { + readRootYaml("duplicate.materials.pipe"); + fail("should have thrown duplicate keys error"); + } + private JsonConfigCollection readRootYaml(String caseFile) throws IOException { return rootTransform.transform(readYamlObject("parts/roots/" + caseFile + ".yaml"), "test code"); } diff --git a/src/test/resources/examples/duplicate-materials.gocd.yaml b/src/test/resources/examples/duplicate-materials.gocd.yaml new file mode 100644 index 00000000..659ae073 --- /dev/null +++ b/src/test/resources/examples/duplicate-materials.gocd.yaml @@ -0,0 +1,10 @@ +pipelines: + pipe1: + group: group + materials: + upstream: + pipeline: upstream-pipeline-1 + stage: test1 + upstream: + pipeline: upstream-pipeline-2 + stage: test2 \ No newline at end of file diff --git a/src/test/resources/parts/roots/duplicate.materials.pipe.yaml b/src/test/resources/parts/roots/duplicate.materials.pipe.yaml new file mode 100644 index 00000000..659ae073 --- /dev/null +++ b/src/test/resources/parts/roots/duplicate.materials.pipe.yaml @@ -0,0 +1,10 @@ +pipelines: + pipe1: + group: group + materials: + upstream: + pipeline: upstream-pipeline-1 + stage: test1 + upstream: + pipeline: upstream-pipeline-2 + stage: test2 \ No newline at end of file