diff --git a/charts/gha-runner-scale-set-experimental/values.yaml b/charts/gha-runner-scale-set-experimental/values.yaml index 41dc900a81..7131a76d58 100644 --- a/charts/gha-runner-scale-set-experimental/values.yaml +++ b/charts/gha-runner-scale-set-experimental/values.yaml @@ -323,7 +323,7 @@ controllerServiceAccount: # counters: # gha_started_jobs_total: # labels: -# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref", "job_workflow_name", "job_workflow_target"] +# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref", "job_workflow_name", "job_workflow_repo", "job_workflow_path", "job_workflow_target"] # gha_completed_jobs_total: # labels: # [ @@ -335,6 +335,8 @@ controllerServiceAccount: # "job_result", # "job_workflow_ref", # "job_workflow_name", +# "job_workflow_repo", +# "job_workflow_path", # "job_workflow_target", # ] # gauges: @@ -357,7 +359,7 @@ controllerServiceAccount: # histograms: # gha_job_startup_duration_seconds: # labels: -# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref", "job_workflow_name", "job_workflow_target"] +# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref", "job_workflow_name", "job_workflow_repo", "job_workflow_path", "job_workflow_target"] # buckets: # [ # 0.01, @@ -417,6 +419,8 @@ controllerServiceAccount: # "job_result", # "job_workflow_ref", # "job_workflow_name", +# "job_workflow_repo", +# "job_workflow_path", # "job_workflow_target" # ] # buckets: diff --git a/charts/gha-runner-scale-set/values.yaml b/charts/gha-runner-scale-set/values.yaml index 4b4640cf9e..0731e4cc4a 100644 --- a/charts/gha-runner-scale-set/values.yaml +++ b/charts/gha-runner-scale-set/values.yaml @@ -157,7 +157,7 @@ githubConfigSecret: # counters: # gha_started_jobs_total: # labels: -# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref", "job_workflow_name", "job_workflow_target"] +# ["repository", "organization", "enterprise", "job_name", "event_name", "job_workflow_ref", "job_workflow_name", "job_workflow_repo", "job_workflow_path", "job_workflow_target"] # gha_completed_jobs_total: # labels: # [ @@ -169,6 +169,8 @@ githubConfigSecret: # "job_result", # "job_workflow_ref", # "job_workflow_name", +# "job_workflow_repo", +# "job_workflow_path", # "job_workflow_target", # ] # gauges: @@ -191,7 +193,7 @@ githubConfigSecret: # histograms: # gha_job_startup_duration_seconds: # labels: -# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref", "job_workflow_name", "job_workflow_target"] +# ["repository", "organization", "enterprise", "job_name", "event_name","job_workflow_ref", "job_workflow_name", "job_workflow_repo", "job_workflow_path", "job_workflow_target"] # buckets: # [ # 0.01, @@ -251,6 +253,8 @@ githubConfigSecret: # "job_result", # "job_workflow_ref", # "job_workflow_name", +# "job_workflow_repo", +# "job_workflow_path", # "job_workflow_target" # ] # buckets: diff --git a/cmd/ghalistener/metrics/metrics.go b/cmd/ghalistener/metrics/metrics.go index a1bbd47297..81737482dc 100644 --- a/cmd/ghalistener/metrics/metrics.go +++ b/cmd/ghalistener/metrics/metrics.go @@ -22,6 +22,8 @@ const ( labelKeyJobName = "job_name" labelKeyJobWorkflowRef = "job_workflow_ref" labelKeyJobWorkflowName = "job_workflow_name" + labelKeyJobWorkflowRepo = "job_workflow_repo" + labelKeyJobWorkflowPath = "job_workflow_path" labelKeyJobWorkflowTarget = "job_workflow_target" labelKeyEventName = "event_name" labelKeyJobResult = "job_result" @@ -84,6 +86,8 @@ func (e *exporter) jobLabels(jobBase *scaleset.JobMessageBase) prometheus.Labels labelKeyJobName: jobBase.JobDisplayName, labelKeyJobWorkflowRef: jobBase.JobWorkflowRef, labelKeyJobWorkflowName: workflowRefInfo.Name, + labelKeyJobWorkflowRepo: workflowRefInfo.Repo, + labelKeyJobWorkflowPath: workflowRefInfo.FilePath, labelKeyJobWorkflowTarget: workflowRefInfo.Target, labelKeyEventName: jobBase.EventName, } diff --git a/cmd/ghalistener/metrics/metrics_integration_test.go b/cmd/ghalistener/metrics/metrics_integration_test.go index 264fddf807..8199457f4f 100644 --- a/cmd/ghalistener/metrics/metrics_integration_test.go +++ b/cmd/ghalistener/metrics/metrics_integration_test.go @@ -24,6 +24,8 @@ func TestMetricsWithWorkflowRefParsing(t *testing.T) { name string jobBase scaleset.JobMessageBase wantName string + wantRepo string + wantPath string wantTarget string }{ { @@ -36,6 +38,8 @@ func TestMetricsWithWorkflowRefParsing(t *testing.T) { EventName: "push", }, wantName: "build", + wantRepo: "actions/runner", + wantPath: ".github/workflows/build.yml", wantTarget: "heads/main", }, { @@ -48,6 +52,8 @@ func TestMetricsWithWorkflowRefParsing(t *testing.T) { EventName: "push", }, wantName: "ci-cd-pipeline", + wantRepo: "myorg/myrepo", + wantPath: ".github/workflows/ci-cd-pipeline.yml", wantTarget: "heads/feature/new-metrics", }, { @@ -60,6 +66,8 @@ func TestMetricsWithWorkflowRefParsing(t *testing.T) { EventName: "pull_request", }, wantName: "pr-checks", + wantRepo: "actions/runner", + wantPath: ".github/workflows/pr-checks.yml", wantTarget: "pull/123", }, { @@ -72,6 +80,8 @@ func TestMetricsWithWorkflowRefParsing(t *testing.T) { EventName: "release", }, wantName: "release", + wantRepo: "actions/runner", + wantPath: ".github/workflows/release.yml", wantTarget: "tags/v1.2.3", }, } @@ -88,6 +98,8 @@ func TestMetricsWithWorkflowRefParsing(t *testing.T) { labelKeyJobName: tt.jobBase.JobDisplayName, labelKeyJobWorkflowRef: tt.jobBase.JobWorkflowRef, labelKeyJobWorkflowName: tt.wantName, + labelKeyJobWorkflowRepo: tt.wantRepo, + labelKeyJobWorkflowPath: tt.wantPath, labelKeyJobWorkflowTarget: tt.wantTarget, labelKeyEventName: tt.jobBase.EventName, } diff --git a/cmd/ghalistener/metrics/workflow_ref_parser.go b/cmd/ghalistener/metrics/workflow_ref_parser.go index 065ac9f78a..44beaab571 100644 --- a/cmd/ghalistener/metrics/workflow_ref_parser.go +++ b/cmd/ghalistener/metrics/workflow_ref_parser.go @@ -9,6 +9,13 @@ import ( type WorkflowRefInfo struct { // Name is the workflow file name without extension Name string + // Repo is the {owner}/{repo} hosting the workflow file, which can differ + // from the repository running the job when the workflow is reusable + // Example: myorg/myrepo + Repo string + // FilePath is the workflow file path within the hosting repository + // Example: .github/workflows/blank.yml + FilePath string // Target is the target ref with type prefix retained for clarity // Examples: // - heads/main (branch) @@ -18,7 +25,8 @@ type WorkflowRefInfo struct { Target string } -// ParseWorkflowRef parses a job_workflow_ref string to extract workflow name and target +// ParseWorkflowRef parses a job_workflow_ref string to extract workflow name, +// hosting repository, file path, and target // Format: {owner}/{repo}/.github/workflows/{workflow_file}@{ref} // Example: mygithuborg/myrepo/.github/workflows/blank.yml@refs/heads/main // @@ -48,6 +56,15 @@ func ParseWorkflowRef(workflowRef string) WorkflowRefInfo { // Remove .yml or .yaml extension info.Name = strings.TrimSuffix(strings.TrimSuffix(workflowFile, ".yml"), ".yaml") + // Extract the hosting repository ({owner}/{repo}) and the workflow file + // path within it, so that reusable workflows called from another + // repository remain distinguishable + segments := strings.SplitN(workflowPath, "/", 3) + if len(segments) == 3 { + info.Repo = segments[0] + "/" + segments[1] + info.FilePath = segments[2] + } + // Extract target from ref based on type // Branch refs: refs/heads/{branch} // Tag refs: refs/tags/{tag} diff --git a/cmd/ghalistener/metrics/workflow_ref_parser_test.go b/cmd/ghalistener/metrics/workflow_ref_parser_test.go index fcb29efae0..105dcdab0d 100644 --- a/cmd/ghalistener/metrics/workflow_ref_parser_test.go +++ b/cmd/ghalistener/metrics/workflow_ref_parser_test.go @@ -11,60 +11,88 @@ func TestParseWorkflowRef(t *testing.T) { name string workflowRef string wantName string + wantRepo string + wantPath 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", + wantRepo: "actions-runner-controller-sandbox/mumoshu-orgrunner-test-01", + wantPath: ".github/workflows/blank.yml", wantTarget: "heads/main", }, { name: "branch with special characters", workflowRef: "owner/repo/.github/workflows/ci-cd.yml@refs/heads/feature/new-feature", wantName: "ci-cd", + wantRepo: "owner/repo", + wantPath: ".github/workflows/ci-cd.yml", wantTarget: "heads/feature/new-feature", }, { name: "yaml extension", workflowRef: "owner/repo/.github/workflows/deploy.yaml@refs/heads/develop", wantName: "deploy", + wantRepo: "owner/repo", + wantPath: ".github/workflows/deploy.yaml", wantTarget: "heads/develop", }, { name: "tag reference", workflowRef: "owner/repo/.github/workflows/release.yml@refs/tags/v1.0.0", wantName: "release", + wantRepo: "owner/repo", + wantPath: ".github/workflows/release.yml", wantTarget: "tags/v1.0.0", }, { name: "pull request reference", workflowRef: "owner/repo/.github/workflows/test.yml@refs/pull/123/merge", wantName: "test", + wantRepo: "owner/repo", + wantPath: ".github/workflows/test.yml", wantTarget: "pull/123", }, { name: "empty workflow ref", workflowRef: "", wantName: "", + wantRepo: "", + wantPath: "", wantTarget: "", }, { name: "invalid format - no @ separator", workflowRef: "owner/repo/.github/workflows/test.yml", wantName: "", + wantRepo: "", + wantPath: "", wantTarget: "", }, + { + name: "invalid format - path without repo", + workflowRef: "test.yml@refs/heads/main", + wantName: "test", + wantRepo: "", + wantPath: "", + wantTarget: "heads/main", + }, { name: "workflow with dots in name", workflowRef: "owner/repo/.github/workflows/build.test.yml@refs/heads/main", wantName: "build.test", + wantRepo: "owner/repo", + wantPath: ".github/workflows/build.test.yml", 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", + wantRepo: "owner/repo", + wantPath: ".github/workflows/build-test_deploy.yml", wantTarget: "heads/main", }, } @@ -73,8 +101,10 @@ func TestParseWorkflowRef(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := ParseWorkflowRef(tt.workflowRef) expected := WorkflowRefInfo{ - Name: tt.wantName, - Target: tt.wantTarget, + Name: tt.wantName, + Repo: tt.wantRepo, + FilePath: tt.wantPath, + Target: tt.wantTarget, } assert.Equal(t, expected, got, "ParseWorkflowRef(%q) returned unexpected result", tt.workflowRef) })