Skip to content

Commit

Permalink
Merge pull request bfraser#86 from simp/SIMP-4206
Browse files Browse the repository at this point in the history
(SIMP-4206) Added organization provider and updated datasource provider
  • Loading branch information
bastelfreak authored Feb 2, 2018
2 parents 157109a + 05b6e35 commit 487ec99
Show file tree
Hide file tree
Showing 7 changed files with 491 additions and 7 deletions.
117 changes: 115 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,25 @@ Example:

The module includes several custom types:

##### `grafana_dashboard`
#### `grafana_organization`

In order to use the organization resource, add the following to your manifest:

```puppet
grafana_organization { 'example_org':
grafana_url => 'http://localhost:3000',
grafana_user => 'admin',
grafana_password => '5ecretPassw0rd',
}
```

`grafana_url`, `grafana_user`, and `grafana_password` are required to create organizations via the API.

`name` is optional if the name will differ from example_org above.

`address` is an optional parameter that requires a hash. Address settings are `{"address1":"","address2":"","city":"","zipCode":"","state":"","country":""}`

#### `grafana_dashboard`

In order to use the dashboard resource, add the following to your manifest:

Expand Down Expand Up @@ -386,6 +404,7 @@ grafana_datasource { 'influxdb':
grafana_user => 'admin',
grafana_password => '5ecretPassw0rd',
type => 'influxdb',
org_name => 'NewOrg',
url => 'http://localhost:8086',
user => 'admin',
password => '1nFlux5ecret',
Expand All @@ -396,11 +415,15 @@ grafana_datasource { 'influxdb':
}
```

Available default types are: influxdb, elasticsearch, graphite, kairosdb, opentsdb, prometheus
Available types are: influxdb, elasticsearch, graphite, cloudwatch, mysql, opentsdb, and prometheus

`org_name` is used to set which organization a datasource will be created on. If this parameter is not set, it will default to organization ID 1 (Main Org. by default). If the default org is deleted, organizations will need to be specified.

Access mode determines how Grafana connects to the datasource, either `direct`
from the browser, or `proxy` to send requests via grafana.

Setting `basic_auth` to `true` will allow use of the `basic_auth_user` and `basic_auth_password` params.

Authentication is optional, as is `database`; additional `json_data` can be
provided to allow custom configuration options.

Expand Down Expand Up @@ -430,6 +453,96 @@ http_conn_validator { 'grafana-conn-validator' :
}
```

Note that the `database` is dynamic, setting things other than "database" for separate types. Ex: for Elasticsearch it will set the Index Name.

**`jsonData` Settings**

Note that there are separate options for json_data based on the type of datasource you create.

##### **Elasticsearch**

`esVersion` - Required, either 2 or 5, set as a bare number.

`timeField` - Required. By default this is @timestamp, but without setting it in jsonData, the datasource won't work without refreshing it in the GUI.

`timeInterval` - Optional. A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example "1m" if your data is written every minute.

Example:
```puppet
json_data => {"esVersion":5,"timeField":"@timestamp","timeInterval":"1m"}
```

##### **CloudWatch**

`authType` - Required. Options are `Access & Secret Key`, `Credentials File`, or `ARN`.

-"keys" = Access & Secret Key

-"credentials" = Credentials File

-"arn" = ARN

*When setting authType to `credentials`, the `database` param will set the Credentials Profile Name.*

*When setting authType to `arn`, another jsonData value of `assumeRoleARN` is available, which is not required for other authType settings*

`customMetricsNamespaces` - Optional. Namespaces of Custom Metrics, separated by commas within double quotes.

`defaultRegion` - Required. Options are "ap-northeast-(1 or 2)", "ap-southeast-(1 or 2)", "ap-south-1", "ca-central-1", "cn-north-1", "eu-central-1", "eu-west-(1 or 2)", "sa-east-(1 or 2)", "us-east-(1 or 2)", "us-gov-west-1", "us-west-(1 or 2)".

`timeField`

Example:
```puppet
{"authType":"arn","assumeRoleARN":"arn:aws:iam:*","customMetricsNamespaces":"Namespace1,Namespace2","defaultRegion":"us-east-1","timeField":"@timestamp"}
```
##### **Graphite**

`graphiteVersion` - Required. Available versions are `0.9` or `1.0`.

`tlsAuth` - Set to `true` or `false`

`tlsAuthWithCACert` - Set to `true` or `false`

Example:
```puppet
{"graphiteVersion":"0.9","tlsAuth":true,"tlsAuthWithCACert":false}
```

##### **OpenTSDB**

`tsdbResolution` - Required. Options are `1` or `2`.

`1` = second

`2` = millisecond

`tsdbVersion` - Required. Options are `1`, `2`, or `3`.

`1` &nbsp;&nbsp; = &nbsp;&nbsp; <=2.1

`2` &nbsp;&nbsp; = &nbsp;&nbsp; ==2.2

`3` &nbsp;&nbsp; = &nbsp;&nbsp; ==2.3

Example:
```puppet
{"tsdbResolution:1,"tsdbVersion":3}
```

##### **InfluxDB**

N/A

##### **MySQL**

N/A

##### **Prometheus**

N/A

##### `grafana_plugin`

An example is provided for convenience; for more details, please view the
Expand Down
99 changes: 97 additions & 2 deletions lib/puppet/provider/grafana_datasource/grafana.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,44 @@

defaultfor kernel: 'Linux'

def organization
resource[:organization]
end

def fetch_organizations
response = send_request('GET', '/api/orgs')
if response.code != '200'
raise format('Fail to retrieve organizations (HTTP response: %s/%s)', response.code, response.body)
end

begin
fetch_organizations = JSON.parse(response.body)

fetch_organizations.map { |x| x['id'] }.map do |id|
response = send_request 'GET', format('/api/orgs/%s', id)
if response.code != '200'
raise format('Failed to retrieve organization %d (HTTP response: %s/%s)', id, response.code, response.body)
end

fetch_organization = JSON.parse(response.body)

{
id: fetch_organization['id'],
name: fetch_organization['name']
}
end
rescue JSON::ParserError
raise format('Failed to parse response: %s', response.body)
end
end

def fetch_organization
unless @fetch_organization
@fetch_organization = fetch_organizations.find { |x| x[:name] == resource[:organization] }
end
@organization
end

def datasources
response = send_request('GET', '/api/datasources')
if response.code != '200'
Expand All @@ -21,7 +59,7 @@ def datasources
datasources.map { |x| x['id'] }.map do |id|
response = send_request 'GET', format('/api/datasources/%s', id)
if response.code != '200'
raise format('Fail to retrieve datasource %d (HTTP response: %s/%s)', id, response.code, response.body)
raise format('Failed to retrieve datasource %d (HTTP response: %s/%s)', id, response.code, response.body)
end

datasource = JSON.parse(response.body)
Expand All @@ -36,11 +74,15 @@ def datasources
database: datasource['database'],
access_mode: datasource['access'],
is_default: datasource['isDefault'] ? :true : :false,
with_credentials: datasource['withCredentials'] ? :true : :false,
basic_auth: datasource['basicAuth'] ? :true : :false,
basic_auth_user: datasource['basicAuthUser'],
basic_auth_password: datasource['basicAuthPassword'],
json_data: datasource['jsonData']
}
end
rescue JSON::ParserError
raise format('Fail to parse response: %s', response.body)
raise format('Failed to parse response: %s', response.body)
end
end

Expand Down Expand Up @@ -118,6 +160,42 @@ def is_default=(value)
end
# rubocop:enable Style/PredicateName

def basic_auth
datasource[:basic_auth]
end

def basic_auth=(value)
resource[:basic_auth] = value
save_datasource
end

def basic_auth_user
datasource[:basic_auth_user]
end

def basic_auth_user=(value)
resource[:basic_auth_user] = value
save_datasource
end

def basic_auth_password
datasource[:basic_auth_password]
end

def basic_auth_password=(value)
resource[:basic_auth_password] = value
save_datasource
end

def with_credentials
datasource[:is_default]
end

def with_credentials=(value)
resource[:with_credentials] = value
save_datasource
end

def json_data
datasource[:json_data]
end
Expand All @@ -128,6 +206,19 @@ def json_data=(value)
end

def save_datasource
if fetch_organization.nil?
response = send_request('POST', '/api/user/using/1')
if response.code != '200'
raise format('Failed to switch to org 1 (HTTP response: %s/%s)', response.code, response.body)
end
else
organization_id = fetch_organization[:id]
response = send_request 'POST', format('/api/user/using/%s', organization_id)
if response.code != '200'
raise format('Failed to switch to org %s (HTTP response: %s/%s)', organization_id, response.code, response.body)
end
end

data = {
name: resource[:name],
type: resource[:type],
Expand All @@ -137,6 +228,10 @@ def save_datasource
user: resource[:user],
password: resource[:password],
isDefault: (resource[:is_default] == :true),
basicAuth: (resource[:basic_auth] == :true),
basicAuthUser: resource[:basic_auth_user],
basicAuthPassword: resource[:basic_auth_password],
withCredentials: (resource[:with_credentials] == :true),
jsonData: resource[:json_data]
}

Expand Down
102 changes: 102 additions & 0 deletions lib/puppet/provider/grafana_organization/grafana.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
require 'json'

require File.expand_path(File.join(File.dirname(__FILE__), '..', 'grafana'))

Puppet::Type.type(:grafana_organization).provide(:grafana, parent: Puppet::Provider::Grafana) do
desc 'Support for Grafana organizations'

defaultfor kernel: 'Linux'

def organizations
response = send_request('GET', '/api/orgs')
if response.code != '200'
raise format('Failed to retrieve organizations (HTTP response: %s/%s)', response.code, response.body)
end

begin
organizations = JSON.parse(response.body)

organizations.map { |x| x['id'] }.map do |id|
response = send_request 'GET', format('/api/orgs/%s', id)
if response.code != '200'
raise format('Failed to retrieve organization %d (HTTP response: %s/%s)', id, response.code, response.body)
end

organization = JSON.parse(response.body)

{
id: organization['id'],
name: organization['name'],
address: organization['address']
}
end
rescue JSON::ParserError
raise format('Failed to parse response: %s', response.body)
end
end

def organization
unless @organization
@organization = organizations.find { |x| x[:name] == resource[:name] }
end
@organization
end

attr_writer :organization

# rubocop:enable Style/PredicateName

def id
organization[:id]
end

def id=(value)
resource[:id] = value
save_organization
end

def address
organization[:json_data]
end

def address=(value)
resource[:address] = value
save_organization
end

def save_organization
data = {
id: resource[:id],
name: resource[:name],
address: resource[:address]
}

response = send_request('POST', '/api/orgs', data) if organization.nil?

if response.code != '200'
raise format('Failed to create save %s (HTTP response: %s/%s)', resource[:name], response.code, response.body)
end
self.organization = nil
end

def delete_organization
response = send_request 'DELETE', format('/api/orgs/%s', organization[:id])

if response.code != '200'
raise format('Failed to delete organization %s (HTTP response: %s/%s)', resource[:name], response.code, response.body)
end
self.organization = nil
end

def create
save_organization
end

def destroy
delete_organization
end

def exists?
organization
end
end
Loading

0 comments on commit 487ec99

Please sign in to comment.