Skip to content

Commit 3c64fd1

Browse files
committed
chore(oauth2): add user datasources
1 parent 7142541 commit 3c64fd1

5 files changed

Lines changed: 105 additions & 71 deletions

File tree

File renamed without changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "Prometheus on Drycc",
3+
"type": "prometheus",
4+
"url": "${controller_api_url}/v2/prometheus/${username}",
5+
"access": "proxy",
6+
"isDefault": true,
7+
"basicAuth": false,
8+
"jsonData": {
9+
"httpHeaderName1": "Authorization",
10+
"httpMethod": "GET",
11+
"timeInterval": "${time_interval}"
12+
},
13+
"secureJsonData": {
14+
"httpHeaderValue1": "Token ${token}"
15+
}
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "Application Logs",
3+
"type": "quickwit-quickwit-datasource",
4+
"url": "${controller_api_url}/v2/quickwit/${username}",
5+
"access": "proxy",
6+
"basicAuth": false,
7+
"jsonData": {
8+
"httpHeaderName1": "Authorization",
9+
"index": "logs-*",
10+
"logMessageField": "log"
11+
},
12+
"secureJsonData": {
13+
"httpHeaderValue1": "Token ${token}"
14+
}
15+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from .grafana import (
2-
init_org, sync_user, sync_role, sync_datasource, sync_alerting, sync_dashboard
2+
init_org, sync_user, sync_role, sync_datasources, sync_alerting, sync_dashboards
33
)
44

55
startup_hooks = [init_org]
6-
login_hooks = [sync_user, sync_role, sync_datasource, sync_alerting, sync_dashboard]
6+
login_hooks = [sync_user, sync_role, sync_datasources, sync_alerting, sync_dashboards]
77
destroy_hooks = []

rootfs/usr/share/grafana/oauth2/hook/grafana.py

Lines changed: 72 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import json
33
import httpx
4+
from string import Template
45
from psycopg import AsyncConnection
56

67
DEFAULT_HEADERS = {"Content-Type": "application/json"}
@@ -96,60 +97,46 @@ async def sync_alerting(context: dict, token: dict, userinfo: dict):
9697
)
9798

9899

99-
async def sync_datasource(context: dict, token: dict, userinfo: dict):
100-
username = userinfo["preferred_username"]
101-
prometheus_url = f"{DRYCC_CONTROLLER_API_URL}/v2/prometheus/{username}"
102-
datasource_name = "Prometheus on Drycc"
100+
async def sync_datasources(context: dict, token: dict, userinfo: dict):
101+
headers = api_headers(context, userinfo)
102+
datasources_path = os.path.join(os.path.dirname(__file__), "..", "datasources")
103+
created, drycc_token = await _get_or_create_drycc_token(userinfo["preferred_username"], token)
103104
async with httpx.AsyncClient() as client:
104-
resp = await client.get(
105-
api_url("/api/datasources/name/{datasource_name}"),
106-
headers=api_headers(context, userinfo))
107-
if resp.status_code == 200:
108-
datasource = resp.json()
109-
drycc_token_uuid = datasource.get("jsonData", {}).get("tokenId", None)
110-
drycc_token = await _get_or_create_drycc_token(drycc_token_uuid, token)
111-
if "token" in drycc_token:
112-
datasource["jsonData"]["tokenId"] = drycc_token["uuid"]
113-
datasource["secureJsonData"] = {
114-
"httpHeaderValue1": f"Token {drycc_token["token"]}",
115-
}
116-
await _set_datasource_read_only(False, datasource["id"])
117-
await client.put(
118-
api_url(f"/api/datasources/uid/{datasource["uid"]}"),
119-
headers=api_headers(context, userinfo),
120-
json=datasource,
121-
)
122-
await _set_datasource_read_only(True, datasource["id"])
123-
return
124-
drycc_token = await _get_or_create_drycc_token(None, token)
125-
resp = await client.post(
126-
api_url("/api/datasources"),
127-
headers=api_headers(context, userinfo),
128-
json={
129-
"name": datasource_name,
130-
"type": "prometheus",
131-
"url": prometheus_url,
132-
"access": "proxy",
133-
"basicAuth": False,
134-
"jsonData": {
135-
"tokenId": drycc_token["uuid"],
136-
"httpHeaderName1": "Authorization",
137-
"httpMethod": "GET",
138-
"timeInterval": DRYCC_GRAFANA_REFRESH,
139-
},
140-
"secureJsonData": {
141-
"httpHeaderValue1": f"Token {drycc_token["token"]}",
142-
},
143-
},
144-
)
145-
await _set_datasource_read_only(True, resp.json()["datasource"]["id"])
146-
147-
148-
async def sync_dashboard(context: dict, token: dict, userinfo: dict):
149-
dashboard_path = os.path.join(os.path.dirname(__file__), "..", "dashboard")
105+
for filename in os.listdir(datasources_path):
106+
with open(os.path.join(datasources_path, filename)) as f:
107+
template = Template(f.read())
108+
datasource = json.loads(template.substitute(
109+
controller_api_url=DRYCC_CONTROLLER_API_URL,
110+
username=userinfo["preferred_username"],
111+
time_interval=DRYCC_GRAFANA_REFRESH,
112+
token=drycc_token
113+
))
114+
resp = await client.get(
115+
api_url(f"/api/datasources/name/{datasource["name"]}"), headers=headers)
116+
if resp.status_code == 200:
117+
if created:
118+
datasource = resp.json()
119+
datasource["secureJsonData"] = {
120+
"httpHeaderValue1": f"Token {drycc_token}",
121+
}
122+
await _set_datasource_read_only(False, datasource["id"])
123+
await client.put(
124+
api_url(f"/api/datasources/uid/{datasource["uid"]}"),
125+
headers=headers, json=datasource)
126+
await _set_datasource_read_only(True, datasource["id"])
127+
elif resp.status_code == 404:
128+
resp = await client.post(
129+
api_url("/api/datasources"), headers=headers, json=datasource)
130+
await _set_datasource_read_only(True, resp.json()["datasource"]["id"])
131+
else:
132+
raise ValueError(f"grafana returned an unexpected status: {resp.status_code}")
133+
134+
135+
async def sync_dashboards(context: dict, token: dict, userinfo: dict):
136+
dashboards_path = os.path.join(os.path.dirname(__file__), "..", "dashboards")
150137
async with httpx.AsyncClient() as client:
151-
for filename in os.listdir(dashboard_path):
152-
with open(os.path.join(dashboard_path, filename)) as f:
138+
for filename in os.listdir(dashboards_path):
139+
with open(os.path.join(dashboards_path, filename)) as f:
153140
dashboard = json.load(f)
154141
dashboard.update({"id": None, "refresh": DRYCC_GRAFANA_REFRESH})
155142
await client.post(
@@ -172,21 +159,37 @@ async def _set_datasource_read_only(read_only: bool, id: str):
172159
await conn.commit()
173160

174161

175-
async def _get_or_create_drycc_token(drycc_token_uuid: str, token: dict):
176-
headers = {"Authorization": f"Bearer {token["access_token"]}"}
177-
async with httpx.AsyncClient() as client:
178-
if drycc_token_uuid:
179-
resp = await client.get(
180-
f"{DRYCC_CONTROLLER_API_URL}/v2/tokens/{drycc_token_uuid}", headers=headers)
181-
if resp.status_code == 200:
182-
return resp.json()
183-
elif resp.status_code != 404:
184-
raise ValueError(
185-
f"the Drycc controller returned an unsupported status code {resp.status_code}"
162+
async def _get_or_create_drycc_token(username, token: dict):
163+
async def _check_or_create_drycc_token(drycc_token, token):
164+
async with httpx.AsyncClient() as client:
165+
created = False if drycc_token else True
166+
if drycc_token:
167+
headers = {"Authorization": f"Token {drycc_token}"}
168+
resp = await client.get(
169+
f"{DRYCC_CONTROLLER_API_URL}/v2/auth/whoami", headers=headers)
170+
if resp.status_code in [401, 403]:
171+
created = True
172+
if created:
173+
headers = {"Authorization": f"Bearer {token["access_token"]}"}
174+
data = (await client.post(
175+
f"{DRYCC_CONTROLLER_API_URL}/v2/auth/token/?alias=grafana-datasource",
176+
headers=headers, json=token)).json()
177+
drycc_token = data["token"]
178+
return created, drycc_token
179+
180+
async with await AsyncConnection.connect(os.environ.get("GF_DATABASE_URL")) as conn:
181+
async with conn.cursor() as cursor:
182+
await cursor.execute(
183+
"SELECT o_auth_id_token FROM user_auth WHERE auth_module=%s AND auth_id=%s",
184+
("authproxy", username)
185+
)
186+
drycc_token = (await cursor.fetchone())[0]
187+
created, drycc_token = await _check_or_create_drycc_token(drycc_token, token)
188+
if created:
189+
async with conn.cursor() as cursor:
190+
await cursor.execute(
191+
"UPDATE user_auth SET o_auth_id_token=%s WHERE auth_module=%s AND auth_id=%s",
192+
(drycc_token, "authproxy", username)
186193
)
187-
resp = await client.post(
188-
f"{DRYCC_CONTROLLER_API_URL}/v2/auth/token/?alias=grafana-datasource",
189-
headers=headers,
190-
json=token,
191-
)
192-
return resp.json()
194+
await conn.commit()
195+
return created, drycc_token

0 commit comments

Comments
 (0)