diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1d68a72
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+\#*\#
+*~
+*.bak
+*.swp
+*.swo
+.DS_Store
+
+atlassian-ide-plugin.xml
+*.class
+
+target/
+test-output/
+
+.project
+.classpath
+.settings/
+.metadata/
+
+.idea/
+*.iml
+
+prodDb.*
+
+*.log
+brooklyn*.log.*
+
+*brooklyn-persisted-state/
+
+*node_modules/
+*bower_components/
+
+ignored
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..20da0f3
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,38 @@
+
+ 4.0.0
+
+ org.brooklyncentral.catalog
+ parent
+ 1.0-SNAPSHOT
+ pom
+
+ Brooklyn-Central Community Catalog Directory
+
+ Brooklyn-Central Community Catalog Directory parent / project root,
+ serving as the ancestor POM for all projects --
+ declaring versions, profiles, and the modules to build.
+
+
+ http://catalog.cloudsoft.io/
+
+
+ 0.9.0
+
+
+
+ validation-testing
+
+
+
+
+
+ maven-compiler-plugin
+
+ 1.8
+ 1.8
+
+
+
+
+
diff --git a/validation-testing/pom.xml b/validation-testing/pom.xml
new file mode 100644
index 0000000..b6e5c4d
--- /dev/null
+++ b/validation-testing/pom.xml
@@ -0,0 +1,84 @@
+
+
+ 4.0.0
+
+
+ org.brooklyncentral.catalog
+ parent
+ 1.0-SNAPSHOT
+
+
+ validation-testing
+
+ Brooklyn-Central Community Catalog Blueprint Validation Testing
+
+ Tests to determine if blueprint repositories are correctly formatted
+ and can be successfully scraped by the catalog server.
+
+
+
+ 6.1.1
+ 4.3.0.201604071810-r
+ 19.0
+ 2.4
+ 1.7.21
+ 1.0-SNAPSHOT
+
+
+
+
+ org.testng
+ testng
+ ${testng.version}
+ test
+
+
+ org.eclipse.jgit
+ org.eclipse.jgit
+ ${jgit.version}
+ test
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+ test
+
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j-simple.version}
+
+
+ io.cloudsoft.catalog
+ util-validator
+ ${util-validator.version}
+ test
+
+
+
+
+
+
+ false
+
+ cloudsoft-release
+ libs-release
+ https://artifactory.cloudsoftcorp.com/artifactory/libs-release-local
+
+
+
+ cloudsoft-snapshots
+ libs-snapshot
+ https://artifactory.cloudsoftcorp.com/artifactory/libs-snapshot-local
+
+
+
diff --git a/validation-testing/src/test/java/org/brooklyncentral/catalog/validation/testing/PullRequestValidationTest.java b/validation-testing/src/test/java/org/brooklyncentral/catalog/validation/testing/PullRequestValidationTest.java
new file mode 100644
index 0000000..382ea03
--- /dev/null
+++ b/validation-testing/src/test/java/org/brooklyncentral/catalog/validation/testing/PullRequestValidationTest.java
@@ -0,0 +1,169 @@
+package org.brooklyncentral.catalog.validation.testing;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+import io.cloudsoft.catalog.util.CatalogValidator;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+
+public class PullRequestValidationTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PullRequestValidationTest.class);
+
+ private static final String DIRECTORY_REPO_URI_ENV_VAR = "DIRECTORY_REPO_URI";
+ private static final String PR_NUMBER_ENV_VAR = "PR_NUMBER";
+ private static final String AUTH_TOKEN_ENV_VAR = "AUTH_TOKEN";
+
+ private static final String DEFAULT_REPOSITORIES_URI = "https://github.com/brooklyncentral/brooklyn-community-catalog";
+ private static final String FILE_TO_DIFF = "directory.yaml";
+ private static final String BRANCH_TO_TEST = "master";
+
+ private File cloneDirectory;
+
+ private Git git;
+
+ private String masterHash;
+ private String prHash;
+
+ @BeforeClass
+ public void setUp() throws Exception {
+ String repoUrl = Optional.fromNullable(System.getenv(DIRECTORY_REPO_URI_ENV_VAR)).or(DEFAULT_REPOSITORIES_URI);
+ String prNumber = System.getenv(PR_NUMBER_ENV_VAR);
+ String authToken = System.getenv(AUTH_TOKEN_ENV_VAR);
+
+ checkNotNull(prNumber, "PR number environment variable 'PR_NUMBER' must be set.");
+
+ cloneDirectory = Files.createTempDir();
+
+ CloneCommand cloneCommand = Git.cloneRepository()
+ .setDirectory(cloneDirectory)
+ .setURI(repoUrl)
+ .setBranchesToClone(ImmutableList.of("refs/heads/master"))
+ .setBranch("refs/heads/master");
+
+ if (authToken != null) {
+ cloneCommand = cloneCommand.setCredentialsProvider(new UsernamePasswordCredentialsProvider(authToken, ""));
+ }
+
+ git = cloneCommand.call();
+ git.fetch().setRefSpecs(new RefSpec(String.format("+refs/pull/%s/head:refs/remotes/origin/pr/%s", prNumber, prNumber))).call();
+
+ masterHash = git.getRepository().exactRef("refs/heads/master").getObjectId().getName();
+ prHash = git.getRepository().exactRef("refs/remotes/origin/pr/" + prNumber).getObjectId().getName();
+ }
+
+ @AfterClass
+ public void tearDown() throws Exception {
+ FileUtils.deleteDirectory(cloneDirectory);
+ }
+
+ @Test
+ public void validateNewRepos() throws Exception {
+ List urlsToValidate = getURLsToValidate();
+
+ LOG.info("There are " + urlsToValidate.size() + " URL(s) to validate.");
+
+ for (String urlToValidate : urlsToValidate) {
+ LOG.info("Validating URL: '" + urlToValidate + "'");
+
+ try {
+ CatalogValidator catalogValidator = new CatalogValidator(urlToValidate, BRANCH_TO_TEST);
+ catalogValidator.validate();
+ } catch (Exception e) {
+ fail("Validation failed for URL: '" + urlToValidate + "'", e);
+ }
+
+ LOG.info("Successfully validated URL: '" + urlToValidate + "'");
+ }
+
+ LOG.info("All URL(s) successfully validated.");
+ }
+
+ private List getURLsToValidate() throws Exception {
+ String diff = getDiff();
+ List diffLines = IOUtils.readLines(new StringReader(diff));
+
+ List urlsToValidate = new LinkedList<>();
+
+ for (String diffLine : diffLines) {
+ if (diffLine.startsWith("+-")) {
+ String urlToValidate = diffLine.replace("+-", "").trim();
+ urlsToValidate.add(urlToValidate);
+ }
+ }
+
+ return urlsToValidate;
+ }
+
+ private String getDiff() throws Exception {
+ AbstractTreeIterator oldTreeIterator = generateTreeIterator(git.getRepository(), masterHash);
+ AbstractTreeIterator newTreeIterator = generateTreeIterator(git.getRepository(), prHash);
+
+ List diff = git.diff()
+ .setOldTree(oldTreeIterator)
+ .setNewTree(newTreeIterator)
+ .setPathFilter(PathFilter.create(FILE_TO_DIFF))
+ .call();
+
+ assertTrue(diff.size() == 1, "Exactly one diff must match for file: '" + FILE_TO_DIFF + "'.");
+ DiffEntry entry = diff.get(0);
+
+ OutputStream diffOutputStream = new ByteArrayOutputStream();
+
+ try (DiffFormatter formatter = new DiffFormatter(diffOutputStream)) {
+ formatter.setRepository(git.getRepository());
+ formatter.format(entry);
+ }
+
+ return diffOutputStream.toString();
+ }
+
+ private static AbstractTreeIterator generateTreeIterator(Repository repository, String objectId) throws Exception {
+ try (RevWalk revWalk = new RevWalk(repository)) {
+ RevCommit revCommit = revWalk.parseCommit(ObjectId.fromString(objectId));
+ RevTree revTree = revWalk.parseTree(revCommit.getTree().getId());
+
+ CanonicalTreeParser canonicalTreeParser = new CanonicalTreeParser();
+ try (ObjectReader objectReader = repository.newObjectReader()) {
+ canonicalTreeParser.reset(objectReader, revTree.getId());
+ }
+
+ revWalk.dispose();
+
+ return canonicalTreeParser;
+ }
+ }
+}