Skip to content

Commit

Permalink
clean up; add invalid and rich case to itest
Browse files Browse the repository at this point in the history
  • Loading branch information
tomzo committed Jul 16, 2016
1 parent 9078a47 commit cab6534
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 73 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/richCase/rich.gocd.yaml
/simpleInvalidCase/simple-invalid.gocd.yaml
/simpleCase/simple.gocd.yaml
*.class

Expand Down
150 changes: 124 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

# gocd-yaml-config-plugin

Plugin to declare [Go's](go.cd) pipelines and environments configuration in YAML.

With this plugin and GoCD `>= 16.7.0` you can keep your pipeline and environments
configuration in source control.
[GoCD](go.cd) server plugin to keep **pipelines** and **environments**
configuration in source-control in [YAML](http://www.yaml.org/).
See [this document](https://docs.google.com/document/d/1_eGZaqIz9ydnYQJ_Xrcb3obXc-T6jIfV_pgZQNCydVk/pub)
to find out what are GoCD configuration repositories.

This is second GoCD configuration plugin, which enhances some of shortcomings of
[JSON configuration plugin](https://github.com/tomzo/gocd-json-config-plugin)
Expand All @@ -16,6 +16,27 @@ This is second GoCD configuration plugin, which enhances some of shortcomings of
* **Comments in configuration files** - YAML supports comments,
so you can explain why pipeline/environment it is configured like this.

## Some highlights

* Shorter syntax for declaring [single-job stages](#single-job-stage)
* Very short syntax for [declaring tasks with script executor plugin](#script)

## Setup

Download plugin from releases page and place in your Go server (`>= 16.7.0`)
in `plugins/external` directory.

Add `config-repos` element right above first `<pipelines />`. Then you can
add any number of YAML configuration repositories as such:

```xml
<config-repos>
<config-repo plugin="yaml.config.plugin">
<git url="https://github.com/tomzo/gocd-yaml-config-example.git" />
</config-repo>
</config-repos>
```

### Example

More examples are in [test resources](src/test/resources/examples/).
Expand Down Expand Up @@ -77,31 +98,16 @@ pipelines:
- script: ./build.sh ci
```
## Setup
Download plugin from releases page and place in you Go server (`>= 16.7.0`)
in `plugins/external` directory.

Add `config-repos` element right above first `<pipelines />`. Then you can
add any number of YAML configuration repositories as such:

```xml
<config-repos>
<config-repo plugin="yaml.config.plugin">
<git url="https://github.com/tomzo/gocd-yaml-config-example.git" />
</config-repo>
</config-repos>
```

# Specification
See [official GoCD XML configuration reference](https://docs.go.cd/current/configuration/configuration_reference.html)
for details about each element.
for details about each element. Below is a reference of format supported by this plugin.
Feel free to improve it!
1. [Environment](#environment)
1. [Environment variables](#environment-variables)
1. [Pipeline](#pipeline)
* [Mingle](#mingle)
* [Tabs](#tabs)
* [Tracking tool](#tracking-tool)
* [Timer](#timer)
1. [Stage](#stage)
Expand Down Expand Up @@ -154,9 +160,9 @@ All elements available on a pipeline object are:
* `group`
* `label_template`
* `locking`
* `tracking_tool` or `mingle`
* `timer`
* `environment_variables`
* [tracking_tool](#tracking-tool) or `mingle`
* [timer](#timer)
* [environment_variables](#environment-variables)
* `secure_variables`
* [materials](#materials)
* [stages](#stage)
Expand Down Expand Up @@ -186,12 +192,28 @@ Please note:
* [parameters](https://docs.go.cd/current/configuration/configuration_reference.html#params) are not supported
* pipeline declares a group to which it belongs

### Tracking tool

```yaml
tracking_tool:
link: "http://your-trackingtool/yourproject/${ID}"
regex: "evo-(\\d+)"
```

### Timer

```yaml
timer:
spec: "0 15 10 * * ? *"
only_on_changes: yes
```

## Stage

A minimal stage must contain `jobs:` element or `tasks:` in [single-job stage case](single-job-stage).
```yaml
build:
jobbs:
jobs:
firstJob:
...
secondJob:
Expand Down Expand Up @@ -240,6 +262,65 @@ approval:

## Job

[Job](https://docs.go.cd/current/configuration/configuration_reference.html#job) is a hash starting with jobs name:

```yaml
test:
run_instances: 7
environment_variables:
LD_LIBRARY_PATH: .
tabs:
test: results.xml
resources:
- linux
artifacts:
- test:
source: src
destination: dest
- build:
source: bin
properties:
perf:
source: test.xml
xpath: "substring-before(//report/data/all/coverage[starts-with(@type,\u0027class\u0027)]/@value, \u0027%\u0027)"
tasks:
...
```

### Run many instances

Part of job object can be [number of job to runs](https://docs.go.cd/current/advanced_usage/admin_spawn_multiple_jobs.html):
```yaml
run_instances: 7
```
Or to run on all agents:
```yaml
run_instances: all
```

### Tabs

Tabs are a hash with `<tab-name>: <path>` pairs.
Path should exist in Go servers artifacts.
```yaml
tabs:
tests: test-reports/index.html
gauge: functional-reports/index.html
```

### Property

Job can have properties, declared as a hash:
```yaml
properties:
cov1: # this is the name of property
source: test.xml
xpath: "substring-before(//report/data/all/coverage[starts-with(@type,\u0027class\u0027)]/@value, \u0027%\u0027)"
performance.ind1.mbps:
source: PerfTestReport.xml
xpath: "//PerformanceSuiteReport/WriteOnly/MBps"
```

### Single job stage

A common use case is that stage has only one job. This plugin provides a shorthand
Expand Down Expand Up @@ -502,6 +583,23 @@ Above executes a **single line** script:
./build.sh compile && make test
```

## Environment

*NOTE: The agents should be a guid, which is currently impossible to get for user*

```yaml
testing:
environment_variables:
DEPLOYMENT: testing
secure_variables:
ENV_PASSWORD: "s&Du#@$xsSa"
pipelines:
- example-deploy-testing
- build-testing
agents:
- 123
```

### Environment variables

[Environment variables](https://docs.go.cd/current/configuration/configuration_reference.html#environmentvariables)
Expand Down
16 changes: 0 additions & 16 deletions src/main/java/cd/go/plugin/config/yaml/RootParser.java

This file was deleted.

28 changes: 28 additions & 0 deletions src/test/java/cd/go/plugin/config/yaml/JsonObjectMatcher.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cd.go.plugin.config.yaml;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.hamcrest.Description;
Expand Down Expand Up @@ -27,12 +28,39 @@ protected boolean matchesSafely(JsonObject actual) {
if(!inner.matchesSafely(otherObj))
return false;
}
else if(field.getValue().isJsonArray()){
JsonArray thisArray = field.getValue().getAsJsonArray();
JsonArray otherArray = actual.get(field.getKey()).getAsJsonArray();
if(otherArray == null)
return false;
if(thisArray.size() != otherArray.size())
return false;
for(int i = 0; i < thisArray.size(); i++){
JsonElement thisItem = thisArray.get(i);
if(!equalToAnyOther(thisItem,otherArray))
return false;
}
}
else if(!field.getValue().equals(actual.get(field.getKey())))
return false;
}
return true;
}

private boolean equalToAnyOther(JsonElement thisItem, JsonArray otherArray) {
for(int i = 0; i < otherArray.size();i++) {
JsonElement otherItem = otherArray.get(i);
if (thisItem.isJsonObject()) {
if (!otherItem.isJsonObject())
continue;
if (new JsonObjectMatcher(thisItem.getAsJsonObject()).matchesSafely(otherItem.getAsJsonObject()))
return true;
} else if (thisItem.equals(otherItem))
return true;
}
return false;
}

@Override
public void describeTo(Description description) {
description.appendText(expected.toString());
Expand Down
21 changes: 0 additions & 21 deletions src/test/java/cd/go/plugin/config/yaml/RootParserTest.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static cd.go.plugin.config.yaml.TestUtils.getResourceAsStream;
import static cd.go.plugin.config.yaml.TestUtils.readJsonObject;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsCollectionContaining.hasItem;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
Expand Down Expand Up @@ -113,7 +114,7 @@ public void shouldRespondSuccessToParseDirectoryRequestWhenEmpty() throws Unhand

@Test
public void shouldRespondSuccessToParseDirectoryRequestWhenSimpleCaseFile() throws UnhandledRequestTypeException, IOException {
setupSimpleCase();
setupCase("simpleCase", "simple");

DefaultGoPluginApiRequest parseDirectoryRequest = new DefaultGoPluginApiRequest("configrepo","1.0","parse-directory");
String requestBody = "{\n" +
Expand All @@ -132,12 +133,54 @@ public void shouldRespondSuccessToParseDirectoryRequestWhenSimpleCaseFile() thro
assertThat(responseJsonObject,is(new JsonObjectMatcher(expected)));
}

private void setupSimpleCase() throws IOException {
File simpleCase = new File("simpleCase");
FileUtils.deleteDirectory(simpleCase);
FileUtils.forceMkdir(simpleCase);
File simpleFile = new File("simpleCase/simple.gocd.yaml");
InputStream in = getResourceAsStream("examples/simple.gocd.yaml");
@Test
public void shouldRespondSuccessToParseDirectoryRequestWhenRichCaseFile() throws UnhandledRequestTypeException, IOException {
setupCase("richCase", "rich");

DefaultGoPluginApiRequest parseDirectoryRequest = new DefaultGoPluginApiRequest("configrepo","1.0","parse-directory");
String requestBody = "{\n" +
" \"directory\":\"richCase\",\n" +
" \"configurations\":[]\n" +
"}";
parseDirectoryRequest.setRequestBody(requestBody);

GoPluginApiResponse response = plugin.handle(parseDirectoryRequest);
assertThat(response.responseCode(), is(DefaultGoPluginApiResponse.SUCCESS_RESPONSE_CODE));
JsonObject responseJsonObject = getJsonObjectFromResponse(response);
assertThat(responseJsonObject.get("errors"), Is.<JsonElement>is(new JsonArray()));
JsonArray pipelines = responseJsonObject.get("pipelines").getAsJsonArray();
assertThat(pipelines.size(),is(1));
JsonObject expected = (JsonObject)readJsonObject("examples.out/rich.gocd.json");
assertThat(responseJsonObject,is(new JsonObjectMatcher(expected)));
}

@Test
public void shouldRespondSuccessWithErrorMessagesToParseDirectoryRequestWhenSimpleInvalidCaseFile() throws UnhandledRequestTypeException, IOException {
setupCase("simpleInvalidCase", "simple-invalid");

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("Failed to parse pipeline pipe1; expected a hash of pipeline materials"));
assertThat(errors.get(0).getAsJsonObject().getAsJsonPrimitive("location").getAsString(),is("simple-invalid.gocd.yaml"));
}

private void setupCase(String folder, String caseName) throws IOException {
File caseFolder = new File(folder);
FileUtils.deleteDirectory(caseFolder);
FileUtils.forceMkdir(caseFolder);
File simpleFile = new File(folder, caseName + ".gocd.yaml");
InputStream in = getResourceAsStream("examples/" + caseName + ".gocd.yaml");
OutputStream out = new FileOutputStream(simpleFile);
IOUtils.copy(in,out);
in.close();
Expand Down
Loading

0 comments on commit cab6534

Please sign in to comment.