Skip to content

Commit 276e130

Browse files
authored
Merge pull request #13 from scality/feature/RELENG-6059-extra-label
RELENG-6059 add the ability to add more relabel jobs
2 parents 633e726 + e4f59de commit 276e130

File tree

12 files changed

+280
-67
lines changed

12 files changed

+280
-67
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: {{ .Release.Name }}-config
5+
data:
6+
config.yaml: |-
7+
{{ .Values.config | toYaml | indent 4 }}

charts/gh-action-exporter/templates/deployment.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ spec:
1515
metadata:
1616
{{- with .Values.podAnnotations }}
1717
annotations:
18+
checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }}
1819
{{- toYaml . | nindent 8 }}
1920
{{- end }}
2021
labels:
@@ -47,6 +48,16 @@ spec:
4748
port: http
4849
resources:
4950
{{- toYaml .Values.resources | nindent 12 }}
51+
env:
52+
- name: CONFIG_FILE
53+
value: /app/config/config.yaml
54+
volumeMounts:
55+
- name: config
56+
mountPath: /app/config
57+
volumes:
58+
- name: config
59+
configMap:
60+
name: {{ .Release.Name }}-config
5061
{{- with .Values.nodeSelector }}
5162
nodeSelector:
5263
{{- toYaml . | nindent 8 }}

charts/gh-action-exporter/values.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ imagePullSecrets: []
1414
nameOverride: ""
1515
fullnameOverride: ""
1616

17+
18+
config:
19+
job_relabelling: []
20+
1721
serviceAccount:
1822
# Specifies whether a service account should be created
1923
create: true

gh_actions_exporter/Webhook.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from gh_actions_exporter.config import Settings
23
from gh_actions_exporter.types import WebHook
34
from gh_actions_exporter.metrics import Metrics
45

@@ -9,10 +10,11 @@ class WebhookManager(object):
910
event: str
1011
payload: WebHook
1112

12-
def __init__(self, payload: WebHook, event: str, metrics: Metrics):
13+
def __init__(self, payload: WebHook, event: str, metrics: Metrics, settings: Settings):
1314
self.event = event
1415
self.payload = payload
1516
self.metrics = metrics
17+
self.settings = settings
1618

1719
def __call__(self, *args, **kwargs):
1820
# Check if we managed this event
@@ -28,8 +30,8 @@ def workflow_run(self):
2830
self.metrics.handle_workflow_rebuild(self.payload)
2931

3032
def workflow_job(self):
31-
self.metrics.handle_job_status(self.payload)
32-
self.metrics.handle_job_duration(self.payload)
33+
self.metrics.handle_job_status(self.payload, self.settings)
34+
self.metrics.handle_job_duration(self.payload, self.settings)
3335

3436
def ping(self):
3537
logger.info('Ping from Github')

gh_actions_exporter/config.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import yaml
2+
from enum import Enum
3+
from pathlib import Path
4+
from typing import Dict, Any, List, Optional
5+
6+
from pydantic import BaseModel, BaseSettings
7+
8+
9+
def yaml_config_settings_source(settings: BaseSettings) -> Dict[str, Any]:
10+
"""
11+
A simple settings source that loads variables from a yaml file
12+
13+
"""
14+
config_file: Path = settings.__config__.config.config_file
15+
if config_file:
16+
return yaml.full_load(config_file.read_text())
17+
return {}
18+
19+
20+
class RelabelType(str, Enum):
21+
name = 'name'
22+
label = 'label'
23+
24+
25+
class Relabel(BaseModel):
26+
label: str
27+
type: RelabelType = RelabelType.label
28+
description: Optional[str]
29+
default: Optional[str] = None
30+
values: List[str]
31+
32+
33+
class ConfigFile(BaseSettings):
34+
config_file: Optional[Path]
35+
36+
37+
class Settings(BaseSettings):
38+
job_relabelling: Optional[List[Relabel]] = []
39+
40+
class Config:
41+
config: ConfigFile = ConfigFile()
42+
43+
@classmethod
44+
def customise_sources(
45+
cls,
46+
init_settings,
47+
env_settings,
48+
file_secret_settings,
49+
):
50+
return (
51+
init_settings,
52+
yaml_config_settings_source,
53+
env_settings,
54+
file_secret_settings,
55+
)

gh_actions_exporter/main.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
import uvicorn
22

3-
from fastapi import FastAPI, Request
3+
from fastapi import Depends, FastAPI, Request
4+
from functools import lru_cache
45

56
from gh_actions_exporter.metrics import prometheus_metrics, Metrics
67
from gh_actions_exporter.types import WebHook
78
from gh_actions_exporter.Webhook import WebhookManager
9+
from gh_actions_exporter.config import Settings
10+
11+
12+
@lru_cache()
13+
def get_settings() -> Settings:
14+
return Settings()
15+
16+
17+
@lru_cache()
18+
def metrics() -> Metrics:
19+
return Metrics(get_settings())
20+
821

922
app = FastAPI()
10-
metrics = Metrics()
23+
1124

1225
app.add_route('/metrics', prometheus_metrics)
1326

@@ -18,13 +31,23 @@ def index():
1831

1932

2033
@app.post("/webhook", status_code=202)
21-
async def webhook(webhook: WebHook, request: Request):
22-
WebhookManager(payload=webhook, event=request.headers['X-Github-Event'], metrics=metrics)()
34+
async def webhook(
35+
webhook: WebHook,
36+
request: Request,
37+
settings: Settings = Depends(get_settings),
38+
metrics: Metrics = Depends(metrics)
39+
):
40+
WebhookManager(
41+
payload=webhook,
42+
event=request.headers['X-Github-Event'],
43+
metrics=metrics,
44+
settings=settings
45+
)()
2346
return "Accepted"
2447

2548

2649
@app.delete("/clear", status_code=200)
27-
async def clear():
50+
async def clear(metrics: Metrics = Depends(metrics)):
2851
metrics.workflow_rebuild.clear()
2952
metrics.workflow_duration.clear()
3053
metrics.job_duration.clear()

gh_actions_exporter/metrics.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
from prometheus_client import (CONTENT_TYPE_LATEST, REGISTRY,
44
CollectorRegistry, generate_latest)
55
from prometheus_client.multiprocess import MultiProcessCollector
6+
from typing import Dict, List
67
from fastapi.requests import Request
78
from fastapi.responses import Response
8-
from gh_actions_exporter.types import WebHook
9+
from gh_actions_exporter.config import Relabel, RelabelType, Settings
10+
from gh_actions_exporter.types import WebHook, WorkflowJob
911
from prometheus_client import Counter, Histogram
1012

1113

@@ -23,8 +25,8 @@ def prometheus_metrics(request: Request) -> Response:
2325

2426

2527
class Metrics(object):
26-
27-
def __init__(self):
28+
def __init__(self, settings: Settings):
29+
self.settings = settings
2830
self.workflow_labelnames = [
2931
'repository',
3032
'workflow_name',
@@ -36,6 +38,9 @@ def __init__(self):
3638
'repository_visibility',
3739
'runner_type'
3840
]
41+
for relabel in self.settings.job_relabelling:
42+
self.job_labelnames.append(relabel.label)
43+
3944
self.workflow_rebuild = Counter(
4045
'github_actions_workflow_rebuild_count', 'The number of workflow rebuild',
4146
labelnames=self.workflow_labelnames
@@ -123,13 +128,38 @@ def runner_type(self, webhook: WebHook) -> str:
123128
return 'self-hosted'
124129
return 'github-hosted'
125130

126-
def job_labels(self, webhook: WebHook) -> dict:
131+
def relabel_job_labels(self, relabel: Relabel, labels: List[str]) -> Dict[str, str or None]:
132+
result = {
133+
relabel.label: relabel.default
134+
}
135+
for label in relabel.values:
136+
if label in labels:
137+
result[relabel.label] = label
138+
return result
139+
140+
def relabel_job_names(self, relabel: Relabel, job: WorkflowJob) -> dict:
141+
if job.status == 'queued':
142+
return dict()
143+
result = {
144+
relabel.label: relabel.default
145+
}
146+
for label in relabel.values:
147+
if label in job.runner_name:
148+
result[relabel.label] = label
149+
return result
150+
151+
def job_labels(self, webhook: WebHook, settings: Settings) -> dict:
127152
labels = dict(
128153
runner_type=self.runner_type(webhook),
129154
job_name=webhook.workflow_job.name,
130155
repository_visibility=webhook.repository.visibility,
131156
repository=webhook.repository.full_name,
132157
)
158+
for relabel in settings.job_relabelling:
159+
if relabel.type == RelabelType.label:
160+
labels.update(self.relabel_job_labels(relabel, webhook.workflow_job.labels))
161+
elif relabel.type == RelabelType.name:
162+
labels.update(self.relabel_job_names(relabel, webhook.workflow_job))
133163
return labels
134164

135165
def handle_workflow_rebuild(self, webhook: WebHook):
@@ -165,8 +195,8 @@ def handle_workflow_duration(self, webhook: WebHook):
165195
- webhook.workflow_run.run_started_at.timestamp())
166196
self.workflow_duration.labels(**labels).observe(duration)
167197

168-
def handle_job_status(self, webhook: WebHook):
169-
labels = self.job_labels(webhook)
198+
def handle_job_status(self, webhook: WebHook, settings: Settings):
199+
labels = self.job_labels(webhook, settings)
170200
if webhook.workflow_job.conclusion:
171201
if webhook.workflow_job.conclusion == 'success':
172202
self.job_status_success.labels(**labels).inc()
@@ -180,8 +210,8 @@ def handle_job_status(self, webhook: WebHook):
180210
elif webhook.workflow_job.status == 'queued':
181211
self.job_status_queued.labels(**labels).inc()
182212

183-
def handle_job_duration(self, webhook: WebHook):
184-
labels = self.job_labels(webhook)
213+
def handle_job_duration(self, webhook: WebHook, settings: Settings):
214+
labels = self.job_labels(webhook, settings)
185215
if webhook.workflow_job.conclusion:
186216
duration = (webhook.workflow_job.completed_at.timestamp()
187217
- webhook.workflow_job.started_at.timestamp())

gh_actions_exporter/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class WorkflowJob(BaseModel):
1818
run_id: int
1919
run_url: str
2020
run_attempt: int
21+
runner_name: Optional[str] = None
2122
node_id: str
2223
head_sha: str
2324
url: str

0 commit comments

Comments
 (0)