A Duct library that provides an Integrant key for managing dashboards and associated users and organizations in Grafana.
To use this library add the following key to your configuration:
:dev.gethop.dashboard-manager/grafana
This key expects a configuration map with two mandatory keys These are the mandatory keys:
:uri
: The URI where Grafana's server is listening.:credentials
: A vector with two elements, a username and password, that are used for basic HTTP authentication.These are the optional keys:
:auth-method
: :basic-auth
or :regular-login
; defaults to :basic-auth
:timeout
: Timeout value (in milli-seconds) for an connection attempt with Grafana.:max-retries
: If the connection attempt fails, how many retries we want to attempt before giving up.:backoff-ms
: This is a vector in the form [initial-delay-ms max-delay-ms multiplier] to control the delay between each retry. The delay for nth retry will be (max (* initial-delay-ms n multiplier) max-delay-ms). If multiplier is not specified (or if it is nil), a multiplier of 2 is used. All times are in milli-seconds.Key initialization returns a Grafana
record that can be used to perform the Grafana operations described below.
Basic configuration:
:dev.gethop.dashboard-manager/grafana
{:uri #duct/env ["GRAFANA_URI" Str :or "http://localhost:3000"]
:credentials [#duct/env ["GRAFANA_USERNAME" Str :or "admin"]
#duct/env ["GRAFANA_TEST_PASSWORD" Str :or "admin"]]}
Configuration with custom request retry policy:
:dev.gethop.dashboard-manager/grafana
{:uri #duct/env ["GRAFANA_URI" Str :or "http://localhost:3000"]
:credentials [#duct/env ["GRAFANA_USERNAME" Str :or "admin"]
#duct/env ["GRAFANA_TEST_PASSWORD" Str :or "admin"]]
:timeout 300
:max-retries 5
:backoff-ms [10 500]}
Grafana
recordIf you are using the library as part of a Duct-based project, adding any of the previous configurations to your config.edn
file will perform all the steps necessary to initialize the key and return a Grafana
record for the associated configuration. In order to show a few interactive usages of the library, we will do all the steps manually in the REPL.
First we require the relevant namespaces:
user> (require '[dev.gethop.dashboard-manager.core :as core]
'[integrant.core :as ig])
nil
user>
Next we create the configuration var holding the Grafana integration configuration details:
user> (def config {:uri "http://localhost:3000"
:credentials ["admin" "admin"]})
#'user/config
user>
Now that we have all pieces in place, we can initialize the :dev.gethop.dashboard-manager/grafana
Integrant key to get a Grafana
record. As we are doing all this from the REPL, we have to manually require dev.gethop.dashboard-manager.grafana
namespace, where the init-key
multimethod for that key is defined (this is not needed when Duct takes care of initializing the key as part of the application start up):
user> (require '[dev.gethop.dashboard-manager.grafana :as grafana])
nil
user>
And we finally initialize the key with the configuration defined above, to get our Grafana
record:
user> (def gf-record (->
config
(->> (ig/init-key :dev.gethop.dashboard-manager/grafana))))
#'user/gf-record
user> gf-record
#dev.gethop.dashboard_manager.grafana.Grafana{:uri "http://localhost:4000",
:credentials ["admin"
"admin"],
:timeout 200,
:max-retries 10,
:backoff-ms [500 1000 2.0]}
user>
user> (require '[dev.gethop.dashboard-manager.grafana :as grafana])
(grafana/connect "http://localhost:4000", ["admin" "adamin"])
#dev.gethop.dashboard_manager.grafana.Grafana{:uri "http://localhost:4000",
:credentials ["admin"
"admin"],
:timeout 200,
:max-retries 10,
:backoff-ms [500 1000 2.0]}
Now that we have our Grafana
record, we are ready to use the methods defined by the protocols defined in dev.gethop.dashboard-manager.core
namespace.
create-org
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
, :already-exists
:id
: ID assigned to the created organizationuser> (core/create-org gf-record "foo")
{:status :ok :id 2}
get-orgs
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
:orgs
: A vector of maps. Each map representing an existing organization.user> (core/get-orgs gf-record)
{:status :ok :orgs [{:id 1 :name "Main Org"}
{:id 2 :name "foo"}]}
update-org
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
, :already-exists
user> (core/update-org gf-record 2 "foo-bar")
{:status :ok}
delete-org
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
user> (core/delete-org gf-record 2)
{:status :ok}
add-org-user
Grafana
record:status
: :ok
,:access-denied
, :not-found
, :error
,:role-not-found
, :user-not-found
, :already-exists
user> (core/add-org-user gf-record 1 "foo-bar" "Editor")
{:status :ok}
update-org-user
Grafana
record:status
: :ok
,:access-denied
, :error
,:invalid-data
user> (core/update-org-user gf-record 1 1 {:role "Editor"})
{:status :ok}
get-org-users
Grafana
record:status
: :ok
,:access-denied
, :not-found
, :error
, :not-found
:users
: A vector of maps. Each map representing an existing user.user> (core/get-org-users gf-record 1)
{:status :ok :users [{:orgId 1, :userId 1, :email "admin@localhost", :avatarUrl "/avatar/46d229b033af06a191ff2267bca9ae56", :login "admin", :role "Admin", :lastSeenAt "2019-05-27T14:21:51Z", :lastSeenAtAge "< 1m"}
{:orgId 1, :userId 2, :email "foo-bar@email.com", :avatarUrl "/avatar/46d234t033af06a191ff2267bca9ae56", :login "foo-bar", :role "Editor", :lastSeenAt "2019-05-27T14:21:51Z", :lastSeenAtAge "< 1m"}]}
delete-org-user
Grafana
record:status
: :ok
,:access-denied
, :error
user> (core/delete-org-user gf-record 1 2)
{:status :ok}
create-user
Grafana
record:name
(OPTIONAL):email
(REQUIRED if login is not specified):login
(REQUIRED if email is not specified):password
(REQUIRED):status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
, :already-exists
, :invalid-data
:id
: The created user's ID.user> (core/create-user gf-record {:login "login" :password "password"})
{:status :ok :id 3}
update-user
Grafana
record:name
:email
(REQUIRED if login is not specified):login
(REQUIRED if email is not specified):status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
, :already-exists
, :missing-mandatory-data
:id
: The created user's ID.user> (core/update-user gf-record 3 {:name "fooo" :login "login"})
{:status :ok}
get-user
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
:user
: A map with the user information.user> (core/get-user gf-record "login")
{:status :ok :user {:id 3, :email "fooo@email.com", :name "fooo", :login "login", :theme "", :orgId 1, :isGrafanaAdmin false}}
get-user-orgs
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
, not-found
:orgs
: A vector of maps. Each map representing an organization.user> (core/get-user-orgs gf-record 1)
{:status :ok :orgs [{:orgId 1, :name "Main Org.", :role "Admin"}]}
get-current-user-orgs
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
, not-found
:orgs
: A vector of maps. Each map representing an organization.user> (core/get-user-orgs gf-record 1)
{:status :ok :orgs [{:orgId 1, :name "Main Org.", :role "Admin"}]}
delete-user
Grafana
record:status
: :ok
, :access-denied
, :unknown-host
, :connection-refused
, :error
user> (core/delete-user gf-record 2)
{:status :ok}
get-dashboard
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
:meta
: A map with the dashboard metadata:dashboard
: A map with the dashboard definitionuser> (core/get-dashboard gf-record 1 "eD_Es2vMk")
{:status :ok,
:meta
{:provisionedExternalId "",
:slug "bat",
:canStar true,
:createdBy "admin",
:updated "2020-09-04T07:37:43Z",
:provisioned false,
:type "db",
:created "2020-09-04T07:14:17Z",
:folderUrl "/dashboards/f/kXFTY2vGk/default",
:canSave true,
:expires "0001-01-01T00:00:00Z",
:updatedBy "admin",
:canEdit true,
:url "/d/eD_Es2vMk/bat",
:folderId 1,
:folderTitle "default",
:version 4,
:canAdmin true,
:isFolder false,
:hasAcl false},
:dashboard
{:templating {:list []},
:timepicker
{:refresh_intervals
["5s" "10s" "30s" "1m" "5m" "15m" "30m" "1h" "2h" "1d"]},
:tags ["default"],
:timezone "",
:editable false,
:graphTooltip 0,
:uid "eD_Es2vMk",
:time {:from "now-6h", :to "now"},
:gnetId nil,
:variables {:list []},
:title "bat",
:style "dark",
:id 6,
:annotations
{:list
[{:$$hashKey "object:85",
:builtIn 1,
:datasource "-- Grafana --",
:enable true,
:hide true,
:iconColor "rgba(0, 211, 255, 1)",
:name "Annotations & Alerts",
:type "dashboard"}]},
:version 4,
:panels
[{:datasource nil,
:gridPos {:h 9, :w 12, :x 0, :y 0},
:pluginVersion "6.7.3",
:timeShift nil,
:type "gauge",
:title "bat",
:id 2,
:timeFrom nil,
:options
{:fieldOptions
{:calcs ["mean"],
:defaults
{:mappings [],
:thresholds
{:mode "absolute",
:steps
[{:color "green", :value nil} {:color "red", :value 80}]}},
:overrides [],
:values false},
:orientation "auto",
:showThresholdLabels false,
:showThresholdMarkers true}}],
:links [],
:schemaVersion 22}}
update-or-create-dashboard
Grafana
recordget-dashboard
),
overwrite,
messageand
refresh`. See Grafana's documentation for more details.:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
:id
: dashboard id:uid
: dashboard uid:version
: dashboard versionuser> (def dashboard (-> (core/get-dashboard gf-record 1 "eD_Es2vMk") :dashboard (dissoc :id :uid)))
#'dev.gethop.dashboard.grafana/dashboard
user> (core/update-or-create-dashboard gf-record 2 dashboard {:overwrite false})
{:status :ok :id 4 :uid "UQ48PhDGk" :version 1}
delete-dashboard
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
user> (core/delete-dashboard gf-record 1 "eD_Es2vMk")
{:status :ok}
get-org-dashboards
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
:dashboards
: A list of maps. Each map representing a dashboard and it's panels.user> (core/get-org-dashboards gf-record 1)
{:status :ok, :dashboards ({:uid "yYtEB6WZz", :title "Example Dashboard", :url "/d/yYtEB6WZz/example-dashboard", :panels ({:id 2, :title "Panel Title\
", :ds-url "/d/yYtEB6WZz/example-dashboard"} {:id 4, :title "Panel Title", :ds-url "/d/yYtEB6WZz/example-dashboard"})})}
get-org-panels
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
:panels
: A list of maps. Each map representing a panel.user> (core/get-org-panels gf-record 1)
{:status :ok, :panels ({:id 2, :title "Panel Title", :ds-url "/d/yYtEB6WZz/example-dashboard", :ds-id "yYtEB6WZz"} {:id 4, :title "Panel Title", :ds-url "/d/yYtEB6WZz/ex\
ample-dashboard", :ds-id "yYtEB6WZz"})}
get-ds-panels
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
:panels
: A list of maps. Each map representing a panel.user> (core/get-org-panels gf-record 1)
{:status :ok, :panels ({:id 2, :title "Panel Title", :ds-url "/d/yYtEB6WZz/example-dashboard", :ds-id "yYtEB6WZz"} {:id 4, :title "Panel Title", :ds-url "/d/yYtEB6WZz/ex\
ample-dashboard", :ds-id "yYtEB6WZz"})}
get-dashboards-with-tag
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
:dashboards
: A list of maps. Each map representing a dashboard with the title, uid, and url keys.user>(core/get-dashboards-with-tag gf-record 1 "test")
{:status :ok
:dashboards (core/get-dashboards-with-tag gf-record 1 "default")}
create-datasource
Grafana
record:name
,:type
, and :access
key are mandatory, see the example, and Grafana's API documentation for more information.:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
, :already-exists
:id
: ID assigned to the created datasourceuser> (core/create-datasource gf-record 1 {:name "Name"
:type "postgres"
:url "postgres:5432"
:access "proxy"
:database "hydrogen"
:user "postgres"
:secureJsonData {:password "pass"}
:isDefault true
:jsonData {:postgresVersion 906 :sslmode "disable"}})
{:status :ok :id 2}
get-datasource
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
:datasource
: A map with the datasource data.user> (core/get-datasource gf-record 1 2)
{:status :ok :datasource {:isDefault false,
:orgId 1,
:password "pass",
:name "b9db0015-e45f-4a81-80ef-c3b5225cbe39",
:secureJsonFields {},
:type "postgres",
:basicAuthUser "",
:typeLogoUrl "",
:readOnly false,
:basicAuthPassword "",
:id 2,
:basicAuth false,
:url "postgres:5432",
:database "hydrogen",
:access "proxy",
:jsonData {:postgresVersion 906, :sslmode "disable"},
:version 1,
:user "postgres",
:withCredentials false}}
get-datasources
Grafana
record:status
: :ok
, :access-denied
, :not-found
, :unknown-host
, :connection-refused
, :error
:datasources
: A vector of maps. Each map representing an existing datasource.user> (core/get-datasources gf-record 1)
{:status :ok :datasources [{:isDefault false,
:orgId 1,
:password "pass",
:name "00f70239-be39-4199-939c-d90fa625b41f",
:type "postgres",
:typeLogoUrl
"public/app/plugins/datasource/postgres/img/postgresql_logo.svg",
:readOnly false,
:id 32,
:basicAuth false,
:url "postgres:5432",
:database "hydrogen",
:access "proxy",
:jsonData {:postgresVersion 906, :sslmode "disable"},
:user "postgres"}
{:name "datasource2"
...}]
update-datasource
Grafana
record:name
,:type
, and :access
key are mandatory, see the example, and Grafana's API documentation for more information.:status
: :ok
, :access-denied
, :unknown-host
, :connection-refused
, :error
user> (core/update-datasource gf-record 1 2 {:name "new-name"})
{:status :ok
:datasource {:name "new-name" ...}}
delete-datasource
Grafana
record:status
: :ok
, :access-denied
, not-found
, :unknown-host
, :connection-refused
, :error
user> (core/delete-datasource gf-record 1 2)
{:status :ok}
./run-tests.sh
Copyright (c) 2022 HOP Technologies
The source code for the library is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
Can you improve this documentation? These fine people already did:
lucassousaf, spietras, Bingen Galartza, Bingen Galartza Iparragirre, Anton Mostovoy, Iñaki Arenaza, Lucas Sousa & joseAyudarte91Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close