org.jenkinsci.plugins.skytap.SkytapUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.jenkinsci.plugins.skytap.SkytapUtils.java

Source

//
// Copyright (c) 2013, Skytap, Inc
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//                        
package org.jenkinsci.plugins.skytap;

import hudson.EnvVars;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.util.VariableResolver;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.InterruptedIOException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.Date;
import java.text.SimpleDateFormat;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FilenameUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
//
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
//
import org.apache.http.client.HttpResponseException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.util.EntityUtils;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

public class SkytapUtils {

    /**
     * This method is used to enable Jenkins variable expansion. The user would
     * include Jenkins variables such as ${BUILD_ID} and these are resolved at
     * runtime, with the expanded string being used for all plugin actions.
     * 
     * @param build
     * @param targetString
     * @return
     */
    public static String expandEnvVars(AbstractBuild build, String targetString) {

        EnvVars env;
        String expandedString = "";

        // if value was null or empty return blank value
        if (targetString == null || targetString.equals("")) {
            return expandedString;
        }

        try {
            env = build.getEnvironment(JenkinsLogger.getListener());
            expandedString = env.expand(targetString);

        } catch (IOException e) {
            JenkinsLogger.error("Jenkins Environment variables could not be resolved successfully.");
        } catch (InterruptedException e) {
            JenkinsLogger.error("Jenkins Environment variables could not be resolved successfully.");
        } catch (NullPointerException e) {
            JenkinsLogger.error("Error. Submitted value was null or empty.");
        }

        if (!expandedString.equals("")) {
            JenkinsLogger.log("Expanding environment variable ...");
            JenkinsLogger.log(targetString + "=>" + expandedString);
        }

        return expandedString;
    }

    public static String getVMIDFromName(String confId, String vname, String authCredentials)
            throws SkytapException {

        // build url to retrieve vm object by name, so we can extract the id
        JenkinsLogger.log("Building request url ...");

        StringBuilder sb = new StringBuilder("https://cloud.skytap.com/");
        sb.append("configurations/");
        sb.append(confId);
        sb.append("/vms");
        String getVmIdURL = sb.toString();

        // create request
        HttpGet hg = SkytapUtils.buildHttpGetRequest(getVmIdURL, authCredentials);

        // execute request
        String hgResponse = "";

        hgResponse = SkytapUtils.executeHttpRequest(hg);

        // check for errors
        SkytapUtils.checkResponseForErrors(hgResponse);

        // find vm that matches name

        JsonParser parser = new JsonParser();
        JsonElement je = parser.parse(hgResponse);
        JsonArray vmArray = je.getAsJsonArray();

        JenkinsLogger.log("Iterating through vm array to match name: " + vname);

        Iterator iter = vmArray.iterator();

        while (iter.hasNext()) {
            JsonObject vmObject = (JsonObject) iter.next();
            JenkinsLogger.log(vmObject.toString());
            String currentName = vmObject.get("name").getAsString();
            JenkinsLogger.log("Found VM with name: " + currentName);

            if (currentName.equals(vname)) {
                JenkinsLogger.log("Name matched. Retrieving vm id.");

                String vid = vmObject.get("id").getAsString();
                JenkinsLogger.log("VM ID: " + vid);
                return vid;
            }

        }

        // if we failed to match the name throw an exception
        throw new SkytapException("No vms were found matching name: " + vname);

    }

    /**
     * This is a utility method used to get the json object in json file used
     * for configs, templates, etc.
     * 
     * @param filepath
     * @return
     */
    public static JsonObject getJsonObjectFromFile(String filepath) {

        JsonObject jo = null;

        try {
            BufferedReader reader = new BufferedReader(new FileReader(filepath));
            String line, results = "";
            while ((line = reader.readLine()) != null) {
                results += line;

            }
            reader.close();

            JsonParser parser = new JsonParser();
            JsonElement je = parser.parse(results);
            jo = je.getAsJsonObject();

        } catch (IOException e) {
            JenkinsLogger.error("Error retrieving JsonObject from file " + filepath + ".");
            JenkinsLogger.error("Error message: " + e.getMessage());
        }

        return jo;

    }

    /**
     * This is a utility method to help retrieve a json element from the
     * response body.
     * 
     * @param jsonRespBody
     * @return
     */
    public static String getValueFromJsonResponseBody(String jsonRespBody, String key) {

        JsonParser parser = new JsonParser();
        JsonElement je = parser.parse(jsonRespBody);
        JsonObject jo = je.getAsJsonObject();
        jo = je.getAsJsonObject();

        String jsonValue = jo.get(key).getAsString();
        return jsonValue;

    }

    /**
     * Takes the Skytap auth credentials and encodes them so they can be used in
     * requests to the API
     * 
     * @param unencodedCredential
     * @return
     */
    private static String encodeAuthCredentials(String unencodedCredential) {

        byte[] encoded = Base64.encodeBase64(unencodedCredential.getBytes());

        String encodedCredential = new String(encoded);

        return encodedCredential;

    }

    /**
     * Retrieves user id and auth key from the project build environment,
     * encodes and returns encoded credential string to the user.
     * 
     * @param AbstractBuild
     * @return String
     */
    public static String getAuthCredentials(AbstractBuild build) {

        VariableResolver vr = build.getBuildVariableResolver();
        String uid = vr.resolve("userId").toString();
        String authkey = vr.resolve("authKey").toString();
        String cred = uid + ":" + authkey;

        String encodedCred = encodeAuthCredentials(cred);
        return encodedCred;

    }

    /**
     * This method packages an http get request object, given a url and the
     * encoded Skytap authorization token.
     * 
     * @param requestUrl
     * @param AuthToken
     * @return
     */
    public static HttpGet buildHttpGetRequest(String requestUrl, String AuthToken) {

        HttpGet hg = new HttpGet(requestUrl);
        String authHeaderValue = "Basic " + AuthToken;

        hg.addHeader("Authorization", authHeaderValue);
        hg.addHeader("Accept", "application/json");
        hg.addHeader("Content-Type", "application/json");

        JenkinsLogger.log("HTTP GET Request: " + hg.toString());
        return hg;
    }

    /**
     * This method packages an http post request object, given a url and the
     * encoded Skytap authorization token.
     * 
     * @param requestUrl
     * @param AuthToken
     * @return
     */
    public static HttpPost buildHttpPostRequest(String requestUrl, String AuthToken) {

        HttpPost hp = new HttpPost(requestUrl);
        String authHeaderValue = "Basic " + AuthToken;

        hp.addHeader("Authorization", authHeaderValue);
        hp.addHeader("Accept", "application/json");
        hp.addHeader("Content-Type", "application/json");

        JenkinsLogger.log("HTTP POST Request: " + hp.toString());

        return hp;
    }

    /**
     * This method returns an http put request object, given a url and the
     * encoded Skytap authorization token.
     * 
     * @param requestUrl
     * @param AuthToken
     * @return
     */
    public static HttpPut buildHttpPutRequest(String requestUrl, String AuthToken) {

        HttpPut httpput = new HttpPut(requestUrl);
        String authHeaderValue = "Basic " + AuthToken;

        httpput.addHeader("Authorization", authHeaderValue);
        httpput.addHeader("Accept", "application/json");
        httpput.addHeader("Content-Type", "application/json");

        JenkinsLogger.log("HTTP PUT Request: " + httpput.toString());

        return httpput;
    }

    /**
     * This method returns an http delete request object, given a url and the
     * encoded Skytap authorization token.
     * 
     * @param requestUrl
     * @param AuthToken
     * @return
     */
    public static HttpDelete buildHttpDeleteRequest(String requestUrl, String AuthToken) {

        HttpDelete hd = new HttpDelete(requestUrl);
        String authHeaderValue = "Basic " + AuthToken;

        hd.addHeader("Authorization", authHeaderValue);
        hd.addHeader("Accept", "application/json");
        hd.addHeader("Content-Type", "application/json");

        JenkinsLogger.log("HTTP DELETE Request: " + hd.toString());

        return hd;
    }

    /**
     * Utility method to execute any type of http request (except delete), to
     * catch any exceptions thrown and return the response string.
     * 
     * @param hr
     * @return
     * @throws SkytapException
     * @throws IOException
     * @throws ParseException
     */
    public static String executeHttpRequest(HttpRequestBase hr) throws SkytapException {

        boolean retryHttpRequest = true;
        int retryCount = 1;
        String responseString = "";
        while (retryHttpRequest == true) {
            HttpClient httpclient = new DefaultHttpClient();
            //
            // Set timeouts for httpclient requests to 60 seconds
            //
            HttpConnectionParams.setConnectionTimeout(httpclient.getParams(), 60000);
            HttpConnectionParams.setSoTimeout(httpclient.getParams(), 60000);
            //
            responseString = "";
            HttpResponse response = null;
            try {
                Date myDate = new Date();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd:HH-mm-ss");
                String myDateString = sdf.format(myDate);

                JenkinsLogger.log(myDateString + "\n" + "Executing Request: " + hr.getRequestLine());
                response = httpclient.execute(hr);

                String responseStatusLine = response.getStatusLine().toString();
                if (responseStatusLine.contains("423 Locked")) {
                    retryCount = retryCount + 1;
                    if (retryCount > 5) {
                        retryHttpRequest = false;
                        JenkinsLogger.error("Object busy too long - giving up.");
                    } else {
                        JenkinsLogger.log("Object busy - Retrying...");
                        try {
                            Thread.sleep(15000);
                        } catch (InterruptedException e1) {
                            JenkinsLogger.error(e1.getMessage());
                        }
                    }
                } else if (responseStatusLine.contains("409 Conflict")) {

                    throw new SkytapException(responseStatusLine);

                } else {

                    JenkinsLogger.log(response.getStatusLine().toString());
                    HttpEntity entity = response.getEntity();
                    responseString = EntityUtils.toString(entity, "UTF-8");
                    retryHttpRequest = false;
                }

            } catch (HttpResponseException e) {
                retryHttpRequest = false;
                JenkinsLogger.error("HTTP Response Code: " + e.getStatusCode());

            } catch (ParseException e) {
                retryHttpRequest = false;
                JenkinsLogger.error(e.getMessage());

            } catch (InterruptedIOException e) {
                Date myDate = new Date();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd:HH-mm-ss");
                String myDateString = sdf.format(myDate);

                retryCount = retryCount + 1;
                if (retryCount > 5) {
                    retryHttpRequest = false;
                    JenkinsLogger.error("API Timeout - giving up. " + e.getMessage());
                } else {
                    JenkinsLogger.log(myDateString + "\n" + e.getMessage() + "\n" + "API Timeout - Retrying...");
                }
            } catch (IOException e) {
                retryHttpRequest = false;
                JenkinsLogger.error(e.getMessage());
            } finally {
                if (response != null) {
                    // response will be null if this is a timeout retry
                    HttpEntity entity = response.getEntity();
                    try {
                        responseString = EntityUtils.toString(entity, "UTF-8");
                    } catch (IOException e) {
                        // JenkinsLogger.error(e.getMessage());
                    }
                }

                httpclient.getConnectionManager().shutdown();
            }
        }

        return responseString;

    }

    /**
     * Utility method used to execute an http delete. Returns the status line
     * which can be parsed as desired by the caller.
     * 
     * @param hd
     * @return
     * @throws SkytapException
     */
    public static String executeHttpDeleteRequest(HttpDelete hd) {

        String responseString = "";

        HttpClient httpclient = new DefaultHttpClient();
        HttpResponse response = null;

        JenkinsLogger.log("Executing Request: " + hd.getRequestLine());

        try {

            response = httpclient.execute(hd);
            String statusLine = response.getStatusLine().toString();
            JenkinsLogger.log(statusLine);
            HttpEntity entity = response.getEntity();
            responseString = EntityUtils.toString(entity, "UTF-8");

        } catch (HttpResponseException e) {

            JenkinsLogger.error("HTTP Response Code: " + e.getStatusCode());

        } catch (ParseException e) {
            JenkinsLogger.error(e.getMessage());
        } catch (IOException e) {
            JenkinsLogger.error(e.getMessage());
        } finally {

            HttpEntity entity = response.getEntity();
            try {
                responseString = EntityUtils.toString(entity, "UTF-8");
            } catch (IOException e) {
                // JenkinsLogger.error(e.getMessage());
            }

            httpclient.getConnectionManager().shutdown();
        }

        return responseString;
    }

    /**
     * Utility method to extract errors, if any, from the Skytap json response,
     * and throw an exception which can be handled by the caller.
     * 
     * @param response
     * @throws SkytapException
     */
    public static void checkResponseForErrors(String response) throws SkytapException {

        // check skytap response body for errors
        JsonParser parser = new JsonParser();
        JsonElement je = parser.parse(response);
        JsonObject jo = null;

        if (je.isJsonNull()) {
            return;
        } else if (je.isJsonArray()) {
            return;
        }

        je = parser.parse(response);
        jo = je.getAsJsonObject();

        if (!(jo.has("error") || jo.has("errors"))) {
            return;
        }

        // handle case where skytap returns an array of errors
        if (jo.has("errors")) {

            String errorString = "";

            JsonArray skytapErrors = (JsonArray) je.getAsJsonObject().get("errors");

            Iterator itr = skytapErrors.iterator();
            while (itr.hasNext()) {
                JsonElement errorElem = (JsonElement) itr.next();
                String errMsg = errorElem.toString();

                errorString += errMsg + "\n";

            }

            throw new SkytapException(errorString);

        }

        if (jo.has("error")) {

            // handle case where 'error' element is null value
            if (jo.get("error").isJsonNull()) {
                return;
            }

            // handle case where 'error' element is a boolean OR quoted string
            if (jo.get("error").isJsonPrimitive()) {

                String error = jo.get("error").getAsString();

                // handle boolean cases
                if (error.equals("false")) {
                    return;
                }

                // TODO: find out where the error msg would be in this case
                if (error.equals("true")) {
                    throw new SkytapException(error);
                }

                if (!error.equals("")) {
                    throw new SkytapException(error);
                }
            }

        }

    }

    /**
     * This method is used to obtain an id. If a file was provided, it extracts
     * the id from there. Otherwise it just uses the id provided by the user.
     * 
     * @param confId
     * @return runtimeConfigurationId
     */
    public static String getRuntimeId(String usersId, String usersFile) throws FileNotFoundException {

        String runtimeID = "";

        if (!usersFile.equals("")) {

            JenkinsLogger.log("User provided file: " + usersFile);

            // read the id from the config file
            JsonObject jo = SkytapUtils.getJsonObjectFromFile(usersFile);

            // if object returned was null, reading the file has failed, so fail
            // the build step
            if (jo == null) {
                JenkinsLogger.error("Unable to read file: " + usersFile);
                throw new FileNotFoundException("Unable to read file: " + usersFile);
            }

            runtimeID = jo.get("id").getAsString();

        } else {
            runtimeID = usersId;
        }

        return runtimeID;
    }

    /**
     * Makes call to skytap to retrieve the id of a named project.
     * 
     * @param projName
     * @param authCredentials
     * @return projId
     */
    public static String getProjectID(String projName, String authCredentials) {

        // build url
        StringBuilder sb = new StringBuilder("https://cloud.skytap.com");
        sb.append("/projects");

        // create http get
        HttpGet hg = SkytapUtils.buildHttpGetRequest(sb.toString(), authCredentials);

        // execute request
        String response;
        try {
            response = SkytapUtils.executeHttpRequest(hg);
        } catch (SkytapException e) {
            JenkinsLogger.error("Skytap Exception: " + e.getMessage());
            return "";
        }

        // response string will be a json array of all projects
        JsonParser parser = new JsonParser();
        JsonElement je = parser.parse(response);
        JsonArray ja = je.getAsJsonArray();

        Iterator itr = ja.iterator();
        while (itr.hasNext()) {
            JsonElement projElement = (JsonElement) itr.next();
            String projElementName = projElement.getAsJsonObject().get("name").getAsString();

            if (projElementName.equals(projName)) {
                String projElementId = projElement.getAsJsonObject().get("id").getAsString();
                return projElementId;
            }

        }

        JenkinsLogger.error("No project matching name \"" + projName + "\"" + " was found.");
        return "";
    }

    /**
     * Prepends the workspace path to a save file name as a default if user has
     * not provided a full path.
     * 
     * @param build
     * @param savefile
     * 
     * @return fullpath
     * 
     */
    public static String convertFileNameToFullPath(AbstractBuild build, String savefile) {

        FilenameUtils fu = new FilenameUtils();

        // if its just a filename with no path, prepend workspace dir

        if (fu.getPath(savefile).equals("")) {
            JenkinsLogger.log(
                    "File: " + savefile + " was specified without a path. Defaulting path to Jenkins workspace.");
            String workspacePath = SkytapUtils.expandEnvVars(build, "${WORKSPACE}");

            savefile = workspacePath + "/" + savefile;
            savefile = fu.separatorsToSystem(savefile);
            return savefile;

        } else {
            return fu.separatorsToSystem(savefile);
        }

    }

    /**
     * Executes a Skytap API call in order to get the network id of the network
     * whose name was provided by the user.
     * 
     * @param confId
     * @param netName
     * @return
     */
    public static String getNetworkIdFromName(String confId, String netName, String authCredential)
            throws SkytapException {

        // build request url to get config info
        StringBuilder sb = new StringBuilder("https://cloud.skytap.com/");
        sb.append("configurations/");
        sb.append(confId);
        String reqUrl = sb.toString();

        // create request
        HttpGet hg = SkytapUtils.buildHttpGetRequest(reqUrl, authCredential);

        // execute request
        String httpRespBody = "";
        httpRespBody = SkytapUtils.executeHttpRequest(hg);
        SkytapUtils.checkResponseForErrors(httpRespBody);

        // parse the response, first get the array of networks
        JsonParser parser = new JsonParser();
        JsonElement je = parser.parse(httpRespBody);
        JsonArray networkArray = (JsonArray) je.getAsJsonObject().get("networks");

        JenkinsLogger.log("Searching configuration's networks for network: " + netName);

        Iterator itr = networkArray.iterator();
        while (itr.hasNext()) {
            JsonElement networkElement = (JsonElement) itr.next();
            String networkElementName = networkElement.getAsJsonObject().get("name").getAsString();

            JenkinsLogger.log("Network Name: " + networkElementName);

            if (networkElementName.equals(netName)) {
                String networkElementId = networkElement.getAsJsonObject().get("id").getAsString();

                JenkinsLogger.log("Network Name Matched.");
                JenkinsLogger.log("Network ID: " + networkElementId);
                return networkElementId;
            }

        }

        throw new SkytapException("No network matching name \"" + netName + "\""
                + " is associated with configuration id " + confId + ".");
    }

}