Skip to content

Commit 531df0f

Browse files
committed
feat: add registryMonitor.status.registryStatus to keep track of registries health
1 parent a73527d commit 531df0f

File tree

5 files changed

+85
-7
lines changed

5 files changed

+85
-7
lines changed

api/kuik/v1alpha1/registrymonitor_types.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,25 @@ type RegistryMonitorSpec struct {
2222
Interval metav1.Duration `json:"interval"`
2323
}
2424

25+
type RegistryStatus string
26+
27+
const (
28+
RegistryStatusUp = RegistryStatus("Up")
29+
RegistryStatusDown = RegistryStatus("Down")
30+
)
31+
2532
// RegistryMonitorStatus defines the observed state of RegistryMonitor.
26-
type RegistryMonitorStatus struct{}
33+
type RegistryMonitorStatus struct {
34+
// RegistryStatus is the status of the registry being monitored
35+
// +kubebuilder:validation:Enum=Up;Down
36+
RegistryStatus RegistryStatus `json:"registryStatus"`
37+
}
2738

2839
// +kubebuilder:object:root=true
2940
// +kubebuilder:subresource:status
3041
// +kubebuilder:resource:scope=Cluster,shortName=regmon
3142
// +kubebuilder:printcolumn:name="Registry",type="string",JSONPath=".spec.registry"
43+
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.registryStatus"
3244
// +kubebuilder:printcolumn:name="Parallel",type="integer",JSONPath=".spec.parallel"
3345
// +kubebuilder:printcolumn:name="MaxPerInterval",type="integer",JSONPath=".spec.maxPerInterval"
3446
// +kubebuilder:printcolumn:name="Interval",type="string",JSONPath=".spec.interval"

config/crd/bases/kuik.enix.io_registrymonitors.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ spec:
2020
- jsonPath: .spec.registry
2121
name: Registry
2222
type: string
23+
- jsonPath: .status.registryStatus
24+
name: Status
25+
type: string
2326
- jsonPath: .spec.parallel
2427
name: Parallel
2528
type: integer
@@ -83,6 +86,15 @@ spec:
8386
type: object
8487
status:
8588
description: RegistryMonitorStatus defines the observed state of RegistryMonitor.
89+
properties:
90+
registryStatus:
91+
description: RegistryStatus is the status of the registry being monitored
92+
enum:
93+
- Up
94+
- Down
95+
type: string
96+
required:
97+
- registryStatus
8698
type: object
8799
type: object
88100
served: true

helm/kube-image-keeper/crds/kuik.enix.io_registrymonitors.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ spec:
1919
- jsonPath: .spec.registry
2020
name: Registry
2121
type: string
22+
- jsonPath: .status.registryStatus
23+
name: Status
24+
type: string
2225
- jsonPath: .spec.parallel
2326
name: Parallel
2427
type: integer
@@ -82,6 +85,15 @@ spec:
8285
type: object
8386
status:
8487
description: RegistryMonitorStatus defines the observed state of RegistryMonitor.
88+
properties:
89+
registryStatus:
90+
description: RegistryStatus is the status of the registry being monitored
91+
enum:
92+
- Up
93+
- Down
94+
type: string
95+
required:
96+
- registryStatus
8597
type: object
8698
type: object
8799
served: true

internal/controller/kuik/registrymonitor_controller.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"sigs.k8s.io/controller-runtime/pkg/client"
2121
"sigs.k8s.io/controller-runtime/pkg/controller"
2222
logf "sigs.k8s.io/controller-runtime/pkg/log"
23+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2324
)
2425

2526
const (
@@ -87,6 +88,21 @@ func (r *RegistryMonitorReconciler) Reconcile(ctx context.Context, req ctrl.Requ
8788

8889
log.Info("found images matching registry", "count", len(images.Items), "monitoredDuringInterval", monitoredDuringInterval)
8990

91+
patch := client.MergeFrom(registryMonitor.DeepCopy())
92+
registryMonitor.Status.RegistryStatus = kuikv1alpha1.RegistryStatusUp
93+
err := registry.HealthCheck(registryMonitor.Spec.Registry, nil, nil)
94+
if err != nil {
95+
registryMonitor.Status.RegistryStatus = kuikv1alpha1.RegistryStatusDown
96+
}
97+
98+
if errStatus := r.Status().Patch(ctx, &registryMonitor, patch); errStatus != nil {
99+
return reconcile.Result{}, fmt.Errorf("failed to patch registrymonitor status: %w", errStatus)
100+
}
101+
102+
if err != nil {
103+
return reconcile.Result{}, fmt.Errorf("registry seems to be down, skipping monitoring of images: %w", err)
104+
}
105+
90106
for i := range min(min(registryMonitor.Spec.MaxPerInterval-monitoredDuringInterval, registryMonitor.Spec.Parallel), len(images.Items)) {
91107
image := images.Items[i]
92108
logImage := logf.Log.WithValues("controller", "imagemonitor", "image", klog.KObj(&image), "reference", image.Reference()).V(1)

internal/registry/registry.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"net/http"
99
"slices"
10+
"time"
1011

1112
"github.com/google/go-containerregistry/pkg/authn"
1213
"github.com/google/go-containerregistry/pkg/name"
@@ -37,17 +38,42 @@ func GetDescriptor(imageName string, insecureRegistries []string, rootCAs *x509.
3738
return remote.Get(sourceRef, opts...)
3839
}
3940

40-
func options(ref name.Reference, keychain authn.Keychain, insecureRegistries []string, rootCAs *x509.CertPool) []remote.Option {
41-
auth := remote.WithAuthFromKeychain(keychain)
42-
opts := []remote.Option{auth}
41+
func HealthCheck(registry string, insecureRegistries []string, rootCAs *x509.CertPool) error {
42+
client := &http.Client{
43+
Transport: unauthenticatedTransport(registry, insecureRegistries, rootCAs),
44+
Timeout: 5 * time.Second, // TODO: make this configurable
45+
}
46+
47+
url := "https://" + registry + "/v2/"
48+
49+
resp, err := client.Head(url)
50+
if err != nil {
51+
return err
52+
}
53+
54+
_ = resp.Body.Close()
55+
56+
if slices.Contains([]int{http.StatusOK, http.StatusUnauthorized, http.StatusForbidden}, resp.StatusCode) {
57+
return nil
58+
}
59+
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
60+
}
61+
62+
func unauthenticatedTransport(registry string, insecureRegistries []string, rootCAs *x509.CertPool) *http.Transport {
4363
transport := http.DefaultTransport.(*http.Transport).Clone()
4464
transport.TLSClientConfig = &tls.Config{RootCAs: rootCAs}
4565

46-
if slices.Contains(insecureRegistries, ref.Context().Registry.RegistryStr()) {
66+
if slices.Contains(insecureRegistries, registry) {
4767
transport.TLSClientConfig.InsecureSkipVerify = true
4868
}
4969

50-
opts = append(opts, remote.WithTransport(transport))
70+
return transport
71+
}
5172

52-
return opts
73+
func options(ref name.Reference, keychain authn.Keychain, insecureRegistries []string, rootCAs *x509.CertPool) []remote.Option {
74+
transport := unauthenticatedTransport(ref.Context().RegistryStr(), insecureRegistries, rootCAs)
75+
return []remote.Option{
76+
remote.WithAuthFromKeychain(keychain),
77+
remote.WithTransport(transport),
78+
}
5379
}

0 commit comments

Comments
 (0)