From 8dd80ce4775e96a4586fc9ce31d52af89c688b6f Mon Sep 17 00:00:00 2001 From: Jimmi Dyson Date: Mon, 7 Aug 2023 20:45:57 +0100 Subject: [PATCH] feat: Use interface to register handlers --- .markdownlint.json | 6 + .markdownlintrc | 10 -- README.md | 90 +++++++++++--- go.mod | 15 ++- go.sum | 29 ++++- internal/runtimehooks/webhooks/server.go | 147 ++++++++++++---------- make/kind.mk | 11 +- pkg/handlers/interfaces.go | 63 ++++++++++ pkg/handlers/lifecycle/handlers.go | 82 ------------- pkg/handlers/mutation/handlers.go | 107 ---------------- pkg/handlers/servicelbgc/deleter.go | 150 +++++++++++++++++++++++ pkg/handlers/servicelbgc/handler.go | 90 ++++++++++++++ 12 files changed, 507 insertions(+), 293 deletions(-) create mode 100644 .markdownlint.json delete mode 100644 .markdownlintrc create mode 100644 pkg/handlers/interfaces.go delete mode 100644 pkg/handlers/lifecycle/handlers.go delete mode 100644 pkg/handlers/mutation/handlers.go create mode 100644 pkg/handlers/servicelbgc/deleter.go create mode 100644 pkg/handlers/servicelbgc/handler.go diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 000000000..4af4c680f --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,6 @@ +{ + "heading-style": { "style": "atx" }, + "ul-style": { "style": "dash" }, + "line-length": { "line_length": 120, "stern": true }, + "hr-style": { "style": "---" } +} diff --git a/.markdownlintrc b/.markdownlintrc deleted file mode 100644 index 0ad7d5bb1..000000000 --- a/.markdownlintrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "default": true, - "MD003": { "style": "atx" }, - "MD004": { "style": "dash" }, - "MD007": { "indent": 4 }, - "MD013": { "line_length": 120 }, - "MD030": { "ul_multi": 3, "ol_multi": 2 }, - "MD033": { "allowed_elements": ["h1", "img"]}, - "MD035": { "style": "---" } -} diff --git a/README.md b/README.md index 548495911..f9c3a6be5 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,6 @@ make dev.run-on-kind eval $(make kind.kubeconfig) ``` -By default this will use the `ClusterResourceSet` addons provider. To use the `FluxHelmRelease` addons provider run: - -```shell -make ADDONS_PROVIDER=FluxHelmRelease dev.run-on-kind -eval $(make kind.kubeconfig) -``` - Pro-tip: to redeploy without rebuilding the binaries, images, etc (useful if you have only changed the Helm chart for example), run: @@ -33,14 +26,13 @@ make SKIP_BUILD=true dev.run-on-kind To create a cluster with [clusterctl](https://cluster-api.sigs.k8s.io/user/quick-start.html), run: ```shell -clusterctl generate cluster capi-quickstart \ - --flavor development \ - --kubernetes-version v1.26.0 \ - --control-plane-machine-count=1 \ - --worker-machine-count=1 | \ - gojq --yaml-input --yaml-output --slurp \ - '.[] | (select( .kind=="Cluster").metadata.labels += {"capi-runtime-extensions.d2iq-labs.com/cni": "calico"})' | \ - kubectl apply -f - +env POD_SECURITY_STANDARD_ENABLED=false \ + clusterctl generate cluster capi-quickstart \ + --flavor development \ + --kubernetes-version v1.27.2 \ + --control-plane-machine-count=1 \ + --worker-machine-count=1 | \ + kubectl apply --server-side -f - ``` Wait until control plane is ready: @@ -63,6 +55,19 @@ kubectl config set-cluster capi-quickstart \ --server=https://$(docker port capi-quickstart-lb 6443/tcp) ``` +Deploy Calico to the workload cluster (TODO deploy via lifecycle hook): + +```shell +helm repo add --force-update projectcalico https://docs.tigera.io/calico/charts +helm upgrade --install calico projectcalico/tigera-operator \ + --version v3.26.1 \ + --namespace tigera-operator \ + --create-namespace \ + --wait \ + --wait-for-jobs \ + --kubeconfig capd-kubeconfig +``` + Wait until all nodes are ready (this indicates that CNI has been deployed successfully): ```shell @@ -75,12 +80,67 @@ Show that Calico is running successfully on the workload cluster: kubectl --kubeconfig capd-kubeconfig get daemonsets -n calico-system ``` +Deploy kube-vip to provide service load-balancer: + +```shell +helm repo add --force-update kube-vip https://kube-vip.github.io/helm-charts +helm repo update +kind_subnet_prefix="$(docker network inspect kind -f '{{ (index .IPAM.Config 0).Subnet }}' | \ + grep -o '^[[:digit:]]\+\.[[:digit:]]\+\.')" +kubectl create configmap \ + --namespace kube-system kubevip \ + --from-literal "range-global=${kind_subnet_prefix}100.0-${kind_subnet_prefix}100.20" \ + --dry-run=client -oyaml | + kubectl --kubeconfig capd-kubeconfig apply --server-side -n kube-system -f - + +helm upgrade kube-vip-cloud-provider kube-vip/kube-vip-cloud-provider --version 0.2.2 \ + --install \ + --wait --wait-for-jobs \ + --namespace kube-system \ + --kubeconfig capd-kubeconfig \ + --set-string=image.tag=v0.0.6 + +helm upgrade kube-vip kube-vip/kube-vip --version 0.4.2 \ + --install \ + --wait --wait-for-jobs \ + --namespace kube-system \ + --kubeconfig capd-kubeconfig \ + --set-string=image.tag=v0.6.0 +``` + +Deploy traefik as a LB service: + +```shell +helm --kubeconfig capd-kubeconfig repo add traefik https://helm.traefik.io/traefik +helm repo update &>/dev/null +helm --kubeconfig capd-kubeconfig upgrade --install traefik traefik/traefik \ + --version v10.9.1 \ + --wait --wait-for-jobs \ + --set ports.web.hostPort=80 \ + --set ports.websecure.hostPort=443 \ + --set service.type=LoadBalancer +``` + +Watch for traefik LB service to get an external address: + +```shell +watch -n 0.5 kubectl --kubeconfig capd-kubeconfig get service/traefik +``` + To delete the workload cluster, run: ```shell kubectl delete cluster capi-quickstart ``` +Notice that the traefik service is deleted before the cluster is actually finally deleted. + +Check the pod logs: + +```shell +kubectl logs deployment/capi-runtime-extensions -f +``` + To delete the dev KinD cluster, run: ```shell diff --git a/go.mod b/go.mod index 6dd6a884d..6a4b14f55 100644 --- a/go.mod +++ b/go.mod @@ -6,32 +6,31 @@ module github.com/d2iq-labs/capi-runtime-extensions go 1.20 require ( + github.com/go-logr/logr v1.2.4 github.com/spf13/pflag v1.0.5 golang.org/x/sync v0.3.0 + k8s.io/api v0.27.4 k8s.io/apimachinery v0.27.4 + k8s.io/client-go v0.27.4 k8s.io/component-base v0.27.4 k8s.io/klog/v2 v2.100.1 sigs.k8s.io/cluster-api v1.5.0 sigs.k8s.io/controller-runtime v0.15.1 ) -require ( - github.com/blang/semver/v4 v4.0.0 // indirect - github.com/stretchr/testify v1.8.2 // indirect -) - require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver v3.5.1+incompatible // indirect + github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/logr v1.2.4 github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/gobuffalo/flect v1.0.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.3 // indirect @@ -48,6 +47,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/onsi/gomega v1.27.8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect @@ -66,9 +66,8 @@ require ( 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/api v0.27.4 // indirect k8s.io/apiextensions-apiserver v0.27.2 // indirect - k8s.io/client-go v0.27.4 + k8s.io/cluster-bootstrap v0.27.2 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect diff --git a/go.sum b/go.sum index e98de1bee..a084406cc 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,14 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= @@ -18,11 +24,14 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0= +github.com/coredns/corefile-migration v1.0.20 h1:MdOkT6F3ehju/n9tgxlGct8XAajOX2vN+wG7To4BWSI= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= @@ -50,6 +59,8 @@ github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En 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-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= +github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -72,6 +83,7 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS 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/google/cel-go v0.12.6 h1:kjeKudqV0OygrAqA9fX6J55S8gj+Jre2tckIm5RoG4M= github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -90,6 +102,7 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -112,6 +125,9 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 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= @@ -121,6 +137,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= +github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -138,11 +156,14 @@ github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPH github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 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/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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= @@ -151,9 +172,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= @@ -167,6 +187,7 @@ go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= 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.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -248,6 +269,7 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -293,8 +315,11 @@ k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laCl k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ= k8s.io/apimachinery v0.27.4 h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs= k8s.io/apimachinery v0.27.4/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= +k8s.io/apiserver v0.27.2 h1:p+tjwrcQEZDrEorCZV2/qE8osGTINPuS5ZNqWAvKm5E= k8s.io/client-go v0.27.4 h1:vj2YTtSJ6J4KxaC88P4pMPEQECWMY8gqPqsTgUKzvjk= k8s.io/client-go v0.27.4/go.mod h1:ragcly7lUlN0SRPk5/ZkGnDjPknzb37TICq07WhI6Xc= +k8s.io/cluster-bootstrap v0.27.2 h1:OL3onrOwrUD7NQxBUqQwTl1Uu2GQKCkw9BMHpc4PbiA= +k8s.io/cluster-bootstrap v0.27.2/go.mod h1:b++PF0mjUOiTKdPQFlDw7p4V2VquANZ8SfhAwzxZJFM= k8s.io/component-base v0.27.4 h1:Wqc0jMKEDGjKXdae8hBXeskRP//vu1m6ypC+gwErj4c= k8s.io/component-base v0.27.4/go.mod h1:hoiEETnLc0ioLv6WPeDt8vD34DDeB35MfQnxCARq3kY= k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= diff --git a/internal/runtimehooks/webhooks/server.go b/internal/runtimehooks/webhooks/server.go index 4e9d82869..6e984e5c5 100644 --- a/internal/runtimehooks/webhooks/server.go +++ b/internal/runtimehooks/webhooks/server.go @@ -13,8 +13,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" ctrclient "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/lifecycle" - "github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/mutation" + "github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers" + "github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/servicelbgc" ) type Server struct { @@ -78,68 +78,87 @@ func (s *Server) Start(ctx context.Context) error { return err } - // Create the ExtensionHandlers for the lifecycle hooks - lifecycleExtensionHandlers := lifecycle.NewExtensionHandlers(client) - - // Register extension handlers. - if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ - Hook: runtimehooksv1.BeforeClusterCreate, - Name: "before-cluster-create", - HandlerFunc: lifecycleExtensionHandlers.DoBeforeClusterCreate, - }); err != nil { - setupLog.Error(err, "error adding handler") - return err - } - if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ - Hook: runtimehooksv1.AfterControlPlaneInitialized, - Name: "after-control-plane-initialized", - HandlerFunc: lifecycleExtensionHandlers.DoAfterControlPlaneInitialized, - }); err != nil { - setupLog.Error(err, "error adding handler") - return err - } - if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ - Hook: runtimehooksv1.BeforeClusterUpgrade, - Name: "before-cluster-upgrade", - HandlerFunc: lifecycleExtensionHandlers.DoBeforeClusterUpgrade, - }); err != nil { - setupLog.Error(err, "error adding handler") - return err - } - if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ - Hook: runtimehooksv1.BeforeClusterDelete, - Name: "before-cluster-delete", - HandlerFunc: lifecycleExtensionHandlers.DoBeforeClusterDelete, - }); err != nil { - setupLog.Error(err, "error adding handler") - return err - } - - // Create the ExtensionHandlers for the topology mutation hooks - topologyMutationHandlers := mutation.NewExtensionHandlers(client) - if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ - Hook: runtimehooksv1.DiscoverVariables, - Name: "discover-variables", - HandlerFunc: topologyMutationHandlers.DoDiscoverVariables, - }); err != nil { - setupLog.Error(err, "error adding handler") - return err - } - if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ - Hook: runtimehooksv1.GeneratePatches, - Name: "generate-patches", - HandlerFunc: topologyMutationHandlers.DoGeneratePatches, - }); err != nil { - setupLog.Error(err, "error adding handler") - return err - } - if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ - Hook: runtimehooksv1.ValidateTopology, - Name: "validate-topology", - HandlerFunc: topologyMutationHandlers.DoValidateTopology, - }); err != nil { - setupLog.Error(err, "error adding handler") - return err + allHandlers := []handlers.NamedHandler{servicelbgc.New(client)} + + for idx := range allHandlers { + h := allHandlers[idx] + + if t, ok := h.(handlers.BeforeClusterCreateLifecycleHandler); ok { + if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ + Hook: runtimehooksv1.BeforeClusterCreate, + Name: h.Name(), + HandlerFunc: t.BeforeClusterCreate, + }); err != nil { + setupLog.Error(err, "error adding handler") + return err + } + } + + if t, ok := h.(handlers.AfterControlPlaneInitializedLifecycleHandler); ok { + if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ + Hook: runtimehooksv1.AfterControlPlaneInitialized, + Name: h.Name(), + HandlerFunc: t.AfterControlPlaneInitialized, + }); err != nil { + setupLog.Error(err, "error adding handler") + return err + } + } + + if t, ok := h.(handlers.BeforeClusterUpgradeLifecycleHandler); ok { + if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ + Hook: runtimehooksv1.BeforeClusterUpgrade, + Name: h.Name(), + HandlerFunc: t.BeforeClusterUpgrade, + }); err != nil { + setupLog.Error(err, "error adding handler") + return err + } + } + + if t, ok := h.(handlers.BeforeClusterDeleteLifecycleHandler); ok { + if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ + Hook: runtimehooksv1.BeforeClusterDelete, + Name: h.Name(), + HandlerFunc: t.BeforeClusterDelete, + }); err != nil { + setupLog.Error(err, "error adding handler") + return err + } + } + + if t, ok := h.(handlers.DiscoverVariablesMutationHandler); ok { + if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ + Hook: runtimehooksv1.DiscoverVariables, + Name: h.Name(), + HandlerFunc: t.DiscoverVariables, + }); err != nil { + setupLog.Error(err, "error adding handler") + return err + } + } + + if t, ok := h.(handlers.GeneratePatchesMutationHandler); ok { + if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ + Hook: runtimehooksv1.GeneratePatches, + Name: h.Name(), + HandlerFunc: t.GeneratePatches, + }); err != nil { + setupLog.Error(err, "error adding handler") + return err + } + } + + if t, ok := h.(handlers.ValidateTopologyMutationHandler); ok { + if err := webhookServer.AddExtensionHandler(server.ExtensionHandler{ + Hook: runtimehooksv1.ValidateTopology, + Name: h.Name(), + HandlerFunc: t.ValidateTopology, + }); err != nil { + setupLog.Error(err, "error adding handler") + return err + } + } } // Start the https server. diff --git a/make/kind.mk b/make/kind.mk index 8522fb96e..6901f15cd 100644 --- a/make/kind.mk +++ b/make/kind.mk @@ -6,12 +6,13 @@ KIND_DIR := $(REPO_ROOT)/.local/kind KIND_CLUSTER_NAME ?= $(GITHUB_REPOSITORY)-dev KIND_KUBECONFIG ?= $(KIND_DIR)/$(KIND_CLUSTER_NAME)/kubeconfig -KINDEST_NODE_IMAGE ?= kindest/node -KINDEST_NODE_VERSION_v1.24 ?= v1.24.7@sha256:577c630ce8e509131eab1aea12c022190978dd2f745aac5eb1fe65c0807eb315 -KINDEST_NODE_VERSION_v1.25 ?= v1.25.3@sha256:f52781bc0d7a19fb6c405c2af83abfeb311f130707a0e219175677e366cc45d1 -KINDEST_NODE_VERSION_v1.26 ?= v1.26.0@sha256:691e24bd2417609db7e589e1a479b902d2e209892a10ce375fab60a8407c7352 +KINDEST_NODE_IMAGE ?= ghcr.io/mesosphere/kind-node +KINDEST_NODE_VERSION_v1.24 ?= v1.24.16 +KINDEST_NODE_VERSION_v1.25 ?= v1.25.12 +KINDEST_NODE_VERSION_v1.26 ?= v1.26.7 +KINDEST_NODE_VERSION_v1.27 ?= v1.27.4 # Allow easy override of Kubernetes version to use via `make KIND_KUBERNETES_VERSION=v1.23` to use in CI -KIND_KUBERNETES_VERSION ?= v1.26 +KIND_KUBERNETES_VERSION ?= v1.27 ifndef KINDEST_NODE_VERSION_$(KIND_KUBERNETES_VERSION) $(error Unsupported Kind Kubernetes version: $(KIND_KUBERNETES_VERSION) (use on of: [$(patsubst KINDEST_NODE_VERSION_%,%,$(filter KINDEST_NODE_VERSION_%,$(.VARIABLES)))])) endif diff --git a/pkg/handlers/interfaces.go b/pkg/handlers/interfaces.go new file mode 100644 index 000000000..837f111a1 --- /dev/null +++ b/pkg/handlers/interfaces.go @@ -0,0 +1,63 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package handlers + +import ( + "context" + + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" +) + +type NamedHandler interface { + Name() string +} + +type BeforeClusterCreateLifecycleHandler interface { + BeforeClusterCreate( + context.Context, + *runtimehooksv1.BeforeClusterCreateRequest, + *runtimehooksv1.BeforeClusterCreateResponse, + ) +} +type AfterControlPlaneInitializedLifecycleHandler interface { + AfterControlPlaneInitialized( + context.Context, + *runtimehooksv1.BeforeClusterCreateRequest, *runtimehooksv1.BeforeClusterCreateResponse) +} +type BeforeClusterUpgradeLifecycleHandler interface { + BeforeClusterUpgrade( + context.Context, + *runtimehooksv1.BeforeClusterUpgradeRequest, + *runtimehooksv1.BeforeClusterUpgradeResponse, + ) +} +type BeforeClusterDeleteLifecycleHandler interface { + BeforeClusterDelete( + context.Context, + *runtimehooksv1.BeforeClusterDeleteRequest, + *runtimehooksv1.BeforeClusterDeleteResponse, + ) +} + +type DiscoverVariablesMutationHandler interface { + DiscoverVariables( + context.Context, + *runtimehooksv1.DiscoverVariablesRequest, + *runtimehooksv1.DiscoverVariablesResponse, + ) +} +type GeneratePatchesMutationHandler interface { + GeneratePatches( + context.Context, + *runtimehooksv1.GeneratePatchesRequest, + *runtimehooksv1.GeneratePatchesResponse, + ) +} +type ValidateTopologyMutationHandler interface { + ValidateTopology( + context.Context, + *runtimehooksv1.ValidateTopologyRequest, + *runtimehooksv1.ValidateTopologyResponse, + ) +} diff --git a/pkg/handlers/lifecycle/handlers.go b/pkg/handlers/lifecycle/handlers.go deleted file mode 100644 index 78193b68e..000000000 --- a/pkg/handlers/lifecycle/handlers.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2023 D2iQ, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package lifecycle - -import ( - "context" - - runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" - ctrl "sigs.k8s.io/controller-runtime" - ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" -) - -// ExtensionHandlers provides a common struct shared across the lifecycle hook handlers. -type ExtensionHandlers struct { - client ctrlclient.Client -} - -// NewExtensionHandlers returns a ExtensionHandlers for the lifecycle hooks handlers. -func NewExtensionHandlers( - client ctrlclient.Client, -) *ExtensionHandlers { - return &ExtensionHandlers{ - client: client, - } -} - -func (m *ExtensionHandlers) DoBeforeClusterCreate( - ctx context.Context, - request *runtimehooksv1.BeforeClusterCreateRequest, - response *runtimehooksv1.BeforeClusterCreateResponse, -) { - log := ctrl.LoggerFrom(ctx).WithValues( - "Cluster", - request.Cluster.GetName(), - "Namespace", - request.Cluster.GetNamespace(), - ) - log.Info("BeforeClusterCreate is called") -} - -func (m *ExtensionHandlers) DoAfterControlPlaneInitialized( - ctx context.Context, - request *runtimehooksv1.AfterControlPlaneInitializedRequest, - response *runtimehooksv1.AfterControlPlaneInitializedResponse, -) { - log := ctrl.LoggerFrom(ctx).WithValues( - "Cluster", - request.Cluster.GetName(), - "Namespace", - request.Cluster.GetNamespace(), - ) - log.Info("AfterControlPlaneInitialized is called") -} - -func (m *ExtensionHandlers) DoBeforeClusterUpgrade( - ctx context.Context, - request *runtimehooksv1.BeforeClusterUpgradeRequest, - response *runtimehooksv1.BeforeClusterUpgradeResponse, -) { - log := ctrl.LoggerFrom(ctx).WithValues( - "Cluster", - request.Cluster.GetName(), - "Namespace", - request.Cluster.GetNamespace(), - ) - log.Info("BeforeClusterUpgrade is called") -} - -func (m *ExtensionHandlers) DoBeforeClusterDelete( - ctx context.Context, - request *runtimehooksv1.BeforeClusterDeleteRequest, - response *runtimehooksv1.BeforeClusterDeleteResponse, -) { - log := ctrl.LoggerFrom(ctx).WithValues( - "Cluster", - request.Cluster.GetName(), - "Namespace", - request.Cluster.GetNamespace(), - ) - log.Info("BeforeClusterDelete is called") -} diff --git a/pkg/handlers/mutation/handlers.go b/pkg/handlers/mutation/handlers.go deleted file mode 100644 index c5dc09113..000000000 --- a/pkg/handlers/mutation/handlers.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2023 D2iQ, Inc. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package mutation - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/serializer" - capiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1" - runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" - ctrl "sigs.k8s.io/controller-runtime" - ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" -) - -// ExtensionHandlers provides a common struct shared across the topology mutation hook handlers. -type ExtensionHandlers struct { - client ctrlclient.Client -} - -// NewExtensionHandlers returns a ExtensionHandlers for the topology mutation hooks handlers. -func NewExtensionHandlers( - client ctrlclient.Client, -) *ExtensionHandlers { - return &ExtensionHandlers{ - client: client, - } -} - -func (m *ExtensionHandlers) DoDiscoverVariables( - ctx context.Context, - request *runtimehooksv1.DiscoverVariablesRequest, - response *runtimehooksv1.DiscoverVariablesResponse, -) { - log := ctrl.LoggerFrom(ctx) - log.Info("DiscoverVariables is called") -} - -var ( - clusterGK = capiv1beta1.GroupVersion.WithKind("Cluster").GroupKind() - - unstructuredDecoder = serializer.NewCodecFactory(nil).UniversalDeserializer() -) - -func (m *ExtensionHandlers) DoGeneratePatches( - ctx context.Context, - request *runtimehooksv1.GeneratePatchesRequest, - response *runtimehooksv1.GeneratePatchesResponse, -) { - log := ctrl.LoggerFrom(ctx) - - for i := range request.Items { - gvk := request.Items[i].Object.Object.GetObjectKind().GroupVersionKind() - if gvk.GroupKind() == clusterGK { - obj := &unstructured.Unstructured{} - _, _, err := unstructuredDecoder.Decode(request.Items[i].Object.Raw, nil, obj) - if err != nil { - response.Status = runtimehooksv1.ResponseStatusFailure - response.Message = fmt.Sprintf("failed to decode cluster object: %v", err) - return - } - - log = log.WithValues( - "Cluster", - obj.GetName(), - "Namespace", - obj.GetNamespace(), - ) - break - } - } - - log.Info("GeneratePatches is called") -} - -func (m *ExtensionHandlers) DoValidateTopology( - ctx context.Context, - request *runtimehooksv1.ValidateTopologyRequest, - response *runtimehooksv1.ValidateTopologyResponse, -) { - log := ctrl.LoggerFrom(ctx) - - for i := range request.Items { - gvk := request.Items[i].Object.Object.GetObjectKind().GroupVersionKind() - if gvk.GroupKind() == clusterGK { - obj := &unstructured.Unstructured{} - _, _, err := unstructuredDecoder.Decode(request.Items[i].Object.Raw, nil, obj) - if err != nil { - response.Status = runtimehooksv1.ResponseStatusFailure - response.Message = fmt.Sprintf("failed to decode cluster object: %v", err) - return - } - - log = log.WithValues( - "Cluster", - obj.GetName(), - "Namespace", - obj.GetNamespace(), - ) - break - } - } - - log.Info("ValidateTopology is called") -} diff --git a/pkg/handlers/servicelbgc/deleter.go b/pkg/handlers/servicelbgc/deleter.go new file mode 100644 index 000000000..94ba8f8ca --- /dev/null +++ b/pkg/handlers/servicelbgc/deleter.go @@ -0,0 +1,150 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package servicelbgc + +import ( + "context" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/cluster-api/util/conditions" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + LoadBalancerGCAnnotation = "capiext.labs.d2iq.io/loadbalancer-gc" +) + +var ( + ErrFailedToDeleteService = errors.New("failed to delete kubernetes services") + ErrServicesStillExist = errors.New("waiting for kubernetes services to be fully deleted") +) + +func deleteServicesWithLoadBalancer( + ctx context.Context, + c client.Client, + log logr.Logger, +) error { + log.Info("Listing Services with type LoadBalancer") + services := &corev1.ServiceList{} + err := c.List(ctx, services) + if err != nil { + return fmt.Errorf("error listing Services: %w", err) + } + + var ( + svcsFailedToBeDeleted []client.ObjectKey + svcsStillExisting []client.ObjectKey + ) + for idx := range services.Items { + svc := &services.Items[idx] + svcKey := client.ObjectKeyFromObject(svc) + if needsDelete(svc) { + svcsStillExisting = append(svcsStillExisting, svcKey) + + if svc.DeletionTimestamp != nil { + continue + } + + log.Info(fmt.Sprintf("Deleting Service %s", svcKey)) + if err = c.Delete(ctx, svc); err != nil { + if client.IgnoreNotFound(err) == nil { + continue + } + log.Error( + err, + fmt.Sprintf( + "Error deleting Service %s/%s", + svc.Namespace, + svc.Name, + ), + ) + svcsFailedToBeDeleted = append(svcsFailedToBeDeleted, svcKey) + } + } + } + if len(svcsFailedToBeDeleted) > 0 { + return failedToDeleteServicesError(svcsFailedToBeDeleted) + } + if len(svcsStillExisting) > 0 { + return servicesStillExistError(svcsStillExisting) + } + + return nil +} + +// needsDelete will return true if the Service needs to be deleted to allow for cluster cleanup. +func needsDelete(service *corev1.Service) bool { + if service.Spec.Type != corev1.ServiceTypeLoadBalancer || + len(service.Status.LoadBalancer.Ingress) == 0 { + return false + } + return service.Status.LoadBalancer.Ingress[0].IP != "" || + service.Status.LoadBalancer.Ingress[0].Hostname != "" +} + +func toStringSlice[T fmt.Stringer](stringers []T) []string { + strs := make([]string, 0, len(stringers)) + for _, k := range stringers { + strs = append(strs, k.String()) + } + + return strs +} + +func failedToDeleteServicesError(svcsFailedToBeDeleted []client.ObjectKey) error { + return fmt.Errorf("%w: the following Services could not be deleted "+ + "and must cleaned up manually before deleting the cluster: %s", + ErrFailedToDeleteService, + strings.Join(toStringSlice(svcsFailedToBeDeleted), ","), + ) +} + +func servicesStillExistError(svcsStillExisting []client.ObjectKey) error { + return fmt.Errorf("%w: waiting for the following services to be fully deleted: %s", + ErrServicesStillExist, + strings.Join(toStringSlice(svcsStillExisting), ","), + ) +} + +func shouldDeleteServicesWithLoadBalancer(cluster *v1beta1.Cluster) (bool, error) { + // Use the Cluster annotations to skip deleting + val, found := cluster.GetAnnotations()[LoadBalancerGCAnnotation] + if !found { + val = "true" + } + shouldDeleteBasedOnAnnotation, err := strconv.ParseBool(val) + if err != nil { + return false, fmt.Errorf( + "failed to convert value %s of annotation %s to bool: %w", + val, + LoadBalancerGCAnnotation, + err, + ) + } + + // Use the Cluster phase to determine if it's safe to skip deleting: + // + // - when ClusterPhasePending or ClusterPhaseProvisioning Kubernetes API has not been created + // and the user would not have been able to create any Kubernetes resources that would prevent cleanup + // + // - when ClusterPhaseDeleting it's too late to try to cleanup. + phase := cluster.Status.GetTypedPhase() + skipDeleteBasedOnPhase := phase == v1beta1.ClusterPhasePending || + phase == v1beta1.ClusterPhaseProvisioning || + phase == v1beta1.ClusterPhaseDeleting + + // use the Cluster conditions to determine if the API server is even reachable + controlPlaneReachable := conditions.IsTrue(cluster, v1beta1.ControlPlaneInitializedCondition) + + return shouldDeleteBasedOnAnnotation && controlPlaneReachable && !skipDeleteBasedOnPhase, nil +} diff --git a/pkg/handlers/servicelbgc/handler.go b/pkg/handlers/servicelbgc/handler.go new file mode 100644 index 000000000..328a5f5f4 --- /dev/null +++ b/pkg/handlers/servicelbgc/handler.go @@ -0,0 +1,90 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package servicelbgc + +import ( + "context" + "errors" + "fmt" + + "sigs.k8s.io/cluster-api/controllers/remote" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + ctrl "sigs.k8s.io/controller-runtime" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers" +) + +type ServiceLoadBalancerGC struct { + client ctrlclient.Client +} + +var ( + _ handlers.NamedHandler = &ServiceLoadBalancerGC{} + _ handlers.BeforeClusterDeleteLifecycleHandler = &ServiceLoadBalancerGC{} +) + +func New(client ctrlclient.Client) *ServiceLoadBalancerGC { + return &ServiceLoadBalancerGC{client: client} +} + +func (s *ServiceLoadBalancerGC) Name() string { + return "service-loadbalancer-gc" +} + +func (s *ServiceLoadBalancerGC) BeforeClusterDelete( + ctx context.Context, + req *runtimehooksv1.BeforeClusterDeleteRequest, + resp *runtimehooksv1.BeforeClusterDeleteResponse, +) { + clusterKey := ctrlclient.ObjectKeyFromObject(&req.Cluster) + + log := ctrl.LoggerFrom(ctx).WithValues( + "cluster", + clusterKey, + ) + + shouldDelete, err := shouldDeleteServicesWithLoadBalancer(&req.Cluster) + if err != nil { + resp.Status = runtimehooksv1.ResponseStatusFailure + resp.Message = fmt.Sprintf( + "error determining if Services of type LoadBalancer should be deleted: %v", + err, + ) + return + } + + if !shouldDelete { + return + } + + log.Info("Will attempt to delete Services with type LoadBalancer") + remoteClient, err := remote.NewClusterClient( + ctx, + "", + s.client, + clusterKey, + ) + if err != nil { + resp.Status = runtimehooksv1.ResponseStatusFailure + resp.Message = fmt.Sprintf( + "error creating remote cluster client: %v", + err, + ) + return + } + + err = deleteServicesWithLoadBalancer(ctx, remoteClient, log) + switch { + case errors.Is(err, ErrFailedToDeleteService): + resp.Status = runtimehooksv1.ResponseStatusFailure + resp.Message = err.Error() + case errors.Is(err, ErrServicesStillExist): + resp.Status = runtimehooksv1.ResponseStatusSuccess + resp.Message = err.Error() + resp.RetryAfterSeconds = 5 + default: + resp.Status = runtimehooksv1.ResponseStatusSuccess + } +}