├── test
├── __init__.py
├── api
│ ├── __init__.py
│ ├── test_search.py
│ ├── test_snapshot.py
│ ├── test_annotations.py
│ ├── test_dashboard.py
│ ├── test_team.py
│ ├── test_organization.py
│ ├── test_admin.py
│ └── test_folder.py
└── test_grafana.py
├── _config.yml
├── setup.cfg
├── grafana_api
├── __init__.py
├── api
│ ├── base.py
│ ├── __init__.py
│ ├── search.py
│ ├── admin.py
│ ├── dashboard.py
│ ├── folder.py
│ ├── snapshots.py
│ ├── datasource.py
│ ├── team.py
│ ├── annotations.py
│ ├── user.py
│ └── organization.py
├── grafana_face.py
└── grafana_api.py
├── .github
├── ISSUE_TEMPLATE
│ ├── missing-grafana-api-endpoint.md
│ ├── incorrect-response.md
│ └── bug_report.md
└── main.workflow
├── Pipfile
├── PULL_REQUEST_TEMPLATE.md
├── meta.yaml
├── LICENSE
├── .gitignore
├── setup.py
├── docs
└── index.html
├── README.md
├── .circleci
└── config.yml
└── CHANGELOG.md
/test/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/api/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal=1
3 |
--------------------------------------------------------------------------------
/grafana_api/__init__.py:
--------------------------------------------------------------------------------
1 | from .grafana_face import GrafanaFace
2 |
--------------------------------------------------------------------------------
/grafana_api/api/base.py:
--------------------------------------------------------------------------------
1 | class Base(object):
2 | def __init__(self, api):
3 | self.api = api
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/missing-grafana-api-endpoint.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Missing Grafana API endpoint
3 | about: Report missing endpoint
4 |
5 | ---
6 |
7 | **What Grafana API endpoint the library is missing?**
8 | Describe and link the Grafana API endpoint which are currently missing.
9 |
--------------------------------------------------------------------------------
/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | url = "https://pypi.python.org/simple"
3 | verify_ssl = true
4 | name = "grafana_api"
5 |
6 | [packages]
7 |
8 | [dev-packages]
9 | codecov = "~=2.0"
10 | coverage = "~=4.5"
11 | mock = {version = "*", markers = "python_version <= '2.7'"}
12 | pylint = ">=1.9"
13 | requests-mock = "~=1.6"
14 | unittest-xml-reporting = "~=2.5"
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/incorrect-response.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Incorrect response
3 | about: Create a report on incorrect API call to help us improve
4 |
5 | ---
6 |
7 | **What API call you use?**
8 | Provide a small example to reproduce the bug.
9 |
10 | **What is the actual output?**
11 | Copy or describe the actual result.
12 |
13 | **What is the expected output?**
14 | And what output is expected.
15 |
--------------------------------------------------------------------------------
/grafana_api/api/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 | from .admin import Admin
3 | from .dashboard import Dashboard
4 | from .datasource import Datasource
5 | from .folder import Folder
6 | from .organization import Organization, Organizations
7 | from .search import Search
8 | from .user import User, Users
9 | from .team import Teams
10 | from .annotations import Annotations
11 | from .snapshots import Snapshots
12 |
13 |
--------------------------------------------------------------------------------
/.github/main.workflow:
--------------------------------------------------------------------------------
1 | workflow "Publish" {
2 | resolves = ["publish-to-conda", "publish-to-pypi"]
3 | on = "release"
4 | }
5 |
6 | action "publish-to-conda" {
7 | uses = "m0nhawk/conda-package-publish-action@master"
8 | secrets = ["ANACONDA_USERNAME", "ANACONDA_PASSWORD"]
9 | }
10 |
11 | action "publish-to-pypi" {
12 | uses = "mariamrf/py-package-publish-action@master"
13 | secrets = ["TWINE_PASSWORD", "TWINE_USERNAME"]
14 | env = {
15 | BRANCH = "master"
16 | PYTHON_VERSION = "3.7.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Pull Request Template
2 |
3 | ## Description
4 |
5 | Please include a summary of the change and which issue is fixed.
6 |
7 | Fixes #(issue)
8 |
9 | ## Have you written tests?
10 |
11 | - [ ] Yes
12 | - [ ] No
13 |
14 | ## Checklist:
15 |
16 | - [ ] My code follows the style guidelines of this project
17 | - [ ] I have performed a self-review of my own code
18 | - [ ] I have commented my code, particularly in hard-to-understand areas
19 | - [ ] I have made corresponding changes to the documentation
20 | - [ ] My changes generate no new warnings
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Describe the bug**
8 | A clear and concise description of what the bug is.
9 |
10 | **To Reproduce**
11 | Steps to reproduce the behavior:
12 | 1. Create '...'
13 | 2. Run call '...'
14 | 3. See error
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Versions**
20 | - Grafana: [ ]
21 | - `grafana_api`: [ ]
22 | - Authentication: [Basic] or [Token]
23 |
24 | **Additional context**
25 | Add any other context about the problem here.
26 |
--------------------------------------------------------------------------------
/meta.yaml:
--------------------------------------------------------------------------------
1 | {% set data = load_setup_py_data(setup_file='setup.py', from_recipe_dir=True) %}
2 | {% set name = data['name'] %}
3 | {% set version = data['version'] %}
4 | {% set url = data['url'] %}
5 | {% set license = data['license'] %}
6 |
7 | package:
8 | name: "{{ name|lower }}"
9 | version: "{{ version }}"
10 |
11 | source:
12 | path: .
13 |
14 | build:
15 | noarch: python
16 | number: 0
17 | script: "{{ PYTHON }} -m pip install . --no-deps --ignore-installed -vv "
18 |
19 | requirements:
20 | host:
21 | - python
22 | - requests
23 | - pyyaml
24 | run:
25 | - python
26 | - requests
27 | - pyyaml
28 |
29 | test:
30 | imports:
31 | - grafana_api
32 | - grafana_api.api
33 |
34 | about:
35 | home: "{{ url }}"
36 | license: "{{ license }}"
37 | license_family: "{{ license }}"
38 | license_file: LICENSE
39 | summary: Yet another Python library for Grafana API
40 | doc_url: https://m0nhawk.github.io/grafana_api/
41 | dev_url:
42 |
43 | extra:
44 | recipe-maintainers:
45 | - m0nhawk
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Andrew Prokhorenkov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/grafana_api/grafana_face.py:
--------------------------------------------------------------------------------
1 | from .grafana_api import GrafanaAPI
2 | from .api import (
3 | Admin,
4 | Dashboard,
5 | Datasource,
6 | Folder,
7 | Organization,
8 | Organizations,
9 | Search,
10 | User,
11 | Users,
12 | Teams,
13 | Snapshots,
14 | Annotations,
15 | )
16 |
17 |
18 | class GrafanaFace:
19 | def __init__(
20 | self,
21 | auth,
22 | host="localhost",
23 | port=None,
24 | url_path_prefix="",
25 | protocol="http",
26 | verify=True,
27 | ):
28 | self.api = GrafanaAPI(
29 | auth,
30 | host=host,
31 | port=port,
32 | url_path_prefix=url_path_prefix,
33 | protocol=protocol,
34 | verify=verify,
35 | )
36 | self.admin = Admin(self.api)
37 | self.dashboard = Dashboard(self.api)
38 | self.datasource = Datasource(self.api)
39 | self.folder = Folder(self.api)
40 | self.organization = Organization(self.api)
41 | self.organizations = Organizations(self.api)
42 | self.search = Search(self.api)
43 | self.user = User(self.api)
44 | self.users = Users(self.api)
45 | self.teams = Teams(self.api)
46 | self.annotations = Annotations(self.api)
47 | self.snapshots = Snapshots(self.api)
48 |
--------------------------------------------------------------------------------
/grafana_api/api/search.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 |
3 |
4 | class Search(Base):
5 | def __init__(self, api):
6 | super(Search, self).__init__(api)
7 | self.api = api
8 |
9 | def search_dashboards(
10 | self,
11 | query=None,
12 | tag=None,
13 | type_=None,
14 | dashboard_ids=None,
15 | folder_ids=None,
16 | starred=None,
17 | limit=None,
18 | ):
19 | """
20 |
21 | :param query:
22 | :param tag:
23 | :param type_:
24 | :param dashboard_ids:
25 | :param folder_ids:
26 | :param starred:
27 | :param limit:
28 | :return:
29 | """
30 | list_dashboard_path = "/search"
31 | params = []
32 |
33 | if query:
34 | params.append("query=%s" % query)
35 |
36 | if tag:
37 | params.append("tag=%s" % tag)
38 |
39 | if type_:
40 | params.append("type=%s" % type_)
41 |
42 | if dashboard_ids:
43 | params.append("dashboardIds=%s" % dashboard_ids)
44 |
45 | if folder_ids:
46 | params.append("folderIds=%s" % folder_ids)
47 |
48 | if starred:
49 | params.append("starred=%s" % starred)
50 |
51 | if limit:
52 | params.append("limit=%s" % limit)
53 |
54 | list_dashboard_path += "?"
55 | list_dashboard_path += "&".join(params)
56 |
57 | r = self.api.GET(list_dashboard_path)
58 | return r
59 |
--------------------------------------------------------------------------------
/test/api/test_search.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import requests_mock
4 |
5 | from grafana_api.grafana_face import GrafanaFace
6 | from grafana_api.grafana_api import GrafanaServerError, GrafanaClientError, GrafanaUnauthorizedError, \
7 | GrafanaBadInputError
8 |
9 |
10 | class AnnotationsTestCase(unittest.TestCase):
11 | def setUp(self):
12 | self.cli = GrafanaFace(
13 | ("admin", "admin"), host="localhost", url_path_prefix="", protocol="http"
14 | )
15 |
16 | @requests_mock.Mocker()
17 | def test_search_dashboards(self, m):
18 | m.get(
19 | "http://localhost/api/search?folderIds=11&query=str_e&starred=false&tag=test_tag"
20 | "&type=dash-folder&dashboardIds=163&limit=10",
21 | json=[
22 | {
23 | "id": 163,
24 | "uid": "000000163",
25 | "title": "Folder",
26 | "url": "/dashboards/f/000000163/folder",
27 | "type": "dash-folder",
28 | "tags": [],
29 | "isStarred": 'false',
30 | "uri": "db/folder"
31 | }
32 | ]
33 | )
34 |
35 | result = self.cli.search.search_dashboards(query="str_e", folder_ids=11, starred="false", tag="test_tag",
36 | type_="dash-folder", dashboard_ids=163, limit=10)
37 | self.assertEqual(result[0]["id"], 163)
38 | self.assertEqual(len(result), 1)
39 |
40 | @requests_mock.Mocker()
41 | def test_search_dashboards_with_out_filter(self, m):
42 | m.get(
43 | "http://localhost/api/search",
44 | json={
45 | "message": "Not found"
46 | }, status_code=400
47 | )
48 |
49 | result = self.cli.search.search_dashboards()
50 | self.assertRaises(GrafanaBadInputError)
51 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # ignore lock file, it's a library
2 | Pipfile.lock
3 |
4 | # Created by .ignore support plugin (hsz.mobi)
5 | ### Python template
6 | # Byte-compiled / optimized / DLL files
7 | __pycache__/
8 | *.py[cod]
9 | *$py.class
10 |
11 | # C extensions
12 | *.so
13 |
14 | # Distribution / packaging
15 | .Python
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | wheels/
28 | *.egg-info/
29 | .installed.cfg
30 | *.egg
31 | MANIFEST
32 |
33 | # PyInstaller
34 | # Usually these files are written by a python script from a template
35 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
36 | *.manifest
37 | *.spec
38 |
39 | # Installer logs
40 | pip-log.txt
41 | pip-delete-this-directory.txt
42 |
43 | # Unit test / coverage reports
44 | htmlcov/
45 | .tox/
46 | .coverage
47 | .coverage.*
48 | .cache
49 | nosetests.xml
50 | coverage.xml
51 | *.cover
52 | .hypothesis/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | .static_storage/
61 | .media/
62 | local_settings.py
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # pyenv
81 | .python-version
82 |
83 | # celery beat schedule file
84 | celerybeat-schedule
85 |
86 | # SageMath parsed files
87 | *.sage.py
88 |
89 | # Environments
90 | .env
91 | .venv
92 | env/
93 | venv/
94 | ENV/
95 | env.bak/
96 | venv.bak/
97 |
98 | # Spyder project settings
99 | .spyderproject
100 | .spyproject
101 |
102 | # Rope project settings
103 | .ropeproject
104 |
105 | # mkdocs documentation
106 | /site
107 |
108 | # mypy
109 | .mypy_cache/
110 |
111 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | from subprocess import check_output
3 |
4 |
5 | def get_version():
6 | try:
7 | tag = check_output(
8 | ["git", "describe", "--tags", "--abbrev=0", "--match=[0-9]*"]
9 | )
10 | return tag.decode("utf-8").strip("\n")
11 | except Exception:
12 | raise RuntimeError(
13 | "The version number cannot be extracted from git tag in this source "
14 | "distribution; please either download the source from PyPI, or check out "
15 | "from GitHub and make sure that the git CLI is available."
16 | )
17 |
18 |
19 | with open("README.md") as file:
20 | long_description = file.read()
21 |
22 | setup(
23 | name="grafana_api",
24 | version=get_version(),
25 | description="Yet another Python library for Grafana API",
26 | long_description=long_description,
27 | long_description_content_type="text/markdown",
28 | url="https://github.com/m0nhawk/grafana_api",
29 | author="Andrew Prokhorenkov",
30 | author_email="andrew.prokhorenkov@gmail.com",
31 | license="MIT",
32 | packages=find_packages(),
33 | install_requires=["pyyaml", "requests"],
34 | tests_require=["unittest-xml-reporting", "requests-mock"],
35 | classifiers=[
36 | "Development Status :: 3 - Alpha",
37 | "Intended Audience :: Developers",
38 | "Topic :: Internet",
39 | "Topic :: Software Development :: Libraries",
40 | "Topic :: Software Development :: Libraries :: Python Modules",
41 | "Operating System :: OS Independent",
42 | "License :: OSI Approved :: MIT License",
43 | "Programming Language :: Python :: 2",
44 | "Programming Language :: Python :: 2.7",
45 | "Programming Language :: Python :: 3",
46 | "Programming Language :: Python :: 3.6",
47 | "Programming Language :: Python :: 3.7",
48 | ],
49 | zip_safe=False,
50 | )
51 |
--------------------------------------------------------------------------------
/grafana_api/api/admin.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 |
3 |
4 | class Admin(Base):
5 | def __init__(self, api):
6 | super(Admin, self).__init__(api)
7 | self.api = api
8 |
9 | def settings(self):
10 | """
11 |
12 | :return:
13 | """
14 | path = "/admin/settings"
15 | r = self.api.GET(path)
16 | return r
17 |
18 | def stats(self):
19 | """
20 |
21 | :return:
22 | """
23 | path = "/admin/stats"
24 | r = self.api.GET(path)
25 | return r
26 |
27 | def create_user(self, user):
28 | """
29 |
30 | :param user:
31 | :return:
32 | """
33 | create_user_path = "/admin/users"
34 | r = self.api.POST(create_user_path, json=user)
35 | return r
36 |
37 | def change_user_password(self, user_id, password):
38 | """
39 |
40 | :param user_id:
41 | :param password:
42 | :return:
43 | """
44 | change_user_password_path = "/admin/users/%s/password" % user_id
45 | r = self.api.PUT(change_user_password_path, json={"password": password})
46 | return r
47 |
48 | def change_user_permissions(self, user_id, is_grafana_admin):
49 | """
50 |
51 | :param user_id:
52 | :param is_grafana_admin:
53 | :return:
54 | """
55 | change_user_permissions = "/admin/users/%s/permissions" % user_id
56 | r = self.api.PUT(
57 | change_user_permissions, json={"isGrafanaAdmin": is_grafana_admin}
58 | )
59 | return r
60 |
61 | def delete_user(self, user_id):
62 | """
63 |
64 | :param user_id:
65 | :return:
66 | """
67 | delete_user_path = "/admin/users/%s" % user_id
68 | r = self.api.DELETE(delete_user_path)
69 | return r
70 |
71 | def pause_all_alerts(self, pause):
72 | """
73 |
74 | :param pause:
75 | :return:
76 | """
77 | change_user_permissions = "/admin/pause-all-alerts"
78 | r = self.api.POST(change_user_permissions, json={"paused": pause})
79 | return r
80 |
--------------------------------------------------------------------------------
/grafana_api/api/dashboard.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 |
3 |
4 | class Dashboard(Base):
5 | def __init__(self, api):
6 | super(Dashboard, self).__init__(api)
7 | self.api = api
8 |
9 | def get_dashboard(self, dashboard_uid):
10 | """
11 |
12 | :param dashboard_uid:
13 | :return:
14 | """
15 | get_dashboard_path = "/dashboards/uid/%s" % dashboard_uid
16 | r = self.api.GET(get_dashboard_path)
17 | return r
18 |
19 | def update_dashboard(self, dashboard):
20 | """
21 |
22 | :param dashboard:
23 | :return:
24 | """
25 | put_dashboard_path = "/dashboards/db"
26 | r = self.api.POST(put_dashboard_path, json=dashboard)
27 | return r
28 |
29 | def delete_dashboard(self, dashboard_uid):
30 | """
31 |
32 | :param dashboard_uid:
33 | :return:
34 | """
35 | delete_dashboard_path = "/dashboards/uid/%s" % dashboard_uid
36 | r = self.api.DELETE(delete_dashboard_path)
37 | return r
38 |
39 | def get_home_dashboard(self):
40 | """
41 |
42 | :return:
43 | """
44 | get_home_dashboard_path = "/dashboards/home"
45 | r = self.api.GET(get_home_dashboard_path)
46 | return r
47 |
48 | def get_dashboards_tags(self):
49 | """
50 |
51 | :return:
52 | """
53 | get_dashboards_tags_path = "/dashboards/tags"
54 | r = self.api.GET(get_dashboards_tags_path)
55 | return r
56 |
57 | def get_dashboard_permissions(self, dashboard_id):
58 | """
59 |
60 | :param dashboard_id:
61 | :return:
62 | """
63 | get_dashboard_permissions_path = "/dashboards/id/%s/permissions" % dashboard_id
64 | r = self.api.GET(get_dashboard_permissions_path)
65 | return r
66 |
67 | def update_dashboard_permissions(self, dashboard_id, items):
68 | """
69 |
70 | :param dashboard_id:
71 | :param items:
72 | :return:
73 | """
74 | update_dashboard_permissions_path = (
75 | "/dashboards/id/%s/permissions" % dashboard_id
76 | )
77 | r = self.api.POST(update_dashboard_permissions_path, json=items)
78 | return r
79 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | grafana_api
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
42 |
43 |
44 |
45 |
58 |
59 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/grafana_api/api/folder.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 |
3 |
4 | class Folder(Base):
5 | def __init__(self, api):
6 | super(Folder, self).__init__(api)
7 | self.api = api
8 |
9 | def get_all_folders(self):
10 | """
11 |
12 | :return:
13 | """
14 | path = "/folders"
15 | r = self.api.GET(path)
16 | return r
17 |
18 | def get_folder(self, uid):
19 | """
20 |
21 | :param uid:
22 | :return:
23 | """
24 | path = "/folders/%s" % uid
25 | r = self.api.GET(path)
26 | return r
27 |
28 | def create_folder(self, title, uid=None):
29 | """
30 |
31 | :param title:
32 | :param uid:
33 | :return:
34 | """
35 | json_data = dict(title=title)
36 | if uid is not None:
37 | json_data["uid"] = uid
38 | return self.api.POST("/folders", json=json_data)
39 |
40 | def update_folder(self, uid, title, version=None, overwrite=False):
41 | """
42 |
43 | :param uid:
44 | :param title:
45 | :param version:
46 | :param overwrite:
47 | :return:
48 | """
49 | body = {"title": title}
50 | if version is not None:
51 | body['version'] = version
52 | if overwrite:
53 | body['overwrite'] = True
54 |
55 | path = "/folders/%s" % uid
56 | r = self.api.PUT(path, json=body)
57 | return r
58 |
59 | def delete_folder(self, uid):
60 | """
61 |
62 | :param uid:
63 | :return:
64 | """
65 | path = "/folders/%s" % uid
66 | r = self.api.DELETE(path)
67 | return r
68 |
69 | def get_folder_by_id(self, folder_id):
70 | """
71 |
72 | :param folder_id:
73 | :return:
74 | """
75 | path = "/folders/id/%s" % folder_id
76 | r = self.api.GET(path)
77 | return r
78 |
79 | def get_folder_permissions(self,uid):
80 | """
81 |
82 | :return:
83 | """
84 | path = "/folders/%s/permissions" % uid
85 | r = self.api.GET(path)
86 | return r
87 |
88 | def update_folder_permissions(self, uid, items):
89 | """
90 |
91 | :param uid:
92 | :param items:
93 | :return:
94 | """
95 | update_folder_permissions_path = "/folders/%s/permissions" % uid
96 | r = self.api.POST(update_folder_permissions_path, json=items)
97 | return r
98 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # grafana_api [](https://circleci.com/gh/m0nhawk/workflows/grafana_api/tree/master) [](https://github.com/m0nhawk/grafana_api/blob/master/LICENSE) [](https://codecov.io/gh/m0nhawk/grafana_api/)
2 |
3 | [](https://pypi.org/project/grafana-api/) [](https://anaconda.org/m0nhawk/grafana_api)
4 |
5 | ## What is this library for?
6 |
7 | Yet another Grafana API library for Python. Support both 2 and 3 Python versions.
8 |
9 | ## Requirements
10 |
11 | You need either 2nd or 3rd version of Python and only the `requests` library installed.
12 |
13 | ## Quick start
14 |
15 | Install the pip package:
16 |
17 | ```
18 | pip install -U grafana_api
19 | ```
20 |
21 | And then connect to your Grafana API endpoint:
22 |
23 | ```python
24 | from grafana_api.grafana_face import GrafanaFace
25 |
26 | grafana_api = GrafanaFace(auth='abcde....', host='api.my-grafana-host.com')
27 |
28 | # Search dashboards based on tag
29 | grafana_api.search.search_dashboards(tag='applications')
30 |
31 | # Find a user by email
32 | user = grafana_api.users.find_user('test@test.com')
33 |
34 | # Add user to team 2
35 | grafana_api.teams.add_team_member(2, user["id"])
36 |
37 | # Create or update a dashboard
38 | grafana_api.dashboard.update_dashboard(dashboard={'dashboard': {...}, 'folderId': 0, 'overwrite': True})
39 |
40 | # Delete a dashboard by UID
41 | grafana_api.dashboard.delete_dashboard(dashboard_uid='abcdefgh')
42 | ```
43 |
44 | ## Status of REST API realization
45 |
46 | Work on API implementation still in progress.
47 |
48 | | API | Status |
49 | |---|---|
50 | | Admin | + |
51 | | Alerting | - |
52 | | Annotations | + |
53 | | Authentication | +- |
54 | | Dashboard | + |
55 | | Dashboard Versions | - |
56 | | Dashboard Permissions | + |
57 | | Data Source | + |
58 | | Folder | + |
59 | | Folder Permissions | + |
60 | | Folder/Dashboard Search | +- |
61 | | Organisation | + |
62 | | Other | + |
63 | | Preferences | + |
64 | | Snapshot | + |
65 | | Teams | + |
66 | | User | + |
67 |
68 | ## Issue tracker
69 |
70 | Please report any bugs and enhancement ideas using the `grafana_api` issue tracker:
71 |
72 | https://github.com/m0nhawk/grafana_api/issues
73 |
74 | Feel free to also ask questions on the tracker.
75 |
76 | ## License
77 |
78 | `grafana_api` is licensed under the terms of the MIT License (see the file
79 | [LICENSE](LICENSE)).
80 |
--------------------------------------------------------------------------------
/grafana_api/api/snapshots.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 |
3 |
4 | class Snapshots(Base):
5 | def __init__(self, api):
6 | super(Snapshots, self).__init__(api)
7 | self.api = api
8 |
9 | def create_new_snapshot(
10 | self,
11 | dashboard=None,
12 | name=None,
13 | expires=None,
14 | external=None,
15 | key=None,
16 | delete_key=None,
17 | ):
18 | """
19 |
20 | :param dashboard: Required. The complete dashboard model.
21 | :param name: Optional. snapshot name
22 | :param expires: Optional. When the snapshot should expire in seconds. 3600 is 1 hour, 86400 is 1 day. Default is never to expire.
23 | :param external: Optional. Save the snapshot on an external server rather than locally. Default is false.
24 | :param key: Optional. Define the unique key. Required if external is true.
25 | :param deleteKey: Optional. Unique key used to delete the snapshot. It is different from the key so that only the creator can delete the snapshot. Required if external is true.
26 | :return:
27 | """
28 |
29 | path = "/snapshots"
30 | post_json = {
31 | "dashboard": dashboard
32 | }
33 | if name:
34 | post_json["name"] = name
35 | if expires:
36 | post_json["expires"] = expires
37 | if external:
38 | post_json["external"] = external
39 | if key:
40 | post_json["key"] = key
41 | if delete_key:
42 | post_json["deleteKey"] = delete_key
43 |
44 | r = self.api.POST(path, json=post_json)
45 | return r
46 |
47 | def get_dashboard_snapshots(self):
48 | """
49 |
50 | :return:
51 | """
52 | path = "/dashboard/snapshots"
53 | r = self.api.GET(path)
54 | return r
55 |
56 | def get_snapshot_by_key(self, key):
57 | """
58 |
59 | :param key:
60 | :return:
61 | """
62 | path = "/snapshots/%s" % key
63 | r = self.api.GET(path)
64 | return r
65 |
66 | def delete_snapshot_by_key(
67 | self,
68 | snapshot_id=None
69 | ):
70 | """
71 |
72 | :param snapshot_id:
73 | :return:
74 | """
75 | path = "/snapshots/{}".format(snapshot_id)
76 | r = self.api.DELETE(path)
77 |
78 | return r
79 |
80 | def delete_snapshot_by_delete_key(
81 | self,
82 | snapshot_delete_key=None
83 | ):
84 | """
85 |
86 | :param snapshot_delete_key:
87 | :return:
88 | """
89 | path = "/snapshots-delete/{}".format(snapshot_delete_key)
90 | r = self.api.DELETE(path)
91 |
92 | return r
93 |
--------------------------------------------------------------------------------
/grafana_api/api/datasource.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 |
3 |
4 | class Datasource(Base):
5 | def __init__(self, api):
6 | super(Datasource, self).__init__(api)
7 | self.api = api
8 |
9 | def find_datasource(self, datasource_name):
10 | """
11 |
12 | :param datasource_name:
13 | :return:
14 | """
15 | get_datasource_path = "/datasources/name/%s" % datasource_name
16 | r = self.api.GET(get_datasource_path)
17 | return r
18 |
19 | def get_datasource_by_id(self, datasource_id):
20 | """
21 |
22 | :param datasource_id:
23 | :return:
24 | """
25 | get_datasource_path = "/datasources/%s" % datasource_id
26 | r = self.api.GET(get_datasource_path)
27 | return r
28 |
29 | def get_datasource_by_name(self, datasource_name):
30 | """
31 |
32 | :param datasource_name:
33 | :return:
34 | """
35 | get_datasource_path = "/datasources/name/%s" % datasource_name
36 | r = self.api.GET(get_datasource_path)
37 | return r
38 |
39 | def get_datasource_id_by_name(self, datasource_name):
40 | """
41 |
42 | :param datasource_name:
43 | :return:
44 | """
45 | get_datasource_path = "/datasources/id/%s" % datasource_name
46 | r = self.api.GET(get_datasource_path)
47 | return r
48 |
49 | def create_datasource(self, datasource):
50 | """
51 |
52 | :param datasource:
53 | :return:
54 | """
55 | create_datasources_path = "/datasources"
56 | r = self.api.POST(create_datasources_path, json=datasource)
57 | return r
58 |
59 | def update_datasource(self, datasource_id, datasource):
60 | """
61 |
62 | :param datasource_id:
63 | :param datasource:
64 | :return:
65 | """
66 | update_datasource = "/datasources/%s" % datasource_id
67 | r = self.api.PUT(update_datasource, json=datasource)
68 | return r
69 |
70 | def list_datasources(self):
71 | """
72 |
73 | :return:
74 | """
75 | list_datasources_path = "/datasources"
76 | r = self.api.GET(list_datasources_path)
77 | return r
78 |
79 | def delete_datasource_by_id(self, datasource_id):
80 | """
81 |
82 | :param datasource_id:
83 | :return:
84 | """
85 | delete_datasource = "/datasources/%s" % datasource_id
86 | r = self.api.DELETE(delete_datasource)
87 | return r
88 |
89 | def delete_datasource_by_name(self, datasource_name):
90 | """
91 |
92 | :param datasource_name:
93 | :return:
94 | """
95 | delete_datasource = "/datasources/name/%s" % datasource_name
96 | r = self.api.DELETE(delete_datasource)
97 | return r
98 |
--------------------------------------------------------------------------------
/test/test_grafana.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import sys
3 |
4 | if sys.version_info > (3, 0):
5 | from unittest.mock import patch, Mock
6 | else:
7 | from mock import patch, Mock
8 |
9 | import requests
10 |
11 | from grafana_api.grafana_face import GrafanaFace
12 | from grafana_api.grafana_api import TokenAuth
13 |
14 |
15 | class MockResponse:
16 | def __init__(self, json_data, status_code):
17 | self.json_data = json_data
18 | self.status_code = status_code
19 |
20 | def json(self):
21 | return self.json_data
22 |
23 |
24 | class TestGrafanaAPI(unittest.TestCase):
25 | @patch("grafana_api.grafana_api.GrafanaAPI.__getattr__")
26 | def test_grafana_api(self, mock_get):
27 | mock_get.return_value = Mock()
28 | mock_get.return_value.return_value = """{
29 | "email": "user@mygraf.com",
30 | "name": "admin",
31 | "login": "admin",
32 | "theme": "light",
33 | "orgId": 1,
34 | "isGrafanaAdmin": true
35 | }"""
36 | cli = GrafanaFace(
37 | ("admin", "admin"), host="localhost", url_path_prefix="", protocol="https"
38 | )
39 | cli.users.find_user("test@test.com")
40 |
41 | def test_grafana_api_no_verify(self):
42 | cli = GrafanaFace(
43 | ("admin", "admin"),
44 | host="localhost",
45 | url_path_prefix="",
46 | protocol="https",
47 | verify=False,
48 | )
49 | cli.api.s.get = Mock(name="get")
50 | cli.api.s.get.return_value = MockResponse(
51 | {
52 | "email": "user@mygraf.com",
53 | "name": "admin",
54 | "login": "admin",
55 | "theme": "light",
56 | "orgId": 1,
57 | "isGrafanaAdmin": True,
58 | },
59 | 200,
60 | )
61 |
62 | basic_auth = requests.auth.HTTPBasicAuth("admin", "admin")
63 | cli.users.find_user("test@test.com")
64 | cli.api.s.get.assert_called_once_with(
65 | "https://localhost/api/users/lookup?loginOrEmail=test@test.com",
66 | auth=basic_auth,
67 | headers=None,
68 | json=None,
69 | verify=False,
70 | )
71 |
72 | def test_grafana_api_basic_auth(self):
73 | cli = GrafanaFace(
74 | ("admin", "admin"), host="localhost", url_path_prefix="", protocol="https",port="3000"
75 | )
76 | self.assertTrue(isinstance(cli.api.auth, requests.auth.HTTPBasicAuth))
77 |
78 | def test_grafana_api_token_auth(self):
79 | cli = GrafanaFace(
80 | "alongtoken012345etc",
81 | host="localhost",
82 | url_path_prefix="",
83 | protocol="https",
84 | )
85 | self.assertTrue(isinstance(cli.api.auth, TokenAuth))
86 |
87 |
88 | if __name__ == "__main__":
89 | import xmlrunner
90 |
91 | unittest.main(testRunner=xmlrunner.XMLTestRunner(output="test-reports"))
92 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 |
3 | jobs:
4 | tests-3.7: &test-template
5 | working_directory: ~/grafana_api
6 | docker:
7 | - image: circleci/python:3.7
8 | environment:
9 | PIPENV_VENV_IN_PROJECT: true
10 | steps:
11 | - checkout # checkout source code to working directory
12 | - restore_cache: # ensure this step occurs *before* installing dependencies
13 | key: py-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}-{{ checksum "Pipfile" }}
14 | - run:
15 | command: |
16 | sudo pip install pipenv
17 | pipenv install
18 | pipenv install --dev
19 | - run: pipenv run coverage run --source grafana_api -m xmlrunner discover -o test-reports
20 | - run: pipenv run codecov
21 | - save_cache:
22 | key: py-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}-{{ checksum "Pipfile" }}
23 | paths:
24 | - ".venv"
25 | - store_test_results:
26 | path: test-reports
27 | - store_artifacts:
28 | path: test-reports
29 |
30 | tests-3.6:
31 | <<: *test-template
32 | docker:
33 | - image: circleci/python:3.6
34 | environment:
35 | PIPENV_VENV_IN_PROJECT: true
36 |
37 | tests-2.7:
38 | <<: *test-template
39 | docker:
40 | - image: circleci/python:2.7
41 | environment:
42 | PIPENV_VENV_IN_PROJECT: true
43 |
44 | build:
45 | docker:
46 | - image: circleci/python:3.7
47 | steps:
48 | - checkout
49 | - run:
50 | name: "Build package"
51 | command: |
52 | mkdir -p ./dist/
53 | export VERSION=$(git describe --abbrev=0 --tags)
54 | echo ${VERSION} > ./dist/VERSION
55 | export PREV_VERSION=$(git describe --tags --abbrev=0 ${VERSION}^)
56 | sed '/"'${VERSION}'"/,/"'${PREV_VERSION}'"/!d;/"'${PREV_VERSION}'"/q' CHANGELOG.md | tail -n+2 | head -n-3 > ./dist/CHANGELOG
57 | python setup.py sdist
58 | python setup.py bdist_wheel
59 | - persist_to_workspace:
60 | root: .
61 | paths:
62 | - dist
63 |
64 | publish-github-release:
65 | docker:
66 | - image: cibuilds/github:0.12
67 | steps:
68 | - attach_workspace:
69 | at: ./dist
70 | - run:
71 | name: "Publish Release on GitHub"
72 | command: |
73 | VERSION=$(<./dist/dist/VERSION)
74 | rm ./dist/dist/VERSION
75 | ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -b "$(<./dist/dist/CHANGELOG)" -delete ${VERSION} ./dist/dist/
76 |
77 | workflows:
78 | version: 2
79 | main:
80 | jobs:
81 | - tests-3.7
82 | - tests-3.6
83 | - tests-2.7
84 | - build:
85 | filters:
86 | tags:
87 | only: /^\d+\.\d+\.\d+$/
88 | - publish-github-release:
89 | requires:
90 | - build
91 | filters:
92 | branches:
93 | ignore: /.*/
94 | tags:
95 | only: /^\d+\.\d+\.\d+$/
96 |
--------------------------------------------------------------------------------
/grafana_api/grafana_api.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import requests.auth
3 |
4 |
5 | class GrafanaException(Exception):
6 | pass
7 |
8 |
9 | class GrafanaServerError(Exception):
10 | """
11 | 5xx
12 | """
13 |
14 | pass
15 |
16 |
17 | class GrafanaClientError(Exception):
18 | """
19 | Invalid input (4xx errors)
20 | """
21 |
22 | pass
23 |
24 |
25 | class GrafanaBadInputError(GrafanaClientError):
26 | """
27 | 400
28 | """
29 |
30 | pass
31 |
32 |
33 | class GrafanaUnauthorizedError(GrafanaClientError):
34 | """
35 | 401
36 | """
37 |
38 | pass
39 |
40 |
41 | class TokenAuth(requests.auth.AuthBase):
42 | def __init__(self, token):
43 | self.token = token
44 |
45 | def __call__(self, request):
46 | request.headers.update({"Authorization": "Bearer {0}".format(self.token)})
47 | return request
48 |
49 |
50 | class GrafanaAPI:
51 | def __init__(
52 | self,
53 | auth,
54 | host="localhost",
55 | port=None,
56 | url_path_prefix="",
57 | protocol="http",
58 | verify=True,
59 | ):
60 | self.auth = auth
61 | self.verify = verify
62 | self.url_host = host
63 | self.url_port = port
64 | self.url_path_prefix = url_path_prefix
65 | self.url_protocol = protocol
66 |
67 | def construct_api_url():
68 | params = {
69 | "protocol": self.url_protocol,
70 | "host": self.url_host,
71 | "url_path_prefix": self.url_path_prefix,
72 | }
73 |
74 | if self.url_port is None:
75 | url_pattern = "{protocol}://{host}/{url_path_prefix}api"
76 | else:
77 | params["port"] = self.url_port
78 | url_pattern = "{protocol}://{host}:{port}/{url_path_prefix}api"
79 |
80 | return url_pattern.format(**params)
81 |
82 | self.url = construct_api_url()
83 |
84 | self.s = requests.Session()
85 | if not isinstance(self.auth, tuple):
86 | self.auth = TokenAuth(self.auth)
87 | else:
88 | self.auth = requests.auth.HTTPBasicAuth(*self.auth)
89 |
90 | def __getattr__(self, item):
91 | def __request_runnner(url, json=None, headers=None):
92 | __url = "%s%s" % (self.url, url)
93 | runner = getattr(self.s, item.lower())
94 | r = runner(
95 | __url, json=json, headers=headers, auth=self.auth, verify=self.verify
96 | )
97 | try:
98 |
99 | if 500 <= r.status_code < 600:
100 | raise GrafanaServerError(
101 | "Client Error {0}: {1}".format(r.status_code, r.json()['message'])
102 | )
103 | elif r.status_code == 400:
104 | raise GrafanaBadInputError("Bad Input: `{0}`".format(r.text))
105 | elif r.status_code == 401:
106 | raise GrafanaUnauthorizedError("Unauthorized")
107 | elif 400 <= r.status_code < 500:
108 | raise GrafanaClientError(
109 | "Client Error {0}: {1}".format(r.status_code, r.text)
110 | )
111 | return r.json()
112 |
113 | except Exception as error:
114 | print('Caught this error: ' + repr(error))
115 |
116 |
117 |
118 | return __request_runnner
119 |
--------------------------------------------------------------------------------
/grafana_api/api/team.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 |
3 |
4 | class Teams(Base):
5 | def __init__(self, api):
6 | super(Teams, self).__init__(api)
7 | self.api = api
8 |
9 | def search_teams(self, query=None, page=None, perpage=None):
10 | """
11 |
12 | :return:
13 | """
14 | list_of_teams = []
15 | search_teams_path = "/teams/search"
16 | params = []
17 |
18 | if query:
19 | params.append("query=%s" % query)
20 |
21 | if page:
22 | iterate = False
23 | params.append("page=%s" % page)
24 | else:
25 | iterate = True
26 | params.append("page=%s")
27 | page = 1
28 |
29 | if perpage:
30 | params.append("perpage=%s" % perpage)
31 |
32 | search_teams_path += "?"
33 | search_teams_path += "&".join(params)
34 |
35 | if iterate:
36 | while True:
37 | teams_on_page = self.api.GET(search_teams_path % page)
38 | list_of_teams += teams_on_page["teams"]
39 | if len(list_of_teams) == teams_on_page["totalCount"]:
40 | break
41 | page += 1
42 | else:
43 | teams_on_page = self.api.GET(search_teams_path)
44 | list_of_teams += teams_on_page["teams"]
45 |
46 | return list_of_teams
47 |
48 | def get_team_by_name(self, team_name):
49 | """
50 |
51 | :param team_name:
52 | :return:
53 | """
54 | search_teams_path = "/teams/search"
55 |
56 | search_teams_path += "?name=%s" % team_name
57 |
58 | teams_on_page = self.api.GET(search_teams_path)
59 | return teams_on_page["teams"]
60 |
61 | def get_team(self, team_id):
62 | """
63 |
64 | :param team_id:
65 | :return:
66 | """
67 | get_team_path = "/teams/%s" % team_id
68 | r = self.api.GET(get_team_path)
69 | return r
70 |
71 | def add_team(self, team):
72 | """
73 |
74 | :param team:
75 | :return:
76 | """
77 | add_team_path = "/teams"
78 | r = self.api.POST(add_team_path, json=team)
79 | return r
80 |
81 | def update_team(self, team_id, team):
82 | """
83 |
84 | :param team_id:
85 | :param team:
86 | :return:
87 | """
88 | update_team_path = "/teams/%s" % team_id
89 | r = self.api.PUT(update_team_path, json=team)
90 | return r
91 |
92 | def delete_team(self, team_id):
93 | """
94 |
95 | :param team_id:
96 | :return:
97 | """
98 | delete_team_path = "/teams/%s" % team_id
99 | r = self.api.DELETE(delete_team_path)
100 | return True
101 |
102 | def get_team_members(self, team_id):
103 | """
104 |
105 | :param team_id:
106 | :return:
107 | """
108 | get_team_members_path = "/teams/%s/members" % team_id
109 | r = self.api.GET(get_team_members_path)
110 | return r
111 |
112 | def add_team_member(self, team_id, user_id):
113 | """
114 |
115 | :param team_id:
116 | :param user_id:
117 | :return:
118 | """
119 | add_team_member_path = "/teams/%s/members" % team_id
120 | payload = {"userId": user_id}
121 | r = self.api.POST(add_team_member_path, json=payload)
122 | return r
123 |
124 | def remove_team_member(self, team_id, user_id):
125 | """
126 |
127 | :param team_id:
128 | :param user_id:
129 | :return:
130 | """
131 | remove_team_member_path = "/teams/%s/members/%s" % (team_id, user_id)
132 | r = self.api.DELETE(remove_team_member_path)
133 | return r
134 |
135 | def get_team_preferences(self, team_id):
136 | """
137 |
138 | :param team_id:
139 | :return:
140 | """
141 | get_team_preferences_path = "/teams/%s/preferences" % team_id
142 | r = self.api.GET(get_team_preferences_path)
143 | return r
144 |
145 | def update_team_preferences(self, team_id, preferences):
146 | """
147 |
148 | :param team_id:
149 | :param preferences:
150 | :return:
151 | """
152 | update_team_preferences_path = "/teams/%s/preferences" % team_id
153 | r = self.api.PUT(update_team_preferences_path, json=preferences)
154 | return r
155 |
--------------------------------------------------------------------------------
/grafana_api/api/annotations.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 |
3 |
4 | class Annotations(Base):
5 | def __init__(self, api):
6 | super(Annotations, self).__init__(api)
7 | self.api = api
8 |
9 | def get_annotation(
10 | self,
11 | time_from=None,
12 | time_to=None,
13 | alert_id=None,
14 | dashboard_id=None,
15 | panel_id=None,
16 | tags=None,
17 | limit=None,
18 | ):
19 |
20 | """
21 | :param time_from:
22 | :param time_to:
23 | :param alert_id:
24 | :param dashboard_id:
25 | :param panel_id:
26 | :param tags:
27 | :param limit:
28 | :return:
29 | """
30 | list_annotations_path = "/annotations"
31 | params = []
32 |
33 | if time_from:
34 | params.append("time_from=%s" % time_from)
35 |
36 | if time_to:
37 | params.append("time_to=%s" % time_to)
38 |
39 | if alert_id:
40 | params.append("alertId=%s" % alert_id)
41 |
42 | if dashboard_id:
43 | params.append("dashboardID=%s" % dashboard_id)
44 |
45 | if panel_id:
46 | params.append("panelId=%s" % panel_id)
47 |
48 | if tags:
49 | params.append("tags=%s" % tags)
50 |
51 | if limit:
52 | params.append("limit=%s" % limit)
53 |
54 | list_annotations_path += "?"
55 | list_annotations_path += "&".join(params)
56 |
57 | r = self.api.GET(list_annotations_path)
58 |
59 | return r
60 |
61 | def add_annotation(
62 | self,
63 | time_from=None,
64 | time_to=None,
65 | is_region=True,
66 | tags=None,
67 | text=None,
68 | ):
69 |
70 | """
71 | :param time_from:
72 | :param time_to:
73 | :param is_region:
74 | :param tags:
75 | :param text:
76 | :return:
77 | """
78 | annotations_path = "/annotations"
79 | payload = {
80 | "time": time_from,
81 | "timeEnd": time_to,
82 | "isRegion": bool(is_region),
83 | "tags": [tags],
84 | "text": text
85 |
86 | }
87 |
88 | r = self.api.POST(annotations_path, json=payload)
89 |
90 | return r
91 |
92 | def add_annotation_graphite(
93 | self,
94 | what=None,
95 | tags=True,
96 | when=None,
97 | data=None,
98 | ):
99 | """
100 | :param what:
101 | :param tags:
102 | :param when:
103 | :param data:
104 | :return:
105 | """
106 |
107 | annotations_path = "/annotations/graphite"
108 | payload = {
109 | "what": what,
110 | "tags": [tags],
111 | "when": when,
112 | "data": data
113 |
114 | }
115 |
116 | r = self.api.POST(annotations_path, json=payload)
117 |
118 | return r
119 |
120 | def update_annotation(
121 | self,
122 | time_from=None,
123 | time_to=None,
124 | is_region=True,
125 | tags=None,
126 | text=None,
127 | ):
128 | """
129 |
130 | :param time_from:
131 | :param time_to:
132 | :param is_region:
133 | :param tags:
134 | :param text:
135 | :return:
136 | """
137 | annotations_path = "/annotations"
138 | payload = {
139 | "time": time_from,
140 | "timeEnd": time_to,
141 | "isRegion": bool(is_region),
142 | "tags": [tags],
143 | "text": text
144 |
145 | }
146 |
147 | r = self.api.PUT(annotations_path, json=payload)
148 |
149 | return r
150 |
151 | def delete_annotations_by_region_id(
152 | self,
153 | region_id=None
154 | ):
155 |
156 | """
157 | :param region_id:
158 | :return:
159 | """
160 | annotations_path = "/annotations/region/{}".format(region_id)
161 | r = self.api.DELETE(annotations_path)
162 |
163 | return r
164 |
165 | def delete_annotations_by_id(
166 | self,
167 | annotations_id=None
168 | ):
169 |
170 | """
171 | :param annotations_id:
172 | :return:
173 | """
174 | annotations_path = "/annotations/{}".format(annotations_id)
175 | r = self.api.DELETE(annotations_path)
176 |
177 | return r
178 |
--------------------------------------------------------------------------------
/grafana_api/api/user.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 |
3 |
4 | class Users(Base):
5 | def __init__(self, api):
6 | super(Users, self).__init__(api)
7 | self.api = api
8 |
9 | def search_users(self, query=None, page=None, perpage=None):
10 | """
11 |
12 | :return:
13 | """
14 | list_of_users = []
15 | users_on_page = None
16 | show_users_path = "/users"
17 | params = []
18 |
19 | if query:
20 | params.append("query=%s" % query)
21 |
22 | if page:
23 | iterate = False
24 | params.append("page=%s" % page)
25 | else:
26 | iterate = True
27 | params.append("page=%s")
28 | page = 1
29 |
30 | if perpage:
31 | params.append("perpage=%s" % perpage)
32 |
33 | show_users_path += "?"
34 | show_users_path += "&".join(params)
35 |
36 | if iterate:
37 | while users_on_page is not []:
38 | users_on_page = self.api.GET(show_users_path % page)
39 | list_of_users += users_on_page
40 | page += 1
41 | else:
42 | users_on_page = self.api.GET(show_users_path)
43 | list_of_users += users_on_page
44 |
45 | return list_of_users
46 |
47 | def get_user(self, user_id):
48 | """
49 |
50 | :param user_id:
51 | :return:
52 | """
53 | get_user_path = "/users/%s" % user_id
54 | r = self.api.GET(get_user_path)
55 | return r
56 |
57 | def find_user(self, login_or_email):
58 | """
59 |
60 | :param login_or_email:
61 | :return:
62 | """
63 | search_user_path = "/users/lookup?loginOrEmail=%s" % login_or_email
64 | r = self.api.GET(search_user_path)
65 | return r
66 |
67 | def update_user(self, user_id, user):
68 | """
69 |
70 | :param user_id:
71 | :param user:
72 | :return:
73 | """
74 | update_user_path = "/users/%s" % user_id
75 | r = self.api.PUT(update_user_path, json=user)
76 | return r
77 |
78 | def get_user_organisations(self, user_id):
79 | """
80 |
81 | :param user_id:
82 | :return:
83 | """
84 | get_user_organisations_path = "/users/%s/orgs" % user_id
85 | r = self.api.GET(get_user_organisations_path)
86 | return r
87 |
88 |
89 | class User(Base):
90 | def __init__(self, api):
91 | super(User, self).__init__(api)
92 | self.api = api
93 | self.path = "/user"
94 |
95 | def get_actual_user(self):
96 | """
97 |
98 | :return:
99 | """
100 | get_actual_user_path = "/user"
101 | r = self.api.GET(get_actual_user_path)
102 | return r
103 |
104 | def change_actual_user_password(self, old_password, new_password):
105 | """
106 |
107 | :param old_password:
108 | :param new_password:
109 | :return:
110 | """
111 | change_actual_user_password_path = "/user/password"
112 | change_actual_user_password_json = {
113 | "oldPassword": old_password,
114 | "newPassword": new_password,
115 | "confirmNew": new_password,
116 | }
117 | r = self.api.PUT(
118 | change_actual_user_password_path, json=change_actual_user_password_json
119 | )
120 | return r
121 |
122 | def switch_user_organisation(self, user_id, organisation_id):
123 | """
124 |
125 | :param user_id:
126 | :param organisation_id:
127 | :return:
128 | """
129 | switch_user_organisation_path = "/users/%s/using/%s" % (
130 | user_id,
131 | organisation_id,
132 | )
133 | r = self.api.POST(switch_user_organisation_path)
134 | return r
135 |
136 | def switch_actual_user_organisation(self, organisation_id):
137 | """
138 |
139 | :param organisation_id:
140 | :return:
141 | """
142 | switch_actual_user_organisation_path = "/user/using/%s" % organisation_id
143 | r = self.api.POST(switch_actual_user_organisation_path)
144 | return r
145 |
146 | def get_actual_user_organisations(self):
147 | """
148 |
149 | :return:
150 | """
151 | get_actual_user_organisations_path = "/user/orgs"
152 | r = self.api.GET(get_actual_user_organisations_path)
153 | return r
154 |
155 | def star_actual_user_dashboard(self, dashboard_id):
156 | """
157 |
158 | :param dashboard_id:
159 | :return:
160 | """
161 | star_dashboard = "/user/stars/dashboard/%s" % dashboard_id
162 | r = self.api.POST(star_dashboard)
163 | return r
164 |
165 | def unstar_actual_user_dashboard(self, dashboard_id):
166 | """
167 |
168 | :param dashboard_id:
169 | :return:
170 | """
171 | unstar_dashboard = "/user/stars/dashboard/%s" % dashboard_id
172 | r = self.api.DELETE(unstar_dashboard)
173 | return r
174 |
--------------------------------------------------------------------------------
/test/api/test_snapshot.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import requests_mock
4 |
5 | from grafana_api.grafana_face import GrafanaFace
6 |
7 |
8 | class SnapshotTestCase(unittest.TestCase):
9 | def setUp(self):
10 | self.cli = GrafanaFace(
11 | ("admin", "admin"), host="localhost", url_path_prefix="", protocol="http"
12 | )
13 |
14 | @requests_mock.Mocker()
15 | def test_create_new_snapshot(self, m):
16 | m.post(
17 | "http://localhost/api/snapshots",
18 | json={
19 | "deleteKey": "XXXXXXX",
20 | "deleteUrl": "myurl/api/snapshots.py-delete/XXXXXXX",
21 | "key": "YYYYYYY",
22 | "url": "myurl/dashboard/snapshot/YYYYYYY"
23 | },
24 | )
25 | snapshot = self.cli.snapshots.create_new_snapshot(dashboard={
26 | "editable": "false",
27 | "hideControls": "true",
28 | "nav": [
29 | {
30 | "enable": "false",
31 | "type": "timepicker"
32 | }
33 | ],
34 | "rows": [
35 | {
36 |
37 | }
38 | ],
39 | "style": "dark",
40 | "tags": [],
41 | "templating": {
42 | "list": [
43 | ]
44 | },
45 | "time": {
46 | },
47 | "timezone": "browser",
48 | "title": "Home",
49 | "version": 5
50 | }, name="Test", key="YYYYYYY", delete_key="XXXXXXX", external=True, expires=3600)
51 | self.assertEqual(snapshot["key"], "YYYYYYY")
52 |
53 | @requests_mock.Mocker()
54 | def test_create_new_snapshot_without_optional(self, m):
55 | m.post(
56 | "http://localhost/api/snapshots",
57 | json={
58 | "deleteKey": "XXXXXXX",
59 | "deleteUrl": "myurl/api/snapshots.py-delete/XXXXXXX",
60 | "key": "YYYYYYY",
61 | "url": "myurl/dashboard/snapshot/YYYYYYY"
62 | },
63 | )
64 | snapshot = self.cli.snapshots.create_new_snapshot(dashboard={
65 | "editable": "false",
66 | "hideControls": "true",
67 | "nav": [
68 | {
69 | "enable": "false",
70 | "type": "timepicker"
71 | }
72 | ],
73 | "rows": [
74 | {
75 |
76 | }
77 | ],
78 | "style": "dark",
79 | "tags": [],
80 | "templating": {
81 | "list": [
82 | ]
83 | },
84 | "time": {
85 | },
86 | "timezone": "browser",
87 | "title": "Home",
88 | "version": 5
89 | })
90 | self.assertEqual(snapshot["key"], "YYYYYYY")
91 |
92 | @requests_mock.Mocker()
93 | def test_get_dashboard_snapshots(self, m):
94 | m.get(
95 | "http://localhost/api/dashboard/snapshots",
96 | json=[
97 | {
98 | "id": 8,
99 | "name": "Home",
100 | "key": "YYYYYYY",
101 | "orgId": 1,
102 | "userId": 1,
103 | "external": False,
104 | "externalUrl": "",
105 | "expires": "2200-13-32T25:23:23+02:00",
106 | "created": "2200-13-32T28:24:23+02:00",
107 | "updated": "2200-13-32T28:24:23+02:00"
108 | }
109 | ]
110 | )
111 | dashboards = self.cli.snapshots.get_dashboard_snapshots()
112 | self.assertEqual(len(dashboards), 1)
113 |
114 | @requests_mock.Mocker()
115 | def test_get_snapshot_by_key(self, m):
116 | m.get(
117 | "http://localhost/api/snapshots/YYYYYYY",
118 | json=[
119 | {
120 | "id": 8,
121 | "name": "Home",
122 | "key": "YYYYYYY",
123 | "orgId": 1,
124 | "userId": 1,
125 | "external": False,
126 | "externalUrl": "",
127 | "expires": "2200-13-32T25:23:23+02:00",
128 | "created": "2200-13-32T28:24:23+02:00",
129 | "updated": "2200-13-32T28:24:23+02:00"
130 | }
131 | ]
132 | )
133 | dashboards = self.cli.snapshots.get_snapshot_by_key(key="YYYYYYY")
134 | self.assertEqual(len(dashboards), 1)
135 |
136 | @requests_mock.Mocker()
137 | def test_delete_snapshot_by_key(self, m):
138 | m.delete('http://localhost/api/snapshots/YYYYYYY', json={"message": "Snapshot deleted. It might take an hour "
139 | "before it's cleared from any CDN "
140 | "caches."})
141 | annotation = self.cli.snapshots.delete_snapshot_by_key(snapshot_id="YYYYYYY")
142 | self.assertEqual(annotation['message'], "Snapshot deleted. It might take an hour before it's cleared from any "
143 | "CDN caches.")
144 |
145 | @requests_mock.Mocker()
146 | def test_delete_snapshot_by_delete_key(self, m):
147 | m.delete('http://localhost/api/snapshots-delete/XXXXXXX', json={"message": "Snapshot deleted. It might take an hour "
148 | "before it's cleared from any CDN "
149 | "caches."})
150 | annotation = self.cli.snapshots.delete_snapshot_by_delete_key(snapshot_delete_key="XXXXXXX")
151 | self.assertEqual(annotation['message'], "Snapshot deleted. It might take an hour before it's cleared from any "
152 | "CDN caches.")
153 |
154 |
--------------------------------------------------------------------------------
/grafana_api/api/organization.py:
--------------------------------------------------------------------------------
1 | from .base import Base
2 |
3 |
4 | class Organization(Base):
5 | def __init__(self, api):
6 | super(Organization, self).__init__(api)
7 | self.api = api
8 |
9 | def find_organization(self, org_name):
10 | """
11 |
12 | :param org_name:
13 | :return:
14 | """
15 | get_org_path = "/orgs/name/%s" % org_name
16 | r = self.api.GET(get_org_path)
17 | return r
18 |
19 | def get_current_organization(self):
20 | """
21 |
22 | :return:
23 | """
24 | get_current_organization_path = "/org"
25 | r = self.api.GET(get_current_organization_path)
26 | return r
27 |
28 | def create_organization(self, organization):
29 | """
30 |
31 | :param organization:
32 | :return:
33 | """
34 | create_orgs_path = "/orgs"
35 | r = self.api.POST(create_orgs_path, json={"name": organization["name"]})
36 | return r
37 |
38 | def update_current_organization(self, organization):
39 | """
40 |
41 | :param organization:
42 | :return:
43 | """
44 | update_current_organization_path = "/org"
45 | r = self.api.PUT(update_current_organization_path, json=organization)
46 | return r
47 |
48 | def get_current_organization_users(self):
49 | """
50 |
51 | :return:
52 | """
53 | get_current_organization_users_path = "/org/users"
54 | r = self.api.GET(get_current_organization_users_path)
55 | return r
56 |
57 | def add_user_current_organization(self, user):
58 | """
59 |
60 | :param user:
61 | :return:
62 | """
63 | add_user_current_organization_path = "/org/users"
64 | r = self.api.POST(add_user_current_organization_path, json=user)
65 | return r
66 |
67 | def update_user_current_organization(self, user_id, user):
68 | """
69 |
70 | :param user_id:
71 | :param user:
72 | :return:
73 | """
74 | update_user_current_organization_path = "/org/users/%s" % user_id
75 | r = self.api.PATCH(update_user_current_organization_path, json=user)
76 | return r
77 |
78 | def delete_user_current_organization(self, user_id):
79 | """
80 |
81 | :param user_id:
82 | :return:
83 | """
84 | delete_user_current_organization_path = "/org/users/%s" % user_id
85 | r = self.api.DELETE(delete_user_current_organization_path)
86 | return r
87 |
88 |
89 | class Organizations(Base):
90 | def __init__(self, api):
91 | super(Organizations, self).__init__(api)
92 | self.api = api
93 | self.path = "/users"
94 |
95 | def update_organization(self, organization_id, organization):
96 | """
97 |
98 | :param organization_id:
99 | :param organization:
100 | :return:
101 | """
102 | update_org_path = "/orgs/%s" % organization_id
103 | r = self.api.PUT(update_org_path, json=organization)
104 | return r
105 |
106 | def delete_organization(self, organization_id):
107 | """
108 |
109 | :param organization_id:
110 | :return:
111 | """
112 | delete_org_path = "/orgs/%s" % organization_id
113 | r = self.api.DELETE(delete_org_path)
114 | return r
115 |
116 | def list_organization(self):
117 | """
118 |
119 | :return:
120 | """
121 | search_org_path = "/orgs"
122 | r = self.api.GET(search_org_path)
123 | return r
124 |
125 | def switch_organization(self, organization_id):
126 | """
127 |
128 | :param organization_id:
129 | :return:
130 | """
131 | switch_user_organization = "/user/using/%s" % organization_id
132 | r = self.api.POST(switch_user_organization)
133 | return r
134 |
135 | def organization_user_list(self, organization_id):
136 | """
137 |
138 | :param organization_id:
139 | :return:
140 | """
141 | users_in_org = "/orgs/%s/users" % organization_id
142 | r = self.api.GET(users_in_org)
143 | return r
144 |
145 | def organization_user_add(self, organization_id, user):
146 | """
147 |
148 | :param organization_id:
149 | :param user:
150 | :return:
151 | """
152 | add_user_path = "/orgs/%s/users" % organization_id
153 | r = self.api.POST(add_user_path, json=user)
154 | return r
155 |
156 | def organization_user_update(self, organization_id, user_id, user_role):
157 | """
158 |
159 | :param organization_id:
160 | :param user_id:
161 | :param user_role:
162 | :return:
163 | """
164 | patch_user = "/orgs/%s/users/%s" % (organization_id, user_id)
165 | r = self.api.PATCH(patch_user, json={"role": user_role})
166 | return r
167 |
168 | def organization_user_delete(self, organization_id, user_id):
169 | """
170 |
171 | :param organization_id:
172 | :param user_id:
173 | :return:
174 | """
175 | delete_user = "/orgs/%s/users/%s" % (organization_id, user_id)
176 | r = self.api.DELETE(delete_user)
177 | return r
178 |
179 | def organization_preference_get(self):
180 | """
181 | :return:
182 | """
183 | update_preference = "/org/preferences"
184 | r = self.api.GET(update_preference)
185 | return r
186 |
187 | def organization_preference_update(
188 | self, theme="", home_dashboard_id=0, timezone="utc"
189 | ):
190 | """
191 |
192 | :param theme:
193 | :param home_dashboard_id:
194 | :param timezone:
195 | :return:
196 | """
197 | update_preference = "/org/preferences"
198 | r = self.api.PUT(
199 | update_preference,
200 | json={
201 | "theme": theme,
202 | "homeDashboardId": home_dashboard_id,
203 | "timezone": timezone,
204 | },
205 | )
206 | return r
207 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | ## [Unreleased]
3 |
4 |
5 |
6 | ## [0.8.1] - 2019-07-20
7 | ### Chore
8 | - **changelog:** 0.8.0
9 | - **dep:** remove Pipfile, now in .gitignore
10 | - **dep:** Python 2 compatible pylint version
11 | - **release:** release body with latest changes
12 | - **test:** run all the tests
13 | - **ver:** 0.8.1
14 |
15 |
16 |
17 | ## [0.8.0] - 2019-07-20
18 | ### Chore
19 | - **changelog:** 0.7.5
20 | - **dep:** improve dependency handling
21 |
22 | ### Feat
23 | - **circleci:** test on Python 2.7, 3.6 & 3.7
24 | - **dep:** adding missing dependency for Python 2 testing
25 | - **travis:** remove
26 |
27 |
28 |
29 | ## [0.7.5] - 2019-06-06
30 | ### Chore
31 | - **changelog:** 0.7.4
32 |
33 |
34 |
35 | ## [0.7.4] - 2019-06-05
36 | ### Chore
37 | - **changelog:** 0.7.3
38 | - **codestyle:** reformat
39 | - **dep:** update
40 | - **deps:** bump requests from 2.21.0 to 2.22.0
41 |
42 | ### Fix
43 | - **api:** python2 Teams support, fix [#24](https://github.com/m0nhawk/grafana_api/issues/24)
44 |
45 |
46 |
47 | ## [0.7.3] - 2019-05-04
48 | ### Chore
49 | - **changelog:** 0.7.2
50 |
51 |
52 |
53 | ## [0.7.2] - 2019-04-28
54 | ### Chore
55 | - **changelog:** v0.7.1
56 |
57 |
58 |
59 | ## [0.7.1] - 2019-04-22
60 | ### Chore
61 | - **dependency:** fix "urllib3" version
62 |
63 | ### Feat
64 | - **circleci:** remove debug statements
65 |
66 | ### Fix
67 | - **README:** remove coveralls badge
68 | - **circleci:** do not put VERSION file to Github release
69 |
70 |
71 |
72 | ## [0.7.0] - 2019-04-05
73 | ### Feat
74 | - **auto-deploy:** build package automatically
75 | - **circleci:** pre-create git version
76 |
77 | ### Fix
78 | - **organization:** fix [#11](https://github.com/m0nhawk/grafana_api/issues/11), rename to “Organization” the same as in Grafana API
79 |
80 |
81 |
82 | ## [0.6.0] - 2019-03-31
83 | ### Chore
84 | - **anaconda:** anaconda package configuration
85 | - **deploy:** add Python package deployment
86 | - **gitlab:** remove Gitlab CI
87 | - **setup:** more configurable Python package configuration
88 |
89 | ### Feat
90 | - **deploy:** setup Github actions for deployment
91 | - **test:** store Artifacts
92 | - **test:** JUnit reporting for CircleCI
93 |
94 | ### Test
95 | - **codecov:** update badge
96 | - **codecov:** move to Codecov
97 | - **coverage:** alternative Python library for coveralls
98 | - **coverage:** remove sonacloud
99 | - **coverage:** include only “grafana_api” folder for coverage
100 | - **coverage:** coveralls support
101 | - **gitlab:** update Gitlab CI
102 | - **test:** test file
103 | - **test:** remove test file
104 |
105 |
106 |
107 | ## [v0.5.2] - 2019-02-26
108 |
109 |
110 | ## [v0.5.1] - 2019-02-04
111 |
112 |
113 | ## [v0.5.0] - 2018-11-25
114 |
115 |
116 | ## [v0.3.5] - 2018-11-24
117 | ### Workaround
118 | - always deploy
119 |
120 |
121 |
122 | ## [v0.3.4] - 2018-11-24
123 |
124 |
125 | ## [v0.3.3] - 2018-11-24
126 | ### Feat
127 | - **tests:** improve coverage for SonarCloud
128 |
129 |
130 |
131 | ## [v0.3.1] - 2018-11-24
132 |
133 |
134 | ## [v0.3.0] - 2018-11-24
135 |
136 |
137 | ## [v0.2.9] - 2018-11-24
138 |
139 |
140 | ## [v0.2.8] - 2018-11-24
141 |
142 |
143 | ## [v0.2.6] - 2018-11-24
144 |
145 |
146 | ## [v0.2.2] - 2018-11-24
147 |
148 |
149 | ## [v0.2.1] - 2018-11-24
150 |
151 |
152 | ## [v0.2.0] - 2018-11-24
153 |
154 |
155 | ## [v0.1.7] - 2018-11-24
156 |
157 |
158 | ## [v0.1.6] - 2018-11-24
159 |
160 |
161 | ## [v0.1.5] - 2018-11-24
162 |
163 |
164 | ## [v0.1.4] - 2018-11-24
165 |
166 |
167 | ## [v0.1.3] - 2018-11-24
168 |
169 |
170 | ## v0.3.6 - 2018-11-03
171 | ### Feat
172 | - **tests:** improve coverage for SonarCloud
173 |
174 | ### Workaround
175 | - always deploy
176 |
177 |
178 | [Unreleased]: https://github.com/m0nhawk/grafana_api/compare/0.8.1...HEAD
179 | [0.8.1]: https://github.com/m0nhawk/grafana_api/compare/0.8.0...0.8.1
180 | [0.8.0]: https://github.com/m0nhawk/grafana_api/compare/0.7.5...0.8.0
181 | [0.7.5]: https://github.com/m0nhawk/grafana_api/compare/0.7.4...0.7.5
182 | [0.7.4]: https://github.com/m0nhawk/grafana_api/compare/0.7.3...0.7.4
183 | [0.7.3]: https://github.com/m0nhawk/grafana_api/compare/0.7.2...0.7.3
184 | [0.7.2]: https://github.com/m0nhawk/grafana_api/compare/0.7.1...0.7.2
185 | [0.7.1]: https://github.com/m0nhawk/grafana_api/compare/0.7.0...0.7.1
186 | [0.7.0]: https://github.com/m0nhawk/grafana_api/compare/0.6.0...0.7.0
187 | [0.6.0]: https://github.com/m0nhawk/grafana_api/compare/v0.5.2...0.6.0
188 | [v0.5.2]: https://github.com/m0nhawk/grafana_api/compare/v0.5.1...v0.5.2
189 | [v0.5.1]: https://github.com/m0nhawk/grafana_api/compare/v0.5.0...v0.5.1
190 | [v0.5.0]: https://github.com/m0nhawk/grafana_api/compare/v0.3.5...v0.5.0
191 | [v0.3.5]: https://github.com/m0nhawk/grafana_api/compare/v0.3.4...v0.3.5
192 | [v0.3.4]: https://github.com/m0nhawk/grafana_api/compare/v0.3.3...v0.3.4
193 | [v0.3.3]: https://github.com/m0nhawk/grafana_api/compare/v0.3.1...v0.3.3
194 | [v0.3.1]: https://github.com/m0nhawk/grafana_api/compare/v0.3.0...v0.3.1
195 | [v0.3.0]: https://github.com/m0nhawk/grafana_api/compare/v0.2.9...v0.3.0
196 | [v0.2.9]: https://github.com/m0nhawk/grafana_api/compare/v0.2.8...v0.2.9
197 | [v0.2.8]: https://github.com/m0nhawk/grafana_api/compare/v0.2.6...v0.2.8
198 | [v0.2.6]: https://github.com/m0nhawk/grafana_api/compare/v0.2.2...v0.2.6
199 | [v0.2.2]: https://github.com/m0nhawk/grafana_api/compare/v0.2.1...v0.2.2
200 | [v0.2.1]: https://github.com/m0nhawk/grafana_api/compare/v0.2.0...v0.2.1
201 | [v0.2.0]: https://github.com/m0nhawk/grafana_api/compare/v0.1.7...v0.2.0
202 | [v0.1.7]: https://github.com/m0nhawk/grafana_api/compare/v0.1.6...v0.1.7
203 | [v0.1.6]: https://github.com/m0nhawk/grafana_api/compare/v0.1.5...v0.1.6
204 | [v0.1.5]: https://github.com/m0nhawk/grafana_api/compare/v0.1.4...v0.1.5
205 | [v0.1.4]: https://github.com/m0nhawk/grafana_api/compare/v0.1.3...v0.1.4
206 | [v0.1.3]: https://github.com/m0nhawk/grafana_api/compare/v0.3.6...v0.1.3
207 |
--------------------------------------------------------------------------------
/test/api/test_annotations.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import requests_mock
4 |
5 | from grafana_api.grafana_face import GrafanaFace
6 | from grafana_api.grafana_api import GrafanaServerError,GrafanaClientError,GrafanaUnauthorizedError,GrafanaBadInputError
7 |
8 |
9 | class AnnotationsTestCase(unittest.TestCase):
10 | def setUp(self):
11 | self.cli = GrafanaFace(
12 | ("admin", "admin"), host="localhost", url_path_prefix="", protocol="http"
13 | )
14 |
15 | @requests_mock.Mocker()
16 | def test_annotations(self, m):
17 | m.get(
18 | "http://localhost/api/annotations?time_from=1563183710618&time_to=1563185212275"
19 | "&alertId=11&dashboardID=111&panelId=22&tags=tags-test&limit=1",
20 | json=[
21 | {
22 | "id": 80,
23 | "alertId": 11,
24 | "alertName": "",
25 | "dashboardId": 111,
26 | "panelId": 22,
27 | "userId": 0,
28 | "newState": "",
29 | "prevState": "",
30 | "created": 1563280160455,
31 | "updated": 1563280160455,
32 | "time": 1563156456006,
33 | "text": "Annotation Description",
34 | "regionId": 79,
35 | "tags": [
36 | "tags-test"
37 | ],
38 | "login": "",
39 | "email": "",
40 | "avatarUrl": "",
41 | "data": {}
42 | },
43 | ]
44 | )
45 | annotations = self.cli.annotations.get_annotation(time_from=1563183710618, time_to=1563185212275, alert_id=11,
46 | dashboard_id=111, panel_id=22, tags="tags-test", limit=1)
47 | self.assertEqual(annotations[0]["text"], "Annotation Description")
48 | self.assertEqual(annotations[0]["alertId"], 11)
49 | self.assertEqual(annotations[0]["dashboardId"], 111)
50 | self.assertEqual(annotations[0]["panelId"], 22)
51 | self.assertEqual(annotations[0]["tags"][0], "tags-test")
52 |
53 | self.assertEqual(len(annotations), 1)
54 |
55 | @requests_mock.Mocker()
56 | def test_annotations_with_out_param(self, m):
57 | m.get(
58 | "http://localhost/api/annotations",
59 | json=[
60 | {
61 | "id": 80,
62 | "alertId": 11,
63 | "alertName": "",
64 | "dashboardId": 111,
65 | "panelId": 22,
66 | "userId": 0,
67 | "newState": "",
68 | "prevState": "",
69 | "created": 1563280160455,
70 | "updated": 1563280160455,
71 | "time": 1563156456006,
72 | "text": "Annotation Description",
73 | "regionId": 79,
74 | "tags": [
75 | "tags-test"
76 | ],
77 | "login": "",
78 | "email": "",
79 | "avatarUrl": "",
80 | "data": {}
81 | },
82 | ]
83 | )
84 | annotations = self.cli.annotations.get_annotation()
85 | self.assertEqual(len(annotations), 1)
86 |
87 | @requests_mock.Mocker()
88 | def test_delete_annotations_by_region_id(self, m):
89 | m.delete("http://localhost/api/annotations/region/99", json={"message": "Annotation region deleted"})
90 | response = self.cli.annotations.delete_annotations_by_region_id(99)
91 | self.assertEqual(response['message'], "Annotation region deleted")
92 |
93 | @requests_mock.Mocker()
94 | def test_delete_annotations_by_id(self, m):
95 | m.delete('http://localhost/api/annotations/99', json={"message": "Annotation deleted"})
96 | annotation = self.cli.annotations.delete_annotations_by_id(annotations_id=99)
97 | self.assertEqual(annotation['message'], "Annotation deleted")
98 |
99 | @requests_mock.Mocker()
100 | def test_delete_annotations_by_id_could_not_find(self, m):
101 | m.delete("http://localhost/api/annotations/None", json={"message": "Could not find annotation to update"},status_code=500)
102 | response = self.cli.annotations.delete_annotations_by_id(annotations_id=None)
103 | self.assertRaises(GrafanaServerError)
104 |
105 | @requests_mock.Mocker()
106 | def test_delete_annotations_by_id_forbidden(self, m):
107 | m.delete("http://localhost/api/annotations/None", json={"message": "Forbidden"},
108 | status_code=403)
109 | response = self.cli.annotations.delete_annotations_by_id(annotations_id=None)
110 | self.assertRaises(GrafanaClientError)
111 |
112 | @requests_mock.Mocker()
113 | def test_delete_annotations_by_id_unauthorized(self, m):
114 | m.delete("http://localhost/api/annotations/None", json={"message": "Unauthorized"},
115 | status_code=401)
116 | response = self.cli.annotations.delete_annotations_by_id(annotations_id=None)
117 | self.assertRaises(GrafanaUnauthorizedError)
118 |
119 | @requests_mock.Mocker()
120 | def test_delete_annotations_by_id_bad_input(self, m):
121 | m.delete("http://localhost/api/annotations/None", json={"message": "Bad Input"},
122 | status_code=400)
123 | response = self.cli.annotations.delete_annotations_by_id(annotations_id=None)
124 | self.assertRaises(GrafanaBadInputError)
125 |
126 |
127 | @requests_mock.Mocker()
128 | def test_add_annotation(self, m):
129 | m.post(
130 | "http://localhost/api/annotations",
131 | json={"endId": 80, "id": 79, "message": "Annotation added"},
132 | )
133 | annotation = self.cli.annotations.add_annotation(time_from=1563183710618, time_to=1563185212275
134 | , is_region=True, tags="tags-test", text="Test")
135 | self.assertEqual(annotation["endId"], 80)
136 | self.assertEqual(annotation["id"], 79)
137 | self.assertEqual(annotation["message"], "Annotation added")
138 |
139 | @requests_mock.Mocker()
140 | def test_update_annotation(self, m):
141 | m.put(
142 | "http://localhost/api/annotations",
143 | json={"endId": 80, "id": 79, "message": "Annotation updated"},
144 | )
145 | annotation = self.cli.annotations.update_annotation(time_from=1563183710618, time_to=1563185212275
146 | , is_region=True, tags="tags-test", text="Test")
147 | self.assertEqual(annotation["endId"], 80)
148 | self.assertEqual(annotation["id"], 79)
149 | self.assertEqual(annotation["message"], "Annotation updated")
150 |
151 | @requests_mock.Mocker()
152 | def test_add_annotation_graphite(self, m):
153 | m.post(
154 | "http://localhost/api/annotations/graphite",
155 | json={"message": "Graphite annotation added", "id": 1},
156 | )
157 | annotation = self.cli.annotations.add_annotation_graphite(what="Event - deploy", tags="deploy, production",
158 | when=1467844481, data="Data")
159 |
160 | self.assertEqual(annotation["id"], 1)
161 | self.assertEqual(annotation["message"], "Graphite annotation added")
162 |
--------------------------------------------------------------------------------
/test/api/test_dashboard.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import requests_mock
4 |
5 | from grafana_api.grafana_face import GrafanaFace
6 |
7 |
8 | class DashboardTestCase(unittest.TestCase):
9 | def setUp(self):
10 | self.cli = GrafanaFace(
11 | ("admin", "admin"), host="localhost", url_path_prefix="", protocol="http"
12 | )
13 |
14 | @requests_mock.Mocker()
15 | def test_get_dashboard(self, m):
16 | m.get(
17 | "http://localhost/api/dashboards/uid/cIBgcSjkk",
18 | json={
19 | "dashboard": {
20 | "id": 1,
21 | "uid": "cIBgcSjkk",
22 | "title": "Production Overview",
23 | "tags": ["templated"],
24 | "timezone": "browser",
25 | "schemaVersion": 16,
26 | "version": 0
27 | },
28 | "meta": {
29 | "isStarred": 'false',
30 | "url": "/d/cIBgcSjkk/production-overview",
31 | "slug": "production-overview"
32 | }
33 | }
34 | )
35 | dashboard = self.cli.dashboard.get_dashboard("cIBgcSjkk")
36 | self.assertEqual(dashboard["dashboard"]["uid"], "cIBgcSjkk")
37 |
38 | @requests_mock.Mocker()
39 | def test_update_dashboard(self, m):
40 | m.post(
41 | "http://localhost/api/dashboards/db",
42 | json={
43 | "id": 1,
44 | "uid": "cIBgcSjkk",
45 | "url": "/d/cIBgcSjkk/production-overview",
46 | "status": "success",
47 | "version": 1,
48 | "slug": "production-overview"
49 | }
50 | )
51 | dashboard = self.cli.dashboard.update_dashboard({
52 | "dashboard": {
53 | "id": 1,
54 | "uid": 'cIBgcSjkk',
55 | "title": "Production Overview",
56 | "tags": ["templated"],
57 | "timezone": "browser",
58 | "schemaVersion": 16,
59 | "version": 0
60 | },
61 | "folderId": 0,
62 | "overwrite": 'false'
63 | })
64 |
65 | self.assertEqual(dashboard["uid"], "cIBgcSjkk")
66 | self.assertEqual(dashboard["status"], "success")
67 |
68 | @requests_mock.Mocker()
69 | def test_get_home_dashboard(self, m):
70 | m.get(
71 | "http://localhost/api/dashboards/home",
72 | json={
73 | "dashboard": {
74 | "editable": 'false',
75 | "hideControls": 'true',
76 | "nav": [
77 | {
78 | "enable": 'false',
79 | "type": "timepicker"
80 | }
81 | ],
82 | "style": "dark",
83 | "tags": [],
84 | "templating": {
85 | "list": [
86 | ]
87 | },
88 | "time": {
89 | },
90 | "timezone": "browser",
91 | "title": "Home",
92 | "version": 5
93 | },
94 | "meta": {
95 | "isHome": 'true',
96 | "canSave": 'false',
97 | "canEdit": 'false',
98 | "canStar": 'false',
99 | "url": "",
100 | "expires": "0001-01-01T00:00:00Z",
101 | "created": "0001-01-01T00:00:00Z"
102 | }
103 | }
104 | )
105 | dashboard = self.cli.dashboard.get_home_dashboard()
106 | self.assertEqual(dashboard["meta"]["isHome"], "true")
107 |
108 | @requests_mock.Mocker()
109 | def test_delete_dashboard(self, m):
110 | m.delete("http://localhost/api/dashboards/uid/cIBgcSjkk", json={"title": "Production Overview"})
111 | response = self.cli.dashboard.delete_dashboard("cIBgcSjkk")
112 | self.assertEqual(response['title'], "Production Overview")
113 |
114 | @requests_mock.Mocker()
115 | def test_get_dashboards_tags(self, m):
116 | m.get(
117 | "http://localhost/api/dashboards/tags",
118 | json=[
119 | {
120 | "term": "tag1",
121 | "count": 1
122 | },
123 | {
124 | "term": "tag2",
125 | "count": 4
126 | }
127 | ]
128 | )
129 | tags = self.cli.dashboard.get_dashboards_tags()
130 | self.assertEqual(len(tags), 2)
131 | self.assertEqual(tags[0]["term"], "tag1")
132 |
133 | @requests_mock.Mocker()
134 | def test_get_dashboard_permissions(self, m):
135 | m.get(
136 | "http://localhost/api/dashboards/id/1/permissions",
137 | json=[
138 | {
139 | "id": 1,
140 | "dashboardId": 1,
141 | "created": "2017-06-20T02:00:00+02:00",
142 | "updated": "2017-06-20T02:00:00+02:00",
143 | "userId": 0,
144 | "userLogin": "",
145 | "userEmail": "",
146 | "teamId": 0,
147 | "team": "",
148 | "role": "Viewer",
149 | "permission": 1,
150 | "permissionName": "View",
151 | "uid": "",
152 | "title": "",
153 | "slug": "",
154 | "isFolder": 'false',
155 | "url": ""
156 | },
157 | {
158 | "id": 2,
159 | "dashboardId": 1,
160 | "created": "2017-06-20T02:00:00+02:00",
161 | "updated": "2017-06-20T02:00:00+02:00",
162 | "userId": 0,
163 | "userLogin": "",
164 | "userEmail": "",
165 | "teamId": 0,
166 | "team": "",
167 | "role": "Editor",
168 | "permission": 2,
169 | "permissionName": "Edit",
170 | "uid": "",
171 | "title": "",
172 | "slug": "",
173 | "isFolder": 'false',
174 | "url": ""
175 | }
176 | ]
177 | )
178 | permissions = self.cli.dashboard.get_dashboard_permissions(1)
179 | self.assertEqual(len(permissions), 2)
180 | self.assertEqual(permissions[0]["dashboardId"], 1)
181 |
182 | @requests_mock.Mocker()
183 | def test_update_dashboard_permissions(self, m):
184 | m.post(
185 | "http://localhost/api/dashboards/id/1/permissions",
186 | json={"message": "Dashboard permissions updated"}
187 | )
188 | permissions = self.cli.dashboard.update_dashboard_permissions(1,{
189 | "items": [
190 | {
191 | "role": "Viewer",
192 | "permission": 1
193 | },
194 | {
195 | "role": "Editor",
196 | "permission": 2
197 | },
198 | {
199 | "teamId": 1,
200 | "permission": 1
201 | },
202 | {
203 | "userId": 11,
204 | "permission": 4
205 | }
206 | ]
207 | })
208 | self.assertEqual(permissions['message'], "Dashboard permissions updated")
209 |
--------------------------------------------------------------------------------
/test/api/test_team.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import requests_mock
4 |
5 | from grafana_api.grafana_face import GrafanaFace
6 |
7 |
8 | class TeamsTestCase(unittest.TestCase):
9 | def setUp(self):
10 | self.cli = GrafanaFace(
11 | ("admin", "admin"), host="localhost", url_path_prefix="", protocol="http"
12 | )
13 |
14 | @requests_mock.Mocker()
15 | def test_search_teams_url_encodes_query(self, m):
16 | m.get(
17 | "http://localhost/api/teams/search?query=my%20team&page=1",
18 | json={
19 | "totalCount": 1,
20 | "teams": [
21 | {
22 | "id": 1,
23 | "orgId": 1,
24 | "name": "MyTestTeam",
25 | "email": "",
26 | "avatarUrl": "/avatar/3f49c15916554246daa714b9bd0ee398",
27 | "memberCount": 1,
28 | }
29 | ],
30 | "page": 1,
31 | "perPage": 1000,
32 | },
33 | )
34 | teams = self.cli.teams.search_teams("my team")
35 | self.assertEqual(teams[0]["name"], "MyTestTeam")
36 | self.assertEqual(len(teams), 1)
37 |
38 | @requests_mock.Mocker()
39 | def test_search_teams_loads_all_pages(self, m):
40 | m.get(
41 | "http://localhost/api/teams/search?query=team&page=1&perpage=1",
42 | json={
43 | "totalCount": 2,
44 | "teams": [
45 | {
46 | "id": 1,
47 | "orgId": 1,
48 | "name": "MyTestTeam",
49 | "email": "",
50 | "avatarUrl": "/avatar/3f49c15916554246daa714b9bd0ee398",
51 | "memberCount": 1,
52 | }
53 | ],
54 | "page": 1,
55 | "perPage": 1,
56 | },
57 | )
58 |
59 | m.get(
60 | "http://localhost/api/teams/search?query=team&page=2&perpage=1",
61 | json={
62 | "totalCount": 2,
63 | "teams": [
64 | {
65 | "id": 2,
66 | "orgId": 1,
67 | "name": "SecondTeam",
68 | "email": "",
69 | "avatarUrl": "/avatar/3f49c15916554246daa714b9bd0ee398",
70 | "memberCount": 23,
71 | }
72 | ],
73 | "page": 2,
74 | "perPage": 1,
75 | },
76 | )
77 | teams = self.cli.teams.search_teams("team",perpage=1)
78 | self.assertEqual(teams[0]["name"], "MyTestTeam")
79 | self.assertEqual(teams[1]["name"], "SecondTeam")
80 | self.assertEqual(len(teams), 2)
81 |
82 | @requests_mock.Mocker()
83 | def test_search_teams_only_loads_requested_page(self, m):
84 | m.get(
85 | "http://localhost/api/teams/search?query=my%20team&page=2",
86 | json={
87 | "totalCount": 10,
88 | "teams": [
89 | {
90 | "id": 2,
91 | "orgId": 1,
92 | "name": "MyTestTeam",
93 | "email": "",
94 | "avatarUrl": "/avatar/3f49c15916554246daa714b9bd0ee398",
95 | "memberCount": 1,
96 | }
97 | ],
98 | "page": 1,
99 | "perPage": 1,
100 | },
101 | )
102 | teams = self.cli.teams.search_teams("my team", 2)
103 | self.assertEqual(teams[0]["name"], "MyTestTeam")
104 | self.assertEqual(len(teams), 1)
105 |
106 | @requests_mock.Mocker()
107 | def test_get_team_by_name(self, m):
108 | m.get(
109 | "http://localhost/api/teams/search?name=my%20team",
110 | json={
111 | "totalCount": 1,
112 | "teams": [
113 | {
114 | "id": 2,
115 | "orgId": 1,
116 | "name": "my team",
117 | "email": "",
118 | "avatarUrl": "/avatar/3f49c15916554246daa714b9bd0ee398",
119 | "memberCount": 1,
120 | }
121 | ],
122 | "page": 1,
123 | "perPage": 1000,
124 | },
125 | )
126 | teams = self.cli.teams.get_team_by_name("my team")
127 | self.assertEqual(teams[0]["name"], "my team")
128 | self.assertEqual(len(teams), 1)
129 |
130 | @requests_mock.Mocker()
131 | def test_get_team(self, m):
132 | m.get(
133 | "http://localhost/api/teams/1",
134 | json={
135 | "id": 1,
136 | "orgId": 1,
137 | "name": "MyTestTeam",
138 | "email": "",
139 | "created": "2017-12-15T10:40:45+01:00",
140 | "updated": "2017-12-15T10:40:45+01:00",
141 | },
142 | )
143 | team = self.cli.teams.get_team("1")
144 | self.assertEqual(team["name"], "MyTestTeam")
145 |
146 | @requests_mock.Mocker()
147 | def test_add_team(self, m):
148 | m.post(
149 | "http://localhost/api/teams", json={"message": "Team created", "teamId": 2}
150 | )
151 | team = {"name": "MySecondTestTeam", "email": "email@test.com"}
152 | new_team = self.cli.teams.add_team(team)
153 | self.assertEqual(new_team["teamId"], 2)
154 |
155 | @requests_mock.Mocker()
156 | def test_update_team(self, m):
157 | m.put("http://localhost/api/teams/3", json={"message": "Team updated"})
158 | team = {"name": "MyThirdTestTeam", "email": "email@test.com"}
159 | response = self.cli.teams.update_team(3, team)
160 | self.assertEqual(response["message"], "Team updated")
161 |
162 | @requests_mock.Mocker()
163 | def test_delete_team(self, m):
164 | m.delete("http://localhost/api/teams/3", json={"message": "Team deleted"})
165 | response = self.cli.teams.delete_team(3)
166 | self.assertEqual(response, True)
167 |
168 | @requests_mock.Mocker()
169 | def test_get_team_members(self, m):
170 | m.get(
171 | "http://localhost/api/teams/1/members",
172 | json=[
173 | {
174 | "orgId": 1,
175 | "teamId": 1,
176 | "userId": 3,
177 | "email": "user1@email.com",
178 | "login": "user1",
179 | "avatarUrl": "/avatar/1b3c32f6386b0185c40d359cdc733a79",
180 | }
181 | ],
182 | )
183 | members = self.cli.teams.get_team_members("1")
184 | self.assertEqual(members[0]["login"], "user1")
185 |
186 | @requests_mock.Mocker()
187 | def test_add_team_member(self, m):
188 | m.post(
189 | "http://localhost/api/teams/1/members",
190 | json={"message": "Member added to Team"},
191 | )
192 | history = m.request_history
193 | add_res = self.cli.teams.add_team_member("1", "3")
194 | self.assertEqual(history[0].json()["userId"], "3")
195 | self.assertEqual(add_res["message"], "Member added to Team")
196 |
197 | @requests_mock.Mocker()
198 | def test_remove_team_member(self, m):
199 | m.delete(
200 | "http://localhost/api/teams/13/members/2",
201 | json={"message": "Team member removed"},
202 | )
203 | remove_res = self.cli.teams.remove_team_member("13", "2")
204 | self.assertEqual(remove_res["message"], "Team member removed")
205 |
206 | @requests_mock.Mocker()
207 | def test_get_team_preferences(self, m):
208 | m.get(
209 | "http://localhost/api/teams/1/preferences",
210 | json={"theme": "", "homeDashboardId": 0, "timezone": ""},
211 | )
212 | prefs = self.cli.teams.get_team_preferences("1")
213 | self.assertEqual(prefs["homeDashboardId"], 0)
214 |
215 | @requests_mock.Mocker()
216 | def test_update_team_preferences(self, m):
217 | m.put(
218 | "http://localhost/api/teams/1/preferences",
219 | json={"message": "Preferences updated"},
220 | )
221 | prefs = {"theme": "light", "homeDashboardId": 0, "timezone": ""}
222 |
223 | updates = self.cli.teams.update_team_preferences("1", prefs)
224 | history = m.request_history
225 | json_payload = history[0].json()
226 | self.assertEqual(json_payload["theme"], "light")
227 | self.assertEqual(updates["message"], "Preferences updated")
228 |
--------------------------------------------------------------------------------
/test/api/test_organization.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import requests_mock
4 |
5 | from grafana_api.grafana_face import GrafanaFace
6 |
7 |
8 | class OrganizationTestCase(unittest.TestCase):
9 | def setUp(self):
10 | self.cli = GrafanaFace(
11 | ("admin", "admin"), host="localhost", url_path_prefix="", protocol="http"
12 | )
13 |
14 | @requests_mock.Mocker()
15 | def test_delete_snapshot_by_key(self, m):
16 | m.delete('http://localhost/api/orgs/1/users/2', json={"message": "User removed from organization"})
17 | annotation = self.cli.organizations.organization_user_delete(organization_id=1, user_id=2)
18 | self.assertEqual(annotation['message'], "User removed from organization")
19 |
20 | @requests_mock.Mocker()
21 | def test_organization_preference_get(self, m):
22 | m.get(
23 | "http://localhost/api/org/preferences",
24 | json={"theme": "", "homeDashboardId": 0, "timezone": ""}
25 | )
26 |
27 | result = self.cli.organizations.organization_preference_get()
28 | self.assertEqual(result["homeDashboardId"], 0)
29 |
30 | @requests_mock.Mocker()
31 | def test_organization_preference_update(self, m):
32 | m.put(
33 | "http://localhost/api/org/preferences",
34 | json={"message": "Preferences updated"}
35 | )
36 | preference = self.cli.organizations.organization_preference_update(theme="", home_dashboard_id=0,
37 | timezone="utc")
38 | self.assertEqual(preference["message"], "Preferences updated")
39 |
40 | @requests_mock.Mocker()
41 | def test_organization_user_update(self, m):
42 | m.patch(
43 | "http://localhost/api/orgs/1/users/2",
44 | json={"message": "Organization user updated"}
45 | )
46 | preference = self.cli.organizations.organization_user_update(organization_id=1, user_id=2, user_role="Admin")
47 | self.assertEqual(preference["message"], "Organization user updated")
48 |
49 | @requests_mock.Mocker()
50 | def test_organization_user_add(self, m):
51 | m.post(
52 | "http://localhost/api/orgs/1/users",
53 | json={"message": "User added to organization"}
54 | )
55 | preference = self.cli.organizations.organization_user_add(organization_id=1, user={
56 | "loginOrEmail": "user",
57 | "role": "Viewer"
58 | })
59 | self.assertEqual(preference["message"], "User added to organization")
60 |
61 | @requests_mock.Mocker()
62 | def test_organization_user_list(self, m):
63 | m.get(
64 | "http://localhost/api/orgs/1/users",
65 | json=[
66 | {
67 | "orgId": 1,
68 | "userId": 1,
69 | "email": "admin@mygraf.com",
70 | "login": "admin",
71 | "role": "Admin"
72 | }
73 | ]
74 | )
75 | users = self.cli.organizations.organization_user_list(organization_id=1)
76 | self.assertEqual(len(users), 1)
77 |
78 | @requests_mock.Mocker()
79 | def test_list_organization(self, m):
80 | m.get(
81 | "http://localhost/api/orgs",
82 | json=[
83 | {
84 | "orgId": 1,
85 | "userId": 1,
86 | "email": "admin@mygraf.com",
87 | "login": "admin",
88 | "role": "Admin"
89 | }
90 | ]
91 | )
92 | users = self.cli.organizations.list_organization()
93 | self.assertEqual(len(users), 1)
94 |
95 | @requests_mock.Mocker()
96 | def test_get_current_organization(self, m):
97 | m.get(
98 | "http://localhost/api/org",
99 | json={
100 | "id": 1,
101 | "name": "Main Org."
102 | }
103 | )
104 | orgs = self.cli.organization.get_current_organization()
105 | self.assertEqual(orgs['name'], "Main Org.")
106 |
107 | @requests_mock.Mocker()
108 | def test_update_current_organization(self, m):
109 | m.put(
110 | "http://localhost/api/org",
111 | json={"message": "Organization updated"}
112 | )
113 | org = self.cli.organization.update_current_organization(organization={
114 | "name": "Main Org."
115 | })
116 | self.assertEqual(org['message'], "Organization updated")
117 |
118 | @requests_mock.Mocker()
119 | def test_update_organization(self, m):
120 | m.put(
121 | "http://localhost/api/orgs/1",
122 | json={"message": "Organization updated"}
123 | )
124 | preference = self.cli.organizations.update_organization(organization_id=1, organization={
125 | "name": "Main Org 2."
126 | })
127 | self.assertEqual(preference["message"], "Organization updated")
128 |
129 | @requests_mock.Mocker()
130 | def test_delete_organization(self, m):
131 | m.delete(
132 | "http://localhost/api/orgs/1",
133 | json={"message": "Organization deleted"}
134 | )
135 | preference = self.cli.organizations.delete_organization(organization_id=1)
136 | self.assertEqual(preference["message"], "Organization deleted")
137 |
138 | @requests_mock.Mocker()
139 | def test_create_organization(self, m):
140 | m.post(
141 | "http://localhost/api/orgs",
142 | json={
143 | "orgId": "1",
144 | "message": "Organization created"
145 | }
146 | )
147 | preference = self.cli.organization.create_organization(organization={
148 | "name": "New Org."
149 | })
150 | self.assertEqual(preference["message"], "Organization created")
151 |
152 | @requests_mock.Mocker()
153 | def test_delete_user_current_organization(self, m):
154 | m.delete(
155 | "http://localhost/api/org/users/1",
156 | json={"message": "User removed from organization"}
157 | )
158 | preference = self.cli.organization.delete_user_current_organization(user_id=1)
159 | self.assertEqual(preference["message"], "User removed from organization")
160 |
161 | @requests_mock.Mocker()
162 | def test_add_user_current_organization(self, m):
163 | m.post(
164 | "http://localhost/api/org/users",
165 | json={"message": "User added to organization"}
166 | )
167 | preference = self.cli.organization.add_user_current_organization({
168 | "role": "Admin",
169 | "loginOrEmail": "admin"
170 | })
171 | self.assertEqual(preference["message"], "User added to organization")
172 |
173 | @requests_mock.Mocker()
174 | def test_update_user_current_organization(self, m):
175 | m.patch(
176 | "http://localhost/api/org/users/1",
177 | json={"message": "Organization user updated"}
178 | )
179 | preference = self.cli.organization.update_user_current_organization(user_id=1, user={
180 | "role": "Viewer",
181 | })
182 | self.assertEqual(preference["message"], "Organization user updated")
183 |
184 | @requests_mock.Mocker()
185 | def test_get_current_organization_users(self, m):
186 | m.get(
187 | "http://localhost/api/org/users",
188 | json=[
189 | {
190 | "orgId": 1,
191 | "userId": 1,
192 | "email": "admin@mygraf.com",
193 | "login": "admin",
194 | "role": "Admin"
195 | }
196 | ]
197 | )
198 | org = self.cli.organization.get_current_organization_users()
199 | self.assertEqual(len(org), 1)
200 |
201 | @requests_mock.Mocker()
202 | def test_find_organization(self, m):
203 | m.get(
204 | "http://localhost/api/orgs/name/Main",
205 | json=
206 | {
207 | "id": 1,
208 | "name": "Main Org.",
209 | "address": {
210 | "address1": "",
211 | "address2": "",
212 | "city": "",
213 | "zipCode": "",
214 | "state": "",
215 | "country": ""
216 | }
217 | }
218 | )
219 | org = self.cli.organization.find_organization(org_name="Main")
220 | self.assertEqual(org['id'], 1)
221 |
222 | @requests_mock.Mocker()
223 | def test_switch_organization(self, m):
224 | m.post(
225 | "http://localhost/api/user/using/2",
226 | json={"message":"Active organization changed"}
227 | )
228 | preference = self.cli.organizations.switch_organization(organization_id=2)
229 | self.assertEqual(preference["message"], "Active organization changed")
230 |
231 |
232 |
--------------------------------------------------------------------------------
/test/api/test_admin.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import requests_mock
4 |
5 | from grafana_api.grafana_face import GrafanaFace
6 |
7 |
8 | class AdminTestCase(unittest.TestCase):
9 | def setUp(self):
10 | self.cli = GrafanaFace(
11 | ("admin", "admin"), host="localhost", url_path_prefix="", protocol="http"
12 | )
13 |
14 | @requests_mock.Mocker()
15 | def test_settings(self, m):
16 | m.get(
17 | "http://localhost/api/admin/settings",
18 | json={
19 | "DEFAULT": {
20 | "app_mode": "production"
21 | },
22 | "analytics": {
23 | "google_analytics_ua_id": "",
24 | "reporting_enabled": "false"
25 | },
26 | "auth.anonymous": {
27 | "enabled": "true",
28 | "org_name": "Main Org.",
29 | "org_role": "Viewer"
30 | },
31 | "auth.basic": {
32 | "enabled": "false"
33 | },
34 | "auth.github": {
35 | "allow_sign_up": "false",
36 | "allowed_domains": "",
37 | "allowed_organizations": "",
38 | "api_url": "https://api.github.com/user",
39 | "auth_url": "https://github.com/login/oauth/authorize",
40 | "client_id": "some_id",
41 | "client_secret": "************",
42 | "enabled": "false",
43 | "scopes": "user:email,read:org",
44 | "team_ids": "",
45 | "token_url": "https://github.com/login/oauth/access_token"
46 | },
47 | "auth.google": {
48 | "allow_sign_up": "false", "allowed_domains": "",
49 | "api_url": "https://www.googleapis.com/oauth2/v1/userinfo",
50 | "auth_url": "https://accounts.google.com/o/oauth2/auth",
51 | "client_id": "some_client_id",
52 | "client_secret": "************",
53 | "enabled": "false",
54 | "scopes": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
55 | "token_url": "https://accounts.google.com/o/oauth2/token"
56 | },
57 | "auth.ldap": {
58 | "config_file": "/etc/grafana/ldap.toml",
59 | "enabled": "false"
60 | },
61 | "auth.proxy": {
62 | "auto_sign_up": "true",
63 | "enabled": "false",
64 | "header_name": "X-WEBAUTH-USER",
65 | "header_property": "username"
66 | },
67 | "dashboards.json": {
68 | "enabled": "false",
69 | "path": "/var/lib/grafana/dashboards"
70 | },
71 | "database": {
72 | "host": "127.0.0.1:0000",
73 | "name": "grafana",
74 | "password": "************",
75 | "path": "grafana.db",
76 | "ssl_mode": "disable",
77 | "type": "sqlite3",
78 | "user": "root"
79 | },
80 | "emails": {
81 | "templates_pattern": "emails/*.html",
82 | "welcome_email_on_sign_up": "false"
83 | },
84 | "log": {
85 | "buffer_len": "10000",
86 | "level": "Info",
87 | "mode": "file"
88 | },
89 | "log.console": {
90 | "level": ""
91 | },
92 | "log.file": {
93 | "daily_rotate": "true",
94 | "file_name": "",
95 | "level": "",
96 | "log_rotate": "true",
97 | "max_days": "7",
98 | "max_lines": "1000000",
99 | "max_lines_shift": "28",
100 | "max_size_shift": ""
101 | },
102 | "paths": {
103 | "data": "/tsdb/grafana",
104 | "logs": "/logs/apps/grafana"},
105 | "security": {
106 | "admin_password": "************",
107 | "admin_user": "admin",
108 | "cookie_remember_name": "grafana_remember",
109 | "cookie_username": "grafana_user",
110 | "disable_gravatar": "false",
111 | "login_remember_days": "7",
112 | "secret_key": "************"
113 | },
114 | "server": {
115 | "cert_file": "",
116 | "cert_key": "",
117 | "domain": "mygraf.com",
118 | "enable_gzip": "false",
119 | "enforce_domain": "false",
120 | "http_addr": "127.0.0.1",
121 | "http_port": "0000",
122 | "protocol": "http",
123 | "root_url": "%(protocol)s://%(domain)s:%(http_port)s/",
124 | "router_logging": "true",
125 | "data_proxy_logging": "true",
126 | "static_root_path": "public"
127 | },
128 | "session": {
129 | "cookie_name": "grafana_sess",
130 | "cookie_secure": "false",
131 | "gc_interval_time": "",
132 | "provider": "file",
133 | "provider_config": "sessions",
134 | "session_life_time": "86400"
135 | },
136 | "smtp": {
137 | "cert_file": "",
138 | "enabled": "false",
139 | "from_address": "admin@grafana.localhost",
140 | "from_name": "Grafana",
141 | "ehlo_identity": "dashboard.example.com",
142 | "host": "localhost:25",
143 | "key_file": "",
144 | "password": "************",
145 | "skip_verify": "false",
146 | "user": ""
147 | },
148 | "users": {
149 | "allow_org_create": "true",
150 | "allow_sign_up": "false",
151 | "auto_assign_org": "true",
152 | "auto_assign_org_role": "Viewer"
153 | }
154 | }
155 | )
156 | admin = self.cli.admin.settings()
157 | self.assertEqual(admin["users"]["allow_org_create"], "true")
158 |
159 | @requests_mock.Mocker()
160 | def test_stats(self, m):
161 | m.get(
162 | "http://localhost/api/admin/stats",
163 | json={
164 | "users": 2,
165 | "orgs": 1,
166 | "dashboards": 4,
167 | "snapshots": 2,
168 | "tags": 6,
169 | "datasources": 1,
170 | "playlists": 1,
171 | "stars": 2,
172 | "alerts": 2,
173 | "activeUsers": 1
174 | }
175 | )
176 | stats = self.cli.admin.stats()
177 | self.assertEqual(len(stats), 10)
178 |
179 | @requests_mock.Mocker()
180 | def test_create_user(self, m):
181 | m.post(
182 | "http://localhost/api/admin/users",
183 | json={"id": 5, "message": "User created"}
184 | )
185 | user = self.cli.admin.create_user({
186 | "name": "User",
187 | "email": "user@graf.com",
188 | "login": "user",
189 | "password": "userpassword"
190 | })
191 | self.assertEqual(user['message'], "User created")
192 |
193 | @requests_mock.Mocker()
194 | def test_change_user_password(self, m):
195 | m.put(
196 | "http://localhost/api/admin/users/2/password",
197 | json={"message": "User password updated"}
198 | )
199 | user = self.cli.admin.change_user_password(user_id=2, password="password")
200 | self.assertEqual(user['message'], "User password updated")
201 |
202 | @requests_mock.Mocker()
203 | def test_change_user_permissions(self, m):
204 | m.put(
205 | "http://localhost/api/admin/users/2/permissions",
206 | json={"message": "User permissions updated"}
207 | )
208 | user = self.cli.admin.change_user_permissions(user_id=2, is_grafana_admin=True)
209 | self.assertEqual(user['message'], "User permissions updated")
210 |
211 | @requests_mock.Mocker()
212 | def test_delete_user(self, m):
213 | m.delete(
214 | "http://localhost/api/admin/users/2",
215 | json={"message": "User deleted"}
216 | )
217 | user = self.cli.admin.delete_user(user_id=2)
218 | self.assertEqual(user['message'], "User deleted")
219 |
220 | @requests_mock.Mocker()
221 | def test_pause_all_alerts(self, m):
222 | m.post(
223 | "http://localhost/api/admin/pause-all-alerts",
224 | json={
225 | "state": "Paused",
226 | "message": "alert paused",
227 | "alertsAffected": 1
228 | }
229 | )
230 | pause = self.cli.admin.pause_all_alerts(pause='True')
231 | self.assertEqual(pause['message'], "alert paused")
232 |
--------------------------------------------------------------------------------
/test/api/test_folder.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | import requests_mock
4 |
5 | from grafana_api.grafana_face import GrafanaFace
6 | from grafana_api.grafana_api import GrafanaBadInputError
7 |
8 |
9 | class FolderTestCase(unittest.TestCase):
10 | def setUp(self):
11 | self.cli = GrafanaFace(
12 | ("admin", "admin"), host="localhost", url_path_prefix="", protocol="http"
13 | )
14 |
15 | @requests_mock.Mocker()
16 | def test_get_all_folders(self, m):
17 | m.get(
18 | "http://localhost/api/folders",
19 | json=[
20 | {
21 | "id": 1,
22 | "uid": "nErXDvCkzz",
23 | "title": "Departmenet ABC",
24 | "url": "/dashboards/f/nErXDvCkzz/department-abc",
25 | "hasAcl": "false",
26 | "canSave": "false",
27 | "canEdit": "false",
28 | "canAdmin": "false",
29 | "createdBy": "admin",
30 | "created": "2018-01-31T17:43:12+01:00",
31 | "updatedBy": "admin",
32 | "updated": "2018-01-31T17:43:12+01:00",
33 | "version": 1
34 | }
35 | ]
36 | )
37 | folders = self.cli.folder.get_all_folders()
38 | self.assertEqual(folders[0]["id"], 1)
39 | self.assertEqual(len(folders), 1)
40 |
41 | @requests_mock.Mocker()
42 | def test_get_folder(self, m):
43 | m.get(
44 | "http://localhost/api/folders/nErXDvCkzzh",
45 | json={
46 | "id": 1,
47 | "uid": "nErXDvCkzzh",
48 | "title": "Departmenet ABC",
49 | "url": "/dashboards/f/nErXDvCkzz/department-abc",
50 | "hasAcl": "false",
51 | "canSave": "false",
52 | "canEdit": "false",
53 | "canAdmin": "false",
54 | "createdBy": "admin",
55 | "created": "2018-01-31T17:43:12+01:00",
56 | "updatedBy": "admin",
57 | "updated": "2018-01-31T17:43:12+01:00",
58 | "version": 1
59 | }
60 | )
61 | folders = self.cli.folder.get_folder(uid="nErXDvCkzzh")
62 | self.assertEqual(folders["uid"], "nErXDvCkzzh")
63 |
64 | @requests_mock.Mocker()
65 | def test_create_folder(self, m):
66 | m.post(
67 | "http://localhost/api/folders",
68 | json={
69 | "id": 1,
70 | "uid": "nErXDvCkzz",
71 | "title": "Departmenet ABC",
72 | "url": "/dashboards/f/nErXDvCkzz/department-abc",
73 | "hasAcl": "false",
74 | "canSave": "false",
75 | "canEdit": "false",
76 | "canAdmin": "false",
77 | "createdBy": "admin",
78 | "created": "2018-01-31T17:43:12+01:00",
79 | "updatedBy": "admin",
80 | "updated": "2018-01-31T17:43:12+01:00",
81 | "version": 1
82 | }
83 | )
84 | folder = self.cli.folder.create_folder(title="Departmenet ABC", uid="nErXDvCkzz")
85 | self.assertEqual(folder["uid"], "nErXDvCkzz")
86 |
87 | @requests_mock.Mocker()
88 | def test_create_folder_empty_uid(self, m):
89 | m.post(
90 | "http://localhost/api/folders",
91 | json={
92 | "message": "Folder title cannot be empty"
93 | }, status_code=400
94 | )
95 | folder = self.cli.folder.create_folder(title="Departmenet ABC")
96 | self.assertRaises(GrafanaBadInputError)
97 |
98 | @requests_mock.Mocker()
99 | def test_update_folder(self, m):
100 | m.put(
101 | "http://localhost/api/folders/nErXDvCkzz",
102 | json={
103 | "id": 1,
104 | "uid": "nErXDvCkzz",
105 | "title": "Departmenet DEF",
106 | "url": "/dashboards/f/nErXDvCkzz/department-def",
107 | "hasAcl": "false",
108 | "canSave": "false",
109 | "canEdit": "false",
110 | "canAdmin": "false",
111 | "createdBy": "admin",
112 | "created": "2018-01-31T17:43:12+01:00",
113 | "updatedBy": "admin",
114 | "updated": "2018-01-31T17:43:12+01:00",
115 | "version": 1
116 | }
117 | )
118 | folder = self.cli.folder.update_folder(title="Departmenet DEF", uid="nErXDvCkzz", version=1, overwrite=True)
119 | self.assertEqual(folder["title"], "Departmenet DEF")
120 |
121 | @requests_mock.Mocker()
122 | def test_update_folder_some_param(self, m):
123 | m.put(
124 | "http://localhost/api/folders/nErXDvCkzz",
125 | json={
126 | "id": 1,
127 | "uid": "nErXDvCkzz",
128 | "title": "Departmenet DEF",
129 | "url": "/dashboards/f/nErXDvCkzz/department-def",
130 | "hasAcl": "false",
131 | "canSave": "false",
132 | "canEdit": "false",
133 | "canAdmin": "false",
134 | "createdBy": "admin",
135 | "created": "2018-01-31T17:43:12+01:00",
136 | "updatedBy": "admin",
137 | "updated": "2018-01-31T17:43:12+01:00",
138 | "version": 1
139 | }
140 | )
141 | folder = self.cli.folder.update_folder(title="Departmenet DEF", uid="nErXDvCkzz")
142 | self.assertEqual(folder["title"], "Departmenet DEF")
143 |
144 | @requests_mock.Mocker()
145 | def test_get_folder_by_id(self, m):
146 | m.get(
147 | "http://localhost/api/folders/id/1",
148 | json={
149 | "id": 1,
150 | "uid": "nErXDvCkzz",
151 | "title": "Departmenet ABC",
152 | "url": "/dashboards/f/nErXDvCkzz/department-abc",
153 | "hasAcl": "false",
154 | "canSave": "false",
155 | "canEdit": "false",
156 | "canAdmin": "false",
157 | "createdBy": "admin",
158 | "created": "2018-01-31T17:43:12+01:00",
159 | "updatedBy": "admin",
160 | "updated": "2018-01-31T17:43:12+01:00",
161 | "version": 1
162 | }
163 | )
164 | folder = self.cli.folder.get_folder_by_id(folder_id=1)
165 | self.assertEqual(folder["id"], 1)
166 |
167 | @requests_mock.Mocker()
168 | def test_get_folder_permissions(self, m):
169 | m.get(
170 | "http://localhost/api/folders/nErXDvCkzz/permissions",
171 | json=[
172 | {
173 | "id": 1,
174 | "folderId": -1,
175 | "created": "2017-06-20T02:00:00+02:00",
176 | "updated": "2017-06-20T02:00:00+02:00",
177 | "userId": 0,
178 | "userLogin": "",
179 | "userEmail": "",
180 | "teamId": 0,
181 | "team": "",
182 | "role": "Viewer",
183 | "permission": 1,
184 | "permissionName": "View",
185 | "uid": "nErXDvCkzz",
186 | "title": "",
187 | "slug": "",
188 | "isFolder": "false",
189 | "url": ""
190 | },
191 | {
192 | "id": 2,
193 | "dashboardId": -1,
194 | "created": "2017-06-20T02:00:00+02:00",
195 | "updated": "2017-06-20T02:00:00+02:00",
196 | "userId": 0,
197 | "userLogin": "",
198 | "userEmail": "",
199 | "teamId": 0,
200 | "team": "",
201 | "role": "Editor",
202 | "permission": 2,
203 | "permissionName": "Edit",
204 | "uid": "",
205 | "title": "",
206 | "slug": "",
207 | "isFolder": "false",
208 | "url": ""
209 | }
210 | ]
211 | )
212 | folder_permissions = self.cli.folder.get_folder_permissions(uid="nErXDvCkzz")
213 | self.assertEqual(folder_permissions[0]["permissionName"], "View")
214 |
215 | @requests_mock.Mocker()
216 | def test_update_folder_permissions(self, m):
217 | m.post(
218 | "http://localhost/api/folders/nErXDvCkzz/permissions",
219 | json={"message": "Folder permissions updated"}
220 | )
221 | folder = self.cli.folder.update_folder_permissions(uid="nErXDvCkzz", items=[
222 | {
223 | "role": "Viewer",
224 | "permission": 1
225 | },
226 | {
227 | "role": "Editor",
228 | "permission": 2
229 | },
230 | {
231 | "teamId": 1,
232 | "permission": 1
233 | },
234 | {
235 | "userId": 11,
236 | "permission": 4
237 | }
238 | ])
239 | self.assertEqual(folder["message"], "Folder permissions updated")
240 |
241 | @requests_mock.Mocker()
242 | def test_delete_folder(self, m):
243 | m.delete("http://localhost/api/folders/nErXDvCkzz", json={"message": "Folder deleted"})
244 | folder = self.cli.folder.delete_folder(uid="nErXDvCkzz")
245 | self.assertEqual(folder['message'], "Folder deleted")
246 |
--------------------------------------------------------------------------------