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()) }