Support Graphite Web with HTTP basic authn

refs #36
This commit is contained in:
Alexander A. Klimov 2017-08-31 14:34:32 +02:00
parent 346990bde0
commit 32ddce05bc
8 changed files with 231 additions and 42 deletions

View file

@ -7,6 +7,7 @@ use Icinga\Exception\NotFoundError;
use Icinga\Module\Graphite\GraphiteChart;
use Icinga\Module\Graphite\GraphiteUtil;
use Icinga\Module\Graphite\GraphiteWeb;
use Icinga\Module\Graphite\GraphiteWebClient;
use Icinga\Module\Graphite\GraphTemplate;
use Icinga\Module\Graphite\TemplateStore;
use Icinga\Web\Controller;
@ -14,8 +15,6 @@ use Icinga\Web\Widget;
class ShowController extends Controller
{
protected $baseUrl;
protected $graphiteWeb;
protected $templates;
@ -34,8 +33,7 @@ class ShowController extends Controller
{
$config = $this->Config();
$this->templateStore = new TemplateStore();
$this->baseUrl = $this->Config()->get('graphite', 'web_url');
$graphite = $this->graphiteWeb = new GraphiteWeb($this->baseUrl);
$graphite = $this->graphiteWeb = new GraphiteWeb(GraphiteWebClient::getInstance());
$this->template = $this->view->template = $this->loadTemplate();
$this->params->shift('r');
}

View file

@ -3,6 +3,7 @@
namespace Icinga\Module\Graphite\Forms;
use Icinga\Forms\ConfigForm as BaseConfigForm;
use Icinga\Module\Graphite\Web\Form\Validator\HttpUserValidator;
class ConfigForm extends BaseConfigForm
{
@ -14,15 +15,34 @@ class ConfigForm extends BaseConfigForm
public function createElements(array $formData)
{
$this->addElement(
'text',
'graphite_web_url',
array(
'required' => true,
'label' => $this->translate('Graphite Web URL'),
'description' => $this->translate('URL to your Graphite Web'),
'validators' => ['UrlValidator']
)
);
$this->addElements([
[
'text',
'graphite_web_url',
[
'required' => true,
'label' => $this->translate('Graphite Web URL'),
'description' => $this->translate('URL to your Graphite Web'),
'validators' => ['UrlValidator']
]
],
[
'text',
'graphite_web_user',
[
'label' => $this->translate('Graphite Web user'),
'description' => $this->translate('A user with access to your Graphite Web'),
'validators' => [new HttpUserValidator()]
]
],
[
'password',
'graphite_web_password',
[
'label' => $this->translate('Graphite Web password'),
'description' => $this->translate('The above user\'s password')
]
]
]);
}
}

View file

@ -95,27 +95,20 @@ class GraphiteChart
public function getUrl()
{
$urlPattern = '/^' . preg_quote(Url::fromPath('/'), '/') . '/';
$url = Url::fromPath('/render', $this->getParams());
$this->template->extendUrl($url, $this->metric, $this->vars);
$url->getParams()->add('_ext', 'whatever.svg');
$url = preg_replace($urlPattern, $this->web->getBaseUrl() . '/', $url);
return $url;
}
public function fetchImage()
{
$options = array(
'http'=>array(
'method'=>"POST",
'header'=>
"Accept-language: en\r\n".
"Content-type: application/x-www-form-urlencoded\r\n",
// 'content'=> $data
)
$url = $this->getUrl();
return $this->web->getClient()->request(
$url->getPath(),
$url->getParams(),
'POST',
['Accept-language' => 'en', 'Content-type' => 'application/x-www-form-urlencoded']
);
$context = stream_context_create($options);
return file_get_contents($this->getUrl(), false, $context);
}
}

View file

@ -13,20 +13,20 @@ use Icinga\Module\Graphite\GraphiteQuery;
class GraphiteWeb
{
/**
* Graphite webapp base url
* HTTP interface to Graphite Web
*
* @var string
* @var GraphiteWebClientInterface
*/
protected $baseUrl;
private $client;
/**
* Construct a new graphite webapp instance
*
* @param $baseUrl string Graphite webapp base url
* @param GraphiteWebClientInterface $client HTTP interface to Graphite Web
*/
public function __construct($baseUrl)
public function __construct(GraphiteWebClientInterface $client)
{
$this->baseUrl = $baseUrl;
$this->client = $client;
}
/**
@ -40,13 +40,13 @@ class GraphiteWeb
}
/**
* Retrieve out base url
* Get {@link client}
*
* @return string
* @return GraphiteWebClientInterface
*/
public function getBaseUrl()
public function getClient()
{
return $this->baseUrl;
return $this->client;
}
/**
@ -56,11 +56,7 @@ class GraphiteWeb
*/
public function listMetrics($filter)
{
$res = json_decode(
file_get_contents(
$this->baseUrl . '/metrics/expand?query=' . $filter
)
);
$res = json_decode($this->client->request('metrics/expand', ['query' => $filter]));
natsort($res->results);
return array_values($res->results);
}

View file

@ -0,0 +1,67 @@
<?php
namespace Icinga\Module\Graphite;
use Icinga\Application\Config;
use Icinga\Exception\ConfigurationError;
use Icinga\Module\Graphite\Pattern\Singleton;
use Icinga\Web\Url;
use Icinga\Web\UrlParams;
class GraphiteWebClient implements GraphiteWebClientInterface
{
use Singleton;
/**
* Base URL as configured
*
* @var string
*/
private $baseUrl;
/**
* Default headers
*
* @var string[string]
*/
private $baseHeaders;
public function request($url, $params = null, $method = 'GET', array $headers = [], $body = null)
{
$httpOptions = ['method' => $method, 'header' => ''];
foreach (array_merge($this->baseHeaders, $headers) as $header => $headerValue) {
$httpOptions['header'] .= "$header: $headerValue\r\n";
}
if ($body !== null) {
$httpOptions['content'] = $body;
}
$url = Url::fromPath($this->baseUrl . ltrim($url, '/'));
if ($params !== null) {
$url->setParams($params);
}
// TODO(ak): use our CurlClient (one nice day)
return file_get_contents($url->getAbsoluteUrl(), false, stream_context_create(['http' => $httpOptions]));
}
private function init()
{
$config = Config::module('graphite');
$graphite = $config->getSection('graphite');
$baseUrl = $graphite->web_url;
if ($baseUrl === null) {
throw new ConfigurationError('Missing graphite.web_url in "%s"', $config->getConfigFile());
}
$this->baseUrl = rtrim($baseUrl, '/') . '/';
$this->baseHeaders = ['User-Agent' => 'icingaweb2-module-graphite'];
if (isset($graphite->web_user) && isset($graphite->web_password)) {
$this->baseHeaders['Authorization'] = 'Basic ' . base64_encode(
"{$graphite->web_user}:{$graphite->web_password}"
);
}
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Icinga\Module\Graphite;
use Icinga\Application\Config;
use Icinga\Exception\ConfigurationError;
use Icinga\Module\Graphite\Pattern\Singleton;
use Icinga\Web\Url;
use Icinga\Web\UrlParams;
/**
* HTTP interface to Graphite Web
*/
interface GraphiteWebClientInterface
{
/**
* Send an HTTP request to the configured Graphite Web and return the response's body
*
* @param string $url
* @param UrlParams|string[string] $params
* @param string $method
* @param string[string] $headers
* @param string $body
*
* @return string
*/
public function request($url, $params = null, $method = 'GET', array $headers = [], $body = null);
}

View file

@ -0,0 +1,57 @@
<?php
namespace Icinga\Module\Graphite\Pattern;
use Exception;
/**
* Everything needed for a singleton class
*/
trait Singleton
{
/**
* The only instance of a class
*
* @var static
*/
private static $instance;
/**
* Initialize and return {@link instance}
*
* @return static
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new static();
}
return self::$instance;
}
/**
* Constructor
*/
final private function __construct()
{
$this->init();
}
/**
* Ensure that no one can clone the object
*/
final private function __clone()
{
throw new Exception('Won\'t clone a singleton');
}
/**
* Initializer
*
* May be overridden.
*/
private function init()
{
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Icinga\Module\Graphite\Web\Form\Validator;
use Zend_Validate_Abstract;
/**
* Validates http basic authn user names
*
* TODO(ak): move to Icinga Web 2
*/
class HttpUserValidator extends Zend_Validate_Abstract
{
/**
* Constructor
*/
public function __construct()
{
$this->_messageTemplates = ['HAS_COLON' => mt('graphite', 'The username must not contain colons.')];
}
public function isValid($value)
{
$hasColon = false !== strpos($value, ':');
if ($hasColon) {
$this->_error('HAS_COLON');
}
return ! $hasColon;
}
}