*: add peer VPN support
This commit adds support for defining arbitrary peers that should have access to the VPN. In k8s, this is accomplished using the new Peer CRD.
This commit is contained in:
parent
46f55c337b
commit
2425a06cd8
103
Makefile
103
Makefile
@ -1,5 +1,5 @@
|
||||
export GO111MODULE=on
|
||||
.PHONY: all push container clean container-name container-latest push-latest fmt lint test unit vendor header
|
||||
#export GO111MODULE=on
|
||||
.PHONY: all push container clean container-name container-latest push-latest fmt lint test unit vendor header generate client deepcopy informer lister openapi
|
||||
|
||||
BINS := $(addprefix bin/,kg kgctl)
|
||||
PROJECT := kilo
|
||||
@ -22,12 +22,88 @@ SRC := $(shell find . -type f -name '*.go' -not -path "./vendor/*")
|
||||
GO_FILES ?= $$(find . -name '*.go' -not -path './vendor/*')
|
||||
GO_PKGS ?= $$(go list ./... | grep -v "$(PKG)/vendor")
|
||||
|
||||
CLIENT_GO_VERSION := release-11.0
|
||||
CODE_GENERATOR_VERSION := release-1.14
|
||||
KUBE_OPENAPI_VERSION := b3a7cee44
|
||||
CLIENT_GEN_BINARY:=$(GOPATH)/bin/client-gen
|
||||
DEEPCOPY_GEN_BINARY:=$(GOPATH)/bin/deepcopy-gen
|
||||
INFORMER_GEN_BINARY:=$(GOPATH)/bin/informer-gen
|
||||
LISTER_GEN_BINARY:=$(GOPATH)/bin/lister-gen
|
||||
OPENAPI_GEN_BINARY:=$(GOPATH)/bin/openapi-gen
|
||||
|
||||
BUILD_IMAGE ?= golang:1.12.1-alpine
|
||||
|
||||
all: build
|
||||
|
||||
build: $(BINS)
|
||||
|
||||
generate: client deepcopy informer lister openapi
|
||||
|
||||
client: pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go
|
||||
pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go $(CLIENT_GEN_BINARY)
|
||||
$(CLIENT_GEN_BINARY) \
|
||||
--clientset-name versioned \
|
||||
--input-base "" \
|
||||
--input $(PKG)/pkg/k8s/apis/kilo/v1alpha1 \
|
||||
--output-base $(CURDIR) \
|
||||
--output-package $(PKG)/pkg/k8s/clientset \
|
||||
--go-header-file=.header \
|
||||
--logtostderr
|
||||
rm -r pkg/k8s/clientset
|
||||
mv $(PKG)/pkg/k8s/clientset pkg/k8s
|
||||
rm -r github.com
|
||||
go fmt ./pkg/k8s/clientset/...
|
||||
|
||||
deepcopy: pkg/k8s/apis/kilo/v1alpha1/zz_generated.deepcopy.go
|
||||
pkg/k8s/apis/kilo/v1alpha1/zz_generated.deepcopy.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go $(DEEPCOPY_GEN_BINARY)
|
||||
$(DEEPCOPY_GEN_BINARY) \
|
||||
--input-dirs ./$(@D) \
|
||||
--go-header-file=.header \
|
||||
--logtostderr \
|
||||
--output-base $(CURDIR) \
|
||||
--output-file-base zz_generated.deepcopy \
|
||||
go fmt $@
|
||||
|
||||
informer: pkg/k8s/informers/kilo/v1alpha1/peer.go
|
||||
pkg/k8s/informers/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go $(INFORMER_GEN_BINARY)
|
||||
$(INFORMER_GEN_BINARY) \
|
||||
--input-dirs $(PKG)/pkg/k8s/apis/kilo/v1alpha1 \
|
||||
--go-header-file=.header \
|
||||
--logtostderr \
|
||||
--versioned-clientset-package $(PKG)/pkg/k8s/clientset/versioned \
|
||||
--listers-package $(PKG)/pkg/k8s/listers \
|
||||
--output-base $(CURDIR) \
|
||||
--output-package $(PKG)/pkg/k8s/informers \
|
||||
--single-directory
|
||||
rm -r pkg/k8s/informers
|
||||
mv $(PKG)/pkg/k8s/informers pkg/k8s
|
||||
rm -r github.com
|
||||
go fmt ./pkg/k8s/informers/...
|
||||
|
||||
lister: pkg/k8s/listers/kilo/v1alpha1/peer.go
|
||||
pkg/k8s/listers/kilo/v1alpha1/peer.go: .header pkg/k8s/apis/kilo/v1alpha1/types.go $(LISTER_GEN_BINARY)
|
||||
$(LISTER_GEN_BINARY) \
|
||||
--input-dirs $(PKG)/pkg/k8s/apis/kilo/v1alpha1 \
|
||||
--go-header-file=.header \
|
||||
--logtostderr \
|
||||
--output-base $(CURDIR) \
|
||||
--output-package $(PKG)/pkg/k8s/listers
|
||||
rm -r pkg/k8s/listers
|
||||
mv $(PKG)/pkg/k8s/listers pkg/k8s
|
||||
rm -r github.com
|
||||
go fmt ./pkg/k8s/listers/...
|
||||
|
||||
openapi: pkg/k8s/apis/kilo/v1alpha1/openapi_generated.go
|
||||
pkg/k8s/apis/kilo/v1alpha1/openapi_generated.go: pkg/k8s/apis/kilo/v1alpha1/types.go $(OPENAPI_GEN_BINARY)
|
||||
$(OPENAPI_GEN_BINARY) \
|
||||
--input-dirs ./$(@D),k8s.io/apimachinery/pkg/apis/meta/v1,k8s.io/api/core/v1 \
|
||||
--output-base $(CURDIR) \
|
||||
--output-package ./$(@D) \
|
||||
--logtostderr \
|
||||
--report-filename /dev/null \
|
||||
--go-header-file=.header
|
||||
go fmt $@
|
||||
|
||||
$(BINS): $(SRC) go.mod
|
||||
@mkdir -p bin
|
||||
@echo "building: $@"
|
||||
@ -82,10 +158,14 @@ test: lint unit
|
||||
|
||||
header: .header
|
||||
@HEADER=$$(sed "s/YEAR/$$(date '+%Y')/" .header); \
|
||||
HEADER_LEN=$$(wc -l .header | awk '{print $$1}'); \
|
||||
FILES=; \
|
||||
for f in $(GO_FILES); do \
|
||||
FILE=$$(head -n $$(wc -l .header | awk '{print $$1}') $$f); \
|
||||
[ "$$FILE" != "$$HEADER" ] && FILES="$$FILES$$f "; \
|
||||
for i in 0 1 2 3 4 5; do \
|
||||
FILE=$$(tail -n +$$i $$f | head -n $$HEADER_LEN); \
|
||||
[ "$$FILE" = "$$HEADER" ] && continue 2; \
|
||||
done; \
|
||||
FILES="$$FILES$$f "; \
|
||||
done; \
|
||||
if [ -n "$$FILES" ]; then \
|
||||
printf 'the following files are missing the license header: %s\n' "$$FILES"; \
|
||||
@ -128,3 +208,18 @@ bin-clean:
|
||||
vendor:
|
||||
go mod tidy
|
||||
go mod vendor
|
||||
|
||||
$(CLIENT_GEN_BINARY):
|
||||
go get k8s.io/code-generator/cmd/client-gen@$(CODE_GENERATOR_VERSION)
|
||||
|
||||
$(DEEPCOPY_GEN_BINARY):
|
||||
go get k8s.io/code-generator/cmd/deepcopy-gen@$(CODE_GENERATOR_VERSION)
|
||||
|
||||
$(INFORMER_GEN_BINARY):
|
||||
go get k8s.io/code-generator/cmd/informer-gen@$(CODE_GENERATOR_VERSION)
|
||||
|
||||
$(LISTER_GEN_BINARY):
|
||||
go get k8s.io/code-generator/cmd/lister-gen@$(CODE_GENERATOR_VERSION)
|
||||
|
||||
$(OPENAPI_GEN_BINARY):
|
||||
go get k8s.io/kube-openapi/cmd/openapi-gen@$(KUBE_OPENAPI_VERSION)
|
||||
|
@ -30,10 +30,12 @@ import (
|
||||
"github.com/oklog/run"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/squat/kilo/pkg/k8s"
|
||||
kiloclient "github.com/squat/kilo/pkg/k8s/clientset/versioned"
|
||||
"github.com/squat/kilo/pkg/mesh"
|
||||
"github.com/squat/kilo/pkg/version"
|
||||
)
|
||||
@ -81,7 +83,8 @@ func Main() error {
|
||||
local := flag.Bool("local", true, "Should Kilo manage routes within a location.")
|
||||
logLevel := flag.String("log-level", logLevelInfo, fmt.Sprintf("Log level to use. Possible values: %s", availableLogLevels))
|
||||
master := flag.String("master", "", "The address of the Kubernetes API server (overrides any value in kubeconfig).")
|
||||
port := flag.Int("port", 51820, "The port over which WireGuard peers should communicate.")
|
||||
var port uint
|
||||
flag.UintVar(&port, "port", mesh.DefaultKiloPort, "The port over which WireGuard peers should communicate.")
|
||||
subnet := flag.String("subnet", "10.4.0.0/16", "CIDR from which to allocate addresses for WireGuard interfaces.")
|
||||
printVersion := flag.Bool("version", false, "Print version and exit")
|
||||
flag.Parse()
|
||||
@ -148,13 +151,15 @@ func Main() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Kubernetes config: %v", err)
|
||||
}
|
||||
client := kubernetes.NewForConfigOrDie(config)
|
||||
b = k8s.New(client)
|
||||
c := kubernetes.NewForConfigOrDie(config)
|
||||
kc := kiloclient.NewForConfigOrDie(config)
|
||||
ec := apiextensions.NewForConfigOrDie(config)
|
||||
b = k8s.New(c, kc, ec)
|
||||
default:
|
||||
return fmt.Errorf("backend %v unknown; possible values are: %s", *backend, availableBackends)
|
||||
}
|
||||
|
||||
m, err := mesh.New(b, e, gr, *hostname, *port, s, *local, log.With(logger, "component", "kilo"))
|
||||
m, err := mesh.New(b, e, gr, *hostname, uint32(port), s, *local, log.With(logger, "component", "kilo"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Kilo mesh: %v", err)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
"github.com/squat/kilo/pkg/mesh"
|
||||
)
|
||||
|
||||
func newGraph() *cobra.Command {
|
||||
func graph() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "graph",
|
||||
Short: "Generates a graph of the Kilo network",
|
||||
@ -31,7 +31,7 @@ func newGraph() *cobra.Command {
|
||||
}
|
||||
|
||||
func runGraph(_ *cobra.Command, _ []string) error {
|
||||
ns, err := opts.backend.List()
|
||||
ns, err := opts.backend.Nodes().List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list nodes: %v", err)
|
||||
}
|
||||
@ -46,7 +46,7 @@ func runGraph(_ *cobra.Command, _ []string) error {
|
||||
if len(nodes) == 0 {
|
||||
return fmt.Errorf("did not find any valid Kilo nodes in the cluster")
|
||||
}
|
||||
t, err := mesh.NewTopology(nodes, opts.granularity, hostname, 0, []byte{}, opts.subnet)
|
||||
t, err := mesh.NewTopology(nodes, nil, opts.granularity, hostname, 0, []byte{}, opts.subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create topology: %v", err)
|
||||
}
|
||||
|
@ -20,11 +20,13 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/squat/kilo/pkg/k8s"
|
||||
kiloclient "github.com/squat/kilo/pkg/k8s/clientset/versioned"
|
||||
"github.com/squat/kilo/pkg/mesh"
|
||||
"github.com/squat/kilo/pkg/version"
|
||||
)
|
||||
@ -86,14 +88,20 @@ func runRoot(_ *cobra.Command, _ []string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create Kubernetes config: %v", err)
|
||||
}
|
||||
client := kubernetes.NewForConfigOrDie(config)
|
||||
opts.backend = k8s.New(client)
|
||||
c := kubernetes.NewForConfigOrDie(config)
|
||||
kc := kiloclient.NewForConfigOrDie(config)
|
||||
ec := apiextensions.NewForConfigOrDie(config)
|
||||
opts.backend = k8s.New(c, kc, ec)
|
||||
default:
|
||||
return fmt.Errorf("backend %v unknown; posible values are: %s", backend, availableBackends)
|
||||
}
|
||||
|
||||
if err := opts.backend.Init(make(chan struct{})); err != nil {
|
||||
return fmt.Errorf("failed to initialize backend: %v", err)
|
||||
if err := opts.backend.Nodes().Init(make(chan struct{})); err != nil {
|
||||
return fmt.Errorf("failed to initialize node backend: %v", err)
|
||||
}
|
||||
|
||||
if err := opts.backend.Peers().Init(make(chan struct{})); err != nil {
|
||||
return fmt.Errorf("failed to initialize peer backend: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -112,7 +120,8 @@ func main() {
|
||||
cmd.PersistentFlags().StringVar(&subnet, "subnet", "10.4.0.0/16", "CIDR from which to allocate addressees to WireGuard interfaces.")
|
||||
|
||||
for _, subCmd := range []*cobra.Command{
|
||||
newGraph(),
|
||||
graph(),
|
||||
showConf(),
|
||||
} {
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
|
146
cmd/kgctl/showconf.go
Normal file
146
cmd/kgctl/showconf.go
Normal file
@ -0,0 +1,146 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/squat/kilo/pkg/mesh"
|
||||
)
|
||||
|
||||
func showConf() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "showconf",
|
||||
Short: "Show the WireGuard configuration for a node or peer in the Kilo network",
|
||||
Long: "",
|
||||
}
|
||||
|
||||
for _, subCmd := range []*cobra.Command{
|
||||
showConfNode(),
|
||||
showConfPeer(),
|
||||
} {
|
||||
cmd.AddCommand(subCmd)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func showConfNode() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "node",
|
||||
Short: "Show the WireGuard configuration for a node in the Kilo network",
|
||||
Long: "",
|
||||
RunE: runShowConfNode,
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
}
|
||||
|
||||
func showConfPeer() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "peer",
|
||||
Short: "Show the WireGuard configuration for a peer in the Kilo network",
|
||||
Long: "",
|
||||
RunE: runShowConfPeer,
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
}
|
||||
|
||||
func runShowConfNode(_ *cobra.Command, args []string) error {
|
||||
ns, err := opts.backend.Nodes().List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list nodes: %v", err)
|
||||
}
|
||||
ps, err := opts.backend.Peers().List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list peers: %v", err)
|
||||
}
|
||||
hostname := args[0]
|
||||
nodes := make(map[string]*mesh.Node)
|
||||
for _, n := range ns {
|
||||
if n.Ready() {
|
||||
nodes[n.Name] = n
|
||||
}
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return errors.New("did not find any valid Kilo nodes in the cluster")
|
||||
}
|
||||
if _, ok := nodes[hostname]; !ok {
|
||||
return fmt.Errorf("did not find any node named %q in the cluster", hostname)
|
||||
}
|
||||
|
||||
peers := make(map[string]*mesh.Peer)
|
||||
for _, p := range ps {
|
||||
if p.Ready() {
|
||||
peers[p.Name] = p
|
||||
}
|
||||
}
|
||||
|
||||
t, err := mesh.NewTopology(nodes, peers, opts.granularity, hostname, mesh.DefaultKiloPort, []byte{}, opts.subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create topology: %v", err)
|
||||
}
|
||||
c, err := t.Conf().Bytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate configuration: %v", err)
|
||||
}
|
||||
fmt.Printf(string(c))
|
||||
return nil
|
||||
}
|
||||
|
||||
func runShowConfPeer(_ *cobra.Command, args []string) error {
|
||||
ns, err := opts.backend.Nodes().List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list nodes: %v", err)
|
||||
}
|
||||
ps, err := opts.backend.Peers().List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list peers: %v", err)
|
||||
}
|
||||
var hostname string
|
||||
nodes := make(map[string]*mesh.Node)
|
||||
for _, n := range ns {
|
||||
if n.Ready() {
|
||||
nodes[n.Name] = n
|
||||
hostname = n.Name
|
||||
}
|
||||
}
|
||||
if len(nodes) == 0 {
|
||||
return errors.New("did not find any valid Kilo nodes in the cluster")
|
||||
}
|
||||
|
||||
peer := args[0]
|
||||
peers := make(map[string]*mesh.Peer)
|
||||
for _, p := range ps {
|
||||
if p.Ready() {
|
||||
peers[p.Name] = p
|
||||
}
|
||||
}
|
||||
if _, ok := peers[peer]; !ok {
|
||||
return fmt.Errorf("did not find any peer named %q in the cluster", peer)
|
||||
}
|
||||
|
||||
t, err := mesh.NewTopology(nodes, peers, opts.granularity, hostname, mesh.DefaultKiloPort, []byte{}, opts.subnet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create topology: %v", err)
|
||||
}
|
||||
c, err := t.PeerConf(peer).Bytes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate configuration: %v", err)
|
||||
}
|
||||
fmt.Printf(string(c))
|
||||
return nil
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
# Annotations
|
||||
|
||||
The following annotations can be added to any Kubernetes Node object to configure the Kilo network.
|
||||
|
||||
|Name|type|example|
|
||||
|
@ -17,6 +17,20 @@ rules:
|
||||
- list
|
||||
- patch
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kilo.squat.ai
|
||||
resources:
|
||||
- peers
|
||||
verbs:
|
||||
- list
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apiextensions.k8s.io
|
||||
resources:
|
||||
- customresourcedefinitions
|
||||
verbs:
|
||||
- create
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
|
@ -17,6 +17,20 @@ rules:
|
||||
- list
|
||||
- patch
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kilo.squat.ai
|
||||
resources:
|
||||
- peers
|
||||
verbs:
|
||||
- list
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apiextensions.k8s.io
|
||||
resources:
|
||||
- customresourcedefinitions
|
||||
verbs:
|
||||
- create
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
|
@ -17,6 +17,20 @@ rules:
|
||||
- list
|
||||
- patch
|
||||
- watch
|
||||
- apiGroups:
|
||||
- kilo.squat.ai
|
||||
resources:
|
||||
- peers
|
||||
verbs:
|
||||
- list
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- apiextensions.k8s.io
|
||||
resources:
|
||||
- customresourcedefinitions
|
||||
verbs:
|
||||
- create
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
|
@ -254,13 +254,17 @@ func EncapsulateRules(nodes []*net.IPNet) []Rule {
|
||||
|
||||
// ForwardRules returns a set of iptables rules that are necessary
|
||||
// when traffic must be forwarded for the overlay.
|
||||
func ForwardRules(subnet *net.IPNet) []Rule {
|
||||
s := subnet.String()
|
||||
return []Rule{
|
||||
// Forward traffic to and from the overlay.
|
||||
&rule{"filter", "FORWARD", []string{"-s", s, "-j", "ACCEPT"}, nil},
|
||||
&rule{"filter", "FORWARD", []string{"-d", s, "-j", "ACCEPT"}, nil},
|
||||
func ForwardRules(subnets ...*net.IPNet) []Rule {
|
||||
var rules []Rule
|
||||
for _, subnet := range subnets {
|
||||
s := subnet.String()
|
||||
rules = append(rules, []Rule{
|
||||
// Forward traffic to and from the overlay.
|
||||
&rule{"filter", "FORWARD", []string{"-s", s, "-j", "ACCEPT"}, nil},
|
||||
&rule{"filter", "FORWARD", []string{"-d", s, "-j", "ACCEPT"}, nil},
|
||||
}...)
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
// MasqueradeRules returns a set of iptables rules that are necessary
|
||||
|
20
pkg/k8s/apis/kilo/register.go
Normal file
20
pkg/k8s/apis/kilo/register.go
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package kilo
|
||||
|
||||
const (
|
||||
// GroupName contains the API group name for Kilo API group.
|
||||
GroupName = "kilo"
|
||||
)
|
19
pkg/k8s/apis/kilo/v1alpha1/doc.go
Normal file
19
pkg/k8s/apis/kilo/v1alpha1/doc.go
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +k8s:deepcopy-gen=package,register
|
||||
|
||||
// Package v1alpha1 is the v1alpha1 version of the API.
|
||||
// +groupName=kilo
|
||||
package v1alpha1
|
12127
pkg/k8s/apis/kilo/v1alpha1/openapi_generated.go
Normal file
12127
pkg/k8s/apis/kilo/v1alpha1/openapi_generated.go
Normal file
File diff suppressed because it is too large
Load Diff
49
pkg/k8s/apis/kilo/v1alpha1/register.go
Normal file
49
pkg/k8s/apis/kilo/v1alpha1/register.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
// SchemeBuilder exposes an API scheme builder for this API version.
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
// AddToScheme exposes an AddToScheme func for this API version.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// GroupName is the group name used in this package.
|
||||
const GroupName = "kilo.squat.ai"
|
||||
|
||||
// SchemeGroupVersion is the group version used to register these objects.
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group-qualified GroupResource.
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
// addKnownTypes adds the set of types defined in this package to the supplied scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Peer{},
|
||||
&PeerList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
143
pkg/k8s/apis/kilo/v1alpha1/types.go
Normal file
143
pkg/k8s/apis/kilo/v1alpha1/types.go
Normal file
@ -0,0 +1,143 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// PeerKind is the API kind for the peer resource.
|
||||
PeerKind = "Peer"
|
||||
// PeerPlural is the plural name for the peer resource.
|
||||
PeerPlural = "peers"
|
||||
)
|
||||
|
||||
// PeerShortNames are convenient shortnames for the peer resource.
|
||||
var PeerShortNames = []string{"peer"}
|
||||
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
// Peer is a WireGuard peer that should have access to the VPN.
|
||||
type Peer struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard object’s metadata. More info:
|
||||
// https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata
|
||||
// +k8s:openapi-gen=false
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
// Specification of the desired behavior of the Kilo Peer. More info:
|
||||
// https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#spec-and-status
|
||||
Spec PeerSpec `json:"spec"`
|
||||
}
|
||||
|
||||
// PeerSpec is the description and configuration of a peer.
|
||||
// +k8s:openapi-gen=true
|
||||
type PeerSpec struct {
|
||||
// AllowedIPs is the list of IP addresses that are allowed
|
||||
// for the given peer's tunnel.
|
||||
AllowedIPs []string `json:"allowedIPs"`
|
||||
// Endpoint is the initial endpoint for connections to the peer.
|
||||
// +optional
|
||||
Endpoint *PeerEndpoint `json:"endpoint"`
|
||||
// PersistentKeepalive is the interval in seconds of the emission
|
||||
// of keepalive packets to the peer. This defaults to 0, which
|
||||
// disables the feature.
|
||||
PersistentKeepalive int `json:"persistentKeepalive"`
|
||||
// PublicKey is the WireGuard public key for the node.
|
||||
PublicKey string `json:"publicKey"`
|
||||
}
|
||||
|
||||
// PeerEndpoint represents a WireGuard enpoint, which is a ip:port tuple.
|
||||
type PeerEndpoint struct {
|
||||
// IP must be a valid IP address.
|
||||
IP string `json:"ip"`
|
||||
// Port must be a valid port number.
|
||||
Port uint32 `json:"port"`
|
||||
}
|
||||
|
||||
// PeerName is the peer resource's FQDN.
|
||||
var PeerName = PeerPlural + "." + GroupName
|
||||
|
||||
// AsOwner creates a new owner reference for the peer to apply to dependent resource.
|
||||
func (p *Peer) AsOwner() metav1.OwnerReference {
|
||||
trueVar := true
|
||||
return metav1.OwnerReference{
|
||||
APIVersion: p.APIVersion,
|
||||
Kind: p.Kind,
|
||||
Name: p.Name,
|
||||
UID: p.UID,
|
||||
BlockOwnerDeletion: &trueVar,
|
||||
Controller: &trueVar,
|
||||
}
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of the peer.
|
||||
func (p *Peer) Copy() *Peer {
|
||||
new := Peer{}
|
||||
b, err := json.Marshal(*p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = json.Unmarshal(b, &new)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &new
|
||||
}
|
||||
|
||||
// Validate ensures that all the fields of a peer's spec are valid.
|
||||
func (p *Peer) Validate() error {
|
||||
for _, ip := range p.Spec.AllowedIPs {
|
||||
if _, n, err := net.ParseCIDR(ip); err != nil {
|
||||
return fmt.Errorf("failed to parse %q as a valid IP address: %v", ip, err)
|
||||
} else if n == nil {
|
||||
return fmt.Errorf("got invalid IP address for %q", ip)
|
||||
}
|
||||
}
|
||||
if p.Spec.Endpoint != nil {
|
||||
if net.ParseIP(p.Spec.Endpoint.IP) == nil {
|
||||
return fmt.Errorf("failed to parse %q as a valid IP address", p.Spec.Endpoint.IP)
|
||||
}
|
||||
if p.Spec.Endpoint.Port == 0 {
|
||||
return fmt.Errorf("port must be a valid UDP port number, got %d", p.Spec.Endpoint.Port)
|
||||
}
|
||||
}
|
||||
if p.Spec.PersistentKeepalive < 0 {
|
||||
return fmt.Errorf("persistent keepalive must be greater than or equal to zero; got %q", p.Spec.PersistentKeepalive)
|
||||
}
|
||||
if len(p.Spec.PublicKey) == 0 {
|
||||
return errors.New("public keys cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// PeerList is a list of peers.
|
||||
type PeerList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
// List of peers.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md
|
||||
Items []Peer `json:"items"`
|
||||
}
|
125
pkg/k8s/apis/kilo/v1alpha1/zz_generated.deepcopy.go
Normal file
125
pkg/k8s/apis/kilo/v1alpha1/zz_generated.deepcopy.go
Normal file
@ -0,0 +1,125 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Peer) DeepCopyInto(out *Peer) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Peer.
|
||||
func (in *Peer) DeepCopy() *Peer {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Peer)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Peer) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PeerEndpoint) DeepCopyInto(out *PeerEndpoint) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeerEndpoint.
|
||||
func (in *PeerEndpoint) DeepCopy() *PeerEndpoint {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PeerEndpoint)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PeerList) DeepCopyInto(out *PeerList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
out.ListMeta = in.ListMeta
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Peer, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeerList.
|
||||
func (in *PeerList) DeepCopy() *PeerList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PeerList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PeerList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PeerSpec) DeepCopyInto(out *PeerSpec) {
|
||||
*out = *in
|
||||
if in.AllowedIPs != nil {
|
||||
in, out := &in.AllowedIPs, &out.AllowedIPs
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Endpoint != nil {
|
||||
in, out := &in.Endpoint, &out.Endpoint
|
||||
*out = new(PeerEndpoint)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeerSpec.
|
||||
func (in *PeerSpec) DeepCopy() *PeerSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PeerSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
@ -24,7 +24,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
crdutils "github.com/ant31/crd-validation/pkg"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
apiextensions "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
@ -33,7 +37,12 @@ import (
|
||||
v1listers "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||
kiloclient "github.com/squat/kilo/pkg/k8s/clientset/versioned"
|
||||
v1alpha1informers "github.com/squat/kilo/pkg/k8s/informers/kilo/v1alpha1"
|
||||
v1alpha1listers "github.com/squat/kilo/pkg/k8s/listers/kilo/v1alpha1"
|
||||
"github.com/squat/kilo/pkg/mesh"
|
||||
"github.com/squat/kilo/pkg/wireguard"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -52,43 +61,74 @@ const (
|
||||
)
|
||||
|
||||
type backend struct {
|
||||
nodes *nodeBackend
|
||||
peers *peerBackend
|
||||
}
|
||||
|
||||
// Nodes implements the mesh.Backend interface.
|
||||
func (b *backend) Nodes() mesh.NodeBackend {
|
||||
return b.nodes
|
||||
}
|
||||
|
||||
// Peers implements the mesh.Backend interface.
|
||||
func (b *backend) Peers() mesh.PeerBackend {
|
||||
return b.peers
|
||||
}
|
||||
|
||||
type nodeBackend struct {
|
||||
client kubernetes.Interface
|
||||
events chan *mesh.Event
|
||||
events chan *mesh.NodeEvent
|
||||
informer cache.SharedIndexInformer
|
||||
lister v1listers.NodeLister
|
||||
}
|
||||
|
||||
type peerBackend struct {
|
||||
client kiloclient.Interface
|
||||
extensionsClient apiextensions.Interface
|
||||
events chan *mesh.PeerEvent
|
||||
informer cache.SharedIndexInformer
|
||||
lister v1alpha1listers.PeerLister
|
||||
}
|
||||
|
||||
// New creates a new instance of a mesh.Backend.
|
||||
func New(client kubernetes.Interface) mesh.Backend {
|
||||
informer := v1informers.NewNodeInformer(client, 5*time.Minute, nil)
|
||||
func New(c kubernetes.Interface, kc kiloclient.Interface, ec apiextensions.Interface) mesh.Backend {
|
||||
ni := v1informers.NewNodeInformer(c, 5*time.Minute, nil)
|
||||
pi := v1alpha1informers.NewPeerInformer(kc, 5*time.Minute, nil)
|
||||
|
||||
b := &backend{
|
||||
client: client,
|
||||
events: make(chan *mesh.Event),
|
||||
informer: informer,
|
||||
lister: v1listers.NewNodeLister(informer.GetIndexer()),
|
||||
return &backend{
|
||||
&nodeBackend{
|
||||
client: c,
|
||||
events: make(chan *mesh.NodeEvent),
|
||||
informer: ni,
|
||||
lister: v1listers.NewNodeLister(ni.GetIndexer()),
|
||||
},
|
||||
&peerBackend{
|
||||
client: kc,
|
||||
extensionsClient: ec,
|
||||
events: make(chan *mesh.PeerEvent),
|
||||
informer: pi,
|
||||
lister: v1alpha1listers.NewPeerLister(pi.GetIndexer()),
|
||||
},
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// CleanUp removes configuration applied to the backend.
|
||||
func (b *backend) CleanUp(name string) error {
|
||||
func (nb *nodeBackend) CleanUp(name string) error {
|
||||
patch := []byte("[" + strings.Join([]string{
|
||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(externalIPAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(internalIPAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(keyAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||
fmt.Sprintf(jsonRemovePatch, path.Join("/metadata", "annotations", strings.Replace(lastSeenAnnotationKey, "/", jsonPatchSlash, 1))),
|
||||
}, ",") + "]")
|
||||
if _, err := b.client.CoreV1().Nodes().Patch(name, types.JSONPatchType, patch); err != nil {
|
||||
if _, err := nb.client.CoreV1().Nodes().Patch(name, types.JSONPatchType, patch); err != nil {
|
||||
return fmt.Errorf("failed to patch node: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets a single Node by name.
|
||||
func (b *backend) Get(name string) (*mesh.Node, error) {
|
||||
n, err := b.lister.Get(name)
|
||||
func (nb *nodeBackend) Get(name string) (*mesh.Node, error) {
|
||||
n, err := nb.lister.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -97,14 +137,14 @@ func (b *backend) Get(name string) (*mesh.Node, error) {
|
||||
|
||||
// Init initializes the backend; for this backend that means
|
||||
// syncing the informer cache.
|
||||
func (b *backend) Init(stop <-chan struct{}) error {
|
||||
go b.informer.Run(stop)
|
||||
func (nb *nodeBackend) Init(stop <-chan struct{}) error {
|
||||
go nb.informer.Run(stop)
|
||||
if ok := cache.WaitForCacheSync(stop, func() bool {
|
||||
return b.informer.HasSynced()
|
||||
return nb.informer.HasSynced()
|
||||
}); !ok {
|
||||
return errors.New("failed to start sync node cache")
|
||||
}
|
||||
b.informer.AddEventHandler(
|
||||
nb.informer.AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
n, ok := obj.(*v1.Node)
|
||||
@ -112,7 +152,7 @@ func (b *backend) Init(stop <-chan struct{}) error {
|
||||
// Failed to decode Node; ignoring...
|
||||
return
|
||||
}
|
||||
b.events <- &mesh.Event{Type: mesh.AddEvent, Node: translateNode(n)}
|
||||
nb.events <- &mesh.NodeEvent{Type: mesh.AddEvent, Node: translateNode(n)}
|
||||
},
|
||||
UpdateFunc: func(_, obj interface{}) {
|
||||
n, ok := obj.(*v1.Node)
|
||||
@ -120,7 +160,7 @@ func (b *backend) Init(stop <-chan struct{}) error {
|
||||
// Failed to decode Node; ignoring...
|
||||
return
|
||||
}
|
||||
b.events <- &mesh.Event{Type: mesh.UpdateEvent, Node: translateNode(n)}
|
||||
nb.events <- &mesh.NodeEvent{Type: mesh.UpdateEvent, Node: translateNode(n)}
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
n, ok := obj.(*v1.Node)
|
||||
@ -128,7 +168,7 @@ func (b *backend) Init(stop <-chan struct{}) error {
|
||||
// Failed to decode Node; ignoring...
|
||||
return
|
||||
}
|
||||
b.events <- &mesh.Event{Type: mesh.DeleteEvent, Node: translateNode(n)}
|
||||
nb.events <- &mesh.NodeEvent{Type: mesh.DeleteEvent, Node: translateNode(n)}
|
||||
},
|
||||
},
|
||||
)
|
||||
@ -136,8 +176,8 @@ func (b *backend) Init(stop <-chan struct{}) error {
|
||||
}
|
||||
|
||||
// List gets all the Nodes in the cluster.
|
||||
func (b *backend) List() ([]*mesh.Node, error) {
|
||||
ns, err := b.lister.List(labels.Everything())
|
||||
func (nb *nodeBackend) List() ([]*mesh.Node, error) {
|
||||
ns, err := nb.lister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -149,8 +189,8 @@ func (b *backend) List() ([]*mesh.Node, error) {
|
||||
}
|
||||
|
||||
// Set sets the fields of a node.
|
||||
func (b *backend) Set(name string, node *mesh.Node) error {
|
||||
old, err := b.lister.Get(name)
|
||||
func (nb *nodeBackend) Set(name string, node *mesh.Node) error {
|
||||
old, err := nb.lister.Get(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find node: %v", err)
|
||||
}
|
||||
@ -171,15 +211,15 @@ func (b *backend) Set(name string, node *mesh.Node) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create patch for node %q: %v", n.Name, err)
|
||||
}
|
||||
if _, err = b.client.CoreV1().Nodes().Patch(name, types.StrategicMergePatchType, patch); err != nil {
|
||||
if _, err = nb.client.CoreV1().Nodes().Patch(name, types.StrategicMergePatchType, patch); err != nil {
|
||||
return fmt.Errorf("failed to patch node: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch returns a chan of node events.
|
||||
func (b *backend) Watch() <-chan *mesh.Event {
|
||||
return b.events
|
||||
func (nb *nodeBackend) Watch() <-chan *mesh.NodeEvent {
|
||||
return nb.events
|
||||
}
|
||||
|
||||
// translateNode translates a Kubernetes Node to a mesh.Node.
|
||||
@ -214,7 +254,7 @@ func translateNode(node *v1.Node) *mesh.Node {
|
||||
}
|
||||
return &mesh.Node{
|
||||
// ExternalIP and InternalIP should only ever fail to parse if the
|
||||
// remote node's mesh has not yet set its IP address;
|
||||
// remote node's agent has not yet set its IP address;
|
||||
// in this case the IP will be nil and
|
||||
// the mesh can wait for the node to be updated.
|
||||
ExternalIP: normalizeIP(externalIP),
|
||||
@ -228,10 +268,179 @@ func translateNode(node *v1.Node) *mesh.Node {
|
||||
}
|
||||
}
|
||||
|
||||
// translatePeer translates a Peer CRD to a mesh.Peer.
|
||||
func translatePeer(peer *v1alpha1.Peer) *mesh.Peer {
|
||||
if peer == nil {
|
||||
return nil
|
||||
}
|
||||
var aips []*net.IPNet
|
||||
for _, aip := range peer.Spec.AllowedIPs {
|
||||
aip := normalizeIP(aip)
|
||||
// Skip any invalid IPs.
|
||||
if aip == nil {
|
||||
continue
|
||||
}
|
||||
aips = append(aips, aip)
|
||||
}
|
||||
var endpoint *wireguard.Endpoint
|
||||
if peer.Spec.Endpoint != nil {
|
||||
ip := net.ParseIP(peer.Spec.Endpoint.IP)
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
ip = ip4
|
||||
} else {
|
||||
ip = ip.To16()
|
||||
}
|
||||
if peer.Spec.Endpoint.Port > 0 && ip != nil {
|
||||
endpoint = &wireguard.Endpoint{
|
||||
IP: ip,
|
||||
Port: peer.Spec.Endpoint.Port,
|
||||
}
|
||||
}
|
||||
}
|
||||
var key []byte
|
||||
if len(peer.Spec.PublicKey) > 0 {
|
||||
key = []byte(peer.Spec.PublicKey)
|
||||
}
|
||||
var pka int
|
||||
if peer.Spec.PersistentKeepalive > 0 {
|
||||
pka = peer.Spec.PersistentKeepalive
|
||||
}
|
||||
return &mesh.Peer{
|
||||
Name: peer.Name,
|
||||
Peer: wireguard.Peer{
|
||||
AllowedIPs: aips,
|
||||
Endpoint: endpoint,
|
||||
PublicKey: key,
|
||||
PersistentKeepalive: pka,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CleanUp removes configuration applied to the backend.
|
||||
func (pb *peerBackend) CleanUp(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets a single Peer by name.
|
||||
func (pb *peerBackend) Get(name string) (*mesh.Peer, error) {
|
||||
p, err := pb.lister.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return translatePeer(p), nil
|
||||
}
|
||||
|
||||
// Init initializes the backend; for this backend that means
|
||||
// syncing the informer cache.
|
||||
func (pb *peerBackend) Init(stop <-chan struct{}) error {
|
||||
// Register CRD.
|
||||
crd := crdutils.NewCustomResourceDefinition(crdutils.Config{
|
||||
SpecDefinitionName: "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1.Peer",
|
||||
EnableValidation: true,
|
||||
ResourceScope: string(v1beta1.ClusterScoped),
|
||||
Group: v1alpha1.GroupName,
|
||||
Kind: v1alpha1.PeerKind,
|
||||
Version: v1alpha1.SchemeGroupVersion.Version,
|
||||
Plural: v1alpha1.PeerPlural,
|
||||
ShortNames: v1alpha1.PeerShortNames,
|
||||
GetOpenAPIDefinitions: v1alpha1.GetOpenAPIDefinitions,
|
||||
})
|
||||
crd.Spec.Subresources.Scale = nil
|
||||
crd.Spec.Subresources.Status = nil
|
||||
|
||||
_, err := pb.extensionsClient.ApiextensionsV1beta1().CustomResourceDefinitions().Create(crd)
|
||||
if err != nil && !apierrors.IsAlreadyExists(err) {
|
||||
return fmt.Errorf("failed to create CRD: %v", err)
|
||||
}
|
||||
|
||||
go pb.informer.Run(stop)
|
||||
if ok := cache.WaitForCacheSync(stop, func() bool {
|
||||
return pb.informer.HasSynced()
|
||||
}); !ok {
|
||||
return errors.New("failed to start sync peer cache")
|
||||
}
|
||||
pb.informer.AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
p, ok := obj.(*v1alpha1.Peer)
|
||||
if !ok || p.Validate() != nil {
|
||||
// Failed to decode Peer; ignoring...
|
||||
return
|
||||
}
|
||||
pb.events <- &mesh.PeerEvent{Type: mesh.AddEvent, Peer: translatePeer(p)}
|
||||
},
|
||||
UpdateFunc: func(_, obj interface{}) {
|
||||
p, ok := obj.(*v1alpha1.Peer)
|
||||
if !ok || p.Validate() != nil {
|
||||
// Failed to decode Peer; ignoring...
|
||||
return
|
||||
}
|
||||
pb.events <- &mesh.PeerEvent{Type: mesh.UpdateEvent, Peer: translatePeer(p)}
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
p, ok := obj.(*v1alpha1.Peer)
|
||||
if !ok || p.Validate() != nil {
|
||||
// Failed to decode Peer; ignoring...
|
||||
return
|
||||
}
|
||||
pb.events <- &mesh.PeerEvent{Type: mesh.DeleteEvent, Peer: translatePeer(p)}
|
||||
},
|
||||
},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// List gets all the Peers in the cluster.
|
||||
func (pb *peerBackend) List() ([]*mesh.Peer, error) {
|
||||
ps, err := pb.lister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peers := make([]*mesh.Peer, len(ps))
|
||||
for i := range ps {
|
||||
// Skip invalid peers.
|
||||
if ps[i].Validate() != nil {
|
||||
continue
|
||||
}
|
||||
peers[i] = translatePeer(ps[i])
|
||||
}
|
||||
return peers, nil
|
||||
}
|
||||
|
||||
// Set sets the fields of a peer.
|
||||
func (pb *peerBackend) Set(name string, peer *mesh.Peer) error {
|
||||
old, err := pb.lister.Get(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find peer: %v", err)
|
||||
}
|
||||
p := old.DeepCopy()
|
||||
p.Spec.AllowedIPs = make([]string, len(peer.AllowedIPs))
|
||||
for i := range peer.AllowedIPs {
|
||||
p.Spec.AllowedIPs[i] = peer.AllowedIPs[i].String()
|
||||
}
|
||||
if peer.Endpoint != nil {
|
||||
p.Spec.Endpoint = &v1alpha1.PeerEndpoint{
|
||||
IP: peer.Endpoint.IP.String(),
|
||||
Port: peer.Endpoint.Port,
|
||||
}
|
||||
}
|
||||
p.Spec.PersistentKeepalive = peer.PersistentKeepalive
|
||||
p.Spec.PublicKey = string(peer.PublicKey)
|
||||
if _, err = pb.client.KiloV1alpha1().Peers().Update(p); err != nil {
|
||||
return fmt.Errorf("failed to update peer: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Watch returns a chan of peer events.
|
||||
func (pb *peerBackend) Watch() <-chan *mesh.PeerEvent {
|
||||
return pb.events
|
||||
}
|
||||
|
||||
func normalizeIP(ip string) *net.IPNet {
|
||||
i, ipNet, _ := net.ParseCIDR(ip)
|
||||
if ipNet == nil {
|
||||
return ipNet
|
||||
i, ipNet, err := net.ParseCIDR(ip)
|
||||
if err != nil || ipNet == nil {
|
||||
return nil
|
||||
}
|
||||
if ip4 := i.To4(); ip4 != nil {
|
||||
ipNet.IP = ip4
|
||||
|
@ -21,7 +21,9 @@ import (
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||
"github.com/squat/kilo/pkg/mesh"
|
||||
"github.com/squat/kilo/pkg/wireguard"
|
||||
)
|
||||
|
||||
func TestTranslateNode(t *testing.T) {
|
||||
@ -152,3 +154,113 @@ func TestTranslateNode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslatePeer(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
out *mesh.Peer
|
||||
spec v1alpha1.PeerSpec
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
out: &mesh.Peer{},
|
||||
},
|
||||
{
|
||||
name: "invalid ips",
|
||||
spec: v1alpha1.PeerSpec{
|
||||
AllowedIPs: []string{
|
||||
"10.0.0.1",
|
||||
"foo",
|
||||
},
|
||||
},
|
||||
out: &mesh.Peer{},
|
||||
},
|
||||
{
|
||||
name: "valid ips",
|
||||
spec: v1alpha1.PeerSpec{
|
||||
AllowedIPs: []string{
|
||||
"10.0.0.1/24",
|
||||
"10.0.0.2/32",
|
||||
},
|
||||
},
|
||||
out: &mesh.Peer{
|
||||
Peer: wireguard.Peer{
|
||||
AllowedIPs: []*net.IPNet{
|
||||
{IP: net.ParseIP("10.0.0.1"), Mask: net.CIDRMask(24, 32)},
|
||||
{IP: net.ParseIP("10.0.0.2"), Mask: net.CIDRMask(32, 32)},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid endpoint ip",
|
||||
spec: v1alpha1.PeerSpec{
|
||||
Endpoint: &v1alpha1.PeerEndpoint{
|
||||
IP: "foo",
|
||||
Port: mesh.DefaultKiloPort,
|
||||
},
|
||||
},
|
||||
out: &mesh.Peer{},
|
||||
},
|
||||
{
|
||||
name: "valid endpoint",
|
||||
spec: v1alpha1.PeerSpec{
|
||||
Endpoint: &v1alpha1.PeerEndpoint{
|
||||
IP: "10.0.0.1",
|
||||
Port: mesh.DefaultKiloPort,
|
||||
},
|
||||
},
|
||||
out: &mesh.Peer{
|
||||
Peer: wireguard.Peer{
|
||||
Endpoint: &wireguard.Endpoint{
|
||||
IP: net.ParseIP("10.0.0.1"),
|
||||
Port: mesh.DefaultKiloPort,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty key",
|
||||
spec: v1alpha1.PeerSpec{
|
||||
PublicKey: "",
|
||||
},
|
||||
out: &mesh.Peer{},
|
||||
},
|
||||
{
|
||||
name: "valid key",
|
||||
spec: v1alpha1.PeerSpec{
|
||||
PublicKey: "foo",
|
||||
},
|
||||
out: &mesh.Peer{
|
||||
Peer: wireguard.Peer{
|
||||
PublicKey: []byte("foo"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid keepalive",
|
||||
spec: v1alpha1.PeerSpec{
|
||||
PersistentKeepalive: -1,
|
||||
},
|
||||
out: &mesh.Peer{},
|
||||
},
|
||||
{
|
||||
name: "valid keepalive",
|
||||
spec: v1alpha1.PeerSpec{
|
||||
PersistentKeepalive: 1,
|
||||
},
|
||||
out: &mesh.Peer{
|
||||
Peer: wireguard.Peer{
|
||||
PersistentKeepalive: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
p := &v1alpha1.Peer{}
|
||||
p.Spec = tc.spec
|
||||
peer := translatePeer(p)
|
||||
if diff := pretty.Compare(peer, tc.out); diff != "" {
|
||||
t.Errorf("test case %q: got diff: %v", tc.name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
88
pkg/k8s/clientset/versioned/clientset.go
Normal file
88
pkg/k8s/clientset/versioned/clientset.go
Normal file
@ -0,0 +1,88 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package versioned
|
||||
|
||||
import (
|
||||
kilov1alpha1 "github.com/squat/kilo/pkg/k8s/clientset/versioned/typed/kilo/v1alpha1"
|
||||
discovery "k8s.io/client-go/discovery"
|
||||
rest "k8s.io/client-go/rest"
|
||||
flowcontrol "k8s.io/client-go/util/flowcontrol"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Discovery() discovery.DiscoveryInterface
|
||||
KiloV1alpha1() kilov1alpha1.KiloV1alpha1Interface
|
||||
}
|
||||
|
||||
// Clientset contains the clients for groups. Each group has exactly one
|
||||
// version included in a Clientset.
|
||||
type Clientset struct {
|
||||
*discovery.DiscoveryClient
|
||||
kiloV1alpha1 *kilov1alpha1.KiloV1alpha1Client
|
||||
}
|
||||
|
||||
// KiloV1alpha1 retrieves the KiloV1alpha1Client
|
||||
func (c *Clientset) KiloV1alpha1() kilov1alpha1.KiloV1alpha1Interface {
|
||||
return c.kiloV1alpha1
|
||||
}
|
||||
|
||||
// Discovery retrieves the DiscoveryClient
|
||||
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.DiscoveryClient
|
||||
}
|
||||
|
||||
// NewForConfig creates a new Clientset for the given config.
|
||||
func NewForConfig(c *rest.Config) (*Clientset, error) {
|
||||
configShallowCopy := *c
|
||||
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
|
||||
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
|
||||
}
|
||||
var cs Clientset
|
||||
var err error
|
||||
cs.kiloV1alpha1, err = kilov1alpha1.NewForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cs, nil
|
||||
}
|
||||
|
||||
// NewForConfigOrDie creates a new Clientset for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *Clientset {
|
||||
var cs Clientset
|
||||
cs.kiloV1alpha1 = kilov1alpha1.NewForConfigOrDie(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
|
||||
return &cs
|
||||
}
|
||||
|
||||
// New creates a new Clientset for the given RESTClient.
|
||||
func New(c rest.Interface) *Clientset {
|
||||
var cs Clientset
|
||||
cs.kiloV1alpha1 = kilov1alpha1.New(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
|
||||
return &cs
|
||||
}
|
18
pkg/k8s/clientset/versioned/doc.go
Normal file
18
pkg/k8s/clientset/versioned/doc.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package has the automatically generated clientset.
|
||||
package versioned
|
75
pkg/k8s/clientset/versioned/fake/clientset_generated.go
Normal file
75
pkg/k8s/clientset/versioned/fake/clientset_generated.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
clientset "github.com/squat/kilo/pkg/k8s/clientset/versioned"
|
||||
kilov1alpha1 "github.com/squat/kilo/pkg/k8s/clientset/versioned/typed/kilo/v1alpha1"
|
||||
fakekilov1alpha1 "github.com/squat/kilo/pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/fake"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/discovery"
|
||||
fakediscovery "k8s.io/client-go/discovery/fake"
|
||||
"k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// NewSimpleClientset returns a clientset that will respond with the provided objects.
|
||||
// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,
|
||||
// without applying any validations and/or defaults. It shouldn't be considered a replacement
|
||||
// for a real clientset and is mostly useful in simple unit tests.
|
||||
func NewSimpleClientset(objects ...runtime.Object) *Clientset {
|
||||
o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())
|
||||
for _, obj := range objects {
|
||||
if err := o.Add(obj); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
cs := &Clientset{}
|
||||
cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake}
|
||||
cs.AddReactor("*", "*", testing.ObjectReaction(o))
|
||||
cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) {
|
||||
gvr := action.GetResource()
|
||||
ns := action.GetNamespace()
|
||||
watch, err := o.Watch(gvr, ns)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
return true, watch, nil
|
||||
})
|
||||
|
||||
return cs
|
||||
}
|
||||
|
||||
// Clientset implements clientset.Interface. Meant to be embedded into a
|
||||
// struct to get a default implementation. This makes faking out just the method
|
||||
// you want to test easier.
|
||||
type Clientset struct {
|
||||
testing.Fake
|
||||
discovery *fakediscovery.FakeDiscovery
|
||||
}
|
||||
|
||||
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
||||
return c.discovery
|
||||
}
|
||||
|
||||
var _ clientset.Interface = &Clientset{}
|
||||
|
||||
// KiloV1alpha1 retrieves the KiloV1alpha1Client
|
||||
func (c *Clientset) KiloV1alpha1() kilov1alpha1.KiloV1alpha1Interface {
|
||||
return &fakekilov1alpha1.FakeKiloV1alpha1{Fake: &c.Fake}
|
||||
}
|
18
pkg/k8s/clientset/versioned/fake/doc.go
Normal file
18
pkg/k8s/clientset/versioned/fake/doc.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package has the automatically generated fake clientset.
|
||||
package fake
|
54
pkg/k8s/clientset/versioned/fake/register.go
Normal file
54
pkg/k8s/clientset/versioned/fake/register.go
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
kilov1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
var scheme = runtime.NewScheme()
|
||||
var codecs = serializer.NewCodecFactory(scheme)
|
||||
var parameterCodec = runtime.NewParameterCodec(scheme)
|
||||
var localSchemeBuilder = runtime.SchemeBuilder{
|
||||
kilov1alpha1.AddToScheme,
|
||||
}
|
||||
|
||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||
// of clientsets, like in:
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
//
|
||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||
// correctly.
|
||||
var AddToScheme = localSchemeBuilder.AddToScheme
|
||||
|
||||
func init() {
|
||||
v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
|
||||
utilruntime.Must(AddToScheme(scheme))
|
||||
}
|
18
pkg/k8s/clientset/versioned/scheme/doc.go
Normal file
18
pkg/k8s/clientset/versioned/scheme/doc.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package contains the scheme of the automatically generated clientset.
|
||||
package scheme
|
54
pkg/k8s/clientset/versioned/scheme/register.go
Normal file
54
pkg/k8s/clientset/versioned/scheme/register.go
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package scheme
|
||||
|
||||
import (
|
||||
kilov1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
var Scheme = runtime.NewScheme()
|
||||
var Codecs = serializer.NewCodecFactory(Scheme)
|
||||
var ParameterCodec = runtime.NewParameterCodec(Scheme)
|
||||
var localSchemeBuilder = runtime.SchemeBuilder{
|
||||
kilov1alpha1.AddToScheme,
|
||||
}
|
||||
|
||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||
// of clientsets, like in:
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
//
|
||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||
// correctly.
|
||||
var AddToScheme = localSchemeBuilder.AddToScheme
|
||||
|
||||
func init() {
|
||||
v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
|
||||
utilruntime.Must(AddToScheme(Scheme))
|
||||
}
|
18
pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/doc.go
Normal file
18
pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/doc.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package has the automatically generated typed clients.
|
||||
package v1alpha1
|
18
pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/fake/doc.go
Normal file
18
pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/fake/doc.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// Package fake has the automatically generated clients.
|
||||
package fake
|
@ -0,0 +1,38 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
v1alpha1 "github.com/squat/kilo/pkg/k8s/clientset/versioned/typed/kilo/v1alpha1"
|
||||
rest "k8s.io/client-go/rest"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
type FakeKiloV1alpha1 struct {
|
||||
*testing.Fake
|
||||
}
|
||||
|
||||
func (c *FakeKiloV1alpha1) Peers() v1alpha1.PeerInterface {
|
||||
return &FakePeers{c}
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *FakeKiloV1alpha1) RESTClient() rest.Interface {
|
||||
var ret *rest.RESTClient
|
||||
return ret
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
v1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// FakePeers implements PeerInterface
|
||||
type FakePeers struct {
|
||||
Fake *FakeKiloV1alpha1
|
||||
}
|
||||
|
||||
var peersResource = schema.GroupVersionResource{Group: "kilo", Version: "v1alpha1", Resource: "peers"}
|
||||
|
||||
var peersKind = schema.GroupVersionKind{Group: "kilo", Version: "v1alpha1", Kind: "Peer"}
|
||||
|
||||
// Get takes name of the peer, and returns the corresponding peer object, and an error if there is any.
|
||||
func (c *FakePeers) Get(name string, options v1.GetOptions) (result *v1alpha1.Peer, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewRootGetAction(peersResource, name), &v1alpha1.Peer{})
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.Peer), err
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of Peers that match those selectors.
|
||||
func (c *FakePeers) List(opts v1.ListOptions) (result *v1alpha1.PeerList, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewRootListAction(peersResource, peersKind, opts), &v1alpha1.PeerList{})
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
label, _, _ := testing.ExtractFromListOptions(opts)
|
||||
if label == nil {
|
||||
label = labels.Everything()
|
||||
}
|
||||
list := &v1alpha1.PeerList{ListMeta: obj.(*v1alpha1.PeerList).ListMeta}
|
||||
for _, item := range obj.(*v1alpha1.PeerList).Items {
|
||||
if label.Matches(labels.Set(item.Labels)) {
|
||||
list.Items = append(list.Items, item)
|
||||
}
|
||||
}
|
||||
return list, err
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested peers.
|
||||
func (c *FakePeers) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||
return c.Fake.
|
||||
InvokesWatch(testing.NewRootWatchAction(peersResource, opts))
|
||||
}
|
||||
|
||||
// Create takes the representation of a peer and creates it. Returns the server's representation of the peer, and an error, if there is any.
|
||||
func (c *FakePeers) Create(peer *v1alpha1.Peer) (result *v1alpha1.Peer, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewRootCreateAction(peersResource, peer), &v1alpha1.Peer{})
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.Peer), err
|
||||
}
|
||||
|
||||
// Update takes the representation of a peer and updates it. Returns the server's representation of the peer, and an error, if there is any.
|
||||
func (c *FakePeers) Update(peer *v1alpha1.Peer) (result *v1alpha1.Peer, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewRootUpdateAction(peersResource, peer), &v1alpha1.Peer{})
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.Peer), err
|
||||
}
|
||||
|
||||
// Delete takes name of the peer and deletes it. Returns an error if one occurs.
|
||||
func (c *FakePeers) Delete(name string, options *v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewRootDeleteAction(peersResource, name), &v1alpha1.Peer{})
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *FakePeers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||
action := testing.NewRootDeleteCollectionAction(peersResource, listOptions)
|
||||
|
||||
_, err := c.Fake.Invokes(action, &v1alpha1.PeerList{})
|
||||
return err
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched peer.
|
||||
func (c *FakePeers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Peer, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewRootPatchSubresourceAction(peersResource, name, pt, data, subresources...), &v1alpha1.Peer{})
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.Peer), err
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
type PeerExpansion interface{}
|
@ -0,0 +1,88 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
v1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||
"github.com/squat/kilo/pkg/k8s/clientset/versioned/scheme"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type KiloV1alpha1Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
PeersGetter
|
||||
}
|
||||
|
||||
// KiloV1alpha1Client is used to interact with features provided by the kilo group.
|
||||
type KiloV1alpha1Client struct {
|
||||
restClient rest.Interface
|
||||
}
|
||||
|
||||
func (c *KiloV1alpha1Client) Peers() PeerInterface {
|
||||
return newPeers(c)
|
||||
}
|
||||
|
||||
// NewForConfig creates a new KiloV1alpha1Client for the given config.
|
||||
func NewForConfig(c *rest.Config) (*KiloV1alpha1Client, error) {
|
||||
config := *c
|
||||
if err := setConfigDefaults(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := rest.RESTClientFor(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &KiloV1alpha1Client{client}, nil
|
||||
}
|
||||
|
||||
// NewForConfigOrDie creates a new KiloV1alpha1Client for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *KiloV1alpha1Client {
|
||||
client, err := NewForConfig(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// New creates a new KiloV1alpha1Client for the given RESTClient.
|
||||
func New(c rest.Interface) *KiloV1alpha1Client {
|
||||
return &KiloV1alpha1Client{c}
|
||||
}
|
||||
|
||||
func setConfigDefaults(config *rest.Config) error {
|
||||
gv := v1alpha1.SchemeGroupVersion
|
||||
config.GroupVersion = &gv
|
||||
config.APIPath = "/apis"
|
||||
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
|
||||
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *KiloV1alpha1Client) RESTClient() rest.Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.restClient
|
||||
}
|
162
pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go
Normal file
162
pkg/k8s/clientset/versioned/typed/kilo/v1alpha1/peer.go
Normal file
@ -0,0 +1,162 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
v1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||
scheme "github.com/squat/kilo/pkg/k8s/clientset/versioned/scheme"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// PeersGetter has a method to return a PeerInterface.
|
||||
// A group's client should implement this interface.
|
||||
type PeersGetter interface {
|
||||
Peers() PeerInterface
|
||||
}
|
||||
|
||||
// PeerInterface has methods to work with Peer resources.
|
||||
type PeerInterface interface {
|
||||
Create(*v1alpha1.Peer) (*v1alpha1.Peer, error)
|
||||
Update(*v1alpha1.Peer) (*v1alpha1.Peer, error)
|
||||
Delete(name string, options *v1.DeleteOptions) error
|
||||
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
|
||||
Get(name string, options v1.GetOptions) (*v1alpha1.Peer, error)
|
||||
List(opts v1.ListOptions) (*v1alpha1.PeerList, error)
|
||||
Watch(opts v1.ListOptions) (watch.Interface, error)
|
||||
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Peer, err error)
|
||||
PeerExpansion
|
||||
}
|
||||
|
||||
// peers implements PeerInterface
|
||||
type peers struct {
|
||||
client rest.Interface
|
||||
}
|
||||
|
||||
// newPeers returns a Peers
|
||||
func newPeers(c *KiloV1alpha1Client) *peers {
|
||||
return &peers{
|
||||
client: c.RESTClient(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get takes name of the peer, and returns the corresponding peer object, and an error if there is any.
|
||||
func (c *peers) Get(name string, options v1.GetOptions) (result *v1alpha1.Peer, err error) {
|
||||
result = &v1alpha1.Peer{}
|
||||
err = c.client.Get().
|
||||
Resource("peers").
|
||||
Name(name).
|
||||
VersionedParams(&options, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of Peers that match those selectors.
|
||||
func (c *peers) List(opts v1.ListOptions) (result *v1alpha1.PeerList, err error) {
|
||||
var timeout time.Duration
|
||||
if opts.TimeoutSeconds != nil {
|
||||
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||
}
|
||||
result = &v1alpha1.PeerList{}
|
||||
err = c.client.Get().
|
||||
Resource("peers").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Timeout(timeout).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested peers.
|
||||
func (c *peers) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||
var timeout time.Duration
|
||||
if opts.TimeoutSeconds != nil {
|
||||
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||
}
|
||||
opts.Watch = true
|
||||
return c.client.Get().
|
||||
Resource("peers").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Timeout(timeout).
|
||||
Watch()
|
||||
}
|
||||
|
||||
// Create takes the representation of a peer and creates it. Returns the server's representation of the peer, and an error, if there is any.
|
||||
func (c *peers) Create(peer *v1alpha1.Peer) (result *v1alpha1.Peer, err error) {
|
||||
result = &v1alpha1.Peer{}
|
||||
err = c.client.Post().
|
||||
Resource("peers").
|
||||
Body(peer).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Update takes the representation of a peer and updates it. Returns the server's representation of the peer, and an error, if there is any.
|
||||
func (c *peers) Update(peer *v1alpha1.Peer) (result *v1alpha1.Peer, err error) {
|
||||
result = &v1alpha1.Peer{}
|
||||
err = c.client.Put().
|
||||
Resource("peers").
|
||||
Name(peer.Name).
|
||||
Body(peer).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the peer and deletes it. Returns an error if one occurs.
|
||||
func (c *peers) Delete(name string, options *v1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
Resource("peers").
|
||||
Name(name).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *peers) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||
var timeout time.Duration
|
||||
if listOptions.TimeoutSeconds != nil {
|
||||
timeout = time.Duration(*listOptions.TimeoutSeconds) * time.Second
|
||||
}
|
||||
return c.client.Delete().
|
||||
Resource("peers").
|
||||
VersionedParams(&listOptions, scheme.ParameterCodec).
|
||||
Timeout(timeout).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched peer.
|
||||
func (c *peers) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1alpha1.Peer, err error) {
|
||||
result = &v1alpha1.Peer{}
|
||||
err = c.client.Patch(pt).
|
||||
Resource("peers").
|
||||
SubResource(subresources...).
|
||||
Name(name).
|
||||
Body(data).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
178
pkg/k8s/informers/factory.go
Normal file
178
pkg/k8s/informers/factory.go
Normal file
@ -0,0 +1,178 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package informers
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
time "time"
|
||||
|
||||
versioned "github.com/squat/kilo/pkg/k8s/clientset/versioned"
|
||||
internalinterfaces "github.com/squat/kilo/pkg/k8s/informers/internalinterfaces"
|
||||
kilo "github.com/squat/kilo/pkg/k8s/informers/kilo"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// SharedInformerOption defines the functional option type for SharedInformerFactory.
|
||||
type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory
|
||||
|
||||
type sharedInformerFactory struct {
|
||||
client versioned.Interface
|
||||
namespace string
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
lock sync.Mutex
|
||||
defaultResync time.Duration
|
||||
customResync map[reflect.Type]time.Duration
|
||||
|
||||
informers map[reflect.Type]cache.SharedIndexInformer
|
||||
// startedInformers is used for tracking which informers have been started.
|
||||
// This allows Start() to be called multiple times safely.
|
||||
startedInformers map[reflect.Type]bool
|
||||
}
|
||||
|
||||
// WithCustomResyncConfig sets a custom resync period for the specified informer types.
|
||||
func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption {
|
||||
return func(factory *sharedInformerFactory) *sharedInformerFactory {
|
||||
for k, v := range resyncConfig {
|
||||
factory.customResync[reflect.TypeOf(k)] = v
|
||||
}
|
||||
return factory
|
||||
}
|
||||
}
|
||||
|
||||
// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory.
|
||||
func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption {
|
||||
return func(factory *sharedInformerFactory) *sharedInformerFactory {
|
||||
factory.tweakListOptions = tweakListOptions
|
||||
return factory
|
||||
}
|
||||
}
|
||||
|
||||
// WithNamespace limits the SharedInformerFactory to the specified namespace.
|
||||
func WithNamespace(namespace string) SharedInformerOption {
|
||||
return func(factory *sharedInformerFactory) *sharedInformerFactory {
|
||||
factory.namespace = namespace
|
||||
return factory
|
||||
}
|
||||
}
|
||||
|
||||
// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces.
|
||||
func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory {
|
||||
return NewSharedInformerFactoryWithOptions(client, defaultResync)
|
||||
}
|
||||
|
||||
// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory.
|
||||
// Listers obtained via this SharedInformerFactory will be subject to the same filters
|
||||
// as specified here.
|
||||
// Deprecated: Please use NewSharedInformerFactoryWithOptions instead
|
||||
func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory {
|
||||
return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions))
|
||||
}
|
||||
|
||||
// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options.
|
||||
func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {
|
||||
factory := &sharedInformerFactory{
|
||||
client: client,
|
||||
namespace: v1.NamespaceAll,
|
||||
defaultResync: defaultResync,
|
||||
informers: make(map[reflect.Type]cache.SharedIndexInformer),
|
||||
startedInformers: make(map[reflect.Type]bool),
|
||||
customResync: make(map[reflect.Type]time.Duration),
|
||||
}
|
||||
|
||||
// Apply all options
|
||||
for _, opt := range options {
|
||||
factory = opt(factory)
|
||||
}
|
||||
|
||||
return factory
|
||||
}
|
||||
|
||||
// Start initializes all requested informers.
|
||||
func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
for informerType, informer := range f.informers {
|
||||
if !f.startedInformers[informerType] {
|
||||
go informer.Run(stopCh)
|
||||
f.startedInformers[informerType] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WaitForCacheSync waits for all started informers' cache were synced.
|
||||
func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool {
|
||||
informers := func() map[reflect.Type]cache.SharedIndexInformer {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
informers := map[reflect.Type]cache.SharedIndexInformer{}
|
||||
for informerType, informer := range f.informers {
|
||||
if f.startedInformers[informerType] {
|
||||
informers[informerType] = informer
|
||||
}
|
||||
}
|
||||
return informers
|
||||
}()
|
||||
|
||||
res := map[reflect.Type]bool{}
|
||||
for informType, informer := range informers {
|
||||
res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// InternalInformerFor returns the SharedIndexInformer for obj using an internal
|
||||
// client.
|
||||
func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
informerType := reflect.TypeOf(obj)
|
||||
informer, exists := f.informers[informerType]
|
||||
if exists {
|
||||
return informer
|
||||
}
|
||||
|
||||
resyncPeriod, exists := f.customResync[informerType]
|
||||
if !exists {
|
||||
resyncPeriod = f.defaultResync
|
||||
}
|
||||
|
||||
informer = newFunc(f.client, resyncPeriod)
|
||||
f.informers[informerType] = informer
|
||||
|
||||
return informer
|
||||
}
|
||||
|
||||
// SharedInformerFactory provides shared informers for resources in all known
|
||||
// API group versions.
|
||||
type SharedInformerFactory interface {
|
||||
internalinterfaces.SharedInformerFactory
|
||||
ForResource(resource schema.GroupVersionResource) (GenericInformer, error)
|
||||
WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool
|
||||
|
||||
Kilo() kilo.Interface
|
||||
}
|
||||
|
||||
func (f *sharedInformerFactory) Kilo() kilo.Interface {
|
||||
return kilo.New(f, f.namespace, f.tweakListOptions)
|
||||
}
|
60
pkg/k8s/informers/generic.go
Normal file
60
pkg/k8s/informers/generic.go
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package informers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
v1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// GenericInformer is type of SharedIndexInformer which will locate and delegate to other
|
||||
// sharedInformers based on type
|
||||
type GenericInformer interface {
|
||||
Informer() cache.SharedIndexInformer
|
||||
Lister() cache.GenericLister
|
||||
}
|
||||
|
||||
type genericInformer struct {
|
||||
informer cache.SharedIndexInformer
|
||||
resource schema.GroupResource
|
||||
}
|
||||
|
||||
// Informer returns the SharedIndexInformer.
|
||||
func (f *genericInformer) Informer() cache.SharedIndexInformer {
|
||||
return f.informer
|
||||
}
|
||||
|
||||
// Lister returns the GenericLister.
|
||||
func (f *genericInformer) Lister() cache.GenericLister {
|
||||
return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource)
|
||||
}
|
||||
|
||||
// ForResource gives generic access to a shared informer of the matching type
|
||||
// TODO extend this to unknown resources with a client pool
|
||||
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
|
||||
switch resource {
|
||||
// Group=kilo, Version=v1alpha1
|
||||
case v1alpha1.SchemeGroupVersion.WithResource("peers"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Kilo().V1alpha1().Peers().Informer()}, nil
|
||||
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no informer found for %v", resource)
|
||||
}
|
38
pkg/k8s/informers/internalinterfaces/factory_interfaces.go
Normal file
38
pkg/k8s/informers/internalinterfaces/factory_interfaces.go
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package internalinterfaces
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
versioned "github.com/squat/kilo/pkg/k8s/clientset/versioned"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer.
|
||||
type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer
|
||||
|
||||
// SharedInformerFactory a small interface to allow for adding an informer without an import cycle
|
||||
type SharedInformerFactory interface {
|
||||
Start(stopCh <-chan struct{})
|
||||
InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer
|
||||
}
|
||||
|
||||
// TweakListOptionsFunc is a function that transforms a v1.ListOptions.
|
||||
type TweakListOptionsFunc func(*v1.ListOptions)
|
44
pkg/k8s/informers/kilo/interface.go
Normal file
44
pkg/k8s/informers/kilo/interface.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package kilo
|
||||
|
||||
import (
|
||||
internalinterfaces "github.com/squat/kilo/pkg/k8s/informers/internalinterfaces"
|
||||
v1alpha1 "github.com/squat/kilo/pkg/k8s/informers/kilo/v1alpha1"
|
||||
)
|
||||
|
||||
// Interface provides access to each of this group's versions.
|
||||
type Interface interface {
|
||||
// V1alpha1 provides access to shared informers for resources in V1alpha1.
|
||||
V1alpha1() v1alpha1.Interface
|
||||
}
|
||||
|
||||
type group struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
namespace string
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
}
|
||||
|
||||
// New returns a new Interface.
|
||||
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
|
||||
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
|
||||
}
|
||||
|
||||
// V1alpha1 returns a new v1alpha1.Interface.
|
||||
func (g *group) V1alpha1() v1alpha1.Interface {
|
||||
return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions)
|
||||
}
|
43
pkg/k8s/informers/kilo/v1alpha1/interface.go
Normal file
43
pkg/k8s/informers/kilo/v1alpha1/interface.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
internalinterfaces "github.com/squat/kilo/pkg/k8s/informers/internalinterfaces"
|
||||
)
|
||||
|
||||
// Interface provides access to all the informers in this group version.
|
||||
type Interface interface {
|
||||
// Peers returns a PeerInformer.
|
||||
Peers() PeerInformer
|
||||
}
|
||||
|
||||
type version struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
namespace string
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
}
|
||||
|
||||
// New returns a new Interface.
|
||||
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
|
||||
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
|
||||
}
|
||||
|
||||
// Peers returns a PeerInformer.
|
||||
func (v *version) Peers() PeerInformer {
|
||||
return &peerInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
|
||||
}
|
86
pkg/k8s/informers/kilo/v1alpha1/peer.go
Normal file
86
pkg/k8s/informers/kilo/v1alpha1/peer.go
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
time "time"
|
||||
|
||||
kilov1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||
versioned "github.com/squat/kilo/pkg/k8s/clientset/versioned"
|
||||
internalinterfaces "github.com/squat/kilo/pkg/k8s/informers/internalinterfaces"
|
||||
v1alpha1 "github.com/squat/kilo/pkg/k8s/listers/kilo/v1alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// PeerInformer provides access to a shared informer and lister for
|
||||
// Peers.
|
||||
type PeerInformer interface {
|
||||
Informer() cache.SharedIndexInformer
|
||||
Lister() v1alpha1.PeerLister
|
||||
}
|
||||
|
||||
type peerInformer struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
}
|
||||
|
||||
// NewPeerInformer constructs a new informer for Peer type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewPeerInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||
return NewFilteredPeerInformer(client, resyncPeriod, indexers, nil)
|
||||
}
|
||||
|
||||
// NewFilteredPeerInformer constructs a new informer for Peer type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewFilteredPeerInformer(client versioned.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
|
||||
return cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.KiloV1alpha1().Peers().List(options)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.KiloV1alpha1().Peers().Watch(options)
|
||||
},
|
||||
},
|
||||
&kilov1alpha1.Peer{},
|
||||
resyncPeriod,
|
||||
indexers,
|
||||
)
|
||||
}
|
||||
|
||||
func (f *peerInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
|
||||
return NewFilteredPeerInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||
}
|
||||
|
||||
func (f *peerInformer) Informer() cache.SharedIndexInformer {
|
||||
return f.factory.InformerFor(&kilov1alpha1.Peer{}, f.defaultInformer)
|
||||
}
|
||||
|
||||
func (f *peerInformer) Lister() v1alpha1.PeerLister {
|
||||
return v1alpha1.NewPeerLister(f.Informer().GetIndexer())
|
||||
}
|
21
pkg/k8s/listers/kilo/v1alpha1/expansion_generated.go
Normal file
21
pkg/k8s/listers/kilo/v1alpha1/expansion_generated.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by lister-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// PeerListerExpansion allows custom methods to be added to
|
||||
// PeerLister.
|
||||
type PeerListerExpansion interface{}
|
63
pkg/k8s/listers/kilo/v1alpha1/peer.go
Normal file
63
pkg/k8s/listers/kilo/v1alpha1/peer.go
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by lister-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
v1alpha1 "github.com/squat/kilo/pkg/k8s/apis/kilo/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// PeerLister helps list Peers.
|
||||
type PeerLister interface {
|
||||
// List lists all Peers in the indexer.
|
||||
List(selector labels.Selector) (ret []*v1alpha1.Peer, err error)
|
||||
// Get retrieves the Peer from the index for a given name.
|
||||
Get(name string) (*v1alpha1.Peer, error)
|
||||
PeerListerExpansion
|
||||
}
|
||||
|
||||
// peerLister implements the PeerLister interface.
|
||||
type peerLister struct {
|
||||
indexer cache.Indexer
|
||||
}
|
||||
|
||||
// NewPeerLister returns a new PeerLister.
|
||||
func NewPeerLister(indexer cache.Indexer) PeerLister {
|
||||
return &peerLister{indexer: indexer}
|
||||
}
|
||||
|
||||
// List lists all Peers in the indexer.
|
||||
func (s *peerLister) List(selector labels.Selector) (ret []*v1alpha1.Peer, err error) {
|
||||
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*v1alpha1.Peer))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Get retrieves the Peer from the index for a given name.
|
||||
func (s *peerLister) Get(name string) (*v1alpha1.Peer, error) {
|
||||
obj, exists, err := s.indexer.GetByKey(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(v1alpha1.Resource("peer"), name)
|
||||
}
|
||||
return obj.(*v1alpha1.Peer), nil
|
||||
}
|
@ -37,22 +37,22 @@ func (t *Topology) Dot() (string, error) {
|
||||
if err := g.SetDir(true); err != nil {
|
||||
return "", fmt.Errorf("failed to set direction")
|
||||
}
|
||||
leaders := make([]string, len(t.Segments))
|
||||
leaders := make([]string, len(t.segments))
|
||||
nodeAttrs := map[string]string{
|
||||
string(gographviz.Shape): "ellipse",
|
||||
}
|
||||
for i, s := range t.Segments {
|
||||
if err := g.AddSubGraph("kilo", subGraphName(s.Location), nil); err != nil {
|
||||
for i, s := range t.segments {
|
||||
if err := g.AddSubGraph("kilo", subGraphName(s.location), nil); err != nil {
|
||||
return "", fmt.Errorf("failed to add subgraph")
|
||||
}
|
||||
if err := g.AddAttr(subGraphName(s.Location), string(gographviz.Label), graphEscape(s.Location)); err != nil {
|
||||
if err := g.AddAttr(subGraphName(s.location), string(gographviz.Label), graphEscape(s.location)); err != nil {
|
||||
return "", fmt.Errorf("failed to add label to subgraph")
|
||||
}
|
||||
if err := g.AddAttr(subGraphName(s.Location), string(gographviz.Style), `"dashed,rounded"`); err != nil {
|
||||
if err := g.AddAttr(subGraphName(s.location), string(gographviz.Style), `"dashed,rounded"`); err != nil {
|
||||
return "", fmt.Errorf("failed to add style to subgraph")
|
||||
}
|
||||
for j := range s.cidrs {
|
||||
if err := g.AddNode(subGraphName(s.Location), graphEscape(s.hostnames[j]), nodeAttrs); err != nil {
|
||||
if err := g.AddNode(subGraphName(s.location), graphEscape(s.hostnames[j]), nodeAttrs); err != nil {
|
||||
return "", fmt.Errorf("failed to add node to subgraph")
|
||||
}
|
||||
var wg net.IP
|
||||
@ -62,11 +62,11 @@ func (t *Topology) Dot() (string, error) {
|
||||
return "", fmt.Errorf("failed to add rank to node")
|
||||
}
|
||||
}
|
||||
if err := g.Nodes.Lookup[graphEscape(s.hostnames[j])].Attrs.Add(string(gographviz.Label), nodeLabel(s.Location, s.hostnames[j], s.cidrs[j], s.privateIPs[j], wg)); err != nil {
|
||||
if err := g.Nodes.Lookup[graphEscape(s.hostnames[j])].Attrs.Add(string(gographviz.Label), nodeLabel(s.location, s.hostnames[j], s.cidrs[j], s.privateIPs[j], wg)); err != nil {
|
||||
return "", fmt.Errorf("failed to add label to node")
|
||||
}
|
||||
}
|
||||
meshSubGraph(g, g.Relations.SortedChildren(subGraphName(s.Location)), s.leader)
|
||||
meshSubGraph(g, g.Relations.SortedChildren(subGraphName(s.location)), s.leader)
|
||||
leaders[i] = graphEscape(s.hostnames[s.leader])
|
||||
}
|
||||
meshSubGraph(g, leaders, 0)
|
||||
|
263
pkg/mesh/mesh.go
263
pkg/mesh/mesh.go
@ -15,6 +15,7 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@ -43,6 +44,8 @@ const (
|
||||
PrivateKeyPath = KiloPath + "/key"
|
||||
// ConfPath is the filepath where the WireGuard configuration is stored.
|
||||
ConfPath = KiloPath + "/conf"
|
||||
// DefaultKiloPort is the default UDP port Kilo uses.
|
||||
DefaultKiloPort = 51820
|
||||
)
|
||||
|
||||
// Granularity represents the abstraction level at which the network
|
||||
@ -93,6 +96,17 @@ func (n *Node) Ready() bool {
|
||||
return n != nil && n.ExternalIP != nil && n.Key != nil && n.InternalIP != nil && n.Subnet != nil && time.Now().Unix()-n.LastSeen < int64(resyncPeriod)*2/int64(time.Second)
|
||||
}
|
||||
|
||||
// Peer represents a peer in the network.
|
||||
type Peer struct {
|
||||
wireguard.Peer
|
||||
Name string
|
||||
}
|
||||
|
||||
// Ready indicates whether or not the peer is ready.
|
||||
func (p *Peer) Ready() bool {
|
||||
return p != nil && p.AllowedIPs != nil && len(p.AllowedIPs) != 0 && p.PublicKey != nil
|
||||
}
|
||||
|
||||
// EventType describes what kind of an action an event represents.
|
||||
type EventType string
|
||||
|
||||
@ -105,24 +119,53 @@ const (
|
||||
UpdateEvent EventType = "update"
|
||||
)
|
||||
|
||||
// Event represents an update event concerning a node in the cluster.
|
||||
type Event struct {
|
||||
// NodeEvent represents an event concerning a node in the cluster.
|
||||
type NodeEvent struct {
|
||||
Type EventType
|
||||
Node *Node
|
||||
}
|
||||
|
||||
// Backend can get nodes by name, init itself,
|
||||
// PeerEvent represents an event concerning a peer in the cluster.
|
||||
type PeerEvent struct {
|
||||
Type EventType
|
||||
Peer *Peer
|
||||
}
|
||||
|
||||
// Backend can create clients for all of the
|
||||
// primitive types that Kilo deals with, namely:
|
||||
// * nodes; and
|
||||
// * peers.
|
||||
type Backend interface {
|
||||
Nodes() NodeBackend
|
||||
Peers() PeerBackend
|
||||
}
|
||||
|
||||
// NodeBackend can get nodes by name, init itself,
|
||||
// list the nodes that should be meshed,
|
||||
// set Kilo properties for a node,
|
||||
// clean up any changes applied to the backend,
|
||||
// and watch for changes to nodes.
|
||||
type Backend interface {
|
||||
type NodeBackend interface {
|
||||
CleanUp(string) error
|
||||
Get(string) (*Node, error)
|
||||
Init(<-chan struct{}) error
|
||||
List() ([]*Node, error)
|
||||
Set(string, *Node) error
|
||||
Watch() <-chan *Event
|
||||
Watch() <-chan *NodeEvent
|
||||
}
|
||||
|
||||
// PeerBackend can get peers by name, init itself,
|
||||
// list the peers that should be in the mesh,
|
||||
// set fields for a peer,
|
||||
// clean up any changes applied to the backend,
|
||||
// and watch for changes to peers.
|
||||
type PeerBackend interface {
|
||||
CleanUp(string) error
|
||||
Get(string) (*Peer, error)
|
||||
Init(<-chan struct{}) error
|
||||
List() ([]*Peer, error)
|
||||
Set(string, *Peer) error
|
||||
Watch() <-chan *PeerEvent
|
||||
}
|
||||
|
||||
// Mesh is able to create Kilo network meshes.
|
||||
@ -138,7 +181,7 @@ type Mesh struct {
|
||||
kiloIface int
|
||||
key []byte
|
||||
local bool
|
||||
port int
|
||||
port uint32
|
||||
priv []byte
|
||||
privIface int
|
||||
pub []byte
|
||||
@ -148,23 +191,26 @@ type Mesh struct {
|
||||
table *route.Table
|
||||
tunlIface int
|
||||
|
||||
// nodes is a mutable field in the struct
|
||||
// nodes and peers are mutable fields in the struct
|
||||
// and needs to be guarded.
|
||||
nodes map[string]*Node
|
||||
peers map[string]*Peer
|
||||
mu sync.Mutex
|
||||
|
||||
errorCounter *prometheus.CounterVec
|
||||
nodesGuage prometheus.Gauge
|
||||
peersGuage prometheus.Gauge
|
||||
reconcileCounter prometheus.Counter
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// New returns a new Mesh instance.
|
||||
func New(backend Backend, encapsulate Encapsulate, granularity Granularity, hostname string, port int, subnet *net.IPNet, local bool, logger log.Logger) (*Mesh, error) {
|
||||
func New(backend Backend, encapsulate Encapsulate, granularity Granularity, hostname string, port uint32, subnet *net.IPNet, local bool, logger log.Logger) (*Mesh, error) {
|
||||
if err := os.MkdirAll(KiloPath, 0700); err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory to store configuration: %v", err)
|
||||
}
|
||||
private, err := ioutil.ReadFile(PrivateKeyPath)
|
||||
private = bytes.Trim(private, "\n")
|
||||
if err != nil {
|
||||
level.Warn(logger).Log("msg", "no private key found on disk; generating one now")
|
||||
if private, err = wireguard.GenKey(); err != nil {
|
||||
@ -224,6 +270,7 @@ func New(backend Backend, encapsulate Encapsulate, granularity Granularity, host
|
||||
ipTables: ipTables,
|
||||
kiloIface: kiloIface,
|
||||
nodes: make(map[string]*Node),
|
||||
peers: make(map[string]*Peer),
|
||||
port: port,
|
||||
priv: private,
|
||||
privIface: privIface,
|
||||
@ -240,7 +287,11 @@ func New(backend Backend, encapsulate Encapsulate, granularity Granularity, host
|
||||
}, []string{"event"}),
|
||||
nodesGuage: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "kilo_nodes",
|
||||
Help: "Number of in the mesh.",
|
||||
Help: "Number of nodes in the mesh.",
|
||||
}),
|
||||
peersGuage: prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "kilo_peers",
|
||||
Help: "Number of peers in the mesh.",
|
||||
}),
|
||||
reconcileCounter: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: "kilo_reconciles_total",
|
||||
@ -252,8 +303,11 @@ func New(backend Backend, encapsulate Encapsulate, granularity Granularity, host
|
||||
|
||||
// Run starts the mesh.
|
||||
func (m *Mesh) Run() error {
|
||||
if err := m.Init(m.stop); err != nil {
|
||||
return fmt.Errorf("failed to initialize backend: %v", err)
|
||||
if err := m.Nodes().Init(m.stop); err != nil {
|
||||
return fmt.Errorf("failed to initialize node backend: %v", err)
|
||||
}
|
||||
if err := m.Peers().Init(m.stop); err != nil {
|
||||
return fmt.Errorf("failed to initialize peer backend: %v", err)
|
||||
}
|
||||
ipsetErrors, err := m.ipset.Run(m.stop)
|
||||
if err != nil {
|
||||
@ -285,14 +339,19 @@ func (m *Mesh) Run() error {
|
||||
}()
|
||||
defer m.cleanUp()
|
||||
t := time.NewTimer(resyncPeriod)
|
||||
w := m.Watch()
|
||||
nw := m.Nodes().Watch()
|
||||
pw := m.Peers().Watch()
|
||||
var ne *NodeEvent
|
||||
var pe *PeerEvent
|
||||
for {
|
||||
var e *Event
|
||||
select {
|
||||
case e = <-w:
|
||||
m.sync(e)
|
||||
case ne = <-nw:
|
||||
m.syncNodes(ne)
|
||||
case pe = <-pw:
|
||||
m.syncPeers(pe)
|
||||
case <-t.C:
|
||||
m.checkIn()
|
||||
m.syncEndpoints()
|
||||
m.applyTopology()
|
||||
t.Reset(resyncPeriod)
|
||||
case <-m.stop:
|
||||
@ -301,9 +360,50 @@ func (m *Mesh) Run() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mesh) sync(e *Event) {
|
||||
// WireGuard updates the endpoints of peers to match the
|
||||
// last place a valid packet was received from.
|
||||
// Periodically we need to syncronize the endpoints
|
||||
// of peers in the backend to match the WireGuard configuration.
|
||||
func (m *Mesh) syncEndpoints() {
|
||||
link, err := linkByIndex(m.kiloIface)
|
||||
if err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("endpoints").Inc()
|
||||
return
|
||||
}
|
||||
conf, err := wireguard.ShowConf(link.Attrs().Name)
|
||||
if err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("endpoints").Inc()
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
c := wireguard.Parse(conf)
|
||||
var key string
|
||||
var tmp *Peer
|
||||
for i := range c.Peers {
|
||||
// Peers are indexed by public key.
|
||||
key = string(c.Peers[i].PublicKey)
|
||||
if p, ok := m.peers[key]; ok {
|
||||
tmp = &Peer{
|
||||
Name: p.Name,
|
||||
Peer: *c.Peers[i],
|
||||
}
|
||||
if !peersAreEqual(tmp, p) {
|
||||
p.Endpoint = tmp.Endpoint
|
||||
if err := m.Peers().Set(p.Name, p); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("endpoints").Inc()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mesh) syncNodes(e *NodeEvent) {
|
||||
logger := log.With(m.logger, "event", e.Type)
|
||||
level.Debug(logger).Log("msg", "syncing", "event", e.Type)
|
||||
level.Debug(logger).Log("msg", "syncing nodes", "event", e.Type)
|
||||
if isSelf(m.hostname, e.Node) {
|
||||
level.Debug(logger).Log("msg", "processing local node", "node", e.Node)
|
||||
m.handleLocal(e.Node)
|
||||
@ -326,9 +426,11 @@ func (m *Mesh) sync(e *Event) {
|
||||
fallthrough
|
||||
case UpdateEvent:
|
||||
if !nodesAreEqual(m.nodes[e.Node.Name], e.Node) {
|
||||
m.nodes[e.Node.Name] = e.Node
|
||||
diff = true
|
||||
}
|
||||
// Even if the nodes are the same,
|
||||
// overwrite the old node to update the timestamp.
|
||||
m.nodes[e.Node.Name] = e.Node
|
||||
case DeleteEvent:
|
||||
delete(m.nodes, e.Node.Name)
|
||||
diff = true
|
||||
@ -341,6 +443,43 @@ func (m *Mesh) sync(e *Event) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Mesh) syncPeers(e *PeerEvent) {
|
||||
logger := log.With(m.logger, "event", e.Type)
|
||||
level.Debug(logger).Log("msg", "syncing peers", "event", e.Type)
|
||||
var diff bool
|
||||
m.mu.Lock()
|
||||
// Peers are indexed by public key.
|
||||
key := string(e.Peer.PublicKey)
|
||||
if !e.Peer.Ready() {
|
||||
level.Debug(logger).Log("msg", "received incomplete peer", "peer", e.Peer)
|
||||
// An existing peer is no longer valid
|
||||
// so remove it from the mesh.
|
||||
if _, ok := m.peers[key]; ok {
|
||||
level.Info(logger).Log("msg", "peer is no longer in the mesh", "peer", e.Peer)
|
||||
delete(m.peers, key)
|
||||
diff = true
|
||||
}
|
||||
} else {
|
||||
switch e.Type {
|
||||
case AddEvent:
|
||||
fallthrough
|
||||
case UpdateEvent:
|
||||
if !peersAreEqual(m.peers[key], e.Peer) {
|
||||
m.peers[key] = e.Peer
|
||||
diff = true
|
||||
}
|
||||
case DeleteEvent:
|
||||
delete(m.peers, key)
|
||||
diff = true
|
||||
}
|
||||
}
|
||||
m.mu.Unlock()
|
||||
if diff {
|
||||
level.Info(logger).Log("peer", e.Peer)
|
||||
m.applyTopology()
|
||||
}
|
||||
}
|
||||
|
||||
// checkIn will try to update the local node's LastSeen timestamp
|
||||
// in the backend.
|
||||
func (m *Mesh) checkIn() {
|
||||
@ -352,7 +491,7 @@ func (m *Mesh) checkIn() {
|
||||
return
|
||||
}
|
||||
n.LastSeen = time.Now().Unix()
|
||||
if err := m.Set(m.hostname, n); err != nil {
|
||||
if err := m.Nodes().Set(m.hostname, n); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to set local node: %v", err), "node", n)
|
||||
m.errorCounter.WithLabelValues("checkin").Inc()
|
||||
return
|
||||
@ -380,7 +519,7 @@ func (m *Mesh) handleLocal(n *Node) {
|
||||
}
|
||||
if !nodesAreEqual(n, local) {
|
||||
level.Debug(m.logger).Log("msg", "local node differs from backend")
|
||||
if err := m.Set(m.hostname, local); err != nil {
|
||||
if err := m.Nodes().Set(m.hostname, local); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to set local node: %v", err), "node", local)
|
||||
m.errorCounter.WithLabelValues("local").Inc()
|
||||
return
|
||||
@ -406,31 +545,42 @@ func (m *Mesh) applyTopology() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
// Ensure all unready nodes are removed.
|
||||
var ready float64
|
||||
for n := range m.nodes {
|
||||
if !m.nodes[n].Ready() {
|
||||
delete(m.nodes, n)
|
||||
var readyNodes float64
|
||||
for k := range m.nodes {
|
||||
if !m.nodes[k].Ready() {
|
||||
delete(m.nodes, k)
|
||||
continue
|
||||
}
|
||||
ready++
|
||||
readyNodes++
|
||||
}
|
||||
m.nodesGuage.Set(ready)
|
||||
// Ensure all unready peers are removed.
|
||||
var readyPeers float64
|
||||
for k := range m.peers {
|
||||
if !m.peers[k].Ready() {
|
||||
delete(m.peers, k)
|
||||
continue
|
||||
}
|
||||
readyPeers++
|
||||
}
|
||||
m.nodesGuage.Set(readyNodes)
|
||||
m.peersGuage.Set(readyPeers)
|
||||
// We cannot do anything with the topology until the local node is available.
|
||||
if m.nodes[m.hostname] == nil {
|
||||
return
|
||||
}
|
||||
t, err := NewTopology(m.nodes, m.granularity, m.hostname, m.port, m.priv, m.subnet)
|
||||
t, err := NewTopology(m.nodes, m.peers, m.granularity, m.hostname, m.port, m.priv, m.subnet)
|
||||
if err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
conf, err := t.Conf()
|
||||
conf := t.Conf()
|
||||
buf, err := conf.Bytes()
|
||||
if err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
}
|
||||
if err := ioutil.WriteFile(ConfPath, conf, 0600); err != nil {
|
||||
if err := ioutil.WriteFile(ConfPath, buf, 0600); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
@ -443,6 +593,9 @@ func (m *Mesh) applyTopology() {
|
||||
}
|
||||
rules := iptables.MasqueradeRules(private, m.nodes[m.hostname].Subnet, t.RemoteSubnets())
|
||||
rules = append(rules, iptables.ForwardRules(m.subnet)...)
|
||||
for _, p := range m.peers {
|
||||
rules = append(rules, iptables.ForwardRules(p.AllowedIPs...)...)
|
||||
}
|
||||
if err := m.ipTables.Set(rules); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
@ -450,8 +603,8 @@ func (m *Mesh) applyTopology() {
|
||||
}
|
||||
if m.encapsulate != NeverEncapsulate {
|
||||
var peers []net.IP
|
||||
for _, s := range t.Segments {
|
||||
if s.Location == m.nodes[m.hostname].Location {
|
||||
for _, s := range t.segments {
|
||||
if s.location == m.nodes[m.hostname].Location {
|
||||
peers = s.privateIPs
|
||||
break
|
||||
}
|
||||
@ -461,6 +614,8 @@ func (m *Mesh) applyTopology() {
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
return
|
||||
}
|
||||
// If we are handling local routes, ensure the local
|
||||
// tunnel has an IP address.
|
||||
if m.local {
|
||||
if err := iproute.SetAddress(m.tunlIface, oneAddressCIDR(newAllocator(*m.nodes[m.hostname].Subnet).next().IP)); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
@ -489,14 +644,9 @@ func (m *Mesh) applyTopology() {
|
||||
}
|
||||
// Setting the WireGuard configuration interrupts existing connections
|
||||
// so only set the configuration if it has changed.
|
||||
equal, err := wireguard.CompareConf(conf, oldConf)
|
||||
if err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
// Don't return here, simply overwrite the old configuration.
|
||||
equal = false
|
||||
}
|
||||
equal := conf.Equal(wireguard.Parse(oldConf))
|
||||
if !equal {
|
||||
level.Info(m.logger).Log("msg", "WireGuard configurations are different")
|
||||
if err := wireguard.SetConf(link.Attrs().Name, ConfPath); err != nil {
|
||||
level.Error(m.logger).Log("error", err)
|
||||
m.errorCounter.WithLabelValues("apply").Inc()
|
||||
@ -531,6 +681,7 @@ func (m *Mesh) RegisterMetrics(r prometheus.Registerer) {
|
||||
r.MustRegister(
|
||||
m.errorCounter,
|
||||
m.nodesGuage,
|
||||
m.peersGuage,
|
||||
m.reconcileCounter,
|
||||
)
|
||||
}
|
||||
@ -558,11 +709,15 @@ func (m *Mesh) cleanUp() {
|
||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||
}
|
||||
if err := iproute.RemoveInterface(m.kiloIface); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to remove wireguard interface: %v", err))
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to remove WireGuard interface: %v", err))
|
||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||
}
|
||||
if err := m.CleanUp(m.hostname); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up backend: %v", err))
|
||||
if err := m.Nodes().CleanUp(m.hostname); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up node backend: %v", err))
|
||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||
}
|
||||
if err := m.Peers().CleanUp(m.hostname); err != nil {
|
||||
level.Error(m.logger).Log("error", fmt.Sprintf("failed to clean up peer backend: %v", err))
|
||||
m.errorCounter.WithLabelValues("cleanUp").Inc()
|
||||
}
|
||||
if err := m.ipset.CleanUp(); err != nil {
|
||||
@ -586,6 +741,32 @@ func nodesAreEqual(a, b *Node) bool {
|
||||
return ipNetsEqual(a.ExternalIP, b.ExternalIP) && string(a.Key) == string(b.Key) && ipNetsEqual(a.InternalIP, b.InternalIP) && a.Leader == b.Leader && a.Location == b.Location && a.Name == b.Name && subnetsEqual(a.Subnet, b.Subnet)
|
||||
}
|
||||
|
||||
func peersAreEqual(a, b *Peer) bool {
|
||||
if !(a != nil) == (b != nil) {
|
||||
return false
|
||||
}
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
if !(a.Endpoint != nil) == (b.Endpoint != nil) {
|
||||
return false
|
||||
}
|
||||
if a.Endpoint != nil {
|
||||
if !a.Endpoint.IP.Equal(b.Endpoint.IP) || a.Endpoint.Port != b.Endpoint.Port {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(a.AllowedIPs) != len(b.AllowedIPs) {
|
||||
return false
|
||||
}
|
||||
for i := range a.AllowedIPs {
|
||||
if !ipNetsEqual(a.AllowedIPs[i], b.AllowedIPs[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return string(a.PublicKey) == string(b.PublicKey) && a.PersistentKeepalive == b.PersistentKeepalive
|
||||
}
|
||||
|
||||
func ipNetsEqual(a, b *net.IPNet) bool {
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
|
@ -15,41 +15,24 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/squat/kilo/pkg/wireguard"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
confTemplate = template.Must(template.New("").Parse(`[Interface]
|
||||
PrivateKey = {{.Key}}
|
||||
ListenPort = {{.Port}}
|
||||
{{range .Segments -}}
|
||||
{{if ne .Location $.Location}}
|
||||
[Peer]
|
||||
PublicKey = {{.Key}}
|
||||
Endpoint = {{.Endpoint}}:{{$.Port}}
|
||||
AllowedIPs = {{.AllowedIPs}}
|
||||
{{end}}
|
||||
{{- end -}}
|
||||
`))
|
||||
)
|
||||
|
||||
// Topology represents the logical structure of the overlay network.
|
||||
type Topology struct {
|
||||
// Some fields need to be exported so that the template can read them.
|
||||
Key string
|
||||
Port int
|
||||
// key is the private key of the node creating the topology.
|
||||
key []byte
|
||||
port uint32
|
||||
// Location is the logical location of the local host.
|
||||
Location string
|
||||
Segments []*segment
|
||||
location string
|
||||
segments []*segment
|
||||
peers []*Peer
|
||||
|
||||
// hostname is the hostname of the local host.
|
||||
hostname string
|
||||
@ -69,11 +52,11 @@ type Topology struct {
|
||||
|
||||
type segment struct {
|
||||
// Some fields need to be exported so that the template can read them.
|
||||
AllowedIPs string
|
||||
Endpoint string
|
||||
Key string
|
||||
allowedIPs []*net.IPNet
|
||||
endpoint net.IP
|
||||
key []byte
|
||||
// Location is the logical location of this segment.
|
||||
Location string
|
||||
location string
|
||||
|
||||
// cidrs is a slice of subnets of all peers in the segment.
|
||||
cidrs []*net.IPNet
|
||||
@ -88,8 +71,8 @@ type segment struct {
|
||||
wireGuardIP net.IP
|
||||
}
|
||||
|
||||
// NewTopology creates a new Topology struct from a given set of nodes.
|
||||
func NewTopology(nodes map[string]*Node, granularity Granularity, hostname string, port int, key []byte, subnet *net.IPNet) (*Topology, error) {
|
||||
// NewTopology creates a new Topology struct from a given set of nodes and peers.
|
||||
func NewTopology(nodes map[string]*Node, peers map[string]*Peer, granularity Granularity, hostname string, port uint32, key []byte, subnet *net.IPNet) (*Topology, error) {
|
||||
topoMap := make(map[string][]*Node)
|
||||
for _, node := range nodes {
|
||||
var location string
|
||||
@ -109,7 +92,7 @@ func NewTopology(nodes map[string]*Node, granularity Granularity, hostname strin
|
||||
localLocation = hostname
|
||||
}
|
||||
|
||||
t := Topology{Key: strings.TrimSpace(string(key)), Port: port, hostname: hostname, Location: localLocation, subnet: subnet, privateIP: nodes[hostname].InternalIP}
|
||||
t := Topology{key: key, port: port, hostname: hostname, location: localLocation, subnet: subnet, privateIP: nodes[hostname].InternalIP}
|
||||
for location := range topoMap {
|
||||
// Sort the location so the result is stable.
|
||||
sort.Slice(topoMap[location], func(i, j int) bool {
|
||||
@ -119,7 +102,7 @@ func NewTopology(nodes map[string]*Node, granularity Granularity, hostname strin
|
||||
if location == localLocation && topoMap[location][leader].Name == hostname {
|
||||
t.leader = true
|
||||
}
|
||||
var allowedIPs []string
|
||||
var allowedIPs []*net.IPNet
|
||||
var cidrs []*net.IPNet
|
||||
var hostnames []string
|
||||
var privateIPs []net.IP
|
||||
@ -128,37 +111,45 @@ func NewTopology(nodes map[string]*Node, granularity Granularity, hostname strin
|
||||
// - the node's allocated subnet
|
||||
// - the node's WireGuard IP
|
||||
// - the node's internal IP
|
||||
allowedIPs = append(allowedIPs, node.Subnet.String(), oneAddressCIDR(node.InternalIP.IP).String())
|
||||
allowedIPs = append(allowedIPs, node.Subnet, oneAddressCIDR(node.InternalIP.IP))
|
||||
cidrs = append(cidrs, node.Subnet)
|
||||
hostnames = append(hostnames, node.Name)
|
||||
privateIPs = append(privateIPs, node.InternalIP.IP)
|
||||
}
|
||||
t.Segments = append(t.Segments, &segment{
|
||||
AllowedIPs: strings.Join(allowedIPs, ", "),
|
||||
Endpoint: topoMap[location][leader].ExternalIP.IP.String(),
|
||||
Key: strings.TrimSpace(string(topoMap[location][leader].Key)),
|
||||
Location: location,
|
||||
t.segments = append(t.segments, &segment{
|
||||
allowedIPs: allowedIPs,
|
||||
endpoint: topoMap[location][leader].ExternalIP.IP,
|
||||
key: topoMap[location][leader].Key,
|
||||
location: location,
|
||||
cidrs: cidrs,
|
||||
hostnames: hostnames,
|
||||
leader: leader,
|
||||
privateIPs: privateIPs,
|
||||
})
|
||||
}
|
||||
// Sort the Topology so the result is stable.
|
||||
sort.Slice(t.Segments, func(i, j int) bool {
|
||||
return t.Segments[i].Location < t.Segments[j].Location
|
||||
// Sort the Topology segments so the result is stable.
|
||||
sort.Slice(t.segments, func(i, j int) bool {
|
||||
return t.segments[i].location < t.segments[j].location
|
||||
})
|
||||
|
||||
for _, peer := range peers {
|
||||
t.peers = append(t.peers, peer)
|
||||
}
|
||||
// Sort the Topology peers so the result is stable.
|
||||
sort.Slice(t.peers, func(i, j int) bool {
|
||||
return t.peers[i].Name < t.peers[j].Name
|
||||
})
|
||||
|
||||
// Allocate IPs to the segment leaders in a stable, coordination-free manner.
|
||||
a := newAllocator(*subnet)
|
||||
for _, segment := range t.Segments {
|
||||
for _, segment := range t.segments {
|
||||
ipNet := a.next()
|
||||
if ipNet == nil {
|
||||
return nil, errors.New("failed to allocate an IP address; ran out of IP addresses")
|
||||
}
|
||||
segment.wireGuardIP = ipNet.IP
|
||||
segment.AllowedIPs = fmt.Sprintf("%s, %s", segment.AllowedIPs, ipNet.String())
|
||||
if t.leader && segment.Location == t.Location {
|
||||
segment.allowedIPs = append(segment.allowedIPs, ipNet)
|
||||
if t.leader && segment.location == t.location {
|
||||
t.wireGuardCIDR = &net.IPNet{IP: ipNet.IP, Mask: t.subnet.Mask}
|
||||
}
|
||||
}
|
||||
@ -169,8 +160,8 @@ func NewTopology(nodes map[string]*Node, granularity Granularity, hostname strin
|
||||
// RemoteSubnets identifies the subnets of the hosts in segments different than the host's.
|
||||
func (t *Topology) RemoteSubnets() []*net.IPNet {
|
||||
var remote []*net.IPNet
|
||||
for _, s := range t.Segments {
|
||||
if s == nil || s.Location == t.Location {
|
||||
for _, s := range t.segments {
|
||||
if s == nil || s.location == t.location {
|
||||
continue
|
||||
}
|
||||
remote = append(remote, s.cidrs...)
|
||||
@ -184,13 +175,13 @@ func (t *Topology) Routes(kiloIface, privIface, tunlIface int, local bool, encap
|
||||
if !t.leader {
|
||||
// Find the leader for this segment.
|
||||
var leader net.IP
|
||||
for _, segment := range t.Segments {
|
||||
if segment.Location == t.Location {
|
||||
for _, segment := range t.segments {
|
||||
if segment.location == t.location {
|
||||
leader = segment.privateIPs[segment.leader]
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, segment := range t.Segments {
|
||||
for _, segment := range t.segments {
|
||||
// First, add a route to the WireGuard IP of the segment.
|
||||
routes = append(routes, encapsulateRoute(&netlink.Route{
|
||||
Dst: oneAddressCIDR(segment.wireGuardIP),
|
||||
@ -200,7 +191,7 @@ func (t *Topology) Routes(kiloIface, privIface, tunlIface int, local bool, encap
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
}, encapsulate, t.privateIP, tunlIface))
|
||||
// Add routes for the current segment if local is true.
|
||||
if segment.Location == t.Location {
|
||||
if segment.location == t.location {
|
||||
if local {
|
||||
for i := range segment.cidrs {
|
||||
// Don't add routes for the local node.
|
||||
@ -239,11 +230,23 @@ func (t *Topology) Routes(kiloIface, privIface, tunlIface int, local bool, encap
|
||||
}, encapsulate, t.privateIP, tunlIface))
|
||||
}
|
||||
}
|
||||
// Add routes for the allowed IPs of peers.
|
||||
for _, peer := range t.peers {
|
||||
for i := range peer.AllowedIPs {
|
||||
routes = append(routes, encapsulateRoute(&netlink.Route{
|
||||
Dst: peer.AllowedIPs[i],
|
||||
Flags: int(netlink.FLAG_ONLINK),
|
||||
Gw: leader,
|
||||
LinkIndex: privIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
}, encapsulate, t.privateIP, tunlIface))
|
||||
}
|
||||
}
|
||||
return routes
|
||||
}
|
||||
for _, segment := range t.Segments {
|
||||
for _, segment := range t.segments {
|
||||
// Add routes for the current segment if local is true.
|
||||
if segment.Location == t.Location {
|
||||
if segment.location == t.location {
|
||||
if local {
|
||||
for i := range segment.cidrs {
|
||||
// Don't add routes for the local node.
|
||||
@ -282,6 +285,16 @@ func (t *Topology) Routes(kiloIface, privIface, tunlIface int, local bool, encap
|
||||
})
|
||||
}
|
||||
}
|
||||
// Add routes for the allowed IPs of peers.
|
||||
for _, peer := range t.peers {
|
||||
for i := range peer.AllowedIPs {
|
||||
routes = append(routes, &netlink.Route{
|
||||
Dst: peer.AllowedIPs[i],
|
||||
LinkIndex: kiloIface,
|
||||
Protocol: unix.RTPROT_STATIC,
|
||||
})
|
||||
}
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
@ -293,12 +306,66 @@ func encapsulateRoute(route *netlink.Route, encapsulate Encapsulate, subnet *net
|
||||
}
|
||||
|
||||
// Conf generates a WireGuard configuration file for a given Topology.
|
||||
func (t *Topology) Conf() ([]byte, error) {
|
||||
conf := new(bytes.Buffer)
|
||||
if err := confTemplate.Execute(conf, t); err != nil {
|
||||
return nil, err
|
||||
func (t *Topology) Conf() *wireguard.Conf {
|
||||
c := &wireguard.Conf{
|
||||
Interface: &wireguard.Interface{
|
||||
PrivateKey: t.key,
|
||||
ListenPort: t.port,
|
||||
},
|
||||
}
|
||||
return conf.Bytes(), nil
|
||||
for _, s := range t.segments {
|
||||
if s.location == t.location {
|
||||
continue
|
||||
}
|
||||
peer := &wireguard.Peer{
|
||||
AllowedIPs: s.allowedIPs,
|
||||
Endpoint: &wireguard.Endpoint{
|
||||
IP: s.endpoint,
|
||||
Port: uint32(t.port),
|
||||
},
|
||||
PublicKey: s.key,
|
||||
}
|
||||
c.Peers = append(c.Peers, peer)
|
||||
}
|
||||
for _, p := range t.peers {
|
||||
peer := &wireguard.Peer{
|
||||
AllowedIPs: p.AllowedIPs,
|
||||
PersistentKeepalive: p.PersistentKeepalive,
|
||||
PublicKey: p.PublicKey,
|
||||
Endpoint: p.Endpoint,
|
||||
}
|
||||
c.Peers = append(c.Peers, peer)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// PeerConf generates a WireGuard configuration file for a given peer in a Topology.
|
||||
func (t *Topology) PeerConf(name string) *wireguard.Conf {
|
||||
c := &wireguard.Conf{}
|
||||
for _, s := range t.segments {
|
||||
peer := &wireguard.Peer{
|
||||
AllowedIPs: s.allowedIPs,
|
||||
Endpoint: &wireguard.Endpoint{
|
||||
IP: s.endpoint,
|
||||
Port: uint32(t.port),
|
||||
},
|
||||
PublicKey: s.key,
|
||||
}
|
||||
c.Peers = append(c.Peers, peer)
|
||||
}
|
||||
for _, p := range t.peers {
|
||||
if p.Name == name {
|
||||
continue
|
||||
}
|
||||
peer := &wireguard.Peer{
|
||||
AllowedIPs: p.AllowedIPs,
|
||||
PersistentKeepalive: p.PersistentKeepalive,
|
||||
PublicKey: p.PublicKey,
|
||||
Endpoint: p.Endpoint,
|
||||
}
|
||||
c.Peers = append(c.Peers, peer)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// oneAddressCIDR takes an IP address and returns a CIDR
|
||||
|
File diff suppressed because it is too large
Load Diff
391
pkg/wireguard/conf.go
Normal file
391
pkg/wireguard/conf.go
Normal file
@ -0,0 +1,391 @@
|
||||
// Copyright 2019 the Kilo authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package wireguard
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type section string
|
||||
type key string
|
||||
|
||||
const (
|
||||
separator = "="
|
||||
interfaceSection section = "Interface"
|
||||
peerSection section = "Peer"
|
||||
listenPortKey key = "ListenPort"
|
||||
allowedIPsKey key = "AllowedIPs"
|
||||
endpointKey key = "Endpoint"
|
||||
persistentKeepaliveKey key = "PersistentKeepalive"
|
||||
privateKeyKey key = "PrivateKey"
|
||||
publicKeyKey key = "PublicKey"
|
||||
)
|
||||
|
||||
// Conf represents a WireGuard configuration file.
|
||||
type Conf struct {
|
||||
Interface *Interface
|
||||
Peers []*Peer
|
||||
}
|
||||
|
||||
// Interface represents the `interface` section of a WireGuard configuration.
|
||||
type Interface struct {
|
||||
ListenPort uint32
|
||||
PrivateKey []byte
|
||||
}
|
||||
|
||||
// Peer represents a `peer` section of a WireGuard configuration.
|
||||
type Peer struct {
|
||||
AllowedIPs []*net.IPNet
|
||||
Endpoint *Endpoint
|
||||
PersistentKeepalive int
|
||||
PublicKey []byte
|
||||
}
|
||||
|
||||
// Endpoint represents an `endpoint` key of a `peer` section.
|
||||
type Endpoint struct {
|
||||
IP net.IP
|
||||
Port uint32
|
||||
}
|
||||
|
||||
// Parse parses a given WireGuard configuration file and produces a Conf struct.
|
||||
func Parse(buf []byte) *Conf {
|
||||
var (
|
||||
active section
|
||||
ai *net.IPNet
|
||||
kv []string
|
||||
c Conf
|
||||
err error
|
||||
iface *Interface
|
||||
i int
|
||||
ip, ip4 net.IP
|
||||
k key
|
||||
line, v string
|
||||
peer *Peer
|
||||
port uint64
|
||||
)
|
||||
s := bufio.NewScanner(bytes.NewBuffer(buf))
|
||||
for s.Scan() {
|
||||
line = strings.TrimSpace(s.Text())
|
||||
// Skip comments.
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
// Line is a section title.
|
||||
if strings.HasPrefix(line, "[") {
|
||||
if peer != nil {
|
||||
c.Peers = append(c.Peers, peer)
|
||||
peer = nil
|
||||
}
|
||||
if iface != nil {
|
||||
c.Interface = iface
|
||||
iface = nil
|
||||
}
|
||||
active = section(strings.TrimSpace(strings.Trim(line, "[]")))
|
||||
switch active {
|
||||
case interfaceSection:
|
||||
iface = new(Interface)
|
||||
case peerSection:
|
||||
peer = new(Peer)
|
||||
}
|
||||
continue
|
||||
}
|
||||
kv = strings.SplitN(line, separator, 2)
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
k = key(strings.TrimSpace(kv[0]))
|
||||
v = strings.TrimSpace(kv[1])
|
||||
switch active {
|
||||
case interfaceSection:
|
||||
switch k {
|
||||
case listenPortKey:
|
||||
port, err = strconv.ParseUint(v, 10, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
iface.ListenPort = uint32(port)
|
||||
case privateKeyKey:
|
||||
iface.PrivateKey = []byte(v)
|
||||
}
|
||||
case peerSection:
|
||||
switch k {
|
||||
case allowedIPsKey:
|
||||
// Reuse string slice.
|
||||
kv = strings.Split(v, ",")
|
||||
for i = range kv {
|
||||
ip, ai, err = net.ParseCIDR(strings.TrimSpace(kv[i]))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ip4 = ip.To4(); ip4 != nil {
|
||||
ip = ip4
|
||||
} else {
|
||||
ip = ip.To16()
|
||||
}
|
||||
ai.IP = ip
|
||||
peer.AllowedIPs = append(peer.AllowedIPs, ai)
|
||||
}
|
||||
case endpointKey:
|
||||
// Reuse string slice.
|
||||
kv = strings.Split(v, ":")
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
ip = net.ParseIP(kv[0])
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
port, err = strconv.ParseUint(kv[1], 10, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ip4 = ip.To4(); ip4 != nil {
|
||||
ip = ip4
|
||||
} else {
|
||||
ip = ip.To16()
|
||||
}
|
||||
peer.Endpoint = &Endpoint{
|
||||
IP: ip,
|
||||
Port: uint32(port),
|
||||
}
|
||||
case persistentKeepaliveKey:
|
||||
i, err = strconv.Atoi(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
peer.PersistentKeepalive = i
|
||||
case publicKeyKey:
|
||||
peer.PublicKey = []byte(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
if peer != nil {
|
||||
c.Peers = append(c.Peers, peer)
|
||||
}
|
||||
if iface != nil {
|
||||
c.Interface = iface
|
||||
}
|
||||
return &c
|
||||
}
|
||||
|
||||
// Bytes renders a WireGuard configuration to bytes.
|
||||
func (c *Conf) Bytes() ([]byte, error) {
|
||||
var err error
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 512))
|
||||
if c.Interface != nil {
|
||||
if err = writeSection(buf, interfaceSection); err != nil {
|
||||
return nil, fmt.Errorf("failed to write interface: %v", err)
|
||||
}
|
||||
if err = writePKey(buf, privateKeyKey, c.Interface.PrivateKey); err != nil {
|
||||
return nil, fmt.Errorf("failed to write private key: %v", err)
|
||||
}
|
||||
if err = writeValue(buf, listenPortKey, strconv.FormatUint(uint64(c.Interface.ListenPort), 10)); err != nil {
|
||||
return nil, fmt.Errorf("failed to write listen port: %v", err)
|
||||
}
|
||||
}
|
||||
for i, p := range c.Peers {
|
||||
// Add newlines to make the formatting nicer.
|
||||
if i == 0 && c.Interface != nil || i != 0 {
|
||||
if err = buf.WriteByte('\n'); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = writeSection(buf, peerSection); err != nil {
|
||||
return nil, fmt.Errorf("failed to write interface: %v", err)
|
||||
}
|
||||
if err = writeAllowedIPs(buf, p.AllowedIPs); err != nil {
|
||||
return nil, fmt.Errorf("failed to write allowed IPs: %v", err)
|
||||
}
|
||||
if err = writeEndpoint(buf, p.Endpoint); err != nil {
|
||||
return nil, fmt.Errorf("failed to write endpoint: %v", err)
|
||||
}
|
||||
if err = writeValue(buf, persistentKeepaliveKey, strconv.Itoa(p.PersistentKeepalive)); err != nil {
|
||||
return nil, fmt.Errorf("failed to write persistent keepalive: %v", err)
|
||||
}
|
||||
if err = writePKey(buf, publicKeyKey, p.PublicKey); err != nil {
|
||||
return nil, fmt.Errorf("failed to write public key: %v", err)
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Equal checks if two WireGuare configurations are equivalent.
|
||||
func (c *Conf) Equal(b *Conf) bool {
|
||||
if (c.Interface == nil) != (b.Interface == nil) {
|
||||
return false
|
||||
}
|
||||
if c.Interface != nil {
|
||||
if c.Interface.ListenPort != b.Interface.ListenPort || !bytes.Equal(c.Interface.PrivateKey, b.Interface.PrivateKey) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(c.Peers) != len(b.Peers) {
|
||||
return false
|
||||
}
|
||||
sortPeers(c.Peers)
|
||||
sortPeers(b.Peers)
|
||||
for i := range c.Peers {
|
||||
if len(c.Peers[i].AllowedIPs) != len(b.Peers[i].AllowedIPs) {
|
||||
return false
|
||||
}
|
||||
sortCIDRs(c.Peers[i].AllowedIPs)
|
||||
sortCIDRs(b.Peers[i].AllowedIPs)
|
||||
for j := range c.Peers[i].AllowedIPs {
|
||||
if c.Peers[i].AllowedIPs[j].String() != b.Peers[i].AllowedIPs[j].String() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (c.Peers[i].Endpoint == nil) != (b.Peers[i].Endpoint == nil) {
|
||||
return false
|
||||
}
|
||||
if c.Peers[i].Endpoint != nil {
|
||||
if !c.Peers[i].Endpoint.IP.Equal(b.Peers[i].Endpoint.IP) || c.Peers[i].Endpoint.Port != b.Peers[i].Endpoint.Port {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if c.Peers[i].PersistentKeepalive != b.Peers[i].PersistentKeepalive || !bytes.Equal(c.Peers[i].PublicKey, b.Peers[i].PublicKey) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func sortPeers(peers []*Peer) {
|
||||
sort.Slice(peers, func(i, j int) bool {
|
||||
if bytes.Compare(peers[i].PublicKey, peers[j].PublicKey) < 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func sortCIDRs(cidrs []*net.IPNet) {
|
||||
sort.Slice(cidrs, func(i, j int) bool {
|
||||
return cidrs[i].String() < cidrs[j].String()
|
||||
})
|
||||
}
|
||||
|
||||
func writeAllowedIPs(buf *bytes.Buffer, ais []*net.IPNet) error {
|
||||
if len(ais) == 0 {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
if err = writeKey(buf, allowedIPsKey); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range ais {
|
||||
if i != 0 {
|
||||
if _, err = buf.WriteString(", "); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err = buf.WriteString(ais[i].String()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = buf.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writePKey(buf *bytes.Buffer, k key, b []byte) error {
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
if err = writeKey(buf, k); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = buf.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = buf.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeValue(buf *bytes.Buffer, k key, v string) error {
|
||||
var err error
|
||||
if err = writeKey(buf, k); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = buf.WriteString(v); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = buf.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeEndpoint(buf *bytes.Buffer, e *Endpoint) error {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
if err = writeKey(buf, endpointKey); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = buf.WriteString(e.IP.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = buf.WriteByte(':'); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = buf.WriteString(strconv.FormatUint(uint64(e.Port), 10)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = buf.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeSection(buf *bytes.Buffer, s section) error {
|
||||
var err error
|
||||
if err = buf.WriteByte('['); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = buf.WriteString(string(s)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = buf.WriteByte(']'); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = buf.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeKey(buf *bytes.Buffer, k key) error {
|
||||
var err error
|
||||
if _, err = buf.WriteString(string(k)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = buf.WriteString(" = "); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -19,11 +19,9 @@ import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
type wgLink struct {
|
||||
@ -84,7 +82,8 @@ func Keys() ([]byte, []byte, error) {
|
||||
|
||||
// GenKey generates a WireGuard private key.
|
||||
func GenKey() ([]byte, error) {
|
||||
return exec.Command("wg", "genkey").Output()
|
||||
key, err := exec.Command("wg", "genkey").Output()
|
||||
return bytes.Trim(key, "\n"), err
|
||||
}
|
||||
|
||||
// PubKey generates a WireGuard public key for a given private key.
|
||||
@ -104,7 +103,7 @@ func PubKey(key []byte) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate public key: %v", err)
|
||||
}
|
||||
return public, nil
|
||||
return bytes.Trim(public, "\n"), nil
|
||||
}
|
||||
|
||||
// SetConf applies a WireGuard configuration file to the given interface.
|
||||
@ -129,55 +128,3 @@ func ShowConf(iface string) ([]byte, error) {
|
||||
}
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
// CompareConf compares two WireGuard configurations.
|
||||
// It returns true if they are equal, false if they are not,
|
||||
// and any error that was encountered.
|
||||
// Note: CompareConf only goes one level deep, as WireGuard
|
||||
// configurations are not nested further than that.
|
||||
func CompareConf(a, b []byte) (bool, error) {
|
||||
iniA, err := ini.Load(a)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse configuration: %v", err)
|
||||
}
|
||||
iniB, err := ini.Load(b)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse configuration: %v", err)
|
||||
}
|
||||
secsA, secsB := iniA.SectionStrings(), iniB.SectionStrings()
|
||||
if len(secsA) != len(secsB) {
|
||||
return false, nil
|
||||
}
|
||||
sort.Strings(secsA)
|
||||
sort.Strings(secsB)
|
||||
var keysA, keysB []string
|
||||
var valsA, valsB []string
|
||||
for i := range secsA {
|
||||
if secsA[i] != secsB[i] {
|
||||
return false, nil
|
||||
}
|
||||
keysA, keysB = iniA.Section(secsA[i]).KeyStrings(), iniB.Section(secsB[i]).KeyStrings()
|
||||
if len(keysA) != len(keysB) {
|
||||
return false, nil
|
||||
}
|
||||
sort.Strings(keysA)
|
||||
sort.Strings(keysB)
|
||||
for j := range keysA {
|
||||
if keysA[j] != keysB[j] {
|
||||
return false, nil
|
||||
}
|
||||
valsA, valsB = iniA.Section(secsA[i]).Key(keysA[j]).Strings(","), iniB.Section(secsB[i]).Key(keysB[j]).Strings(",")
|
||||
if len(valsA) != len(valsB) {
|
||||
return false, nil
|
||||
}
|
||||
sort.Strings(valsA)
|
||||
sort.Strings(valsB)
|
||||
for k := range valsA {
|
||||
if valsA[k] != valsB[k] {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
@ -34,108 +34,137 @@ func TestCompareConf(t *testing.T) {
|
||||
{
|
||||
name: "key and value order",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(`[Interface]
|
||||
ListenPort = 51820
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
PrivateKey = private
|
||||
|
||||
[Peer]
|
||||
PublicKey = key
|
||||
AllowedIPs = 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32, 10.2.2.0/24
|
||||
Endpoint = 10.1.0.2:51820
|
||||
`),
|
||||
[Peer]
|
||||
PublicKey = key
|
||||
AllowedIPs = 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32, 10.2.2.0/24
|
||||
Endpoint = 10.1.0.2:51820
|
||||
`),
|
||||
out: true,
|
||||
},
|
||||
{
|
||||
name: "whitespace",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(`[Interface]
|
||||
PrivateKey=private
|
||||
ListenPort=51820
|
||||
[Peer]
|
||||
Endpoint=10.1.0.2:51820
|
||||
PublicKey=key
|
||||
AllowedIPs=10.2.2.0/24,192.168.0.1/32,10.2.3.0/24,192.168.0.2/32,10.4.0.2/32
|
||||
`),
|
||||
PrivateKey=private
|
||||
ListenPort=51820
|
||||
[Peer]
|
||||
Endpoint=10.1.0.2:51820
|
||||
PublicKey=key
|
||||
AllowedIPs=10.2.2.0/24,192.168.0.1/32,10.2.3.0/24,192.168.0.2/32,10.4.0.2/32
|
||||
`),
|
||||
out: true,
|
||||
},
|
||||
{
|
||||
name: "missing key",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
[Peer]
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
out: false,
|
||||
},
|
||||
{
|
||||
name: "section order",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(`[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
|
||||
[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
`),
|
||||
[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
`),
|
||||
out: true,
|
||||
},
|
||||
{
|
||||
name: "out of order peers",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key2
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key1
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key1
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key2
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
out: true,
|
||||
},
|
||||
{
|
||||
name: "one empty",
|
||||
a: []byte(`[Interface]
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
PrivateKey = private
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
[Peer]
|
||||
Endpoint = 10.1.0.2:51820
|
||||
PublicKey = key
|
||||
AllowedIPs = 10.2.2.0/24, 192.168.0.1/32, 10.2.3.0/24, 192.168.0.2/32, 10.4.0.2/32
|
||||
`),
|
||||
b: []byte(``),
|
||||
out: false,
|
||||
},
|
||||
} {
|
||||
equal, err := CompareConf(tc.a, tc.b)
|
||||
if err != nil {
|
||||
t.Errorf("test case %q: got unexpected error: %v", tc.name, err)
|
||||
}
|
||||
equal := Parse(tc.a).Equal(Parse(tc.b))
|
||||
if equal != tc.out {
|
||||
t.Errorf("test case %q: expected %t, got %t", tc.name, tc.out, equal)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user