Repository: hashicorp/terraform-provider-kubernetes

Issue:
`services`: add `trafficDistribution` field in `service.spec`
### Description

<!--- Please leave a helpful description of the feature request here. --->

`v1.31` graduated `trafficDistributions` field from alpha to beta with the feature gate being set to enabled by default.

k8s PR: https://github.com/kubernetes/enhancements/issues/4444
Service Documentation: https://kubernetes.io/docs/concepts/services-networking/service/#traffic-distribution

The API docs mentions that this is in alpha, this is wrong and an issue has been raised on the k8s website repo: https://github.com/kubernetes/website/issues/47776

changes would needed to be applied with `structure_service_spec.go`: https://github.com/hashicorp/terraform-provider-kubernetes/blob/65f1a7dd7d46d6e371e083f3e5ed0b0abca09daa/kubernetes/structure_service_spec.go#L56-L69

### References

<!---
Information about referencing Github Issues: https://help.github.com/articles/basic-writing-and-formatting-syntax/#referencing-issues-and-pull-requests

Are there any other GitHub issues (open or closed) or pull requests that should be linked here? Vendor blog posts or documentation?
--->

<!--- Please keep this note for the community --->

### Community Note

* Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original issue to help the community and maintainers prioritize this request
* If you are interested in working on this issue or have submitted a pull request, please leave a comment

<!--- Thank you for keeping this note for the community --->


Discussion:
@aayushsss1: @BBBmau I can take this up if no one's currently working on it!@BBBmau: @aayushsss1 this would be good to pick up, however this also would fall under blocked since it can only be merged once we've addressed the k8s version bump in the next major release of the provider.

You are welcome to work on it if still interested!@aayushsss1: Okay sure, I'll have a look into it!@JaylonmcShan03: @BBBmau I'll glance over this issue next week and if it's not tackled I don't mind tackling this for Q4@Jasper-Ben: can this issue be closed then, since it will be fixed in v3? Or will it remain open until v3 release? 

Solution patch:
diff --git a/.changelog/2625.txt b/.changelog/2625.txt
new file mode 100644
index 00000000..9528ae9d
--- /dev/null
+++ b/.changelog/2625.txt
@@ -0,0 +1,7 @@
+```release-note:enhancement
+`resource/kubernetes_service`:  add a new field `traffic_distribution`.
+```
+
+```
+`resource/kubernetes_service_v1`:  add a new field `traffic_distribution`.
+```
\ No newline at end of file
diff --git a/docs/data-sources/service.md b/docs/data-sources/service.md
index 97e05082..ab08f4f5 100644
--- a/docs/data-sources/service.md
+++ b/docs/data-sources/service.md
@@ -50,6 +50,7 @@ Read-Only:
 - `external_ips` (Set of String)
 - `external_name` (String)
 - `external_traffic_policy` (String)
+- `traffic_distribution` (String)
 - `health_check_node_port` (Number)
 - `internal_traffic_policy` (String)
 - `ip_families` (List of String)
diff --git a/docs/data-sources/service_v1.md b/docs/data-sources/service_v1.md
index 6d56bc0c..be9d82b0 100644
--- a/docs/data-sources/service_v1.md
+++ b/docs/data-sources/service_v1.md
@@ -50,6 +50,7 @@ Read-Only:
 - `external_ips` (Set of String)
 - `external_name` (String)
 - `external_traffic_policy` (String)
+- `traffic_distribution` (String)
 - `health_check_node_port` (Number)
 - `internal_traffic_policy` (String)
 - `ip_families` (List of String)
diff --git a/docs/resources/service.md b/docs/resources/service.md
index b35b2ba5..24a84da8 100644
--- a/docs/resources/service.md
+++ b/docs/resources/service.md
@@ -56,6 +56,7 @@ Optional:
 - `external_ips` (Set of String) A list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP.  A common example is external load-balancers that are not part of the Kubernetes system.
 - `external_name` (String) The external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires `type` to be `ExternalName`.
 - `external_traffic_policy` (String) Denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. `Local` preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. `Cluster` obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading. More info: https://kubernetes.io/docs/tutorials/services/source-ip/
+- `traffic_distribution` (String) Specifies the preferred strategy for distributing traffic to Service endpoints. When set to PreferClose, the Kubernetes will prioritize routing traffic to endpoints that are topologically closer
 - `health_check_node_port` (Number) Specifies the Healthcheck NodePort for the service. Only effects when type is set to `LoadBalancer` and external_traffic_policy is set to `Local`.
 - `internal_traffic_policy` (String) Specifies if the cluster internal traffic should be routed to all endpoints or node-local endpoints only. `Cluster` routes internal traffic to a Service to all endpoints. `Local` routes traffic to node-local endpoints only, traffic is dropped if no node-local endpoints are ready. The default value is `Cluster`.
 - `ip_families` (List of String) IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this service. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. If this field is specified manually, the requested family is available in the cluster, and ipFamilyPolicy allows it, it will be used; otherwise creation of the service will fail. This field is conditionally mutable: it allows for adding or removing a secondary IP family, but it does not allow changing the primary IP family of the Service.
diff --git a/docs/resources/service_v1.md b/docs/resources/service_v1.md
index 006428ce..57598475 100644
--- a/docs/resources/service_v1.md
+++ b/docs/resources/service_v1.md
@@ -56,6 +56,7 @@ Optional:
 - `external_ips` (Set of String) A list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP.  A common example is external load-balancers that are not part of the Kubernetes system.
 - `external_name` (String) The external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires `type` to be `ExternalName`.
 - `external_traffic_policy` (String) Denotes if this Service desires to route external traffic to node-local or cluster-wide endpoints. `Local` preserves the client source IP and avoids a second hop for LoadBalancer and Nodeport type services, but risks potentially imbalanced traffic spreading. `Cluster` obscures the client source IP and may cause a second hop to another node, but should have good overall load-spreading. More info: https://kubernetes.io/docs/tutorials/services/source-ip/
+- `traffic_distribution` (String) Specifies the preferred strategy for distributing traffic to Service endpoints. When set to PreferClose, the Kubernetes will prioritize routing traffic to endpoints that are topologically closer
 - `health_check_node_port` (Number) Specifies the Healthcheck NodePort for the service. Only effects when type is set to `LoadBalancer` and external_traffic_policy is set to `Local`.
 - `internal_traffic_policy` (String) Specifies if the cluster internal traffic should be routed to all endpoints or node-local endpoints only. `Cluster` routes internal traffic to a Service to all endpoints. `Local` routes traffic to node-local endpoints only, traffic is dropped if no node-local endpoints are ready. The default value is `Cluster`.
 - `ip_families` (List of String) IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this service. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. If this field is specified manually, the requested family is available in the cluster, and ipFamilyPolicy allows it, it will be used; otherwise creation of the service will fail. This field is conditionally mutable: it allows for adding or removing a secondary IP family, but it does not allow changing the primary IP family of the Service.
diff --git a/kubernetes/resource_kubernetes_service_v1.go b/kubernetes/resource_kubernetes_service_v1.go
index 934f0380..5628a8e4 100644
--- a/kubernetes/resource_kubernetes_service_v1.go
+++ b/kubernetes/resource_kubernetes_service_v1.go
@@ -268,6 +268,14 @@ func resourceKubernetesServiceSchemaV1() map[string]*schema.Schema {
 							},
 						},
 					},
+					"traffic_distribution": {
+						Type:        schema.TypeString,
+						Description: "Specifies the preferred strategy for distributing traffic to Service endpoints. When set to PreferClose, the Kubernetes will prioritize routing traffic to endpoints that are topologically closer",
+						Optional:    true,
+						ValidateFunc: validation.StringInSlice([]string{
+							corev1.ServiceTrafficDistributionPreferClose,
+						}, false),
+					},
 					"type": {
 						Type:        schema.TypeString,
 						Description: "Determines how the service is exposed. Defaults to `ClusterIP`. Valid options are `ExternalName`, `ClusterIP`, `NodePort`, and `LoadBalancer`. `ExternalName` maps to the specified `external_name`. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types",
diff --git a/kubernetes/structure_service_spec.go b/kubernetes/structure_service_spec.go
index ab0b8696..68b0f4c2 100644
--- a/kubernetes/structure_service_spec.go
+++ b/kubernetes/structure_service_spec.go
@@ -116,6 +116,9 @@ func flattenServiceSpec(in v1.ServiceSpec) []interface{} {
 	if in.ExternalTrafficPolicy != "" {
 		att["external_traffic_policy"] = string(in.ExternalTrafficPolicy)
 	}
+	if in.TrafficDistribution != nil {
+		att["traffic_distribution"] = *in.TrafficDistribution
+	}
 
 	att["health_check_node_port"] = int(in.HealthCheckNodePort)
 
@@ -281,7 +284,9 @@ func expandServiceSpec(l []interface{}) v1.ServiceSpec {
 	if v, ok := in["health_check_node_port"].(int); ok {
 		obj.HealthCheckNodePort = int32(v)
 	}
-
+	if v, ok := in["traffic_distribution"].(string); ok && v != "" {
+		obj.TrafficDistribution = &v
+	}
 	return obj
 }
 
@@ -427,5 +432,12 @@ func patchServiceSpec(keyPrefix, pathPrefix string, d *schema.ResourceData, kv *
 			Value: int32(d.Get(keyPrefix + "health_check_node_port").(int)),
 		})
 	}
+	if d.HasChange(keyPrefix + "traffic_distribution") {
+		ops = append(ops, &ReplaceOperation{
+			Path:  pathPrefix + "trafficDistribution",
+			Value: d.Get(keyPrefix + "traffic_distribution").(string),
+		})
+	}
+
 	return ops
 }



Test patch:
diff --git a/kubernetes/resource_kubernetes_service_v1_test.go b/kubernetes/resource_kubernetes_service_v1_test.go
index 0cbf4b4b..166c891f 100644
--- a/kubernetes/resource_kubernetes_service_v1_test.go
+++ b/kubernetes/resource_kubernetes_service_v1_test.go
@@ -819,6 +819,39 @@ func TestAccKubernetesServiceV1_ipFamilies(t *testing.T) {
 	})
 }
 
+func TestAccKubernetesServiceV1_trafficDistribution(t *testing.T) {
+	var conf corev1.Service
+	name := acctest.RandomWithPrefix("tf-acc-test")
+	resourceName := "kubernetes_service_v1.test"
+
+	resource.ParallelTest(t, resource.TestCase{
+		PreCheck: func() {
+			testAccPreCheck(t)
+			skipIfClusterVersionLessThan(t, "1.31")
+		},
+		IDRefreshIgnore:   []string{"metadata.0.resource_version"},
+		ProviderFactories: testAccProviderFactories,
+		CheckDestroy:      testAccCheckKubernetesServiceV1Destroy,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccKubernetesServiceV1Config_trafficDistribution(name, "PreferClose"),
+				Check: resource.ComposeAggregateTestCheckFunc(
+					testAccCheckKubernetesServiceV1Exists(resourceName, &conf),
+					resource.TestCheckResourceAttr(resourceName, "metadata.0.name", name),
+					resource.TestCheckResourceAttr(resourceName, "spec.#", "1"),
+					resource.TestCheckResourceAttr(resourceName, "spec.0.traffic_distribution", "PreferClose"),
+				),
+			},
+			{
+				ResourceName:            resourceName,
+				ImportState:             true,
+				ImportStateVerify:       true,
+				ImportStateVerifyIgnore: []string{"metadata.0.resource_version", "wait_for_load_balancer"},
+			},
+		},
+	})
+}
+
 func testAccCheckServiceV1Ports(svc *corev1.Service, expected []corev1.ServicePort) resource.TestCheckFunc {
 	return func(s *terraform.State) error {
 		if len(expected) == 0 && len(svc.Spec.Ports) == 0 {
@@ -1417,3 +1450,26 @@ func testAccKubernetesServiceV1ConfigV1_ipFamilies(prefix string) string {
 }
 `, prefix)
 }
+
+func testAccKubernetesServiceV1Config_trafficDistribution(name, trafficDistribution string) string {
+	return fmt.Sprintf(`
+resource "kubernetes_service_v1" "test" {
+  metadata {
+    name = "%s"
+  }
+
+  spec {
+    traffic_distribution = "%s"
+
+    port {
+      port        = 8080
+      target_port = 80
+    }
+
+    selector = {
+      app = "MyApp"
+    }
+  }
+}
+`, name, trafficDistribution)
+}