diff --git a/cluster-autoscaler/cloudprovider/digitalocean/README.md b/cluster-autoscaler/cloudprovider/digitalocean/README.md index e5b33cb55529..beb3db935658 100644 --- a/cluster-autoscaler/cloudprovider/digitalocean/README.md +++ b/cluster-autoscaler/cloudprovider/digitalocean/README.md @@ -28,24 +28,24 @@ picks up the configuration from the API and adjusts the behavior accordingly. # Development Make sure you're inside the root path of the [autoscaler -repository](https://github.com/kubernetes/autoscaler) +repository](https://github.com/kubernetes/autoscaler/cluster-autoscaler) 1.) Build the `cluster-autoscaler` binary: ``` -make build-in-docker +GOARCH=amd64 make build-in-docker ``` 2.) Build the docker image: ``` -docker build -t digitalocean/cluster-autoscaler:dev . +docker build --platform linux/amd64 -f Dockerfile.amd64 -t digitalocean/cluster-autoscaler:dev . ``` 3.) Push the docker image to Docker hub: ``` -docker push digitalocean/cluster-autoscaler:dev +docker push --platform linux/amd64 digitalocean/cluster-autoscaler:dev ``` diff --git a/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_cloud_provider_test.go b/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_cloud_provider_test.go index e5772eac2fd7..c72d1a48c542 100644 --- a/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_cloud_provider_test.go +++ b/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_cloud_provider_test.go @@ -29,6 +29,73 @@ import ( "k8s.io/autoscaler/cluster-autoscaler/cloudprovider" ) +func defaultDOClientMock(clusterID string) *doClientMock { + client := &doClientMock{} + ctx := context.Background() + + client.On("ListNodePools", ctx, clusterID, nil).Return( + []*godo.KubernetesNodePool{ + { + ID: "1", + Nodes: []*godo.KubernetesNode{ + {ID: "1", Status: &godo.KubernetesNodeStatus{State: "running"}}, + {ID: "2", Status: &godo.KubernetesNodeStatus{State: "running"}}, + }, + AutoScale: true, + }, + { + ID: "2", + Nodes: []*godo.KubernetesNode{ + {ID: "3", Status: &godo.KubernetesNodeStatus{State: "deleting"}}, + {ID: "4", Status: &godo.KubernetesNodeStatus{State: "running"}}, + }, + AutoScale: true, + }, + { + ID: "3", + Nodes: []*godo.KubernetesNode{ + {ID: "5", Status: &godo.KubernetesNodeStatus{State: "provisioning"}}, + {ID: "6", Status: &godo.KubernetesNodeStatus{State: "running"}}, + }, + AutoScale: true, + }, + { + ID: "4", + Nodes: []*godo.KubernetesNode{ + {ID: "7", Status: &godo.KubernetesNodeStatus{State: "draining"}}, + {ID: "8", Status: &godo.KubernetesNodeStatus{State: "running"}}, + }, + AutoScale: false, + }, + }, + &godo.Response{}, + nil, + ).Once() + return client +} + +func setGetNodeTemplateMock(c *doClientMock, times int) *doClientMock { + c.On("GetNodePoolTemplate", context.Background(), "123456", "").Return(&godo.KubernetesNodePoolTemplateResponse{ + ClusterUUID: "123456", + Name: "some-pool", + Slug: "s-1vcpu-2gb", + Template: &godo.KubernetesNodePoolTemplate{ + Labels: make(map[string]string), + Capacity: &godo.KubernetesNodePoolResources{ + CPU: 1, + Memory: "2048Mi", + Pods: 110, + }, + Allocatable: &godo.KubernetesNodePoolResources{ + CPU: 380, + Memory: "1024MI", + Pods: 110, + }, + }, + }, &godo.Response{}, nil).Times(times) + return c +} + func testCloudProvider(t *testing.T, client *doClientMock) *digitaloceanCloudProvider { cfg := `{"cluster_id": "123456", "token": "123-123-123", "url": "https://api.digitalocean.com/v2", "version": "dev"}` @@ -38,47 +105,7 @@ func testCloudProvider(t *testing.T, client *doClientMock) *digitaloceanCloudPro // fill the test provider with some example if client == nil { - client = &doClientMock{} - ctx := context.Background() - - client.On("ListNodePools", ctx, manager.clusterID, nil).Return( - []*godo.KubernetesNodePool{ - { - ID: "1", - Nodes: []*godo.KubernetesNode{ - {ID: "1", Status: &godo.KubernetesNodeStatus{State: "running"}}, - {ID: "2", Status: &godo.KubernetesNodeStatus{State: "running"}}, - }, - AutoScale: true, - }, - { - ID: "2", - Nodes: []*godo.KubernetesNode{ - {ID: "3", Status: &godo.KubernetesNodeStatus{State: "deleting"}}, - {ID: "4", Status: &godo.KubernetesNodeStatus{State: "running"}}, - }, - AutoScale: true, - }, - { - ID: "3", - Nodes: []*godo.KubernetesNode{ - {ID: "5", Status: &godo.KubernetesNodeStatus{State: "provisioning"}}, - {ID: "6", Status: &godo.KubernetesNodeStatus{State: "running"}}, - }, - AutoScale: true, - }, - { - ID: "4", - Nodes: []*godo.KubernetesNode{ - {ID: "7", Status: &godo.KubernetesNodeStatus{State: "draining"}}, - {ID: "8", Status: &godo.KubernetesNodeStatus{State: "running"}}, - }, - AutoScale: false, - }, - }, - &godo.Response{}, - nil, - ).Once() + client = defaultDOClientMock(manager.clusterID) } manager.client = client @@ -102,7 +129,10 @@ func TestDigitalOceanCloudProvider_Name(t *testing.T) { } func TestDigitalOceanCloudProvider_NodeGroups(t *testing.T) { - provider := testCloudProvider(t, nil) + c := defaultDOClientMock("123456") + c = setGetNodeTemplateMock(c, 3) + + provider := testCloudProvider(t, c) err := provider.manager.Refresh() assert.NoError(t, err) @@ -124,7 +154,7 @@ func TestDigitalOceanCloudProvider_NodeGroupForNode(t *testing.T) { t.Run("success", func(t *testing.T) { client := &doClientMock{} ctx := context.Background() - + client = setGetNodeTemplateMock(client, 2) client.On("ListNodePools", ctx, clusterID, nil).Return( []*godo.KubernetesNodePool{ { diff --git a/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_manager.go b/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_manager.go index 76ba538ef338..03eaa7a3f8a3 100644 --- a/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_manager.go +++ b/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_manager.go @@ -35,6 +35,9 @@ var ( ) type nodeGroupClient interface { + // GetNodePoolTemplate returns the template for a given node pool - used in helping CA for scale up from zero simulations. + GetNodePoolTemplate(ctx context.Context, clusterID string, nodePoolName string) (*godo.KubernetesNodePoolTemplateResponse, *godo.Response, error) + // ListNodePools lists all the node pools found in a Kubernetes cluster. ListNodePools(ctx context.Context, clusterID string, opts *godo.ListOptions) ([]*godo.KubernetesNodePool, *godo.Response, error) @@ -147,17 +150,22 @@ func (m *Manager) Refresh() error { if !nodePool.AutoScale { continue } - + nodePoolTemplateResponse, _, err := m.client.GetNodePoolTemplate(ctx, m.clusterID, nodePool.Name) + klog.V(4).Infof("fetched template response - %v", nodePoolTemplateResponse) + if err != nil { + return err + } klog.V(4).Infof("adding node pool: %q name: %s min: %d max: %d", nodePool.ID, nodePool.Name, nodePool.MinNodes, nodePool.MaxNodes) group = append(group, &NodeGroup{ - id: nodePool.ID, - clusterID: m.clusterID, - client: m.client, - nodePool: nodePool, - minSize: nodePool.MinNodes, - maxSize: nodePool.MaxNodes, + id: nodePool.ID, + clusterID: m.clusterID, + client: m.client, + nodePool: nodePool, + nodePoolTemplate: nodePoolTemplateResponse, + minSize: nodePool.MinNodes, + maxSize: nodePool.MaxNodes, }) } diff --git a/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_manager_test.go b/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_manager_test.go index daff90c5c7d1..494a0091fa4a 100644 --- a/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_manager_test.go +++ b/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_manager_test.go @@ -90,6 +90,7 @@ func TestDigitalOceanManager_Refresh(t *testing.T) { assert.NoError(t, err) client := &doClientMock{} + client = setGetNodeTemplateMock(client, 4) ctx := context.Background() client.On("ListNodePools", ctx, manager.clusterID, nil).Return( @@ -147,6 +148,8 @@ func TestDigitalOceanManager_RefreshWithNodeSpec(t *testing.T) { assert.NoError(t, err) client := &doClientMock{} + client = setGetNodeTemplateMock(client, 4) + ctx := context.Background() client.On("ListNodePools", ctx, manager.clusterID, nil).Return( diff --git a/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_node_group.go b/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_node_group.go index 3f880553f2ce..a4a8a9f74aed 100644 --- a/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_node_group.go +++ b/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_node_group.go @@ -20,13 +20,14 @@ import ( "context" "errors" "fmt" - "github.com/digitalocean/godo" apiv1 "k8s.io/api/core/v1" - + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider" "k8s.io/autoscaler/cluster-autoscaler/config" "k8s.io/autoscaler/cluster-autoscaler/simulator/framework" + utilerrors "k8s.io/autoscaler/cluster-autoscaler/utils/errors" ) const ( @@ -43,13 +44,13 @@ var ( // configuration info and functions to control a set of nodes that have the // same capacity and set of labels. type NodeGroup struct { - id string - clusterID string - client nodeGroupClient - nodePool *godo.KubernetesNodePool - - minSize int - maxSize int + id string + clusterID string + client nodeGroupClient + nodePool *godo.KubernetesNodePool + nodePoolTemplate *godo.KubernetesNodePoolTemplateResponse + minSize int + maxSize int } // MaxSize returns maximum size of the node group. @@ -213,7 +214,21 @@ func (n *NodeGroup) Nodes() ([]cloudprovider.Instance, error) { // that are started on the node by default, using manifest (most likely only // kube-proxy). Implementation optional. func (n *NodeGroup) TemplateNodeInfo() (*framework.NodeInfo, error) { - return nil, cloudprovider.ErrNotImplemented + if n.nodePoolTemplate != nil { + // Template has already been populated from cache - convert to node info and return + tni, err := toNodeInfoTemplate(n.nodePoolTemplate) + if err != nil { + return nil, utilerrors.NewAutoscalerError(utilerrors.InternalError, err.Error()) + } + return tni, nil + } + + // No template present in cache - attempt to fetch from API + resp, _, err := n.client.GetNodePoolTemplate(context.TODO(), n.clusterID, n.nodePool.Name) + if err != nil { + return nil, utilerrors.NewAutoscalerError(utilerrors.InternalError, err.Error()) + } + return toNodeInfoTemplate(resp) } // Exist checks if the node group really exists on the cloud provider side. @@ -292,3 +307,57 @@ func toInstanceStatus(nodeState *godo.KubernetesNodeStatus) *cloudprovider.Insta return st } + +func toNodeInfoTemplate(resp *godo.KubernetesNodePoolTemplateResponse) (*framework.NodeInfo, error) { + allocatable, err := parseToQuanitity(resp.Template.Allocatable.CPU, resp.Template.Allocatable.Pods, resp.Template.Allocatable.Memory) + if err != nil { + return nil, fmt.Errorf("failed to create allocatable resources - %s", err) + } + capacity, err := parseToQuanitity(resp.Template.Capacity.CPU, resp.Template.Capacity.Pods, resp.Template.Capacity.Memory) + if err != nil { + return nil, fmt.Errorf("failed to create capacity resources - %s", err) + } + l := map[string]string{ + apiv1.LabelOSStable: cloudprovider.DefaultOS, + apiv1.LabelArchStable: cloudprovider.DefaultArch, + } + l = cloudprovider.JoinStringMaps(l, resp.Template.Labels) + node := &apiv1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: generateWorkerName(resp.Name, 1), + Labels: l, + }, + Spec: apiv1.NodeSpec{}, + Status: apiv1.NodeStatus{ + Allocatable: allocatable, + Capacity: capacity, + Phase: apiv1.NodeRunning, + Conditions: cloudprovider.BuildReadyConditions(), + }, + } + return framework.NewNodeInfo(node, nil), nil +} + +func parseToQuanitity(cpu int64, pods int64, memory string) (apiv1.ResourceList, error) { + c := resource.NewQuantity(cpu, resource.DecimalSI) + p := resource.NewQuantity(pods, resource.DecimalSI) + m, err := resource.ParseQuantity(memory) + if err != nil { + return nil, err + } + return apiv1.ResourceList{ + apiv1.ResourceCPU: *c, + apiv1.ResourceMemory: m, + apiv1.ResourcePods: *p, + }, nil +} + +func generateWorkerName(poolName string, workerID int64) string { + var customAlphabet string = "n38uc7mqfyxojrbwgea6tl2ps5kh4ivd01z9" + var customAlphabetSize int64 = int64(len(customAlphabet)) + s := "" + for ; workerID > 0; workerID = workerID / customAlphabetSize { + s = string(customAlphabet[workerID%customAlphabetSize]) + s + } + return fmt.Sprintf("%s-%s", poolName, s) +} diff --git a/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_node_group_test.go b/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_node_group_test.go index 13ce9a134e3f..6ab3c7ee834b 100644 --- a/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_node_group_test.go +++ b/cluster-autoscaler/cloudprovider/digitalocean/digitalocean_node_group_test.go @@ -460,3 +460,8 @@ func (m *doClientMock) DeleteNode(ctx context.Context, clusterID, poolID, nodeID args := m.Called(ctx, clusterID, poolID, nodeID, nil) return args.Get(0).(*godo.Response), args.Error(1) } + +func (m *doClientMock) GetNodePoolTemplate(ctx context.Context, clusterID string, nodePoolName string) (*godo.KubernetesNodePoolTemplateResponse, *godo.Response, error) { + args := m.Called(ctx, clusterID, nodePoolName) + return args.Get(0).(*godo.KubernetesNodePoolTemplateResponse), args.Get(1).(*godo.Response), args.Error(2) +} diff --git a/cluster-autoscaler/go.mod b/cluster-autoscaler/go.mod index f5227ad3e1f4..952524f58ba4 100644 --- a/cluster-autoscaler/go.mod +++ b/cluster-autoscaler/go.mod @@ -23,7 +23,7 @@ require ( github.com/gofrs/uuid v4.4.0+incompatible github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 - github.com/google/go-querystring v1.0.0 + github.com/google/go-querystring v1.1.0 github.com/google/uuid v1.6.0 github.com/jmattheis/goverter v1.4.0 github.com/jmespath/go-jmespath v0.4.0 @@ -52,6 +52,7 @@ require ( k8s.io/cloud-provider-gcp/providers v0.28.2 k8s.io/component-base v0.33.0-alpha.0 k8s.io/component-helpers v0.33.0-alpha.0 + k8s.io/dynamic-resource-allocation v0.0.0 k8s.io/klog/v2 v2.130.1 k8s.io/kubelet v0.33.0-alpha.0 k8s.io/kubernetes v1.33.0-alpha.0 @@ -136,6 +137,8 @@ require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -206,7 +209,6 @@ require ( k8s.io/cri-api v0.33.0-alpha.0 // indirect k8s.io/cri-client v0.0.0 // indirect k8s.io/csi-translation-lib v0.27.0 // indirect - k8s.io/dynamic-resource-allocation v0.0.0 // indirect k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 // indirect k8s.io/kms v0.33.0-alpha.0 // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect @@ -221,7 +223,7 @@ require ( replace github.com/aws/aws-sdk-go/service/eks => github.com/aws/aws-sdk-go/service/eks v1.38.49 -replace github.com/digitalocean/godo => github.com/digitalocean/godo v1.27.0 +replace github.com/digitalocean/godo => github.com/digitalocean/godo v1.137.0 replace github.com/rancher/go-rancher => github.com/rancher/go-rancher v0.1.0 diff --git a/cluster-autoscaler/go.sum b/cluster-autoscaler/go.sum index 540100deaa3d..5da65ee8515d 100644 --- a/cluster-autoscaler/go.sum +++ b/cluster-autoscaler/go.sum @@ -1,7 +1,6 @@ cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/Azure/azure-sdk-for-go v46.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -138,8 +137,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/digitalocean/godo v1.27.0 h1:78iE9oVvTnAEqhMip2UHFvL01b8LJcydbNUpr0cAmN4= -github.com/digitalocean/godo v1.27.0/go.mod h1:iJnN9rVu6K5LioLxLimlq0uRI+y/eAQjROUmeU/r0hY= +github.com/digitalocean/godo v1.137.0 h1:bkPG5ram9bHErbCWCKT6j/fMs8jisckhczd3IvueJHg= +github.com/digitalocean/godo v1.137.0/go.mod h1:PU8JB6I1XYkQIdHFop8lLAY9ojp6M0XcU0TWaQSxbrc= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -162,6 +161,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/euank/go-kmsg-parser v2.0.0+incompatible h1:cHD53+PLQuuQyLZeriD1V/esuG4MuU0Pjs5y6iknohY= github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -230,12 +231,13 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -260,6 +262,12 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.4.1-0.20181029123624-5de817a9aa20 h1:dAOsPLhnBzIyxu0VvmnKjlNcIlgMK+erD6VRHDtweMI= @@ -295,6 +303,10 @@ github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffkt github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible h1:aKW/4cBs+yK6gpqU3K/oIwk9Q/XICqd3zOX/UFuvqmk= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -459,7 +471,6 @@ golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -477,12 +488,10 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=