Skip to content

Commit

Permalink
Tests: Add software tests and CI configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
amotl committed Apr 22, 2024
1 parent 49cdb58 commit 2d7f44d
Show file tree
Hide file tree
Showing 17 changed files with 737 additions and 80 deletions.
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
72 changes: 72 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Tests

on:
pull_request: ~
push:
branches: [ main ]

# Allow job to be triggered manually.
workflow_dispatch:

# Cancel in-progress jobs when pushing to the same branch.
concurrency:
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.ref }}

jobs:

tests:

runs-on: ${{ matrix.os }}
strategy:
matrix:
os: ["ubuntu-latest"]
python-version: ["3.9", "3.12"]
fail-fast: false

env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}

name: "
Python ${{ matrix.python-version }},
OS ${{ matrix.os }}"
steps:

- name: Acquire sources
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: x64
cache: 'pip'
cache-dependency-path: |
pyproject.toml
setup.py
- name: Set up project
run: |
# `setuptools 0.64.0` adds support for editable install hooks (PEP 660).
# https://github.com/pypa/setuptools/blob/main/CHANGES.rst#v6400
pip install "setuptools>=64" --upgrade
# Install package in editable mode.
pip install --use-pep517 --prefer-binary --editable=.[test,develop]
- name: Run linter and software tests
run: |
poe check
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
files: ./coverage.xml
flags: unittests
env_vars: OS,PYTHON
name: codecov-umbrella
fail_ci_if_error: false
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ build/*
dist/*
*.egg-*/
.venv*
.coverage*
coverage.xml
9 changes: 4 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Contributing

Contributions are welcome, and they are greatly appreciated! Every
little bit helps, and credit will always be given.
Contributions are welcome, and they are greatly appreciated. Every
little helps, and credit will always be given.

You can contribute in many ways:

Expand Down Expand Up @@ -88,6 +88,5 @@ Before you submit a pull request, check that it meets these guidelines:

## Tips

To run a subset of tests:

$ pytest tests
For installing a development sandbox, please refer to the documentation
about the [development sandbox](./docs/sandbox.md).
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
include *.md
include LICENSE
recursive-include grafana_import *.yml *.yaml
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,5 +155,16 @@ grafana-import -f Applications -d "my-first-dashboard" remove
```


## Contributing

Contributions are welcome, and they are greatly appreciated. You can contribute
in many ways, and credit will always be given.

For learning more how to contribute, see the [contribution guidelines] and
learn how to set up a [development sandbox].


[contribution guidelines]: ./CONTRIBUTING.md
[development sandbox]: ./docs/sandbox.md
[Grafana HTTP API]: https://grafana.com/docs/grafana/latest/http_api/
[grafana-client]: https://github.com/panodata/grafana-client
48 changes: 48 additions & 0 deletions docs/sandbox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Development Sandbox


## Setup
Those commands will get you started with a sandboxed development environment.
After invoking `poe check`, and observing the software tests succeed, you
should be ready to start hacking.

```shell
git clone https://github.com/peekjef72/grafana-import-tool
cd grafana-import-tool
python3 -m venv .venv
source .venv/bin/activate
pip install --editable='.[develop,test]'
```


## Software tests

For running the software tests after setup, invoke `poe check`.
Optionally, activate the virtualenv, if you are coming back to
development using a fresh terminal session.

Run linters and software tests.
```shell
source .venv/bin/activate
poe check
```

Run a subset of tests.
```shell
pytest -k core
```


## Releasing

```shell
# Install a few more prerequisites.
pip install --editable='.[release]'

# Designate a new version.
git tag v0.1.0
git push --tags

# Build package, and publish to PyPI.
poe release
```
84 changes: 30 additions & 54 deletions grafana_import/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import grafana_client.client as GrafanaApi
import grafana_import.grafana as Grafana

import yaml
from grafana_import.util import load_yaml_config, grafana_settings

#******************************************************************************************
config = None
Expand All @@ -34,18 +34,19 @@
def save_dashboard(config, args, base_path, dashboard_name, dashboard, action):

output_file = base_path
file_name = dashboard_name

if 'exports_path' in config['general'] and \
not re.search(r'^(\.|\/)?/', config['general']['exports_path']):
output_file = os.path.join(output_file, config['general']['exports_path'] )

if 'export_suffix' in config['general']:
dashboard_name += datetime.today().strftime(config['general']['export_suffix'])
file_name += datetime.today().strftime(config['general']['export_suffix'])

if 'meta' in dashboard and 'folderId' in dashboard['meta'] and dashboard['meta']['folderId'] != 0:
dashboard_name = dashboard['meta']['folderTitle'] + '_' + dashboard_name
file_name = dashboard['meta']['folderTitle'] + '_' + file_name

file_name = Grafana.remove_accents_and_space( dashboard_name )
file_name = Grafana.remove_accents_and_space( file_name )
output_file = os.path.join(output_file, file_name + '.json')
try:
output = open(output_file, 'w')
Expand All @@ -60,7 +61,7 @@ def save_dashboard(config, args, base_path, dashboard_name, dashboard, action):
content = json.dumps( dashboard['dashboard'] )
output.write( content )
output.close()
print("OK: dashboard {1} to '{0}'.".format(output_file, action))
print(f"OK: Dashboard '{dashboard_name}' {action} to: {output_file}")

#******************************************************************************************
class myArgs:
Expand Down Expand Up @@ -154,19 +155,7 @@ def main():
else:
config_file = args.config_file

config = None
try:
with open(config_file, 'r') as cfg_fh:
try:
config = yaml.safe_load(cfg_fh)
except yaml.scanner.ScannerError as exc:
mark = exc.problem_mark
print("Yaml file parsing unsuccessul : %s - line: %s column: %s => %s" % (config_file, mark.line+1, mark.column+1, exc.problem) )
except Exception as exp:
print('ERROR: config file not read: %s' % str(exp))

if config is None:
sys.exit(1)
config = load_yaml_config(config_file)

if args.verbose is None:
if 'debug' in config['general']:
Expand Down Expand Up @@ -203,34 +192,16 @@ def main():
if 'export_suffix' not in config['general'] or config['general']['export_suffix'] is None:
config['general']['export_suffix'] = "_%Y%m%d%H%M%S"

if not args.grafana_label in config['grafana']:
print("ERROR: invalid grafana config label has been specified (-g {0}).".format(args.grafana_label))
sys.exit(1)

#** init default conf from grafana with set label.
config['grafana'] = config['grafana'][args.grafana_label]

#************
if not 'token' in config['grafana']:
print("ERROR: no token has been specified in grafana config label '{0}'.".format(args.grafana_label))
sys.exit(1)

params = {
'host': config['grafana'].get('host', 'localhost'),
'protocol': config['grafana'].get('protocol', 'http'),
'port': config['grafana'].get('port', '3000'),
'token': config['grafana'].get('token'),
'verify_ssl': config['grafana'].get('verify_ssl', True),
'search_api_limit': config['grafana'].get('search_api_limit', 5000),
'folder': config['general'].get('grafana_folder', 'General'),
'overwrite': args.overwrite,
'allow_new': args.allow_new,
}
params = grafana_settings(config=config, label=args.grafana_label)
params.update({
'overwrite': args.overwrite,
'allow_new': args.allow_new,
})

try:
grafana_api = Grafana.Grafana( **params )
except Exception as e:
print("ERROR: {} - message: {}".format(e) )
print(f"ERROR: {e}")
sys.exit(1)

#*******************************************************************************
Expand Down Expand Up @@ -268,44 +239,49 @@ def main():
print("maybe you want to set --overwrite option.")
sys.exit(1)

title = dash['title']
folder_name = grafana_api.grafana_folder
if res:
print("OK: dashboard '{0}' imported into '{1}'.".format(dash['title'], grafana_api.grafana_folder))
print(f"OK: Dashboard '{title}' imported into folder '{folder_name}'")
sys.exit(0)
else:
print("KO: dashboard '{0}' not imported into '{1}'.".format(dash['title'], grafana_api.grafana_folder))
print(f"KO: Dashboard '{title}' not imported into folder '{folder_name}'")
sys.exit(1)

#*******************************************************************************
elif args.action == 'remove':
dashboard_name = config['general']['dashboard_name']
try:
res = grafana_api.remove_dashboard(config['general']['dashboard_name'])
print("OK: dashboard '{0}' removed.".format(config['general']['dashboard_name']))
res = grafana_api.remove_dashboard(dashboard_name)
print(f"OK: Dashboard removed: {dashboard_name}")
sys.exit(0)
except Grafana.GrafanaDashboardNotFoundError as exp:
print("KO: dashboard '{0}' not found in '{1}".format(exp.dashboard, exp.folder))
print(f"KO: Dashboard not found in folder '{exp.folder}': {exp.dashboard}")
sys.exit(0)
except Grafana.GrafanaFolderNotFoundError as exp:
print("KO: folder '{0}' not found".format(exp.folder))
print(f"KO: Folder not found: {exp.folder}")
sys.exit(0)
except GrafanaApi.GrafanaBadInputError as exp:
print("KO: dashboard '{0}' not removed: {1}".format(config['general']['dashboard_name'], exp))
print(f"KO: Removing dashboard failed: {dashboard_name}. Reason: {exp}")
sys.exit(1)
except Exception as exp:
print("error: dashboard '{0}' remove exception '{1}'".format(config['general']['dashboard_name'], traceback.format_exc()))
print("ERROR: Dashboard '{0}' remove exception '{1}'".format(dashboard_name, traceback.format_exc()))
sys.exit(1)

#*******************************************************************************
else: # export or
dashboard_name = config['general']['dashboard_name']
try:
dash = grafana_api.export_dashboard(config['general']['dashboard_name'])
dash = grafana_api.export_dashboard(dashboard_name)
except (Grafana.GrafanaFolderNotFoundError, Grafana.GrafanaDashboardNotFoundError):
print("KO: dashboard name not found '{0}'".format(config['general']['dashboard_name']))
print("KO: Dashboard name not found: {0}".format(dashboard_name))
sys.exit(1)
except Exception as exp:
print("error: dashboard '{0}' export exception '{1}'".format(config['general']['dashboard_name'], traceback.format_exc()))
print("ERROR: Dashboard '{0}' export exception '{1}'".format(dashboard_name, traceback.format_exc()))
sys.exit(1)

if dash is not None:
save_dashboard(config, args, base_path, config['general']['dashboard_name'], dash, 'exported')
save_dashboard(config, args, base_path, dashboard_name, dash, 'exported')
sys.exit(0)

# end main...
Expand Down
Loading

0 comments on commit 2d7f44d

Please sign in to comment.