├── .github └── workflows │ ├── merge.yaml │ ├── pull-request.yaml │ └── release.yml ├── .gitignore ├── Dockerfile.controller ├── Dockerfile.node ├── LICENSE.txt ├── Makefile ├── PROJECT ├── README.md ├── api └── v1alpha1 │ ├── groupversion_info.go │ ├── networkinterface_types.go │ ├── privatenetwork_types.go │ └── zz_generated.deepcopy.go ├── cmd ├── controller │ └── controller.go └── node │ └── node.go ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── controller │ ├── controller.yaml │ └── kustomization.yaml ├── crd │ ├── bases │ │ ├── vpc.scaleway.com_networkinterfaces.yaml │ │ └── vpc.scaleway.com_privatenetworks.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_networkinterfaces.yaml │ │ ├── cainjection_in_privatenetworks.yaml │ │ ├── webhook_in_networkinterfaces.yaml │ │ └── webhook_in_privatenetworks.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── node │ ├── kustomization.yaml │ └── node.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── controller-role.yaml │ ├── controller-role_binding.yaml │ ├── controller-sa.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── networkinterface_editor_role.yaml │ ├── networkinterface_viewer_role.yaml │ ├── node-role.yaml │ ├── node-role_binding.yaml │ ├── node-sa.yaml │ ├── privatenetwork_editor_role.yaml │ ├── privatenetwork_viewer_role.yaml │ └── role.yaml ├── samples │ └── vpc_v1alpha1_privatenetwork.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── service.yaml ├── controllers ├── helpers.go ├── networkinterface_controller.go ├── privatenetwork_controller.go └── suite_test.go ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── internal └── constants │ └── constants.go ├── nodes └── networkinterface_controller.go ├── pkg ├── ipam │ └── ipam.go └── nics │ └── nics.go └── secret.yaml /.github/workflows/merge.yaml: -------------------------------------------------------------------------------- 1 | name: merge 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | jobs: 7 | merge_main: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | - name: Unshallow 13 | run: git fetch --prune --unshallow 14 | - name: Extract branch name 15 | shell: bash 16 | run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" 17 | id: extract_branch 18 | - name: Enable experimental on dockerd 19 | run: | 20 | echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json 21 | sudo service docker restart 22 | - name: Set up Docker Buildx 23 | id: buildx 24 | uses: crazy-max/ghaction-docker-buildx@v1 25 | with: 26 | version: latest 27 | - name: Docker login 28 | run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USER }} --password-stdin 29 | - name: Make latest release 30 | run: make release 31 | env: 32 | DOCKER_CLI_EXPERIMENTAL: enabled 33 | IMAGE_TAG: latest 34 | - name: Make tagged release 35 | run: make release 36 | env: 37 | DOCKER_CLI_EXPERIMENTAL: enabled 38 | IMAGE_TAG: ${{ github.sha }} 39 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yaml: -------------------------------------------------------------------------------- 1 | name: pull-request 2 | 3 | on: 4 | - pull_request 5 | 6 | jobs: 7 | unit-test: 8 | strategy: 9 | matrix: 10 | go-version: [1.16.x] 11 | platform: [ubuntu-latest] 12 | runs-on: ${{ matrix.platform }} 13 | steps: 14 | - name: Install Go 15 | uses: actions/setup-go@v1 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | - name: checkout 19 | uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 1 22 | - name: Run unit tests 23 | run: make test 24 | build-test: 25 | strategy: 26 | matrix: 27 | go-version: [1.16.x] 28 | platform: [ubuntu-latest] 29 | arch: [amd64] 30 | runs-on: ${{ matrix.platform }} 31 | steps: 32 | - name: Install Go 33 | uses: actions/setup-go@v1 34 | with: 35 | go-version: ${{ matrix.go-version }} 36 | - name: checkout 37 | uses: actions/checkout@v2 38 | with: 39 | fetch-depth: 1 40 | - name: Building binary 41 | run: GOARCH=${{ matrix.arch }} make 42 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | - name: Unshallow 13 | run: git fetch --prune --unshallow 14 | - name: Highest tag 15 | id: highestTag 16 | run: | 17 | git fetch --depth=1 origin +refs/tags/*:refs/tags/* 18 | echo ::set-output name=highest::$(git tag | grep -E "^v?([0-9]+\.)+[0-9]+$" | sort -r -V | head -n1) 19 | - name: Enable experimental on dockerd 20 | run: | 21 | echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json 22 | sudo service docker restart 23 | - name: Set up Docker Buildx 24 | id: buildx 25 | uses: crazy-max/ghaction-docker-buildx@v1 26 | with: 27 | version: latest 28 | - name: Docker login 29 | run: echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USER }} --password-stdin 30 | - name: Get tag 31 | uses: olegtarasov/get-tag@v1 32 | id: tagName 33 | - name: Make release 34 | run: make release 35 | env: 36 | DOCKER_CLI_EXPERIMENTAL: enabled 37 | IMAGE_TAG: ${{ steps.tagName.outputs.tag }} 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Kubernetes Generated files - skip generated files, except for vendored files 17 | 18 | !vendor/**/zz_generated.* 19 | 20 | # editor and IDE paraphernalia 21 | .idea 22 | *.swp 23 | *.swo 24 | *~ 25 | 26 | kubebuilder-bin/ 27 | -------------------------------------------------------------------------------- /Dockerfile.controller: -------------------------------------------------------------------------------- 1 | FROM golang:1.16 as builder 2 | 3 | WORKDIR /workspace 4 | 5 | COPY go.mod go.mod 6 | COPY go.sum go.sum 7 | 8 | RUN go mod download 9 | 10 | COPY cmd/ cmd/ 11 | COPY api/ api/ 12 | COPY pkg/ pkg/ 13 | COPY controllers/ controllers/ 14 | COPY nodes/ nodes/ 15 | COPY internal/ internal/ 16 | 17 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o controller ./cmd/controller/ 18 | 19 | FROM gcr.io/distroless/static:nonroot 20 | WORKDIR / 21 | COPY --from=builder /workspace/controller . 22 | USER nonroot:nonroot 23 | 24 | ENTRYPOINT ["/controller"] 25 | -------------------------------------------------------------------------------- /Dockerfile.node: -------------------------------------------------------------------------------- 1 | FROM golang:1.16 as builder 2 | 3 | WORKDIR /workspace 4 | 5 | COPY go.mod go.mod 6 | COPY go.sum go.sum 7 | 8 | RUN go mod download 9 | 10 | COPY cmd/ cmd/ 11 | COPY api/ api/ 12 | COPY pkg/ pkg/ 13 | COPY controllers/ controllers/ 14 | COPY nodes/ nodes/ 15 | COPY internal/ internal/ 16 | 17 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o node ./cmd/node/ 18 | 19 | FROM alpine 20 | RUN apk add --update-cache iptables dhcpcd \ 21 | && rm -rf /var/cache/apk/* 22 | WORKDIR / 23 | COPY --from=builder /workspace/node . 24 | 25 | ENTRYPOINT ["/node"] 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 Patirk Cyvoct 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OS ?= $(shell go env GOOS) 2 | ARCH ?= $(shell go env GOARCH) 3 | ALL_PLATFORM = linux/amd64 4 | 5 | # Image URL to use all building/pushing image targets 6 | REGISTRY ?= sh4d1 7 | CONTROLLER_IMG ?= scaleway-k8s-vpc 8 | NODE_IMG ?= scaleway-k8s-vpc-node 9 | CONTROLLER_FULL_IMG ?= $(REGISTRY)/$(CONTROLLER_IMG) 10 | NODE_FULL_IMG ?= $(REGISTRY)/$(NODE_IMG) 11 | 12 | IMAGE_TAG ?= $(shell git rev-parse HEAD) 13 | 14 | DOCKER_CLI_EXPERIMENTAL ?= enabled 15 | 16 | CRD_OPTIONS ?= "crd:crdVersions=v1" 17 | 18 | KUBEBUILDER_VERSION=2.3.1 19 | 20 | TEST_ASSET_KUBE_APISERVER ?= $(shell pwd)/kubebuilder-bin/kube-apiserver 21 | TEST_ASSET_ETCD ?= $(shell pwd)/kubebuilder-bin/etcd 22 | TEST_ASSET_KUBECTL ?= $(shell pwd)/kubebuilder-bin/kubectl 23 | 24 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 25 | ifeq (,$(shell go env GOBIN)) 26 | GOBIN=$(shell go env GOPATH)/bin 27 | else 28 | GOBIN=$(shell go env GOBIN) 29 | endif 30 | 31 | all: controller node 32 | 33 | # Run tests 34 | test: kubebuilder-bin generate fmt vet manifests 35 | TEST_ASSET_KUBE_APISERVER=$(TEST_ASSET_KUBE_APISERVER) TEST_ASSET_ETCD=$(TEST_ASSET_ETCD) TEST_ASSET_KUBECTL=$(TEST_ASSET_KUBECTL) go test ./... -coverprofile cover.out 36 | 37 | kubebuilder-bin: 38 | curl -fsSL https://github.com/kubernetes-sigs/kubebuilder/releases/download/v$(KUBEBUILDER_VERSION)/kubebuilder_$(KUBEBUILDER_VERSION)_$(OS)_$(ARCH).tar.gz -o kubebuilder-tools.tar.gz 39 | mkdir kubebuilder-bin 40 | tar -xvf kubebuilder-tools.tar.gz 41 | mv kubebuilder_$(KUBEBUILDER_VERSION)_$(OS)_$(ARCH)/bin/* kubebuilder-bin/ 42 | rm kubebuilder-tools.tar.gz 43 | rm -R kubebuilder_$(KUBEBUILDER_VERSION)_$(OS)_$(ARCH) 44 | 45 | clean-kubebuilder: 46 | rm -Rf kubebuilder-bin/ 47 | 48 | # Build controller binary 49 | controller: generate fmt vet 50 | go build -o bin/controller ./cmd/controller/ 51 | 52 | # Build node binary 53 | node: generate fmt vet 54 | go build -o bin/node ./cmd/node/ 55 | 56 | # Run against the configured Kubernetes cluster in ~/.kube/config 57 | run: generate fmt vet manifests 58 | go run ./cmd/controller/controller.go 59 | 60 | # Install CRDs into a cluster 61 | install: manifests 62 | kustomize build config/crd | kubectl apply -f - 63 | 64 | # Uninstall CRDs from a cluster 65 | uninstall: manifests 66 | kustomize build config/crd | kubectl delete -f - 67 | 68 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 69 | deploy: manifests 70 | cd config/controller && kustomize edit set image controller=${CONTROLLER_FULL_IMG} 71 | cd config/node && kustomize edit set image node=${NODE_FULL_IMG} 72 | kustomize build config/default | kubectl apply -f - 73 | 74 | rbac: controller-gen 75 | $(CONTROLLER_GEN) rbac:roleName=controller-role paths="./controllers/" output:stdout > config/rbac/controller-role.yaml 76 | $(CONTROLLER_GEN) rbac:roleName=node-role paths="./nodes/" output:stdout > config/rbac/node-role.yaml 77 | 78 | # Generate manifests e.g. CRD, RBAC etc. 79 | manifests: rbac controller-gen 80 | $(CONTROLLER_GEN) $(CRD_OPTIONS) webhook paths="./..." output:crd:artifacts:config=config/crd/bases 81 | 82 | # Run go fmt against code 83 | fmt: 84 | go fmt ./... 85 | 86 | # Run go vet against code 87 | vet: 88 | go vet ./... 89 | 90 | # Generate code 91 | generate: controller-gen 92 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 93 | 94 | # Build the docker image 95 | docker-build: test 96 | docker build --platform=linux/$(ARCH) -f Dockerfile.controller . -t ${CONTROLLER_FULL_IMG}:$(IMAGE_TAG) 97 | docker build --platform=linux/$(ARCH) -f Dockerfile.node . -t ${NODE_FULL_IMG}:$(IMAGE_TAG) 98 | 99 | # Push the docker image 100 | docker-push: 101 | docker push ${CONTROLLER_FULL_IMG}:$(IMAGE_TAG) 102 | docker push ${NODE_FULL_IMG}:$(IMAGE_TAG) 103 | 104 | docker-buildx-all: 105 | @echo "Making release for tag $(IMAGE_TAG)" 106 | docker buildx build --platform=$(ALL_PLATFORM) -f Dockerfile.controller --push -t $(CONTROLLER_FULL_IMG):$(IMAGE_TAG) . 107 | docker buildx build --platform=$(ALL_PLATFORM) -f Dockerfile.node --push -t $(NODE_FULL_IMG):$(IMAGE_TAG) . 108 | 109 | release: docker-buildx-all 110 | 111 | # find or download controller-gen 112 | # download controller-gen if necessary 113 | controller-gen: 114 | ifeq (, $(shell which controller-gen)) 115 | @{ \ 116 | set -e ;\ 117 | CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ 118 | cd $$CONTROLLER_GEN_TMP_DIR ;\ 119 | go mod init tmp ;\ 120 | go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.5.0 ;\ 121 | rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ 122 | } 123 | CONTROLLER_GEN=$(GOBIN)/controller-gen 124 | else 125 | CONTROLLER_GEN=$(shell which controller-gen) 126 | endif 127 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: scaleway.com 2 | repo: github.com/Sh4d1/scaleway-k8s-vpc 3 | resources: 4 | - group: vpc 5 | kind: PrivateNetwork 6 | version: v1alpha1 7 | - group: vpc 8 | kind: NetworkInterface 9 | version: v1alpha1 10 | version: "2" 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scaleway K8S VPC 2 | 3 | **Note**: This in just a Proof of Concept, it is not suited for production usage. 4 | 5 | Scaleway K8S VPC is a controller for Kubernetes running on Scaleway, leveraging CRDs to use PrivateNetwork in the cluster. 6 | 7 | ## Getting started 8 | 9 | Install the controller and the node daemon with: 10 | ```yaml 11 | kubectl create -k https://github.com/Sh4d1/scaleway-k8s-vpc/config/default 12 | ``` 13 | 14 | Create and enter your Scaleway credentials with: 15 | ```yaml 16 | kubectl create -f https://raw.githubusercontent.com/Sh4d1/scaleway-k8s-vpc/main/secret.yaml --edit --namespace scaleway-k8s-vpc-system 17 | ``` 18 | 19 | You can now create the following PrivateNetwork object: 20 | ```yaml 21 | apiVersion: vpc.scaleway.com/v1alpha1 22 | kind: PrivateNetwork 23 | metadata: 24 | name: my-privatenetwork 25 | spec: 26 | id: 27 | ipam: 28 | type: Static 29 | static: 30 | cidr: 192.168.0.0/24 31 | routes: 32 | - to: 1.2.3.4/16 33 | via: 192.168.0.10 34 | ``` 35 | 36 | This will attach the private network to all nodes in the cluster, set up the interfaces with IPs in the range, and add the routes if needed. 37 | 38 | If you have a DHCP running in the private network you can use it to assign IPs: 39 | ```yaml 40 | apiVersion: vpc.scaleway.com/v1alpha1 41 | kind: PrivateNetwork 42 | metadata: 43 | name: my-privatenetwork 44 | spec: 45 | id: 46 | ipam: 47 | type: DHCP 48 | routes: 49 | - to: 1.2.3.4/16 50 | via: 192.168.0.10 51 | ``` 52 | 53 | ## Contribution 54 | 55 | Feel free to submit any issue, feature request or pull request :smile:! 56 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the vpc v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=vpc.scaleway.com 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "vpc.scaleway.com", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1alpha1/networkinterface_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // NetworkInterfaceSpec defines the desired state of NetworkInterface 24 | type NetworkInterfaceSpec struct { 25 | // ID is the ID of the NIC 26 | ID string `json:"id"` 27 | 28 | // NodeName is the name of the node the interface is attached to 29 | NodeName string `json:"nodeName"` 30 | 31 | // Address is the address of the interface 32 | // deprecated 33 | Address string `json:"address,omitempty"` 34 | } 35 | 36 | // NetworkInterfaceStatus defines the observed state of NetworkInterface 37 | type NetworkInterfaceStatus struct { 38 | // LinkName is the name of the Interface 39 | LinkName string `json:"linkName"` 40 | 41 | // MacAddress is the mac address of the interface 42 | MacAddress string `json:"macAddress"` 43 | 44 | // Address is the address of the interface 45 | Address string `json:"address,omitempty"` 46 | 47 | // ParentCIDR is the parent cidr of the Address 48 | ParentCIDR string `json:"parentCidr,omitempty"` 49 | } 50 | 51 | // +kubebuilder:object:root=true 52 | // +kubebuilder:subresource:status 53 | // +kubebuilder:resource:scope=Cluster,shortName=ni;nif;networkinterface;netiface;niface 54 | // +kubebuilder:printcolumn:name="address",type="string",JSONPath=".status.address" 55 | // +kubebuilder:printcolumn:name="node name",type="string",JSONPath=".spec.nodeName" 56 | // +kubebuilder:printcolumn:name="mac address",type="string",JSONPath=".status.macAddress" 57 | // +kubebuilder:printcolumn:name="link name",type="string",JSONPath=".status.linkName" 58 | 59 | // NetworkInterface is the Schema for the networkinterfaces API 60 | type NetworkInterface struct { 61 | metav1.TypeMeta `json:",inline"` 62 | metav1.ObjectMeta `json:"metadata,omitempty"` 63 | 64 | Spec NetworkInterfaceSpec `json:"spec,omitempty"` 65 | Status NetworkInterfaceStatus `json:"status,omitempty"` 66 | } 67 | 68 | // +kubebuilder:object:root=true 69 | 70 | // NetworkInterfaceList contains a list of NetworkInterface 71 | type NetworkInterfaceList struct { 72 | metav1.TypeMeta `json:",inline"` 73 | metav1.ListMeta `json:"metadata,omitempty"` 74 | Items []NetworkInterface `json:"items"` 75 | } 76 | 77 | func init() { 78 | SchemeBuilder.Register(&NetworkInterface{}, &NetworkInterfaceList{}) 79 | } 80 | -------------------------------------------------------------------------------- /api/v1alpha1/privatenetwork_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // PrivateNetworkSpec defines the desired state of PrivateNetwork 24 | type PrivateNetworkSpec struct { 25 | // ID is the ID of the PrivateNetwork 26 | ID string `json:"id"` 27 | 28 | // Zone is the Zone of the PrivateNetwork 29 | // Will default to the SCW_DEFAULT_ZONE env variable 30 | // +optional 31 | Zone string `json:"zone,omitempty"` 32 | 33 | IPAM *PrivateNetworkIPAM `json:"ipam,omitempty"` 34 | 35 | // Routes are the routes injected in the cluster to this PrivateNetwork 36 | // +optional 37 | Routes []PrivateNetworkRoute `json:"routes,omitempty"` 38 | 39 | // Masquerade represents whether the private network needs to be masqueraded 40 | // +optional 41 | // +kubebuilder:default:=true 42 | Masquerade bool `json:"masquerade,omitempty"` 43 | 44 | // CIDR is the CIDR of the PrivateNetwork 45 | // deprecated 46 | CIDR string `json:"cidr,omitempty"` 47 | } 48 | 49 | // PrivateNetworkRoute defines a route from the PrivateNetwork 50 | type PrivateNetworkRoute struct { 51 | To string `json:"to"` 52 | Via string `json:"via"` 53 | } 54 | 55 | // +kubebuilder:validation:Enum=DHCP;Static 56 | // IPAMType represents a type of IPAM 57 | type IPAMType string 58 | 59 | const ( 60 | // IPAMTypeDHCP represents the dhcp IPAM type 61 | IPAMTypeDHCP IPAMType = "DHCP" 62 | // IPAMTypeStatic represents the static IPAM type 63 | IPAMTypeStatic IPAMType = "Static" 64 | ) 65 | 66 | type PrivateNetworkIPAMStatic struct { 67 | // CIDR represents the CIDR associated to this private network 68 | CIDR string `json:"cidr"` 69 | // AvailableRanges allows to restrict which ranges of addresses should be used when choosing an IP address 70 | // Defaults to the whole CIDR 71 | AvailableRanges []string `json:"availableRanges,omitempty"` 72 | } 73 | 74 | // PrivateNetworkIPAM defines the IPAM for the PrivateNetwork 75 | type PrivateNetworkIPAM struct { 76 | Type IPAMType `json:"type"` 77 | Static *PrivateNetworkIPAMStatic `json:"static,omitempty"` 78 | } 79 | 80 | // PrivateNetworkStatus defines the observed state of PrivateNetwork 81 | type PrivateNetworkStatus struct { 82 | } 83 | 84 | // +kubebuilder:object:root=true 85 | // +kubebuilder:subresource:status 86 | // +kubebuilder:resource:scope=Cluster,shortName=pn;privnet;privatenet;privatenetwork 87 | // +kubebuilder:printcolumn:name="id",type="string",JSONPath=".spec.id" 88 | // +kubebuilder:printcolumn:name="ipam type",type="string",JSONPath=".spec.ipam.type" 89 | 90 | // PrivateNetwork is the Schema for the privatenetworks API 91 | type PrivateNetwork struct { 92 | metav1.TypeMeta `json:",inline"` 93 | metav1.ObjectMeta `json:"metadata,omitempty"` 94 | 95 | Spec PrivateNetworkSpec `json:"spec,omitempty"` 96 | Status PrivateNetworkStatus `json:"status,omitempty"` 97 | } 98 | 99 | // +kubebuilder:object:root=true 100 | 101 | // PrivateNetworkList contains a list of PrivateNetwork 102 | type PrivateNetworkList struct { 103 | metav1.TypeMeta `json:",inline"` 104 | metav1.ListMeta `json:"metadata,omitempty"` 105 | Items []PrivateNetwork `json:"items"` 106 | } 107 | 108 | func init() { 109 | SchemeBuilder.Register(&PrivateNetwork{}, &PrivateNetworkList{}) 110 | } 111 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | /* 4 | 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | ) 26 | 27 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 28 | func (in *NetworkInterface) DeepCopyInto(out *NetworkInterface) { 29 | *out = *in 30 | out.TypeMeta = in.TypeMeta 31 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 32 | out.Spec = in.Spec 33 | out.Status = in.Status 34 | } 35 | 36 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkInterface. 37 | func (in *NetworkInterface) DeepCopy() *NetworkInterface { 38 | if in == nil { 39 | return nil 40 | } 41 | out := new(NetworkInterface) 42 | in.DeepCopyInto(out) 43 | return out 44 | } 45 | 46 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 47 | func (in *NetworkInterface) DeepCopyObject() runtime.Object { 48 | if c := in.DeepCopy(); c != nil { 49 | return c 50 | } 51 | return nil 52 | } 53 | 54 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 55 | func (in *NetworkInterfaceList) DeepCopyInto(out *NetworkInterfaceList) { 56 | *out = *in 57 | out.TypeMeta = in.TypeMeta 58 | in.ListMeta.DeepCopyInto(&out.ListMeta) 59 | if in.Items != nil { 60 | in, out := &in.Items, &out.Items 61 | *out = make([]NetworkInterface, len(*in)) 62 | for i := range *in { 63 | (*in)[i].DeepCopyInto(&(*out)[i]) 64 | } 65 | } 66 | } 67 | 68 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkInterfaceList. 69 | func (in *NetworkInterfaceList) DeepCopy() *NetworkInterfaceList { 70 | if in == nil { 71 | return nil 72 | } 73 | out := new(NetworkInterfaceList) 74 | in.DeepCopyInto(out) 75 | return out 76 | } 77 | 78 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 79 | func (in *NetworkInterfaceList) DeepCopyObject() runtime.Object { 80 | if c := in.DeepCopy(); c != nil { 81 | return c 82 | } 83 | return nil 84 | } 85 | 86 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 87 | func (in *NetworkInterfaceSpec) DeepCopyInto(out *NetworkInterfaceSpec) { 88 | *out = *in 89 | } 90 | 91 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkInterfaceSpec. 92 | func (in *NetworkInterfaceSpec) DeepCopy() *NetworkInterfaceSpec { 93 | if in == nil { 94 | return nil 95 | } 96 | out := new(NetworkInterfaceSpec) 97 | in.DeepCopyInto(out) 98 | return out 99 | } 100 | 101 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 102 | func (in *NetworkInterfaceStatus) DeepCopyInto(out *NetworkInterfaceStatus) { 103 | *out = *in 104 | } 105 | 106 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkInterfaceStatus. 107 | func (in *NetworkInterfaceStatus) DeepCopy() *NetworkInterfaceStatus { 108 | if in == nil { 109 | return nil 110 | } 111 | out := new(NetworkInterfaceStatus) 112 | in.DeepCopyInto(out) 113 | return out 114 | } 115 | 116 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 117 | func (in *PrivateNetwork) DeepCopyInto(out *PrivateNetwork) { 118 | *out = *in 119 | out.TypeMeta = in.TypeMeta 120 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 121 | in.Spec.DeepCopyInto(&out.Spec) 122 | out.Status = in.Status 123 | } 124 | 125 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateNetwork. 126 | func (in *PrivateNetwork) DeepCopy() *PrivateNetwork { 127 | if in == nil { 128 | return nil 129 | } 130 | out := new(PrivateNetwork) 131 | in.DeepCopyInto(out) 132 | return out 133 | } 134 | 135 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 136 | func (in *PrivateNetwork) DeepCopyObject() runtime.Object { 137 | if c := in.DeepCopy(); c != nil { 138 | return c 139 | } 140 | return nil 141 | } 142 | 143 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 144 | func (in *PrivateNetworkIPAM) DeepCopyInto(out *PrivateNetworkIPAM) { 145 | *out = *in 146 | if in.Static != nil { 147 | in, out := &in.Static, &out.Static 148 | *out = new(PrivateNetworkIPAMStatic) 149 | (*in).DeepCopyInto(*out) 150 | } 151 | } 152 | 153 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateNetworkIPAM. 154 | func (in *PrivateNetworkIPAM) DeepCopy() *PrivateNetworkIPAM { 155 | if in == nil { 156 | return nil 157 | } 158 | out := new(PrivateNetworkIPAM) 159 | in.DeepCopyInto(out) 160 | return out 161 | } 162 | 163 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 164 | func (in *PrivateNetworkIPAMStatic) DeepCopyInto(out *PrivateNetworkIPAMStatic) { 165 | *out = *in 166 | if in.AvailableRanges != nil { 167 | in, out := &in.AvailableRanges, &out.AvailableRanges 168 | *out = make([]string, len(*in)) 169 | copy(*out, *in) 170 | } 171 | } 172 | 173 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateNetworkIPAMStatic. 174 | func (in *PrivateNetworkIPAMStatic) DeepCopy() *PrivateNetworkIPAMStatic { 175 | if in == nil { 176 | return nil 177 | } 178 | out := new(PrivateNetworkIPAMStatic) 179 | in.DeepCopyInto(out) 180 | return out 181 | } 182 | 183 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 184 | func (in *PrivateNetworkList) DeepCopyInto(out *PrivateNetworkList) { 185 | *out = *in 186 | out.TypeMeta = in.TypeMeta 187 | in.ListMeta.DeepCopyInto(&out.ListMeta) 188 | if in.Items != nil { 189 | in, out := &in.Items, &out.Items 190 | *out = make([]PrivateNetwork, len(*in)) 191 | for i := range *in { 192 | (*in)[i].DeepCopyInto(&(*out)[i]) 193 | } 194 | } 195 | } 196 | 197 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateNetworkList. 198 | func (in *PrivateNetworkList) DeepCopy() *PrivateNetworkList { 199 | if in == nil { 200 | return nil 201 | } 202 | out := new(PrivateNetworkList) 203 | in.DeepCopyInto(out) 204 | return out 205 | } 206 | 207 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 208 | func (in *PrivateNetworkList) DeepCopyObject() runtime.Object { 209 | if c := in.DeepCopy(); c != nil { 210 | return c 211 | } 212 | return nil 213 | } 214 | 215 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 216 | func (in *PrivateNetworkRoute) DeepCopyInto(out *PrivateNetworkRoute) { 217 | *out = *in 218 | } 219 | 220 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateNetworkRoute. 221 | func (in *PrivateNetworkRoute) DeepCopy() *PrivateNetworkRoute { 222 | if in == nil { 223 | return nil 224 | } 225 | out := new(PrivateNetworkRoute) 226 | in.DeepCopyInto(out) 227 | return out 228 | } 229 | 230 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 231 | func (in *PrivateNetworkSpec) DeepCopyInto(out *PrivateNetworkSpec) { 232 | *out = *in 233 | if in.IPAM != nil { 234 | in, out := &in.IPAM, &out.IPAM 235 | *out = new(PrivateNetworkIPAM) 236 | (*in).DeepCopyInto(*out) 237 | } 238 | if in.Routes != nil { 239 | in, out := &in.Routes, &out.Routes 240 | *out = make([]PrivateNetworkRoute, len(*in)) 241 | copy(*out, *in) 242 | } 243 | } 244 | 245 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateNetworkSpec. 246 | func (in *PrivateNetworkSpec) DeepCopy() *PrivateNetworkSpec { 247 | if in == nil { 248 | return nil 249 | } 250 | out := new(PrivateNetworkSpec) 251 | in.DeepCopyInto(out) 252 | return out 253 | } 254 | 255 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 256 | func (in *PrivateNetworkStatus) DeepCopyInto(out *PrivateNetworkStatus) { 257 | *out = *in 258 | } 259 | 260 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateNetworkStatus. 261 | func (in *PrivateNetworkStatus) DeepCopy() *PrivateNetworkStatus { 262 | if in == nil { 263 | return nil 264 | } 265 | out := new(PrivateNetworkStatus) 266 | in.DeepCopyInto(out) 267 | return out 268 | } 269 | -------------------------------------------------------------------------------- /cmd/controller/controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | "time" 23 | 24 | goipam "github.com/metal-stack/go-ipam" 25 | instance "github.com/scaleway/scaleway-sdk-go/api/instance/v1" 26 | vpc "github.com/scaleway/scaleway-sdk-go/api/vpc/v1" 27 | "github.com/scaleway/scaleway-sdk-go/scw" 28 | "k8s.io/apimachinery/pkg/runtime" 29 | "k8s.io/apimachinery/pkg/types" 30 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 31 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 32 | "k8s.io/klog" 33 | "k8s.io/klog/klogr" 34 | ctrl "sigs.k8s.io/controller-runtime" 35 | 36 | vpcv1alpha1 "github.com/Sh4d1/scaleway-k8s-vpc/api/v1alpha1" 37 | "github.com/Sh4d1/scaleway-k8s-vpc/controllers" 38 | "github.com/Sh4d1/scaleway-k8s-vpc/pkg/ipam" 39 | // +kubebuilder:scaffold:imports 40 | ) 41 | 42 | var ( 43 | scheme = runtime.NewScheme() 44 | setupLog = ctrl.Log.WithName("setup") 45 | 46 | defaultCmName = "scaleway-k8s-vpc-ipam" 47 | defaultCmNamespace = "default" 48 | cacheUpdateFrequency = time.Minute * 20 49 | ) 50 | 51 | func init() { 52 | _ = clientgoscheme.AddToScheme(scheme) 53 | 54 | _ = vpcv1alpha1.AddToScheme(scheme) 55 | // +kubebuilder:scaffold:scheme 56 | } 57 | 58 | func main() { 59 | var metricsAddr string 60 | var enableLeaderElection bool 61 | flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") 62 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, 63 | "Enable leader election for controller manager. "+ 64 | "Enabling this will ensure there is only one active controller manager.") 65 | klog.InitFlags(nil) 66 | flag.Parse() 67 | 68 | ctrl.SetLogger(klogr.New()) 69 | 70 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 71 | Scheme: scheme, 72 | MetricsBindAddress: metricsAddr, 73 | Port: 9443, 74 | LeaderElection: enableLeaderElection, 75 | LeaderElectionID: "be46b6df.scaleway.com", 76 | }) 77 | if err != nil { 78 | setupLog.Error(err, "unable to start manager") 79 | os.Exit(1) 80 | } 81 | 82 | scwClient, err := scw.NewClient( 83 | scw.WithEnv(), 84 | scw.WithUserAgent("scaleway-k8s-vpc"), 85 | ) 86 | if err != nil { 87 | setupLog.Error(err, "unable to init scaleway client") 88 | } 89 | 90 | stopCh := ctrl.SetupSignalHandler() 91 | 92 | cmNamespace := os.Getenv("CONFIGMAP_NAMESPACE") 93 | if cmNamespace == "" { 94 | cmNamespace = defaultCmNamespace 95 | } 96 | cmName := os.Getenv("CONFIGMAP_NAME") 97 | if cmName == "" { 98 | cmName = defaultCmName 99 | } 100 | 101 | cmIPAM, err := ipam.NewConfigMapIPAM(types.NamespacedName{ 102 | Name: cmName, 103 | Namespace: cmNamespace, 104 | }, stopCh) 105 | if err != nil { 106 | setupLog.Error(err, "error creating ipam storage") 107 | os.Exit(1) 108 | } 109 | ipam := goipam.NewWithStorage(cmIPAM) 110 | 111 | if err = (&controllers.PrivateNetworkReconciler{ 112 | Client: mgr.GetClient(), 113 | Log: ctrl.Log.WithName("controllers").WithName("PrivateNetwork"), 114 | Scheme: mgr.GetScheme(), 115 | IPAM: ipam, 116 | InstanceAPI: instance.NewAPI(scwClient), 117 | VpcAPI: vpc.NewAPI(scwClient), 118 | }).SetupWithManager(mgr); err != nil { 119 | setupLog.Error(err, "unable to create controller", "controller", "PrivateNetwork") 120 | os.Exit(1) 121 | } 122 | if err = (&controllers.NetworkInterfaceReconciler{ 123 | Client: mgr.GetClient(), 124 | Log: ctrl.Log.WithName("controllers").WithName("NetworkInterface"), 125 | Scheme: mgr.GetScheme(), 126 | IPAM: ipam, 127 | InstanceAPI: instance.NewAPI(scwClient), 128 | }).SetupWithManager(mgr); err != nil { 129 | setupLog.Error(err, "unable to create controller", "controller", "NetworkInterface") 130 | os.Exit(1) 131 | } 132 | // +kubebuilder:scaffold:builder 133 | 134 | setupLog.Info("starting manager") 135 | if err := mgr.Start(stopCh); err != nil { 136 | setupLog.Error(err, "problem running manager") 137 | os.Exit(1) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /cmd/node/node.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | "time" 23 | 24 | "k8s.io/apimachinery/pkg/runtime" 25 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 26 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 27 | "k8s.io/klog" 28 | "k8s.io/klog/klogr" 29 | ctrl "sigs.k8s.io/controller-runtime" 30 | 31 | vpcv1alpha1 "github.com/Sh4d1/scaleway-k8s-vpc/api/v1alpha1" 32 | "github.com/Sh4d1/scaleway-k8s-vpc/nodes" 33 | "github.com/Sh4d1/scaleway-k8s-vpc/pkg/nics" 34 | instance "github.com/scaleway/scaleway-sdk-go/api/instance/v1" 35 | // +kubebuilder:scaffold:imports 36 | ) 37 | 38 | var ( 39 | scheme = runtime.NewScheme() 40 | setupLog = ctrl.Log.WithName("setup") 41 | 42 | cacheUpdateFrequency = time.Minute * 20 43 | ) 44 | 45 | func init() { 46 | _ = clientgoscheme.AddToScheme(scheme) 47 | 48 | _ = vpcv1alpha1.AddToScheme(scheme) 49 | // +kubebuilder:scaffold:scheme 50 | } 51 | 52 | func main() { 53 | var metricsAddr string 54 | flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") 55 | klog.InitFlags(nil) 56 | flag.Parse() 57 | 58 | ctrl.SetLogger(klogr.New()) 59 | 60 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 61 | Scheme: scheme, 62 | MetricsBindAddress: metricsAddr, 63 | Port: 9443, 64 | LeaderElection: false, 65 | }) 66 | if err != nil { 67 | setupLog.Error(err, "unable to start manager") 68 | os.Exit(1) 69 | } 70 | 71 | metadataAPI := instance.NewMetadataAPI() 72 | md, err := metadataAPI.GetMetadata() 73 | if err != nil { 74 | setupLog.Error(err, "unable to fetch Scaleway metdata") 75 | os.Exit(1) 76 | } 77 | 78 | nodeName := os.Getenv("NODE_NAME") 79 | if nodeName == "" { 80 | setupLog.Info("Node name not specified, using hostname") 81 | nodeName = md.Hostname 82 | } 83 | 84 | macs := []string{} 85 | for _, pn := range md.PrivateNICs { 86 | macs = append(macs, pn.MacAddress) 87 | } 88 | 89 | nics, err := nics.NewNICs(macs) 90 | if err != nil { 91 | setupLog.Error(err, "unable to init nics handler") 92 | os.Exit(1) 93 | } 94 | 95 | if err = (&nodes.NetworkInterfaceReconciler{ 96 | Client: mgr.GetClient(), 97 | Log: ctrl.Log.WithName("controllers").WithName("NetworkInterface"), 98 | Scheme: mgr.GetScheme(), 99 | MetadataAPI: metadataAPI, 100 | NodeName: nodeName, 101 | NICs: nics, 102 | }).SetupWithManager(mgr); err != nil { 103 | setupLog.Error(err, "unable to create controller", "controller", "NetworkInterface") 104 | os.Exit(1) 105 | } 106 | // +kubebuilder:scaffold:builder 107 | 108 | setupLog.Info("starting manager") 109 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 110 | setupLog.Error(err, "problem running manager") 111 | os.Exit(1) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager 0.11 check https://docs.cert-manager.io/en/latest/tasks/upgrading/index.html for 4 | # breaking changes 5 | apiVersion: cert-manager.io/v1alpha2 6 | kind: Issuer 7 | metadata: 8 | name: selfsigned-issuer 9 | namespace: system 10 | spec: 11 | selfSigned: {} 12 | --- 13 | apiVersion: cert-manager.io/v1alpha2 14 | kind: Certificate 15 | metadata: 16 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 17 | namespace: system 18 | spec: 19 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 20 | dnsNames: 21 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 22 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 23 | issuerRef: 24 | kind: Issuer 25 | name: selfsigned-issuer 26 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 27 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/controller/controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller 12 | namespace: system 13 | labels: 14 | control-plane: controller 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | control-plane: controller 24 | spec: 25 | serviceAccountName: controller 26 | containers: 27 | - command: 28 | - /controller 29 | env: 30 | - name: CONFIGMAP_NAMESPACE 31 | valueFrom: 32 | fieldRef: 33 | fieldPath: metadata.namespace 34 | envFrom: 35 | - secretRef: 36 | name: scaleway-k8s-vpc-secret 37 | args: 38 | - --enable-leader-election 39 | image: sh4d1/scaleway-k8s-vpc:latest 40 | name: controller 41 | resources: 42 | limits: 43 | cpu: 100m 44 | memory: 30Mi 45 | requests: 46 | cpu: 100m 47 | memory: 20Mi 48 | terminationGracePeriodSeconds: 10 49 | -------------------------------------------------------------------------------- /config/controller/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - controller.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: sh4d1/scaleway-k8s-vpc 8 | -------------------------------------------------------------------------------- /config/crd/bases/vpc.scaleway.com_networkinterfaces.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.5.0 8 | creationTimestamp: null 9 | name: networkinterfaces.vpc.scaleway.com 10 | spec: 11 | group: vpc.scaleway.com 12 | names: 13 | kind: NetworkInterface 14 | listKind: NetworkInterfaceList 15 | plural: networkinterfaces 16 | shortNames: 17 | - ni 18 | - nif 19 | - networkinterface 20 | - netiface 21 | - niface 22 | singular: networkinterface 23 | scope: Cluster 24 | versions: 25 | - additionalPrinterColumns: 26 | - jsonPath: .status.address 27 | name: address 28 | type: string 29 | - jsonPath: .spec.nodeName 30 | name: node name 31 | type: string 32 | - jsonPath: .status.macAddress 33 | name: mac address 34 | type: string 35 | - jsonPath: .status.linkName 36 | name: link name 37 | type: string 38 | name: v1alpha1 39 | schema: 40 | openAPIV3Schema: 41 | description: NetworkInterface is the Schema for the networkinterfaces API 42 | properties: 43 | apiVersion: 44 | 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' 45 | type: string 46 | kind: 47 | 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' 48 | type: string 49 | metadata: 50 | type: object 51 | spec: 52 | description: NetworkInterfaceSpec defines the desired state of NetworkInterface 53 | properties: 54 | address: 55 | description: Address is the address of the interface deprecated 56 | type: string 57 | id: 58 | description: ID is the ID of the NIC 59 | type: string 60 | nodeName: 61 | description: NodeName is the name of the node the interface is attached to 62 | type: string 63 | required: 64 | - id 65 | - nodeName 66 | type: object 67 | status: 68 | description: NetworkInterfaceStatus defines the observed state of NetworkInterface 69 | properties: 70 | address: 71 | description: Address is the address of the interface 72 | type: string 73 | linkName: 74 | description: LinkName is the name of the Interface 75 | type: string 76 | macAddress: 77 | description: MacAddress is the mac address of the interface 78 | type: string 79 | parentCidr: 80 | description: ParentCIDR is the parent cidr of the Address 81 | type: string 82 | type: object 83 | type: object 84 | served: true 85 | storage: true 86 | subresources: 87 | status: {} 88 | status: 89 | acceptedNames: 90 | kind: "" 91 | plural: "" 92 | conditions: [] 93 | storedVersions: [] 94 | -------------------------------------------------------------------------------- /config/crd/bases/vpc.scaleway.com_privatenetworks.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.5.0 8 | creationTimestamp: null 9 | name: privatenetworks.vpc.scaleway.com 10 | spec: 11 | group: vpc.scaleway.com 12 | names: 13 | kind: PrivateNetwork 14 | listKind: PrivateNetworkList 15 | plural: privatenetworks 16 | shortNames: 17 | - pn 18 | - privnet 19 | - privatenet 20 | - privatenetwork 21 | singular: privatenetwork 22 | scope: Cluster 23 | versions: 24 | - additionalPrinterColumns: 25 | - jsonPath: .spec.id 26 | name: id 27 | type: string 28 | - jsonPath: .spec.ipam.type 29 | name: ipam type 30 | type: string 31 | name: v1alpha1 32 | schema: 33 | openAPIV3Schema: 34 | description: PrivateNetwork is the Schema for the privatenetworks API 35 | properties: 36 | apiVersion: 37 | 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' 38 | type: string 39 | kind: 40 | 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' 41 | type: string 42 | metadata: 43 | type: object 44 | spec: 45 | description: PrivateNetworkSpec defines the desired state of PrivateNetwork 46 | properties: 47 | cidr: 48 | description: CIDR is the CIDR of the PrivateNetwork deprecated 49 | type: string 50 | id: 51 | description: ID is the ID of the PrivateNetwork 52 | type: string 53 | ipam: 54 | description: PrivateNetworkIPAM defines the IPAM for the PrivateNetwork 55 | properties: 56 | static: 57 | properties: 58 | availableRanges: 59 | description: AvailableRanges allows to restrict which ranges of addresses should be used when choosing an IP address Defaults to the whole CIDR 60 | items: 61 | type: string 62 | type: array 63 | cidr: 64 | description: CIDR represents the CIDR associated to this private network 65 | type: string 66 | required: 67 | - cidr 68 | type: object 69 | type: 70 | description: IPAMType represents a type of IPAM 71 | enum: 72 | - DHCP 73 | - Static 74 | type: string 75 | required: 76 | - type 77 | type: object 78 | masquerade: 79 | default: true 80 | description: Masquerade represents whether the private network needs to be masqueraded 81 | type: boolean 82 | routes: 83 | description: Routes are the routes injected in the cluster to this PrivateNetwork 84 | items: 85 | description: PrivateNetworkRoute defines a route from the PrivateNetwork 86 | properties: 87 | to: 88 | type: string 89 | via: 90 | type: string 91 | required: 92 | - to 93 | - via 94 | type: object 95 | type: array 96 | zone: 97 | description: Zone is the Zone of the PrivateNetwork Will default to the SCW_DEFAULT_ZONE env variable 98 | type: string 99 | required: 100 | - id 101 | type: object 102 | status: 103 | description: PrivateNetworkStatus defines the observed state of PrivateNetwork 104 | type: object 105 | type: object 106 | served: true 107 | storage: true 108 | subresources: 109 | status: {} 110 | status: 111 | acceptedNames: 112 | kind: "" 113 | plural: "" 114 | conditions: [] 115 | storedVersions: [] 116 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/vpc.scaleway.com_privatenetworks.yaml 6 | - bases/vpc.scaleway.com_networkinterfaces.yaml 7 | # +kubebuilder:scaffold:crdkustomizeresource 8 | 9 | patchesStrategicMerge: 10 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 11 | # patches here are for enabling the conversion webhook for each CRD 12 | #- patches/webhook_in_privatenetworks.yaml 13 | #- patches/webhook_in_networkinterfaces.yaml 14 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 15 | 16 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 17 | # patches here are for enabling the CA injection for each CRD 18 | #- patches/cainjection_in_privatenetworks.yaml 19 | #- patches/cainjection_in_networkinterfaces.yaml 20 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 21 | 22 | # the following config is for teaching kustomize how to do kustomization for CRDs. 23 | configurations: 24 | - kustomizeconfig.yaml 25 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | group: apiextensions.k8s.io 8 | path: spec/conversion/webhookClientConfig/service/name 9 | 10 | namespace: 11 | - kind: CustomResourceDefinition 12 | group: apiextensions.k8s.io 13 | path: spec/conversion/webhookClientConfig/service/namespace 14 | create: false 15 | 16 | varReference: 17 | - path: metadata/annotations 18 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_networkinterfaces.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: networkinterfaces.vpc.scaleway.com 9 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_privatenetworks.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: privatenetworks.vpc.scaleway.com 9 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_networkinterfaces.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: networkinterfaces.vpc.scaleway.com 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_privatenetworks.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1beta1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: privatenetworks.vpc.scaleway.com 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: scaleway-k8s-vpc-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: scaleway-k8s-vpc- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../controller 19 | - ../node 20 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 21 | # crd/kustomization.yaml 22 | #- ../webhook 23 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 24 | #- ../certmanager 25 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 26 | #- ../prometheus 27 | 28 | patchesStrategicMerge: 29 | # Protect the /metrics endpoint by putting it behind auth. 30 | # If you want your controller-manager to expose the /metrics 31 | # endpoint w/o any authn/z, please comment the following line. 32 | #- manager_auth_proxy_patch.yaml 33 | 34 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 35 | # crd/kustomization.yaml 36 | #- manager_webhook_patch.yaml 37 | 38 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 39 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 40 | # 'CERTMANAGER' needs to be enabled to use ca injection 41 | #- webhookcainjection_patch.yaml 42 | 43 | # the following config is for teaching kustomize how to do var substitution 44 | vars: 45 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 46 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 47 | # objref: 48 | # kind: Certificate 49 | # group: cert-manager.io 50 | # version: v1alpha2 51 | # name: serving-cert # this name should match the one in certificate.yaml 52 | # fieldref: 53 | # fieldpath: metadata.namespace 54 | #- name: CERTIFICATE_NAME 55 | # objref: 56 | # kind: Certificate 57 | # group: cert-manager.io 58 | # version: v1alpha2 59 | # name: serving-cert # this name should match the one in certificate.yaml 60 | #- name: SERVICE_NAMESPACE # namespace of the service 61 | # objref: 62 | # kind: Service 63 | # version: v1 64 | # name: webhook-service 65 | # fieldref: 66 | # fieldpath: metadata.namespace 67 | #- name: SERVICE_NAME 68 | # objref: 69 | # kind: Service 70 | # version: v1 71 | # name: webhook-service 72 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | name: https 22 | - name: manager 23 | args: 24 | - "--metrics-addr=127.0.0.1:8080" 25 | - "--enable-leader-election" 26 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1beta1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: mutating-webhook-configuration 7 | annotations: 8 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1beta1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | -------------------------------------------------------------------------------- /config/node/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - node.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: node 7 | newName: sh4d1/scaleway-k8s-vpc-node 8 | -------------------------------------------------------------------------------- /config/node/node.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: DaemonSet 4 | metadata: 5 | name: node 6 | namespace: system 7 | labels: 8 | control-plane: node 9 | spec: 10 | selector: 11 | matchLabels: 12 | control-plane: node 13 | template: 14 | metadata: 15 | labels: 16 | control-plane: node 17 | spec: 18 | serviceAccountName: node 19 | priorityClassName: system-node-critical 20 | hostNetwork: true 21 | containers: 22 | - command: 23 | - /node 24 | image: sh4d1/scaleway-k8s-vpc-node:latest 25 | name: node 26 | env: 27 | - name: NODE_NAME 28 | valueFrom: 29 | fieldRef: 30 | apiVersion: v1 31 | fieldPath: spec.nodeName 32 | resources: 33 | limits: 34 | cpu: 100m 35 | memory: 30Mi 36 | requests: 37 | cpu: 100m 38 | memory: 20Mi 39 | securityContext: 40 | capabilities: 41 | add: 42 | - NET_ADMIN 43 | - SYS_MODULE 44 | privileged: true 45 | volumeMounts: 46 | - mountPath: /run/xtables.lock 47 | name: xtables-lock 48 | terminationGracePeriodSeconds: 10 49 | volumes: 50 | - hostPath: 51 | path: /run/xtables.lock 52 | type: FileOrCreate 53 | name: xtables-lock 54 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | selector: 15 | matchLabels: 16 | control-plane: controller-manager 17 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1beta1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: ["/metrics"] 7 | verbs: ["get"] 8 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | -------------------------------------------------------------------------------- /config/rbac/controller-role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: controller-role 8 | rules: 9 | - apiGroups: 10 | - "" 11 | resources: 12 | - configmaps 13 | verbs: 14 | - create 15 | - get 16 | - list 17 | - patch 18 | - update 19 | - watch 20 | - apiGroups: 21 | - "" 22 | resources: 23 | - nodes 24 | verbs: 25 | - get 26 | - list 27 | - watch 28 | - apiGroups: 29 | - vpc.scaleway.com 30 | resources: 31 | - networkinterfaces 32 | verbs: 33 | - create 34 | - delete 35 | - get 36 | - list 37 | - patch 38 | - update 39 | - watch 40 | - apiGroups: 41 | - vpc.scaleway.com 42 | resources: 43 | - networkinterfaces/status 44 | verbs: 45 | - get 46 | - patch 47 | - update 48 | - apiGroups: 49 | - vpc.scaleway.com 50 | resources: 51 | - privatenetworks 52 | verbs: 53 | - create 54 | - delete 55 | - get 56 | - list 57 | - patch 58 | - update 59 | - watch 60 | - apiGroups: 61 | - vpc.scaleway.com 62 | resources: 63 | - privatenetworks/status 64 | verbs: 65 | - get 66 | - patch 67 | - update 68 | -------------------------------------------------------------------------------- /config/rbac/controller-role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: controller-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: controller-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/controller-sa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - controller-role.yaml 3 | - node-role.yaml 4 | - controller-role_binding.yaml 5 | - node-role_binding.yaml 6 | - controller-sa.yaml 7 | - node-sa.yaml 8 | - leader_election_role.yaml 9 | - leader_election_role_binding.yaml 10 | # Comment the following 4 lines if you want to disable 11 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 12 | # which protects your /metrics endpoint. 13 | #- auth_proxy_service.yaml 14 | #- auth_proxy_role.yaml 15 | #- auth_proxy_role_binding.yaml 16 | #- auth_proxy_client_clusterrole.yaml 17 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - configmaps/status 23 | verbs: 24 | - get 25 | - update 26 | - patch 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | verbs: 32 | - create 33 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/networkinterface_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit networkinterfaces. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: networkinterface-editor-role 6 | rules: 7 | - apiGroups: 8 | - vpc.scaleway.com 9 | resources: 10 | - networkinterfaces 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - vpc.scaleway.com 21 | resources: 22 | - networkinterfaces/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/networkinterface_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view networkinterfaces. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: networkinterface-viewer-role 6 | rules: 7 | - apiGroups: 8 | - vpc.scaleway.com 9 | resources: 10 | - networkinterfaces 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - vpc.scaleway.com 17 | resources: 18 | - networkinterfaces/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/node-role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: node-role 8 | rules: 9 | - apiGroups: 10 | - vpc.scaleway.com 11 | resources: 12 | - networkinterfaces 13 | verbs: 14 | - get 15 | - list 16 | - patch 17 | - watch 18 | - apiGroups: 19 | - vpc.scaleway.com 20 | resources: 21 | - networkinterfaces/status 22 | verbs: 23 | - get 24 | - patch 25 | - apiGroups: 26 | - vpc.scaleway.com 27 | resources: 28 | - privatenetworks 29 | verbs: 30 | - get 31 | - list 32 | - watch 33 | -------------------------------------------------------------------------------- /config/rbac/node-role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: node-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: node-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: node 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/node-sa.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: node 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/rbac/privatenetwork_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit privatenetworks. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: privatenetwork-editor-role 6 | rules: 7 | - apiGroups: 8 | - vpc.scaleway.com 9 | resources: 10 | - privatenetworks 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - vpc.scaleway.com 21 | resources: 22 | - privatenetworks/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/privatenetwork_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view privatenetworks. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: privatenetwork-viewer-role 6 | rules: 7 | - apiGroups: 8 | - vpc.scaleway.com 9 | resources: 10 | - privatenetworks 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - vpc.scaleway.com 17 | resources: 18 | - privatenetworks/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: controller-role 8 | rules: 9 | - apiGroups: 10 | - "" 11 | resources: 12 | - configmaps 13 | verbs: 14 | - create 15 | - get 16 | - list 17 | - patch 18 | - update 19 | - watch 20 | - apiGroups: 21 | - "" 22 | resources: 23 | - nodes 24 | verbs: 25 | - get 26 | - list 27 | - watch 28 | - apiGroups: 29 | - vpc.scaleway.com 30 | resources: 31 | - networkinterfaces 32 | verbs: 33 | - create 34 | - delete 35 | - get 36 | - list 37 | - patch 38 | - update 39 | - watch 40 | - apiGroups: 41 | - vpc.scaleway.com 42 | resources: 43 | - networkinterfaces/status 44 | verbs: 45 | - get 46 | - patch 47 | - update 48 | - apiGroups: 49 | - vpc.scaleway.com 50 | resources: 51 | - privatenetworks 52 | verbs: 53 | - create 54 | - delete 55 | - get 56 | - list 57 | - patch 58 | - update 59 | - watch 60 | - apiGroups: 61 | - vpc.scaleway.com 62 | resources: 63 | - privatenetworks/status 64 | verbs: 65 | - get 66 | - patch 67 | - update 68 | -------------------------------------------------------------------------------- /config/samples/vpc_v1alpha1_privatenetwork.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: vpc.scaleway.com/v1alpha1 2 | kind: PrivateNetwork 3 | metadata: 4 | name: privatenetwork-sample 5 | spec: 6 | id: f8746895-a6bf-4ad2-8b66-ec98041ed4ca 7 | cidr: 192.168.0.0/24 8 | routes: 9 | - to: 1.2.3.4/16 10 | via: 192.168.0.10 11 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | targetPort: 9443 11 | selector: 12 | control-plane: controller-manager 13 | -------------------------------------------------------------------------------- /controllers/helpers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | 6 | instance "github.com/scaleway/scaleway-sdk-go/api/instance/v1" 7 | "github.com/scaleway/scaleway-sdk-go/scw" 8 | corev1 "k8s.io/api/core/v1" 9 | ) 10 | 11 | func getServerFromNode(instanceAPI *instance.API, node *corev1.Node) (*instance.Server, error) { 12 | instanceID := "" 13 | zone := "" 14 | if node.Spec.ProviderID != "" { 15 | providerID := node.Spec.ProviderID 16 | if providerIDRegexp.MatchString(providerID) { 17 | match := providerIDRegexp.FindStringSubmatch(providerID) 18 | 19 | for i, name := range providerIDRegexp.SubexpNames() { 20 | if i != 0 && name != "" { 21 | if match[i] != "" { 22 | switch name { 23 | case regexpUUID: 24 | instanceID = match[i] 25 | case regexpLocalization: 26 | zone = match[i] 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } 33 | 34 | if instanceID != "" { 35 | serverResp, err := instanceAPI.GetServer(&instance.GetServerRequest{ 36 | Zone: scw.Zone(zone), 37 | ServerID: instanceID, 38 | }) 39 | if err == nil { 40 | return serverResp.Server, nil 41 | } 42 | } 43 | 44 | serversListResp, err := instanceAPI.ListServers(&instance.ListServersRequest{ 45 | Zone: scw.Zone(zone), 46 | Name: scw.StringPtr(node.Name), 47 | }) 48 | if err != nil { 49 | return nil, err 50 | } 51 | if len(serversListResp.Servers) != 1 { 52 | return nil, fmt.Errorf("found %d servers with name %s instead of 1", len(serversListResp.Servers), node.Name) 53 | } 54 | return serversListResp.Servers[0], nil 55 | } 56 | -------------------------------------------------------------------------------- /controllers/networkinterface_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "fmt" 23 | "strings" 24 | "time" 25 | 26 | "github.com/go-logr/logr" 27 | goipam "github.com/metal-stack/go-ipam" 28 | instance "github.com/scaleway/scaleway-sdk-go/api/instance/v1" 29 | corev1 "k8s.io/api/core/v1" 30 | apierrors "k8s.io/apimachinery/pkg/api/errors" 31 | "k8s.io/apimachinery/pkg/runtime" 32 | "k8s.io/apimachinery/pkg/types" 33 | "k8s.io/client-go/util/workqueue" 34 | ctrl "sigs.k8s.io/controller-runtime" 35 | "sigs.k8s.io/controller-runtime/pkg/client" 36 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 37 | "sigs.k8s.io/controller-runtime/pkg/event" 38 | "sigs.k8s.io/controller-runtime/pkg/handler" 39 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 40 | "sigs.k8s.io/controller-runtime/pkg/source" 41 | 42 | vpcv1alpha1 "github.com/Sh4d1/scaleway-k8s-vpc/api/v1alpha1" 43 | "github.com/Sh4d1/scaleway-k8s-vpc/internal/constants" 44 | ) 45 | 46 | // NetworkInterfaceReconciler reconciles a NetworkInterface object 47 | type NetworkInterfaceReconciler struct { 48 | client.Client 49 | Log logr.Logger 50 | Scheme *runtime.Scheme 51 | IPAM goipam.Ipamer 52 | InstanceAPI *instance.API 53 | } 54 | 55 | // +kubebuilder:rbac:groups=vpc.scaleway.com,resources=networkinterfaces,verbs=get;list;watch;patch 56 | // +kubebuilder:rbac:groups=vpc.scaleway.com,resources=networkinterfaces/status,verbs=get;patch 57 | // +kubebuilder:rbac:groups=vpc.scaleway.com,resources=privatenetworks,verbs=get;list;watch 58 | // +kubebuilder:rbac:groups="",resources=nodes,verbs=get;list;watch 59 | 60 | func (r *NetworkInterfaceReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 61 | ctx := context.Background() 62 | log := r.Log.WithValues("networkinterface", req.NamespacedName) 63 | 64 | nic := &vpcv1alpha1.NetworkInterface{} 65 | 66 | err := r.Client.Get(ctx, req.NamespacedName, nic) 67 | if err != nil { 68 | log.Error(err, "could not find object") 69 | return ctrl.Result{}, client.IgnoreNotFound(err) 70 | } 71 | 72 | node := corev1.Node{} 73 | err = r.Client.Get(ctx, types.NamespacedName{Name: nic.Spec.NodeName}, &node) 74 | if err != nil && !apierrors.IsNotFound(err) { 75 | log.Error(err, "error getting node") 76 | return ctrl.Result{}, err 77 | } 78 | 79 | nodeDeleted := err != nil && apierrors.IsNotFound(err) 80 | 81 | pn := vpcv1alpha1.PrivateNetwork{} 82 | err = r.Client.Get(ctx, types.NamespacedName{Name: nic.OwnerReferences[0].Name}, &pn) 83 | if err != nil { 84 | log.Error(err, "unable to get private network") 85 | return ctrl.Result{}, err 86 | } 87 | 88 | if nic.ObjectMeta.GetDeletionTimestamp().IsZero() { 89 | if nodeDeleted { 90 | err := r.Client.Delete(ctx, nic) 91 | if err != nil { 92 | log.Error(err, fmt.Sprintf("failed to delete networkInterface %s", nic.Name)) 93 | return ctrl.Result{}, err 94 | } 95 | return ctrl.Result{RequeueAfter: 1 * time.Second}, nil 96 | } 97 | if nic.Status.MacAddress != "" && len(nic.Status.Address) == 0 && pn.Spec.IPAM != nil { 98 | switch pn.Spec.IPAM.Type { 99 | case vpcv1alpha1.IPAMTypeDHCP: 100 | // this case is handled in the node controller 101 | case vpcv1alpha1.IPAMTypeStatic: 102 | if pn.Spec.IPAM.Static == nil { 103 | return ctrl.Result{}, fmt.Errorf("Static CIDR can't be empty on static ipam mode") 104 | } 105 | cidrs := []string{pn.Spec.IPAM.Static.CIDR} 106 | if len(pn.Spec.IPAM.Static.AvailableRanges) != 0 { 107 | cidrs = pn.Spec.IPAM.Static.AvailableRanges 108 | } 109 | 110 | var ip *goipam.IP 111 | var chosenCidr string 112 | 113 | for _, cidr := range cidrs { 114 | prefix, err := r.IPAM.NewPrefix(cidr) 115 | if err != nil { 116 | log.Error(err, "error creating new prefix") 117 | continue 118 | } 119 | ip, err = r.IPAM.AcquireIP(prefix.Cidr) 120 | if err != nil { 121 | log.Error(err, fmt.Sprintf("error acquiring ip for cidr %s", prefix.Cidr)) 122 | continue 123 | } 124 | chosenCidr = prefix.Cidr 125 | break 126 | } 127 | 128 | if ip == nil { 129 | err := fmt.Errorf("could not acquire IP") 130 | log.Error(err, "error while testing all cidrs") 131 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 132 | } 133 | 134 | // TODO have a better idea :D 135 | patch := client.MergeFrom(nic.DeepCopy()) 136 | nic.Status.Address = ip.IP.String() + "/" + strings.Split(pn.Spec.IPAM.Static.CIDR, "/")[1] 137 | nic.Status.ParentCIDR = chosenCidr 138 | err = r.Client.Status().Patch(ctx, nic, patch) 139 | if err != nil { 140 | ipamErr := r.IPAM.ReleaseIPFromPrefix(chosenCidr, strings.Split(nic.Status.Address, "/")[0]) 141 | if ipamErr != nil { 142 | log.Error(ipamErr, fmt.Sprintf("failed to release IP %s", nic.Status.Address)) 143 | } 144 | log.Error(err, fmt.Sprintf("failed to update networkInterface %s", nic.Name)) 145 | return ctrl.Result{}, err 146 | } 147 | default: 148 | return ctrl.Result{}, fmt.Errorf("IPAM type %s is not supported", pn.Spec.IPAM.Type) 149 | } 150 | } 151 | // nothing left to do 152 | return ctrl.Result{}, nil 153 | } 154 | 155 | // nic is deleting 156 | 157 | if controllerutil.ContainsFinalizer(nic, constants.FinalizerName) && nodeDeleted { 158 | patch := client.MergeFrom(nic.DeepCopy()) 159 | controllerutil.RemoveFinalizer(nic, constants.FinalizerName) 160 | err = r.Client.Patch(ctx, nic, patch) 161 | if err != nil { 162 | log.Error(err, fmt.Sprintf("failed to patch networkInterface %s", nic.Name)) 163 | return ctrl.Result{}, err 164 | } 165 | //return ctrl.Result{RequeueAfter: 1 * time.Second}, nil 166 | } 167 | 168 | if !controllerutil.ContainsFinalizer(nic, constants.FinalizerName) { 169 | if pn.Spec.IPAM != nil && pn.Spec.IPAM.Type == vpcv1alpha1.IPAMTypeStatic { 170 | if pn.Spec.IPAM.Static == nil { 171 | return ctrl.Result{}, fmt.Errorf("Static CIDR can't be empty on static ipam mode") 172 | } 173 | 174 | cidr := pn.Spec.IPAM.Static.CIDR 175 | if nic.Status.ParentCIDR != "" { 176 | cidr = nic.Status.ParentCIDR 177 | } 178 | err := r.IPAM.ReleaseIPFromPrefix(cidr, strings.Split(nic.Status.Address, "/")[0]) 179 | if err != nil { 180 | if !errors.As(err, &goipam.NotFoundError{}) { 181 | log.Error(err, fmt.Sprintf("could not delete IP %s from prefix %s", nic.Status.Address, cidr)) 182 | return ctrl.Result{}, err 183 | } 184 | } 185 | } 186 | node := corev1.Node{} 187 | err := r.Client.Get(ctx, types.NamespacedName{Name: nic.Spec.NodeName}, &node) 188 | if err != nil && !apierrors.IsNotFound(err) { 189 | log.Error(err, "error getting node") 190 | return ctrl.Result{}, err 191 | } 192 | if err == nil { 193 | server, err := getServerFromNode(r.InstanceAPI, &node) 194 | if err != nil { 195 | log.Error(err, "error getting server from node") 196 | return ctrl.Result{}, err 197 | } 198 | privateNicID := "" 199 | for _, pnic := range server.PrivateNics { 200 | if pnic.PrivateNetworkID == pn.Spec.ID { 201 | privateNicID = pnic.ID 202 | break 203 | } 204 | } 205 | if privateNicID != "" { 206 | err := r.InstanceAPI.DeletePrivateNIC(&instance.DeletePrivateNICRequest{ 207 | Zone: server.Zone, 208 | PrivateNicID: privateNicID, 209 | ServerID: server.ID, 210 | }) 211 | if err != nil { 212 | log.Error(err, "unable to delete private nic from server") 213 | return ctrl.Result{}, err 214 | } 215 | } 216 | } 217 | 218 | patch := client.MergeFrom(nic.DeepCopy()) 219 | controllerutil.RemoveFinalizer(nic, constants.IPFinalizerName) 220 | err = r.Client.Patch(ctx, nic, patch) 221 | if err != nil { 222 | log.Error(err, fmt.Sprintf("failed to remove finalizer on networkInterface %s", nic.Name)) 223 | return ctrl.Result{}, err 224 | } 225 | } 226 | 227 | return ctrl.Result{}, nil 228 | } 229 | 230 | func (r *NetworkInterfaceReconciler) SetupWithManager(mgr ctrl.Manager) error { 231 | return ctrl.NewControllerManagedBy(mgr). 232 | For(&vpcv1alpha1.NetworkInterface{}). 233 | Watches(&source.Kind{ 234 | Type: &corev1.Node{}, 235 | }, &handler.Funcs{ 236 | DeleteFunc: func(e event.DeleteEvent, q workqueue.RateLimitingInterface) { 237 | nicsList := &vpcv1alpha1.NetworkInterfaceList{} 238 | err := r.Client.List(context.Background(), nicsList, 239 | client.MatchingLabels{ 240 | constants.NodeLabel: e.Meta.GetName(), 241 | }, 242 | ) 243 | if err != nil { 244 | r.Log.Error(err, "unable to sync privatenetwork on node creation") 245 | return 246 | } 247 | for _, nic := range nicsList.Items { 248 | q.Add(reconcile.Request{ 249 | NamespacedName: types.NamespacedName{ 250 | Name: nic.Name, 251 | }, 252 | }) 253 | } 254 | }, 255 | }). 256 | Complete(r) 257 | } 258 | -------------------------------------------------------------------------------- /controllers/privatenetwork_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "fmt" 23 | "regexp" 24 | "strings" 25 | "time" 26 | 27 | "github.com/go-logr/logr" 28 | goipam "github.com/metal-stack/go-ipam" 29 | instance "github.com/scaleway/scaleway-sdk-go/api/instance/v1" 30 | vpc "github.com/scaleway/scaleway-sdk-go/api/vpc/v1" 31 | "github.com/scaleway/scaleway-sdk-go/scw" 32 | corev1 "k8s.io/api/core/v1" 33 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 | "k8s.io/apimachinery/pkg/runtime" 35 | "k8s.io/apimachinery/pkg/types" 36 | "k8s.io/client-go/util/workqueue" 37 | ctrl "sigs.k8s.io/controller-runtime" 38 | "sigs.k8s.io/controller-runtime/pkg/client" 39 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 40 | "sigs.k8s.io/controller-runtime/pkg/event" 41 | "sigs.k8s.io/controller-runtime/pkg/handler" 42 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 43 | "sigs.k8s.io/controller-runtime/pkg/source" 44 | 45 | vpcv1alpha1 "github.com/Sh4d1/scaleway-k8s-vpc/api/v1alpha1" 46 | "github.com/Sh4d1/scaleway-k8s-vpc/internal/constants" 47 | ) 48 | 49 | const ( 50 | regexpProduct = "product" 51 | regexpLocalization = "localization" 52 | regexpUUID = "uuid" 53 | ) 54 | 55 | var ( 56 | providerIDRegexp = regexp.MustCompile(fmt.Sprintf("scaleway://((?P<%s>.*?)/(?P<%s>.*?)/(?P<%s>.*)|(?P<%s>.*))", regexpProduct, regexpLocalization, regexpUUID, regexpUUID)) 57 | 58 | // RequeueDuration is the default requeue duration 59 | RequeueDuration time.Duration = time.Second * 30 60 | ) 61 | 62 | // PrivateNetworkReconciler reconciles a PrivateNetwork object 63 | type PrivateNetworkReconciler struct { 64 | client.Client 65 | Log logr.Logger 66 | Scheme *runtime.Scheme 67 | IPAM goipam.Ipamer 68 | InstanceAPI *instance.API 69 | VpcAPI *vpc.API 70 | } 71 | 72 | // +kubebuilder:rbac:groups=vpc.scaleway.com,resources=privatenetworks,verbs=get;list;watch;create;update;patch;delete 73 | // +kubebuilder:rbac:groups=vpc.scaleway.com,resources=privatenetworks/status,verbs=get;update;patch 74 | // +kubebuilder:rbac:groups=vpc.scaleway.com,resources=networkinterfaces,verbs=get;list;watch;create;update;patch;delete 75 | // +kubebuilder:rbac:groups=vpc.scaleway.com,resources=networkinterfaces/status,verbs=get;update 76 | // +kubebuilder:rbac:groups="",resources=nodes,verbs=get;list;watch 77 | // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch 78 | 79 | func (r *PrivateNetworkReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 80 | ctx := context.Background() 81 | log := r.Log.WithValues("privatenetwork", req.NamespacedName) 82 | 83 | pn := &vpcv1alpha1.PrivateNetwork{} 84 | 85 | err := r.Get(ctx, req.NamespacedName, pn) 86 | if err != nil { 87 | log.Error(err, "could not find object") 88 | return ctrl.Result{}, client.IgnoreNotFound(err) 89 | } 90 | 91 | if pn.Spec.CIDR != "" { 92 | res, err := r.ReconcileDeprecated(req) 93 | return res, err 94 | } 95 | 96 | if !pn.ObjectMeta.GetDeletionTimestamp().IsZero() { 97 | // deletion 98 | if controllerutil.ContainsFinalizer(pn, constants.FinalizerName) { 99 | nicsList := &vpcv1alpha1.NetworkInterfaceList{} 100 | err = r.Client.List(ctx, nicsList, 101 | client.MatchingLabels{ 102 | constants.PrivateNetworkLabel: pn.Name, 103 | }, 104 | ) 105 | if err != nil { 106 | log.Error(err, fmt.Sprintf("could not list NetworkInterface for privateNetwork %s", pn.Name)) 107 | return ctrl.Result{}, err 108 | } 109 | 110 | for _, nic := range nicsList.Items { 111 | if nic.ObjectMeta.GetDeletionTimestamp().IsZero() { 112 | err := r.Client.Delete(ctx, &nic) 113 | if err != nil { 114 | log.Error(err, fmt.Sprintf("failed to delete networkInterface %s", nic.Name)) 115 | return ctrl.Result{}, err 116 | } 117 | } 118 | } 119 | if len(nicsList.Items) == 0 { 120 | _, err = r.IPAM.DeletePrefix(pn.Spec.CIDR) 121 | if err != nil { 122 | if !errors.As(err, &goipam.NotFoundError{}) { 123 | log.Error(err, "failed to delete PrivateNetwork prefix") 124 | return ctrl.Result{}, err 125 | } 126 | } 127 | patch := client.MergeFrom(pn.DeepCopy()) 128 | controllerutil.RemoveFinalizer(pn, constants.FinalizerName) 129 | if err := r.Patch(ctx, pn, patch); err != nil { 130 | log.Error(err, "failed to add finalizer") 131 | return ctrl.Result{}, err 132 | } 133 | return ctrl.Result{}, nil 134 | } 135 | return ctrl.Result{RequeueAfter: RequeueDuration}, nil 136 | } 137 | return ctrl.Result{}, nil 138 | } 139 | 140 | if !controllerutil.ContainsFinalizer(pn, constants.FinalizerName) { 141 | patch := client.MergeFrom(pn.DeepCopy()) 142 | controllerutil.AddFinalizer(pn, constants.FinalizerName) 143 | if err := r.Patch(ctx, pn, patch); err != nil { 144 | log.Error(err, "failed to add finalizer") 145 | return ctrl.Result{}, err 146 | } 147 | } 148 | 149 | _, err = r.VpcAPI.GetPrivateNetwork(&vpc.GetPrivateNetworkRequest{ 150 | Zone: scw.Zone(pn.Spec.Zone), 151 | PrivateNetworkID: pn.Spec.ID, 152 | }) 153 | if err != nil { 154 | log.Error(err, "error getting private network from api") 155 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 156 | } 157 | 158 | nodesList := &corev1.NodeList{} 159 | err = r.Client.List(ctx, nodesList) 160 | if err != nil { 161 | log.Error(err, "could not list nodes") 162 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 163 | } 164 | 165 | for _, node := range nodesList.Items { 166 | nicsList := &vpcv1alpha1.NetworkInterfaceList{} 167 | err = r.Client.List(ctx, nicsList, 168 | client.MatchingLabels{ 169 | constants.PrivateNetworkLabel: pn.Name, 170 | constants.NodeLabel: node.Name, 171 | }, 172 | ) 173 | if err != nil { 174 | log.Error(err, fmt.Sprintf("could not list NetworkInterface for node %s and privateNetwork %s", node.Name, pn.Name)) 175 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 176 | } 177 | 178 | server, err := getServerFromNode(r.InstanceAPI, &node) 179 | if err != nil { 180 | log.Error(err, fmt.Sprintf("could not get scaleway server from node %s", node.Name)) 181 | break 182 | } 183 | 184 | var privateNIC *instance.PrivateNIC 185 | for _, pnic := range server.PrivateNics { 186 | if pnic.PrivateNetworkID == pn.Spec.ID { 187 | privateNIC = pnic 188 | break 189 | } 190 | } 191 | if privateNIC == nil { 192 | pnicResp, err := r.InstanceAPI.CreatePrivateNIC(&instance.CreatePrivateNICRequest{ 193 | Zone: server.Zone, 194 | PrivateNetworkID: pn.Spec.ID, 195 | ServerID: server.ID, 196 | }) 197 | if err != nil { 198 | log.Error(err, fmt.Sprintf("unable to create private on server %s", server.ID)) 199 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 200 | } 201 | privateNIC = pnicResp.PrivateNic 202 | } 203 | 204 | if len(nicsList.Items) > 1 { 205 | log.Error(fmt.Errorf("node %s have %d networkInterfaces instead of at most one", node.Name, len(nicsList.Items)), "could not handle node") 206 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 207 | } 208 | 209 | if len(nicsList.Items) == 0 { 210 | nic, err := r.constructNetworkInterfaceForPrivateNetwork(pn, node.Name) 211 | if err != nil { 212 | log.Error(err, "unable to construct networkInterface from privateNetwork") 213 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 214 | } 215 | 216 | nic.Spec.ID = privateNIC.ID 217 | err = r.Client.Create(ctx, nic) 218 | if err != nil { 219 | log.Error(err, "could not create networkInterface") 220 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 221 | } 222 | patch := client.MergeFrom(nic.DeepCopy()) 223 | nic.Status.MacAddress = privateNIC.MacAddress 224 | err = r.Client.Status().Patch(ctx, nic, patch) 225 | if err != nil { 226 | log.Error(err, "could not patch networkInterface status") 227 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 228 | } 229 | log.Info(fmt.Sprintf("Successfully created networkInterface %s on node %s", nic.Name, node.Name)) 230 | } 231 | } 232 | 233 | return ctrl.Result{}, nil 234 | } 235 | 236 | func (r *PrivateNetworkReconciler) ReconcileDeprecated(req ctrl.Request) (ctrl.Result, error) { 237 | ctx := context.Background() 238 | log := r.Log.WithValues("privatenetwork", req.NamespacedName) 239 | 240 | pn := &vpcv1alpha1.PrivateNetwork{} 241 | 242 | err := r.Get(ctx, req.NamespacedName, pn) 243 | if err != nil { 244 | log.Error(err, "could not find object") 245 | return ctrl.Result{}, client.IgnoreNotFound(err) 246 | } 247 | 248 | prefix, err := r.IPAM.NewPrefix(pn.Spec.CIDR) 249 | if err != nil { 250 | log.Error(err, "error creating new prefix") 251 | return ctrl.Result{}, err 252 | } 253 | 254 | if !pn.ObjectMeta.GetDeletionTimestamp().IsZero() { 255 | // deletion 256 | if controllerutil.ContainsFinalizer(pn, constants.FinalizerName) { 257 | nicsList := &vpcv1alpha1.NetworkInterfaceList{} 258 | err = r.Client.List(ctx, nicsList, 259 | client.MatchingLabels{ 260 | constants.PrivateNetworkLabel: pn.Name, 261 | }, 262 | ) 263 | if err != nil { 264 | log.Error(err, fmt.Sprintf("could not list NetworkInterface for privateNetwork %s", pn.Name)) 265 | return ctrl.Result{}, err 266 | } 267 | 268 | for _, nic := range nicsList.Items { 269 | if nic.ObjectMeta.GetDeletionTimestamp().IsZero() { 270 | err := r.Client.Delete(ctx, &nic) 271 | if err != nil { 272 | log.Error(err, fmt.Sprintf("failed to delete networkInterface %s", nic.Name)) 273 | return ctrl.Result{}, err 274 | } 275 | } 276 | } 277 | if len(nicsList.Items) == 0 { 278 | _, err = r.IPAM.DeletePrefix(pn.Spec.CIDR) 279 | if err != nil { 280 | if !errors.As(err, &goipam.NotFoundError{}) { 281 | log.Error(err, "failed to delete PrivateNetwork prefix") 282 | return ctrl.Result{}, err 283 | } 284 | } 285 | patch := client.MergeFrom(pn.DeepCopy()) 286 | controllerutil.RemoveFinalizer(pn, constants.FinalizerName) 287 | if err := r.Patch(ctx, pn, patch); err != nil { 288 | log.Error(err, "failed to add finalizer") 289 | return ctrl.Result{}, err 290 | } 291 | return ctrl.Result{}, nil 292 | } 293 | return ctrl.Result{RequeueAfter: RequeueDuration}, nil 294 | } 295 | return ctrl.Result{}, nil 296 | } 297 | 298 | if !controllerutil.ContainsFinalizer(pn, constants.FinalizerName) { 299 | patch := client.MergeFrom(pn.DeepCopy()) 300 | controllerutil.AddFinalizer(pn, constants.FinalizerName) 301 | if err := r.Patch(ctx, pn, patch); err != nil { 302 | log.Error(err, "failed to add finalizer") 303 | return ctrl.Result{}, err 304 | } 305 | } 306 | 307 | _, err = r.VpcAPI.GetPrivateNetwork(&vpc.GetPrivateNetworkRequest{ 308 | Zone: scw.Zone(pn.Spec.Zone), 309 | PrivateNetworkID: pn.Spec.ID, 310 | }) 311 | if err != nil { 312 | log.Error(err, "error getting private network from api") 313 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 314 | } 315 | 316 | nodesList := &corev1.NodeList{} 317 | err = r.Client.List(ctx, nodesList) 318 | if err != nil { 319 | log.Error(err, "could not list nodes") 320 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 321 | } 322 | 323 | for _, node := range nodesList.Items { 324 | nicsList := &vpcv1alpha1.NetworkInterfaceList{} 325 | err = r.Client.List(ctx, nicsList, 326 | client.MatchingLabels{ 327 | constants.PrivateNetworkLabel: pn.Name, 328 | constants.NodeLabel: node.Name, 329 | }, 330 | ) 331 | if err != nil { 332 | log.Error(err, fmt.Sprintf("could not list NetworkInterface for node %s and privateNetwork %s", node.Name, pn.Name)) 333 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 334 | } 335 | 336 | server, err := getServerFromNode(r.InstanceAPI, &node) 337 | if err != nil { 338 | log.Error(err, fmt.Sprintf("could not get scaleway server from node %s", node.Name)) 339 | break 340 | } 341 | 342 | var privateNIC *instance.PrivateNIC 343 | for _, pnic := range server.PrivateNics { 344 | if pnic.PrivateNetworkID == pn.Spec.ID { 345 | privateNIC = pnic 346 | break 347 | } 348 | } 349 | if privateNIC == nil { 350 | pnicResp, err := r.InstanceAPI.CreatePrivateNIC(&instance.CreatePrivateNICRequest{ 351 | Zone: server.Zone, 352 | PrivateNetworkID: pn.Spec.ID, 353 | ServerID: server.ID, 354 | }) 355 | if err != nil { 356 | log.Error(err, fmt.Sprintf("unable to create private on server %s", server.ID)) 357 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 358 | } 359 | privateNIC = pnicResp.PrivateNic 360 | } 361 | 362 | if len(nicsList.Items) > 1 { 363 | log.Error(fmt.Errorf("node %s have %d networkInterfaces instead of at most one", node.Name, len(nicsList.Items)), "could not handle node") 364 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 365 | } 366 | 367 | if len(nicsList.Items) == 0 { 368 | nic, err := r.constructNetworkInterfaceForPrivateNetwork(pn, node.Name) 369 | if err != nil { 370 | log.Error(err, "unable to construct networkInterface from privateNetwork") 371 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 372 | } 373 | ip, err := r.IPAM.AcquireIP(prefix.Cidr) 374 | if err != nil { 375 | log.Error(err, fmt.Sprintf("error acquiring ip for cidr %s", prefix.Cidr)) 376 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 377 | } 378 | 379 | // TODO have a better idea :D 380 | nic.Spec.Address = ip.IP.String() + "/" + strings.Split(prefix.Cidr, "/")[1] 381 | nic.Spec.ID = privateNIC.ID 382 | err = r.Client.Create(ctx, nic) 383 | if err != nil { 384 | log.Error(err, "could not create networkInterface") 385 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 386 | } 387 | patch := client.MergeFrom(nic.DeepCopy()) 388 | nic.Status.MacAddress = privateNIC.MacAddress 389 | err = r.Client.Status().Patch(ctx, nic, patch) 390 | if err != nil { 391 | log.Error(err, "could not patch networkInterface status") 392 | return ctrl.Result{RequeueAfter: RequeueDuration}, err 393 | } 394 | log.Info(fmt.Sprintf("Successfully created networkInterface %s on node %s", nic.Name, node.Name)) 395 | } 396 | } 397 | 398 | return ctrl.Result{}, nil 399 | } 400 | 401 | func (r *PrivateNetworkReconciler) constructNetworkInterfaceForPrivateNetwork(pn *vpcv1alpha1.PrivateNetwork, nodeName string) (*vpcv1alpha1.NetworkInterface, error) { 402 | nic := &vpcv1alpha1.NetworkInterface{ 403 | ObjectMeta: metav1.ObjectMeta{ 404 | Labels: make(map[string]string), 405 | Annotations: make(map[string]string), 406 | GenerateName: pn.Name + "-", 407 | }, 408 | Spec: vpcv1alpha1.NetworkInterfaceSpec{ 409 | NodeName: nodeName, 410 | }, 411 | } 412 | for k, v := range pn.Annotations { 413 | nic.Annotations[k] = v 414 | } 415 | for k, v := range pn.Labels { 416 | nic.Labels[k] = v 417 | } 418 | nic.Labels[constants.PrivateNetworkLabel] = pn.Name 419 | nic.Labels[constants.NodeLabel] = nodeName 420 | if err := ctrl.SetControllerReference(pn, nic, r.Scheme); err != nil { 421 | return nil, err 422 | } 423 | controllerutil.AddFinalizer(nic, constants.FinalizerName) 424 | controllerutil.AddFinalizer(nic, constants.IPFinalizerName) 425 | 426 | return nic, nil 427 | } 428 | 429 | func (r *PrivateNetworkReconciler) SetupWithManager(mgr ctrl.Manager) error { 430 | return ctrl.NewControllerManagedBy(mgr). 431 | For(&vpcv1alpha1.PrivateNetwork{}). 432 | Owns(&vpcv1alpha1.NetworkInterface{}). 433 | Watches(&source.Kind{ 434 | Type: &corev1.Node{}, 435 | }, &handler.Funcs{ 436 | CreateFunc: func(e event.CreateEvent, q workqueue.RateLimitingInterface) { 437 | pnsList := &vpcv1alpha1.PrivateNetworkList{} 438 | err := r.Client.List(context.Background(), pnsList) 439 | if err != nil { 440 | r.Log.Error(err, "unable to sync privatenetwork on node creation") 441 | return 442 | } 443 | for _, pn := range pnsList.Items { 444 | q.Add(reconcile.Request{ 445 | NamespacedName: types.NamespacedName{ 446 | Name: pn.Name, 447 | }, 448 | }) 449 | } 450 | }, 451 | }). 452 | Complete(r) 453 | } 454 | -------------------------------------------------------------------------------- /controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | "k8s.io/client-go/kubernetes/scheme" 26 | "k8s.io/client-go/rest" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest" 29 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 30 | logf "sigs.k8s.io/controller-runtime/pkg/log" 31 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 32 | 33 | vpcv1alpha1 "github.com/Sh4d1/scaleway-k8s-vpc/api/v1alpha1" 34 | // +kubebuilder:scaffold:imports 35 | ) 36 | 37 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 38 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 39 | 40 | var cfg *rest.Config 41 | var k8sClient client.Client 42 | var testEnv *envtest.Environment 43 | 44 | func TestAPIs(t *testing.T) { 45 | RegisterFailHandler(Fail) 46 | 47 | RunSpecsWithDefaultAndCustomReporters(t, 48 | "Controller Suite", 49 | []Reporter{printer.NewlineReporter{}}) 50 | } 51 | 52 | var _ = BeforeSuite(func(done Done) { 53 | logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) 54 | 55 | By("bootstrapping test environment") 56 | testEnv = &envtest.Environment{ 57 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 58 | } 59 | 60 | var err error 61 | cfg, err = testEnv.Start() 62 | Expect(err).ToNot(HaveOccurred()) 63 | Expect(cfg).ToNot(BeNil()) 64 | 65 | err = vpcv1alpha1.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | err = vpcv1alpha1.AddToScheme(scheme.Scheme) 69 | Expect(err).NotTo(HaveOccurred()) 70 | 71 | // +kubebuilder:scaffold:scheme 72 | 73 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 74 | Expect(err).ToNot(HaveOccurred()) 75 | Expect(k8sClient).ToNot(BeNil()) 76 | 77 | close(done) 78 | }, 60) 79 | 80 | var _ = AfterSuite(func() { 81 | By("tearing down the test environment") 82 | err := testEnv.Stop() 83 | Expect(err).ToNot(HaveOccurred()) 84 | }) 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Sh4d1/scaleway-k8s-vpc 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/coreos/go-iptables v0.5.0 7 | github.com/go-logr/logr v0.1.0 8 | github.com/metal-stack/go-ipam v1.8.1 9 | github.com/onsi/ginkgo v1.12.1 10 | github.com/onsi/gomega v1.10.1 11 | github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210223165440-c65ae3540d44 12 | github.com/vishvananda/netlink v1.1.0 13 | google.golang.org/appengine v1.6.6 // indirect 14 | k8s.io/api v0.18.6 15 | k8s.io/apimachinery v0.18.6 16 | k8s.io/client-go v0.18.6 17 | k8s.io/klog v1.0.0 18 | sigs.k8s.io/controller-runtime v0.6.4 19 | ) 20 | 21 | replace github.com/coreos/go-iptables => github.com/Sh4d1/go-iptables v0.5.1-0.20210224084650-91aadf86de0a 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= 4 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 5 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= 6 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 7 | github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= 8 | github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= 9 | github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= 10 | github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 11 | github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 12 | github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= 13 | github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= 16 | github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= 17 | github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA= 18 | github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= 19 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 20 | github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 21 | github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 22 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 23 | github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 24 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 25 | github.com/Sh4d1/go-iptables v0.5.1-0.20210224084650-91aadf86de0a h1:fF9qAVl5kt+Ug96EzuysxsuVqW0+dQYKc3zP9wBBxvk= 26 | github.com/Sh4d1/go-iptables v0.5.1-0.20210224084650-91aadf86de0a/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= 27 | github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= 28 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 29 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 30 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= 31 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 32 | github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 33 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 34 | github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= 35 | github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= 36 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 37 | github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= 38 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 39 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 40 | github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 41 | github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= 42 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 43 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 44 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 45 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= 46 | github.com/containerd/containerd v1.4.1 h1:pASeJT3R3YyVn+94qEPk0SnU1OQ20Jd/T+SPKy9xehY= 47 | github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= 48 | github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= 49 | github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= 50 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 51 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 52 | github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 53 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 54 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 55 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 56 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 57 | github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 58 | github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 59 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 60 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 61 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 62 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 63 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 64 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 65 | github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= 66 | github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= 67 | github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE= 68 | github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 69 | github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 70 | github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible h1:SiUATuP//KecDjpOK2tvZJgeScYAklvyjfK8JZlU6fo= 71 | github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 72 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 73 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 74 | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 75 | github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= 76 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 77 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 78 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 79 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 80 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 81 | github.com/dvyukov/go-fuzz v0.0.0-20201127111758-49e582c6c23d/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= 82 | github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 83 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 84 | github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 85 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 86 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 87 | github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 88 | github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 89 | github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= 90 | github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 91 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 92 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 93 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 94 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 95 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 96 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 97 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 98 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= 99 | github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 100 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 101 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 102 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 103 | github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= 104 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 105 | github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= 106 | github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= 107 | github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= 108 | github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 109 | github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 110 | github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= 111 | github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= 112 | github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 113 | github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 114 | github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= 115 | github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 116 | github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 117 | github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 118 | github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 119 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 120 | github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= 121 | github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 122 | github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 123 | github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= 124 | github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= 125 | github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 126 | github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 127 | github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 128 | github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= 129 | github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= 130 | github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= 131 | github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= 132 | github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= 133 | github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= 134 | github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 135 | github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 136 | github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= 137 | github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= 138 | github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 139 | github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 140 | github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= 141 | github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= 142 | github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= 143 | github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 144 | github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 145 | github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 146 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 147 | github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= 148 | github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= 149 | github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= 150 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 151 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 152 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 153 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= 154 | github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= 155 | github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 156 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 157 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 158 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 159 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 160 | github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 161 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 162 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 163 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 164 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 165 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 166 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= 167 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 168 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 169 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 170 | github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 171 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 172 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 173 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 174 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 175 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 176 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 177 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 178 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 179 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 180 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 181 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 182 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 183 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 184 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 185 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 186 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 187 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= 188 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 189 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 190 | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= 191 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 192 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 193 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 194 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 195 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 196 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= 197 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 198 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 199 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 200 | github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 201 | github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= 202 | github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= 203 | github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= 204 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= 205 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 206 | github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= 207 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 208 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 209 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 210 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 211 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 212 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 213 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 214 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 215 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 216 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 217 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 218 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 219 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 220 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 221 | github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= 222 | github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 223 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 224 | github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= 225 | github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= 226 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 227 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 228 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 229 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 230 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 231 | github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= 232 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 233 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 234 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 235 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 236 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 237 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 238 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 239 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 240 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 241 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 242 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 243 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 244 | github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 245 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 246 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 247 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 248 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 249 | github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= 250 | github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 251 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 252 | github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 253 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 254 | github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 255 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 256 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 257 | github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= 258 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 259 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 260 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 261 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 262 | github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= 263 | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 264 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 265 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 266 | github.com/metal-stack/go-ipam v1.8.1 h1:s+nlV0SjAE6Xx+jI9jplDddSllb3WtL8mpwC5zGXKsY= 267 | github.com/metal-stack/go-ipam v1.8.1/go.mod h1:Hf+BjZGraDarRfpGmfO1MvsSTpYNyAazVwdtwTwJ7Q0= 268 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 269 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 270 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 271 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 272 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 273 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 274 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 275 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 276 | github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c h1:nXxl5PrvVm2L/wCy8dQu6DMTwH4oIuGN8GJDAlqDdVE= 277 | github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 278 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 279 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 280 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 281 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 282 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 283 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 284 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 285 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 286 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 287 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 288 | github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 289 | github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= 290 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 291 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 292 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 293 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 294 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 295 | github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= 296 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 297 | github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= 298 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 299 | github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= 300 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 301 | github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= 302 | github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 303 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 304 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 305 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 306 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 307 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 308 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 309 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 310 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 311 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 312 | github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= 313 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 314 | github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= 315 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 316 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 317 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 318 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 319 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 320 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 321 | github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= 322 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 323 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 324 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 325 | github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI= 326 | github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 327 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 328 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 329 | github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210223165440-c65ae3540d44 h1:3egqo0Vut6daANFm7tOXdNAa8v5/uLU+sgCJrc88Meo= 330 | github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210223165440-c65ae3540d44/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= 331 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 332 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 333 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 334 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 335 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 336 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 337 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 338 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 339 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 340 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 341 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 342 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 343 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 344 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 345 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 346 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 347 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 348 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 349 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 350 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 351 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 352 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 353 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 354 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 355 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 356 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 357 | github.com/testcontainers/testcontainers-go v0.9.0 h1:ZyftCfROjGrKlxk3MOUn2DAzWrUtzY/mj17iAkdUIvI= 358 | github.com/testcontainers/testcontainers-go v0.9.0/go.mod h1:b22BFXhRbg4PJmeMVWh6ftqjyZHgiIl3w274e9r3C2E= 359 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 360 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 361 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 362 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 363 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 364 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 365 | github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= 366 | github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= 367 | github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= 368 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= 369 | github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= 370 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 371 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 372 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 373 | go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= 374 | go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 375 | go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 376 | go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 377 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 378 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 379 | go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= 380 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 381 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 382 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 383 | go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= 384 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 385 | go4.org/intern v0.0.0-20210108033219-3eb7198706b2 h1:VFTf+jjIgsldaz/Mr00VaCSswHJrI2hIjQygE/W4IMg= 386 | go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= 387 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= 388 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063 h1:1tk03FUNpulq2cuWpXZWj649rwJpk0d20rxWiopKRmc= 389 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= 390 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 391 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 392 | golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 393 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 394 | golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 395 | golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 396 | golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 397 | golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 398 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= 399 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 400 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 401 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 402 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 403 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 404 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 405 | golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 406 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 407 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 408 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 409 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 410 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 411 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 412 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 413 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 414 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 415 | golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 416 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 417 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 418 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 419 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 420 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 421 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 422 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 423 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 424 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= 425 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 426 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 427 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 428 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= 429 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 430 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 431 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 432 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 433 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 434 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 435 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 436 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 437 | golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 438 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 439 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 440 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 441 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 442 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 443 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 444 | golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 445 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 446 | golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 447 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 448 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 449 | golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 450 | golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 451 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 452 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 453 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 454 | golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 455 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 456 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 457 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 458 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 459 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 460 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= 461 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 462 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 463 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= 464 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 465 | golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 466 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 467 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 468 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 469 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 470 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 471 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 472 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 473 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 474 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 475 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 476 | golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 477 | golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 478 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 479 | golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 480 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 481 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 482 | golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 483 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 484 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 485 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 486 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 487 | golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 488 | golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 489 | golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 490 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 491 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 492 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 493 | gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= 494 | gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= 495 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 496 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 497 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 498 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 499 | google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= 500 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 501 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 502 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 503 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 504 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 505 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 506 | google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= 507 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 508 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 509 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 510 | google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= 511 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 512 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 513 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 514 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 515 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 516 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 517 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= 518 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 519 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 520 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 521 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 522 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 523 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 524 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 525 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 526 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 527 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 528 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 529 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 530 | gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 531 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 532 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 533 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 534 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 535 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 536 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 537 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 538 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 539 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 540 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 541 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 542 | gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90= 543 | gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= 544 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 545 | honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 546 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 547 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 548 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 549 | inet.af/netaddr v0.0.0-20210129021658-06debf945877 h1:kiGwZoOS5kyefrTgIAvPzyfTuVfensOlU73GJN9OQKY= 550 | inet.af/netaddr v0.0.0-20210129021658-06debf945877/go.mod h1:I2i9ONCXRZDnG1+7O8fSuYzjcPxHQXrIfzD/IkR87x4= 551 | k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE= 552 | k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= 553 | k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo= 554 | k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M= 555 | k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag= 556 | k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= 557 | k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg= 558 | k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw= 559 | k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= 560 | k8s.io/code-generator v0.18.6/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= 561 | k8s.io/component-base v0.18.6/go.mod h1:knSVsibPR5K6EW2XOjEHik6sdU5nCvKMrzMt2D4In14= 562 | k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 563 | k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 564 | k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 565 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 566 | k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= 567 | k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 568 | k8s.io/klog/v2 v2.0.0 h1:Foj74zO6RbjjP4hBEKjnYtjjAhGg4jNynUdYF6fJrok= 569 | k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= 570 | k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= 571 | k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= 572 | k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 573 | k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE= 574 | k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 575 | sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= 576 | sigs.k8s.io/controller-runtime v0.6.4 h1:4013CKsBs5bEqo+LevzDett+LLxag/FjQWG94nVZ/9g= 577 | sigs.k8s.io/controller-runtime v0.6.4/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY= 578 | sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= 579 | sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= 580 | sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= 581 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 582 | sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= 583 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 584 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /internal/constants/constants.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | const ( 4 | // IPFinalizerName is the name of the IP finalizer 5 | IPFinalizerName = "scaleway.com/finalizer-ip" 6 | 7 | // FinalizerName is the name of the finalizer 8 | FinalizerName = "scaleway.com/finalizer" 9 | 10 | // PrivateNetworkLabel is the private network label 11 | PrivateNetworkLabel = "private-network" 12 | 13 | // NodeLabel is the node label 14 | NodeLabel = "node" 15 | ) 16 | -------------------------------------------------------------------------------- /nodes/networkinterface_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package nodes 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "net" 23 | "time" 24 | 25 | "github.com/coreos/go-iptables/iptables" 26 | "github.com/go-logr/logr" 27 | instance "github.com/scaleway/scaleway-sdk-go/api/instance/v1" 28 | "github.com/vishvananda/netlink" 29 | "k8s.io/apimachinery/pkg/runtime" 30 | "k8s.io/apimachinery/pkg/types" 31 | "k8s.io/client-go/util/workqueue" 32 | ctrl "sigs.k8s.io/controller-runtime" 33 | "sigs.k8s.io/controller-runtime/pkg/client" 34 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 35 | "sigs.k8s.io/controller-runtime/pkg/event" 36 | "sigs.k8s.io/controller-runtime/pkg/handler" 37 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 38 | "sigs.k8s.io/controller-runtime/pkg/source" 39 | 40 | vpcv1alpha1 "github.com/Sh4d1/scaleway-k8s-vpc/api/v1alpha1" 41 | "github.com/Sh4d1/scaleway-k8s-vpc/internal/constants" 42 | "github.com/Sh4d1/scaleway-k8s-vpc/pkg/nics" 43 | ) 44 | 45 | // NetworkInterfaceReconciler reconciles a NetworkInterface object (part running on all nodes) 46 | type NetworkInterfaceReconciler struct { 47 | client.Client 48 | Log logr.Logger 49 | Scheme *runtime.Scheme 50 | MetadataAPI *instance.MetadataAPI 51 | NodeName string 52 | NICs *nics.NICs 53 | } 54 | 55 | // +kubebuilder:rbac:groups=vpc.scaleway.com,resources=networkinterfaces,verbs=get;list;watch;patch 56 | // +kubebuilder:rbac:groups=vpc.scaleway.com,resources=networkinterfaces/status,verbs=get;patch 57 | // +kubebuilder:rbac:groups=vpc.scaleway.com,resources=privatenetworks,verbs=get;list;watch 58 | 59 | func (r *NetworkInterfaceReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 60 | ctx := context.Background() 61 | log := r.Log.WithValues("networkinterface", req.NamespacedName) 62 | 63 | nic := &vpcv1alpha1.NetworkInterface{} 64 | 65 | err := r.Client.Get(ctx, req.NamespacedName, nic) 66 | if err != nil { 67 | log.Error(err, "could not find object") 68 | return ctrl.Result{}, client.IgnoreNotFound(err) 69 | } 70 | 71 | if nic.Spec.NodeName != r.NodeName { 72 | return ctrl.Result{}, nil 73 | } 74 | 75 | pnet := vpcv1alpha1.PrivateNetwork{} 76 | err = r.Client.Get(ctx, types.NamespacedName{Name: nic.OwnerReferences[0].Name}, &pnet) 77 | if err != nil { 78 | log.Error(err, "unable to get private network") 79 | return ctrl.Result{}, err 80 | } 81 | 82 | if nic.Status.MacAddress == "" { 83 | return ctrl.Result{RequeueAfter: time.Second * 1}, nil 84 | } 85 | 86 | if !nic.ObjectMeta.GetDeletionTimestamp().IsZero() { 87 | if controllerutil.ContainsFinalizer(nic, constants.FinalizerName) { 88 | 89 | if pnet.Spec.IPAM == nil { 90 | err := r.NICs.TearDownStaticLink(nic.Status.MacAddress, nic.Spec.Address) 91 | if err != nil { 92 | log.Error(err, "unable to configure link") 93 | return ctrl.Result{}, err 94 | } 95 | } else { 96 | switch pnet.Spec.IPAM.Type { 97 | case vpcv1alpha1.IPAMTypeStatic: 98 | err := r.NICs.TearDownStaticLink(nic.Status.MacAddress, nic.Status.Address) 99 | if err != nil { 100 | log.Error(err, "unable to configure link") 101 | return ctrl.Result{}, err 102 | } 103 | case vpcv1alpha1.IPAMTypeDHCP: 104 | err := r.NICs.TearDownDHCPLink(nic.Status.MacAddress) 105 | if err != nil { 106 | log.Error(err, "unable to configure link") 107 | return ctrl.Result{}, err 108 | } 109 | default: 110 | return ctrl.Result{}, fmt.Errorf("IPAM type %s not supported", pnet.Spec.IPAM.Type) 111 | } 112 | } 113 | 114 | patch := client.MergeFrom(nic.DeepCopy()) 115 | controllerutil.RemoveFinalizer(nic, constants.FinalizerName) 116 | err = r.Client.Patch(ctx, nic, patch) 117 | if err != nil { 118 | log.Error(err, fmt.Sprintf("failed to patch networkInterface %s", nic.Name)) 119 | return ctrl.Result{}, err 120 | } 121 | } 122 | } 123 | 124 | md, err := r.MetadataAPI.GetMetadata() 125 | if err != nil { 126 | log.Error(err, "unable to get metadata") 127 | return ctrl.Result{}, err 128 | } 129 | 130 | found := false 131 | for _, n := range md.PrivateNICs { 132 | if n.MacAddress == nic.Status.MacAddress { 133 | found = true 134 | break 135 | } 136 | } 137 | if !found { 138 | err := fmt.Errorf("nic not found on node") 139 | log.Error(err, "unable to find nic") 140 | return ctrl.Result{}, err 141 | } 142 | 143 | linkName, err := r.NICs.GetLinkName(nic.Status.MacAddress) 144 | if err != nil { 145 | log.Error(err, "unable to get link") 146 | return ctrl.Result{}, err 147 | } 148 | 149 | patch := client.MergeFrom(nic.DeepCopy()) 150 | nic.Status.LinkName = linkName 151 | err = r.Client.Status().Patch(ctx, nic, patch) 152 | if err != nil { 153 | log.Error(err, "unable to patch status") 154 | return ctrl.Result{}, err 155 | } 156 | 157 | if pnet.Spec.IPAM == nil { 158 | err := r.NICs.ConfigureStaticLink(nic.Status.MacAddress, nic.Spec.Address) 159 | if err != nil { 160 | log.Error(err, "unable to configure link") 161 | return ctrl.Result{}, err 162 | } 163 | } else { 164 | switch pnet.Spec.IPAM.Type { 165 | case vpcv1alpha1.IPAMTypeStatic: 166 | err := r.NICs.ConfigureStaticLink(nic.Status.MacAddress, nic.Status.Address) 167 | if err != nil { 168 | log.Error(err, "unable to configure link") 169 | return ctrl.Result{}, err 170 | } 171 | case vpcv1alpha1.IPAMTypeDHCP: 172 | ip, err := r.NICs.ConfigureDHCPLink(nic.Status.MacAddress) 173 | if err != nil { 174 | log.Error(err, "unable to configure link") 175 | return ctrl.Result{}, err 176 | } 177 | patch := client.MergeFrom(nic.DeepCopy()) 178 | nic.Status.Address = ip 179 | err = r.Client.Status().Patch(ctx, nic, patch) 180 | if err != nil { 181 | log.Error(err, "unable to patch status") 182 | return ctrl.Result{}, err 183 | } 184 | default: 185 | return ctrl.Result{}, fmt.Errorf("IPAM type %s not supported", pnet.Spec.IPAM.Type) 186 | } 187 | } 188 | 189 | ip, err := iptables.New() 190 | if err != nil { 191 | log.Error(err, "unable to create iptables helper") 192 | return ctrl.Result{}, err 193 | } 194 | 195 | isMasquerade, err := ip.Exists("nat", "POSTROUTING", "-o", linkName, "-j", "MASQUERADE") 196 | if err != nil { 197 | log.Error(err, "unable to check masquerade iptables rules") 198 | return ctrl.Result{}, err 199 | } 200 | 201 | if pnet.Spec.Masquerade && !isMasquerade { 202 | err := ip.AppendUnique("nat", "POSTROUTING", "-o", linkName, "-j", "MASQUERADE") 203 | if err != nil { 204 | log.Error(err, "unable to append masquerade iptables rule") 205 | return ctrl.Result{}, err 206 | } 207 | } 208 | 209 | if !pnet.Spec.Masquerade && isMasquerade { 210 | err := ip.DeleteIfExists("nat", "POSTROUTING", "-o", linkName, "-j", "MASQUERADE") 211 | if err != nil { 212 | log.Error(err, "unable to delete masquerade iptables rule") 213 | return ctrl.Result{}, err 214 | } 215 | } 216 | 217 | routes := []nics.Route{} 218 | for _, route := range pnet.Spec.Routes { 219 | via := net.ParseIP(route.Via) 220 | to, err := netlink.ParseIPNet(route.To) 221 | if err != nil { 222 | log.Error(err, fmt.Sprintf("unable to parse to route %s", route.To)) 223 | return ctrl.Result{}, err 224 | } 225 | routes = append(routes, nics.Route{ 226 | To: to, 227 | Via: via, 228 | }) 229 | } 230 | 231 | err = r.NICs.SyncRoutes(nic.Status.MacAddress, routes) 232 | if err != nil { 233 | log.Error(err, "unable to sync routes") 234 | return ctrl.Result{}, err 235 | } 236 | 237 | return ctrl.Result{}, nil 238 | } 239 | 240 | func (r *NetworkInterfaceReconciler) SetupWithManager(mgr ctrl.Manager) error { 241 | return ctrl.NewControllerManagedBy(mgr). 242 | For(&vpcv1alpha1.NetworkInterface{}). 243 | Watches(&source.Kind{ 244 | Type: &vpcv1alpha1.PrivateNetwork{}, 245 | }, &handler.Funcs{ 246 | UpdateFunc: func(e event.UpdateEvent, q workqueue.RateLimitingInterface) { 247 | r.Log.Info("got update PrivateNetwork event") 248 | nicsList := &vpcv1alpha1.NetworkInterfaceList{} 249 | err := r.Client.List(context.Background(), nicsList, 250 | client.MatchingLabels{ 251 | constants.PrivateNetworkLabel: e.MetaNew.GetName(), 252 | }, 253 | ) 254 | if err != nil { 255 | r.Log.Error(err, "unable to sync nics on privateNetwork update") 256 | return 257 | } 258 | for _, nic := range nicsList.Items { 259 | r.Log.Info(fmt.Sprintf("adding event for nic %s", nic.Name)) 260 | q.Add(reconcile.Request{ 261 | NamespacedName: types.NamespacedName{ 262 | Name: nic.Name, 263 | }, 264 | }) 265 | } 266 | }, 267 | }). 268 | Complete(r) 269 | } 270 | -------------------------------------------------------------------------------- /pkg/ipam/ipam.go: -------------------------------------------------------------------------------- 1 | package ipam 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | corev1 "k8s.io/api/core/v1" 11 | apierrors "k8s.io/apimachinery/pkg/api/errors" 12 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/types" 14 | ctrl "sigs.k8s.io/controller-runtime" 15 | "sigs.k8s.io/controller-runtime/pkg/cache" 16 | "sigs.k8s.io/controller-runtime/pkg/client" 17 | 18 | goipam "github.com/metal-stack/go-ipam" 19 | ) 20 | 21 | var ( 22 | ipamLog = ctrl.Log.WithName("ipam") 23 | ) 24 | 25 | type ConfigMapIPAM struct { 26 | name types.NamespacedName 27 | client client.Client 28 | 29 | lock sync.RWMutex 30 | } 31 | 32 | func NewConfigMapIPAM(name types.NamespacedName, stopCh <-chan struct{}) (*ConfigMapIPAM, error) { 33 | cmConfig := ctrl.GetConfigOrDie() 34 | cmCache, err := cache.New(cmConfig, cache.Options{ 35 | Namespace: name.Namespace, 36 | }) 37 | if err != nil { 38 | ipamLog.Error(err, "unable to create cache for configmap") 39 | return nil, err 40 | } 41 | cmClient, err := client.New(cmConfig, client.Options{}) 42 | if err != nil { 43 | ipamLog.Error(err, "unable to create client for configmap") 44 | return nil, err 45 | } 46 | cmCacheClient := &client.DelegatingClient{ 47 | Reader: &client.DelegatingReader{ 48 | CacheReader: cmCache, 49 | ClientReader: cmClient, 50 | }, 51 | Writer: cmClient, 52 | StatusClient: cmClient, 53 | } 54 | 55 | go func() { 56 | err := cmCache.Start(stopCh) 57 | if err != nil { 58 | ipamLog.Error(err, "unable to start cache for configmap") 59 | } 60 | <-stopCh 61 | }() 62 | 63 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) 64 | defer cancel() 65 | cacheOk := cmCache.WaitForCacheSync(ctx.Done()) 66 | if !cacheOk { 67 | ipamLog.Error(err, "unable to wait for configmap cache") 68 | return nil, err 69 | } 70 | 71 | cm := &corev1.ConfigMap{} 72 | err = cmCacheClient.Get(context.Background(), types.NamespacedName{ 73 | Name: name.Name, 74 | Namespace: name.Namespace, 75 | }, cm) 76 | if err != nil { 77 | if !apierrors.IsNotFound(err) { 78 | ipamLog.Error(err, "error getting ipam configmap") 79 | return nil, err 80 | } 81 | cm := &corev1.ConfigMap{ 82 | ObjectMeta: metav1.ObjectMeta{ 83 | Name: name.Name, 84 | Namespace: name.Namespace, 85 | }, 86 | BinaryData: make(map[string][]byte), 87 | } 88 | err = cmCacheClient.Create(context.Background(), cm) 89 | if err != nil { 90 | ipamLog.Error(err, "error creating ipam configmap") 91 | return nil, err 92 | } 93 | } 94 | 95 | return &ConfigMapIPAM{ 96 | name: name, 97 | client: cmCacheClient, 98 | }, nil 99 | } 100 | 101 | func encode(prefix *goipam.Prefix) ([]byte, error) { 102 | return prefix.GobEncode() 103 | } 104 | 105 | func decode(b []byte) (*goipam.Prefix, error) { 106 | prefix := &goipam.Prefix{} 107 | err := prefix.GobDecode(b) 108 | return prefix, err 109 | } 110 | 111 | func getCmCIDR(cidr string) string { 112 | return strings.ReplaceAll(strings.ReplaceAll(cidr, "/", "_"), ":", "-") 113 | } 114 | 115 | func (c *ConfigMapIPAM) CreatePrefix(prefix goipam.Prefix) (goipam.Prefix, error) { 116 | c.lock.Lock() 117 | defer c.lock.Unlock() 118 | 119 | cm := &corev1.ConfigMap{ 120 | ObjectMeta: metav1.ObjectMeta{ 121 | Name: c.name.Name, 122 | Namespace: c.name.Namespace, 123 | }, 124 | } 125 | err := c.client.Get(context.Background(), c.name, cm) 126 | if err != nil { 127 | return goipam.Prefix{}, err 128 | } 129 | data, ok := cm.BinaryData[getCmCIDR(prefix.Cidr)] 130 | if ok { 131 | p, err := decode(data) 132 | if err != nil { 133 | return goipam.Prefix{}, err 134 | } 135 | return *p, nil 136 | } 137 | 138 | data, err = encode(&prefix) 139 | if err != nil { 140 | return goipam.Prefix{}, err 141 | } 142 | 143 | patch := client.MergeFrom(cm.DeepCopy()) 144 | if cm.BinaryData == nil { 145 | cm.BinaryData = make(map[string][]byte) 146 | } 147 | cm.BinaryData[getCmCIDR(prefix.Cidr)] = data 148 | 149 | err = c.client.Patch(context.Background(), cm, patch) 150 | if err != nil { 151 | return goipam.Prefix{}, err 152 | } 153 | 154 | return prefix, nil 155 | } 156 | 157 | func (c *ConfigMapIPAM) ReadPrefix(prefix string) (goipam.Prefix, error) { 158 | c.lock.RLock() 159 | defer c.lock.RUnlock() 160 | 161 | cm := &corev1.ConfigMap{} 162 | err := c.client.Get(context.Background(), c.name, cm) 163 | if err != nil { 164 | return goipam.Prefix{}, err 165 | } 166 | data, ok := cm.BinaryData[getCmCIDR(prefix)] 167 | if !ok { 168 | return goipam.Prefix{}, fmt.Errorf("prefix %s not found", prefix) 169 | } 170 | 171 | newPrefix, err := decode(data) 172 | if err != nil { 173 | return goipam.Prefix{}, err 174 | } 175 | 176 | return *newPrefix, nil 177 | } 178 | 179 | func (c *ConfigMapIPAM) ReadAllPrefixes() ([]goipam.Prefix, error) { 180 | c.lock.RLock() 181 | defer c.lock.RUnlock() 182 | 183 | cm := &corev1.ConfigMap{} 184 | err := c.client.Get(context.Background(), c.name, cm) 185 | if err != nil { 186 | return nil, err 187 | } 188 | 189 | ps := make([]goipam.Prefix, 0, len(cm.BinaryData)) 190 | for _, v := range cm.BinaryData { 191 | p, err := decode(v) 192 | if err != nil { 193 | return nil, err 194 | } 195 | ps = append(ps, *p) 196 | } 197 | return ps, nil 198 | } 199 | 200 | func (c *ConfigMapIPAM) UpdatePrefix(prefix goipam.Prefix) (goipam.Prefix, error) { 201 | c.lock.Lock() 202 | defer c.lock.Unlock() 203 | cm := &corev1.ConfigMap{} 204 | err := c.client.Get(context.Background(), c.name, cm) 205 | if err != nil { 206 | return goipam.Prefix{}, err 207 | } 208 | 209 | if prefix.Cidr == "" { 210 | return goipam.Prefix{}, fmt.Errorf("prefix not present:%v", prefix) 211 | } 212 | 213 | _, ok := cm.BinaryData[getCmCIDR(prefix.Cidr)] 214 | if !ok { 215 | return goipam.Prefix{}, fmt.Errorf("prefix %s not found", prefix.Cidr) 216 | } 217 | 218 | data, err := encode(&prefix) 219 | if err != nil { 220 | return goipam.Prefix{}, err 221 | } 222 | 223 | patch := client.MergeFrom(cm.DeepCopy()) 224 | cm.BinaryData[getCmCIDR(prefix.Cidr)] = data 225 | 226 | return prefix, c.client.Patch(context.Background(), cm, patch) 227 | } 228 | 229 | func (c *ConfigMapIPAM) DeletePrefix(prefix goipam.Prefix) (goipam.Prefix, error) { 230 | c.lock.Lock() 231 | defer c.lock.Unlock() 232 | cm := &corev1.ConfigMap{} 233 | err := c.client.Get(context.Background(), c.name, cm) 234 | if err != nil { 235 | return goipam.Prefix{}, err 236 | } 237 | 238 | _, ok := cm.BinaryData[getCmCIDR(prefix.Cidr)] 239 | if !ok { 240 | return prefix, nil 241 | } 242 | patch := client.MergeFrom(cm.DeepCopy()) 243 | delete(cm.BinaryData, getCmCIDR(prefix.Cidr)) 244 | 245 | return prefix, c.client.Patch(context.Background(), cm, patch) 246 | } 247 | -------------------------------------------------------------------------------- /pkg/nics/nics.go: -------------------------------------------------------------------------------- 1 | package nics 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net" 7 | "os" 8 | "os/exec" 9 | 10 | "github.com/vishvananda/netlink" 11 | ) 12 | 13 | const ( 14 | dhcpcdRunFilePrefix = "/var/run/dhcpcd-" 15 | dhcpcdRunFileSuffix = "-4.pid" 16 | ) 17 | 18 | var ( 19 | nicNotFoundErr = errors.New("NIC not found") 20 | ) 21 | 22 | type Route struct { 23 | To *net.IPNet 24 | Via net.IP 25 | } 26 | 27 | func (r Route) isIn(routes []netlink.Route) bool { 28 | for _, route := range routes { 29 | if route.Dst.String() == r.To.String() && route.Gw.Equal(r.Via) { 30 | return true 31 | } 32 | } 33 | return false 34 | } 35 | 36 | func isIn(r netlink.Route, routes []Route) bool { 37 | for _, route := range routes { 38 | if r.Dst.String() == route.To.String() && r.Gw.Equal(route.Via) { 39 | return true 40 | } 41 | } 42 | return false 43 | } 44 | 45 | type NICs struct { 46 | Handle *netlink.Handle 47 | Links map[string]netlink.Link 48 | } 49 | 50 | func NewNICs(macs []string) (*NICs, error) { 51 | handle, err := netlink.NewHandle() 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | nics := &NICs{ 57 | Handle: handle, 58 | Links: make(map[string]netlink.Link), 59 | } 60 | 61 | links, err := handle.LinkList() 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | for _, link := range links { 67 | for _, mac := range macs { 68 | if link.Attrs().HardwareAddr.String() == mac { 69 | nics.Links[mac] = link 70 | break 71 | } 72 | } 73 | } 74 | 75 | return nics, nil 76 | } 77 | 78 | func (n *NICs) GetLinkName(mac string) (string, error) { 79 | link, err := n.getLink(mac) 80 | if err != nil { 81 | return "", err 82 | } 83 | return link.Attrs().Name, nil 84 | } 85 | 86 | func (n *NICs) getLink(mac string) (netlink.Link, error) { 87 | if link, ok := n.Links[mac]; ok { 88 | return link, nil 89 | } 90 | 91 | links, err := n.Handle.LinkList() 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | for _, link := range links { 97 | if link.Attrs().HardwareAddr.String() == mac { 98 | n.Links[mac] = link 99 | return link, nil 100 | } 101 | } 102 | 103 | return nil, fmt.Errorf("link with address %s: %w", mac, nicNotFoundErr) 104 | } 105 | 106 | func maskEqual(m1, m2 net.IPMask) bool { 107 | if len(m1) != len(m2) { 108 | return false 109 | } 110 | 111 | for i, v := range m1 { 112 | if m2[i] != v { 113 | return false 114 | } 115 | } 116 | return true 117 | } 118 | 119 | func (n *NICs) ConfigureDHCPLink(mac string) (string, error) { 120 | link, err := n.getLink(mac) 121 | if err != nil { 122 | return "", err 123 | } 124 | if _, err := os.Stat(dhcpcdRunFilePrefix + link.Attrs().Name + dhcpcdRunFileSuffix); err != nil { 125 | if !os.IsNotExist(err) { 126 | return "", err 127 | } 128 | cmd := exec.Command("dhcpcd", "-A4", "--waitip", "-C", "resolv.conf", "-G", link.Attrs().Name) 129 | cmd.Stdout = os.Stdout 130 | cmd.Stderr = os.Stderr 131 | if err := cmd.Run(); err != nil { 132 | return "", err 133 | } 134 | } 135 | 136 | err = netlink.LinkSetUp(link) 137 | if err != nil { 138 | return "", err 139 | } 140 | 141 | addrs, err := netlink.AddrList(link, netlink.FAMILY_V4) 142 | if err != nil { 143 | return "", err 144 | } 145 | 146 | if len(addrs) != 1 { 147 | return "", fmt.Errorf("found %d address for link %s instead of 1", len(addrs), link.Attrs().Name) 148 | } 149 | 150 | return addrs[0].IP.String(), nil 151 | } 152 | 153 | func (n *NICs) ConfigureStaticLink(mac string, ip string) error { 154 | link, err := n.getLink(mac) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | ipnet, err := netlink.ParseIPNet(ip) 160 | if err != nil { 161 | return err 162 | } 163 | 164 | addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL) 165 | if err != nil { 166 | return err 167 | } 168 | 169 | ipFound := false 170 | for _, addr := range addrs { 171 | if maskEqual(addr.IPNet.Mask, ipnet.Mask) && addr.IPNet.IP.Equal(ipnet.IP) { 172 | ipFound = true 173 | break 174 | } 175 | } 176 | 177 | if !ipFound { 178 | err := netlink.AddrAdd(link, &netlink.Addr{ 179 | IPNet: ipnet, 180 | }) 181 | if err != nil { 182 | return err 183 | } 184 | } 185 | 186 | err = netlink.LinkSetUp(link) 187 | if err != nil { 188 | return err 189 | } 190 | return nil 191 | } 192 | 193 | func (n *NICs) TearDownDHCPLink(mac string) error { 194 | link, err := n.getLink(mac) 195 | if err != nil { 196 | if errors.Is(err, nicNotFoundErr) { 197 | return nil 198 | } 199 | return err 200 | } 201 | 202 | _, err = os.Stat(dhcpcdRunFilePrefix + link.Attrs().Name + dhcpcdRunFileSuffix) 203 | if err != nil && !os.IsNotExist(err) { 204 | return err 205 | } 206 | 207 | if err == nil { 208 | cmd := exec.Command("dhcpcd", "-A4", "--waitip", "-C", "resolv.conf", "-G", "-k", link.Attrs().Name) 209 | cmd.Stdout = os.Stdout 210 | cmd.Stderr = os.Stderr 211 | if err := cmd.Run(); err != nil { 212 | return err 213 | } 214 | } 215 | 216 | err = netlink.LinkSetDown(link) 217 | if err != nil { 218 | return err 219 | } 220 | return nil 221 | } 222 | 223 | func (n *NICs) TearDownStaticLink(mac string, ip string) error { 224 | link, err := n.getLink(mac) 225 | if err != nil { 226 | if errors.Is(err, nicNotFoundErr) { 227 | return nil 228 | } 229 | return err 230 | } 231 | 232 | ipnet, err := netlink.ParseIPNet(ip) 233 | if err != nil { 234 | return err 235 | } 236 | 237 | addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL) 238 | if err != nil { 239 | return err 240 | } 241 | 242 | ipFound := false 243 | for _, addr := range addrs { 244 | if maskEqual(addr.IPNet.Mask, ipnet.Mask) && addr.IPNet.IP.Equal(ipnet.IP) { 245 | ipFound = true 246 | break 247 | } 248 | } 249 | 250 | if ipFound { 251 | err := netlink.AddrDel(link, &netlink.Addr{ 252 | IPNet: ipnet, 253 | }) 254 | if err != nil { 255 | return err 256 | } 257 | } 258 | 259 | err = netlink.LinkSetDown(link) 260 | if err != nil { 261 | return err 262 | } 263 | return nil 264 | } 265 | 266 | func (n *NICs) SyncRoutes(mac string, routes []Route) error { 267 | link, err := n.getLink(mac) 268 | if err != nil { 269 | return err 270 | } 271 | 272 | existingRoutes, err := netlink.RouteList(link, netlink.FAMILY_ALL) 273 | if err != nil { 274 | return err 275 | } 276 | 277 | for _, existingRoute := range existingRoutes { 278 | if !isIn(existingRoute, routes) && existingRoute.Src == nil { 279 | err := netlink.RouteDel(&existingRoute) 280 | if err != nil { 281 | return err 282 | } 283 | } 284 | } 285 | 286 | for _, route := range routes { 287 | if !route.isIn(existingRoutes) { 288 | err := netlink.RouteAdd(&netlink.Route{ 289 | LinkIndex: link.Attrs().Index, 290 | Dst: route.To, 291 | Gw: route.Via, 292 | }) 293 | if err != nil { 294 | return err 295 | } 296 | } 297 | } 298 | return nil 299 | } 300 | -------------------------------------------------------------------------------- /secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | stringData: 3 | SCW_ACCESS_KEY: 4 | SCW_SECRET_KEY: 5 | SCW_DEFAULT_PROJECT_ID: 6 | SCW_DEFAULT_ZONE: 7 | kind: Secret 8 | metadata: 9 | name: scaleway-k8s-vpc-secret 10 | namespace: scaleway-k8s-vpc-system 11 | type: Opaque 12 | --------------------------------------------------------------------------------