diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml new file mode 100644 index 0000000..4b3be89 --- /dev/null +++ b/.github/workflows/workflow.yaml @@ -0,0 +1,100 @@ +name: Test Suite +on: + push: + branches: + - main + pull_request: + +jobs: + call-inclusive-naming-check: + name: Inclusive Naming + uses: canonical-web-and-design/Inclusive-naming/.github/workflows/woke.yaml@main + with: + fail-on-error: "true" + + lint-unit: + name: Lint Unit + uses: charmed-kubernetes/workflows/.github/workflows/lint-unit.yaml@main + with: + python: "['3.8', '3.9', '3.10', '3.11']" + needs: + - call-inclusive-naming-check + + integration-test-metallb: + runs-on: ubuntu-latest + name: Integration test + timeout-minutes: 30 + strategy: + matrix: + rbac: [ "without RBAC", "with RBAC" ] + steps: + - name: Check out code + uses: actions/checkout@v3 + - name: Setup operator environment + uses: charmed-kubernetes/actions-operator@main + with: + provider: microk8s + channel: 1.27-strict/stable + juju-channel: 3.1/stable + - name: Disable RBAC If Needed + if: ${{ matrix.rbac == 'without RBAC' }} + run: | + sudo microk8s disable rbac + sudo microk8s status --wait-ready + - name: Run test + run: sg snap_microk8s -c "tox -e integration" + - name: Setup Debug Artifact Collection + if: ${{ failure() }} + run: mkdir tmp + - name: Collect K8s Status + if: ${{ failure() }} + run: sudo microk8s.kubectl get all -A 2>&1 | tee tmp/microk8s-status-all.txt + - name: Collect Juju Status + if: ${{ failure() }} + run: sudo juju status 2>&1 | tee tmp/juju-status.txt + - name: Collect K8s Deployment details + if: ${{ failure() }} + run: sudo microk8s.kubectl describe deployments -A 2>&1 | tee tmp/microk8s-deployments.txt + - name: Collect K8s ReplicaSet details + if: ${{ failure() }} + run: sudo microk8s.kubectl describe replicasets -A 2>&1 | tee tmp/microk8s-replicasets.txt + - name: Collect K8s DaemonSet details + if: ${{ failure() }} + run: sudo microk8s.kubectl describe daemonsets -A 2>&1 | tee tmp/microk8s-daemonsets.txt + - name: Collect K8s ServiceAccount details + if: ${{ failure() }} + run: sudo microk8s.kubectl describe serviceaccounts -A 2>&1 | tee tmp/microk8s-serviceaccounts.txt + - name: Collect K8s Role details + if: ${{ failure() }} + run: sudo microk8s.kubectl describe roles -A 2>&1 | tee tmp/microk8s-roles.txt + - name: Collect K8s ClusterRole details + if: ${{ failure() }} + run: sudo microk8s.kubectl describe clusterroles 2>&1 | tee tmp/microk8s-clusterroles.txt + - name: Collect K8s RoleBinding details + if: ${{ failure() }} + run: sudo microk8s.kubectl describe rolebindings -A 2>&1 | tee tmp/microk8s-rolebindings.txt + - name: Collect K8s ClusterRoleBinding details + if: ${{ failure() }} + run: sudo microk8s.kubectl describe clusterrolebindings 2>&1 | tee tmp/microk8s-clusterrolebindings.txt + - name: Collect K8s pod logs + if: ${{ failure() }} + run: | + for pod in `sudo microk8s.kubectl get pods -n metallb-system | awk '{print$1}' | grep -v NAME`; do + echo "Pod logs for: $pod" + echo "----------------------------------" + sudo microk8s.kubectl logs $pod -n metallb-system 2>&1 | tee tmp/pod-$pod-logs.txt + echo + echo + done + - name: Collect microk8s snap logs + if: ${{ failure() }} + run: sudo snap logs -n 300 microk8s 2>&1 | tee tmp/snap-log-microk8s.txt + - name: Collect Juju logs + if: ${{ failure() }} + run: sudo juju debug-log --replay | tee tmp/juju.log + - name: Upload debug artifacts + if: ${{ failure() }} + uses: actions/upload-artifact@v2 + with: + name: test-run-artifacts + path: tmp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89b61c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +venv/ +build/ +*.charm +.tox/ +.coverage +__pycache__/ +*.py[cod] diff --git a/.wokeignore b/.wokeignore new file mode 100644 index 0000000..0479c90 --- /dev/null +++ b/.wokeignore @@ -0,0 +1 @@ +upstream/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..288f021 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing + +To make contributions to this charm, you'll need a working [development setup](https://juju.is/docs/sdk/dev-setup). + +You can create an environment for development with `tox`: + +```shell +tox devenv -e integration +source venv/bin/activate +``` + +## Testing + +This project uses `tox` for managing test environments. There are some pre-configured environments +that can be used for linting and formatting code when you're preparing contributions to the charm: + +```shell +tox run -e format # update your code according to linting rules +tox run -e lint # code style +tox run -e unit # unit tests +tox run -e integration # integration tests +tox # runs 'format', 'lint', and 'unit' environments +``` + +## Build the charm + +Build the charm in this git repository using: + +```shell +charmcraft pack +``` + + +[charmcraft]: https://github.com/canonical/charmcraft/ +[MicroK8s]: http://microk8s.io/ + diff --git a/charmcraft.yaml b/charmcraft.yaml new file mode 100644 index 0000000..17a55fb --- /dev/null +++ b/charmcraft.yaml @@ -0,0 +1,18 @@ +# Architectures based on supported arch's in upstream +# https://hub.docker.com/layers/bitnami/metallb-controller/0.13.10/images/sha256-d9bbb30d02d02ad499a8390105ebe5b94c5fd9086da9591f4e88b7855a9f5e46?context=explore +type: charm +bases: + - build-on: + - name: "ubuntu" + channel: "20.04" + architectures: ["amd64"] + run-on: + - name: "ubuntu" + channel: "22.04" + architectures: + - amd64 + - arm64 +parts: + charm: + prime: + - upstream/** \ No newline at end of file diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..7763150 --- /dev/null +++ b/config.yaml @@ -0,0 +1,36 @@ +# This file defines charm config options, and populates the Configure tab on Charmhub. +# If your charm does not require configuration options, delete this file entirely. +# +# See https://juju.is/docs/config for guidance. + +options: + namespace: + type: string + description: | + Namespace that the metallb resources will be installed in. This namespace will be created by the charm, + and should not currently exist + default: "metallb-system" + + image-registry: + type: string + description: | + Image registry for metallb container images. + The value set here will replace the host portion of each image URL in + the release manifests. + default: "rocks.canonical.com:443/cdk" + + metallb-release: + type: string + description: | + Specify the version of metallb to deploy. The version must be available in the upstream/metallb-native/manifests + directory of the charm source code in order to be deployed + default: "v0.13.10" + + iprange: + type: string + description: | + Comma-separated list of CIDRs and/or IPV4 and IPV6 ranges that define the IP addresses + MetalLB will assign to services + Example: + 192.168.10.0/24,192.168.9.1-192.168.9.5,fc00:f853:0ccd:e799::/124 + default: 192.168.1.240-192.168.1.247 \ No newline at end of file diff --git a/docs/how-to/h-manage-units.md b/docs/how-to/h-manage-units.md new file mode 100644 index 0000000..918bac0 --- /dev/null +++ b/docs/how-to/h-manage-units.md @@ -0,0 +1,17 @@ +# How to deploy and manage units + +## Basic Usage + +To deploy a single unit of MetalLB using its default configuration + +```shell +juju deploy metallb --channel 1.28/stable --trust +``` + +## Removing MetalLB + +To remove MetalLB, ending any LoadBalanced services it provides, you may remove the application from its model + +```shell +juju remove-application metallb +``` diff --git a/docs/how-to/h-migrate-from-split.md b/docs/how-to/h-migrate-from-split.md new file mode 100644 index 0000000..19a3138 --- /dev/null +++ b/docs/how-to/h-migrate-from-split.md @@ -0,0 +1,34 @@ +# How to Migrate from Split + +With the charms available through `1.27/stable` channels, one would typically create a model named `metallb-system` and deploy the 2 charms (`metallb-controller` and `metallb-speaker`) into that model, since juju maps a model name into a kubernetes namespace. While operating in this namespace isn't a hard requirement for metallb, it is the suggested namespace. + +Starting in `1.28/stable`, this two charm deployment has been unified into a single charm which applies the upstream manifests into the system and manages those manifests, rather than directly managing the sidecar containers. The following is the process to use this new charm. + +## Basic Steps + +First create a new model (call it whatever is preferred) so long as it is not named `metallb-system` and deploy the charm into that model. + +```shell +juju add-model juju-metallb +juju deploy metallb --channel 1.28/stable --trust --config namespace=metallb-system-2 +``` + +Next, wait until the metallb charm is active/idle + +```shell +juju status -m juju-metallb --watch=1s +``` + +Once stable, the new MetalLB installation will take over managing existing LoadBalancer services, and the model containing the old charms may be deleted. + +```shell +juju switch metallb-system +juju remove-application metallb-speaker +juju remove-application metallb-controller +``` + +Once the model is empty, it should be safe to remove the model + +```shell +juju destroy-model metallb-system --no-prompt +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..d7418f6 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,54 @@ +The Charmed MetalLB Operator delivers automated operations management from day 0 to day 2 +on the [MetalLB Load Balancer Implementation for Bare Metal Kubernetes](https://metallb.universe.tf/). +It is an open source, production-ready charm on top of [Juju](https://juju.is/) + +MetalLB is a load-balancer implementation for bare metal Kubernetes clusters, using standard routing protocols. + +The Charmed MetalLB Operator provides Layer 2 (with ARP [Address Resolution Protocol](https://en.wikipedia.org/wiki/Address_Resolution_Protocol)) or BGP([Border Gateway Protocol](https://en.wikipedia.org/wiki/Border_Gateway_Protocol)) to expose services. + +MetalLB has support for local traffic, meaning that the machine that receives the data will be the machine that services the request. It is not suggested to use a virtual IP with high traffic workloads because only one machine will receive the traffic for a service - the other machines are solely used for failover. + +BGP does not have this limitation but does see nodes as the atomic unit. This means if the service is running on two of five nodes then only those two nodes will receive traffic, but they will each receive 50% of the traffic even if one of the nodes has three pods and the other only has one pod running on it. It is recommended to use node anti-affinity to prevent Kubernetes pods from stacking on a single node. + +[note type="important" status="Note"] +For more information on configuring MetalLB with Calico in BGP mode, please see this [explanation of the required configuration](https://metallb.universe.tf/configuration/calico/) from the [MetalLB website](https://metallb.universe.tf/) +[/note] + + +## In this documentation + +| | | +|--|--| +| [Tutorials](/t/charmed-metallb-tutorial-overview/11359?channel=1.28/stable)
Get started - a hands-on introduction to using Charmed Metallb operator for new users
| [How-to guides](/t/charmed-metallb-how-to-managed-units/11363?channel=1.28/stable)
Step-by-step guides covering key operations and common tasks | +| [Reference](https://charmhub.io/metallb/actions?channel=1.28/stable)
Technical information - specifications, APIs, architecture | [Explanation](/t/charmed-metallb-explanation/####?channel=1.28/stable)
Concepts - discussion and clarification of key topics | + + +# Navigation + +[details=Navigation] + +| Level | Path | Navlink | +| ----- | -------- | ------------------------------- | +| 1 | tutorial | [Tutorial]() | +| 2 | t-overview | [1. Introduction](/t/charmed-metallb-tutorial-overview/11359) | +| 2 | t-setup-environment | [2. Set up the environment](/t/charmed-metallb-tutorial-setup-environment/11360) | +| 2 | t-deploy-metallb | [3. Deploy MetalLB](/t/charmed-metallb-tutorial-deploy-metallb/11361) | +| 2 | t-configure | [4. Configure MetalLB](/t/charmed-metallb-tutorial-configure/11362) | +| 1 | how-to | [How To]() | +| 2 | h-manage-units | [1. Managed Units](/t/charmed-metallb-how-to-managed-units/11363) | +| 2 | h-migrate-from-split | [2. Migrate From 1.27 Release](/t/charmed-metallb-how-to-migrate-from-1-27-stable-charms/11423) | +| 1 | reference | [Reference]() | +| 2 | r-actions | [Actions](https://charmhub.io/metallb/actions) | +| 2 | r-configurations | [Configurations](https://charmhub.io/metallb/configure) | +| 2 | r-libraries | [Libraries](https://charmhub.io/metallb/libraries) | +| 2 | r-integrations | [Integrations](https://charmhub.io/metallb/integrations) | +| 1 | explanation | [Explanation]() | + +[/details] + +# Redirects + +[details=Mapping table] +| Path | Location | +| ---- | -------- | +[/details] diff --git a/docs/tutorial/t-configure.md b/docs/tutorial/t-configure.md new file mode 100644 index 0000000..a4cd674 --- /dev/null +++ b/docs/tutorial/t-configure.md @@ -0,0 +1,21 @@ +# Configuration + +This is part of the [Charmed MetalLB Tutorial](/t/charmed-metalb-tutorial-overview/11359?channel=1.28/stable). Please refer to this page for more information and the overview of the content. + +You will need to change the IP addresses allocated to MetalLB to suit your environment. The IP addresses can be specified as a range, such as “192.168.1.88-192.168.1.89”, or as a comma-separated list of pools in CIDR notation, such as “192.168.1.240/28, 10.0.0.0/28”. + +Configuring the IP addresses can be done either at time of deployment via single-line config or later by changing the charm config via Juju. + +This will adjust the default `IPAddressPool.spec.addresses` created by the charm according to the [specification](https://metallb.universe.tf/configuration/_advanced_ipaddresspool_configuration/) + +An example single-line config adjustment might look like: + +```shell +juju deploy metallb --config iprange='192.168.1.88-192.168.1.89' --trust +``` + +Alternatively, you can change the config directly on the metallb charm at any time: + +```shell +juju config metallb iprange="192.168.1.240/28, 10.0.0.0/28" +``` diff --git a/docs/tutorial/t-deploy-metallb.md b/docs/tutorial/t-deploy-metallb.md new file mode 100644 index 0000000..d476eb0 --- /dev/null +++ b/docs/tutorial/t-deploy-metallb.md @@ -0,0 +1,36 @@ +# Deployment + +This is part of the [Charmed MetalLB Tutorial](/t/charmed-metalb-tutorial-overview/11359?channel=1.28/stable). Please refer to this page for more information and the overview of the content. + +## Deploy Charmed MetalLB + +To deploy Charmed MetaLB, all you need to do is + +The best way to deploy MetalLB in Layer 2 mode on Charmed Kubernetes is with the MetalLB charm, which activates +both the metallb controller Deployment and metallb speaker DaemonSet. + +Run the following command, which will fetch the charm from [Charmhub](https://charmhub.io/metallb?channel=1.28/stable) and deploy it to your model: +```shell +juju deploy metallb --channel 1.28/stable --trust +``` + +Juju will now fetch Charmed MetalLB and begin deploying it to the Kubernetes cluster. This process can take several minutes depending on how provisioned (RAM, CPU, etc) your machine is. You can track the progress by running: + +```shell +juju status --watch 1s +``` + +This command is useful for checking the status of Charmed MetalLB and gathering information about the containers hosting Charmed MetalLB. Some of the helpful information it displays include IP addresses, ports, state, etc. The command updates the status of Charmed MetalLB every second and as the application starts you can watch the status and messages of Charmed MetalLB change. Wait until the application is ready - when it is ready, `juju status` will show: +``` +Model Controller Cloud/Region Version SLA Timestamp +juju-metallb overlord k8s-cloud/default 3.1.5 unsupported 13:32:58-05:00 + +App Version Status Scale Charm Channel Rev Address Exposed Message +metallb active 1 metallb 1.28/stable 9 10.152.183.85 no + +Unit Workload Agent Address Ports Message +metallb/0* active idle 192.168.0.15 +``` +To exit the screen with `juju status --watch 1s`, enter `Ctrl+c`. +If you want to further inspect juju logs, can watch for logs with `juju debug-log`. +More info on logging at [juju logs](https://juju.is/docs/olm/juju-logs). diff --git a/docs/tutorial/t-overview.md b/docs/tutorial/t-overview.md new file mode 100644 index 0000000..3ff414f --- /dev/null +++ b/docs/tutorial/t-overview.md @@ -0,0 +1,19 @@ +# Charmed MetalLB tutorial +The Charmed MetalLB Operator delivers automated operations management from day 0 to day 2 +on the [MetalLB Load Balancer Implementation for Bare Metal Kubernetes](https://metallb.universe.tf/). It is an open source, production-ready charm on top of [Juju](https://juju.is/) + +As a first step this tutorial shows you how to get Charmed MetalLB up and running, but the tutorial does not stop there. In this tutorial we will walk through how to: +- Set up an environment using Kubernetes and [Juju](https://juju.is/). +- Deploy MetalLB using a single command. +- Configure the IPAddressPool. + +While this tutorial intends to guide and teach you as you deploy Charmed MetalLB, it will be most beneficial if you already have a familiarity with: +- Basic terminal commands. +- MetalLB concepts. + +## Step-by-step guide + +Here’s an overview of the steps required with links to our separate tutorials that deal with each individual step: +* [Set up the environment](/t/charmed-metallb-tutorial-setup-environment/11360?channel=1.28/stable) +* [Deploy MetalLB](/t/charmed-metallb-tutorial-deploy-metallb/11361?channel=1.28/stable) +* [Configure MetalLB](/t/charmed-metallb-tutorial-configure/11362?channel=1.28/stable) diff --git a/docs/tutorial/t-setup-environment.md b/docs/tutorial/t-setup-environment.md new file mode 100644 index 0000000..dc345ee --- /dev/null +++ b/docs/tutorial/t-setup-environment.md @@ -0,0 +1,26 @@ +# Environment Setup +This is part of the [Charmed MetalLB Tutorial](/t/charmed-metalb-tutorial-overview/11359?channel=1.28/stable). Please refer to this page for more information and the overview of the content. + +## Minimum requirements +Before we start, make sure your Kubernetes cloud meeting the following requirements: +- Access to the internet for downloading the required charms and containers +- Access to a Kubernetes Cloud either + * [deployed via microk8s](https://juju.is/docs/juju/get-started-with-juju#heading--prepare-your-cloud) + * [deployed via charmed-kubernetes](https://ubuntu.com/kubernetes/docs/quickstart) + * or otherwise deployed + +## Represent the cloud in juju +Juju recognizes your MicroK8s cloud automatically. You can already see it if you run: +```shell +juju clouds +``` + +``` +Cloud Regions Default Type Credentials Source Description +microk8s 1 localhost k8s 1 built-in A Kubernetes Cluster +``` + +If it is not there, you may add the cloud with: +```shell +KUBECONFIG=path/to/kubeconfig/file juju add-k8s --client +``` diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..c1c4f2b --- /dev/null +++ b/icon.svg @@ -0,0 +1,68 @@ + + + + + + + MetalLB + + + + + + + MetalLB + + + + diff --git a/metadata.yaml b/metadata.yaml new file mode 100644 index 0000000..71f7ccb --- /dev/null +++ b/metadata.yaml @@ -0,0 +1,21 @@ +name: metallb +summary: | + This charm deploys MetalLB in a Kubernetes model, which provides a software + defined load balancer. +docs: https://discourse.charmhub.io/t/charmed-metallb-documentation/11357 +issues: https://bugs.launchpad.net/operator-metallb +source: https://github.com/charmed-kubernetes/metallb-operator +website: https://metallb.universe.tf/ +description: | + MetalLB offers a software network load balancing implementation that allows for + LoadBalancing services in Kubernetes. It is a young open-source project that could + be charmed to integrate it easily with the Canonical suite of projects. Upstream + documentation can be found here : https://metallb.universe.tf/. + The charm includes both the cluster-wide controller that handles IP address + assignments, along with the speaker DaemonSet that makes the services + reachable. +tags: + - kubernetes + - metallb +assumes: + - k8s-api diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3998e7e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +# Testing tools configuration +[tool.coverage.run] +branch = true + +[tool.coverage.report] +show_missing = true + +[tool.pytest.ini_options] +minversion = "6.0" +log_cli_level = "INFO" + +# Formatting tools configuration +[tool.black] +line-length = 99 +target-version = ["py38"] + +# Linting tools configuration +[tool.ruff] +line-length = 99 +select = ["E", "W", "F", "C", "N", "D", "I001"] +extend-ignore = [ + "D203", + "D204", + "D213", + "D215", + "D400", + "D404", + "D406", + "D407", + "D408", + "D409", + "D413", +] +ignore = ["E501", "D107"] +extend-exclude = ["__pycache__", "*.egg_info"] +per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} + +[tool.ruff.mccabe] +max-complexity = 10 + +[tool.codespell] +skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage" + +[tool.flake8] +ignore = ['D100', 'E501', 'D103', 'D107', 'D101', 'D102', 'W503'] + +[tool.isort] +line_length = 120 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4a34a66 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +ops >= 2.2.0 +lightkube>=0.10.1,<1.0.0 +pyyaml +ops.manifest>=1.1.0,<2.0.0 +tenacity \ No newline at end of file diff --git a/src/charm.py b/src/charm.py new file mode 100755 index 0000000..4c40289 --- /dev/null +++ b/src/charm.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. +# +# Learn more at: https://juju.is/docs/sdk + +import ipaddress +import logging + +import ops +from lightkube import Client +from lightkube.core.exceptions import ApiError +from lightkube.generic_resource import create_namespaced_resource +from ops import ActiveStatus, BlockedStatus, MaintenanceStatus, WaitingStatus +from ops.main import main +from ops.manifests import Collector, Manifests +from tenacity import before_log, retry, retry_if_exception_type, wait_exponential + +from metallb_manifests import MetallbNativeManifest + +# Log messages can be retrieved using juju debug-log +logger = logging.getLogger(__name__) + + +def _missing_resources(manifest: Manifests): + expected = manifest.resources + installed = manifest.installed_resources() + missing = expected - installed + return missing + + +def _is_ip_address(str_to_test): + try: + ipaddress.ip_address(str_to_test) + return True + except ValueError: + return False + + +def _is_ip_address_range(str_to_test): + addresses = str_to_test.split("-") + if len(addresses) != 2: + return False + + for address in addresses: + if not _is_ip_address(address): + return False + + return True + + +def _is_cidr(str_to_test): + try: + ipaddress.ip_network(str_to_test) + return True + except ValueError: + return False + + +def validate_iprange(iprange): + if not iprange: + return False, "iprange must not be empty" + + items = iprange.split(",") + for item in items: + is_ip_range = _is_ip_address_range(item) + is_cidr = _is_cidr(item) + if not is_ip_range and not is_cidr: + return False, f"{item} is not a valid CIDR or ip range" + + return True, "" + + +class MetallbCharm(ops.CharmBase): + """Charm the service.""" + + def __init__(self, *args): + super().__init__(*args) + if not self.unit.is_leader(): + self.unit.status = BlockedStatus("MetalLB charm cannot be scaled > n1.") + logger.error(f"{self} was initialized without leadership.") + return + + self.native_manifest = MetallbNativeManifest(self, self.config) + self.native_collector = Collector(self.native_manifest) + self.client = Client(namespace=self.model.name, field_manager=self.app.name) + self.l2_adv_name = self.pool_name = f"{self.model.name}-{self.app.name}" + + # Create generic lightkube resource class for the MetalLB IPAddressPool + # Create generic lightkube resource class for the MetalLB L2Advertisement + # https://metallb.universe.tf/configuration/ + self.IPAddressPool = create_namespaced_resource( + group="metallb.io", + version="v1beta1", + kind="IPAddressPool", + plural="ipaddresspools", + ) + + self.L2Advertisement = create_namespaced_resource( + group="metallb.io", + version="v1beta1", + kind="L2Advertisement", + plural="l2advertisements", + ) + + self.framework.observe(self.on.install, self._install_or_upgrade) + self.framework.observe(self.on.config_changed, self._on_config_changed) + self.framework.observe(self.on.upgrade_charm, self._install_or_upgrade) + self.framework.observe(self.on.update_status, self._update_status) + self.framework.observe(self.on.remove, self._cleanup) + + def _confirm_resource(self, resource, name) -> bool: + try: + self.client.get(resource, name=name, namespace=self.config["namespace"]) + except ApiError as e: + if "not found" in e.status.message: + logger.info(f"{resource.__name__} not found yet") + self.unit.status = WaitingStatus(f"Waiting for {resource.__name__} to be created") + return False + else: + # surface any other errors besides not found + logger.exception(e) + self.unit.status = WaitingStatus("Waiting for Kubernetes API") + return False + return True + + def _update_status(self, _): + missing = _missing_resources(self.native_manifest) + if len(missing) != 0: + logger.error(f"missing MetalLB resources: {missing}") + self.unit.status = BlockedStatus( + "missing \n".join(sorted(str(rsc) for rsc in missing)) + ) + return + + native_unready = self.native_collector.unready + if native_unready: + logger.warning(f"Unready MetalLB resources: {native_unready}") + self.unit.status = WaitingStatus(", ".join(native_unready)) + return + + if not self._confirm_resource(self.IPAddressPool, self.pool_name): + return + if not self._confirm_resource(self.L2Advertisement, self.l2_adv_name): + return + + self.unit.status = ActiveStatus("Ready") + self.unit.set_workload_version(self.native_collector.short_version) + self.app.status = ActiveStatus(self.native_collector.long_version) + + def _install_or_upgrade(self, event): + logger.info("Installing MetalLB native manifest resources ...") + self.native_manifest.apply_manifests() + logger.info("MetalLB native manifest has been installed") + + def _cleanup(self, event): + self.unit.status = MaintenanceStatus("Cleaning up MetalLB resources") + self.native_manifest.delete_manifests(ignore_unauthorized=True, ignore_not_found=True) + self.unit.status = MaintenanceStatus("Shutting down") + + def _on_config_changed(self, event): + logger.info("Updating MetalLB IPAddressPool to reflect charm configuration") + # strip all whitespace from string + stripped = "".join(self.config["iprange"].split()) + valid_iprange, msg = validate_iprange(stripped) + if not valid_iprange: + err_msg = f"Invalid iprange: {msg}" + logger.error(err_msg) + self.unit.status = BlockedStatus(err_msg) + return + + addresses = stripped.split(",") + self._update_ip_pool(addresses) + self._update_l2_adv() + self.unit.status = ActiveStatus() + + # retrying is necessary as the ip address pool webhooks take some time to come up + @retry( + retry=retry_if_exception_type(ApiError), + reraise=True, + before=before_log(logger, logging.DEBUG), + wait=wait_exponential(multiplier=1, min=2, max=60 * 2), + ) + def _update_ip_pool(self, addresses): + ip_pool = self.IPAddressPool( + metadata={"name": self.pool_name, "namespace": self.config["namespace"]}, + spec={"addresses": addresses}, + ) + + self.client.apply(ip_pool, force=True) + + def _update_l2_adv(self): + l2_adv = self.L2Advertisement( + metadata={"name": self.l2_adv_name, "namespace": self.config["namespace"]}, + spec={"ipAddressPools": [self.pool_name]}, + ) + + self.client.apply(l2_adv, force=True) + + +if __name__ == "__main__": # pragma: nocover + main(MetallbCharm) diff --git a/src/metallb_manifests.py b/src/metallb_manifests.py new file mode 100644 index 0000000..fde4d49 --- /dev/null +++ b/src/metallb_manifests.py @@ -0,0 +1,89 @@ +import logging +from typing import Dict + +from lightkube.codecs import AnyResource +from ops.manifests import ConfigRegistry, ManifestLabel, Manifests, Patch + +logger = logging.getLogger(__name__) + + +class PatchNamespace(Patch): + def __call__(self, obj: AnyResource): + ns_name = self.manifests.config["namespace"] + # Patch the namespace object itself + if obj.kind == "Namespace": + logger.info(f"Patching namespace name for {obj.kind} {obj.metadata.name} to {ns_name}") + obj.metadata.name = ns_name + return + + # Patch the addresspools CRD + if obj.metadata.name == "addresspools.metallb.io": + logger.info(f"Patching namespace for {obj.kind} {obj.metadata.name} to {ns_name}") + obj.spec.conversion.webhook.clientConfig.service.namespace = ns_name + return + + # Patch the bgppeers CRD + if obj.metadata.name == "bgppeers.metallb.io": + logger.info(f"Patching namespace for {obj.kind} {obj.metadata.name} to {ns_name}") + obj.spec.conversion.webhook.clientConfig.service.namespace = ns_name + return + + # patch ns in webhook configs + if ( + obj.kind == "ValidatingWebhookConfiguration" + or obj.kind == "MutatingWebhookConfiguration" + ): + for webhook in obj.webhooks: + logger.info( + f"Patching clientConfig service namespace for {obj.kind} {obj.metadata.name} to {ns_name}" + ) + webhook.clientConfig.service.namespace = ns_name + + # patch ns in RoleBinding (both ns and subjects ns) + if obj.kind == "RoleBinding": + logger.info(f"Patching namespace for {obj.kind} {obj.metadata.name} to {ns_name}") + obj.metadata.namespace = ns_name + for subject in obj.subjects: + logger.info( + f"Patching subject namespace for {subject.kind} {subject.name} to {ns_name}" + ) + subject.namespace = ns_name + return + + # patch ns in ClusterRoleBinding subjects + if obj.kind == "ClusterRoleBinding": + for subject in obj.subjects: + logger.info( + f"Patching subject namespace for {subject.kind} {subject.name} to {ns_name}" + ) + subject.namespace = ns_name + return + + # Patch any resources with a namespace in their metadata + if obj.metadata.namespace: + logger.info(f"Patching namespace for {obj.kind} {obj.metadata.name} to {ns_name}") + obj.metadata.namespace = ns_name + return + + +class MetallbNativeManifest(Manifests): + def __init__(self, charm, charm_config): + manipulations = [ + ManifestLabel(self), + ConfigRegistry(self), + PatchNamespace(self), + ] + + super().__init__("metallb", charm.model, "upstream/metallb-native", manipulations) + self.charm_config = charm_config + + @property + def config(self) -> Dict: + """Returns config mapped from charm config and joined relations.""" + config = dict(**self.charm_config) + for key, value in dict(**config).items(): + if value == "" or value is None: + del config[key] # blank out keys not currently set to something + + config["release"] = config.pop("metallb-release", None) + return config diff --git a/tests/data/microbot.yaml b/tests/data/microbot.yaml new file mode 100644 index 0000000..a539c95 --- /dev/null +++ b/tests/data/microbot.yaml @@ -0,0 +1,56 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: microbot +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + namespace: microbot + creationTimestamp: null + labels: + app: microbot-lb + name: microbot-lb +spec: + replicas: 3 + selector: + matchLabels: + app: microbot-lb + strategy: {} + template: + metadata: + creationTimestamp: null + labels: + app: microbot-lb + spec: + containers: + - image: rocks.canonical.com:443/cdk/cdkbot/microbot-amd64:latest + imagePullPolicy: "" + name: microbot-lb + ports: + - containerPort: 80 + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + timeoutSeconds: 30 + resources: {} + restartPolicy: Always + serviceAccountName: "" +status: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: microbot-lb + namespace: microbot +spec: + type: LoadBalancer + selector: + app: microbot-lb + ports: + - name: microbot-lb + protocol: TCP + port: 80 + targetPort: 80 \ No newline at end of file diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000..ae18e35 --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,62 @@ +import logging +from pathlib import Path + +import pytest +import pytest_asyncio +from lightkube import Client, codecs +from lightkube.generic_resource import create_namespaced_resource +from lightkube.resources.apps_v1 import Deployment +from lightkube.resources.core_v1 import Service + +logger = logging.getLogger(__name__) + + +def pytest_addoption(parser): + parser.addoption( + "--iprange", + action="store", + default="10.1.240.240-10.1.240.241", + help="Juju controller to use", + ) + + +@pytest.fixture +def iprange(request): + return request.config.getoption("--iprange") + + +@pytest.fixture(scope="module") +def client(): + return Client() + + +@pytest.fixture(scope="module") +def ip_address_pool(): + return create_namespaced_resource( + group="metallb.io", + version="v1beta1", + kind="IPAddressPool", + plural="ipaddresspools", + ) + + +@pytest_asyncio.fixture(scope="function") +async def microbot_service_ip(client): + logger.info("Creating microbot resources ...") + path = Path("tests/data/microbot.yaml") + for obj in codecs.load_all_yaml(path.read_text()): + if obj.kind == "Namespace": + namespace = obj.metadata.name + client.create(obj) + + client.wait(Deployment, "microbot-lb", for_conditions=["Available"], namespace=namespace) + logger.info("Microbot deployment is now available") + + svc = client.get(Service, name="microbot-lb", namespace="microbot") + ingress_ip = svc.status.loadBalancer.ingress[0].ip + + yield ingress_ip + + logger.info("Deleting microbot resources ...") + for obj in codecs.load_all_yaml(path.read_text()): + client.delete(type(obj), obj.metadata.name, namespace=obj.metadata.namespace) diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py new file mode 100644 index 0000000..028416f --- /dev/null +++ b/tests/integration/test_charm.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# Copyright 2023 Stone +# See LICENSE file for licensing details. +import asyncio +import logging +from pathlib import Path + +import aiohttp +import pytest +import yaml +from pytest_operator.plugin import OpsTest + +logger = logging.getLogger(__name__) + +METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) +APP_NAME = METADATA["name"] +NAMESPACE = "metallb-system-test" + + +@pytest.mark.abort_on_fail +async def test_build_and_deploy(ops_test: OpsTest): + """Build the charm-under-test and deploy it together with related charms. + + Assert on the unit status before any relations/configurations take place. + """ + # Build and deploy charm from local source folder + charm = await ops_test.build_charm(".") + + # Deploy the charm and wait for active/idle status + await asyncio.gather( + ops_test.model.deploy( + charm, application_name=APP_NAME, trust=True, config={"namespace": NAMESPACE} + ), + ops_test.model.wait_for_idle( + apps=[APP_NAME], status="active", raise_on_blocked=True, timeout=1500 + ), + ) + + +async def test_iprange_config_option(ops_test: OpsTest, client, ip_address_pool, iprange): + # test that default option is applied correctly before changing + pool_name = f"{ops_test.model_name}-{APP_NAME}" + pool = client.get(ip_address_pool, name=pool_name, namespace=NAMESPACE) + assert pool.spec["addresses"][0] == "192.168.1.240-192.168.1.247" + + app = ops_test.model.applications[APP_NAME] + logger.info("Updating iprange ...") + await app.set_config( + { + "iprange": iprange, + } + ) + await ops_test.model.wait_for_idle(status="active", timeout=60 * 10) + pool = client.get(ip_address_pool, name=pool_name, namespace=NAMESPACE) + assert pool.spec["addresses"][0] == iprange + + +async def test_loadbalancer_service(ops_test: OpsTest, client, microbot_service_ip): + logger.info("Testing microbot load balancer service") + timeout = aiohttp.ClientTimeout(connect=30) + async with aiohttp.request("GET", f"http://{microbot_service_ip}", timeout=timeout) as resp: + logger.info(f"response: {resp}") + assert resp.status == 200 diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py new file mode 100644 index 0000000..cac3069 --- /dev/null +++ b/tests/unit/conftest.py @@ -0,0 +1,19 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. +import unittest.mock as mock + +import pytest + + +# Autouse to prevent calling out to the k8s API via lightkube client in manifests +@pytest.fixture(autouse=True) +def lk_manifests_client(): + with mock.patch("ops.manifests.manifest.Client", autospec=True) as mock_lightkube: + yield mock_lightkube.return_value + + +# Autouse to prevent calling out to the k8s API via lightkube client in charm +@pytest.fixture(autouse=True) +def lk_charm_client(): + with mock.patch("charm.Client", autospec=True) as mock_lightkube: + yield mock_lightkube.return_value diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py new file mode 100644 index 0000000..823fe26 --- /dev/null +++ b/tests/unit/test_charm.py @@ -0,0 +1,217 @@ +# Copyright 2023 Stone +# See LICENSE file for licensing details. +# +# Learn more about testing at: https://juju.is/docs/sdk/testing + +import unittest.mock as mock + +import ops +import ops.testing +import pytest +import yaml +from lightkube.core.exceptions import ApiError +from ops import ActiveStatus, BlockedStatus, WaitingStatus +from ops.testing import Harness + +from charm import MetallbCharm +from metallb_manifests import MetallbNativeManifest + +ops.testing.SIMULATE_CAN_CONNECT = True + + +@pytest.fixture +def harness(): + harness = Harness(MetallbCharm) + try: + yield harness + finally: + harness.cleanup() + + +def test_not_leader(harness): + harness.begin() + assert harness.charm.model.unit.status == BlockedStatus("MetalLB charm cannot be scaled > n1.") + + +def test_install_applies_manifest_objects(harness, lk_manifests_client): + # Test that the install-handler applies the objects specified in the manifest + lk_manifests_client.reset_mock() + harness.set_leader(True) + harness.begin() + harness.charm.on.install.emit() + + version = harness.charm.config["metallb-release"] + file_name = f"upstream/metallb-native/manifests/{version}/metallb-native.yaml" + expected_objects = list(yaml.safe_load_all(open(file_name))) + + # the apply method is called for every object in the manifest + for call in lk_manifests_client.apply.call_args_list: + # The first (and only) argument to the apply method is the obj + call_obj = call.args[0].to_dict() + # look for this object in the manifest by name + # the manifest objects are manipulated slightly (image registries changed, labels added, etc) + # so it won't be an EXACT match, but each object should still be accounted for + found = False + for obj in expected_objects: + if obj["metadata"]["name"] == call_obj["metadata"]["name"]: + found = True + assert found + + +def test_config_change_updates_ip_pool(harness, lk_charm_client): + lk_charm_client.reset_mock() + + harness.set_leader(True) + harness.begin() + + # test with single ip range + harness.update_config({"iprange": "10.1.240.240-10.1.240.241"}) + assert len(lk_charm_client.apply.call_args_list) == 2 + apply_ip_pool_call = lk_charm_client.apply.call_args_list[0] + apply_l2_adv_call = lk_charm_client.apply.call_args_list[1] + + call_obj = apply_ip_pool_call.args[0].to_dict() + assert len(call_obj["spec"]["addresses"]) == 1 + assert call_obj["spec"]["addresses"][0] == "10.1.240.240-10.1.240.241" + + call_obj = apply_l2_adv_call.args[0].to_dict() + assert len(call_obj["spec"]["ipAddressPools"]) == 1 + assert call_obj["spec"]["ipAddressPools"][0] == "None-metallb" + + # test with multiple ranges + lk_charm_client.reset_mock() + harness.update_config( + { + "iprange": "192.168.1.240-192.168.1.247,10.1.240.240-10.1.240.241,192.168.10.0/24,fc00:f853:0ccd:e799::/124" + } + ) + assert len(lk_charm_client.apply.call_args_list) == 2 + apply_ip_pool_call = lk_charm_client.apply.call_args_list[0] + call_obj = apply_ip_pool_call.args[0].to_dict() + assert len(call_obj["spec"]["addresses"]) == 4 + assert call_obj["spec"]["addresses"][0] == "192.168.1.240-192.168.1.247" + assert call_obj["spec"]["addresses"][1] == "10.1.240.240-10.1.240.241" + assert call_obj["spec"]["addresses"][2] == "192.168.10.0/24" + assert call_obj["spec"]["addresses"][3] == "fc00:f853:0ccd:e799::/124" + + # test with multiple ranges with spaces thrown in + lk_charm_client.reset_mock() + harness.update_config( + { + "iprange": " 192. 168.1.240-192. 168.1.247, 10.1.240.240 -10.1.240.241, 192.168.10.0/24,fc00:f853:0ccd:e799::/124 " + } + ) + assert len(lk_charm_client.apply.call_args_list) == 2 + apply_ip_pool_call = lk_charm_client.apply.call_args_list[0] + call_obj = apply_ip_pool_call.args[0].to_dict() + assert len(call_obj["spec"]["addresses"]) == 4 + assert call_obj["spec"]["addresses"][0] == "192.168.1.240-192.168.1.247" + assert call_obj["spec"]["addresses"][1] == "10.1.240.240-10.1.240.241" + assert call_obj["spec"]["addresses"][2] == "192.168.10.0/24" + assert call_obj["spec"]["addresses"][3] == "fc00:f853:0ccd:e799::/124" + + # test with an empty range + lk_charm_client.reset_mock() + harness.update_config({"iprange": ""}) + assert harness.charm.model.unit.status == BlockedStatus( + "Invalid iprange: iprange must not be empty" + ) + + # test with an invalid range + lk_charm_client.reset_mock() + harness.update_config({"iprange": "256.256.256.256-256.256.256.256,10.1.240.240-10.1.240.241"}) + assert harness.charm.model.unit.status == BlockedStatus( + "Invalid iprange: 256.256.256.256-256.256.256.256 is not a valid CIDR or ip range" + ) + + # test with an invalid separator in range + lk_charm_client.reset_mock() + harness.update_config({"iprange": "10.1.240.240+10.1.240.241"}) + assert harness.charm.model.unit.status == BlockedStatus( + "Invalid iprange: 10.1.240.240+10.1.240.241 is not a valid CIDR or ip range" + ) + + # test with an invalid CIDR + lk_charm_client.reset_mock() + harness.update_config({"iprange": "256.256.256.256/24"}) + assert harness.charm.model.unit.status == BlockedStatus( + "Invalid iprange: 256.256.256.256/24 is not a valid CIDR or ip range" + ) + + +def test_remove_deletes_manifest_objects(harness, lk_manifests_client): + # Test that the remove-handler deletes the objects specified in the manifest + lk_manifests_client.reset_mock() + harness.set_leader(True) + harness.begin() + harness.charm.on.remove.emit() + + version = harness.charm.config["metallb-release"] + file_name = f"upstream/metallb-native/manifests/{version}/metallb-native.yaml" + actual_kind_name_list = [] + expected_objects = list(yaml.safe_load_all(open(file_name))) + expected_kind_name_list = [] + for obj in expected_objects: + kind_name = {"kind": obj["kind"], "name": obj["metadata"]["name"]} + expected_kind_name_list.append(kind_name) + + for call in lk_manifests_client.return_value.delete.call_args_list: + # The first argument is the resource class + # The second argument is the object name + kind_name = {"kind": call.args[0].__name__, "name": call.args[1]} + actual_kind_name_list.append(kind_name) + + +def test_update_status(harness, lk_manifests_client, lk_charm_client): + lk_manifests_client.reset_mock() + harness.set_leader(True) + harness.begin() + + # With nothing mocked, all resources will appear as missing + harness.charm.on.update_status.emit() + assert "missing" in harness.charm.model.unit.status.message + assert harness.charm.model.unit.status.name == "blocked" + + # mock to get past missing resources code path + with mock.patch("charm._missing_resources", autospec=True) as mock_missing: + mock_missing.return_value = [] + # Test path where some resources are not ready + with mock.patch.object(harness.charm, "native_collector") as mock_collector: + mock_collector.unready = [ + "some_name: some_obj is not Ready", + "other_name: other_obj is not Ready", + ] + harness.charm.on.update_status.emit() + assert harness.charm.model.unit.status == WaitingStatus( + "some_name: some_obj is not Ready, other_name: other_obj is not Ready" + ) + + # test path where APIError occurs during IP Address Pool lookup + # test path where ip address pool is not found + lk_charm_client.reset_mock() + api_error = ApiError(response=mock.MagicMock()) + api_error.status.message = "not found" + lk_charm_client.get.side_effect = api_error + harness.charm.on.update_status.emit() + assert harness.charm.model.unit.status == WaitingStatus( + "Waiting for IPAddressPool to be created" + ) + + # test path where some other API error occurs + api_error.status.message = "something else happened" + lk_charm_client.get.side_effect = api_error + harness.charm.on.update_status.emit() + assert harness.charm.model.unit.status == WaitingStatus("Waiting for Kubernetes API") + + # test ready path + lk_charm_client.get.side_effect = None + harness.charm.on.update_status.emit() + assert harness.charm.model.unit.status == ActiveStatus("Ready") + + +def test_empty_config_option_not_used_by_manifest(harness): + # Not super important, but can't get 100% coverage without it + harness.update_config({"iprange": ""}) + harness.begin() + manifest = MetallbNativeManifest(harness.charm, harness.charm.config) + assert "iprange" not in manifest.config diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..8b6a2c3 --- /dev/null +++ b/tox.ini @@ -0,0 +1,77 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. + +[tox] +skipsdist=True +skip_missing_interpreters = True +envlist = lint, unit + +[vars] +src_path = {toxinidir}/src/ +tst_path = {toxinidir}/tests/ +;lib_path = {toxinidir}/lib/charms/operator_name_with_underscores +all_path = {[vars]src_path} {[vars]tst_path} + +[testenv] +setenv = + PYTHONPATH = {toxinidir}:{toxinidir}/lib:{[vars]src_path} + PYTHONBREAKPOINT=pdb.set_trace + PY_COLORS=1 +passenv = + PYTHONPATH + CHARM_BUILD_DIR + MODEL_SETTINGS + +[testenv:format] +description = Apply coding style standards to code +deps = + black + isort +commands = + isort {[vars]all_path} + black {[vars]all_path} + +[testenv:lint] +description = Check code against coding style standards +deps = + black + flake8-docstrings + flake8-builtins + pyproject-flake8 + pep8-naming + isort + codespell +commands = + # uncomment the following line if this charm owns a lib + # codespell {[vars]lib_path} + codespell {toxinidir} --skip {toxinidir}/.git --skip {toxinidir}/.tox \ + --skip {toxinidir}/build --skip {toxinidir}/lib --skip {toxinidir}/venv \ + --skip {toxinidir}/.mypy_cache --skip {toxinidir}/icon.svg \ + --skip "*.yaml" + # pflake8 wrapper supports config from pyproject.toml + pflake8 {[vars]all_path} --classmethod-decorator=classmethod,validator + isort --check-only --diff {[vars]all_path} + black --check --diff {[vars]all_path} + +[testenv:unit] +description = Run unit tests +deps = + pytest + coverage[toml] + -r{toxinidir}/requirements.txt +commands = + coverage run --source={[vars]src_path} \ + -m pytest --ignore={[vars]tst_path}integration -vv --tb native -s {posargs} + coverage report + +[testenv:integration] +description = Run integration tests +deps = + aiohttp + pytest + juju + pytest-operator + pytest-asyncio + -r{toxinidir}/requirements.txt +commands = + pytest -v --tb native --asyncio-mode=auto --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} diff --git a/upstream/metallb-native/manifests/v0.13.10/metallb-native.yaml b/upstream/metallb-native/manifests/v0.13.10/metallb-native.yaml new file mode 100644 index 0000000..de6ca21 --- /dev/null +++ b/upstream/metallb-native/manifests/v0.13.10/metallb-native.yaml @@ -0,0 +1,2042 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/warn: privileged + name: metallb-system +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: addresspools.metallb.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlGWlRDQ0EwMmdBd0lCQWdJVU5GRW1XcTM3MVpKdGkrMmlSQzk1WmpBV1MxZ3dEUVlKS29aSWh2Y05BUUVMDQpCUUF3UWpFTE1Ba0dBMVVFQmhNQ1dGZ3hGVEFUQmdOVkJBY01ERVJsWm1GMWJIUWdRMmwwZVRFY01Cb0dBMVVFDQpDZ3dUUkdWbVlYVnNkQ0JEYjIxd1lXNTVJRXgwWkRBZUZ3MHlNakEzTVRrd09UTXlNek5hRncweU1qQTRNVGd3DQpPVE15TXpOYU1FSXhDekFKQmdOVkJBWVRBbGhZTVJVd0V3WURWUVFIREF4RVpXWmhkV3gwSUVOcGRIa3hIREFhDQpCZ05WQkFvTUUwUmxabUYxYkhRZ1EyOXRjR0Z1ZVNCTWRHUXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDDQpEd0F3Z2dJS0FvSUNBUUNxVFpxMWZRcC9vYkdlenhES0o3OVB3Ny94azJwellualNzMlkzb1ZYSm5sRmM4YjVlDQpma2ZZQnY2bndscW1keW5PL2phWFBaQmRQSS82aFdOUDBkdVhadEtWU0NCUUpyZzEyOGNXb3F0MGNTN3pLb1VpDQpvcU1tQ0QvRXVBeFFNZjhRZDF2c1gvVllkZ0poVTZBRXJLZEpIaXpFOUJtUkNkTDBGMW1OVW55Rk82UnRtWFZUDQpidkxsTDVYeTc2R0FaQVBLOFB4aVlDa0NtbDdxN0VnTWNiOXlLWldCYmlxQ3VkTXE5TGJLNmdKNzF6YkZnSXV4DQo1L1pXK2JraTB2RlplWk9ZODUxb1psckFUNzJvMDI4NHNTWW9uN0pHZVZkY3NoUnh5R1VpSFpSTzdkaXZVTDVTDQpmM2JmSDFYbWY1ZDQzT0NWTWRuUUV2NWVaOG8zeWVLa3ZrbkZQUGVJMU9BbjdGbDlFRVNNR2dhOGFaSG1URSttDQpsLzlMSmdDYjBnQmtPT0M0WnV4bWh2aERKV1EzWnJCS3pMQlNUZXN0NWlLNVlwcXRWVVk2THRyRW9FelVTK1lsDQpwWndXY2VQWHlHeHM5ZURsR3lNVmQraW15Y3NTU1UvVno2Mmx6MnZCS21NTXBkYldDQWhud0RsRTVqU2dyMjRRDQp0eGNXLys2N3d5KzhuQlI3UXdqVTFITndVRjBzeERWdEwrZ1NHVERnSEVZSlhZelYvT05zMy94TkpoVFNPSkxNDQpoeXNVdyttaGdackdhbUdXcHVIVU1DUitvTWJzMTc1UkcrQjJnUFFHVytPTjJnUTRyOXN2b0ZBNHBBQm8xd1dLDQpRYjRhY3pmeVVscElBOVFoSmFsZEY3S3dPSHVlV3gwRUNrNXg0T2tvVDBvWVp0dzFiR0JjRGtaSmF3SURBUUFCDQpvMU13VVRBZEJnTlZIUTRFRmdRVW90UlNIUm9IWTEyRFZ4R0NCdEhpb1g2ZmVFQXdId1lEVlIwakJCZ3dGb0FVDQpvdFJTSFJvSFkxMkRWeEdDQnRIaW9YNmZlRUF3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCDQpBUXNGQUFPQ0FnRUFSbkpsWWRjMTFHd0VxWnh6RDF2R3BDR2pDN2VWTlQ3aVY1d3IybXlybHdPYi9aUWFEa0xYDQpvVStaOVVXT1VlSXJTdzUydDdmQUpvVVAwSm5iYkMveVIrU1lqUGhvUXNiVHduOTc2ZldBWTduM3FMOXhCd1Y0DQphek41OXNjeUp0dlhMeUtOL2N5ak1ReDRLajBIMFg0bWJ6bzVZNUtzWWtYVU0vOEFPdWZMcEd0S1NGVGgrSEFDDQpab1Q5YnZHS25adnNHd0tYZFF0Wnh0akhaUjVqK3U3ZGtQOTJBT051RFNabS8rWVV4b2tBK09JbzdSR3BwSHNXDQo1ZTdNY0FTVXRtb1FORXd6dVFoVkJaRWQ1OGtKYjUrV0VWbGNzanlXNnRTbzErZ25tTWNqR1BsMWgxR2hVbjV4DQpFY0lWRnBIWXM5YWo1NmpBSjk1MVQvZjhMaWxmTlVnanBLQ0c1bnl0SUt3emxhOHNtdGlPdm1UNEpYbXBwSkI2DQo4bmdHRVluVjUrUTYwWFJ2OEhSSGp1VG9CRHVhaERrVDA2R1JGODU1d09FR2V4bkZpMXZYWUxLVllWb1V2MXRKDQo4dVdUR1pwNllDSVJldlBqbzg5ZytWTlJSaVFYUThJd0dybXE5c0RoVTlqTjA0SjdVL1RvRDFpNHE3VnlsRUc5DQorV1VGNkNLaEdBeTJIaEhwVncyTGFoOS9lUzdZMUZ1YURrWmhPZG1laG1BOCtqdHNZamJadnR5Mm1SWlF0UUZzDQpUU1VUUjREbUR2bVVPRVRmeStpRHdzK2RkWXVNTnJGeVVYV2dkMnpBQU4ydVl1UHFGY2pRcFNPODFzVTJTU3R3DQoxVzAyeUtYOGJEYmZFdjBzbUh3UzliQnFlSGo5NEM1Mjg0YXpsdTBmaUdpTm1OUEM4ckJLRmhBPQ0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + service: + name: webhook-service + namespace: metallb-system + path: /convert + conversionReviewVersions: + - v1alpha1 + - v1beta1 + group: metallb.io + names: + kind: AddressPool + listKind: AddressPoolList + plural: addresspools + singular: addresspool + scope: Namespaced + versions: + - deprecated: true + deprecationWarning: metallb.io v1alpha1 AddressPool is deprecated + name: v1alpha1 + schema: + openAPIV3Schema: + description: AddressPool is the Schema for the addresspools API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AddressPoolSpec defines the desired state of AddressPool. + properties: + addresses: + description: A list of IP address ranges over which MetalLB has authority. + You can list multiple ranges in a single pool, they will all share + the same settings. Each range can be either a CIDR prefix, or an + explicit start-end range of IPs. + items: + type: string + type: array + autoAssign: + default: true + description: AutoAssign flag used to prevent MetallB from automatic + allocation for a pool. + type: boolean + bgpAdvertisements: + description: When an IP is allocated from this pool, how should it + be translated into BGP announcements? + items: + properties: + aggregationLength: + default: 32 + description: The aggregation-length advertisement option lets + you “roll up” the /32s into a larger prefix. + format: int32 + minimum: 1 + type: integer + aggregationLengthV6: + default: 128 + description: Optional, defaults to 128 (i.e. no aggregation) + if not specified. + format: int32 + type: integer + communities: + description: BGP communities + items: + type: string + type: array + localPref: + description: BGP LOCAL_PREF attribute which is used by BGP best + path algorithm, Path with higher localpref is preferred over + one with lower localpref. + format: int32 + type: integer + type: object + type: array + protocol: + description: Protocol can be used to select how the announcement is + done. + enum: + - layer2 + - bgp + type: string + required: + - addresses + - protocol + type: object + status: + description: AddressPoolStatus defines the observed state of AddressPool. + type: object + required: + - spec + type: object + served: true + storage: false + subresources: + status: {} + - deprecated: true + deprecationWarning: metallb.io v1beta1 AddressPool is deprecated, consider using + IPAddressPool + name: v1beta1 + schema: + openAPIV3Schema: + description: AddressPool represents a pool of IP addresses that can be allocated + to LoadBalancer services. AddressPool is deprecated and being replaced by + IPAddressPool. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: AddressPoolSpec defines the desired state of AddressPool. + properties: + addresses: + description: A list of IP address ranges over which MetalLB has authority. + You can list multiple ranges in a single pool, they will all share + the same settings. Each range can be either a CIDR prefix, or an + explicit start-end range of IPs. + items: + type: string + type: array + autoAssign: + default: true + description: AutoAssign flag used to prevent MetallB from automatic + allocation for a pool. + type: boolean + bgpAdvertisements: + description: Drives how an IP allocated from this pool should translated + into BGP announcements. + items: + properties: + aggregationLength: + default: 32 + description: The aggregation-length advertisement option lets + you “roll up” the /32s into a larger prefix. + format: int32 + minimum: 1 + type: integer + aggregationLengthV6: + default: 128 + description: Optional, defaults to 128 (i.e. no aggregation) + if not specified. + format: int32 + type: integer + communities: + description: BGP communities to be associated with the given + advertisement. + items: + type: string + type: array + localPref: + description: BGP LOCAL_PREF attribute which is used by BGP best + path algorithm, Path with higher localpref is preferred over + one with lower localpref. + format: int32 + type: integer + type: object + type: array + protocol: + description: Protocol can be used to select how the announcement is + done. + enum: + - layer2 + - bgp + type: string + required: + - addresses + - protocol + type: object + status: + description: AddressPoolStatus defines the observed state of AddressPool. + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: bfdprofiles.metallb.io +spec: + group: metallb.io + names: + kind: BFDProfile + listKind: BFDProfileList + plural: bfdprofiles + singular: bfdprofile + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.passiveMode + name: Passive Mode + type: boolean + - jsonPath: .spec.transmitInterval + name: Transmit Interval + type: integer + - jsonPath: .spec.receiveInterval + name: Receive Interval + type: integer + - jsonPath: .spec.detectMultiplier + name: Multiplier + type: integer + name: v1beta1 + schema: + openAPIV3Schema: + description: BFDProfile represents the settings of the bfd session that can + be optionally associated with a BGP session. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BFDProfileSpec defines the desired state of BFDProfile. + properties: + detectMultiplier: + description: Configures the detection multiplier to determine packet + loss. The remote transmission interval will be multiplied by this + value to determine the connection loss detection timer. + format: int32 + maximum: 255 + minimum: 2 + type: integer + echoInterval: + description: Configures the minimal echo receive transmission interval + that this system is capable of handling in milliseconds. Defaults + to 50ms + format: int32 + maximum: 60000 + minimum: 10 + type: integer + echoMode: + description: Enables or disables the echo transmission mode. This + mode is disabled by default, and not supported on multi hops setups. + type: boolean + minimumTtl: + description: 'For multi hop sessions only: configure the minimum expected + TTL for an incoming BFD control packet.' + format: int32 + maximum: 254 + minimum: 1 + type: integer + passiveMode: + description: 'Mark session as passive: a passive session will not + attempt to start the connection and will wait for control packets + from peer before it begins replying.' + type: boolean + receiveInterval: + description: The minimum interval that this system is capable of receiving + control packets in milliseconds. Defaults to 300ms. + format: int32 + maximum: 60000 + minimum: 10 + type: integer + transmitInterval: + description: The minimum transmission interval (less jitter) that + this system wants to use to send BFD control packets in milliseconds. + Defaults to 300ms + format: int32 + maximum: 60000 + minimum: 10 + type: integer + type: object + status: + description: BFDProfileStatus defines the observed state of BFDProfile. + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: bgpadvertisements.metallb.io +spec: + group: metallb.io + names: + kind: BGPAdvertisement + listKind: BGPAdvertisementList + plural: bgpadvertisements + singular: bgpadvertisement + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.ipAddressPools + name: IPAddressPools + type: string + - jsonPath: .spec.ipAddressPoolSelectors + name: IPAddressPool Selectors + type: string + - jsonPath: .spec.peers + name: Peers + type: string + - jsonPath: .spec.nodeSelectors + name: Node Selectors + priority: 10 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: BGPAdvertisement allows to advertise the IPs coming from the + selected IPAddressPools via BGP, setting the parameters of the BGP Advertisement. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BGPAdvertisementSpec defines the desired state of BGPAdvertisement. + properties: + aggregationLength: + default: 32 + description: The aggregation-length advertisement option lets you + “roll up” the /32s into a larger prefix. Defaults to 32. Works for + IPv4 addresses. + format: int32 + minimum: 1 + type: integer + aggregationLengthV6: + default: 128 + description: The aggregation-length advertisement option lets you + “roll up” the /128s into a larger prefix. Defaults to 128. Works + for IPv6 addresses. + format: int32 + type: integer + communities: + description: The BGP communities to be associated with the announcement. + Each item can be a standard community of the form 1234:1234, a large + community of the form large:1234:1234:1234 or the name of an alias + defined in the Community CRD. + items: + type: string + type: array + ipAddressPoolSelectors: + description: A selector for the IPAddressPools which would get advertised + via this advertisement. If no IPAddressPool is selected by this + or by the list, the advertisement is applied to all the IPAddressPools. + items: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: array + ipAddressPools: + description: The list of IPAddressPools to advertise via this advertisement, + selected by name. + items: + type: string + type: array + localPref: + description: The BGP LOCAL_PREF attribute which is used by BGP best + path algorithm, Path with higher localpref is preferred over one + with lower localpref. + format: int32 + type: integer + nodeSelectors: + description: NodeSelectors allows to limit the nodes to announce as + next hops for the LoadBalancer IP. When empty, all the nodes having are + announced as next hops. + items: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: array + peers: + description: Peers limits the bgppeer to advertise the ips of the + selected pools to. When empty, the loadbalancer IP is announced + to all the BGPPeers configured. + items: + type: string + type: array + type: object + status: + description: BGPAdvertisementStatus defines the observed state of BGPAdvertisement. + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: bgppeers.metallb.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlGWlRDQ0EwMmdBd0lCQWdJVU5GRW1XcTM3MVpKdGkrMmlSQzk1WmpBV1MxZ3dEUVlKS29aSWh2Y05BUUVMDQpCUUF3UWpFTE1Ba0dBMVVFQmhNQ1dGZ3hGVEFUQmdOVkJBY01ERVJsWm1GMWJIUWdRMmwwZVRFY01Cb0dBMVVFDQpDZ3dUUkdWbVlYVnNkQ0JEYjIxd1lXNTVJRXgwWkRBZUZ3MHlNakEzTVRrd09UTXlNek5hRncweU1qQTRNVGd3DQpPVE15TXpOYU1FSXhDekFKQmdOVkJBWVRBbGhZTVJVd0V3WURWUVFIREF4RVpXWmhkV3gwSUVOcGRIa3hIREFhDQpCZ05WQkFvTUUwUmxabUYxYkhRZ1EyOXRjR0Z1ZVNCTWRHUXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDDQpEd0F3Z2dJS0FvSUNBUUNxVFpxMWZRcC9vYkdlenhES0o3OVB3Ny94azJwellualNzMlkzb1ZYSm5sRmM4YjVlDQpma2ZZQnY2bndscW1keW5PL2phWFBaQmRQSS82aFdOUDBkdVhadEtWU0NCUUpyZzEyOGNXb3F0MGNTN3pLb1VpDQpvcU1tQ0QvRXVBeFFNZjhRZDF2c1gvVllkZ0poVTZBRXJLZEpIaXpFOUJtUkNkTDBGMW1OVW55Rk82UnRtWFZUDQpidkxsTDVYeTc2R0FaQVBLOFB4aVlDa0NtbDdxN0VnTWNiOXlLWldCYmlxQ3VkTXE5TGJLNmdKNzF6YkZnSXV4DQo1L1pXK2JraTB2RlplWk9ZODUxb1psckFUNzJvMDI4NHNTWW9uN0pHZVZkY3NoUnh5R1VpSFpSTzdkaXZVTDVTDQpmM2JmSDFYbWY1ZDQzT0NWTWRuUUV2NWVaOG8zeWVLa3ZrbkZQUGVJMU9BbjdGbDlFRVNNR2dhOGFaSG1URSttDQpsLzlMSmdDYjBnQmtPT0M0WnV4bWh2aERKV1EzWnJCS3pMQlNUZXN0NWlLNVlwcXRWVVk2THRyRW9FelVTK1lsDQpwWndXY2VQWHlHeHM5ZURsR3lNVmQraW15Y3NTU1UvVno2Mmx6MnZCS21NTXBkYldDQWhud0RsRTVqU2dyMjRRDQp0eGNXLys2N3d5KzhuQlI3UXdqVTFITndVRjBzeERWdEwrZ1NHVERnSEVZSlhZelYvT05zMy94TkpoVFNPSkxNDQpoeXNVdyttaGdackdhbUdXcHVIVU1DUitvTWJzMTc1UkcrQjJnUFFHVytPTjJnUTRyOXN2b0ZBNHBBQm8xd1dLDQpRYjRhY3pmeVVscElBOVFoSmFsZEY3S3dPSHVlV3gwRUNrNXg0T2tvVDBvWVp0dzFiR0JjRGtaSmF3SURBUUFCDQpvMU13VVRBZEJnTlZIUTRFRmdRVW90UlNIUm9IWTEyRFZ4R0NCdEhpb1g2ZmVFQXdId1lEVlIwakJCZ3dGb0FVDQpvdFJTSFJvSFkxMkRWeEdDQnRIaW9YNmZlRUF3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFOQmdrcWhraUc5dzBCDQpBUXNGQUFPQ0FnRUFSbkpsWWRjMTFHd0VxWnh6RDF2R3BDR2pDN2VWTlQ3aVY1d3IybXlybHdPYi9aUWFEa0xYDQpvVStaOVVXT1VlSXJTdzUydDdmQUpvVVAwSm5iYkMveVIrU1lqUGhvUXNiVHduOTc2ZldBWTduM3FMOXhCd1Y0DQphek41OXNjeUp0dlhMeUtOL2N5ak1ReDRLajBIMFg0bWJ6bzVZNUtzWWtYVU0vOEFPdWZMcEd0S1NGVGgrSEFDDQpab1Q5YnZHS25adnNHd0tYZFF0Wnh0akhaUjVqK3U3ZGtQOTJBT051RFNabS8rWVV4b2tBK09JbzdSR3BwSHNXDQo1ZTdNY0FTVXRtb1FORXd6dVFoVkJaRWQ1OGtKYjUrV0VWbGNzanlXNnRTbzErZ25tTWNqR1BsMWgxR2hVbjV4DQpFY0lWRnBIWXM5YWo1NmpBSjk1MVQvZjhMaWxmTlVnanBLQ0c1bnl0SUt3emxhOHNtdGlPdm1UNEpYbXBwSkI2DQo4bmdHRVluVjUrUTYwWFJ2OEhSSGp1VG9CRHVhaERrVDA2R1JGODU1d09FR2V4bkZpMXZYWUxLVllWb1V2MXRKDQo4dVdUR1pwNllDSVJldlBqbzg5ZytWTlJSaVFYUThJd0dybXE5c0RoVTlqTjA0SjdVL1RvRDFpNHE3VnlsRUc5DQorV1VGNkNLaEdBeTJIaEhwVncyTGFoOS9lUzdZMUZ1YURrWmhPZG1laG1BOCtqdHNZamJadnR5Mm1SWlF0UUZzDQpUU1VUUjREbUR2bVVPRVRmeStpRHdzK2RkWXVNTnJGeVVYV2dkMnpBQU4ydVl1UHFGY2pRcFNPODFzVTJTU3R3DQoxVzAyeUtYOGJEYmZFdjBzbUh3UzliQnFlSGo5NEM1Mjg0YXpsdTBmaUdpTm1OUEM4ckJLRmhBPQ0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQ== + service: + name: webhook-service + namespace: metallb-system + path: /convert + conversionReviewVersions: + - v1beta1 + - v1beta2 + group: metallb.io + names: + kind: BGPPeer + listKind: BGPPeerList + plural: bgppeers + singular: bgppeer + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.peerAddress + name: Address + type: string + - jsonPath: .spec.peerASN + name: ASN + type: string + - jsonPath: .spec.bfdProfile + name: BFD Profile + type: string + - jsonPath: .spec.ebgpMultiHop + name: Multi Hops + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: BGPPeer is the Schema for the peers API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BGPPeerSpec defines the desired state of Peer. + properties: + bfdProfile: + type: string + ebgpMultiHop: + description: EBGP peer is multi-hops away + type: boolean + holdTime: + description: Requested BGP hold time, per RFC4271. + type: string + keepaliveTime: + description: Requested BGP keepalive time, per RFC4271. + type: string + myASN: + description: AS number to use for the local end of the session. + format: int32 + maximum: 4294967295 + minimum: 0 + type: integer + nodeSelectors: + description: Only connect to this peer on nodes that match one of + these selectors. + items: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + minItems: 1 + type: array + required: + - key + - operator + - values + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: array + password: + description: Authentication password for routers enforcing TCP MD5 + authenticated sessions + type: string + peerASN: + description: AS number to expect from the remote end of the session. + format: int32 + maximum: 4294967295 + minimum: 0 + type: integer + peerAddress: + description: Address to dial when establishing the session. + type: string + peerPort: + description: Port to dial when establishing the session. + maximum: 16384 + minimum: 0 + type: integer + routerID: + description: BGP router ID to advertise to the peer + type: string + sourceAddress: + description: Source address to use when establishing the session. + type: string + required: + - myASN + - peerASN + - peerAddress + type: object + status: + description: BGPPeerStatus defines the observed state of Peer. + type: object + type: object + served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - jsonPath: .spec.peerAddress + name: Address + type: string + - jsonPath: .spec.peerASN + name: ASN + type: string + - jsonPath: .spec.bfdProfile + name: BFD Profile + type: string + - jsonPath: .spec.ebgpMultiHop + name: Multi Hops + type: string + name: v1beta2 + schema: + openAPIV3Schema: + description: BGPPeer is the Schema for the peers API. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: BGPPeerSpec defines the desired state of Peer. + properties: + bfdProfile: + description: The name of the BFD Profile to be used for the BFD session + associated to the BGP session. If not set, the BFD session won't + be set up. + type: string + ebgpMultiHop: + description: To set if the BGPPeer is multi-hops away. Needed for + FRR mode only. + type: boolean + holdTime: + description: Requested BGP hold time, per RFC4271. + type: string + keepaliveTime: + description: Requested BGP keepalive time, per RFC4271. + type: string + myASN: + description: AS number to use for the local end of the session. + format: int32 + maximum: 4294967295 + minimum: 0 + type: integer + nodeSelectors: + description: Only connect to this peer on nodes that match one of + these selectors. + items: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: array + password: + description: Authentication password for routers enforcing TCP MD5 + authenticated sessions + type: string + passwordSecret: + description: passwordSecret is name of the authentication secret for + BGP Peer. the secret must be of type "kubernetes.io/basic-auth", + and created in the same namespace as the MetalLB deployment. The + password is stored in the secret as the key "password". + properties: + name: + description: name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: namespace defines the space within which the secret + name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + peerASN: + description: AS number to expect from the remote end of the session. + format: int32 + maximum: 4294967295 + minimum: 0 + type: integer + peerAddress: + description: Address to dial when establishing the session. + type: string + peerPort: + default: 179 + description: Port to dial when establishing the session. + maximum: 16384 + minimum: 0 + type: integer + routerID: + description: BGP router ID to advertise to the peer + type: string + sourceAddress: + description: Source address to use when establishing the session. + type: string + vrf: + description: To set if we want to peer with the BGPPeer using an interface + belonging to a host vrf + type: string + required: + - myASN + - peerASN + - peerAddress + type: object + status: + description: BGPPeerStatus defines the observed state of Peer. + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: communities.metallb.io +spec: + group: metallb.io + names: + kind: Community + listKind: CommunityList + plural: communities + singular: community + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: Community is a collection of aliases for communities. Users can + define named aliases to be used in the BGPPeer CRD. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CommunitySpec defines the desired state of Community. + properties: + communities: + items: + properties: + name: + description: The name of the alias for the community. + type: string + value: + description: The BGP community value corresponding to the given + name. Can be a standard community of the form 1234:1234 or + a large community of the form large:1234:1234:1234. + type: string + type: object + type: array + type: object + status: + description: CommunityStatus defines the observed state of Community. + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: ipaddresspools.metallb.io +spec: + group: metallb.io + names: + kind: IPAddressPool + listKind: IPAddressPoolList + plural: ipaddresspools + singular: ipaddresspool + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.autoAssign + name: Auto Assign + type: boolean + - jsonPath: .spec.avoidBuggyIPs + name: Avoid Buggy IPs + type: boolean + - jsonPath: .spec.addresses + name: Addresses + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: IPAddressPool represents a pool of IP addresses that can be allocated + to LoadBalancer services. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IPAddressPoolSpec defines the desired state of IPAddressPool. + properties: + addresses: + description: A list of IP address ranges over which MetalLB has authority. + You can list multiple ranges in a single pool, they will all share + the same settings. Each range can be either a CIDR prefix, or an + explicit start-end range of IPs. + items: + type: string + type: array + autoAssign: + default: true + description: AutoAssign flag used to prevent MetallB from automatic + allocation for a pool. + type: boolean + avoidBuggyIPs: + default: false + description: AvoidBuggyIPs prevents addresses ending with .0 and .255 + to be used by a pool. + type: boolean + serviceAllocation: + description: AllocateTo makes ip pool allocation to specific namespace + and/or service. The controller will use the pool with lowest value + of priority in case of multiple matches. A pool with no priority + set will be used only if the pools with priority can't be used. + If multiple matching IPAddressPools are available it will check + for the availability of IPs sorting the matching IPAddressPools + by priority, starting from the highest to the lowest. If multiple + IPAddressPools have the same priority, choice will be random. + properties: + namespaceSelectors: + description: NamespaceSelectors list of label selectors to select + namespace(s) for ip pool, an alternative to using namespace + list. + items: + description: A label selector is a label query over a set of + resources. The result of matchLabels and matchExpressions + are ANDed. An empty label selector matches all objects. A + null label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. This + array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: array + namespaces: + description: Namespaces list of namespace(s) on which ip pool + can be attached. + items: + type: string + type: array + priority: + description: Priority priority given for ip pool while ip allocation + on a service. + type: integer + serviceSelectors: + description: ServiceSelectors list of label selector to select + service(s) for which ip pool can be used for ip allocation. + items: + description: A label selector is a label query over a set of + resources. The result of matchLabels and matchExpressions + are ANDed. An empty label selector matches all objects. A + null label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. + If the operator is In or NotIn, the values array + must be non-empty. If the operator is Exists or + DoesNotExist, the values array must be empty. This + array is replaced during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. + A single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is + "key", the operator is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: array + type: object + required: + - addresses + type: object + status: + description: IPAddressPoolStatus defines the observed state of IPAddressPool. + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.11.1 + creationTimestamp: null + name: l2advertisements.metallb.io +spec: + group: metallb.io + names: + kind: L2Advertisement + listKind: L2AdvertisementList + plural: l2advertisements + singular: l2advertisement + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.ipAddressPools + name: IPAddressPools + type: string + - jsonPath: .spec.ipAddressPoolSelectors + name: IPAddressPool Selectors + type: string + - jsonPath: .spec.interfaces + name: Interfaces + type: string + - jsonPath: .spec.nodeSelectors + name: Node Selectors + priority: 10 + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: L2Advertisement allows to advertise the LoadBalancer IPs provided + by the selected pools via L2. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: L2AdvertisementSpec defines the desired state of L2Advertisement. + properties: + interfaces: + description: A list of interfaces to announce from. The LB IP will + be announced only from these interfaces. If the field is not set, + we advertise from all the interfaces on the host. + items: + type: string + type: array + ipAddressPoolSelectors: + description: A selector for the IPAddressPools which would get advertised + via this advertisement. If no IPAddressPool is selected by this + or by the list, the advertisement is applied to all the IPAddressPools. + items: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: array + ipAddressPools: + description: The list of IPAddressPools to advertise via this advertisement, + selected by name. + items: + type: string + type: array + nodeSelectors: + description: NodeSelectors allows to limit the nodes to announce as + next hops for the LoadBalancer IP. When empty, all the nodes having are + announced as next hops. + items: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: array + type: object + status: + description: L2AdvertisementStatus defines the observed state of L2Advertisement. + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: metallb + name: controller + namespace: metallb-system +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: metallb + name: speaker + namespace: metallb-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app: metallb + name: controller + namespace: metallb-system +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resourceNames: + - memberlist + resources: + - secrets + verbs: + - list +- apiGroups: + - apps + resourceNames: + - controller + resources: + - deployments + verbs: + - get +- apiGroups: + - metallb.io + resources: + - bgppeers + verbs: + - get + - list +- apiGroups: + - metallb.io + resources: + - addresspools + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - bfdprofiles + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - ipaddresspools + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - bgpadvertisements + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - l2advertisements + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - communities + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app: metallb + name: pod-lister + namespace: metallb-system +rules: +- apiGroups: + - "" + resources: + - pods + verbs: + - list +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - addresspools + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - bfdprofiles + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - bgppeers + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - l2advertisements + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - bgpadvertisements + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - ipaddresspools + verbs: + - get + - list + - watch +- apiGroups: + - metallb.io + resources: + - communities + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app: metallb + name: metallb-system:controller +rules: +- apiGroups: + - "" + resources: + - services + - namespaces + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - list +- apiGroups: + - "" + resources: + - services/status + verbs: + - update +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - policy + resourceNames: + - controller + resources: + - podsecuritypolicies + verbs: + - use +- apiGroups: + - admissionregistration.k8s.io + resourceNames: + - metallb-webhook-configuration + resources: + - validatingwebhookconfigurations + - mutatingwebhookconfigurations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - admissionregistration.k8s.io + resources: + - validatingwebhookconfigurations + - mutatingwebhookconfigurations + verbs: + - list + - watch +- apiGroups: + - apiextensions.k8s.io + resourceNames: + - addresspools.metallb.io + - bfdprofiles.metallb.io + - bgpadvertisements.metallb.io + - bgppeers.metallb.io + - ipaddresspools.metallb.io + - l2advertisements.metallb.io + - communities.metallb.io + resources: + - customresourcedefinitions + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app: metallb + name: metallb-system:speaker +rules: +- apiGroups: + - "" + resources: + - services + - endpoints + - nodes + - namespaces + verbs: + - get + - list + - watch +- apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - policy + resourceNames: + - speaker + resources: + - podsecuritypolicies + verbs: + - use +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app: metallb + name: controller + namespace: metallb-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: controller +subjects: +- kind: ServiceAccount + name: controller + namespace: metallb-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app: metallb + name: pod-lister + namespace: metallb-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: pod-lister +subjects: +- kind: ServiceAccount + name: speaker + namespace: metallb-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app: metallb + name: metallb-system:controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: metallb-system:controller +subjects: +- kind: ServiceAccount + name: controller + namespace: metallb-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app: metallb + name: metallb-system:speaker +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: metallb-system:speaker +subjects: +- kind: ServiceAccount + name: speaker + namespace: metallb-system +--- +apiVersion: v1 +data: + excludel2.yaml: | + announcedInterfacesToExclude: ["docker.*", "cbr.*", "dummy.*", "virbr.*", "lxcbr.*", "veth.*", "lo", "^cali.*", "^tunl.*", "flannel.*", "kube-ipvs.*", "cni.*", "^nodelocaldns.*"] +kind: ConfigMap +metadata: + name: metallb-excludel2 + namespace: metallb-system +--- +apiVersion: v1 +kind: Secret +metadata: + name: webhook-server-cert + namespace: metallb-system +--- +apiVersion: v1 +kind: Service +metadata: + name: webhook-service + namespace: metallb-system +spec: + ports: + - port: 443 + targetPort: 9443 + selector: + component: controller +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: metallb + component: controller + name: controller + namespace: metallb-system +spec: + revisionHistoryLimit: 3 + selector: + matchLabels: + app: metallb + component: controller + template: + metadata: + annotations: + prometheus.io/port: "7472" + prometheus.io/scrape: "true" + labels: + app: metallb + component: controller + spec: + containers: + - args: + - --port=7472 + - --log-level=info + env: + - name: METALLB_ML_SECRET_NAME + value: memberlist + - name: METALLB_DEPLOYMENT + value: controller + image: quay.io/metallb/controller:v0.13.10 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /metrics + port: monitoring + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: controller + ports: + - containerPort: 7472 + name: monitoring + - containerPort: 9443 + name: webhook-server + protocol: TCP + readinessProbe: + failureThreshold: 3 + httpGet: + path: /metrics + port: monitoring + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + nodeSelector: + kubernetes.io/os: linux + securityContext: + fsGroup: 65534 + runAsNonRoot: true + runAsUser: 65534 + serviceAccountName: controller + terminationGracePeriodSeconds: 0 + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-cert +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + app: metallb + component: speaker + name: speaker + namespace: metallb-system +spec: + selector: + matchLabels: + app: metallb + component: speaker + template: + metadata: + annotations: + prometheus.io/port: "7472" + prometheus.io/scrape: "true" + labels: + app: metallb + component: speaker + spec: + containers: + - args: + - --port=7472 + - --log-level=info + env: + - name: METALLB_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: METALLB_HOST + valueFrom: + fieldRef: + fieldPath: status.hostIP + - name: METALLB_ML_BIND_ADDR + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: METALLB_ML_LABELS + value: app=metallb,component=speaker + - name: METALLB_ML_SECRET_KEY_PATH + value: /etc/ml_secret_key + image: quay.io/metallb/speaker:v0.13.10 + livenessProbe: + failureThreshold: 3 + httpGet: + path: /metrics + port: monitoring + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: speaker + ports: + - containerPort: 7472 + name: monitoring + - containerPort: 7946 + name: memberlist-tcp + - containerPort: 7946 + name: memberlist-udp + protocol: UDP + readinessProbe: + failureThreshold: 3 + httpGet: + path: /metrics + port: monitoring + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - NET_RAW + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /etc/ml_secret_key + name: memberlist + readOnly: true + - mountPath: /etc/metallb + name: metallb-excludel2 + readOnly: true + hostNetwork: true + nodeSelector: + kubernetes.io/os: linux + serviceAccountName: speaker + terminationGracePeriodSeconds: 2 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/master + operator: Exists + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + operator: Exists + volumes: + - name: memberlist + secret: + defaultMode: 420 + secretName: memberlist + - configMap: + defaultMode: 256 + name: metallb-excludel2 + name: metallb-excludel2 +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: metallb-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: metallb-system + path: /validate-metallb-io-v1beta2-bgppeer + failurePolicy: Fail + name: bgppeersvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta2 + operations: + - CREATE + - UPDATE + resources: + - bgppeers + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: metallb-system + path: /validate-metallb-io-v1beta1-addresspool + failurePolicy: Fail + name: addresspoolvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - addresspools + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: metallb-system + path: /validate-metallb-io-v1beta1-bfdprofile + failurePolicy: Fail + name: bfdprofilevalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - DELETE + resources: + - bfdprofiles + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: metallb-system + path: /validate-metallb-io-v1beta1-bgpadvertisement + failurePolicy: Fail + name: bgpadvertisementvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - bgpadvertisements + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: metallb-system + path: /validate-metallb-io-v1beta1-community + failurePolicy: Fail + name: communityvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - communities + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: metallb-system + path: /validate-metallb-io-v1beta1-ipaddresspool + failurePolicy: Fail + name: ipaddresspoolvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - ipaddresspools + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: metallb-system + path: /validate-metallb-io-v1beta1-l2advertisement + failurePolicy: Fail + name: l2advertisementvalidationwebhook.metallb.io + rules: + - apiGroups: + - metallb.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - l2advertisements + sideEffects: None diff --git a/upstream/metallb-native/version b/upstream/metallb-native/version new file mode 100644 index 0000000..11e6abc --- /dev/null +++ b/upstream/metallb-native/version @@ -0,0 +1 @@ +v0.13.10 \ No newline at end of file