├── scripts ├── header.go.txt ├── refresh-mod ├── generate-grpc ├── format ├── _env ├── publish-container-images ├── deploy ├── build ├── _trap ├── delete ├── build-container-images ├── generate-apis └── _functions ├── .gitignore ├── provider ├── common.go ├── client.go └── provider.pb.go ├── NOTICE ├── resources └── knap.github.com │ ├── common.go │ └── v1alpha1 │ ├── doc.go │ ├── scheme.go │ ├── zz_generated.deepcopy.go │ └── network.go ├── apis ├── clientset │ └── versioned │ │ ├── fake │ │ ├── doc.go │ │ ├── register.go │ │ └── clientset_generated.go │ │ ├── scheme │ │ ├── doc.go │ │ └── register.go │ │ ├── typed │ │ └── knap.github.com │ │ │ └── v1alpha1 │ │ │ ├── generated_expansion.go │ │ │ ├── fake │ │ │ ├── doc.go │ │ │ ├── fake_knap.github.com_client.go │ │ │ └── fake_network.go │ │ │ ├── doc.go │ │ │ ├── knap.github.com_client.go │ │ │ └── network.go │ │ └── clientset.go ├── listers │ └── knap.github.com │ │ └── v1alpha1 │ │ ├── expansion_generated.go │ │ └── network.go ├── informers │ └── externalversions │ │ ├── internalinterfaces │ │ └── factory_interfaces.go │ │ ├── knap.github.com │ │ ├── v1alpha1 │ │ │ ├── interface.go │ │ │ └── network.go │ │ └── interface.go │ │ ├── generic.go │ │ └── factory.go └── applyconfiguration │ ├── utils.go │ ├── internal │ └── internal.go │ └── knap.github.com │ └── v1alpha1 │ ├── networkspec.go │ ├── networkstatus.go │ └── network.go ├── knap ├── commands │ ├── common.go │ ├── version.go │ ├── uninstall.go │ ├── install.go │ ├── logs.go │ ├── client.go │ └── root.go └── main.go ├── knap-operator ├── common.go ├── main.go ├── controller.go └── command.go ├── controller ├── common.go ├── daemon-set.go ├── deployment.go ├── stateful-set.go ├── network.go ├── cni.go ├── annotations.go └── controller.go ├── knap-provider-bridge ├── common.go ├── server │ ├── cni-config.go │ ├── server.go │ └── state.go └── main.go ├── assets ├── grpc │ └── provider.proto └── kubernetes │ ├── custom-resource-definition.yaml │ └── knap.yaml ├── client ├── logs.go ├── client.go ├── network.go ├── common.go ├── network-attachment-definition.go ├── wait.go └── operator.go ├── examples └── hello-world │ ├── implicit.yaml │ └── explicit.yaml ├── README.md ├── go.mod └── LICENSE /scripts/header.go.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | go.work 3 | go.work.sum 4 | 5 | dist/ 6 | -------------------------------------------------------------------------------- /provider/common.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | //go:generate protoc -I ../assets/grpc --go_out=plugins=grpc:. ../assets/grpc/provider.proto 4 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Knap 2 | Copyright 2020-2023 Tal Liron 3 | 4 | --- 5 | 6 | "Kubernetes" and "K8s" are registered trademarks of The Linux Foundation. 7 | -------------------------------------------------------------------------------- /resources/knap.github.com/common.go: -------------------------------------------------------------------------------- 1 | package knap 2 | 3 | // Kubernetes API group name (must have a ".") 4 | const GroupName = "knap.github.com" 5 | -------------------------------------------------------------------------------- /apis/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package has the automatically generated fake clientset. 4 | package fake 5 | -------------------------------------------------------------------------------- /apis/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package contains the scheme of the automatically generated clientset. 4 | package scheme 5 | -------------------------------------------------------------------------------- /apis/clientset/versioned/typed/knap.github.com/v1alpha1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | type NetworkExpansion interface{} 6 | -------------------------------------------------------------------------------- /knap/commands/common.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/tliron/commonlog" 5 | ) 6 | 7 | const toolName = "knap" 8 | 9 | var log = commonlog.GetLogger(toolName) 10 | -------------------------------------------------------------------------------- /apis/clientset/versioned/typed/knap.github.com/v1alpha1/fake/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // Package fake has the automatically generated clients. 4 | package fake 5 | -------------------------------------------------------------------------------- /knap-operator/common.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tliron/commonlog" 5 | ) 6 | 7 | const toolName = "knap-operator" 8 | 9 | var log = commonlog.GetLogger(toolName) 10 | -------------------------------------------------------------------------------- /apis/clientset/versioned/typed/knap.github.com/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | // This package has the automatically generated typed clients. 4 | package v1alpha1 5 | -------------------------------------------------------------------------------- /knap/commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/tliron/kutil/cobra" 5 | ) 6 | 7 | func init() { 8 | rootCommand.AddCommand(cobra.NewVersionCommand(toolName)) 9 | } 10 | -------------------------------------------------------------------------------- /controller/common.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | const ( 4 | NamePrefix = "knap" 5 | PartOf = "Knap" 6 | ManagedBy = "Knap" 7 | OperatorImageName = "tliron/knap-operator" 8 | ) 9 | -------------------------------------------------------------------------------- /knap-provider-bridge/common.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tliron/commonlog" 5 | ) 6 | 7 | const toolName = "knap-provider-bridge" 8 | 9 | var log = commonlog.GetLogger(toolName) 10 | -------------------------------------------------------------------------------- /resources/knap.github.com/v1alpha1/doc.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | // Note: Generator *requires* file to be called "doc.go" 4 | 5 | // +k8s:deepcopy-gen=package 6 | // +groupName=knap.github.com 7 | const Version = "v1alpha1" 8 | -------------------------------------------------------------------------------- /knap-operator/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tliron/kutil/util" 5 | 6 | _ "github.com/tliron/commonlog/simple" 7 | ) 8 | 9 | func main() { 10 | err := command.Execute() 11 | util.FailOnError(err) 12 | util.Exit(0) 13 | } 14 | -------------------------------------------------------------------------------- /knap/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/tliron/knap/knap/commands" 5 | "github.com/tliron/kutil/util" 6 | 7 | _ "github.com/tliron/commonlog/simple" 8 | ) 9 | 10 | func main() { 11 | commands.Execute() 12 | util.Exit(0) 13 | } 14 | -------------------------------------------------------------------------------- /scripts/refresh-mod: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | . "$HERE/_trap" 7 | 8 | rm --force "$ROOT/go.mod" "$ROOT/go.sum" 9 | 10 | cd "$ROOT" 11 | go mod init "$MODULE" 12 | go get k8s.io/client-go@v$K8S_VERSION 13 | go mod tidy 14 | 15 | #"$HERE/test" 16 | 17 | go mod tidy 18 | -------------------------------------------------------------------------------- /scripts/generate-grpc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # sudo dnf install protobuf-compiler 5 | 6 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 7 | . "$HERE/_env" 8 | . "$HERE/_trap" 9 | 10 | function generate () { 11 | local TOOL=$1 12 | pushd "$ROOT/$TOOL" > /dev/null 13 | go generate 14 | popd > /dev/null 15 | } 16 | 17 | generate provider 18 | -------------------------------------------------------------------------------- /scripts/format: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | 7 | gofmt -w -s -e \ 8 | "$ROOT/client" \ 9 | "$ROOT/controller" \ 10 | "$ROOT/knap" \ 11 | "$ROOT/knap-operator" \ 12 | "$ROOT/knap-provider-bridge" \ 13 | "$ROOT/resources" \ 14 | "$ROOT/resources/knap.github.com" \ 15 | "$ROOT/resources/knap.github.com/v1alpha1" 16 | -------------------------------------------------------------------------------- /assets/grpc/provider.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = ".;provider"; 4 | 5 | package provider; 6 | 7 | service Provider { 8 | rpc CreateCniConfig (CreateCniConfigRequest) returns (CreateCniConfigReply) {} 9 | } 10 | 11 | message CreateCniConfigRequest { 12 | string name = 1; 13 | map hints = 2; 14 | } 15 | 16 | message CreateCniConfigReply { 17 | string config = 1; 18 | } 19 | -------------------------------------------------------------------------------- /apis/listers/knap.github.com/v1alpha1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | // Code generated by lister-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | // NetworkListerExpansion allows custom methods to be added to 6 | // NetworkLister. 7 | type NetworkListerExpansion interface{} 8 | 9 | // NetworkNamespaceListerExpansion allows custom methods to be added to 10 | // NetworkNamespaceLister. 11 | type NetworkNamespaceListerExpansion interface{} 12 | -------------------------------------------------------------------------------- /scripts/_env: -------------------------------------------------------------------------------- 1 | 2 | _HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 3 | 4 | . "$_HERE/_functions" 5 | 6 | MODULE=github.com/tliron/knap 7 | 8 | K8S_VERSION=0.27.3 9 | 10 | ROOT=$(readlink --canonicalize "$_HERE/..") 11 | 12 | GOPATH=${GOPATH:-$HOME/go} 13 | export PATH=$GOPATH/bin:$PATH 14 | 15 | WORKSPACE=${WORKSPACE:-workspace} 16 | 17 | if [ -d /Depot/Temporary ]; then 18 | export TMPDIR=/Depot/Temporary 19 | else 20 | export TMPDIR=/tmp 21 | fi 22 | -------------------------------------------------------------------------------- /knap/commands/uninstall.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | func init() { 8 | rootCommand.AddCommand(uninstallCommand) 9 | uninstallCommand.Flags().BoolVarP(&wait, "wait", "w", false, "wait for uninstallation to succeed") 10 | } 11 | 12 | var uninstallCommand = &cobra.Command{ 13 | Use: "uninstall", 14 | Short: "Uninstall Knap", 15 | Run: func(cmd *cobra.Command, args []string) { 16 | NewClient().Uninstall(wait) 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /scripts/publish-container-images: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | . "$HERE/_trap" 7 | 8 | m 'Login to docker.io' 9 | buildah login docker.io 10 | 11 | function push () { 12 | local EXECUTABLE=$1 13 | local IMAGE=$EXECUTABLE 14 | local LOCAL=localhost/$IMAGE 15 | local REMOTE=docker://docker.io/tliron/$IMAGE 16 | 17 | skopeo delete --tls-verify=false "$REMOTE" || true 18 | buildah push --tls-verify=false "$LOCAL" "$REMOTE" 19 | } 20 | 21 | push knap-operator 22 | push knap-provider-bridge 23 | -------------------------------------------------------------------------------- /scripts/deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | . "$HERE/_trap" 7 | 8 | export NAMESPACE=${NAMESPACE:-knap} 9 | 10 | for ARG in "$@"; do 11 | case "$ARG" in 12 | -c) 13 | NAMESPACE=$NAMESPACE "$HERE/delete" 14 | ;; 15 | -b) 16 | "$HERE/build-container-images" 17 | "$HERE/publish-container-images" 18 | ;; 19 | esac 20 | done 21 | 22 | m "deploying operator to namespace \"$NAMESPACE\"..." 23 | knap install --namespace="$NAMESPACE" --wait -v 24 | 25 | m "deploying \"implicit\" example..." 26 | kubectl apply --namespace="$NAMESPACE" -f "$ROOT/examples/hello-world/implicit.yaml" 27 | -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # sudo dnf install protobuf-compiler 5 | 6 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 7 | . "$HERE/_env" 8 | . "$HERE/_trap" 9 | 10 | git_version 11 | 12 | function build () { 13 | local TOOL=$1 14 | pushd "$ROOT/$TOOL" > /dev/null 15 | go install \ 16 | -ldflags " \ 17 | -X 'github.com/tliron/kutil/version.GitVersion=$VERSION' \ 18 | -X 'github.com/tliron/kutil/version.GitRevision=$REVISION' \ 19 | -X 'github.com/tliron/kutil/version.Timestamp=$TIMESTAMP'" 20 | popd > /dev/null 21 | m "built $GOPATH/bin/$TOOL" 22 | } 23 | 24 | build knap-operator 25 | build knap-provider-bridge 26 | build knap 27 | 28 | ln --symbolic --force "$GOPATH/bin/knap" "$GOPATH/bin/kubectl-network" 29 | -------------------------------------------------------------------------------- /apis/clientset/versioned/typed/knap.github.com/v1alpha1/fake/fake_knap.github.com_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | v1alpha1 "github.com/tliron/knap/apis/clientset/versioned/typed/knap.github.com/v1alpha1" 7 | rest "k8s.io/client-go/rest" 8 | testing "k8s.io/client-go/testing" 9 | ) 10 | 11 | type FakeKnapV1alpha1 struct { 12 | *testing.Fake 13 | } 14 | 15 | func (c *FakeKnapV1alpha1) Networks(namespace string) v1alpha1.NetworkInterface { 16 | return &FakeNetworks{c, namespace} 17 | } 18 | 19 | // RESTClient returns a RESTClient that is used to communicate 20 | // with API server by this client implementation. 21 | func (c *FakeKnapV1alpha1) RESTClient() rest.Interface { 22 | var ret *rest.RESTClient 23 | return ret 24 | } 25 | -------------------------------------------------------------------------------- /scripts/_trap: -------------------------------------------------------------------------------- 1 | 2 | function goodbye () { 3 | local DURATION=$(date --date=@$(( "$(date +%s)" - "$TRAP_START_TIME" )) --utc +%T) 4 | local CODE=$1 5 | cd "$TRAP_DIR" 6 | if [ "$CODE" == 0 ]; then 7 | m "$(realpath --relative-to="$ROOT" "$0") succeeded! $DURATION" "$GREEN" 8 | elif [ "$CODE" == abort ]; then 9 | m "Aborted $(realpath --relative-to="$ROOT" "$0")! $DURATION" "$RED" 10 | else 11 | m "Oh no! $(realpath --relative-to="$ROOT" "$0") failed! $DURATION" "$RED" 12 | fi 13 | } 14 | 15 | function trap_EXIT () { 16 | local ERR=$? 17 | goodbye "$ERR" 18 | exit "$ERR" 19 | } 20 | 21 | function trap_INT () { 22 | goodbye abort 23 | trap - EXIT 24 | exit 1 25 | } 26 | 27 | TRAP_DIR=$PWD 28 | TRAP_START_TIME=$(date +%s) 29 | 30 | trap trap_INT INT 31 | 32 | trap trap_EXIT EXIT 33 | -------------------------------------------------------------------------------- /knap-provider-bridge/server/cni-config.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func (self *Server) CreateBridgeCniConfig(name string) (string, error) { 8 | if bridgePrefix, err := self.ProvideBridgePrefix(); err == nil { 9 | return fmt.Sprintf(`{ 10 | "cniVersion": "0.3.1", 11 | "type": "bridge", 12 | "name": "%s", 13 | "bridge": "%s", 14 | "isDefaultGateway": true, 15 | "ipMasq": true, 16 | "promiscMode": true, 17 | "ipam": { 18 | "type": "host-local", 19 | "subnet": "%s.0/24", 20 | "rangeStart": "%s.2", 21 | "rangeEnd": "%s.254", 22 | "routes": [ 23 | { "dst": "0.0.0.0/0" } 24 | ], 25 | "gateway": "%s.1" 26 | } 27 | }`, name, name, bridgePrefix, bridgePrefix, bridgePrefix, bridgePrefix), nil 28 | } else { 29 | return "", err 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /knap/commands/install.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | "github.com/tliron/kutil/util" 6 | ) 7 | 8 | var cluster bool 9 | var registry string 10 | var wait bool 11 | 12 | func init() { 13 | rootCommand.AddCommand(installCommand) 14 | installCommand.Flags().BoolVarP(&cluster, "cluster", "c", false, "cluster mode") 15 | installCommand.Flags().StringVarP(®istry, "registry", "g", "docker.io", "registry URL (use special value \"internal\" to discover internally deployed registry)") 16 | installCommand.Flags().BoolVarP(&wait, "wait", "w", false, "wait for installation to succeed") 17 | } 18 | 19 | var installCommand = &cobra.Command{ 20 | Use: "install", 21 | Short: "Install Knap", 22 | Run: func(cmd *cobra.Command, args []string) { 23 | err := NewClient().Install(registry, wait) 24 | util.FailOnError(err) 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /scripts/delete: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | . "$HERE/_trap" 7 | 8 | export NAMESPACE=${NAMESPACE:-knap} 9 | 10 | m "deleting \"implicit\" example..." 11 | kubectl delete --namespace="$NAMESPACE" --wait -f "$ROOT/examples/hello-world/implicit.yaml" || true 12 | 13 | m "deleting operator from namespace \"$NAMESPACE\"..." 14 | knap uninstall --namespace="$NAMESPACE" --wait -v 15 | 16 | m "cleaning up events..." 17 | kubectl delete events --all --namespace="$NAMESPACE" 18 | 19 | m "cleaning up virtual links creating by bridge CNI..." 20 | echo "sudo ip link delete explicit-a || true && exit" | minikube ssh 21 | echo "sudo ip link delete explicit-b || true && exit" | minikube ssh 22 | echo "sudo ip link delete implicit-a || true && exit" | minikube ssh 23 | echo "sudo ip link delete implicit-b || true && exit" | minikube ssh 24 | -------------------------------------------------------------------------------- /client/logs.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/tliron/kutil/kubernetes" 8 | ) 9 | 10 | func (self *Client) Logs(appNameSuffix string, containerName string, tail int, follow bool) ([]io.ReadCloser, error) { 11 | appName := fmt.Sprintf("%s-%s", self.NamePrefix, appNameSuffix) 12 | 13 | if podNames, err := kubernetes.GetPodNames(self.Context, self.Kubernetes, self.Namespace, appName); err == nil { 14 | readers := make([]io.ReadCloser, len(podNames)) 15 | for index, podName := range podNames { 16 | if reader, err := kubernetes.Log(self.Context, self.Kubernetes, self.Namespace, podName, containerName, tail, follow); err == nil { 17 | readers[index] = reader 18 | } else { 19 | for i := 0; i < index; i++ { 20 | readers[i].Close() 21 | } 22 | return nil, err 23 | } 24 | } 25 | return readers, nil 26 | } else { 27 | return nil, err 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | netpkg "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned" 7 | "github.com/tliron/commonlog" 8 | knappkg "github.com/tliron/knap/apis/clientset/versioned" 9 | apiextensionspkg "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 10 | kubernetespkg "k8s.io/client-go/kubernetes" 11 | "k8s.io/client-go/rest" 12 | ) 13 | 14 | type Client struct { 15 | Config *rest.Config 16 | Kubernetes kubernetespkg.Interface 17 | APIExtensions apiextensionspkg.Interface 18 | Net netpkg.Interface 19 | Knap knappkg.Interface 20 | 21 | Cluster bool 22 | Namespace string 23 | NamePrefix string 24 | PartOf string 25 | ManagedBy string 26 | OperatorImageName string 27 | 28 | Context context.Context 29 | Log commonlog.Logger 30 | } 31 | -------------------------------------------------------------------------------- /knap/commands/logs.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/tliron/kutil/util" 9 | ) 10 | 11 | var tail int 12 | var follow bool 13 | 14 | func init() { 15 | rootCommand.AddCommand(logsCommand) 16 | logsCommand.Flags().IntVarP(&tail, "tail", "t", -1, "number of most recent lines to print (<0 means all lines)") 17 | logsCommand.Flags().BoolVarP(&follow, "follow", "f", false, "keep printing incoming logs") 18 | } 19 | 20 | var logsCommand = &cobra.Command{ 21 | Use: "logs", 22 | Short: "Show the logs of the Knap operator", 23 | Run: func(cmd *cobra.Command, args []string) { 24 | // TODO: what happens if we follow more than one log? 25 | readers, err := NewClient().Logs("operator", "operator", tail, follow) 26 | util.FailOnError(err) 27 | for _, reader := range readers { 28 | defer reader.Close() 29 | } 30 | for _, reader := range readers { 31 | io.Copy(os.Stdout, reader) 32 | } 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /apis/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package internalinterfaces 4 | 5 | import ( 6 | time "time" 7 | 8 | versioned "github.com/tliron/knap/apis/clientset/versioned" 9 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | runtime "k8s.io/apimachinery/pkg/runtime" 11 | cache "k8s.io/client-go/tools/cache" 12 | ) 13 | 14 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 15 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 16 | 17 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 18 | type SharedInformerFactory interface { 19 | Start(stopCh <-chan struct{}) 20 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 21 | } 22 | 23 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 24 | type TweakListOptionsFunc func(*v1.ListOptions) 25 | -------------------------------------------------------------------------------- /scripts/build-container-images: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | . "$HERE/_trap" 7 | 8 | "$HERE/build" 9 | 10 | # https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/building_running_and_managing_containers/using_red_hat_universal_base_images_standard_minimal_and_runtimes 11 | 12 | function build () { 13 | local EXECUTABLE=$1 14 | local IMAGE=$EXECUTABLE 15 | local LOCAL=localhost/$IMAGE 16 | local BASE_IMAGE=registry.access.redhat.com/ubi8/ubi 17 | # note: ubi-minimal does not have "tar" which is needed for kubectl cp 18 | 19 | local CONTAINER_ID=$(buildah from "$BASE_IMAGE") 20 | buildah copy "$CONTAINER_ID" "$GOPATH/bin/$EXECUTABLE" /usr/bin/ 21 | buildah config \ 22 | --entrypoint "/usr/bin/$EXECUTABLE" \ 23 | --author Knap \ 24 | --created-by buildah \ 25 | "$CONTAINER_ID" 26 | buildah commit "$CONTAINER_ID" "$LOCAL" 27 | } 28 | 29 | build knap-operator 30 | build knap-provider-bridge 31 | -------------------------------------------------------------------------------- /scripts/generate-apis: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | HERE=$(dirname "$(readlink --canonicalize "$BASH_SOURCE")") 5 | . "$HERE/_env" 6 | . "$HERE/_trap" 7 | 8 | go get -d k8s.io/code-generator@v$K8S_VERSION 9 | CODE_GENERATOR=$(go list -f '{{.Dir}}' -m k8s.io/code-generator@v$K8S_VERSION) 10 | 11 | chmod +x "$CODE_GENERATOR/generate-groups.sh" 12 | 13 | WORK=$(mktemp --directory) 14 | 15 | m "work directory: $WORK" 16 | 17 | copy_function goodbye old_goodbye 18 | function goodbye () { 19 | m "deleting work directory: $WORK" 20 | rm --recursive "$WORK" 21 | old_goodbye $1 22 | } 23 | 24 | GOPATH=$GOPATH \ 25 | "$CODE_GENERATOR/generate-groups.sh" \ 26 | all \ 27 | github.com/tliron/knap/apis \ 28 | github.com/tliron/knap/resources \ 29 | knap.github.com:v1alpha1 \ 30 | --go-header-file "$HERE/header.go.txt" \ 31 | --output-base "$WORK" 32 | 33 | if [ "$1" == -c ]; then 34 | rm --recursive --force "$ROOT/apis" 35 | fi 36 | 37 | cp --recursive \ 38 | "$WORK/github.com/tliron/knap/"* \ 39 | "$ROOT/" 40 | -------------------------------------------------------------------------------- /apis/applyconfiguration/utils.go: -------------------------------------------------------------------------------- 1 | // Code generated by applyconfiguration-gen. DO NOT EDIT. 2 | 3 | package applyconfiguration 4 | 5 | import ( 6 | knapgithubcomv1alpha1 "github.com/tliron/knap/apis/applyconfiguration/knap.github.com/v1alpha1" 7 | v1alpha1 "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 8 | schema "k8s.io/apimachinery/pkg/runtime/schema" 9 | ) 10 | 11 | // ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no 12 | // apply configuration type exists for the given GroupVersionKind. 13 | func ForKind(kind schema.GroupVersionKind) interface{} { 14 | switch kind { 15 | // Group=knap.github.com, Version=v1alpha1 16 | case v1alpha1.SchemeGroupVersion.WithKind("Network"): 17 | return &knapgithubcomv1alpha1.NetworkApplyConfiguration{} 18 | case v1alpha1.SchemeGroupVersion.WithKind("NetworkSpec"): 19 | return &knapgithubcomv1alpha1.NetworkSpecApplyConfiguration{} 20 | case v1alpha1.SchemeGroupVersion.WithKind("NetworkStatus"): 21 | return &knapgithubcomv1alpha1.NetworkStatusApplyConfiguration{} 22 | 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /apis/informers/externalversions/knap.github.com/v1alpha1/interface.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | internalinterfaces "github.com/tliron/knap/apis/informers/externalversions/internalinterfaces" 7 | ) 8 | 9 | // Interface provides access to all the informers in this group version. 10 | type Interface interface { 11 | // Networks returns a NetworkInformer. 12 | Networks() NetworkInformer 13 | } 14 | 15 | type version struct { 16 | factory internalinterfaces.SharedInformerFactory 17 | namespace string 18 | tweakListOptions internalinterfaces.TweakListOptionsFunc 19 | } 20 | 21 | // New returns a new Interface. 22 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 23 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 24 | } 25 | 26 | // Networks returns a NetworkInformer. 27 | func (v *version) Networks() NetworkInformer { 28 | return &networkInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 29 | } 30 | -------------------------------------------------------------------------------- /apis/applyconfiguration/internal/internal.go: -------------------------------------------------------------------------------- 1 | // Code generated by applyconfiguration-gen. DO NOT EDIT. 2 | 3 | package internal 4 | 5 | import ( 6 | "fmt" 7 | "sync" 8 | 9 | typed "sigs.k8s.io/structured-merge-diff/v4/typed" 10 | ) 11 | 12 | func Parser() *typed.Parser { 13 | parserOnce.Do(func() { 14 | var err error 15 | parser, err = typed.NewParser(schemaYAML) 16 | if err != nil { 17 | panic(fmt.Sprintf("Failed to parse schema: %v", err)) 18 | } 19 | }) 20 | return parser 21 | } 22 | 23 | var parserOnce sync.Once 24 | var parser *typed.Parser 25 | var schemaYAML = typed.YAMLObject(`types: 26 | - name: __untyped_atomic_ 27 | scalar: untyped 28 | list: 29 | elementType: 30 | namedType: __untyped_atomic_ 31 | elementRelationship: atomic 32 | map: 33 | elementType: 34 | namedType: __untyped_atomic_ 35 | elementRelationship: atomic 36 | - name: __untyped_deduced_ 37 | scalar: untyped 38 | list: 39 | elementType: 40 | namedType: __untyped_atomic_ 41 | elementRelationship: atomic 42 | map: 43 | elementType: 44 | namedType: __untyped_deduced_ 45 | elementRelationship: separable 46 | `) 47 | -------------------------------------------------------------------------------- /apis/informers/externalversions/knap.github.com/interface.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package knap 4 | 5 | import ( 6 | internalinterfaces "github.com/tliron/knap/apis/informers/externalversions/internalinterfaces" 7 | v1alpha1 "github.com/tliron/knap/apis/informers/externalversions/knap.github.com/v1alpha1" 8 | ) 9 | 10 | // Interface provides access to each of this group's versions. 11 | type Interface interface { 12 | // V1alpha1 provides access to shared informers for resources in V1alpha1. 13 | V1alpha1() v1alpha1.Interface 14 | } 15 | 16 | type group struct { 17 | factory internalinterfaces.SharedInformerFactory 18 | namespace string 19 | tweakListOptions internalinterfaces.TweakListOptionsFunc 20 | } 21 | 22 | // New returns a new Interface. 23 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 24 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 25 | } 26 | 27 | // V1alpha1 returns a new v1alpha1.Interface. 28 | func (g *group) V1alpha1() v1alpha1.Interface { 29 | return v1alpha1.New(g.factory, g.namespace, g.tweakListOptions) 30 | } 31 | -------------------------------------------------------------------------------- /client/network.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | resources "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 5 | meta "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | func (self *Client) GetNetwork(namespace string, networkName string) (*resources.Network, error) { 9 | // Default to same namespace as operator 10 | if namespace == "" { 11 | namespace = self.Namespace 12 | } 13 | 14 | if network, err := self.Knap.KnapV1alpha1().Networks(namespace).Get(self.Context, networkName, meta.GetOptions{}); err == nil { 15 | // When retrieved from cache the GVK may be empty 16 | if network.Kind == "" { 17 | network = network.DeepCopy() 18 | network.APIVersion, network.Kind = resources.NetworkGVK.ToAPIVersionAndKind() 19 | } 20 | return network, nil 21 | } else { 22 | return nil, err 23 | } 24 | } 25 | 26 | func (self *Client) ListNetworks() (*resources.NetworkList, error) { 27 | // TODO: all networks in cluster mode 28 | return self.Knap.KnapV1alpha1().Networks(self.Namespace).List(self.Context, meta.ListOptions{}) 29 | } 30 | 31 | func (self *Client) DeleteNetwork(namespace string, serviceName string) error { 32 | // Default to same namespace as operator 33 | if namespace == "" { 34 | namespace = self.Namespace 35 | } 36 | 37 | return self.Knap.KnapV1alpha1().Networks(namespace).Delete(self.Context, serviceName, meta.DeleteOptions{}) 38 | } 39 | -------------------------------------------------------------------------------- /resources/knap.github.com/v1alpha1/scheme.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | meta "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | "k8s.io/apimachinery/pkg/runtime" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | 8 | group "github.com/tliron/knap/resources/knap.github.com" 9 | ) 10 | 11 | // Group version used to register these objects 12 | // Note: Generator *requires* it to be called "SchemeGroupVersion" 13 | var SchemeGroupVersion = schema.GroupVersion{Group: group.GroupName, Version: Version} 14 | 15 | // Takes an unqualified kind and returns a group-qualified GroupKind 16 | // Note: Generator *requires* it to be called "Kind" 17 | func Kind(kind string) schema.GroupKind { 18 | return SchemeGroupVersion.WithKind(kind).GroupKind() 19 | } 20 | 21 | // Takes an unqualified resource and returns a group-qualified GroupResource 22 | // Note: Generator *requires* it to be called "Resource" 23 | func Resource(resource string) schema.GroupResource { 24 | return SchemeGroupVersion.WithResource(resource).GroupResource() 25 | } 26 | 27 | // Registers this API group and version to a scheme 28 | // Note: Generator *requires* it to be called "AddToScheme" 29 | var AddToScheme = schemeBuilder.AddToScheme 30 | 31 | var schemeBuilder = runtime.NewSchemeBuilder(func(scheme *runtime.Scheme) error { 32 | scheme.AddKnownTypes( 33 | SchemeGroupVersion, 34 | &Network{}, 35 | &NetworkList{}, 36 | ) 37 | meta.AddToGroupVersion(scheme, SchemeGroupVersion) 38 | return nil 39 | }) 40 | -------------------------------------------------------------------------------- /apis/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | knapv1alpha1 "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | runtime "k8s.io/apimachinery/pkg/runtime" 9 | schema "k8s.io/apimachinery/pkg/runtime/schema" 10 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 11 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 12 | ) 13 | 14 | var scheme = runtime.NewScheme() 15 | var codecs = serializer.NewCodecFactory(scheme) 16 | 17 | var localSchemeBuilder = runtime.SchemeBuilder{ 18 | knapv1alpha1.AddToScheme, 19 | } 20 | 21 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 22 | // of clientsets, like in: 23 | // 24 | // import ( 25 | // "k8s.io/client-go/kubernetes" 26 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 27 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 28 | // ) 29 | // 30 | // kclientset, _ := kubernetes.NewForConfig(c) 31 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 32 | // 33 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 34 | // correctly. 35 | var AddToScheme = localSchemeBuilder.AddToScheme 36 | 37 | func init() { 38 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 39 | utilruntime.Must(AddToScheme(scheme)) 40 | } 41 | -------------------------------------------------------------------------------- /apis/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package scheme 4 | 5 | import ( 6 | knapv1alpha1 "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 7 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | runtime "k8s.io/apimachinery/pkg/runtime" 9 | schema "k8s.io/apimachinery/pkg/runtime/schema" 10 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 11 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 12 | ) 13 | 14 | var Scheme = runtime.NewScheme() 15 | var Codecs = serializer.NewCodecFactory(Scheme) 16 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 17 | var localSchemeBuilder = runtime.SchemeBuilder{ 18 | knapv1alpha1.AddToScheme, 19 | } 20 | 21 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 22 | // of clientsets, like in: 23 | // 24 | // import ( 25 | // "k8s.io/client-go/kubernetes" 26 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 27 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 28 | // ) 29 | // 30 | // kclientset, _ := kubernetes.NewForConfig(c) 31 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 32 | // 33 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 34 | // correctly. 35 | var AddToScheme = localSchemeBuilder.AddToScheme 36 | 37 | func init() { 38 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 39 | utilruntime.Must(AddToScheme(Scheme)) 40 | } 41 | -------------------------------------------------------------------------------- /assets/kubernetes/custom-resource-definition.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | 4 | metadata: 5 | name: networks.knap.github.com 6 | 7 | spec: 8 | group: knap.github.com 9 | names: 10 | singular: network 11 | plural: networks 12 | kind: Network 13 | listKind: NetworkList 14 | shortNames: 15 | - nw 16 | categories: 17 | - all # will appear in "kubectl get all" 18 | scope: Namespaced 19 | versions: 20 | - name: v1alpha1 21 | served: true 22 | storage: true # one and only one version must be marked with storage=true 23 | subresources: # requires CustomResourceSubresources feature gate enabled 24 | status: {} 25 | schema: 26 | openAPIV3Schema: 27 | type: object 28 | required: [ spec ] 29 | properties: 30 | spec: 31 | type: object 32 | required: [ provider ] 33 | properties: 34 | provider: 35 | type: string 36 | hints: 37 | type: object 38 | nullable: true 39 | additionalProperties: 40 | type: string 41 | status: 42 | type: object 43 | properties: 44 | networkAttachmentDefinitions: 45 | type: array 46 | nullable: true 47 | items: 48 | type: string 49 | deployments: 50 | type: array 51 | nullable: true 52 | items: 53 | type: string 54 | -------------------------------------------------------------------------------- /knap-provider-bridge/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/tliron/commonlog" 8 | "github.com/tliron/knap/provider" 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/reflection" 11 | ) 12 | 13 | // 14 | // Server 15 | // 16 | 17 | type Server struct { 18 | StateFilename string 19 | SocketName string 20 | Log commonlog.Logger 21 | } 22 | 23 | func NewServer(stateFilename string, socketName string, log commonlog.Logger) *Server { 24 | return &Server{ 25 | StateFilename: stateFilename, 26 | SocketName: socketName, 27 | Log: log, 28 | } 29 | } 30 | 31 | func (self *Server) Start() error { 32 | if err := self.InitializeState(); err == nil { 33 | self.Log.Infof("starting provider gRPC server on socket %s", self.SocketName) 34 | if listener, err := net.Listen("unix", self.SocketName); err == nil { 35 | server := grpc.NewServer() 36 | provider.RegisterProviderServer(server, self) 37 | reflection.Register(server) 38 | return server.Serve(listener) 39 | } else { 40 | return err 41 | } 42 | } else { 43 | return err 44 | } 45 | } 46 | 47 | // provider.ProviderServer interface 48 | func (self *Server) CreateCniConfig(context context.Context, request *provider.CreateCniConfigRequest) (*provider.CreateCniConfigReply, error) { 49 | self.Log.Infof("gRPC CreateCniConfig called on socket %s", self.SocketName) 50 | if config, err := self.CreateBridgeCniConfig(request.Name); err == nil { 51 | return &provider.CreateCniConfigReply{ 52 | Config: config, 53 | }, nil 54 | } else { 55 | return nil, err 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /controller/daemon-set.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | cniresources "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" 5 | resources "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 6 | meta "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | ) 8 | 9 | func (self *Controller) processDaemonSets(network *resources.Network, networkAttachmentDefinition *cniresources.NetworkAttachmentDefinition) error { 10 | if daemonSets, err := self.Kubernetes.AppsV1().DaemonSets(networkAttachmentDefinition.Namespace).List(self.Context, meta.ListOptions{}); err == nil { 11 | for _, daemonSet := range daemonSets.Items { 12 | object := &daemonSet.Spec.Template.ObjectMeta 13 | 14 | if ObjectHasNetwork(object, network.Name) { 15 | self.Log.Infof("processing daemon set %s/%s for network %q", daemonSet.Namespace, daemonSet.Name, network.Name) 16 | 17 | if !ObjectHasNetworkAttachmentDefinition(object, networkAttachmentDefinition.Name) { 18 | daemonSet_ := daemonSet.DeepCopy() 19 | object = &daemonSet_.Spec.Template.ObjectMeta 20 | AddNetworkAttachmentDefinitionToObject(object, networkAttachmentDefinition.Name) 21 | if _, err := self.Kubernetes.AppsV1().DaemonSets(networkAttachmentDefinition.Namespace).Update(self.Context, daemonSet_, meta.UpdateOptions{}); err == nil { 22 | self.Log.Infof("attached daemon set %s/%s to network attachment definition %q", daemonSet.Namespace, daemonSet.Name, networkAttachmentDefinition.Name) 23 | } else { 24 | return err 25 | } 26 | } 27 | } 28 | } 29 | return nil 30 | } else { 31 | return err 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /controller/deployment.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | cniresources "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" 5 | resources "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 6 | meta "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | ) 8 | 9 | func (self *Controller) processDeployments(network *resources.Network, networkAttachmentDefinition *cniresources.NetworkAttachmentDefinition) error { 10 | if deployments, err := self.Kubernetes.AppsV1().Deployments(networkAttachmentDefinition.Namespace).List(self.Context, meta.ListOptions{}); err == nil { 11 | for _, deployment := range deployments.Items { 12 | object := &deployment.Spec.Template.ObjectMeta 13 | 14 | if ObjectHasNetwork(object, network.Name) { 15 | self.Log.Infof("processing deployment %s/%s for network %q", deployment.Namespace, deployment.Name, network.Name) 16 | 17 | if !ObjectHasNetworkAttachmentDefinition(object, networkAttachmentDefinition.Name) { 18 | deployment_ := deployment.DeepCopy() 19 | object = &deployment_.Spec.Template.ObjectMeta 20 | AddNetworkAttachmentDefinitionToObject(object, networkAttachmentDefinition.Name) 21 | if _, err := self.Kubernetes.AppsV1().Deployments(networkAttachmentDefinition.Namespace).Update(self.Context, deployment_, meta.UpdateOptions{}); err == nil { 22 | self.Log.Infof("attached deployment %s/%s to network attachment definition %q", deployment.Namespace, deployment.Name, networkAttachmentDefinition.Name) 23 | } else { 24 | return err 25 | } 26 | } 27 | } 28 | } 29 | return nil 30 | } else { 31 | return err 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apis/informers/externalversions/generic.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package externalversions 4 | 5 | import ( 6 | "fmt" 7 | 8 | v1alpha1 "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 9 | schema "k8s.io/apimachinery/pkg/runtime/schema" 10 | cache "k8s.io/client-go/tools/cache" 11 | ) 12 | 13 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other 14 | // sharedInformers based on type 15 | type GenericInformer interface { 16 | Informer() cache.SharedIndexInformer 17 | Lister() cache.GenericLister 18 | } 19 | 20 | type genericInformer struct { 21 | informer cache.SharedIndexInformer 22 | resource schema.GroupResource 23 | } 24 | 25 | // Informer returns the SharedIndexInformer. 26 | func (f *genericInformer) Informer() cache.SharedIndexInformer { 27 | return f.informer 28 | } 29 | 30 | // Lister returns the GenericLister. 31 | func (f *genericInformer) Lister() cache.GenericLister { 32 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) 33 | } 34 | 35 | // ForResource gives generic access to a shared informer of the matching type 36 | // TODO extend this to unknown resources with a client pool 37 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { 38 | switch resource { 39 | // Group=knap.github.com, Version=v1alpha1 40 | case v1alpha1.SchemeGroupVersion.WithResource("networks"): 41 | return &genericInformer{resource: resource.GroupResource(), informer: f.Knap().V1alpha1().Networks().Informer()}, nil 42 | 43 | } 44 | 45 | return nil, fmt.Errorf("no informer found for %v", resource) 46 | } 47 | -------------------------------------------------------------------------------- /controller/stateful-set.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | cniresources "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" 5 | resources "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 6 | meta "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | ) 8 | 9 | func (self *Controller) processStatefulSets(network *resources.Network, networkAttachmentDefinition *cniresources.NetworkAttachmentDefinition) error { 10 | if statefulSets, err := self.Kubernetes.AppsV1().StatefulSets(networkAttachmentDefinition.Namespace).List(self.Context, meta.ListOptions{}); err == nil { 11 | for _, statefulSet := range statefulSets.Items { 12 | object := &statefulSet.Spec.Template.ObjectMeta 13 | 14 | if ObjectHasNetwork(object, network.Name) { 15 | self.Log.Infof("processing stateful set %s/%s for network %q", statefulSet.Namespace, statefulSet.Name, network.Name) 16 | 17 | if !ObjectHasNetworkAttachmentDefinition(object, networkAttachmentDefinition.Name) { 18 | statefulSet_ := statefulSet.DeepCopy() 19 | object = &statefulSet_.Spec.Template.ObjectMeta 20 | AddNetworkAttachmentDefinitionToObject(object, networkAttachmentDefinition.Name) 21 | if _, err := self.Kubernetes.AppsV1().StatefulSets(networkAttachmentDefinition.Namespace).Update(self.Context, statefulSet_, meta.UpdateOptions{}); err == nil { 22 | self.Log.Infof("attached stateful set %s/%s to network attachment definition %q", statefulSet.Namespace, statefulSet.Name, networkAttachmentDefinition.Name) 23 | } else { 24 | return err 25 | } 26 | } 27 | } 28 | } 29 | return nil 30 | } else { 31 | return err 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /controller/network.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | cniresources "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" 5 | resources "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 6 | "k8s.io/apimachinery/pkg/api/errors" 7 | ) 8 | 9 | func (self *Controller) processNetwork(network *resources.Network) (bool, error) { 10 | var err error 11 | var networkAttachmentDefinition *cniresources.NetworkAttachmentDefinition 12 | 13 | // Validate network attachment definition 14 | if networkAttachmentDefinition, err = self.Client.GetNetworkAttachmentDefinition(network.Namespace, network.Name); err == nil { 15 | // TODO: should the provider ensure that it's correct? 16 | } else { 17 | if errors.IsNotFound(err) { 18 | if cniConfig, err := self.createCniConfig(network); err == nil { 19 | if networkAttachmentDefinition, err = self.Client.CreateNetworkAttachmentDefinition(network, cniConfig); err != nil { 20 | return false, err 21 | } 22 | } else { 23 | return false, err 24 | } 25 | } else { 26 | return false, err 27 | } 28 | } 29 | 30 | // Process deployments 31 | if err := self.processDeployments(network, networkAttachmentDefinition); err != nil { 32 | return false, err 33 | } 34 | 35 | // Process stateful sets 36 | if err := self.processStatefulSets(network, networkAttachmentDefinition); err != nil { 37 | return false, err 38 | } 39 | 40 | // Process daemon sets 41 | if err := self.processDaemonSets(network, networkAttachmentDefinition); err == nil { 42 | return true, nil 43 | } else { 44 | return false, err 45 | } 46 | 47 | // TODO: What about Independent ReplicaSets? Independent Pods even? 48 | } 49 | -------------------------------------------------------------------------------- /client/common.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/tliron/kutil/version" 7 | apps "k8s.io/api/apps/v1" 8 | core "k8s.io/api/core/v1" 9 | "k8s.io/apimachinery/pkg/api/errors" 10 | meta "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | var true_ = true 14 | var false_ = false 15 | 16 | func (self *Client) CreateDeployment(deployment *apps.Deployment, appName string) (*apps.Deployment, error) { 17 | if deployment, err := self.Kubernetes.AppsV1().Deployments(self.Namespace).Create(self.Context, deployment, meta.CreateOptions{}); err == nil { 18 | return deployment, nil 19 | } else if errors.IsAlreadyExists(err) { 20 | self.Log.Infof("%s", err.Error()) 21 | return self.Kubernetes.AppsV1().Deployments(self.Namespace).Get(self.Context, appName, meta.GetOptions{}) 22 | } else { 23 | return nil, err 24 | } 25 | } 26 | 27 | func (self *Client) Labels(appName string, component string, namespace string) map[string]string { 28 | return map[string]string{ 29 | "app.kubernetes.io/name": appName, 30 | "app.kubernetes.io/instance": fmt.Sprintf("%s-%s", appName, namespace), 31 | "app.kubernetes.io/version": version.GitVersion, 32 | "app.kubernetes.io/component": component, 33 | "app.kubernetes.io/part-of": self.PartOf, 34 | "app.kubernetes.io/managed-by": self.ManagedBy, 35 | } 36 | } 37 | 38 | func (self *Client) DefaultSecurityContext() *core.SecurityContext { 39 | var user int64 = 1000 40 | return &core.SecurityContext{ 41 | AllowPrivilegeEscalation: &false_, 42 | Capabilities: &core.Capabilities{ 43 | Drop: []core.Capability{"ALL"}, 44 | }, 45 | RunAsNonRoot: &true_, 46 | RunAsUser: &user, 47 | SeccompProfile: &core.SeccompProfile{ 48 | Type: core.SeccompProfileTypeRuntimeDefault, 49 | }, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /apis/applyconfiguration/knap.github.com/v1alpha1/networkspec.go: -------------------------------------------------------------------------------- 1 | // Code generated by applyconfiguration-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | // NetworkSpecApplyConfiguration represents an declarative configuration of the NetworkSpec type for use 6 | // with apply. 7 | type NetworkSpecApplyConfiguration struct { 8 | Provider *string `json:"provider,omitempty"` 9 | Hints map[string]string `json:"hints,omitempty"` 10 | } 11 | 12 | // NetworkSpecApplyConfiguration constructs an declarative configuration of the NetworkSpec type for use with 13 | // apply. 14 | func NetworkSpec() *NetworkSpecApplyConfiguration { 15 | return &NetworkSpecApplyConfiguration{} 16 | } 17 | 18 | // WithProvider sets the Provider field in the declarative configuration to the given value 19 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 20 | // If called multiple times, the Provider field is set to the value of the last call. 21 | func (b *NetworkSpecApplyConfiguration) WithProvider(value string) *NetworkSpecApplyConfiguration { 22 | b.Provider = &value 23 | return b 24 | } 25 | 26 | // WithHints puts the entries into the Hints field in the declarative configuration 27 | // and returns the receiver, so that objects can be build by chaining "With" function invocations. 28 | // If called multiple times, the entries provided by each call will be put on the Hints field, 29 | // overwriting an existing map entries in Hints field with the same key. 30 | func (b *NetworkSpecApplyConfiguration) WithHints(entries map[string]string) *NetworkSpecApplyConfiguration { 31 | if b.Hints == nil && len(entries) > 0 { 32 | b.Hints = make(map[string]string, len(entries)) 33 | } 34 | for k, v := range entries { 35 | b.Hints[k] = v 36 | } 37 | return b 38 | } 39 | -------------------------------------------------------------------------------- /provider/client.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "time" 7 | 8 | "github.com/tliron/commonlog" 9 | "google.golang.org/grpc" 10 | ) 11 | 12 | // 13 | // Client 14 | // 15 | 16 | type Client struct { 17 | socketName string 18 | connection *grpc.ClientConn 19 | client ProviderClient 20 | log commonlog.Logger 21 | } 22 | 23 | func NewClient(socketName string, log commonlog.Logger) (*Client, error) { 24 | log.Infof("creating provider gRPC client on socket %s", socketName) 25 | if connection, err := grpc.Dial(socketName, grpc.WithInsecure(), grpc.WithDialer(dialer)); err == nil { 26 | return &Client{ 27 | socketName: socketName, 28 | connection: connection, 29 | client: NewProviderClient(connection), 30 | log: log, 31 | }, nil 32 | } else { 33 | return nil, err 34 | } 35 | } 36 | 37 | func (self *Client) Release() { 38 | self.log.Infof("closing provider gRPC client connection on socket %s", self.socketName) 39 | self.connection.Close() 40 | } 41 | 42 | func (self *Client) CreateCniConfig(name string, hints map[string]string) (string, error) { 43 | self.log.Infof("calling gRPC CreateCniConfig on socket %s", self.socketName) 44 | 45 | context, cancel := context.WithTimeout(context.Background(), time.Second) 46 | defer cancel() 47 | 48 | request := CreateCniConfigRequest{ 49 | Name: name, 50 | Hints: hints, 51 | } 52 | 53 | if reply, err := self.client.CreateCniConfig(context, &request); err == nil { 54 | return reply.Config, nil 55 | } else { 56 | return "", err 57 | } 58 | } 59 | 60 | func dialer(address string, duration time.Duration) (net.Conn, error) { 61 | if unixAddress, err := net.ResolveUnixAddr("unix", address); err == nil { 62 | return net.DialUnix("unix", nil, unixAddress) 63 | } else { 64 | return nil, err 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /apis/applyconfiguration/knap.github.com/v1alpha1/networkstatus.go: -------------------------------------------------------------------------------- 1 | // Code generated by applyconfiguration-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | // NetworkStatusApplyConfiguration represents an declarative configuration of the NetworkStatus type for use 6 | // with apply. 7 | type NetworkStatusApplyConfiguration struct { 8 | NetworkAttachmentDefinitions []string `json:"networkAttachmentDefinitions,omitempty"` 9 | Deployments []string `json:"deployments,omitempty"` 10 | } 11 | 12 | // NetworkStatusApplyConfiguration constructs an declarative configuration of the NetworkStatus type for use with 13 | // apply. 14 | func NetworkStatus() *NetworkStatusApplyConfiguration { 15 | return &NetworkStatusApplyConfiguration{} 16 | } 17 | 18 | // WithNetworkAttachmentDefinitions adds the given value to the NetworkAttachmentDefinitions field in the declarative configuration 19 | // and returns the receiver, so that objects can be build by chaining "With" function invocations. 20 | // If called multiple times, values provided by each call will be appended to the NetworkAttachmentDefinitions field. 21 | func (b *NetworkStatusApplyConfiguration) WithNetworkAttachmentDefinitions(values ...string) *NetworkStatusApplyConfiguration { 22 | for i := range values { 23 | b.NetworkAttachmentDefinitions = append(b.NetworkAttachmentDefinitions, values[i]) 24 | } 25 | return b 26 | } 27 | 28 | // WithDeployments adds the given value to the Deployments field in the declarative configuration 29 | // and returns the receiver, so that objects can be build by chaining "With" function invocations. 30 | // If called multiple times, values provided by each call will be appended to the Deployments field. 31 | func (b *NetworkStatusApplyConfiguration) WithDeployments(values ...string) *NetworkStatusApplyConfiguration { 32 | for i := range values { 33 | b.Deployments = append(b.Deployments, values[i]) 34 | } 35 | return b 36 | } 37 | -------------------------------------------------------------------------------- /knap/commands/client.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | contextpkg "context" 5 | 6 | netpkg "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned" 7 | knappkg "github.com/tliron/knap/apis/clientset/versioned" 8 | "github.com/tliron/knap/client" 9 | "github.com/tliron/knap/controller" 10 | kubernetesutil "github.com/tliron/kutil/kubernetes" 11 | "github.com/tliron/kutil/util" 12 | apiextensionspkg "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 13 | kubernetespkg "k8s.io/client-go/kubernetes" 14 | ) 15 | 16 | func NewClient() *client.Client { 17 | config, err := kubernetesutil.NewConfigFromFlags(masterUrl, kubeconfigPath, context, log) 18 | util.FailOnError(err) 19 | 20 | kubernetes, err := kubernetespkg.NewForConfig(config) 21 | util.FailOnError(err) 22 | 23 | apiExtensions, err := apiextensionspkg.NewForConfig(config) 24 | util.FailOnError(err) 25 | 26 | net, err := netpkg.NewForConfig(config) 27 | util.FailOnError(err) 28 | 29 | knap, err := knappkg.NewForConfig(config) 30 | util.FailOnError(err) 31 | 32 | namespace_ := namespace 33 | if cluster { 34 | namespace_ = "" 35 | } else if namespace_ == "" { 36 | if namespace__, ok := kubernetesutil.GetConfiguredNamespace(kubeconfigPath, context); ok { 37 | namespace_ = namespace__ 38 | } 39 | if namespace_ == "" { 40 | util.Fail("could not discover namespace and \"--namespace\" not provided") 41 | } 42 | } 43 | 44 | return &client.Client{ 45 | Config: config, 46 | Kubernetes: kubernetes, 47 | APIExtensions: apiExtensions, 48 | Net: net, 49 | Knap: knap, 50 | Cluster: cluster, 51 | Namespace: namespace_, 52 | NamePrefix: controller.NamePrefix, 53 | PartOf: controller.PartOf, 54 | ManagedBy: controller.ManagedBy, 55 | OperatorImageName: controller.OperatorImageName, 56 | Context: contextpkg.TODO(), 57 | Log: log, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /controller/cni.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | resources "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 8 | "github.com/tliron/kutil/kubernetes" 9 | "github.com/tliron/kutil/transcribe" 10 | core "k8s.io/api/core/v1" 11 | "k8s.io/client-go/kubernetes/scheme" 12 | "k8s.io/client-go/tools/remotecommand" 13 | ) 14 | 15 | func (self *Controller) createCniConfig(network *resources.Network) (string, error) { 16 | appName := fmt.Sprintf("knap-provider-%s", network.Spec.Provider) 17 | 18 | podName, err := kubernetes.GetFirstPodName(self.Context, self.Kubernetes, self.Client.Namespace, appName) 19 | if err != nil { 20 | return "", fmt.Errorf("cannot find provider for network %s/%s: %s\n%s", network.Namespace, network.Name, network.Spec.Provider, err.Error()) 21 | } 22 | 23 | var stdout strings.Builder 24 | var stderr strings.Builder 25 | 26 | execOptions := core.PodExecOptions{ 27 | Container: "provider", 28 | Command: []string{appName, "provide", network.Name}, 29 | Stdout: true, 30 | Stderr: true, 31 | TTY: false, 32 | } 33 | 34 | streamOptions := remotecommand.StreamOptions{ 35 | Stdout: &stdout, 36 | Stderr: &stderr, 37 | Tty: false, 38 | } 39 | 40 | if network.Spec.Hints != nil { 41 | if hints, err := transcribe.EncodeYAML(network.Spec.Hints, " ", false); err == nil { 42 | execOptions.Stdin = true 43 | streamOptions.Stdin = strings.NewReader(hints) 44 | } else { 45 | return "", err 46 | } 47 | } 48 | 49 | request := self.REST.Post().Namespace(self.Client.Namespace).Resource("pods").Name(podName).SubResource("exec").VersionedParams(&execOptions, scheme.ParameterCodec) 50 | 51 | if executor, err := remotecommand.NewSPDYExecutor(self.Config, "POST", request.URL()); err == nil { 52 | if err = executor.Stream(streamOptions); err == nil { 53 | return stdout.String(), nil 54 | } else { 55 | return "", fmt.Errorf("%s\n%s", err.Error(), stderr.String()) 56 | } 57 | } else { 58 | return "", err 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /examples/hello-world/implicit.yaml: -------------------------------------------------------------------------------- 1 | # Two networks 2 | # Each a separate bridge network 3 | 4 | apiVersion: knap.github.com/v1alpha1 5 | kind: Network 6 | 7 | metadata: 8 | name: implicit-a 9 | labels: 10 | app.kubernetes.io/name: implicit-a 11 | app.kubernetes.io/component: network 12 | 13 | spec: 14 | provider: bridge 15 | 16 | --- 17 | 18 | apiVersion: knap.github.com/v1alpha1 19 | kind: Network 20 | 21 | metadata: 22 | name: implicit-b 23 | labels: 24 | app.kubernetes.io/name: implicit-b 25 | app.kubernetes.io/component: network 26 | 27 | spec: 28 | provider: bridge 29 | 30 | --- 31 | 32 | # Two deployments 33 | # The first is attached just to network A 34 | # The second is attached to both networks 35 | 36 | apiVersion: apps/v1 37 | kind: Deployment 38 | 39 | metadata: 40 | name: knap-hello-world1 41 | labels: 42 | app.kubernetes.io/name: knap-hello-world1 43 | app.kubernetes.io/component: deployment 44 | 45 | spec: 46 | replicas: 1 47 | selector: 48 | matchLabels: 49 | app.kubernetes.io/name: knap-hello-world1 50 | app.kubernetes.io/component: deployment 51 | template: 52 | metadata: 53 | labels: 54 | app.kubernetes.io/name: knap-hello-world1 55 | app.kubernetes.io/component: deployment 56 | annotations: 57 | knap.github.com/networks: implicit-a 58 | spec: 59 | containers: 60 | - name: hello-world 61 | image: docker.io/paulbouwer/hello-kubernetes:1.8 62 | imagePullPolicy: Always 63 | 64 | --- 65 | 66 | apiVersion: apps/v1 67 | kind: Deployment 68 | 69 | metadata: 70 | name: knap-hello-world2 71 | labels: 72 | app.kubernetes.io/name: knap-hello-world2 73 | app.kubernetes.io/component: deployment 74 | 75 | spec: 76 | replicas: 1 77 | selector: 78 | matchLabels: 79 | app.kubernetes.io/name: knap-hello-world2 80 | app.kubernetes.io/component: deployment 81 | template: 82 | metadata: 83 | labels: 84 | app.kubernetes.io/name: knap-hello-world2 85 | app.kubernetes.io/component: deployment 86 | annotations: 87 | knap.github.com/networks: implicit-a, implicit-b 88 | spec: 89 | containers: 90 | - name: hello-world 91 | image: docker.io/paulbouwer/hello-kubernetes:1.8 92 | imagePullPolicy: Always 93 | -------------------------------------------------------------------------------- /controller/annotations.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | cniresources "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" 8 | resources "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 9 | meta "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | func ObjectHasNetwork(object *meta.ObjectMeta, name string) bool { 13 | if networkNames, ok := GetObjectNetworkNames(object); ok { 14 | for _, networkName := range networkNames { 15 | if networkName == name { 16 | return true 17 | } 18 | } 19 | } 20 | return false 21 | } 22 | 23 | func ObjectHasNetworkAttachmentDefinition(object *meta.ObjectMeta, name string) bool { 24 | if networkAttachmentDefinitionNames, ok := GetObjectNetworkAttachmentDefinition(object); ok { 25 | for _, networkAttachmentDefinitionName := range networkAttachmentDefinitionNames { 26 | if networkAttachmentDefinitionName == name { 27 | return true 28 | } 29 | } 30 | } 31 | return false 32 | } 33 | 34 | func GetObjectNetworkNames(object *meta.ObjectMeta) ([]string, bool) { 35 | if annotation, ok := object.Annotations[resources.NetworkAnnotation]; ok { 36 | var networkNames []string 37 | for _, networkName := range strings.Split(annotation, ",") { 38 | networkName = strings.TrimSpace(networkName) 39 | networkNames = append(networkNames, networkName) 40 | } 41 | return networkNames, true 42 | } else { 43 | return nil, false 44 | } 45 | } 46 | 47 | func GetObjectNetworkAttachmentDefinition(object *meta.ObjectMeta) ([]string, bool) { 48 | if annotation, ok := object.Annotations[cniresources.NetworkAttachmentAnnot]; ok { 49 | var networkNames []string 50 | for _, networkName := range strings.Split(annotation, ",") { 51 | networkName = strings.TrimSpace(networkName) 52 | networkNames = append(networkNames, networkName) 53 | } 54 | return networkNames, true 55 | } else { 56 | return nil, false 57 | } 58 | } 59 | 60 | func AddNetworkAttachmentDefinitionToObject(object *meta.ObjectMeta, name string) { 61 | if existing, ok := object.Annotations[cniresources.NetworkAttachmentAnnot]; ok { 62 | object.Annotations[cniresources.NetworkAttachmentAnnot] = fmt.Sprintf("%s,%s", existing, name) 63 | } else { 64 | object.Annotations[cniresources.NetworkAttachmentAnnot] = name 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /client/network-attachment-definition.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | cniresources "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" 5 | resources "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 6 | "k8s.io/apimachinery/pkg/api/errors" 7 | meta "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | ) 9 | 10 | func (self *Client) GetNetworkAttachmentDefinition(namespace string, networkName string) (*cniresources.NetworkAttachmentDefinition, error) { 11 | // Default to same namespace as operator 12 | if namespace == "" { 13 | namespace = self.Namespace 14 | } 15 | 16 | if networkAttachmentDefinition, err := self.Net.K8sCniCncfIoV1().NetworkAttachmentDefinitions(namespace).Get(self.Context, networkName, meta.GetOptions{}); err == nil { 17 | // When retrieved from cache the GVK may be empty 18 | if networkAttachmentDefinition.Kind == "" { 19 | networkAttachmentDefinition = networkAttachmentDefinition.DeepCopy() 20 | networkAttachmentDefinition.APIVersion = cniresources.SchemeGroupVersion.String() 21 | networkAttachmentDefinition.Kind = "NetworkAttachmentDefinition" 22 | } 23 | return networkAttachmentDefinition, nil 24 | } else { 25 | return nil, err 26 | } 27 | } 28 | 29 | func (self *Client) CreateNetworkAttachmentDefinition(network *resources.Network, config string) (*cniresources.NetworkAttachmentDefinition, error) { 30 | networkAttachmentDefinition := &cniresources.NetworkAttachmentDefinition{ 31 | ObjectMeta: meta.ObjectMeta{ 32 | Name: network.Name, 33 | Namespace: network.Namespace, 34 | OwnerReferences: []meta.OwnerReference{ 35 | *meta.NewControllerRef(network, network.GroupVersionKind()), 36 | }, 37 | }, 38 | Spec: cniresources.NetworkAttachmentDefinitionSpec{ 39 | Config: config, 40 | }, 41 | } 42 | 43 | self.Log.Infof("creating network attachment definition: %s/%s\n%s", networkAttachmentDefinition.Namespace, networkAttachmentDefinition.Name, networkAttachmentDefinition.Spec.Config) 44 | 45 | if networkAttachmentDefinition, err := self.Net.K8sCniCncfIoV1().NetworkAttachmentDefinitions(networkAttachmentDefinition.Namespace).Create(self.Context, networkAttachmentDefinition, meta.CreateOptions{}); err == nil { 46 | return networkAttachmentDefinition, nil 47 | } else if errors.IsAlreadyExists(err) { 48 | return self.Net.K8sCniCncfIoV1().NetworkAttachmentDefinitions(network.Namespace).Get(self.Context, network.Name, meta.GetOptions{}) 49 | } else { 50 | return nil, err 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /knap-operator/controller.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/heptiolabs/healthcheck" 8 | netpkg "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned" 9 | knappkg "github.com/tliron/knap/apis/clientset/versioned" 10 | controllerpkg "github.com/tliron/knap/controller" 11 | kubernetesutil "github.com/tliron/kutil/kubernetes" 12 | "github.com/tliron/kutil/util" 13 | versionpkg "github.com/tliron/kutil/version" 14 | apiextensionspkg "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 15 | kubernetespkg "k8s.io/client-go/kubernetes" 16 | "k8s.io/client-go/tools/clientcmd" 17 | 18 | // Load all auth plugins: 19 | _ "k8s.io/client-go/plugin/pkg/client/auth" 20 | ) 21 | 22 | func Controller() { 23 | if version { 24 | versionpkg.Print() 25 | util.Exit(0) 26 | return 27 | } 28 | 29 | log.Noticef("%s version=%s revision=%s", toolName, versionpkg.GitVersion, versionpkg.GitRevision) 30 | 31 | // Config 32 | 33 | config, err := clientcmd.BuildConfigFromFlags(masterUrl, kubeconfigPath) 34 | util.FailOnError(err) 35 | 36 | if cluster { 37 | namespace = "" 38 | } else if namespace == "" { 39 | if namespace_, ok := kubernetesutil.GetConfiguredNamespace(kubeconfigPath, context); ok { 40 | namespace = namespace_ 41 | } 42 | if namespace == "" { 43 | namespace = kubernetesutil.GetServiceAccountNamespace() 44 | } 45 | if namespace == "" { 46 | util.Fail("could not discover namespace and namespace not provided") 47 | } 48 | } 49 | 50 | // Clients 51 | 52 | kubernetesClient, err := kubernetespkg.NewForConfig(config) 53 | util.FailOnError(err) 54 | 55 | apiExtensionsClient, err := apiextensionspkg.NewForConfig(config) 56 | util.FailOnError(err) 57 | 58 | netClient, err := netpkg.NewForConfig(config) 59 | util.FailOnError(err) 60 | 61 | knapClient, err := knappkg.NewForConfig(config) 62 | util.FailOnError(err) 63 | 64 | // Controller 65 | 66 | controller := controllerpkg.NewController( 67 | toolName, 68 | cluster, 69 | namespace, 70 | kubernetesClient, 71 | apiExtensionsClient, 72 | netClient, 73 | knapClient, 74 | config, 75 | resyncPeriod, 76 | util.SetupSignalHandler(), 77 | ) 78 | 79 | // Run 80 | 81 | err = controller.Run(concurrency, func() { 82 | log.Info("starting health monitor") 83 | health := healthcheck.NewHandler() 84 | err := http.ListenAndServe(fmt.Sprintf(":%d", healthPort), health) 85 | util.FailOnError(err) 86 | }) 87 | util.FailOnError(err) 88 | } 89 | -------------------------------------------------------------------------------- /knap/commands/root.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "os/user" 5 | "path/filepath" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/tliron/commonlog" 9 | "github.com/tliron/kutil/util" 10 | "k8s.io/klog/v2" 11 | ) 12 | 13 | var logTo string 14 | var verbose int 15 | var maxWidth int 16 | var colorize string 17 | var format string 18 | var pretty bool 19 | var strict bool 20 | 21 | var masterUrl string 22 | var kubeconfigPath string 23 | var context string 24 | var namespace string 25 | 26 | func init() { 27 | var defaultKubeconfigPath string 28 | if u, err := user.Current(); err == nil { 29 | defaultKubeconfigPath = filepath.Join(u.HomeDir, ".kube", "config") 30 | } 31 | 32 | rootCommand.PersistentFlags().StringVarP(&logTo, "log", "l", "", "log to file (defaults to stderr)") 33 | rootCommand.PersistentFlags().CountVarP(&verbose, "verbose", "v", "add a log verbosity level (can be used twice)") 34 | rootCommand.PersistentFlags().IntVarP(&maxWidth, "width", "j", 0, "maximum output width (0 to use terminal width, -1 for no maximum)") 35 | rootCommand.PersistentFlags().StringVarP(&colorize, "colorize", "z", "true", "colorize output (boolean or \"force\")") 36 | rootCommand.PersistentFlags().StringVarP(&format, "format", "o", "", "output format (\"bare\", \"yaml\", \"json\", \"cjson\", \"xml\", \"cbor\", \"messagepack\", or \"go\")") 37 | rootCommand.PersistentFlags().BoolVarP(&strict, "strict", "y", false, "strict output (for \"yaml\" format only)") 38 | rootCommand.PersistentFlags().BoolVarP(&pretty, "pretty", "r", true, "prettify output") 39 | 40 | rootCommand.PersistentFlags().StringVarP(&masterUrl, "master", "m", "", "address of the Kubernetes API server") 41 | rootCommand.PersistentFlags().StringVarP(&kubeconfigPath, "kubeconfig", "k", defaultKubeconfigPath, "path to Kubernetes configuration") 42 | rootCommand.PersistentFlags().StringVarP(&context, "context", "x", "", "name of context in Kubernetes configuration") 43 | rootCommand.PersistentFlags().StringVarP(&namespace, "namespace", "n", "", "namespace (overrides context namespace in Kubernetes configuration)") 44 | } 45 | 46 | var rootCommand = &cobra.Command{ 47 | Use: toolName, 48 | Short: "Control Knap", 49 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 50 | util.InitializeColorization(colorize) 51 | commonlog.Initialize(verbose, logTo) 52 | if writer := commonlog.GetWriter(); writer != nil { 53 | klog.SetOutput(writer) 54 | } 55 | }, 56 | } 57 | 58 | func Execute() { 59 | err := rootCommand.Execute() 60 | util.FailOnError(err) 61 | } 62 | -------------------------------------------------------------------------------- /apis/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | clientset "github.com/tliron/knap/apis/clientset/versioned" 7 | knapv1alpha1 "github.com/tliron/knap/apis/clientset/versioned/typed/knap.github.com/v1alpha1" 8 | fakeknapv1alpha1 "github.com/tliron/knap/apis/clientset/versioned/typed/knap.github.com/v1alpha1/fake" 9 | "k8s.io/apimachinery/pkg/runtime" 10 | "k8s.io/apimachinery/pkg/watch" 11 | "k8s.io/client-go/discovery" 12 | fakediscovery "k8s.io/client-go/discovery/fake" 13 | "k8s.io/client-go/testing" 14 | ) 15 | 16 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 17 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 18 | // without applying any validations and/or defaults. It shouldn't be considered a replacement 19 | // for a real clientset and is mostly useful in simple unit tests. 20 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 21 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 22 | for _, obj := range objects { 23 | if err := o.Add(obj); err != nil { 24 | panic(err) 25 | } 26 | } 27 | 28 | cs := &Clientset{tracker: o} 29 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 30 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 31 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 32 | gvr := action.GetResource() 33 | ns := action.GetNamespace() 34 | watch, err := o.Watch(gvr, ns) 35 | if err != nil { 36 | return false, nil, err 37 | } 38 | return true, watch, nil 39 | }) 40 | 41 | return cs 42 | } 43 | 44 | // Clientset implements clientset.Interface. Meant to be embedded into a 45 | // struct to get a default implementation. This makes faking out just the method 46 | // you want to test easier. 47 | type Clientset struct { 48 | testing.Fake 49 | discovery *fakediscovery.FakeDiscovery 50 | tracker testing.ObjectTracker 51 | } 52 | 53 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 54 | return c.discovery 55 | } 56 | 57 | func (c *Clientset) Tracker() testing.ObjectTracker { 58 | return c.tracker 59 | } 60 | 61 | var ( 62 | _ clientset.Interface = &Clientset{} 63 | _ testing.FakeClient = &Clientset{} 64 | ) 65 | 66 | // KnapV1alpha1 retrieves the KnapV1alpha1Client 67 | func (c *Clientset) KnapV1alpha1() knapv1alpha1.KnapV1alpha1Interface { 68 | return &fakeknapv1alpha1.FakeKnapV1alpha1{Fake: &c.Fake} 69 | } 70 | -------------------------------------------------------------------------------- /knap-provider-bridge/server/state.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | 7 | "github.com/gofrs/flock" 8 | "github.com/tliron/go-ard" 9 | "github.com/tliron/kutil/transcribe" 10 | ) 11 | 12 | var initialState ard.Map 13 | 14 | func init() { 15 | availableBridgePrefixes := ard.List{ 16 | "192.168.2", 17 | "192.168.3", 18 | "192.168.4", 19 | "192.168.5", 20 | "192.168.6", 21 | "192.168.7", 22 | } 23 | 24 | initialState = make(ard.Map) 25 | initialState["availableBridgePrefixes"] = availableBridgePrefixes 26 | } 27 | 28 | func (self *Server) InitializeState() error { 29 | _, err := self.SetState(initialState) 30 | return err 31 | } 32 | 33 | func (self *Server) GetState() (ard.Map, error) { 34 | if file, err := os.Open(self.StateFilename); err == nil { 35 | defer file.Close() 36 | if state, _, err := ard.ReadYAML(file, false); err == nil { 37 | if map_, ok := state.(ard.Map); ok { 38 | return map_, nil 39 | } else { 40 | return nil, errors.New("state is not a map") 41 | } 42 | } else { 43 | return nil, err 44 | } 45 | } else { 46 | return nil, err 47 | } 48 | } 49 | 50 | func (self *Server) SetState(state ard.Map) (ard.Map, error) { 51 | if file, err := os.Create(self.StateFilename); err == nil { 52 | defer file.Close() 53 | if err := transcribe.WriteYAML(state, file, " ", false); err == nil { 54 | return state, nil 55 | } else { 56 | return nil, err 57 | } 58 | } else { 59 | return nil, err 60 | } 61 | } 62 | 63 | func (self *Server) ProvideBridgePrefix() (string, error) { 64 | // Lock on state file 65 | lock := flock.New(self.StateFilename) 66 | if err := lock.Lock(); err == nil { 67 | defer lock.Unlock() 68 | } else { 69 | return "", err 70 | } 71 | 72 | if state, err := self.GetState(); err == nil { 73 | if availableBridgePrefixes, ok := state["availableBridgePrefixes"]; ok { 74 | if availableBridgePrefixes_, ok := availableBridgePrefixes.(ard.List); ok { 75 | if len(availableBridgePrefixes_) > 0 { 76 | bridgePrefix := availableBridgePrefixes_[0] 77 | if bridgePrefix_, ok := bridgePrefix.(string); ok { 78 | availableBridgePrefixes_ = availableBridgePrefixes_[1:] 79 | state["availableBridgePrefixes"] = availableBridgePrefixes_ 80 | if _, err := self.SetState(state); err == nil { 81 | return bridgePrefix_, nil 82 | } else { 83 | return "", err 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } else { 90 | return "", err 91 | } 92 | 93 | return "", errors.New("no bridge prefixes available") 94 | } 95 | -------------------------------------------------------------------------------- /knap-operator/command.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/tliron/commonlog" 8 | cobrautil "github.com/tliron/kutil/cobra" 9 | "github.com/tliron/kutil/util" 10 | "k8s.io/klog/v2" 11 | ) 12 | 13 | var logTo string 14 | var verbose int 15 | var colorize string 16 | 17 | var masterUrl string 18 | var kubeconfigPath string 19 | var context string 20 | 21 | var version bool 22 | var cluster bool 23 | var namespace string 24 | var concurrency uint 25 | var resyncPeriod time.Duration 26 | var cachePath string 27 | var healthPort uint 28 | 29 | func init() { 30 | command.Flags().StringVarP(&logTo, "log", "l", "", "log to file (defaults to stderr)") 31 | command.Flags().CountVarP(&verbose, "verbose", "v", "add a log verbosity level (can be used twice)") 32 | command.Flags().StringVarP(&colorize, "colorize", "z", "true", "colorize output (boolean or \"force\")") 33 | 34 | // Conventional flags for Kubernetes controllers 35 | command.Flags().StringVar(&masterUrl, "master", "", "address of Kubernetes API server") 36 | command.Flags().StringVar(&kubeconfigPath, "kubeconfig", "", "path to Kubernetes configuration") 37 | command.Flags().StringVarP(&context, "context", "x", "", "name of context in Kubernetes configuration") 38 | 39 | // Our additional flags 40 | command.Flags().BoolVar(&version, "version", false, "print version") 41 | command.Flags().BoolVar(&cluster, "cluster", false, "enable cluster mode") 42 | command.Flags().StringVar(&namespace, "namespace", "", "namespace (overrides context namespace in Kubernetes configuration)") 43 | command.Flags().UintVar(&concurrency, "concurrency", 1, "number of concurrent workers per processor") 44 | command.Flags().DurationVar(&resyncPeriod, "resync", time.Second*30, "informer resync period") 45 | command.Flags().StringVar(&cachePath, "cache", "", "cache path") 46 | command.Flags().UintVar(&healthPort, "health-port", 8086, "HTTP port for health check (for liveness and readiness probes)") 47 | 48 | cobrautil.SetFlagsFromEnvironment("KNAP_OPERATOR_", command) 49 | } 50 | 51 | var command = &cobra.Command{ 52 | Use: toolName, 53 | Short: "Start the Knap operator", 54 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 55 | util.InitializeColorization(colorize) 56 | commonlog.Initialize(verbose, logTo) 57 | if writer := commonlog.GetWriter(); writer != nil { 58 | klog.SetOutput(writer) 59 | } 60 | }, 61 | Run: func(cmd *cobra.Command, args []string) { 62 | Controller() 63 | }, 64 | } 65 | 66 | func Execute() { 67 | err := command.Execute() 68 | util.FailOnError(err) 69 | } 70 | -------------------------------------------------------------------------------- /assets/kubernetes/knap.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | 4 | metadata: 5 | name: knap 6 | namespace: !!string $NAMESPACE 7 | 8 | --- 9 | 10 | apiVersion: rbac.authorization.k8s.io/v1 11 | kind: Role 12 | 13 | metadata: 14 | name: knap 15 | namespace: !!string $NAMESPACE 16 | 17 | rules: 18 | - apiGroups: ["*"] 19 | resources: ["*"] 20 | verbs: ["*"] 21 | 22 | --- 23 | 24 | apiVersion: rbac.authorization.k8s.io/v1 25 | kind: RoleBinding 26 | 27 | metadata: 28 | name: knap 29 | namespace: !!string $NAMESPACE 30 | 31 | subjects: 32 | - kind: ServiceAccount 33 | name: knap 34 | namespace: !!string $NAMESPACE # required 35 | 36 | roleRef: 37 | apiGroup: rbac.authorization.k8s.io 38 | kind: Role 39 | name: knap # must be in our namespace 40 | 41 | --- 42 | 43 | apiVersion: apps/v1 44 | kind: Deployment 45 | 46 | metadata: 47 | name: knap-operator 48 | namespace: !!string $NAMESPACE 49 | labels: 50 | app.kubernetes.io/name: knap-operator 51 | app.kubernetes.io/instance: knap-operator-$NAMESPACE 52 | app.kubernetes.io/version: !!string $VERSION 53 | app.kubernetes.io/component: operator 54 | app.kubernetes.io/part-of: knap 55 | app.kubernetes.io/managed-by: knap 56 | 57 | spec: 58 | replicas: 1 59 | selector: 60 | matchLabels: 61 | app.kubernetes.io/name: knap-operator 62 | app.kubernetes.io/instance: knap-operator-$NAMESPACE 63 | app.kubernetes.io/version: !!string $VERSION 64 | app.kubernetes.io/component: operator 65 | template: 66 | metadata: 67 | labels: 68 | app.kubernetes.io/name: knap-operator 69 | app.kubernetes.io/instance: knap-operator-$NAMESPACE 70 | app.kubernetes.io/version: !!string $VERSION 71 | app.kubernetes.io/component: operator 72 | app.kubernetes.io/part-of: knap 73 | app.kubernetes.io/managed-by: knap 74 | spec: 75 | serviceAccountName: knap # must be in our namespace 76 | containers: 77 | - name: operator 78 | image: $REGISTRY_URL/tliron/knap-operator:latest 79 | imagePullPolicy: Always 80 | env: 81 | # Vars with the "KNAP_OPERATOR_" prefix become CLI flags 82 | - name: KNAP_OPERATOR_concurrency 83 | value: '3' 84 | - name: KNAP_OPERATOR_verbose 85 | value: '1' 86 | # To enable cluster mode we also need: cluster-mode-authorization.yaml 87 | #- name: KNAP_OPERATOR_cluster 88 | # value: 'true' 89 | livenessProbe: 90 | httpGet: 91 | port: 8086 92 | path: /live 93 | readinessProbe: 94 | httpGet: 95 | port: 8086 96 | path: /ready 97 | -------------------------------------------------------------------------------- /scripts/_functions: -------------------------------------------------------------------------------- 1 | 2 | RED='\033[0;31m' 3 | GREEN='\033[0;32m' 4 | BLUE='\033[0;34m' 5 | CYAN='\033[0;36m' 6 | RESET='\033[0m' 7 | 8 | # Colored messages (blue is the default) 9 | # Examples: 10 | # m "hello world" 11 | # m "hello world" "$GREEN" 12 | function m () { 13 | local COLOR=${2:-$BLUE} 14 | echo -e "$COLOR$1$RESET" 15 | } 16 | 17 | function copy_function () { 18 | local ORIG_FUNC=$(declare -f $1) 19 | local NEWNAME_FUNC="$2${ORIG_FUNC#$1}" 20 | eval "$NEWNAME_FUNC" 21 | } 22 | 23 | # git 24 | 25 | function git_version () { 26 | VERSION=$(git -C "$ROOT" describe --tags --always 2> /dev/null || echo '') 27 | SHORT_VERSION=$(git -C "$ROOT" describe --tags --always --abbrev=0 2> /dev/null || echo '') 28 | REVISION=$(git -C "$ROOT" rev-parse HEAD 2> /dev/null || echo '') 29 | TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S %Z") 30 | GO_VERSION=$(go version | { read _ _ v _; echo ${v#go}; }) 31 | } 32 | 33 | # kubectl 34 | 35 | function kubectl_apply_template () { 36 | cat "$1" | NAMESPACE=$WORKSPACE envsubst | kubectl apply -f - 37 | } 38 | 39 | function kubectl_delete_template () { 40 | cat "$1" | NAMESPACE=$WORKSPACE envsubst | kubectl delete -f - || true 41 | } 42 | 43 | function kubectl_first_pod () { 44 | local NAME=$1 45 | kubectl get pods --selector="app.kubernetes.io/name=$NAME" --field-selector=status.phase=Running --namespace="$WORKSPACE" \ 46 | --output=jsonpath={.items[0].metadata.name} 47 | } 48 | 49 | function kubectl_wait_for_deployment () { 50 | local NAME=$1 51 | kubectl wait "deployments/$NAME" --namespace="$WORKSPACE" \ 52 | --for=condition=available 53 | kubectl_wait_for_pod "$NAME" 54 | } 55 | 56 | function kubectl_wait_for_pod () { 57 | local NAME=$1 58 | local POD=$(kubectl_first_pod "$NAME") 59 | kubectl wait "pods/$POD" --namespace="$WORKSPACE" \ 60 | --for=condition=ready 61 | } 62 | 63 | function kubectl_cluster_ip () { 64 | local NAME=$1 65 | kubectl get services "$NAME" --namespace="$WORKSPACE" \ 66 | --output=jsonpath={.spec.clusterIP} 67 | } 68 | 69 | function kubectl_external_ip () { 70 | local NAME=$1 71 | kubectl get services "$NAME" --namespace="$WORKSPACE" \ 72 | --output=jsonpath={.status.loadBalancer.ingress[0].ip} 73 | } 74 | 75 | function kubectl_control_plane_ip () { 76 | local NAME=$1 77 | local POD=$(kubectl_first_pod "$NAME") 78 | kubectl get pods "$POD" --namespace="$WORKSPACE" \ 79 | --output=jsonpath={.status.podIP} 80 | } 81 | 82 | function kubectl_data_plane_ip () { 83 | local NAME=$1 84 | local POD=$(kubectl_first_pod "$NAME") 85 | kubectl get pods "$POD" --namespace="$WORKSPACE" \ 86 | --output=jsonpath="{.metadata.annotations['k8s\.v1\.cni\.cncf\.io/networks-status']}" | 87 | jq --raw-output .[1].ips[0] 88 | } 89 | 90 | function kubectl_registry_url () { 91 | echo $(kubectl get services registry --namespace=kube-system --output=jsonpath={.spec.clusterIP}):80 92 | } 93 | -------------------------------------------------------------------------------- /knap-provider-bridge/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/heptiolabs/healthcheck" 10 | "github.com/tliron/commonlog" 11 | "github.com/tliron/go-ard" 12 | "github.com/tliron/knap/knap-provider-bridge/server" 13 | "github.com/tliron/knap/provider" 14 | "github.com/tliron/kutil/terminal" 15 | "github.com/tliron/kutil/util" 16 | "k8s.io/klog/v2" 17 | 18 | _ "github.com/tliron/commonlog/simple" 19 | ) 20 | 21 | const SOCKET_NAME = "/tmp/knap-provider-bridge.sock" 22 | const STATE_FILENAME = "/tmp/knap-provider-bridge.state" 23 | const HEALTH_PORT uint = 8086 24 | 25 | var logTo string = "" 26 | var verbose int = 1 27 | var colorize string = "true" 28 | 29 | func main() { 30 | cleanup, err := terminal.ProcessColorizeFlag(colorize) 31 | util.FailOnError(err) 32 | if cleanup != nil { 33 | util.OnExitError(cleanup) 34 | } 35 | if logTo == "" { 36 | commonlog.Configure(verbose, nil) 37 | } else { 38 | commonlog.Configure(verbose, &logTo) 39 | } 40 | if writer := commonlog.GetWriter(); writer != nil { 41 | klog.SetOutput(writer) 42 | } 43 | 44 | if (len(os.Args) == 3) && os.Args[1] == "provide" { 45 | Client(os.Args[2]) 46 | } else { 47 | Server() 48 | } 49 | } 50 | 51 | // Output a CNI configuration to stdout 52 | func Client(name string) { 53 | hints := GetHints() 54 | 55 | client, err := provider.NewClient(SOCKET_NAME, log) 56 | util.FailOnError(err) 57 | defer client.Release() 58 | 59 | cni, err := client.CreateCniConfig(name, hints) 60 | util.FailOnError(err) 61 | fmt.Fprintln(os.Stdout, cni) 62 | } 63 | 64 | func GetHints() map[string]string { 65 | bytes, err := io.ReadAll(os.Stdin) 66 | util.FailOnError(err) 67 | if len(bytes) == 0 { 68 | return nil 69 | } 70 | 71 | hints, _, err := ard.DecodeYAML(util.BytesToString(bytes), false) 72 | util.FailOnError(err) 73 | 74 | var hints_ map[string]string 75 | if hints__, ok := hints.(ard.Map); ok { 76 | hints_ = make(map[string]string) 77 | for key, value := range hints__ { 78 | if key_, ok := key.(string); ok { 79 | if value_, ok := value.(string); ok { 80 | hints_[key_] = value_ 81 | } else { 82 | util.Fail("malformed hints in stdin") 83 | } 84 | } else { 85 | util.Fail("malformed hints in stdin") 86 | } 87 | } 88 | } 89 | 90 | return hints_ 91 | } 92 | 93 | // Run infinitely and provide services for the client 94 | func Server() { 95 | go func() { 96 | log.Infof("starting health monitor on port %d", HEALTH_PORT) 97 | health := healthcheck.NewHandler() 98 | err := http.ListenAndServe(fmt.Sprintf(":%d", HEALTH_PORT), health) 99 | util.FailOnError(err) 100 | }() 101 | 102 | err := server.NewServer(STATE_FILENAME, SOCKET_NAME, log).Start() 103 | util.FailOnError(err) 104 | } 105 | -------------------------------------------------------------------------------- /apis/clientset/versioned/typed/knap.github.com/v1alpha1/knap.github.com_client.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | "net/http" 7 | 8 | "github.com/tliron/knap/apis/clientset/versioned/scheme" 9 | v1alpha1 "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 10 | rest "k8s.io/client-go/rest" 11 | ) 12 | 13 | type KnapV1alpha1Interface interface { 14 | RESTClient() rest.Interface 15 | NetworksGetter 16 | } 17 | 18 | // KnapV1alpha1Client is used to interact with features provided by the knap.github.com group. 19 | type KnapV1alpha1Client struct { 20 | restClient rest.Interface 21 | } 22 | 23 | func (c *KnapV1alpha1Client) Networks(namespace string) NetworkInterface { 24 | return newNetworks(c, namespace) 25 | } 26 | 27 | // NewForConfig creates a new KnapV1alpha1Client for the given config. 28 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 29 | // where httpClient was generated with rest.HTTPClientFor(c). 30 | func NewForConfig(c *rest.Config) (*KnapV1alpha1Client, error) { 31 | config := *c 32 | if err := setConfigDefaults(&config); err != nil { 33 | return nil, err 34 | } 35 | httpClient, err := rest.HTTPClientFor(&config) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return NewForConfigAndClient(&config, httpClient) 40 | } 41 | 42 | // NewForConfigAndClient creates a new KnapV1alpha1Client for the given config and http client. 43 | // Note the http client provided takes precedence over the configured transport values. 44 | func NewForConfigAndClient(c *rest.Config, h *http.Client) (*KnapV1alpha1Client, error) { 45 | config := *c 46 | if err := setConfigDefaults(&config); err != nil { 47 | return nil, err 48 | } 49 | client, err := rest.RESTClientForConfigAndClient(&config, h) 50 | if err != nil { 51 | return nil, err 52 | } 53 | return &KnapV1alpha1Client{client}, nil 54 | } 55 | 56 | // NewForConfigOrDie creates a new KnapV1alpha1Client for the given config and 57 | // panics if there is an error in the config. 58 | func NewForConfigOrDie(c *rest.Config) *KnapV1alpha1Client { 59 | client, err := NewForConfig(c) 60 | if err != nil { 61 | panic(err) 62 | } 63 | return client 64 | } 65 | 66 | // New creates a new KnapV1alpha1Client for the given RESTClient. 67 | func New(c rest.Interface) *KnapV1alpha1Client { 68 | return &KnapV1alpha1Client{c} 69 | } 70 | 71 | func setConfigDefaults(config *rest.Config) error { 72 | gv := v1alpha1.SchemeGroupVersion 73 | config.GroupVersion = &gv 74 | config.APIPath = "/apis" 75 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 76 | 77 | if config.UserAgent == "" { 78 | config.UserAgent = rest.DefaultKubernetesUserAgent() 79 | } 80 | 81 | return nil 82 | } 83 | 84 | // RESTClient returns a RESTClient that is used to communicate 85 | // with API server by this client implementation. 86 | func (c *KnapV1alpha1Client) RESTClient() rest.Interface { 87 | if c == nil { 88 | return nil 89 | } 90 | return c.restClient 91 | } 92 | -------------------------------------------------------------------------------- /apis/listers/knap.github.com/v1alpha1/network.go: -------------------------------------------------------------------------------- 1 | // Code generated by lister-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | v1alpha1 "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 7 | "k8s.io/apimachinery/pkg/api/errors" 8 | "k8s.io/apimachinery/pkg/labels" 9 | "k8s.io/client-go/tools/cache" 10 | ) 11 | 12 | // NetworkLister helps list Networks. 13 | // All objects returned here must be treated as read-only. 14 | type NetworkLister interface { 15 | // List lists all Networks in the indexer. 16 | // Objects returned here must be treated as read-only. 17 | List(selector labels.Selector) (ret []*v1alpha1.Network, err error) 18 | // Networks returns an object that can list and get Networks. 19 | Networks(namespace string) NetworkNamespaceLister 20 | NetworkListerExpansion 21 | } 22 | 23 | // networkLister implements the NetworkLister interface. 24 | type networkLister struct { 25 | indexer cache.Indexer 26 | } 27 | 28 | // NewNetworkLister returns a new NetworkLister. 29 | func NewNetworkLister(indexer cache.Indexer) NetworkLister { 30 | return &networkLister{indexer: indexer} 31 | } 32 | 33 | // List lists all Networks in the indexer. 34 | func (s *networkLister) List(selector labels.Selector) (ret []*v1alpha1.Network, err error) { 35 | err = cache.ListAll(s.indexer, selector, func(m interface{}) { 36 | ret = append(ret, m.(*v1alpha1.Network)) 37 | }) 38 | return ret, err 39 | } 40 | 41 | // Networks returns an object that can list and get Networks. 42 | func (s *networkLister) Networks(namespace string) NetworkNamespaceLister { 43 | return networkNamespaceLister{indexer: s.indexer, namespace: namespace} 44 | } 45 | 46 | // NetworkNamespaceLister helps list and get Networks. 47 | // All objects returned here must be treated as read-only. 48 | type NetworkNamespaceLister interface { 49 | // List lists all Networks in the indexer for a given namespace. 50 | // Objects returned here must be treated as read-only. 51 | List(selector labels.Selector) (ret []*v1alpha1.Network, err error) 52 | // Get retrieves the Network from the indexer for a given namespace and name. 53 | // Objects returned here must be treated as read-only. 54 | Get(name string) (*v1alpha1.Network, error) 55 | NetworkNamespaceListerExpansion 56 | } 57 | 58 | // networkNamespaceLister implements the NetworkNamespaceLister 59 | // interface. 60 | type networkNamespaceLister struct { 61 | indexer cache.Indexer 62 | namespace string 63 | } 64 | 65 | // List lists all Networks in the indexer for a given namespace. 66 | func (s networkNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.Network, err error) { 67 | err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { 68 | ret = append(ret, m.(*v1alpha1.Network)) 69 | }) 70 | return ret, err 71 | } 72 | 73 | // Get retrieves the Network from the indexer for a given namespace and name. 74 | func (s networkNamespaceLister) Get(name string) (*v1alpha1.Network, error) { 75 | obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) 76 | if err != nil { 77 | return nil, err 78 | } 79 | if !exists { 80 | return nil, errors.NewNotFound(v1alpha1.Resource("network"), name) 81 | } 82 | return obj.(*v1alpha1.Network), nil 83 | } 84 | -------------------------------------------------------------------------------- /apis/informers/externalversions/knap.github.com/v1alpha1/network.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | "context" 7 | time "time" 8 | 9 | versioned "github.com/tliron/knap/apis/clientset/versioned" 10 | internalinterfaces "github.com/tliron/knap/apis/informers/externalversions/internalinterfaces" 11 | v1alpha1 "github.com/tliron/knap/apis/listers/knap.github.com/v1alpha1" 12 | knapgithubcomv1alpha1 "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 13 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | runtime "k8s.io/apimachinery/pkg/runtime" 15 | watch "k8s.io/apimachinery/pkg/watch" 16 | cache "k8s.io/client-go/tools/cache" 17 | ) 18 | 19 | // NetworkInformer provides access to a shared informer and lister for 20 | // Networks. 21 | type NetworkInformer interface { 22 | Informer() cache.SharedIndexInformer 23 | Lister() v1alpha1.NetworkLister 24 | } 25 | 26 | type networkInformer struct { 27 | factory internalinterfaces.SharedInformerFactory 28 | tweakListOptions internalinterfaces.TweakListOptionsFunc 29 | namespace string 30 | } 31 | 32 | // NewNetworkInformer constructs a new informer for Network type. 33 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 34 | // one. This reduces memory footprint and number of connections to the server. 35 | func NewNetworkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 36 | return NewFilteredNetworkInformer(client, namespace, resyncPeriod, indexers, nil) 37 | } 38 | 39 | // NewFilteredNetworkInformer constructs a new informer for Network type. 40 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 41 | // one. This reduces memory footprint and number of connections to the server. 42 | func NewFilteredNetworkInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 43 | return cache.NewSharedIndexInformer( 44 | &cache.ListWatch{ 45 | ListFunc: func(options v1.ListOptions) (runtime.Object, error) { 46 | if tweakListOptions != nil { 47 | tweakListOptions(&options) 48 | } 49 | return client.KnapV1alpha1().Networks(namespace).List(context.TODO(), options) 50 | }, 51 | WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { 52 | if tweakListOptions != nil { 53 | tweakListOptions(&options) 54 | } 55 | return client.KnapV1alpha1().Networks(namespace).Watch(context.TODO(), options) 56 | }, 57 | }, 58 | &knapgithubcomv1alpha1.Network{}, 59 | resyncPeriod, 60 | indexers, 61 | ) 62 | } 63 | 64 | func (f *networkInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 65 | return NewFilteredNetworkInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 66 | } 67 | 68 | func (f *networkInformer) Informer() cache.SharedIndexInformer { 69 | return f.factory.InformerFor(&knapgithubcomv1alpha1.Network{}, f.defaultInformer) 70 | } 71 | 72 | func (f *networkInformer) Lister() v1alpha1.NetworkLister { 73 | return v1alpha1.NewNetworkLister(f.Informer().GetIndexer()) 74 | } 75 | -------------------------------------------------------------------------------- /client/wait.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tliron/kutil/kubernetes" 7 | apps "k8s.io/api/apps/v1" 8 | core "k8s.io/api/core/v1" 9 | errorspkg "k8s.io/apimachinery/pkg/api/errors" 10 | meta "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | waitpkg "k8s.io/apimachinery/pkg/util/wait" 12 | ) 13 | 14 | var timeout = 60 * time.Second 15 | 16 | func (self *Client) WaitForPod(namespace string, appName string) (*core.Pod, error) { 17 | self.Log.Infof("waiting for a pod for app %q", appName) 18 | 19 | var pod *core.Pod 20 | err := waitpkg.PollImmediate(time.Second, timeout, func() (bool, error) { 21 | if pods, err := kubernetes.GetPods(self.Context, self.Kubernetes, namespace, appName); err == nil { 22 | for _, pod_ := range pods.Items { 23 | for _, containerStatus := range pod_.Status.ContainerStatuses { 24 | if containerStatus.Ready { 25 | self.Log.Infof("container %q ready for pod %q", containerStatus.Name, pod_.Name) 26 | } else { 27 | return false, nil 28 | } 29 | } 30 | 31 | for _, condition := range pod_.Status.Conditions { 32 | switch condition.Type { 33 | case core.ContainersReady: 34 | if condition.Status == core.ConditionTrue { 35 | pod = &pod_ 36 | return true, nil 37 | } 38 | } 39 | } 40 | } 41 | return false, nil 42 | } else if errorspkg.IsNotFound(err) { 43 | return false, nil 44 | } else { 45 | return false, err 46 | } 47 | }) 48 | 49 | if (err == nil) && (pod != nil) { 50 | self.Log.Infof("a pod is available for app %q", appName) 51 | return pod, nil 52 | } else { 53 | return nil, err 54 | } 55 | } 56 | 57 | func (self *Client) WaitForDeployment(namespace string, appName string) (*apps.Deployment, error) { 58 | self.Log.Infof("waiting for a deployment for app %q", appName) 59 | 60 | var deployment *apps.Deployment 61 | err := waitpkg.PollImmediate(time.Second, timeout, func() (bool, error) { 62 | var err error 63 | if deployment, err = self.Kubernetes.AppsV1().Deployments(namespace).Get(self.Context, appName, meta.GetOptions{}); err == nil { 64 | for _, condition := range deployment.Status.Conditions { 65 | switch condition.Type { 66 | case apps.DeploymentAvailable: 67 | if condition.Status == core.ConditionTrue { 68 | return true, nil 69 | } 70 | case apps.DeploymentReplicaFailure: 71 | if condition.Status == core.ConditionTrue { 72 | self.Log.Infof("replica failure for a deployment for app %q", appName) 73 | } 74 | } 75 | } 76 | return false, nil 77 | } else { 78 | return false, err 79 | } 80 | }) 81 | 82 | if err == nil { 83 | self.Log.Infof("a deployment is available for app %q", appName) 84 | //if err := self.waitForPods(appName, deployment); err == nil { 85 | return deployment, nil 86 | /*} else { 87 | return nil, err 88 | }*/ 89 | } else { 90 | return nil, err 91 | } 92 | } 93 | 94 | func (self *Client) WaitForDeletion(name string, condition func() bool) { 95 | err := waitpkg.PollImmediate(time.Second, timeout, func() (bool, error) { 96 | self.Log.Infof("waiting for %s to delete", name) 97 | return !condition(), nil 98 | }) 99 | if err != nil { 100 | self.Log.Warningf("error while waiting for %s to delete: %s", name, err.Error()) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /apis/clientset/versioned/clientset.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package versioned 4 | 5 | import ( 6 | "fmt" 7 | "net/http" 8 | 9 | knapv1alpha1 "github.com/tliron/knap/apis/clientset/versioned/typed/knap.github.com/v1alpha1" 10 | discovery "k8s.io/client-go/discovery" 11 | rest "k8s.io/client-go/rest" 12 | flowcontrol "k8s.io/client-go/util/flowcontrol" 13 | ) 14 | 15 | type Interface interface { 16 | Discovery() discovery.DiscoveryInterface 17 | KnapV1alpha1() knapv1alpha1.KnapV1alpha1Interface 18 | } 19 | 20 | // Clientset contains the clients for groups. 21 | type Clientset struct { 22 | *discovery.DiscoveryClient 23 | knapV1alpha1 *knapv1alpha1.KnapV1alpha1Client 24 | } 25 | 26 | // KnapV1alpha1 retrieves the KnapV1alpha1Client 27 | func (c *Clientset) KnapV1alpha1() knapv1alpha1.KnapV1alpha1Interface { 28 | return c.knapV1alpha1 29 | } 30 | 31 | // Discovery retrieves the DiscoveryClient 32 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 33 | if c == nil { 34 | return nil 35 | } 36 | return c.DiscoveryClient 37 | } 38 | 39 | // NewForConfig creates a new Clientset for the given config. 40 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 41 | // NewForConfig will generate a rate-limiter in configShallowCopy. 42 | // NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), 43 | // where httpClient was generated with rest.HTTPClientFor(c). 44 | func NewForConfig(c *rest.Config) (*Clientset, error) { 45 | configShallowCopy := *c 46 | 47 | if configShallowCopy.UserAgent == "" { 48 | configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() 49 | } 50 | 51 | // share the transport between all clients 52 | httpClient, err := rest.HTTPClientFor(&configShallowCopy) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return NewForConfigAndClient(&configShallowCopy, httpClient) 58 | } 59 | 60 | // NewForConfigAndClient creates a new Clientset for the given config and http client. 61 | // Note the http client provided takes precedence over the configured transport values. 62 | // If config's RateLimiter is not set and QPS and Burst are acceptable, 63 | // NewForConfigAndClient will generate a rate-limiter in configShallowCopy. 64 | func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { 65 | configShallowCopy := *c 66 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 67 | if configShallowCopy.Burst <= 0 { 68 | return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") 69 | } 70 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 71 | } 72 | 73 | var cs Clientset 74 | var err error 75 | cs.knapV1alpha1, err = knapv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) 81 | if err != nil { 82 | return nil, err 83 | } 84 | return &cs, nil 85 | } 86 | 87 | // NewForConfigOrDie creates a new Clientset for the given config and 88 | // panics if there is an error in the config. 89 | func NewForConfigOrDie(c *rest.Config) *Clientset { 90 | cs, err := NewForConfig(c) 91 | if err != nil { 92 | panic(err) 93 | } 94 | return cs 95 | } 96 | 97 | // New creates a new Clientset for the given RESTClient. 98 | func New(c rest.Interface) *Clientset { 99 | var cs Clientset 100 | cs.knapV1alpha1 = knapv1alpha1.New(c) 101 | 102 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 103 | return &cs 104 | } 105 | -------------------------------------------------------------------------------- /examples/hello-world/explicit.yaml: -------------------------------------------------------------------------------- 1 | # Two network attachment definitions 2 | # Each a separate bridge network with an explicit CNI configuration 3 | 4 | # This is the direct use of Multus 5 | 6 | # Note that in order to write these CNI configurations you would need to know the network layout 7 | # of the *specific* node on the cluster, as well as know about any other CNI configurations 8 | # created on this *specific* node 9 | 10 | apiVersion: k8s.cni.cncf.io/v1 11 | kind: NetworkAttachmentDefinition 12 | 13 | metadata: 14 | name: explicit-a 15 | labels: 16 | app.kubernetes.io/name: explicit-a 17 | app.kubernetes.io/component: network 18 | 19 | spec: 20 | config: '{ 21 | "cniVersion": "0.3.1", 22 | "type": "bridge", 23 | "name": "explicit-a", 24 | "bridge": "explicit-a", 25 | "isDefaultGateway": true, 26 | "ipMasq": true, 27 | "promiscMode": true, 28 | "ipam": { 29 | "type": "host-local", 30 | "subnet": "192.168.2.0/24", 31 | "rangeStart": "192.168.2.2", 32 | "rangeEnd": "192.168.2.254", 33 | "routes": [ 34 | { "dst": "0.0.0.0/0" } 35 | ], 36 | "gateway": "192.168.2.1" 37 | } 38 | }' 39 | 40 | --- 41 | 42 | apiVersion: k8s.cni.cncf.io/v1 43 | kind: NetworkAttachmentDefinition 44 | 45 | metadata: 46 | name: explicit-b 47 | labels: 48 | app.kubernetes.io/name: explicit-b 49 | app.kubernetes.io/component: network 50 | 51 | spec: 52 | config: '{ 53 | "cniVersion": "0.3.1", 54 | "type": "bridge", 55 | "name": "explicit-b", 56 | "bridge": "explicit-b", 57 | "isDefaultGateway": true, 58 | "ipMasq": true, 59 | "promiscMode": true, 60 | "ipam": { 61 | "type": "host-local", 62 | "subnet": "192.168.3.0/24", 63 | "rangeStart": "192.168.3.2", 64 | "rangeEnd": "192.168.3.254", 65 | "routes": [ 66 | { "dst": "0.0.0.0/0" } 67 | ], 68 | "gateway": "192.168.3.1" 69 | } 70 | }' 71 | 72 | --- 73 | 74 | # Two deployments 75 | # The first is attached just to network A 76 | # The second is attached to both networks 77 | 78 | apiVersion: apps/v1 79 | kind: Deployment 80 | 81 | metadata: 82 | name: knap-hello-world1 83 | labels: 84 | app.kubernetes.io/name: knap-hello-world1 85 | app.kubernetes.io/component: deployment 86 | 87 | spec: 88 | replicas: 1 89 | selector: 90 | matchLabels: 91 | app.kubernetes.io/name: knap-hello-world1 92 | app.kubernetes.io/component: deployment 93 | template: 94 | metadata: 95 | labels: 96 | app.kubernetes.io/name: knap-hello-world1 97 | app.kubernetes.io/component: deployment 98 | annotations: 99 | k8s.v1.cni.cncf.io/networks: explicit-a 100 | spec: 101 | containers: 102 | - name: hello-world 103 | image: docker.io/paulbouwer/hello-kubernetes:1.8 104 | imagePullPolicy: Always 105 | 106 | --- 107 | 108 | apiVersion: apps/v1 109 | kind: Deployment 110 | 111 | metadata: 112 | name: knap-hello-world2 113 | labels: 114 | app.kubernetes.io/name: knap-hello-world2 115 | app.kubernetes.io/component: deployment 116 | 117 | spec: 118 | replicas: 1 119 | selector: 120 | matchLabels: 121 | app.kubernetes.io/name: knap-hello-world2 122 | app.kubernetes.io/component: deployment 123 | template: 124 | metadata: 125 | labels: 126 | app.kubernetes.io/name: knap-hello-world2 127 | app.kubernetes.io/component: deployment 128 | annotations: 129 | k8s.v1.cni.cncf.io/networks: explicit-a, explicit-b 130 | spec: 131 | containers: 132 | - name: hello-world 133 | image: docker.io/paulbouwer/hello-kubernetes:1.8 134 | imagePullPolicy: Always 135 | -------------------------------------------------------------------------------- /resources/knap.github.com/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | // +build !ignore_autogenerated 3 | 4 | // Code generated by deepcopy-gen. DO NOT EDIT. 5 | 6 | package v1alpha1 7 | 8 | import ( 9 | runtime "k8s.io/apimachinery/pkg/runtime" 10 | ) 11 | 12 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 13 | func (in *Network) DeepCopyInto(out *Network) { 14 | *out = *in 15 | out.TypeMeta = in.TypeMeta 16 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 17 | in.Spec.DeepCopyInto(&out.Spec) 18 | in.Status.DeepCopyInto(&out.Status) 19 | return 20 | } 21 | 22 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Network. 23 | func (in *Network) DeepCopy() *Network { 24 | if in == nil { 25 | return nil 26 | } 27 | out := new(Network) 28 | in.DeepCopyInto(out) 29 | return out 30 | } 31 | 32 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 33 | func (in *Network) DeepCopyObject() runtime.Object { 34 | if c := in.DeepCopy(); c != nil { 35 | return c 36 | } 37 | return nil 38 | } 39 | 40 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 41 | func (in *NetworkList) DeepCopyInto(out *NetworkList) { 42 | *out = *in 43 | out.TypeMeta = in.TypeMeta 44 | in.ListMeta.DeepCopyInto(&out.ListMeta) 45 | if in.Items != nil { 46 | in, out := &in.Items, &out.Items 47 | *out = make([]Network, len(*in)) 48 | for i := range *in { 49 | (*in)[i].DeepCopyInto(&(*out)[i]) 50 | } 51 | } 52 | return 53 | } 54 | 55 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkList. 56 | func (in *NetworkList) DeepCopy() *NetworkList { 57 | if in == nil { 58 | return nil 59 | } 60 | out := new(NetworkList) 61 | in.DeepCopyInto(out) 62 | return out 63 | } 64 | 65 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 66 | func (in *NetworkList) DeepCopyObject() runtime.Object { 67 | if c := in.DeepCopy(); c != nil { 68 | return c 69 | } 70 | return nil 71 | } 72 | 73 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 74 | func (in *NetworkSpec) DeepCopyInto(out *NetworkSpec) { 75 | *out = *in 76 | if in.Hints != nil { 77 | in, out := &in.Hints, &out.Hints 78 | *out = make(map[string]string, len(*in)) 79 | for key, val := range *in { 80 | (*out)[key] = val 81 | } 82 | } 83 | return 84 | } 85 | 86 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkSpec. 87 | func (in *NetworkSpec) DeepCopy() *NetworkSpec { 88 | if in == nil { 89 | return nil 90 | } 91 | out := new(NetworkSpec) 92 | in.DeepCopyInto(out) 93 | return out 94 | } 95 | 96 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 97 | func (in *NetworkStatus) DeepCopyInto(out *NetworkStatus) { 98 | *out = *in 99 | if in.NetworkAttachmentDefinitions != nil { 100 | in, out := &in.NetworkAttachmentDefinitions, &out.NetworkAttachmentDefinitions 101 | *out = make([]string, len(*in)) 102 | copy(*out, *in) 103 | } 104 | if in.Deployments != nil { 105 | in, out := &in.Deployments, &out.Deployments 106 | *out = make([]string, len(*in)) 107 | copy(*out, *in) 108 | } 109 | return 110 | } 111 | 112 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkStatus. 113 | func (in *NetworkStatus) DeepCopy() *NetworkStatus { 114 | if in == nil { 115 | return nil 116 | } 117 | out := new(NetworkStatus) 118 | in.DeepCopyInto(out) 119 | return out 120 | } 121 | -------------------------------------------------------------------------------- /resources/knap.github.com/v1alpha1/network.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "fmt" 5 | 6 | group "github.com/tliron/knap/resources/knap.github.com" 7 | apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 8 | meta "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | var NetworkGVK = SchemeGroupVersion.WithKind(NetworkKind) 12 | 13 | const ( 14 | NetworkKind = "Network" 15 | NetworkListKind = "NetworkList" 16 | 17 | NetworkSingular = "network" 18 | NetworkPlural = "networks" 19 | NetworkShortName = "nw" 20 | 21 | NetworkAnnotation = "knap.github.com/networks" 22 | ) 23 | 24 | // 25 | // Network 26 | // 27 | 28 | // +genclient 29 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 30 | type Network struct { 31 | meta.TypeMeta `json:",inline"` 32 | meta.ObjectMeta `json:"metadata,omitempty"` 33 | 34 | Spec NetworkSpec `json:"spec"` 35 | Status NetworkStatus `json:"status"` 36 | } 37 | 38 | type NetworkSpec struct { 39 | Provider string `json:"provider"` 40 | Hints map[string]string `json:"hints"` 41 | } 42 | 43 | type NetworkStatus struct { 44 | NetworkAttachmentDefinitions []string `json:"networkAttachmentDefinitions"` 45 | Deployments []string `json:"deployments"` 46 | } 47 | 48 | // 49 | // NetworkList 50 | // 51 | 52 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 53 | type NetworkList struct { 54 | meta.TypeMeta `json:",inline"` 55 | meta.ListMeta `json:"metadata"` 56 | 57 | Items []Network `json:"items"` 58 | } 59 | 60 | // 61 | // NetworkCustomResourceDefinition 62 | // 63 | 64 | // See: assets/custom-resource-definitions.yaml 65 | 66 | var NetworkResourcesName = fmt.Sprintf("%s.%s", NetworkPlural, group.GroupName) 67 | 68 | var NetworkCustomResourceDefinition = apiextensions.CustomResourceDefinition{ 69 | ObjectMeta: meta.ObjectMeta{ 70 | Name: NetworkResourcesName, 71 | }, 72 | Spec: apiextensions.CustomResourceDefinitionSpec{ 73 | Group: group.GroupName, 74 | Names: apiextensions.CustomResourceDefinitionNames{ 75 | Singular: NetworkSingular, 76 | Plural: NetworkPlural, 77 | Kind: NetworkKind, 78 | ListKind: NetworkListKind, 79 | ShortNames: []string{ 80 | NetworkShortName, 81 | }, 82 | Categories: []string{ 83 | "all", // will appear in "kubectl get all" 84 | }, 85 | }, 86 | Scope: apiextensions.NamespaceScoped, 87 | Versions: []apiextensions.CustomResourceDefinitionVersion{ 88 | { 89 | Name: Version, 90 | Served: true, 91 | Storage: true, // one and only one version must be marked with storage=true 92 | Subresources: &apiextensions.CustomResourceSubresources{ // requires CustomResourceSubresources feature gate enabled 93 | Status: &apiextensions.CustomResourceSubresourceStatus{}, 94 | }, 95 | Schema: &apiextensions.CustomResourceValidation{ 96 | OpenAPIV3Schema: &apiextensions.JSONSchemaProps{ 97 | Type: "object", 98 | Required: []string{"spec"}, 99 | Properties: map[string]apiextensions.JSONSchemaProps{ 100 | "spec": { 101 | Type: "object", 102 | Required: []string{"provider"}, 103 | Properties: map[string]apiextensions.JSONSchemaProps{ 104 | "provider": { 105 | Type: "string", 106 | }, 107 | "hints": { 108 | Type: "object", 109 | Nullable: true, 110 | AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{ 111 | Schema: &apiextensions.JSONSchemaProps{ 112 | Type: "string", 113 | }, 114 | }, 115 | }, 116 | }, 117 | }, 118 | "status": { 119 | Type: "object", 120 | Properties: map[string]apiextensions.JSONSchemaProps{ 121 | "networkAttachmentDefinitions": { 122 | Type: "array", 123 | Nullable: true, 124 | Items: &apiextensions.JSONSchemaPropsOrArray{ 125 | Schema: &apiextensions.JSONSchemaProps{ 126 | Type: "string", 127 | }, 128 | }, 129 | }, 130 | "deployments": { 131 | Type: "array", 132 | Nullable: true, 133 | Items: &apiextensions.JSONSchemaPropsOrArray{ 134 | Schema: &apiextensions.JSONSchemaProps{ 135 | Type: "string", 136 | }, 137 | }, 138 | }, 139 | }, 140 | }, 141 | }, 142 | }, 143 | }, 144 | }, 145 | }, 146 | }, 147 | } 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *Work in progress* 2 | 3 | Knap 4 | ==== 5 | 6 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/tliron/knap)](https://goreportcard.com/report/github.com/tliron/knap) 8 | 9 | The Kubernetes Network Attachment Provider enables "network-as-a-service" for Kubernetes. 10 | 11 | This [Kubernetes](https://kubernetes.io/) 12 | [operator](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) 13 | introduces a new custom resource, "Network", that is used to create and manage 14 | ["NetworkAttachmentDefinition" resources](https://github.com/k8snetworkplumbingwg/network-attachment-definition-client/blob/master/artifacts/networks-crd.yaml), 15 | which are in turn used by 16 | [Multus CNI](https://github.com/intel/multus-cni) 17 | to attach extra network interfaces to pods and 18 | [KubeVirt](https://kubevirt.io/) virtual machines. 19 | 20 | 21 | Rationale 22 | --------- 23 | 24 | Kubernetes intentionally has little to say about networking, requiring only a single cluster-wide 25 | network for the management platform to be able to communicate with pods, i.e. "the control plane". 26 | While this single network can often be good enough for other communication needs, such as 27 | connections to databases, serving HTTP to a loadbalancer, and can even be used in multi-cluster 28 | scenarios, for many use cases it is insufficient. Additional networks might be required for access 29 | to high performance features such as hardware acceleration, as well as enforcing isolation and 30 | better security. Moreover, network functions (CNFs and VNFs) running in Kubernetes might be used 31 | to connect such networks together via routing, switching, etc., i.e. "the data plane". 32 | 33 | Multus is a great solution for adding such extra networks. However, using it directly requires 34 | administrative knowledge of the cluster's and even the particular node's infrastructure in order to 35 | write valid 36 | [CNI](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/) 37 | configurations. Moreover, CNI configurations within a cluster must be validated against each other 38 | to ensure that there are no conflicts. This makes it challenging to create portable applications and 39 | deploy them at scale. 40 | 41 | Knap bridges this gap by separating the concerns. Knap itself contains the contextual administrative 42 | knowledge and can generate CNI configurations for Multus as needed. Application developers need only 43 | request a network according to the features they require. 44 | 45 | 46 | Plugins 47 | ------- 48 | 49 | Because one size cannot fit all in networking, Knap is designed with a plugin architecture, allowing 50 | custom plugins to be created for specific and proprietary network management needs. 51 | 52 | Plugins are implemented as independent pods and can be created and packaged independently of Knap. 53 | They can be as complex as necessary. 54 | 55 | Note that a Knap plugin could be backed by network functions running within the cluster, and that 56 | those network functions could in turn be using Knap (via other plugins) to gain access to other 57 | networks. 58 | 59 | 60 | FAQ 61 | --- 62 | 63 | ### Is this "Neutron for Kubernetes"? 64 | 65 | If the description of Knap reads a bit like a description of the 66 | [Neutron component in OpenStack](https://docs.openstack.org/neutron/latest/), it is intentional. 67 | The goal is to bring Kubernetes application development in line with what we have been doing in 68 | "legacy" cloud platforms while staying true to cloud-native orchestration principles. 69 | 70 | For example, we absolutely do not want to mimic Neutron's isolation-centric opinions. In OpenStack 71 | any application can create any IP subnet and allocation pool and indeed must specify it. This 72 | requires Neutron plugins to have isolation support built in, e.g. via overlay networks, VLAN 73 | bridging, etc. By contrast, with Knap you may very well not have any isolation features for a 74 | particular plugin and must query for information such as IP ranges and gateways after the network 75 | is provided. 76 | 77 | ### How do you pronounce "Knap"? 78 | 79 | Like "nap". The "k" is silent. If emphasis of the "k" is necessary, try saying: "kay-nap". 80 | 81 | According to the [Merriam-Webster Dictionary](https://www.merriam-webster.com/dictionary/knap) 82 | "knap" as a noun means "a crest of a small hill" and as a verb means to "to break with a quick 83 | blow". If you break things please fix them! 84 | 85 | 86 | See Also 87 | -------- 88 | 89 | * [nmstate](https://github.com/nmstate/kubernetes-nmstate) 90 | * [ENO](https://github.com/Nordix/eno) 91 | -------------------------------------------------------------------------------- /controller/controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | contextpkg "context" 5 | "time" 6 | 7 | netpkg "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/client/clientset/versioned" 8 | "github.com/tliron/commonlog" 9 | knapclientset "github.com/tliron/knap/apis/clientset/versioned" 10 | knapinformers "github.com/tliron/knap/apis/informers/externalversions" 11 | knaplisters "github.com/tliron/knap/apis/listers/knap.github.com/v1alpha1" 12 | clientpkg "github.com/tliron/knap/client" 13 | knapresources "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 14 | kubernetesutil "github.com/tliron/kutil/kubernetes" 15 | apiextensionspkg "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 16 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 17 | "k8s.io/client-go/informers" 18 | "k8s.io/client-go/kubernetes" 19 | restpkg "k8s.io/client-go/rest" 20 | "k8s.io/client-go/tools/record" 21 | ) 22 | 23 | // 24 | // Controller 25 | // 26 | 27 | type Controller struct { 28 | Config *restpkg.Config 29 | Kubernetes kubernetes.Interface 30 | Knap knapclientset.Interface 31 | REST restpkg.Interface 32 | Client *clientpkg.Client 33 | StopChannel <-chan struct{} 34 | 35 | Processors *kubernetesutil.Processors 36 | Events record.EventRecorder 37 | 38 | KubernetesInformerFactory informers.SharedInformerFactory 39 | KnapInformerFactory knapinformers.SharedInformerFactory 40 | 41 | Networks knaplisters.NetworkLister 42 | 43 | Context contextpkg.Context 44 | Log commonlog.Logger 45 | } 46 | 47 | func NewController(toolName string, cluster bool, namespace string, kubernetes kubernetes.Interface, apiExtensions apiextensionspkg.Interface, net netpkg.Interface, knap knapclientset.Interface, config *restpkg.Config, informerResyncPeriod time.Duration, stopChannel <-chan struct{}) *Controller { 48 | context := contextpkg.TODO() 49 | 50 | if cluster { 51 | namespace = "" 52 | } 53 | 54 | log := commonlog.GetLoggerf("%s.controller", toolName) 55 | 56 | self := Controller{ 57 | Config: config, 58 | Kubernetes: kubernetes, 59 | Knap: knap, 60 | REST: kubernetes.CoreV1().RESTClient(), 61 | StopChannel: stopChannel, 62 | Processors: kubernetesutil.NewProcessors(toolName), 63 | Events: kubernetesutil.CreateEventRecorder(kubernetes, "Knap", log), 64 | Context: context, 65 | Log: log, 66 | } 67 | 68 | self.Client = &clientpkg.Client{ 69 | Config: config, 70 | Kubernetes: kubernetes, 71 | APIExtensions: apiExtensions, 72 | Net: net, 73 | Knap: knap, 74 | Cluster: cluster, 75 | Namespace: namespace, 76 | NamePrefix: NamePrefix, 77 | PartOf: PartOf, 78 | ManagedBy: ManagedBy, 79 | OperatorImageName: OperatorImageName, 80 | Context: contextpkg.TODO(), 81 | Log: log, 82 | } 83 | 84 | if cluster { 85 | self.KubernetesInformerFactory = informers.NewSharedInformerFactory(kubernetes, informerResyncPeriod) 86 | self.KnapInformerFactory = knapinformers.NewSharedInformerFactory(knap, informerResyncPeriod) 87 | } else { 88 | self.KubernetesInformerFactory = informers.NewSharedInformerFactoryWithOptions(kubernetes, informerResyncPeriod, informers.WithNamespace(namespace)) 89 | self.KnapInformerFactory = knapinformers.NewSharedInformerFactoryWithOptions(knap, informerResyncPeriod, knapinformers.WithNamespace(namespace)) 90 | } 91 | 92 | // Informers 93 | networkInformer := self.KnapInformerFactory.Knap().V1alpha1().Networks() 94 | 95 | // Listers 96 | self.Networks = networkInformer.Lister() 97 | 98 | // Processors 99 | 100 | processorPeriod := 5 * time.Second 101 | 102 | self.Processors.Add(knapresources.NetworkGVK, kubernetesutil.NewProcessor( 103 | toolName, 104 | "networks", 105 | networkInformer.Informer(), 106 | processorPeriod, 107 | func(name string, namespace string) (interface{}, error) { 108 | return self.Client.GetNetwork(namespace, name) 109 | }, 110 | func(object interface{}) (bool, error) { 111 | return self.processNetwork(object.(*knapresources.Network)) 112 | }, 113 | )) 114 | 115 | return &self 116 | } 117 | 118 | func (self *Controller) Run(concurrency uint, startup func()) error { 119 | defer utilruntime.HandleCrash() 120 | 121 | self.Log.Info("starting informer factories") 122 | self.KubernetesInformerFactory.Start(self.StopChannel) 123 | self.KnapInformerFactory.Start(self.StopChannel) 124 | 125 | self.Log.Info("waiting for processor informer caches to sync") 126 | utilruntime.HandleError(self.Processors.WaitForCacheSync(self.StopChannel)) 127 | 128 | self.Log.Infof("starting processors (concurrency=%d)", concurrency) 129 | self.Processors.Start(concurrency, self.StopChannel) 130 | defer self.Processors.ShutDown() 131 | 132 | if startup != nil { 133 | go startup() 134 | } 135 | 136 | <-self.StopChannel 137 | 138 | self.Log.Info("shutting down") 139 | 140 | return nil 141 | } 142 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tliron/knap 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/gofrs/flock v0.8.1 7 | github.com/golang/protobuf v1.5.3 8 | github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb 9 | github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0 10 | github.com/spf13/cobra v1.7.0 11 | github.com/tliron/commonlog v0.1.1 12 | github.com/tliron/go-ard v0.1.1 13 | github.com/tliron/kutil v0.2.6 14 | google.golang.org/grpc v1.56.1 15 | google.golang.org/protobuf v1.31.0 16 | k8s.io/api v0.27.3 17 | k8s.io/apiextensions-apiserver v0.27.3 18 | k8s.io/apimachinery v0.27.3 19 | k8s.io/client-go v0.27.3 20 | k8s.io/klog/v2 v2.100.1 21 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 22 | ) 23 | 24 | require ( 25 | github.com/Microsoft/go-winio v0.6.1 // indirect 26 | github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect 27 | github.com/acomagu/bufpipe v1.0.4 // indirect 28 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect 29 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 30 | github.com/beevik/etree v1.2.0 // indirect 31 | github.com/beorn7/perks v1.0.1 // indirect 32 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 33 | github.com/cloudflare/circl v1.3.3 // indirect 34 | github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect 35 | github.com/davecgh/go-spew v1.1.1 // indirect 36 | github.com/docker/cli v23.0.5+incompatible // indirect 37 | github.com/docker/distribution v2.8.1+incompatible // indirect 38 | github.com/docker/docker v23.0.5+incompatible // indirect 39 | github.com/docker/docker-credential-helpers v0.7.0 // indirect 40 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 41 | github.com/emirpasic/gods v1.18.1 // indirect 42 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 43 | github.com/fatih/color v1.15.0 // indirect 44 | github.com/fxamacker/cbor/v2 v2.4.0 // indirect 45 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 46 | github.com/go-git/go-billy/v5 v5.4.1 // indirect 47 | github.com/go-git/go-git/v5 v5.7.0 // indirect 48 | github.com/go-logr/logr v1.2.3 // indirect 49 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 50 | github.com/go-openapi/jsonreference v0.20.1 // indirect 51 | github.com/go-openapi/swag v0.22.3 // indirect 52 | github.com/goccy/go-yaml v1.11.0 // indirect 53 | github.com/gogo/protobuf v1.3.2 // indirect 54 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 55 | github.com/google/btree v1.0.1 // indirect 56 | github.com/google/gnostic v0.5.7-v3refs // indirect 57 | github.com/google/go-cmp v0.5.9 // indirect 58 | github.com/google/go-containerregistry v0.15.2 // indirect 59 | github.com/google/gofuzz v1.1.0 // indirect 60 | github.com/google/uuid v1.3.0 // indirect 61 | github.com/hashicorp/errwrap v1.0.0 // indirect 62 | github.com/hashicorp/go-immutable-radix v1.0.0 // indirect 63 | github.com/hashicorp/go-msgpack v0.5.3 // indirect 64 | github.com/hashicorp/go-multierror v1.0.0 // indirect 65 | github.com/hashicorp/go-sockaddr v1.0.0 // indirect 66 | github.com/hashicorp/golang-lru v0.5.1 // indirect 67 | github.com/hashicorp/memberlist v0.5.0 // indirect 68 | github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect 69 | github.com/iancoleman/strcase v0.2.0 // indirect 70 | github.com/imdario/mergo v0.3.15 // indirect 71 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 72 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 73 | github.com/josharian/intern v1.0.0 // indirect 74 | github.com/json-iterator/go v1.1.12 // indirect 75 | github.com/kevinburke/ssh_config v1.2.0 // indirect 76 | github.com/klauspost/compress v1.16.6 // indirect 77 | github.com/klauspost/pgzip v1.2.6 // indirect 78 | github.com/kortschak/utter v1.5.0 // indirect 79 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 80 | github.com/mailru/easyjson v0.7.7 // indirect 81 | github.com/mattn/go-colorable v0.1.13 // indirect 82 | github.com/mattn/go-isatty v0.0.18 // indirect 83 | github.com/mattn/go-runewidth v0.0.14 // indirect 84 | github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect 85 | github.com/miekg/dns v1.1.26 // indirect 86 | github.com/mitchellh/go-homedir v1.1.0 // indirect 87 | github.com/moby/spdystream v0.2.0 // indirect 88 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 89 | github.com/modern-go/reflect2 v1.0.2 // indirect 90 | github.com/muesli/termenv v0.15.2 // indirect 91 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 92 | github.com/opencontainers/go-digest v1.0.0 // indirect 93 | github.com/opencontainers/image-spec v1.1.0-rc3 // indirect 94 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect 95 | github.com/pjbgf/sha1cd v0.3.0 // indirect 96 | github.com/pkg/errors v0.9.1 // indirect 97 | github.com/prometheus/client_golang v1.14.0 // indirect 98 | github.com/prometheus/client_model v0.3.0 // indirect 99 | github.com/prometheus/common v0.37.0 // indirect 100 | github.com/prometheus/procfs v0.8.0 // indirect 101 | github.com/rivo/uniseg v0.2.0 // indirect 102 | github.com/sasha-s/go-deadlock v0.3.1 // indirect 103 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect 104 | github.com/segmentio/ksuid v1.0.4 // indirect 105 | github.com/sergi/go-diff v1.1.0 // indirect 106 | github.com/sirupsen/logrus v1.9.0 // indirect 107 | github.com/skeema/knownhosts v1.1.1 // indirect 108 | github.com/spf13/pflag v1.0.5 // indirect 109 | github.com/tliron/exturl v0.2.4 // indirect 110 | github.com/tliron/yamlkeys v1.3.6 // indirect 111 | github.com/vbatts/tar-split v0.11.3 // indirect 112 | github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect 113 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 114 | github.com/x448/float16 v0.8.4 // indirect 115 | github.com/xanzy/ssh-agent v0.3.3 // indirect 116 | golang.org/x/crypto v0.10.0 // indirect 117 | golang.org/x/mod v0.10.0 // indirect 118 | golang.org/x/net v0.10.0 // indirect 119 | golang.org/x/oauth2 v0.7.0 // indirect 120 | golang.org/x/sync v0.1.0 // indirect 121 | golang.org/x/sys v0.9.0 // indirect 122 | golang.org/x/term v0.9.0 // indirect 123 | golang.org/x/text v0.10.0 // indirect 124 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 125 | golang.org/x/tools v0.8.0 // indirect 126 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect 127 | google.golang.org/appengine v1.6.7 // indirect 128 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect 129 | gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect 130 | gopkg.in/inf.v0 v0.9.1 // indirect 131 | gopkg.in/warnings.v0 v0.1.2 // indirect 132 | gopkg.in/yaml.v2 v2.4.0 // indirect 133 | gopkg.in/yaml.v3 v3.0.1 // indirect 134 | k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect 135 | k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect 136 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 137 | sigs.k8s.io/yaml v1.3.0 // indirect 138 | ) 139 | -------------------------------------------------------------------------------- /apis/clientset/versioned/typed/knap.github.com/v1alpha1/fake/fake_network.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package fake 4 | 5 | import ( 6 | "context" 7 | json "encoding/json" 8 | "fmt" 9 | 10 | knapgithubcomv1alpha1 "github.com/tliron/knap/apis/applyconfiguration/knap.github.com/v1alpha1" 11 | v1alpha1 "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 12 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | labels "k8s.io/apimachinery/pkg/labels" 14 | types "k8s.io/apimachinery/pkg/types" 15 | watch "k8s.io/apimachinery/pkg/watch" 16 | testing "k8s.io/client-go/testing" 17 | ) 18 | 19 | // FakeNetworks implements NetworkInterface 20 | type FakeNetworks struct { 21 | Fake *FakeKnapV1alpha1 22 | ns string 23 | } 24 | 25 | var networksResource = v1alpha1.SchemeGroupVersion.WithResource("networks") 26 | 27 | var networksKind = v1alpha1.SchemeGroupVersion.WithKind("Network") 28 | 29 | // Get takes name of the network, and returns the corresponding network object, and an error if there is any. 30 | func (c *FakeNetworks) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Network, err error) { 31 | obj, err := c.Fake. 32 | Invokes(testing.NewGetAction(networksResource, c.ns, name), &v1alpha1.Network{}) 33 | 34 | if obj == nil { 35 | return nil, err 36 | } 37 | return obj.(*v1alpha1.Network), err 38 | } 39 | 40 | // List takes label and field selectors, and returns the list of Networks that match those selectors. 41 | func (c *FakeNetworks) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.NetworkList, err error) { 42 | obj, err := c.Fake. 43 | Invokes(testing.NewListAction(networksResource, networksKind, c.ns, opts), &v1alpha1.NetworkList{}) 44 | 45 | if obj == nil { 46 | return nil, err 47 | } 48 | 49 | label, _, _ := testing.ExtractFromListOptions(opts) 50 | if label == nil { 51 | label = labels.Everything() 52 | } 53 | list := &v1alpha1.NetworkList{ListMeta: obj.(*v1alpha1.NetworkList).ListMeta} 54 | for _, item := range obj.(*v1alpha1.NetworkList).Items { 55 | if label.Matches(labels.Set(item.Labels)) { 56 | list.Items = append(list.Items, item) 57 | } 58 | } 59 | return list, err 60 | } 61 | 62 | // Watch returns a watch.Interface that watches the requested networks. 63 | func (c *FakeNetworks) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { 64 | return c.Fake. 65 | InvokesWatch(testing.NewWatchAction(networksResource, c.ns, opts)) 66 | 67 | } 68 | 69 | // Create takes the representation of a network and creates it. Returns the server's representation of the network, and an error, if there is any. 70 | func (c *FakeNetworks) Create(ctx context.Context, network *v1alpha1.Network, opts v1.CreateOptions) (result *v1alpha1.Network, err error) { 71 | obj, err := c.Fake. 72 | Invokes(testing.NewCreateAction(networksResource, c.ns, network), &v1alpha1.Network{}) 73 | 74 | if obj == nil { 75 | return nil, err 76 | } 77 | return obj.(*v1alpha1.Network), err 78 | } 79 | 80 | // Update takes the representation of a network and updates it. Returns the server's representation of the network, and an error, if there is any. 81 | func (c *FakeNetworks) Update(ctx context.Context, network *v1alpha1.Network, opts v1.UpdateOptions) (result *v1alpha1.Network, err error) { 82 | obj, err := c.Fake. 83 | Invokes(testing.NewUpdateAction(networksResource, c.ns, network), &v1alpha1.Network{}) 84 | 85 | if obj == nil { 86 | return nil, err 87 | } 88 | return obj.(*v1alpha1.Network), err 89 | } 90 | 91 | // UpdateStatus was generated because the type contains a Status member. 92 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 93 | func (c *FakeNetworks) UpdateStatus(ctx context.Context, network *v1alpha1.Network, opts v1.UpdateOptions) (*v1alpha1.Network, error) { 94 | obj, err := c.Fake. 95 | Invokes(testing.NewUpdateSubresourceAction(networksResource, "status", c.ns, network), &v1alpha1.Network{}) 96 | 97 | if obj == nil { 98 | return nil, err 99 | } 100 | return obj.(*v1alpha1.Network), err 101 | } 102 | 103 | // Delete takes name of the network and deletes it. Returns an error if one occurs. 104 | func (c *FakeNetworks) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { 105 | _, err := c.Fake. 106 | Invokes(testing.NewDeleteActionWithOptions(networksResource, c.ns, name, opts), &v1alpha1.Network{}) 107 | 108 | return err 109 | } 110 | 111 | // DeleteCollection deletes a collection of objects. 112 | func (c *FakeNetworks) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { 113 | action := testing.NewDeleteCollectionAction(networksResource, c.ns, listOpts) 114 | 115 | _, err := c.Fake.Invokes(action, &v1alpha1.NetworkList{}) 116 | return err 117 | } 118 | 119 | // Patch applies the patch and returns the patched network. 120 | func (c *FakeNetworks) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Network, err error) { 121 | obj, err := c.Fake. 122 | Invokes(testing.NewPatchSubresourceAction(networksResource, c.ns, name, pt, data, subresources...), &v1alpha1.Network{}) 123 | 124 | if obj == nil { 125 | return nil, err 126 | } 127 | return obj.(*v1alpha1.Network), err 128 | } 129 | 130 | // Apply takes the given apply declarative configuration, applies it and returns the applied network. 131 | func (c *FakeNetworks) Apply(ctx context.Context, network *knapgithubcomv1alpha1.NetworkApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.Network, err error) { 132 | if network == nil { 133 | return nil, fmt.Errorf("network provided to Apply must not be nil") 134 | } 135 | data, err := json.Marshal(network) 136 | if err != nil { 137 | return nil, err 138 | } 139 | name := network.Name 140 | if name == nil { 141 | return nil, fmt.Errorf("network.Name must be provided to Apply") 142 | } 143 | obj, err := c.Fake. 144 | Invokes(testing.NewPatchSubresourceAction(networksResource, c.ns, *name, types.ApplyPatchType, data), &v1alpha1.Network{}) 145 | 146 | if obj == nil { 147 | return nil, err 148 | } 149 | return obj.(*v1alpha1.Network), err 150 | } 151 | 152 | // ApplyStatus was generated because the type contains a Status member. 153 | // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). 154 | func (c *FakeNetworks) ApplyStatus(ctx context.Context, network *knapgithubcomv1alpha1.NetworkApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.Network, err error) { 155 | if network == nil { 156 | return nil, fmt.Errorf("network provided to Apply must not be nil") 157 | } 158 | data, err := json.Marshal(network) 159 | if err != nil { 160 | return nil, err 161 | } 162 | name := network.Name 163 | if name == nil { 164 | return nil, fmt.Errorf("network.Name must be provided to Apply") 165 | } 166 | obj, err := c.Fake. 167 | Invokes(testing.NewPatchSubresourceAction(networksResource, c.ns, *name, types.ApplyPatchType, data, "status"), &v1alpha1.Network{}) 168 | 169 | if obj == nil { 170 | return nil, err 171 | } 172 | return obj.(*v1alpha1.Network), err 173 | } 174 | -------------------------------------------------------------------------------- /apis/informers/externalversions/factory.go: -------------------------------------------------------------------------------- 1 | // Code generated by informer-gen. DO NOT EDIT. 2 | 3 | package externalversions 4 | 5 | import ( 6 | reflect "reflect" 7 | sync "sync" 8 | time "time" 9 | 10 | versioned "github.com/tliron/knap/apis/clientset/versioned" 11 | internalinterfaces "github.com/tliron/knap/apis/informers/externalversions/internalinterfaces" 12 | knapgithubcom "github.com/tliron/knap/apis/informers/externalversions/knap.github.com" 13 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | runtime "k8s.io/apimachinery/pkg/runtime" 15 | schema "k8s.io/apimachinery/pkg/runtime/schema" 16 | cache "k8s.io/client-go/tools/cache" 17 | ) 18 | 19 | // SharedInformerOption defines the functional option type for SharedInformerFactory. 20 | type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory 21 | 22 | type sharedInformerFactory struct { 23 | client versioned.Interface 24 | namespace string 25 | tweakListOptions internalinterfaces.TweakListOptionsFunc 26 | lock sync.Mutex 27 | defaultResync time.Duration 28 | customResync map[reflect.Type]time.Duration 29 | 30 | informers map[reflect.Type]cache.SharedIndexInformer 31 | // startedInformers is used for tracking which informers have been started. 32 | // This allows Start() to be called multiple times safely. 33 | startedInformers map[reflect.Type]bool 34 | // wg tracks how many goroutines were started. 35 | wg sync.WaitGroup 36 | // shuttingDown is true when Shutdown has been called. It may still be running 37 | // because it needs to wait for goroutines. 38 | shuttingDown bool 39 | } 40 | 41 | // WithCustomResyncConfig sets a custom resync period for the specified informer types. 42 | func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { 43 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 44 | for k, v := range resyncConfig { 45 | factory.customResync[reflect.TypeOf(k)] = v 46 | } 47 | return factory 48 | } 49 | } 50 | 51 | // WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. 52 | func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { 53 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 54 | factory.tweakListOptions = tweakListOptions 55 | return factory 56 | } 57 | } 58 | 59 | // WithNamespace limits the SharedInformerFactory to the specified namespace. 60 | func WithNamespace(namespace string) SharedInformerOption { 61 | return func(factory *sharedInformerFactory) *sharedInformerFactory { 62 | factory.namespace = namespace 63 | return factory 64 | } 65 | } 66 | 67 | // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. 68 | func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { 69 | return NewSharedInformerFactoryWithOptions(client, defaultResync) 70 | } 71 | 72 | // NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. 73 | // Listers obtained via this SharedInformerFactory will be subject to the same filters 74 | // as specified here. 75 | // Deprecated: Please use NewSharedInformerFactoryWithOptions instead 76 | func NewFilteredSharedInformerFactory(client versioned.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { 77 | return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) 78 | } 79 | 80 | // NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. 81 | func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { 82 | factory := &sharedInformerFactory{ 83 | client: client, 84 | namespace: v1.NamespaceAll, 85 | defaultResync: defaultResync, 86 | informers: make(map[reflect.Type]cache.SharedIndexInformer), 87 | startedInformers: make(map[reflect.Type]bool), 88 | customResync: make(map[reflect.Type]time.Duration), 89 | } 90 | 91 | // Apply all options 92 | for _, opt := range options { 93 | factory = opt(factory) 94 | } 95 | 96 | return factory 97 | } 98 | 99 | func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { 100 | f.lock.Lock() 101 | defer f.lock.Unlock() 102 | 103 | if f.shuttingDown { 104 | return 105 | } 106 | 107 | for informerType, informer := range f.informers { 108 | if !f.startedInformers[informerType] { 109 | f.wg.Add(1) 110 | // We need a new variable in each loop iteration, 111 | // otherwise the goroutine would use the loop variable 112 | // and that keeps changing. 113 | informer := informer 114 | go func() { 115 | defer f.wg.Done() 116 | informer.Run(stopCh) 117 | }() 118 | f.startedInformers[informerType] = true 119 | } 120 | } 121 | } 122 | 123 | func (f *sharedInformerFactory) Shutdown() { 124 | f.lock.Lock() 125 | f.shuttingDown = true 126 | f.lock.Unlock() 127 | 128 | // Will return immediately if there is nothing to wait for. 129 | f.wg.Wait() 130 | } 131 | 132 | func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { 133 | informers := func() map[reflect.Type]cache.SharedIndexInformer { 134 | f.lock.Lock() 135 | defer f.lock.Unlock() 136 | 137 | informers := map[reflect.Type]cache.SharedIndexInformer{} 138 | for informerType, informer := range f.informers { 139 | if f.startedInformers[informerType] { 140 | informers[informerType] = informer 141 | } 142 | } 143 | return informers 144 | }() 145 | 146 | res := map[reflect.Type]bool{} 147 | for informType, informer := range informers { 148 | res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) 149 | } 150 | return res 151 | } 152 | 153 | // InternalInformerFor returns the SharedIndexInformer for obj using an internal 154 | // client. 155 | func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { 156 | f.lock.Lock() 157 | defer f.lock.Unlock() 158 | 159 | informerType := reflect.TypeOf(obj) 160 | informer, exists := f.informers[informerType] 161 | if exists { 162 | return informer 163 | } 164 | 165 | resyncPeriod, exists := f.customResync[informerType] 166 | if !exists { 167 | resyncPeriod = f.defaultResync 168 | } 169 | 170 | informer = newFunc(f.client, resyncPeriod) 171 | f.informers[informerType] = informer 172 | 173 | return informer 174 | } 175 | 176 | // SharedInformerFactory provides shared informers for resources in all known 177 | // API group versions. 178 | // 179 | // It is typically used like this: 180 | // 181 | // ctx, cancel := context.Background() 182 | // defer cancel() 183 | // factory := NewSharedInformerFactory(client, resyncPeriod) 184 | // defer factory.WaitForStop() // Returns immediately if nothing was started. 185 | // genericInformer := factory.ForResource(resource) 186 | // typedInformer := factory.SomeAPIGroup().V1().SomeType() 187 | // factory.Start(ctx.Done()) // Start processing these informers. 188 | // synced := factory.WaitForCacheSync(ctx.Done()) 189 | // for v, ok := range synced { 190 | // if !ok { 191 | // fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) 192 | // return 193 | // } 194 | // } 195 | // 196 | // // Creating informers can also be created after Start, but then 197 | // // Start must be called again: 198 | // anotherGenericInformer := factory.ForResource(resource) 199 | // factory.Start(ctx.Done()) 200 | type SharedInformerFactory interface { 201 | internalinterfaces.SharedInformerFactory 202 | 203 | // Start initializes all requested informers. They are handled in goroutines 204 | // which run until the stop channel gets closed. 205 | Start(stopCh <-chan struct{}) 206 | 207 | // Shutdown marks a factory as shutting down. At that point no new 208 | // informers can be started anymore and Start will return without 209 | // doing anything. 210 | // 211 | // In addition, Shutdown blocks until all goroutines have terminated. For that 212 | // to happen, the close channel(s) that they were started with must be closed, 213 | // either before Shutdown gets called or while it is waiting. 214 | // 215 | // Shutdown may be called multiple times, even concurrently. All such calls will 216 | // block until all goroutines have terminated. 217 | Shutdown() 218 | 219 | // WaitForCacheSync blocks until all started informers' caches were synced 220 | // or the stop channel gets closed. 221 | WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool 222 | 223 | // ForResource gives generic access to a shared informer of the matching type. 224 | ForResource(resource schema.GroupVersionResource) (GenericInformer, error) 225 | 226 | // InternalInformerFor returns the SharedIndexInformer for obj using an internal 227 | // client. 228 | InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer 229 | 230 | Knap() knapgithubcom.Interface 231 | } 232 | 233 | func (f *sharedInformerFactory) Knap() knapgithubcom.Interface { 234 | return knapgithubcom.New(f, f.namespace, f.tweakListOptions) 235 | } 236 | -------------------------------------------------------------------------------- /apis/clientset/versioned/typed/knap.github.com/v1alpha1/network.go: -------------------------------------------------------------------------------- 1 | // Code generated by client-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | "context" 7 | json "encoding/json" 8 | "fmt" 9 | "time" 10 | 11 | knapgithubcomv1alpha1 "github.com/tliron/knap/apis/applyconfiguration/knap.github.com/v1alpha1" 12 | scheme "github.com/tliron/knap/apis/clientset/versioned/scheme" 13 | v1alpha1 "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 14 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | types "k8s.io/apimachinery/pkg/types" 16 | watch "k8s.io/apimachinery/pkg/watch" 17 | rest "k8s.io/client-go/rest" 18 | ) 19 | 20 | // NetworksGetter has a method to return a NetworkInterface. 21 | // A group's client should implement this interface. 22 | type NetworksGetter interface { 23 | Networks(namespace string) NetworkInterface 24 | } 25 | 26 | // NetworkInterface has methods to work with Network resources. 27 | type NetworkInterface interface { 28 | Create(ctx context.Context, network *v1alpha1.Network, opts v1.CreateOptions) (*v1alpha1.Network, error) 29 | Update(ctx context.Context, network *v1alpha1.Network, opts v1.UpdateOptions) (*v1alpha1.Network, error) 30 | UpdateStatus(ctx context.Context, network *v1alpha1.Network, opts v1.UpdateOptions) (*v1alpha1.Network, error) 31 | Delete(ctx context.Context, name string, opts v1.DeleteOptions) error 32 | DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error 33 | Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Network, error) 34 | List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.NetworkList, error) 35 | Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) 36 | Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Network, err error) 37 | Apply(ctx context.Context, network *knapgithubcomv1alpha1.NetworkApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.Network, err error) 38 | ApplyStatus(ctx context.Context, network *knapgithubcomv1alpha1.NetworkApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.Network, err error) 39 | NetworkExpansion 40 | } 41 | 42 | // networks implements NetworkInterface 43 | type networks struct { 44 | client rest.Interface 45 | ns string 46 | } 47 | 48 | // newNetworks returns a Networks 49 | func newNetworks(c *KnapV1alpha1Client, namespace string) *networks { 50 | return &networks{ 51 | client: c.RESTClient(), 52 | ns: namespace, 53 | } 54 | } 55 | 56 | // Get takes name of the network, and returns the corresponding network object, and an error if there is any. 57 | func (c *networks) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.Network, err error) { 58 | result = &v1alpha1.Network{} 59 | err = c.client.Get(). 60 | Namespace(c.ns). 61 | Resource("networks"). 62 | Name(name). 63 | VersionedParams(&options, scheme.ParameterCodec). 64 | Do(ctx). 65 | Into(result) 66 | return 67 | } 68 | 69 | // List takes label and field selectors, and returns the list of Networks that match those selectors. 70 | func (c *networks) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.NetworkList, err error) { 71 | var timeout time.Duration 72 | if opts.TimeoutSeconds != nil { 73 | timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 74 | } 75 | result = &v1alpha1.NetworkList{} 76 | err = c.client.Get(). 77 | Namespace(c.ns). 78 | Resource("networks"). 79 | VersionedParams(&opts, scheme.ParameterCodec). 80 | Timeout(timeout). 81 | Do(ctx). 82 | Into(result) 83 | return 84 | } 85 | 86 | // Watch returns a watch.Interface that watches the requested networks. 87 | func (c *networks) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { 88 | var timeout time.Duration 89 | if opts.TimeoutSeconds != nil { 90 | timeout = time.Duration(*opts.TimeoutSeconds) * time.Second 91 | } 92 | opts.Watch = true 93 | return c.client.Get(). 94 | Namespace(c.ns). 95 | Resource("networks"). 96 | VersionedParams(&opts, scheme.ParameterCodec). 97 | Timeout(timeout). 98 | Watch(ctx) 99 | } 100 | 101 | // Create takes the representation of a network and creates it. Returns the server's representation of the network, and an error, if there is any. 102 | func (c *networks) Create(ctx context.Context, network *v1alpha1.Network, opts v1.CreateOptions) (result *v1alpha1.Network, err error) { 103 | result = &v1alpha1.Network{} 104 | err = c.client.Post(). 105 | Namespace(c.ns). 106 | Resource("networks"). 107 | VersionedParams(&opts, scheme.ParameterCodec). 108 | Body(network). 109 | Do(ctx). 110 | Into(result) 111 | return 112 | } 113 | 114 | // Update takes the representation of a network and updates it. Returns the server's representation of the network, and an error, if there is any. 115 | func (c *networks) Update(ctx context.Context, network *v1alpha1.Network, opts v1.UpdateOptions) (result *v1alpha1.Network, err error) { 116 | result = &v1alpha1.Network{} 117 | err = c.client.Put(). 118 | Namespace(c.ns). 119 | Resource("networks"). 120 | Name(network.Name). 121 | VersionedParams(&opts, scheme.ParameterCodec). 122 | Body(network). 123 | Do(ctx). 124 | Into(result) 125 | return 126 | } 127 | 128 | // UpdateStatus was generated because the type contains a Status member. 129 | // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). 130 | func (c *networks) UpdateStatus(ctx context.Context, network *v1alpha1.Network, opts v1.UpdateOptions) (result *v1alpha1.Network, err error) { 131 | result = &v1alpha1.Network{} 132 | err = c.client.Put(). 133 | Namespace(c.ns). 134 | Resource("networks"). 135 | Name(network.Name). 136 | SubResource("status"). 137 | VersionedParams(&opts, scheme.ParameterCodec). 138 | Body(network). 139 | Do(ctx). 140 | Into(result) 141 | return 142 | } 143 | 144 | // Delete takes name of the network and deletes it. Returns an error if one occurs. 145 | func (c *networks) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { 146 | return c.client.Delete(). 147 | Namespace(c.ns). 148 | Resource("networks"). 149 | Name(name). 150 | Body(&opts). 151 | Do(ctx). 152 | Error() 153 | } 154 | 155 | // DeleteCollection deletes a collection of objects. 156 | func (c *networks) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { 157 | var timeout time.Duration 158 | if listOpts.TimeoutSeconds != nil { 159 | timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second 160 | } 161 | return c.client.Delete(). 162 | Namespace(c.ns). 163 | Resource("networks"). 164 | VersionedParams(&listOpts, scheme.ParameterCodec). 165 | Timeout(timeout). 166 | Body(&opts). 167 | Do(ctx). 168 | Error() 169 | } 170 | 171 | // Patch applies the patch and returns the patched network. 172 | func (c *networks) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.Network, err error) { 173 | result = &v1alpha1.Network{} 174 | err = c.client.Patch(pt). 175 | Namespace(c.ns). 176 | Resource("networks"). 177 | Name(name). 178 | SubResource(subresources...). 179 | VersionedParams(&opts, scheme.ParameterCodec). 180 | Body(data). 181 | Do(ctx). 182 | Into(result) 183 | return 184 | } 185 | 186 | // Apply takes the given apply declarative configuration, applies it and returns the applied network. 187 | func (c *networks) Apply(ctx context.Context, network *knapgithubcomv1alpha1.NetworkApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.Network, err error) { 188 | if network == nil { 189 | return nil, fmt.Errorf("network provided to Apply must not be nil") 190 | } 191 | patchOpts := opts.ToPatchOptions() 192 | data, err := json.Marshal(network) 193 | if err != nil { 194 | return nil, err 195 | } 196 | name := network.Name 197 | if name == nil { 198 | return nil, fmt.Errorf("network.Name must be provided to Apply") 199 | } 200 | result = &v1alpha1.Network{} 201 | err = c.client.Patch(types.ApplyPatchType). 202 | Namespace(c.ns). 203 | Resource("networks"). 204 | Name(*name). 205 | VersionedParams(&patchOpts, scheme.ParameterCodec). 206 | Body(data). 207 | Do(ctx). 208 | Into(result) 209 | return 210 | } 211 | 212 | // ApplyStatus was generated because the type contains a Status member. 213 | // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). 214 | func (c *networks) ApplyStatus(ctx context.Context, network *knapgithubcomv1alpha1.NetworkApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.Network, err error) { 215 | if network == nil { 216 | return nil, fmt.Errorf("network provided to Apply must not be nil") 217 | } 218 | patchOpts := opts.ToPatchOptions() 219 | data, err := json.Marshal(network) 220 | if err != nil { 221 | return nil, err 222 | } 223 | 224 | name := network.Name 225 | if name == nil { 226 | return nil, fmt.Errorf("network.Name must be provided to Apply") 227 | } 228 | 229 | result = &v1alpha1.Network{} 230 | err = c.client.Patch(types.ApplyPatchType). 231 | Namespace(c.ns). 232 | Resource("networks"). 233 | Name(*name). 234 | SubResource("status"). 235 | VersionedParams(&patchOpts, scheme.ParameterCodec). 236 | Body(data). 237 | Do(ctx). 238 | Into(result) 239 | return 240 | } 241 | -------------------------------------------------------------------------------- /apis/applyconfiguration/knap.github.com/v1alpha1/network.go: -------------------------------------------------------------------------------- 1 | // Code generated by applyconfiguration-gen. DO NOT EDIT. 2 | 3 | package v1alpha1 4 | 5 | import ( 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | types "k8s.io/apimachinery/pkg/types" 8 | v1 "k8s.io/client-go/applyconfigurations/meta/v1" 9 | ) 10 | 11 | // NetworkApplyConfiguration represents an declarative configuration of the Network type for use 12 | // with apply. 13 | type NetworkApplyConfiguration struct { 14 | v1.TypeMetaApplyConfiguration `json:",inline"` 15 | *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` 16 | Spec *NetworkSpecApplyConfiguration `json:"spec,omitempty"` 17 | Status *NetworkStatusApplyConfiguration `json:"status,omitempty"` 18 | } 19 | 20 | // Network constructs an declarative configuration of the Network type for use with 21 | // apply. 22 | func Network(name, namespace string) *NetworkApplyConfiguration { 23 | b := &NetworkApplyConfiguration{} 24 | b.WithName(name) 25 | b.WithNamespace(namespace) 26 | b.WithKind("Network") 27 | b.WithAPIVersion("knap.github.com/v1alpha1") 28 | return b 29 | } 30 | 31 | // WithKind sets the Kind field in the declarative configuration to the given value 32 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 33 | // If called multiple times, the Kind field is set to the value of the last call. 34 | func (b *NetworkApplyConfiguration) WithKind(value string) *NetworkApplyConfiguration { 35 | b.Kind = &value 36 | return b 37 | } 38 | 39 | // WithAPIVersion sets the APIVersion field in the declarative configuration to the given value 40 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 41 | // If called multiple times, the APIVersion field is set to the value of the last call. 42 | func (b *NetworkApplyConfiguration) WithAPIVersion(value string) *NetworkApplyConfiguration { 43 | b.APIVersion = &value 44 | return b 45 | } 46 | 47 | // WithName sets the Name field in the declarative configuration to the given value 48 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 49 | // If called multiple times, the Name field is set to the value of the last call. 50 | func (b *NetworkApplyConfiguration) WithName(value string) *NetworkApplyConfiguration { 51 | b.ensureObjectMetaApplyConfigurationExists() 52 | b.Name = &value 53 | return b 54 | } 55 | 56 | // WithGenerateName sets the GenerateName field in the declarative configuration to the given value 57 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 58 | // If called multiple times, the GenerateName field is set to the value of the last call. 59 | func (b *NetworkApplyConfiguration) WithGenerateName(value string) *NetworkApplyConfiguration { 60 | b.ensureObjectMetaApplyConfigurationExists() 61 | b.GenerateName = &value 62 | return b 63 | } 64 | 65 | // WithNamespace sets the Namespace field in the declarative configuration to the given value 66 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 67 | // If called multiple times, the Namespace field is set to the value of the last call. 68 | func (b *NetworkApplyConfiguration) WithNamespace(value string) *NetworkApplyConfiguration { 69 | b.ensureObjectMetaApplyConfigurationExists() 70 | b.Namespace = &value 71 | return b 72 | } 73 | 74 | // WithUID sets the UID field in the declarative configuration to the given value 75 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 76 | // If called multiple times, the UID field is set to the value of the last call. 77 | func (b *NetworkApplyConfiguration) WithUID(value types.UID) *NetworkApplyConfiguration { 78 | b.ensureObjectMetaApplyConfigurationExists() 79 | b.UID = &value 80 | return b 81 | } 82 | 83 | // WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value 84 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 85 | // If called multiple times, the ResourceVersion field is set to the value of the last call. 86 | func (b *NetworkApplyConfiguration) WithResourceVersion(value string) *NetworkApplyConfiguration { 87 | b.ensureObjectMetaApplyConfigurationExists() 88 | b.ResourceVersion = &value 89 | return b 90 | } 91 | 92 | // WithGeneration sets the Generation field in the declarative configuration to the given value 93 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 94 | // If called multiple times, the Generation field is set to the value of the last call. 95 | func (b *NetworkApplyConfiguration) WithGeneration(value int64) *NetworkApplyConfiguration { 96 | b.ensureObjectMetaApplyConfigurationExists() 97 | b.Generation = &value 98 | return b 99 | } 100 | 101 | // WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value 102 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 103 | // If called multiple times, the CreationTimestamp field is set to the value of the last call. 104 | func (b *NetworkApplyConfiguration) WithCreationTimestamp(value metav1.Time) *NetworkApplyConfiguration { 105 | b.ensureObjectMetaApplyConfigurationExists() 106 | b.CreationTimestamp = &value 107 | return b 108 | } 109 | 110 | // WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value 111 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 112 | // If called multiple times, the DeletionTimestamp field is set to the value of the last call. 113 | func (b *NetworkApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *NetworkApplyConfiguration { 114 | b.ensureObjectMetaApplyConfigurationExists() 115 | b.DeletionTimestamp = &value 116 | return b 117 | } 118 | 119 | // WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value 120 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 121 | // If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. 122 | func (b *NetworkApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *NetworkApplyConfiguration { 123 | b.ensureObjectMetaApplyConfigurationExists() 124 | b.DeletionGracePeriodSeconds = &value 125 | return b 126 | } 127 | 128 | // WithLabels puts the entries into the Labels field in the declarative configuration 129 | // and returns the receiver, so that objects can be build by chaining "With" function invocations. 130 | // If called multiple times, the entries provided by each call will be put on the Labels field, 131 | // overwriting an existing map entries in Labels field with the same key. 132 | func (b *NetworkApplyConfiguration) WithLabels(entries map[string]string) *NetworkApplyConfiguration { 133 | b.ensureObjectMetaApplyConfigurationExists() 134 | if b.Labels == nil && len(entries) > 0 { 135 | b.Labels = make(map[string]string, len(entries)) 136 | } 137 | for k, v := range entries { 138 | b.Labels[k] = v 139 | } 140 | return b 141 | } 142 | 143 | // WithAnnotations puts the entries into the Annotations field in the declarative configuration 144 | // and returns the receiver, so that objects can be build by chaining "With" function invocations. 145 | // If called multiple times, the entries provided by each call will be put on the Annotations field, 146 | // overwriting an existing map entries in Annotations field with the same key. 147 | func (b *NetworkApplyConfiguration) WithAnnotations(entries map[string]string) *NetworkApplyConfiguration { 148 | b.ensureObjectMetaApplyConfigurationExists() 149 | if b.Annotations == nil && len(entries) > 0 { 150 | b.Annotations = make(map[string]string, len(entries)) 151 | } 152 | for k, v := range entries { 153 | b.Annotations[k] = v 154 | } 155 | return b 156 | } 157 | 158 | // WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration 159 | // and returns the receiver, so that objects can be build by chaining "With" function invocations. 160 | // If called multiple times, values provided by each call will be appended to the OwnerReferences field. 161 | func (b *NetworkApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *NetworkApplyConfiguration { 162 | b.ensureObjectMetaApplyConfigurationExists() 163 | for i := range values { 164 | if values[i] == nil { 165 | panic("nil value passed to WithOwnerReferences") 166 | } 167 | b.OwnerReferences = append(b.OwnerReferences, *values[i]) 168 | } 169 | return b 170 | } 171 | 172 | // WithFinalizers adds the given value to the Finalizers field in the declarative configuration 173 | // and returns the receiver, so that objects can be build by chaining "With" function invocations. 174 | // If called multiple times, values provided by each call will be appended to the Finalizers field. 175 | func (b *NetworkApplyConfiguration) WithFinalizers(values ...string) *NetworkApplyConfiguration { 176 | b.ensureObjectMetaApplyConfigurationExists() 177 | for i := range values { 178 | b.Finalizers = append(b.Finalizers, values[i]) 179 | } 180 | return b 181 | } 182 | 183 | func (b *NetworkApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { 184 | if b.ObjectMetaApplyConfiguration == nil { 185 | b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{} 186 | } 187 | } 188 | 189 | // WithSpec sets the Spec field in the declarative configuration to the given value 190 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 191 | // If called multiple times, the Spec field is set to the value of the last call. 192 | func (b *NetworkApplyConfiguration) WithSpec(value *NetworkSpecApplyConfiguration) *NetworkApplyConfiguration { 193 | b.Spec = value 194 | return b 195 | } 196 | 197 | // WithStatus sets the Status field in the declarative configuration to the given value 198 | // and returns the receiver, so that objects can be built by chaining "With" function invocations. 199 | // If called multiple times, the Status field is set to the value of the last call. 200 | func (b *NetworkApplyConfiguration) WithStatus(value *NetworkStatusApplyConfiguration) *NetworkApplyConfiguration { 201 | b.Status = value 202 | return b 203 | } 204 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /provider/provider.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.24.0 4 | // protoc v3.11.2 5 | // source: provider.proto 6 | 7 | package provider 8 | 9 | import ( 10 | context "context" 11 | proto "github.com/golang/protobuf/proto" 12 | grpc "google.golang.org/grpc" 13 | codes "google.golang.org/grpc/codes" 14 | status "google.golang.org/grpc/status" 15 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 16 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 17 | reflect "reflect" 18 | sync "sync" 19 | ) 20 | 21 | const ( 22 | // Verify that this generated code is sufficiently up-to-date. 23 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 24 | // Verify that runtime/protoimpl is sufficiently up-to-date. 25 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 26 | ) 27 | 28 | // This is a compile-time assertion that a sufficiently up-to-date version 29 | // of the legacy proto package is being used. 30 | const _ = proto.ProtoPackageIsVersion4 31 | 32 | type CreateCniConfigRequest struct { 33 | state protoimpl.MessageState 34 | sizeCache protoimpl.SizeCache 35 | unknownFields protoimpl.UnknownFields 36 | 37 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 38 | Hints map[string]string `protobuf:"bytes,2,rep,name=hints,proto3" json:"hints,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` 39 | } 40 | 41 | func (x *CreateCniConfigRequest) Reset() { 42 | *x = CreateCniConfigRequest{} 43 | if protoimpl.UnsafeEnabled { 44 | mi := &file_provider_proto_msgTypes[0] 45 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 46 | ms.StoreMessageInfo(mi) 47 | } 48 | } 49 | 50 | func (x *CreateCniConfigRequest) String() string { 51 | return protoimpl.X.MessageStringOf(x) 52 | } 53 | 54 | func (*CreateCniConfigRequest) ProtoMessage() {} 55 | 56 | func (x *CreateCniConfigRequest) ProtoReflect() protoreflect.Message { 57 | mi := &file_provider_proto_msgTypes[0] 58 | if protoimpl.UnsafeEnabled && x != nil { 59 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 60 | if ms.LoadMessageInfo() == nil { 61 | ms.StoreMessageInfo(mi) 62 | } 63 | return ms 64 | } 65 | return mi.MessageOf(x) 66 | } 67 | 68 | // Deprecated: Use CreateCniConfigRequest.ProtoReflect.Descriptor instead. 69 | func (*CreateCniConfigRequest) Descriptor() ([]byte, []int) { 70 | return file_provider_proto_rawDescGZIP(), []int{0} 71 | } 72 | 73 | func (x *CreateCniConfigRequest) GetName() string { 74 | if x != nil { 75 | return x.Name 76 | } 77 | return "" 78 | } 79 | 80 | func (x *CreateCniConfigRequest) GetHints() map[string]string { 81 | if x != nil { 82 | return x.Hints 83 | } 84 | return nil 85 | } 86 | 87 | type CreateCniConfigReply struct { 88 | state protoimpl.MessageState 89 | sizeCache protoimpl.SizeCache 90 | unknownFields protoimpl.UnknownFields 91 | 92 | Config string `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` 93 | } 94 | 95 | func (x *CreateCniConfigReply) Reset() { 96 | *x = CreateCniConfigReply{} 97 | if protoimpl.UnsafeEnabled { 98 | mi := &file_provider_proto_msgTypes[1] 99 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 100 | ms.StoreMessageInfo(mi) 101 | } 102 | } 103 | 104 | func (x *CreateCniConfigReply) String() string { 105 | return protoimpl.X.MessageStringOf(x) 106 | } 107 | 108 | func (*CreateCniConfigReply) ProtoMessage() {} 109 | 110 | func (x *CreateCniConfigReply) ProtoReflect() protoreflect.Message { 111 | mi := &file_provider_proto_msgTypes[1] 112 | if protoimpl.UnsafeEnabled && x != nil { 113 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 114 | if ms.LoadMessageInfo() == nil { 115 | ms.StoreMessageInfo(mi) 116 | } 117 | return ms 118 | } 119 | return mi.MessageOf(x) 120 | } 121 | 122 | // Deprecated: Use CreateCniConfigReply.ProtoReflect.Descriptor instead. 123 | func (*CreateCniConfigReply) Descriptor() ([]byte, []int) { 124 | return file_provider_proto_rawDescGZIP(), []int{1} 125 | } 126 | 127 | func (x *CreateCniConfigReply) GetConfig() string { 128 | if x != nil { 129 | return x.Config 130 | } 131 | return "" 132 | } 133 | 134 | var File_provider_proto protoreflect.FileDescriptor 135 | 136 | var file_provider_proto_rawDesc = []byte{ 137 | 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 138 | 0x12, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x22, 0xa9, 0x01, 0x0a, 0x16, 0x43, 139 | 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6e, 0x69, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 140 | 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 141 | 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x05, 0x68, 0x69, 0x6e, 142 | 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 143 | 0x64, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6e, 0x69, 0x43, 0x6f, 0x6e, 144 | 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x69, 0x6e, 0x74, 0x73, 145 | 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x1a, 0x38, 0x0a, 0x0a, 146 | 0x48, 0x69, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 147 | 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 148 | 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 149 | 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2e, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 150 | 0x43, 0x6e, 0x69, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 151 | 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 152 | 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x32, 0x61, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 153 | 0x65, 0x72, 0x12, 0x55, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6e, 0x69, 0x43, 154 | 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 155 | 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6e, 0x69, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 156 | 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 157 | 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6e, 0x69, 0x43, 0x6f, 0x6e, 0x66, 158 | 0x69, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x0c, 0x5a, 0x0a, 0x2e, 0x3b, 0x70, 159 | 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 160 | } 161 | 162 | var ( 163 | file_provider_proto_rawDescOnce sync.Once 164 | file_provider_proto_rawDescData = file_provider_proto_rawDesc 165 | ) 166 | 167 | func file_provider_proto_rawDescGZIP() []byte { 168 | file_provider_proto_rawDescOnce.Do(func() { 169 | file_provider_proto_rawDescData = protoimpl.X.CompressGZIP(file_provider_proto_rawDescData) 170 | }) 171 | return file_provider_proto_rawDescData 172 | } 173 | 174 | var file_provider_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 175 | var file_provider_proto_goTypes = []interface{}{ 176 | (*CreateCniConfigRequest)(nil), // 0: provider.CreateCniConfigRequest 177 | (*CreateCniConfigReply)(nil), // 1: provider.CreateCniConfigReply 178 | nil, // 2: provider.CreateCniConfigRequest.HintsEntry 179 | } 180 | var file_provider_proto_depIdxs = []int32{ 181 | 2, // 0: provider.CreateCniConfigRequest.hints:type_name -> provider.CreateCniConfigRequest.HintsEntry 182 | 0, // 1: provider.Provider.CreateCniConfig:input_type -> provider.CreateCniConfigRequest 183 | 1, // 2: provider.Provider.CreateCniConfig:output_type -> provider.CreateCniConfigReply 184 | 2, // [2:3] is the sub-list for method output_type 185 | 1, // [1:2] is the sub-list for method input_type 186 | 1, // [1:1] is the sub-list for extension type_name 187 | 1, // [1:1] is the sub-list for extension extendee 188 | 0, // [0:1] is the sub-list for field type_name 189 | } 190 | 191 | func init() { file_provider_proto_init() } 192 | func file_provider_proto_init() { 193 | if File_provider_proto != nil { 194 | return 195 | } 196 | if !protoimpl.UnsafeEnabled { 197 | file_provider_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 198 | switch v := v.(*CreateCniConfigRequest); i { 199 | case 0: 200 | return &v.state 201 | case 1: 202 | return &v.sizeCache 203 | case 2: 204 | return &v.unknownFields 205 | default: 206 | return nil 207 | } 208 | } 209 | file_provider_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 210 | switch v := v.(*CreateCniConfigReply); i { 211 | case 0: 212 | return &v.state 213 | case 1: 214 | return &v.sizeCache 215 | case 2: 216 | return &v.unknownFields 217 | default: 218 | return nil 219 | } 220 | } 221 | } 222 | type x struct{} 223 | out := protoimpl.TypeBuilder{ 224 | File: protoimpl.DescBuilder{ 225 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 226 | RawDescriptor: file_provider_proto_rawDesc, 227 | NumEnums: 0, 228 | NumMessages: 3, 229 | NumExtensions: 0, 230 | NumServices: 1, 231 | }, 232 | GoTypes: file_provider_proto_goTypes, 233 | DependencyIndexes: file_provider_proto_depIdxs, 234 | MessageInfos: file_provider_proto_msgTypes, 235 | }.Build() 236 | File_provider_proto = out.File 237 | file_provider_proto_rawDesc = nil 238 | file_provider_proto_goTypes = nil 239 | file_provider_proto_depIdxs = nil 240 | } 241 | 242 | // Reference imports to suppress errors if they are not otherwise used. 243 | var _ context.Context 244 | var _ grpc.ClientConnInterface 245 | 246 | // This is a compile-time assertion to ensure that this generated file 247 | // is compatible with the grpc package it is being compiled against. 248 | const _ = grpc.SupportPackageIsVersion6 249 | 250 | // ProviderClient is the client API for Provider service. 251 | // 252 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 253 | type ProviderClient interface { 254 | CreateCniConfig(ctx context.Context, in *CreateCniConfigRequest, opts ...grpc.CallOption) (*CreateCniConfigReply, error) 255 | } 256 | 257 | type providerClient struct { 258 | cc grpc.ClientConnInterface 259 | } 260 | 261 | func NewProviderClient(cc grpc.ClientConnInterface) ProviderClient { 262 | return &providerClient{cc} 263 | } 264 | 265 | func (c *providerClient) CreateCniConfig(ctx context.Context, in *CreateCniConfigRequest, opts ...grpc.CallOption) (*CreateCniConfigReply, error) { 266 | out := new(CreateCniConfigReply) 267 | err := c.cc.Invoke(ctx, "/provider.Provider/CreateCniConfig", in, out, opts...) 268 | if err != nil { 269 | return nil, err 270 | } 271 | return out, nil 272 | } 273 | 274 | // ProviderServer is the server API for Provider service. 275 | type ProviderServer interface { 276 | CreateCniConfig(context.Context, *CreateCniConfigRequest) (*CreateCniConfigReply, error) 277 | } 278 | 279 | // UnimplementedProviderServer can be embedded to have forward compatible implementations. 280 | type UnimplementedProviderServer struct { 281 | } 282 | 283 | func (*UnimplementedProviderServer) CreateCniConfig(context.Context, *CreateCniConfigRequest) (*CreateCniConfigReply, error) { 284 | return nil, status.Errorf(codes.Unimplemented, "method CreateCniConfig not implemented") 285 | } 286 | 287 | func RegisterProviderServer(s *grpc.Server, srv ProviderServer) { 288 | s.RegisterService(&_Provider_serviceDesc, srv) 289 | } 290 | 291 | func _Provider_CreateCniConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 292 | in := new(CreateCniConfigRequest) 293 | if err := dec(in); err != nil { 294 | return nil, err 295 | } 296 | if interceptor == nil { 297 | return srv.(ProviderServer).CreateCniConfig(ctx, in) 298 | } 299 | info := &grpc.UnaryServerInfo{ 300 | Server: srv, 301 | FullMethod: "/provider.Provider/CreateCniConfig", 302 | } 303 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 304 | return srv.(ProviderServer).CreateCniConfig(ctx, req.(*CreateCniConfigRequest)) 305 | } 306 | return interceptor(ctx, in, info, handler) 307 | } 308 | 309 | var _Provider_serviceDesc = grpc.ServiceDesc{ 310 | ServiceName: "provider.Provider", 311 | HandlerType: (*ProviderServer)(nil), 312 | Methods: []grpc.MethodDesc{ 313 | { 314 | MethodName: "CreateCniConfig", 315 | Handler: _Provider_CreateCniConfig_Handler, 316 | }, 317 | }, 318 | Streams: []grpc.StreamDesc{}, 319 | Metadata: "provider.proto", 320 | } 321 | -------------------------------------------------------------------------------- /client/operator.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | 6 | resources "github.com/tliron/knap/resources/knap.github.com/v1alpha1" 7 | apps "k8s.io/api/apps/v1" 8 | core "k8s.io/api/core/v1" 9 | rbac "k8s.io/api/rbac/v1" 10 | apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 11 | "k8s.io/apimachinery/pkg/api/errors" 12 | meta "k8s.io/apimachinery/pkg/apis/meta/v1" 13 | "k8s.io/apimachinery/pkg/util/intstr" 14 | ) 15 | 16 | func (self *Client) Install(registry string, wait bool) error { 17 | var err error 18 | 19 | if _, err = self.createCustomResourceDefinition(); err != nil { 20 | return err 21 | } 22 | 23 | if _, err = self.createNamespace(); err != nil { 24 | return err 25 | } 26 | 27 | var serviceAccount *core.ServiceAccount 28 | if serviceAccount, err = self.createServiceAccount(); err != nil { 29 | return err 30 | } 31 | 32 | if self.Cluster { 33 | if _, err = self.createClusterRoleBinding(serviceAccount); err != nil { 34 | return err 35 | } 36 | } else { 37 | var role *rbac.Role 38 | if role, err = self.createRole(); err != nil { 39 | return err 40 | } 41 | if _, err = self.createRoleBinding(serviceAccount, role); err != nil { 42 | return err 43 | } 44 | } 45 | 46 | // TODO: should these be DaemonSets? 47 | 48 | var operatorDeployment *apps.Deployment 49 | if operatorDeployment, err = self.createOperatorDeployment(registry, serviceAccount, 1); err != nil { 50 | return err 51 | } 52 | 53 | var bridgeProviderDeployment *apps.Deployment 54 | if bridgeProviderDeployment, err = self.createProviderDeployment("bridge", "tliron/knap-provider-bridge", registry, serviceAccount, 1); err != nil { 55 | return err 56 | } 57 | 58 | if wait { 59 | if _, err := self.WaitForDeployment(self.Namespace, operatorDeployment.Name); err != nil { 60 | return err 61 | } 62 | if _, err := self.WaitForDeployment(self.Namespace, bridgeProviderDeployment.Name); err != nil { 63 | return err 64 | } 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func (self *Client) Uninstall(wait bool) { 71 | var gracePeriodSeconds int64 = 0 72 | deleteOptions := meta.DeleteOptions{ 73 | GracePeriodSeconds: &gracePeriodSeconds, 74 | } 75 | 76 | // Operator deployment 77 | if err := self.Kubernetes.AppsV1().Deployments(self.Namespace).Delete(self.Context, fmt.Sprintf("%s-operator", self.NamePrefix), deleteOptions); err != nil { 78 | self.Log.Warningf("%s", err) 79 | } 80 | 81 | // Bridge provider deployment 82 | if err := self.Kubernetes.AppsV1().Deployments(self.Namespace).Delete(self.Context, fmt.Sprintf("%s-provider-bridge", self.NamePrefix), deleteOptions); err != nil { 83 | self.Log.Warningf("%s", err) 84 | } 85 | 86 | if self.Cluster { 87 | // Cluster role binding 88 | if err := self.Kubernetes.RbacV1().ClusterRoleBindings().Delete(self.Context, self.NamePrefix, deleteOptions); err != nil { 89 | self.Log.Warningf("%s", err) 90 | } 91 | } else { 92 | // Role binding 93 | if err := self.Kubernetes.RbacV1().RoleBindings(self.Namespace).Delete(self.Context, self.NamePrefix, deleteOptions); err != nil { 94 | self.Log.Warningf("%s", err) 95 | } 96 | 97 | // Role 98 | if err := self.Kubernetes.RbacV1().Roles(self.Namespace).Delete(self.Context, self.NamePrefix, deleteOptions); err != nil { 99 | self.Log.Warningf("%s", err) 100 | } 101 | } 102 | 103 | // Service account 104 | if err := self.Kubernetes.CoreV1().ServiceAccounts(self.Namespace).Delete(self.Context, self.NamePrefix, deleteOptions); err != nil { 105 | self.Log.Warningf("%s", err) 106 | } 107 | 108 | // Custom resource definition 109 | if err := self.APIExtensions.ApiextensionsV1().CustomResourceDefinitions().Delete(self.Context, resources.NetworkCustomResourceDefinition.Name, deleteOptions); err != nil { 110 | self.Log.Warningf("%s", err) 111 | } 112 | 113 | if wait { 114 | self.WaitForDeletion("service", func() bool { 115 | _, err := self.Kubernetes.CoreV1().Services(self.Namespace).Get(self.Context, fmt.Sprintf("%s-inventory", self.NamePrefix), meta.GetOptions{}) 116 | return err == nil 117 | }) 118 | self.WaitForDeletion("operator deployment", func() bool { 119 | _, err := self.Kubernetes.AppsV1().Deployments(self.Namespace).Get(self.Context, fmt.Sprintf("%s-operator", self.NamePrefix), meta.GetOptions{}) 120 | return err == nil 121 | }) 122 | self.WaitForDeletion("bridge provider deployment", func() bool { 123 | _, err := self.Kubernetes.AppsV1().Deployments(self.Namespace).Get(self.Context, fmt.Sprintf("%s-provider-bridge", self.NamePrefix), meta.GetOptions{}) 124 | return err == nil 125 | }) 126 | if self.Cluster { 127 | self.WaitForDeletion("cluster role binding", func() bool { 128 | _, err := self.Kubernetes.RbacV1().ClusterRoleBindings().Get(self.Context, self.NamePrefix, meta.GetOptions{}) 129 | return err == nil 130 | }) 131 | } else { 132 | self.WaitForDeletion("role binding", func() bool { 133 | _, err := self.Kubernetes.RbacV1().RoleBindings(self.Namespace).Get(self.Context, self.NamePrefix, meta.GetOptions{}) 134 | return err == nil 135 | }) 136 | self.WaitForDeletion("role", func() bool { 137 | _, err := self.Kubernetes.RbacV1().Roles(self.Namespace).Get(self.Context, self.NamePrefix, meta.GetOptions{}) 138 | return err == nil 139 | }) 140 | } 141 | self.WaitForDeletion("service account", func() bool { 142 | _, err := self.Kubernetes.CoreV1().ServiceAccounts(self.Namespace).Get(self.Context, self.NamePrefix, meta.GetOptions{}) 143 | return err == nil 144 | }) 145 | self.WaitForDeletion("custom resource definition", func() bool { 146 | _, err := self.APIExtensions.ApiextensionsV1().CustomResourceDefinitions().Get(self.Context, resources.NetworkCustomResourceDefinition.Name, meta.GetOptions{}) 147 | return err == nil 148 | }) 149 | } 150 | } 151 | 152 | func (self *Client) createCustomResourceDefinition() (*apiextensions.CustomResourceDefinition, error) { 153 | customResourceDefinition := &resources.NetworkCustomResourceDefinition 154 | 155 | if customResourceDefinition, err := self.APIExtensions.ApiextensionsV1().CustomResourceDefinitions().Create(self.Context, customResourceDefinition, meta.CreateOptions{}); err == nil { 156 | return customResourceDefinition, nil 157 | } else if errors.IsAlreadyExists(err) { 158 | self.Log.Infof("%s", err.Error()) 159 | return self.APIExtensions.ApiextensionsV1().CustomResourceDefinitions().Get(self.Context, resources.NetworkCustomResourceDefinition.Name, meta.GetOptions{}) 160 | } else { 161 | return nil, err 162 | } 163 | } 164 | 165 | func (self *Client) createNamespace() (*core.Namespace, error) { 166 | namespace := &core.Namespace{ 167 | ObjectMeta: meta.ObjectMeta{ 168 | Name: self.Namespace, 169 | }, 170 | } 171 | 172 | if namespace, err := self.Kubernetes.CoreV1().Namespaces().Create(self.Context, namespace, meta.CreateOptions{}); err == nil { 173 | return namespace, nil 174 | } else if errors.IsAlreadyExists(err) { 175 | self.Log.Infof("%s", err.Error()) 176 | return self.Kubernetes.CoreV1().Namespaces().Get(self.Context, self.Namespace, meta.GetOptions{}) 177 | } else { 178 | return nil, err 179 | } 180 | } 181 | 182 | func (self *Client) createServiceAccount() (*core.ServiceAccount, error) { 183 | serviceAccount := &core.ServiceAccount{ 184 | ObjectMeta: meta.ObjectMeta{ 185 | Name: self.NamePrefix, 186 | }, 187 | } 188 | 189 | if serviceAccount, err := self.Kubernetes.CoreV1().ServiceAccounts(self.Namespace).Create(self.Context, serviceAccount, meta.CreateOptions{}); err == nil { 190 | return serviceAccount, nil 191 | } else if errors.IsAlreadyExists(err) { 192 | self.Log.Infof("%s", err.Error()) 193 | return self.Kubernetes.CoreV1().ServiceAccounts(self.Namespace).Get(self.Context, self.NamePrefix, meta.GetOptions{}) 194 | } else { 195 | return nil, err 196 | } 197 | } 198 | 199 | func (self *Client) createRole() (*rbac.Role, error) { 200 | role := &rbac.Role{ 201 | ObjectMeta: meta.ObjectMeta{ 202 | Name: self.NamePrefix, 203 | }, 204 | Rules: []rbac.PolicyRule{ 205 | { 206 | APIGroups: []string{rbac.APIGroupAll}, 207 | Resources: []string{rbac.ResourceAll}, 208 | Verbs: []string{rbac.VerbAll}, 209 | }, 210 | }, 211 | } 212 | 213 | if role, err := self.Kubernetes.RbacV1().Roles(self.Namespace).Create(self.Context, role, meta.CreateOptions{}); err == nil { 214 | return role, err 215 | } else if errors.IsAlreadyExists(err) { 216 | self.Log.Infof("%s", err.Error()) 217 | return self.Kubernetes.RbacV1().Roles(self.Namespace).Get(self.Context, self.NamePrefix, meta.GetOptions{}) 218 | } else { 219 | return nil, err 220 | } 221 | } 222 | 223 | func (self *Client) createRoleBinding(serviceAccount *core.ServiceAccount, role *rbac.Role) (*rbac.RoleBinding, error) { 224 | roleBinding := &rbac.RoleBinding{ 225 | ObjectMeta: meta.ObjectMeta{ 226 | Name: self.NamePrefix, 227 | }, 228 | Subjects: []rbac.Subject{ 229 | { 230 | Kind: rbac.ServiceAccountKind, // serviceAccount.Kind is empty 231 | Name: serviceAccount.Name, 232 | Namespace: self.Namespace, // required 233 | }, 234 | }, 235 | RoleRef: rbac.RoleRef{ 236 | APIGroup: rbac.GroupName, // role.GroupVersionKind().Group is empty 237 | Kind: "Role", // role.Kind is empty 238 | Name: role.Name, 239 | }, 240 | } 241 | 242 | if roleBinding, err := self.Kubernetes.RbacV1().RoleBindings(self.Namespace).Create(self.Context, roleBinding, meta.CreateOptions{}); err == nil { 243 | return roleBinding, nil 244 | } else if errors.IsAlreadyExists(err) { 245 | self.Log.Infof("%s", err.Error()) 246 | return self.Kubernetes.RbacV1().RoleBindings(self.Namespace).Get(self.Context, self.NamePrefix, meta.GetOptions{}) 247 | } else { 248 | return nil, err 249 | } 250 | } 251 | 252 | func (self *Client) createClusterRoleBinding(serviceAccount *core.ServiceAccount) (*rbac.ClusterRoleBinding, error) { 253 | clusterRoleBinding := &rbac.ClusterRoleBinding{ 254 | ObjectMeta: meta.ObjectMeta{ 255 | Name: self.NamePrefix, 256 | }, 257 | Subjects: []rbac.Subject{ 258 | { 259 | Kind: rbac.ServiceAccountKind, // serviceAccount.Kind is empty 260 | Name: serviceAccount.Name, 261 | Namespace: self.Namespace, // required 262 | }, 263 | }, 264 | RoleRef: rbac.RoleRef{ 265 | APIGroup: rbac.GroupName, 266 | Kind: "ClusterRole", 267 | Name: "cluster-admin", 268 | }, 269 | } 270 | 271 | if clusterRoleBinding, err := self.Kubernetes.RbacV1().ClusterRoleBindings().Create(self.Context, clusterRoleBinding, meta.CreateOptions{}); err == nil { 272 | return clusterRoleBinding, nil 273 | } else if errors.IsAlreadyExists(err) { 274 | self.Log.Infof("%s", err.Error()) 275 | return self.Kubernetes.RbacV1().ClusterRoleBindings().Get(self.Context, self.NamePrefix, meta.GetOptions{}) 276 | } else { 277 | return nil, err 278 | } 279 | } 280 | 281 | func (self *Client) createOperatorDeployment(registry string, serviceAccount *core.ServiceAccount, replicas int32) (*apps.Deployment, error) { 282 | appName := fmt.Sprintf("%s-operator", self.NamePrefix) 283 | labels := self.Labels(appName, "operator", self.Namespace) 284 | 285 | deployment := &apps.Deployment{ 286 | ObjectMeta: meta.ObjectMeta{ 287 | Name: appName, 288 | Labels: labels, 289 | }, 290 | Spec: apps.DeploymentSpec{ 291 | Replicas: &replicas, 292 | Selector: &meta.LabelSelector{ 293 | MatchLabels: labels, 294 | }, 295 | Template: core.PodTemplateSpec{ 296 | ObjectMeta: meta.ObjectMeta{ 297 | Labels: labels, 298 | }, 299 | Spec: core.PodSpec{ 300 | ServiceAccountName: serviceAccount.Name, 301 | Containers: []core.Container{ 302 | { 303 | Name: "operator", 304 | Image: fmt.Sprintf("%s/%s", registry, self.OperatorImageName), 305 | ImagePullPolicy: core.PullAlways, 306 | Env: []core.EnvVar{ 307 | { 308 | Name: "KNAP_OPERATOR_concurrency", 309 | Value: "3", 310 | }, 311 | { 312 | Name: "KNAP_OPERATOR_verbose", 313 | Value: "1", 314 | }, 315 | }, 316 | LivenessProbe: &core.Probe{ 317 | ProbeHandler: core.ProbeHandler{ 318 | HTTPGet: &core.HTTPGetAction{ 319 | Port: intstr.FromInt(8086), 320 | Path: "/live", 321 | }, 322 | }, 323 | }, 324 | ReadinessProbe: &core.Probe{ 325 | ProbeHandler: core.ProbeHandler{ 326 | HTTPGet: &core.HTTPGetAction{ 327 | Port: intstr.FromInt(8086), 328 | Path: "/ready", 329 | }, 330 | }, 331 | }, 332 | SecurityContext: self.DefaultSecurityContext(), 333 | }, 334 | }, 335 | }, 336 | }, 337 | }, 338 | } 339 | 340 | return self.CreateDeployment(deployment, appName) 341 | } 342 | 343 | func (self *Client) createProviderDeployment(provider string, imageName string, registry string, serviceAccount *core.ServiceAccount, replicas int32) (*apps.Deployment, error) { 344 | appName := fmt.Sprintf("%s-provider-%s", self.NamePrefix, provider) 345 | labels := self.Labels(appName, "provider", self.Namespace) 346 | 347 | deployment := &apps.Deployment{ 348 | ObjectMeta: meta.ObjectMeta{ 349 | Name: appName, 350 | Labels: labels, 351 | }, 352 | Spec: apps.DeploymentSpec{ 353 | Replicas: &replicas, 354 | Selector: &meta.LabelSelector{ 355 | MatchLabels: labels, 356 | }, 357 | Template: core.PodTemplateSpec{ 358 | ObjectMeta: meta.ObjectMeta{ 359 | Labels: labels, 360 | }, 361 | Spec: core.PodSpec{ 362 | ServiceAccountName: serviceAccount.Name, 363 | Containers: []core.Container{ 364 | { 365 | Name: "provider", 366 | Image: fmt.Sprintf("%s/%s", registry, imageName), 367 | ImagePullPolicy: core.PullAlways, 368 | LivenessProbe: &core.Probe{ 369 | ProbeHandler: core.ProbeHandler{ 370 | HTTPGet: &core.HTTPGetAction{ 371 | Port: intstr.FromInt(8086), 372 | Path: "/live", 373 | }, 374 | }, 375 | }, 376 | ReadinessProbe: &core.Probe{ 377 | ProbeHandler: core.ProbeHandler{ 378 | HTTPGet: &core.HTTPGetAction{ 379 | Port: intstr.FromInt(8086), 380 | Path: "/ready", 381 | }, 382 | }, 383 | }, 384 | SecurityContext: self.DefaultSecurityContext(), 385 | }, 386 | }, 387 | }, 388 | }, 389 | }, 390 | } 391 | 392 | return self.CreateDeployment(deployment, appName) 393 | } 394 | --------------------------------------------------------------------------------