From 3075cf2e33c6cda3930beb25db7240a62c020334 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 9 May 2026 14:32:51 +0000 Subject: [PATCH 1/3] Initial plan From 547808cfd7abeb7ce8cac4c3b6dc9a844c05e050 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 9 May 2026 14:37:19 +0000 Subject: [PATCH 2/3] fix: prune stale version metrics per container Agent-Logs-Url: https://github.com/jetstack/version-checker/sessions/f773d382-07fb-40b4-8c53-eec3cb652854 Co-authored-by: davidcollom <1504448+davidcollom@users.noreply.github.com> --- pkg/metrics/metrics.go | 8 ++++++++ pkg/metrics/metrics_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index f9235d45..fdf81a84 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -118,6 +118,14 @@ func (m *Metrics) AddImage(namespace, pod, container, containerType, imageURL st isLatestF = 1.0 } + labels := buildContainerPartialLabels(namespace, pod, container, containerType) + + // Remove any existing "current state" gauge series for this container before + // registering the newest values. Otherwise each version change leaves behind + // a distinct Prometheus series due to the current/latest version labels. + m.containerImageVersion.DeletePartialMatch(labels) + m.containerImageChecked.DeletePartialMatch(labels) + m.containerImageVersion.With( buildFullLabels(namespace, pod, container, containerType, imageURL, currentVersion, latestVersion), ).Set(isLatestF) diff --git a/pkg/metrics/metrics_test.go b/pkg/metrics/metrics_test.go index 8fe0efd9..c687e64e 100644 --- a/pkg/metrics/metrics_test.go +++ b/pkg/metrics/metrics_test.go @@ -72,6 +72,33 @@ func TestCache(t *testing.T) { } } +func TestAddImageReplacesExistingVersionMetrics(t *testing.T) { + reg := prometheus.NewRegistry() + m := New(logrus.NewEntry(logrus.New()), reg, fakek8s) + + m.AddImage("namespace", "pod", "container", "container", "url", false, "1.0.0", "1.1.0") + m.AddImage("namespace", "pod", "container", "container", "url", true, "1.1.0", "1.1.0") + + assert.Equal(t, 1, + testutil.CollectAndCount(m.containerImageVersion.MetricVec, MetricNamespace+"_is_latest_version"), + ) + assert.Equal(t, 1, + testutil.CollectAndCount(m.containerImageChecked.MetricVec, MetricNamespace+"_last_checked"), + ) + + staleMetric, err := m.containerImageVersion.GetMetricWith( + buildFullLabels("namespace", "pod", "container", "container", "url", "1.0.0", "1.1.0"), + ) + require.NoError(t, err) + assert.Equal(t, float64(0), testutil.ToFloat64(staleMetric)) + + currentMetric, err := m.containerImageVersion.GetMetricWith( + buildFullLabels("namespace", "pod", "container", "container", "url", "1.1.0", "1.1.0"), + ) + require.NoError(t, err) + assert.Equal(t, float64(1), testutil.ToFloat64(currentMetric)) +} + // TestErrorsReporting verifies that the error metric increments correctly func TestErrorsReporting(t *testing.T) { m := New(logrus.NewEntry(logrus.New()), prometheus.NewRegistry(), fakek8s) From 24e50357d52056334da84563ded36710cfe28580 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 9 May 2026 14:40:11 +0000 Subject: [PATCH 3/3] test: assert stale version metrics are removed Agent-Logs-Url: https://github.com/jetstack/version-checker/sessions/f773d382-07fb-40b4-8c53-eec3cb652854 Co-authored-by: davidcollom <1504448+davidcollom@users.noreply.github.com> --- pkg/metrics/metrics_test.go | 45 +++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/pkg/metrics/metrics_test.go b/pkg/metrics/metrics_test.go index c687e64e..a7847a7c 100644 --- a/pkg/metrics/metrics_test.go +++ b/pkg/metrics/metrics_test.go @@ -15,6 +15,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" + dto "github.com/prometheus/client_model/go" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -86,11 +87,17 @@ func TestAddImageReplacesExistingVersionMetrics(t *testing.T) { testutil.CollectAndCount(m.containerImageChecked.MetricVec, MetricNamespace+"_last_checked"), ) - staleMetric, err := m.containerImageVersion.GetMetricWith( - buildFullLabels("namespace", "pod", "container", "container", "url", "1.0.0", "1.1.0"), - ) + metricFamilies, err := reg.Gather() require.NoError(t, err) - assert.Equal(t, float64(0), testutil.ToFloat64(staleMetric)) + assert.False(t, hasMetricWithLabels(metricFamilies, MetricNamespace+"_is_latest_version", map[string]string{ + "namespace": "namespace", + "pod": "pod", + "container": "container", + "container_type": "container", + "image": "url", + "current_version": "1.0.0", + "latest_version": "1.1.0", + })) currentMetric, err := m.containerImageVersion.GetMetricWith( buildFullLabels("namespace", "pod", "container", "container", "url", "1.1.0", "1.1.0"), @@ -99,6 +106,36 @@ func TestAddImageReplacesExistingVersionMetrics(t *testing.T) { assert.Equal(t, float64(1), testutil.ToFloat64(currentMetric)) } +func hasMetricWithLabels(metricFamilies []*dto.MetricFamily, name string, labels map[string]string) bool { + for _, metricFamily := range metricFamilies { + if metricFamily.GetName() != name { + continue + } + + for _, metric := range metricFamily.GetMetric() { + if metricHasLabels(metric, labels) { + return true + } + } + } + + return false +} + +func metricHasLabels(metric *dto.Metric, labels map[string]string) bool { + if len(metric.GetLabel()) != len(labels) { + return false + } + + for _, label := range metric.GetLabel() { + if labels[label.GetName()] != label.GetValue() { + return false + } + } + + return true +} + // TestErrorsReporting verifies that the error metric increments correctly func TestErrorsReporting(t *testing.T) { m := New(logrus.NewEntry(logrus.New()), prometheus.NewRegistry(), fakek8s)