Add workflow name and target labels
This commit is contained in:
parent
0a0be027fd
commit
5585eaf8a8
|
|
@ -154,7 +154,7 @@ githubConfigSecret:
|
||||||
# counters:
|
# counters:
|
||||||
# gha_started_jobs_total:
|
# gha_started_jobs_total:
|
||||||
# labels:
|
# labels:
|
||||||
# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref"]
|
# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref", "job_workflow_name", "job_workflow_target"]
|
||||||
# gha_completed_jobs_total:
|
# gha_completed_jobs_total:
|
||||||
# labels:
|
# labels:
|
||||||
# [
|
# [
|
||||||
|
|
@ -165,6 +165,8 @@ githubConfigSecret:
|
||||||
# "event_name",
|
# "event_name",
|
||||||
# "job_result",
|
# "job_result",
|
||||||
# "job_workflow_ref",
|
# "job_workflow_ref",
|
||||||
|
# "job_workflow_name",
|
||||||
|
# "job_workflow_target",
|
||||||
# ]
|
# ]
|
||||||
# gauges:
|
# gauges:
|
||||||
# gha_assigned_jobs:
|
# gha_assigned_jobs:
|
||||||
|
|
@ -186,7 +188,7 @@ githubConfigSecret:
|
||||||
# histograms:
|
# histograms:
|
||||||
# gha_job_startup_duration_seconds:
|
# gha_job_startup_duration_seconds:
|
||||||
# labels:
|
# labels:
|
||||||
# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref"]
|
# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref", "job_workflow_name", "job_workflow_target"]
|
||||||
# buckets:
|
# buckets:
|
||||||
# [
|
# [
|
||||||
# 0.01,
|
# 0.01,
|
||||||
|
|
@ -244,7 +246,9 @@ githubConfigSecret:
|
||||||
# "job_name",
|
# "job_name",
|
||||||
# "event_name",
|
# "event_name",
|
||||||
# "job_result",
|
# "job_result",
|
||||||
# "job_workflow_ref"
|
# "job_workflow_ref",
|
||||||
|
# "job_workflow_name",
|
||||||
|
# "job_workflow_target"
|
||||||
# ]
|
# ]
|
||||||
# buckets:
|
# buckets:
|
||||||
# [
|
# [
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ const (
|
||||||
labelKeyRepository = "repository"
|
labelKeyRepository = "repository"
|
||||||
labelKeyJobName = "job_name"
|
labelKeyJobName = "job_name"
|
||||||
labelKeyJobWorkflowRef = "job_workflow_ref"
|
labelKeyJobWorkflowRef = "job_workflow_ref"
|
||||||
|
labelKeyJobWorkflowName = "job_workflow_name"
|
||||||
|
labelKeyJobWorkflowTarget = "job_workflow_target"
|
||||||
labelKeyEventName = "event_name"
|
labelKeyEventName = "event_name"
|
||||||
labelKeyJobResult = "job_result"
|
labelKeyJobResult = "job_result"
|
||||||
)
|
)
|
||||||
|
|
@ -75,13 +77,16 @@ var metricsHelp = metricsHelpRegistry{
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *exporter) jobLabels(jobBase *actions.JobMessageBase) prometheus.Labels {
|
func (e *exporter) jobLabels(jobBase *actions.JobMessageBase) prometheus.Labels {
|
||||||
|
workflowRefInfo := ParseWorkflowRef(jobBase.JobWorkflowRef)
|
||||||
return prometheus.Labels{
|
return prometheus.Labels{
|
||||||
labelKeyEnterprise: e.scaleSetLabels[labelKeyEnterprise],
|
labelKeyEnterprise: e.scaleSetLabels[labelKeyEnterprise],
|
||||||
labelKeyOrganization: jobBase.OwnerName,
|
labelKeyOrganization: jobBase.OwnerName,
|
||||||
labelKeyRepository: jobBase.RepositoryName,
|
labelKeyRepository: jobBase.RepositoryName,
|
||||||
labelKeyJobName: jobBase.JobDisplayName,
|
labelKeyJobName: jobBase.JobDisplayName,
|
||||||
labelKeyJobWorkflowRef: jobBase.JobWorkflowRef,
|
labelKeyJobWorkflowRef: jobBase.JobWorkflowRef,
|
||||||
labelKeyEventName: jobBase.EventName,
|
labelKeyJobWorkflowName: workflowRefInfo.Name,
|
||||||
|
labelKeyJobWorkflowTarget: workflowRefInfo.Target,
|
||||||
|
labelKeyEventName: jobBase.EventName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMetricsWithWorkflowRefParsing(t *testing.T) {
|
||||||
|
// Create a test exporter
|
||||||
|
exporter := &exporter{
|
||||||
|
scaleSetLabels: prometheus.Labels{
|
||||||
|
labelKeyEnterprise: "test-enterprise",
|
||||||
|
labelKeyOrganization: "test-org",
|
||||||
|
labelKeyRepository: "test-repo",
|
||||||
|
labelKeyRunnerScaleSetName: "test-scale-set",
|
||||||
|
labelKeyRunnerScaleSetNamespace: "test-namespace",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
jobBase actions.JobMessageBase
|
||||||
|
wantName string
|
||||||
|
wantTarget string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "main branch workflow",
|
||||||
|
jobBase: actions.JobMessageBase{
|
||||||
|
OwnerName: "actions",
|
||||||
|
RepositoryName: "runner",
|
||||||
|
JobDisplayName: "Build and Test",
|
||||||
|
JobWorkflowRef: "actions/runner/.github/workflows/build.yml@refs/heads/main",
|
||||||
|
EventName: "push",
|
||||||
|
},
|
||||||
|
wantName: "build",
|
||||||
|
wantTarget: "heads/main",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "feature branch workflow",
|
||||||
|
jobBase: actions.JobMessageBase{
|
||||||
|
OwnerName: "myorg",
|
||||||
|
RepositoryName: "myrepo",
|
||||||
|
JobDisplayName: "CI/CD Pipeline",
|
||||||
|
JobWorkflowRef: "myorg/myrepo/.github/workflows/ci-cd-pipeline.yml@refs/heads/feature/new-metrics",
|
||||||
|
EventName: "push",
|
||||||
|
},
|
||||||
|
wantName: "ci-cd-pipeline",
|
||||||
|
wantTarget: "heads/feature/new-metrics",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pull request workflow",
|
||||||
|
jobBase: actions.JobMessageBase{
|
||||||
|
OwnerName: "actions",
|
||||||
|
RepositoryName: "runner",
|
||||||
|
JobDisplayName: "PR Checks",
|
||||||
|
JobWorkflowRef: "actions/runner/.github/workflows/pr-checks.yml@refs/pull/123/merge",
|
||||||
|
EventName: "pull_request",
|
||||||
|
},
|
||||||
|
wantName: "pr-checks",
|
||||||
|
wantTarget: "pull/123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tag workflow",
|
||||||
|
jobBase: actions.JobMessageBase{
|
||||||
|
OwnerName: "actions",
|
||||||
|
RepositoryName: "runner",
|
||||||
|
JobDisplayName: "Release",
|
||||||
|
JobWorkflowRef: "actions/runner/.github/workflows/release.yml@refs/tags/v1.2.3",
|
||||||
|
EventName: "release",
|
||||||
|
},
|
||||||
|
wantName: "release",
|
||||||
|
wantTarget: "tags/v1.2.3",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
labels := exporter.jobLabels(&tt.jobBase)
|
||||||
|
|
||||||
|
// Build expected labels
|
||||||
|
expectedLabels := prometheus.Labels{
|
||||||
|
labelKeyEnterprise: "test-enterprise",
|
||||||
|
labelKeyOrganization: tt.jobBase.OwnerName,
|
||||||
|
labelKeyRepository: tt.jobBase.RepositoryName,
|
||||||
|
labelKeyJobName: tt.jobBase.JobDisplayName,
|
||||||
|
labelKeyJobWorkflowRef: tt.jobBase.JobWorkflowRef,
|
||||||
|
labelKeyJobWorkflowName: tt.wantName,
|
||||||
|
labelKeyJobWorkflowTarget: tt.wantTarget,
|
||||||
|
labelKeyEventName: tt.jobBase.EventName,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert all expected labels match
|
||||||
|
assert.Equal(t, expectedLabels, labels, "jobLabels() returned unexpected labels for %s", tt.name)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WorkflowRefInfo contains parsed information from a job_workflow_ref
|
||||||
|
type WorkflowRefInfo struct {
|
||||||
|
// Name is the workflow file name without extension
|
||||||
|
Name string
|
||||||
|
// Target is the target ref with type prefix retained for clarity
|
||||||
|
// Examples:
|
||||||
|
// - heads/main (branch)
|
||||||
|
// - heads/feature/new-feature (branch)
|
||||||
|
// - tags/v1.2.3 (tag)
|
||||||
|
// - pull/123 (pull request)
|
||||||
|
Target string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseWorkflowRef parses a job_workflow_ref string to extract workflow name and target
|
||||||
|
// Format: {owner}/{repo}/.github/workflows/{workflow_file}@{ref}
|
||||||
|
// Example: mygithuborg/myrepo/.github/workflows/blank.yml@refs/heads/main
|
||||||
|
//
|
||||||
|
// The target field preserves type prefixes to differentiate between:
|
||||||
|
// - Branch references: "heads/{branch}" (from refs/heads/{branch})
|
||||||
|
// - Tag references: "tags/{tag}" (from refs/tags/{tag})
|
||||||
|
// - Pull requests: "pull/{number}" (from refs/pull/{number}/merge)
|
||||||
|
func ParseWorkflowRef(workflowRef string) WorkflowRefInfo {
|
||||||
|
info := WorkflowRefInfo{}
|
||||||
|
|
||||||
|
if workflowRef == "" {
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split by @ to separate path and ref
|
||||||
|
parts := strings.Split(workflowRef, "@")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
workflowPath := parts[0]
|
||||||
|
ref := parts[1]
|
||||||
|
|
||||||
|
// Extract workflow name from path
|
||||||
|
// The path format is: {owner}/{repo}/.github/workflows/{workflow_file}
|
||||||
|
workflowFile := path.Base(workflowPath)
|
||||||
|
// Remove .yml or .yaml extension
|
||||||
|
info.Name = strings.TrimSuffix(strings.TrimSuffix(workflowFile, ".yml"), ".yaml")
|
||||||
|
|
||||||
|
// Extract target from ref based on type
|
||||||
|
// Branch refs: refs/heads/{branch}
|
||||||
|
// Tag refs: refs/tags/{tag}
|
||||||
|
// PR refs: refs/pull/{number}/merge
|
||||||
|
const (
|
||||||
|
branchPrefix = "refs/heads/"
|
||||||
|
tagPrefix = "refs/tags/"
|
||||||
|
prPrefix = "refs/pull/"
|
||||||
|
)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(ref, branchPrefix):
|
||||||
|
// Keep "heads/" prefix to indicate branch
|
||||||
|
info.Target = "heads/" + strings.TrimPrefix(ref, branchPrefix)
|
||||||
|
case strings.HasPrefix(ref, tagPrefix):
|
||||||
|
// Keep "tags/" prefix to indicate tag
|
||||||
|
info.Target = "tags/" + strings.TrimPrefix(ref, tagPrefix)
|
||||||
|
case strings.HasPrefix(ref, prPrefix):
|
||||||
|
// Extract PR number from refs/pull/{number}/merge
|
||||||
|
// Keep "pull/" prefix to indicate pull request
|
||||||
|
prPart := strings.TrimPrefix(ref, prPrefix)
|
||||||
|
if idx := strings.Index(prPart, "/"); idx > 0 {
|
||||||
|
info.Target = "pull/" + prPart[:idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseWorkflowRef(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
workflowRef string
|
||||||
|
wantName string
|
||||||
|
wantTarget string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "standard branch reference with yml",
|
||||||
|
workflowRef: "actions-runner-controller-sandbox/mumoshu-orgrunner-test-01/.github/workflows/blank.yml@refs/heads/main",
|
||||||
|
wantName: "blank",
|
||||||
|
wantTarget: "heads/main",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "branch with special characters",
|
||||||
|
workflowRef: "owner/repo/.github/workflows/ci-cd.yml@refs/heads/feature/new-feature",
|
||||||
|
wantName: "ci-cd",
|
||||||
|
wantTarget: "heads/feature/new-feature",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "yaml extension",
|
||||||
|
workflowRef: "owner/repo/.github/workflows/deploy.yaml@refs/heads/develop",
|
||||||
|
wantName: "deploy",
|
||||||
|
wantTarget: "heads/develop",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tag reference",
|
||||||
|
workflowRef: "owner/repo/.github/workflows/release.yml@refs/tags/v1.0.0",
|
||||||
|
wantName: "release",
|
||||||
|
wantTarget: "tags/v1.0.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pull request reference",
|
||||||
|
workflowRef: "owner/repo/.github/workflows/test.yml@refs/pull/123/merge",
|
||||||
|
wantName: "test",
|
||||||
|
wantTarget: "pull/123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty workflow ref",
|
||||||
|
workflowRef: "",
|
||||||
|
wantName: "",
|
||||||
|
wantTarget: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid format - no @ separator",
|
||||||
|
workflowRef: "owner/repo/.github/workflows/test.yml",
|
||||||
|
wantName: "",
|
||||||
|
wantTarget: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "workflow with dots in name",
|
||||||
|
workflowRef: "owner/repo/.github/workflows/build.test.yml@refs/heads/main",
|
||||||
|
wantName: "build.test",
|
||||||
|
wantTarget: "heads/main",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "workflow with hyphen and underscore",
|
||||||
|
workflowRef: "owner/repo/.github/workflows/build-test_deploy.yml@refs/heads/main",
|
||||||
|
wantName: "build-test_deploy",
|
||||||
|
wantTarget: "heads/main",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := ParseWorkflowRef(tt.workflowRef)
|
||||||
|
expected := WorkflowRefInfo{
|
||||||
|
Name: tt.wantName,
|
||||||
|
Target: tt.wantTarget,
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, got, "ParseWorkflowRef(%q) returned unexpected result", tt.workflowRef)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue