Skip to content

Commit bf87bb1

Browse files
Merge pull request #59 from PDOK/jd/uptime-operator
Improved uptime-operator annotations
2 parents f21b003 + 83b9e2b commit bf87bb1

File tree

9 files changed

+135
-140
lines changed

9 files changed

+135
-140
lines changed

api/v3/shared_types.go

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package v3
22

33
import (
4-
//nolint:gosec
5-
"crypto/sha1"
6-
"encoding/hex"
7-
"io"
84
"net/url"
95
"strings"
106

@@ -45,14 +41,20 @@ type WMSWFS interface {
4541
PodSpecPatch() *corev1.PodSpec
4642
HorizontalPodAutoscalerPatch() *HorizontalPodAutoscalerPatch
4743
Type() ServiceType
44+
TypedName() string
4845
Options() Options
4946
HasPostgisData() bool
50-
// Sha1 hash of the objects name
51-
ID() string
47+
5248
// URLPath returns the configured service URL
5349
URLPath() string
5450

5551
GeoPackages() []*Gpkg
52+
53+
ReadinessQueryString() (string, error)
54+
55+
// TODO implement healthcheck in CR
56+
// StartUpQueryString() string
57+
// LivenessQueryString() string
5658
}
5759

5860
// Mapfile references a ConfigMap key where an external mapfile is stored.
@@ -260,12 +262,6 @@ func GetBaseURLPath[T WMSWFS](o T) string {
260262
return strings.TrimPrefix(parsed.Path, "/")
261263
}
262264

263-
func GetBaseURLPathWithoutTypeAndVersion[T WMSWFS](o T) string {
264-
serviceURL := o.URLPath()
265-
parsed, _ := url.Parse(serviceURL)
266-
return strings.TrimPrefix(parsed.Path, "/")
267-
}
268-
269265
func (d *Data) GetColumns() *[]Column {
270266
switch {
271267
case d.Gpkg != nil:
@@ -299,14 +295,6 @@ func (d *Data) GetGeometryType() *string {
299295
}
300296
}
301297

302-
func Sha1HashOfName[O WMSWFS](obj O) string {
303-
//nolint:gosec
304-
s := sha1.New()
305-
_, _ = io.WriteString(s, obj.GetName())
306-
307-
return hex.EncodeToString(s.Sum(nil))
308-
}
309-
310298
func (o Options) UseWebserviceProxy() bool {
311299
// options.DisableWebserviceProxy not set or false
312300
return !o.DisableWebserviceProxy

api/v3/wfs_types.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ SOFTWARE.
2525
package v3
2626

2727
import (
28+
"errors"
2829
"slices"
30+
"strings"
2931

3032
shared_model "github.com/pdok/smooth-operator/model"
3133
corev1 "k8s.io/api/core/v1"
@@ -233,6 +235,17 @@ func (wfs *WFS) Type() ServiceType {
233235
return ServiceTypeWFS
234236
}
235237

238+
func (wfs *WFS) TypedName() string {
239+
name := wfs.GetName()
240+
typeSuffix := strings.ToLower(string(ServiceTypeWFS))
241+
242+
if strings.HasSuffix(name, typeSuffix) {
243+
return name
244+
}
245+
246+
return name + "-" + typeSuffix
247+
}
248+
236249
func (wfs *WFS) PodSpecPatch() *corev1.PodSpec {
237250
return wfs.Spec.PodSpecPatch
238251
}
@@ -249,10 +262,6 @@ func (wfs *WFS) Options() Options {
249262
return *wfs.Spec.Options
250263
}
251264

252-
func (wfs *WFS) ID() string {
253-
return Sha1HashOfName(wfs)
254-
}
255-
256265
func (wfs *WFS) URLPath() string {
257266
return wfs.Spec.Service.URL
258267
}
@@ -268,3 +277,10 @@ func (wfs *WFS) GeoPackages() []*Gpkg {
268277

269278
return gpkgs
270279
}
280+
281+
func (wfs *WFS) ReadinessQueryString() (string, error) {
282+
if len(wfs.Spec.Service.FeatureTypes) == 0 {
283+
return "nil", errors.New("cannot get readiness probe for WFS, featuretypes could not be found")
284+
}
285+
return "SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAMES=" + wfs.Spec.Service.FeatureTypes[0].Name + "&STARTINDEX=0&COUNT=1", nil
286+
}

api/v3/wms_types.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@ SOFTWARE.
2525
package v3
2626

2727
import (
28+
"errors"
29+
"fmt"
2830
"maps"
2931
"slices"
3032
"sort"
33+
"strings"
3134

3235
"k8s.io/apimachinery/pkg/runtime/schema"
3336

@@ -571,6 +574,17 @@ func (wms *WMS) Type() ServiceType {
571574
return ServiceTypeWMS
572575
}
573576

577+
func (wms *WMS) TypedName() string {
578+
name := wms.GetName()
579+
typeSuffix := strings.ToLower(string(ServiceTypeWMS))
580+
581+
if strings.HasSuffix(name, typeSuffix) {
582+
return name
583+
}
584+
585+
return name + "-" + typeSuffix
586+
}
587+
574588
func (wms *WMS) PodSpecPatch() *corev1.PodSpec {
575589
return wms.Spec.PodSpecPatch
576590
}
@@ -587,10 +601,6 @@ func (wms *WMS) Options() Options {
587601
return *wms.Spec.Options
588602
}
589603

590-
func (wms *WMS) ID() string {
591-
return Sha1HashOfName(wms)
592-
}
593-
594604
func (wms *WMS) URLPath() string {
595605
return wms.Spec.Service.URL
596606
}
@@ -614,3 +624,24 @@ func (wms *WMS) GeoPackages() []*Gpkg {
614624

615625
return gpkgs
616626
}
627+
628+
func (wms *WMS) HealthCheckBBox() string {
629+
// TODO make dynamic
630+
return "190061.4619730016857,462435.5987861062749,202917.7508707302331,473761.6884966178914"
631+
}
632+
633+
func (wms *WMS) ReadinessQueryString() (string, error) {
634+
// TODO implement healthcheck from CR
635+
firstDataLayerName := ""
636+
for _, layer := range wms.Spec.Service.GetAllLayers() {
637+
if layer.IsDataLayer() {
638+
firstDataLayerName = *layer.Name
639+
break
640+
}
641+
}
642+
if firstDataLayerName == "" {
643+
return "", errors.New("cannot get readiness probe for WMS, the first datalayer could not be found")
644+
}
645+
646+
return fmt.Sprintf("SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&BBOX=%s&CRS=EPSG:28992&WIDTH=100&HEIGHT=100&LAYERS=%s&STYLES=&FORMAT=image/png", wms.HealthCheckBBox(), firstDataLayerName), nil
647+
}

cmd/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func main() {
9292
var multitoolImage, mapfileGeneratorImage, mapserverImage, capabilitiesGeneratorImage, featureinfoGeneratorImage, ogcWebserviceProxyImage, apacheExporterImage string
9393
var slackWebhookURL string
9494
var logLevel int
95+
var setUptimeOperatorAnnotations bool
9596
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metrics endpoint binds to. "+
9697
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
9798
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
@@ -114,6 +115,7 @@ func main() {
114115
flag.IntVar(&mapserverDebugLevel, "mapserver-debug-level", 0, "Debug level for the mapserver container, between 0 (error only) and 5 (very very verbose).")
115116
flag.StringVar(&slackWebhookURL, "slack-webhook-url", "", "The webhook url for sending slack messages. Disabled if left empty")
116117
flag.IntVar(&logLevel, "log-level", 0, "The zapcore loglevel. 0 = info, 1 = warn, 2 = error")
118+
flag.BoolVar(&setUptimeOperatorAnnotations, "set-uptime-operator-annotations", true, "When enabled IngressRoutes get annotations that are used by the pdok/uptime-operator.")
117119

118120
opts := zap.Options{
119121
Development: true,
@@ -137,6 +139,7 @@ func main() {
137139
}
138140
pdoknlv3.SetHost(host)
139141
mapfilegenerator.SetDebugLevel(mapserverDebugLevel)
142+
controller.SetUptimeOperatorAnnotations(setUptimeOperatorAnnotations)
140143

141144
// if the enable-http2 flag is false (the default), http/2 should be disabled
142145
// due to its vulnerabilities. More specifically, disabling http/2 will

internal/controller/ingressroute.go

Lines changed: 40 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package controller
22

33
import (
4-
"errors"
4+
"regexp"
55
"strings"
66

7+
"github.com/pdok/mapserver-operator/internal/controller/utils"
8+
79
pdoknlv3 "github.com/pdok/mapserver-operator/api/v3"
810
smoothoperatorutils "github.com/pdok/smooth-operator/pkg/util"
911
traefikiov1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
@@ -12,6 +14,12 @@ import (
1214
ctrl "sigs.k8s.io/controller-runtime"
1315
)
1416

17+
var setUptimeOperatorAnnotations = true
18+
19+
func SetUptimeOperatorAnnotations(set bool) {
20+
setUptimeOperatorAnnotations = set
21+
}
22+
1523
func getBareIngressRoute[O pdoknlv3.WMSWFS](obj O) *traefikiov1alpha1.IngressRoute {
1624
return &traefikiov1alpha1.IngressRoute{
1725
ObjectMeta: metav1.ObjectMeta{
@@ -29,23 +37,24 @@ func mutateIngressRoute[R Reconciler, O pdoknlv3.WMSWFS](r R, obj O, ingressRout
2937
return err
3038
}
3139

32-
var uptimeURL string
33-
switch any(obj).(type) {
34-
case *pdoknlv3.WFS:
35-
uptimeURL = any(obj).(*pdoknlv3.WFS).Spec.Service.URL // TODO add healthcheck query
36-
case *pdoknlv3.WMS:
37-
uptimeURL = any(obj).(*pdoknlv3.WMS).Spec.Service.URL // TODO add healthcheck query
38-
}
40+
annotations := smoothoperatorutils.CloneOrEmptyMap(obj.GetAnnotations())
41+
if setUptimeOperatorAnnotations {
42+
tags := []string{"public-stats", strings.ToLower(string(obj.Type()))}
3943

40-
uptimeName, err := makeUptimeName(obj)
41-
if err != nil {
42-
return err
44+
if obj.Inspire() != nil {
45+
tags = append(tags, "inspire")
46+
}
47+
48+
queryString, err := obj.ReadinessQueryString()
49+
if err != nil {
50+
return err
51+
}
52+
53+
annotations["uptime.pdok.nl/id"] = utils.Sha1Hash(obj.TypedName())
54+
annotations["uptime.pdok.nl/name"] = getUptimeName(obj)
55+
annotations["uptime.pdok.nl/url"] = obj.URLPath() + "?" + queryString
56+
annotations["uptime.pdok.nl/tags"] = strings.Join(tags, ",")
4357
}
44-
annotations := smoothoperatorutils.CloneOrEmptyMap(obj.GetAnnotations())
45-
annotations["uptime.pdok.nl/id"] = obj.ID()
46-
annotations["uptime.pdok.nl/name"] = uptimeName
47-
annotations["uptime.pdok.nl/url"] = uptimeURL
48-
annotations["uptime.pdok.nl/tags"] = strings.Join(makeUptimeTags(obj), ",")
4958
ingressRoute.SetAnnotations(annotations)
5059

5160
mapserverService := traefikiov1alpha1.Service{
@@ -108,90 +117,33 @@ func mutateIngressRoute[R Reconciler, O pdoknlv3.WMSWFS](r R, obj O, ingressRout
108117
}}
109118
}
110119

111-
// Add finalizers
112-
ingressRoute.Finalizers = []string{"uptime.pdok.nl/finalizer"}
113-
114120
if err := smoothoperatorutils.EnsureSetGVK(reconcilerClient, ingressRoute, ingressRoute); err != nil {
115121
return err
116122
}
117123
return ctrl.SetControllerReference(obj, ingressRoute, getReconcilerScheme(r))
118124
}
119125

120-
func makeUptimeTags[O pdoknlv3.WMSWFS](obj O) []string {
121-
tags := []string{"public-stats", strings.ToLower(string(obj.Type()))}
122-
123-
switch any(obj).(type) {
124-
case *pdoknlv3.WFS:
125-
wfs, _ := any(obj).(*pdoknlv3.WFS)
126-
if wfs.Spec.Service.Inspire != nil {
127-
tags = append(tags, "inspire")
128-
}
129-
case *pdoknlv3.WMS:
130-
wms, _ := any(obj).(*pdoknlv3.WMS)
131-
if wms.Spec.Service.Inspire != nil {
132-
tags = append(tags, "inspire")
133-
}
134-
}
135-
136-
return tags
137-
}
138-
139-
func makeUptimeName[O pdoknlv3.WMSWFS](obj O) (string, error) {
140-
var parts []string
141-
142-
inspire := false
143-
switch any(obj).(type) {
144-
case *pdoknlv3.WFS:
145-
inspire = any(obj).(*pdoknlv3.WFS).Spec.Service.Inspire != nil
146-
case *pdoknlv3.WMS:
147-
inspire = any(obj).(*pdoknlv3.WMS).Spec.Service.Inspire != nil
148-
}
149-
150-
ownerID, ok := obj.GetLabels()["dataset-owner"]
151-
if !ok {
152-
ownerID, ok = obj.GetLabels()["pdok.nl/owner-id"]
153-
if !ok {
154-
return "", errors.New("dataset-owner and pdok.nl/owner-id labels are not found in object")
155-
}
156-
}
157-
parts = append(parts, strings.ToUpper(strings.ReplaceAll(ownerID, "-", "")))
158-
159-
datasetID, ok := obj.GetLabels()["dataset"]
160-
if !ok {
161-
// V3 label
162-
datasetID, ok = obj.GetLabels()["pdok.nl/dataset-id"]
163-
if !ok {
164-
return "", errors.New("dataset label not found in object")
165-
}
166-
}
167-
parts = append(parts, strings.ReplaceAll(datasetID, "-", ""))
126+
// getUptimeName transforms the CR name into a uptime.pdok.nl/name value
127+
// owner-dataset-v1-0 -> OWNER dataset v1_0 [INSPIRE] [WMS|WFS]
128+
func getUptimeName[O pdoknlv3.WMSWFS](obj O) string {
129+
// Extract the version from the CR name, owner-dataset-v1-0 -> owner-dataset + v1-0
130+
versionMatcher := regexp.MustCompile("^(.*)(?:-(v?[1-9](?:-[0-9])?))?$")
131+
match := versionMatcher.FindStringSubmatch(obj.GetName())
168132

169-
theme, ok := obj.GetLabels()["theme"]
170-
if !ok {
171-
// V3 label
172-
theme, ok = obj.GetLabels()["pdok.nl/tag"]
173-
}
133+
nameParts := strings.Split(match[1], "-")
134+
nameParts[0] = strings.ToUpper(nameParts[0])
174135

175-
if ok {
176-
parts = append(parts, strings.ReplaceAll(theme, "-", ""))
136+
// Add service version if found
137+
if len(match) > 2 && len(match[2]) > 0 {
138+
nameParts = append(nameParts, strings.ReplaceAll(match[2], "-", "_"))
177139
}
178140

179-
version, ok := obj.GetLabels()["service-version"]
180-
if !ok {
181-
version, ok = obj.GetLabels()["pdok.nl/service-version"]
182-
if !ok {
183-
return "", errors.New("service-version label not found in object")
184-
}
185-
}
186-
parts = append(parts, version)
187-
188-
if inspire {
189-
parts = append(parts, "INSPIRE")
141+
// Add inspire
142+
if obj.Inspire() != nil {
143+
nameParts = append(nameParts, "INSPIRE")
190144
}
191145

192-
parts = append(parts, string(obj.Type()))
193-
194-
return strings.Join(parts, " "), nil
146+
return strings.Join(append(nameParts, string(obj.Type())), " ")
195147
}
196148

197149
func getMatchRule[O pdoknlv3.WMSWFS](obj O) string {

0 commit comments

Comments
 (0)