/*******************************************************************************
*
* Copyright (c) 2004-2009 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi
*
*
*******************************************************************************/
package hudson;
import hudson.util.DualOutputStream;
import hudson.util.EncodingStream;
import com.thoughtworks.xstream.core.util.Base64Encoder;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.HttpRetryException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.nio.charset.Charset;
/**
* Entry point to Hudson from command line.
*
* <p> This tool runs another process and sends its result to Hudson.
*
* @author Kohsuke Kawaguchi
*/
public class Main {
public static void main(String[] args) {
try {
System.exit(run(args));
} catch (Exception e) {
e.printStackTrace();
System.exit(-1);
}
}
public static int run(String[] args) throws Exception {
String home = getHudsonHome();
if (home == null) {
System.err.println("HUDSON_HOME is not set.");
return -1;
}
if (args.length < 2) {
System.err.println("Usage: <job-name> <command> <args..>");
return -1;
}
return remotePost(args);
}
private static String getHudsonHome() {
return EnvVars.masterEnvVars.get("HUDSON_HOME");
}
/**
* Run command and place the result to a remote Hudson installation
*/
public static int remotePost(String[] args) throws Exception {
String projectName = args[0];
String home = getHudsonHome();
if (!home.endsWith("/")) {
home = home + '/'; // make sure it ends with '/'
}
// check for authentication info
String auth = new URL(home).getUserInfo();
if (auth != null) {
auth = "Basic " + new Base64Encoder().encode(auth.getBytes("UTF-8"));
}
{// check if the home is set correctly
HttpURLConnection con = open(new URL(home));
if (auth != null) {
con.setRequestProperty("Authorization", auth);
}
con.connect();
if (con.getResponseCode() != 200
|| con.getHeaderField("X-Hudson") == null) {
System.err.println(home + " is not Hudson (" + con.getResponseMessage() + ")");
return -1;
}
}
String projectNameEnc = URLEncoder.encode(projectName, "UTF-8").replaceAll("\\+", "%20");
{// check if the job name is correct
HttpURLConnection con = open(new URL(home + "job/" + projectNameEnc + "/acceptBuildResult"));
if (auth != null) {
con.setRequestProperty("Authorization", auth);
}
con.connect();
if (con.getResponseCode() != 200) {
System.err.println(projectName + " is not a valid job name on " + home + " (" + con.getResponseMessage() + ")");
return -1;
}
}
// get a crumb to pass the csrf check
String crumbField = null, crumbValue = null;
try {
HttpURLConnection con = open(new URL(home
+ "crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\":\",//crumb)'"));
if (auth != null) {
con.setRequestProperty("Authorization", auth);
}
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String line = in.readLine();
in.close();
String[] components = line.split(":");
if (components.length == 2) {
crumbField = components[0];
crumbValue = components[1];
}
} catch (IOException e) {
// presumably this Hudson doesn't use CSRF protection
}
// write the output to a temporary file first.
File tmpFile = File.createTempFile("hudson", "log");
try {
FileOutputStream os = new FileOutputStream(tmpFile);
Writer w = new OutputStreamWriter(os, "UTF-8");
w.write("<?xml version='1.0' encoding='UTF-8'?>");
w.write("<run><log encoding='hexBinary' content-encoding='" + Charset.defaultCharset().name() + "'>");
w.flush();
// run the command
long start = System.currentTimeMillis();
List<String> cmd = new ArrayList<String>();
for (int i = 1; i < args.length; i++) {
cmd.add(args[i]);
}
Proc proc = new Proc.LocalProc(cmd.toArray(new String[cmd.size()]), (String[]) null, System.in,
new DualOutputStream(System.out, new EncodingStream(os)));
int ret = proc.join();
w.write("</log><result>" + ret + "</result><duration>" + (System.currentTimeMillis() - start) + "</duration></run>");
w.close();
String location = home + "job/" + projectNameEnc + "/postBuildResult";
while (true) {
try {
// start a remote connection
HttpURLConnection con = open(new URL(location));
if (auth != null) {
con.setRequestProperty("Authorization", auth);
}
if (crumbField != null && crumbValue != null) {
con.setRequestProperty(crumbField, crumbValue);
}
con.setDoOutput(true);
// this tells HttpURLConnection not to buffer the whole thing
con.setFixedLengthStreamingMode((int) tmpFile.length());
con.connect();
// send the data
FileInputStream in = new FileInputStream(tmpFile);
Util.copyStream(in, con.getOutputStream());
in.close();
if (con.getResponseCode() != 200) {
Util.copyStream(con.getErrorStream(), System.err);
}
return ret;
} catch (HttpRetryException e) {
if (e.getLocation() != null) {
// retry with the new location
location = e.getLocation();
continue;
}
// otherwise failed for reasons beyond us.
throw e;
}
}
} finally {
tmpFile.delete();
}
}
/**
* Connects to the given HTTP URL and configure time out, to avoid infinite
* hang.
*/
private static HttpURLConnection open(URL url) throws IOException {
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setReadTimeout(TIMEOUT);
c.setConnectTimeout(TIMEOUT);
return c;
}
/**
* Set to true if we are running unit tests.
*/
public static boolean isUnitTest = false;
/**
* Set to true if we are running inside "mvn hpi:run" or "mvn
* hudson-dev:run"
*/
public static boolean isDevelopmentMode = Boolean.getBoolean(Main.class.getName() + ".development");
/**
* Time out for socket connection to Hudson.
*/
public static final int TIMEOUT = Integer.getInteger(Main.class.getName() + ".timeout", 15000);
}