global_dashboard_as_code/createDash.py

532 lines
38 KiB
Python

import yaml
from decouple import config
import json
import argparse
import requests
from datetime import datetime
from git import Repo
from KRParser import krparser
import os
import re
import warnings
warnings.filterwarnings("ignore")
#set STAGING global dashboard name
DASHBOARD_NAME = "[STAGING]Global Offboard Reliability 2.0"
AUTHSTRING = config("BITBUCKET_USERNAME")+":"+config("BITBUCKET_TOKEN")
CONFIG_REPO_URL = "https://"+AUTHSTRING+"@atc.bmwgroup.net/bitbucket/scm/opapm/shared_configuration.git"
CONFIG_REPO_NAME = "shared_configuration"
ARCHIVE_REPO_URL = "https://"+AUTHSTRING+"@atc.bmwgroup.net/bitbucket/scm/opapm/archive.git"
ARCHIVE_REPO_NAME = "archive"
KEYREQ_REPO_URL = "https://"+AUTHSTRING+"@atc.bmwgroup.net/bitbucket/scm/opapm/keyrequestparser.git"
KEYREQ_REPO_NAME = "keyrequestparser"
BUSINESS_LINES = {'DE-3':'My Journey','DE-7':'Connected Vehicle Platform','DE-4':'My Life','EC-DE':'China Services'}
parser = argparse.ArgumentParser(description="Generate and deploy the Dynatrace Global Dashboard as Code. Auto deployment works only for STAGING dashboard",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-R", "--rows", type=int, help="Number of rows per dashboard. If not specified, all rows will be added to single dashboard")
parser.add_argument('--auto-upload', default=False, action='store_true', help="Auto upload to STAGING dashboard")
parser.add_argument('-D', '--department', type=str,default="ALL", required=False, help="Define department for which the dashboard should be updated: 'DE-3', 'DE-7', 'DE-4' or 'EC-DE'. Leave empty or use 'ALL' if you want to generate 1 cumulated dashboard")
parser.add_argument('--wall', default=False, action='store_true', help="By default script is generating desktop version. Use parameter to set dashboard generation to type 'Wall'.")
args = parser.parse_args()
def clone_repo_if_notexist(repourl, reponame):
if(not os.path.isdir(reponame)):
repo = Repo.clone_from(repourl, reponame)
return repo
return Repo(reponame)
def pull_repo(repo):
origin = repo.remotes.origin
origin.pull()
def push_repo(repo, message):
repo.git.add(all=True)
repo.index.commit(message)
origin = repo.remotes.origin
origin.push()
def load_slo_parameter(path):
# the first part is to read a yaml and only select latest, valid config
with open(path) as file:
slo_doc = yaml.safe_load(file)
return slo_doc
def make_request(url, DTAPIToken,verify, method, jsondata):
headers = {
'Content-Type': 'application/json',
'Authorization': 'Api-Token ' + DTAPIToken
}
try:
if method == "get":
response = requests.get(url, headers=headers,verify=verify)
elif method == "post":
response = requests.post(url, headers=headers,verify=verify, data=jsondata)
elif method == "put":
response = requests.put(url, headers=headers,verify=verify, data=jsondata)
elif method == "delete":
response = requests.delete(url, headers=headers,verify=verify)
response.raise_for_status()
except requests.exceptions.HTTPError as errh:
return "An Http Error occurred:" + repr(errh)
except requests.exceptions.ConnectionError as errc:
return "An Error Connecting to the API occurred:" + repr(errc)
except requests.exceptions.Timeout as errt:
return "A Timeout Error occurred:" + repr(errt)
except requests.exceptions.RequestException as err:
return "An Unknown Error occurred" + repr(err)
return response
def getSLO(env, envurl,sloid, DTAPIToken):
url = envurl+"/api/v2/slo/"+sloid+"?timeFrame=CURRENT"
response = make_request(url, DTAPIToken,True, "get", "")
responseobj = response.json()
responseobj["env"] = env
return responseobj
def get_all_dashboards_withname(DTAPIToken, DTENV,name):
DTAPIURL= DTENV + "api/config/v1/dashboards"
r = make_request(DTAPIURL,DTAPIToken,True,"get",None)
print(r)
entityResponse = r.json()
result = []
if("dashboards" in entityResponse):
for dashboard in entityResponse["dashboards"]:
if(dashboard["name"]).startswith(name):
result.append(dashboard)
result = sorted(result, key=lambda x : x['name'], reverse=False)
return result
def backup_dashboards(DTAPIToken, DTENV, dashboards):
for dashboard in dashboards:
DTAPIURL = DTENV + "api/config/v1/dashboards/" + dashboard["id"]
r = make_request(DTAPIURL,DTAPIToken,True,"get",None)
entityResponse = r.json()
print("Downloaded dashboard from Dynatrace: "+entityResponse["dashboardMetadata"]["name"]+", creating backup...")
now=datetime.now()
strnow = now.strftime("%Y%m%d_%H%M%S")
strnowdate = now.strftime("%Y%m%d")
if not os.path.isdir("./archive/"+strnowdate):
os.makedirs("./archive/"+strnowdate)
with open("./archive/"+strnowdate+"/"+entityResponse["dashboardMetadata"]["name"]+"_"+strnow+".json", "w") as file:
json.dump(entityResponse, file, indent=2)
def remove_dashboards(DTAPIToken, DTENV, dashboards):
for dashboard in dashboards:
print("Removing STAGING dashboard from Dynatrace: "+dashboard["name"])
DTAPIURL = DTENV + "api/config/v1/dashboards/" + dashboard["id"]
print(make_request(DTAPIURL,DTAPIToken,True,"delete",None))
def create_or_update_dashboard(DTAPIToken, DTENV, dashboards, files, businessline):
if(files):
for index, filename in enumerate(files,start=1):
with open('./'+filename) as file:
tilesjson = json.load(file)
if any(dashboard["name"].endswith("#"+str(index)) for dashboard in dashboards):
existingdashboard = next((dashboard for dashboard in dashboards if dashboard["name"].endswith("#"+str(index))), None)
if existingdashboard:
print("Found dashboard for file: "+filename + ", Name: "+ existingdashboard["name"])
DTAPIURL = DTENV + "api/config/v1/dashboards/" + existingdashboard["id"]
r = make_request(DTAPIURL,DTAPIToken,True,"get",None)
entityResponse = r.json()
entityResponse["tiles"] = tilesjson
print("Updating dashboard: "+entityResponse["dashboardMetadata"]["name"])
print(make_request(DTAPIURL,DTAPIToken,True,"put",json.dumps(entityResponse)))
dashboards.remove(existingdashboard)
else:
print("Dashboard for file: "+filename + " not found.")
if(args.department == "ALL"):
dashfullname = DASHBOARD_NAME
else:
dashfullname = DASHBOARD_NAME + " - " + businessline + " #" + str(index)
newdashboard = {
"dashboardMetadata":{
"name": dashfullname,
"owner": "PATRYK.GUDALEWICZ@partner.bmw.de"
},
"tiles":[]
}
DTAPIURL = DTENV + "api/config/v1/dashboards"
newdashboard["tiles"] = tilesjson
print("Creating dashboard: "+newdashboard["dashboardMetadata"]["name"])
creationresult = make_request(DTAPIURL,DTAPIToken,True,"post",json.dumps(newdashboard))
print(creationresult)
remove_dashboards(DTAPIToken, DTENV, dashboards)
def get_bounds (grid_row, grid_column, tile_columnwidth, tile_rowheight):
grid_brick = 38
grid_top = 0 if grid_row == 0 else grid_row * grid_brick
grod_left = 0 if grid_column == 0 else grid_column * grid_brick
grod_width = 0 if tile_columnwidth == 0 else tile_columnwidth * grid_brick
grod_height = 0 if tile_rowheight == 0 else tile_rowheight * grid_brick
bounds = { "top": grid_top, "left": grod_left, "width": grod_width, "height": grod_height }
return bounds
def get_dataExplorerTileSloThreshold(sloThresholdValuesAndColor):
value1 = int(str(sloThresholdValuesAndColor).split("|")[0].split("_")[0])
value2 = int(str(sloThresholdValuesAndColor).split("|")[1].split("_")[0])
value3 = int(str(sloThresholdValuesAndColor).split("|")[2].split("_")[0])
color1 = str(sloThresholdValuesAndColor).split("|")[0].split("_")[1]
color2 = str(sloThresholdValuesAndColor).split("|")[1].split("_")[1]
color3 = str(sloThresholdValuesAndColor).split("|")[2].split("_")[1]
dataExplorerTileThreshold = [ { "value": value1, "color": color1 }, { "value": value2, "color": color2 }, { "value": value3, "color": color3 } ]
return dataExplorerTileThreshold
def get_DataExplorerTile_Markdown(name_short, department, bounds, detailDashboardUrl_EMEA,detailDashboardUrl_NA, detailDashboardUrl_CN, docURL, slourl_EMEA, slourl_NA, slourl_CN,slorelevant, wall ):
# dataExplorerTile_Markdown = {
# "name": "Markdown",
# "tileType": "MARKDOWN",
# "configured": "true",
# "bounds": bounds,
# "tileFilter": {},
# "markdown": "___________\n## " + name_short + "\n\n" + department + " | --> [EMEA](" + detailDashboardUrl_EMEA + ") [NA](" + detailDashboardUrl_NA + ") [CN](" + detailDashboardUrl_CN + ")\n [Documentation](" + docURL + ")"
# }
#without team links
if(not wall):
markdown = "___________\n## " + name_short + "\n\n" + department + " [Documentation](" + docURL + ")"
else:
markdown = "___________\n## " + name_short + "\n\n" + department
if(slorelevant):
markdown = markdown + " [QM-Report] \n"
else:
markdown = markdown + " \n"
if(slourl_EMEA and not wall):
markdown = markdown + "[EMEA]("+slourl_EMEA+") "
if(slourl_NA and not wall):
markdown = markdown + "[NA]("+slourl_NA+") "
if(slourl_CN and not wall):
markdown = markdown + "[CN]("+slourl_CN+") "
dataExplorerTile_Markdown = {
"name": "Markdown",
"tileType": "MARKDOWN",
"configured": "true",
"bounds": bounds,
"tileFilter": {},
"markdown": markdown
}
return dataExplorerTile_Markdown
def get_DataExplorerTile_SingleValue(customName, metricSelector, remoteEnvironmentUrl, bounds, timeframe, graphThreshold):
dataExplorerTile_SingleValue = {
"name": "",
"tileType": "DATA_EXPLORER",
"configured": "true",
"bounds": bounds,
"tileFilter": { "timeframe": timeframe },
"remoteEnvironmentUri": remoteEnvironmentUrl,
"customName": metricSelector,
"queries": [
{
"id": "A",
"timeAggregation": "DEFAULT",
"metricSelector": metricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
}
],
"visualConfig": {
"type": "SINGLE_VALUE", "global": { "seriesType": "LINE", "hideLegend": "true" },
"rules": [ { "matcher": "A:", "properties": { "color": "DEFAULT", "seriesType": "LINE", "alias": "SLO" }, "seriesOverrides": [{"name": metricSelector, "color": "#ffffff"}] } ],
"axes": { "xAxis": { "visible": "true" }, "yAxes": [] },
"heatmapSettings": {},
"singleValueSettings": { "showTrend": "false", "showSparkLine": "false", "linkTileColorToThreshold": "true" },
"thresholds": [ { "axisTarget": "LEFT", "rules": graphThreshold, "queryId": "", "visible": "true" } ],
"tableSettings": { "isThresholdBackgroundAppliedToCell": "false" },
"graphChartSettings": { "connectNulls": "false" } },
"queriesSettings": { "resolution": "" }
}
return dataExplorerTile_SingleValue
def get_DataExplorerTile_Graph(customName, metricSelector, metricName, remoteEnvironmentUrl, bounds, timeframe, axisTargetMin, axisTargetMax, graphThreshold,countMetricSelector,responseMetricSelector ):
dataExplorerTile_Graph = {
"name": "",
"tileType": "DATA_EXPLORER",
"configured": "true",
"bounds": bounds,
"tileFilter": { "timeframe": timeframe },
"remoteEnvironmentUri": remoteEnvironmentUrl,
"customName": metricSelector,
"queries": [
{
"id": "A",
"timeAggregation": "DEFAULT",
"metricSelector": metricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
},
{
"id": "B",
"timeAggregation": "DEFAULT",
"metricSelector": countMetricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
},
{
"id": "C",
"timeAggregation": "DEFAULT",
"metricSelector": responseMetricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
}
],
"visualConfig": {
"type": "GRAPH_CHART", "global": { "seriesType": "LINE", "hideLegend": "true" },
"rules": [ { "matcher": "A:", "properties": { "color": "GREEN", "seriesType": "LINE", "alias": customName }, "seriesOverrides": [{"name": customName, "color": "#9cd575"}] },
{ "matcher": "B:", "properties": { "color": "BLUE", "seriesType": "COLUMN", "alias": "Request count - server" }, "seriesOverrides": [{"name": "Request count - server", "color": "#74cff7"}] },
{ "matcher": "C:", "properties": { "color": "PURPLE", "seriesType": "LINE", "alias": "Key request response time" }, "seriesOverrides": [{"name": "Key request response time", "color": "#c396e0"}] } ],
"axes": { "xAxis": { "visible": "true" }, "yAxes": [
{ "displayName": "", "visible": "true", "min": axisTargetMin, "max": axisTargetMax, "position": "LEFT", "queryIds": [ "A" ], "defaultAxis": "true" },
{ "displayName": "", "visible": "true", "min": "AUTO", "max": "AUTO", "position": "RIGHT", "queryIds": [ "B" ], "defaultAxis": "true" },
{ "displayName": "", "visible": "true", "min": "AUTO", "max": "AUTO", "position": "LEFT", "queryIds": [ "C" ], "defaultAxis": "true" }
] },
"heatmapSettings": {},
"singleValueSettings": { "showTrend": "false", "showSparkLine": "false", "linkTileColorToThreshold": "true" },
"thresholds": [ { "axisTarget": "LEFT", "rules": graphThreshold, "queryId": "", "visible": "false" } ],
"tableSettings": { "isThresholdBackgroundAppliedToCell": "false" },
"graphChartSettings": { "connectNulls": "false" } },
"queriesSettings": { "resolution": "" }
}
return dataExplorerTile_Graph
def create_default_tiles():
newDashboardTiles = []
# HEADER TILES
# Picture Tile
newDashboardTiles.append(
{
"name": "Image", "tileType": "IMAGE", "configured": "true", "bounds": {"top": 0, "left": 0, "width": 76, "height": 114}, "tileFilter": {},
"image": "data:image/webp;base64,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"
}
)
if(args.wall):
# EMEA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 7 , 20 , 2), "tileFilter": {}, "markdown": "# EMEA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 7 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Graph (3 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 11 , 12 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 3 d" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 23 , 4 , 1), "tileFilter": {} })
# NORTH AMERICA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 28 , 20 , 2), "tileFilter": {}, "markdown": "# NORTH AMERICA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 28 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": " Graph (3 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 32 , 12 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 3 d" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 44 , 4 , 1), "tileFilter": {} })
# CHINA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 49 , 20 , 2), "tileFilter": {}, "markdown": "# CHINA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 49 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Graph (3 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 53 , 12 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 3 d" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 65 , 4 , 1), "tileFilter": {} })
else:
# EMEA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 7 , 14 , 2), "tileFilter": {}, "markdown": "# EMEA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 7 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Graph (3 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 11 , 6 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 3 d" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 17 , 4 , 1), "tileFilter": {} })
# NORTH AMERICA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 22 , 14 , 2), "tileFilter": {}, "markdown": "# NORTH AMERICA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 22 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Graph (3 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 26 , 6 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 3 d" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 32 , 4 , 1), "tileFilter": {} })
# CHINA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 37 , 14 , 2), "tileFilter": {}, "markdown": "# CHINA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 37 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Graph (3 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 41 , 6 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 3 d" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 47 , 4 , 1), "tileFilter": {} })
return newDashboardTiles
def getSloReqCountSelector(service_names):
selector = ""
if(service_names["selectortype"] == "KR"):
service_names = service_names["services"]
print("Building Keyrequest count selector for: "+service_names)
selector = "builtin:service.keyRequest.count.total:filter(and(or(in(\"dt.entity.service_method\",entitySelector(\"type(service_method), fromRelationship.isServiceMethodOfService( type(~\"SERVICE~\"),entityName.in("+service_names+"))\"))))):splitBy()"
elif(service_names["selectortype"] == "SRV"):
service_names = service_names["services"]
print("Building Service requests count selector for: "+service_names)
selector = "builtin:service.requestCount.total:filter(and(or(in(\"dt.entity.service\",entitySelector(\"type(service),entityName.in("+service_names+")\"))))):splitBy()"
return selector
def getSloReqTimeSelector(service_names):
selector = ""
if(service_names["selectortype"] == "KR"):
selector = "builtin:service.keyRequest.response.server:filter(and(or(in(\"dt.entity.service_method\",entitySelector(\"type(service_method), fromRelationship.isServiceMethodOfService( type(~\"SERVICE~\"),entityName.in("+service_names["services"]+"))\"))))):splitBy()"
elif(service_names["selectortype"] == "SRV"):
selector = "builtin:service.response.server:filter(and(or(in(\"dt.entity.service\",entitySelector(\"type(service),entityName.in("+service_names["services"]+")\"))))):splitBy()"
return selector
def getSloSrvNames(hub_config, configuration, doc, env):
hub = ""
namestr = ""
if env=="euprod":
hub = "emea"
elif env=="naprod":
hub = "na"
elif env=="cnprod":
hub = "cn"
krp = krparser.KRParser(name=env,options=krparser.KROption.RESOLVESERVICES, config={"threads":10, "serviceLookupParams":{"fields":"tags"}, "extendResultObjects":{"env":env}}, DTAPIURL=hub_config[env]["remote_url"], DTAPIToken=config(doc[env][2].get('env-token-name')))
if(configuration["hubs"][env]["type"] == "gcdm"):
sloobj = getSLO(hub,hub_config[env]["remote_url_gcdm"],configuration["ids"][hub],config(doc[env][3].get('gcdm-token-name')))
else:
sloobj = getSLO(hub,hub_config[env]["remote_url"],configuration["ids"][hub],config(doc[env][2].get('env-token-name')))
krs = krp.parse(sloobj)
slosrvnames = []
outputslos = []
selectortype = ""
for krslo in krs:
if("builtin:service.keyRequest" in krslo.metadata["metricExpression"]):
selectortype = "KR"
elif("builtin:service.keyRequest" not in krslo.metadata["metricExpression"]):
selectortype = "SRV"
for group in krslo.matchedGroups._list:
for srv in group["services"]:
slosrvnames.append(srv)
slosrvnames = list(dict.fromkeys(slosrvnames))
if(sloobj["name"] == "TP_Vehicle_Teleservices"):
print(sloobj["name"])
for srv in slosrvnames:
outputslos.append("~\""+srv+"~\"")
return {"selectortype":selectortype, "services":",".join(outputslos)}
def main(slo_path):
configrepo = clone_repo_if_notexist(CONFIG_REPO_URL, CONFIG_REPO_NAME)
pull_repo(configrepo)
archiverepo = clone_repo_if_notexist(ARCHIVE_REPO_URL, ARCHIVE_REPO_NAME)
pull_repo(archiverepo)
keyreqrepo = clone_repo_if_notexist(KEYREQ_REPO_URL, KEYREQ_REPO_NAME)
pull_repo(keyreqrepo)
print("Generating dashboard tiles...")
with open('./environment.yaml') as file:
doc = yaml.safe_load(file)
slo_doc = load_slo_parameter(slo_path)
dashboard_json = create_default_tiles()
if(args.wall):
offsets = [0,21,42]
else:
offsets = [0,15,30]
hub_config = {
"euprod":
{
"offset": offsets[0],
"remote_url": 'https://xxu26128.live.dynatrace.com',
"remote_url_gcdm": 'https://moh22956.live.dynatrace.com'
},
"naprod":
{
"offset": offsets[1],
"remote_url": 'https://wgv50241.live.dynatrace.com',
"remote_url_gcdm": 'https://pcj77768.live.dynatrace.com'
},
"cnprod":
{
"offset": offsets[2],
"remote_url": 'https://dynatrace-cn-int.bmwgroup.com:443/e/b921f1b9-c00e-4031-b9d1-f5a0d530757b'
}
}
timeframe_actual = "-1h"
timeframe_graph = "-3d"
dahboardcount = 1
rowcount = 0
boundindex = 1
generatedfiles = []
if(args.rows is not None):
rowcount = args.rows
if(args.department == "ALL"):
blname = "ALL"
else:
blname = BUSINESS_LINES[args.department]
blvalue = args.department
slorelevant = False
if(blname and blvalue):
for slo_name, configuration in slo_doc.items():
if configuration['department'].startswith(blvalue) or blvalue == "ALL":
if(configuration['selector_var'] == "CoCo-QM-Report_Mobile"):
slorelevant = True
print("Dashboard #"+str(dahboardcount)+" : Configurint SLO "+slo_name)
if rowcount > 0 and boundindex > rowcount:
dashboard_json = create_default_tiles()
dahboardcount = dahboardcount+1
boundindex = 1
slo_display = configuration["displayname"]
slo_department = configuration["department"]
#timeframe_ytd = configuration["yearstart"] + " 00:00 to now"
timeframe_ytd = "-3d"
slo_graphThreshold_SingleValue = get_dataExplorerTileSloThreshold(configuration["thresholds"]["single_value"])
slo_graphThreshold_Graph = get_dataExplorerTileSloThreshold(configuration["thresholds"]["graph_value"])
emeaslourl = ""
naslourl = ""
cnslourl = ""
if len(configuration["hubs"]) > 0:
if(configuration["ids"]["emea"]):
emeaslourl = hub_config["euprod"]["remote_url"] + "/ui/slo?id="+configuration["ids"]["emea"]+"&sloexp="+configuration["ids"]["emea"]+"&slovis=Table"
if(configuration["ids"]["na"]):
naslourl = hub_config["naprod"]["remote_url"] + "/ui/slo?id="+configuration["ids"]["na"]+"&sloexp="+configuration["ids"]["na"]+"&slovis=Table"
if(configuration["ids"]["cn"]):
cnslourl = hub_config["cnprod"]["remote_url"] + "/ui/slo?id="+configuration["ids"]["cn"]+"&sloexp="+configuration["ids"]["cn"]+"&slovis=Table"
dashboard_json.append(get_DataExplorerTile_Markdown(slo_display, slo_department, get_bounds(((boundindex)*(3)) , 0 , 7 , 3), configuration["ops_dashboard"]["emea"], configuration["ops_dashboard"]["na"], configuration["ops_dashboard"]["cn"],configuration["doc_url"],emeaslourl,naslourl,cnslourl,slorelevant,args.wall))
for hub,tiles in configuration["hubs"].items():
if(configuration["hubs"][hub]["type"] == "gcdm"):
remoteurl = hub_config[hub]["remote_url_gcdm"]
else:
remoteurl = hub_config[hub]["remote_url"]
if 'actual' in tiles["tiles"]:
dashboard_json.append(get_DataExplorerTile_SingleValue(slo_name, configuration["metric"], remoteurl, get_bounds(((boundindex)*(3)) , 7 + hub_config[hub]["offset"] , 4 , 3), timeframe_actual, slo_graphThreshold_SingleValue))
if "graph" in tiles["tiles"]:
if(args.wall):
dashboard_json.append(get_DataExplorerTile_Graph(slo_name, configuration["metric"], configuration["selector_var"].replace("~",""), remoteurl, get_bounds(((boundindex)*(3)) , 11 + hub_config[hub]["offset"] , 12 , 3), timeframe_graph, "97", "100.1", slo_graphThreshold_Graph, getSloReqCountSelector(getSloSrvNames(hub_config, configuration, doc, hub)),getSloReqTimeSelector(getSloSrvNames(hub_config, configuration, doc, hub))))
else:
dashboard_json.append(get_DataExplorerTile_Graph(slo_name, configuration["metric"], configuration["selector_var"].replace("~",""), remoteurl, get_bounds(((boundindex)*(3)) , 11 + hub_config[hub]["offset"] , 6 , 3), timeframe_graph, "97", "100.1", slo_graphThreshold_Graph, getSloReqCountSelector(getSloSrvNames(hub_config, configuration, doc, hub)),getSloReqTimeSelector(getSloSrvNames(hub_config, configuration, doc, hub))))
if "ytd" in tiles["tiles"]:
if(args.wall):
dashboard_json.append(get_DataExplorerTile_SingleValue(slo_name, configuration["metric"], remoteurl, get_bounds(((boundindex)*(3)) , 23 + hub_config[hub]["offset"] , 4 , 3), timeframe_ytd, slo_graphThreshold_SingleValue))
else:
dashboard_json.append(get_DataExplorerTile_SingleValue(slo_name, configuration["metric"], remoteurl, get_bounds(((boundindex)*(3)) , 17 + hub_config[hub]["offset"] , 4 , 3), timeframe_ytd, slo_graphThreshold_SingleValue))
boundindex = boundindex+1
if rowcount > 0 and boundindex > rowcount:
with open("dashboard_tiles_"+str(dahboardcount)+".json", "w") as file:
json.dump(dashboard_json, file, indent=2)
generatedfiles.append("dashboard_tiles_"+str(dahboardcount)+".json")
if rowcount == 0 or (args.rows is not None and boundindex%args.rows != 0):
with open("dashboard_tiles_"+str(dahboardcount)+".json", "w") as file:
json.dump(dashboard_json, file, indent=2)
generatedfiles.append("dashboard_tiles_"+str(dahboardcount)+".json")
if args.auto_upload:
print("Getting existing STAGING dashboards from Dynatrace")
for item, doc in doc.items():
if(item == "globaldashboard"):
token = dict(doc[2])
url = dict(doc[1])
print("Crawling through: " + item)
print("Gather data, hold on a minute")
DTTOKEN = config(token.get('env-token-name'))
DTURL = url.get('env-url')
print("Downloading STAGING dashboards to local repo ("+blname+")...")
existingdashboards = get_all_dashboards_withname(DTTOKEN, DTURL,DASHBOARD_NAME +blname)
print("Uploading STAGING dashboards to Dynatrace ("+blname+")...")
backup_dashboards(DTTOKEN, DTURL, existingdashboards)
now=datetime.now()
strnowdate = now.strftime("%Y%m%d")
push_repo(archiverepo, strnowdate+"_Global dashboard as code auto-upload backup")
create_or_update_dashboard(DTTOKEN, DTURL, existingdashboards, generatedfiles, blname)
else:
print("ERROR: Could not find Business line for given department.")
if __name__ == "__main__":
main('./shared_configuration/slo_parameter.yaml')