ezbake.deployer.publishers.openShift.RhcApplication.java Source code

Java tutorial

Introduction

Here is the source code for ezbake.deployer.publishers.openShift.RhcApplication.java

Source

/*   Copyright (C) 2013-2015 Computer Sciences Corporation
 *
 * 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 ezbake.deployer.publishers.openShift;

import com.openshift.client.IApplication;
import com.openshift.client.IDomain;
import ezbake.deployer.impl.BasicFileAttributes;
import ezbake.deployer.impl.FileVisitResult;
import ezbake.deployer.impl.Files;
import ezbake.deployer.impl.PosixFilePermission;
import ezbake.deployer.impl.SimpleFileVisitor;
import ezbake.services.deploy.thrift.DeploymentException;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
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.api.PullCommand;
import org.eclipse.jgit.api.PullResult;
import org.eclipse.jgit.api.PushCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.PushResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;

import static org.apache.commons.io.FileUtils.openOutputStream;

/**
 * The Rhc(Red Hat Cloud, e.g. OpenShift) application context.  This is the information related to the artifact for
 * deployment to OpenShift.  It has the Git Repository pointer, the Application pointer to within OpenShift and the
 * directory to where the application is stored on local disk (for modification).
 */
public class RhcApplication {
    private static final Logger log = LoggerFactory.getLogger(RhcApplication.class);

    private Git gitRepo;
    private IApplication applicationInstance;
    private IDomain applicationDomain;
    private File appTmpDir;
    private CredentialsProvider gitCredentialsProvider;

    public RhcApplication(Git gitRepo, IApplication applicationInstance, IDomain applicationDomain, File appTmpDir,
            CredentialsProvider gitCredentialsProvider) throws DeploymentException {
        setGitRepo(gitRepo);
        this.applicationInstance = applicationInstance;
        this.applicationDomain = applicationDomain;
        this.appTmpDir = appTmpDir;
        this.gitCredentialsProvider = gitCredentialsProvider;
    }

    public RhcApplication(IApplication applicationInstance, IDomain applicationDomain, File appTmpDir,
            CredentialsProvider gitCredentialsProvider) {
        this.applicationInstance = applicationInstance;
        this.applicationDomain = applicationDomain;
        this.appTmpDir = appTmpDir;
        this.gitCredentialsProvider = gitCredentialsProvider;
    }

    /**
     * Get the Git Repository object for this RhcApplication artifact
     * If the gitRepository hasn't been initialized yet, it will clone the git repository via {@link #getOrCreateGitRepo()}
     *
     * @return Git Repository object for this RhcApplication artifact
     */
    public Git getGitRepo() throws DeploymentException {
        if (gitRepo == null)
            return getOrCreateGitRepo();
        else
            return gitRepo;
    }

    /**
     * Sets the git repository object.  Probably not useful but here for Java Bean completeness
     *
     * @param gitRepo - git repository to set it to
     */
    public void setGitRepo(Git gitRepo) throws DeploymentException {
        this.gitRepo = gitRepo;

        try {
            StoredConfig config = gitRepo.getRepository().getConfig();
            config.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_FILEMODE, true);
            config.save();
        } catch (IOException e) {
            log.error("There was an error saving the  git configuration to disk", e);
            throw new DeploymentException("Could not save git configuration: " + e.getMessage());
        }
    }

    /**
     * Get the application instance of the OpenShift application for this artifact
     *
     * @return application instance of the OpenShift application for this artifact
     */
    public IApplication getApplicationInstance() {
        return applicationInstance;
    }

    /**
     * Sets the application instance for OpenShift for this artifact.  Probably not useful but here for Java Bean completeness
     *
     * @param applicationInstance - application instance to set it to
     */
    public void setApplicationInstance(IApplication applicationInstance) {
        this.applicationInstance = applicationInstance;
    }

    /**
     * get the OpenShift domain for this application.
     *
     * @return - The OpenShift domain this application is on.
     */
    public IDomain getApplicationDomain() {
        return applicationDomain;
    }

    /**
     * Set the OpenShift domain for this application. Probably not useful but here for Java Bean completeness
     *
     * @param applicationDomain - Domain to set it to
     */
    public void setApplicationDomain(IDomain applicationDomain) {
        this.applicationDomain = applicationDomain;
    }

    /**
     * Get the directory to which this OpenShift application is checked out to on local disk.
     * This directory isn't guaranteed to persist across different sessions or launches of the deployer.
     *
     * @return The application temp directory. (A git repository)
     */
    public File getAppTmpDir() {
        return appTmpDir;
    }

    /**
     * Sets the application temp directory.  This will not copy anything over to the new location.  This is probably
     * not useful but here for Java Bean completeness
     *
     * @param appTmpDir
     */
    public void setAppTmpDir(File appTmpDir) {
        this.appTmpDir = appTmpDir;
    }

    /**
     * Get the application name from OpenShift.  Should match the application name given to the artifact.
     *
     * @return The application name
     */
    public String getApplicationName() {
        return this.applicationInstance.getName();
    }

    /**
     * Tell this class to update the local copy of the artifact to REPLACE COMPLETELY with the artifact given by the
     * artifact tar.gz byte[] given.  This will not preserve any old files in the repository and will slam the entire
     * directory.  Readying the new artifact to be published to openshift via the {@link #publishChanges()} method.
     * <p/>
     * This method assumes anything that will belong into the repository is already in the tar file (no SSL certs are added)
     *
     * @param artifact - byte[] containing the tar.gz data for the artifact.
     * @param version  - Version of the application to stamp this binary with.  This way we can track the version easily.
     * @throws DeploymentException - On any errors updating the artifact. Including either git errors or OpenShift errors.
     */
    public void updateWithTarBall(byte[] artifact, String version) throws DeploymentException {
        clearProjectDirectory();
        extractTarGzFile(artifact);
        addVersionToWorkingTree(version);
        addFilesToGitWorkingTree();
    }

    public void addStreamAsFile(File p, InputStream ios) throws DeploymentException {
        addStreamAsFile(p, ios, null);
    }

    public void addStreamAsFile(File p, InputStream ios, Set<PosixFilePermission> filePermissions)
            throws DeploymentException {
        File resolvedPath = Files.resolve(getGitRepoPath(), p);
        if (Files.isDirectory(resolvedPath)) {
            throw new DeploymentException(
                    "Directory exist by the name of file wishing to write to: " + resolvedPath.toString());
        }

        try {
            Files.createDirectories(resolvedPath.getParent());
            OutputStream oos = new FileOutputStream(resolvedPath);
            boolean permissions = (filePermissions != null && !filePermissions.isEmpty());
            DirCache cache = null;
            try {
                IOUtils.copy(ios, oos);
                if (permissions) {
                    Files.setPosixFilePermissions(resolvedPath, filePermissions);
                }

                cache = gitRepo.add().addFilepattern(p.toString()).call();
                if (permissions) {
                    // Add executable permissions
                    cache.lock();
                    // Most of these mthods throw an IOException so we will catch that and unlock
                    DirCacheEntry entry = cache.getEntry(0);
                    log.debug("Setting executable permissions for: " + entry.getPathString());
                    entry.setFileMode(FileMode.EXECUTABLE_FILE);
                    cache.write();
                    cache.commit();
                }
            } catch (IOException e) {
                log.error("[" + getApplicationName() + "]" + "Error writing to file: " + resolvedPath.toString(),
                        e);
                // Most of the DirCache entries should just thow IOExceptions
                if (cache != null) {
                    cache.unlock();
                }
                throw new DeploymentException("Error writing to file: " + resolvedPath.toString());
            } catch (GitAPIException e) {
                log.error("[" + getApplicationName() + "]" + "Error writing to file: " + resolvedPath.toString(),
                        e);
                throw new DeploymentException("Error writing to file: " + resolvedPath.toString());
            } finally {
                IOUtils.closeQuietly(oos);
            }
        } catch (IOException e) {
            log.error(
                    "[" + getApplicationName() + "]" + "Error opening file for output: " + resolvedPath.toString(),
                    e);
            throw new DeploymentException("Error opening file for output: " + resolvedPath.toString());
        }

    }

    /**
     * Updates the OpenShift application with the changes locally applied. (git commit, git Push)
     * This will cause the new app to be deployed to openshift.  After this the new version should be visible.
     *
     * @throws DeploymentException On any error deploying the application.
     */
    public void publishChanges() throws DeploymentException {

        try {
            RevCommit commit = gitRepo.commit().setMessage("RhcApplication: publishing newest version")
                    .setAuthor(applicationDomain.getUser().getRhlogin(), applicationDomain.getUser().getRhlogin())
                    .call();

            log.info("[" + getApplicationName() + "][GIT-COMMIT] committed changes to local git repo. "
                    + commit.toString());
            PushCommand pushCommand = gitRepo.push();
            if (gitCredentialsProvider != null)
                pushCommand.setCredentialsProvider(gitCredentialsProvider);
            for (PushResult result : pushCommand.setForce(true).setTimeout(100000).call()) {
                if (!result.getMessages().isEmpty())
                    log.info("[" + getApplicationName() + "][GIT-PUSH] " + result.getMessages());
                log.info("[" + getApplicationName() + "][GIT-PUSH] Successfully pushed application.");
            }
        } catch (GitAPIException e) {
            log.error("[" + getApplicationName() + "] Error adding updated files to deployment git repo", e);
            throw new DeploymentException(e.getMessage());
        }
    }

    /**
     * Calls delete on every file in this application directory.  (basically git rm -r .)  This should allow for only
     * new files to be seen in the project directory.  This is automatically called by @{link #updateWithTarBall}, this
     * is here to allow a more flexible updating of the project if needed.
     *
     * @throws DeploymentException - on any git error while removing.
     */
    public void clearProjectDirectory() throws DeploymentException {

        final File gitDbPath = gitRepo.getRepository().getDirectory();
        final File gitRepoPath = gitDbPath.getParentFile();
        try {

            Files.walkFileTree(gitRepoPath, new SimpleFileVisitor<File>() {
                public FileVisitResult visitFile(File file, BasicFileAttributes attrs) throws IOException {
                    File rel = Files.relativize(gitRepoPath, file);
                    try {
                        gitRepo.rm().addFilepattern(rel.toString()).call();
                    } catch (GitAPIException e) {
                        throw new IOException(e);
                    }
                    return FileVisitResult.CONTINUE;
                }

                public FileVisitResult visitFileFailed(File file, IOException exc) throws IOException {
                    return FileVisitResult.CONTINUE;
                }

                public FileVisitResult postVisitDirectory(File dir, IOException exc) throws IOException {
                    //println("postVisitDirectory(${dir})");
                    if (exc == null) {
                        if (dir.equals(gitDbPath))
                            return FileVisitResult.SKIP_SUBTREE;
                        return FileVisitResult.CONTINUE;
                    } else {
                        // directory iteration failed; propagate exception
                        throw exc;
                    }
                }

                public FileVisitResult preVisitDirectory(File dir, BasicFileAttributes attrs) throws IOException {
                    //println("preVisitDirectory(${dir})");
                    if (dir.equals(gitDbPath))
                        return FileVisitResult.SKIP_SUBTREE;
                    else
                        return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            log.error("[" + getApplicationName() + "]Error cleaning out directory: " + gitRepoPath.toString(), e);
            throw new DeploymentException(e.getMessage());
        }
    }

    private File getGitRepoPath() {
        return getGitRepoDir();
    }

    private File getGitRepoDir() {
        return gitRepo.getRepository().getDirectory().getParentFile();
    }

    private void addVersionToWorkingTree(String version) throws DeploymentException {

        try {
            FileUtils.writeStringToFile(new File(getGitRepoDir(), "deployed_version.txt"), version);
        } catch (IOException e) {
            log.error("[" + getApplicationName() + "] Error writing version file to deployment git repo", e);
            throw new DeploymentException(e.getMessage());
        }
    }

    private void extractTarGzFile(byte[] artifact) throws DeploymentException {
        writeTarFileToProjectDirectory(artifact);
    }

    private void addFilesToGitWorkingTree() throws DeploymentException {
        try {
            gitRepo.add().addFilepattern(".").call();
        } catch (GitAPIException e) {
            log.error("[" + getApplicationName() + "] Error adding updated files to deployment git repo", e);
            throw new DeploymentException(e.getMessage());
        }
    }

    private void writeTarFileToProjectDirectory(byte[] artifact) throws DeploymentException {
        final File gitDbPath = gitRepo.getRepository().getDirectory();
        final File projectDir = gitDbPath.getParentFile();
        try {
            CompressorInputStream uncompressedInput = new GzipCompressorInputStream(
                    new ByteArrayInputStream(artifact));
            TarArchiveInputStream inputStream = new TarArchiveInputStream(uncompressedInput);

            // copy the existing entries
            TarArchiveEntry nextEntry;
            while ((nextEntry = (TarArchiveEntry) inputStream.getNextEntry()) != null) {
                File fileToWrite = new File(projectDir, nextEntry.getName());
                if (nextEntry.isDirectory()) {
                    fileToWrite.mkdirs();
                } else {
                    File house = fileToWrite.getParentFile();
                    if (!house.exists()) {
                        house.mkdirs();
                    }
                    copyInputStreamToFile(inputStream, fileToWrite);
                    Files.setPosixFilePermissions(fileToWrite, nextEntry.getMode());
                }
            }
        } catch (IOException e) {
            log.error("[" + getApplicationName() + "]" + e.getMessage(), e);
            throw new DeploymentException(e.getMessage());
        }
    }

    /**
     * From apache common's FileUtils.... However, they CLOSE the input stream even though its not documented as such!
     *
     * @param source      - the source stream, to which will not be closed
     * @param destination - destination file to write to
     * @throws IOException - on error writing to output stream
     */
    public static void copyInputStreamToFile(InputStream source, File destination) throws IOException {
        FileOutputStream output = openOutputStream(destination);
        try {
            IOUtils.copy(source, output);
            output.close(); // don't swallow close Exception if copy completes normally
        } finally {
            IOUtils.closeQuietly(output);
        }
    }

    /**
     * This will make sure that a directory is on disk for the git repository.  It will clone the git repo to the directory
     * if needed, or git pull the new contents if required.
     *
     * @return git repository created/retrieved from OpenShift with the remote pointing to OpenShift
     * @throws DeploymentException - on any exception getting it from git
     */
    public Git getOrCreateGitRepo() throws DeploymentException {
        String gitUrl = applicationInstance.getGitUrl();

        Files.createDirectories(appTmpDir);
        File gitDir = new File(appTmpDir, ".git");
        if (gitRepo != null || gitDir.exists() && gitDir.isDirectory()) {
            try {
                Git git = gitRepo != null ? gitRepo : Git.open(appTmpDir);
                // stash to get to a clean state of git dir so that we can pull without conflicts
                git.stashCreate();
                git.stashDrop();
                PullCommand pullCommand = git.pull();
                if (gitCredentialsProvider != null)
                    pullCommand.setCredentialsProvider(gitCredentialsProvider);
                PullResult result = pullCommand.call();
                if (!result.isSuccessful())
                    throw new DeploymentException("Git pull was not successful: " + result.toString());
                setGitRepo(git);
                return git;
            } catch (IOException | GitAPIException e) {
                log.error("Error opening existing cached git repo", e);
                throw new DeploymentException("Error opening existing cached git repo: " + e.getMessage());
            }
        } else {
            try {
                log.info("Cloning to " + appTmpDir.toString());
                CloneCommand cloneCommand = Git.cloneRepository().setURI(gitUrl).setTimeout(10000)
                        .setDirectory(appTmpDir);
                if (gitCredentialsProvider != null)
                    cloneCommand.setCredentialsProvider(gitCredentialsProvider);
                gitRepo = cloneCommand.call();
                log.info("Cloned to directory: "
                        + gitRepo.getRepository().getDirectory().getParentFile().getAbsolutePath());
                return gitRepo;
            } catch (GitAPIException | JGitInternalException e) {
                log.error("Error cloning repository from OpenShift", e);
                throw new DeploymentException("Error cloning repository from OpenShift: " + e.getMessage());
            }
        }
    }

    public void delete() throws DeploymentException {
        // Call stop to run the stop script
        applicationInstance.stop();
        // Call destory to remove the app from OpenShift
        applicationInstance.destroy();
        gitRepo = null;
        if (Files.exists(appTmpDir)) {
            try {
                Files.deleteRecursively(appTmpDir);
            } catch (IOException e) {
                log.error("Unable to clean up directory: " + appTmpDir.getAbsolutePath(), e);
                throw new DeploymentException("Unable to clean up directory: " + appTmpDir.getAbsolutePath());
            }
        }
    }
}