From ffdb682517351c2f3eec802821eaa4b31a5bcf23 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Tue, 29 Jun 2021 16:55:03 +0200 Subject: [PATCH 01/16] Extract the kubernetes connection related data to a struct To preserve the existing code base, we embed the new type within the KubernetesIPAM (old type). In a follow-up commit, this new struct will be used on a separate golang binary (which doesn't require the KubernetesIPAM configuration), to provide a simple connection to the Kubernetes backend (does not support etcd). Signed-off-by: Miguel Duarte Barroso --- pkg/storage/kubernetes.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pkg/storage/kubernetes.go b/pkg/storage/kubernetes.go index 6d1d5966c..7f0bae0ce 100644 --- a/pkg/storage/kubernetes.go +++ b/pkg/storage/kubernetes.go @@ -68,17 +68,27 @@ func NewKubernetesIPAM(containerID string, ipamConf whereaboutstypes.IPAMConfig) if err != nil { return nil, err } - return &KubernetesIPAM{c, clientSet, ipamConf, containerID, namespace, DatastoreRetries}, nil + + k8sIPAM := &KubernetesIPAM{config: ipamConf, containerID: containerID, namespace: namespace} + k8sIPAM.client = c + k8sIPAM.clientSet = clientSet + k8sIPAM.retries = DatastoreRetries + return k8sIPAM, nil } -// KubernetesIPAM manages ip blocks in an kubernetes CRD backend -type KubernetesIPAM struct { +// KubernetesClient has info on how to connect to the kubernetes cluster +type KubernetesClient struct { client client.Client clientSet *kubernetes.Clientset + retries int +} + +// KubernetesIPAM manages ip blocks in an kubernetes CRD backend +type KubernetesIPAM struct { + KubernetesClient config whereaboutstypes.IPAMConfig containerID string namespace string - retries int } func toIPReservationList(allocations map[string]whereaboutsv1alpha1.IPAllocation, firstip net.IP) []whereaboutstypes.IPReservation { From ac7507e5bbc58a0e0a8b596662775715a7578736 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Tue, 29 Jun 2021 16:11:46 +0200 Subject: [PATCH 02/16] ip-reconciler: add new binary Fow now, we only read the kubeconfig file, which is specified via the cmd line - as its only argument. Signed-off-by: Miguel Duarte Barroso --- cmd/reconciler/errors.go | 4 ++++ cmd/reconciler/ip.go | 20 ++++++++++++++++++++ hack/build-go.sh | 1 + 3 files changed, 25 insertions(+) create mode 100644 cmd/reconciler/errors.go create mode 100644 cmd/reconciler/ip.go diff --git a/cmd/reconciler/errors.go b/cmd/reconciler/errors.go new file mode 100644 index 000000000..38f74fe43 --- /dev/null +++ b/cmd/reconciler/errors.go @@ -0,0 +1,4 @@ +package main + +const kubeconfigNotFound = iota + 1 + diff --git a/cmd/reconciler/ip.go b/cmd/reconciler/ip.go new file mode 100644 index 000000000..716cb9e4a --- /dev/null +++ b/cmd/reconciler/ip.go @@ -0,0 +1,20 @@ +package main + +import ( + "flag" + "os" + + "github.com/dougbtv/whereabouts/pkg/logging" +) + +func main() { + kubeConfigFile := flag.String("kubeconfig", "", "the path to the Kubernetes configuration file") + flag.Parse() + + if *kubeConfigFile == "" { + _ = logging.Errorf("must specify the kubernetes config file, via the '-kubeconfig' flag") + os.Exit(kubeconfigNotFound) + } + + logging.Debugf("Kubernetes config file located at: %s", kubeConfigFile) +} diff --git a/hack/build-go.sh b/hack/build-go.sh index ac71eb6f9..ffb14a033 100755 --- a/hack/build-go.sh +++ b/hack/build-go.sh @@ -7,3 +7,4 @@ GOARCH=${GOARCH:-${GOHOSTARCH}} GOFLAGS=${GOFLAGS:-} GLDFLAGS=${GLDFLAGS:-} CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build ${GOFLAGS} -ldflags "${GLDFLAGS}" -o bin/${cmd} cmd/${cmd}.go +CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build ${GOFLAGS} -ldflags "${GLDFLAGS}" -o bin/ip-reconciler cmd/reconciler/*.go From 7d70fb84c74e42dbe8e9e6380bca3242ccf77d30 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Tue, 29 Jun 2021 17:00:20 +0200 Subject: [PATCH 03/16] Generate the Kubernetes client object Introduce a dedicated constructor in the storage pkg to create the newly created `KubernetesClient` struct. The existing `KubernetesIPAM` struct will re-use this new constructor. Signed-off-by: Miguel Duarte Barroso --- cmd/reconciler/errors.go | 6 +- cmd/reconciler/ip.go | 9 +- cmd/whereabouts.go | 5 +- go.mod | 1 + pkg/storage/etcd.go | 15 ++- pkg/storage/kubernetes/client.go | 93 +++++++++++++++++++ pkg/storage/kubernetes/errors.go | 9 ++ .../{kubernetes.go => kubernetes/ipam.go} | 91 ++++++------------ pkg/storage/storage.go | 4 + 9 files changed, 154 insertions(+), 79 deletions(-) create mode 100644 pkg/storage/kubernetes/client.go create mode 100644 pkg/storage/kubernetes/errors.go rename pkg/storage/{kubernetes.go => kubernetes/ipam.go} (88%) diff --git a/cmd/reconciler/errors.go b/cmd/reconciler/errors.go index 38f74fe43..589d40e27 100644 --- a/cmd/reconciler/errors.go +++ b/cmd/reconciler/errors.go @@ -1,4 +1,6 @@ package main -const kubeconfigNotFound = iota + 1 - +const ( + kubeconfigNotFound = iota + 1 + couldNotConnectToKubernetes +) diff --git a/cmd/reconciler/ip.go b/cmd/reconciler/ip.go index 716cb9e4a..a57e01ccd 100644 --- a/cmd/reconciler/ip.go +++ b/cmd/reconciler/ip.go @@ -5,6 +5,7 @@ import ( "os" "github.com/dougbtv/whereabouts/pkg/logging" + "github.com/dougbtv/whereabouts/pkg/storage/kubernetes" ) func main() { @@ -15,6 +16,12 @@ func main() { _ = logging.Errorf("must specify the kubernetes config file, via the '-kubeconfig' flag") os.Exit(kubeconfigNotFound) } + logging.Debugf("Kubernetes config file located at: %s", *kubeConfigFile) - logging.Debugf("Kubernetes config file located at: %s", kubeConfigFile) + _, err := kubernetes.NewClient(*kubeConfigFile) + if err != nil { + _ = logging.Errorf("failed to instantiate the Kubernetes client: %+v", err) + os.Exit(couldNotConnectToKubernetes) + } + logging.Debugf("created kubernetes client via kubeconfig located at: %s", *kubeConfigFile) } diff --git a/cmd/whereabouts.go b/cmd/whereabouts.go index 2cc41c85c..463191ecc 100644 --- a/cmd/whereabouts.go +++ b/cmd/whereabouts.go @@ -13,6 +13,7 @@ import ( "github.com/dougbtv/whereabouts/pkg/config" "github.com/dougbtv/whereabouts/pkg/logging" "github.com/dougbtv/whereabouts/pkg/storage" + "github.com/dougbtv/whereabouts/pkg/storage/kubernetes" "github.com/dougbtv/whereabouts/pkg/types" ) @@ -46,7 +47,7 @@ func cmdAdd(args *skel.CmdArgs) error { case types.DatastoreETCD: newip, err = storage.IPManagementEtcd(types.Allocate, *ipamConf, args.ContainerID, getPodRef(args.Args)) case types.DatastoreKubernetes: - newip, err = storage.IPManagementKubernetes(types.Allocate, *ipamConf, args.ContainerID, getPodRef(args.Args)) + newip, err = kubernetes.IPManagement(types.Allocate, *ipamConf, args.ContainerID, getPodRef(args.Args)) } if err != nil { logging.Errorf("Error at storage engine: %s", err) @@ -90,7 +91,7 @@ func cmdDel(args *skel.CmdArgs) error { case types.DatastoreETCD: _, err = storage.IPManagementEtcd(types.Deallocate, *ipamConf, args.ContainerID, getPodRef(args.Args)) case types.DatastoreKubernetes: - _, err = storage.IPManagementKubernetes(types.Deallocate, *ipamConf, args.ContainerID, getPodRef(args.Args)) + _, err = kubernetes.IPManagement(types.Deallocate, *ipamConf, args.ContainerID, getPodRef(args.Args)) } if err != nil { logging.Verbosef("WARNING: Problem deallocating IP: %s", err) diff --git a/go.mod b/go.mod index aee2b11f2..62d1c9aaa 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( gomodules.xyz/jsonpatch/v2 v2.0.1 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect google.golang.org/grpc v1.23.0 // indirect + k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible sigs.k8s.io/controller-runtime v0.2.2 diff --git a/pkg/storage/etcd.go b/pkg/storage/etcd.go index 46b65b527..548d9e7d8 100644 --- a/pkg/storage/etcd.go +++ b/pkg/storage/etcd.go @@ -178,15 +178,12 @@ func IPManagementEtcd(mode int, ipamConf types.IPAMConfig, containerID string, p var ipam Store var pool IPPool var err error - switch ipamConf.Datastore { - case types.DatastoreETCD: - ipam, err = NewETCDIPAM(ipamConf) - case types.DatastoreKubernetes: - ipam, err = NewKubernetesIPAM(containerID, ipamConf) + if ipamConf.Datastore != types.DatastoreETCD { + return net.IPNet{}, logging.Errorf("wrong 'datastore' value in IPAM config: %s", ipamConf.Datastore) } + ipam, err = NewETCDIPAM(ipamConf) if err != nil { - logging.Errorf("IPAM %s client initialization error: %v", ipamConf.Datastore, err) - return newip, fmt.Errorf("IPAM %s client initialization error: %v", ipamConf.Datastore, err) + return newip, logging.Errorf("IPAM %s client initialization error: %v", ipamConf.Datastore, err) } defer ipam.Close() @@ -212,7 +209,7 @@ RETRYLOOP: pool, err = ipam.GetIPPool(ctx, ipamConf.Range) if err != nil { logging.Errorf("IPAM error reading pool allocations (attempt: %d): %v", j, err) - if e, ok := err.(temporary); ok && e.Temporary() { + if e, ok := err.(Temporary); ok && e.Temporary() { continue } return newip, err @@ -238,7 +235,7 @@ RETRYLOOP: err = pool.Update(ctx, updatedreservelist) if err != nil { logging.Errorf("IPAM error updating pool (attempt: %d): %v", j, err) - if e, ok := err.(temporary); ok && e.Temporary() { + if e, ok := err.(Temporary); ok && e.Temporary() { continue } break RETRYLOOP diff --git a/pkg/storage/kubernetes/client.go b/pkg/storage/kubernetes/client.go new file mode 100644 index 000000000..96fad7164 --- /dev/null +++ b/pkg/storage/kubernetes/client.go @@ -0,0 +1,93 @@ +package kubernetes + +import ( + "context" + whereaboutsv1alpha1 "github.com/dougbtv/whereabouts/pkg/api/v1alpha1" + "github.com/dougbtv/whereabouts/pkg/logging" + "github.com/dougbtv/whereabouts/pkg/storage" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" +) + +// Client has info on how to connect to the kubernetes cluster +type Client struct { + client client.Client + clientSet *kubernetes.Clientset + retries int +} + +func NewClient(kubeconfigPath string) (*Client, error) { + scheme := runtime.NewScheme() + _ = whereaboutsv1alpha1.AddToScheme(scheme) + + config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath}, + &clientcmd.ConfigOverrides{}).ClientConfig() + if err != nil { + return nil, err + } + + clientSet, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + mapper, err := apiutil.NewDiscoveryRESTMapper(config) + if err != nil { + return nil, err + } + c, err := client.New(config, client.Options{Scheme: scheme, Mapper: mapper}) + if err != nil { + return nil, err + } + + return newKubernetesClient(c, clientSet), nil +} + +func newKubernetesClient(k8sClient client.Client, k8sClientSet *kubernetes.Clientset) *Client { + return &Client{ + client: k8sClient, + clientSet: k8sClientSet, + retries: storage.DatastoreRetries, + } +} + +func (i *Client) ListIPPools(ctx context.Context) ([]storage.IPPool, error) { + logging.Debugf("listing IP pools") + ipPoolList := &whereaboutsv1alpha1.IPPoolList{} + + if err := i.client.List(ctx, ipPoolList, &client.ListOptions{}); err != nil { + return nil, err + } + + var whereaboutsApiIPPoolList []storage.IPPool + for idx, pool := range ipPoolList.Items { + firstIP, _, err := pool.ParseCIDR() + if err != nil { + return nil, err + } + whereaboutsApiIPPoolList = append( + whereaboutsApiIPPoolList, + &KubernetesIPPool{client: i.client, firstIP: firstIP, pool: &ipPoolList.Items[idx]}) + } + return whereaboutsApiIPPoolList, nil +} + +func (i *Client) ListPods() ([]v1.Pod, error) { + podList, err := i.clientSet.CoreV1().Pods(metav1.NamespaceAll).List(metav1.ListOptions{}) + if err != nil { + return nil, err + } + + var podEntries []v1.Pod + for _, pod := range podList.Items { + podEntries = append(podEntries, pod) + } + return podEntries, nil +} diff --git a/pkg/storage/kubernetes/errors.go b/pkg/storage/kubernetes/errors.go new file mode 100644 index 000000000..5cd654a11 --- /dev/null +++ b/pkg/storage/kubernetes/errors.go @@ -0,0 +1,9 @@ +package kubernetes + +type temporaryError struct { + error +} + +func (t *temporaryError) Temporary() bool { + return true +} diff --git a/pkg/storage/kubernetes.go b/pkg/storage/kubernetes/ipam.go similarity index 88% rename from pkg/storage/kubernetes.go rename to pkg/storage/kubernetes/ipam.go index 7f0bae0ce..123d44936 100644 --- a/pkg/storage/kubernetes.go +++ b/pkg/storage/kubernetes/ipam.go @@ -1,4 +1,4 @@ -package storage +package kubernetes import ( "context" @@ -17,41 +17,18 @@ import ( "github.com/dougbtv/whereabouts/pkg/allocate" whereaboutsv1alpha1 "github.com/dougbtv/whereabouts/pkg/api/v1alpha1" "github.com/dougbtv/whereabouts/pkg/logging" + "github.com/dougbtv/whereabouts/pkg/storage" whereaboutstypes "github.com/dougbtv/whereabouts/pkg/types" jsonpatch "gomodules.xyz/jsonpatch/v2" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) // NewKubernetesIPAM returns a new KubernetesIPAM Client configured to a kubernetes CRD backend func NewKubernetesIPAM(containerID string, ipamConf whereaboutstypes.IPAMConfig) (*KubernetesIPAM, error) { - scheme := runtime.NewScheme() - _ = whereaboutsv1alpha1.AddToScheme(scheme) - - overrides := &clientcmd.ConfigOverrides{} - if apiURL := ipamConf.Kubernetes.K8sAPIRoot; apiURL != "" { - overrides.ClusterInfo = clientcmdapi.Cluster{ - Server: apiURL, - } - } - config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - &clientcmd.ClientConfigLoadingRules{ExplicitPath: ipamConf.Kubernetes.KubeConfigPath}, - overrides).ClientConfig() - if err != nil { - return nil, err - } - - clientSet, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - var namespace string if cfg, err := clientcmd.LoadFromFile(ipamConf.Kubernetes.KubeConfigPath); err != nil { return nil, err @@ -60,32 +37,28 @@ func NewKubernetesIPAM(containerID string, ipamConf whereaboutstypes.IPAMConfig) } else { return nil, fmt.Errorf("k8s config: namespace not present in context") } - mapper, err := apiutil.NewDiscoveryRESTMapper(config) - if err != nil { - return nil, err - } - c, err := client.New(config, client.Options{Scheme: scheme, Mapper: mapper}) + + kubernetesClient, err := NewClient(ipamConf.Kubernetes.KubeConfigPath) if err != nil { - return nil, err + return nil, fmt.Errorf("failed instantiating kubernetes client: %v", err) } - - k8sIPAM := &KubernetesIPAM{config: ipamConf, containerID: containerID, namespace: namespace} - k8sIPAM.client = c - k8sIPAM.clientSet = clientSet - k8sIPAM.retries = DatastoreRetries + k8sIPAM := newKubernetesIPAM(containerID, ipamConf, namespace, *kubernetesClient) return k8sIPAM, nil } -// KubernetesClient has info on how to connect to the kubernetes cluster -type KubernetesClient struct { - client client.Client - clientSet *kubernetes.Clientset - retries int +func newKubernetesIPAM(containerID string, ipamConf whereaboutstypes.IPAMConfig, namespace string, kubernetesClient Client) *KubernetesIPAM { + return &KubernetesIPAM{ + + config: ipamConf, + containerID: containerID, + namespace: namespace, + Client: kubernetesClient, + } } // KubernetesIPAM manages ip blocks in an kubernetes CRD backend type KubernetesIPAM struct { - KubernetesClient + Client config whereaboutstypes.IPAMConfig containerID string namespace string @@ -117,7 +90,7 @@ func toAllocationMap(reservelist []whereaboutstypes.IPReservation, firstip net.I } // GetIPPool returns a storage.IPPool for the given range -func (i *KubernetesIPAM) GetIPPool(ctx context.Context, ipRange string) (IPPool, error) { +func (i *KubernetesIPAM) GetIPPool(ctx context.Context, ipRange string) (storage.IPPool, error) { // v6 filter normalized := strings.ReplaceAll(ipRange, ":", "-") // replace subnet cidr slash @@ -180,7 +153,7 @@ type KubernetesOverlappingRangeStore struct { } // GetOverlappingRangeStore returns a clusterstore interface -func (i *KubernetesIPAM) GetOverlappingRangeStore() (OverlappingRangeStore, error) { +func (i *KubernetesIPAM) GetOverlappingRangeStore() (storage.OverlappingRangeStore, error) { return &KubernetesOverlappingRangeStore{i.client, i.containerID, i.namespace}, nil } @@ -313,18 +286,6 @@ func (p *KubernetesIPPool) Update(ctx context.Context, reservations []whereabout return nil } -type temporaryError struct { - error -} - -func (t *temporaryError) Temporary() bool { - return true -} - -type temporary interface { - Temporary() bool -} - // newLeaderElector creates a new leaderelection.LeaderElector and associated // channels by which to observe elections and depositions. func newLeaderElector(clientset *kubernetes.Clientset, namespace string, podNamespace string, podID string, leaseDuration int, renewDeadline int, retryPeriod int) (*leaderelection.LeaderElector, chan struct{}, chan struct{}) { @@ -373,8 +334,8 @@ func newLeaderElector(clientset *kubernetes.Clientset, namespace string, podName return le, leaderOK, deposed } -// IPManagementKubernetes manages ip allocation and deallocation from a storage perspective -func IPManagementKubernetes(mode int, ipamConf whereaboutstypes.IPAMConfig, containerID string, podRef string) (net.IPNet, error) { +// IPManagement manages ip allocation and deallocation from a storage perspective +func IPManagement(mode int, ipamConf whereaboutstypes.IPAMConfig, containerID string, podRef string) (net.IPNet, error) { var newip net.IPNet ipam, err := NewKubernetesIPAM(containerID, ipamConf) @@ -437,7 +398,7 @@ func IPManagementKubernetes(mode int, ipamConf whereaboutstypes.IPAMConfig, cont wg.Wait() close(stopM) - logging.Debugf("IPManagementKubernetes: %v, %v", newip, err) + logging.Debugf("IPManagement: %v, %v", newip, err) return newip, err } @@ -453,11 +414,11 @@ func IPManagementKubernetesUpdate(mode int, ipam *KubernetesIPAM, ipamConf where return newip, fmt.Errorf("Got an unknown mode passed to IPManagement: %v", mode) } - var overlappingrangestore OverlappingRangeStore - var pool IPPool + var overlappingrangestore storage.OverlappingRangeStore + var pool storage.IPPool var err error - ctx, cancel := context.WithTimeout(context.Background(), RequestTimeout) + ctx, cancel := context.WithTimeout(context.Background(), storage.RequestTimeout) defer cancel() // Check our connectivity first @@ -470,7 +431,7 @@ func IPManagementKubernetesUpdate(mode int, ipam *KubernetesIPAM, ipamConf where var overlappingrangeallocations []whereaboutstypes.IPReservation var ipforoverlappingrangeupdate net.IP RETRYLOOP: - for j := 0; j < DatastoreRetries; j++ { + for j := 0; j < storage.DatastoreRetries; j++ { select { case <-ctx.Done(): return newip, nil @@ -487,7 +448,7 @@ RETRYLOOP: pool, err = ipam.GetIPPool(ctx, ipamConf.Range) if err != nil { logging.Errorf("IPAM error reading pool allocations (attempt: %d): %v", j, err) - if e, ok := err.(temporary); ok && e.Temporary() { + if e, ok := err.(storage.Temporary); ok && e.Temporary() { continue } return newip, err @@ -542,7 +503,7 @@ RETRYLOOP: err = pool.Update(ctx, usereservelist) if err != nil { logging.Errorf("IPAM error updating pool (attempt: %d): %v", j, err) - if e, ok := err.(temporary); ok && e.Temporary() { + if e, ok := err.(storage.Temporary); ok && e.Temporary() { continue } break RETRYLOOP diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 168f16bff..a2bda0306 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -35,3 +35,7 @@ type OverlappingRangeStore interface { IsAllocatedInOverlappingRange(ctx context.Context, ip net.IP) (bool, error) UpdateOverlappingRangeAllocation(ctx context.Context, mode int, ip net.IP, containerID string, podRef string) error } + +type Temporary interface { + Temporary() bool +} From edf1bf523c29f85130422f2e0679e7cd194b985e Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Wed, 30 Jun 2021 13:55:09 +0200 Subject: [PATCH 04/16] Have IPReservation implement the Stringer interface Follow-up commits will use this new method for debugging, which makes it simpler to track the IP to pod assignments. Signed-off-by: Miguel Duarte Barroso --- pkg/types/types.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/types/types.go b/pkg/types/types.go index 54e5f8466..1228c0473 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "net" cnitypes "github.com/containernetworking/cni/pkg/types" @@ -87,6 +88,10 @@ type IPReservation struct { IsAllocated bool } +func (ir IPReservation) String() string { + return fmt.Sprintf("IP: %s is reserved for pod: %s", ir.IP.String(), ir.PodRef) +} + const ( // Allocate operation identifier Allocate = 0 From 34acf15a91b052e59ee44d10067d03fc4ea86c7b Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Wed, 30 Jun 2021 18:35:02 +0200 Subject: [PATCH 05/16] Export find IPReservation matching ID to a func Currently, matching the owner of the IP when deallocating the address happens only based on the containerID. In follow-up commits we will require this behavior to happen based on the pod reference attribute. This commit prepares the code base for that, by enabling the caller to inject the criteria for identifying the owner of an IP into the IP address de-allocation function. Signed-off-by: Miguel Duarte Barroso --- pkg/allocate/allocate.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/pkg/allocate/allocate.go b/pkg/allocate/allocate.go index a236fbd8d..45c95d44d 100644 --- a/pkg/allocate/allocate.go +++ b/pkg/allocate/allocate.go @@ -37,7 +37,7 @@ func AssignIP(ipamConf types.IPAMConfig, reservelist []types.IPReservation, cont // DeallocateIP assigns an IP using a range and a reserve list. func DeallocateIP(reservelist []types.IPReservation, containerID string) ([]types.IPReservation, net.IP, error) { - updatedreservelist, hadip, err := IterateForDeallocation(reservelist, containerID) + updatedreservelist, hadip, err := IterateForDeallocation(reservelist, containerID, getMatchingIPReservationIndex) if err != nil { return nil, nil, err } @@ -48,17 +48,12 @@ func DeallocateIP(reservelist []types.IPReservation, containerID string) ([]type } // IterateForDeallocation iterates overs currently reserved IPs and the deallocates given the container id. -func IterateForDeallocation(reservelist []types.IPReservation, containerID string) ([]types.IPReservation, net.IP, error) { - - // Cycle through and find the index that corresponds to our containerID - foundidx := -1 - for idx, v := range reservelist { - if v.ContainerID == containerID { - foundidx = idx - break - } - } +func IterateForDeallocation( + reservelist []types.IPReservation, + containerID string, + matchingFunction func(reservation []types.IPReservation, id string) int) ([]types.IPReservation, net.IP, error) { + foundidx := matchingFunction(reservelist, containerID) // Check if it's a valid index if foundidx < 0 { return reservelist, nil, fmt.Errorf("Did not find reserved IP for container %v", containerID) @@ -70,6 +65,17 @@ func IterateForDeallocation(reservelist []types.IPReservation, containerID strin return updatedreservelist, returnip, nil } +func getMatchingIPReservationIndex(reservelist []types.IPReservation, id string) int { + foundidx := -1 + for idx, v := range reservelist { + if v.ContainerID == id { + foundidx = idx + break + } + } + return foundidx +} + func removeIdxFromSlice(s []types.IPReservation, i int) []types.IPReservation { s[i] = s[len(s)-1] return s[:len(s)-1] From 4e0aa2e9222b8fc8df34a71a9c65e475b07fbfba Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Tue, 29 Jun 2021 17:59:26 +0200 Subject: [PATCH 06/16] ip-reconciler: find and remove orphaned IP addresses This behavior will be encapsulated in a new struct named `ReconcileLooper`, which features methods for: - CancelContext; cancels the current execution context - FindOrphanedIPsPerPool; returns a list of the orphaned IP addresses, indexed by pool - ReconcileIPPool; receives a list of orphaned IP addresses, and cleans them up from the respective pools where they are currently allocated. Signed-off-by: Miguel Duarte Barroso --- cmd/reconciler/errors.go | 3 +- cmd/reconciler/ip.go | 24 +++-- pkg/reconciler/iploop.go | 156 +++++++++++++++++++++++++++++++++ pkg/storage/kubernetes/ipam.go | 1 - 4 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 pkg/reconciler/iploop.go diff --git a/cmd/reconciler/errors.go b/cmd/reconciler/errors.go index 589d40e27..7acd86821 100644 --- a/cmd/reconciler/errors.go +++ b/cmd/reconciler/errors.go @@ -2,5 +2,6 @@ package main const ( kubeconfigNotFound = iota + 1 - couldNotConnectToKubernetes + couldNotStartOrphanedIPMonitor + failedToReconcile ) diff --git a/cmd/reconciler/ip.go b/cmd/reconciler/ip.go index a57e01ccd..7d9b4cffa 100644 --- a/cmd/reconciler/ip.go +++ b/cmd/reconciler/ip.go @@ -1,11 +1,13 @@ package main import ( + "context" "flag" + "github.com/dougbtv/whereabouts/pkg/storage" "os" "github.com/dougbtv/whereabouts/pkg/logging" - "github.com/dougbtv/whereabouts/pkg/storage/kubernetes" + "github.com/dougbtv/whereabouts/pkg/reconciler" ) func main() { @@ -16,12 +18,22 @@ func main() { _ = logging.Errorf("must specify the kubernetes config file, via the '-kubeconfig' flag") os.Exit(kubeconfigNotFound) } - logging.Debugf("Kubernetes config file located at: %s", *kubeConfigFile) - _, err := kubernetes.NewClient(*kubeConfigFile) + ctx, cancel := context.WithTimeout(context.Background(), storage.RequestTimeout) + defer cancel() + ipReconcileLoop, err := reconciler.NewReconcileLooper(*kubeConfigFile, ctx) if err != nil { - _ = logging.Errorf("failed to instantiate the Kubernetes client: %+v", err) - os.Exit(couldNotConnectToKubernetes) + _ = logging.Errorf("failed to create the reconcile looper: %v", err) + os.Exit(couldNotStartOrphanedIPMonitor) } - logging.Debugf("created kubernetes client via kubeconfig located at: %s", *kubeConfigFile) + + cleanedUpIps, err := ipReconcileLoop.ReconcileIPPools() + if err != nil { + _ = logging.Errorf("failed to clean up IP for allocations: %v", err) + os.Exit(failedToReconcile) + } + if len(cleanedUpIps) > 0 { + logging.Debugf("successfully cleanup IPs: %+v", cleanedUpIps) + } + logging.Debugf("no IP addresses to cleanup") } diff --git a/pkg/reconciler/iploop.go b/pkg/reconciler/iploop.go new file mode 100644 index 000000000..d729e7717 --- /dev/null +++ b/pkg/reconciler/iploop.go @@ -0,0 +1,156 @@ +package reconciler + +import ( + "context" + "fmt" + + "github.com/dougbtv/whereabouts/pkg/allocate" + "github.com/dougbtv/whereabouts/pkg/logging" + "github.com/dougbtv/whereabouts/pkg/storage" + "github.com/dougbtv/whereabouts/pkg/storage/kubernetes" + "github.com/dougbtv/whereabouts/pkg/types" +) + +type ReconcileLooper struct { + ctx context.Context + k8sClient kubernetes.Client + livePodRefs []string + orphanedIPs []OrphanedIPReservations +} + +type OrphanedIPReservations struct { + Pool storage.IPPool + Allocations []types.IPReservation +} + +func NewReconcileLooper(kubeConfigPath string, ctx context.Context) (*ReconcileLooper, error) { + logging.Debugf("NewReconcileLooper - Kubernetes config file located at: %s", kubeConfigPath) + k8sClient, err := kubernetes.NewClient(kubeConfigPath) + if err != nil { + return nil, logging.Errorf("failed to instantiate the Kubernetes client: %+v", err) + } + logging.Debugf("successfully read the kubernetes configuration file located at: %s", kubeConfigPath) + + podRefs, err := getPodRefs(*k8sClient) + if err != nil { + return nil, err + } + + looper := &ReconcileLooper{ + ctx: ctx, + k8sClient: *k8sClient, + livePodRefs: podRefs, + } + + if err := looper.findOrphanedIPsPerPool(); err != nil { + return nil, err + } + return looper, nil +} + +func getPodRefs(k8sClient kubernetes.Client) ([]string, error) { + pods, err := k8sClient.ListPods() + if err != nil { + return nil, err + } + + var podRefs []string + for _, pod := range pods { + podRefs = append(podRefs, fmt.Sprintf("%s/%s", pod.GetNamespace(), pod.GetName())) + } + return podRefs, err +} + +func (rl *ReconcileLooper) findOrphanedIPsPerPool() error { + ipPools, err := rl.k8sClient.ListIPPools(rl.ctx) + if err != nil { + return logging.Errorf("failed to retrieve all IP pools: %v", err) + } + + for _, pool := range ipPools { + orphanIP := OrphanedIPReservations{ + Pool: pool, + } + for _, allocation := range pool.Allocations() { + logging.Debugf("the IP reservation: %s", allocation) + if allocation.PodRef == "" { + _ = logging.Errorf("pod ref missing for Allocations: %s", allocation) + continue + } + if !rl.isPodAlive(allocation.PodRef) { + logging.Debugf("pod ref %s is not listed in the live pods list", allocation.PodRef) + orphanIP.Allocations = append(orphanIP.Allocations, allocation) + } + } + if len(orphanIP.Allocations) > 0 { + rl.orphanedIPs = append(rl.orphanedIPs, orphanIP) + } + } + + return nil +} + +func (rl ReconcileLooper) isPodAlive(podRef string) bool { + for _, livePodRef := range rl.livePodRefs { + if podRef == livePodRef { + return true + } + } + return false +} + +func (rl ReconcileLooper) ReconcileIPPools() ([]types.IPReservation, error) { + matchByPodRef := func(reservations []types.IPReservation, podRef string) int { + foundidx := -1 + for idx, v := range reservations { + if v.PodRef == podRef { + return idx + } + } + return foundidx + } + + var err error + var totalCleanedUpIps []types.IPReservation + for _, orphanedIP := range rl.orphanedIPs { + originalIPReservations := orphanedIP.Pool.Allocations() + currentIPReservations := orphanedIP.Pool.Allocations() + podRefsToDeallocate := findOutPodRefsToDeallocateIPsFrom(orphanedIP) + for _, podRef := range podRefsToDeallocate { + currentIPReservations, _, err = allocate.IterateForDeallocation(currentIPReservations, podRef, matchByPodRef) + if err != nil { + return nil, err + } + } + + logging.Debugf("Going to update the reserve list to: %+v", currentIPReservations) + if err := orphanedIP.Pool.Update(rl.ctx, currentIPReservations); err != nil { + return nil, logging.Errorf("failed to update the reservation list: %v", err) + } + totalCleanedUpIps = append(totalCleanedUpIps, computeCleanedUpIPs(originalIPReservations, currentIPReservations)...) + } + + return totalCleanedUpIps, nil +} + +func computeCleanedUpIPs(oldIPReservations []types.IPReservation, newIPReservations []types.IPReservation) []types.IPReservation { + var deletedReservations []types.IPReservation + ledger := make(map[string]bool) + for _, reservation := range newIPReservations { + ledger[reservation.IP.String()] = true + } + for _, reservation := range oldIPReservations { + if _, found := ledger[reservation.IP.String()]; !found { + deletedReservations = append(deletedReservations, reservation) + } + } + return deletedReservations +} + +func findOutPodRefsToDeallocateIPsFrom(orphanedIP OrphanedIPReservations) []string { + var podRefsToDeallocate []string + for _, orphanedAllocation := range orphanedIP.Allocations { + podRefsToDeallocate = append(podRefsToDeallocate, orphanedAllocation.PodRef) + } + return podRefsToDeallocate +} diff --git a/pkg/storage/kubernetes/ipam.go b/pkg/storage/kubernetes/ipam.go index 123d44936..d8008ebd4 100644 --- a/pkg/storage/kubernetes/ipam.go +++ b/pkg/storage/kubernetes/ipam.go @@ -48,7 +48,6 @@ func NewKubernetesIPAM(containerID string, ipamConf whereaboutstypes.IPAMConfig) func newKubernetesIPAM(containerID string, ipamConf whereaboutstypes.IPAMConfig, namespace string, kubernetesClient Client) *KubernetesIPAM { return &KubernetesIPAM{ - config: ipamConf, containerID: containerID, namespace: namespace, From 8f731970c978f8e8cd2db40336693f0f57755b11 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Fri, 2 Jul 2021 13:31:15 +0200 Subject: [PATCH 07/16] reconciler: add e2e tests Signed-off-by: Miguel Duarte Barroso --- cmd/reconciler/ip.go | 3 +- cmd/reconciler/ip_test.go | 204 +++++++++++++++++++++++++++++++++++ cmd/reconciler/suite_test.go | 114 ++++++++++++++++++++ go.mod | 4 + go.sum | 32 ++++++ 5 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 cmd/reconciler/ip_test.go create mode 100644 cmd/reconciler/suite_test.go diff --git a/cmd/reconciler/ip.go b/cmd/reconciler/ip.go index 7d9b4cffa..6cac19f94 100644 --- a/cmd/reconciler/ip.go +++ b/cmd/reconciler/ip.go @@ -34,6 +34,7 @@ func main() { } if len(cleanedUpIps) > 0 { logging.Debugf("successfully cleanup IPs: %+v", cleanedUpIps) + } else { + logging.Debugf("no IP addresses to cleanup") } - logging.Debugf("no IP addresses to cleanup") } diff --git a/cmd/reconciler/ip_test.go b/cmd/reconciler/ip_test.go new file mode 100644 index 000000000..05a3a31d7 --- /dev/null +++ b/cmd/reconciler/ip_test.go @@ -0,0 +1,204 @@ +package main + +import ( + "context" + "fmt" + "net" + "strings" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/dougbtv/whereabouts/pkg/api/v1alpha1" + "github.com/dougbtv/whereabouts/pkg/reconciler" + "github.com/dougbtv/whereabouts/pkg/types" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8stypes "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("Whereabouts IP reconciler", func() { + const ( + ipRange = "10.10.10.0/16" + namespace = "testns" + networkName = "net1" + podName = "pod1" + ) + + var ( + reconcileLooper *reconciler.ReconcileLooper + ) + + Context("a single running pod", func() { + var pod *v1.Pod + + BeforeEach(func() { + var err error + pod, err = k8sClientSet.CoreV1().Pods(namespace).Create(generatePod(namespace, podName, networkName)) + Expect(err).NotTo(HaveOccurred()) + }) + + Context("with IP from a single IPPool", func() { + const poolName = "pool1" + + var pool *v1alpha1.IPPool + + BeforeEach(func() { + pool = generateIPPoolSpec(ipRange, namespace, poolName, pod.Name) + Expect(k8sClient.Create(context.Background(), pool)).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(k8sClient.Delete(context.Background(), pool)).NotTo(HaveOccurred()) + }) + + Context("the pod dies", func() { + BeforeEach(func() { + Expect(k8sClientSet.CoreV1().Pods(namespace).Delete(pod.Name, &metav1.DeleteOptions{})).NotTo(HaveOccurred()) + }) + + Context("reconciling the IPPool", func() { + BeforeEach(func() { + var err error + reconcileLooper, err = reconciler.NewReconcileLooper(kubeConfigPath, context.TODO()) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should report the deleted IP reservation", func() { + expectedIPReservation := types.IPReservation{ + IP: net.ParseIP("10.10.10.1"), + PodRef: fmt.Sprintf("%s/%s", namespace, podName), + } + Expect(reconcileLooper.ReconcileIPPools()).To(ConsistOf(expectedIPReservation)) + }) + + It("the pool's orphaned IP should be deleted after the reconcile loop", func() { + _, err := reconcileLooper.ReconcileIPPools() + Expect(err).NotTo(HaveOccurred()) + var poolAfterCleanup v1alpha1.IPPool + poolKey := k8stypes.NamespacedName{Namespace: namespace, Name: pool.Name} + Expect(k8sClient.Get(context.Background(), poolKey, &poolAfterCleanup)).To(Succeed()) + Expect(poolAfterCleanup.Spec.Allocations).To(BeEmpty()) + }) + }) + }) + }) + }) + + Context("multiple pods", func() { + const ( + deadPodIndex = 0 + livePodIndex = 1 + numberOfPods = 2 + ) + + var pods []v1.Pod + + BeforeEach(func() { + for i := 0; i < numberOfPods; i++ { + pod := generatePod(namespace, fmt.Sprintf("pod%d", i+1), networkName) + if i == livePodIndex { + _, err := k8sClientSet.CoreV1().Pods(namespace).Create(pod) + Expect(err).NotTo(HaveOccurred()) + } + pods = append(pods, *pod) + } + }) + + AfterEach(func() { + Expect(k8sClientSet.CoreV1().Pods(namespace).Delete(pods[livePodIndex].Name, &metav1.DeleteOptions{})).NotTo(HaveOccurred()) + pods = nil + }) + + Context("each with IP from the same IPPool", func() { + const poolName = "pool1" + + var pool *v1alpha1.IPPool + + BeforeEach(func() { + var podNames []string + for _, pod := range pods { + podNames = append(podNames, pod.Name) + } + pool = generateIPPoolSpec(ipRange, namespace, poolName, podNames...) + Expect(k8sClient.Create(context.Background(), pool)).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + Expect(k8sClient.Delete(context.Background(), pool)).NotTo(HaveOccurred()) + }) + + Context("reconciling the IPPool", func() { + BeforeEach(func() { + var err error + reconcileLooper, err = reconciler.NewReconcileLooper(kubeConfigPath, context.TODO()) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should report the dead pod's IP address as deleted", func() { + expectedReservation := types.IPReservation{ + IP: net.ParseIP("10.10.10.1"), + PodRef: fmt.Sprintf("%s/%s", namespace, pods[deadPodIndex].Name), + } + deletedIPAddrs, err := reconcileLooper.ReconcileIPPools() + Expect(err).NotTo(HaveOccurred()) + Expect(deletedIPAddrs).To(ConsistOf(expectedReservation)) + }) + + It("the IPPool should have only the IP reservation of the live pod", func() { + deletedIPAddrs, err := reconcileLooper.ReconcileIPPools() + Expect(err).NotTo(HaveOccurred()) + Expect(deletedIPAddrs).NotTo(BeEmpty()) + + var poolAfterCleanup v1alpha1.IPPool + poolKey := k8stypes.NamespacedName{Namespace: namespace, Name: pool.Name} + Expect(k8sClient.Get(context.Background(), poolKey, &poolAfterCleanup)).To(Succeed()) + + remainingAllocation := map[string]v1alpha1.IPAllocation{ + "2": { + PodRef: fmt.Sprintf("%s/%s", namespace, pods[livePodIndex].Name), + }, + } + Expect(poolAfterCleanup.Spec.Allocations).To(Equal(remainingAllocation)) + }) + }) + }) + }) +}) + +func generateIPPoolSpec(ipRange string, namespace string, poolName string, podNames ...string) *v1alpha1.IPPool { + allocations := map[string]v1alpha1.IPAllocation{} + for i, podName := range podNames { + allocations[fmt.Sprintf("%d", i+1)] = v1alpha1.IPAllocation{ + PodRef: fmt.Sprintf("%s/%s", namespace, podName), + } + } + return &v1alpha1.IPPool{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: poolName}, + Spec: v1alpha1.IPPoolSpec{ + Range: ipRange, + Allocations: allocations, + }, + } +} + +func generatePod(namespace string, podName string, networks ...string) *v1.Pod { + networkAnnotations := map[string]string{"k8s.v1.cni.cncf.io/networks": strings.Join(networks, ",")} + return &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: namespace, + Annotations: networkAnnotations, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Name: podName, + Image: "alpine", + Command: []string{"/bin/bash", "-c", "sleep 2000000000000"}, + }, + }, + }, + } +} diff --git a/cmd/reconciler/suite_test.go b/cmd/reconciler/suite_test.go new file mode 100644 index 000000000..e1b8df3a1 --- /dev/null +++ b/cmd/reconciler/suite_test.go @@ -0,0 +1,114 @@ +package main + +import ( + "encoding/base64" + "fmt" + "io/ioutil" + "k8s.io/client-go/kubernetes" + "os" + "path/filepath" + "strings" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + whereaboutsv1alpha1 "github.com/dougbtv/whereabouts/pkg/api/v1alpha1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +var ( + cfg *rest.Config + k8sClient client.Client + k8sClientSet *kubernetes.Clientset + kubeConfigPath string + testEnv *envtest.Environment + tmpdir string +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Whereabouts IP reconciler Suite", + []Reporter{envtest.NewlineReporter{}}) +} + +var _ = BeforeSuite(func(done Done) { + logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("../../", "doc")}, + } + + var err error + cfg, err = testEnv.Start() + Expect(err).ToNot(HaveOccurred()) + Expect(cfg).ToNot(BeNil()) + + Expect(testEnv.ControlPlane.Etcd).ToNot(BeNil()) + etcdURL := testEnv.ControlPlane.Etcd.URL + Expect(etcdURL).ToNot(BeNil()) + + Expect(testEnv.ControlPlane.APIServer).ToNot(BeNil()) + apiURL := testEnv.ControlPlane.APIServer.URL.String() + Expect(apiURL).ToNot(BeNil()) + + var caContents string + for _, s := range strings.Split(string(cfg.TLSClientConfig.CAData), "\n") { + caContents += base64.StdEncoding.EncodeToString([]byte(s)) + } + + tmpdir, err = ioutil.TempDir("/tmp", "whereabouts") + Expect(err).ToNot(HaveOccurred()) + + kubeconfig := clientcmdapi.Config{ + Clusters: map[string]*clientcmdapi.Cluster{ + "local": {Server: apiURL, CertificateAuthorityData: []byte(caContents)}, + }, + AuthInfos: map[string]*clientcmdapi.AuthInfo{ + "whereabouts": { + Token: cfg.BearerToken, + }, + }, + Contexts: map[string]*clientcmdapi.Context{ + "whereabouts-context": { + Cluster: "local", + AuthInfo: "whereabouts", + Namespace: "default", + }, + }, + CurrentContext: "whereabouts-context", + } + kubeConfigPath = fmt.Sprintf("%s/whereabouts.kubeconfig", tmpdir) + err = clientcmd.WriteToFile(kubeconfig, kubeConfigPath) + Expect(err).ToNot(HaveOccurred()) + + err = whereaboutsv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).ToNot(HaveOccurred()) + Expect(k8sClient).ToNot(BeNil()) + + k8sClientSet, err = kubernetes.NewForConfig(cfg) + Expect(err).NotTo(HaveOccurred()) + + close(done) +}, 60) + +var _ = AfterSuite(func() { + defer os.RemoveAll(tmpdir) + + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).ToNot(HaveOccurred()) +}) diff --git a/go.mod b/go.mod index 62d1c9aaa..f273aece8 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/gobuffalo/envy v1.6.15 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.0.0 // indirect github.com/google/uuid v1.1.1 // indirect @@ -20,10 +21,12 @@ require ( github.com/imdario/mergo v0.3.8 github.com/jonboulle/clockwork v0.1.0 // indirect github.com/json-iterator/go v1.1.6 // indirect + github.com/markbates/inflect v1.0.4 // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/onsi/ginkgo v1.8.0 github.com/onsi/gomega v1.5.0 github.com/pkg/errors v0.8.1 + github.com/rogpeppe/go-internal v1.2.2 // indirect github.com/soheilhy/cmux v0.1.4 // indirect github.com/spf13/pflag v1.0.3 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect @@ -36,6 +39,7 @@ require ( k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible sigs.k8s.io/controller-runtime v0.2.2 + sigs.k8s.io/controller-tools v0.2.0 // indirect ) replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2 diff --git a/go.sum b/go.sum index 486ac9ca7..0ca37feae 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -42,6 +44,10 @@ github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.5 h1:xpKq9ap8MbYfhuPCF0dBH854Gp9CxZjr/IocxELFflo= +github.com/gobuffalo/flect v0.1.5/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -57,6 +63,7 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= @@ -77,7 +84,10 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -93,6 +103,11 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -123,16 +138,21 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7q github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= @@ -162,6 +182,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -171,6 +192,7 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= @@ -186,11 +208,14 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= @@ -199,13 +224,16 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190501045030-23463209683d/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= @@ -223,6 +251,7 @@ gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0= @@ -247,6 +276,7 @@ k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7 k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ= k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c h1:3KSCztE7gPitlZmWbNwue/2U0YruD65DqX3INopDAQM= @@ -255,6 +285,8 @@ k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 h1:VBM/0P5TWxwk+Nw6Z+lAw3DKgO76g k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= sigs.k8s.io/controller-runtime v0.2.2 h1:JT/vJJhUjjL9NZNwnm8AXmqCBUXSCFKmTaNjwDi28N0= sigs.k8s.io/controller-runtime v0.2.2/go.mod h1:9dyohw3ZtoXQuV1e766PHUn+cmrRCIcBh6XIMFNMZ+I= +sigs.k8s.io/controller-tools v0.2.0 h1:AmQ/0JKBJAjyAiPAkrAf9QW06jkx2lc5hpxMjamsFpw= +sigs.k8s.io/controller-tools v0.2.0/go.mod h1:8t/X+FVWvk6TaBcsa+UKUBbn7GMtvyBKX30SGl4em6Y= sigs.k8s.io/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkWhVrs= sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= From 94c6bd1f73b2da27a333f2749ecd602a8f904407 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Mon, 5 Jul 2021 11:50:37 +0200 Subject: [PATCH 08/16] ip-reconciler: fire up cronjob every 5 mins The service account for whereabouts requires the ability of listing the pods on all namespaces, so it can check which ones feature orphaned IPs. Signed-off-by: Miguel Duarte Barroso --- Dockerfile | 1 + doc/daemonset-install.yaml | 5 +++++ doc/ip-reconciler-job.yaml | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 doc/ip-reconciler-job.yaml diff --git a/Dockerfile b/Dockerfile index d993fe14f..45964032b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,5 +8,6 @@ RUN ./hack/build-go.sh FROM alpine:latest LABEL org.opencontainers.image.source https://github.com/k8snetworkplumbingwg/whereabouts COPY --from=0 /go/src/github.com/dougbtv/whereabouts/bin/whereabouts . +COPY --from=0 /go/src/github.com/dougbtv/whereabouts/bin/ip-reconciler . COPY script/install-cni.sh . CMD ["/install-cni.sh"] diff --git a/doc/daemonset-install.yaml b/doc/daemonset-install.yaml index b2de82a27..0562f5580 100644 --- a/doc/daemonset-install.yaml +++ b/doc/daemonset-install.yaml @@ -42,6 +42,11 @@ rules: - leases verbs: - '*' +- apiGroups: [""] + resources: + - pods + verbs: + - list --- apiVersion: apps/v1 kind: DaemonSet diff --git a/doc/ip-reconciler-job.yaml b/doc/ip-reconciler-job.yaml new file mode 100644 index 000000000..d23c3a1c5 --- /dev/null +++ b/doc/ip-reconciler-job.yaml @@ -0,0 +1,34 @@ +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: ip-reconciler + labels: + tier: node + app: whereabouts +spec: + schedule: "*/5 * * * *" + jobTemplate: + spec: + template: + spec: + containers: + - name: whereabouts + image: ghcr.io/k8snetworkplumbingwg/whereabouts:latest-amd64 + resources: + requests: + cpu: "100m" + memory: "50Mi" + limits: + cpu: "100m" + memory: "50Mi" + command: + - /ip-reconciler + - -kubeconfig=/host/etc/cni/net.d/whereabouts.d/whereabouts.kubeconfig + volumeMounts: + - name: cni-net-dir + mountPath: /host/etc/cni/net.d + volumes: + - name: cni-net-dir + hostPath: + path: /etc/cni/net.d + restartPolicy: OnFailure From 2f3f64e42e0426ed32ed83861914192b0fc75586 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Wed, 7 Jul 2021 18:28:59 +0200 Subject: [PATCH 09/16] ip-reconciler: add unit tests Signed-off-by: Miguel Duarte Barroso --- pkg/reconciler/iploop_test.go | 163 ++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 pkg/reconciler/iploop_test.go diff --git a/pkg/reconciler/iploop_test.go b/pkg/reconciler/iploop_test.go new file mode 100644 index 000000000..f0d7f9f14 --- /dev/null +++ b/pkg/reconciler/iploop_test.go @@ -0,0 +1,163 @@ +package reconciler + +import ( + "context" + "fmt" + "net" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + whereaboutsv1alpha1 "github.com/dougbtv/whereabouts/pkg/api/v1alpha1" + "github.com/dougbtv/whereabouts/pkg/types" +) + +func TestIPReconciler(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Reconcile IP address allocation in the system") +} + +// mock the pool +type dummyPool struct { + orphans []types.IPReservation + pool whereaboutsv1alpha1.IPPool +} + +func (dp dummyPool) Allocations() []types.IPReservation { + return dp.orphans +} + +func (dp dummyPool) Update(context.Context, []types.IPReservation) error { + return nil +} + +var _ = Describe("IPReconciler", func() { + var ipReconciler *ReconcileLooper + + newIPReconciler := func(orphanedIPs ...OrphanedIPReservations) *ReconcileLooper { + reconciler := &ReconcileLooper{ + cancelFunc: func() {}, + ctx: context.TODO(), + orphanedIPs: orphanedIPs, + } + + return reconciler + } + + When("there are no IP addresses to reconcile", func() { + BeforeEach(func() { + ipReconciler = newIPReconciler() + }) + + It("does not delete anything", func() { + reconciledIPs, err := ipReconciler.ReconcileIPPools() + Expect(err).NotTo(HaveOccurred()) + Expect(reconciledIPs).To(BeEmpty()) + }) + }) + + When("there are IP addresses to reconcile", func() { + const ( + firstIPInRange = "192.168.14.1" + ipCIDR = "192.168.14.0/24" + namespace = "default" + podName = "pod1" + ) + + BeforeEach(func() { + podRef := "default/pod1" + reservations := generateIPReservation(firstIPInRange, podRef) + + pool := generateIPPool(ipCIDR, podRef) + orphanedIPAddr := OrphanedIPReservations{ + Pool: dummyPool{orphans: reservations, pool: pool}, + Allocations: reservations, + } + + ipReconciler = newIPReconciler(orphanedIPAddr) + }) + + It("does delete the orphaned IP address", func() { + reconciledIPs, err := ipReconciler.ReconcileIPPools() + Expect(err).NotTo(HaveOccurred()) + + reapedIPAddr := types.IPReservation{IP: net.ParseIP(firstIPInRange), PodRef: generatePodRef(namespace, podName)} + Expect(reconciledIPs).To(ConsistOf(reapedIPAddr)) + }) + + Context("and they are actually multiple IPs", func() { + BeforeEach(func() { + podRef := "default/pod2" + reservations := generateIPReservation("192.168.14.2", podRef) + + pool := generateIPPool(ipCIDR, podRef, "default/pod2", "default/pod3") + orphanedIPAddr := OrphanedIPReservations{ + Pool: dummyPool{orphans: reservations, pool: pool}, + Allocations: reservations, + } + + ipReconciler = newIPReconciler(orphanedIPAddr) + }) + + It("does delete *only the orphaned* the IP address", func() { + reconciledIPs, err := ipReconciler.ReconcileIPPools() + Expect(err).NotTo(HaveOccurred()) + + reapedIPAddr := types.IPReservation{IP: net.ParseIP("192.168.14.2"), PodRef: generatePodRef(namespace, "pod2")} + Expect(reconciledIPs).To(ConsistOf(reapedIPAddr)) + }) + }) + + Context("but the IP reservation owner does not match", func() { + var reservationPodRef string + BeforeEach(func() { + reservationPodRef = "default/pod2" + podRef := "default/pod1" + reservations := generateIPReservation(firstIPInRange, podRef) + erroredReservations := generateIPReservation(firstIPInRange, reservationPodRef) + + pool := generateIPPool(ipCIDR, podRef) + orphanedIPAddr := OrphanedIPReservations{ + Pool: dummyPool{orphans: reservations, pool: pool}, + Allocations: erroredReservations, + } + + ipReconciler = newIPReconciler(orphanedIPAddr) + }) + + It("errors when attempting to clean up the IP address", func() { + reconciledIPs, err := ipReconciler.ReconcileIPPools() + Expect(err).To(MatchError(fmt.Sprintf("Did not find reserved IP for container %s", reservationPodRef))) + Expect(reconciledIPs).To(BeEmpty()) + }) + }) + }) +}) + +func generateIPPool(cidr string, podRefs ...string) whereaboutsv1alpha1.IPPool { + allocations := map[string]whereaboutsv1alpha1.IPAllocation{} + for i, podRef := range podRefs { + allocations[fmt.Sprintf("%d", i)] = whereaboutsv1alpha1.IPAllocation{PodRef: podRef} + } + + return whereaboutsv1alpha1.IPPool{ + Spec: whereaboutsv1alpha1.IPPoolSpec{ + Range: cidr, + Allocations: allocations, + }, + } +} + +func generateIPReservation(ip string, podRef string) []types.IPReservation { + return []types.IPReservation{ + { + IP: net.ParseIP(ip), + PodRef: podRef, + }, + } +} + +func generatePodRef(namespace, podName string) string { + return fmt.Sprintf("%s/%s", namespace, podName) +} \ No newline at end of file From 90c7617a1ca817b27f5f815de263edce94cfd713 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Fri, 16 Jul 2021 15:31:04 +0200 Subject: [PATCH 10/16] ip-reconciler: add plumbers network status entity Signed-off-by: Miguel Duarte Barroso --- go.mod | 7 +- go.sum | 91 +++++++- .../LICENSE | 201 ++++++++++++++++++ .../pkg/apis/k8s.cni.cncf.io/register.go | 5 + .../pkg/apis/k8s.cni.cncf.io/v1/doc.go | 5 + .../pkg/apis/k8s.cni.cncf.io/v1/register.go | 41 ++++ .../pkg/apis/k8s.cni.cncf.io/v1/types.go | 115 ++++++++++ .../v1/zz_generated.deepcopy.go | 101 +++++++++ vendor/github.com/onsi/ginkgo/CHANGELOG.md | 26 +++ .../github.com/onsi/ginkgo/config/config.go | 10 +- .../onsi/ginkgo/extensions/table/table.go | 98 +++++++++ .../ginkgo/extensions/table/table_entry.go | 81 +++++++ vendor/github.com/onsi/ginkgo/ginkgo_dsl.go | 4 +- .../internal/codelocation/code_location.go | 20 +- .../ginkgo/internal/leafnodes/benchmarker.go | 2 +- .../onsi/ginkgo/internal/remote/aggregator.go | 12 +- .../onsi/ginkgo/internal/remote/server.go | 2 +- .../onsi/ginkgo/internal/spec/spec.go | 10 +- .../onsi/ginkgo/internal/spec/specs.go | 4 +- .../ginkgo/internal/specrunner/spec_runner.go | 2 +- .../onsi/ginkgo/reporters/default_reporter.go | 3 + .../onsi/ginkgo/reporters/junit_reporter.go | 25 ++- .../ginkgo/reporters/teamcity_reporter.go | 9 +- vendor/github.com/onsi/ginkgo/types/types.go | 2 +- vendor/github.com/onsi/gomega/.travis.yml | 1 + vendor/github.com/onsi/gomega/CHANGELOG.md | 32 +++ .../github.com/onsi/gomega/format/format.go | 37 +++- .../onsi/gomega/gbytes/say_matcher.go | 4 +- vendor/github.com/onsi/gomega/gexec/build.go | 4 +- .../onsi/gomega/gexec/exit_matcher.go | 2 + .../onsi/gomega/gexec/prefixed_writer.go | 4 +- .../github.com/onsi/gomega/gexec/session.go | 3 + vendor/github.com/onsi/gomega/gomega_dsl.go | 10 +- .../asyncassertion/async_assertion.go | 2 + vendor/github.com/onsi/gomega/matchers.go | 16 ++ .../matchers/assignable_to_type_of_matcher.go | 2 + .../onsi/gomega/matchers/be_a_directory.go | 2 + .../onsi/gomega/matchers/be_a_regular_file.go | 2 + .../gomega/matchers/be_an_existing_file.go | 2 + .../onsi/gomega/matchers/be_closed_matcher.go | 2 + .../gomega/matchers/be_element_of_matcher.go | 57 +++++ .../onsi/gomega/matchers/be_empty_matcher.go | 2 + .../matchers/be_equivalent_to_matcher.go | 2 + .../onsi/gomega/matchers/be_false_matcher.go | 2 + .../onsi/gomega/matchers/be_identical_to.go | 2 + .../onsi/gomega/matchers/be_nil_matcher.go | 2 + .../gomega/matchers/be_numerically_matcher.go | 2 + .../onsi/gomega/matchers/be_sent_matcher.go | 2 + .../gomega/matchers/be_temporally_matcher.go | 2 + .../onsi/gomega/matchers/be_true_matcher.go | 2 + .../onsi/gomega/matchers/consist_of.go | 2 + .../matchers/contain_element_matcher.go | 22 +- .../matchers/contain_substring_matcher.go | 2 + .../onsi/gomega/matchers/have_cap_matcher.go | 2 + .../onsi/gomega/matchers/have_key_matcher.go | 2 + .../matchers/have_key_with_value_matcher.go | 2 + .../gomega/matchers/have_occurred_matcher.go | 2 + .../onsi/gomega/matchers/receive_matcher.go | 2 + .../matchers/semi_structured_data_support.go | 2 + .../goraph/bipartitegraph/bipartitegraph.go | 3 +- .../onsi/gomega/matchers/type_support.go | 3 + vendor/github.com/spf13/pflag/.travis.yml | 7 +- vendor/github.com/spf13/pflag/README.md | 4 +- vendor/github.com/spf13/pflag/bool_slice.go | 38 ++++ vendor/github.com/spf13/pflag/count.go | 4 +- .../github.com/spf13/pflag/duration_slice.go | 38 ++++ vendor/github.com/spf13/pflag/flag.go | 16 +- .../github.com/spf13/pflag/float32_slice.go | 174 +++++++++++++++ .../github.com/spf13/pflag/float64_slice.go | 166 +++++++++++++++ vendor/github.com/spf13/pflag/go.mod | 3 + vendor/github.com/spf13/pflag/go.sum | 0 vendor/github.com/spf13/pflag/int32_slice.go | 174 +++++++++++++++ vendor/github.com/spf13/pflag/int64_slice.go | 166 +++++++++++++++ vendor/github.com/spf13/pflag/int_slice.go | 30 +++ vendor/github.com/spf13/pflag/ip_slice.go | 40 +++- vendor/github.com/spf13/pflag/string_array.go | 26 +++ vendor/github.com/spf13/pflag/string_slice.go | 22 +- .../github.com/spf13/pflag/string_to_int64.go | 149 +++++++++++++ vendor/github.com/spf13/pflag/uint_slice.go | 42 ++++ vendor/golang.org/x/oauth2/README.md | 13 +- vendor/golang.org/x/oauth2/internal/oauth2.go | 2 +- vendor/golang.org/x/oauth2/internal/token.go | 4 +- .../golang.org/x/oauth2/internal/transport.go | 3 +- vendor/golang.org/x/oauth2/oauth2.go | 8 +- vendor/golang.org/x/oauth2/token.go | 2 +- vendor/golang.org/x/time/rate/rate.go | 16 +- vendor/golang.org/x/time/rate/rate_go16.go | 21 -- vendor/golang.org/x/time/rate/rate_go17.go | 21 -- vendor/gopkg.in/yaml.v2/decode.go | 13 ++ vendor/gopkg.in/yaml.v2/resolve.go | 2 +- vendor/k8s.io/klog/.travis.yml | 3 +- vendor/k8s.io/klog/README.md | 2 +- vendor/k8s.io/klog/go.mod | 5 + vendor/k8s.io/klog/go.sum | 2 + vendor/k8s.io/klog/klog.go | 131 +++++++----- vendor/modules.txt | 18 +- 96 files changed, 2272 insertions(+), 213 deletions(-) create mode 100644 vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/LICENSE create mode 100644 vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/register.go create mode 100644 vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/doc.go create mode 100644 vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/register.go create mode 100644 vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/types.go create mode 100644 vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/zz_generated.deepcopy.go create mode 100644 vendor/github.com/onsi/ginkgo/extensions/table/table.go create mode 100644 vendor/github.com/onsi/ginkgo/extensions/table/table_entry.go create mode 100644 vendor/github.com/onsi/gomega/matchers/be_element_of_matcher.go create mode 100644 vendor/github.com/spf13/pflag/float32_slice.go create mode 100644 vendor/github.com/spf13/pflag/float64_slice.go create mode 100644 vendor/github.com/spf13/pflag/go.mod create mode 100644 vendor/github.com/spf13/pflag/go.sum create mode 100644 vendor/github.com/spf13/pflag/int32_slice.go create mode 100644 vendor/github.com/spf13/pflag/int64_slice.go create mode 100644 vendor/github.com/spf13/pflag/string_to_int64.go delete mode 100644 vendor/golang.org/x/time/rate/rate_go16.go delete mode 100644 vendor/golang.org/x/time/rate/rate_go17.go create mode 100644 vendor/k8s.io/klog/go.mod create mode 100644 vendor/k8s.io/klog/go.sum diff --git a/go.mod b/go.mod index f273aece8..92f5f2fc3 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/gobuffalo/envy v1.6.15 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/btree v1.0.0 // indirect github.com/google/uuid v1.1.1 // indirect github.com/gorilla/websocket v1.4.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect @@ -21,14 +20,14 @@ require ( github.com/imdario/mergo v0.3.8 github.com/jonboulle/clockwork v0.1.0 // indirect github.com/json-iterator/go v1.1.6 // indirect + github.com/k8snetworkplumbingwg/network-attachment-definition-client v0.0.0-20191119172530-79f836b90111 github.com/markbates/inflect v1.0.4 // indirect github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/onsi/ginkgo v1.8.0 - github.com/onsi/gomega v1.5.0 + github.com/onsi/ginkgo v1.10.1 + github.com/onsi/gomega v1.7.0 github.com/pkg/errors v0.8.1 github.com/rogpeppe/go-internal v1.2.2 // indirect github.com/soheilhy/cmux v0.1.4 // indirect - github.com/spf13/pflag v1.0.3 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.3.3 // indirect diff --git a/go.sum b/go.sum index 0ca37feae..ff325bc74 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -33,6 +36,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/emicklei/go-restful v2.10.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= @@ -44,6 +49,12 @@ github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/flect v0.1.5 h1:xpKq9ap8MbYfhuPCF0dBH854Gp9CxZjr/IocxELFflo= @@ -53,8 +64,9 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE= github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -68,10 +80,12 @@ github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeq github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= @@ -79,8 +93,10 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.11.1 h1:/dBYI+n4xIL+Y9SKXQrjlKTmJJDwCSlNLRwZ5nBhIek= github.com/grpc-ecosystem/grpc-gateway v1.11.1/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.0.0-20171009183408-7fe0c75c13ab/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -90,19 +106,25 @@ github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6t github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/testing v0.0.0-20190613124551-e81189438503/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v0.0.0-20191119172530-79f836b90111 h1:Lq6HJa0JqSg5ko/mkizFWlpIrY7845g9Dzz9qeD5aXI= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v0.0.0-20191119172530-79f836b90111/go.mod h1:MP2HbArq3QT+oVp8pmtHNZnSnkhdkHtDnc7h6nJXmBU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -118,13 +140,16 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -137,6 +162,7 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrO github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -148,13 +174,17 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= @@ -174,13 +204,19 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -193,12 +229,16 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 h1:JIqe8uIcRBHXDQVvZtHwp80ai3Lw3IJAeJEs55Dc1W0= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -209,23 +249,31 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190501045030-23463209683d/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190930201159-7c411dea38b0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a h1:CB3a9Nez8M13wwlr/E2YtwoU+qYHKfC+JrDa45RXXoQ= @@ -237,7 +285,11 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -249,13 +301,15 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= @@ -264,25 +318,40 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.0.0-20181115043458-b799cb063522/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbuQba2w+SXqezo= k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8 h1:q1Qvjzs/iEdXF6A1a8H3AKVFDzJNcJn3nXMs6R6qFtA= k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= +k8s.io/apimachinery v0.0.0-20181110190943-2a7c93004028/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA= k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/client-go v0.0.0-20181115111358-9bea17718df8/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ= k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/code-generator v0.0.0-20181114232248-ae218e241252/go.mod h1:IPqxl/YHk05nodzupwjke6ctMjyNRdV2zZ5/j3/F204= +k8s.io/gengo v0.0.0-20181106084056-51747d6e00da/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190907103519-ebc107f98eab/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20190306015804-8e90cee79f82/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c h1:3KSCztE7gPitlZmWbNwue/2U0YruD65DqX3INopDAQM= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be h1:aWEq4nbj7HRJ0mtKYjNSk/7X28Tl6TI6FeG8gKF+r7Q= +k8s.io/kube-openapi v0.0.0-20181114233023-0317810137be/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 h1:VBM/0P5TWxwk+Nw6Z+lAw3DKgO76g90ETOiA6rfLV1Y= k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= sigs.k8s.io/controller-runtime v0.2.2 h1:JT/vJJhUjjL9NZNwnm8AXmqCBUXSCFKmTaNjwDi28N0= sigs.k8s.io/controller-runtime v0.2.2/go.mod h1:9dyohw3ZtoXQuV1e766PHUn+cmrRCIcBh6XIMFNMZ+I= sigs.k8s.io/controller-tools v0.2.0 h1:AmQ/0JKBJAjyAiPAkrAf9QW06jkx2lc5hpxMjamsFpw= diff --git a/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/LICENSE b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/register.go b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/register.go new file mode 100644 index 000000000..8ea2a3028 --- /dev/null +++ b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/register.go @@ -0,0 +1,5 @@ +package k8scnicncfio + +const ( + GroupName = "k8s.cni.cncf.io" +) diff --git a/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/doc.go b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/doc.go new file mode 100644 index 000000000..2882952a0 --- /dev/null +++ b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/doc.go @@ -0,0 +1,5 @@ +// +k8s:deepcopy-gen=package,register +// +groupName=k8s.cni.cncf.io +// +groupGoName=K8sCniCncfIo + +package v1 diff --git a/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/register.go b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/register.go new file mode 100644 index 000000000..e40da2572 --- /dev/null +++ b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/register.go @@ -0,0 +1,41 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + k8scnicncfio "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: k8scnicncfio.GroupName, Version: "v1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes) +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &NetworkAttachmentDefinition{}, + &NetworkAttachmentDefinitionList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/types.go b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/types.go new file mode 100644 index 000000000..77e08f8b0 --- /dev/null +++ b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/types.go @@ -0,0 +1,115 @@ +package v1 + +import ( + "net" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +genclient:noStatus +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +resourceName=network-attachment-definitions + +type NetworkAttachmentDefinition struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NetworkAttachmentDefinitionSpec `json:"spec"` +} + +type NetworkAttachmentDefinitionSpec struct { + Config string `json:"config"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type NetworkAttachmentDefinitionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []NetworkAttachmentDefinition `json:"items"` +} + +// DNS contains values interesting for DNS resolvers +// +k8s:deepcopy-gen=false +type DNS struct { + Nameservers []string `json:"nameservers,omitempty"` + Domain string `json:"domain,omitempty"` + Search []string `json:"search,omitempty"` + Options []string `json:"options,omitempty"` +} + +// NetworkStatus is for network status annotation for pod +// +k8s:deepcopy-gen=false +type NetworkStatus struct { + Name string `json:"name"` + Interface string `json:"interface,omitempty"` + IPs []string `json:"ips,omitempty"` + Mac string `json:"mac,omitempty"` + Default bool `json:"default,omitempty"` + DNS DNS `json:"dns,omitempty"` +} + +// PortMapEntry for CNI PortMapEntry +// +k8s:deepcopy-gen=false +type PortMapEntry struct { + HostPort int `json:"hostPort"` + ContainerPort int `json:"containerPort"` + Protocol string `json:"protocol,omitempty"` + HostIP string `json:"hostIP,omitempty"` +} + +// BandwidthEntry for CNI BandwidthEntry +// +k8s:deepcopy-gen=false +type BandwidthEntry struct { + IngressRate int `json:"ingressRate"` + IngressBurst int `json:"ingressBurst"` + + EgressRate int `json:"egressRate"` + EgressBurst int `json:"egressBurst"` +} + +// NetworkSelectionElement represents one element of the JSON format +// Network Attachment Selection Annotation as described in section 4.1.2 +// of the CRD specification. +// +k8s:deepcopy-gen=false +type NetworkSelectionElement struct { + // Name contains the name of the Network object this element selects + Name string `json:"name"` + // Namespace contains the optional namespace that the network referenced + // by Name exists in + Namespace string `json:"namespace,omitempty"` + // IPRequest contains an optional requested IP address for this network + // attachment + IPRequest string `json:"ips,omitempty"` + // MacRequest contains an optional requested MAC address for this + // network attachment + MacRequest string `json:"mac,omitempty"` + // InterfaceRequest contains an optional requested name for the + // network interface this attachment will create in the container + InterfaceRequest string `json:"interface,omitempty"` + // PortMappingsRequest contains an optional requested port mapping + // for the network + PortMappingsRequest []*PortMapEntry `json:"portMappings,omitempty"` + // BandwidthRequest contains an optional requested bandwidth for + // the network + BandwidthRequest *BandwidthEntry `json:"bandwidth,omitempty"` + // CNIArgs contains additional CNI arguments for the network interface + CNIArgs *map[string]interface{} `json:"cni-args"` + // GatewayRequest contains default route IP address for the pod + GatewayRequest []net.IP `json:"default-route,omitempty"` +} + +const ( + // Pod annotation for network-attachment-definition + NetworkAttachmentAnnot = "k8s.v1.cni.cncf.io/networks" + // Pod annotation for network status + NetworkStatusAnnot = "k8s.v1.cni.cncf.io/networks-status" +) + +// NoK8sNetworkError indicates error, no network in kubernetes +// +k8s:deepcopy-gen=false +type NoK8sNetworkError struct { + Message string +} + +func (e *NoK8sNetworkError) Error() string { return string(e.Message) } diff --git a/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/zz_generated.deepcopy.go b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/zz_generated.deepcopy.go new file mode 100644 index 000000000..16919978a --- /dev/null +++ b/vendor/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1/zz_generated.deepcopy.go @@ -0,0 +1,101 @@ +// +build !ignore_autogenerated + +/* +Copyright 2019 The Kubernetes Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkAttachmentDefinition) DeepCopyInto(out *NetworkAttachmentDefinition) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkAttachmentDefinition. +func (in *NetworkAttachmentDefinition) DeepCopy() *NetworkAttachmentDefinition { + if in == nil { + return nil + } + out := new(NetworkAttachmentDefinition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NetworkAttachmentDefinition) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkAttachmentDefinitionList) DeepCopyInto(out *NetworkAttachmentDefinitionList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NetworkAttachmentDefinition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkAttachmentDefinitionList. +func (in *NetworkAttachmentDefinitionList) DeepCopy() *NetworkAttachmentDefinitionList { + if in == nil { + return nil + } + out := new(NetworkAttachmentDefinitionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NetworkAttachmentDefinitionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkAttachmentDefinitionSpec) DeepCopyInto(out *NetworkAttachmentDefinitionSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkAttachmentDefinitionSpec. +func (in *NetworkAttachmentDefinitionSpec) DeepCopy() *NetworkAttachmentDefinitionSpec { + if in == nil { + return nil + } + out := new(NetworkAttachmentDefinitionSpec) + in.DeepCopyInto(out) + return out +} diff --git a/vendor/github.com/onsi/ginkgo/CHANGELOG.md b/vendor/github.com/onsi/ginkgo/CHANGELOG.md index 4920406ae..aeadb66e0 100644 --- a/vendor/github.com/onsi/ginkgo/CHANGELOG.md +++ b/vendor/github.com/onsi/ginkgo/CHANGELOG.md @@ -1,3 +1,29 @@ +## 1.10.1 + +## Fixes +- stack backtrace: fix skipping (#600) [2a4c0bd] + +## 1.10.0 + +## Fixes +- stack backtrace: fix alignment and skipping [66915d6] +- fix typo in documentation [8f97b93] + +## 1.9.0 + +## Features +- Option to print output into report, when tests have passed [0545415] + +## Fixes +- Fixed typos in comments [0ecbc58] +- gofmt code [a7f8bfb] +- Simplify code [7454d00] +- Simplify concatenation, incrementation and function assignment [4825557] +- Avoid unnecessary conversions [9d9403c] +- JUnit: include more detailed information about panic [19cca4b] +- Print help to stdout when the user asks for help [4cb7441] + + ## 1.8.0 ### New Features diff --git a/vendor/github.com/onsi/ginkgo/config/config.go b/vendor/github.com/onsi/ginkgo/config/config.go index dab2a2470..ac55a5ad2 100644 --- a/vendor/github.com/onsi/ginkgo/config/config.go +++ b/vendor/github.com/onsi/ginkgo/config/config.go @@ -20,7 +20,7 @@ import ( "fmt" ) -const VERSION = "1.8.0" +const VERSION = "1.10.1" type GinkgoConfigType struct { RandomSeed int64 @@ -52,13 +52,14 @@ type DefaultReporterConfigType struct { Succinct bool Verbose bool FullTrace bool + ReportPassed bool } var DefaultReporterConfig = DefaultReporterConfigType{} func processPrefix(prefix string) string { if prefix != "" { - prefix = prefix + "." + prefix += "." } return prefix } @@ -98,6 +99,7 @@ func Flags(flagSet *flag.FlagSet, prefix string, includeParallelFlags bool) { flagSet.BoolVar(&(DefaultReporterConfig.Verbose), prefix+"v", false, "If set, default reporter print out all specs as they begin.") flagSet.BoolVar(&(DefaultReporterConfig.Succinct), prefix+"succinct", false, "If set, default reporter prints out a very succinct report") flagSet.BoolVar(&(DefaultReporterConfig.FullTrace), prefix+"trace", false, "If set, default reporter prints out the full stack trace when a failure occurs") + flagSet.BoolVar(&(DefaultReporterConfig.ReportPassed), prefix+"reportPassed", false, "If set, default reporter prints out captured output of passed tests.") } func BuildFlagArgs(prefix string, ginkgo GinkgoConfigType, reporter DefaultReporterConfigType) []string { @@ -196,5 +198,9 @@ func BuildFlagArgs(prefix string, ginkgo GinkgoConfigType, reporter DefaultRepor result = append(result, fmt.Sprintf("--%strace", prefix)) } + if reporter.ReportPassed { + result = append(result, fmt.Sprintf("--%sreportPassed", prefix)) + } + return result } diff --git a/vendor/github.com/onsi/ginkgo/extensions/table/table.go b/vendor/github.com/onsi/ginkgo/extensions/table/table.go new file mode 100644 index 000000000..ae8ab7d24 --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/extensions/table/table.go @@ -0,0 +1,98 @@ +/* + +Table provides a simple DSL for Ginkgo-native Table-Driven Tests + +The godoc documentation describes Table's API. More comprehensive documentation (with examples!) is available at http://onsi.github.io/ginkgo#table-driven-tests + +*/ + +package table + +import ( + "fmt" + "reflect" + + "github.com/onsi/ginkgo" +) + +/* +DescribeTable describes a table-driven test. + +For example: + + DescribeTable("a simple table", + func(x int, y int, expected bool) { + Ω(x > y).Should(Equal(expected)) + }, + Entry("x > y", 1, 0, true), + Entry("x == y", 0, 0, false), + Entry("x < y", 0, 1, false), + ) + +The first argument to `DescribeTable` is a string description. +The second argument is a function that will be run for each table entry. Your assertions go here - the function is equivalent to a Ginkgo It. +The subsequent arguments must be of type `TableEntry`. We recommend using the `Entry` convenience constructors. + +The `Entry` constructor takes a string description followed by an arbitrary set of parameters. These parameters are passed into your function. + +Under the hood, `DescribeTable` simply generates a new Ginkgo `Describe`. Each `Entry` is turned into an `It` within the `Describe`. + +It's important to understand that the `Describe`s and `It`s are generated at evaluation time (i.e. when Ginkgo constructs the tree of tests and before the tests run). + +Individual Entries can be focused (with FEntry) or marked pending (with PEntry or XEntry). In addition, the entire table can be focused or marked pending with FDescribeTable and PDescribeTable/XDescribeTable. +*/ +func DescribeTable(description string, itBody interface{}, entries ...TableEntry) bool { + describeTable(description, itBody, entries, false, false) + return true +} + +/* +You can focus a table with `FDescribeTable`. This is equivalent to `FDescribe`. +*/ +func FDescribeTable(description string, itBody interface{}, entries ...TableEntry) bool { + describeTable(description, itBody, entries, false, true) + return true +} + +/* +You can mark a table as pending with `PDescribeTable`. This is equivalent to `PDescribe`. +*/ +func PDescribeTable(description string, itBody interface{}, entries ...TableEntry) bool { + describeTable(description, itBody, entries, true, false) + return true +} + +/* +You can mark a table as pending with `XDescribeTable`. This is equivalent to `XDescribe`. +*/ +func XDescribeTable(description string, itBody interface{}, entries ...TableEntry) bool { + describeTable(description, itBody, entries, true, false) + return true +} + +func describeTable(description string, itBody interface{}, entries []TableEntry, pending bool, focused bool) { + itBodyValue := reflect.ValueOf(itBody) + if itBodyValue.Kind() != reflect.Func { + panic(fmt.Sprintf("DescribeTable expects a function, got %#v", itBody)) + } + + if pending { + ginkgo.PDescribe(description, func() { + for _, entry := range entries { + entry.generateIt(itBodyValue) + } + }) + } else if focused { + ginkgo.FDescribe(description, func() { + for _, entry := range entries { + entry.generateIt(itBodyValue) + } + }) + } else { + ginkgo.Describe(description, func() { + for _, entry := range entries { + entry.generateIt(itBodyValue) + } + }) + } +} diff --git a/vendor/github.com/onsi/ginkgo/extensions/table/table_entry.go b/vendor/github.com/onsi/ginkgo/extensions/table/table_entry.go new file mode 100644 index 000000000..5fa645bce --- /dev/null +++ b/vendor/github.com/onsi/ginkgo/extensions/table/table_entry.go @@ -0,0 +1,81 @@ +package table + +import ( + "reflect" + + "github.com/onsi/ginkgo" +) + +/* +TableEntry represents an entry in a table test. You generally use the `Entry` constructor. +*/ +type TableEntry struct { + Description string + Parameters []interface{} + Pending bool + Focused bool +} + +func (t TableEntry) generateIt(itBody reflect.Value) { + if t.Pending { + ginkgo.PIt(t.Description) + return + } + + values := []reflect.Value{} + for i, param := range t.Parameters { + var value reflect.Value + + if param == nil { + inType := itBody.Type().In(i) + value = reflect.Zero(inType) + } else { + value = reflect.ValueOf(param) + } + + values = append(values, value) + } + + body := func() { + itBody.Call(values) + } + + if t.Focused { + ginkgo.FIt(t.Description, body) + } else { + ginkgo.It(t.Description, body) + } +} + +/* +Entry constructs a TableEntry. + +The first argument is a required description (this becomes the content of the generated Ginkgo `It`). +Subsequent parameters are saved off and sent to the callback passed in to `DescribeTable`. + +Each Entry ends up generating an individual Ginkgo It. +*/ +func Entry(description string, parameters ...interface{}) TableEntry { + return TableEntry{description, parameters, false, false} +} + +/* +You can focus a particular entry with FEntry. This is equivalent to FIt. +*/ +func FEntry(description string, parameters ...interface{}) TableEntry { + return TableEntry{description, parameters, false, true} +} + +/* +You can mark a particular entry as pending with PEntry. This is equivalent to PIt. +*/ +func PEntry(description string, parameters ...interface{}) TableEntry { + return TableEntry{description, parameters, true, false} +} + +/* +You can mark a particular entry as pending with XEntry. This is equivalent to XIt. +*/ +func XEntry(description string, parameters ...interface{}) TableEntry { + return TableEntry{description, parameters, true, false} +} diff --git a/vendor/github.com/onsi/ginkgo/ginkgo_dsl.go b/vendor/github.com/onsi/ginkgo/ginkgo_dsl.go index a6b96d88f..8734c061d 100644 --- a/vendor/github.com/onsi/ginkgo/ginkgo_dsl.go +++ b/vendor/github.com/onsi/ginkgo/ginkgo_dsl.go @@ -283,7 +283,7 @@ func GinkgoRecover() { //BeforeEach, AfterEach, JustBeforeEach, It, and Measurement blocks. // //In addition you can nest Describe, Context and When blocks. Describe, Context and When blocks are functionally -//equivalent. The difference is purely semantic -- you typical Describe the behavior of an object +//equivalent. The difference is purely semantic -- you typically Describe the behavior of an object //or method and, within that Describe, outline a number of Contexts and Whens. func Describe(text string, body func()) bool { globalSuite.PushContainerNode(text, body, types.FlagTypeNone, codelocation.New(1)) @@ -499,7 +499,7 @@ func AfterSuite(body interface{}, timeout ...float64) bool { //until that node is done before running. // //SynchronizedBeforeSuite accomplishes this by taking *two* function arguments. The first is only run on parallel node #1. The second is -//run on all nodes, but *only* after the first function completes succesfully. Ginkgo also makes it possible to send data from the first function (on Node 1) +//run on all nodes, but *only* after the first function completes successfully. Ginkgo also makes it possible to send data from the first function (on Node 1) //to the second function (on all the other nodes). // //The functions have the following signatures. The first function (which only runs on node 1) has the signature: diff --git a/vendor/github.com/onsi/ginkgo/internal/codelocation/code_location.go b/vendor/github.com/onsi/ginkgo/internal/codelocation/code_location.go index fa2f0bf73..aa89d6cba 100644 --- a/vendor/github.com/onsi/ginkgo/internal/codelocation/code_location.go +++ b/vendor/github.com/onsi/ginkgo/internal/codelocation/code_location.go @@ -11,19 +11,35 @@ import ( func New(skip int) types.CodeLocation { _, file, line, _ := runtime.Caller(skip + 1) - stackTrace := PruneStack(string(debug.Stack()), skip) + stackTrace := PruneStack(string(debug.Stack()), skip+1) return types.CodeLocation{FileName: file, LineNumber: line, FullStackTrace: stackTrace} } +// PruneStack removes references to functions that are internal to Ginkgo +// and the Go runtime from a stack string and a certain number of stack entries +// at the beginning of the stack. The stack string has the format +// as returned by runtime/debug.Stack. The leading goroutine information is +// optional and always removed if present. Beware that runtime/debug.Stack +// adds itself as first entry, so typically skip must be >= 1 to remove that +// entry. func PruneStack(fullStackTrace string, skip int) string { stack := strings.Split(fullStackTrace, "\n") + // Ensure that the even entries are the method names and the + // the odd entries the source code information. + if len(stack) > 0 && strings.HasPrefix(stack[0], "goroutine ") { + // Ignore "goroutine 29 [running]:" line. + stack = stack[1:] + } + // The "+1" is for skipping over the initial entry, which is + // runtime/debug.Stack() itself. if len(stack) > 2*(skip+1) { stack = stack[2*(skip+1):] } prunedStack := []string{} re := regexp.MustCompile(`\/ginkgo\/|\/pkg\/testing\/|\/pkg\/runtime\/`) for i := 0; i < len(stack)/2; i++ { - if !re.Match([]byte(stack[i*2])) { + // We filter out based on the source code file name. + if !re.Match([]byte(stack[i*2+1])) { prunedStack = append(prunedStack, stack[i*2]) prunedStack = append(prunedStack, stack[i*2+1]) } diff --git a/vendor/github.com/onsi/ginkgo/internal/leafnodes/benchmarker.go b/vendor/github.com/onsi/ginkgo/internal/leafnodes/benchmarker.go index d6d54234c..393901e11 100644 --- a/vendor/github.com/onsi/ginkgo/internal/leafnodes/benchmarker.go +++ b/vendor/github.com/onsi/ginkgo/internal/leafnodes/benchmarker.go @@ -17,7 +17,7 @@ type benchmarker struct { func newBenchmarker() *benchmarker { return &benchmarker{ - measurements: make(map[string]*types.SpecMeasurement, 0), + measurements: make(map[string]*types.SpecMeasurement), } } diff --git a/vendor/github.com/onsi/ginkgo/internal/remote/aggregator.go b/vendor/github.com/onsi/ginkgo/internal/remote/aggregator.go index 6b54afe01..f9ab30067 100644 --- a/vendor/github.com/onsi/ginkgo/internal/remote/aggregator.go +++ b/vendor/github.com/onsi/ginkgo/internal/remote/aggregator.go @@ -54,11 +54,11 @@ func NewAggregator(nodeCount int, result chan bool, config config.DefaultReporte config: config, stenographer: stenographer, - suiteBeginnings: make(chan configAndSuite, 0), - beforeSuites: make(chan *types.SetupSummary, 0), - afterSuites: make(chan *types.SetupSummary, 0), - specCompletions: make(chan *types.SpecSummary, 0), - suiteEndings: make(chan *types.SuiteSummary, 0), + suiteBeginnings: make(chan configAndSuite), + beforeSuites: make(chan *types.SetupSummary), + afterSuites: make(chan *types.SetupSummary), + specCompletions: make(chan *types.SpecSummary), + suiteEndings: make(chan *types.SuiteSummary), } go aggregator.mux() @@ -227,7 +227,7 @@ func (aggregator *Aggregator) registerSuiteEnding(suite *types.SuiteSummary) (fi aggregatedSuiteSummary.SuiteSucceeded = true for _, suiteSummary := range aggregator.aggregatedSuiteEndings { - if suiteSummary.SuiteSucceeded == false { + if !suiteSummary.SuiteSucceeded { aggregatedSuiteSummary.SuiteSucceeded = false } diff --git a/vendor/github.com/onsi/ginkgo/internal/remote/server.go b/vendor/github.com/onsi/ginkgo/internal/remote/server.go index 367c54daf..93e9dac05 100644 --- a/vendor/github.com/onsi/ginkgo/internal/remote/server.go +++ b/vendor/github.com/onsi/ginkgo/internal/remote/server.go @@ -213,7 +213,7 @@ func (server *Server) handleCounter(writer http.ResponseWriter, request *http.Re c := spec_iterator.Counter{} server.lock.Lock() c.Index = server.counter - server.counter = server.counter + 1 + server.counter++ server.lock.Unlock() json.NewEncoder(writer).Encode(c) diff --git a/vendor/github.com/onsi/ginkgo/internal/spec/spec.go b/vendor/github.com/onsi/ginkgo/internal/spec/spec.go index 7fd68ee8e..6eef40a0e 100644 --- a/vendor/github.com/onsi/ginkgo/internal/spec/spec.go +++ b/vendor/github.com/onsi/ginkgo/internal/spec/spec.go @@ -107,11 +107,11 @@ func (spec *Spec) Summary(suiteID string) *types.SpecSummary { NumberOfSamples: spec.subject.Samples(), ComponentTexts: componentTexts, ComponentCodeLocations: componentCodeLocations, - State: spec.getState(), - RunTime: runTime, - Failure: spec.failure, - Measurements: spec.measurementsReport(), - SuiteID: suiteID, + State: spec.getState(), + RunTime: runTime, + Failure: spec.failure, + Measurements: spec.measurementsReport(), + SuiteID: suiteID, } } diff --git a/vendor/github.com/onsi/ginkgo/internal/spec/specs.go b/vendor/github.com/onsi/ginkgo/internal/spec/specs.go index 27c0d1d6c..8a2007137 100644 --- a/vendor/github.com/onsi/ginkgo/internal/spec/specs.go +++ b/vendor/github.com/onsi/ginkgo/internal/spec/specs.go @@ -107,11 +107,11 @@ func (e *Specs) applyRegExpFocusAndSkip(description string, focusString string, toMatch := e.toMatch(description, i) if focusFilter != nil { - matchesFocus = focusFilter.Match([]byte(toMatch)) + matchesFocus = focusFilter.Match(toMatch) } if skipFilter != nil { - matchesSkip = skipFilter.Match([]byte(toMatch)) + matchesSkip = skipFilter.Match(toMatch) } if !matchesFocus || matchesSkip { diff --git a/vendor/github.com/onsi/ginkgo/internal/specrunner/spec_runner.go b/vendor/github.com/onsi/ginkgo/internal/specrunner/spec_runner.go index 2c683cb8b..c9a0a60d8 100644 --- a/vendor/github.com/onsi/ginkgo/internal/specrunner/spec_runner.go +++ b/vendor/github.com/onsi/ginkgo/internal/specrunner/spec_runner.go @@ -300,7 +300,7 @@ func (runner *SpecRunner) reportSpecWillRun(summary *types.SpecSummary) { } func (runner *SpecRunner) reportSpecDidComplete(summary *types.SpecSummary, failed bool) { - if failed && len(summary.CapturedOutput) == 0 { + if len(summary.CapturedOutput) == 0 { summary.CapturedOutput = string(runner.writer.Bytes()) } for i := len(runner.reporters) - 1; i >= 1; i-- { diff --git a/vendor/github.com/onsi/ginkgo/reporters/default_reporter.go b/vendor/github.com/onsi/ginkgo/reporters/default_reporter.go index ac58dd5f7..c76283b46 100644 --- a/vendor/github.com/onsi/ginkgo/reporters/default_reporter.go +++ b/vendor/github.com/onsi/ginkgo/reporters/default_reporter.go @@ -62,6 +62,9 @@ func (reporter *DefaultReporter) SpecDidComplete(specSummary *types.SpecSummary) reporter.stenographer.AnnounceSuccesfulSlowSpec(specSummary, reporter.config.Succinct) } else { reporter.stenographer.AnnounceSuccesfulSpec(specSummary) + if reporter.config.ReportPassed { + reporter.stenographer.AnnounceCapturedOutput(specSummary.CapturedOutput) + } } case types.SpecStatePending: reporter.stenographer.AnnouncePendingSpec(specSummary, reporter.config.NoisyPendings && !reporter.config.Succinct) diff --git a/vendor/github.com/onsi/ginkgo/reporters/junit_reporter.go b/vendor/github.com/onsi/ginkgo/reporters/junit_reporter.go index 2c9f3c792..89a7c8465 100644 --- a/vendor/github.com/onsi/ginkgo/reporters/junit_reporter.go +++ b/vendor/github.com/onsi/ginkgo/reporters/junit_reporter.go @@ -32,12 +32,17 @@ type JUnitTestSuite struct { type JUnitTestCase struct { Name string `xml:"name,attr"` ClassName string `xml:"classname,attr"` + PassedMessage *JUnitPassedMessage `xml:"passed,omitempty"` FailureMessage *JUnitFailureMessage `xml:"failure,omitempty"` Skipped *JUnitSkipped `xml:"skipped,omitempty"` Time float64 `xml:"time,attr"` SystemOut string `xml:"system-out,omitempty"` } +type JUnitPassedMessage struct { + Message string `xml:",chardata"` +} + type JUnitFailureMessage struct { Type string `xml:"type,attr"` Message string `xml:",chardata"` @@ -48,9 +53,10 @@ type JUnitSkipped struct { } type JUnitReporter struct { - suite JUnitTestSuite - filename string - testSuiteName string + suite JUnitTestSuite + filename string + testSuiteName string + ReporterConfig config.DefaultReporterConfigType } //NewJUnitReporter creates a new JUnit XML reporter. The XML will be stored in the passed in filename. @@ -60,12 +66,13 @@ func NewJUnitReporter(filename string) *JUnitReporter { } } -func (reporter *JUnitReporter) SpecSuiteWillBegin(config config.GinkgoConfigType, summary *types.SuiteSummary) { +func (reporter *JUnitReporter) SpecSuiteWillBegin(ginkgoConfig config.GinkgoConfigType, summary *types.SuiteSummary) { reporter.suite = JUnitTestSuite{ Name: summary.SuiteDescription, TestCases: []JUnitTestCase{}, } reporter.testSuiteName = summary.SuiteDescription + reporter.ReporterConfig = config.DefaultReporterConfig } func (reporter *JUnitReporter) SpecWillRun(specSummary *types.SpecSummary) { @@ -105,11 +112,21 @@ func (reporter *JUnitReporter) SpecDidComplete(specSummary *types.SpecSummary) { Name: strings.Join(specSummary.ComponentTexts[1:], " "), ClassName: reporter.testSuiteName, } + if reporter.ReporterConfig.ReportPassed && specSummary.State == types.SpecStatePassed { + testCase.PassedMessage = &JUnitPassedMessage{ + Message: specSummary.CapturedOutput, + } + } if specSummary.State == types.SpecStateFailed || specSummary.State == types.SpecStateTimedOut || specSummary.State == types.SpecStatePanicked { testCase.FailureMessage = &JUnitFailureMessage{ Type: reporter.failureTypeForState(specSummary.State), Message: failureMessage(specSummary.Failure), } + if specSummary.State == types.SpecStatePanicked { + testCase.FailureMessage.Message += fmt.Sprintf("\n\nPanic: %s\n\nFull stack:\n%s", + specSummary.Failure.ForwardedPanic, + specSummary.Failure.Location.FullStackTrace) + } testCase.SystemOut = specSummary.CapturedOutput } if specSummary.State == types.SpecStateSkipped || specSummary.State == types.SpecStatePending { diff --git a/vendor/github.com/onsi/ginkgo/reporters/teamcity_reporter.go b/vendor/github.com/onsi/ginkgo/reporters/teamcity_reporter.go index 36ee2a600..c8e27b2a7 100644 --- a/vendor/github.com/onsi/ginkgo/reporters/teamcity_reporter.go +++ b/vendor/github.com/onsi/ginkgo/reporters/teamcity_reporter.go @@ -22,8 +22,9 @@ const ( ) type TeamCityReporter struct { - writer io.Writer - testSuiteName string + writer io.Writer + testSuiteName string + ReporterConfig config.DefaultReporterConfigType } func NewTeamCityReporter(writer io.Writer) *TeamCityReporter { @@ -65,6 +66,10 @@ func (reporter *TeamCityReporter) SpecWillRun(specSummary *types.SpecSummary) { func (reporter *TeamCityReporter) SpecDidComplete(specSummary *types.SpecSummary) { testName := escape(strings.Join(specSummary.ComponentTexts[1:], " ")) + if reporter.ReporterConfig.ReportPassed && specSummary.State == types.SpecStatePassed { + details := escape(specSummary.CapturedOutput) + fmt.Fprintf(reporter.writer, "%s[testPassed name='%s' details='%s']", messageId, testName, details) + } if specSummary.State == types.SpecStateFailed || specSummary.State == types.SpecStateTimedOut || specSummary.State == types.SpecStatePanicked { message := escape(specSummary.Failure.ComponentCodeLocation.String()) details := escape(specSummary.Failure.Message) diff --git a/vendor/github.com/onsi/ginkgo/types/types.go b/vendor/github.com/onsi/ginkgo/types/types.go index 0e89521be..e4e32b761 100644 --- a/vendor/github.com/onsi/ginkgo/types/types.go +++ b/vendor/github.com/onsi/ginkgo/types/types.go @@ -17,7 +17,7 @@ each node does not deterministically know how many specs it will end up running. Unfortunately making such a change would break backward compatibility. -Until Ginkgo 2.0 comes out we will continue to reuse this struct but populate unkown fields +Until Ginkgo 2.0 comes out we will continue to reuse this struct but populate unknown fields with -1. */ type SuiteSummary struct { diff --git a/vendor/github.com/onsi/gomega/.travis.yml b/vendor/github.com/onsi/gomega/.travis.yml index 2420a5d07..d147e451d 100644 --- a/vendor/github.com/onsi/gomega/.travis.yml +++ b/vendor/github.com/onsi/gomega/.travis.yml @@ -4,6 +4,7 @@ go: - 1.10.x - 1.11.x - 1.12.x + - gotip env: - GO111MODULE=on diff --git a/vendor/github.com/onsi/gomega/CHANGELOG.md b/vendor/github.com/onsi/gomega/CHANGELOG.md index 5d1eda837..f67074016 100644 --- a/vendor/github.com/onsi/gomega/CHANGELOG.md +++ b/vendor/github.com/onsi/gomega/CHANGELOG.md @@ -1,3 +1,35 @@ +## 1.7.0 + +### Features +- export format property variables (#347) [642e5ba] + +### Fixes +- minor fix in the documentation of ExpectWithOffset (#358) [beea727] + +## 1.6.0 + +### Features + +- Display special chars on error [41e1b26] +- Add BeElementOf matcher [6a48b48] + +### Fixes + +- Remove duplication in XML matcher tests [cc1a6cb] +- Remove unnecessary conversions (#357) [7bf756a] +- Fixed import order (#353) [2e3b965] +- Added missing error handling in test (#355) [c98d3eb] +- Simplify code (#356) [0001ed9] +- Simplify code (#354) [0d9100e] +- Fixed typos (#352) [3f647c4] +- Add failure message tests to BeElementOf matcher [efe19c3] +- Update go-testcov untested sections [37ee382] +- Mark all uncovered files so go-testcov ./... works [53b150e] +- Reenable gotip in travis [5c249dc] +- Fix the typo of comment (#345) [f0e010e] +- Optimize contain_element_matcher [abeb93d] + + ## 1.5.0 ### Features diff --git a/vendor/github.com/onsi/gomega/format/format.go b/vendor/github.com/onsi/gomega/format/format.go index 6559525f1..fae25adce 100644 --- a/vendor/github.com/onsi/gomega/format/format.go +++ b/vendor/github.com/onsi/gomega/format/format.go @@ -1,6 +1,9 @@ /* Gomega's format package pretty-prints objects. It explores input objects recursively and generates formatted, indented output with type information. */ + +// untested sections: 4 + package format import ( @@ -33,7 +36,15 @@ var PrintContextObjects = false // TruncatedDiff choose if we should display a truncated pretty diff or not var TruncatedDiff = true -// Ctx interface defined here to keep backwards compatability with go < 1.7 +// TruncateThreshold (default 50) specifies the maximum length string to print in string comparison assertion error +// messages. +var TruncateThreshold uint = 50 + +// CharactersAroundMismatchToInclude (default 5) specifies how many contextual characters should be printed before and +// after the first diff location in a truncated string assertion error message. +var CharactersAroundMismatchToInclude uint = 5 + +// Ctx interface defined here to keep backwards compatibility with go < 1.7 // It matches the context.Context interface type Ctx interface { Deadline() (deadline time.Time, ok bool) @@ -58,7 +69,7 @@ Generates a formatted matcher success/failure message of the form: -If expected is omited, then the message looks like: +If expected is omitted, then the message looks like: Expected @@ -85,7 +96,7 @@ to equal | */ func MessageWithDiff(actual, message, expected string) string { - if TruncatedDiff && len(actual) >= truncateThreshold && len(expected) >= truncateThreshold { + if TruncatedDiff && len(actual) >= int(TruncateThreshold) && len(expected) >= int(TruncateThreshold) { diffPoint := findFirstMismatch(actual, expected) formattedActual := truncateAndFormat(actual, diffPoint) formattedExpected := truncateAndFormat(expected, diffPoint) @@ -97,14 +108,23 @@ func MessageWithDiff(actual, message, expected string) string { padding := strings.Repeat(" ", spaceFromMessageToActual+spacesBeforeFormattedMismatch) + "|" return Message(formattedActual, message+padding, formattedExpected) } + + actual = escapedWithGoSyntax(actual) + expected = escapedWithGoSyntax(expected) + return Message(actual, message, expected) } +func escapedWithGoSyntax(str string) string { + withQuotes := fmt.Sprintf("%q", str) + return withQuotes[1 : len(withQuotes)-1] +} + func truncateAndFormat(str string, index int) string { leftPadding := `...` rightPadding := `...` - start := index - charactersAroundMismatchToInclude + start := index - int(CharactersAroundMismatchToInclude) if start < 0 { start = 0 leftPadding = "" @@ -112,7 +132,7 @@ func truncateAndFormat(str string, index int) string { // slice index must include the mis-matched character lengthOfMismatchedCharacter := 1 - end := index + charactersAroundMismatchToInclude + lengthOfMismatchedCharacter + end := index + int(CharactersAroundMismatchToInclude) + lengthOfMismatchedCharacter if end > len(str) { end = len(str) rightPadding = "" @@ -141,11 +161,6 @@ func findFirstMismatch(a, b string) int { return 0 } -const ( - truncateThreshold = 50 - charactersAroundMismatchToInclude = 5 -) - /* Pretty prints the passed in object at the passed in indentation level. @@ -288,7 +303,7 @@ func formatString(object interface{}, indentation uint) string { } } - return fmt.Sprintf("%s", result) + return result } else { return fmt.Sprintf("%q", object) } diff --git a/vendor/github.com/onsi/gomega/gbytes/say_matcher.go b/vendor/github.com/onsi/gomega/gbytes/say_matcher.go index 14317182b..0763f5e2d 100644 --- a/vendor/github.com/onsi/gomega/gbytes/say_matcher.go +++ b/vendor/github.com/onsi/gomega/gbytes/say_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 1 + package gbytes import ( @@ -19,7 +21,7 @@ Say is a Gomega matcher that operates on gbytes.Buffers: will succeed if the unread portion of the buffer matches the regular expression "something". -When Say succeeds, it fast forwards the gbytes.Buffer's read cursor to just after the succesful match. +When Say succeeds, it fast forwards the gbytes.Buffer's read cursor to just after the successful match. Thus, subsequent calls to Say will only match against the unread portion of the buffer Say pairs very well with Eventually. To assert that a buffer eventually receives data matching "[123]-star" within 3 seconds you can: diff --git a/vendor/github.com/onsi/gomega/gexec/build.go b/vendor/github.com/onsi/gomega/gexec/build.go index 869c1ead8..741d845f4 100644 --- a/vendor/github.com/onsi/gomega/gexec/build.go +++ b/vendor/github.com/onsi/gomega/gexec/build.go @@ -1,3 +1,5 @@ +// untested sections: 5 + package gexec import ( @@ -66,7 +68,7 @@ func doBuild(gopath, packagePath string, env []string, args ...string) (compiled executable := filepath.Join(tmpDir, path.Base(packagePath)) if runtime.GOOS == "windows" { - executable = executable + ".exe" + executable += ".exe" } cmdArgs := append([]string{"build"}, args...) diff --git a/vendor/github.com/onsi/gomega/gexec/exit_matcher.go b/vendor/github.com/onsi/gomega/gexec/exit_matcher.go index 98a354937..6e70de68d 100644 --- a/vendor/github.com/onsi/gomega/gexec/exit_matcher.go +++ b/vendor/github.com/onsi/gomega/gexec/exit_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package gexec import ( diff --git a/vendor/github.com/onsi/gomega/gexec/prefixed_writer.go b/vendor/github.com/onsi/gomega/gexec/prefixed_writer.go index 05e695abc..feb6620c5 100644 --- a/vendor/github.com/onsi/gomega/gexec/prefixed_writer.go +++ b/vendor/github.com/onsi/gomega/gexec/prefixed_writer.go @@ -1,3 +1,5 @@ +// untested sections: 1 + package gexec import ( @@ -6,7 +8,7 @@ import ( ) /* -PrefixedWriter wraps an io.Writer, emiting the passed in prefix at the beginning of each new line. +PrefixedWriter wraps an io.Writer, emitting the passed in prefix at the beginning of each new line. This can be useful when running multiple gexec.Sessions concurrently - you can prefix the log output of each session by passing in a PrefixedWriter: diff --git a/vendor/github.com/onsi/gomega/gexec/session.go b/vendor/github.com/onsi/gomega/gexec/session.go index 5cb00ca65..6a09140fb 100644 --- a/vendor/github.com/onsi/gomega/gexec/session.go +++ b/vendor/github.com/onsi/gomega/gexec/session.go @@ -1,6 +1,9 @@ /* Package gexec provides support for testing external processes. */ + +// untested sections: 1 + package gexec import ( diff --git a/vendor/github.com/onsi/gomega/gomega_dsl.go b/vendor/github.com/onsi/gomega/gomega_dsl.go index 448d595da..b145768cf 100644 --- a/vendor/github.com/onsi/gomega/gomega_dsl.go +++ b/vendor/github.com/onsi/gomega/gomega_dsl.go @@ -24,7 +24,7 @@ import ( "github.com/onsi/gomega/types" ) -const GOMEGA_VERSION = "1.5.0" +const GOMEGA_VERSION = "1.7.0" const nilFailHandlerPanic = `You are trying to make an assertion, but Gomega's fail handler is nil. If you're using Ginkgo then you probably forgot to put your assertion in an It(). @@ -155,7 +155,7 @@ func Expect(actual interface{}, extra ...interface{}) Assertion { // ExpectWithOffset(1, "foo").To(Equal("foo")) // // Unlike `Expect` and `Ω`, `ExpectWithOffset` takes an additional integer argument -// this is used to modify the call-stack offset when computing line numbers. +// that is used to modify the call-stack offset when computing line numbers. // // This is most useful in helper functions that make assertions. If you want Gomega's // error message to refer to the calling line in the test (as opposed to the line in the helper function) @@ -242,7 +242,7 @@ func EventuallyWithOffset(offset int, actual interface{}, intervals ...interface // assert that all other values are nil/zero. // This allows you to pass Consistently a function that returns a value and an error - a common pattern in Go. // -// Consistently is useful in cases where you want to assert that something *does not happen* over a period of tiem. +// Consistently is useful in cases where you want to assert that something *does not happen* over a period of time. // For example, you want to assert that a goroutine does *not* send data down a channel. In this case, you could: // // Consistently(channel).ShouldNot(Receive()) @@ -280,7 +280,7 @@ func SetDefaultEventuallyPollingInterval(t time.Duration) { defaultEventuallyPollingInterval = t } -// SetDefaultConsistentlyDuration sets the default duration for Consistently. Consistently will verify that your condition is satsified for this long. +// SetDefaultConsistentlyDuration sets the default duration for Consistently. Consistently will verify that your condition is satisfied for this long. func SetDefaultConsistentlyDuration(t time.Duration) { defaultConsistentlyDuration = t } @@ -320,7 +320,7 @@ type GomegaAsyncAssertion = AsyncAssertion // All methods take a variadic optionalDescription argument. This is passed on to fmt.Sprintf() // and is used to annotate failure messages. // -// All methods return a bool that is true if hte assertion passed and false if it failed. +// All methods return a bool that is true if the assertion passed and false if it failed. // // Example: // diff --git a/vendor/github.com/onsi/gomega/internal/asyncassertion/async_assertion.go b/vendor/github.com/onsi/gomega/internal/asyncassertion/async_assertion.go index cdab233eb..a233e48c0 100644 --- a/vendor/github.com/onsi/gomega/internal/asyncassertion/async_assertion.go +++ b/vendor/github.com/onsi/gomega/internal/asyncassertion/async_assertion.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package asyncassertion import ( diff --git a/vendor/github.com/onsi/gomega/matchers.go b/vendor/github.com/onsi/gomega/matchers.go index c3a326dd4..9ec8893cb 100644 --- a/vendor/github.com/onsi/gomega/matchers.go +++ b/vendor/github.com/onsi/gomega/matchers.go @@ -269,6 +269,22 @@ func ContainElement(element interface{}) types.GomegaMatcher { } } +//BeElementOf succeeds if actual is contained in the passed in elements. +//BeElementOf() always uses Equal() to perform the match. +//When the passed in elements are comprised of a single element that is either an Array or Slice, BeElementOf() behaves +//as the reverse of ContainElement() that operates with Equal() to perform the match. +// Expect(2).Should(BeElementOf([]int{1, 2})) +// Expect(2).Should(BeElementOf([2]int{1, 2})) +//Otherwise, BeElementOf() provides a syntactic sugar for Or(Equal(_), Equal(_), ...): +// Expect(2).Should(BeElementOf(1, 2)) +// +//Actual must be typed. +func BeElementOf(elements ...interface{}) types.GomegaMatcher { + return &matchers.BeElementOfMatcher{ + Elements: elements, + } +} + //ConsistOf succeeds if actual contains precisely the elements passed into the matcher. The ordering of the elements does not matter. //By default ConsistOf() uses Equal() to match the elements, however custom matchers can be passed in instead. Here are some examples: // diff --git a/vendor/github.com/onsi/gomega/matchers/assignable_to_type_of_matcher.go b/vendor/github.com/onsi/gomega/matchers/assignable_to_type_of_matcher.go index 51f8be6ae..be4839520 100644 --- a/vendor/github.com/onsi/gomega/matchers/assignable_to_type_of_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/assignable_to_type_of_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_a_directory.go b/vendor/github.com/onsi/gomega/matchers/be_a_directory.go index 7b6975e41..acffc8570 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_a_directory.go +++ b/vendor/github.com/onsi/gomega/matchers/be_a_directory.go @@ -1,3 +1,5 @@ +// untested sections: 5 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_a_regular_file.go b/vendor/github.com/onsi/gomega/matchers/be_a_regular_file.go index e239131fb..89441c800 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_a_regular_file.go +++ b/vendor/github.com/onsi/gomega/matchers/be_a_regular_file.go @@ -1,3 +1,5 @@ +// untested sections: 5 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_an_existing_file.go b/vendor/github.com/onsi/gomega/matchers/be_an_existing_file.go index d42eba223..ec6506b00 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_an_existing_file.go +++ b/vendor/github.com/onsi/gomega/matchers/be_an_existing_file.go @@ -1,3 +1,5 @@ +// untested sections: 3 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_closed_matcher.go b/vendor/github.com/onsi/gomega/matchers/be_closed_matcher.go index 80c9c8bb1..f13c24490 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_closed_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/be_closed_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_element_of_matcher.go b/vendor/github.com/onsi/gomega/matchers/be_element_of_matcher.go new file mode 100644 index 000000000..1f9d7a8e6 --- /dev/null +++ b/vendor/github.com/onsi/gomega/matchers/be_element_of_matcher.go @@ -0,0 +1,57 @@ +// untested sections: 1 + +package matchers + +import ( + "fmt" + "reflect" + + "github.com/onsi/gomega/format" +) + +type BeElementOfMatcher struct { + Elements []interface{} +} + +func (matcher *BeElementOfMatcher) Match(actual interface{}) (success bool, err error) { + if reflect.TypeOf(actual) == nil { + return false, fmt.Errorf("BeElement matcher expects actual to be typed") + } + + length := len(matcher.Elements) + valueAt := func(i int) interface{} { + return matcher.Elements[i] + } + // Special handling of a single element of type Array or Slice + if length == 1 && isArrayOrSlice(valueAt(0)) { + element := valueAt(0) + value := reflect.ValueOf(element) + length = value.Len() + valueAt = func(i int) interface{} { + return value.Index(i).Interface() + } + } + + var lastError error + for i := 0; i < length; i++ { + matcher := &EqualMatcher{Expected: valueAt(i)} + success, err := matcher.Match(actual) + if err != nil { + lastError = err + continue + } + if success { + return true, nil + } + } + + return false, lastError +} + +func (matcher *BeElementOfMatcher) FailureMessage(actual interface{}) (message string) { + return format.Message(actual, "to be an element of", matcher.Elements) +} + +func (matcher *BeElementOfMatcher) NegatedFailureMessage(actual interface{}) (message string) { + return format.Message(actual, "not to be an element of", matcher.Elements) +} diff --git a/vendor/github.com/onsi/gomega/matchers/be_empty_matcher.go b/vendor/github.com/onsi/gomega/matchers/be_empty_matcher.go index 8b00311b0..527c1a1c1 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_empty_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/be_empty_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_equivalent_to_matcher.go b/vendor/github.com/onsi/gomega/matchers/be_equivalent_to_matcher.go index 97ab20a4e..263627f40 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_equivalent_to_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/be_equivalent_to_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_false_matcher.go b/vendor/github.com/onsi/gomega/matchers/be_false_matcher.go index 91d3b779e..e326c0157 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_false_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/be_false_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_identical_to.go b/vendor/github.com/onsi/gomega/matchers/be_identical_to.go index fdcda4d1f..631ce11e3 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_identical_to.go +++ b/vendor/github.com/onsi/gomega/matchers/be_identical_to.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_nil_matcher.go b/vendor/github.com/onsi/gomega/matchers/be_nil_matcher.go index 7ee84fe1b..551d99d74 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_nil_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/be_nil_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import "github.com/onsi/gomega/format" diff --git a/vendor/github.com/onsi/gomega/matchers/be_numerically_matcher.go b/vendor/github.com/onsi/gomega/matchers/be_numerically_matcher.go index 9f4f77eec..f72591a1a 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_numerically_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/be_numerically_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 4 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_sent_matcher.go b/vendor/github.com/onsi/gomega/matchers/be_sent_matcher.go index 302dd1a0a..cf582a3fc 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_sent_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/be_sent_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 3 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_temporally_matcher.go b/vendor/github.com/onsi/gomega/matchers/be_temporally_matcher.go index cb7c038ef..dec4db024 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_temporally_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/be_temporally_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 3 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/be_true_matcher.go b/vendor/github.com/onsi/gomega/matchers/be_true_matcher.go index ec57c5db4..60bc1e3fa 100644 --- a/vendor/github.com/onsi/gomega/matchers/be_true_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/be_true_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/consist_of.go b/vendor/github.com/onsi/gomega/matchers/consist_of.go index 7b0e08868..cbbf61802 100644 --- a/vendor/github.com/onsi/gomega/matchers/consist_of.go +++ b/vendor/github.com/onsi/gomega/matchers/consist_of.go @@ -1,3 +1,5 @@ +// untested sections: 3 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/contain_element_matcher.go b/vendor/github.com/onsi/gomega/matchers/contain_element_matcher.go index 4159335d0..8d6c44c7a 100644 --- a/vendor/github.com/onsi/gomega/matchers/contain_element_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/contain_element_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import ( @@ -22,19 +24,21 @@ func (matcher *ContainElementMatcher) Match(actual interface{}) (success bool, e } value := reflect.ValueOf(actual) - var keys []reflect.Value + var valueAt func(int) interface{} if isMap(actual) { - keys = value.MapKeys() + keys := value.MapKeys() + valueAt = func(i int) interface{} { + return value.MapIndex(keys[i]).Interface() + } + } else { + valueAt = func(i int) interface{} { + return value.Index(i).Interface() + } } + var lastError error for i := 0; i < value.Len(); i++ { - var success bool - var err error - if isMap(actual) { - success, err = elemMatcher.Match(value.MapIndex(keys[i]).Interface()) - } else { - success, err = elemMatcher.Match(value.Index(i).Interface()) - } + success, err := elemMatcher.Match(valueAt(i)) if err != nil { lastError = err continue diff --git a/vendor/github.com/onsi/gomega/matchers/contain_substring_matcher.go b/vendor/github.com/onsi/gomega/matchers/contain_substring_matcher.go index f8dc41e74..e725f8c27 100644 --- a/vendor/github.com/onsi/gomega/matchers/contain_substring_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/contain_substring_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/have_cap_matcher.go b/vendor/github.com/onsi/gomega/matchers/have_cap_matcher.go index 7ace93dc3..9856752f1 100644 --- a/vendor/github.com/onsi/gomega/matchers/have_cap_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/have_cap_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/have_key_matcher.go b/vendor/github.com/onsi/gomega/matchers/have_key_matcher.go index ea5b92336..00cffec70 100644 --- a/vendor/github.com/onsi/gomega/matchers/have_key_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/have_key_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 6 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/have_key_with_value_matcher.go b/vendor/github.com/onsi/gomega/matchers/have_key_with_value_matcher.go index 06355b1e9..4c5916804 100644 --- a/vendor/github.com/onsi/gomega/matchers/have_key_with_value_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/have_key_with_value_matcher.go @@ -1,3 +1,5 @@ +// untested sections:10 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/have_occurred_matcher.go b/vendor/github.com/onsi/gomega/matchers/have_occurred_matcher.go index bef00ae21..5bcfdd2ad 100644 --- a/vendor/github.com/onsi/gomega/matchers/have_occurred_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/have_occurred_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 2 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/receive_matcher.go b/vendor/github.com/onsi/gomega/matchers/receive_matcher.go index 2018a6128..1936a2ba5 100644 --- a/vendor/github.com/onsi/gomega/matchers/receive_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/receive_matcher.go @@ -1,3 +1,5 @@ +// untested sections: 3 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/semi_structured_data_support.go b/vendor/github.com/onsi/gomega/matchers/semi_structured_data_support.go index 639295684..1369c1e87 100644 --- a/vendor/github.com/onsi/gomega/matchers/semi_structured_data_support.go +++ b/vendor/github.com/onsi/gomega/matchers/semi_structured_data_support.go @@ -1,3 +1,5 @@ +// untested sections: 5 + package matchers import ( diff --git a/vendor/github.com/onsi/gomega/matchers/support/goraph/bipartitegraph/bipartitegraph.go b/vendor/github.com/onsi/gomega/matchers/support/goraph/bipartitegraph/bipartitegraph.go index 8aaf8759d..108f28586 100644 --- a/vendor/github.com/onsi/gomega/matchers/support/goraph/bipartitegraph/bipartitegraph.go +++ b/vendor/github.com/onsi/gomega/matchers/support/goraph/bipartitegraph/bipartitegraph.go @@ -1,6 +1,5 @@ package bipartitegraph -import "errors" import "fmt" import . "github.com/onsi/gomega/matchers/support/goraph/node" @@ -28,7 +27,7 @@ func NewBipartiteGraph(leftValues, rightValues []interface{}, neighbours func(in for j, rightValue := range rightValues { neighbours, err := neighbours(leftValue, rightValue) if err != nil { - return nil, errors.New(fmt.Sprintf("error determining adjacency for %v and %v: %s", leftValue, rightValue, err.Error())) + return nil, fmt.Errorf("error determining adjacency for %v and %v: %s", leftValue, rightValue, err.Error()) } if neighbours { diff --git a/vendor/github.com/onsi/gomega/matchers/type_support.go b/vendor/github.com/onsi/gomega/matchers/type_support.go index 75afcd844..dced2419e 100644 --- a/vendor/github.com/onsi/gomega/matchers/type_support.go +++ b/vendor/github.com/onsi/gomega/matchers/type_support.go @@ -6,6 +6,9 @@ See the docs for Gomega for documentation on the matchers http://onsi.github.io/gomega/ */ + +// untested sections: 11 + package matchers import ( diff --git a/vendor/github.com/spf13/pflag/.travis.yml b/vendor/github.com/spf13/pflag/.travis.yml index f8a63b308..00d04cb9b 100644 --- a/vendor/github.com/spf13/pflag/.travis.yml +++ b/vendor/github.com/spf13/pflag/.travis.yml @@ -3,8 +3,9 @@ sudo: false language: go go: - - 1.7.3 - - 1.8.1 + - 1.9.x + - 1.10.x + - 1.11.x - tip matrix: @@ -12,7 +13,7 @@ matrix: - go: tip install: - - go get github.com/golang/lint/golint + - go get golang.org/x/lint/golint - export PATH=$GOPATH/bin:$PATH - go install ./... diff --git a/vendor/github.com/spf13/pflag/README.md b/vendor/github.com/spf13/pflag/README.md index b052414d1..7eacc5bdb 100644 --- a/vendor/github.com/spf13/pflag/README.md +++ b/vendor/github.com/spf13/pflag/README.md @@ -86,8 +86,8 @@ fmt.Println("ip has value ", *ip) fmt.Println("flagvar has value ", flagvar) ``` -There are helpers function to get values later if you have the FlagSet but -it was difficult to keep up with all of the flag pointers in your code. +There are helper functions available to get the value stored in a Flag if you have a FlagSet but find +it difficult to keep up with all of the pointers in your code. If you have a pflag.FlagSet with a flag called 'flagname' of type int you can use GetInt() to get the int value. But notice that 'flagname' must exist and it must be an int. GetString("flagname") will fail. diff --git a/vendor/github.com/spf13/pflag/bool_slice.go b/vendor/github.com/spf13/pflag/bool_slice.go index 5af02f1a7..3731370d6 100644 --- a/vendor/github.com/spf13/pflag/bool_slice.go +++ b/vendor/github.com/spf13/pflag/bool_slice.go @@ -71,6 +71,44 @@ func (s *boolSliceValue) String() string { return "[" + out + "]" } +func (s *boolSliceValue) fromString(val string) (bool, error) { + return strconv.ParseBool(val) +} + +func (s *boolSliceValue) toString(val bool) string { + return strconv.FormatBool(val) +} + +func (s *boolSliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *boolSliceValue) Replace(val []string) error { + out := make([]bool, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *boolSliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func boolSliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/vendor/github.com/spf13/pflag/count.go b/vendor/github.com/spf13/pflag/count.go index aa126e44d..a0b2679f7 100644 --- a/vendor/github.com/spf13/pflag/count.go +++ b/vendor/github.com/spf13/pflag/count.go @@ -46,7 +46,7 @@ func (f *FlagSet) GetCount(name string) (int, error) { // CountVar defines a count flag with specified name, default value, and usage string. // The argument p points to an int variable in which to store the value of the flag. -// A count flag will add 1 to its value evey time it is found on the command line +// A count flag will add 1 to its value every time it is found on the command line func (f *FlagSet) CountVar(p *int, name string, usage string) { f.CountVarP(p, name, "", usage) } @@ -69,7 +69,7 @@ func CountVarP(p *int, name, shorthand string, usage string) { // Count defines a count flag with specified name, default value, and usage string. // The return value is the address of an int variable that stores the value of the flag. -// A count flag will add 1 to its value evey time it is found on the command line +// A count flag will add 1 to its value every time it is found on the command line func (f *FlagSet) Count(name string, usage string) *int { p := new(int) f.CountVarP(p, name, "", usage) diff --git a/vendor/github.com/spf13/pflag/duration_slice.go b/vendor/github.com/spf13/pflag/duration_slice.go index 52c6b6dc1..badadda53 100644 --- a/vendor/github.com/spf13/pflag/duration_slice.go +++ b/vendor/github.com/spf13/pflag/duration_slice.go @@ -51,6 +51,44 @@ func (s *durationSliceValue) String() string { return "[" + strings.Join(out, ",") + "]" } +func (s *durationSliceValue) fromString(val string) (time.Duration, error) { + return time.ParseDuration(val) +} + +func (s *durationSliceValue) toString(val time.Duration) string { + return fmt.Sprintf("%s", val) +} + +func (s *durationSliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *durationSliceValue) Replace(val []string) error { + out := make([]time.Duration, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *durationSliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func durationSliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/vendor/github.com/spf13/pflag/flag.go b/vendor/github.com/spf13/pflag/flag.go index 9beeda8ec..24a5036e9 100644 --- a/vendor/github.com/spf13/pflag/flag.go +++ b/vendor/github.com/spf13/pflag/flag.go @@ -57,9 +57,9 @@ that give one-letter shorthands for flags. You can use these by appending var ip = flag.IntP("flagname", "f", 1234, "help message") var flagvar bool func init() { - flag.BoolVarP("boolname", "b", true, "help message") + flag.BoolVarP(&flagvar, "boolname", "b", true, "help message") } - flag.VarP(&flagVar, "varname", "v", 1234, "help message") + flag.VarP(&flagval, "varname", "v", "help message") Shorthand letters can be used with single dashes on the command line. Boolean shorthand flags can be combined with other shorthand flags. @@ -190,6 +190,18 @@ type Value interface { Type() string } +// SliceValue is a secondary interface to all flags which hold a list +// of values. This allows full control over the value of list flags, +// and avoids complicated marshalling and unmarshalling to csv. +type SliceValue interface { + // Append adds the specified value to the end of the flag value list. + Append(string) error + // Replace will fully overwrite any data currently in the flag value list. + Replace([]string) error + // GetSlice returns the flag value list as an array of strings. + GetSlice() []string +} + // sortFlags returns the flags as a slice in lexicographical sorted order. func sortFlags(flags map[NormalizedName]*Flag) []*Flag { list := make(sort.StringSlice, len(flags)) diff --git a/vendor/github.com/spf13/pflag/float32_slice.go b/vendor/github.com/spf13/pflag/float32_slice.go new file mode 100644 index 000000000..caa352741 --- /dev/null +++ b/vendor/github.com/spf13/pflag/float32_slice.go @@ -0,0 +1,174 @@ +package pflag + +import ( + "fmt" + "strconv" + "strings" +) + +// -- float32Slice Value +type float32SliceValue struct { + value *[]float32 + changed bool +} + +func newFloat32SliceValue(val []float32, p *[]float32) *float32SliceValue { + isv := new(float32SliceValue) + isv.value = p + *isv.value = val + return isv +} + +func (s *float32SliceValue) Set(val string) error { + ss := strings.Split(val, ",") + out := make([]float32, len(ss)) + for i, d := range ss { + var err error + var temp64 float64 + temp64, err = strconv.ParseFloat(d, 32) + if err != nil { + return err + } + out[i] = float32(temp64) + + } + if !s.changed { + *s.value = out + } else { + *s.value = append(*s.value, out...) + } + s.changed = true + return nil +} + +func (s *float32SliceValue) Type() string { + return "float32Slice" +} + +func (s *float32SliceValue) String() string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = fmt.Sprintf("%f", d) + } + return "[" + strings.Join(out, ",") + "]" +} + +func (s *float32SliceValue) fromString(val string) (float32, error) { + t64, err := strconv.ParseFloat(val, 32) + if err != nil { + return 0, err + } + return float32(t64), nil +} + +func (s *float32SliceValue) toString(val float32) string { + return fmt.Sprintf("%f", val) +} + +func (s *float32SliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *float32SliceValue) Replace(val []string) error { + out := make([]float32, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *float32SliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + +func float32SliceConv(val string) (interface{}, error) { + val = strings.Trim(val, "[]") + // Empty string would cause a slice with one (empty) entry + if len(val) == 0 { + return []float32{}, nil + } + ss := strings.Split(val, ",") + out := make([]float32, len(ss)) + for i, d := range ss { + var err error + var temp64 float64 + temp64, err = strconv.ParseFloat(d, 32) + if err != nil { + return nil, err + } + out[i] = float32(temp64) + + } + return out, nil +} + +// GetFloat32Slice return the []float32 value of a flag with the given name +func (f *FlagSet) GetFloat32Slice(name string) ([]float32, error) { + val, err := f.getFlagType(name, "float32Slice", float32SliceConv) + if err != nil { + return []float32{}, err + } + return val.([]float32), nil +} + +// Float32SliceVar defines a float32Slice flag with specified name, default value, and usage string. +// The argument p points to a []float32 variable in which to store the value of the flag. +func (f *FlagSet) Float32SliceVar(p *[]float32, name string, value []float32, usage string) { + f.VarP(newFloat32SliceValue(value, p), name, "", usage) +} + +// Float32SliceVarP is like Float32SliceVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float32SliceVarP(p *[]float32, name, shorthand string, value []float32, usage string) { + f.VarP(newFloat32SliceValue(value, p), name, shorthand, usage) +} + +// Float32SliceVar defines a float32[] flag with specified name, default value, and usage string. +// The argument p points to a float32[] variable in which to store the value of the flag. +func Float32SliceVar(p *[]float32, name string, value []float32, usage string) { + CommandLine.VarP(newFloat32SliceValue(value, p), name, "", usage) +} + +// Float32SliceVarP is like Float32SliceVar, but accepts a shorthand letter that can be used after a single dash. +func Float32SliceVarP(p *[]float32, name, shorthand string, value []float32, usage string) { + CommandLine.VarP(newFloat32SliceValue(value, p), name, shorthand, usage) +} + +// Float32Slice defines a []float32 flag with specified name, default value, and usage string. +// The return value is the address of a []float32 variable that stores the value of the flag. +func (f *FlagSet) Float32Slice(name string, value []float32, usage string) *[]float32 { + p := []float32{} + f.Float32SliceVarP(&p, name, "", value, usage) + return &p +} + +// Float32SliceP is like Float32Slice, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float32SliceP(name, shorthand string, value []float32, usage string) *[]float32 { + p := []float32{} + f.Float32SliceVarP(&p, name, shorthand, value, usage) + return &p +} + +// Float32Slice defines a []float32 flag with specified name, default value, and usage string. +// The return value is the address of a []float32 variable that stores the value of the flag. +func Float32Slice(name string, value []float32, usage string) *[]float32 { + return CommandLine.Float32SliceP(name, "", value, usage) +} + +// Float32SliceP is like Float32Slice, but accepts a shorthand letter that can be used after a single dash. +func Float32SliceP(name, shorthand string, value []float32, usage string) *[]float32 { + return CommandLine.Float32SliceP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/float64_slice.go b/vendor/github.com/spf13/pflag/float64_slice.go new file mode 100644 index 000000000..85bf3073d --- /dev/null +++ b/vendor/github.com/spf13/pflag/float64_slice.go @@ -0,0 +1,166 @@ +package pflag + +import ( + "fmt" + "strconv" + "strings" +) + +// -- float64Slice Value +type float64SliceValue struct { + value *[]float64 + changed bool +} + +func newFloat64SliceValue(val []float64, p *[]float64) *float64SliceValue { + isv := new(float64SliceValue) + isv.value = p + *isv.value = val + return isv +} + +func (s *float64SliceValue) Set(val string) error { + ss := strings.Split(val, ",") + out := make([]float64, len(ss)) + for i, d := range ss { + var err error + out[i], err = strconv.ParseFloat(d, 64) + if err != nil { + return err + } + + } + if !s.changed { + *s.value = out + } else { + *s.value = append(*s.value, out...) + } + s.changed = true + return nil +} + +func (s *float64SliceValue) Type() string { + return "float64Slice" +} + +func (s *float64SliceValue) String() string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = fmt.Sprintf("%f", d) + } + return "[" + strings.Join(out, ",") + "]" +} + +func (s *float64SliceValue) fromString(val string) (float64, error) { + return strconv.ParseFloat(val, 64) +} + +func (s *float64SliceValue) toString(val float64) string { + return fmt.Sprintf("%f", val) +} + +func (s *float64SliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *float64SliceValue) Replace(val []string) error { + out := make([]float64, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *float64SliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + +func float64SliceConv(val string) (interface{}, error) { + val = strings.Trim(val, "[]") + // Empty string would cause a slice with one (empty) entry + if len(val) == 0 { + return []float64{}, nil + } + ss := strings.Split(val, ",") + out := make([]float64, len(ss)) + for i, d := range ss { + var err error + out[i], err = strconv.ParseFloat(d, 64) + if err != nil { + return nil, err + } + + } + return out, nil +} + +// GetFloat64Slice return the []float64 value of a flag with the given name +func (f *FlagSet) GetFloat64Slice(name string) ([]float64, error) { + val, err := f.getFlagType(name, "float64Slice", float64SliceConv) + if err != nil { + return []float64{}, err + } + return val.([]float64), nil +} + +// Float64SliceVar defines a float64Slice flag with specified name, default value, and usage string. +// The argument p points to a []float64 variable in which to store the value of the flag. +func (f *FlagSet) Float64SliceVar(p *[]float64, name string, value []float64, usage string) { + f.VarP(newFloat64SliceValue(value, p), name, "", usage) +} + +// Float64SliceVarP is like Float64SliceVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float64SliceVarP(p *[]float64, name, shorthand string, value []float64, usage string) { + f.VarP(newFloat64SliceValue(value, p), name, shorthand, usage) +} + +// Float64SliceVar defines a float64[] flag with specified name, default value, and usage string. +// The argument p points to a float64[] variable in which to store the value of the flag. +func Float64SliceVar(p *[]float64, name string, value []float64, usage string) { + CommandLine.VarP(newFloat64SliceValue(value, p), name, "", usage) +} + +// Float64SliceVarP is like Float64SliceVar, but accepts a shorthand letter that can be used after a single dash. +func Float64SliceVarP(p *[]float64, name, shorthand string, value []float64, usage string) { + CommandLine.VarP(newFloat64SliceValue(value, p), name, shorthand, usage) +} + +// Float64Slice defines a []float64 flag with specified name, default value, and usage string. +// The return value is the address of a []float64 variable that stores the value of the flag. +func (f *FlagSet) Float64Slice(name string, value []float64, usage string) *[]float64 { + p := []float64{} + f.Float64SliceVarP(&p, name, "", value, usage) + return &p +} + +// Float64SliceP is like Float64Slice, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Float64SliceP(name, shorthand string, value []float64, usage string) *[]float64 { + p := []float64{} + f.Float64SliceVarP(&p, name, shorthand, value, usage) + return &p +} + +// Float64Slice defines a []float64 flag with specified name, default value, and usage string. +// The return value is the address of a []float64 variable that stores the value of the flag. +func Float64Slice(name string, value []float64, usage string) *[]float64 { + return CommandLine.Float64SliceP(name, "", value, usage) +} + +// Float64SliceP is like Float64Slice, but accepts a shorthand letter that can be used after a single dash. +func Float64SliceP(name, shorthand string, value []float64, usage string) *[]float64 { + return CommandLine.Float64SliceP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/go.mod b/vendor/github.com/spf13/pflag/go.mod new file mode 100644 index 000000000..b2287eec1 --- /dev/null +++ b/vendor/github.com/spf13/pflag/go.mod @@ -0,0 +1,3 @@ +module github.com/spf13/pflag + +go 1.12 diff --git a/vendor/github.com/spf13/pflag/go.sum b/vendor/github.com/spf13/pflag/go.sum new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/github.com/spf13/pflag/int32_slice.go b/vendor/github.com/spf13/pflag/int32_slice.go new file mode 100644 index 000000000..ff128ff06 --- /dev/null +++ b/vendor/github.com/spf13/pflag/int32_slice.go @@ -0,0 +1,174 @@ +package pflag + +import ( + "fmt" + "strconv" + "strings" +) + +// -- int32Slice Value +type int32SliceValue struct { + value *[]int32 + changed bool +} + +func newInt32SliceValue(val []int32, p *[]int32) *int32SliceValue { + isv := new(int32SliceValue) + isv.value = p + *isv.value = val + return isv +} + +func (s *int32SliceValue) Set(val string) error { + ss := strings.Split(val, ",") + out := make([]int32, len(ss)) + for i, d := range ss { + var err error + var temp64 int64 + temp64, err = strconv.ParseInt(d, 0, 32) + if err != nil { + return err + } + out[i] = int32(temp64) + + } + if !s.changed { + *s.value = out + } else { + *s.value = append(*s.value, out...) + } + s.changed = true + return nil +} + +func (s *int32SliceValue) Type() string { + return "int32Slice" +} + +func (s *int32SliceValue) String() string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = fmt.Sprintf("%d", d) + } + return "[" + strings.Join(out, ",") + "]" +} + +func (s *int32SliceValue) fromString(val string) (int32, error) { + t64, err := strconv.ParseInt(val, 0, 32) + if err != nil { + return 0, err + } + return int32(t64), nil +} + +func (s *int32SliceValue) toString(val int32) string { + return fmt.Sprintf("%d", val) +} + +func (s *int32SliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *int32SliceValue) Replace(val []string) error { + out := make([]int32, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *int32SliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + +func int32SliceConv(val string) (interface{}, error) { + val = strings.Trim(val, "[]") + // Empty string would cause a slice with one (empty) entry + if len(val) == 0 { + return []int32{}, nil + } + ss := strings.Split(val, ",") + out := make([]int32, len(ss)) + for i, d := range ss { + var err error + var temp64 int64 + temp64, err = strconv.ParseInt(d, 0, 32) + if err != nil { + return nil, err + } + out[i] = int32(temp64) + + } + return out, nil +} + +// GetInt32Slice return the []int32 value of a flag with the given name +func (f *FlagSet) GetInt32Slice(name string) ([]int32, error) { + val, err := f.getFlagType(name, "int32Slice", int32SliceConv) + if err != nil { + return []int32{}, err + } + return val.([]int32), nil +} + +// Int32SliceVar defines a int32Slice flag with specified name, default value, and usage string. +// The argument p points to a []int32 variable in which to store the value of the flag. +func (f *FlagSet) Int32SliceVar(p *[]int32, name string, value []int32, usage string) { + f.VarP(newInt32SliceValue(value, p), name, "", usage) +} + +// Int32SliceVarP is like Int32SliceVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int32SliceVarP(p *[]int32, name, shorthand string, value []int32, usage string) { + f.VarP(newInt32SliceValue(value, p), name, shorthand, usage) +} + +// Int32SliceVar defines a int32[] flag with specified name, default value, and usage string. +// The argument p points to a int32[] variable in which to store the value of the flag. +func Int32SliceVar(p *[]int32, name string, value []int32, usage string) { + CommandLine.VarP(newInt32SliceValue(value, p), name, "", usage) +} + +// Int32SliceVarP is like Int32SliceVar, but accepts a shorthand letter that can be used after a single dash. +func Int32SliceVarP(p *[]int32, name, shorthand string, value []int32, usage string) { + CommandLine.VarP(newInt32SliceValue(value, p), name, shorthand, usage) +} + +// Int32Slice defines a []int32 flag with specified name, default value, and usage string. +// The return value is the address of a []int32 variable that stores the value of the flag. +func (f *FlagSet) Int32Slice(name string, value []int32, usage string) *[]int32 { + p := []int32{} + f.Int32SliceVarP(&p, name, "", value, usage) + return &p +} + +// Int32SliceP is like Int32Slice, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int32SliceP(name, shorthand string, value []int32, usage string) *[]int32 { + p := []int32{} + f.Int32SliceVarP(&p, name, shorthand, value, usage) + return &p +} + +// Int32Slice defines a []int32 flag with specified name, default value, and usage string. +// The return value is the address of a []int32 variable that stores the value of the flag. +func Int32Slice(name string, value []int32, usage string) *[]int32 { + return CommandLine.Int32SliceP(name, "", value, usage) +} + +// Int32SliceP is like Int32Slice, but accepts a shorthand letter that can be used after a single dash. +func Int32SliceP(name, shorthand string, value []int32, usage string) *[]int32 { + return CommandLine.Int32SliceP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/int64_slice.go b/vendor/github.com/spf13/pflag/int64_slice.go new file mode 100644 index 000000000..25464638f --- /dev/null +++ b/vendor/github.com/spf13/pflag/int64_slice.go @@ -0,0 +1,166 @@ +package pflag + +import ( + "fmt" + "strconv" + "strings" +) + +// -- int64Slice Value +type int64SliceValue struct { + value *[]int64 + changed bool +} + +func newInt64SliceValue(val []int64, p *[]int64) *int64SliceValue { + isv := new(int64SliceValue) + isv.value = p + *isv.value = val + return isv +} + +func (s *int64SliceValue) Set(val string) error { + ss := strings.Split(val, ",") + out := make([]int64, len(ss)) + for i, d := range ss { + var err error + out[i], err = strconv.ParseInt(d, 0, 64) + if err != nil { + return err + } + + } + if !s.changed { + *s.value = out + } else { + *s.value = append(*s.value, out...) + } + s.changed = true + return nil +} + +func (s *int64SliceValue) Type() string { + return "int64Slice" +} + +func (s *int64SliceValue) String() string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = fmt.Sprintf("%d", d) + } + return "[" + strings.Join(out, ",") + "]" +} + +func (s *int64SliceValue) fromString(val string) (int64, error) { + return strconv.ParseInt(val, 0, 64) +} + +func (s *int64SliceValue) toString(val int64) string { + return fmt.Sprintf("%d", val) +} + +func (s *int64SliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *int64SliceValue) Replace(val []string) error { + out := make([]int64, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *int64SliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + +func int64SliceConv(val string) (interface{}, error) { + val = strings.Trim(val, "[]") + // Empty string would cause a slice with one (empty) entry + if len(val) == 0 { + return []int64{}, nil + } + ss := strings.Split(val, ",") + out := make([]int64, len(ss)) + for i, d := range ss { + var err error + out[i], err = strconv.ParseInt(d, 0, 64) + if err != nil { + return nil, err + } + + } + return out, nil +} + +// GetInt64Slice return the []int64 value of a flag with the given name +func (f *FlagSet) GetInt64Slice(name string) ([]int64, error) { + val, err := f.getFlagType(name, "int64Slice", int64SliceConv) + if err != nil { + return []int64{}, err + } + return val.([]int64), nil +} + +// Int64SliceVar defines a int64Slice flag with specified name, default value, and usage string. +// The argument p points to a []int64 variable in which to store the value of the flag. +func (f *FlagSet) Int64SliceVar(p *[]int64, name string, value []int64, usage string) { + f.VarP(newInt64SliceValue(value, p), name, "", usage) +} + +// Int64SliceVarP is like Int64SliceVar, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int64SliceVarP(p *[]int64, name, shorthand string, value []int64, usage string) { + f.VarP(newInt64SliceValue(value, p), name, shorthand, usage) +} + +// Int64SliceVar defines a int64[] flag with specified name, default value, and usage string. +// The argument p points to a int64[] variable in which to store the value of the flag. +func Int64SliceVar(p *[]int64, name string, value []int64, usage string) { + CommandLine.VarP(newInt64SliceValue(value, p), name, "", usage) +} + +// Int64SliceVarP is like Int64SliceVar, but accepts a shorthand letter that can be used after a single dash. +func Int64SliceVarP(p *[]int64, name, shorthand string, value []int64, usage string) { + CommandLine.VarP(newInt64SliceValue(value, p), name, shorthand, usage) +} + +// Int64Slice defines a []int64 flag with specified name, default value, and usage string. +// The return value is the address of a []int64 variable that stores the value of the flag. +func (f *FlagSet) Int64Slice(name string, value []int64, usage string) *[]int64 { + p := []int64{} + f.Int64SliceVarP(&p, name, "", value, usage) + return &p +} + +// Int64SliceP is like Int64Slice, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) Int64SliceP(name, shorthand string, value []int64, usage string) *[]int64 { + p := []int64{} + f.Int64SliceVarP(&p, name, shorthand, value, usage) + return &p +} + +// Int64Slice defines a []int64 flag with specified name, default value, and usage string. +// The return value is the address of a []int64 variable that stores the value of the flag. +func Int64Slice(name string, value []int64, usage string) *[]int64 { + return CommandLine.Int64SliceP(name, "", value, usage) +} + +// Int64SliceP is like Int64Slice, but accepts a shorthand letter that can be used after a single dash. +func Int64SliceP(name, shorthand string, value []int64, usage string) *[]int64 { + return CommandLine.Int64SliceP(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/int_slice.go b/vendor/github.com/spf13/pflag/int_slice.go index 1e7c9edde..e71c39d91 100644 --- a/vendor/github.com/spf13/pflag/int_slice.go +++ b/vendor/github.com/spf13/pflag/int_slice.go @@ -51,6 +51,36 @@ func (s *intSliceValue) String() string { return "[" + strings.Join(out, ",") + "]" } +func (s *intSliceValue) Append(val string) error { + i, err := strconv.Atoi(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *intSliceValue) Replace(val []string) error { + out := make([]int, len(val)) + for i, d := range val { + var err error + out[i], err = strconv.Atoi(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *intSliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = strconv.Itoa(d) + } + return out +} + func intSliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/vendor/github.com/spf13/pflag/ip_slice.go b/vendor/github.com/spf13/pflag/ip_slice.go index 7dd196fe3..775faae4f 100644 --- a/vendor/github.com/spf13/pflag/ip_slice.go +++ b/vendor/github.com/spf13/pflag/ip_slice.go @@ -72,9 +72,47 @@ func (s *ipSliceValue) String() string { return "[" + out + "]" } +func (s *ipSliceValue) fromString(val string) (net.IP, error) { + return net.ParseIP(strings.TrimSpace(val)), nil +} + +func (s *ipSliceValue) toString(val net.IP) string { + return val.String() +} + +func (s *ipSliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *ipSliceValue) Replace(val []string) error { + out := make([]net.IP, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *ipSliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func ipSliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") - // Emtpy string would cause a slice with one (empty) entry + // Empty string would cause a slice with one (empty) entry if len(val) == 0 { return []net.IP{}, nil } diff --git a/vendor/github.com/spf13/pflag/string_array.go b/vendor/github.com/spf13/pflag/string_array.go index fa7bc6018..4894af818 100644 --- a/vendor/github.com/spf13/pflag/string_array.go +++ b/vendor/github.com/spf13/pflag/string_array.go @@ -23,6 +23,32 @@ func (s *stringArrayValue) Set(val string) error { return nil } +func (s *stringArrayValue) Append(val string) error { + *s.value = append(*s.value, val) + return nil +} + +func (s *stringArrayValue) Replace(val []string) error { + out := make([]string, len(val)) + for i, d := range val { + var err error + out[i] = d + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *stringArrayValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = d + } + return out +} + func (s *stringArrayValue) Type() string { return "stringArray" } diff --git a/vendor/github.com/spf13/pflag/string_slice.go b/vendor/github.com/spf13/pflag/string_slice.go index 0cd3ccc08..3cb2e69db 100644 --- a/vendor/github.com/spf13/pflag/string_slice.go +++ b/vendor/github.com/spf13/pflag/string_slice.go @@ -62,6 +62,20 @@ func (s *stringSliceValue) String() string { return "[" + str + "]" } +func (s *stringSliceValue) Append(val string) error { + *s.value = append(*s.value, val) + return nil +} + +func (s *stringSliceValue) Replace(val []string) error { + *s.value = val + return nil +} + +func (s *stringSliceValue) GetSlice() []string { + return *s.value +} + func stringSliceConv(sval string) (interface{}, error) { sval = sval[1 : len(sval)-1] // An empty string would cause a slice with one (empty) string @@ -84,7 +98,7 @@ func (f *FlagSet) GetStringSlice(name string) ([]string, error) { // The argument p points to a []string variable in which to store the value of the flag. // Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. // For example: -// --ss="v1,v2" -ss="v3" +// --ss="v1,v2" --ss="v3" // will result in // []string{"v1", "v2", "v3"} func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string) { @@ -100,7 +114,7 @@ func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string, value []s // The argument p points to a []string variable in which to store the value of the flag. // Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. // For example: -// --ss="v1,v2" -ss="v3" +// --ss="v1,v2" --ss="v3" // will result in // []string{"v1", "v2", "v3"} func StringSliceVar(p *[]string, name string, value []string, usage string) { @@ -116,7 +130,7 @@ func StringSliceVarP(p *[]string, name, shorthand string, value []string, usage // The return value is the address of a []string variable that stores the value of the flag. // Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. // For example: -// --ss="v1,v2" -ss="v3" +// --ss="v1,v2" --ss="v3" // will result in // []string{"v1", "v2", "v3"} func (f *FlagSet) StringSlice(name string, value []string, usage string) *[]string { @@ -136,7 +150,7 @@ func (f *FlagSet) StringSliceP(name, shorthand string, value []string, usage str // The return value is the address of a []string variable that stores the value of the flag. // Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly. // For example: -// --ss="v1,v2" -ss="v3" +// --ss="v1,v2" --ss="v3" // will result in // []string{"v1", "v2", "v3"} func StringSlice(name string, value []string, usage string) *[]string { diff --git a/vendor/github.com/spf13/pflag/string_to_int64.go b/vendor/github.com/spf13/pflag/string_to_int64.go new file mode 100644 index 000000000..a807a04a0 --- /dev/null +++ b/vendor/github.com/spf13/pflag/string_to_int64.go @@ -0,0 +1,149 @@ +package pflag + +import ( + "bytes" + "fmt" + "strconv" + "strings" +) + +// -- stringToInt64 Value +type stringToInt64Value struct { + value *map[string]int64 + changed bool +} + +func newStringToInt64Value(val map[string]int64, p *map[string]int64) *stringToInt64Value { + ssv := new(stringToInt64Value) + ssv.value = p + *ssv.value = val + return ssv +} + +// Format: a=1,b=2 +func (s *stringToInt64Value) Set(val string) error { + ss := strings.Split(val, ",") + out := make(map[string]int64, len(ss)) + for _, pair := range ss { + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 { + return fmt.Errorf("%s must be formatted as key=value", pair) + } + var err error + out[kv[0]], err = strconv.ParseInt(kv[1], 10, 64) + if err != nil { + return err + } + } + if !s.changed { + *s.value = out + } else { + for k, v := range out { + (*s.value)[k] = v + } + } + s.changed = true + return nil +} + +func (s *stringToInt64Value) Type() string { + return "stringToInt64" +} + +func (s *stringToInt64Value) String() string { + var buf bytes.Buffer + i := 0 + for k, v := range *s.value { + if i > 0 { + buf.WriteRune(',') + } + buf.WriteString(k) + buf.WriteRune('=') + buf.WriteString(strconv.FormatInt(v, 10)) + i++ + } + return "[" + buf.String() + "]" +} + +func stringToInt64Conv(val string) (interface{}, error) { + val = strings.Trim(val, "[]") + // An empty string would cause an empty map + if len(val) == 0 { + return map[string]int64{}, nil + } + ss := strings.Split(val, ",") + out := make(map[string]int64, len(ss)) + for _, pair := range ss { + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 { + return nil, fmt.Errorf("%s must be formatted as key=value", pair) + } + var err error + out[kv[0]], err = strconv.ParseInt(kv[1], 10, 64) + if err != nil { + return nil, err + } + } + return out, nil +} + +// GetStringToInt64 return the map[string]int64 value of a flag with the given name +func (f *FlagSet) GetStringToInt64(name string) (map[string]int64, error) { + val, err := f.getFlagType(name, "stringToInt64", stringToInt64Conv) + if err != nil { + return map[string]int64{}, err + } + return val.(map[string]int64), nil +} + +// StringToInt64Var defines a string flag with specified name, default value, and usage string. +// The argument p point64s to a map[string]int64 variable in which to store the values of the multiple flags. +// The value of each argument will not try to be separated by comma +func (f *FlagSet) StringToInt64Var(p *map[string]int64, name string, value map[string]int64, usage string) { + f.VarP(newStringToInt64Value(value, p), name, "", usage) +} + +// StringToInt64VarP is like StringToInt64Var, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringToInt64VarP(p *map[string]int64, name, shorthand string, value map[string]int64, usage string) { + f.VarP(newStringToInt64Value(value, p), name, shorthand, usage) +} + +// StringToInt64Var defines a string flag with specified name, default value, and usage string. +// The argument p point64s to a map[string]int64 variable in which to store the value of the flag. +// The value of each argument will not try to be separated by comma +func StringToInt64Var(p *map[string]int64, name string, value map[string]int64, usage string) { + CommandLine.VarP(newStringToInt64Value(value, p), name, "", usage) +} + +// StringToInt64VarP is like StringToInt64Var, but accepts a shorthand letter that can be used after a single dash. +func StringToInt64VarP(p *map[string]int64, name, shorthand string, value map[string]int64, usage string) { + CommandLine.VarP(newStringToInt64Value(value, p), name, shorthand, usage) +} + +// StringToInt64 defines a string flag with specified name, default value, and usage string. +// The return value is the address of a map[string]int64 variable that stores the value of the flag. +// The value of each argument will not try to be separated by comma +func (f *FlagSet) StringToInt64(name string, value map[string]int64, usage string) *map[string]int64 { + p := map[string]int64{} + f.StringToInt64VarP(&p, name, "", value, usage) + return &p +} + +// StringToInt64P is like StringToInt64, but accepts a shorthand letter that can be used after a single dash. +func (f *FlagSet) StringToInt64P(name, shorthand string, value map[string]int64, usage string) *map[string]int64 { + p := map[string]int64{} + f.StringToInt64VarP(&p, name, shorthand, value, usage) + return &p +} + +// StringToInt64 defines a string flag with specified name, default value, and usage string. +// The return value is the address of a map[string]int64 variable that stores the value of the flag. +// The value of each argument will not try to be separated by comma +func StringToInt64(name string, value map[string]int64, usage string) *map[string]int64 { + return CommandLine.StringToInt64P(name, "", value, usage) +} + +// StringToInt64P is like StringToInt64, but accepts a shorthand letter that can be used after a single dash. +func StringToInt64P(name, shorthand string, value map[string]int64, usage string) *map[string]int64 { + return CommandLine.StringToInt64P(name, shorthand, value, usage) +} diff --git a/vendor/github.com/spf13/pflag/uint_slice.go b/vendor/github.com/spf13/pflag/uint_slice.go index edd94c600..5fa924835 100644 --- a/vendor/github.com/spf13/pflag/uint_slice.go +++ b/vendor/github.com/spf13/pflag/uint_slice.go @@ -50,6 +50,48 @@ func (s *uintSliceValue) String() string { return "[" + strings.Join(out, ",") + "]" } +func (s *uintSliceValue) fromString(val string) (uint, error) { + t, err := strconv.ParseUint(val, 10, 0) + if err != nil { + return 0, err + } + return uint(t), nil +} + +func (s *uintSliceValue) toString(val uint) string { + return fmt.Sprintf("%d", val) +} + +func (s *uintSliceValue) Append(val string) error { + i, err := s.fromString(val) + if err != nil { + return err + } + *s.value = append(*s.value, i) + return nil +} + +func (s *uintSliceValue) Replace(val []string) error { + out := make([]uint, len(val)) + for i, d := range val { + var err error + out[i], err = s.fromString(d) + if err != nil { + return err + } + } + *s.value = out + return nil +} + +func (s *uintSliceValue) GetSlice() []string { + out := make([]string, len(*s.value)) + for i, d := range *s.value { + out[i] = s.toString(d) + } + return out +} + func uintSliceConv(val string) (interface{}, error) { val = strings.Trim(val, "[]") // Empty string would cause a slice with one (empty) entry diff --git a/vendor/golang.org/x/oauth2/README.md b/vendor/golang.org/x/oauth2/README.md index eb8dcee17..68f436ed9 100644 --- a/vendor/golang.org/x/oauth2/README.md +++ b/vendor/golang.org/x/oauth2/README.md @@ -24,7 +24,9 @@ See godoc for further documentation and examples. In change 96e89be (March 2015), we removed the `oauth2.Context2` type in favor of the [`context.Context`](https://golang.org/x/net/context#Context) type from -the `golang.org/x/net/context` package +the `golang.org/x/net/context` package. Later replaced by the standard `context` package +of the [`context.Context`](https://golang.org/pkg/context#Context) type. + This means it's no longer possible to use the "Classic App Engine" `appengine.Context` type with the `oauth2` package. (You're using @@ -44,7 +46,7 @@ with the `oauth2` package. ```go import ( - "golang.org/x/net/context" + "context" "golang.org/x/oauth2" "golang.org/x/oauth2/google" newappengine "google.golang.org/appengine" @@ -68,6 +70,13 @@ func handler(w http.ResponseWriter, r *http.Request) { } ``` +## Policy for new packages + +We no longer accept new provider-specific packages in this repo. For +defining provider endpoints and provider-specific OAuth2 behavior, we +encourage you to create packages elsewhere. We'll keep the existing +packages for compatibility. + ## Report Issues / Send Patches This repository uses Gerrit for code changes. To learn how to submit changes to diff --git a/vendor/golang.org/x/oauth2/internal/oauth2.go b/vendor/golang.org/x/oauth2/internal/oauth2.go index fc63fcab3..c0ab196cf 100644 --- a/vendor/golang.org/x/oauth2/internal/oauth2.go +++ b/vendor/golang.org/x/oauth2/internal/oauth2.go @@ -26,7 +26,7 @@ func ParseKey(key []byte) (*rsa.PrivateKey, error) { if err != nil { parsedKey, err = x509.ParsePKCS1PrivateKey(key) if err != nil { - return nil, fmt.Errorf("private key should be a PEM or plain PKSC1 or PKCS8; parse error: %v", err) + return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8; parse error: %v", err) } } parsed, ok := parsedKey.(*rsa.PrivateKey) diff --git a/vendor/golang.org/x/oauth2/internal/token.go b/vendor/golang.org/x/oauth2/internal/token.go index 5c5451ad8..5ab17b9a5 100644 --- a/vendor/golang.org/x/oauth2/internal/token.go +++ b/vendor/golang.org/x/oauth2/internal/token.go @@ -5,6 +5,7 @@ package internal import ( + "context" "encoding/json" "errors" "fmt" @@ -17,7 +18,6 @@ import ( "strings" "time" - "golang.org/x/net/context" "golang.org/x/net/context/ctxhttp" ) @@ -110,6 +110,7 @@ var brokenAuthHeaderProviders = []string{ "https://login.salesforce.com/", "https://login.windows.net", "https://login.live.com/", + "https://login.live-int.com/", "https://oauth.sandbox.trainingpeaks.com/", "https://oauth.trainingpeaks.com/", "https://oauth.vk.com/", @@ -132,6 +133,7 @@ var brokenAuthHeaderProviders = []string{ "https://whats.todaysplan.com.au/rest/oauth/access_token", "https://stackoverflow.com/oauth/access_token", "https://account.health.nokia.com", + "https://accounts.zoho.com", } // brokenAuthHeaderDomains lists broken providers that issue dynamic endpoints. diff --git a/vendor/golang.org/x/oauth2/internal/transport.go b/vendor/golang.org/x/oauth2/internal/transport.go index d16f9ae1f..572074a63 100644 --- a/vendor/golang.org/x/oauth2/internal/transport.go +++ b/vendor/golang.org/x/oauth2/internal/transport.go @@ -5,9 +5,8 @@ package internal import ( + "context" "net/http" - - "golang.org/x/net/context" ) // HTTPClient is the context key to use with golang.org/x/net/context's diff --git a/vendor/golang.org/x/oauth2/oauth2.go b/vendor/golang.org/x/oauth2/oauth2.go index 16775d081..1e8e1b741 100644 --- a/vendor/golang.org/x/oauth2/oauth2.go +++ b/vendor/golang.org/x/oauth2/oauth2.go @@ -10,13 +10,13 @@ package oauth2 // import "golang.org/x/oauth2" import ( "bytes" + "context" "errors" "net/http" "net/url" "strings" "sync" - "golang.org/x/net/context" "golang.org/x/oauth2/internal" ) @@ -164,8 +164,7 @@ func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string { // and when other authorization grant types are not available." // See https://tools.ietf.org/html/rfc6749#section-4.3 for more info. // -// The HTTP client to use is derived from the context. -// If nil, http.DefaultClient is used. +// The provided context optionally controls which HTTP client is used. See the HTTPClient variable. func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) { v := url.Values{ "grant_type": {"password"}, @@ -183,8 +182,7 @@ func (c *Config) PasswordCredentialsToken(ctx context.Context, username, passwor // It is used after a resource provider redirects the user back // to the Redirect URI (the URL obtained from AuthCodeURL). // -// The HTTP client to use is derived from the context. -// If a client is not provided via the context, http.DefaultClient is used. +// The provided context optionally controls which HTTP client is used. See the HTTPClient variable. // // The code will be in the *http.Request.FormValue("code"). Before // calling Exchange, be sure to validate FormValue("state"). diff --git a/vendor/golang.org/x/oauth2/token.go b/vendor/golang.org/x/oauth2/token.go index 34db8cdc8..9be1ae537 100644 --- a/vendor/golang.org/x/oauth2/token.go +++ b/vendor/golang.org/x/oauth2/token.go @@ -5,6 +5,7 @@ package oauth2 import ( + "context" "fmt" "net/http" "net/url" @@ -12,7 +13,6 @@ import ( "strings" "time" - "golang.org/x/net/context" "golang.org/x/oauth2/internal" ) diff --git a/vendor/golang.org/x/time/rate/rate.go b/vendor/golang.org/x/time/rate/rate.go index 7228d97e9..ae93e2471 100644 --- a/vendor/golang.org/x/time/rate/rate.go +++ b/vendor/golang.org/x/time/rate/rate.go @@ -6,6 +6,7 @@ package rate import ( + "context" "fmt" "math" "sync" @@ -212,19 +213,8 @@ func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation { return &r } -// contextContext is a temporary(?) copy of the context.Context type -// to support both Go 1.6 using golang.org/x/net/context and Go 1.7+ -// with the built-in context package. If people ever stop using Go 1.6 -// we can remove this. -type contextContext interface { - Deadline() (deadline time.Time, ok bool) - Done() <-chan struct{} - Err() error - Value(key interface{}) interface{} -} - // Wait is shorthand for WaitN(ctx, 1). -func (lim *Limiter) wait(ctx contextContext) (err error) { +func (lim *Limiter) Wait(ctx context.Context) (err error) { return lim.WaitN(ctx, 1) } @@ -232,7 +222,7 @@ func (lim *Limiter) wait(ctx contextContext) (err error) { // It returns an error if n exceeds the Limiter's burst size, the Context is // canceled, or the expected wait time exceeds the Context's Deadline. // The burst limit is ignored if the rate limit is Inf. -func (lim *Limiter) waitN(ctx contextContext, n int) (err error) { +func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) { if n > lim.burst && lim.limit != Inf { return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, lim.burst) } diff --git a/vendor/golang.org/x/time/rate/rate_go16.go b/vendor/golang.org/x/time/rate/rate_go16.go deleted file mode 100644 index 6bab1850f..000000000 --- a/vendor/golang.org/x/time/rate/rate_go16.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !go1.7 - -package rate - -import "golang.org/x/net/context" - -// Wait is shorthand for WaitN(ctx, 1). -func (lim *Limiter) Wait(ctx context.Context) (err error) { - return lim.waitN(ctx, 1) -} - -// WaitN blocks until lim permits n events to happen. -// It returns an error if n exceeds the Limiter's burst size, the Context is -// canceled, or the expected wait time exceeds the Context's Deadline. -func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) { - return lim.waitN(ctx, n) -} diff --git a/vendor/golang.org/x/time/rate/rate_go17.go b/vendor/golang.org/x/time/rate/rate_go17.go deleted file mode 100644 index f90d85f51..000000000 --- a/vendor/golang.org/x/time/rate/rate_go17.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build go1.7 - -package rate - -import "context" - -// Wait is shorthand for WaitN(ctx, 1). -func (lim *Limiter) Wait(ctx context.Context) (err error) { - return lim.waitN(ctx, 1) -} - -// WaitN blocks until lim permits n events to happen. -// It returns an error if n exceeds the Limiter's burst size, the Context is -// canceled, or the expected wait time exceeds the Context's Deadline. -func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) { - return lim.waitN(ctx, n) -} diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v2/decode.go index e4e56e28e..91679b5b4 100644 --- a/vendor/gopkg.in/yaml.v2/decode.go +++ b/vendor/gopkg.in/yaml.v2/decode.go @@ -229,6 +229,10 @@ type decoder struct { mapType reflect.Type terrors []string strict bool + + decodeCount int + aliasCount int + aliasDepth int } var ( @@ -315,6 +319,13 @@ func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unm } func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) { + d.decodeCount++ + if d.aliasDepth > 0 { + d.aliasCount++ + } + if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > 0.99 { + failf("document contains excessive aliasing") + } switch n.kind { case documentNode: return d.document(n, out) @@ -353,7 +364,9 @@ func (d *decoder) alias(n *node, out reflect.Value) (good bool) { failf("anchor '%s' value contains itself", n.value) } d.aliases[n] = true + d.aliasDepth++ good = d.unmarshal(n.alias, out) + d.aliasDepth-- delete(d.aliases, n) return good } diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v2/resolve.go index 6c151db6f..4120e0c91 100644 --- a/vendor/gopkg.in/yaml.v2/resolve.go +++ b/vendor/gopkg.in/yaml.v2/resolve.go @@ -81,7 +81,7 @@ func resolvableTag(tag string) bool { return false } -var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`) +var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`) func resolve(tag string, in string) (rtag string, out interface{}) { if !resolvableTag(tag) { diff --git a/vendor/k8s.io/klog/.travis.yml b/vendor/k8s.io/klog/.travis.yml index 0f508dae6..5677664c2 100644 --- a/vendor/k8s.io/klog/.travis.yml +++ b/vendor/k8s.io/klog/.travis.yml @@ -5,11 +5,12 @@ go: - 1.9.x - 1.10.x - 1.11.x + - 1.12.x script: - go get -t -v ./... - diff -u <(echo -n) <(gofmt -d .) - diff -u <(echo -n) <(golint $(go list -e ./...)) - - go tool vet . + - go tool vet . || go vet . - go test -v -race ./... install: - go get golang.org/x/lint/golint diff --git a/vendor/k8s.io/klog/README.md b/vendor/k8s.io/klog/README.md index bee306f39..841468b4b 100644 --- a/vendor/k8s.io/klog/README.md +++ b/vendor/k8s.io/klog/README.md @@ -31,7 +31,7 @@ How to use klog - Use `klog.InitFlags(nil)` explicitly for initializing global flags as we no longer use `init()` method to register the flags - You can now use `log-file` instead of `log-dir` for logging to a single file (See `examples/log_file/usage_log_file.go`) - If you want to redirect everything logged using klog somewhere else (say syslog!), you can use `klog.SetOutput()` method and supply a `io.Writer`. (See `examples/set_output/usage_set_output.go`) -- For more logging conventions (See [Logging Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md)) +- For more logging conventions (See [Logging Conventions](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md)) ### Coexisting with glog This package can be used side by side with glog. [This example](examples/coexist_glog/coexist_glog.go) shows how to initialize and syncronize flags from the global `flag.CommandLine` FlagSet. In addition, the example makes use of stderr as combined output by setting `alsologtostderr` (or `logtostderr`) to `true`. diff --git a/vendor/k8s.io/klog/go.mod b/vendor/k8s.io/klog/go.mod new file mode 100644 index 000000000..3877d8546 --- /dev/null +++ b/vendor/k8s.io/klog/go.mod @@ -0,0 +1,5 @@ +module k8s.io/klog + +go 1.12 + +require github.com/go-logr/logr v0.1.0 diff --git a/vendor/k8s.io/klog/go.sum b/vendor/k8s.io/klog/go.sum new file mode 100644 index 000000000..fb64d277a --- /dev/null +++ b/vendor/k8s.io/klog/go.sum @@ -0,0 +1,2 @@ +github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= diff --git a/vendor/k8s.io/klog/klog.go b/vendor/k8s.io/klog/klog.go index 887ea62df..2712ce0af 100644 --- a/vendor/k8s.io/klog/klog.go +++ b/vendor/k8s.io/klog/klog.go @@ -20,26 +20,26 @@ // // Basic examples: // -// glog.Info("Prepare to repel boarders") +// klog.Info("Prepare to repel boarders") // -// glog.Fatalf("Initialization failed: %s", err) +// klog.Fatalf("Initialization failed: %s", err) // // See the documentation for the V function for an explanation of these examples: // -// if glog.V(2) { -// glog.Info("Starting transaction...") +// if klog.V(2) { +// klog.Info("Starting transaction...") // } // -// glog.V(2).Infoln("Processed", nItems, "elements") +// klog.V(2).Infoln("Processed", nItems, "elements") // // Log output is buffered and written periodically using Flush. Programs // should call Flush before exiting to guarantee all log output is written. // -// By default, all log statements write to files in a temporary directory. +// By default, all log statements write to standard error. // This package provides several flags that modify this behavior. // As a result, flag.Parse must be called before any logging is done. // -// -logtostderr=false +// -logtostderr=true // Logs are written to standard error instead of to files. // -alsologtostderr=false // Logs are written to standard error as well as to files. @@ -142,7 +142,7 @@ func (s *severity) Set(value string) error { if v, ok := severityByName(value); ok { threshold = v } else { - v, err := strconv.Atoi(value) + v, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } @@ -226,7 +226,7 @@ func (l *Level) Get() interface{} { // Set is part of the flag.Value interface. func (l *Level) Set(value string) error { - v, err := strconv.Atoi(value) + v, err := strconv.ParseInt(value, 10, 32) if err != nil { return err } @@ -294,7 +294,7 @@ func (m *moduleSpec) Set(value string) error { return errVmoduleSyntax } pattern := patLev[0] - v, err := strconv.Atoi(patLev[1]) + v, err := strconv.ParseInt(patLev[1], 10, 32) if err != nil { return errors.New("syntax error: expect comma-separated list of filename=N") } @@ -396,29 +396,38 @@ type flushSyncWriter interface { io.Writer } +// init sets up the defaults and runs flushDaemon. func init() { - // Default stderrThreshold is ERROR. - logging.stderrThreshold = errorLog - + logging.stderrThreshold = errorLog // Default stderrThreshold is ERROR. logging.setVState(0, nil, false) + logging.logDir = "" + logging.logFile = "" + logging.logFileMaxSizeMB = 1800 + logging.toStderr = true + logging.alsoToStderr = false + logging.skipHeaders = false + logging.addDirHeader = false + logging.skipLogHeaders = false go logging.flushDaemon() } -// InitFlags is for explicitly initializing the flags +// InitFlags is for explicitly initializing the flags. func InitFlags(flagset *flag.FlagSet) { if flagset == nil { flagset = flag.CommandLine } - flagset.StringVar(&logging.logDir, "log_dir", "", "If non-empty, write log files in this directory") - flagset.StringVar(&logging.logFile, "log_file", "", "If non-empty, use this log file") - flagset.Uint64Var(&logging.logFileMaxSizeMB, "log_file_max_size", 1800, + + flagset.StringVar(&logging.logDir, "log_dir", logging.logDir, "If non-empty, write log files in this directory") + flagset.StringVar(&logging.logFile, "log_file", logging.logFile, "If non-empty, use this log file") + flagset.Uint64Var(&logging.logFileMaxSizeMB, "log_file_max_size", logging.logFileMaxSizeMB, "Defines the maximum size a log file can grow to. Unit is megabytes. "+ "If the value is 0, the maximum file size is unlimited.") - flagset.BoolVar(&logging.toStderr, "logtostderr", true, "log to standard error instead of files") - flagset.BoolVar(&logging.alsoToStderr, "alsologtostderr", false, "log to standard error as well as files") + flagset.BoolVar(&logging.toStderr, "logtostderr", logging.toStderr, "log to standard error instead of files") + flagset.BoolVar(&logging.alsoToStderr, "alsologtostderr", logging.alsoToStderr, "log to standard error as well as files") flagset.Var(&logging.verbosity, "v", "number for the log level verbosity") - flagset.BoolVar(&logging.skipHeaders, "skip_headers", false, "If true, avoid header prefixes in the log messages") - flagset.BoolVar(&logging.skipLogHeaders, "skip_log_headers", false, "If true, avoid headers when openning log files") + flagset.BoolVar(&logging.skipHeaders, "add_dir_header", logging.addDirHeader, "If true, adds the file directory to the header") + flagset.BoolVar(&logging.skipHeaders, "skip_headers", logging.skipHeaders, "If true, avoid header prefixes in the log messages") + flagset.BoolVar(&logging.skipLogHeaders, "skip_log_headers", logging.skipLogHeaders, "If true, avoid headers when opening log files") flagset.Var(&logging.stderrThreshold, "stderrthreshold", "logs at or above this threshold go to stderr") flagset.Var(&logging.vmodule, "vmodule", "comma-separated list of pattern=N settings for file-filtered logging") flagset.Var(&logging.traceLocation, "log_backtrace_at", "when logging hits line file:N, emit a stack trace") @@ -485,6 +494,9 @@ type loggingT struct { // If true, do not add the headers to log files skipLogHeaders bool + + // If true, add the file directory to the header + addDirHeader bool } // buffer holds a byte Buffer for reuse. The zero value is ready for use. @@ -570,9 +582,14 @@ func (l *loggingT) header(s severity, depth int) (*buffer, string, int) { file = "???" line = 1 } else { - slash := strings.LastIndex(file, "/") - if slash >= 0 { - file = file[slash+1:] + if slash := strings.LastIndex(file, "/"); slash >= 0 { + path := file + file = path[slash+1:] + if l.addDirHeader { + if dirsep := strings.LastIndex(path[:slash], "/"); dirsep >= 0 { + file = path[dirsep+1:] + } + } } } return l.formatHeader(s, file, line), file, line @@ -721,6 +738,8 @@ func (rb *redirectBuffer) Write(bytes []byte) (n int, err error) { // SetOutput sets the output destination for all severities func SetOutput(w io.Writer) { + logging.mu.Lock() + defer logging.mu.Unlock() for s := fatalLog; s >= infoLog; s-- { rb := &redirectBuffer{ w: w, @@ -731,6 +750,8 @@ func SetOutput(w io.Writer) { // SetOutputBySeverity sets the output destination for specific severity func SetOutputBySeverity(name string, w io.Writer) { + logging.mu.Lock() + defer logging.mu.Unlock() sev, ok := severityByName(name) if !ok { panic(fmt.Sprintf("SetOutputBySeverity(%q): unrecognized severity name", name)) @@ -756,24 +777,38 @@ func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoTo if alsoToStderr || l.alsoToStderr || s >= l.stderrThreshold.get() { os.Stderr.Write(data) } - if l.file[s] == nil { - if err := l.createFiles(s); err != nil { - os.Stderr.Write(data) // Make sure the message appears somewhere. - l.exit(err) + + if logging.logFile != "" { + // Since we are using a single log file, all of the items in l.file array + // will point to the same file, so just use one of them to write data. + if l.file[infoLog] == nil { + if err := l.createFiles(infoLog); err != nil { + os.Stderr.Write(data) // Make sure the message appears somewhere. + l.exit(err) + } } - } - switch s { - case fatalLog: - l.file[fatalLog].Write(data) - fallthrough - case errorLog: - l.file[errorLog].Write(data) - fallthrough - case warningLog: - l.file[warningLog].Write(data) - fallthrough - case infoLog: l.file[infoLog].Write(data) + } else { + if l.file[s] == nil { + if err := l.createFiles(s); err != nil { + os.Stderr.Write(data) // Make sure the message appears somewhere. + l.exit(err) + } + } + + switch s { + case fatalLog: + l.file[fatalLog].Write(data) + fallthrough + case errorLog: + l.file[errorLog].Write(data) + fallthrough + case warningLog: + l.file[warningLog].Write(data) + fallthrough + case infoLog: + l.file[infoLog].Write(data) + } } } if s == fatalLog { @@ -812,7 +847,7 @@ func (l *loggingT) output(s severity, buf *buffer, file string, line int, alsoTo // timeoutFlush calls Flush and returns when it completes or after timeout // elapses, whichever happens first. This is needed because the hooks invoked -// by Flush may deadlock when glog.Fatal is called from a hook that holds +// by Flush may deadlock when klog.Fatal is called from a hook that holds // a lock. func timeoutFlush(timeout time.Duration) { done := make(chan bool, 1) @@ -823,7 +858,7 @@ func timeoutFlush(timeout time.Duration) { select { case <-done: case <-time.After(timeout): - fmt.Fprintln(os.Stderr, "glog: Flush took longer than", timeout) + fmt.Fprintln(os.Stderr, "klog: Flush took longer than", timeout) } } @@ -1079,9 +1114,9 @@ type Verbose bool // The returned value is a boolean of type Verbose, which implements Info, Infoln // and Infof. These methods will write to the Info log if called. // Thus, one may write either -// if glog.V(2) { glog.Info("log this") } +// if klog.V(2) { klog.Info("log this") } // or -// glog.V(2).Info("log this") +// klog.V(2).Info("log this") // The second form is shorter but the first is cheaper if logging is off because it does // not evaluate its arguments. // @@ -1155,7 +1190,7 @@ func InfoDepth(depth int, args ...interface{}) { } // Infoln logs to the INFO log. -// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +// Arguments are handled in the manner of fmt.Println; a newline is always appended. func Infoln(args ...interface{}) { logging.println(infoLog, args...) } @@ -1179,7 +1214,7 @@ func WarningDepth(depth int, args ...interface{}) { } // Warningln logs to the WARNING and INFO logs. -// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +// Arguments are handled in the manner of fmt.Println; a newline is always appended. func Warningln(args ...interface{}) { logging.println(warningLog, args...) } @@ -1203,7 +1238,7 @@ func ErrorDepth(depth int, args ...interface{}) { } // Errorln logs to the ERROR, WARNING, and INFO logs. -// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +// Arguments are handled in the manner of fmt.Println; a newline is always appended. func Errorln(args ...interface{}) { logging.println(errorLog, args...) } @@ -1229,7 +1264,7 @@ func FatalDepth(depth int, args ...interface{}) { // Fatalln logs to the FATAL, ERROR, WARNING, and INFO logs, // including a stack trace of all running goroutines, then calls os.Exit(255). -// Arguments are handled in the manner of fmt.Println; a newline is appended if missing. +// Arguments are handled in the manner of fmt.Println; a newline is always appended. func Fatalln(args ...interface{}) { logging.println(fatalLog, args...) } diff --git a/vendor/modules.txt b/vendor/modules.txt index afc00d23a..c68d42198 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -68,13 +68,17 @@ github.com/hpcloud/tail/winfile github.com/imdario/mergo # github.com/json-iterator/go v1.1.6 github.com/json-iterator/go +# github.com/k8snetworkplumbingwg/network-attachment-definition-client v0.0.0-20191119172530-79f836b90111 +github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io +github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1 # github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd github.com/modern-go/concurrent # github.com/modern-go/reflect2 v1.0.1 github.com/modern-go/reflect2 -# github.com/onsi/ginkgo v1.8.0 +# github.com/onsi/ginkgo v1.10.1 github.com/onsi/ginkgo github.com/onsi/ginkgo/config +github.com/onsi/ginkgo/extensions/table github.com/onsi/ginkgo/internal/codelocation github.com/onsi/ginkgo/internal/containernode github.com/onsi/ginkgo/internal/failer @@ -91,7 +95,7 @@ github.com/onsi/ginkgo/reporters/stenographer github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty github.com/onsi/ginkgo/types -# github.com/onsi/gomega v1.5.0 +# github.com/onsi/gomega v1.7.0 github.com/onsi/gomega github.com/onsi/gomega/format github.com/onsi/gomega/gbytes @@ -108,7 +112,7 @@ github.com/onsi/gomega/matchers/support/goraph/util github.com/onsi/gomega/types # github.com/pkg/errors v0.8.1 github.com/pkg/errors -# github.com/spf13/pflag v1.0.3 +# github.com/spf13/pflag v1.0.5 github.com/spf13/pflag # go.uber.org/atomic v1.3.2 go.uber.org/atomic @@ -135,7 +139,7 @@ golang.org/x/net/http2/hpack golang.org/x/net/idna golang.org/x/net/internal/timeseries golang.org/x/net/trace -# golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be +# golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 golang.org/x/oauth2 golang.org/x/oauth2/internal # golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f @@ -163,7 +167,7 @@ golang.org/x/text/secure/bidirule golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm -# golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 +# golang.org/x/time v0.0.0-20181108054448-85acf8d2951c golang.org/x/time/rate # gomodules.xyz/jsonpatch/v2 v2.0.1 gomodules.xyz/jsonpatch/v2 @@ -218,7 +222,7 @@ gopkg.in/fsnotify.v1 gopkg.in/inf.v0 # gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 gopkg.in/tomb.v1 -# gopkg.in/yaml.v2 v2.2.2 +# gopkg.in/yaml.v2 v2.2.3 gopkg.in/yaml.v2 # k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b k8s.io/api/admissionregistration/v1beta1 @@ -364,7 +368,7 @@ k8s.io/client-go/util/connrotation k8s.io/client-go/util/flowcontrol k8s.io/client-go/util/homedir k8s.io/client-go/util/keyutil -# k8s.io/klog v0.3.0 +# k8s.io/klog v1.0.0 k8s.io/klog # k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 k8s.io/utils/integer From 8fc04e458af3b9d02d8f37f034c45250d8af6b94 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Fri, 16 Jul 2021 15:21:32 +0200 Subject: [PATCH 11/16] ip-reconciler: also account for the pods IP addresses Only mark a pod as "alive" when the pod's annotations feature the IP being de-allocated. This makes the reconciler binary *dependent* on multus, which adds these `network-status` annotations into the pod. Signed-off-by: Miguel Duarte Barroso --- cmd/reconciler/ip_test.go | 66 +++++++++--- pkg/reconciler/iploop.go | 57 +++++------ pkg/reconciler/iploop_test.go | 8 +- pkg/reconciler/wrappedPod.go | 62 ++++++++++++ pkg/reconciler/wrappedPod_test.go | 163 ++++++++++++++++++++++++++++++ 5 files changed, 311 insertions(+), 45 deletions(-) create mode 100644 pkg/reconciler/wrappedPod.go create mode 100644 pkg/reconciler/wrappedPod_test.go diff --git a/cmd/reconciler/ip_test.go b/cmd/reconciler/ip_test.go index 05a3a31d7..3983f30ca 100644 --- a/cmd/reconciler/ip_test.go +++ b/cmd/reconciler/ip_test.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "fmt" "net" "strings" @@ -12,6 +13,7 @@ import ( "github.com/dougbtv/whereabouts/pkg/api/v1alpha1" "github.com/dougbtv/whereabouts/pkg/reconciler" "github.com/dougbtv/whereabouts/pkg/types" + multusv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -20,10 +22,11 @@ import ( var _ = Describe("Whereabouts IP reconciler", func() { const ( - ipRange = "10.10.10.0/16" - namespace = "testns" - networkName = "net1" - podName = "pod1" + firstIPInRange = "10.10.10.1" + ipRange = "10.10.10.0/16" + namespace = "testns" + networkName = "net1" + podName = "pod1" ) var ( @@ -35,7 +38,8 @@ var _ = Describe("Whereabouts IP reconciler", func() { BeforeEach(func() { var err error - pod, err = k8sClientSet.CoreV1().Pods(namespace).Create(generatePod(namespace, podName, networkName)) + pod, err = k8sClientSet.CoreV1().Pods(namespace).Create( + generatePod(namespace, podName, ipInNetwork{ip: firstIPInRange, networkName: networkName})) Expect(err).NotTo(HaveOccurred()) }) @@ -88,16 +92,21 @@ var _ = Describe("Whereabouts IP reconciler", func() { Context("multiple pods", func() { const ( - deadPodIndex = 0 - livePodIndex = 1 - numberOfPods = 2 + deadPodIndex = 0 + livePodIndex = 1 + numberOfPods = 2 + secondIPInRange = "10.10.10.2" ) var pods []v1.Pod BeforeEach(func() { + ips := []string{firstIPInRange, secondIPInRange} for i := 0; i < numberOfPods; i++ { - pod := generatePod(namespace, fmt.Sprintf("pod%d", i+1), networkName) + pod := generatePod(namespace, fmt.Sprintf("pod%d", i+1), ipInNetwork{ + ip: ips[i], + networkName: networkName, + }) if i == livePodIndex { _, err := k8sClientSet.CoreV1().Pods(namespace).Create(pod) Expect(err).NotTo(HaveOccurred()) @@ -183,13 +192,17 @@ func generateIPPoolSpec(ipRange string, namespace string, poolName string, podNa } } -func generatePod(namespace string, podName string, networks ...string) *v1.Pod { - networkAnnotations := map[string]string{"k8s.v1.cni.cncf.io/networks": strings.Join(networks, ",")} +type ipInNetwork struct { + ip string + networkName string +} + +func generatePod(namespace string, podName string, ipNetworks ...ipInNetwork) *v1.Pod { return &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: podName, Namespace: namespace, - Annotations: networkAnnotations, + Annotations: generatePodAnnotations(ipNetworks...), }, Spec: v1.PodSpec{ Containers: []v1.Container{ @@ -202,3 +215,32 @@ func generatePod(namespace string, podName string, networks ...string) *v1.Pod { }, } } + +func generatePodAnnotations(ipNetworks ...ipInNetwork) map[string]string { + var networks []string + for _, ipNetworkInfo := range ipNetworks { + networks = append(networks, ipNetworkInfo.networkName) + } + networkAnnotations := map[string]string{ + reconciler.MultusNetworkAnnotation: strings.Join(networks, ","), + reconciler.MultusNetworkStatusAnnotation: generatePodNetworkStatusAnnotation(ipNetworks...), + } + return networkAnnotations +} + +func generatePodNetworkStatusAnnotation(ipNetworks ...ipInNetwork) string { + var networkStatus []multusv1.NetworkStatus + for i, ipNetworkInfo := range ipNetworks { + networkStatus = append(networkStatus, multusv1.NetworkStatus{ + Name: ipNetworkInfo.networkName, + Interface: fmt.Sprintf("net%d", i+1), + IPs: []string{ipNetworkInfo.ip}, + }) + } + networkStatusStr, err := json.Marshal(networkStatus) + if err != nil { + return "" + } + + return string(networkStatusStr) +} diff --git a/pkg/reconciler/iploop.go b/pkg/reconciler/iploop.go index d729e7717..2c3ae5483 100644 --- a/pkg/reconciler/iploop.go +++ b/pkg/reconciler/iploop.go @@ -9,12 +9,13 @@ import ( "github.com/dougbtv/whereabouts/pkg/storage" "github.com/dougbtv/whereabouts/pkg/storage/kubernetes" "github.com/dougbtv/whereabouts/pkg/types" + v1 "k8s.io/api/core/v1" ) type ReconcileLooper struct { ctx context.Context k8sClient kubernetes.Client - livePodRefs []string + livePods map[string]podWrapper orphanedIPs []OrphanedIPReservations } @@ -31,15 +32,15 @@ func NewReconcileLooper(kubeConfigPath string, ctx context.Context) (*ReconcileL } logging.Debugf("successfully read the kubernetes configuration file located at: %s", kubeConfigPath) - podRefs, err := getPodRefs(*k8sClient) + pods, err := k8sClient.ListPods() if err != nil { return nil, err } looper := &ReconcileLooper{ - ctx: ctx, - k8sClient: *k8sClient, - livePodRefs: podRefs, + ctx: ctx, + k8sClient: *k8sClient, + livePods: indexPods(pods), } if err := looper.findOrphanedIPsPerPool(); err != nil { @@ -48,19 +49,6 @@ func NewReconcileLooper(kubeConfigPath string, ctx context.Context) (*ReconcileL return looper, nil } -func getPodRefs(k8sClient kubernetes.Client) ([]string, error) { - pods, err := k8sClient.ListPods() - if err != nil { - return nil, err - } - - var podRefs []string - for _, pod := range pods { - podRefs = append(podRefs, fmt.Sprintf("%s/%s", pod.GetNamespace(), pod.GetName())) - } - return podRefs, err -} - func (rl *ReconcileLooper) findOrphanedIPsPerPool() error { ipPools, err := rl.k8sClient.ListIPPools(rl.ctx) if err != nil { @@ -71,15 +59,15 @@ func (rl *ReconcileLooper) findOrphanedIPsPerPool() error { orphanIP := OrphanedIPReservations{ Pool: pool, } - for _, allocation := range pool.Allocations() { - logging.Debugf("the IP reservation: %s", allocation) - if allocation.PodRef == "" { - _ = logging.Errorf("pod ref missing for Allocations: %s", allocation) + for _, ipReservation := range pool.Allocations() { + logging.Debugf("the IP reservation: %s", ipReservation) + if ipReservation.PodRef == "" { + _ = logging.Errorf("pod ref missing for Allocations: %s", ipReservation) continue } - if !rl.isPodAlive(allocation.PodRef) { - logging.Debugf("pod ref %s is not listed in the live pods list", allocation.PodRef) - orphanIP.Allocations = append(orphanIP.Allocations, allocation) + if !rl.isPodAlive(ipReservation) { + logging.Debugf("pod ref %s is not listed in the live pods list", ipReservation.PodRef) + orphanIP.Allocations = append(orphanIP.Allocations, ipReservation) } } if len(orphanIP.Allocations) > 0 { @@ -90,15 +78,26 @@ func (rl *ReconcileLooper) findOrphanedIPsPerPool() error { return nil } -func (rl ReconcileLooper) isPodAlive(podRef string) bool { - for _, livePodRef := range rl.livePodRefs { - if podRef == livePodRef { - return true +func (rl ReconcileLooper) isPodAlive(allocation types.IPReservation) bool { + for livePodRef, livePod := range rl.livePods { + if allocation.PodRef == livePodRef { + livePodIPs := livePod.ips + logging.Debugf( + "pod reference %s matches allocation; Allocation IP: %s; PodIPs: %s", + livePodRef, + allocation.IP.String(), + livePodIPs) + _, isFound := livePodIPs[allocation.IP.String()] + return isFound } } return false } +func composePodRef(pod v1.Pod) string { + return fmt.Sprintf("%s/%s", pod.GetNamespace(), pod.GetName()) +} + func (rl ReconcileLooper) ReconcileIPPools() ([]types.IPReservation, error) { matchByPodRef := func(reservations []types.IPReservation, podRef string) int { foundidx := -1 diff --git a/pkg/reconciler/iploop_test.go b/pkg/reconciler/iploop_test.go index f0d7f9f14..a60d4ac81 100644 --- a/pkg/reconciler/iploop_test.go +++ b/pkg/reconciler/iploop_test.go @@ -37,8 +37,8 @@ var _ = Describe("IPReconciler", func() { newIPReconciler := func(orphanedIPs ...OrphanedIPReservations) *ReconcileLooper { reconciler := &ReconcileLooper{ - cancelFunc: func() {}, - ctx: context.TODO(), + cancelFunc: func() {}, + ctx: context.TODO(), orphanedIPs: orphanedIPs, } @@ -143,7 +143,7 @@ func generateIPPool(cidr string, podRefs ...string) whereaboutsv1alpha1.IPPool { return whereaboutsv1alpha1.IPPool{ Spec: whereaboutsv1alpha1.IPPoolSpec{ - Range: cidr, + Range: cidr, Allocations: allocations, }, } @@ -160,4 +160,4 @@ func generateIPReservation(ip string, podRef string) []types.IPReservation { func generatePodRef(namespace, podName string) string { return fmt.Sprintf("%s/%s", namespace, podName) -} \ No newline at end of file +} diff --git a/pkg/reconciler/wrappedPod.go b/pkg/reconciler/wrappedPod.go new file mode 100644 index 000000000..82845e9e1 --- /dev/null +++ b/pkg/reconciler/wrappedPod.go @@ -0,0 +1,62 @@ +package reconciler + +import ( + "encoding/json" + + "github.com/dougbtv/whereabouts/pkg/logging" + k8snetworkplumbingwgv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + + v1 "k8s.io/api/core/v1" +) + +const ( + multusInterfaceNamePrefix = "net" + multusPrefixSize = len(multusInterfaceNamePrefix) + MultusNetworkAnnotation = "k8s.v1.cni.cncf.io/networks" + MultusNetworkStatusAnnotation = "k8s.v1.cni.cncf.io/networks-status" +) + +type podWrapper struct { + ips map[string]void +} + +type void struct{} + +func wrapPod(pod v1.Pod) *podWrapper { + return &podWrapper{ + ips: getFlatIPSet(pod), + } +} + +func indexPods(podList []v1.Pod) map[string]podWrapper { + podMap := map[string]podWrapper{} + + for _, pod := range podList { + wrappedPod := wrapPod(pod) + if wrappedPod != nil { + podMap[composePodRef(pod)] = *wrappedPod + } + } + return podMap +} + +func getFlatIPSet(pod v1.Pod) map[string]void { + var empty void + ipSet := map[string]void{} + networkStatusAnnotationValue := []byte(pod.Annotations[MultusNetworkStatusAnnotation]) + var networkStatusList []k8snetworkplumbingwgv1.NetworkStatus + if err := json.Unmarshal(networkStatusAnnotationValue, &networkStatusList); err != nil { + _ = logging.Errorf("could not parse network annotation %s for pod: %s; error: %v", networkStatusAnnotationValue, composePodRef(pod), err) + return ipSet + } + + for _, network := range networkStatusList { + // we're only after multus secondary interfaces + if network.Interface[:multusPrefixSize] == multusInterfaceNamePrefix { + for _, ip := range network.IPs { + ipSet[ip] = empty + } + } + } + return ipSet +} diff --git a/pkg/reconciler/wrappedPod_test.go b/pkg/reconciler/wrappedPod_test.go new file mode 100644 index 000000000..a34a5d5c5 --- /dev/null +++ b/pkg/reconciler/wrappedPod_test.go @@ -0,0 +1,163 @@ +package reconciler + +import ( + "encoding/json" + "fmt" + + . "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + + k8snetworkplumbingwgv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe("Pod Wrapper operations", func() { + generateMultusNetworkStatus := func(ifaceName string, networkName string, ip string) k8snetworkplumbingwgv1.NetworkStatus { + return k8snetworkplumbingwgv1.NetworkStatus{ + Name: networkName, + Interface: ifaceName, + IPs: []string{ip}, + } + } + + generateMultusNetworkStatusList := func(ips ...string) []k8snetworkplumbingwgv1.NetworkStatus { + var networkStatus []k8snetworkplumbingwgv1.NetworkStatus + for i, ip := range ips { + networkStatus = append( + networkStatus, + generateMultusNetworkStatus( + fmt.Sprintf("network-%d", i), + fmt.Sprintf("net%d", i), + ip)) + } + return networkStatus + } + + generateMultusNetworkStatusAnnotationFromIPs := func(ips ...string) map[string]string { + annotationString, err := json.Marshal( + generateMultusNetworkStatusList(ips...)) + if err != nil { + annotationString = []byte("") + } + return map[string]string{ + MultusNetworkStatusAnnotation: string(annotationString), + } + } + + generateMultusNetworkStatusAnnotationFromNetworkStatus := func(networkStatus ...k8snetworkplumbingwgv1.NetworkStatus) map[string]string { + annotationString, err := json.Marshal(networkStatus) + if err != nil { + annotationString = []byte("") + } + return map[string]string{ + MultusNetworkStatusAnnotation: string(annotationString), + } + } + + generatePodSpec := func(ips ...string) v1.Pod { + return v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: generateMultusNetworkStatusAnnotationFromIPs(ips...), + }, + } + } + + generatePodSpecWithNameAndNamespace := func(name string, namespace string, ips ...string) v1.Pod { + return v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: generateMultusNetworkStatusAnnotationFromIPs(ips...), + Name: name, + Namespace: namespace, + }, + } + } + + Context("the wrap pod operation", func() { + table.DescribeTable("should extract the IPs from the network status annotations", func(ips ...string) { + expectedIPs := map[string]void{} + for _, ip := range ips { + expectedIPs[ip] = void{} + } + + pod := generatePodSpec(ips...) + Expect(wrapPod(pod).ips).To(Equal(expectedIPs)) + }, + table.Entry("when the annotation does not feature multus networks"), + table.Entry("when the annotation has a multus networks", "192.168.14.14"), + table.Entry("when the annotation has multiple multus networks", "192.168.14.14", "10.10.10.10"), + ) + + It("should filter out non-multus annotations", func() { + secondaryIfacesNetworkStatuses := generateMultusNetworkStatusList("192.168.14.14", "10.10.10.10") + + networkStatus := append( + secondaryIfacesNetworkStatuses, + generateMultusNetworkStatus("eth0", "network33", "14.15.16.20")) + pod := v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: generateMultusNetworkStatusAnnotationFromNetworkStatus(networkStatus...), + }, + } + + podSecondaryIPs := wrapPod(pod).ips + Expect(podSecondaryIPs).To(HaveLen(2)) + Expect(podSecondaryIPs).To(Equal(map[string]void{"192.168.14.14": {}, "10.10.10.10": {}})) + }) + + It("return an empty list when the network annotations of a pod are invalid", func() { + pod := v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{MultusNetworkStatusAnnotation: "this-wont-fly"}, + }, + } + Expect(wrapPod(pod).ips).To(BeEmpty()) + }) + }) + + Context("the index pods operation", func() { + type podInfo struct { + ips []string + name string + namespace string + } + + table.DescribeTable("", func(podsInfo ...podInfo) { + var pods []v1.Pod + for _, info := range podsInfo { + pods = append( + pods, + generatePodSpecWithNameAndNamespace(info.name, info.namespace, info.ips...)) + } + expectedPodWrapper := map[string]podWrapper{} + for _, info := range podsInfo { + indexedPodIPs := map[string]void{} + for _, ip := range info.ips { + indexedPodIPs[ip] = void{} + } + expectedPodWrapper[fmt.Sprintf("%s/%s", info.namespace, info.name)] = podWrapper{ips: indexedPodIPs} + } + + Expect(indexPods(pods)).To(Equal(expectedPodWrapper)) + }, + table.Entry("when no pods are passed"), + table.Entry("when a pod is passed", podInfo{ + ips: []string{"10.10.10.10"}, + name: "pod1", + namespace: "default", + }), + table.Entry("when multiple pods are passed", + podInfo{ + ips: []string{"10.10.10.10"}, + name: "pod1", + namespace: "default", + }, + podInfo{ + ips: []string{"192.168.14.14", "200.200.200.200s"}, + name: "pod200", + namespace: "secretns", + })) + }) +}) From 5ed98e5ab90cb03454b82f6ecc45ee38b9d7ed3b Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Mon, 19 Jul 2021 14:58:42 +0200 Subject: [PATCH 12/16] ip-reconciler, docs: mention the reconciler in the README Signed-off-by: Miguel Duarte Barroso --- README.md | 6 +++++- doc/extended-configuration.md | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 169d5d732..8bad58dbf 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,11 @@ You can install this plugin with a Daemonset, using: ``` git clone https://github.com/dougbtv/whereabouts && cd whereabouts -kubectl apply -f ./doc/daemonset-install.yaml -f ./doc/whereabouts.cni.cncf.io_ippools.yaml -f ./doc/whereabouts.cni.cncf.io_overlappingrangeipreservations.yaml +kubectl apply \ + -f ./doc/daemonset-install.yaml \ + -f ./doc/whereabouts.cni.cncf.io_ippools.yaml \ + -f ./doc/whereabouts.cni.cncf.io_overlappingrangeipreservations.yaml \ + -f doc/ip-reconciler-job.yaml ``` The daemonset installation requires Kubernetes Version 1.16 or later. diff --git a/doc/extended-configuration.md b/doc/extended-configuration.md index 13cc05bd9..7f20b631a 100644 --- a/doc/extended-configuration.md +++ b/doc/extended-configuration.md @@ -2,6 +2,19 @@ Should you need to further configure Whereabouts, you might find these options valuable. +## IP Reconciliation + +Whereabouts includes a tool which is intended be run as a k8s `CronJob`. This +utility scans the currently allocated IP addresses, and reconciles them against +the currently running pods, and deallocates IP addresses which have been left +stranded. +Stranded IP addresses can occur due to node failures (e.g. a sudden power off / +reboot event) or potentially from pods that have been force deleted +(e.g. `kubectl delete pod foo --grace-period=0 --force`) + +A reference deployment of this tool is available in the +`/docs/ip-reconcilier-job.yaml` file. + ## Installation options The daemonset installation as shown on the README is for use with Kubernetes version 1.16 and later. It may also be useful with previous versions, however you'll need to change the `apiVersion` of the daemonset in the provided yaml, [see the deprecation notice](https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/). From 66b75ad61a312b42d9c4044ab2e27a5433c519f5 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Tue, 20 Jul 2021 11:27:13 +0200 Subject: [PATCH 13/16] ip-reconciler: simplify ip looper code By not returning an `IPReservation` entry - we now return an IP address instead - we simplify the code. Signed-off-by: Miguel Duarte Barroso --- cmd/reconciler/ip_test.go | 14 ++------------ pkg/reconciler/iploop.go | 25 ++++++------------------- pkg/reconciler/iploop_test.go | 8 ++------ 3 files changed, 10 insertions(+), 37 deletions(-) diff --git a/cmd/reconciler/ip_test.go b/cmd/reconciler/ip_test.go index 3983f30ca..dae463135 100644 --- a/cmd/reconciler/ip_test.go +++ b/cmd/reconciler/ip_test.go @@ -12,7 +12,6 @@ import ( "github.com/dougbtv/whereabouts/pkg/api/v1alpha1" "github.com/dougbtv/whereabouts/pkg/reconciler" - "github.com/dougbtv/whereabouts/pkg/types" multusv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" v1 "k8s.io/api/core/v1" @@ -70,11 +69,7 @@ var _ = Describe("Whereabouts IP reconciler", func() { }) It("should report the deleted IP reservation", func() { - expectedIPReservation := types.IPReservation{ - IP: net.ParseIP("10.10.10.1"), - PodRef: fmt.Sprintf("%s/%s", namespace, podName), - } - Expect(reconcileLooper.ReconcileIPPools()).To(ConsistOf(expectedIPReservation)) + Expect(reconcileLooper.ReconcileIPPools()).To(Equal([]net.IP{net.ParseIP("10.10.10.1")})) }) It("the pool's orphaned IP should be deleted after the reconcile loop", func() { @@ -92,7 +87,6 @@ var _ = Describe("Whereabouts IP reconciler", func() { Context("multiple pods", func() { const ( - deadPodIndex = 0 livePodIndex = 1 numberOfPods = 2 secondIPInRange = "10.10.10.2" @@ -146,13 +140,9 @@ var _ = Describe("Whereabouts IP reconciler", func() { }) It("should report the dead pod's IP address as deleted", func() { - expectedReservation := types.IPReservation{ - IP: net.ParseIP("10.10.10.1"), - PodRef: fmt.Sprintf("%s/%s", namespace, pods[deadPodIndex].Name), - } deletedIPAddrs, err := reconcileLooper.ReconcileIPPools() Expect(err).NotTo(HaveOccurred()) - Expect(deletedIPAddrs).To(ConsistOf(expectedReservation)) + Expect(deletedIPAddrs).To(Equal([]net.IP{net.ParseIP("10.10.10.1")})) }) It("the IPPool should have only the IP reservation of the live pod", func() { diff --git a/pkg/reconciler/iploop.go b/pkg/reconciler/iploop.go index 2c3ae5483..a35193abd 100644 --- a/pkg/reconciler/iploop.go +++ b/pkg/reconciler/iploop.go @@ -3,6 +3,7 @@ package reconciler import ( "context" "fmt" + "net" "github.com/dougbtv/whereabouts/pkg/allocate" "github.com/dougbtv/whereabouts/pkg/logging" @@ -98,7 +99,7 @@ func composePodRef(pod v1.Pod) string { return fmt.Sprintf("%s/%s", pod.GetNamespace(), pod.GetName()) } -func (rl ReconcileLooper) ReconcileIPPools() ([]types.IPReservation, error) { +func (rl ReconcileLooper) ReconcileIPPools() ([]net.IP, error) { matchByPodRef := func(reservations []types.IPReservation, podRef string) int { foundidx := -1 for idx, v := range reservations { @@ -110,13 +111,13 @@ func (rl ReconcileLooper) ReconcileIPPools() ([]types.IPReservation, error) { } var err error - var totalCleanedUpIps []types.IPReservation + var totalCleanedUpIps []net.IP for _, orphanedIP := range rl.orphanedIPs { - originalIPReservations := orphanedIP.Pool.Allocations() currentIPReservations := orphanedIP.Pool.Allocations() podRefsToDeallocate := findOutPodRefsToDeallocateIPsFrom(orphanedIP) + var deallocatedIP net.IP for _, podRef := range podRefsToDeallocate { - currentIPReservations, _, err = allocate.IterateForDeallocation(currentIPReservations, podRef, matchByPodRef) + currentIPReservations, deallocatedIP, err = allocate.IterateForDeallocation(currentIPReservations, podRef, matchByPodRef) if err != nil { return nil, err } @@ -126,26 +127,12 @@ func (rl ReconcileLooper) ReconcileIPPools() ([]types.IPReservation, error) { if err := orphanedIP.Pool.Update(rl.ctx, currentIPReservations); err != nil { return nil, logging.Errorf("failed to update the reservation list: %v", err) } - totalCleanedUpIps = append(totalCleanedUpIps, computeCleanedUpIPs(originalIPReservations, currentIPReservations)...) + totalCleanedUpIps = append(totalCleanedUpIps, deallocatedIP) } return totalCleanedUpIps, nil } -func computeCleanedUpIPs(oldIPReservations []types.IPReservation, newIPReservations []types.IPReservation) []types.IPReservation { - var deletedReservations []types.IPReservation - ledger := make(map[string]bool) - for _, reservation := range newIPReservations { - ledger[reservation.IP.String()] = true - } - for _, reservation := range oldIPReservations { - if _, found := ledger[reservation.IP.String()]; !found { - deletedReservations = append(deletedReservations, reservation) - } - } - return deletedReservations -} - func findOutPodRefsToDeallocateIPsFrom(orphanedIP OrphanedIPReservations) []string { var podRefsToDeallocate []string for _, orphanedAllocation := range orphanedIP.Allocations { diff --git a/pkg/reconciler/iploop_test.go b/pkg/reconciler/iploop_test.go index a60d4ac81..26ea932a6 100644 --- a/pkg/reconciler/iploop_test.go +++ b/pkg/reconciler/iploop_test.go @@ -81,9 +81,7 @@ var _ = Describe("IPReconciler", func() { It("does delete the orphaned IP address", func() { reconciledIPs, err := ipReconciler.ReconcileIPPools() Expect(err).NotTo(HaveOccurred()) - - reapedIPAddr := types.IPReservation{IP: net.ParseIP(firstIPInRange), PodRef: generatePodRef(namespace, podName)} - Expect(reconciledIPs).To(ConsistOf(reapedIPAddr)) + Expect(reconciledIPs).To(Equal([]net.IP{net.ParseIP(firstIPInRange)})) }) Context("and they are actually multiple IPs", func() { @@ -103,9 +101,7 @@ var _ = Describe("IPReconciler", func() { It("does delete *only the orphaned* the IP address", func() { reconciledIPs, err := ipReconciler.ReconcileIPPools() Expect(err).NotTo(HaveOccurred()) - - reapedIPAddr := types.IPReservation{IP: net.ParseIP("192.168.14.2"), PodRef: generatePodRef(namespace, "pod2")} - Expect(reconciledIPs).To(ConsistOf(reapedIPAddr)) + Expect(reconciledIPs).To(ConsistOf([]net.IP{net.ParseIP("192.168.14.2")})) }) }) From 0154617a833e669c3bcd1932d97c6fa6a2cd1ad9 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Tue, 20 Jul 2021 15:09:40 +0200 Subject: [PATCH 14/16] reconcile cluster wide IPs Signed-off-by: Miguel Duarte Barroso --- cmd/reconciler/errors.go | 3 +- cmd/reconciler/ip.go | 6 +++- pkg/reconciler/iploop.go | 61 +++++++++++++++++++++++++++----- pkg/reconciler/iploop_test.go | 1 - pkg/storage/kubernetes/client.go | 17 +++++++++ 5 files changed, 76 insertions(+), 12 deletions(-) diff --git a/cmd/reconciler/errors.go b/cmd/reconciler/errors.go index 7acd86821..1046e6519 100644 --- a/cmd/reconciler/errors.go +++ b/cmd/reconciler/errors.go @@ -3,5 +3,6 @@ package main const ( kubeconfigNotFound = iota + 1 couldNotStartOrphanedIPMonitor - failedToReconcile + failedToReconcileIPPools + failedToReconcileClusterWideIPs ) diff --git a/cmd/reconciler/ip.go b/cmd/reconciler/ip.go index 6cac19f94..580412918 100644 --- a/cmd/reconciler/ip.go +++ b/cmd/reconciler/ip.go @@ -30,11 +30,15 @@ func main() { cleanedUpIps, err := ipReconcileLoop.ReconcileIPPools() if err != nil { _ = logging.Errorf("failed to clean up IP for allocations: %v", err) - os.Exit(failedToReconcile) + os.Exit(failedToReconcileIPPools) } if len(cleanedUpIps) > 0 { logging.Debugf("successfully cleanup IPs: %+v", cleanedUpIps) } else { logging.Debugf("no IP addresses to cleanup") } + + if err := ipReconcileLoop.ReconcileOverlappingIPAddresses(); err != nil { + os.Exit(failedToReconcileClusterWideIPs) + } } diff --git a/pkg/reconciler/iploop.go b/pkg/reconciler/iploop.go index a35193abd..98af00b27 100644 --- a/pkg/reconciler/iploop.go +++ b/pkg/reconciler/iploop.go @@ -6,18 +6,21 @@ import ( "net" "github.com/dougbtv/whereabouts/pkg/allocate" + whereaboutsv1alpha1 "github.com/dougbtv/whereabouts/pkg/api/v1alpha1" "github.com/dougbtv/whereabouts/pkg/logging" "github.com/dougbtv/whereabouts/pkg/storage" "github.com/dougbtv/whereabouts/pkg/storage/kubernetes" "github.com/dougbtv/whereabouts/pkg/types" + v1 "k8s.io/api/core/v1" ) type ReconcileLooper struct { - ctx context.Context - k8sClient kubernetes.Client - livePods map[string]podWrapper - orphanedIPs []OrphanedIPReservations + ctx context.Context + k8sClient kubernetes.Client + livePods map[string]podWrapper + orphanedIPs []OrphanedIPReservations + orphanedClusterWideIPs []whereaboutsv1alpha1.OverlappingRangeIPReservation } type OrphanedIPReservations struct { @@ -47,6 +50,10 @@ func NewReconcileLooper(kubeConfigPath string, ctx context.Context) (*ReconcileL if err := looper.findOrphanedIPsPerPool(); err != nil { return nil, err } + + if err := looper.findClusterWideIPReservations(); err != nil { + return nil, err + } return looper, nil } @@ -66,7 +73,7 @@ func (rl *ReconcileLooper) findOrphanedIPsPerPool() error { _ = logging.Errorf("pod ref missing for Allocations: %s", ipReservation) continue } - if !rl.isPodAlive(ipReservation) { + if !rl.isPodAlive(ipReservation.PodRef, ipReservation.IP.String()) { logging.Debugf("pod ref %s is not listed in the live pods list", ipReservation.PodRef) orphanIP.Allocations = append(orphanIP.Allocations, ipReservation) } @@ -79,16 +86,16 @@ func (rl *ReconcileLooper) findOrphanedIPsPerPool() error { return nil } -func (rl ReconcileLooper) isPodAlive(allocation types.IPReservation) bool { +func (rl ReconcileLooper) isPodAlive(podRef string, ip string) bool { for livePodRef, livePod := range rl.livePods { - if allocation.PodRef == livePodRef { + if podRef == livePodRef { livePodIPs := livePod.ips logging.Debugf( "pod reference %s matches allocation; Allocation IP: %s; PodIPs: %s", livePodRef, - allocation.IP.String(), + ip, livePodIPs) - _, isFound := livePodIPs[allocation.IP.String()] + _, isFound := livePodIPs[ip] return isFound } } @@ -133,6 +140,42 @@ func (rl ReconcileLooper) ReconcileIPPools() ([]net.IP, error) { return totalCleanedUpIps, nil } +func (rl *ReconcileLooper) findClusterWideIPReservations() error { + clusterWideIPReservations, err := rl.k8sClient.ListOverlappingIPs(rl.ctx) + if err != nil { + return logging.Errorf("failed to list all OverLappingIPs: %v", err) + } + + for _, clusterWideIPReservation := range clusterWideIPReservations { + ip := clusterWideIPReservation.GetName() + podRef := clusterWideIPReservation.Spec.PodRef + + if !rl.isPodAlive(podRef, ip) { + logging.Debugf("pod ref %s is not listed in the live pods list", podRef) + rl.orphanedClusterWideIPs = append(rl.orphanedClusterWideIPs, clusterWideIPReservation) + } + } + + return nil +} + +func (rl ReconcileLooper) ReconcileOverlappingIPAddresses() error { + var failedReconciledClusterWideIPs []string + for _, overlappingIPStruct := range rl.orphanedClusterWideIPs { + if err := rl.k8sClient.DeleteOverlappingIP(rl.ctx, &overlappingIPStruct); err != nil { + logging.Errorf("failed to remove cluster wide IP: %s", overlappingIPStruct.GetName()) + failedReconciledClusterWideIPs = append(failedReconciledClusterWideIPs, overlappingIPStruct.GetName()) + continue + } + logging.Debugf("removed stale overlappingIP allocation [%s]", overlappingIPStruct.GetName()) + } + + if len(failedReconciledClusterWideIPs) != 0 { + return logging.Errorf("could not reconcile cluster wide IPs: %v", failedReconciledClusterWideIPs) + } + return nil +} + func findOutPodRefsToDeallocateIPsFrom(orphanedIP OrphanedIPReservations) []string { var podRefsToDeallocate []string for _, orphanedAllocation := range orphanedIP.Allocations { diff --git a/pkg/reconciler/iploop_test.go b/pkg/reconciler/iploop_test.go index 26ea932a6..9bab9fe0b 100644 --- a/pkg/reconciler/iploop_test.go +++ b/pkg/reconciler/iploop_test.go @@ -37,7 +37,6 @@ var _ = Describe("IPReconciler", func() { newIPReconciler := func(orphanedIPs ...OrphanedIPReservations) *ReconcileLooper { reconciler := &ReconcileLooper{ - cancelFunc: func() {}, ctx: context.TODO(), orphanedIPs: orphanedIPs, } diff --git a/pkg/storage/kubernetes/client.go b/pkg/storage/kubernetes/client.go index 96fad7164..f7962e5aa 100644 --- a/pkg/storage/kubernetes/client.go +++ b/pkg/storage/kubernetes/client.go @@ -91,3 +91,20 @@ func (i *Client) ListPods() ([]v1.Pod, error) { } return podEntries, nil } + +func (i *Client) ListOverlappingIPs(ctx context.Context) ([]whereaboutsv1alpha1.OverlappingRangeIPReservation, error) { + overlappingIPsList := whereaboutsv1alpha1.OverlappingRangeIPReservationList{} + if err := i.client.List(ctx, &overlappingIPsList, &client.ListOptions{}); err != nil { + return nil, err + } + + var clusterWiderReservations []whereaboutsv1alpha1.OverlappingRangeIPReservation + for _, reservationInfo := range overlappingIPsList.Items { + clusterWiderReservations = append(clusterWiderReservations, reservationInfo) + } + return clusterWiderReservations, nil +} + +func (i *Client) DeleteOverlappingIP(ctx context.Context, clusterWideIP *whereaboutsv1alpha1.OverlappingRangeIPReservation) error { + return i.client.Delete(ctx, clusterWideIP) +} From 5f160d8c81256ab7ae1da49f3514c1d77303d3e5 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Wed, 21 Jul 2021 17:14:40 +0200 Subject: [PATCH 15/16] e2e test overlapping IPs Signed-off-by: Miguel Duarte Barroso --- cmd/reconciler/ip_test.go | 99 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/cmd/reconciler/ip_test.go b/cmd/reconciler/ip_test.go index dae463135..59bba3af7 100644 --- a/cmd/reconciler/ip_test.go +++ b/cmd/reconciler/ip_test.go @@ -32,7 +32,7 @@ var _ = Describe("Whereabouts IP reconciler", func() { reconcileLooper *reconciler.ReconcileLooper ) - Context("a single running pod", func() { + Context("reconciling IP pools with a single running pod", func() { var pod *v1.Pod BeforeEach(func() { @@ -85,7 +85,7 @@ var _ = Describe("Whereabouts IP reconciler", func() { }) }) - Context("multiple pods", func() { + Context("reconciling an IP pool with multiple pods attached", func() { const ( livePodIndex = 1 numberOfPods = 2 @@ -164,6 +164,92 @@ var _ = Describe("Whereabouts IP reconciler", func() { }) }) }) + + Context("reconciling cluster wide IPs - overlapping IPs", func() { + const ( + numberOfPods = 3 + firstNetworkName = "network1" + firstNetworkRange = "10.10.10.0/16" + firstPoolName = "pool1" + podIndexToRemove = 0 + secondIPInRange = "10.10.10.2" + secondNetworkName = "network2" + secondNetworkRange = "10.10.10.0/24" // overlaps w/ firstNetworkRange + secondPoolName = "pool2" + thirdIPInRange = "10.10.10.3" + ) + + var pods []v1.Pod + var pools []v1alpha1.IPPool + var clusterWideIPs []v1alpha1.OverlappingRangeIPReservation + + BeforeEach(func() { + ips := []string{firstIPInRange, secondIPInRange, thirdIPInRange} + networks := []string{firstNetworkName, secondNetworkName} + for i := 0; i < numberOfPods; i++ { + pod := generatePod(namespace, fmt.Sprintf("pod%d", i+1), ipInNetwork{ + ip: ips[i], + networkName: networks[i%2], // pod1 and pod3 connected to network1; pod2 connected to network2 + }) + _, err := k8sClientSet.CoreV1().Pods(namespace).Create(pod) + Expect(err).NotTo(HaveOccurred()) + pods = append(pods, *pod) + } + }) + + BeforeEach(func() { + firstPool := generateIPPoolSpec(firstNetworkRange, namespace, firstPoolName, pods[0].GetName(), pods[2].GetName()) + secondPool := generateIPPoolSpec(secondNetworkRange, namespace, secondPoolName, pods[1].GetName()) + Expect(k8sClient.Create(context.Background(), firstPool)).NotTo(HaveOccurred()) + Expect(k8sClient.Create(context.Background(), secondPool)).NotTo(HaveOccurred()) + pools = append(pools, *firstPool, *secondPool) + }) + + BeforeEach(func() { + podIPs := []string{firstIPInRange, secondIPInRange, thirdIPInRange} + for i := 0; i < numberOfPods; i++ { + var clusterWideIP v1alpha1.OverlappingRangeIPReservation + ownerPodRef := fmt.Sprintf("%s/%s", namespace, pods[i].GetName()) + Expect(k8sClient.Create(context.TODO(), generateClusterWideIPReservation(namespace, podIPs[i], ownerPodRef))).To(Succeed()) + clusterWideIPs = append(clusterWideIPs, clusterWideIP) + } + }) + + AfterEach(func() { + podIPs := []string{firstIPInRange, secondIPInRange, thirdIPInRange} + for i := podIndexToRemove + 1; i < numberOfPods; i++ { + ownerPodRef := fmt.Sprintf("%s/%s", namespace, pods[i].GetName()) + Expect(k8sClient.Delete(context.TODO(), generateClusterWideIPReservation(namespace, podIPs[i], ownerPodRef))).To(Succeed()) + } + clusterWideIPs = nil + }) + + AfterEach(func() { + for i := podIndexToRemove + 1; i < numberOfPods; i++ { + Expect(k8sClientSet.CoreV1().Pods(namespace).Delete(pods[i].Name, &metav1.DeleteOptions{})).NotTo(HaveOccurred()) + } + pods = nil + }) + + AfterEach(func() { + for i := range pools { + Expect(k8sClient.Delete(context.Background(), &pools[i])).NotTo(HaveOccurred()) + } + pools = nil + }) + + It("will delete an orphaned IP address", func() { + Expect(k8sClientSet.CoreV1().Pods(namespace).Delete(pods[podIndexToRemove].Name, &metav1.DeleteOptions{})).NotTo(HaveOccurred()) + newReconciler, err := reconciler.NewReconcileLooper(kubeConfigPath, context.TODO()) + Expect(err).NotTo(HaveOccurred()) + Expect(newReconciler.ReconcileOverlappingIPAddresses()).To(Succeed()) + + expectedClusterWideIPs := 2 + var clusterWideIPAllocations v1alpha1.OverlappingRangeIPReservationList + Expect(k8sClient.List(context.TODO(), &clusterWideIPAllocations)).To(Succeed()) + Expect(clusterWideIPAllocations.Items).To(HaveLen(expectedClusterWideIPs)) + }) + }) }) func generateIPPoolSpec(ipRange string, namespace string, poolName string, podNames ...string) *v1alpha1.IPPool { @@ -182,6 +268,15 @@ func generateIPPoolSpec(ipRange string, namespace string, poolName string, podNa } } +func generateClusterWideIPReservation(namespace string, ip string, ownerPodRef string) *v1alpha1.OverlappingRangeIPReservation { + return &v1alpha1.OverlappingRangeIPReservation{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: ip}, + Spec: v1alpha1.OverlappingRangeIPReservationSpec{ + PodRef: ownerPodRef, + }, + } +} + type ipInNetwork struct { ip string networkName string From d1b0bf0a4ef0b7a8d55af16b5953b91b9609f329 Mon Sep 17 00:00:00 2001 From: Miguel Duarte Barroso Date: Thu, 22 Jul 2021 10:42:09 +0200 Subject: [PATCH 16/16] ip-reconciler: improve efficiency This commit changes the reconciler logic to only parse the network-status annotations of whereabouts pods - e.g. pods whose pod referecences (/) feature in any of the whereabouts IPPools. Signed-off-by: Miguel Duarte Barroso --- pkg/reconciler/iploop.go | 25 +++++++++++++------------ pkg/reconciler/wrappedPod.go | 21 ++++++++++++++++++--- pkg/reconciler/wrappedPod_test.go | 10 ++++++---- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/pkg/reconciler/iploop.go b/pkg/reconciler/iploop.go index 98af00b27..42c3a5340 100644 --- a/pkg/reconciler/iploop.go +++ b/pkg/reconciler/iploop.go @@ -18,7 +18,7 @@ import ( type ReconcileLooper struct { ctx context.Context k8sClient kubernetes.Client - livePods map[string]podWrapper + liveWhereaboutsPods map[string]podWrapper orphanedIPs []OrphanedIPReservations orphanedClusterWideIPs []whereaboutsv1alpha1.OverlappingRangeIPReservation } @@ -41,13 +41,19 @@ func NewReconcileLooper(kubeConfigPath string, ctx context.Context) (*ReconcileL return nil, err } + ipPools, err := k8sClient.ListIPPools(ctx) + if err != nil { + return nil, logging.Errorf("failed to retrieve all IP pools: %v", err) + } + + whereaboutsPodRefs := getPodRefsServedByWhereabouts(ipPools) looper := &ReconcileLooper{ - ctx: ctx, - k8sClient: *k8sClient, - livePods: indexPods(pods), + ctx: ctx, + k8sClient: *k8sClient, + liveWhereaboutsPods: indexPods(pods, whereaboutsPodRefs), } - if err := looper.findOrphanedIPsPerPool(); err != nil { + if err := looper.findOrphanedIPsPerPool(ipPools); err != nil { return nil, err } @@ -57,12 +63,7 @@ func NewReconcileLooper(kubeConfigPath string, ctx context.Context) (*ReconcileL return looper, nil } -func (rl *ReconcileLooper) findOrphanedIPsPerPool() error { - ipPools, err := rl.k8sClient.ListIPPools(rl.ctx) - if err != nil { - return logging.Errorf("failed to retrieve all IP pools: %v", err) - } - +func (rl *ReconcileLooper) findOrphanedIPsPerPool(ipPools []storage.IPPool) error { for _, pool := range ipPools { orphanIP := OrphanedIPReservations{ Pool: pool, @@ -87,7 +88,7 @@ func (rl *ReconcileLooper) findOrphanedIPsPerPool() error { } func (rl ReconcileLooper) isPodAlive(podRef string, ip string) bool { - for livePodRef, livePod := range rl.livePods { + for livePodRef, livePod := range rl.liveWhereaboutsPods { if podRef == livePodRef { livePodIPs := livePod.ips logging.Debugf( diff --git a/pkg/reconciler/wrappedPod.go b/pkg/reconciler/wrappedPod.go index 82845e9e1..86f2cfb86 100644 --- a/pkg/reconciler/wrappedPod.go +++ b/pkg/reconciler/wrappedPod.go @@ -4,6 +4,7 @@ import ( "encoding/json" "github.com/dougbtv/whereabouts/pkg/logging" + "github.com/dougbtv/whereabouts/pkg/storage" k8snetworkplumbingwgv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" v1 "k8s.io/api/core/v1" @@ -28,13 +29,27 @@ func wrapPod(pod v1.Pod) *podWrapper { } } -func indexPods(podList []v1.Pod) map[string]podWrapper { +func getPodRefsServedByWhereabouts(ipPools []storage.IPPool) map[string]void { + whereaboutsPodRefs := map[string]void{} + for _, pool := range ipPools { + for _, ipReservation := range pool.Allocations() { + whereaboutsPodRefs[ipReservation.PodRef] = void{} + } + } + return whereaboutsPodRefs +} + +func indexPods(livePodList []v1.Pod, whereaboutsPodNames map[string]void) map[string]podWrapper { podMap := map[string]podWrapper{} - for _, pod := range podList { + for _, pod := range livePodList { + podRef := composePodRef(pod) + if _, isWhereaboutsPod := whereaboutsPodNames[podRef]; !isWhereaboutsPod { + continue + } wrappedPod := wrapPod(pod) if wrappedPod != nil { - podMap[composePodRef(pod)] = *wrappedPod + podMap[podRef] = *wrappedPod } } return podMap diff --git a/pkg/reconciler/wrappedPod_test.go b/pkg/reconciler/wrappedPod_test.go index a34a5d5c5..351bd1607 100644 --- a/pkg/reconciler/wrappedPod_test.go +++ b/pkg/reconciler/wrappedPod_test.go @@ -126,10 +126,12 @@ var _ = Describe("Pod Wrapper operations", func() { table.DescribeTable("", func(podsInfo ...podInfo) { var pods []v1.Pod + whereaboutsPods := map[string]void{} + for _, info := range podsInfo { - pods = append( - pods, - generatePodSpecWithNameAndNamespace(info.name, info.namespace, info.ips...)) + newPod := generatePodSpecWithNameAndNamespace(info.name, info.namespace, info.ips...) + pods = append(pods, newPod) + whereaboutsPods[composePodRef(newPod)] = void{} } expectedPodWrapper := map[string]podWrapper{} for _, info := range podsInfo { @@ -140,7 +142,7 @@ var _ = Describe("Pod Wrapper operations", func() { expectedPodWrapper[fmt.Sprintf("%s/%s", info.namespace, info.name)] = podWrapper{ips: indexedPodIPs} } - Expect(indexPods(pods)).To(Equal(expectedPodWrapper)) + Expect(indexPods(pods, whereaboutsPods)).To(Equal(expectedPodWrapper)) }, table.Entry("when no pods are passed"), table.Entry("when a pod is passed", podInfo{