Added admin functionality; allowed user credentials in guzzle driver

This commit is contained in:
Stephen Hoogendijk
2015-08-02 22:13:17 +02:00
parent a41cfab6f2
commit 969829296c
11 changed files with 532 additions and 100 deletions

View File

@@ -262,9 +262,58 @@ Some functions are too general for a database. So these are available in the cli
$result = $client->listDatabases();
```
### Admin functionality
You can use the client's $client->admin functionality to administer InfluxDB via the API.
```php
// add a new user without privileges
$client->admin->createUser('testuser123', 'testpassword');
// add a new user with ALL cluster-wide privileges
$client->admin->createUser('admin_user', 'password', \InfluxDB\Client\Admin::PRIVILEGE_ALL);
// drop user testuser123
$client->admin->dropUser('testuser123');
```
List all the users:
```php
// show a list of all users
$results = $client->admin->showUsers();
// show users returns a ResultSet object
$users = $results->getPoints();
```
#### Granting and revoking privileges
Granting permissions can be done on both the database level and cluster-wide.
To grant a user specific privileges on a database, provide a database object or a database name.
```php
// grant permissions using a database object
$database = $client->selectDB('test_db');
$client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', $database);
// give user testuser123 read privileges on database test_db
$client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', 'test_db');
// revoke user testuser123's read privileges on database test_db
$client->admin->revoke(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123', 'test_db');
// grant a user cluster-wide privileges
$client->admin->grant(\InfluxDB\Client\Admin::PRIVILEGE_READ, 'testuser123');
// Revoke an admin's cluster-wide privileges
$client->admin->revoke(\InfluxDB\Client\Admin::PRIVILEGE_ALL, 'admin_user');
```
## Todo
* Add more admin features
* More unit tests
* Increase documentation (wiki?)
* Add more features to the query builder
@@ -273,6 +322,10 @@ Some functions are too general for a database. So these are available in the cli
## Changelog
####1.0.1
* Added support for authentication in the guzzle driver
* Added admin functionality
####1.0.0
* -BREAKING CHANGE- Dropped support for PHP 5.3 and PHP 5.4
* Allowing for custom drivers

View File

@@ -2,8 +2,10 @@
namespace InfluxDB;
use InfluxDB\Client\Admin;
use InfluxDB\Client\Exception as ClientException;
use InfluxDB\Driver\DriverInterface;
use InfluxDB\Driver\Exception as DriverException;
use InfluxDB\Driver\Guzzle;
use InfluxDB\Driver\QueryDriverInterface;
use InfluxDB\Driver\UDP;
@@ -16,6 +18,11 @@ use InfluxDB\Driver\UDP;
*/
class Client
{
/**
* @var Admin
*/
public $admin;
/**
* @var string
*/
@@ -124,6 +131,8 @@ class Client
]
)
);
$this->admin = new Admin($this);
}
/**
@@ -142,11 +151,12 @@ class Client
*
* @param string $database
* @param string $query
* @param array $params
* @param array $parameters
*
* @return ResultSet
* @throws Exception
*/
public function query($database, $query, $params = [])
public function query($database, $query, $parameters = [])
{
if (!$this->driver instanceof QueryDriverInterface) {
@@ -154,22 +164,29 @@ class Client
}
if ($database) {
$params['db'] = $database;
$parameters['db'] = $database;
}
$driver = $this->getDriver();
$driver->setParameters([
'url' => 'query?' . http_build_query(array_merge(['q' => $query], $params)),
'database' => $database,
'method' => 'get'
]);
$parameters = [
'url' => 'query?' . http_build_query(array_merge(['q' => $query], $parameters)),
'database' => $database,
'method' => 'get'
];
// add authentication to the driver if needed
if (!empty($this->username) && !empty($this->password)) {
$parameters += ['auth' => [$this->username, $this->password]];
}
$driver->setParameters($parameters);
try {
// perform the query and return the resultset
return $driver->query();
} catch (\Exception $e) {
} catch (DriverException $e) {
throw new Exception('Query has failed', $e->getCode(), $e);
}
}
@@ -265,21 +282,6 @@ class Client
return $this->timeout;
}
/**
* @param Point[] $points
* @return array
*/
protected function pointsToArray(array $points)
{
$names = [];
foreach ($points as $item) {
$names[] = $item['name'];
}
return $names;
}
/**
* @param Driver\DriverInterface $driver
*/
@@ -303,4 +305,20 @@ class Client
{
return $this->host;
}
/**
* @param Point[] $points
* @return array
*/
protected function pointsToArray(array $points)
{
$names = [];
foreach ($points as $item) {
$names[] = $item['name'];
}
return $names;
}
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* @author Stephen "TheCodeAssassin" Hoogendijk
*/
namespace InfluxDB\Client;
use InfluxDB\Client;
use InfluxDB\Database;
/**
* Class Admin
*
* @package InfluxDB\Client
*/
class Admin
{
/**
* @var Client
*/
private $client;
const PRIVILEGE_READ = 'READ';
const PRIVILEGE_WRITE = 'WRITE';
const PRIVILEGE_ALL= 'ALL';
public function __construct(Client $client)
{
$this->client = $client;
}
/**
* Create a user
*
* @param string $username
* @param string $password
*
* @param string $privilege
*
* @throws \InfluxDB\Exception
* @return \InfluxDB\ResultSet
*/
public function createUser($username, $password, $privilege = null)
{
$query = sprintf('CREATE USER %s WITH PASSWORD \'%s\'', $username, $password);
if ($privilege) {
$query .= " WITH $privilege PRIVILEGES";
}
return $this->client->query(null, $query);
}
/**
* @param string $username
*
* @return \InfluxDB\ResultSet
* @throws \InfluxDB\Exception
*/
public function dropUser($username)
{
return $this->client->query(null, 'DROP USER ' . $username);
}
/**
* Change a users password
*
* @param string $username
* @param string $newPassword
*
* @return \InfluxDB\ResultSet
* @throws \InfluxDB\Exception
*/
public function changeUserPassword($username, $newPassword)
{
return $this->client->query(null, "SET PASSWORD FOR $username = '$newPassword'");
}
/**
* Shows a list of all the users
*
* @return \InfluxDB\ResultSet
* @throws \InfluxDB\Exception
*/
public function showUsers()
{
return $this->client->query(null, "SHOW USERS");
}
/**
* Grants permissions
*
* @param string $privilege
* @param string $username
* @param Database|string $database
*
* @return \InfluxDB\ResultSet
*/
public function grant($privilege, $username, $database = null)
{
return $this->executePrivilege('GRANT', $privilege, $username, $database);
}
/**
* Revokes permissions
*
* @param string $privilege
* @param string $username
* @param Database|string $database
*
* @throws \InfluxDB\Exception
* @return \InfluxDB\ResultSet
*/
public function revoke($privilege, $username, $database = null)
{
return $this->executePrivilege('REVOKE', $privilege, $username, $database);
}
/**
* @param string $type
* @param string $privilege
* @param string $username
* @param Database|string $database
*
* @throws \InfluxDB\Exception
* @return \InfluxDB\ResultSet
*/
private function executePrivilege($type, $privilege, $username, $database = null)
{
if (!in_array($privilege, [self::PRIVILEGE_READ, self::PRIVILEGE_WRITE, self::PRIVILEGE_ALL])) {
throw new Exception($privilege . ' is not a valid privileges, allowed privileges: READ, WRITE, ALL');
}
if ($privilege != self::PRIVILEGE_ALL && !$database) {
throw new Exception('Only grant ALL cluster-wide privileges are allowed');
}
$database = ($database instanceof Database ? $database->getName() : (string) $database);
$query = "$type $privilege";
if ($database) {
$query .= sprintf(' ON %s ', $database);
} else {
$query .= " PRIVILEGES ";
}
if ($username && $type == 'GRANT') {
$query .= "TO $username";
} elseif ($username && $type == 'REVOKE') {
$query .= "FROM $username";
}
return $this->client->query(null, $query);
}
}

View File

@@ -9,8 +9,6 @@ use InfluxDB\Query\Builder as QueryBuilder;
/**
* Class Database
*
* @todo admin functionality
*
* @package InfluxDB
* @author Stephen "TheCodeAssassin" Hoogendijk
*/
@@ -128,11 +126,19 @@ class Database
try {
$driver = $this->client->getDriver();
$driver->setParameters([
$parameters = [
'url' => sprintf('write?db=%s&precision=%s', $this->name, $precision),
'database' => $this->name,
'method' => 'post'
]);
];
// add authentication to the driver if needed
if (!empty($this->username) && !empty($this->password)) {
$parameters += ['auth' => [$this->username, $this->password]];
}
$driver->setParameters($parameters);
// send the points to influxDB
$driver->write(implode(PHP_EOL, $payload));
@@ -140,7 +146,7 @@ class Database
return $driver->isSuccess();
} catch (\Exception $e) {
throw new Exception('Writing has failed', $e->getCode(), $e);
throw new Exception($e->getMessage(), $e->getCode());
}
}

View File

@@ -13,7 +13,6 @@ namespace InfluxDB\Driver;
interface DriverInterface
{
/**
* Called by the client write() method, will pass an array of required parameters such as db name
*

View File

@@ -7,6 +7,7 @@ namespace InfluxDB\Driver;
use GuzzleHttp\Client;
use Guzzle\Http\Message\Response;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Psr7\Request;
use InfluxDB\ResultSet;
@@ -77,18 +78,26 @@ class Guzzle implements DriverInterface, QueryDriverInterface
*/
public function write($data = null)
{
$this->response = $this->httpClient->post($this->parameters['url'], ['body' => $data]);
$this->response = $this->httpClient->post($this->parameters['url'], $this->getRequestParameters($data));
}
/**
* @throws Exception
* @return ResultSet
*/
public function query()
{
$response = $this->httpClient->get($this->parameters['url']);
$response = $this->httpClient->get($this->parameters['url'], $this->getRequestParameters());
$raw = (string) $response->getBody();
$responseJson = json_encode($raw);
if (isset($responseJson->error)) {
throw new Exception($responseJson->error);
}
return new ResultSet($raw);
}
@@ -102,4 +111,24 @@ class Guzzle implements DriverInterface, QueryDriverInterface
{
return in_array($this->response->getStatusCode(), ['200', '204']);
}
/**
* @param null $data
*
* @return array
*/
protected function getRequestParameters($data = null)
{
$requestParameters = ['http_errors' => false];
if ($data) {
$requestParameters += ['body' => $data];
}
if (isset($this->parameters['auth'])) {
$requestParameters += ['auth' => $this->parameters['auth']];
}
return $requestParameters;
}
}

124
tests/unit/AbstractTest.php Normal file
View File

@@ -0,0 +1,124 @@
<?php
/**
* @author Stephen "TheCodeAssassin" Hoogendijk
*/
namespace InfluxDB\Test;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use InfluxDB\Client;
use InfluxDB\Database;
use InfluxDB\Driver\Guzzle;
use InfluxDB\ResultSet;
use PHPUnit_Framework_MockObject_MockObject;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Psr7\Response;
/**
* @property mixed resultData
*/
abstract class AbstractTest extends \PHPUnit_Framework_TestCase
{
/** @var Client|PHPUnit_Framework_MockObject_MockObject $client */
protected $mockClient;
/**
* @var string
*/
protected $emptyResult = '{"results":[{}]}';
/**
* @var ResultSet
*/
protected $mockResultSet;
/** @var Database $database */
protected $database = null;
public function setUp()
{
$this->mockClient = $this->getMockBuilder('\InfluxDB\Client')
->disableOriginalConstructor()
->getMock();
$this->resultData = file_get_contents(dirname(__FILE__) . '/result.example.json');
$this->mockClient->expects($this->any())
->method('getBaseURI')
->will($this->returnValue($this->equalTo('http://localhost:8086')));
$this->mockClient->expects($this->any())
->method('query')
->will($this->returnValue(new ResultSet($this->resultData)));
$httpMockClient = new Guzzle($this->buildHttpMockClient(''));
// make sure the client has a valid driver
$this->mockClient->expects($this->any())
->method('getDriver')
->will($this->returnValue($httpMockClient));
$this->database = new Database('influx_test_db', $this->mockClient);
}
/**
* @return mixed
*/
public function getMockResultSet()
{
return $this->mockResultSet;
}
/**
* @param mixed $mockResultSet
*/
public function setMockResultSet($mockResultSet)
{
$this->mockResultSet = $mockResultSet;
}
/**
* @return GuzzleClient
*/
public function buildHttpMockClient($body)
{
// Create a mock and queue two responses.
$mock = new MockHandler([new Response(200, array(), $body)]);
$handler = HandlerStack::create($mock);
return new GuzzleClient(['handler' => $handler]);
}
/**
* @return string
*/
public function getEmptyResult()
{
return $this->emptyResult;
}
/**
* @param bool $emptyResult
*
* @return PHPUnit_Framework_MockObject_MockObject|Client
*/
public function getClientMock($emptyResult = false)
{
$mockClient = $this->getMockBuilder('\InfluxDB\Client')
->disableOriginalConstructor()
->getMock();
if ($emptyResult) {
$mockClient->expects($this->once())
->method('query')
->will($this->returnValue(new ResultSet($this->getEmptyResult())));
}
return $mockClient;
}
}

72
tests/unit/AdminTest.php Normal file
View File

@@ -0,0 +1,72 @@
<?php
namespace InfluxDB\Test;
use InfluxDB\Client;
use InfluxDB\Database;
use InfluxDB\Driver\Guzzle;
use InfluxDB\Point;
use InfluxDB\ResultSet;
use PHPUnit_Framework_MockObject_MockObject;
use PHPUnit_Framework_TestCase;
class AdminTest extends AbstractTest
{
/**
*
*/
public function setUp()
{
parent::setUp();
}
/**
*
*/
public function testCreateUser()
{
$adminObject = $this->getAdminObject(true);
$this->assertEquals(
new ResultSet($this->emptyResult),
$adminObject->createUser('test', 'test', Client\Admin::PRIVILEGE_ALL)
);
}
public function testChangeUserPassword()
{
$adminObject = $this->getAdminObject(true);
$this->assertEquals(
new ResultSet($this->emptyResult),
$adminObject->changeUserPassword('test', 'test')
);
}
public function testShowUsers()
{
$testJson = file_get_contents(dirname(__FILE__) . '/result-test-users.example.json');
$clientMock = $this->getClientMock();
$testResult = new ResultSet($testJson);
$clientMock->expects($this->once())
->method('query')
->will($this->returnValue($testResult));
$adminMock = new Client\Admin($clientMock);
$this->assertEquals($testResult, $adminMock->showUsers());
}
/**
* @return Client\Admin
*/
private function getAdminObject()
{
return new Client\Admin($this->getClientMock(true));
}
}

View File

@@ -2,16 +2,20 @@
namespace InfluxDB\Test;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use InfluxDB\Client;
use InfluxDB\Driver\Guzzle;
class ClientTest extends \PHPUnit_Framework_TestCase
class ClientTest extends AbstractTest
{
/**
*
*/
public function setUp()
{
parent::setUp();
}
/** @var Client $client */
protected $client = null;
@@ -43,7 +47,7 @@ class ClientTest extends \PHPUnit_Framework_TestCase
$query = "some-bad-query";
$bodyResponse = file_get_contents(dirname(__FILE__) . '/result.example.json');
$httpMockClient = self::buildHttpMockClient($bodyResponse);
$httpMockClient = $this->buildHttpMockClient($bodyResponse);
$client->setDriver(new Guzzle($httpMockClient));
@@ -53,15 +57,4 @@ class ClientTest extends \PHPUnit_Framework_TestCase
$this->assertInstanceOf('\InfluxDB\ResultSet', $result);
}
/**
* @return GuzzleClient
*/
public static function buildHttpMockClient($body)
{
// Create a mock and queue two responses.
$mock = new MockHandler([new Response(200, array(), $body)]);
$handler = HandlerStack::create($mock);
return new GuzzleClient(['handler' => $handler]);
}
}

View File

@@ -10,30 +10,14 @@ use InfluxDB\ResultSet;
use PHPUnit_Framework_MockObject_MockObject;
use PHPUnit_Framework_TestCase;
class DatabaseTest extends PHPUnit_Framework_TestCase
class DatabaseTest extends AbstractTest
{
/** @var Database $db */
protected $db = null;
/** @var Client|PHPUnit_Framework_MockObject_MockObject $client */
protected $mockClient;
/**
* @var string
*/
protected $dataToInsert;
/**
* @var string
*/
protected $resultData;
/**
* @var string
*/
static $emptyResult = '{"results":[{}]}';
/**
* @var
*/
@@ -41,34 +25,14 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
public function setUp()
{
$this->mockClient = $this->getMockBuilder('\InfluxDB\Client')
->disableOriginalConstructor()
->getMock();
parent::setUp();
$this->resultData = file_get_contents(dirname(__FILE__) . '/result.example.json');
$this->mockClient->expects($this->any())
->method('getBaseURI')
->will($this->returnValue($this->equalTo('http://localhost:8086')));
$this->mockClient->expects($this->any())
->method('query')
->will($this->returnValue(new ResultSet($this->resultData)));
$this->mockClient->expects($this->any())
->method('listDatabases')
->will($this->returnValue(array('test123', 'test')));
$httpMockClient = new Guzzle(ClientTest::buildHttpMockClient(''));
// make sure the client has a valid driver
$this->mockClient->expects($this->any())
->method('getDriver')
->will($this->returnValue($httpMockClient));
$this->db = new Database('influx_test_db', $this->mockClient);
$this->dataToInsert = file_get_contents(dirname(__FILE__) . '/input.example.json');
}
@@ -79,26 +43,18 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
public function testQuery()
{
$testResultSet = new ResultSet($this->resultData);
$this->assertEquals($this->db->query('SELECT * FROM test_metric'), $testResultSet);
$this->assertEquals($this->database->query('SELECT * FROM test_metric'), $testResultSet);
}
public function testCreateRetentionPolicy()
{
$retentionPolicy = new Database\RetentionPolicy('test', '1d', 1, true);
$mockClient = $this->getMockBuilder('\InfluxDB\Client')
->disableOriginalConstructor()
->getMock();
$mockClient->expects($this->once())
->method('query')
->will($this->returnValue(new ResultSet(self::$emptyResult)));
$mockClient = $this->getClientMock(true);
$database = new Database('test', $mockClient);
$this->assertEquals($database->createRetentionPolicy($retentionPolicy), new ResultSet(self::$emptyResult));
$this->assertEquals($database->createRetentionPolicy($retentionPolicy), new ResultSet($this->getEmptyResult()));
}
/**
@@ -139,6 +95,6 @@ class DatabaseTest extends PHPUnit_Framework_TestCase
0.84
);
$this->assertEquals(true, $this->db->writePoints(array($point1, $point2)));
$this->assertEquals(true, $this->database->writePoints(array($point1, $point2)));
}
}

View File

@@ -0,0 +1,24 @@
{
"results": [
{
"series": [
{
"columns": [
"user",
"admin"
],
"values": [
[
"test1",
true
],
[
"test2",
false
]
]
}
]
}
]
}