de.fraunhofer.ipa.CobPipelineProperty.java Source code

Java tutorial

Introduction

Here is the source code for de.fraunhofer.ipa.CobPipelineProperty.java

Source

/**
 * Copyright (c) 2012
 * Fraunhofer Institute for Manufacturing Engineering
 * and Automation (IPA)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 * - Neither the name of the Fraunhofer Institute for Manufacturing
 * Engineering and Automation (IPA) nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * This program is free software: you can redistribute it and/or
 * modify
 * it under the terms of the GNU Lesser General Public License LGPL as
 * published by the Free Software Foundation, either version 3 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 Lesser General Public License LGPL for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License LGPL along with this program.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package de.fraunhofer.ipa;

import hudson.Extension;
import hudson.Util;
import hudson.model.Descriptor;
import hudson.model.Descriptor.FormException;
import hudson.model.RootAction;
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
//import hudson.tasks.Mailer;
import hudson.util.FormValidation;
import hudson.util.QuotedStringTokenizer;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;

import jenkins.model.Jenkins;
import net.sf.json.JSONObject;

import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.bind.JavaScriptMethod;

import org.eclipse.egit.github.core.client.GitHubClient;
import org.eclipse.egit.github.core.service.*;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.storage.file.FileRepository;

import org.yaml.snakeyaml.*;

/**
 * A UserProperty that can store a build pipeline consisting of a defined
 * order of dependent projects
 * 
 * @author Jannik Kett
 */
public class CobPipelineProperty extends UserProperty {

    /**
     * user email address
     */
    private String email = null;

    /**
     * stores whether the committer of a change should be informed as well
     */
    private boolean committerEmailEnabled;

    /**
     * list of configured repositories to build and test
     */
    private volatile RootRepositoryList rootRepos = new RootRepositoryList();

    @DataBoundConstructor
    public CobPipelineProperty() {
        this.rootRepos = rootRepos;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getEmail() {
        //TODO 'if' necessary?
        if (!this.email.isEmpty()) {
            return this.email;
        }
        return "";
    }

    public void setCommitterEmailEnabled(boolean enabled) {
        this.committerEmailEnabled = enabled;
    }

    public boolean getCommitterEmailEnabled() {
        return this.committerEmailEnabled;
    }

    private String getMasterName() {
        String url = Jenkins.getInstance().getRootUrl();
        if (url.endsWith("/")) {
            url = url.replace(":8080/", "");
        } else {
            url = url.replace(":8080", "");
        }
        url = url.replace("http://", "");

        return url;
    }

    public void setRootRepos(RootRepositoryList rootRepos) throws IOException {
        this.rootRepos = new RootRepositoryList(rootRepos);
        save();
    }

    public RootRepositoryList getRootRepos() {
        return rootRepos;
    }

    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl) super.getDescriptor();
    }

    @Extension
    public static class DescriptorImpl extends UserPropertyDescriptor {

        private String jenkinsLogin;

        private String jenkinsPassword;

        private String configFolder;

        private String tarballLocation;

        private String githubLogin;

        private String githubPassword;

        private String pipelineReposOwner;

        private ArrayList<String> allRosDistros;

        private ArrayList<String> robots;

        private String targetsURL;

        private List<Map<String, List<String>>> targets;

        public DescriptorImpl() {
            load();
        }

        @Override
        public String getDisplayName() {
            return "Pipeline Configurations";
        }

        @Override
        public UserProperty newInstance(User user) {
            return new CobPipelineProperty();
        }

        /**
         * Checks if the given String is a email address
         */
        public FormValidation doCheckEmail(@QueryParameter String value) throws IOException, ServletException {
            if (value.length() == 0) {
                return FormValidation.error(Messages.Email_Empty());
            }
            return FormValidation.ok();
        }

        public void setJenkinsLogin(String jenkinsLogin) {
            this.jenkinsLogin = jenkinsLogin;
        }

        public String getJenkinsLogin() {
            return jenkinsLogin;
        }

        //TODO save password encrypted
        public void setJenkinsPassword(String jenkinsPassword) {
            this.jenkinsPassword = jenkinsPassword;
        }

        public String getJenkinsPassword() {
            return jenkinsPassword;
        }

        public void setConfigFolder(String configFolder) {
            this.configFolder = configFolder;
        }

        public String getConfigFolder() {
            return this.configFolder;
        }

        public void setTarballLocation(String tarballLocation) {
            this.tarballLocation = tarballLocation;
        }

        public String getTarballLocation() {
            return this.tarballLocation;
        }

        public void setGithubLogin(String githubLogin) {
            this.githubLogin = githubLogin;
        }

        public String getGithubLogin() {
            return githubLogin;
        }

        //TODO save password encrypted
        public void setGithubPassword(String githubPassword) {
            this.githubPassword = githubPassword;
        }

        public String getGithubPassword() {
            return githubPassword;
        }

        public void setPipelineReposOwner(String pipelineReposOwner) {
            this.pipelineReposOwner = pipelineReposOwner;
        }

        public String getPipelineReposOwner() {
            return this.pipelineReposOwner;
        }

        public void setAllRosDistrosString(String rosDistrosString) {
            this.allRosDistros = new ArrayList<String>(Arrays.asList(Util.tokenize(rosDistrosString)));
        }

        public String getAllRosDistrosString() {
            int len = 0;
            for (String rosDistro : allRosDistros)
                len += rosDistro.length();
            char delim = len > 30 ? '\n' : ' ';
            // Build string connected with delimiter, quoting as needed
            StringBuilder buf = new StringBuilder(len + allRosDistros.size() * 3);
            for (String rosDistro : allRosDistros)
                buf.append(delim).append(QuotedStringTokenizer.quote(rosDistro, ""));
            return buf.substring(1);
        }

        public List<String> getAllRosDistros() {
            return Collections.unmodifiableList(allRosDistros);
        }

        public void setRobotsString(String robotsString) {
            this.robots = new ArrayList<String>(Arrays.asList(Util.tokenize(robotsString)));
        }

        public String getRobotsString() {
            int len = 0;
            for (String robot : robots)
                len += robot.length();
            char delim = len > 30 ? '\n' : ' ';
            // Build string connected with delimiter, quoting as needed
            StringBuilder buf = new StringBuilder(len + robots.size() * 3);
            for (String robot : robots)
                buf.append(delim).append(QuotedStringTokenizer.quote(robot, ""));
            return buf.substring(1);
        }

        public List<String> getRobots() {
            return Collections.unmodifiableList(robots);
        }

        @SuppressWarnings("unchecked")
        public void setTargetsURL(String url) throws Exception {
            this.targetsURL = url;
            URL targets = new URL(url);
            BufferedReader in = new BufferedReader(new InputStreamReader(targets.openStream()));

            String aux = "";
            String yamlString = "";
            while ((aux = in.readLine()) != null) {
                yamlString += aux;
                yamlString += "\n";
            }
            in.close();

            Yaml yaml = new Yaml();
            this.targets = new ArrayList<Map<String, List<String>>>();
            this.targets = (List<Map<String, List<String>>>) yaml.load(yamlString);
        }

        public String getTargetsURL() {
            return this.targetsURL;
        }

        public List<Map<String, List<String>>> getTargets() {
            return this.targets;
        }

        /**
         * Checks if given String is valid Jenkins user
         */
        public FormValidation doCheckJenkinsLogin(@QueryParameter String value) {
            //TODO
            return FormValidation.ok();
        }

        /**
         * Checks if given password String is fits to Jenkins user
         */
        public FormValidation doCheckJenkinsPassword(@QueryParameter String value,
                @QueryParameter String jenkinsLogin) {
            //TODO
            return FormValidation.ok();
        }

        /**
         * Checks if folder and jenkins_setup repository exist
         */
        public FormValidation doCheckConfigFolder(@QueryParameter String value) {
            File pipeDir = new File(value);

            if (!pipeDir.exists()) {
                return FormValidation.error(Messages.ConfigFolder_NotExistent());
            }
            if (!pipeDir.isDirectory()) {
                return FormValidation.error(Messages.ConfigFolder_NotADirectory());
            }

            for (String inPipeDir : pipeDir.list()) {
                if (inPipeDir.equals("jenkins_setup")) {
                    File setupDir = new File(value, inPipeDir);
                    if (!setupDir.isDirectory()) {
                        return FormValidation.error(Messages.ConfigFolder_NoSetupDir(inPipeDir));
                    }
                    if (setupDir.list().length == 0) {
                        return FormValidation.error(Messages.ConfigFolder_RepoEmpty(inPipeDir));
                    }
                    File gitRepo = new File(setupDir, "/.git");
                    if (!gitRepo.exists()) {
                        return FormValidation.error(Messages.ConfigFolder_NoGitRepo(inPipeDir));
                    }
                    return FormValidation.ok(Messages.ConfigFolder_Ok());
                }
            }

            return FormValidation.error(Messages.ConfigFolder_NoSetupRepo());
        }

        /**
         * Checks if given URL exists
         */
        public FormValidation doCheckTarballLocation(@QueryParameter String value) {
            //TODO
            return FormValidation.ok();
        }

        /**
         * Checks if given String is valid GitHub user
         */
        public FormValidation doCheckGithubLogin(@QueryParameter String value)
                throws IOException, ServletException {
            if (value.length() == 0) {
                return FormValidation.error(Messages.Github_Login());
            }
            try {
                UserService githubUserSrv = new UserService();
                githubUserSrv.getUser(value);
                return FormValidation.ok();
            } catch (IOException ex) {
                return FormValidation.error(Messages.Github_LoginInvalid() + "\n" + ex.getMessage());
            }
        }

        /**
         * Checks if given password String is fits to GitHub user
         */
        public FormValidation doCheckGithubPassword(@QueryParameter String value,
                @QueryParameter String githubLogin) throws IOException, ServletException {
            if (value.length() == 0) {
                return FormValidation.error(Messages.Github_Password());
            }
            try {
                GitHubClient client = new GitHubClient();
                client.setCredentials(githubLogin, value);
                UserService githubUserSrv = new UserService(client);
                org.eclipse.egit.github.core.User user = githubUserSrv.getUser(githubLogin);
                return FormValidation
                        .ok("GitHub user name: " + user.getName() + "\nUser ownes " + user.getPublicRepos()
                                + " public and " + user.getTotalPrivateRepos() + " private repositories");
            } catch (Exception ex) {
                return FormValidation.error(Messages.Github_PasswordIncorrect() + "\n" + ex.getMessage());
            }
        }

        public FormValidation doCheckPipelineReposOwner(@QueryParameter String value)
                throws IOException, ServletException {
            return doCheckGithubLogin(value);
        }

        public FormValidation doCheckTargets(@QueryParameter String value) throws IOException, ServletException {
            if (value.length() == 0) {
                return FormValidation.warning(Messages.Targets_Empty());
            }
            return FormValidation.ok();
        }

        @Override
        public boolean configure(StaplerRequest req, JSONObject form) throws FormException {
            req.bindJSON(this, form);
            super.save();
            return super.configure(req, form);
        }

        /**
         * All {@link RepositoryDescriptor}s
         */
        public List<RepositoryDescriptor> getRootRepositoryDescriptors() {
            List<RepositoryDescriptor> r = new ArrayList<RepositoryDescriptor>();
            for (RepositoryDescriptor d : RootRepository.all()) {
                //add only RootRepositoryDescriptors
                if (d.isRoot())
                    r.add(d);
            }
            return r;
        }
    }

    @Override
    public UserProperty reconfigure(StaplerRequest req, JSONObject form) throws FormException {
        req.bindJSON(this, form);
        return this;
    }

    public void save() throws IOException {
        user.save();
        LOGGER.log(Level.INFO, "Saved user configuration"); //TODO
    }

    @JavaScriptMethod
    public JSONObject doGeneratePipeline() throws Exception {
        JSONObject response = new JSONObject();
        String message = "";

        // wait until config.xml is updated
        File configFile = new File(Jenkins.getInstance().getRootDir(), "users/" + user.getId() + "/config.xml");
        Date mod = new Date();
        Long start = mod.getTime();
        Date now;
        do {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
            if (configFile.exists()) {
                try {
                    mod = new Date(configFile.lastModified());
                } catch (Exception ex) {
                }
            }

            now = new Date();
            if (now.getTime() - start > 30000) {
                throw new Exception("Timeout");
            }
        } while (start.equals(mod.getTime()) || now.getTime() - mod.getTime() > 15000);

        try {
            Map<String, Object> data = new HashMap<String, Object>();
            data.put("user_name", user.getId());
            data.put("server_name", getMasterName());
            data.put("email", this.email);
            data.put("committer_email_enabled", this.committerEmailEnabled);
            Map<String, Object> repos = new HashMap<String, Object>();
            for (RootRepository rootRepo : this.rootRepos) {
                Map<String, Object> repo = new HashMap<String, Object>();
                repo.put("type", rootRepo.type);
                repo.put("url", rootRepo.url);
                repo.put("version", rootRepo.branch);
                repo.put("poll", rootRepo.poll);
                repo.put("ros_distro", rootRepo.getRosDistro());
                repo.put("prio_ubuntu_distro", rootRepo.getPrioUbuntuDistro());
                repo.put("prio_arch", rootRepo.getPrioArch());
                repo.put("regular_matrix", rootRepo.getMatrixDistroArch());
                repo.put("jobs", rootRepo.getJobs());
                repo.put("robots", rootRepo.robot);

                Map<String, Object> deps = new HashMap<String, Object>();
                for (Repository repoDep : rootRepo.getRepoDeps()) {
                    Map<String, Object> dep = new HashMap<String, Object>();
                    if (repoDep.fork.equals("") || repoDep.fork.equals(null)) {
                        response.put("message", Messages.Pipeline_GenerationNoFork(rootRepo.fullName));
                        response.put("status",
                                "<font color=\"red\">" + Messages.Pipeline_GenerationFailure() + "</font>");
                        return response;
                    }
                    if (repoDep.name.equals("") || repoDep.name.equals(null)) {
                        response.put("message", Messages.Pipeline_GenerationNoDepName(rootRepo.fullName));
                        response.put("status",
                                "<font color=\"red\">" + Messages.Pipeline_GenerationFailure() + "</font>");
                        return response;
                    }
                    if (repoDep.branch.equals("") || repoDep.branch.equals(null)) {
                        response.put("message", Messages.Pipeline_GenerationNoBranch(rootRepo.fullName));
                        response.put("status",
                                "<font color=\"red\">" + Messages.Pipeline_GenerationFailure() + "</font>");
                        return response;
                    }
                    dep.put("type", repoDep.type);
                    dep.put("url", repoDep.url);
                    dep.put("version", repoDep.branch);
                    dep.put("poll", repoDep.poll);
                    dep.put("test", repoDep.test);
                    deps.put(repoDep.name, dep);
                }
                repo.put("dependencies", deps);

                repos.put(rootRepo.fullName, repo);
            }
            data.put("repositories", repos);
            Yaml yaml = new Yaml();
            yaml.dump(data, getPipelineConfigFile());
            LOGGER.log(Level.INFO, "Created " + getPipelineConfigFilePath().getAbsolutePath()); //TODO

        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Failed to save " + getPipelineConfigFilePath().getAbsolutePath(), e); //TODO
        }

        // clone/pull configuration repository
        File configRepoFolder = new File(Jenkins.getInstance()
                .getDescriptorByType(CobPipelineProperty.DescriptorImpl.class).getConfigFolder(), "jenkins_config");
        String configRepoURL = "git@github.com:" + Jenkins.getInstance()
                .getDescriptorByType(CobPipelineProperty.DescriptorImpl.class).getPipelineReposOwner()
                + "/jenkins_config.git";
        Git git = new Git(new FileRepository(configRepoFolder + "/.git"));

        // check if configuration repository exists
        if (!configRepoFolder.isDirectory()) {
            try {
                Git.cloneRepository().setURI(configRepoURL).setDirectory(configRepoFolder).call();
                LOGGER.log(Level.INFO, "Successfully cloned configuration repository from " + configRepoURL); //TODO
            } catch (Exception ex) {
                LOGGER.log(Level.WARNING, "Failed to clone configuration repository", ex); //TODO
            }
        } else {
            try {
                git.pull().call();
                LOGGER.log(Level.INFO, "Successfully pulled configuration repository from " + configRepoURL); //TODO
            } catch (Exception ex) {
                LOGGER.log(Level.WARNING, "Failed to pull configuration repository", ex); //TODO
            }
        }

        // copy pipeline-config.yaml into repository
        File configRepoFile = new File(configRepoFolder, getMasterName() + "/" + user.getId() + "/");
        if (!configRepoFile.isDirectory())
            configRepoFile.mkdirs();
        String[] cpCommand = { "cp", "-f", getPipelineConfigFilePath().getAbsolutePath(),
                configRepoFile.getAbsolutePath() };

        Runtime rt = Runtime.getRuntime();
        Process proc;
        BufferedReader readIn, readErr;
        String s, feedback;
        proc = rt.exec(cpCommand);
        readIn = new BufferedReader(new InputStreamReader(proc.getInputStream()));
        readErr = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
        feedback = "";
        while ((s = readErr.readLine()) != null)
            feedback += s + "\n";
        if (feedback.length() != 0) {
            LOGGER.log(Level.WARNING, "Failed to copy " + getPipelineConfigFilePath().getAbsolutePath()
                    + " to config repository: " + configRepoFile.getAbsolutePath()); //TODO
            LOGGER.log(Level.WARNING, feedback); //TODO
        } else {
            LOGGER.log(Level.INFO, "Successfully copied " + getPipelineConfigFilePath().getAbsolutePath()
                    + " to config repository: " + configRepoFile.getAbsolutePath()); //TODO
        }

        // add
        try {
            git.add().addFilepattern(getMasterName() + "/" + user.getId() + "/pipeline_config.yaml").call();
            LOGGER.log(Level.INFO, "Successfully added file to configuration repository"); //TODO
        } catch (Exception e) {
            LOGGER.log(Level.WARNING,
                    "Failed to add " + getMasterName() + "/" + user.getId() + "/pipeline_config.yaml", e); //TODO
        }

        // commit
        try {
            git.commit().setMessage("Updated pipeline configuration for " + user.getId()).call();
        } catch (Exception e) {
            LOGGER.log(Level.WARNING,
                    "Failed to commit change in " + getMasterName() + "/" + user.getId() + "/pipeline_config.yaml",
                    e); //TODO
        }

        // push
        try {
            git.push().call();
            LOGGER.log(Level.INFO, "Successfully pushed configuration repository"); //TODO
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, "Failed to push configuration repository", e); //TODO
        }

        // trigger Python job generation script
        String[] generationCall = {
                new File(Jenkins.getInstance().getDescriptorByType(CobPipelineProperty.DescriptorImpl.class)
                        .getConfigFolder(), "jenkins_setup/scripts/generate_buildpipeline.py").toString(),
                "-m", Jenkins.getInstance().getRootUrl(), "-l",
                Jenkins.getInstance().getDescriptorByType(CobPipelineProperty.DescriptorImpl.class)
                        .getJenkinsLogin(),
                "-p",
                Jenkins.getInstance().getDescriptorByType(CobPipelineProperty.DescriptorImpl.class)
                        .getJenkinsPassword(),
                "-c",
                Jenkins.getInstance().getDescriptorByType(CobPipelineProperty.DescriptorImpl.class)
                        .getConfigFolder(),
                "-o",
                Jenkins.getInstance().getDescriptorByType(CobPipelineProperty.DescriptorImpl.class)
                        .getPipelineReposOwner(),
                "-t", Jenkins.getInstance().getDescriptorByType(CobPipelineProperty.DescriptorImpl.class)
                        .getTarballLocation(),
                "-u", user.getId() };

        proc = rt.exec(generationCall);
        readIn = new BufferedReader(new InputStreamReader(proc.getInputStream()));
        readErr = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
        feedback = "";
        while ((s = readErr.readLine()) != null)
            feedback += s + "\n";
        if (feedback.length() != 0) {
            LOGGER.log(Level.WARNING, "Failed to generate pipeline: "); //TODO
            LOGGER.log(Level.WARNING, feedback);
            response.put("message", feedback.replace("\n", "<br/>"));
            response.put("status", "<font color=\"red\">" + Messages.Pipeline_GenerationFailure() + "</font>");
            return response;
        } else {
            feedback = "";
            while ((s = readIn.readLine()) != null)
                feedback += s + "\n";
            if (feedback.length() != 0) {
                LOGGER.log(Level.INFO, feedback);
                LOGGER.log(Level.INFO, "Successfully generated pipeline"); //TODO
                message += feedback;
            }
        }
        response.put("message", message.replace("\n", "<br/>"));
        response.put("status", "<font color=\"green\">" + Messages.Pipeline_GenerationSuccess() + "</font>");
        return response;
    }

    private Writer getPipelineConfigFile() throws IOException {
        return new FileWriter(getPipelineConfigFilePath());
    }

    private File getPipelineConfigFilePath() {
        return new File(Jenkins.getInstance().getRootDir(), "users/" + user.getId() + "/pipeline_config.yaml");
    }

    private static final Logger LOGGER = Logger.getLogger(Descriptor.class.getName());

    @Extension
    public static class GlobalAction implements RootAction {

        public String getDisplayName() {
            return Messages.Pipeline_DisplayName();
        }

        public String getIconFileName() {
            // do not show when not logged in
            if (User.current() == null) {
                return null;
            }
            return "/plugin/cob-pipeline/images/pipe_conf_small.png";
        }

        public String getUrlName() {
            if (User.current() == null) {
                return null;
            }
            //return "/me/configure";
            return "/user/" + User.current().getId() + "/configure";
        }
    }
}