From accd68aafe36d59dae44e8d1c0d907e95e72ab09 Mon Sep 17 00:00:00 2001 From: Daniel Aharon Date: Mon, 22 Aug 2011 09:39:09 -0700 Subject: [PATCH] Initial. --- Chef.php | 389 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ README | 0 2 files changed, 389 insertions(+) create mode 100644 Chef.php create mode 100644 README diff --git a/Chef.php b/Chef.php new file mode 100644 index 0000000..7ab604d --- /dev/null +++ b/Chef.php @@ -0,0 +1,389 @@ + + * @copyright 2011 Sazze, Inc. + */ + +namespace chef { + + class Chef { + + private $host; + private $port; + private $privateKey; + private $userId; + private $version; + + /** + * Chef::__construct() + * + * Set the variables used for every request. + * + * @param string $hostname FQDN or IP address of the Chef server. + * @param integer $port The port that the Chef server is listening on. + * @param string $userId The client name associated with the private key. + * @param string $privateKey Either the key itself, or the filename containing the key. + * @param string $chefVersion The Chef server version. + */ + public function __construct($host, $port, $userId, $privateKey, $chefVersion = '0.9.12') { + + $this->host = $host; + $this->port = $port; + $this->userId = $userId; + $this->version = $chefVersion; + + if (file_exists($privateKey)) { + $this->privateKey = file_get_contents($privateKey); + } else { + $this->privateKey = $privateKey; + } + } + + /** + * Chef::factory() + * + * Return a Chef instance. + * + * @param string $hostname FQDN or IP address of the Chef server. + * @param integer $port The port that the Chef server is listening on. + * @param string $privateKey Either the key itself, or the filename containing the key. + * @param string $userId The client name associated with the private key. + * @param string $chefVersion The Chef server version. + * + * @return Chef An instance of the Chef class. + */ + public static function factory($hostname, $port, $privateKey, $userId, $chefVersion = '0.9.12') { + return new static($hostname, $port, $privateKey, $userId, $chefVersion); + } + + /** + * Chef::searchIndexes() + * + * @return array A list of the indexes available for search on the Chef server. + */ + public function searchIndexes() { + $uri = '/search'; + return $this->httpGet($uri); + } + + /** + * Chef::search() + * + * Search a Chef server index. + * + * @param string $indexName The index to search. + * @param string $searchString A valid search string. eg: 'recipes:"php::fpm"' + * @param string $sort Sort the results by the specified attribute. + * @param integer $start The result number to start from. + * @param integer $rows How many rows to return. + * + * @return array The result set. + */ + public function search($indexName, $searchString, $sort = '', $start = 0, $rows = 9999) { + + $uri = '/search/' . $indexName; + $queryString = array( + 'q=' . urlencode($searchString), + 'rows=' . $rows + ); + + if ($start > 0) { + $queryString[] = 'start=' . $start; + } + + // Perform the search. + $result = $this->httpGet($uri, join('&', $queryString)); + + // Sort the results. + if (!empty($sort)) { + static::sortSearchResult($result, $sort); + } + + return $result; + } + + /** + * Chef::getNodes() + * + * Retrieve the list of nodes serviced by the Chef server. + * + * @return array The list of nodes known to the Chef server. + */ + public function getNodes() { + $uri = '/nodes'; + return $this->httpGet($uri); + } + + /** + * Chef::getNode() + * + * Retrieve the information associated with a specific node. + * + * @param string $nodeName The name of the node. + * + * @return array An associative array containing all known info about the node. + */ + public function getNode($nodeName) { + $uri = '/nodes/' . $nodeName; + return $this->httpGet($uri); + } + + /** + * Chef::getNodeRunList() + * + * Retrieve the run list of a specific node. + * + * @param string $nodeName The name of the node. + * + * @return array A list of the roles and recipes in the node's run list. + */ + public function getNodeRunList($nodeName) { + $uri = '/nodes/' . $nodeName . '/cookbooks'; + return $this->httpGet($uri); + } + + /** + * Chef::getRoles() + * + * Retrieve the list of roles. + * + * @return array The list of roles. + */ + public function getRoles() { + $uri = '/roles'; + return $this->httpGet($uri); + } + + /** + * Chef::getRole() + * + * Retrieve the specified role information. + * + * @param string $roleName The name of the role. + * + * @return array The roles and recipes contained in the specified role. + */ + public function getRole($roleName) { + $uri = '/roles/' . $roleName; + return $this->httpGet($uri); + } + + /** + * Chef::getCookbooks() + * + * @return array A list of cookbooks. + */ + public function getCookbooks() { + $uri = '/cookbooks'; + return $this->httpGet($uri); + } + + /** + * Chef::getCookbook() + * + * Get the specified cookbook's data. + * + * @param string $cookbookName The name of the cookbook. + * + * @return array An array containing all the cookbook data. + */ + public function getCookbook($cookbookName) { + $uri = '/cookbooks/' . $cookbookName; + return $this->httpGet($uri); + } + + /** + * Chef::getDataBags() + * + * Get a list of the data bags on the Chef server. + * + * @return array A list of the data bags on the Chef Server. + */ + public function getDataBags() { + $uri = '/data'; + return $this->httpGet($uri); + } + + /** + * Chef::getDataBagItems() + * + * Retrieve a list of the items in a data bag. + * + * @param string $dataBagName The name of the data bag. + * + * @return array A list of the items stored in the specified data bag. + */ + public function getDataBagItems($dataBagName) { + $uri = '/data/' . $dataBagName; + return $this->httpGet($uri); + } + + /** + * Chef::getDataBagItem() + * + * Get the contents of a data bag item. + * + * @param string $dataBagName The name of the data bag containing the item. + * @param string $itemName The name of the data bag item. + * + * @return array The contents of the data bag item. + */ + public function getDataBagItem($dataBagName, $itemName) { + $uri = '/data/' . $dataBagName . '/' . $itemName; + return $this->httpGet($uri); + } + + /** + * Chef::httpGet() + * + * Perform a GET request to the Chef server. + * + * @param string $uri The request URI. + * @param string $queryString The query string (for searching). + * + * @return array An associative array generated from the JSON response. + */ + private function httpGet($uri, $queryString = '') { + + $headers = $this->requestHeaders($uri, 'GET', '', $this->userId, $this->privateKey); + $headers['X-Chef-Version'] = $this->version; + $headers['Accept'] = 'application/json'; + + if (!empty($queryString)) { + $uri .= '?' . $queryString; + } + + $request = new \HttpRequest( + 'http://' . $this->host . ':' . $this->port . $uri, + HTTP_METH_GET, + array( + 'headers' => $headers + ) + ); + + $response = $request->send(); + return json_decode($response->getBody(), true); + } + + /** + * Chef::requestHeaders() + * + * Generate the encrypted header entries for the Chef request. + * + * @param string $uri The request URI. + * @param string $httpMethod The HTTP request method GET, or PUT. PUT not implemented yet. + * @param string $body The body of the request. + * @param string $userId The Chef server client name. + * @param string $privateKey The RSA private key for the client. + * + * @return array The headers specific to Chef server requests. + */ + private static function requestHeaders($uri, $httpMethod, $body, $userId, $privateKey) { + + $timestamp = new \DateTime(null, new \DateTimeZone('UTC')); + $timestamp = substr($timestamp->format(\DateTime::ISO8601), 0, -5) . 'Z'; + $hashedBody = base64_encode(sha1($body, true)); + + $headers = array( + 'X-Ops-Sign' => 'version=1.0', + 'X-Ops-Userid' => $userId, + 'X-Ops-TimeStamp' => $timestamp, + 'X-Ops-Content-Hash' => $hashedBody + ); + + $reqSig = static::requestSignature($uri, $httpMethod, $hashedBody, $timestamp, $userId, $privateKey); + + for ($index = 0; $index < strlen($reqSig); $index += 60) { + $key = 'X-Ops-Authorization-' . (string) (($index / 60) + 1); + $headers[$key] = substr($reqSig, $index, 60); + } + + return $headers; + } + + /** + * Chef::requestSignature() + * + * @param string $uri The request URI. + * @param string $httpMethod The HTTP request method GET, or PUT. PUT not implemented yet. + * @param string $hashedBody The hashed (base64 encoded SHA1) body of the request. + * @param string $timestamp ISO 8601 format using T as the separator. The timezone must be UTC, using Z as the indicator. eg: 2010-12-04T15:47:49Z + * @param string $userId The Chef server client name. + * @param string $privateKey The RSA private key for the client. + */ + private static function requestSignature($uri, $httpMethod, $hashedBody, $timestamp, $userId, $privateKey) { + + $httpMethod = strtoupper($httpMethod); + $hashedUri = base64_encode(sha1($uri, true)); + + $reqSig = + "Method:$httpMethod\n" . + "Hashed Path:$hashedUri\n" . + "X-Ops-Content-Hash:$hashedBody\n" . + "X-Ops-Timestamp:$timestamp\n" . + "X-Ops-UserId:$userId"; + + openssl_private_encrypt($reqSig, $crypted, $privateKey); + return base64_encode($crypted); + } + + /** + * Chef::arrayFlatten() + * + * Create a flattened copy of an array. + * + * @param array $arr The array to flatten. Duplicate keys will be overwritten. + * + * @return array A copy of the array that has been flattened. + */ + private static function arrayFlatten(array $arr) { + $output = array(); + + array_walk_recursive( + $arr, + function($value, $key) use (&$output) { + $output[$key] = $value; + } + ); + + return $output; + } + + private static function arrayKeySearch($key, $arr) { + + $arrIt = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($arr)); + + foreach ($arrIt as $sub) { + $subArray = $arrIt->getSubIterator(); + + if (array_key_exists($key, $subArray)) { + return $subArray[$key]; + } + } + + return null; + } + + private static function sortSearchResult(&$arr, $sortBy) { + + $values = array(); + + foreach ($arr['rows'] as $element) { + $values[] = static::arrayKeySearch($sortBy, $element); + } + + array_multisort($values, $arr['rows']); + } + + } +} + +?> diff --git a/README b/README new file mode 100644 index 0000000..e69de29