Skip to content

Commit

Permalink
Added: Support for multiple multiple folders, python client (#78)
Browse files Browse the repository at this point in the history
Modified: Internal logic for creating/updating dashboards
Deleted: Removed old logic as it is not flexible for automation

Co-authored-by: Vishnu Challa <vchalla@vchalla-thinkpadp1gen2.remote.csb>
  • Loading branch information
vishnuchalla and Vishnu Challa committed Oct 2, 2023
1 parent c536c10 commit 779b567
Show file tree
Hide file tree
Showing 25 changed files with 201 additions and 83 deletions.
30 changes: 15 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ on:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
lint:
runs-on: ubuntu-latest

steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2

- name: Get dependencies
run: make deps

- name: Run jsonnetfmt
run: make format

build:
runs-on: ubuntu-latest

Expand All @@ -35,23 +48,10 @@ jobs:

- name: Import dashboards to grafana
run: >
for t in rendered/*.json; do
for t in rendered/**/*.json; do
echo "Importing ${t}";
dashboard=$(cat ${t});
echo "{\"dashboard\": ${dashboard}, \"overwrite\": true}" |
curl -k -Ss -XPOST -H "Content-Type: application/json" -H "Accept: application/json" -d@-
"http://admin:admin@localhost:3000/api/dashboards/db" -o /dev/null;
done
lint:
runs-on: ubuntu-latest

steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2

- name: Get dependencies
run: make deps

- name: Run jsonnetfmt
run: for t in templates/*.jsonnet; do echo "Testing template ${t}"; ./bin/jsonnetfmt --test $t; echo 'Results:' ${?}; done
done
15 changes: 4 additions & 11 deletions .github/workflows/grafana.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ defaults:
run:
shell: bash

env:
# Space separated list as a string of all dashboard json files in "rendered" to load
DASHBOARDS: "kube-burner.json"

on:
push:
branches: [ master ]
Expand All @@ -25,13 +21,10 @@ jobs:
# The secret GRAFANA_URL must be set with the format http://username:password@url.org without a trailing /
- name: Import dashboards to grafana
run: >
dashboard_list=($(echo $DASHBOARDS));
for path in "${dashboard_list[@]}"; do
full_path="rendered/${path}";
echo "Importing ${full_path}";
dashboard=$(cat ${full_path});
for t in rendered/**/*.json; do
echo "Importing ${t}";
dashboard=$(cat ${t});
echo "{\"dashboard\": ${dashboard}, \"overwrite\": true}" |
curl -k -Ss -XPOST -H "Content-Type: application/json" -H "Accept: application/json" -d@-
"${{ secrets.GRAFANA_URL }}/api/dashboards/db" -o /dev/null;
done
done
21 changes: 21 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal

# Set the working directory
WORKDIR /performance-dashboards

# Install necessary libraries for subsequent commands
RUN microdnf install -y podman python3 python3-pip && \
microdnf clean all && \
rm -rf /var/cache/yum

COPY . .

# Set permissions
RUN chmod -R 775 /performance-dashboards

# Install dependencies
RUN pip3 install --upgrade pip && \
pip3 install -r requirements.txt

# Start the command
CMD ["python3", "dittybopper/syncer/entrypoint.py"]
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ SYNCER_IMG_TAG ?= quay.io/cloud-bulldozer/dittybopper-syncer:latest
PLATFORM = linux/amd64,linux/arm64,linux/ppc64le,linux/s390x

# Get all templates at $(TEMPLATESDIR)
TEMPLATES = $(wildcard $(TEMPLATESDIR)/*.jsonnet)
TEMPLATES := $(wildcard $(TEMPLATESDIR)/**/*.jsonnet)

# Replace $(TEMPLATESDIR)/*.jsonnet by $(OUTPUTDIR)/*.json
outputs = $(patsubst $(TEMPLATESDIR)/%.jsonnet, $(OUTPUTDIR)/%.json, $(TEMPLATES))
outputs := $(patsubst $(TEMPLATESDIR)/%.jsonnet, $(OUTPUTDIR)/%.json, $(TEMPLATES))

all: deps format build

Expand Down Expand Up @@ -38,10 +38,11 @@ $(BINDIR)/jsonnet:
# Build each template and output to $(OUTPUTDIR)
$(OUTPUTDIR)/%.json: $(TEMPLATESDIR)/%.jsonnet
@echo "Building template $<"
mkdir -p $(dir $@)
$(BINDIR)/jsonnet $< > $@

build-syncer-image: build
podman build --platform=${PLATFORM} -f dittybopper/syncer/Dockerfile --manifest=${SYNCER_IMG_TAG} .
podman build --platform=${PLATFORM} -f Dockerfile --manifest=${SYNCER_IMG_TAG} .

push-syncer-image:
podman manifest push ${SYNCER_IMG_TAG} ${SYNCER_IMG_TAG}
podman manifest push ${SYNCER_IMG_TAG} ${SYNCER_IMG_TAG}
4 changes: 2 additions & 2 deletions dittybopper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ If using disconnected, you need to sync the cloud-bulldozer grafana image (shown
dittybopper/templates/dittybopper.yaml.template file) and your chosen syncer image
(defaults to quay.io/cloud-bulldozer/dittybopper-syncer:latest).

The syncer image is built with the context at the root of the repository, and the image in the dittybopper/syncer directory.
The syncer image is built with the context at the root of the repository, and the image in the root directory.
You can build it with `make build-syncer-image SYNCER_IMG_TAG=container.registry.org/organization/syncer:latest`
Alternatively, you can run the following command form the root folder of this repository: `podman build -f dittybopper/syncer/Dockerfile -t=container.registry.org/organization/syncer:latest .`
Alternatively, you can run the following command from the root folder of this repository: `podman build -f Dockerfile -t=container.registry.org/organization/syncer:latest .`

## Contribute

Expand Down
4 changes: 2 additions & 2 deletions dittybopper/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ END

export PROMETHEUS_USER=internal
export GRAFANA_ADMIN_PASSWORD=admin
export DASHBOARDS="ocp-performance.json api-performance-overview.json etcd-on-cluster-dashboard.json hypershift-performance.json ovn-dashboard.json"
export SYNCER_IMAGE=${SYNCER_IMAGE:-"quay.io/cloud-bulldozer/dittybopper-syncer:latest"} # Syncer image
export GRAFANA_URL="http://admin:${GRAFANA_ADMIN_PASSWORD}@localhost:3000"
export SYNCER_IMAGE=${SYNCER_IMAGE:-"quay.io/cloud-bulldozer/syncer:latest"} # Syncer image
export GRAFANA_IMAGE=${GRAFANA_IMAGE:-"quay.io/cloud-bulldozer/grafana:9.4.3"} # Syncer image

# Set defaults for command options
Expand Down
1 change: 1 addition & 0 deletions dittybopper/k8s-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ END

export PROMETHEUS_USER=internal
export GRAFANA_ADMIN_PASSWORD=admin
export GRAFANA_URL="http://admin:${GRAFANA_ADMIN_PASSWORD}@localhost:3000"
export DASHBOARDS="k8s-performance.json"
export SYNCER_IMAGE=${SYNCER_IMAGE:-"quay.io/cloud-bulldozer/dittybopper-syncer:latest"} # Syncer image
export GRAFANA_IMAGE=${GRAFANA_IMAGE:-"quay.io/cloud-bulldozer/grafana:9.4.3"} # Syncer image
Expand Down
7 changes: 0 additions & 7 deletions dittybopper/syncer/Dockerfile

This file was deleted.

129 changes: 129 additions & 0 deletions dittybopper/syncer/entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import json
import logging
import os
import requests
import uuid
import time
from collections import defaultdict

logging.basicConfig(level=logging.INFO)


class GrafanaOperations:
"""
This class is responsible for Grafana operations
"""
def __init__(self, grafana_url: str, input_directory: str):
self.grafana_url = grafana_url
self.input_directory = input_directory
self.dashboards = defaultdict(list)
self.folder_map = dict()
self.logger = logging.getLogger(__name__)

def fetch_all_dashboards(self):
"""
This method fetches all rendered dashboards
:return:
"""
self.get_all_folders()
self.folder_map['General'] = None
for root, _, files in os.walk(self.input_directory):
folder_name = os.path.basename(root)
json_files = [os.path.join(root, filename) for filename in files if filename.endswith(".json")]
folder_name = "General" if (folder_name == "") else folder_name
if folder_name in self.folder_map:
folder_id = self.folder_map[folder_name]
else:
folder_id = self.create_folder(folder_name)
self.dashboards[folder_id].extend(json_files)

def get_all_folders(self):
"""
This method gets the entire list of folders in grafana
:return:
"""
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
try:
response = requests.get(
f"{self.grafana_url}/api/folders",
headers=headers,
)
response_json = response.json()
self.folder_map = {each_folder['title']: each_folder['id'] for each_folder in response_json}
except requests.exceptions.RequestException as e:
raise Exception(f"Error listing folders. Message: {e}")

def create_folder(self, folder_name):
"""
This method creates a folder in grafana
:return:
"""
uid = str(uuid.uuid4())
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
try:
response = requests.post(
f"{self.grafana_url}/api/folders",
headers=headers,
json={
"title": folder_name,
"uid": uid,
},
)
response_json = response.json()
self.folder_map[folder_name] = id
return response_json['id']

except requests.exceptions.RequestException as e:
raise Exception(f"Error creating folder with name:'{self.folder_name}' and uid:'{uid}'. Message: {e}")

def read_dashboard_json(self, json_file):
"""
This method reads dashboard from json file
:return:
"""
with open(json_file, 'r') as f:
return json.load(f)

def create_dashboards(self):
"""
This method creates/updates dashboard with new json
:return:
"""
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
for folder_id, files in self.dashboards.items():
for json_file in set(files):
dashboard_json = self.read_dashboard_json(json_file)
try:
response = requests.post(
f"{self.grafana_url}/api/dashboards/db",
headers=headers,
json={
"dashboard": dashboard_json,
"folderId": folder_id,
"overwrite": True,
},
)
if response.status_code == 200:
self.logger.info(f"Dashboard '{dashboard_json['title']}' created successfully in folder '{folder_id}'")
else:
raise Exception(
f"Failed to create dashboard '{dashboard_json['title']}' in folder '{folder_id}'. Status code: {response.status_code}. Message: {response.text}")

except requests.exceptions.RequestException as e:
raise Exception(f"Error creating dashboard '{dashboard_json['title']}' in folder '{folder_id}'. Message: {e}")

if __name__ == '__main__':
grafana_operations = GrafanaOperations(os.environ.get("GRAFANA_URL"), os.environ.get("INPUT_DIR"))
grafana_operations.fetch_all_dashboards()
grafana_operations.create_dashboards()
while True:
time.sleep(60)
22 changes: 0 additions & 22 deletions dittybopper/syncer/entrypoint.sh

This file was deleted.

8 changes: 4 additions & 4 deletions dittybopper/templates/dittybopper.yaml.template
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ spec:
- name: dittybopper-syncer
imagePullPolicy: Always
env:
- name: GRAFANA_ADMIN_PASSWORD
value: ${GRAFANA_ADMIN_PASSWORD}
- name: DASHBOARDS
value: ${DASHBOARDS}
- name: GRAFANA_URL
value: ${GRAFANA_URL}
- name: INPUT_DIR
value: "/performance-dashboards/rendered/"
image: ${SYNCER_IMAGE}
volumes:
- name: sc-grafana-config
Expand Down
8 changes: 4 additions & 4 deletions dittybopper/templates/k8s-dittybopper.yaml.template
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ spec:
- name: dittybopper-syncer
imagePullPolicy: Always
env:
- name: GRAFANA_ADMIN_PASSWORD
value: ${GRAFANA_ADMIN_PASSWORD}
- name: DASHBOARDS
value: ${DASHBOARDS}
- name: GRAFANA_URL
value: ${GRAFANA_URL}
- name: INPUT_DIR
value: "/performance-dashboards/rendered/"
image: ${SYNCER_IMAGE}
volumes:
- name: sc-grafana-config
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
requests==2.26.0

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;

//Panel definitions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;

// Panel definitions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;
local stat = grafana.statPanel;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local es = grafana.elasticsearch;

local worker_count = grafana.statPanel.new(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;


Expand Down
Loading

0 comments on commit 779b567

Please sign in to comment.