Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions api/v3/atom_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,15 @@ var baseURL string
var blobEndpoint string

// AtomSpec defines the desired state of Atom.
// +kubebuilder:validation:XValidation:rule="!has(self.ingressRouteUrls) || self.ingressRouteUrls.exists_one(x, x.url == self.service.baseUrl)",messageExpression="'ingressRouteUrls should include service.baseUrl '+self.service.baseUrl"
type AtomSpec struct {
// Optional lifecycle settings
Lifecycle *smoothoperatormodel.Lifecycle `json:"lifecycle,omitempty"`

// Optional list of URLs where the service can be reached
// By default only the spec.service.baseUrl is used
IngressRouteURLs smoothoperatormodel.IngressRouteURLs `json:"ingressRouteUrls,omitempty"`

// Service specification
Service Service `json:"service"`
}
Expand Down
14 changes: 13 additions & 1 deletion api/v3/atom_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ func (atom *Atom) ValidateUpdate(c client.Client, atomOld *Atom) ([]string, erro
field.NewPath("spec").Child("service").Child("baseUrl"),
)

smoothoperatorvalidation.ValidateIngressRouteURLsNotRemoved(atomOld.Spec.IngressRouteURLs, atom.Spec.IngressRouteURLs, &allErrs, nil)

ValidateAtom(c, atom, &warnings, &allErrs)

if len(allErrs) == 0 {
Expand Down Expand Up @@ -87,9 +89,19 @@ func ValidateAtomWithoutClusterChecks(atom *Atom, warnings *[]string, allErrs *f
fieldPath = field.NewPath("metadata").Child("name")
smoothoperatorvalidation.AddWarning(warnings, *fieldPath, "should not contain atom", atom.GroupVersionKind(), atom.GetName())
}

validateDatasetFeeds(atom, allErrs)

err := smoothoperatorvalidation.ValidateIngressRouteURLsContainsBaseURL(atom.Spec.IngressRouteURLs, atom.Spec.Service.BaseURL, nil)
if err != nil {
*allErrs = append(*allErrs, err)
}
}

func validateDatasetFeeds(atom *Atom, allErrs *field.ErrorList) {
var feedNames []string
for i, datasetFeed := range atom.Spec.Service.DatasetFeeds {
fieldPath = field.NewPath("spec").Child("service").Child("datasetFeeds").Index(i)
fieldPath := field.NewPath("spec").Child("service").Child("datasetFeeds").Index(i)

if slices.Contains(feedNames, datasetFeed.TechnicalName) {
*allErrs = append(*allErrs, field.Duplicate(fieldPath.Child("technicalName"), datasetFeed.TechnicalName))
Expand Down
7 changes: 7 additions & 0 deletions api/v3/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions config/crd/bases/pdok.nl_atoms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,21 @@ spec:
spec:
description: AtomSpec defines the desired state of Atom.
properties:
ingressRouteUrls:
description: |-
Optional list of URLs where the service can be reached
By default only the spec.service.baseUrl is used
items:
properties:
url:
pattern: ^https?://.+/.+
type: string
required:
- url
type: object
maxItems: 30
minItems: 1
type: array
lifecycle:
description: Optional lifecycle settings
properties:
Expand Down Expand Up @@ -704,6 +719,11 @@ spec:
required:
- service
type: object
x-kubernetes-validations:
- messageExpression: '''ingressRouteUrls should include service.baseUrl
''+self.service.baseUrl'
rule: '!has(self.ingressRouteUrls) || self.ingressRouteUrls.exists_one(x,
x.url == self.service.baseUrl)'
status:
description: OperatorStatus defines the observed state of an Atom/WFS/WMS/....
properties:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/onsi/ginkgo/v2 v2.22.1
github.com/onsi/gomega v1.36.2
github.com/pdok/atom-generator v0.6.4
github.com/pdok/smooth-operator v0.1.1
github.com/pdok/smooth-operator v0.1.2
github.com/peterbourgon/ff v1.7.1
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.10.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pdok/atom-generator v0.6.4 h1:UpTTSKskdWLnxTAhSNOlz8dF2tBousD97V03kWzCV2k=
github.com/pdok/atom-generator v0.6.4/go.mod h1:IlPwti5ocXDTjB4xmz0ZpHCOW/suuW5gQMfjfwPX6uM=
github.com/pdok/smooth-operator v0.1.1 h1:rmsup4HmzJsxt4ZT9GWfj498dKLRfDhyuILeEkjju/A=
github.com/pdok/smooth-operator v0.1.1/go.mod h1:przwM7mBGmNPqabyhImKVZ15WL4zbqLqH4ExbuWKhWE=
github.com/pdok/smooth-operator v0.1.2 h1:ibGRgFjKysu665IUToQhs34hHhptftI+7RBFrGNw4nQ=
github.com/pdok/smooth-operator v0.1.2/go.mod h1:przwM7mBGmNPqabyhImKVZ15WL4zbqLqH4ExbuWKhWE=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/peterbourgon/ff v1.7.1 h1:xt1lxTG+Nr2+tFtysY7abFgPoH3Lug8CwYJMOmJRXhk=
github.com/peterbourgon/ff v1.7.1/go.mod h1:fYI5YA+3RDqQRExmFbHnBjEeWzh9TrS8rnRpEq7XIg0=
Expand Down
109 changes: 49 additions & 60 deletions internal/controller/ingressroute.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"sort"
"strconv"

smoothoperatormodel "github.com/pdok/smooth-operator/model"

pdoknlv3 "github.com/pdok/atom-operator/api/v3"
smoothutil "github.com/pdok/smooth-operator/pkg/util"
traefikiov1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
Expand Down Expand Up @@ -40,61 +42,8 @@ func (r *AtomReconciler) mutateIngressRoute(atom *pdoknlv3.Atom, ingressRoute *t
"uptime.pdok.nl/tags": "public-stats,atom",
}

ingressRoute.Spec = traefikiov1alpha1.IngressRouteSpec{
Routes: []traefikiov1alpha1.Route{
{
Kind: "Rule",
Match: getMatchRule(baseURL.JoinPath("index.xml"), false),
Services: []traefikiov1alpha1.Service{
{
LoadBalancerSpec: traefikiov1alpha1.LoadBalancerSpec{
Name: getBareService(atom).GetName(),
Kind: "Service",
Port: intstr.FromInt32(atomPortNr),
},
},
},
Middlewares: []traefikiov1alpha1.MiddlewareRef{
{
Name: atom.Name + headersSuffix,
},
{
Name: atom.Name + stripPrefixSuffix,
},
},
},
},
}

// Set additional routes per datasetFeed
for _, datasetFeed := range atom.Spec.Service.DatasetFeeds {
matchRule := getMatchRule(baseURL.JoinPath(datasetFeed.TechnicalName+".xml"), false)
rule := getDefaultRule(atom, matchRule)
ingressRoute.Spec.Routes = append(ingressRoute.Spec.Routes, rule)
}

azureStorageRule := traefikiov1alpha1.Route{
Kind: "Rule",
Match: getMatchRule(baseURL.JoinPath("downloads/"), true),
Services: []traefikiov1alpha1.Service{
{
LoadBalancerSpec: traefikiov1alpha1.LoadBalancerSpec{
Name: "azure-storage",
Port: intstr.IntOrString{Type: intstr.String, StrVal: "azure-storage"},
PassHostHeader: smoothutil.Pointer(false),
Kind: "Service",
},
},
},
Middlewares: []traefikiov1alpha1.MiddlewareRef{
{
Name: atom.Name + headersSuffix,
},
},
}

var downloadMiddlewares []traefikiov1alpha1.MiddlewareRef
// Set additional Azure storage middleware per download link
var downloadMiddlewares []traefikiov1alpha1.MiddlewareRef
for _, group := range getDownloadLinkGroups(atom.GetDownloadLinks()) {
middlewareRef := traefikiov1alpha1.MiddlewareRef{
Name: atom.Name + downloadsSuffix + strconv.Itoa(*group.index),
Expand All @@ -106,12 +55,14 @@ func (r *AtomReconciler) mutateIngressRoute(atom *pdoknlv3.Atom, ingressRoute *t
return downloadMiddlewares[i].Name < downloadMiddlewares[j].Name
})

azureStorageRule.Middlewares = append(azureStorageRule.Middlewares, downloadMiddlewares...)

ingressRoute.Spec.Routes = append(ingressRoute.Spec.Routes, azureStorageRule)

// Add finalizers
ingressRoute.Finalizers = []string{"uptime.pdok.nl/finalizer"}
ingressRoute.Spec.Routes = []traefikiov1alpha1.Route{}
if len(atom.Spec.IngressRouteURLs) > 0 {
for _, ingressRouteURL := range atom.Spec.IngressRouteURLs {
ingressRoute.Spec.Routes = append(ingressRoute.Spec.Routes, getRoutesForURL(atom, ingressRouteURL.URL, downloadMiddlewares)...)
}
} else {
ingressRoute.Spec.Routes = getRoutesForURL(atom, atom.Spec.Service.BaseURL, downloadMiddlewares)
}

if err := smoothutil.EnsureSetGVK(r.Client, ingressRoute, ingressRoute); err != nil {
return err
Expand Down Expand Up @@ -152,3 +103,41 @@ func getDefaultRule(atom *pdoknlv3.Atom, matchRule string) traefikiov1alpha1.Rou
},
}
}

func getRoutesForURL(atom *pdoknlv3.Atom, url smoothoperatormodel.URL, downloadMiddlewares []traefikiov1alpha1.MiddlewareRef) []traefikiov1alpha1.Route {
routes := []traefikiov1alpha1.Route{
getDefaultRule(atom, getMatchRule(url.JoinPath("index.xml"), false)),
}

// Set additional routes per datasetFeed
for _, datasetFeed := range atom.Spec.Service.DatasetFeeds {
matchRule := getMatchRule(url.JoinPath(datasetFeed.TechnicalName+".xml"), false)
rule := getDefaultRule(atom, matchRule)
routes = append(routes, rule)
}

// Add Azure storage rule
azureStorageRule := traefikiov1alpha1.Route{
Kind: "Rule",
Match: getMatchRule(url.JoinPath("downloads/"), true),
Services: []traefikiov1alpha1.Service{
{
LoadBalancerSpec: traefikiov1alpha1.LoadBalancerSpec{
Name: "azure-storage",
Port: intstr.IntOrString{Type: intstr.String, StrVal: "azure-storage"},
PassHostHeader: smoothutil.Pointer(false),
Kind: "Service",
},
},
},
Middlewares: append([]traefikiov1alpha1.MiddlewareRef{
{
Name: atom.Name + headersSuffix,
}},
downloadMiddlewares...,
),
}
routes = append(routes, azureStorageRule)

return routes
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ metadata:
uid: ""
blockOwnerDeletion: true
controller: true
finalizers:
- uptime.pdok.nl/finalizer
spec:
routes:
- kind: Rule
Expand Down Expand Up @@ -61,3 +59,42 @@ spec:
- name: maximum-atom-downloads-0
- name: maximum-atom-downloads-1
- name: maximum-atom-downloads-2
- kind: Rule
match: (Host(`localhost`) || Host(`test.com`)) && Path(`/path/other/index.xml`)
services:
- kind: Service
name: maximum-atom
port: 80
middlewares:
- name: maximum-atom-headers
- name: maximum-atom-prefixstrip
- kind: Rule
match: (Host(`localhost`) || Host(`test.com`)) && Path(`/path/other/feed-1.xml`)
services:
- kind: Service
name: maximum-atom
port: 80
middlewares:
- name: maximum-atom-headers
- name: maximum-atom-prefixstrip
- kind: Rule
match: (Host(`localhost`) || Host(`test.com`)) && Path(`/path/other/feed-2.xml`)
services:
- kind: Service
name: maximum-atom
port: 80
middlewares:
- name: maximum-atom-headers
- name: maximum-atom-prefixstrip
- kind: Rule
match: (Host(`localhost`) || Host(`test.com`)) && PathPrefix(`/path/other/downloads/`)
services:
- kind: Service
name: azure-storage
port: azure-storage
passHostHeader: false
middlewares:
- name: maximum-atom-headers
- name: maximum-atom-downloads-0
- name: maximum-atom-downloads-1
- name: maximum-atom-downloads-2
3 changes: 3 additions & 0 deletions internal/controller/test_data/maximum-atom/input/atom.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ metadata:
labels:
test: test
spec:
ingressRouteUrls:
- url: https://test.com/path/
- url: https://test.com/path/other/
service:
baseUrl: https://test.com/path/
stylesheet: https://test.com/stylesheet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ metadata:
uid: ""
blockOwnerDeletion: true
controller: true
finalizers:
- uptime.pdok.nl/finalizer
spec:
routes:
- kind: Rule
Expand Down
Loading