org.jenkinsci.plugins.drupal.beans.DrushInvocation.java Source code

Java tutorial

Introduction

Here is the source code for org.jenkinsci.plugins.drupal.beans.DrushInvocation.java

Source

/*
 * Copyright (c) 2015 Fengtan<https://github.com/fengtan/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package org.jenkinsci.plugins.drupal.beans;

import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Launcher.ProcStarter;
import hudson.model.TaskListener;
import hudson.model.Computer;
import hudson.tools.ToolInstallation;
import hudson.util.ArgumentListBuilder;
import hudson.util.StreamTaskListener;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.drupal.config.DrushInstallation;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;

/**
 * Invoke Drush commands.
 * 
 * @author Fengtan https://github.com/fengtan/
 *
 */
public class DrushInvocation {

    protected final FilePath root;
    protected final FilePath workspace;
    protected final Launcher launcher;
    protected final TaskListener listener;
    protected final EnvVars environment;

    public DrushInvocation(FilePath root, FilePath workspace, Launcher launcher, TaskListener listener,
            EnvVars environment) {
        this.root = root;
        this.workspace = workspace;
        this.launcher = launcher;
        this.listener = listener;
        this.environment = environment;
    }

    /**
     * Get default Drush options.
     */
    protected ArgumentListBuilder getArgumentListBuilder() {
        return new ArgumentListBuilder(getDrushExe()).add("--yes").add("--nocolor")
                .add("--root=" + root.getRemote());
    }

    /**
     * Get Drush executable.
     */
    protected String getDrushExe() {
        DrushInstallation installation = getDrushInstallation();
        String defaultExe = launcher.isUnix() ? "drush" : "drush.bat";
        if (installation == null) {
            listener.getLogger()
                    .println("[DRUPAL] No Drush installation configured, fall back to '" + defaultExe + "'");
            return defaultExe;
        }
        try {
            installation = installation.forNode(Computer.currentComputer().getNode(), listener);
            installation = installation.forEnvironment(environment);
            String exe = installation.getExecutable(launcher);
            if (exe == null) {
                listener.getLogger().println("[DRUPAL] Drush executable '" + exe + "' from installation '"
                        + installation.getName() + "' could not be found, fall back to '" + defaultExe + "'");
                return defaultExe;
            }
            return exe;
        } catch (IOException e) {
            listener.getLogger()
                    .println("[DRUPAL] Fall back to '" + defaultExe + "' due to error: " + e.getMessage());
            return defaultExe;
        } catch (InterruptedException e) {
            listener.getLogger()
                    .println("[DRUPAL] Fall back to '" + defaultExe + "' due to error: " + e.getMessage());
            return defaultExe;
        }
    }

    /**
     * Get first Drush installation configured, or null if no installation is configured.
     */
    protected DrushInstallation getDrushInstallation() {
        DrushInstallation[] installations = ToolInstallation.all().get(DrushInstallation.DescriptorImpl.class)
                .getInstallations();
        return (installations.length > 0) ? installations[0] : null;
    }

    /**
     * Execute a Drush command.
     */
    protected boolean execute(ArgumentListBuilder args) throws IOException, InterruptedException {
        return execute(args, null);
    }

    /**
     * Execute a Drush command.
     */
    protected boolean execute(ArgumentListBuilder args, TaskListener out) throws IOException, InterruptedException {
        ProcStarter starter = launcher.launch().pwd(workspace).cmds(args);
        if (out == null) {
            // Output stdout/stderr into listener.
            starter.stdout(listener);
        } else {
            // Output stdout into out.
            // Do not output stderr since this breaks the XML formatting on stdout.
            starter.stdout(out).stderr(NullOutputStream.NULL_OUTPUT_STREAM);
        }
        starter.join();
        return true;
    }

    /**
     * Run update.php.
     */
    public boolean upDb() throws IOException, InterruptedException {
        ArgumentListBuilder args = getArgumentListBuilder();
        args.add("updatedb");
        return execute(args);
    }

    /**
     * Make a Drupal site using a Makefile.
     */
    public boolean make(File makefile) throws IOException, InterruptedException {
        ArgumentListBuilder args = getArgumentListBuilder();
        args.add("make");
        args.add(makefile.getAbsolutePath());
        args.add(root.getRemote());
        return execute(args);
    }

    /**
     * Install a Drupal site using an installation profile.
     */
    public boolean siteInstall(String db, String profile) throws IOException, InterruptedException {
        ArgumentListBuilder args = getArgumentListBuilder();
        args.add("site-install");
        args.add(profile);
        args.add("--db-url=" + db);
        return execute(args);
    }

    /**
     * Download projects/modules into a destination directory.
     */
    public boolean download(String projects, String destination) throws IOException, InterruptedException {
        ArgumentListBuilder args = getArgumentListBuilder();
        args.add("pm-download").add(projects);
        if (StringUtils.isNotEmpty(destination)) {
            args.add("--destination=" + destination);
        }
        return execute(args);
    }

    /**
     * Enable extensions/modules.
     */
    public boolean enable(String extensions) throws IOException, InterruptedException {
        ArgumentListBuilder args = getArgumentListBuilder();
        args.add("pm-enable").add(extensions);
        return execute(args);
    }

    /**
     * Get a map of projects installed on Drupal, keyed by machine name.
     */
    public Map<String, DrupalExtension> getProjects(boolean modulesOnly, boolean enabledOnly) {
        ArgumentListBuilder args = getArgumentListBuilder();
        args.add("pm-list").add("--pipe").add("--format=json");
        if (modulesOnly) {
            args.add("--type=module");
        }
        if (enabledOnly) {
            args.add("--status=enabled");
        }

        OutputStream json = new ByteArrayOutputStream();
        try {
            execute(args, new StreamTaskListener(json));
        } catch (IOException e1) {
            listener.getLogger().println(e1);
            return MapUtils.EMPTY_MAP;
        } catch (InterruptedException e2) {
            listener.getLogger().println(e2);
            return MapUtils.EMPTY_MAP;
        }

        Map<String, DrupalExtension> projects = new HashMap<String, DrupalExtension>();
        JSONObject entries = (JSONObject) JSONValue.parse(json.toString());
        if (entries == null) {
            listener.getLogger().println("[DRUPAL] Could not list available projects");
            return MapUtils.EMPTY_MAP;
        }
        for (Object name : entries.keySet()) {
            JSONObject entry = (JSONObject) entries.get(name);
            DrupalExtension project = new DrupalExtension(Objects.toString(name, ""),
                    Objects.toString(entry.get("type"), ""), Objects.toString(entry.get("status"), ""),
                    Objects.toString(entry.get("version"), ""));
            projects.put(name.toString(), project);
        }

        return projects;
    }

    /**
     * Check if a module exists / is enabled
     */
    public boolean isModuleInstalled(String name, boolean enabledOnly) {
        return getProjects(true, enabledOnly).keySet().contains(name);
    }

    /**
     * Return true if the site is already installed, false otherwise.
     */
    public boolean status() {
        ArgumentListBuilder args = getArgumentListBuilder();
        args.add("status").add("--format=json");

        OutputStream json = new ByteArrayOutputStream();
        try {
            execute(args, new StreamTaskListener(json));
        } catch (IOException e1) {
            listener.getLogger().println(e1);
            return false;
        } catch (InterruptedException e2) {
            listener.getLogger().println(e2);
            return false;
        }

        JSONObject values = (JSONObject) JSONValue.parse(json.toString());
        if (values == null) {
            listener.getLogger().println("[DRUPAL] Could not determine the site status.");
            return false;
        }

        return values.containsKey("db-name");
    }

    /**
     * Get a list of test classes available.
     */
    public Collection<DrupalTest> getTests() {
        ArgumentListBuilder args = getArgumentListBuilder();
        args.add("test-run").add("--format=json");

        OutputStream json = new ByteArrayOutputStream();
        try {
            execute(args, new StreamTaskListener(json));
        } catch (IOException e1) {
            listener.getLogger().println(e1);
            return CollectionUtils.EMPTY_COLLECTION;
        } catch (InterruptedException e2) {
            listener.getLogger().println(e2);
            return CollectionUtils.EMPTY_COLLECTION;
        }

        Collection<DrupalTest> tests = new HashSet<DrupalTest>();
        JSONArray entries = (JSONArray) JSONValue.parse(json.toString());
        if (entries == null) {
            listener.getLogger().println("[DRUPAL] Could not list available tests");
            return CollectionUtils.EMPTY_COLLECTION;
        }
        for (Object entry : entries) {
            JSONObject test = (JSONObject) entry;
            tests.add(new DrupalTest(test.get("group").toString(), test.get("class").toString()));
        }

        return tests;
    }

    /**
     * Run tests.
     */
    public boolean testRun(File outputDir, String uri, Collection<String> targets)
            throws IOException, InterruptedException {
        ArgumentListBuilder args = getArgumentListBuilder();
        args.add("test-run");
        args.add("--xml=" + outputDir.getAbsolutePath());
        if (StringUtils.isNotEmpty(uri)) {
            args.add("--uri=" + uri);
        }
        args.add(StringUtils.join(targets, ","));
        return execute(args);
    }

    /**
     * Run a code review.
     */
    public boolean coderReview(File outputDir, Collection<String> reviews, final Collection<String> projectNames,
            boolean ignoresPass) throws IOException, InterruptedException {
        // Make sure Coder is enabled.
        DrupalExtension coder = getProjects(true, true).get("coder");
        if (coder == null) {
            listener.getLogger().println("[DRUPAL] Coder does not exist: aborting code review");
            return false;
        }

        // Build command depending on Coder's version.
        ArgumentListBuilder args = getArgumentListBuilder();
        args.add("coder-review");
        if (coder.getVersion().startsWith("7.x-2")) {
            args.add("--minor");
            args.add("--checkstyle");
            args.add("--reviews=" + StringUtils.join(reviews, ","));
        } else if (coder.getVersion().startsWith("7.x-1")) {
            args.add("minor");
            args.add("checkstyle");
            for (String review : reviews) {
                args.add(review);
            }
        } else {
            listener.getLogger().println("[DRUPAL] Unsupported Coder version " + coder.getVersion());
            return false;
        }

        // Ignores pass if needed.
        // This option works only with coder-7.x-2.4+.
        if (ignoresPass) {
            if (coder.getVersion().startsWith("7.x-2")
                    && (Integer.parseInt(coder.getVersion().replaceFirst("7\\.x-2\\.", "")) >= 4)) {
                args.add("--ignores-pass");
            } else {
                listener.getLogger().println(
                        "[DRUPAL]'Ignores pass' option is available only with Coder-7.x-2.4+, ignoring option");
            }
        }

        // In coder-7.x-2.x, 'drush coder-review comment' fails with error "use --reviews or --comment". Same for i18n.
        // Ignore projects involved in conflicts.
        Collection<String> conflicts = CollectionUtils.intersection(projectNames, reviews);
        if (!conflicts.isEmpty()) {
            listener.getLogger().println("[DRUPAL] Ignoring project(s) conflicting with Coder options: "
                    + StringUtils.join(conflicts, ", "));
        }
        for (String projectName : projectNames) {
            if (!conflicts.contains(projectName)) {
                args.add(projectName);
            }
        }

        // Run command.
        File outputFile = new File(outputDir, "coder_review.xml");
        return execute(args, new StreamTaskListener(outputFile));
    }

}