Skip to content

Commit 2c99a25

Browse files
vitaliirymaramotl
authored andcommitted
Implemented import dashboards from a directory
1 parent fbd5375 commit 2c99a25

File tree

4 files changed

+61
-36
lines changed

4 files changed

+61
-36
lines changed

HISTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Fixed folder argument issue
77
- Fixed import dashboards into a folder
88
- Added keep-uid argument to preserve the dashboard uid provided in file
9+
- Added an option to import dashboards from a directory
910

1011
Thanks, @vrymar.
1112

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ Watch the input dashboard for changes on disk, and re-upload it, when changed.
7474
grafana-import import --overwrite --reload -i gd-prometheus.py
7575
```
7676

77+
### Import dashboards from a directory
78+
Import all dashboards from provided directory
79+
```shell
80+
grafana-import import -i "./dashboards_folder"
81+
```
82+
7783
### Export
7884
Export the dashboard titled `my-first-dashboard` to the default export directory.
7985
```bash
@@ -198,19 +204,19 @@ jb install github.com/grafana/grafonnet/gen/grafonnet-latest@main
198204
#### Usage
199205
Render dashboard defined in [Grafonnet]/[Jsonnet].
200206
```shell
201-
grafana-import import --overwrite -i /path/to/faro.jsonnet
207+
grafana-import import --overwrite -i ./path/to/faro.jsonnet
202208
```
203209

204210
### grafana-dashboard
205211
Render dashboard defined using [grafana-dashboard].
206212
```shell
207-
grafana-import import --overwrite -i /path/to/gd-dashboard.py
213+
grafana-import import --overwrite -i ./path/to/gd-dashboard.py
208214
```
209215

210216
### grafanalib
211217
Render dashboard defined using [grafanalib].
212218
```shell
213-
grafana-import import --overwrite -i /path/to/gl-dashboard.py
219+
grafana-import import --overwrite -i ./path/to/gl-dashboard.py
214220
```
215221

216222

grafana_import/cli.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def save_dashboard(config, args, base_path, dashboard_name, dashboard, action):
4646
try:
4747
output = open(output_file, "w")
4848
except OSError as e:
49-
print("File {0} error: {1}.".format(output_file, e.strerror))
49+
logger.error("File {0} error: {1}.".format(output_file, e.strerror))
5050
sys.exit(2)
5151

5252
content = None
@@ -56,7 +56,7 @@ def save_dashboard(config, args, base_path, dashboard_name, dashboard, action):
5656
content = json.dumps(dashboard["dashboard"])
5757
output.write(content)
5858
output.close()
59-
print(f"OK: Dashboard '{dashboard_name}' {action} to: {output_file}")
59+
logger.info(f"OK: Dashboard '{dashboard_name}' {action} to: {output_file}")
6060

6161

6262
class myArgs:
@@ -212,7 +212,7 @@ def main():
212212
if args.action == "exporter" and (
213213
"dashboard_name" not in config["general"] or config["general"]["dashboard_name"] is None
214214
):
215-
print("ERROR: no dashboard has been specified.")
215+
logger.error("ERROR: no dashboard has been specified.")
216216
sys.exit(1)
217217

218218
config["check_folder"] = False
@@ -238,29 +238,46 @@ def main():
238238
try:
239239
grafana_api = Grafana.Grafana(**params)
240240
except Exception as e:
241-
print(f"ERROR: {e}")
241+
logger.error(f"ERROR: {e}")
242242
sys.exit(1)
243243

244244
# Import
245245
if args.action == "import":
246246
if args.dashboard_file is None:
247-
print("ERROR: no file to import provided!")
247+
logger.error("ERROR: no file to import provided!")
248248
sys.exit(1)
249249

250250
# Compute effective input file path.
251251
import_path = ""
252252
import_file = args.dashboard_file
253+
import_files = []
254+
253255
if not re.search(r"^(?:(?:/)|(?:\.?\./))", import_file):
254256
import_path = base_path
255-
if "imports_path" in config["general"]:
256-
import_path = os.path.join(import_path, config["general"]["imports_path"])
257-
import_file = os.path.join(import_path, import_file)
258-
259-
def process_dashboard():
257+
if "import_path" in config["general"]:
258+
import_path = os.path.join(import_path, config["general"]["import_path"])
259+
import_file = os.path.join(import_path, import_file)
260+
import_files.append(import_file)
261+
else:
262+
if os.path.isfile(import_file):
263+
logger.info(f"The path is a file: '{import_file}'")
264+
import_file = os.path.join(import_path, import_file)
265+
import_files.append(import_file)
266+
267+
if os.path.isdir(import_file):
268+
logger.info(f"The path is a directory: '{import_file}'")
269+
import_files = [
270+
os.path.join(import_file, f)
271+
for f in os.listdir(import_file)
272+
if os.path.isfile(os.path.join(import_file, f))
273+
]
274+
logger.info(f"Found the following files: '{import_files}' in dir '{import_file}'")
275+
276+
def process_dashboard(file_path):
260277
try:
261-
dash = read_dashboard_file(import_file)
278+
dash = read_dashboard_file(file_path)
262279
except Exception as ex:
263-
msg = f"Failed to load dashboard from: {import_file}. Reason: {ex}"
280+
msg = f"Failed to load dashboard from: {file_path}. Reason: {ex}"
264281
logger.exception(msg)
265282
raise IOError(msg) from ex
266283

@@ -280,13 +297,17 @@ def process_dashboard():
280297
logger.error(msg)
281298
raise IOError(msg)
282299

283-
try:
284-
process_dashboard()
285-
except Exception:
286-
sys.exit(1)
300+
for file in import_files:
301+
print(f"Processing file: {file}")
302+
try:
303+
process_dashboard(file)
304+
except Exception as e:
305+
logger.error(f"Failed to process file {file}. Reason: {str(e)}")
306+
continue
287307

288308
if args.reload:
289-
watchdog_service(import_file, process_dashboard)
309+
for file in import_files:
310+
watchdog_service(import_file, process_dashboard(file))
290311

291312
sys.exit(0)
292313

@@ -295,19 +316,19 @@ def process_dashboard():
295316
dashboard_name = config["general"]["dashboard_name"]
296317
try:
297318
grafana_api.remove_dashboard(dashboard_name)
298-
print(f"OK: Dashboard removed: {dashboard_name}")
319+
logger.info(f"OK: Dashboard removed: {dashboard_name}")
299320
sys.exit(0)
300321
except Grafana.GrafanaDashboardNotFoundError as exp:
301-
print(f"KO: Dashboard not found in folder '{exp.folder}': {exp.dashboard}")
322+
logger.info(f"KO: Dashboard not found in folder '{exp.folder}': {exp.dashboard}")
302323
sys.exit(1)
303324
except Grafana.GrafanaFolderNotFoundError as exp:
304-
print(f"KO: Folder not found: {exp.folder}")
325+
logger.info(f"KO: Folder not found: {exp.folder}")
305326
sys.exit(1)
306327
except GrafanaApi.GrafanaBadInputError as exp:
307-
print(f"KO: Removing dashboard failed: {dashboard_name}. Reason: {exp}")
328+
logger.info(f"KO: Removing dashboard failed: {dashboard_name}. Reason: {exp}")
308329
sys.exit(1)
309330
except Exception:
310-
print("ERROR: Dashboard '{0}' remove exception '{1}'".format(dashboard_name, traceback.format_exc()))
331+
logger.info("ERROR: Dashboard '{0}' remove exception '{1}'".format(dashboard_name, traceback.format_exc()))
311332
sys.exit(1)
312333

313334
# Export
@@ -316,10 +337,10 @@ def process_dashboard():
316337
try:
317338
dash = grafana_api.export_dashboard(dashboard_name)
318339
except (Grafana.GrafanaFolderNotFoundError, Grafana.GrafanaDashboardNotFoundError):
319-
print("KO: Dashboard name not found: {0}".format(dashboard_name))
340+
logger.info("KO: Dashboard name not found: {0}".format(dashboard_name))
320341
sys.exit(1)
321342
except Exception:
322-
print("ERROR: Dashboard '{0}' export exception '{1}'".format(dashboard_name, traceback.format_exc()))
343+
logger.info("ERROR: Dashboard '{0}' export exception '{1}'".format(dashboard_name, traceback.format_exc()))
323344
sys.exit(1)
324345

325346
if dash is not None:

tests/test_cli.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def test_import_dashboard_success(mocked_grafana, mocked_responses, tmp_path, ca
4545

4646

4747
@pytest.mark.parametrize("use_settings", [True, False], ids=["config-yes", "config-no"])
48-
def test_export_dashboard_success(mocked_grafana, mocked_responses, capsys, use_settings):
48+
def test_export_dashboard_success(mocked_grafana, mocked_responses, caplog, use_settings):
4949
"""
5050
Verify "export dashboard" works.
5151
"""
@@ -66,12 +66,11 @@ def test_export_dashboard_success(mocked_grafana, mocked_responses, capsys, use_
6666
m.stop()
6767
assert ex.match("0")
6868

69-
out, err = capsys.readouterr()
70-
assert re.match(r"OK: Dashboard 'foobar' exported to: ./foobar_\d+.json", out)
69+
assert re.match(r".*OK: Dashboard 'foobar' exported to: ./foobar_\d+.json.*", caplog.text, re.DOTALL)
7170

7271

7372
@pytest.mark.parametrize("use_settings", [True, False], ids=["config-yes", "config-no"])
74-
def test_export_dashboard_notfound(mocked_grafana, mocked_responses, capsys, use_settings):
73+
def test_export_dashboard_notfound(mocked_grafana, mocked_responses, caplog, use_settings):
7574
"""
7675
Verify "export dashboard" fails appropriately when addressed dashboard does not exist.
7776
"""
@@ -88,12 +87,11 @@ def test_export_dashboard_notfound(mocked_grafana, mocked_responses, capsys, use
8887
main()
8988
assert ex.match("1")
9089

91-
out, err = capsys.readouterr()
92-
assert "Dashboard name not found: foobar" in out
90+
assert "Dashboard name not found: foobar" in caplog.text
9391

9492

9593
@pytest.mark.parametrize("use_settings", [True, False], ids=["config-yes", "config-no"])
96-
def test_remove_dashboard_success(mocked_grafana, mocked_responses, capsys, use_settings):
94+
def test_remove_dashboard_success(mocked_grafana, mocked_responses, caplog, use_settings):
9795
"""
9896
Verify "remove dashboard" works.
9997
"""
@@ -110,5 +108,4 @@ def test_remove_dashboard_success(mocked_grafana, mocked_responses, capsys, use_
110108
main()
111109
assert ex.match("0")
112110

113-
out, err = capsys.readouterr()
114-
assert "OK: Dashboard removed: foobar" in out
111+
assert "OK: Dashboard removed: foobar" in caplog.text

0 commit comments

Comments
 (0)