Skip to content

Commit 8256141

Browse files
authored
Feature/runner list (#69)
* Start adding Runner structs and garbage collector functions * Add runners and new schemas * Fix Redis methods to manipulate runner data * Fix missing returned error name * Update documentation * Set gathering runner information to true by default * Update store and fix duplicates entries for runners * Update store and fix duplicates entries for runners * Update add new runners as metrics
1 parent fa82b3d commit 8256141

23 files changed

Lines changed: 906 additions & 19 deletions

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ coverage
3838
gitlab-ci-pipelines-exporter
3939

4040
!cmd/gitlab-ci-exporter
41-
!examples/**/gitlab-ci-exporter
41+
42+
#!examples/**/gitlab-ci-exporter
43+
examples/*
4244

4345
# IDE and OS stuff
4446
.idea

docs/metrics.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
| `gcpe_metrics_count` | Number of GitLab pipelines metrics being exported || *available by default* |
1414
| `gcpe_projects_count` | Number of GitLab projects being exported || *available by default* |
1515
| `gcpe_refs_count` | Number of GitLab refs being exported || *available by default* |
16+
| `gcpe_runners_count` | Number of Runners being exported || *available by default* |
1617
| `gitlab_ci_environment_behind_commits_count` | Number of commits the environment is behind given its last deployment | [project], [environment] | `project_defaults.pull.environments.enabled` |
1718
| `gitlab_ci_environment_behind_duration_seconds` | Duration in seconds the environment is behind the most recent commit given its last deployment | [project], [environment] | `project_defaults.pull.environments.enabled` |
1819
| `gitlab_ci_environment_deployment_count` |Number of deployments for an environment | [project], [environment] | `project_defaults.pull.environments.enabled` |

pkg/config/config.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ type Pull struct {
129129
IntervalSeconds int `default:"1800" validate:"gte=1" yaml:"interval_seconds"` // IntervalSeconds defines the interval in seconds between scheduled fetches.
130130
} `yaml:"environments_from_projects"`
131131

132+
// RunnersFromProjects TODO
133+
RunnersFromProjects struct {
134+
OnInit bool `default:"true" yaml:"on_init"`
135+
Scheduled bool `default:"true" yaml:"scheduled"`
136+
IntervalSeconds int `default:"1800" validate:"gte=1" yaml:"interval_seconds"`
137+
} `yaml:"runners_from_projects"`
138+
132139
// RefsFromProjects configures the fetching of refs (branches, tags, MRs) for projects.
133140
RefsFromProjects struct {
134141
OnInit bool `default:"true" yaml:"on_init"` // OnInit determines whether refs should be fetched once at startup.
@@ -160,6 +167,13 @@ type GarbageCollect struct {
160167
IntervalSeconds int `default:"14400" validate:"gte=1" yaml:"interval_seconds"` // 4 hours
161168
} `yaml:"environments"`
162169

170+
// Runners TODO
171+
Runners struct {
172+
OnInit bool `default:"false" yaml:"on_init"`
173+
Scheduled bool `default:"true" yaml:"scheduled"`
174+
IntervalSeconds int `default:"14400" validate:"gte=1" yaml:"interval_seconds"`
175+
} `yaml:"runners"`
176+
163177
// Refs configures cleanup behavior related to Git references (branches, tags, etc).
164178
Refs struct {
165179
OnInit bool `default:"false" yaml:"on_init"`

pkg/config/project.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type ProjectParameters struct {
2121
// such as environments, refs (branches, tags, MRs), and pipelines.
2222
type ProjectPull struct {
2323
Environments ProjectPullEnvironments `yaml:"environments"`
24+
Runners ProjectPullRunners `yaml:"runners"`
2425
Refs ProjectPullRefs `yaml:"refs"`
2526
Pipeline ProjectPullPipeline `yaml:"pipeline"`
2627
}
@@ -40,6 +41,13 @@ type ProjectPullEnvironments struct {
4041
ExcludeStopped bool `default:"true" yaml:"exclude_stopped"`
4142
}
4243

44+
// ProjectPullRunners configures if and how runners are pulled for a project.
45+
type ProjectPullRunners struct {
46+
Enabled bool `default:"true" yaml:"enabled"`
47+
Regexp string `default:".*" yaml:"regexp"`
48+
ExcludeStopped bool `default:"false" yaml:"exclude_stopped"`
49+
}
50+
4351
// ProjectPullRefs contains configuration for pulling refs: branches, tags, and merge requests.
4452
type ProjectPullRefs struct {
4553
Branches ProjectPullRefsBranches `yaml:"branches"`

pkg/controller/collectors.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package controller
22

3-
import "github.com/prometheus/client_golang/prometheus"
3+
import (
4+
"github.com/prometheus/client_golang/prometheus"
5+
)
46

57
// Commonly used label sets for Prometheus metrics in this application.
68
// These labels help categorize and filter metrics for better observability.
@@ -39,6 +41,12 @@ var (
3941
"created", "waiting_for_resource", "preparing", "pending", "running",
4042
"success", "failed", "canceled", "skipped", "manual", "scheduled", "error", "success_with_warnings",
4143
}
44+
45+
// runnerLabels defines labels for metrics related to runner deploy.
46+
runnerLabels = []string{
47+
"project", "runner_description", "runner_name", "runner_id", "is_shared", "runner_type", "runner_projects",
48+
"online", "tag_list", "active", "status", "runner_groups",
49+
}
4250
)
4351

4452
// NewInternalCollectorCurrentlyQueuedTasksCount creates and returns a new Prometheus GaugeVec metric collector
@@ -71,6 +79,21 @@ func NewInternalCollectorEnvironmentsCount() prometheus.Collector {
7179
)
7280
}
7381

82+
// NewInternalCollectorRunnersCount returns a new Prometheus gauge collector
83+
// for the metric "gcpe_runners_count" which tracks the total number of GitLab runners
84+
// currently being exported by the application.
85+
//
86+
// This metric has no labels, representing a simple numeric gauge.
87+
func NewInternalCollectorRunnersCount() prometheus.Collector {
88+
return prometheus.NewGaugeVec(
89+
prometheus.GaugeOpts{
90+
Name: "gcpe_runners_count",
91+
Help: "Number of GitLab runners being exported",
92+
},
93+
[]string{}, // no labels for this metric
94+
)
95+
}
96+
7497
// NewInternalCollectorExecutedTasksCount returns a new Prometheus gauge collector
7598
// for the metric "gcpe_executed_tasks_count" which tracks the total number of tasks
7699
// that have been executed by the system.
@@ -534,6 +557,21 @@ func NewCollectorRunCount() prometheus.Collector {
534557
)
535558
}
536559

560+
// NewCollectorRunners returns a new Prometheus gauge collector for the
561+
// metric "gitlab_ci_runners". This metric reports information about your runners.
562+
//
563+
// The labels include the default set of labels plus an additional "runnerLabels" label
564+
// and "runnnerInformationLabels" that describes the runners state.
565+
func NewCollectorRunners() prometheus.Collector {
566+
return prometheus.NewGaugeVec(
567+
prometheus.GaugeOpts{
568+
Name: "gitlab_ci_runners",
569+
Help: "Status of your runners",
570+
},
571+
runnerLabels,
572+
)
573+
}
574+
537575
// NewCollectorTestReportTotalTime returns a new Prometheus gauge collector for the
538576
// metric "gitlab_ci_pipeline_test_report_total_time". This metric tracks the total
539577
// duration, in seconds, of all tests executed in the most recently finished pipeline.

pkg/controller/controller.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func (c *Controller) registerTasks() {
9494
schemas.TaskTypeGarbageCollectMetrics: c.TaskHandlerGarbageCollectMetrics,
9595
schemas.TaskTypeGarbageCollectProjects: c.TaskHandlerGarbageCollectProjects,
9696
schemas.TaskTypeGarbageCollectRefs: c.TaskHandlerGarbageCollectRefs,
97+
schemas.TaskTypeGarbageCollectRunners: c.TaskHandlerGarbageCollectRunners,
9798
schemas.TaskTypePullEnvironmentMetrics: c.TaskHandlerPullEnvironmentMetrics,
9899
schemas.TaskTypePullEnvironmentsFromProject: c.TaskHandlerPullEnvironmentsFromProject,
99100
schemas.TaskTypePullEnvironmentsFromProjects: c.TaskHandlerPullEnvironmentsFromProjects,
@@ -104,6 +105,9 @@ func (c *Controller) registerTasks() {
104105
schemas.TaskTypePullRefMetrics: c.TaskHandlerPullRefMetrics,
105106
schemas.TaskTypePullRefsFromProject: c.TaskHandlerPullRefsFromProject,
106107
schemas.TaskTypePullRefsFromProjects: c.TaskHandlerPullRefsFromProjects,
108+
schemas.TaskTypePullRunnersMetrics: c.TaskHandlerPullRunnerMetrics,
109+
schemas.TaskTypePullRunnersFromProject: c.TaskHandlerPullRunnersFromProject,
110+
schemas.TaskTypePullRunnersFromProjects: c.TaskHandlerPullRunnersFromProjects,
107111
} {
108112
_, _ = c.TaskController.TaskMap.Register(string(n), &taskq.TaskConfig{
109113
Handler: h,

pkg/controller/garbage_collector.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package controller
22

33
import (
44
"context"
5+
"fmt"
56
"reflect"
67
"regexp"
78

@@ -279,6 +280,115 @@ func (c *Controller) GarbageCollectRefs(ctx context.Context) error {
279280
return nil
280281
}
281282

283+
// GarbageCollectRunners cleans up stored runners that are no longer valid or configured.
284+
func (c *Controller) GarbageCollectRunners(ctx context.Context) error {
285+
// Log the start and end of the garbage collection process
286+
log.Info("starting 'runners' garbage collection")
287+
defer log.Info("ending 'runners' garbage collection")
288+
289+
// Retrieve all Runners currently stored
290+
storedRunners, err := c.Store.Runners(ctx)
291+
if err != nil {
292+
return err
293+
}
294+
295+
// Map to keep track of projects which have runners that need refreshing
296+
runnerProjects := make(map[schemas.Project]bool)
297+
298+
// Iterate over each stored runner
299+
for _, runner := range storedRunners {
300+
// Create a Project object from the runner's project name
301+
p := schemas.NewProject(runner.ProjectName)
302+
303+
// Check if the associated project still exists in the store
304+
projectExists, err := c.Store.ProjectExists(ctx, p.Key())
305+
if err != nil {
306+
return err
307+
}
308+
309+
// If the project no longer exists, delete the runner and continue to next
310+
if !projectExists {
311+
if err = deleteRunner(ctx, c.Store, runner, "non-existent-project"); err != nil {
312+
return err
313+
}
314+
continue
315+
}
316+
317+
// Retrieve the latest project data from the store
318+
if err = c.Store.GetProject(ctx, &p); err != nil {
319+
return err
320+
}
321+
322+
// If runner pulling is disabled for this project, delete the runner
323+
if !p.Pull.Runners.Enabled {
324+
if err = deleteRunner(ctx, c.Store, runner, "project-pull-runners-disabled"); err != nil {
325+
return err
326+
}
327+
continue
328+
}
329+
330+
// Mark this project to refresh its runners from the GitLab API later
331+
runnerProjects[p] = true
332+
333+
// Use the project's configured regex to check if this runner should be kept
334+
re := regexp.MustCompile(p.Pull.Runners.Regexp)
335+
if !re.MatchString(runner.Description) {
336+
if err = deleteRunner(ctx, c.Store, runner, "runner-not-in-regexp"); err != nil {
337+
return err
338+
}
339+
continue
340+
}
341+
342+
// If the environment's OutputSparseStatusMetrics setting differs from the project's,
343+
// update the environment and save it back to the store
344+
if runner.OutputSparseStatusMetrics != p.OutputSparseStatusMetrics {
345+
runner.OutputSparseStatusMetrics = p.OutputSparseStatusMetrics
346+
347+
if err = c.Store.SetRunner(ctx, runner); err != nil {
348+
return err
349+
}
350+
351+
log.WithFields(log.Fields{
352+
"project-name": runner.ProjectName,
353+
"runner-name": runner.Name,
354+
}).Info("updated runner configuration to match associated project")
355+
}
356+
}
357+
358+
// Prepare a map to hold runners refreshed from the GitLab API
359+
existingRunners := make(schemas.Runners)
360+
361+
// For each project with tracked runners, fetch runners from GitLab API
362+
for p := range runnerProjects {
363+
projectRunners, err := c.Gitlab.GetProjectRunners(ctx, p)
364+
if err != nil {
365+
return err
366+
}
367+
368+
// Merge fetched runners into the existingRunners map
369+
if err = mergo.Merge(&existingRunners, projectRunners); err != nil {
370+
return err
371+
}
372+
}
373+
374+
// Reload the stored runners after possible updates
375+
storedRunners, err = c.Store.Runners(ctx)
376+
if err != nil {
377+
return err
378+
}
379+
380+
// Delete runners from the store that do not exist anymore in the API data
381+
for k, runner := range storedRunners {
382+
if _, exists := existingRunners[k]; !exists {
383+
if err = deleteRunner(ctx, c.Store, runner, "non-existent-runner"); err != nil {
384+
return err
385+
}
386+
}
387+
}
388+
389+
return nil
390+
}
391+
282392
// GarbageCollectMetrics performs cleanup of obsolete or invalid metrics in the store.
283393
// It ensures metrics linked to non-existent projects, refs, or environments are removed,
284394
// and also respects project-level settings that may disable certain metrics.
@@ -304,6 +414,13 @@ func (c *Controller) GarbageCollectMetrics(ctx context.Context) error {
304414
return err
305415
}
306416

417+
// TODO: This function must be checked (Runner metrics implementation)
418+
storedRunners, err := c.Store.Runners(ctx)
419+
if err != nil {
420+
return err
421+
}
422+
fmt.Println("Stored Runners Metrics:", storedRunners)
423+
307424
storedRefs, err := c.Store.Refs(ctx)
308425
if err != nil {
309426
return err
@@ -404,6 +521,12 @@ func (c *Controller) GarbageCollectMetrics(ctx context.Context) error {
404521
// no action for other kinds here
405522
}
406523
}
524+
// TODO: Handle metrics related to Runner
525+
/**
526+
switch m.Kind {
527+
case schemas.MetricKindRunner
528+
}
529+
*/
407530

408531
// Handle metrics related to an Environment.
409532
if metricLabelEnvironmentExists {
@@ -469,6 +592,23 @@ func deleteEnv(ctx context.Context, s store.Store, env schemas.Environment, reas
469592
return
470593
}
471594

595+
// deleteRunner removes the specified runner from the store and logs the deletion reason.
596+
// It takes a context, the store interface, the runner to delete, and a reason string.
597+
// If deletion fails, it returns the error to the caller.
598+
func deleteRunner(ctx context.Context, s store.Store, runner schemas.Runner, reason string) (err error) {
599+
if err = s.DelRunner(ctx, runner.Key()); err != nil {
600+
return
601+
}
602+
603+
log.WithFields(log.Fields{
604+
"project-name": runner.ProjectName,
605+
"runner-name": runner.Name,
606+
"reason": reason,
607+
}).Info("deleted runner from the store")
608+
609+
return
610+
}
611+
472612
// deleteRef removes the specified ref from the store and logs the deletion reason.
473613
// It takes a context, the store interface, the ref to delete, and a reason string.
474614
// If deletion fails, it returns the error to the caller.

pkg/controller/metrics.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Registry struct {
2828
MetricsCount prometheus.Collector // Number of exported metrics.
2929
ProjectsCount prometheus.Collector // Total number of GitLab projects tracked.
3030
RefsCount prometheus.Collector // Total number of Git refs (branches, tags, MRs).
31+
RunnersCount prometheus.Collector // Total number of Runners exported.
3132
}
3233

3334
// Collectors maps each MetricKind to its Prometheus collector.
@@ -80,6 +81,7 @@ func NewRegistry(ctx context.Context) *Registry {
8081
schemas.MetricKindTestSuiteErrorCount: NewCollectorTestSuiteErrorCount(),
8182
schemas.MetricKindTestCaseExecutionTime: NewCollectorTestCaseExecutionTime(),
8283
schemas.MetricKindTestCaseStatus: NewCollectorTestCaseStatus(),
84+
schemas.MetricKindRunner: NewCollectorRunners(),
8385
},
8486
}
8587

@@ -109,6 +111,7 @@ func (r *Registry) RegisterInternalCollectors() {
109111
r.InternalCollectors.MetricsCount = NewInternalCollectorMetricsCount() // Number of metrics exported
110112
r.InternalCollectors.ProjectsCount = NewInternalCollectorProjectsCount() // Number of tracked projects
111113
r.InternalCollectors.RefsCount = NewInternalCollectorRefsCount() // Number of Git refs (branches, tags, etc.)
114+
r.InternalCollectors.RunnersCount = NewInternalCollectorRunnersCount() // Number of Runners exported
112115

113116
// Register all initialized internal collectors with the Prometheus registry.
114117
// The underscore `_` ignores any error returned by Register (e.g., if already registered).
@@ -121,6 +124,7 @@ func (r *Registry) RegisterInternalCollectors() {
121124
_ = r.Register(r.InternalCollectors.MetricsCount)
122125
_ = r.Register(r.InternalCollectors.ProjectsCount)
123126
_ = r.Register(r.InternalCollectors.RefsCount)
127+
_ = r.Register(r.InternalCollectors.RunnersCount)
124128
}
125129

126130
// ExportInternalMetrics gathers internal statistics from the store and GitLab client,
@@ -133,6 +137,7 @@ func (r *Registry) ExportInternalMetrics(ctx context.Context, g *gitlab.Client,
133137
metricsCount int64 // Number of stored/exported metrics
134138
projectsCount int64 // Number of projects tracked
135139
refsCount int64 // Number of Git references (branches/tags)
140+
runnersCount int64 // Number of Runners exported
136141
)
137142

138143
// Retrieve the number of currently queued tasks from the store
@@ -171,6 +176,11 @@ func (r *Registry) ExportInternalMetrics(ctx context.Context, g *gitlab.Client,
171176
return
172177
}
173178

179+
runnersCount, err = s.RunnersCount(ctx)
180+
if err != nil {
181+
return
182+
}
183+
174184
// Set Prometheus gauge values for each internal metric.
175185
// All collectors are asserted as GaugeVec and updated with empty labels.
176186
r.InternalCollectors.CurrentlyQueuedTasksCount.(*prometheus.GaugeVec).With(prometheus.Labels{}).Set(float64(currentlyQueuedTasks))
@@ -182,6 +192,7 @@ func (r *Registry) ExportInternalMetrics(ctx context.Context, g *gitlab.Client,
182192
r.InternalCollectors.MetricsCount.(*prometheus.GaugeVec).With(prometheus.Labels{}).Set(float64(metricsCount))
183193
r.InternalCollectors.ProjectsCount.(*prometheus.GaugeVec).With(prometheus.Labels{}).Set(float64(projectsCount))
184194
r.InternalCollectors.RefsCount.(*prometheus.GaugeVec).With(prometheus.Labels{}).Set(float64(refsCount))
195+
r.InternalCollectors.RunnersCount.(*prometheus.GaugeVec).With(prometheus.Labels{}).Set(float64(runnersCount))
185196

186197
return
187198
}

0 commit comments

Comments
 (0)