diff --git a/.golangci.yml b/.golangci.yml index b2c780e8..7697944a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -104,6 +104,8 @@ linters: - opinionated - performance - style + disabled-checks: + - commentedOutCode settings: captLocal: paramsOnly: true diff --git a/cloud/annotations/annotations.go b/cloud/annotations/annotations.go index a9fe60dc..fd165e30 100644 --- a/cloud/annotations/annotations.go +++ b/cloud/annotations/annotations.go @@ -60,9 +60,12 @@ const ( NodeBalancerBackendSubnetName = "service.beta.kubernetes.io/linode-loadbalancer-backend-subnet-name" NodeBalancerBackendSubnetID = "service.beta.kubernetes.io/linode-loadbalancer-backend-subnet-id" - NodeBalancerFrontendIPv4Range = "service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv4-range" - NodeBalancerFrontendIPv6Range = "service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv6-range" - NodeBalancerFrontendVPCName = "service.beta.kubernetes.io/linode-loadbalancer-frontend-vpc-name" - NodeBalancerFrontendSubnetName = "service.beta.kubernetes.io/linode-loadbalancer-frontend-subnet-name" - NodeBalancerFrontendSubnetID = "service.beta.kubernetes.io/linode-loadbalancer-frontend-subnet-id" + // Uncomment once https://github.com/linode/linodego/pull/978 is merged and a release is cut + /* + NodeBalancerFrontendIPv4Range = "service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv4-range" + NodeBalancerFrontendIPv6Range = "service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv6-range" + NodeBalancerFrontendVPCName = "service.beta.kubernetes.io/linode-loadbalancer-frontend-vpc-name" + NodeBalancerFrontendSubnetName = "service.beta.kubernetes.io/linode-loadbalancer-frontend-subnet-name" + NodeBalancerFrontendSubnetID = "service.beta.kubernetes.io/linode-loadbalancer-frontend-subnet-id" + */ ) diff --git a/cloud/linode/fake_linode_test.go b/cloud/linode/fake_linode_test.go index 1c2f26e3..ed844512 100644 --- a/cloud/linode/fake_linode_test.go +++ b/cloud/linode/fake_linode_test.go @@ -391,8 +391,8 @@ func (f *fakeAPI) setupRoutes() { } f.nb[strconv.Itoa(nb.ID)] = &nb - for _, backendVPC := range nbco.BackendVPCs { - if backendVPC.IPv4Range == nil || *backendVPC.IPv4Range == "" { + for _, backendVPC := range nbco.VPCs { + if backendVPC.IPv4Range == "" { continue } subnet, ok := f.subnet[backendVPC.SubnetID] @@ -401,7 +401,7 @@ func (f *fakeAPI) setupRoutes() { } subnet.Nodebalancers = append(subnet.Nodebalancers, linodego.VPCSubnetNodebalancers{ ID: nb.ID, - Ipv4Range: *backendVPC.IPv4Range, + Ipv4Range: backendVPC.IPv4Range, }) } diff --git a/cloud/linode/loadbalancers.go b/cloud/linode/loadbalancers.go index 3ab07244..04324a2e 100644 --- a/cloud/linode/loadbalancers.go +++ b/cloud/linode/loadbalancers.go @@ -691,17 +691,19 @@ func (l *loadbalancers) getNodeBalancerByIP(ctx context.Context, service *v1.Ser return nil, lbNotFoundError{serviceNn: getServiceNn(service)} } - // filter by subnet ID if specified for frontend vpc ip - frontendSubnetID := service.GetAnnotations()[annotations.NodeBalancerFrontendSubnetID] - if frontendSubnetID != "" { - for i := range lbs { - lb := &lbs[i] - if lb.FrontendAddressType == "vpc" && lb.FrontendVPCSubnetID != nil && strconv.Itoa(*lb.FrontendVPCSubnetID) == frontendSubnetID { - return lb, nil + // Uncomment once https://github.com/linode/linodego/pull/978 is merged and a release is cut + /* + // filter by subnet ID if specified for frontend vpc ip + frontendSubnetID := service.GetAnnotations()[annotations.NodeBalancerFrontendSubnetID] + if frontendSubnetID != "" { + for i := range lbs { + lb := &lbs[i] + if lb.FrontendAddressType == "vpc" && lb.FrontendVPCSubnetID != nil && strconv.Itoa(*lb.FrontendVPCSubnetID) == frontendSubnetID { + return lb, nil + } } - } - return nil, lbNotFoundError{serviceNn: getServiceNn(service)} - } + return nil, lbNotFoundError{serviceNn: getServiceNn(service)} + } */ klog.V(2).Infof("found NodeBalancer (%d) for service (%s) via IP (%s)", lbs[0].ID, getServiceNn(service), ip.String()) return &lbs[0], nil @@ -764,7 +766,7 @@ func (l *loadbalancers) GetLinodeNBType(service *v1.Service) linodego.NodeBalanc // 3. NodeBalancerBackendIPv4SubnetID/NodeBalancerBackendIPv4SubnetName flag // 4. NodeBalancerBackendIPv4Subnet flag // 5. Default to using the subnet ID of the service's VPC -func (l *loadbalancers) getVPCCreateOptions(ctx context.Context, service *v1.Service) ([]linodego.NodeBalancerBackendVPCOptions, error) { +func (l *loadbalancers) getVPCCreateOptions(ctx context.Context, service *v1.Service) ([]linodego.NodeBalancerVPCOptions, error) { // Evaluate subnetID based on annotations or flags subnetID, err := l.getSubnetIDForSVC(ctx, service) if err != nil { @@ -780,10 +782,10 @@ func (l *loadbalancers) getVPCCreateOptions(ctx context.Context, service *v1.Ser // If the user has specified a NodeBalancerBackendIPv4Range, use that // for the NodeBalancer backend ipv4 range if backendIPv4Range != "" { - vpcCreateOpts := []linodego.NodeBalancerBackendVPCOptions{ + vpcCreateOpts := []linodego.NodeBalancerVPCOptions{ { SubnetID: subnetID, - IPv4Range: &backendIPv4Range, + IPv4Range: backendIPv4Range, }, } return vpcCreateOpts, nil @@ -807,10 +809,10 @@ func (l *loadbalancers) getVPCCreateOptions(ctx context.Context, service *v1.Ser if err != nil { return nil, err } - return []linodego.NodeBalancerBackendVPCOptions{ + return []linodego.NodeBalancerVPCOptions{ { SubnetID: subnetID, - IPv4Range: &backendIPv4Range, + IPv4Range: backendIPv4Range, }, }, nil } @@ -821,7 +823,7 @@ func (l *loadbalancers) getVPCCreateOptions(ctx context.Context, service *v1.Ser _, vpcInAnnotation := service.GetAnnotations()[annotations.NodeBalancerBackendVPCName] _, subnetInAnnotation := service.GetAnnotations()[annotations.NodeBalancerBackendSubnetName] if vpcInAnnotation || subnetInAnnotation { - vpcCreateOpts := []linodego.NodeBalancerBackendVPCOptions{ + vpcCreateOpts := []linodego.NodeBalancerVPCOptions{ { SubnetID: subnetID, }, @@ -832,7 +834,7 @@ func (l *loadbalancers) getVPCCreateOptions(ctx context.Context, service *v1.Ser // Precedence 3: If the user has specified a NodeBalancerBackendIPv4SubnetID, use that // and auto-allocate subnets from it for the NodeBalancer if options.Options.NodeBalancerBackendIPv4SubnetID != 0 { - vpcCreateOpts := []linodego.NodeBalancerBackendVPCOptions{ + vpcCreateOpts := []linodego.NodeBalancerVPCOptions{ { SubnetID: options.Options.NodeBalancerBackendIPv4SubnetID, }, @@ -843,18 +845,18 @@ func (l *loadbalancers) getVPCCreateOptions(ctx context.Context, service *v1.Ser // Precedence 4: If the user has specified a NodeBalancerBackendIPv4Subnet, use that // and auto-allocate subnets from it for the NodeBalancer. if options.Options.NodeBalancerBackendIPv4Subnet != "" { - vpcCreateOpts := []linodego.NodeBalancerBackendVPCOptions{ + vpcCreateOpts := []linodego.NodeBalancerVPCOptions{ { SubnetID: subnetID, - IPv4Range: &options.Options.NodeBalancerBackendIPv4Subnet, - IPv4RangeAutoAssign: new(true), + IPv4Range: options.Options.NodeBalancerBackendIPv4Subnet, + IPv4RangeAutoAssign: true, }, } return vpcCreateOpts, nil } // Default to using the subnet ID of the service's VPC - vpcCreateOpts := []linodego.NodeBalancerBackendVPCOptions{ + vpcCreateOpts := []linodego.NodeBalancerVPCOptions{ { SubnetID: subnetID, }, @@ -862,6 +864,8 @@ func (l *loadbalancers) getVPCCreateOptions(ctx context.Context, service *v1.Ser return vpcCreateOpts, nil } +// Uncomment once https://github.com/linode/linodego/pull/978 is merged and a release is cut +/* // getFrontendVPCCreateOptions returns the VPC options for the NodeBalancer frontend VPC creation. // Order of precedence: // 1. Frontend Subnet ID Annotation - Direct subnet ID @@ -934,7 +938,7 @@ func (l *loadbalancers) getSubnetIDByVPCAndSubnetNames(ctx context.Context, vpcN // Use the VPC ID and Subnet Name to get the subnet ID return services.GetSubnetID(ctx, l.client, vpcID, subnetName) -} +} */ func (l *loadbalancers) createNodeBalancer(ctx context.Context, clusterName string, service *v1.Service, configs []linodego.NodeBalancerConfigCreateOptions) (lb *linodego.NodeBalancer, err error) { connThrottle := getConnectionThrottle(service) @@ -953,18 +957,20 @@ func (l *loadbalancers) createNodeBalancer(ctx context.Context, clusterName stri } if !useIPv6Backends && len(options.Options.VPCNames) > 0 && !options.Options.DisableNodeBalancerVPCBackends { - createOpts.BackendVPCs, err = l.getVPCCreateOptions(ctx, service) + createOpts.VPCs, err = l.getVPCCreateOptions(ctx, service) if err != nil { return nil, err } } - // Add frontend VPC configuration - if frontendVPCs, err := l.getFrontendVPCCreateOptions(ctx, service); err != nil { - return nil, err - } else if len(frontendVPCs) > 0 { - createOpts.FrontendVPCs = frontendVPCs - } + // Uncomment once https://github.com/linode/linodego/pull/978 is merged and a release is cut + /* + // Add frontend VPC configuration + if frontendVPCs, err := l.getFrontendVPCCreateOptions(ctx, service); err != nil { + return nil, err + } else if len(frontendVPCs) > 0 { + createOpts.FrontendVPCs = frontendVPCs + } */ // Check for static IPv4 address annotation if ipv4, ok := service.GetAnnotations()[annotations.AnnLinodeLoadBalancerReservedIPv4]; ok { @@ -1552,9 +1558,10 @@ func makeLoadBalancerStatus(service *v1.Service, nb *linodego.NodeBalancer) *v1. } } - if nb.FrontendAddressType == "vpc" { + // Uncomment once https://github.com/linode/linodego/pull/978 is merged and a release is cut + /* if nb.FrontendAddressType == "vpc" { klog.V(4).Infof("NodeBalancer (%d) is using frontend VPC address type", nb.ID) - } + } */ // Check for per-service IPv6 annotation first, then fall back to global setting if not set useIPv6 := getServiceBoolAnnotation(service, annotations.AnnLinodeEnableIPv6Ingress) diff --git a/cloud/linode/loadbalancers_test.go b/cloud/linode/loadbalancers_test.go index d6d3ee81..50dca632 100644 --- a/cloud/linode/loadbalancers_test.go +++ b/cloud/linode/loadbalancers_test.go @@ -18,7 +18,6 @@ import ( "strings" "testing" - "github.com/golang/mock/gomock" "github.com/linode/linodego/v2" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" @@ -30,7 +29,6 @@ import ( "github.com/linode/linode-cloud-controller-manager/cloud/annotations" "github.com/linode/linode-cloud-controller-manager/cloud/linode/client" - "github.com/linode/linode-cloud-controller-manager/cloud/linode/client/mocks" "github.com/linode/linode-cloud-controller-manager/cloud/linode/options" "github.com/linode/linode-cloud-controller-manager/cloud/linode/services" ) @@ -4434,8 +4432,8 @@ func Test_buildLoadBalancerRequestOmitsVPCConfigForIPv6Backends(t *testing.T) { if err := json.Unmarshal([]byte(req.Body), &createOpts); err != nil { t.Fatalf("unable to unmarshal create request body %#v, error: %#v", req.Body, err) } - if len(createOpts.BackendVPCs) != 0 { - t.Fatalf("expected nodebalancer create request to omit VPC config for IPv6 backends, got %#v", createOpts.BackendVPCs) + if len(createOpts.VPCs) != 0 { + t.Fatalf("expected nodebalancer create request to omit VPC config for IPv6 backends, got %#v", createOpts.VPCs) } if len(createOpts.Configs) != 1 || len(createOpts.Configs[0].Nodes) != 1 { t.Fatalf("expected a single nodebalancer config with one backend node, got %#v", createOpts.Configs) @@ -5972,7 +5970,9 @@ func Test_validateNodeBalancerFrontendIPRange(t *testing.T) { } } -func Test_makeLoadBalancerStatus_FrontendVPC(t *testing.T) { +// Uncomment once https://github.com/linode/linodego/pull/978 is merged and a release is cut +//nolint:dupword // The commented-out test repeats args in its table data. +/* func Test_makeLoadBalancerStatus_FrontendVPC(t *testing.T) { type args struct { service *v1.Service nb *linodego.NodeBalancer @@ -6254,4 +6254,4 @@ func Test_getFrontendVPCCreateOptions(t *testing.T) { } }) } -} +} */ diff --git a/cloud/linode/nodebalancer_backend_range_test.go b/cloud/linode/nodebalancer_backend_range_test.go index ce292413..a1897f3c 100644 --- a/cloud/linode/nodebalancer_backend_range_test.go +++ b/cloud/linode/nodebalancer_backend_range_test.go @@ -187,8 +187,8 @@ func TestGetVPCCreateOptionsWithReservedBackendRange(t *testing.T) { require.NoError(t, err) require.Len(t, got, 1) assert.Equal(t, 456, got[0].SubnetID) - assert.Equal(t, new("10.63.88.4/30"), got[0].IPv4Range) - assert.Nil(t, got[0].IPv4RangeAutoAssign) + assert.Equal(t, "10.63.88.4/30", got[0].IPv4Range) + assert.False(t, got[0].IPv4RangeAutoAssign) }) } } diff --git a/docs/configuration/annotations.md b/docs/configuration/annotations.md index 4f323d9e..28fe3707 100644 --- a/docs/configuration/annotations.md +++ b/docs/configuration/annotations.md @@ -52,11 +52,6 @@ The keys and the values in [annotations must be strings](https://kubernetes.io/d | `backend-vpc-name` | string | | VPC which is connected to the NodeBalancer backend. See [Nodebalancer VPC Configuration](#nodebalancer-vpc-configuration) | | `backend-subnet-name` | string | | Subnet within VPC which is connected to the NodeBalancer backend. See [Nodebalancer VPC Configuration](#nodebalancer-vpc-configuration) | | `backend-subnet-id` | string | | Subnet ID within VPC which is connected to the NodeBalancer backend. See [Nodebalancer VPC Configuration](#nodebalancer-vpc-configuration) | -| `frontend-subnet-id` | string | | Subnet ID for the NodeBalancer frontend VPC configuration. See [Nodebalancer VPC Configuration](#nodebalancer-vpc-configuration) | -| `frontend-vpc-name` | string | | Frontend VPC name for the NodeBalancer frontend VPC configuration. See [Nodebalancer VPC Configuration](#nodebalancer-vpc-configuration) | -| `frontend-subnet-name` | string | | Frontend subnet name for the NodeBalancer frontend VPC configuration. See [Nodebalancer VPC Configuration](#nodebalancer-vpc-configuration) | -| `frontend-ipv4-range` | string | | Optional IPv4 CIDR range from the frontend subnet. See [Nodebalancer VPC Configuration](#nodebalancer-vpc-configuration) | -| `frontend-ipv6-range` | string | | Optional IPv6 CIDR range from the frontend subnet. See [Nodebalancer VPC Configuration](#nodebalancer-vpc-configuration) | | `reserved-ipv4` | string | | An existing Reserved IPv4 address that will be used to initialize the NodeBalancer instance. See [LoadBalancer Configuration](loadbalancer.md#reserved-ipv4-addresses) | | `retain-reserved-ipv4` | bool | `true` | When `false`, deleting a `LoadBalancer` service also releases the reserved IPv4 address attached to the NodeBalancer | @@ -165,17 +160,6 @@ metadata: service.beta.kubernetes.io/linode-loadbalancer-backend-subnet-name: "subnet1" ``` -Frontend VPC configuration: - -```yaml -metadata: - annotations: - service.beta.kubernetes.io/linode-loadbalancer-frontend-subnet-id: "169341" - # Optional: - # service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv4-range: "10.0.0.0/24" - # service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv6-range: "2001:db8::/64" -``` - ### Service with IPv6 Address ```yaml diff --git a/docs/configuration/loadbalancer.md b/docs/configuration/loadbalancer.md index da7a22e0..4d4cf460 100644 --- a/docs/configuration/loadbalancer.md +++ b/docs/configuration/loadbalancer.md @@ -51,7 +51,7 @@ When IPv6 is enabled (either globally or per-service), both IPv4 and IPv6 addres ### IPv6 Backend Support -IPv6 frontends and IPv6 backends are configured independently. Frontend IPv6 controls what the Service publishes in `status.loadBalancer.ingress`, while backend IPv6 controls which node addresses a NodeBalancer targets. +Backend IPv6 controls which node addresses a NodeBalancer targets. IPv6 backends require a dual-stack workload cluster. In practice, the cluster networking stack must support IPv6 NodePort traffic, and the Service itself should be created as dual-stack. A single-stack IPv4 `LoadBalancer` Service can still be annotated for IPv6 backends, but the NodeBalancer health checks and traffic path may fail because the backend NodePort is not exposed over IPv6. @@ -248,43 +248,6 @@ metadata: service.beta.kubernetes.io/linode-loadbalancer-backend-subnet-name: "subnet1" ``` -### Configuring NodeBalancer frontend with VPC - -NodeBalancers can optionally be configured with a VPC-based frontend address. - -Frontend VPC configuration supports the following annotations: - -1. Choose the frontend subnet: - - - `service.beta.kubernetes.io/linode-loadbalancer-frontend-subnet-id` (preferred) - - OR `service.beta.kubernetes.io/linode-loadbalancer-frontend-vpc-name` and `service.beta.kubernetes.io/linode-loadbalancer-frontend-subnet-name` - -2. Optionally constrain the frontend address assignment within the subnet: - - - `service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv4-range` (CIDR) - - `service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv6-range` (CIDR) - -Order of precedence: - -- If `frontend-subnet-id` is set, it is used. -- Otherwise, `frontend-vpc-name` + `frontend-subnet-name` are used. -- IPv4/IPv6 range annotations are optional add-ons and require one of the subnet selectors above. - -Example: - -```yaml -metadata: - annotations: - service.beta.kubernetes.io/linode-loadbalancer-frontend-subnet-id: "169341" - # Optional: - # service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv4-range: "10.0.0.0/24" - # service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv6-range: "2001:db8::/64" -``` - -For a complete working example, see `examples/vpc-frontend-example.yaml`. - -If CCM is started with `--nodebalancer-backend-ipv4-subnet` flag, then it will not allow provisioning of nodebalancer unless subnet specified in service annotation lie within the subnet specified using the flag. This is to prevent accidental overlap between nodebalancer backend ips and pod CIDRs. - ### Reserving a NodeBalancer backend IPv4 range `--nodebalancer-backend-ipv4-reserved-range` marks one /30 inside `--nodebalancer-backend-ipv4-subnet` as off-limits, so CCM never assigns it to a NodeBalancer. Use it to keep a backend range available for an address block managed outside the cluster. diff --git a/docs/examples/README.md b/docs/examples/README.md index bcbc48b9..2b045216 100644 --- a/docs/examples/README.md +++ b/docs/examples/README.md @@ -19,9 +19,6 @@ This section provides working examples of common CCM configurations. Each exampl - Session Affinity - Custom Node Selection -3. **Frontend VPC NodeBalancer** - - `examples/vpc-frontend-example.yaml` - Note: To test UDP based NBs, one can use [test-server](https://github.com/rahulait/test-server) repo to run server using UDP protocol and then use the client commands in repo's readme to connect to the server. For testing these examples, see the [test script](https://github.com/linode/linode-cloud-controller-manager/blob/master/examples/test.sh). diff --git a/examples/vpc-frontend-example.yaml b/examples/vpc-frontend-example.yaml deleted file mode 100644 index f1032509..00000000 --- a/examples/vpc-frontend-example.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: svc-frontend-vpc-test - annotations: - # Specify the Subnet ID for the NodeBalancer Frontend - service.beta.kubernetes.io/linode-loadbalancer-frontend-subnet-id: "169341" - service.beta.kubernetes.io/linode-loadbalancer-nodebalancer-type: "premium" - # Optional: Specify an IPv4/IPv6 range within the subnet - # service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv4-range: "10.0.0.0/24" - # service.beta.kubernetes.io/linode-loadbalancer-frontend-ipv6-range: "2001:db8::/64" - labels: - app: frontend-vpc-test -spec: - type: LoadBalancer - selector: - app: frontend-vpc-test - ports: - - name: http - protocol: TCP - port: 80 - targetPort: 80 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: frontend-vpc-test - labels: - app: frontend-vpc-test -spec: - replicas: 1 - selector: - matchLabels: - app: frontend-vpc-test - template: - metadata: - labels: - app: frontend-vpc-test - spec: - containers: - - name: nginx - image: nginx:latest - ports: - - containerPort: 80 diff --git a/go.mod b/go.mod index 12dea612..df0a73d8 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/getsentry/sentry-go v0.48.0 github.com/golang/mock v1.6.0 github.com/hexdigest/gowrap v1.4.3 - github.com/linode/linodego/v2 v2.6.0 + github.com/linode/linodego/v2 v2.7.0 github.com/prometheus/client_golang v1.24.1 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.12.1 @@ -144,8 +144,6 @@ require ( ) replace ( - // We need this replace until https://github.com/linode/linodego/pull/978 is merged and a release is cut - github.com/linode/linodego/v2 => github.com/linode/linodego/v2 v2.6.1-0.20260914085859-939665091bb2 k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.36.4 k8s.io/cli-runtime => k8s.io/cli-runtime v0.36.4 k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.35.4 diff --git a/go.sum b/go.sum index 4be79f01..7cadaaa7 100644 --- a/go.sum +++ b/go.sum @@ -167,8 +167,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/linode/linodego/v2 v2.6.1-0.20260914085859-939665091bb2 h1:afno/WuVZdnYiHe138c0h0QLV+L/nAJQSo7tQq9BL5E= -github.com/linode/linodego/v2 v2.6.1-0.20260914085859-939665091bb2/go.mod h1:PYCnQa9auhd8uq62Xhsabt0QTQl0X5LLPTxyF6L++0Q= +github.com/linode/linodego/v2 v2.7.0 h1:vyqxwrTbCPRUO1cHdDOP0D8UDI2cfmtdMqE3+95iApY= +github.com/linode/linodego/v2 v2.7.0/go.mod h1:PYCnQa9auhd8uq62Xhsabt0QTQl0X5LLPTxyF6L++0Q= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=