All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cinchapi.concourse.util.ConcourseCodebase Maven / Gradle / Ivy

Go to download

A framework for writing end-to-end integration tests using the Concourse client and server

There is a newer version: 0.11.5
Show newest version
/*
 * 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