/*
* Copyright (C) 2011 - Gareth Llewellyn
*
* This file is part of Cyllell - http://blog.NetworksAreMadeOfString.co.uk/cyllell/
*
* 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 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 General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>
*/
package net.networksaremadeofstring.cyllell;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.util.HashMap;
import java.util.List;
import org.apache.http.NameValuePair;
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.HttpPut;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
/* A knife is used for cutting so that's what this little class does.
* I'm tempted to make Cuts() used for GETs and Slash() used for POSTS but
* we'll just have to wait and see
*/
public class Cuts
{
private SharedPreferences settings = null;
private String ChefURL = "https://api.opscode.com"; //Hosted Chef
private String PathSuffix = "/organizations/namos"; //Hosted Chef
//private String UserID = "Cyllell"; //The name of the API client
//private String PrivateKey = null;
private ResponseHandler<String> responseHandler;
//private String JSONReturn = "";
private Authentication ChefAuth = null;
//These don't get used directly
private DefaultHttpClient client;
private SingleClientConnManager mgr;
//This one is the HttpClient we do use;
private DefaultHttpClient httpClient;
public HttpGet httpget = null;
public HttpPut httpput = null;
public HttpDelete httpdelete = null;
public Cuts(Context thisContext) throws Exception
{
settings = thisContext.getSharedPreferences("Cyllell", 0);
responseHandler = new BasicResponseHandler();
if(settings.getString("URL", "--").equals("--") == false && settings.getString("ClientName", "--").equals("--") == false && settings.getString("PrivateKey", "--").equals("--") == false)
{
ChefAuth = new Authentication(settings.getString("ClientName", "--"),settings.getString("PrivateKey", "--"));
this.ChefURL = settings.getString("URL", "--");
this.PathSuffix = settings.getString("Suffix", "");
}
else
{
//Log.i("URL",settings.getString("URL", "--"));
//Log.i("ClientName",settings.getString("ClientName", "--"));
//Log.i("PrivateKey",settings.getString("PrivateKey", "--"));
throw new Exception("Chef URL is not set");
}
//Create the httpclient that trusts everything!
this.PrepareSSLHTTPClient();
}
private void PrepareSSLHTTPClient() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException
{
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, 3000);
HttpConnectionParams.setSoTimeout(httpParameters, 5000);
client = new DefaultHttpClient(httpParameters);
SchemeRegistry registry = new SchemeRegistry();
SocketFactory socketFactory = null;
//Check whether people are self signing or not
if(settings.getBoolean("SelfSigned", true))
{
//Log.i("SelfSigned","Allowing Self Signed Certificates");
socketFactory = TrustAllSSLSocketFactory.getDefault();
}
else
{
//Log.i("SelfSigned","Enforcing Certificate checks");
socketFactory = SSLSocketFactory.getSocketFactory();
}
registry.register(new Scheme("https", socketFactory, 443));
mgr = new SingleClientConnManager(client.getParams(), registry);
httpClient = new DefaultHttpClient(mgr, client.getParams());
}
/**
* Gets a list of all Nodes in the system along with the URI's to get additional details
* @return - JSONObject - Returns a hash of uri's for the nodes.
* @throws Exception
*/
public JSONObject GetCookbooks() throws Exception
{
String Path = this.PathSuffix + "/cookbooks";
this.httpget = new HttpGet(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","GET");
for(int i = 0; i < Headers.size(); i++)
{
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
//Log.i("JSONString:",jsonTempString);
JSONObject json = new JSONObject(jsonTempString);
return json;
}
/**
* Gets a list of all Nodes in the system along with the URI's to get additional details
* @return - JSONObject - Returns a hash of uri's for the nodes.
* @throws Exception
*/
public JSONObject GetCookbook(String CookbookURI) throws Exception
{
String Path = this.PathSuffix + "/cookbooks/" + CookbookURI + "/_latest";
this.httpget = new HttpGet(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","GET");
for(int i = 0; i < Headers.size(); i++)
{
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
//Log.i("JSONString:",jsonTempString);
JSONObject json = new JSONObject(jsonTempString);
return json;
}
/**
* Gets a list of all Nodes in the system along with the URI's to get additional details
* @return - JSONObject - Returns a hash of uri's for the nodes.
* @throws Exception
*/
public JSONObject GetNodes() throws Exception
{
String Path = this.PathSuffix + "/nodes";
this.httpget = new HttpGet(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","GET");
for(int i = 0; i < Headers.size(); i++)
{
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
//Log.i("JSONString:",jsonTempString);
JSONObject json = new JSONObject(jsonTempString);
return json;
}
/**
* Gets a list of all Roles in the system along with the URI's to get additional details
* @return - JSONObject - Returns a hash of uri's for the roles.
* @throws Exception
*/
public JSONObject GetRoles() throws Exception
{
String Path = this.PathSuffix + "/roles";
this.httpget = new HttpGet(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","GET");
for(int i = 0; i < Headers.size(); i++)
{
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
//Log.i("JSONString:",jsonTempString);
JSONObject json = new JSONObject(jsonTempString);
return json;
}
/**
* Gets a specific role and it details
* @return - JSONObject - Returns details of a role
* @throws Exception
*/
public JSONObject GetRole(String URI) throws Exception
{
String Path = this.PathSuffix + "/roles/"+URI;
this.httpget = new HttpGet(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","GET");
for(int i = 0; i < Headers.size(); i++)
{
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
//Log.i("JSONString:",jsonTempString);
JSONObject json = new JSONObject(jsonTempString);
return json;
}
public Boolean UpdateRole(JSONObject JSON) throws Exception
{
String Path = this.PathSuffix + "/roles/"+JSON.getString("name");
this.httpput = new HttpPut(this.ChefURL + Path);
Log.i("UpdateNodewithRawJSON",Path);
Log.i("UpdateNodewithRawJSON",JSON.toString(3));
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, JSON.toString(),"PUT");
for(int i = 0; i < Headers.size(); i++)
{
this.httpput.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
this.httpput.setEntity(new StringEntity(JSON.toString()));
httpClient.execute(this.httpput, responseHandler);
return true;
}
public Boolean UpdateRolewithRawJSON(String RoleURI, JSONObject JSON) throws Exception
{
String Path = this.PathSuffix + "/roles/"+RoleURI;
this.httpput = new HttpPut(this.ChefURL + Path);
Log.i("UpdateNodewithRawJSON",Path);
Log.i("UpdateNodewithRawJSON",JSON.toString(3));
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, JSON.toString(),"PUT");
for(int i = 0; i < Headers.size(); i++)
{
this.httpput.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
this.httpput.setEntity(new StringEntity(JSON.toString()));
httpClient.execute(this.httpput, responseHandler);
return true;
}
/**
* Gets a list of all environments in the system along with the URI's to get additional details
* @return - JSONObject - Returns a hash of uri's for the environments.
* @throws Exception
*/
public JSONObject GetEnvironments() throws Exception
{
String Path = this.PathSuffix + "/environments";
this.httpget = new HttpGet(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","GET");
for(int i = 0; i < Headers.size(); i++)
{
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
//Log.i("JSONString:",jsonTempString);
JSONObject json = new JSONObject(jsonTempString);
return json;
}
/**
* Gets a specific environemnt and its details
* @return - JSONObject - Returns details of a role
* @throws Exception
*/
public JSONObject GetEnvironment(String URI) throws Exception
{
String Path = this.PathSuffix + "/environments/"+URI;
this.httpget = new HttpGet(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","GET");
for(int i = 0; i < Headers.size(); i++)
{
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
JSONObject json = new JSONObject(jsonTempString);
return json;
}
public Boolean UpdateEnvironment(JSONObject JSON) throws Exception
{
String Path = this.PathSuffix + "/environments/"+JSON.getString("name");
this.httpput = new HttpPut(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, JSON.toString(),"PUT");
for(int i = 0; i < Headers.size(); i++)
{
this.httpput.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
this.httpput.setEntity(new StringEntity(JSON.toString()));
httpClient.execute(this.httpput, responseHandler);
return true;
}
public Boolean DeleteEnvironment(JSONObject JSON) throws Exception
{
String Path = this.PathSuffix + "/environments/"+JSON.getString("name");
this.httpdelete = new HttpDelete(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","DELETE");
for(int i = 0; i < Headers.size(); i++)
{
this.httpput.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
httpClient.execute(this.httpdelete, responseHandler);
return true;
}
public Boolean SetEnvironment(String NodeURI, String Environment) throws Exception
{
JSONObject NodeDetails = this.GetNode(NodeURI);
NodeDetails.put("chef_environment", Environment);
String Path = this.PathSuffix + "/nodes/"+NodeURI;
this.httpput = new HttpPut(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, NodeDetails.toString(),"PUT");
for(int i = 0; i < Headers.size(); i++)
{
this.httpput.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
this.httpput.setEntity(new StringEntity(NodeDetails.toString()));
String jsonTempString = httpClient.execute(this.httpput, responseHandler);
Log.i("JSONString:",jsonTempString);
JSONObject json = new JSONObject(jsonTempString);
if(json.get("chef_environment").equals(Environment))
{
return true;
}
else
{
return false;
}
}
public Boolean AddRunList(String NodeURI, String Role) throws Exception
{
JSONObject NodeDetails = this.GetNode(NodeURI);
Log.i("NodeDetails",NodeDetails.toString(3));
//TODO Replace with JSONObject.accumulate() at some point
JSONArray RunList = NodeDetails.getJSONArray("run_list");
RunList.put("role["+Role+"]");
NodeDetails.put("run_list", RunList);
Log.i("NodeDetails2",NodeDetails.toString(3));
String Path = this.PathSuffix + "/nodes/"+NodeURI;
this.httpput = new HttpPut(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, NodeDetails.toString(),"PUT");
for(int i = 0; i < Headers.size(); i++)
{
this.httpput.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
this.httpput.setEntity(new StringEntity(NodeDetails.toString()));
httpClient.execute(this.httpput, responseHandler);
//String jsonTempString = httpClient.execute(this.httpput, responseHandler);
//Log.i("JSONString:",jsonTempString);
//JSONObject json = new JSONObject(jsonTempString);
return true;
}
public Boolean UpdateNodewithRawJSON(String NodeURI, JSONObject JSON) throws Exception
{
String Path = this.PathSuffix + "/nodes/"+NodeURI;
this.httpput = new HttpPut(this.ChefURL + Path);
Log.i("UpdateNodewithRawJSON",Path);
Log.i("UpdateNodewithRawJSON",JSON.toString(3));
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, JSON.toString(),"PUT");
for(int i = 0; i < Headers.size(); i++)
{
this.httpput.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
this.httpput.setEntity(new StringEntity(JSON.toString()));
httpClient.execute(this.httpput, responseHandler);
return true;
}
/**
* Try and perform a chef function - it'll either succeed or throw an exception / return false
* @return - Boolean - whether we logged in or not
* @throws Exception
*/
public Boolean ConfirmLogin() throws Exception
{
String Path = this.PathSuffix + "/clients/" + settings.getString("ClientName", "--");
this.httpget = new HttpGet(this.ChefURL + Path);
Log.i("ConfirmLogin",Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","GET");
for(int i = 0; i < Headers.size(); i++)
{
Log.i("Headers",Headers.get(i).getName()+":"+Headers.get(i).getValue());
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
JSONObject json = new JSONObject(jsonTempString);
if(settings.getBoolean("OpenSourceChef", true) == true)
{
if(json.getString("chef_type").equals("client"))
{
return true;
}
else
{
return false;
}
}
else
{
if(json.getString("clientname").equals(settings.getString("ClientName", "--")))
{
return true;
}
else
{
return false;
}
}
}
/**
* Get all the details about a particular node
* @param URI - The unique name of the node in question
* @return - JSONObject - Complete list of cookbook attributes, definitions, libraries and recipes that are required for this node
* @throws Exception
*/
public JSONObject GetNode(String URI) throws Exception
{
String Path = this.PathSuffix + "/nodes/"+URI;
this.httpget = new HttpGet(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","GET");
for(int i = 0; i < Headers.size(); i++)
{
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
//Log.i("JSONString:",jsonTempString);
JSONObject json = new JSONObject(jsonTempString);
return json;
}
public HashMap<String,String> CanonicalizeNode(JSONObject Node)
{
HashMap<String,String> CanonicalNode = new HashMap<String,String>();
//TODO Make some form of awesome array to do this in a loop
try {
CanonicalNode.put("serial_number",
Node.getJSONObject("automatic").getJSONObject("dmi").getJSONObject("system").getString("serial_number"));
}
catch (JSONException e) { CanonicalNode.put("serial_number","Serial Number Not Found"); }
try {
CanonicalNode.put("sku_number",
Node.getJSONObject("automatic").getJSONObject("dmi").getJSONObject("system").getString("sku_number"));
}
catch (JSONException e) { CanonicalNode.put("sku_number","SKU Not Found"); }
try {
CanonicalNode.put("family",
Node.getJSONObject("automatic").getJSONObject("dmi").getJSONObject("system").getString("family"));
}
catch (JSONException e) { CanonicalNode.put("family","Unknown OS"); }
try {
CanonicalNode.put("uptime",
Node.getJSONObject("automatic").getString("uptime"));
//Log.i("uptime",Node.getJSONObject("automatic").getString("uptime"));
}
catch (JSONException e) { CanonicalNode.put("uptime","Uptime unknown"); }
try {
CanonicalNode.put("chef_environment","Environment: " + Node.getString("chef_environment"));
}
catch (JSONException e) { CanonicalNode.put("chef_environment","Chef Environment Unknown"); }
try {
CanonicalNode.put("cpuCountString",Node.getJSONObject("automatic").getJSONObject("cpu").getString("real") + " / " + Node.getJSONObject("automatic").getJSONObject("cpu").getString("total"));
}
catch (JSONException e) { CanonicalNode.put("cpuCountString","0 / 0"); }
try {
CanonicalNode.put("ramStatsString",Node.getJSONObject("automatic").getJSONObject("memory").getString("total") + " / " + Node.getJSONObject("automatic").getJSONObject("memory").getString("free"));
}
catch (JSONException e) { CanonicalNode.put("ramStatsString","0kB Total / 0kB Free"); }
try {
CanonicalNode.put("platform",Node.getJSONObject("automatic").getString("platform"));
}
catch (JSONException e) { CanonicalNode.put("platform",""); }
try
{
JSONArray runListArray = Node.getJSONArray("run_list");
String runList = "";
for(int i = 0; i < runListArray.length(); i++)
{
runList += runListArray.getString(i);
if(i != runListArray.length() - 1)
runList += ", ";
}
CanonicalNode.put("run_list",runList);
//Log.i("CanonicalizeNode",runList);
}
catch (JSONException e) { CanonicalNode.put("run_list","Unknown"); }
return CanonicalNode;
}
public JSONObject Search(String Query, String Index) throws Exception
{
String Path = this.PathSuffix + "/search/"+Index;
Query = Query.replace(' ', '*');
if(Index.equals("node"))
{
this.httpget = new HttpGet(this.ChefURL + Path + "?q=name:*"+Query+"*%20OR%20role:*"+Query+"*");
}
else
{
this.httpget = new HttpGet(this.ChefURL + Path + "?q=name:*"+Query+"*");
}
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","GET");
for(int i = 0; i < Headers.size(); i++)
{
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
//Log.i("JSONString:",jsonTempString);
JSONObject json = new JSONObject(jsonTempString);
return json;
}
public JSONObject CraftedSearch(String Query, String Index) throws Exception
{
String Path = this.PathSuffix + "/search/"+Index.toLowerCase();
Query = Query.replace(" ", "%20");
this.httpget = new HttpGet(this.ChefURL + Path.toLowerCase() + "?q="+Query);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "","GET");
for(int i = 0; i < Headers.size(); i++)
{
this.httpget.setHeader(Headers.get(i).getName(),Headers.get(i).getValue());
}
String jsonTempString = httpClient.execute(this.httpget, responseHandler);
Log.i("CraftedSearch JSONString:",jsonTempString);
JSONObject json = new JSONObject(jsonTempString);
return json;
}
}