Add layout file validation.

Based on voluptuous library.

Basic validation should catch typos, missing or extra attributes.
Can be expanded to do more serious validation (ie, specifying
a comment in a trigger should require the event be comment-added).

Adds a command line option to validate a named layout file and
exit.

(Also add dist/ to .gitignore.)

Change-Id: Ia864ebde1765141d4d1a52bc77033689b6210e81
Reviewed-on: https://review.openstack.org/19443
Reviewed-by: Clark Boylan <clark.boylan@gmail.com>
Reviewed-by: Jeremy Stanley <fungi@yuggoth.org>
Approved: James E. Blair <corvus@inaugust.com>
Tested-by: Jenkins
diff --git a/tests/fixtures/layouts/bad_pipelines b/tests/fixtures/layouts/bad_pipelines
new file mode 100644
index 0000000..f627208
--- /dev/null
+++ b/tests/fixtures/layouts/bad_pipelines
@@ -0,0 +1 @@
+pipelines:
diff --git a/tests/fixtures/layouts/bad_pipelines1.yaml b/tests/fixtures/layouts/bad_pipelines1.yaml
new file mode 100644
index 0000000..4207a2c
--- /dev/null
+++ b/tests/fixtures/layouts/bad_pipelines1.yaml
@@ -0,0 +1,4 @@
+pipelines:
+
+projects:
+  - name: foo
diff --git a/tests/fixtures/layouts/bad_pipelines10.yaml b/tests/fixtures/layouts/bad_pipelines10.yaml
new file mode 100644
index 0000000..5248c17
--- /dev/null
+++ b/tests/fixtures/layouts/bad_pipelines10.yaml
@@ -0,0 +1,7 @@
+pipelines:
+  - name: check
+    manager: IndependentPipelineManager
+
+projects:
+  - name: foo
+    merge-mode: foo
\ No newline at end of file
diff --git a/tests/fixtures/layouts/bad_pipelines2.yaml b/tests/fixtures/layouts/bad_pipelines2.yaml
new file mode 100644
index 0000000..e75a561
--- /dev/null
+++ b/tests/fixtures/layouts/bad_pipelines2.yaml
@@ -0,0 +1,6 @@
+pipelines:
+  - noname: check
+    manager: IndependentPipelineManager
+
+projects:
+  - name: foo
diff --git a/tests/fixtures/layouts/bad_pipelines3.yaml b/tests/fixtures/layouts/bad_pipelines3.yaml
new file mode 100644
index 0000000..0c11a85
--- /dev/null
+++ b/tests/fixtures/layouts/bad_pipelines3.yaml
@@ -0,0 +1,6 @@
+pipelines:
+  - name: check
+    manager: NonexistentPipelineManager
+
+projects:
+  - name: foo
diff --git a/tests/fixtures/layouts/bad_pipelines4.yaml b/tests/fixtures/layouts/bad_pipelines4.yaml
new file mode 100644
index 0000000..a99b9e2
--- /dev/null
+++ b/tests/fixtures/layouts/bad_pipelines4.yaml
@@ -0,0 +1,8 @@
+pipelines:
+  - name: check
+    manager: IndependentPipelineManager
+    trigger:
+      - event: non-event
+
+projects:
+  - name: foo
diff --git a/tests/fixtures/layouts/bad_pipelines5.yaml b/tests/fixtures/layouts/bad_pipelines5.yaml
new file mode 100644
index 0000000..7db7bd1
--- /dev/null
+++ b/tests/fixtures/layouts/bad_pipelines5.yaml
@@ -0,0 +1,9 @@
+pipelines:
+  - name: check
+    manager: IndependentPipelineManager
+    trigger:
+      - approval:
+          - approved: 1
+
+projects:
+  - name: foo
diff --git a/tests/fixtures/layouts/bad_pipelines6.yaml b/tests/fixtures/layouts/bad_pipelines6.yaml
new file mode 100644
index 0000000..8d313bc
--- /dev/null
+++ b/tests/fixtures/layouts/bad_pipelines6.yaml
@@ -0,0 +1,9 @@
+pipelines:
+  - name: check
+    manager: IndependentPipelineManager
+    trigger:
+      - event: comment-added
+        approved: 1
+
+projects:
+  - name: foo
diff --git a/tests/fixtures/layouts/bad_pipelines7.yaml b/tests/fixtures/layouts/bad_pipelines7.yaml
new file mode 100644
index 0000000..7517b9a
--- /dev/null
+++ b/tests/fixtures/layouts/bad_pipelines7.yaml
@@ -0,0 +1,5 @@
+pipelines:
+  - manager: IndependentPipelineManager
+
+projects:
+  - name: foo
diff --git a/tests/fixtures/layouts/bad_pipelines8.yaml b/tests/fixtures/layouts/bad_pipelines8.yaml
new file mode 100644
index 0000000..eeab038
--- /dev/null
+++ b/tests/fixtures/layouts/bad_pipelines8.yaml
@@ -0,0 +1,5 @@
+pipelines:
+  - name: check
+
+projects:
+  - name: foo
diff --git a/tests/fixtures/layouts/bad_pipelines9.yaml b/tests/fixtures/layouts/bad_pipelines9.yaml
new file mode 100644
index 0000000..ebb2e1f
--- /dev/null
+++ b/tests/fixtures/layouts/bad_pipelines9.yaml
@@ -0,0 +1,8 @@
+pipelines:
+  - name: check
+    manager: IndependentPipelineManager
+  - name: check
+    manager: IndependentPipelineManager
+
+projects:
+  - name: foo
diff --git a/tests/fixtures/layouts/bad_projects1.yaml b/tests/fixtures/layouts/bad_projects1.yaml
new file mode 100644
index 0000000..c210c43
--- /dev/null
+++ b/tests/fixtures/layouts/bad_projects1.yaml
@@ -0,0 +1,9 @@
+pipelines:
+  - name: check
+    manager: IndependentPipelineManager
+
+projects:
+  - name: foo
+    gate:
+      - test
+
diff --git a/tests/fixtures/layouts/bad_projects2.yaml b/tests/fixtures/layouts/bad_projects2.yaml
new file mode 100644
index 0000000..b91ed9d
--- /dev/null
+++ b/tests/fixtures/layouts/bad_projects2.yaml
@@ -0,0 +1,9 @@
+pipelines:
+  - name: check
+    manager: IndependentPipelineManager
+
+projects:
+  - name: foo
+    check:
+      - test
+        - foo
diff --git a/tests/fixtures/layouts/good_layout.yaml b/tests/fixtures/layouts/good_layout.yaml
new file mode 100644
index 0000000..ca024ec
--- /dev/null
+++ b/tests/fixtures/layouts/good_layout.yaml
@@ -0,0 +1,58 @@
+includes:
+  - python-file: openstack_functions.py
+
+pipelines:
+  - name: check
+    manager: IndependentPipelineManager
+    trigger:
+      - event: patchset-created
+    success:
+      verified: 1
+    failure:
+      verified: -1
+
+  - name: post
+    manager: IndependentPipelineManager
+    trigger:
+      - event: ref-updated
+        ref: ^(?!refs/).*$
+
+  - name: gate
+    manager: DependentPipelineManager
+    trigger:
+      - event: comment-added
+        approval:
+          - approved: 1
+    success:
+      verified: 2
+      code-review: 1
+      submit: true
+    failure:
+      verified: -2
+      workinprogress: true
+    start:
+      verified: 0
+
+jobs:
+  - name: ^.*-merge$
+    failure-message: Unable to merge change
+    hold-following-changes: true
+  - name: test-merge
+    parameter-function: devstack_params
+  - name: test-test
+  - name: test-merge2
+    success-pattern: http://logs.example.com/{change.number}/{change.patchset}/{pipeline.name}/{job.name}/{build.number}/success
+    failure-pattern: http://logs.example.com/{change.number}/{change.patchset}/{pipeline.name}/{job.name}/{build.number}/fail
+
+projects:
+  - name: test-org/test
+    merge-mode: cherry-pick
+    check:
+      - test-merge2:
+          - test-thing1:
+              - test-thing2
+              - test-thing3
+    gate:
+      - test-thing
+    post:
+      - test-post