com.cinchapi.concourse.util.ConcourseCodebase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of concourse-ete-test-core Show documentation
Show all versions of concourse-ete-test-core Show documentation
A framework for writing end-to-end integration tests using the Concourse client and server
/*
* Copyright (c) 2013-2017 Cinchapi Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cinchapi.concourse.util;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cinchapi.common.process.Processes;
import com.cinchapi.common.process.Processes.ProcessResult;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Throwables;
import com.google.common.collect.Sets;
/**
* An object that can be used to programmatically interact with a local instance
* of the Concourse codebase.
*
* @author Jeff Nelson
*/
public class ConcourseCodebase {
/**
* If necessary, clone the codebase from Github to a local directory and
* return a handler than can be used for programmatic interaction. Multiple
* attempts to clone the codebase from Github will return the same
* {@link ConcourseCodebase codebase object} for the lifetime of the JVM
* process.
*
* @return the handler to interact with the codebase
*/
public static ConcourseCodebase cloneFromGithub() {
if(INSTANCE == null) {
// First we must check to see if the current JVM process is running
// within a directory that is a clone/fork of the github repo. We
// must keep checking the parent directories until we reach the root
// of the repo.
String dir = System.getProperty("user.dir");
String odir = null;
boolean checkParent = true;
while (checkParent) {
try {
Process originProc = new ProcessBuilder("git", "config",
"--get", "remote.origin.url")
.directory(new File(dir)).start();
Process upstreamProc = new ProcessBuilder("git", "config",
"--get", "remote.upstream.url")
.directory(new File(dir)).start();
List originLines = Processes.getStdOut(originProc);
List upstreamLines = Processes
.getStdOut(upstreamProc);
String originOut = !originLines.isEmpty()
? originLines.get(0) : "";
String upstreamOut = !upstreamLines.isEmpty()
? upstreamLines.get(0) : "";
if(VALID_REMOTE_URLS.contains(originOut)
|| VALID_REMOTE_URLS.contains(upstreamOut)) {
checkParent = true;
odir = dir;
dir = dir + "/..";
}
else {
dir = odir;
checkParent = false;
}
}
catch (Exception e) {
dir = odir;
checkParent = false;
}
}
Path cache = Paths.get(REPO_CACHE_FILE);
if(dir == null) {
// If last cloned dir still exists use it, but perform git pull
// to fetch latest changes
if(cache.toFile().exists()) {
List list = FileOps.readLines(REPO_CACHE_FILE);
if(list.size() > 0) {
dir = list.iterator().next();
if(!Paths.get(dir, ".git", "index").toFile().exists()) {
dir = null;
cache.toFile().delete();
}
}
}
if(dir != null) {
try {
LOGGER.info(
"Running 'git pull' to fetch latest changes from Github...");
ProcessBuilder pb = Processes.getBuilder("git", "pull");
pb.directory(new File(dir));
Process p = pb.start();
int exitVal = p.waitFor();
if(exitVal != 0) {
throw new RuntimeException(
Processes.getStdErr(p).toString());
}
}
catch (Exception e) {
throw Throwables.propagate(e);
}
}
else {
// If we're not currently in a github clone/fork, then go
// ahead and clone to some temp directory
dir = getTempDirectory();
StringBuilder sb = new StringBuilder();
sb.append("git clone ");
sb.append(GITHUB_CLONE_URL);
sb.append(" ");
sb.append(dir);
try {
LOGGER.info(
"Running {} to clone the concourse repo from Github...",
sb.toString());
Process p = Runtime.getRuntime().exec(sb.toString());
int exitVal = p.waitFor();
if(exitVal != 0) {
throw new RuntimeException(
Processes.getStdErr(p).toString());
}
// store path of the clone
cache.toFile().getParentFile().mkdirs();
cache.toFile().createNewFile();
FileOps.write(dir, cache.toString());
}
catch (Exception e) {
throw Throwables.propagate(e);
}
}
}
INSTANCE = new ConcourseCodebase(dir);
}
return INSTANCE;
}
/**
* Return a temporary directory.
*
* @return a temp directory to use in the test
*/
private static String getTempDirectory() {
try {
return Files.createTempDirectory("concourse").toString();
}
catch (IOException e) {
throw Throwables.propagate(e);
}
}
/**
* File in user.home directory that will hold path to last clone
*/
@VisibleForTesting
protected static final String REPO_CACHE_FILE = Paths
.get(System.getProperty("user.home"), ".cinchapi", "concourse",
"codebase", "cache.location")
.toString();
/**
* Singleton instance.
*/
@VisibleForTesting
protected static ConcourseCodebase INSTANCE = null;
/**
* The name of the file that contains a cache of the state of the codebase.
*/
private static String CODE_STATE_CACHE_FILENAME = ".codestate";
/**
* The URL from which the repo can be cloned
*/
private static String GITHUB_CLONE_URL = "https://github.com/cinchapi/concourse.git";
/**
* The Logger instance.
*/
private static final Logger LOGGER = LoggerFactory
.getLogger(ConcourseCodebase.class);
/**
* Valid remote URLs for a local repo's origin or upstream that indicate we
* can assume that the local repo is cloned or forked from the official
* repo.
*/
private static Set VALID_REMOTE_URLS = Sets.newHashSet(
"https://github.com/cinchapi/concourse.git",
"[email protected]:cinchapi/concourse.git");
/**
* The path to the codebase on the local machine.
*/
private final String path;
/**
* Construct a new instance.
*
* @param path - the path to the codebase
*/
private ConcourseCodebase(String path) {
this.path = path;
}
/**
* Create a concourse-server.bin installer from this
* {@link ConcourseCodebase codebase} and return the path to the installer
* file.
*
* @return the path to the installer file
*/
public String buildInstaller() {
try {
if(!hasInstaller() || hasCodeChanged()) {
LOGGER.info("A code change was detected, so a NEW installer "
+ "is being generated.");
Process p = new ProcessBuilder("./gradlew", "clean",
"installer").directory(new File(path)).start();
Processes.waitForSuccessfulCompletion(p);
LOGGER.info("Finished generating installer.");
}
return getInstallerPath();
}
catch (Exception e) {
throw Throwables.propagate(e);
}
}
/**
* Return the path to this {@link ConcourseCodebase codebase}.
*
* @return the path to the codebase
*/
public String getPath() {
return path;
}
/**
* Return the path to the installer file for this codebase or an empty
* string if no installer file exists.
*
* @return the path to the installer file
*/
private String getInstallerPath() {
try {
String cmd = new StringBuilder().append("ls -a ").append(path)
.append("/concourse-server/build/distributions | grep bin")
.toString();
Process p = Processes.getBuilderWithPipeSupport(cmd).start();
try {
ProcessResult result = Processes.waitForSuccessfulCompletion(p);
String installer = result.out().get(0);
if(!installer.isEmpty()) {
installer = path + "/concourse-server/build/distributions/"
+ installer;
}
return installer;
}
catch (Exception e) {
return "";
}
}
catch (Exception e) {
throw Throwables.propagate(e);
}
}
/**
* Check to see if there has been a code change since the last check.
*
* @return {@code true} if it appears that the code has changed
*/
private boolean hasCodeChanged() {
Path cache = Paths.get(getPath(), CODE_STATE_CACHE_FILENAME)
.toAbsolutePath();
String cmd = "(git status; git diff; git log -n 1) | "
+ (Platform.isMacOsX() ? "md5" : "md5sum");
try {
Process p = Processes.getBuilderWithPipeSupport(cmd)
.directory(new File(getPath())).start();
ProcessResult result = Processes.waitForSuccessfulCompletion(p);
String state = result.out().get(0);
FileOps.touch(cache.toString());
String cached = FileOps.read(cache.toString());
boolean changed = false;
if(!state.equals(cached)) {
FileOps.write(state, cache.toString());
changed = true;
}
return changed;
}
catch (IOException e) {
throw Throwables.propagate(e);
}
}
/**
* Return {@code true} if the codebase has an installer file, {@code false}
* otherwise.
*
* @return a boolean that indicates whether the codebase has an installer
* file or not
*/
private boolean hasInstaller() {
return !getInstallerPath().isEmpty();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy