diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 25f5285..75c0f47 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - go-version: [1.22.x] + go-version: [1.22.x, 1.23.x] runs-on: ubuntu-latest steps: - name: Install Go diff --git a/go.mod b/go.mod index 9957067..030fbc3 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/serverscom/cloud-controller-manager -go 1.22.0 +go 1.23.0 require ( - github.com/onsi/gomega v1.33.1 - github.com/serverscom/serverscom-go-client v1.0.7 + github.com/onsi/gomega v1.36.2 + github.com/serverscom/serverscom-go-client v1.0.14 github.com/spf13/pflag v1.0.5 go.uber.org/mock v0.4.0 k8s.io/api v0.31.1 @@ -35,6 +35,7 @@ require ( 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.4 // indirect + github.com/go-resty/resty/v2 v2.16.2 // 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.4 // indirect @@ -76,19 +77,19 @@ require ( go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/crypto v0.24.0 // indirect + golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/net v0.37.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/term v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.6.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index 6fc6f43..f8d7384 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg= +github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -73,8 +75,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN 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-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= -github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= @@ -117,10 +119,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= -github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM= +github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -137,8 +139,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/serverscom/serverscom-go-client v1.0.7 h1:3+Gld2eueCNtUOaHn9DmQ9ABowUpAkAMbZlKxeMFVOg= -github.com/serverscom/serverscom-go-client v1.0.7/go.mod h1:X4/JkqDMjEFAqp0hqdRTmcMXc6V2xCitj+kmNyne3/U= +github.com/serverscom/serverscom-go-client v1.0.14 h1:/SR4moqSL6MqW+gt6wtF9Wl5KfckP4RcqeS0AECwwAs= +github.com/serverscom/serverscom-go-client v1.0.14/go.mod h1:o4lNYX+shv5TZ6miuGAaMDJP8y7Z7TdPEhMsCcL9PrU= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= @@ -212,8 +214,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -222,35 +224,35 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= @@ -263,8 +265,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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= diff --git a/serverscom/loadbalancers.go b/serverscom/loadbalancers.go index 7184656..340c4fb 100644 --- a/serverscom/loadbalancers.go +++ b/serverscom/loadbalancers.go @@ -19,6 +19,9 @@ const ( loadBalancerLocationIdAnnotation = "servers.com/load-balancer-location-id" loadBalancerProxyProtocolAnnotation = "servers.com/proxy-protocol" loadBalancerClusterAnnotation = "servers.com/cluster-id" + + loadBalancerServiceUUIDLabel = "k8s.servers.com/service-id" + loadBalancerClusterNameLabel = "k8s.servers.com/cluster-name" ) type loadBalancers struct { @@ -71,6 +74,11 @@ func (l *loadBalancers) EnsureLoadBalancer(ctx context.Context, clusterName stri // empty value for cluster id still returns nil lbClusterID := l.extractLBClusterID(service) + defaultLabels := map[string]string{ + loadBalancerServiceUUIDLabel: string(service.UID), + loadBalancerClusterNameLabel: sanitizeLabelValue(clusterName), + } + if loadBalancer == nil { locationID, err := l.extractLocationID(service) if err != nil { @@ -83,6 +91,7 @@ func (l *loadBalancers) EnsureLoadBalancer(ctx context.Context, clusterName stri input.LocationID = locationID input.Name = l.GetLoadBalancerName(ctx, clusterName, service) input.ClusterID = lbClusterID + input.Labels = defaultLabels loadBalancer, err = l.client.LoadBalancers.CreateL4LoadBalancer(ctx, input) if err != nil { @@ -95,6 +104,7 @@ func (l *loadBalancers) EnsureLoadBalancer(ctx context.Context, clusterName stri } else { name := l.GetLoadBalancerName(ctx, clusterName, service) + mergedLabels := mergeDefaultLabels(loadBalancer.Labels, defaultLabels) input := cli.L4LoadBalancerUpdateInput{} input.VHostZones = vhostZones input.UpstreamZones = upstreamZones @@ -104,6 +114,7 @@ func (l *loadBalancers) EnsureLoadBalancer(ctx context.Context, clusterName stri input.SharedCluster = new(bool) *input.SharedCluster = true } + input.Labels = mergedLabels loadBalancer, err = l.client.LoadBalancers.UpdateL4LoadBalancer(ctx, loadBalancer.ID, input) if err != nil { @@ -128,6 +139,11 @@ func (l *loadBalancers) UpdateLoadBalancer(ctx context.Context, clusterName stri if err != nil { return err } + defaultLabels := map[string]string{ + loadBalancerServiceUUIDLabel: string(service.UID), + loadBalancerClusterNameLabel: sanitizeLabelValue(clusterName), + } + mergedLabels := mergeDefaultLabels(loadBalancer.Labels, defaultLabels) name := l.GetLoadBalancerName(ctx, clusterName, service) @@ -135,6 +151,7 @@ func (l *loadBalancers) UpdateLoadBalancer(ctx context.Context, clusterName stri input.VHostZones = vhostZones input.UpstreamZones = upstreamZones input.Name = &name + input.Labels = mergedLabels _, err = l.client.LoadBalancers.UpdateL4LoadBalancer(ctx, loadBalancer.ID, input) if err != nil { diff --git a/serverscom/loadbalancers_test.go b/serverscom/loadbalancers_test.go index f34afcc..f66c974 100644 --- a/serverscom/loadbalancers_test.go +++ b/serverscom/loadbalancers_test.go @@ -11,6 +11,13 @@ import ( v1 "k8s.io/api/core/v1" ) +var ( + defaultLabels = map[string]string{ + loadBalancerServiceUUIDLabel: "123", + loadBalancerClusterNameLabel: sanitizeLabelValue("!@#cluster^&*"), + } +) + func TestLoadBalancers_GetLoadBalancer(t *testing.T) { g := NewGomegaWithT(t) @@ -244,6 +251,7 @@ func TestLoadBalancers_EnsureLoadBalancer(t *testing.T) { }, }, }, + Labels: defaultLabels, } collection.EXPECT().SetPerPage(100).Return(collection) @@ -351,6 +359,7 @@ func TestLoadBalancers_EnsureLoadBalancerWithCreate(t *testing.T) { }, }, }, + Labels: defaultLabels, } collection.EXPECT().SetPerPage(100).Return(collection).Times(2) @@ -476,6 +485,7 @@ func TestLoadBalancers_UpdateLoadBalancer(t *testing.T) { }, }, }, + Labels: defaultLabels, } collection.EXPECT().SetPerPage(100).Return(collection).Times(2) diff --git a/serverscom/testing/cloud_computing_instances_mock.go b/serverscom/testing/cloud_computing_instances_mock.go index 4da8310..42c3178 100644 --- a/serverscom/testing/cloud_computing_instances_mock.go +++ b/serverscom/testing/cloud_computing_instances_mock.go @@ -21,6 +21,7 @@ import ( type MockCloudComputingInstancesService struct { ctrl *gomock.Controller recorder *MockCloudComputingInstancesServiceMockRecorder + isgomock struct{} } // MockCloudComputingInstancesServiceMockRecorder is the mock recorder for MockCloudComputingInstancesService. diff --git a/serverscom/testing/collection_mock.go b/serverscom/testing/collection_mock.go index 2f7c759..1e20f8f 100644 --- a/serverscom/testing/collection_mock.go +++ b/serverscom/testing/collection_mock.go @@ -21,6 +21,7 @@ import ( type MockCollection[K any] struct { ctrl *gomock.Controller recorder *MockCollectionMockRecorder[K] + isgomock struct{} } // MockCollectionMockRecorder is the mock recorder for MockCollection. diff --git a/serverscom/testing/hosts_mock.go b/serverscom/testing/hosts_mock.go index 1104e4e..04cd257 100644 --- a/serverscom/testing/hosts_mock.go +++ b/serverscom/testing/hosts_mock.go @@ -21,6 +21,7 @@ import ( type MockHostsService struct { ctrl *gomock.Controller recorder *MockHostsServiceMockRecorder + isgomock struct{} } // MockHostsServiceMockRecorder is the mock recorder for MockHostsService. @@ -259,6 +260,36 @@ func (mr *MockHostsServiceMockRecorder) PowerCycleDedicatedServer(ctx, id any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PowerCycleDedicatedServer", reflect.TypeOf((*MockHostsService)(nil).PowerCycleDedicatedServer), ctx, id) } +// PowerCycleKubernetesBaremetalNode mocks base method. +func (m *MockHostsService) PowerCycleKubernetesBaremetalNode(ctx context.Context, id string) (*serverscom.KubernetesBaremetalNode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PowerCycleKubernetesBaremetalNode", ctx, id) + ret0, _ := ret[0].(*serverscom.KubernetesBaremetalNode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PowerCycleKubernetesBaremetalNode indicates an expected call of PowerCycleKubernetesBaremetalNode. +func (mr *MockHostsServiceMockRecorder) PowerCycleKubernetesBaremetalNode(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PowerCycleKubernetesBaremetalNode", reflect.TypeOf((*MockHostsService)(nil).PowerCycleKubernetesBaremetalNode), ctx, id) +} + +// PowerCycleSBMServer mocks base method. +func (m *MockHostsService) PowerCycleSBMServer(ctx context.Context, id string) (*serverscom.SBMServer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PowerCycleSBMServer", ctx, id) + ret0, _ := ret[0].(*serverscom.SBMServer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PowerCycleSBMServer indicates an expected call of PowerCycleSBMServer. +func (mr *MockHostsServiceMockRecorder) PowerCycleSBMServer(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PowerCycleSBMServer", reflect.TypeOf((*MockHostsService)(nil).PowerCycleSBMServer), ctx, id) +} + // PowerOffDedicatedServer mocks base method. func (m *MockHostsService) PowerOffDedicatedServer(ctx context.Context, id string) (*serverscom.DedicatedServer, error) { m.ctrl.T.Helper() @@ -274,6 +305,36 @@ func (mr *MockHostsServiceMockRecorder) PowerOffDedicatedServer(ctx, id any) *go return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PowerOffDedicatedServer", reflect.TypeOf((*MockHostsService)(nil).PowerOffDedicatedServer), ctx, id) } +// PowerOffKubernetesBaremetalNode mocks base method. +func (m *MockHostsService) PowerOffKubernetesBaremetalNode(ctx context.Context, id string) (*serverscom.KubernetesBaremetalNode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PowerOffKubernetesBaremetalNode", ctx, id) + ret0, _ := ret[0].(*serverscom.KubernetesBaremetalNode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PowerOffKubernetesBaremetalNode indicates an expected call of PowerOffKubernetesBaremetalNode. +func (mr *MockHostsServiceMockRecorder) PowerOffKubernetesBaremetalNode(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PowerOffKubernetesBaremetalNode", reflect.TypeOf((*MockHostsService)(nil).PowerOffKubernetesBaremetalNode), ctx, id) +} + +// PowerOffSBMServer mocks base method. +func (m *MockHostsService) PowerOffSBMServer(ctx context.Context, id string) (*serverscom.SBMServer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PowerOffSBMServer", ctx, id) + ret0, _ := ret[0].(*serverscom.SBMServer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PowerOffSBMServer indicates an expected call of PowerOffSBMServer. +func (mr *MockHostsServiceMockRecorder) PowerOffSBMServer(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PowerOffSBMServer", reflect.TypeOf((*MockHostsService)(nil).PowerOffSBMServer), ctx, id) +} + // PowerOnDedicatedServer mocks base method. func (m *MockHostsService) PowerOnDedicatedServer(ctx context.Context, id string) (*serverscom.DedicatedServer, error) { m.ctrl.T.Helper() @@ -289,6 +350,36 @@ func (mr *MockHostsServiceMockRecorder) PowerOnDedicatedServer(ctx, id any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PowerOnDedicatedServer", reflect.TypeOf((*MockHostsService)(nil).PowerOnDedicatedServer), ctx, id) } +// PowerOnKubernetesBaremetalNode mocks base method. +func (m *MockHostsService) PowerOnKubernetesBaremetalNode(ctx context.Context, id string) (*serverscom.KubernetesBaremetalNode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PowerOnKubernetesBaremetalNode", ctx, id) + ret0, _ := ret[0].(*serverscom.KubernetesBaremetalNode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PowerOnKubernetesBaremetalNode indicates an expected call of PowerOnKubernetesBaremetalNode. +func (mr *MockHostsServiceMockRecorder) PowerOnKubernetesBaremetalNode(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PowerOnKubernetesBaremetalNode", reflect.TypeOf((*MockHostsService)(nil).PowerOnKubernetesBaremetalNode), ctx, id) +} + +// PowerOnSBMServer mocks base method. +func (m *MockHostsService) PowerOnSBMServer(ctx context.Context, id string) (*serverscom.SBMServer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PowerOnSBMServer", ctx, id) + ret0, _ := ret[0].(*serverscom.SBMServer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PowerOnSBMServer indicates an expected call of PowerOnSBMServer. +func (mr *MockHostsServiceMockRecorder) PowerOnSBMServer(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PowerOnSBMServer", reflect.TypeOf((*MockHostsService)(nil).PowerOnSBMServer), ctx, id) +} + // ReinstallOperatingSystemForDedicatedServer mocks base method. func (m *MockHostsService) ReinstallOperatingSystemForDedicatedServer(ctx context.Context, id string, input serverscom.OperatingSystemReinstallInput) (*serverscom.DedicatedServer, error) { m.ctrl.T.Helper() @@ -304,6 +395,21 @@ func (mr *MockHostsServiceMockRecorder) ReinstallOperatingSystemForDedicatedServ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReinstallOperatingSystemForDedicatedServer", reflect.TypeOf((*MockHostsService)(nil).ReinstallOperatingSystemForDedicatedServer), ctx, id, input) } +// ReinstallOperatingSystemForSBMServer mocks base method. +func (m *MockHostsService) ReinstallOperatingSystemForSBMServer(ctx context.Context, id string, input serverscom.SBMOperatingSystemReinstallInput) (*serverscom.SBMServer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReinstallOperatingSystemForSBMServer", ctx, id, input) + ret0, _ := ret[0].(*serverscom.SBMServer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReinstallOperatingSystemForSBMServer indicates an expected call of ReinstallOperatingSystemForSBMServer. +func (mr *MockHostsServiceMockRecorder) ReinstallOperatingSystemForSBMServer(ctx, id, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReinstallOperatingSystemForSBMServer", reflect.TypeOf((*MockHostsService)(nil).ReinstallOperatingSystemForSBMServer), ctx, id, input) +} + // ReleaseSBMServer mocks base method. func (m *MockHostsService) ReleaseSBMServer(ctx context.Context, id string) (*serverscom.SBMServer, error) { m.ctrl.T.Helper() @@ -333,3 +439,48 @@ func (mr *MockHostsServiceMockRecorder) ScheduleReleaseForDedicatedServer(ctx, i mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ScheduleReleaseForDedicatedServer", reflect.TypeOf((*MockHostsService)(nil).ScheduleReleaseForDedicatedServer), ctx, id) } + +// UpdateDedicatedServer mocks base method. +func (m *MockHostsService) UpdateDedicatedServer(ctx context.Context, id string, input serverscom.DedicatedServerUpdateInput) (*serverscom.DedicatedServer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDedicatedServer", ctx, id, input) + ret0, _ := ret[0].(*serverscom.DedicatedServer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateDedicatedServer indicates an expected call of UpdateDedicatedServer. +func (mr *MockHostsServiceMockRecorder) UpdateDedicatedServer(ctx, id, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDedicatedServer", reflect.TypeOf((*MockHostsService)(nil).UpdateDedicatedServer), ctx, id, input) +} + +// UpdateKubernetesBaremetalNode mocks base method. +func (m *MockHostsService) UpdateKubernetesBaremetalNode(ctx context.Context, id string, input serverscom.KubernetesBaremetalNodeUpdateInput) (*serverscom.KubernetesBaremetalNode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateKubernetesBaremetalNode", ctx, id, input) + ret0, _ := ret[0].(*serverscom.KubernetesBaremetalNode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateKubernetesBaremetalNode indicates an expected call of UpdateKubernetesBaremetalNode. +func (mr *MockHostsServiceMockRecorder) UpdateKubernetesBaremetalNode(ctx, id, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateKubernetesBaremetalNode", reflect.TypeOf((*MockHostsService)(nil).UpdateKubernetesBaremetalNode), ctx, id, input) +} + +// UpdateSBMServer mocks base method. +func (m *MockHostsService) UpdateSBMServer(ctx context.Context, id string, input serverscom.SBMServerUpdateInput) (*serverscom.SBMServer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateSBMServer", ctx, id, input) + ret0, _ := ret[0].(*serverscom.SBMServer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateSBMServer indicates an expected call of UpdateSBMServer. +func (mr *MockHostsServiceMockRecorder) UpdateSBMServer(ctx, id, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSBMServer", reflect.TypeOf((*MockHostsService)(nil).UpdateSBMServer), ctx, id, input) +} diff --git a/serverscom/testing/load_balancers_mock.go b/serverscom/testing/load_balancers_mock.go index 1b3cba0..e891d9b 100644 --- a/serverscom/testing/load_balancers_mock.go +++ b/serverscom/testing/load_balancers_mock.go @@ -21,6 +21,7 @@ import ( type MockLoadBalancersService struct { ctrl *gomock.Controller recorder *MockLoadBalancersServiceMockRecorder + isgomock struct{} } // MockLoadBalancersServiceMockRecorder is the mock recorder for MockLoadBalancersService. diff --git a/serverscom/utils.go b/serverscom/utils.go index 1141cbd..4e54d99 100644 --- a/serverscom/utils.go +++ b/serverscom/utils.go @@ -2,10 +2,14 @@ package serverscom import ( "fmt" - "k8s.io/klog/v2" "regexp" + "sort" "strings" + "k8s.io/klog/v2" + + "maps" + cli "github.com/serverscom/serverscom-go-client/pkg" v1 "k8s.io/api/core/v1" ) @@ -134,3 +138,108 @@ func anyMatch(str string, matches ...*string) bool { return false } + +// sanitizeLabelValue sanitazes label value according to: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set +// +// rules: +// must be 63 characters or less (can be empty), +// unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]), +// could contain dashes (-), underscores (_), dots (.), and alphanumerics between. +// +// returns empty string if no valid chars found +func sanitizeLabelValue(value string) string { + if len(value) == 0 { + return value + } + + runes := []rune(value) + + // replace any invalid char to '-' + for i, r := range runes { + if !isValidLabelChar(r) { + runes[i] = '-' + } + } + + start := 0 + for start < len(runes) && !isAlphaNumeric(runes[start]) { + start++ + } + + if start == len(runes) { + return "" + } + + end := len(runes) - 1 + for end >= 0 && !isAlphaNumeric(runes[end]) { + end-- + } + + runes = runes[start : end+1] + + if len(runes) > 63 { + runes = runes[:63] + + // check that after truncate we still have valid chars in the end + if !isAlphaNumeric(runes[len(runes)-1]) { + lastValid := len(runes) - 1 + for lastValid >= 0 && !isAlphaNumeric(runes[lastValid]) { + lastValid-- + } + + if lastValid >= 0 { + runes = runes[:lastValid+1] + } else { + return "" + } + } + } + + return string(runes) +} + +func isValidLabelChar(r rune) bool { + return isAlphaNumeric(r) || r == '-' || r == '_' || r == '.' +} + +func isAlphaNumeric(r rune) bool { + return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') +} + +// mergeDefaultLabels merge existing labels with default ones. +// To ensure that we can add default labels, existing labels will sorted by keys and truncated to 64 - count of default labels. +// 64 - max supported labels for resource. +func mergeDefaultLabels(existing, defaultLabels map[string]string) map[string]string { + if defaultLabels == nil { + return existing + } + if existing == nil { + return defaultLabels + } + // max labels - count of default labels + truncateTo := 64 - len(defaultLabels) + + for k := range defaultLabels { + delete(existing, k) + } + + if len(existing) > truncateTo { + keys := make([]string, 0, len(existing)) + for k := range existing { + keys = append(keys, k) + } + sort.Strings(keys) + + truncated := make(map[string]string, 64) + for i := range truncateTo { + k := keys[i] + truncated[k] = existing[k] + } + existing = truncated + } + + maps.Copy(existing, defaultLabels) + + return existing +}