diff --git a/.gitignore b/.gitignore
index c099e65..8d5f59b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,5 +4,8 @@
# Generated documentation
docs/
+# Generated code coverage report
+coverage/
+
# Local settings
*.local.php
diff --git a/Splunk/AmbiguousKeyException.php b/Splunk/AmbiguousKeyException.php
new file mode 100644
index 0000000..e22f04f
--- /dev/null
+++ b/Splunk/AmbiguousKeyException.php
@@ -0,0 +1,21 @@
+children(Splunk_AtomFeed::NS_S)->dict;
+ $listValue = $containerXml->children(Splunk_AtomFeed::NS_S)->list;
+
+ if (Splunk_XmlUtil::elementExists($dictValue))
+ {
+ return Splunk_AtomFeed::parseDict($dictValue);
+ }
+ else if (Splunk_XmlUtil::elementExists($listValue))
+ {
+ return Splunk_AtomFeed::parseList($listValue);
+ }
+ else // value is scalar
+ {
+ return Splunk_XmlUtil::getTextContent($containerXml);
+ }
+ }
+
+ private static function parseDict($dictXml)
+ {
+ $dict = array();
+ foreach ($dictXml->children(Splunk_AtomFeed::NS_S)->key as $keyXml)
+ {
+ $key = Splunk_XmlUtil::getAttributeValue($keyXml, 'name');
+ $value = Splunk_AtomFeed::parseValueInside($keyXml);
+
+ $dict[$key] = $value;
+ }
+ return $dict;
+ }
+
+ private static function parseList($listXml)
+ {
+ $list = array();
+ foreach ($listXml->children(Splunk_AtomFeed::NS_S)->item as $itemXml)
+ $list[] = Splunk_AtomFeed::parseValueInside($itemXml);
+ return $list;
+ }
+}
\ No newline at end of file
diff --git a/Splunk/Collection.php b/Splunk/Collection.php
new file mode 100644
index 0000000..9156ca2
--- /dev/null
+++ b/Splunk/Collection.php
@@ -0,0 +1,89 @@
+service->get($this->path);
+ $xml = new SimpleXMLElement($response->body);
+
+ $entries = array();
+ foreach ($xml->entry as $entryData)
+ {
+ $entries[] = $this->loadEntry($entryData);
+ }
+
+ $this->entries = $entries;
+ $this->loaded = TRUE;
+ }
+
+ private function loadEntry($entryData)
+ {
+ return new Splunk_Entity(
+ $this->service,
+ "{$this->path}/" . urlencode($entryData->title),
+ $entryData);
+ }
+
+ // === Children ===
+
+ /**
+ * Returns the unique entity with the specified name in this collection.
+ *
+ * @param string $name
+ * @return Splunk_Entity
+ * @throws Splunk_NoSuchKeyException
+ * @throws Splunk_AmbiguousKeyException
+ */
+ public function get($name)
+ {
+ $results = array();
+ foreach ($this->validate()->entries as $entry)
+ {
+ if ($entry->getName() == $name)
+ {
+ $results[] = $entry;
+ }
+ }
+
+ if (count($results) == 0)
+ {
+ throw new Splunk_NoSuchKeyException(
+ "No value exists with key '{$name}'.");
+ }
+ else if (count($results) == 1)
+ {
+ return $results[0];
+ }
+ else
+ {
+ throw new Splunk_AmbiguousKeyException(
+ "Multiple values exist with key '{$name}'. " .
+ "Specify a namespace to disambiguate.");
+ }
+ }
+}
diff --git a/Splunk/Context.php b/Splunk/Context.php
index c554093..4b95768 100644
--- a/Splunk/Context.php
+++ b/Splunk/Context.php
@@ -16,7 +16,7 @@
*/
/**
- * This class allows clients to issue HTTP requests to a Splunk server.
+ * Allows clients to issue HTTP requests to a Splunk server.
*
* @package Splunk
*/
@@ -24,17 +24,19 @@ class Splunk_Context
{
private $username;
private $password;
+ private $token;
private $host;
private $port;
private $scheme;
private $http;
- private $token;
-
/**
* @param array $args {
* 'username' => (optional) The username to login with. Defaults to "admin".
* 'password' => (optional) The password to login with. Defaults to "changeme".
+ * 'token' => (optional) The authentication token to use. If provided,
+ * the username and password are ignored and there is no
+ * need to call login(). In the format "Splunk SESSION_KEY".
* 'host' => (optional) The hostname of the Splunk server. Defaults to "localhost".
* 'port' => (optional) The port of the Splunk server. Defaults to 8089.
* 'scheme' => (optional) The scheme to use: either "http" or "https". Defaults to "https".
@@ -46,6 +48,7 @@ public function __construct($args)
$args = array_merge(array(
'username' => 'admin',
'password' => 'changeme',
+ 'token' => NULL,
'host' => 'localhost',
'port' => 8089,
'scheme' => 'https',
@@ -54,6 +57,7 @@ public function __construct($args)
$this->username = $args['username'];
$this->password = $args['password'];
+ $this->token = $args['token'];
$this->host = $args['host'];
$this->port = $args['port'];
$this->scheme = $args['scheme'];
@@ -72,11 +76,25 @@ public function login()
'password' => $this->password,
));
- $sessionKey = Splunk_Util::getTextContentAtXpath(
- new SimpleXMLElement($response['body']),
+ $sessionKey = Splunk_XmlUtil::getTextContentAtXpath(
+ new SimpleXMLElement($response->body),
'/response/sessionKey');
- $this->token = 'Splunk ' . $sessionKey;
+ $this->token = "Splunk {$sessionKey}";
+ }
+
+ /**
+ * Performs an HTTP GET request to the endpoint at the specified path.
+ *
+ * @param string $path relative or absolute URL path.
+ * @return array
+ * @see Splunk_Http::get
+ */
+ public function get($path)
+ {
+ return $this->http->get($this->url($path), array(
+ 'Authorization' => $this->token,
+ ));
}
// === Accessors ===
@@ -92,8 +110,25 @@ public function getToken()
// === Utility ===
+ /**
+ * @param string $path relative or absolute URL path.
+ * @return string absolute URL.
+ */
private function url($path)
{
- return "{$this->scheme}://{$this->host}:{$this->port}{$path}";
+ return "{$this->scheme}://{$this->host}:{$this->port}{$this->abspath($path)}";
+ }
+
+ /**
+ * @param string $path relative or absolute URL path.
+ * @return string absolute URL path.
+ */
+ private function abspath($path)
+ {
+ if ((strlen($path) >= 1) && ($path[0] == '/'))
+ return $path;
+
+ // TODO: Support namespaces
+ return "/services/{$path}";
}
}
diff --git a/Splunk/Endpoint.php b/Splunk/Endpoint.php
new file mode 100644
index 0000000..82d97c2
--- /dev/null
+++ b/Splunk/Endpoint.php
@@ -0,0 +1,55 @@
+service = $service;
+ $this->path = $path;
+ }
+
+ // === Load ===
+
+ /** Loads this resource if not already done. Returns self. */
+ protected function validate()
+ {
+ if (!$this->loaded)
+ {
+ $this->load();
+ assert($this->loaded);
+ }
+ return $this;
+ }
+
+ /**
+ * Loads this resource.
+ *
+ * Implementations must set $this->loaded to TRUE before returning.
+ */
+ protected abstract function load();
+}
\ No newline at end of file
diff --git a/Splunk/Entity.php b/Splunk/Entity.php
new file mode 100644
index 0000000..904a8e5
--- /dev/null
+++ b/Splunk/Entity.php
@@ -0,0 +1,89 @@
+data = $data;
+ if ($this->data != NULL)
+ $this->loadContentsOfData();
+ }
+
+ // === Load ===
+
+ protected function load()
+ {
+ $response = $this->service->get($this->path);
+ $xml = new SimpleXMLElement($response->body);
+
+ $this->data = $xml->entry;
+ $this->loadContentsOfData();
+ }
+
+ private function loadContentsOfData()
+ {
+ $this->content = Splunk_AtomFeed::parseValueInside($this->data->content);
+ $this->loaded = TRUE;
+ }
+
+ // === Accessors ===
+
+ public function getName()
+ {
+ return (string) $this->validate()->data->title;
+ }
+
+ // === ArrayAccess Methods ===
+
+ public function offsetGet($key)
+ {
+ return $this->validate()->content[$key];
+ }
+
+ public function offsetSet($key, $value)
+ {
+ throw new Splunk_UnsupportedOperationException();
+ }
+
+ public function offsetUnset($key)
+ {
+ throw new Splunk_UnsupportedOperationException();
+ }
+
+ public function offsetExists($key)
+ {
+ return isset($this->validate()->content[$key]);
+ }
+}
\ No newline at end of file
diff --git a/Splunk/Http.php b/Splunk/Http.php
index 182c060..198aef5 100644
--- a/Splunk/Http.php
+++ b/Splunk/Http.php
@@ -22,28 +22,32 @@
*/
class Splunk_Http
{
- public function get($url)
+ public function get($url, $request_headers=array())
{
- return $this->request('get', $url);
+ return $this->request('get', $url, $request_headers);
}
public function post($url, $params=array())
{
- return $this->request('post', $url, http_build_query($params));
+ return $this->request('post', $url, array(), http_build_query($params));
}
/**
- * @param string $method HTTP request method (ex: 'get').
- * @param string $url URL to fetch.
- * @param string $request_body content to send in the request.
+ * @param string $method HTTP request method (ex: 'get').
+ * @param string $url URL to fetch.
+ * @param array $request_headers dictionary of header names and values.
+ * @param string $request_body content to send in the request.
* @return object {
* 'status' => HTTP status code (ex: 200).
* 'reason' => HTTP reason string (ex: 'OK').
* 'headers' => Dictionary of headers. (ex: array('Content-Length' => '0')).
* 'body' => Content of the response.
* }
+ * @throws Splunk_ConnectException
+ * @throws Splunk_HttpException
*/
- private function request($method, $url, $request_body='')
+ private function request(
+ $method, $url, $request_headers=array(), $request_body='')
{
$opts = array(
CURLOPT_HTTPGET => TRUE,
@@ -55,6 +59,9 @@ private function request($method, $url, $request_body='')
CURLOPT_SSL_VERIFYPEER => FALSE,
);
+ foreach ($request_headers as $k => $v)
+ $opts[CURLOPT_HTTPHEADER][] = "$k: $v";
+
switch ($method)
{
case 'get':
@@ -93,7 +100,7 @@ private function request($method, $url, $request_body='')
list($http_version, $_, $reason) = explode(' ', $status_line, 3);
- $response = array(
+ $response = (object) array(
'status' => $status,
'reason' => $reason,
'headers' => $headers,
@@ -129,8 +136,7 @@ public function __construct($response)
{
$detail = Splunk_HttpException::parseFirstMessageFrom($response);
- // FIXME: Include HTTP "reason" in message
- $message = "HTTP {$response['status']} {$response['reason']}";
+ $message = "HTTP {$response->status} {$response->reason}";
if ($detail != NULL)
$message .= ' -- ' . $detail;
@@ -140,8 +146,8 @@ public function __construct($response)
private static function parseFirstMessageFrom($response)
{
- return Splunk_Util::getTextContentAtXpath(
- new SimpleXMLElement($response['body']),
+ return Splunk_XmlUtil::getTextContentAtXpath(
+ new SimpleXMLElement($response->body),
'/response/messages/msg');
}
diff --git a/Splunk/NoSuchKeyException.php b/Splunk/NoSuchKeyException.php
new file mode 100644
index 0000000..93a6b2b
--- /dev/null
+++ b/Splunk/NoSuchKeyException.php
@@ -0,0 +1,21 @@
+xpath($xpathExpr);
- return (count($matchingElements) == 0)
- ? NULL
- : Splunk_Util::getTextContentOfXmlElement($matchingElements[0]);
- }
-
- /**
- * @param SimpleXMLElement $xmlElement
- * @return string
- */
- private static function getTextContentOfXmlElement($xmlElement)
- {
- // HACK: Some versions of PHP 5 can't access the [0] element
- // of a SimpleXMLElement object properly.
- return (string) $xmlElement;
- }
-}
diff --git a/Splunk/XmlUtil.php b/Splunk/XmlUtil.php
new file mode 100644
index 0000000..565a5cc
--- /dev/null
+++ b/Splunk/XmlUtil.php
@@ -0,0 +1,71 @@
+getName() != '';
+ }
+
+ /**
+ * @param SimpleXMLElement $xml
+ * @param string $attributeName
+ * @return string|NULL
+ */
+ public static function getAttributeValue($xml, $attributeName)
+ {
+ return (isset($xml->attributes()->$attributeName))
+ ? (string) $xml->attributes()->$attributeName
+ : NULL;
+ }
+
+ /**
+ * @param SimpleXMLElement $xml
+ * @return string
+ */
+ public static function getTextContent($xml)
+ {
+ // HACK: Some versions of PHP 5 can't access the [0] element
+ // of a SimpleXMLElement object properly.
+ return (string) $xml;
+ }
+
+ /**
+ * @param SimpleXMLElement $xml
+ * @param string $xpathExpr
+ * @return string|NULL
+ */
+ public static function getTextContentAtXpath($xml, $xpathExpr)
+ {
+ $matchingElements = $xml->xpath($xpathExpr);
+ return (count($matchingElements) == 0)
+ ? NULL
+ : Splunk_XmlUtil::getTextContent($matchingElements[0]);
+ }
+}
diff --git a/tests/AtomFeedTest.php b/tests/AtomFeedTest.php
new file mode 100644
index 0000000..6dea115
--- /dev/null
+++ b/tests/AtomFeedTest.php
@@ -0,0 +1,126 @@
+1';
+ $expectedValue = 1;
+
+ $this->checkParseResult($xmlString, $expectedValue);
+ }
+
+ public function testParseDict()
+ {
+ $xmlString = '
+