Skip to content

Commit 705fcfb

Browse files
authored
Merge pull request #3 from PDOK/PDOK-17806-mapserver-operator-blob-download-configmap
PDOK-17806 Setup blob download scripts in configmap
2 parents 97f2781 + a3895c2 commit 705fcfb

File tree

8 files changed

+886
-22
lines changed

8 files changed

+886
-22
lines changed

api/v3/wms_types.go

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3232
"maps"
3333
"slices"
34+
"sort"
3435
)
3536

3637
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
@@ -154,21 +155,63 @@ func init() {
154155
SchemeBuilder.Register(&WMS{}, &WMSList{})
155156
}
156157

157-
func (wms *WMS) GetUniqueTiffBlobKeys() []string {
158-
blobKeys := map[string]bool{}
158+
func (layer *Layer) getAllLayers() (layers []Layer) {
159+
layers = append(layers, *layer)
160+
if layer.Layers != nil {
161+
for _, childLayer := range *layer.Layers {
162+
layers = append(layers, childLayer.getAllLayers()...)
163+
}
164+
}
165+
return
166+
}
167+
168+
func (layer *Layer) hasData() bool {
169+
switch {
170+
case layer.Data == nil:
171+
return false
172+
case layer.Data.Gpkg != nil:
173+
return true
174+
case layer.Data.Postgis != nil:
175+
return true
176+
case layer.Data.TIF != nil:
177+
return true
178+
default:
179+
return false
180+
}
181+
}
159182

160-
if wms.Spec.Service.Layer.Data.TIF != nil && wms.Spec.Service.Layer.Data.TIF.BlobKey != "" {
161-
blobKeys[wms.Spec.Service.Layer.Data.TIF.BlobKey] = true
183+
func (layer *Layer) hasTIFData() bool {
184+
if !layer.hasData() {
185+
return false
162186
}
187+
return layer.Data.TIF != nil && layer.Data.TIF.BlobKey != ""
188+
}
163189

164-
if wms.Spec.Service.Layer.Layers != nil && len(*wms.Spec.Service.Layer.Layers) > 0 {
165-
for _, layer := range *wms.Spec.Service.Layer.Layers {
166-
if layer.Data.TIF != nil && layer.Data.TIF.BlobKey != "" {
167-
blobKeys[layer.Data.TIF.BlobKey] = true
190+
func (wms *WMS) GetAllLayersWithLegend() (layers []Layer) {
191+
for _, layer := range wms.Spec.Service.Layer.getAllLayers() {
192+
if !layer.hasData() || len(layer.Styles) == 0 {
193+
continue
194+
}
195+
for _, style := range layer.Styles {
196+
if style.Legend != nil && style.Legend.BlobKey != "" {
197+
layers = append(layers, layer)
198+
break
168199
}
169200
}
170201
}
171-
return slices.Collect(maps.Keys(blobKeys))
202+
return
203+
}
204+
205+
func (wms *WMS) GetUniqueTiffBlobKeys() []string {
206+
blobKeys := map[string]bool{}
207+
for _, layer := range wms.Spec.Service.Layer.getAllLayers() {
208+
if layer.hasTIFData() {
209+
blobKeys[layer.Data.TIF.BlobKey] = true
210+
}
211+
}
212+
keys := slices.Collect(maps.Keys(blobKeys))
213+
sort.Strings(keys) // This is only needed for the unit test
214+
return keys
172215
}
173216

174217
func (wms *WMS) GetAuthority() *Authority {

cmd/main.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ import (
4444
// +kubebuilder:scaffold:imports
4545
)
4646

47+
const (
48+
defaultMultitoolImage = "docker.io/pdok/docker-multitool:0.9.1"
49+
)
50+
4751
var (
4852
scheme = runtime.NewScheme()
4953
setupLog = ctrl.Log.WithName("setup")
@@ -67,6 +71,7 @@ func main() {
6771
var secureMetrics bool
6872
var enableHTTP2 bool
6973
var tlsOpts []func(*tls.Config)
74+
var multitoolImage string
7075
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
7176
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
7277
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
@@ -84,6 +89,7 @@ func main() {
8489
flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.")
8590
flag.BoolVar(&enableHTTP2, "enable-http2", false,
8691
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
92+
flag.StringVar(&multitoolImage, "multitool-image", defaultMultitoolImage, "The image to use in the blob download init-container.")
8793
opts := zap.Options{
8894
Development: true,
8995
}
@@ -213,8 +219,9 @@ func main() {
213219
os.Exit(1)
214220
}
215221
if err = (&controller.WFSReconciler{
216-
Client: mgr.GetClient(),
217-
Scheme: mgr.GetScheme(),
222+
Client: mgr.GetClient(),
223+
Scheme: mgr.GetScheme(),
224+
MultitoolImage: multitoolImage,
218225
}).SetupWithManager(mgr); err != nil {
219226
setupLog.Error(err, "unable to create controller", "controller", "WFS")
220227
os.Exit(1)

go.mod

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ godebug default=go1.23
77
require (
88
github.com/onsi/ginkgo/v2 v2.21.0
99
github.com/onsi/gomega v1.35.1
10-
github.com/pdok/smooth-operator v0.0.2
10+
github.com/pdok/smooth-operator v0.0.4
1111
k8s.io/api v0.32.0
1212
k8s.io/apimachinery v0.32.0
1313
k8s.io/client-go v0.32.0
1414
sigs.k8s.io/controller-runtime v0.20.0
15+
sigs.k8s.io/yaml v1.4.0
1516
)
1617

1718
require (
@@ -26,8 +27,9 @@ require (
2627
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
2728
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
2829
github.com/felixge/httpsnoop v1.0.4 // indirect
29-
github.com/fsnotify/fsnotify v1.8.0 // indirect
30+
github.com/fsnotify/fsnotify v1.8.0 // indirectC
3031
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
32+
github.com/go-errors/errors v1.4.2 // indirect
3133
github.com/go-logr/logr v1.4.2 // indirect
3234
github.com/go-logr/stdr v1.2.2 // indirect
3335
github.com/go-logr/zapr v1.3.0 // indirect
@@ -96,6 +98,7 @@ require (
9698
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
9799
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect
98100
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
101+
sigs.k8s.io/kustomize/api v0.19.0 // indirect
102+
sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
99103
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
100-
sigs.k8s.io/yaml v1.4.0 // indirect
101104
)

go.sum

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
2020
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2121
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
2222
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
23-
github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
24-
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
23+
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
24+
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
2525
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
2626
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
2727
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@@ -30,6 +30,8 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/
3030
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
3131
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
3232
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
33+
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
34+
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
3335
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
3436
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
3537
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -97,8 +99,8 @@ github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM
9799
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
98100
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
99101
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
100-
github.com/pdok/smooth-operator v0.0.2 h1:RWaPpP4q6bFl3UG5aYkK/GIGeqGro7bQ/tPQuqOkync=
101-
github.com/pdok/smooth-operator v0.0.2/go.mod h1:Jz2AcpdfElRbjJsJNUuuk/o5XGZPZAC+820bVpRysbE=
102+
github.com/pdok/smooth-operator v0.0.4 h1:NVt7srlkSyiZooqn9R2pnMapY6jwZDVTj4QxUjy7UB0=
103+
github.com/pdok/smooth-operator v0.0.4/go.mod h1:oZWFuIKJGjN/C6ocgMNfMZ7SbLQi+N0qaWj7j95Wdec=
102104
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
103105
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
104106
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -128,8 +130,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
128130
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
129131
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
130132
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
131-
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
132-
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
133+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
134+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
133135
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
134136
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
135137
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -243,6 +245,10 @@ sigs.k8s.io/controller-runtime v0.20.0 h1:jjkMo29xEXH+02Md9qaVXfEIaMESSpy3TBWPrs
243245
sigs.k8s.io/controller-runtime v0.20.0/go.mod h1:BrP3w158MwvB3ZbNpaAcIKkHQ7YGpYnzpoSTZ8E14WU=
244246
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
245247
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
248+
sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
249+
sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
250+
sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA=
251+
sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY=
246252
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
247253
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
248254
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package blobdownload
2+
3+
import (
4+
"fmt"
5+
pdoknlv3 "github.com/pdok/mapserver-operator/api/v3"
6+
"os"
7+
"regexp"
8+
"strings"
9+
)
10+
11+
const (
12+
scriptPath = "./gpkg_download.sh"
13+
tifPath = "/srv/data/tif"
14+
imagesPath = "/srv/data/images"
15+
fontsPath = "/srv/data/config/fonts"
16+
legendPath = "/var/www/legend"
17+
)
18+
19+
func GetScript() (config string, err error) {
20+
content, err := os.ReadFile(scriptPath)
21+
if err != nil {
22+
return "", err
23+
}
24+
return string(content), nil
25+
}
26+
27+
func GetArgs[W pdoknlv3.WFS | pdoknlv3.WMS](webservice W) (args string, err error) {
28+
var sb strings.Builder
29+
30+
switch any(webservice).(type) {
31+
case pdoknlv3.WFS:
32+
if WFS, ok := any(webservice).(pdoknlv3.WFS); ok {
33+
createConfig(&sb)
34+
downloadGeopackage(&sb, *WFS.Spec.Options.PrefetchData)
35+
// In case of WFS no downloads are needed for TIFFs, styling assets and legends
36+
}
37+
case pdoknlv3.WMS:
38+
if WMS, ok := any(webservice).(pdoknlv3.WMS); ok {
39+
createConfig(&sb)
40+
downloadGeopackage(&sb, *WMS.Spec.Options.PrefetchData)
41+
if err = downloadTiffs(&sb, &WMS); err != nil {
42+
return "", err
43+
}
44+
if err = downloadStylingAssets(&sb, &WMS); err != nil {
45+
return "", err
46+
}
47+
if err = downloadLegends(&sb, &WMS); err != nil {
48+
return "", err
49+
}
50+
}
51+
default:
52+
return "", fmt.Errorf("unexpected input, webservice should be of type WFS or WMS, webservice: %v", webservice)
53+
}
54+
return sb.String(), nil
55+
}
56+
57+
func createConfig(sb *strings.Builder) {
58+
writeLine(sb, "set -e;")
59+
writeLine(sb, "mkdir -p /srv/data/config/;")
60+
writeLine(sb, "rclone config create --non-interactive --obscure blobs azureblob endpoint $BLOBS_ENDPOINT account $BLOBS_ACCOUNT key $BLOBS_KEY use_emulator true;")
61+
}
62+
63+
func downloadGeopackage(sb *strings.Builder, prefetchData bool) {
64+
if prefetchData {
65+
writeLine(sb, "bash /srv/scripts/gpkg_download.sh;")
66+
}
67+
}
68+
69+
func downloadTiffs(sb *strings.Builder, WMS *pdoknlv3.WMS) error {
70+
if !*WMS.Spec.Options.PrefetchData {
71+
return nil
72+
}
73+
74+
for _, blobKey := range WMS.GetUniqueTiffBlobKeys() {
75+
fileName, err := getFilenameFromBlobKey(blobKey)
76+
if err != nil {
77+
return err
78+
}
79+
writeLine(sb, "rclone copyto blobs:/%s %s/%s || exit 1;", blobKey, tifPath, fileName)
80+
}
81+
return nil
82+
}
83+
84+
func downloadStylingAssets(sb *strings.Builder, WMS *pdoknlv3.WMS) error {
85+
for _, blobKey := range WMS.Spec.Service.StylingAssets.BlobKeys {
86+
fileName, err := getFilenameFromBlobKey(blobKey)
87+
if err != nil {
88+
return err
89+
}
90+
path := imagesPath
91+
isTTF, _ := regexp.MatchString(".*\\.(ttf)$", fileName)
92+
if isTTF {
93+
path = fontsPath
94+
}
95+
writeLine(sb, "rclone copyto blobs:/%s %s/%s || exit 1;", blobKey, path, fileName)
96+
if isTTF {
97+
fileRoot, err := getRootFromFilename(fileName)
98+
if err != nil {
99+
return err
100+
}
101+
writeLine(sb, "echo %s %s >> %s/fonts.list;", fileRoot, fileName, fontsPath)
102+
}
103+
}
104+
writeLine(sb, "echo 'generated fonts.list:';")
105+
writeLine(sb, "cat %v/fonts.list;", fontsPath)
106+
return nil
107+
}
108+
109+
func downloadLegends(sb *strings.Builder, WMS *pdoknlv3.WMS) error {
110+
for _, layer := range WMS.GetAllLayersWithLegend() {
111+
writeLine(sb, "mkdir -p %s/%s;", legendPath, layer.Name)
112+
for _, style := range layer.Styles {
113+
writeLine(sb, "rclone copyto blobs:/%s %s/%s/%s.png || exit 1;", style.Legend.BlobKey, legendPath, layer.Name, style.Name)
114+
fileName, err := getFilenameFromBlobKey(style.Legend.BlobKey)
115+
if err != nil {
116+
return err
117+
}
118+
writeLine(sb, "Copied legend %s to %s/%s/%s.png;", fileName, legendPath, layer.Name, style.Name)
119+
}
120+
}
121+
writeLine(sb, "chown -R 999:999 %s", legendPath)
122+
return nil
123+
}
124+
125+
func getFilenameFromBlobKey(blobKey string) (string, error) {
126+
index := strings.LastIndex(blobKey, "/")
127+
if index == -1 {
128+
return "", fmt.Errorf("could not determine filename from blobkey %s", blobKey)
129+
}
130+
return blobKey[index+1:], nil
131+
}
132+
133+
func getRootFromFilename(fileName string) (string, error) {
134+
index := strings.LastIndex(fileName, ".")
135+
if index == -1 {
136+
return "", fmt.Errorf("could not determine root from filename %s", fileName)
137+
}
138+
return fileName[:index], nil
139+
}
140+
141+
func writeLine(sb *strings.Builder, format string, a ...any) {
142+
sb.WriteString(fmt.Sprintf(format, a...) + "\n")
143+
}

0 commit comments

Comments
 (0)