Skip to content

Commit

Permalink
Merge pull request #88 from sabinaaledort/add_address_pool_validation…
Browse files Browse the repository at this point in the history
…_webhook

Add AddressPool validation webhook
  • Loading branch information
fedepaol committed Sep 14, 2021
2 parents 3148095 + e032a63 commit 3f49352
Show file tree
Hide file tree
Showing 30 changed files with 918 additions and 80 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
- name: Deploy Metal LB Operator
run: |
IMG=${built_image} KUSTOMIZE_DEPLOY_DIR="config/kind-ci/" make deploy
IMG=${built_image} KUSTOMIZE_DEPLOY_DIR="config/kind-ci/" ENABLE_OPERATOR_WEBHOOK="true" make deploy
- name: E2E Tests
run: |
Expand Down
19 changes: 17 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ endif
OPERATOR_SDK_VERSION=v1.8.1

OPM_TOOL_URL=https://api.github.com/repos/operator-framework/operator-registry/releases
CERT_MANAGER_URL=https://github.com/jetstack/cert-manager/releases

TESTS_REPORTS_PATH ?= /tmp/test_e2e_logs/
VALIDATION_TESTS_REPORTS_PATH ?= /tmp/test_validation_logs/
Expand Down Expand Up @@ -80,7 +81,20 @@ install: manifests kustomize ## Install CRDs into a cluster
uninstall: manifests kustomize ## Uninstall CRDs from a cluster
$(KUSTOMIZE) build config/crd | kubectl delete -f -

deploy: manifests kustomize ## Deploy controller in the configured cluster
configure-operator-webhook:
KUSTOMIZE=$(KUSTOMIZE) hack/configure_operator_webhook.sh

deploy-cert-manager:
set -e ;\
cert_manager_latest_version=$$(curl -s $(CERT_MANAGER_URL)| grep "title\=\"v" | head -1 | awk -F' ' '{print $$5}' | awk -F'=' '{print $$2}' | xargs) ;\
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/$$cert_manager_latest_version/cert-manager.yaml ;\
hack/wait_for_cert_manager.sh ;\

deploy: export ENABLE_OPERATOR_WEBHOOK?=false
deploy: manifests kustomize configure-operator-webhook ## Deploy controller in the configured cluster
ifeq ($(ENABLE_OPERATOR_WEBHOOK), true)
$(MAKE) deploy-cert-manager
endif
cd config/manager && kustomize edit set image controller=${IMG}
$(KUSTOMIZE) build $(KUSTOMIZE_DEPLOY_DIR) | kubectl apply -f -
$(KUSTOMIZE) build config/metallb_rbac | kubectl apply -f -
Expand All @@ -105,7 +119,8 @@ docker-build: ## Build the docker image
docker-push: ## Push the docker image
docker push ${IMG}

bundle: operator-sdk manifests ## Generate bundle manifests and metadata, then validate generated files.
bundle: export ENABLE_OPERATOR_WEBHOOK?=true
bundle: operator-sdk manifests configure-operator-webhook ## Generate bundle manifests and metadata, then validate generated files.
$(OPERATOR_SDK) generate kustomize manifests --interactive=false -q
cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
$(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle -q --overwrite --version $(CSV_VERSION) $(BUNDLE_METADATA_OPTS) --extra-service-accounts "controller,speaker"
Expand Down
4 changes: 4 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ resources:
kind: AddressPool
path: github.com/metallb/metallb-operator/api/v1alpha1
version: v1alpha1
webhooks:
defaulting: true
validation: true
webhookVersion: v1beta1
- api:
crdVersion: v1beta1
namespaced: true
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Need to install the following packages
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.3.0
```

## AddressPool Validation Webhook

When the AddressPool Validation Webhook is enabled a request to apply an AddressPool with an already defined IP range will be denied.

## Installation

To install the MetalLB Operator using a prebuilt image, run:
Expand All @@ -22,6 +26,12 @@ To install the MetalLB Operator using a prebuilt image, run:
make deploy
```

To install the MetalLB Operator using a prebuilt image and enable the AddressPool Validation Webhook, run:

```shell
ENABLE_OPERATOR_WEBHOOK=true make deploy
```

## Usage

Once the MetalLB Operator is installed, you have to create a `MetalLB` custom resource to install MetalLB. The operator will consume this resource, and create all required MetalLB resources based on it. The `MetalLB` custom resource needs to be created inside the `metallb-system` namespace and be named `metallb`. Only one `MetalLB` resource can exist in a cluster.
Expand All @@ -43,6 +53,8 @@ metadata:

A quick, local installation can be done using a kind cluster using a local registry. Follow the steps below to run a locally built metallb-operator on kind.

To enable the AddressPool Validation Webhook set `ENABLE_OPERATOR_WEBHOOK=true`.

**Install and run kind**

Read more about kind [here](https://kind.sigs.k8s.io/docs/user/quick-start/).
Expand Down
131 changes: 131 additions & 0 deletions api/v1alpha1/addresspool_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"context"
"fmt"
"net"

"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)

// log is for logging addresspool-webhook.
var addresspoollog = logf.Log.WithName("addresspool-webhook")
var c client.Client

func (addressPool *AddressPool) SetupWebhookWithManager(mgr ctrl.Manager) error {
c = mgr.GetClient()

return ctrl.NewWebhookManagedBy(mgr).
For(addressPool).
Complete()
}

//+kubebuilder:webhook:verbs=create;update,path=/validate-metallb-io-v1alpha1-addresspool,mutating=false,failurePolicy=fail,groups=metallb.io,resources=addresspools,versions=v1alpha1,name=addresspoolvalidationwebhook.metallb.io
var _ webhook.Validator = &AddressPool{}

// ValidateCreate implements webhook.Validator so a webhook will be registered for AddressPool.
func (addressPool *AddressPool) ValidateCreate() error {
addresspoollog.Info("validate AddressPool creation", "name", addressPool.Name)

existingAddressPoolList, err := getExistingAddressPools()
if err != nil {
return err
}

return addressPool.validateAddressPool(true, existingAddressPoolList)
}

// ValidateUpdate implements webhook.Validator so a webhook will be registered for AddressPool.
func (addressPool *AddressPool) ValidateUpdate(old runtime.Object) error {
addresspoollog.Info("validate AddressPool update", "name", addressPool.Name)

existingAddressPoolList, err := getExistingAddressPools()
if err != nil {
return err
}

return addressPool.validateAddressPool(false, existingAddressPoolList)
}

// ValidateDelete implements webhook.Validator so a webhook will be registered for AddressPool.
func (addressPool *AddressPool) ValidateDelete() error {
addresspoollog.Info("validate AddressPool deletion", "name", addressPool.Name)

return nil
}

func (addressPool *AddressPool) validateAddressPool(isNewAddressPool bool, existingAddressPoolList *AddressPoolList) error {
addressPoolCIDRS, err := getAddressPoolCIDRs(addressPool)
if err != nil {
return errors.Wrapf(err, "Failed to parse addresses for %s", addressPool.Name)
}

for _, existingAddressPool := range existingAddressPoolList.Items {
if existingAddressPool.Name == addressPool.Name {
// Check that the pool isn't already defined.
// Avoid errors when comparing the AddressPool to itself.
if isNewAddressPool {
return fmt.Errorf("duplicate definition of pool %s", addressPool.Name)
} else {
continue
}
}

existingAddressPoolCIDRS, err := getAddressPoolCIDRs(&existingAddressPool)
if err != nil {
return errors.Wrapf(err, "Failed to parse addresses for %s", existingAddressPool.Name)
}

// Check that the specified CIDR ranges are not overlapping in existing CIDRs.
for _, existingCIDR := range existingAddressPoolCIDRS {
for _, cidr := range addressPoolCIDRS {
if cidrsOverlap(existingCIDR, cidr) {
return fmt.Errorf("CIDR %q in pool %s overlaps with already defined CIDR %q in pool %s", cidr, addressPool.Name, existingCIDR, existingAddressPool.Name)
}
}
}
}
return nil
}

func getExistingAddressPools() (*AddressPoolList, error) {
existingAddressPoolList := &AddressPoolList{}
err := c.List(context.Background(), existingAddressPoolList)
if err != nil {
return nil, errors.Wrapf(err, "Failed to get existing addresspool objects")
}
return existingAddressPoolList, nil
}

func getAddressPoolCIDRs(addressPool *AddressPool) ([]*net.IPNet, error) {
var CIDRs []*net.IPNet
for _, cidr := range addressPool.Spec.Addresses {
nets, err := parseCIDR(cidr)
if err != nil {
return nil, errors.Wrapf(err, "invalid CIDR %q in pool %s", cidr, addressPool.Name)
}
CIDRs = append(CIDRs, nets...)
}
return CIDRs, nil
}

0 comments on commit 3f49352

Please sign in to comment.