/*
* Copyright 2010-2011, CloudBees Inc.
*
* 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 com.cloudbees.api;
import com.cloudbees.upload.ArchiveUtils;
import com.cloudbees.upload.JarUtils;
import com.cloudbees.utils.AppConfigHelper;
import com.cloudbees.utils.ZipHelper;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
import com.thoughtworks.xstream.mapper.MapperWrapper;
import org.codehaus.jettison.json.JSONObject;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
/**
* @author Fabian Donze
*/
public class BeesClient extends BeesClientBase
{
static Logger logger = Logger.getLogger(BeesClient.class.getSimpleName());
public BeesClient(BeesClientConfiguration beesClientConfiguration) {
super(beesClientConfiguration);
}
public BeesClient(String server, String apikey, String secret,
String format, String version)
{
super(server, apikey, encodePassword(secret, version), format,
version);
}
public SayHelloResponse sayHello(String message) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("message", message);
String url = getRequestURL("say.hello", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
SayHelloResponse helloResponse =
(SayHelloResponse)readResponse(response);
return helloResponse;
}
public ApplicationGetSourceUrlResponse applicationGetSourceUrl(
String appId) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
String url = getRequestURL("application.getSourceUrl", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
ApplicationGetSourceUrlResponse apiResponse =
(ApplicationGetSourceUrlResponse)readResponse(response);
return apiResponse;
}
public ApplicationDeleteResponse applicationDelete(String appId) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
String url = getRequestURL("application.delete", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
ApplicationDeleteResponse apiResponse =
(ApplicationDeleteResponse)readResponse(response);
return apiResponse;
}
public ApplicationRestartResponse applicationRestart(String appId) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
String url = getRequestURL("application.restart", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
ApplicationRestartResponse apiResponse =
(ApplicationRestartResponse)readResponse(response);
return apiResponse;
}
public ApplicationStatusResponse applicationStart(String appId) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
String url = getRequestURL("application.start", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
ApplicationStatusResponse apiResponse =
(ApplicationStatusResponse)readResponse(response);
return apiResponse;
}
public ApplicationStatusResponse applicationStop(String appId) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
String url = getRequestURL("application.stop", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
ApplicationStatusResponse apiResponse =
(ApplicationStatusResponse)readResponse(response);
return apiResponse;
}
public ApplicationListResponse applicationList() throws Exception
{
Map<String, String> params = new HashMap<String, String>();
String url = getRequestURL("application.list", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
ApplicationListResponse apiResponse =
(ApplicationListResponse)readResponse(response);
return apiResponse;
}
public ApplicationInfo applicationInfo(String appId) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
String url = getRequestURL("application.info", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
ApplicationInfoResponse apiResponse =
(ApplicationInfoResponse)readResponse(response);
return apiResponse.getApplicationInfo();
}
public ApplicationSetMetaResponse applicationSetMeta(String appId,
Map<String, String> metaAttrs) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.putAll(metaAttrs);
params.put("app_id", appId);
String url = getRequestURL("application.setMeta", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
ApplicationSetMetaResponse apiResponse =
(ApplicationSetMetaResponse)readResponse(response);
return apiResponse;
}
/**
* @deprecated
*/
public ApplicationJarHashesResponse applicationJarCrcs(String appId, Map<String, String> hashes) throws Exception
{
return applicationJarHashes(appId, hashes);
}
public ApplicationJarHashesResponse applicationJarHashes(String appId, Map<String, String> hashes) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
params.put("hashes", createParameter(hashes));
String url = getApiUrl("application.jarHashes").toString();
params.put("action", "application.jarHashes");
// use the upload method (POST) to handle the potentially large "hashes" parameter payload
trace("API call: " + url);
String response = executeUpload(url, params, new HashMap<String, File>(), null);
ApplicationJarHashesResponse apiResponse =
(ApplicationJarHashesResponse)readResponse(response);
return apiResponse;
}
public ApplicationDeployArchiveResponse applicationDeployEar(
String appId, String environment, String description, String earFile,
String srcFile, UploadProgress progress) throws Exception
{
String archiveType = "ear";
return applicationDeployArchive(appId, environment, description,
earFile, srcFile, archiveType, false, progress);
}
public ApplicationDeployArchiveResponse applicationDeployWar(
String appId, String environment, String description, String warFile,
String srcFile, UploadProgress progress) throws Exception
{
return applicationDeployWar(appId, environment, description, warFile,
srcFile, true, progress);
}
public ApplicationDeployArchiveResponse applicationDeployWar(
String appId, String environment, String description, String warFile,
String srcFile, boolean deltaDeploy, UploadProgress progress) throws Exception
{
String archiveType = "war";
return applicationDeployArchive(appId, environment, description,
warFile, srcFile, archiveType, deltaDeploy, progress);
}
public ApplicationDeployArchiveResponse applicationDeployArchive(
String appId, String environment, String description, String earFile,
String srcFile, String archiveType, UploadProgress progress) throws Exception
{
return applicationDeployArchive(appId, environment, description, earFile, srcFile, archiveType, false, progress);
}
public ApplicationDeployArchiveResponse applicationDeployArchive(
String appId, String environment, String description, String earFile,
String srcFile, String archiveType, boolean deltaDeploy, UploadProgress progress) throws Exception
{
return applicationDeployArchive(appId, environment, description, earFile, srcFile, archiveType, deltaDeploy, null, progress);
}
public ApplicationDeployArchiveResponse applicationDeployArchive(
String appId, String environment, String description, String earFile,
String srcFile, String archiveType, boolean deltaDeploy, Map<String, String> parameters, UploadProgress progress) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
Map<String, File> fileParams = new HashMap<String, File>();
params.put("app_id", appId);
File archiveFile = new File(earFile);
// Currently only support WAR file for delta upload
boolean deployDelta = false;
boolean deployJarDelta = false;
// Create delta deploy File
if (deltaDeploy && archiveType.equals("war")) {
trace("Get existing checksums");
ApplicationCheckSumsResponse applicationCheckSumsResponse = applicationCheckSums(appId, false);
if (logger.isLoggable(Level.FINER)) {
for (Map.Entry<String, Long> entry : applicationCheckSumsResponse.getCheckSums().entrySet()) {
logger.finer("Entry: " + entry.getKey() + " CRC: " + entry.getValue());
}
}
if (applicationCheckSumsResponse.getCheckSums().size() == 0) {
trace("No existing checksums, upload full archive");
} else {
trace("Creating Delta archive for: " + archiveFile);
archiveFile = ArchiveUtils.createDeltaWarFile(applicationCheckSumsResponse.getCheckSums(), archiveFile, archiveFile.getParent());
deployDelta = true;
}
}
if (deltaDeploy && archiveType.equals("war")) {
trace("Get existing jar hashes");
ApplicationJarHashesResponse applicationJarHashesResponse = applicationJarHashes(appId, JarUtils.getJarHashes(archiveFile));
if (applicationJarHashesResponse.getJarHash().size() == 0) {
trace("No existing jars");
} else {
trace("Creating Delta2 archive for: " + archiveFile);
File archiveFile2 = JarUtils.createDeltaWarFile(applicationJarHashesResponse.getJarHash(), archiveFile, archiveFile.getParent());
// Delete the old delta archive
if (deployDelta) {
archiveFile.delete();
}
archiveFile = archiveFile2;
deployJarDelta = true;
}
}
if (deployDelta || deployJarDelta)
trace("Uploading delta archive: " + archiveFile);
File archiveFileSrc = srcFile != null ? new File(srcFile) : null;
long uploadSize = archiveFile.length();
if (archiveFileSrc != null)
uploadSize += archiveFileSrc.length();
fileParams.put("archive", archiveFile);
params.put("archive_type", archiveType);
if (environment != null)
params.put("environment", environment);
if (description != null)
params.put("description", description);
if (archiveFileSrc != null)
fileParams.put("src", archiveFileSrc);
params.put("parameters", createParameter(parameters));
// extend the deploy invocation timeout to 4 hours
long expireTime = System.currentTimeMillis() + 4 * 60 * 60 * 1000;
params.put("expires", new Long(expireTime / 1000).toString());
String url = getApiUrl("application.deployArchive").toString();
params.put("action", "application.deployArchive");
trace("API call: " + url);
String response = executeUpload(url, params, fileParams, progress);
try {
ApplicationDeployArchiveResponse apiResponse =
(ApplicationDeployArchiveResponse)readResponse(response);
return apiResponse;
} catch (Exception e) {
logger.log(Level.SEVERE, "Invalid application deployment response: " + appId, e);
logger.log(Level.FINE, "Deploy response trace: " + response);
throw e;
} finally {
// Delete the delta archive file
if (deployDelta || deployJarDelta)
archiveFile.delete();
}
}
public ApplicationCheckSumsResponse applicationCheckSums(String appId) throws Exception
{
return applicationCheckSums(appId, true);
}
public ApplicationCheckSumsResponse applicationCheckSums(String appId, boolean traceResponse) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
String url = getRequestURL("application.checkSums", params);
trace("API call: " + url);
String response = executeRequest(url);
if (traceResponse)
traceResponse(response);
ApplicationCheckSumsResponse apiResponse =
(ApplicationCheckSumsResponse)readResponse(response);
return apiResponse;
}
public DatabaseCreateResponse databaseCreate(String domain, String dbId,
String username, String password) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("database_id", dbId);
params.put("database_username", username);
params.put("database_password", password);
params.put("domain", domain);
String url = getRequestURL("database.create", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
DatabaseCreateResponse apiResponse =
(DatabaseCreateResponse)readResponse(response);
return apiResponse;
}
public DatabaseDeleteResponse databaseDelete(String dbId) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("database_id", dbId);
String url = getRequestURL("database.delete", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
DatabaseDeleteResponse apiResponse =
(DatabaseDeleteResponse)readResponse(response);
return apiResponse;
}
public DatabaseInfo databaseInfo(String dbId, boolean fetchPassword) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("database_id", dbId);
params.put("fetch_password", ((Boolean)fetchPassword).toString());
String url = getRequestURL("database.info", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
DatabaseInfoResponse apiResponse =
(DatabaseInfoResponse)readResponse(response);
return apiResponse.getDatabaseInfo();
}
public DatabaseListResponse databaseList() throws Exception
{
Map<String, String> params = new HashMap<String, String>();
String url = getRequestURL("database.list", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
DatabaseListResponse apiResponse =
(DatabaseListResponse)readResponse(response);
return apiResponse;
}
public AccountKeysResponse accountKeys(String domain, String user, String password) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("user", user);
params.put("password", password);
if (domain != null) params.put("domain", domain);
String url = getRequestURL("account.keys", params);
String response = executeRequest(url);
AccountKeysResponse apiResponse =
(AccountKeysResponse)readResponse(response);
return apiResponse;
}
public AccountListResponse accountList() throws Exception
{
Map<String, String> params = new HashMap<String, String>();
String url = getRequestURL("account.list", params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
AccountListResponse apiResponse =
(AccountListResponse)readResponse(response);
return apiResponse;
}
public ApplicationConfiguration getApplicationConfiguration(String warFilePath, String account, String[] environments) throws Exception {
ApplicationConfiguration appConfig;
File deployFile = new File(warFilePath);
if (deployFile.exists()) {
appConfig = getAppConfig(deployFile, environments, new String[] { "deploy" });
} else {
throw new IllegalArgumentException("File not found: " + warFilePath);
}
String appid = appConfig.getApplicationId();
if (appid == null || appid.equals(""))
throw new IllegalArgumentException("No application id specified");
String[] appIdParts = appid.split("/");
if (appIdParts.length < 2) {
if (account != null && !account.equals("")) {
appConfig.setApplicationId(account + "/" + appid);
} else {
throw new IllegalArgumentException("Application account not specified");
}
}
return appConfig;
}
protected static ApplicationConfiguration getAppConfig(File deployZip, final String[] environments,
final String[] implicitEnvironments) throws IOException {
final ApplicationConfiguration applicationConfiguration = new ApplicationConfiguration();
FileInputStream fin = new FileInputStream(deployZip);
try {
ZipHelper.unzipFile(fin, new ZipHelper.ZipEntryHandler() {
public void unzip(ZipEntry entry, InputStream zis)
throws IOException {
if (entry.getName().equals("META-INF/stax-application.xml")
|| entry.getName().equals("WEB-INF/stax-web.xml")
|| entry.getName().equals("WEB-INF/cloudbees-web.xml")) {
AppConfigHelper.load(applicationConfiguration, zis, environments, implicitEnvironments);
}
}
}, false);
} finally {
fin.close();
}
return applicationConfiguration;
}
private String createParameter(Map<String,String>parameters) {
if (parameters == null)
parameters = new HashMap<String, String>();
JSONObject jsonObject = new JSONObject(parameters);
return jsonObject.toString();
}
public void tailLog(String appId, String logName, OutputStream out) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
params.put("app_id", appId);
params.put("log_name", logName);
String url = getRequestURL("tail", params, false);
trace("API call: " + url);
InputStream input = executeCometRequest(url);
byte[] bytes = new byte[1024];
int numRead = input.read(bytes);
while (numRead != -1) {
out.write(bytes, 0, numRead);
numRead = input.read(bytes);
}
}
public String call(String action, Map<String, String> params) throws Exception
{
String url = getRequestURL(action, params);
trace("API call: " + url);
String response = executeRequest(url);
traceResponse(response);
return response;
}
protected XStream getXStream() throws Exception {
XStream xstream;
if (format.equals("json")) {
xstream = new XStream(new JettisonMappedXmlDriver()) {
protected MapperWrapper wrapMapper(MapperWrapper next) {
return new MapperWrapper(next) {
public boolean shouldSerializeMember(Class definedIn, String fieldName) {
return definedIn != Object.class ? super.shouldSerializeMember(definedIn, fieldName) : false;
}
};
}
};
} else if (format.equals("xml")) {
xstream = new XStream() {
protected MapperWrapper wrapMapper(MapperWrapper next) {
return new MapperWrapper(next) {
public boolean shouldSerializeMember(Class definedIn, String fieldName) {
return definedIn != Object.class ? super.shouldSerializeMember(definedIn, fieldName) : false;
}
};
}
};
} else {
throw new Exception("Unknown format: " + format);
}
xstream.processAnnotations(SayHelloResponse.class);
xstream.processAnnotations(ApplicationGetSourceUrlResponse.class);
xstream.processAnnotations(ApplicationDeleteResponse.class);
xstream.processAnnotations(ApplicationDeployResponse.class);
xstream.processAnnotations(ApplicationDeployArchiveResponse.class);
xstream.processAnnotations(ApplicationInstallResponse.class);
xstream.processAnnotations(ApplicationInfo.class);
xstream.processAnnotations(ApplicationInfoResponse.class);
xstream.processAnnotations(ApplicationListResponse.class);
xstream.processAnnotations(ApplicationRestartResponse.class);
xstream.processAnnotations(ApplicationStatusResponse.class);
xstream.processAnnotations(ApplicationSetMetaResponse.class);
xstream.processAnnotations(ApplicationCheckSumsResponse.class);
xstream.processAnnotations(DatabaseCreateResponse.class);
xstream.processAnnotations(DatabaseDeleteResponse.class);
xstream.processAnnotations(DatabaseInfo.class);
xstream.processAnnotations(DatabaseInfoResponse.class);
xstream.processAnnotations(DatabaseListResponse.class);
xstream.processAnnotations(ErrorResponse.class);
xstream.processAnnotations(AccountKeysResponse.class);
xstream.processAnnotations(AccountInfo.class);
xstream.processAnnotations(AccountListResponse.class);
xstream.processAnnotations(ApplicationJarHashesResponse.class);
// Hack to fix backward compatibility
xstream.alias("net.stax.api.ApplicationStatusResponse", ApplicationStatusResponse.class);
xstream.alias("net.stax.api.ApplicationSetMetaResponse", ApplicationSetMetaResponse.class);
return xstream;
}
protected Object readResponse(String response) throws Exception
{
Object obj = getXStream().fromXML(response);
if (obj instanceof ErrorResponse) {
throw new BeesClientException((ErrorResponse)obj);
}
return obj;
}
public static String encodePassword(String password, String version)
{
if (version.equals("0.1")) {
try {
MessageDigest sha = MessageDigest.getInstance("SHA");
byte[] shaBytes = sha.digest(password.getBytes("UTF8"));
StringBuffer hex = new StringBuffer();
for (int i = 0; i < shaBytes.length; ++i) {
hex.append(Integer.toHexString(
(shaBytes[i] & 0xFF) | 0x100).substring(1, 3));
}
return hex.toString();
} catch (NoSuchAlgorithmException e) {
} catch (UnsupportedEncodingException e) {
}
return null;
} else
return password;
}
public void mainCall(String[] args) throws Exception
{
Map<String, String> params = new HashMap<String, String>();
int argIndex = 0;
if (argIndex < args.length) {
String action = args[argIndex++];
for (; argIndex < args.length; argIndex++) {
String arg = args[argIndex];
String[] pair = arg.split("=", 2);
if (pair.length < 2)
throw new BeesClient.UsageError("Marlformed call parameter pair: " +
arg);
params.put(pair[0], pair[1]);
}
String response = call(action, params);
System.out.println(response);
} else
throw new BeesClient.UsageError("Missing required action argument");
}
public void main(String[] args) throws Exception
{
int argIndex = 0;
Map<String, String> options = new HashMap<String, String>();
for (; argIndex < args.length; argIndex++) {
String arg = args[argIndex];
if (arg.startsWith("-")) {
if (arg.equals("--call") || arg.equals("-c"))
options.put("operation", arg);
else if (arg.equals("--username") || arg.equals("-u"))
options.put("username", arg);
else if (arg.equals("--password") || arg.equals("-p"))
options.put("password", arg);
else if (arg.equals("--url") || arg.equals("-u"))
options.put("url", arg);
else
throw new BeesClient.UsageError("Unsupported option: " + arg);
} else {
break;
}
}
String operation = getRequiredOption("operation", options);
BeesClient client =
new BeesClient(getRequiredOption("url", options),
getRequiredOption("username", options), getRequiredOption(
"password", options), "0.1", "1.0");
if (operation.equals("call")) {
String[] subArgs = new String[args.length - argIndex];
for (int i = 0; i < subArgs.length; i++) {
subArgs[i] = args[argIndex++];
}
client.main(subArgs);
}
}
private static String getRequiredOption(String optionName,
Map<String, String> options) throws BeesClient.UsageError
{
if (options.containsKey(optionName))
return options.get(optionName);
else
throw new BeesClient.UsageError("Missing required flag: --" + optionName);
}
public static class UsageError extends Exception
{
UsageError(String reason)
{
super(reason);
}
}
}