diff --git a/.changelog/2577.txt b/.changelog/2577.txt
new file mode 100644
index 0000000000..6220a04b64
--- /dev/null
+++ b/.changelog/2577.txt
@@ -0,0 +1,3 @@
+```release-note:enhancement
+Added support for `namespace_selector` field in `PodAffinityTerm` to enhance pod affinity and anti-affinity rules, allowing selection of namespaces based on label selectors.
+```
\ No newline at end of file
diff --git a/docs/resources/deployment_v1.md b/docs/resources/deployment_v1.md
index fbff60a8ef..5b8011dfbb 100644
--- a/docs/resources/deployment_v1.md
+++ b/docs/resources/deployment_v1.md
@@ -248,10 +248,12 @@ Required:
Optional:
- `label_selector` (Block List) A label query over a set of resources, in this case pods. (see [below for nested schema](#nestedblock--spec--template--spec--affinity--pod_affinity--preferred_during_scheduling_ignored_during_execution--pod_affinity_term--label_selector))
+- `namespace_selector` (Block List) A label query over a set of namespaces. This allows pod anti-affinity to select pods from a specified namespace, based on namespace labels.
- `namespaces` (Set of String) namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means 'this pod's namespace'
-### Nested Schema for `spec.template.spec.affinity.pod_affinity.preferred_during_scheduling_ignored_during_execution.pod_affinity_term.label_selector`
+
+### Nested Schema for `namespace_selector` and `label_selector` in `spec.template.spec.affinity.pod_affinity.preferred_during_scheduling_ignored_during_execution.pod_affinity_term`
Optional:
@@ -281,10 +283,12 @@ Required:
Optional:
- `label_selector` (Block List) A label query over a set of resources, in this case pods. (see [below for nested schema](#nestedblock--spec--template--spec--affinity--pod_affinity--required_during_scheduling_ignored_during_execution--label_selector))
+- `namespace_selector` (Block List) A label query over a set of namespaces. This allows pod anti-affinity to select pods from a specified namespace, based on namespace labels.
- `namespaces` (Set of String) namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means 'this pod's namespace'
-### Nested Schema for `spec.template.spec.affinity.pod_affinity.required_during_scheduling_ignored_during_execution.label_selector`
+
+### Nested Schema for `namespace_selector` and `label_selector` in `spec.template.spec.affinity.pod_affinity.required_during_scheduling_ignored_during_execution`
Optional:
@@ -330,10 +334,12 @@ Required:
Optional:
- `label_selector` (Block List) A label query over a set of resources, in this case pods. (see [below for nested schema](#nestedblock--spec--template--spec--affinity--pod_anti_affinity--preferred_during_scheduling_ignored_during_execution--pod_affinity_term--label_selector))
+- `namespace_selector` (Block List) A label query over a set of namespaces. This allows pod anti-affinity to select pods from a specified namespace, based on namespace labels.
- `namespaces` (Set of String) namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means 'this pod's namespace'
-### Nested Schema for `spec.template.spec.affinity.pod_anti_affinity.preferred_during_scheduling_ignored_during_execution.pod_affinity_term.label_selector`
+
+### Nested Schema for `namespace_selector` and `label_selector` in `spec.template.spec.affinity.pod_anti_affinity.preferred_during_scheduling_ignored_during_execution.pod_affinity_term`
Optional:
@@ -363,10 +369,12 @@ Required:
Optional:
- `label_selector` (Block List) A label query over a set of resources, in this case pods. (see [below for nested schema](#nestedblock--spec--template--spec--affinity--pod_anti_affinity--required_during_scheduling_ignored_during_execution--label_selector))
+- `namespace_selector` (Block List) A label query over a set of namespaces. This allows pod anti-affinity to select pods from a specified namespace, based on namespace labels.
- `namespaces` (Set of String) namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means 'this pod's namespace'
-### Nested Schema for `spec.template.spec.affinity.pod_anti_affinity.required_during_scheduling_ignored_during_execution.label_selector`
+
+### Nested Schema for `namespace_selector` and `label_selector` in `spec.template.spec.affinity.pod_anti_affinity.required_during_scheduling_ignored_during_execution`
Optional:
diff --git a/docs/resources/stateful_set_v1.md b/docs/resources/stateful_set_v1.md
index c8b68bc5c6..ae02088943 100644
--- a/docs/resources/stateful_set_v1.md
+++ b/docs/resources/stateful_set_v1.md
@@ -272,10 +272,12 @@ Required:
Optional:
- `label_selector` (Block List) A label query over a set of resources, in this case pods. (see [below for nested schema](#nestedblock--spec--template--spec--affinity--pod_affinity--preferred_during_scheduling_ignored_during_execution--pod_affinity_term--label_selector))
+- `namespace_selector` (Block List) A label query over a set of namespaces. This allows pod affinity to select pods from a specified namespace, based on namespace labels.
- `namespaces` (Set of String) namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means 'this pod's namespace'
-### Nested Schema for `spec.template.spec.affinity.pod_affinity.preferred_during_scheduling_ignored_during_execution.pod_affinity_term.label_selector`
+
+### Nested Schema for `namespace_selector` and `label_selector` in `spec.template.spec.affinity.pod_affinity.preferred_during_scheduling_ignored_during_execution.pod_affinity_term`
Optional:
@@ -305,10 +307,12 @@ Required:
Optional:
- `label_selector` (Block List) A label query over a set of resources, in this case pods. (see [below for nested schema](#nestedblock--spec--template--spec--affinity--pod_affinity--required_during_scheduling_ignored_during_execution--label_selector))
+- `namespace_selector` (Block List) A label query over a set of namespaces. This allows pod affinity to select pods from a specified namespace, based on namespace labels.
- `namespaces` (Set of String) namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means 'this pod's namespace'
-### Nested Schema for `spec.template.spec.affinity.pod_affinity.required_during_scheduling_ignored_during_execution.label_selector`
+
+### Nested Schema for `namespace_selector` and `label_selector` in `spec.template.spec.affinity.pod_affinity.required_during_scheduling_ignored_during_execution`
Optional:
@@ -354,10 +358,12 @@ Required:
Optional:
- `label_selector` (Block List) A label query over a set of resources, in this case pods. (see [below for nested schema](#nestedblock--spec--template--spec--affinity--pod_anti_affinity--preferred_during_scheduling_ignored_during_execution--pod_affinity_term--label_selector))
+- `namespace_selector` (Block List) A label query over a set of namespaces. This allows pod anti-affinity to select pods from a specified namespace, based on namespace labels.
- `namespaces` (Set of String) namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means 'this pod's namespace'
-### Nested Schema for `spec.template.spec.affinity.pod_anti_affinity.preferred_during_scheduling_ignored_during_execution.pod_affinity_term.label_selector`
+
+### Nested Schema for `namespace_selector` and `label_selector` in `spec.template.spec.affinity.pod_anti_affinity.preferred_during_scheduling_ignored_during_execution.pod_affinity_term`
Optional:
@@ -387,10 +393,12 @@ Required:
Optional:
- `label_selector` (Block List) A label query over a set of resources, in this case pods. (see [below for nested schema](#nestedblock--spec--template--spec--affinity--pod_anti_affinity--required_during_scheduling_ignored_during_execution--label_selector))
+- `namespace_selector` (Block List) A label query over a set of namespaces. This allows pod anti-affinity to select pods from a specified namespace, based on namespace labels.
- `namespaces` (Set of String) namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means 'this pod's namespace'
-### Nested Schema for `spec.template.spec.affinity.pod_anti_affinity.required_during_scheduling_ignored_during_execution.label_selector`
+
+### Nested Schema for `namespace_selector` and `label_selector` in `spec.template.spec.affinity.pod_anti_affinity.required_during_scheduling_ignored_during_execution`
Optional:
diff --git a/kubernetes/resource_kubernetes_pod_v1_affinity_test.go b/kubernetes/resource_kubernetes_pod_v1_affinity_test.go
index 33347554ee..d4f9cc9fd2 100644
--- a/kubernetes/resource_kubernetes_pod_v1_affinity_test.go
+++ b/kubernetes/resource_kubernetes_pod_v1_affinity_test.go
@@ -142,6 +142,12 @@ func TestAccKubernetesPodV1_with_pod_affinity_with_required_during_scheduling_ig
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.label_selector.0.match_expressions.0.values.0", keyName), "bar"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.label_selector.0.match_expressions.0.values.1", keyName), "foo"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.label_selector.0.match_labels.%%", keyName), "0"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.0.match_expressions.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.0.match_expressions.0.key", keyName), "environment"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.0.match_expressions.0.operator", keyName), "In"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.0.match_expressions.0.values.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.0.match_expressions.0.values.0", keyName), "production"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespaces.#", keyName), "0"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.topology_key", keyName), "kubernetes.io/hostname"),
),
@@ -178,6 +184,12 @@ func TestAccKubernetesPodV1_with_pod_affinity_with_preferred_during_scheduling_i
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.label_selector.0.match_expressions.0.values.0", keyName), "bar"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.label_selector.0.match_expressions.0.values.1", keyName), "foo"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.label_selector.0.match_labels.%%", keyName), "0"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.0.match_expressions.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.0.match_expressions.0.key", keyName), "environment"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.0.match_expressions.0.operator", keyName), "In"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.0.match_expressions.0.values.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.0.match_expressions.0.values.0", keyName), "production"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespaces.#", keyName), "1"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespaces.0", keyName), "default"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.topology_key", keyName), "kubernetes.io/hostname"),
@@ -215,6 +227,12 @@ func TestAccKubernetesPodV1_with_pod_anti_affinity_with_required_during_scheduli
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.label_selector.0.match_expressions.0.values.0", keyName), "bar"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.label_selector.0.match_expressions.0.values.1", keyName), "foo"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.label_selector.0.match_labels.%%", keyName), "0"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.0.match_expressions.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.0.match_expressions.0.key", keyName), "environment"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.0.match_expressions.0.operator", keyName), "In"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.0.match_expressions.0.values.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespace_selector.0.match_expressions.0.values.0", keyName), "production"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.namespaces.#", keyName), "0"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.topology_key", keyName), "kubernetes.io/hostname"),
),
@@ -251,6 +269,12 @@ func TestAccKubernetesPodV1_with_pod_anti_affinity_with_preferred_during_schedul
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.label_selector.0.match_expressions.0.values.0", keyName), "bar"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.label_selector.0.match_expressions.0.values.1", keyName), "foo"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.label_selector.0.match_labels.%%", keyName), "0"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.0.match_expressions.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.0.match_expressions.0.key", keyName), "environment"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.0.match_expressions.0.operator", keyName), "In"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.0.match_expressions.0.values.#", keyName), "1"),
+ resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespace_selector.0.match_expressions.0.values.0", keyName), "production"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.namespaces.#", keyName), "0"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.pod_affinity_term.0.topology_key", keyName), "kubernetes.io/hostname"),
resource.TestCheckResourceAttr(resourceName, fmt.Sprintf("%s.0.weight", keyName), "100"),
@@ -460,6 +484,9 @@ func testAccKubernetesPodV1ConfigWithPodAffinityWithRequiredDuringSchedulingIgno
return fmt.Sprintf(`resource "kubernetes_namespace_v1" "test" {
metadata {
name = %[1]q
+ labels = {
+ environment = "production"
+ }
}
}
@@ -482,6 +509,13 @@ resource "kubernetes_pod_v1" "test" {
values = ["foo", "bar"]
}
}
+ namespace_selector {
+ match_expressions {
+ key = "environment"
+ operator = "In"
+ values = ["production"]
+ }
+ }
topology_key = "kubernetes.io/hostname"
}
}
@@ -507,6 +541,9 @@ func testAccKubernetesPodV1ConfigWithPodAffinityWithPreferredDuringSchedulingIgn
return fmt.Sprintf(`resource "kubernetes_namespace_v1" "test" {
metadata {
name = %[1]q
+ labels = {
+ environment = "production"
+ }
}
}
@@ -531,6 +568,13 @@ resource "kubernetes_pod_v1" "test" {
values = ["foo", "bar"]
}
}
+ namespace_selector {
+ match_expressions {
+ key = "environment"
+ operator = "In"
+ values = ["production"]
+ }
+ }
namespaces = ["default"]
topology_key = "kubernetes.io/hostname"
}
@@ -558,6 +602,9 @@ func testAccKubernetesPodV1ConfigWithPodAntiAffinityWithRequiredDuringScheduling
return fmt.Sprintf(`resource "kubernetes_namespace_v1" "test" {
metadata {
name = %[1]q
+ labels = {
+ environment = "production"
+ }
}
}
@@ -580,6 +627,13 @@ resource "kubernetes_pod_v1" "test" {
values = ["foo", "bar"]
}
}
+ namespace_selector {
+ match_expressions {
+ key = "environment"
+ operator = "In"
+ values = ["production"]
+ }
+ }
topology_key = "kubernetes.io/hostname"
}
}
@@ -605,6 +659,9 @@ func testAccKubernetesPodV1ConfigWithPodAntiAffinityWithPreferredDuringSchedulin
return fmt.Sprintf(`resource "kubernetes_namespace_v1" "test" {
metadata {
name = %[1]q
+ labels = {
+ environment = "production"
+ }
}
}
@@ -629,6 +686,13 @@ resource "kubernetes_pod_v1" "test" {
values = ["foo", "bar"]
}
}
+ namespace_selector {
+ match_expressions {
+ key = "environment"
+ operator = "In"
+ values = ["production"]
+ }
+ }
topology_key = "kubernetes.io/hostname"
}
}
diff --git a/kubernetes/resource_kubernetes_pod_v1_test.go b/kubernetes/resource_kubernetes_pod_v1_test.go
index 11ffe162d2..d1970deb96 100644
--- a/kubernetes/resource_kubernetes_pod_v1_test.go
+++ b/kubernetes/resource_kubernetes_pod_v1_test.go
@@ -10,12 +10,11 @@ import (
"regexp"
"testing"
- api "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
+ api "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestAccKubernetesPodV1_minimal(t *testing.T) {
diff --git a/kubernetes/schema_affinity_spec.go b/kubernetes/schema_affinity_spec.go
index 10f3029dc8..04111a0532 100644
--- a/kubernetes/schema_affinity_spec.go
+++ b/kubernetes/schema_affinity_spec.go
@@ -166,6 +166,14 @@ func podAffinityTermFields() map[string]*schema.Schema {
Schema: labelSelectorFields(true),
},
},
+ "namespace_selector": {
+ Type: schema.TypeList,
+ Description: "A label query over a set of namespaces that matches the namespaceSelector in Kubernetes.",
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: labelSelectorFields(true),
+ },
+ },
"namespaces": {
Type: schema.TypeSet,
Description: "namespaces specifies which namespaces the labelSelector applies to (matches against); null or empty list means 'this pod's namespace'",
diff --git a/kubernetes/structure_label_selector.go b/kubernetes/structure_label_selector.go
index f78e8ee2fa..8dac60fc75 100644
--- a/kubernetes/structure_label_selector.go
+++ b/kubernetes/structure_label_selector.go
@@ -21,6 +21,17 @@ func flattenLabelSelector(in *metav1.LabelSelector) []interface{} {
return []interface{}{att}
}
+func flattenNamespaceSelector(in *metav1.LabelSelector) []interface{} {
+ att := make(map[string]interface{})
+ if len(in.MatchLabels) > 0 {
+ att["match_labels"] = in.MatchLabels
+ }
+ if len(in.MatchExpressions) > 0 {
+ att["match_expressions"] = flattenLabelSelectorRequirement(in.MatchExpressions)
+ }
+ return []interface{}{att}
+}
+
func flattenLabelSelectorRequirement(in []metav1.LabelSelectorRequirement) []interface{} {
att := make([]interface{}, len(in))
for i, n := range in {
@@ -50,6 +61,24 @@ func expandLabelSelector(l []interface{}) *metav1.LabelSelector {
return obj
}
+func expandNamespaceSelector(n []interface{}) *metav1.LabelSelector {
+ if len(n) == 0 || n[0] == nil {
+ return &metav1.LabelSelector{}
+ }
+
+ in := n[0].(map[string]interface{})
+
+ obj := &metav1.LabelSelector{}
+ if v, ok := in["match_labels"].(map[string]interface{}); ok && len(v) > 0 {
+ obj.MatchLabels = expandStringMap(v)
+ }
+ //We are using labelSelector metav1, due to NamespaceSelector not existing as a type in metav1
+ if v, ok := in["match_expressions"].([]interface{}); ok && len(v) > 0 {
+ obj.MatchExpressions = expandLabelSelectorRequirement(v)
+ }
+ return obj
+}
+
func expandLabelSelectorRequirement(l []interface{}) []metav1.LabelSelectorRequirement {
if len(l) == 0 || l[0] == nil {
return []metav1.LabelSelectorRequirement{}
diff --git a/kubernetes/structures_affinity.go b/kubernetes/structures_affinity.go
index 366c7e2e65..778ff57f69 100644
--- a/kubernetes/structures_affinity.go
+++ b/kubernetes/structures_affinity.go
@@ -86,6 +86,9 @@ func flattenPodAffinityTerms(in []v1.PodAffinityTerm) []interface{} {
m := make(map[string]interface{})
m["namespaces"] = newStringSet(schema.HashString, n.Namespaces)
m["topology_key"] = n.TopologyKey
+ if n.NamespaceSelector != nil {
+ m["namespace_selector"] = flattenNamespaceSelector(n.NamespaceSelector)
+ }
if n.LabelSelector != nil {
m["label_selector"] = flattenLabelSelector(n.LabelSelector)
}
@@ -220,6 +223,9 @@ func expandPodAffinityTerms(t []interface{}) []v1.PodAffinityTerm {
if v, ok := in["label_selector"].([]interface{}); ok && len(v) > 0 {
obj[i].LabelSelector = expandLabelSelector(v)
}
+ if v, ok := in["namespace_selector"].([]interface{}); ok && len(v) > 0 {
+ obj[i].NamespaceSelector = expandNamespaceSelector(v)
+ }
if v, ok := in["namespaces"].(*schema.Set); ok {
obj[i].Namespaces = sliceOfString(v.List())
}