├── .gitignore ├── Dockerfile ├── Makefile ├── PROJECT ├── README.md ├── api └── v1beta1 │ ├── dhcphosts_types.go │ ├── dhcpoptions_types.go │ ├── dnshosts_types.go │ ├── dnsmasqoptions_types.go │ ├── groupversion_info.go │ └── zz_generated.deepcopy.go ├── config ├── crd │ └── bases │ │ ├── dnsmasq.kvaps.cf_dhcphosts.yaml │ │ ├── dnsmasq.kvaps.cf_dhcpoptions.yaml │ │ ├── dnsmasq.kvaps.cf_dnshosts.yaml │ │ ├── dnsmasq.kvaps.cf_dnsmasqoptions.yaml │ │ └── kustomization.yaml ├── dhcp-server │ ├── dhcp-server.yaml │ └── kustomization.yaml ├── dns-server │ ├── dns-server.yaml │ └── kustomization.yaml ├── rbac-viewer-editor │ ├── dhcphosts_editor_role.yaml │ ├── dhcphosts_viewer_role.yaml │ ├── dhcpoptions_editor_role.yaml │ ├── dhcpoptions_viewer_role.yaml │ ├── dnshosts_editor_role.yaml │ ├── dnshosts_viewer_role.yaml │ ├── dnsmasqoptions_editor_role.yaml │ ├── dnsmasqoptions_viewer_role.yaml │ └── kustomization.yaml ├── rbac │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml └── samples │ ├── dnsmasq_v1beta1_dhcphosts.yaml │ ├── dnsmasq_v1beta1_dhcpoptions.yaml │ ├── dnsmasq_v1beta1_dnshosts.yaml │ ├── dnsmasq_v1beta1_dnsmasqoptions.yaml │ └── kustomization.yaml ├── controllers ├── dhcphosts_controller.go ├── dhcpoptions_controller.go ├── dnshosts_controller.go ├── dnsmasqoptions_controller.go └── suite_test.go ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── main.go └── pkg ├── conf └── conf.go ├── server └── server.go └── util └── util.go /.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 | # vscode 27 | .vscode -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the dnsmasq-controller binary 2 | FROM golang:1.22.7 AS builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY main.go main.go 14 | COPY api/ api/ 15 | COPY controllers/ controllers/ 16 | COPY pkg/ pkg/ 17 | 18 | # Build 19 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o dnsmasq-controller 20 | 21 | # Load distroless base to get initial passwd/group files 22 | FROM gcr.io/distroless/static-debian12:latest AS app 23 | 24 | # Install dnsmasq 25 | FROM alpine:3.19.1 AS dnsmasq 26 | # Use distroless passwd/group 27 | COPY --from=app /etc/passwd /etc/passwd 28 | COPY --from=app /etc/group /etc/group 29 | RUN apk add --no-cache dnsmasq 30 | 31 | # Copy dnsmasq and altered passwd/group to distroless image 32 | FROM app 33 | 34 | COPY --from=dnsmasq /usr/sbin/dnsmasq /usr/sbin/dnsmasq 35 | COPY --from=dnsmasq /lib/ld-musl-x86_64.so.1 /lib/ 36 | COPY --from=dnsmasq /lib/libc.musl-x86_64.so.1 /lib/ 37 | COPY --from=dnsmasq /etc/passwd /etc/passwd 38 | COPY --from=dnsmasq /etc/group /etc/group 39 | COPY --from=builder /workspace/dnsmasq-controller /dnsmasq-controller 40 | 41 | ENTRYPOINT ["/dnsmasq-controller"] 42 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Image URL to use all building/pushing image targets 3 | IMG ?= docker.io/kvaps/dnsmasq-controller:latest 4 | 5 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 6 | ifeq (,$(shell go env GOBIN)) 7 | GOBIN=$(shell go env GOPATH)/bin 8 | else 9 | GOBIN=$(shell go env GOBIN) 10 | endif 11 | 12 | CONTROLLER_GEN = go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.8.0 13 | 14 | all: manager 15 | 16 | # Run tests 17 | test: generate fmt vet manifests 18 | go test ./... -coverprofile cover.out 19 | 20 | # Build manager binary 21 | manager: generate fmt vet 22 | go build -o bin/dnsmasq-controller main.go 23 | 24 | # Run against the configured Kubernetes cluster in ~/.kube/config 25 | run: generate fmt vet manifests 26 | go run ./main.go 27 | 28 | # Install CRDs into a cluster 29 | install: manifests 30 | kubectl apply -f config/crd/bases 31 | 32 | # Uninstall CRDs from a cluster 33 | uninstall: manifests 34 | kubectl delete -f config/crd/bases 35 | 36 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 37 | deploy: manifests 38 | # RBAC 39 | kubectl apply -n default \ 40 | -f config/rbac/service_account.yaml \ 41 | -f config/rbac/role.yaml \ 42 | -f config/rbac/role_binding.yaml \ 43 | -f config/rbac/leader_election_role.yaml \ 44 | -f config/rbac/leader_election_role_binding.yaml 45 | # DNS-server (for infra.example.org) 46 | kubectl apply -n default -f config/controller/dns-server.yaml 47 | # DHCP-server 48 | kubectl apply -n default -f config/controller/dhcp-server.yaml 49 | 50 | # Generate manifests e.g. CRD, RBAC etc. 51 | manifests: 52 | $(CONTROLLER_GEN) crd rbac:roleName=dnsmasq-controller paths="./..." output:crd:artifacts:config=config/crd/bases 53 | 54 | # Run go fmt against code 55 | fmt: 56 | go fmt ./... 57 | 58 | # Run go vet against code 59 | vet: 60 | go vet ./... 61 | 62 | # Generate code 63 | generate: 64 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 65 | 66 | # Build the docker image 67 | docker-build: test 68 | docker build . -t ${IMG} 69 | 70 | # Push the docker image 71 | docker-push: 72 | docker push ${IMG} 73 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: kvaps.cf 2 | repo: github.com/kvaps/dnsmasq-controller 3 | resources: 4 | - group: dnsmasq 5 | kind: DnsmasqOptions 6 | version: v1beta1 7 | - group: dnsmasq 8 | kind: DnsHosts 9 | version: v1beta1 10 | - group: dnsmasq 11 | kind: DhcpHosts 12 | version: v1beta1 13 | - group: dnsmasq 14 | kind: DhcpOptions 15 | version: v1beta1 16 | version: "2" 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dnsmasq-controller 2 | 3 | A Dnsmasq-controller for Kubernetes, implemented in go using [kubebuilder](https://kubebuilder.io/). 4 | 5 | ## Status 6 | 7 | ![GitHub](https://img.shields.io/badge/status-beta-blue?style=for-the-badge) 8 | ![GitHub](https://img.shields.io/github/license/kristofferahl/healthchecksio-operator?style=for-the-badge) 9 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/kristofferahl/healthchecksio-operator?style=for-the-badge) 10 | 11 | ## Supported resources 12 | 13 | - DnsmasqOptions 14 | - DnsHosts 15 | - DhcpHosts 16 | - DhcpOptions 17 | 18 | 19 | ### Configuration 20 | 21 | | Flag | Type | Required | Description | 22 | |---------------------------|--------|----------|-----------------------------------------------------------------------------------------------------------------------------------------| 23 | | `-cleanup` | bool | false | Cleanup Dnsmasq config directory before start. | 24 | | `-conf-dir` | string | false | Dnsmasq config directory for write configuration to. (default "/etc/dnsmasq.d") | 25 | | `-controller` | string | false | Name of the controller this controller satisfies. (default "") | 26 | | `-development` | bool | false | Run the controller in development mode. | 27 | | `-dhcp` | bool | false | Enable DHCP Service and configuration discovery. | 28 | | `-dns` | bool | false | Enable DNS Service and configuration discovery. | 29 | | `-enable-leader-election` | bool | false | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. | 30 | | `-kubeconfig` | string | false | Paths to a kubeconfig. Only required if out-of-cluster. | 31 | | `-log-level` | string | false | The log level used by the operator. (default "info") | 32 | | `-metrics-addr` | string | false | The address the metric endpoint binds to. (default ":8080") | 33 | | `-sync-delay` | int | false | Time in seconds to syncronise Dnsmasq configuration. (default 1) | 34 | | `-watch-namespace` | string | false | Namespace the controller watches for updates to Kubernetes objects. All namespaces are watched if this parameter is left empty. | 35 | | `--` | array | false | Additional command line arguments for Dnsmasq may be specified after `--` (read [dnsmasq-man] for more details) | 36 | 37 | [dnsmasq-man]: http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html 38 | 39 | ## Installation 40 | 41 | ```bash 42 | # CRDs 43 | kubectl apply -k config/crd/bases 44 | 45 | # RBAC 46 | kubectl apply -k config/rbac 47 | 48 | # DNS-server (for infra.example.org) 49 | kubectl apply -k config/dns-server 50 | 51 | # DHCP-server 52 | kubectl apply -k config/dhcp-server 53 | 54 | # Add dnsmasq role to your nodes 55 | kubectl label node node-role.kubernetes.io/dnsmasq= 56 | ``` 57 | 58 | ## Examples 59 | 60 | Global DHCP-configuration: 61 | 62 | ```yaml 63 | --- 64 | apiVersion: dnsmasq.kvaps.cf/v1beta1 65 | kind: DhcpOptions 66 | metadata: 67 | name: default-network-configuration 68 | spec: 69 | controller: "" 70 | options: 71 | - key: option:router 72 | values: [192.168.67.1] 73 | - key: option:dns-server 74 | values: [192.168.67.1] 75 | - key: option:domain-name 76 | values: [infra.example.org] 77 | - key: option:domain-search 78 | values: [infra.example.org] 79 | --- 80 | apiVersion: dnsmasq.kvaps.cf/v1beta1 81 | kind: DnsmasqOptions 82 | metadata: 83 | name: default-matchers 84 | spec: 85 | controller: "" 86 | options: 87 | - key: dhcp-range 88 | values: [192.168.67.0,static,infinite] 89 | - key: dhcp-match 90 | values: [set:iPXE,"175","39"] 91 | - key: dhcp-match 92 | values: [set:X86PC,option:client-arch,"0"] 93 | - key: dhcp-match 94 | values: [set:X86-64_EFI,option:client-arch,"7"] 95 | - key: dhcp-match 96 | values: [set:X86-64_EFI,option:client-arch,"9"] 97 | ``` 98 | 99 | Global DNS-configuration: 100 | 101 | ```yaml 102 | --- 103 | apiVersion: dnsmasq.kvaps.cf/v1beta1 104 | kind: DnsmasqOptions 105 | metadata: 106 | name: global-dns 107 | spec: 108 | controller: "" 109 | options: 110 | - key: srv-host 111 | values: [_kerberos-master._tcp.infra.example.org,freeipa.example.org,"88"] 112 | - key: srv-host 113 | values: [_kerberos-master._udp.infra.example.org,freeipa.example.org,"88"] 114 | - key: srv-host 115 | values: [_kerberos._tcp.infra.example.org,freeipa.example.org,"88"] 116 | - key: srv-host 117 | values: [_kerberos._udp.infra.example.org,freeipa.example.org,"88"] 118 | - key: srv-host 119 | values: [_kpasswd._tcp.infra.example.org,freeipa.example.org,"464"] 120 | - key: srv-host 121 | values: [_kpasswd._udp.infra.example.org,freeipa.example.org,"464"] 122 | - key: srv-host 123 | values: [_ldap._tcp.infra.example.org,freeipa.example.org,"389"] 124 | - key: srv-host 125 | values: [_ntp._udp.infra.example.org,129.6.15.28,"123"] 126 | - key: srv-host 127 | values: [_ntp._udp.infra.example.org,129.6.15.29,"123"] 128 | - key: txt-record 129 | values: [_kerberos.infra.example.org,EXAMPLE.ORG] 130 | ``` 131 | 132 | Netboot-server configuration with tag `ltsp1`: 133 | 134 | ```yaml 135 | --- 136 | apiVersion: dnsmasq.kvaps.cf/v1beta1 137 | kind: DhcpOptions 138 | metadata: 139 | name: ltsp1 140 | spec: 141 | controller: "" 142 | options: 143 | - key: option:server-ip-address 144 | tags: [ltsp1] 145 | values: [192.168.67.11] 146 | - key: option:tftp-server 147 | tags: [ltsp1] 148 | values: [ltsp1] 149 | - key: option:bootfile-name 150 | tags: [ltsp1,X86PC] 151 | values: [ltsp/grub/i386-pc/core.0] 152 | - key: option:bootfile-name 153 | tags: [ltsp1,X86-64_EFI] 154 | values: [ltsp/grub/x86_64-efi/core.efi] 155 | ``` 156 | 157 | DHCP-client for network booting using assigned tag `ltsp1`: 158 | 159 | ```yaml 160 | --- 161 | apiVersion: dnsmasq.kvaps.cf/v1beta1 162 | kind: DhcpHosts 163 | metadata: 164 | name: netboot-client 165 | spec: 166 | controller: "" 167 | hosts: 168 | - ip: 192.168.67.20 169 | macs: 170 | - 94:57:a5:d3:b6:f2 171 | - 94:57:a5:d3:b6:f3 172 | clientIDs: ["*"] 173 | setTags: [ltsp1] 174 | hostname: node1 175 | leaseTime: infinite 176 | ``` 177 | 178 | Add A, AAAA and PTR records to the DNS: 179 | 180 | ```yaml 181 | --- 182 | apiVersion: dnsmasq.kvaps.cf/v1beta1 183 | kind: DnsHosts 184 | metadata: 185 | name: netboot-client 186 | spec: 187 | controller: "" 188 | hosts: 189 | - ip: 192.168.67.20 190 | hostnames: 191 | - node1 192 | - node1.infra.example.org 193 | ``` 194 | 195 | ## Development 196 | 197 | ### Pre-requisites 198 | - [Go](https://golang.org/) 1.13 or later 199 | - [Kubebuilder](https://kubebuilder.io/) 2.3.1 200 | - [Kubernetes](https://kubernetes.io/) cluster 201 | 202 | ### Getting started 203 | ```bash 204 | make install 205 | make run 206 | ``` 207 | 208 | ### Running tests 209 | ```bash 210 | make test 211 | ``` 212 | -------------------------------------------------------------------------------- /api/v1beta1/dhcphosts_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 v1beta1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // DnsmasqDhcpHost holds the mapping between Macs and IP that will be added to dnsmasq dhcp-hosts file. 24 | type DhcpHost struct { 25 | Macs []string `json:"macs,omitempty"` 26 | ClientID string `json:"clientID,omitempty"` 27 | SetTags []string `json:"setTags,omitempty"` 28 | Tags []string `json:"tags,omitempty"` 29 | IP string `json:"ip,omitempty"` 30 | Hostname string `json:"hostname,omitempty"` 31 | LeaseTime string `json:"leaseTime,omitempty"` 32 | Ignore bool `json:"ignore,omitempty"` 33 | } 34 | 35 | // DhcpHostsSpec defines the desired state of DhcpHosts 36 | type DhcpHostsSpec struct { 37 | Controller string `json:"controller,omitempty"` 38 | Hosts []DhcpHost `json:"hosts,omitempty"` 39 | } 40 | 41 | // DhcpHostsStatus defines the observed state of DhcpHosts 42 | type DhcpHostsStatus struct { 43 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 44 | // Important: Run "make" to regenerate code after modifying this file 45 | } 46 | 47 | // +kubebuilder:object:root=true 48 | // +kubebuilder:printcolumn:name="Controller",type="string",JSONPath=".spec.controller" 49 | // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" 50 | 51 | // DhcpHosts is the Schema for the dhcphosts API 52 | type DhcpHosts struct { 53 | metav1.TypeMeta `json:",inline"` 54 | metav1.ObjectMeta `json:"metadata,omitempty"` 55 | 56 | Spec DhcpHostsSpec `json:"spec,omitempty"` 57 | Status DhcpHostsStatus `json:"status,omitempty"` 58 | } 59 | 60 | // +kubebuilder:object:root=true 61 | 62 | // DhcpHostsList contains a list of DhcpHosts 63 | type DhcpHostsList struct { 64 | metav1.TypeMeta `json:",inline"` 65 | metav1.ListMeta `json:"metadata,omitempty"` 66 | Items []DhcpHosts `json:"items"` 67 | } 68 | 69 | func init() { 70 | SchemeBuilder.Register(&DhcpHosts{}, &DhcpHostsList{}) 71 | } 72 | -------------------------------------------------------------------------------- /api/v1beta1/dhcpoptions_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 v1beta1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // DhcpOption defines dhcp-option for dnsmasq 24 | type DhcpOption struct { 25 | // +kubebuilder:validation:Pattern="^([0-9]+|option:.+|option6:.+)$" 26 | Key string `json:"key"` 27 | Values []string `json:"values"` 28 | Tags []string `json:"tags,omitempty"` 29 | Encap string `json:"encap,omitempty"` 30 | ViEncap string `json:"viEncap,omitempty"` 31 | Vendor string `json:"leaseTime,omitempty"` 32 | } 33 | 34 | // DhcpOptionsSpec defines the desired state of DhcpOptions 35 | type DhcpOptionsSpec struct { 36 | Controller string `json:"controller,omitempty"` 37 | Options []DhcpOption `json:"options,omitempty"` 38 | } 39 | 40 | // DhcpOptionsStatus defines the observed state of DhcpOptions 41 | type DhcpOptionsStatus struct { 42 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 43 | // Important: Run "make" to regenerate code after modifying this file 44 | } 45 | 46 | // +kubebuilder:object:root=true 47 | // +kubebuilder:printcolumn:name="Controller",type="string",JSONPath=".spec.controller" 48 | // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" 49 | 50 | // DhcpOptions is the Schema for the dhcpoptions API 51 | type DhcpOptions struct { 52 | metav1.TypeMeta `json:",inline"` 53 | metav1.ObjectMeta `json:"metadata,omitempty"` 54 | 55 | Spec DhcpOptionsSpec `json:"spec,omitempty"` 56 | Status DhcpOptionsStatus `json:"status,omitempty"` 57 | } 58 | 59 | // +kubebuilder:object:root=true 60 | 61 | // DhcpOptionsList contains a list of DhcpOptions 62 | type DhcpOptionsList struct { 63 | metav1.TypeMeta `json:",inline"` 64 | metav1.ListMeta `json:"metadata,omitempty"` 65 | Items []DhcpOptions `json:"items"` 66 | } 67 | 68 | func init() { 69 | SchemeBuilder.Register(&DhcpOptions{}, &DhcpOptionsList{}) 70 | } 71 | -------------------------------------------------------------------------------- /api/v1beta1/dnshosts_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 v1beta1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // DnsHost holds the mapping between IP and hostnames that will be added to dnsmasq hosts file. 24 | type DnsHost struct { 25 | // IP address of the host file entry. 26 | IP string `json:"ip" protobuf:"bytes,1,opt,name=ip"` 27 | // Hostnames for the above IP address. 28 | Hostnames []string `json:"hostnames,omitempty" protobuf:"bytes,2,rep,name=hostnames"` 29 | } 30 | 31 | // DnsHostsSpec defines the desired state of DnsHosts 32 | type DnsHostsSpec struct { 33 | Controller string `json:"controller,omitempty"` 34 | Hosts []DnsHost `json:"hosts,omitempty"` 35 | } 36 | 37 | // DnsHostsStatus defines the observed state of DnsHosts 38 | type DnsHostsStatus struct { 39 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 40 | // Important: Run "make" to regenerate code after modifying this file 41 | } 42 | 43 | // +kubebuilder:object:root=true 44 | // +kubebuilder:printcolumn:name="Controller",type="string",JSONPath=".spec.controller" 45 | // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" 46 | 47 | // DnsHosts is the Schema for the dnshosts API 48 | type DnsHosts struct { 49 | metav1.TypeMeta `json:",inline"` 50 | metav1.ObjectMeta `json:"metadata,omitempty"` 51 | 52 | Spec DnsHostsSpec `json:"spec,omitempty"` 53 | Status DnsHostsStatus `json:"status,omitempty"` 54 | } 55 | 56 | // +kubebuilder:object:root=true 57 | 58 | // DnsHostsList contains a list of DnsHosts 59 | type DnsHostsList struct { 60 | metav1.TypeMeta `json:",inline"` 61 | metav1.ListMeta `json:"metadata,omitempty"` 62 | Items []DnsHosts `json:"items"` 63 | } 64 | 65 | func init() { 66 | SchemeBuilder.Register(&DnsHosts{}, &DnsHostsList{}) 67 | } 68 | -------------------------------------------------------------------------------- /api/v1beta1/dnsmasqoptions_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 v1beta1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // DnsmasqOption defines option for dnsmasq 24 | type DnsmasqOption struct { 25 | // +kubebuilder:validation:Enum=dhcp-range;dhcp-host;dhcp-userclass;dhcp-circuitid;dhcp-remoteid;dhcp-subscrid;dhcp-ignore;dhcp-broadcast;mx-host;dhcp-boot;dhcp-option;dhcp-option-force;server;rev-server;local;domain;dhcp-vendorclass;alias;dhcp-vendorclass;srv-host;txt-record;ptr-record;bootp-dynamic;dhcp-mac;dhcp-ignore-names;rebind-domain-ok;dhcp-match;dhcp-name-match;tag-if;naptr-record;dhcp-generate-names;cname;pxe-service;add-mac;dhcp-duid;host-record;caa-record;dns-rr;auth-zone;synth-domain 26 | Key string `json:"key"` 27 | Values []string `json:"values"` 28 | } 29 | 30 | // DnsmasqOptionsSpec defines the desired state of DnsmasqOptions 31 | type DnsmasqOptionsSpec struct { 32 | Controller string `json:"controller,omitempty"` 33 | Options []DnsmasqOption `json:"options,omitempty"` 34 | } 35 | 36 | // DnsmasqOptionsStatus defines the observed state of DnsmasqOptions 37 | type DnsmasqOptionsStatus struct { 38 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 39 | // Important: Run "make" to regenerate code after modifying this file 40 | } 41 | 42 | // +kubebuilder:object:root=true 43 | // +kubebuilder:printcolumn:name="Controller",type="string",JSONPath=".spec.controller" 44 | // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" 45 | 46 | // DnsmasqOptions is the Schema for the dnsmasqoptions API 47 | type DnsmasqOptions struct { 48 | metav1.TypeMeta `json:",inline"` 49 | metav1.ObjectMeta `json:"metadata,omitempty"` 50 | 51 | Spec DnsmasqOptionsSpec `json:"spec,omitempty"` 52 | Status DnsmasqOptionsStatus `json:"status,omitempty"` 53 | } 54 | 55 | // +kubebuilder:object:root=true 56 | 57 | // DnsmasqOptionsList contains a list of DnsmasqOptions 58 | type DnsmasqOptionsList struct { 59 | metav1.TypeMeta `json:",inline"` 60 | metav1.ListMeta `json:"metadata,omitempty"` 61 | Items []DnsmasqOptions `json:"items,omitempty"` 62 | } 63 | 64 | func init() { 65 | SchemeBuilder.Register(&DnsmasqOptions{}, &DnsmasqOptionsList{}) 66 | } 67 | -------------------------------------------------------------------------------- /api/v1beta1/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 v1beta1 contains API Schema definitions for the dnsmasq v1beta1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=dnsmasq.kvaps.cf 20 | package v1beta1 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: "dnsmasq.kvaps.cf", Version: "v1beta1"} 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/v1beta1/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 v1beta1 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 *DhcpHost) DeepCopyInto(out *DhcpHost) { 29 | *out = *in 30 | if in.Macs != nil { 31 | in, out := &in.Macs, &out.Macs 32 | *out = make([]string, len(*in)) 33 | copy(*out, *in) 34 | } 35 | if in.SetTags != nil { 36 | in, out := &in.SetTags, &out.SetTags 37 | *out = make([]string, len(*in)) 38 | copy(*out, *in) 39 | } 40 | if in.Tags != nil { 41 | in, out := &in.Tags, &out.Tags 42 | *out = make([]string, len(*in)) 43 | copy(*out, *in) 44 | } 45 | } 46 | 47 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DhcpHost. 48 | func (in *DhcpHost) DeepCopy() *DhcpHost { 49 | if in == nil { 50 | return nil 51 | } 52 | out := new(DhcpHost) 53 | in.DeepCopyInto(out) 54 | return out 55 | } 56 | 57 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 58 | func (in *DhcpHosts) DeepCopyInto(out *DhcpHosts) { 59 | *out = *in 60 | out.TypeMeta = in.TypeMeta 61 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 62 | in.Spec.DeepCopyInto(&out.Spec) 63 | out.Status = in.Status 64 | } 65 | 66 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DhcpHosts. 67 | func (in *DhcpHosts) DeepCopy() *DhcpHosts { 68 | if in == nil { 69 | return nil 70 | } 71 | out := new(DhcpHosts) 72 | in.DeepCopyInto(out) 73 | return out 74 | } 75 | 76 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 77 | func (in *DhcpHosts) DeepCopyObject() runtime.Object { 78 | if c := in.DeepCopy(); c != nil { 79 | return c 80 | } 81 | return nil 82 | } 83 | 84 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 85 | func (in *DhcpHostsList) DeepCopyInto(out *DhcpHostsList) { 86 | *out = *in 87 | out.TypeMeta = in.TypeMeta 88 | in.ListMeta.DeepCopyInto(&out.ListMeta) 89 | if in.Items != nil { 90 | in, out := &in.Items, &out.Items 91 | *out = make([]DhcpHosts, len(*in)) 92 | for i := range *in { 93 | (*in)[i].DeepCopyInto(&(*out)[i]) 94 | } 95 | } 96 | } 97 | 98 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DhcpHostsList. 99 | func (in *DhcpHostsList) DeepCopy() *DhcpHostsList { 100 | if in == nil { 101 | return nil 102 | } 103 | out := new(DhcpHostsList) 104 | in.DeepCopyInto(out) 105 | return out 106 | } 107 | 108 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 109 | func (in *DhcpHostsList) DeepCopyObject() runtime.Object { 110 | if c := in.DeepCopy(); c != nil { 111 | return c 112 | } 113 | return nil 114 | } 115 | 116 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 117 | func (in *DhcpHostsSpec) DeepCopyInto(out *DhcpHostsSpec) { 118 | *out = *in 119 | if in.Hosts != nil { 120 | in, out := &in.Hosts, &out.Hosts 121 | *out = make([]DhcpHost, len(*in)) 122 | for i := range *in { 123 | (*in)[i].DeepCopyInto(&(*out)[i]) 124 | } 125 | } 126 | } 127 | 128 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DhcpHostsSpec. 129 | func (in *DhcpHostsSpec) DeepCopy() *DhcpHostsSpec { 130 | if in == nil { 131 | return nil 132 | } 133 | out := new(DhcpHostsSpec) 134 | in.DeepCopyInto(out) 135 | return out 136 | } 137 | 138 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 139 | func (in *DhcpHostsStatus) DeepCopyInto(out *DhcpHostsStatus) { 140 | *out = *in 141 | } 142 | 143 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DhcpHostsStatus. 144 | func (in *DhcpHostsStatus) DeepCopy() *DhcpHostsStatus { 145 | if in == nil { 146 | return nil 147 | } 148 | out := new(DhcpHostsStatus) 149 | in.DeepCopyInto(out) 150 | return out 151 | } 152 | 153 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 154 | func (in *DhcpOption) DeepCopyInto(out *DhcpOption) { 155 | *out = *in 156 | if in.Values != nil { 157 | in, out := &in.Values, &out.Values 158 | *out = make([]string, len(*in)) 159 | copy(*out, *in) 160 | } 161 | if in.Tags != nil { 162 | in, out := &in.Tags, &out.Tags 163 | *out = make([]string, len(*in)) 164 | copy(*out, *in) 165 | } 166 | } 167 | 168 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DhcpOption. 169 | func (in *DhcpOption) DeepCopy() *DhcpOption { 170 | if in == nil { 171 | return nil 172 | } 173 | out := new(DhcpOption) 174 | in.DeepCopyInto(out) 175 | return out 176 | } 177 | 178 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 179 | func (in *DhcpOptions) DeepCopyInto(out *DhcpOptions) { 180 | *out = *in 181 | out.TypeMeta = in.TypeMeta 182 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 183 | in.Spec.DeepCopyInto(&out.Spec) 184 | out.Status = in.Status 185 | } 186 | 187 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DhcpOptions. 188 | func (in *DhcpOptions) DeepCopy() *DhcpOptions { 189 | if in == nil { 190 | return nil 191 | } 192 | out := new(DhcpOptions) 193 | in.DeepCopyInto(out) 194 | return out 195 | } 196 | 197 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 198 | func (in *DhcpOptions) DeepCopyObject() runtime.Object { 199 | if c := in.DeepCopy(); c != nil { 200 | return c 201 | } 202 | return nil 203 | } 204 | 205 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 206 | func (in *DhcpOptionsList) DeepCopyInto(out *DhcpOptionsList) { 207 | *out = *in 208 | out.TypeMeta = in.TypeMeta 209 | in.ListMeta.DeepCopyInto(&out.ListMeta) 210 | if in.Items != nil { 211 | in, out := &in.Items, &out.Items 212 | *out = make([]DhcpOptions, len(*in)) 213 | for i := range *in { 214 | (*in)[i].DeepCopyInto(&(*out)[i]) 215 | } 216 | } 217 | } 218 | 219 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DhcpOptionsList. 220 | func (in *DhcpOptionsList) DeepCopy() *DhcpOptionsList { 221 | if in == nil { 222 | return nil 223 | } 224 | out := new(DhcpOptionsList) 225 | in.DeepCopyInto(out) 226 | return out 227 | } 228 | 229 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 230 | func (in *DhcpOptionsList) DeepCopyObject() runtime.Object { 231 | if c := in.DeepCopy(); c != nil { 232 | return c 233 | } 234 | return nil 235 | } 236 | 237 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 238 | func (in *DhcpOptionsSpec) DeepCopyInto(out *DhcpOptionsSpec) { 239 | *out = *in 240 | if in.Options != nil { 241 | in, out := &in.Options, &out.Options 242 | *out = make([]DhcpOption, len(*in)) 243 | for i := range *in { 244 | (*in)[i].DeepCopyInto(&(*out)[i]) 245 | } 246 | } 247 | } 248 | 249 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DhcpOptionsSpec. 250 | func (in *DhcpOptionsSpec) DeepCopy() *DhcpOptionsSpec { 251 | if in == nil { 252 | return nil 253 | } 254 | out := new(DhcpOptionsSpec) 255 | in.DeepCopyInto(out) 256 | return out 257 | } 258 | 259 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 260 | func (in *DhcpOptionsStatus) DeepCopyInto(out *DhcpOptionsStatus) { 261 | *out = *in 262 | } 263 | 264 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DhcpOptionsStatus. 265 | func (in *DhcpOptionsStatus) DeepCopy() *DhcpOptionsStatus { 266 | if in == nil { 267 | return nil 268 | } 269 | out := new(DhcpOptionsStatus) 270 | in.DeepCopyInto(out) 271 | return out 272 | } 273 | 274 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 275 | func (in *DnsHost) DeepCopyInto(out *DnsHost) { 276 | *out = *in 277 | if in.Hostnames != nil { 278 | in, out := &in.Hostnames, &out.Hostnames 279 | *out = make([]string, len(*in)) 280 | copy(*out, *in) 281 | } 282 | } 283 | 284 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DnsHost. 285 | func (in *DnsHost) DeepCopy() *DnsHost { 286 | if in == nil { 287 | return nil 288 | } 289 | out := new(DnsHost) 290 | in.DeepCopyInto(out) 291 | return out 292 | } 293 | 294 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 295 | func (in *DnsHosts) DeepCopyInto(out *DnsHosts) { 296 | *out = *in 297 | out.TypeMeta = in.TypeMeta 298 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 299 | in.Spec.DeepCopyInto(&out.Spec) 300 | out.Status = in.Status 301 | } 302 | 303 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DnsHosts. 304 | func (in *DnsHosts) DeepCopy() *DnsHosts { 305 | if in == nil { 306 | return nil 307 | } 308 | out := new(DnsHosts) 309 | in.DeepCopyInto(out) 310 | return out 311 | } 312 | 313 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 314 | func (in *DnsHosts) DeepCopyObject() runtime.Object { 315 | if c := in.DeepCopy(); c != nil { 316 | return c 317 | } 318 | return nil 319 | } 320 | 321 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 322 | func (in *DnsHostsList) DeepCopyInto(out *DnsHostsList) { 323 | *out = *in 324 | out.TypeMeta = in.TypeMeta 325 | in.ListMeta.DeepCopyInto(&out.ListMeta) 326 | if in.Items != nil { 327 | in, out := &in.Items, &out.Items 328 | *out = make([]DnsHosts, len(*in)) 329 | for i := range *in { 330 | (*in)[i].DeepCopyInto(&(*out)[i]) 331 | } 332 | } 333 | } 334 | 335 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DnsHostsList. 336 | func (in *DnsHostsList) DeepCopy() *DnsHostsList { 337 | if in == nil { 338 | return nil 339 | } 340 | out := new(DnsHostsList) 341 | in.DeepCopyInto(out) 342 | return out 343 | } 344 | 345 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 346 | func (in *DnsHostsList) DeepCopyObject() runtime.Object { 347 | if c := in.DeepCopy(); c != nil { 348 | return c 349 | } 350 | return nil 351 | } 352 | 353 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 354 | func (in *DnsHostsSpec) DeepCopyInto(out *DnsHostsSpec) { 355 | *out = *in 356 | if in.Hosts != nil { 357 | in, out := &in.Hosts, &out.Hosts 358 | *out = make([]DnsHost, len(*in)) 359 | for i := range *in { 360 | (*in)[i].DeepCopyInto(&(*out)[i]) 361 | } 362 | } 363 | } 364 | 365 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DnsHostsSpec. 366 | func (in *DnsHostsSpec) DeepCopy() *DnsHostsSpec { 367 | if in == nil { 368 | return nil 369 | } 370 | out := new(DnsHostsSpec) 371 | in.DeepCopyInto(out) 372 | return out 373 | } 374 | 375 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 376 | func (in *DnsHostsStatus) DeepCopyInto(out *DnsHostsStatus) { 377 | *out = *in 378 | } 379 | 380 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DnsHostsStatus. 381 | func (in *DnsHostsStatus) DeepCopy() *DnsHostsStatus { 382 | if in == nil { 383 | return nil 384 | } 385 | out := new(DnsHostsStatus) 386 | in.DeepCopyInto(out) 387 | return out 388 | } 389 | 390 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 391 | func (in *DnsmasqOption) DeepCopyInto(out *DnsmasqOption) { 392 | *out = *in 393 | if in.Values != nil { 394 | in, out := &in.Values, &out.Values 395 | *out = make([]string, len(*in)) 396 | copy(*out, *in) 397 | } 398 | } 399 | 400 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DnsmasqOption. 401 | func (in *DnsmasqOption) DeepCopy() *DnsmasqOption { 402 | if in == nil { 403 | return nil 404 | } 405 | out := new(DnsmasqOption) 406 | in.DeepCopyInto(out) 407 | return out 408 | } 409 | 410 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 411 | func (in *DnsmasqOptions) DeepCopyInto(out *DnsmasqOptions) { 412 | *out = *in 413 | out.TypeMeta = in.TypeMeta 414 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 415 | in.Spec.DeepCopyInto(&out.Spec) 416 | out.Status = in.Status 417 | } 418 | 419 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DnsmasqOptions. 420 | func (in *DnsmasqOptions) DeepCopy() *DnsmasqOptions { 421 | if in == nil { 422 | return nil 423 | } 424 | out := new(DnsmasqOptions) 425 | in.DeepCopyInto(out) 426 | return out 427 | } 428 | 429 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 430 | func (in *DnsmasqOptions) DeepCopyObject() runtime.Object { 431 | if c := in.DeepCopy(); c != nil { 432 | return c 433 | } 434 | return nil 435 | } 436 | 437 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 438 | func (in *DnsmasqOptionsList) DeepCopyInto(out *DnsmasqOptionsList) { 439 | *out = *in 440 | out.TypeMeta = in.TypeMeta 441 | in.ListMeta.DeepCopyInto(&out.ListMeta) 442 | if in.Items != nil { 443 | in, out := &in.Items, &out.Items 444 | *out = make([]DnsmasqOptions, len(*in)) 445 | for i := range *in { 446 | (*in)[i].DeepCopyInto(&(*out)[i]) 447 | } 448 | } 449 | } 450 | 451 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DnsmasqOptionsList. 452 | func (in *DnsmasqOptionsList) DeepCopy() *DnsmasqOptionsList { 453 | if in == nil { 454 | return nil 455 | } 456 | out := new(DnsmasqOptionsList) 457 | in.DeepCopyInto(out) 458 | return out 459 | } 460 | 461 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 462 | func (in *DnsmasqOptionsList) DeepCopyObject() runtime.Object { 463 | if c := in.DeepCopy(); c != nil { 464 | return c 465 | } 466 | return nil 467 | } 468 | 469 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 470 | func (in *DnsmasqOptionsSpec) DeepCopyInto(out *DnsmasqOptionsSpec) { 471 | *out = *in 472 | if in.Options != nil { 473 | in, out := &in.Options, &out.Options 474 | *out = make([]DnsmasqOption, len(*in)) 475 | for i := range *in { 476 | (*in)[i].DeepCopyInto(&(*out)[i]) 477 | } 478 | } 479 | } 480 | 481 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DnsmasqOptionsSpec. 482 | func (in *DnsmasqOptionsSpec) DeepCopy() *DnsmasqOptionsSpec { 483 | if in == nil { 484 | return nil 485 | } 486 | out := new(DnsmasqOptionsSpec) 487 | in.DeepCopyInto(out) 488 | return out 489 | } 490 | 491 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 492 | func (in *DnsmasqOptionsStatus) DeepCopyInto(out *DnsmasqOptionsStatus) { 493 | *out = *in 494 | } 495 | 496 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DnsmasqOptionsStatus. 497 | func (in *DnsmasqOptionsStatus) DeepCopy() *DnsmasqOptionsStatus { 498 | if in == nil { 499 | return nil 500 | } 501 | out := new(DnsmasqOptionsStatus) 502 | in.DeepCopyInto(out) 503 | return out 504 | } 505 | -------------------------------------------------------------------------------- /config/crd/bases/dnsmasq.kvaps.cf_dhcphosts.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.8.0 7 | creationTimestamp: null 8 | name: dhcphosts.dnsmasq.kvaps.cf 9 | spec: 10 | group: dnsmasq.kvaps.cf 11 | names: 12 | kind: DhcpHosts 13 | listKind: DhcpHostsList 14 | plural: dhcphosts 15 | singular: dhcphosts 16 | scope: Namespaced 17 | versions: 18 | - additionalPrinterColumns: 19 | - jsonPath: .spec.controller 20 | name: Controller 21 | type: string 22 | - jsonPath: .metadata.creationTimestamp 23 | name: Age 24 | type: date 25 | name: v1beta1 26 | schema: 27 | openAPIV3Schema: 28 | description: DhcpHosts is the Schema for the dhcphosts API 29 | properties: 30 | apiVersion: 31 | description: 'APIVersion defines the versioned schema of this representation 32 | of an object. Servers should convert recognized schemas to the latest 33 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 34 | type: string 35 | kind: 36 | description: 'Kind is a string value representing the REST resource this 37 | object represents. Servers may infer this from the endpoint the client 38 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 39 | type: string 40 | metadata: 41 | type: object 42 | spec: 43 | description: DhcpHostsSpec defines the desired state of DhcpHosts 44 | properties: 45 | controller: 46 | type: string 47 | hosts: 48 | items: 49 | description: DnsmasqDhcpHost holds the mapping between Macs and 50 | IP that will be added to dnsmasq dhcp-hosts file. 51 | properties: 52 | clientID: 53 | type: string 54 | hostname: 55 | type: string 56 | ignore: 57 | type: boolean 58 | ip: 59 | type: string 60 | leaseTime: 61 | type: string 62 | macs: 63 | items: 64 | type: string 65 | type: array 66 | setTags: 67 | items: 68 | type: string 69 | type: array 70 | tags: 71 | items: 72 | type: string 73 | type: array 74 | type: object 75 | type: array 76 | type: object 77 | status: 78 | description: DhcpHostsStatus defines the observed state of DhcpHosts 79 | type: object 80 | type: object 81 | served: true 82 | storage: true 83 | subresources: {} 84 | status: 85 | acceptedNames: 86 | kind: "" 87 | plural: "" 88 | conditions: [] 89 | storedVersions: [] 90 | -------------------------------------------------------------------------------- /config/crd/bases/dnsmasq.kvaps.cf_dhcpoptions.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.8.0 7 | creationTimestamp: null 8 | name: dhcpoptions.dnsmasq.kvaps.cf 9 | spec: 10 | group: dnsmasq.kvaps.cf 11 | names: 12 | kind: DhcpOptions 13 | listKind: DhcpOptionsList 14 | plural: dhcpoptions 15 | singular: dhcpoptions 16 | scope: Namespaced 17 | versions: 18 | - additionalPrinterColumns: 19 | - jsonPath: .spec.controller 20 | name: Controller 21 | type: string 22 | - jsonPath: .metadata.creationTimestamp 23 | name: Age 24 | type: date 25 | name: v1beta1 26 | schema: 27 | openAPIV3Schema: 28 | description: DhcpOptions is the Schema for the dhcpoptions API 29 | properties: 30 | apiVersion: 31 | description: 'APIVersion defines the versioned schema of this representation 32 | of an object. Servers should convert recognized schemas to the latest 33 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 34 | type: string 35 | kind: 36 | description: 'Kind is a string value representing the REST resource this 37 | object represents. Servers may infer this from the endpoint the client 38 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 39 | type: string 40 | metadata: 41 | type: object 42 | spec: 43 | description: DhcpOptionsSpec defines the desired state of DhcpOptions 44 | properties: 45 | controller: 46 | type: string 47 | options: 48 | items: 49 | description: DhcpOption defines dhcp-option for dnsmasq 50 | properties: 51 | encap: 52 | type: string 53 | key: 54 | pattern: ^([0-9]+|option:.+|option6:.+)$ 55 | type: string 56 | leaseTime: 57 | type: string 58 | tags: 59 | items: 60 | type: string 61 | type: array 62 | values: 63 | items: 64 | type: string 65 | type: array 66 | viEncap: 67 | type: string 68 | required: 69 | - key 70 | - values 71 | type: object 72 | type: array 73 | type: object 74 | status: 75 | description: DhcpOptionsStatus defines the observed state of DhcpOptions 76 | type: object 77 | type: object 78 | served: true 79 | storage: true 80 | subresources: {} 81 | status: 82 | acceptedNames: 83 | kind: "" 84 | plural: "" 85 | conditions: [] 86 | storedVersions: [] 87 | -------------------------------------------------------------------------------- /config/crd/bases/dnsmasq.kvaps.cf_dnshosts.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.8.0 7 | creationTimestamp: null 8 | name: dnshosts.dnsmasq.kvaps.cf 9 | spec: 10 | group: dnsmasq.kvaps.cf 11 | names: 12 | kind: DnsHosts 13 | listKind: DnsHostsList 14 | plural: dnshosts 15 | singular: dnshosts 16 | scope: Namespaced 17 | versions: 18 | - additionalPrinterColumns: 19 | - jsonPath: .spec.controller 20 | name: Controller 21 | type: string 22 | - jsonPath: .metadata.creationTimestamp 23 | name: Age 24 | type: date 25 | name: v1beta1 26 | schema: 27 | openAPIV3Schema: 28 | description: DnsHosts is the Schema for the dnshosts API 29 | properties: 30 | apiVersion: 31 | description: 'APIVersion defines the versioned schema of this representation 32 | of an object. Servers should convert recognized schemas to the latest 33 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 34 | type: string 35 | kind: 36 | description: 'Kind is a string value representing the REST resource this 37 | object represents. Servers may infer this from the endpoint the client 38 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 39 | type: string 40 | metadata: 41 | type: object 42 | spec: 43 | description: DnsHostsSpec defines the desired state of DnsHosts 44 | properties: 45 | controller: 46 | type: string 47 | hosts: 48 | items: 49 | description: DnsHost holds the mapping between IP and hostnames 50 | that will be added to dnsmasq hosts file. 51 | properties: 52 | hostnames: 53 | description: Hostnames for the above IP address. 54 | items: 55 | type: string 56 | type: array 57 | ip: 58 | description: IP address of the host file entry. 59 | type: string 60 | required: 61 | - ip 62 | type: object 63 | type: array 64 | type: object 65 | status: 66 | description: DnsHostsStatus defines the observed state of DnsHosts 67 | type: object 68 | type: object 69 | served: true 70 | storage: true 71 | subresources: {} 72 | status: 73 | acceptedNames: 74 | kind: "" 75 | plural: "" 76 | conditions: [] 77 | storedVersions: [] 78 | -------------------------------------------------------------------------------- /config/crd/bases/dnsmasq.kvaps.cf_dnsmasqoptions.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.8.0 7 | creationTimestamp: null 8 | name: dnsmasqoptions.dnsmasq.kvaps.cf 9 | spec: 10 | group: dnsmasq.kvaps.cf 11 | names: 12 | kind: DnsmasqOptions 13 | listKind: DnsmasqOptionsList 14 | plural: dnsmasqoptions 15 | singular: dnsmasqoptions 16 | scope: Namespaced 17 | versions: 18 | - additionalPrinterColumns: 19 | - jsonPath: .spec.controller 20 | name: Controller 21 | type: string 22 | - jsonPath: .metadata.creationTimestamp 23 | name: Age 24 | type: date 25 | name: v1beta1 26 | schema: 27 | openAPIV3Schema: 28 | description: DnsmasqOptions is the Schema for the dnsmasqoptions API 29 | properties: 30 | apiVersion: 31 | description: 'APIVersion defines the versioned schema of this representation 32 | of an object. Servers should convert recognized schemas to the latest 33 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 34 | type: string 35 | kind: 36 | description: 'Kind is a string value representing the REST resource this 37 | object represents. Servers may infer this from the endpoint the client 38 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 39 | type: string 40 | metadata: 41 | type: object 42 | spec: 43 | description: DnsmasqOptionsSpec defines the desired state of DnsmasqOptions 44 | properties: 45 | controller: 46 | type: string 47 | options: 48 | items: 49 | description: DnsmasqOption defines option for dnsmasq 50 | properties: 51 | key: 52 | enum: 53 | - dhcp-range 54 | - dhcp-host 55 | - dhcp-userclass 56 | - dhcp-circuitid 57 | - dhcp-remoteid 58 | - dhcp-subscrid 59 | - dhcp-ignore 60 | - dhcp-broadcast 61 | - mx-host 62 | - dhcp-boot 63 | - dhcp-option 64 | - dhcp-option-force 65 | - server 66 | - rev-server 67 | - local 68 | - domain 69 | - dhcp-vendorclass 70 | - alias 71 | - dhcp-vendorclass 72 | - srv-host 73 | - txt-record 74 | - ptr-record 75 | - bootp-dynamic 76 | - dhcp-mac 77 | - dhcp-ignore-names 78 | - rebind-domain-ok 79 | - dhcp-match 80 | - dhcp-name-match 81 | - tag-if 82 | - naptr-record 83 | - dhcp-generate-names 84 | - cname 85 | - pxe-service 86 | - add-mac 87 | - dhcp-duid 88 | - host-record 89 | - caa-record 90 | - dns-rr 91 | - auth-zone 92 | - synth-domain 93 | type: string 94 | values: 95 | items: 96 | type: string 97 | type: array 98 | required: 99 | - key 100 | - values 101 | type: object 102 | type: array 103 | type: object 104 | status: 105 | description: DnsmasqOptionsStatus defines the observed state of DnsmasqOptions 106 | type: object 107 | type: object 108 | served: true 109 | storage: true 110 | subresources: {} 111 | status: 112 | acceptedNames: 113 | kind: "" 114 | plural: "" 115 | conditions: [] 116 | storedVersions: [] 117 | -------------------------------------------------------------------------------- /config/crd/bases/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - dnsmasq.kvaps.cf_dhcphosts.yaml 5 | - dnsmasq.kvaps.cf_dhcpoptions.yaml 6 | - dnsmasq.kvaps.cf_dnshosts.yaml 7 | - dnsmasq.kvaps.cf_dnsmasqoptions.yaml -------------------------------------------------------------------------------- /config/dhcp-server/dhcp-server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: dnsmasq-controller-dhcp 5 | labels: 6 | app: dnsmasq-controller 7 | role: dhcp 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: dnsmasq-controller 12 | role: dhcp 13 | template: 14 | metadata: 15 | labels: 16 | app: dnsmasq-controller 17 | role: dhcp 18 | spec: 19 | hostNetwork: true 20 | containers: 21 | - name: dnsmasq 22 | image: docker.io/kvaps/dnsmasq-controller:latest 23 | securityContext: 24 | capabilities: 25 | add: ["NET_ADMIN"] 26 | args: 27 | #- --watch-namespace=$(NAMESPACE) 28 | - --metrics-addr=:0 29 | - --enable-leader-election=true 30 | - --dhcp 31 | - -- 32 | - --dhcp-broadcast 33 | - --dhcp-authoritative 34 | - --dhcp-leasefile=/dev/null 35 | env: 36 | #- name: NAMESPACE 37 | # valueFrom: 38 | # fieldRef: 39 | # fieldPath: metadata.namespace 40 | resources: 41 | limits: 42 | cpu: 100m 43 | memory: 30Mi 44 | requests: 45 | cpu: 100m 46 | memory: 20Mi 47 | priorityClassName: system-node-critical 48 | serviceAccountName: dnsmasq-controller 49 | terminationGracePeriodSeconds: 10 50 | nodeSelector: 51 | node-role.kubernetes.io/dnsmasq: "" 52 | tolerations: 53 | - effect: NoSchedule 54 | operator: Exists 55 | - effect: NoExecute 56 | operator: Exists 57 | - key: CriticalAddonsOnly 58 | operator: Exists 59 | -------------------------------------------------------------------------------- /config/dhcp-server/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - dhcp-server.yaml -------------------------------------------------------------------------------- /config/dns-server/dns-server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: dnsmasq-controller-dns 5 | labels: 6 | app: dnsmasq-controller 7 | role: dns 8 | spec: 9 | selector: 10 | matchLabels: 11 | app: dnsmasq-controller 12 | role: dns 13 | template: 14 | metadata: 15 | labels: 16 | app: dnsmasq-controller 17 | role: dns 18 | spec: 19 | hostNetwork: true 20 | containers: 21 | - name: dnsmasq 22 | image: docker.io/kvaps/dnsmasq-controller:latest 23 | args: 24 | #- --watch-namespace=$(NAMESPACE) 25 | - --metrics-addr=:0 26 | - --dns 27 | - -- 28 | - --expand-hosts 29 | - --domain=infra.example.org 30 | - --auth-server=$(NODE_NAME),$(NODE_IP) 31 | - --auth-zone=infra.example.org 32 | env: 33 | #- name: NAMESPACE 34 | # valueFrom: 35 | # fieldRef: 36 | # fieldPath: metadata.namespace 37 | - name: NODE_NAME 38 | valueFrom: 39 | fieldRef: 40 | fieldPath: spec.nodeName 41 | - name: NODE_IP 42 | valueFrom: 43 | fieldRef: 44 | fieldPath: status.podIP 45 | resources: 46 | limits: 47 | cpu: 100m 48 | memory: 30Mi 49 | requests: 50 | cpu: 100m 51 | memory: 20Mi 52 | priorityClassName: system-node-critical 53 | serviceAccountName: dnsmasq-controller 54 | terminationGracePeriodSeconds: 10 55 | nodeSelector: 56 | node-role.kubernetes.io/dnsmasq: "" 57 | tolerations: 58 | - effect: NoSchedule 59 | operator: Exists 60 | - effect: NoExecute 61 | operator: Exists 62 | - key: CriticalAddonsOnly 63 | operator: Exists 64 | -------------------------------------------------------------------------------- /config/dns-server/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - dns-server.yaml -------------------------------------------------------------------------------- /config/rbac-viewer-editor/dhcphosts_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit dhcphosts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: dhcphosts-editor-role 6 | rules: 7 | - apiGroups: 8 | - dnsmasq.kvaps.cf 9 | resources: 10 | - dhcphosts 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - dnsmasq.kvaps.cf 21 | resources: 22 | - dhcphosts/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac-viewer-editor/dhcphosts_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view dhcphosts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: dhcphosts-viewer-role 6 | rules: 7 | - apiGroups: 8 | - dnsmasq.kvaps.cf 9 | resources: 10 | - dhcphosts 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - dnsmasq.kvaps.cf 17 | resources: 18 | - dhcphosts/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac-viewer-editor/dhcpoptions_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit dhcpoptions. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: dhcpoptions-editor-role 6 | rules: 7 | - apiGroups: 8 | - dnsmasq.kvaps.cf 9 | resources: 10 | - dhcpoptions 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - dnsmasq.kvaps.cf 21 | resources: 22 | - dhcpoptions/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac-viewer-editor/dhcpoptions_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view dhcpoptions. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: dhcpoptions-viewer-role 6 | rules: 7 | - apiGroups: 8 | - dnsmasq.kvaps.cf 9 | resources: 10 | - dhcpoptions 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - dnsmasq.kvaps.cf 17 | resources: 18 | - dhcpoptions/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac-viewer-editor/dnshosts_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit dnshosts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: dnshosts-editor-role 6 | rules: 7 | - apiGroups: 8 | - dnsmasq.kvaps.cf 9 | resources: 10 | - dnshosts 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - dnsmasq.kvaps.cf 21 | resources: 22 | - dnshosts/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac-viewer-editor/dnshosts_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view dnshosts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: dnshosts-viewer-role 6 | rules: 7 | - apiGroups: 8 | - dnsmasq.kvaps.cf 9 | resources: 10 | - dnshosts 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - dnsmasq.kvaps.cf 17 | resources: 18 | - dnshosts/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac-viewer-editor/dnsmasqoptions_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit dnsmasqoptions. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: dnsmasqoptions-editor-role 6 | rules: 7 | - apiGroups: 8 | - dnsmasq.kvaps.cf 9 | resources: 10 | - dnsmasqoptions 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - dnsmasq.kvaps.cf 21 | resources: 22 | - dnsmasqoptions/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac-viewer-editor/dnsmasqoptions_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view dnsmasqoptions. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: dnsmasqoptions-viewer-role 6 | rules: 7 | - apiGroups: 8 | - dnsmasq.kvaps.cf 9 | resources: 10 | - dnsmasqoptions 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - dnsmasq.kvaps.cf 17 | resources: 18 | - dnsmasqoptions/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac-viewer-editor/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - dhcphosts_editor_role.yaml 5 | - dhcphosts_viewer_role.yaml 6 | - dhcpoptions_editor_role.yaml 7 | - dhcpoptions_viewer_role.yaml 8 | - dnshosts_editor_role.yaml 9 | - dnshosts_viewer_role.yaml 10 | - dnsmasqoptions_editor_role.yaml 11 | - dnsmasqoptions_viewer_role.yaml -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - leader_election_role_binding.yaml 5 | - leader_election_role.yaml 6 | - role_binding.yaml 7 | - role.yaml 8 | - service_account.yaml -------------------------------------------------------------------------------- /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: dnsmasq-controller-leader-election 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: dnsmasq-controller-leader-election 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: dnsmasq-controller-leader-election 9 | subjects: 10 | - kind: ServiceAccount 11 | name: dnsmasq-controller 12 | namespace: default 13 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | creationTimestamp: null 6 | name: dnsmasq-controller 7 | rules: 8 | - apiGroups: 9 | - dnsmasq.kvaps.cf 10 | resources: 11 | - dhcphosts 12 | verbs: 13 | - get 14 | - list 15 | - watch 16 | - apiGroups: 17 | - dnsmasq.kvaps.cf 18 | resources: 19 | - dhcpoptions 20 | verbs: 21 | - get 22 | - list 23 | - watch 24 | - apiGroups: 25 | - dnsmasq.kvaps.cf 26 | resources: 27 | - dnshosts 28 | verbs: 29 | - get 30 | - list 31 | - watch 32 | - apiGroups: 33 | - dnsmasq.kvaps.cf 34 | resources: 35 | - dnsmasqoptions 36 | verbs: 37 | - get 38 | - list 39 | - watch 40 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: dnsmasq-controller 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: dnsmasq-controller 9 | subjects: 10 | - kind: ServiceAccount 11 | name: dnsmasq-controller 12 | namespace: default 13 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: dnsmasq-controller 5 | -------------------------------------------------------------------------------- /config/samples/dnsmasq_v1beta1_dhcphosts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dnsmasq.kvaps.cf/v1beta1 2 | kind: DhcpHosts 3 | metadata: 4 | name: node1 5 | spec: 6 | controller: "" 7 | hosts: 8 | - ip: 10.9.8.7 9 | macs: 10 | - 94:57:a5:d3:b6:f2 11 | - 94:57:a5:d3:b6:f3 12 | clientIDs: 13 | - "*" 14 | setTags: 15 | - hp 16 | tags: 17 | - ltsp1 18 | hostname: node1 19 | leaseTime: infinite 20 | ignore: false 21 | -------------------------------------------------------------------------------- /config/samples/dnsmasq_v1beta1_dhcpoptions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dnsmasq.kvaps.cf/v1beta1 2 | kind: DhcpOptions 3 | metadata: 4 | name: ltsp1 5 | spec: 6 | controller: "" 7 | options: 8 | - key: option:ntp-server 9 | values: 10 | - 192.168.0.4 11 | tags: 12 | - ltsp1 13 | -------------------------------------------------------------------------------- /config/samples/dnsmasq_v1beta1_dnshosts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dnsmasq.kvaps.cf/v1beta1 2 | kind: DnsHosts 3 | metadata: 4 | name: node1 5 | spec: 6 | controller: "" 7 | hosts: 8 | - ip: "10.9.8.7" 9 | hostnames: 10 | - "foo.local" 11 | - "bar.local" 12 | - ip: "10.1.2.3" 13 | hostnames: 14 | - "foo.remote" 15 | - "bar.remote" 16 | -------------------------------------------------------------------------------- /config/samples/dnsmasq_v1beta1_dnsmasqoptions.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: dnsmasq.kvaps.cf/v1beta1 2 | kind: DnsmasqOptions 3 | metadata: 4 | name: node1 5 | spec: 6 | controller: "" 7 | options: 8 | - key: txt-record 9 | values: 10 | - example.com 11 | - v=spf1 a -all 12 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - dnsmasq_v1beta1_dhcphosts.yaml 5 | - dnsmasq_v1beta1_dhcpoptions.yaml 6 | - dnsmasq_v1beta1_dnshosts.yaml 7 | - dnsmasq_v1beta1_dnsmasqoptions.yaml -------------------------------------------------------------------------------- /controllers/dhcphosts_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 | "os" 22 | 23 | "github.com/go-logr/logr" 24 | "k8s.io/apimachinery/pkg/api/errors" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | ctrl "sigs.k8s.io/controller-runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | dnsmasqv1beta1 "github.com/kvaps/dnsmasq-controller/api/v1beta1" 30 | "github.com/kvaps/dnsmasq-controller/pkg/conf" 31 | "github.com/kvaps/dnsmasq-controller/pkg/util" 32 | ) 33 | 34 | // DhcpHostsReconciler reconciles a DhcpHosts object 35 | type DhcpHostsReconciler struct { 36 | client.Client 37 | Log logr.Logger 38 | Scheme *runtime.Scheme 39 | } 40 | 41 | // +kubebuilder:rbac:groups=dnsmasq.kvaps.cf,resources=dhcphosts,verbs=get;list;watch 42 | 43 | func (r *DhcpHostsReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 44 | _ = context.Background() 45 | _ = r.Log.WithValues("dhcphost", req.NamespacedName) 46 | config := conf.GetConfig() 47 | 48 | configFile := config.DnsmasqConfDir + "/dhcp-hosts/" + req.Namespace + "-" + req.Name 49 | 50 | res := &dnsmasqv1beta1.DhcpHosts{} 51 | err := r.Client.Get(context.TODO(), req.NamespacedName, res) 52 | if err != nil { 53 | if errors.IsNotFound(err) { 54 | // Request object not found 55 | if _, err := os.Stat(configFile); !os.IsNotExist(err) { 56 | os.Remove(configFile) 57 | r.Log.Info("Removed " + configFile) 58 | config.Generation++ 59 | } 60 | return ctrl.Result{}, nil 61 | } 62 | // Error reading the object - requeue the request. 63 | return ctrl.Result{}, err 64 | } 65 | 66 | if res.Spec.Controller != config.ControllerName { 67 | if _, err := os.Stat(configFile); !os.IsNotExist(err) { 68 | // Controller name has been changed 69 | os.Remove(configFile) 70 | r.Log.Info("Removed " + configFile) 71 | config.Generation++ 72 | } 73 | return ctrl.Result{}, nil 74 | } 75 | 76 | // Write dhcp-hosts 77 | var configData string 78 | var configLine string 79 | for _, h := range res.Spec.Hosts { 80 | configLine = "" 81 | for _, v := range h.Macs { 82 | configLine += "," + v 83 | } 84 | if h.ClientID != "" { 85 | configLine += ",id:" + h.ClientID 86 | } 87 | for _, v := range h.SetTags { 88 | configLine += ",set:" + v 89 | } 90 | for _, v := range h.Tags { 91 | configLine += ",tag:" + v 92 | } 93 | if h.IP != "" { 94 | configLine += "," + h.IP 95 | } 96 | if h.Hostname != "" { 97 | configLine += "," + h.Hostname 98 | } 99 | if h.LeaseTime != "" { 100 | configLine += "," + h.LeaseTime 101 | } 102 | if h.Ignore { 103 | configLine += ",ignore" 104 | } 105 | configLine += "\n" 106 | configData += configLine[1:] 107 | } 108 | configBytes := []byte(configData) 109 | 110 | configWritten, err := util.WriteConfig(configFile, configFile, configBytes) 111 | if err != nil { 112 | r.Log.Error(err, "Failed to update "+configFile) 113 | return ctrl.Result{}, nil 114 | } 115 | 116 | if configWritten { 117 | r.Log.Info("Written " + configFile) 118 | config.Generation++ 119 | } 120 | 121 | return ctrl.Result{}, nil 122 | } 123 | 124 | func (r *DhcpHostsReconciler) SetupWithManager(mgr ctrl.Manager) error { 125 | return ctrl.NewControllerManagedBy(mgr). 126 | For(&dnsmasqv1beta1.DhcpHosts{}). 127 | Complete(r) 128 | } 129 | -------------------------------------------------------------------------------- /controllers/dhcpoptions_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 | "os" 22 | 23 | "github.com/go-logr/logr" 24 | "k8s.io/apimachinery/pkg/api/errors" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | ctrl "sigs.k8s.io/controller-runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | dnsmasqv1beta1 "github.com/kvaps/dnsmasq-controller/api/v1beta1" 30 | "github.com/kvaps/dnsmasq-controller/pkg/conf" 31 | "github.com/kvaps/dnsmasq-controller/pkg/util" 32 | ) 33 | 34 | // DhcpOptionsReconciler reconciles a DhcpOptions object 35 | type DhcpOptionsReconciler struct { 36 | client.Client 37 | Log logr.Logger 38 | Scheme *runtime.Scheme 39 | } 40 | 41 | // +kubebuilder:rbac:groups=dnsmasq.kvaps.cf,resources=dhcpoptions,verbs=get;list;watch 42 | 43 | func (r *DhcpOptionsReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 44 | _ = context.Background() 45 | _ = r.Log.WithValues("dnsmasqdhcpoptionset", req.NamespacedName) 46 | config := conf.GetConfig() 47 | 48 | configFile := config.DnsmasqConfDir + "/dhcp-opts/" + req.Namespace + "-" + req.Name 49 | 50 | res := &dnsmasqv1beta1.DhcpOptions{} 51 | err := r.Client.Get(context.TODO(), req.NamespacedName, res) 52 | if err != nil { 53 | if errors.IsNotFound(err) { 54 | // Request object not found 55 | if _, err := os.Stat(configFile); !os.IsNotExist(err) { 56 | os.Remove(configFile) 57 | r.Log.Info("Removed " + configFile) 58 | config.Generation++ 59 | } 60 | return ctrl.Result{}, nil 61 | } 62 | // Error reading the object - requeue the request. 63 | return ctrl.Result{}, err 64 | } 65 | 66 | if res.Spec.Controller != config.ControllerName { 67 | if _, err := os.Stat(configFile); !os.IsNotExist(err) { 68 | // Controller name has been changed 69 | os.Remove(configFile) 70 | r.Log.Info("Removed " + configFile) 71 | config.Generation++ 72 | } 73 | return ctrl.Result{}, nil 74 | } 75 | 76 | // Write dhcp-hosts 77 | var configData string 78 | var configLine string 79 | for _, r := range res.Spec.Options { 80 | configLine = "" 81 | for _, v := range r.Tags { 82 | configLine += ",tag:" + v 83 | } 84 | if r.Encap != "" { 85 | configLine += ",encap:" + r.Encap 86 | } 87 | if r.ViEncap != "" { 88 | configLine += ",vi-encap:" + r.ViEncap 89 | } 90 | if r.Vendor != "" { 91 | configLine += ",vendor:" + r.Vendor 92 | } 93 | if r.Key != "" { 94 | configLine += "," + r.Key 95 | } 96 | for _, v := range r.Values { 97 | configLine += "," + v 98 | } 99 | configLine += "\n" 100 | configData += configLine[1:] 101 | } 102 | configBytes := []byte(configData) 103 | 104 | configWritten, err := util.WriteConfig(configFile, configFile, configBytes) 105 | if err != nil { 106 | r.Log.Error(err, "Failed to update "+configFile) 107 | return ctrl.Result{}, nil 108 | } 109 | 110 | if configWritten { 111 | r.Log.Info("Written " + configFile) 112 | config.Generation++ 113 | } 114 | 115 | return ctrl.Result{}, nil 116 | } 117 | 118 | func (r *DhcpOptionsReconciler) SetupWithManager(mgr ctrl.Manager) error { 119 | return ctrl.NewControllerManagedBy(mgr). 120 | For(&dnsmasqv1beta1.DhcpOptions{}). 121 | Complete(r) 122 | } 123 | -------------------------------------------------------------------------------- /controllers/dnshosts_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 | "os" 22 | 23 | "github.com/go-logr/logr" 24 | "k8s.io/apimachinery/pkg/api/errors" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | ctrl "sigs.k8s.io/controller-runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | dnsmasqv1beta1 "github.com/kvaps/dnsmasq-controller/api/v1beta1" 30 | "github.com/kvaps/dnsmasq-controller/pkg/conf" 31 | "github.com/kvaps/dnsmasq-controller/pkg/util" 32 | ) 33 | 34 | // DnsHostsReconciler reconciles a DnsHosts object 35 | type DnsHostsReconciler struct { 36 | client.Client 37 | Log logr.Logger 38 | Scheme *runtime.Scheme 39 | } 40 | 41 | // +kubebuilder:rbac:groups=dnsmasq.kvaps.cf,resources=dnshosts,verbs=get;list;watch 42 | 43 | func (r *DnsHostsReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 44 | _ = context.Background() 45 | _ = r.Log.WithValues("dnshost", req.NamespacedName) 46 | config := conf.GetConfig() 47 | 48 | configFile := config.DnsmasqConfDir + "/hosts/" + req.Namespace + "-" + req.Name 49 | 50 | res := &dnsmasqv1beta1.DnsHosts{} 51 | err := r.Client.Get(context.TODO(), req.NamespacedName, res) 52 | if err != nil { 53 | if errors.IsNotFound(err) { 54 | // Request object not found 55 | if _, err := os.Stat(configFile); !os.IsNotExist(err) { 56 | os.Remove(configFile) 57 | r.Log.Info("Removed " + configFile) 58 | config.Generation++ 59 | } 60 | return ctrl.Result{}, nil 61 | } 62 | // Error reading the object - requeue the request. 63 | return ctrl.Result{}, err 64 | } 65 | 66 | if res.Spec.Controller != config.ControllerName { 67 | if _, err := os.Stat(configFile); !os.IsNotExist(err) { 68 | // Controller name has been changed 69 | os.Remove(configFile) 70 | r.Log.Info("Removed " + configFile) 71 | config.Generation++ 72 | } 73 | return ctrl.Result{}, nil 74 | } 75 | 76 | // Write hosts 77 | var configData string 78 | for _, h := range res.Spec.Hosts { 79 | configData += h.IP 80 | for _, hostname := range h.Hostnames { 81 | configData += " " + hostname 82 | } 83 | configData += "\n" 84 | } 85 | configBytes := []byte(configData) 86 | 87 | configWritten, err := util.WriteConfig(configFile, configFile, configBytes) 88 | if err != nil { 89 | r.Log.Error(err, "Failed to update "+configFile) 90 | return ctrl.Result{}, nil 91 | } 92 | 93 | if configWritten { 94 | r.Log.Info("Written " + configFile) 95 | config.Generation++ 96 | } 97 | 98 | return ctrl.Result{}, nil 99 | } 100 | 101 | func (r *DnsHostsReconciler) SetupWithManager(mgr ctrl.Manager) error { 102 | return ctrl.NewControllerManagedBy(mgr). 103 | For(&dnsmasqv1beta1.DnsHosts{}). 104 | Complete(r) 105 | } 106 | -------------------------------------------------------------------------------- /controllers/dnsmasqoptions_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 | "os" 22 | 23 | "github.com/go-logr/logr" 24 | "k8s.io/apimachinery/pkg/api/errors" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | ctrl "sigs.k8s.io/controller-runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | 29 | dnsmasqv1beta1 "github.com/kvaps/dnsmasq-controller/api/v1beta1" 30 | "github.com/kvaps/dnsmasq-controller/pkg/conf" 31 | "github.com/kvaps/dnsmasq-controller/pkg/util" 32 | ) 33 | 34 | // DnsmasqOptionsReconciler reconciles a DnsmasqOptions object 35 | type DnsmasqOptionsReconciler struct { 36 | client.Client 37 | Log logr.Logger 38 | Scheme *runtime.Scheme 39 | } 40 | 41 | // +kubebuilder:rbac:groups=dnsmasq.kvaps.cf,resources=dnsmasqoptions,verbs=get;list;watch 42 | 43 | func (r *DnsmasqOptionsReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 44 | _ = context.Background() 45 | _ = r.Log.WithValues("dnsmasqconfiguration", req.NamespacedName) 46 | config := conf.GetConfig() 47 | 48 | configFile := config.DnsmasqConfDir + "/" + req.Namespace + "-" + req.Name + ".conf" 49 | tmpConfigFile := config.DnsmasqConfDir + "/." + req.Namespace + "-" + req.Name + ".conf.tmp" 50 | 51 | res := &dnsmasqv1beta1.DnsmasqOptions{} 52 | err := r.Client.Get(context.TODO(), req.NamespacedName, res) 53 | if err != nil { 54 | if errors.IsNotFound(err) { 55 | // Request object not found 56 | if _, err := os.Stat(configFile); !os.IsNotExist(err) { 57 | os.Remove(configFile) 58 | r.Log.Info("Removed " + configFile) 59 | config.Generation++ 60 | } 61 | return ctrl.Result{}, nil 62 | } 63 | // Error reading the object - requeue the request. 64 | return ctrl.Result{}, err 65 | } 66 | 67 | if res.Spec.Controller != config.ControllerName { 68 | if _, err := os.Stat(configFile); !os.IsNotExist(err) { 69 | // Controller name has been changed 70 | os.Remove(configFile) 71 | r.Log.Info("Removed " + configFile) 72 | config.Generation++ 73 | } 74 | return ctrl.Result{}, nil 75 | } 76 | 77 | // Write options 78 | var configData string 79 | for _, o := range res.Spec.Options { 80 | if o.Key == "dhcp-range" && !config.EnableDHCP { 81 | continue 82 | } 83 | configData += o.Key + "=" 84 | configValues := "" 85 | for _, v := range o.Values { 86 | configValues += "," + v 87 | } 88 | configData += configValues[1:] 89 | configData += "\n" 90 | } 91 | configBytes := []byte(configData) 92 | 93 | configWritten, err := util.WriteConfig(configFile, tmpConfigFile, configBytes) 94 | if err != nil { 95 | r.Log.Error(err, "Failed to update "+configFile) 96 | return ctrl.Result{}, nil 97 | } 98 | 99 | if configWritten { 100 | if err = util.TestConfig(tmpConfigFile); err != nil { 101 | //os.Remove(tmpConfigFile) 102 | r.Log.Error(err, "Config "+tmpConfigFile+" is invalid!") 103 | return ctrl.Result{}, nil 104 | } 105 | 106 | if err = os.Rename(tmpConfigFile, configFile); err != nil { 107 | os.Remove(tmpConfigFile) 108 | r.Log.Error(err, "Failed to move "+tmpConfigFile+" to "+configFile) 109 | return ctrl.Result{}, nil 110 | } 111 | r.Log.Info("Written " + configFile) 112 | config.Generation++ 113 | } 114 | 115 | return ctrl.Result{}, nil 116 | 117 | } 118 | 119 | func (r *DnsmasqOptionsReconciler) SetupWithManager(mgr ctrl.Manager) error { 120 | return ctrl.NewControllerManagedBy(mgr). 121 | For(&dnsmasqv1beta1.DnsmasqOptions{}). 122 | Complete(r) 123 | } 124 | -------------------------------------------------------------------------------- /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 | dnsmasqv1beta1 "github.com/kvaps/dnsmasq-controller/api/v1beta1" 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 = dnsmasqv1beta1.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | err = dnsmasqv1beta1.AddToScheme(scheme.Scheme) 69 | Expect(err).NotTo(HaveOccurred()) 70 | 71 | err = dnsmasqv1beta1.AddToScheme(scheme.Scheme) 72 | Expect(err).NotTo(HaveOccurred()) 73 | 74 | err = dnsmasqv1beta1.AddToScheme(scheme.Scheme) 75 | Expect(err).NotTo(HaveOccurred()) 76 | 77 | // +kubebuilder:scaffold:scheme 78 | 79 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 80 | Expect(err).ToNot(HaveOccurred()) 81 | Expect(k8sClient).ToNot(BeNil()) 82 | 83 | close(done) 84 | }, 60) 85 | 86 | var _ = AfterSuite(func() { 87 | By("tearing down the test environment") 88 | err := testEnv.Stop() 89 | Expect(err).ToNot(HaveOccurred()) 90 | }) 91 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kvaps/dnsmasq-controller 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/Azure/go-autorest/autorest v0.11.29 // indirect 7 | github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect 8 | github.com/go-logr/logr v0.1.0 9 | github.com/gogo/protobuf v1.3.2 // indirect 10 | github.com/onsi/ginkgo v1.11.0 11 | github.com/onsi/gomega v1.8.1 12 | github.com/prometheus/client_golang v1.11.1 // indirect 13 | go.uber.org/zap v1.10.0 14 | golang.org/x/crypto v0.28.0 // indirect 15 | google.golang.org/protobuf v1.33.0 // indirect 16 | k8s.io/apimachinery v0.17.16 17 | k8s.io/client-go v0.17.16 18 | sigs.k8s.io/controller-runtime v0.5.0 19 | ) 20 | -------------------------------------------------------------------------------- /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/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 6 | github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= 7 | github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 8 | github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= 9 | github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= 10 | github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= 11 | github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= 12 | github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= 13 | github.com/Azure/go-autorest/autorest/adal v0.9.24 h1:BHZfgGsGwdkHDyZdtQRQk1WeUdW0m2WPAwuHZwUi5i4= 14 | github.com/Azure/go-autorest/autorest/adal v0.9.24/go.mod h1:7T1+g0PYFmACYW5LlG2fcoPiPlFHjClyRGL7dRlP5c8= 15 | github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= 16 | github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= 17 | github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= 18 | github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 19 | github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= 20 | github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= 21 | github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= 22 | github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= 23 | github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= 24 | github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= 25 | github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= 26 | github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= 27 | github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= 28 | github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 29 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 30 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 31 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 32 | github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 33 | github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 34 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 35 | github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 36 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 37 | github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= 38 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 39 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 40 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 41 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 42 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 43 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= 44 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 45 | github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 46 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 47 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 48 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 49 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 50 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 51 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 52 | github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 53 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 54 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 55 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 56 | github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= 57 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 58 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 59 | github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 60 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 61 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 62 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 63 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 64 | github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 65 | github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 66 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 67 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 68 | github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 69 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 70 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 71 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 72 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 73 | github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 74 | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 75 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 76 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 77 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 78 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 79 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 80 | github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 81 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 82 | github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 83 | github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 84 | github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 85 | github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= 86 | github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 87 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 88 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 89 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 90 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 91 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 92 | github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 93 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 94 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 95 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 96 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 97 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 98 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 99 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 100 | github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= 101 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 102 | github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= 103 | github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= 104 | github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= 105 | github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 106 | github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 107 | github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= 108 | github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= 109 | github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 110 | github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 111 | github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= 112 | github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 113 | github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 114 | github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 115 | github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= 116 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 117 | github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= 118 | github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 119 | github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 120 | github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= 121 | github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= 122 | github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 123 | github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 124 | github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 125 | github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= 126 | github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= 127 | github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= 128 | github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= 129 | github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= 130 | github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= 131 | github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 132 | github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 133 | github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= 134 | github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= 135 | github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 136 | github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 137 | github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= 138 | github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= 139 | github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= 140 | github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 141 | github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 142 | github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 143 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 144 | github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= 145 | github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= 146 | github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= 147 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 148 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 149 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 150 | github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 151 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 152 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 153 | github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= 154 | github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= 155 | github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 156 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 157 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 158 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE= 159 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 160 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 161 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 162 | github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 163 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 164 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 165 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 166 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 167 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 168 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 169 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 170 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 171 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 172 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 173 | github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= 174 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 175 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 176 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 177 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 178 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 179 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 180 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 181 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 182 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 183 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 184 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 185 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 186 | github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= 187 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 188 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 189 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 190 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 191 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 192 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 193 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 194 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 195 | github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= 196 | github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= 197 | github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= 198 | github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= 199 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 200 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 201 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 202 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 203 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 204 | github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 205 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 206 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 207 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 208 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 209 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 210 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 211 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 212 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 213 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 214 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 215 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 216 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 217 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 218 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 219 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 220 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 221 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 222 | github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= 223 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 224 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 225 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 226 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 227 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 228 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 229 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 230 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 231 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 232 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 233 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 234 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 235 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 236 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 237 | github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 238 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 239 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 240 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 241 | github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 242 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 243 | github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 244 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 245 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 246 | github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= 247 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 248 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 249 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 250 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 251 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 252 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 253 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 254 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 255 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 256 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 257 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 258 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 259 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 260 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 261 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 262 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 263 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 264 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 265 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 266 | github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 267 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 268 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 269 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 270 | github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= 271 | github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 272 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 273 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 274 | github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= 275 | github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= 276 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 277 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 278 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 279 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 280 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 281 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 282 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 283 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 284 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 285 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 286 | github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= 287 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 288 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 289 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 290 | github.com/prometheus/client_golang v1.11.1 h1:+4eQaD7vAZ6DsfsxB15hbE0odUjGI5ARs9yskGu1v4s= 291 | github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 292 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 293 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 294 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 295 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 296 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 297 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 298 | github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= 299 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 300 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 301 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 302 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 303 | github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= 304 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 305 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 306 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 307 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 308 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 309 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 310 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 311 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 312 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 313 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 314 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 315 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 316 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 317 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 318 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 319 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 320 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 321 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 322 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 323 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 324 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 325 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 326 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 327 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 328 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 329 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 330 | github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 331 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 332 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 333 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 334 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 335 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 336 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 337 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 338 | github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 339 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 340 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 341 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 342 | github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= 343 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 344 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 345 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 346 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 347 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 348 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 349 | go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= 350 | go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 351 | go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 352 | go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= 353 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 354 | go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= 355 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 356 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 357 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 358 | go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= 359 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 360 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 361 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 362 | golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 363 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 364 | golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 365 | golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 366 | golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 367 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 368 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 369 | golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 370 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 371 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 372 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 373 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 374 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 375 | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 376 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 377 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 378 | golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= 379 | golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 380 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 381 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 382 | golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 383 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 384 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 385 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 386 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 387 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 388 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 389 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 390 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 391 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 392 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 393 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 394 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 395 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 396 | golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 397 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 398 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 399 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 400 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 401 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 402 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 403 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 404 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 405 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 406 | golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 407 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 408 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 409 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 410 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 411 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 412 | golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 413 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 414 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 415 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 416 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 417 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 418 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 419 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 420 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 421 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 422 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 423 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= 424 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 425 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 426 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 427 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= 428 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 429 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 430 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 431 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 432 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 433 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 434 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 435 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 436 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 437 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 438 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 439 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 440 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 441 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 442 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 443 | golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 444 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 445 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 446 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 447 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 448 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 449 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 450 | golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 451 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 452 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 453 | golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 454 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 455 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 456 | golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 457 | golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 458 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/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-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 461 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 462 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 463 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 464 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 465 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 466 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 467 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 468 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 469 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 470 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 471 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 472 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 473 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 474 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 475 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 476 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 477 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 478 | golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 479 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 480 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 481 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 482 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 483 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 484 | golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 485 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 486 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 487 | golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= 488 | golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 489 | golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 490 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 491 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 492 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 493 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 494 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 495 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 496 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 497 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 498 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 499 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 500 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 501 | golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= 502 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 503 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 504 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 505 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 506 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 507 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 508 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 509 | golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 510 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 511 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 512 | golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 513 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 514 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 515 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 516 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 517 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 518 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 519 | golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 520 | golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 521 | golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 522 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 523 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 524 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 525 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 526 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 527 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 528 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 529 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 530 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 531 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 532 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 533 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 534 | gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= 535 | gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= 536 | gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= 537 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 538 | gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= 539 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 540 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 541 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 542 | google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= 543 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 544 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 545 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 546 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 547 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 548 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 549 | google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 550 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 551 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 552 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 553 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 554 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 555 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 556 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 557 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 558 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 559 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 560 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 561 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 562 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 563 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 564 | gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 565 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 566 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 567 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 568 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 569 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 570 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 571 | gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 572 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 573 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 574 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 575 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 576 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 577 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 578 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 579 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 580 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 581 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 582 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 583 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 584 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 585 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 586 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 587 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 588 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 589 | k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4= 590 | k8s.io/api v0.17.16 h1:whKfZJJp9m5fklRnlvO8+mJzpXat0gX0n+90d1hWTu0= 591 | k8s.io/api v0.17.16/go.mod h1:W8uKRxJeYRlAbWuk4CZv6BzuC7KuZnB6bSTPI7Pi8no= 592 | k8s.io/apiextensions-apiserver v0.17.2 h1:cP579D2hSZNuO/rZj9XFRzwJNYb41DbNANJb6Kolpss= 593 | k8s.io/apiextensions-apiserver v0.17.2/go.mod h1:4KdMpjkEjjDI2pPfBA15OscyNldHWdBCfsWMDWAmSTs= 594 | k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= 595 | k8s.io/apimachinery v0.17.16 h1:A9HqHhUGgUNwki1c1lY6w773WMY1Qx/jR3r9baX1unQ= 596 | k8s.io/apimachinery v0.17.16/go.mod h1:T54ZSpncArE25c5r2PbUPsLeTpkPWY/ivafigSX6+xk= 597 | k8s.io/apiserver v0.17.2/go.mod h1:lBmw/TtQdtxvrTk0e2cgtOxHizXI+d0mmGQURIHQZlo= 598 | k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= 599 | k8s.io/client-go v0.17.16 h1:5g4fmARsp1VFn8tSRZxYpk/DdrT4eGFF/ydYR1tW3VM= 600 | k8s.io/client-go v0.17.16/go.mod h1:TwGfS07/0RyVp+PjSZEg9piBGveZ+hEg9zMUBg1Upbo= 601 | k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= 602 | k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1awLs= 603 | k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 604 | k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 605 | k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 606 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 607 | k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= 608 | k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 609 | k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= 610 | k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 h1:NeQXVJ2XFSkRoPzRo8AId01ZER+j8oV4SZADT4iBOXQ= 611 | k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29/go.mod h1:F+5wygcW0wmRTnM3cOgIqGivxkwSWIWT5YdsDbeAOaU= 612 | k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= 613 | k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 614 | modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= 615 | modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= 616 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 617 | modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= 618 | modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= 619 | sigs.k8s.io/controller-runtime v0.5.0 h1:CbqIy5fbUX+4E9bpnBFd204YAzRYlM9SWW77BbrcDQo= 620 | sigs.k8s.io/controller-runtime v0.5.0/go.mod h1:REiJzC7Y00U+2YkMbT8wxgrsX5USpXKGhb2sCtAXiT8= 621 | sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 622 | sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= 623 | sigs.k8s.io/structured-merge-diff/v2 v2.0.1/go.mod h1:Wb7vfKAodbKgf6tn1Kl0VvGj7mRH6DGaRcixXEJXTsE= 624 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 625 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 626 | -------------------------------------------------------------------------------- /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 | */ -------------------------------------------------------------------------------- /main.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 | 23 | "go.uber.org/zap" 24 | "k8s.io/apimachinery/pkg/runtime" 25 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 26 | _ "k8s.io/client-go/plugin/pkg/client/auth" 27 | "k8s.io/client-go/tools/clientcmd" 28 | ctrl "sigs.k8s.io/controller-runtime" 29 | logrzap "sigs.k8s.io/controller-runtime/pkg/log/zap" 30 | 31 | dnsmasqv1beta1 "github.com/kvaps/dnsmasq-controller/api/v1beta1" 32 | "github.com/kvaps/dnsmasq-controller/controllers" 33 | "github.com/kvaps/dnsmasq-controller/pkg/conf" 34 | "github.com/kvaps/dnsmasq-controller/pkg/server" 35 | // +kubebuilder:scaffold:imports 36 | ) 37 | 38 | var ( 39 | scheme = runtime.NewScheme() 40 | setupLog = ctrl.Log.WithName("setup") 41 | ) 42 | 43 | func init() { 44 | _ = clientgoscheme.AddToScheme(scheme) 45 | 46 | _ = dnsmasqv1beta1.AddToScheme(scheme) 47 | // +kubebuilder:scaffold:scheme 48 | } 49 | 50 | func main() { 51 | config := conf.GetConfig() 52 | flag.StringVar(&config.WatchNamespace, "watch-namespace", "", "Namespace the controller watches for updates to Kubernetes objects."+ 53 | " All namespaces are watched if this parameter is left empty.") 54 | flag.StringVar(&config.ControllerName, "controller", "", "Name of the controller this controller satisfies.") 55 | flag.StringVar(&config.MetricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") 56 | flag.BoolVar(&config.EnableDNS, "dns", false, "Enable DNS Service and configuration discovery.") 57 | flag.BoolVar(&config.EnableDHCP, "dhcp", false, "Enable DHCP Service and configuration discovery.") 58 | flag.BoolVar(&config.EnableLeaderElection, "enable-leader-election", false, 59 | "Enable leader election for controller manager. "+ 60 | "Enabling this will ensure there is only one active controller manager.") 61 | flag.IntVar(&config.SyncDelay, "sync-delay", 1, "Time in seconds to syncronise dnsmasq configuration.") 62 | flag.StringVar(&config.DnsmasqConfDir, "conf-dir", "/etc/dnsmasq.d", "Dnsmasq config directory for write configuration to.") 63 | flag.StringVar(&config.LogLevel, "log-level", "info", "The log level used by the operator.") 64 | flag.BoolVar(&config.Development, "development", false, "Run the controller in development mode.") 65 | flag.BoolVar(&config.CleanupDir, "cleanup", false, "Cleanup dnsmasq config directory before start.") 66 | 67 | flag.Parse() 68 | config.DnsmasqOptions = flag.Args() 69 | 70 | ctrl.SetLogger(logrzap.New(func(o *logrzap.Options) { 71 | o.Development = config.Development 72 | 73 | if o.Development == false { 74 | lev := zap.NewAtomicLevel() 75 | (&lev).UnmarshalText([]byte(config.LogLevel)) 76 | o.Level = &lev 77 | } 78 | })) 79 | 80 | kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 81 | clientcmd.NewDefaultClientConfigLoadingRules(), 82 | &clientcmd.ConfigOverrides{}, 83 | ) 84 | 85 | var err error 86 | config.MyNamespace, _, err = kubeconfig.Namespace() 87 | if err != nil { 88 | setupLog.Error(err, "Failed to get watch namespace") 89 | os.Exit(1) 90 | } 91 | 92 | if config.ControllerName == "" { 93 | config.LeaderElectionID = "dnsmasq-controller-leader" 94 | } else { 95 | config.LeaderElectionID = config.ControllerName + "-dnsmasq-controller-leader" 96 | } 97 | 98 | server.Start() 99 | 100 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 101 | Scheme: scheme, 102 | Namespace: config.WatchNamespace, 103 | MetricsBindAddress: config.MetricsAddr, 104 | Port: 9443, 105 | LeaderElection: config.EnableLeaderElection, 106 | LeaderElectionID: config.LeaderElectionID, 107 | LeaderElectionNamespace: config.MyNamespace, 108 | }) 109 | if err != nil { 110 | setupLog.Error(err, "unable to start manager") 111 | os.Exit(1) 112 | } 113 | 114 | if err = (&controllers.DnsmasqOptionsReconciler{ 115 | Client: mgr.GetClient(), 116 | Log: ctrl.Log.WithName("controllers").WithName("DnsmasqOptions"), 117 | Scheme: mgr.GetScheme(), 118 | }).SetupWithManager(mgr); err != nil { 119 | setupLog.Error(err, "unable to create controller", "controller", "DnsmasqOptions") 120 | os.Exit(1) 121 | } 122 | if config.EnableDNS { 123 | if err = (&controllers.DnsHostsReconciler{ 124 | Client: mgr.GetClient(), 125 | Log: ctrl.Log.WithName("controllers").WithName("DnsHosts"), 126 | Scheme: mgr.GetScheme(), 127 | }).SetupWithManager(mgr); err != nil { 128 | setupLog.Error(err, "unable to create controller", "controller", "DnsHosts") 129 | os.Exit(1) 130 | } 131 | } 132 | if config.EnableDHCP { 133 | if err = (&controllers.DhcpHostsReconciler{ 134 | Client: mgr.GetClient(), 135 | Log: ctrl.Log.WithName("controllers").WithName("DhcpHosts"), 136 | Scheme: mgr.GetScheme(), 137 | }).SetupWithManager(mgr); err != nil { 138 | setupLog.Error(err, "unable to create controller", "controller", "DhcpHosts") 139 | os.Exit(1) 140 | } 141 | if err = (&controllers.DhcpOptionsReconciler{ 142 | Client: mgr.GetClient(), 143 | Log: ctrl.Log.WithName("controllers").WithName("DhcpOptions"), 144 | Scheme: mgr.GetScheme(), 145 | }).SetupWithManager(mgr); err != nil { 146 | setupLog.Error(err, "unable to create controller", "controller", "DhcpOptions") 147 | os.Exit(1) 148 | } 149 | } 150 | 151 | // +kubebuilder:scaffold:builder 152 | 153 | setupLog.Info("starting manager") 154 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 155 | setupLog.Error(err, "problem running manager") 156 | os.Exit(1) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /pkg/conf/conf.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | type Config struct { 4 | Generation int 5 | WatchNamespace string 6 | MyNamespace string 7 | MetricsAddr string 8 | EnableLeaderElection bool 9 | ControllerName string 10 | LeaderElectionID string 11 | SyncDelay int 12 | DnsmasqConfDir string 13 | DnsmasqOptions []string 14 | LogLevel string 15 | Development bool 16 | CleanupDir bool 17 | EnableDHCP bool 18 | EnableDNS bool 19 | } 20 | 21 | var instance *Config 22 | 23 | func GetConfig() *Config { 24 | if instance == nil { 25 | instance = &Config{} 26 | } 27 | return instance 28 | } 29 | -------------------------------------------------------------------------------- /pkg/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bufio" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | "path" 9 | "strings" 10 | "time" 11 | 12 | "github.com/kvaps/dnsmasq-controller/pkg/conf" 13 | ctrl "sigs.k8s.io/controller-runtime" 14 | ) 15 | 16 | var ( 17 | serverLog = ctrl.Log.WithName("server") 18 | dnsmasqLog = ctrl.Log.WithName("dnsmasq") 19 | ) 20 | 21 | func Start() error { 22 | config := conf.GetConfig() 23 | oldGen := 0 24 | 25 | dnsmasqBinary, err := exec.LookPath("dnsmasq") 26 | if err != nil { 27 | panic("dnsmasq binary is not found!") 28 | } 29 | 30 | err = setupDir(config.DnsmasqConfDir, config.CleanupDir) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | args := []string{ 36 | "dnsmasq", 37 | "--no-daemon", 38 | "--no-hosts", 39 | "--conf-dir=" + config.DnsmasqConfDir, 40 | } 41 | 42 | if config.EnableDNS { 43 | args = append(args, 44 | "--addn-hosts="+config.DnsmasqConfDir+"/hosts", 45 | ) 46 | } else { 47 | args = append(args, "--port=0") 48 | } 49 | if config.EnableDHCP { 50 | args = append(args, 51 | "--dhcp-hostsfile="+config.DnsmasqConfDir+"/dhcp-hosts", 52 | "--dhcp-optsfile="+config.DnsmasqConfDir+"/dhcp-opts", 53 | ) 54 | } 55 | 56 | args = append(args, config.DnsmasqOptions...) 57 | 58 | serverLog.Info("Starting dnsmasq: " + strings.Join(args, " ")) 59 | cmd := serverStart(dnsmasqBinary, args) 60 | go func() { 61 | for { 62 | newGen := config.Generation 63 | time.Sleep(time.Duration(config.SyncDelay) * time.Second) 64 | if newGen != oldGen { 65 | serverLog.Info("Configuration changed, restarting dnsmasq.") 66 | serverStop(cmd) 67 | cmd = serverStart(dnsmasqBinary, args) 68 | serverLog.Info("Configuration reloaded.") 69 | oldGen = newGen 70 | } 71 | } 72 | }() 73 | 74 | return nil 75 | } 76 | 77 | func serverStart(dnsmasqBinary string, args []string) *exec.Cmd { 78 | cmd := &exec.Cmd{ 79 | Path: dnsmasqBinary, 80 | Args: args, 81 | } 82 | stderr, err := cmd.StderrPipe() 83 | if err != nil { 84 | panic(err) 85 | } 86 | if err := cmd.Start(); err != nil { 87 | panic(err) 88 | } 89 | buf := bufio.NewReader(stderr) 90 | go func() { 91 | for { 92 | line, _, err := buf.ReadLine() 93 | if err != nil { 94 | break 95 | } 96 | dnsmasqLog.Info(string(line)) 97 | } 98 | }() 99 | return cmd 100 | } 101 | 102 | func serverStop(cmd *exec.Cmd) { 103 | timer := time.AfterFunc(1*time.Second, func() { 104 | err := cmd.Process.Kill() 105 | if err != nil { 106 | panic(err) 107 | } 108 | }) 109 | cmd.Wait() 110 | timer.Stop() 111 | } 112 | 113 | func setupDir(p string, cleanup bool) error { 114 | dir, err := ioutil.ReadDir(p) 115 | if cleanup { 116 | for _, d := range dir { 117 | err = os.RemoveAll(path.Join([]string{p, d.Name()}...)) 118 | if err != nil { 119 | return err 120 | } 121 | } 122 | } 123 | 124 | dirs := []string{"/", "/hosts", "/dhcp-hosts", "/dhcp-opts"} 125 | for _, dir := range dirs { 126 | if _, err := os.Stat(p + dir); os.IsNotExist(err) { 127 | err := os.MkdirAll(p+dir, os.ModePerm) 128 | if err != nil { 129 | return err 130 | } 131 | } 132 | } 133 | return nil 134 | } 135 | -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "os/exec" 11 | ) 12 | 13 | func WriteConfig(orig, dest string, data []byte) (bool, error) { 14 | // If file exists check hash 15 | if _, err := os.Stat(orig); !os.IsNotExist(err) { 16 | hasher := md5.New() 17 | f, err := os.Open(orig) 18 | if err != nil { 19 | return false, err 20 | } 21 | defer f.Close() 22 | if _, err := io.Copy(hasher, f); err != nil { 23 | return false, err 24 | } 25 | oldHash := hasher.Sum(nil)[:16] 26 | 27 | hasher = md5.New() 28 | hasher.Write(data) 29 | newHash := hasher.Sum(nil) 30 | 31 | if bytes.Equal(oldHash, newHash) { 32 | return false, nil 33 | } 34 | f.Close() 35 | } 36 | 37 | err := ioutil.WriteFile(dest, data, 0644) 38 | if err != nil { 39 | return false, err 40 | } 41 | return true, nil 42 | } 43 | 44 | func TestConfig(f string) error { 45 | var stderr bytes.Buffer 46 | dnsmasqBinary, err := exec.LookPath("dnsmasq") 47 | if err != nil { 48 | return err 49 | } 50 | cmd := &exec.Cmd{ 51 | Path: dnsmasqBinary, 52 | Args: []string{"dnsmasq", "--test", "--conf-file=" + f}, 53 | Stderr: &stderr, 54 | } 55 | err = cmd.Run() 56 | if err != nil { 57 | err = fmt.Errorf(string(stderr.Bytes())) 58 | } 59 | return err 60 | } 61 | --------------------------------------------------------------------------------