From 31742809bf43aaf35f02c286dfb444529b66728e Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Tue, 26 Mar 2024 14:09:49 +0100 Subject: [PATCH] feat(k8s): Added Kubernetes ConfigMap storage backend --- file.go | 2 + go.mod | 39 ++++- go.sum | 94 ++++++++++++ kube-configmap.go | 361 ++++++++++++++++++++++++++++++++++++++++++++++ prefix.go | 1 - prefix_test.go | 13 +- testing_test.go | 70 ++++++++- 7 files changed, 569 insertions(+), 11 deletions(-) create mode 100644 kube-configmap.go diff --git a/file.go b/file.go index fceaadb..89995c8 100644 --- a/file.go +++ b/file.go @@ -185,6 +185,7 @@ func (f *file) persist(ctx context.Context) (err error) { f.modTime = f.getModTime() return err } + func (f *file) Name() string { return "file" } @@ -258,6 +259,7 @@ func (f *file) UpdatePrefix(ctx context.Context, prefix Prefix, namespace string } return p, f.persist(ctx) } + func (f *file) DeletePrefix(ctx context.Context, prefix Prefix, namespace string) (p Prefix, err error) { f.lock.Lock() defer f.lock.Unlock() diff --git a/go.mod b/go.mod index f0d15f4..4b2f850 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,9 @@ require ( golang.org/x/net v0.19.0 golang.org/x/sync v0.6.0 google.golang.org/protobuf v1.32.0 + k8s.io/api v0.29.0 + k8s.io/apimachinery v0.29.0 + sigs.k8s.io/controller-runtime v0.17.2 ) require ( @@ -48,22 +51,40 @@ require ( github.com/docker/docker v24.0.7+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.8.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.5.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/term v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/opencontainers/runc v1.1.11 // indirect @@ -77,6 +98,7 @@ require ( github.com/shirou/gopsutil/v3 v3.23.12 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect @@ -91,18 +113,33 @@ require ( go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/otel/sdk v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect - go.uber.org/goleak v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect golang.org/x/mod v0.14.0 // indirect + golang.org/x/oauth2 v0.13.0 // indirect golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.16.1 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect google.golang.org/grpc v1.60.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.29.0 // indirect + k8s.io/client-go v0.29.0 // indirect + k8s.io/component-base v0.29.0 // indirect + k8s.io/klog/v2 v2.110.1 // indirect + k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 778272d..15c7581 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,7 @@ github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoY github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -57,35 +58,70 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.8.0 h1:lRj6N9Nci7MvzrXuX6HFzU8XjmhPiXPlsKEy1u0KQro= +github.com/evanphx/json-patch/v5 v5.8.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= @@ -93,8 +129,11 @@ github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/connect-compress/v2 v2.0.0 h1:L7TVsLa6Oo9Hkkb6r3DwSrhBbcWlXjneqBj7fCRXviU= github.com/klauspost/connect-compress/v2 v2.0.0/go.mod h1:604CD9JSAjGqtVzCM4SRgM/9TFTkWBcp+2wlQfGyJ6c= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -105,6 +144,8 @@ github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIg github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= @@ -117,11 +158,22 @@ github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5 github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= +github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= +github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= @@ -157,12 +209,16 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/testcontainers/testcontainers-go v0.27.0 h1:IeIrJN4twonTDuMuBNQdKZ+K97yd7VrmNGu+lDpYcDk= @@ -243,6 +299,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= +golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -269,6 +327,8 @@ golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -290,6 +350,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= @@ -305,8 +369,38 @@ google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= +k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= +k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= +k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= +k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= +k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= +k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= +k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= +k8s.io/component-base v0.29.0 h1:T7rjd5wvLnPBV1vC4zWd/iWRbV8Mdxs+nGaoaFzGw3s= +k8s.io/component-base v0.29.0/go.mod h1:sADonFTQ9Zc9yFLghpDpmNXEdHyQmFIGbiuZbqAXQ1M= +k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= +k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= +k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.17.2 h1:FwHwD1CTUemg0pW2otk7/U5/i5m2ymzvOXdbeGOUvw0= +sigs.k8s.io/controller-runtime v0.17.2/go.mod h1:+MngTvIQQQhfXtwfdGw/UOQ/aIaqsYywfCINOtwMO/s= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/kube-configmap.go b/kube-configmap.go new file mode 100644 index 0000000..85343b5 --- /dev/null +++ b/kube-configmap.go @@ -0,0 +1,361 @@ +package ipam + +import ( + "context" + "encoding/json" + "fmt" + "sync" + + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type kubeConfigMap struct { + client client.Client + configMapKey types.NamespacedName + lock sync.Mutex +} + +func NewKubeConfigMap(ctx context.Context, client client.Client, namespacedName types.NamespacedName) (Storage, error) { + kcm := &kubeConfigMap{ + client: client, + configMapKey: namespacedName, + } + + if err := kcm.CreateNamespace(ctx, defaultNamespace); err != nil { + return nil, fmt.Errorf("failed to create namespace: %w", err) + } + + return kcm, nil +} + +// loadConfigMap loads the configmap from the kubernetes API. +// If the configmap does not exist, it returns an empty configmap +// with the correct name and namespace. +func (k *kubeConfigMap) loadConfigMap(ctx context.Context) (corev1.ConfigMap, error) { + cm := corev1.ConfigMap{} + + if err := k.client.Get(ctx, k.configMapKey, &cm); err != nil { + if kerrors.IsNotFound(err) { + cm.Name = k.configMapKey.Name + cm.Namespace = k.configMapKey.Namespace + } else { + return cm, fmt.Errorf("get configmap: %w", err) + } + } + + if cm.Data == nil { + cm.Data = make(map[string]string) + } + + return cm, nil +} + +// storeConfigMap stores the configmap in the kubernetes API. +// If the configmap does not exist, it creates it. +func (k *kubeConfigMap) storeConfigMap(ctx context.Context, cm *corev1.ConfigMap) error { + if err := k.client.Update(ctx, cm); err != nil { + if kerrors.IsNotFound(err) { + if err := k.client.Create(ctx, cm); err != nil { + return fmt.Errorf("create configmap: %w", err) + } + } else { + return fmt.Errorf("update configmap: %w", err) + } + } + + return nil +} + +func (k *kubeConfigMap) checkIpamNamespaceExists(cm *corev1.ConfigMap, namespace string) error { + if cm.Data == nil { + cm.Data = make(map[string]string) + } + + for key := range cm.Data { + if key == namespace { + return nil + } + } + + return ErrNamespaceDoesNotExist +} + +// CreateNamespace implements Storage. +func (k *kubeConfigMap) CreateNamespace(ctx context.Context, namespace string) error { + k.lock.Lock() + defer k.lock.Unlock() + + cm, err := k.loadConfigMap(ctx) + if err != nil { + return fmt.Errorf("load configmap: %w", err) + } + + cm.Data[namespace] = "{}" + + if err := k.storeConfigMap(ctx, &cm); err != nil { + return fmt.Errorf("store configmap: %w", err) + } + + return nil +} + +// CreatePrefix implements Storage. +func (k *kubeConfigMap) CreatePrefix(ctx context.Context, prefix Prefix, namespace string) (Prefix, error) { + k.lock.Lock() + defer k.lock.Unlock() + + cm, err := k.loadConfigMap(ctx) + if err != nil { + return Prefix{}, fmt.Errorf("load configmap: %w", err) + } + + if err := k.checkIpamNamespaceExists(&cm, namespace); err != nil { + return Prefix{}, fmt.Errorf("check ipam namespace exists: %w", err) + } + + prefixMap := make(map[string]prefixJSON) + if err := json.Unmarshal([]byte(cm.Data[namespace]), &prefixMap); err != nil { + return Prefix{}, fmt.Errorf("unmarshal namespace: %w", err) + } + + if _, ok := prefixMap[prefix.Cidr]; ok { + return Prefix{}, ErrAlreadyAllocated + } + + prefixMap[prefix.Cidr] = prefix.toPrefixJSON() + + marshalledPrefixes, err := json.Marshal(prefixMap) + if err != nil { + return Prefix{}, fmt.Errorf("marshal namespace: %w", err) + } + + cm.Data[namespace] = string(marshalledPrefixes) + + if err := k.storeConfigMap(ctx, &cm); err != nil { + return Prefix{}, fmt.Errorf("store configmap: %w", err) + } + + return prefix, nil +} + +// DeleteAllPrefixes implements Storage. +func (k *kubeConfigMap) DeleteAllPrefixes(ctx context.Context, namespace string) error { + k.lock.Lock() + defer k.lock.Unlock() + + cm, err := k.loadConfigMap(ctx) + if err != nil { + return fmt.Errorf("load configmap: %w", err) + } + + if err := k.checkIpamNamespaceExists(&cm, namespace); err != nil { + return fmt.Errorf("check ipam namespace exists: %w", err) + } + + cm.Data[namespace] = "{}" + + if err := k.storeConfigMap(ctx, &cm); err != nil { + return fmt.Errorf("store configmap: %w", err) + } + + return nil +} + +// DeleteNamespace implements Storage. +func (k *kubeConfigMap) DeleteNamespace(ctx context.Context, namespace string) error { + k.lock.Lock() + defer k.lock.Unlock() + + cm, err := k.loadConfigMap(ctx) + if err != nil { + return fmt.Errorf("load configmap: %w", err) + } + + delete(cm.Data, namespace) + + if err := k.storeConfigMap(ctx, &cm); err != nil { + return fmt.Errorf("store configmap: %w", err) + } + + return nil +} + +// DeletePrefix implements Storage. +func (k *kubeConfigMap) DeletePrefix(ctx context.Context, prefix Prefix, namespace string) (Prefix, error) { + k.lock.Lock() + defer k.lock.Unlock() + + cm, err := k.loadConfigMap(ctx) + if err != nil { + return Prefix{}, fmt.Errorf("load configmap: %w", err) + } + + if err := k.checkIpamNamespaceExists(&cm, namespace); err != nil { + return Prefix{}, fmt.Errorf("check ipam namespace exists: %w", err) + } + + prefixMap := make(map[string]Prefix) + if err := json.Unmarshal([]byte(cm.Data[namespace]), &prefixMap); err != nil { + return Prefix{}, fmt.Errorf("unmarshal namespace: %w", err) + } + + if _, ok := prefixMap[prefix.Cidr]; !ok { + return Prefix{}, ErrNotFound + } + + delete(prefixMap, prefix.Cidr) + + marshalledPrefixes, err := json.Marshal(prefixMap) + if err != nil { + return Prefix{}, fmt.Errorf("marshal namespace: %w", err) + } + + cm.Data[namespace] = string(marshalledPrefixes) + + if err := k.storeConfigMap(ctx, &cm); err != nil { + return Prefix{}, fmt.Errorf("store configmap: %w", err) + } + + return prefix, nil +} + +// ListNamespaces implements Storage. +func (k *kubeConfigMap) ListNamespaces(ctx context.Context) ([]string, error) { + k.lock.Lock() + defer k.lock.Unlock() + + cm, err := k.loadConfigMap(ctx) + if err != nil { + return nil, fmt.Errorf("load configmap: %w", err) + } + + namespaces := make([]string, 0, len(cm.Data)) + for namespace := range cm.Data { + namespaces = append(namespaces, namespace) + } + + return namespaces, nil +} + +// Name implements Storage. +func (k *kubeConfigMap) Name() string { + return "kube-configmap" +} + +// ReadAllPrefixCidrs implements Storage. +func (k *kubeConfigMap) ReadAllPrefixCidrs(ctx context.Context, namespace string) ([]string, error) { + prefixes, err := k.ReadAllPrefixes(ctx, namespace) + if err != nil { + return nil, fmt.Errorf("read all prefixes: %w", err) + } + + cidrs := make([]string, 0, len(prefixes)) + for _, prefix := range prefixes { + cidrs = append(cidrs, prefix.Cidr) + } + + return cidrs, nil +} + +// ReadAllPrefixes implements Storage. +func (k *kubeConfigMap) ReadAllPrefixes(ctx context.Context, namespace string) (Prefixes, error) { + k.lock.Lock() + defer k.lock.Unlock() + + cm, err := k.loadConfigMap(ctx) + if err != nil { + return Prefixes{}, fmt.Errorf("load configmap: %w", err) + } + + if err := k.checkIpamNamespaceExists(&cm, namespace); err != nil { + return Prefixes{}, fmt.Errorf("check ipam namespace exists: %w", err) + } + + prefixMap := make(map[string]prefixJSON) + if err := json.Unmarshal([]byte(cm.Data[namespace]), &prefixMap); err != nil { + return Prefixes{}, fmt.Errorf("unmarshal namespace: %w", err) + } + + prefixes := make(Prefixes, 0) + for _, pfx := range prefixMap { + prefixes = append(prefixes, pfx.toPrefix()) + } + + return prefixes, nil +} + +// ReadPrefix implements Storage. +func (k *kubeConfigMap) ReadPrefix(ctx context.Context, prefix string, namespace string) (Prefix, error) { + k.lock.Lock() + defer k.lock.Unlock() + + cm, err := k.loadConfigMap(ctx) + if err != nil { + return Prefix{}, fmt.Errorf("load configmap: %w", err) + } + + if err := k.checkIpamNamespaceExists(&cm, namespace); err != nil { + return Prefix{}, fmt.Errorf("check ipam namespace exists: %w", err) + } + + prefixMap := make(map[string]prefixJSON) + if err := json.Unmarshal([]byte(cm.Data[namespace]), &prefixMap); err != nil { + return Prefix{}, fmt.Errorf("unmarshal namespace: %w", err) + } + + pfx, ok := prefixMap[prefix] + if !ok { + return Prefix{}, fmt.Errorf("%w: prefix %v not found", ErrNotFound, prefix) + } + + return pfx.toPrefix(), nil +} + +// UpdatePrefix implements Storage. +func (k *kubeConfigMap) UpdatePrefix(ctx context.Context, prefix Prefix, namespace string) (Prefix, error) { + k.lock.Lock() + defer k.lock.Unlock() + + cm, err := k.loadConfigMap(ctx) + if err != nil { + return Prefix{}, fmt.Errorf("load configmap: %w", err) + } + + if err := k.checkIpamNamespaceExists(&cm, namespace); err != nil { + return Prefix{}, fmt.Errorf("check ipam namespace exists: %w", err) + } + + prefixMap := make(map[string]prefixJSON) + if err := json.Unmarshal([]byte(cm.Data[namespace]), &prefixMap); err != nil { + return Prefix{}, fmt.Errorf("unmarshal namespace: %w", err) + } + + if _, ok := prefixMap[prefix.Cidr]; !ok { + return Prefix{}, ErrNotFound + } + + storedPrefix := prefixMap[prefix.Cidr].toPrefix() + + if storedPrefix.version > prefix.version { + return Prefix{}, fmt.Errorf("%w: unable to update prefix:%s", ErrOptimisticLockError, prefix.Cidr) + } + + prefix.version++ + prefixMap[prefix.Cidr] = prefix.toPrefixJSON() + + marshalledPrefixes, err := json.Marshal(prefixMap) + if err != nil { + return Prefix{}, fmt.Errorf("marshal namespace: %w", err) + } + + cm.Data[namespace] = string(marshalledPrefixes) + + if err := k.storeConfigMap(ctx, &cm); err != nil { + return Prefix{}, fmt.Errorf("store configmap: %w", err) + } + + return prefix, nil +} diff --git a/prefix.go b/prefix.go index 29379e8..4db68b1 100644 --- a/prefix.go +++ b/prefix.go @@ -697,7 +697,6 @@ func (p *Prefix) Usage() Usage { // with ten attempts and jitter delay ~100ms // returns only error of last failed attempt func retryOnOptimisticLock(retryableFunc retry.RetryableFunc) error { - return retry.Do( retryableFunc, retry.RetryIf(func(err error) bool { diff --git a/prefix_test.go b/prefix_test.go index d4a4f44..2b5381e 100644 --- a/prefix_test.go +++ b/prefix_test.go @@ -219,6 +219,7 @@ func TestIpamer_ReleaseIPFromPrefixIPv6(t *testing.T) { require.Contains(t, err.Error(), "NotFound: unable to find prefix for cidr:1001:0db8:85a3::/120") }) } + func TestIpamer_AcquireSpecificIP(t *testing.T) { ctx := context.Background() testWithBackends(t, func(t *testing.T, ipam *ipamer) { @@ -466,7 +467,6 @@ func TestIpamer_AcquireChildPrefixFragmented(t *testing.T) { require.NoError(t, err) require.NotNil(t, c4) require.Equal(t, "192.168.12.0/22", c4.String()) - }) } @@ -878,9 +878,9 @@ func TestIpamer_AcquireChildPrefixNoDuplicatesUntilFullIPv6(t *testing.T) { s, _ = prefix.availablePrefixes() require.Equal(t, uint64(0), s) require.Equal(t, uint64(256), prefix.acquiredPrefixes()) - }) } + func TestIpamer_AcquireChildPrefixNoDuplicatesUntilFullIPv4(t *testing.T) { ctx := context.Background() @@ -910,12 +910,10 @@ func TestIpamer_AcquireChildPrefixNoDuplicatesUntilFullIPv4(t *testing.T) { s, _ = prefix.availablePrefixes() require.Equal(t, uint64(0), s) require.Equal(t, uint64(256), prefix.acquiredPrefixes()) - }) } func TestPrefix_Availableips(t *testing.T) { - tests := []struct { name string Cidr string @@ -1254,6 +1252,7 @@ func TestIpamerAcquireIPv6(t *testing.T) { require.NoError(t, err, "error deleting prefix:%v", err) }) } + func TestIpamerAcquireAlreadyAquiredIPv4(t *testing.T) { ctx := context.Background() @@ -1275,6 +1274,7 @@ func TestIpamerAcquireAlreadyAquiredIPv4(t *testing.T) { require.NoError(t, err) }) } + func TestIpamerAcquireAlreadyAquiredIPv6(t *testing.T) { ctx := context.Background() @@ -1296,6 +1296,7 @@ func TestIpamerAcquireAlreadyAquiredIPv6(t *testing.T) { require.NoError(t, err) }) } + func TestGetHostAddresses(t *testing.T) { ctx := context.Background() testWithBackends(t, func(t *testing.T, ipam *ipamer) { @@ -1353,8 +1354,8 @@ func TestGetHostAddressesIPv6(t *testing.T) { require.Nil(t, ip) }) } -func TestPrefixDeepCopy(t *testing.T) { +func TestPrefixDeepCopy(t *testing.T) { p1 := &Prefix{ Cidr: "4.1.1.0/24", ParentCidr: "4.1.0.0/16", @@ -1538,6 +1539,7 @@ func Test_ipamer_DumpAndLoad(t *testing.T) { require.Equal(t, prefix, newPrefix) }) } + func TestIpamer_ReadAllPrefixCidrs(t *testing.T) { ctx := context.Background() @@ -1675,6 +1677,7 @@ func TestNamespaceFromContext(t *testing.T) { }) } } + func TestAvailablePrefixes(t *testing.T) { testCases := []struct { name string diff --git a/testing_test.go b/testing_test.go index cf7e3b2..ae3c210 100644 --- a/testing_test.go +++ b/testing_test.go @@ -12,6 +12,11 @@ import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" "go.mongodb.org/mongo-driver/mongo/options" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" ) var ( @@ -187,7 +192,8 @@ func startEtcd() (container testcontainers.Container, s *etcd, err error) { req := testcontainers.ContainerRequest{ Image: "quay.io/coreos/etcd:" + etcdVersion, ExposedPorts: []string{"2379:2379", "2380:2380"}, - Cmd: []string{"etcd", + Cmd: []string{ + "etcd", "--name", "etcd", "--advertise-client-urls", "http://0.0.0.0:2379", "--initial-advertise-peer-urls", "http://0.0.0.0:2380", @@ -348,7 +354,6 @@ type docStorage struct { func newLocalFileWithCleanup() (*file, error) { ctx := context.Background() fp, err := os.CreateTemp("", "go-ipam-*.json") - if err != nil { return nil, err } @@ -380,6 +385,7 @@ func newPostgresWithCleanup() (*extendedSQL, error) { return ext, nil } + func newCockroachWithCleanup() (*extendedSQL, error) { c, s, err := startCockroach() if err != nil { @@ -393,6 +399,7 @@ func newCockroachWithCleanup() (*extendedSQL, error) { return ext, nil } + func newRedisWithCleanup() (*kvStorage, error) { c, r, err := startRedis() if err != nil { @@ -406,6 +413,7 @@ func newRedisWithCleanup() (*kvStorage, error) { return kv, nil } + func newEtcdWithCleanup() (*kvEtcdStorage, error) { c, r, err := startEtcd() if err != nil { @@ -495,6 +503,45 @@ func (ds *docStorage) cleanup() error { return ds.mongodb.DeleteAllPrefixes(context.Background(), defaultNamespace) } +type kubeConfigMapWithCleanup struct { + Storage + + client client.Client + ns corev1.Namespace +} + +func (k *kubeConfigMapWithCleanup) postCleanup() error { + if err := k.client.Delete(context.TODO(), &k.ns); err != nil { + return fmt.Errorf("error deleting namespace: %v", err) + } + + return nil +} + +func newKubeConfigMapWithCleanup() (kubeConfigMapWithCleanup, error) { + client := fake.NewClientBuilder().Build() + + ns := corev1.Namespace{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "go-ipam-test", + }, + } + if err := client.Create(context.TODO(), &ns); err != nil { + return kubeConfigMapWithCleanup{}, fmt.Errorf("error creating namespace: %v", err) + } + + storage, err := NewKubeConfigMap(context.Background(), client, types.NamespacedName{ + Name: "go-ipam-test", + Namespace: ns.Name, + }) + + return kubeConfigMapWithCleanup{ + Storage: storage, + client: client, + ns: ns, + }, err +} + type benchMethod func(b *testing.B, ipam *ipamer) func benchWithBackends(b *testing.B, fn benchMethod) { @@ -590,8 +637,10 @@ func testWithSQLBackends(t *testing.T, fn sqlTestMethod) { } } -type provide func() Storage -type providesql func() *sql +type ( + provide func() Storage + providesql func() *sql +) // storageProvider provides different storages type storageProvider struct { @@ -710,5 +759,18 @@ func storageProviders() []storageProvider { return nil }, }, + { + name: "Kubernetes-ConfigMap", + provide: func() Storage { + storage, err := newKubeConfigMapWithCleanup() + if err != nil { + panic(fmt.Sprintf("failed to create new kube configmap storage, error: %v", err)) + } + return storage + }, + providesql: func() *sql { + return nil + }, + }, } }