From d73215358267543f9097a47c66abe2db2404ddb6 Mon Sep 17 00:00:00 2001 From: Aleksandr Rybolovlev Date: Tue, 26 Jul 2022 15:53:15 +0200 Subject: [PATCH] Update resource and data of 'kubernetes_(default_)service_account' to handle deprecated 'default_secret_name' in Kubernetes 1.24.0+ --- .../data_source_kubernetes_service_account.go | 5 +- ..._source_kubernetes_service_account_test.go | 13 ++--- kubernetes/provider.go | 24 +++++++++ ...urce_kubernetes_default_service_account.go | 1 - kubernetes/resource_kubernetes_service.go | 2 +- .../resource_kubernetes_service_account.go | 47 ++++++++++++++-- ...esource_kubernetes_service_account_test.go | 53 ++++++++++++++++++- kubernetes/structure_service_spec.go | 11 ++-- 8 files changed, 132 insertions(+), 24 deletions(-) diff --git a/kubernetes/data_source_kubernetes_service_account.go b/kubernetes/data_source_kubernetes_service_account.go index 45f92ac1c7..77ee53f616 100644 --- a/kubernetes/data_source_kubernetes_service_account.go +++ b/kubernetes/data_source_kubernetes_service_account.go @@ -48,8 +48,9 @@ func dataSourceKubernetesServiceAccount() *schema.Resource { Computed: true, }, "default_secret_name": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Computed: true, + Deprecated: "Starting from version 1.24.0 Kubernetes does not automatically generate a token for service accounts, in this case, `default_secret_name` will be empty and deprecated in future", }, }, } diff --git a/kubernetes/data_source_kubernetes_service_account_test.go b/kubernetes/data_source_kubernetes_service_account_test.go index ce6f4f05a6..02743c08ff 100644 --- a/kubernetes/data_source_kubernetes_service_account_test.go +++ b/kubernetes/data_source_kubernetes_service_account_test.go @@ -25,7 +25,6 @@ func TestAccKubernetesDataSourceServiceAccount_basic(t *testing.T) { resource.TestCheckResourceAttr("kubernetes_service_account.test", "secret.0.name", name+"-secret"), resource.TestCheckResourceAttr("kubernetes_service_account.test", "image_pull_secret.0.name", name+"-image-pull-secret"), resource.TestCheckResourceAttr("kubernetes_service_account.test", "automount_service_account_token", "true"), - resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "default_secret_name"), ), }, { @@ -38,7 +37,6 @@ func TestAccKubernetesDataSourceServiceAccount_basic(t *testing.T) { resource.TestCheckResourceAttr("data.kubernetes_service_account.test", "secret.0.name", name+"-secret"), resource.TestCheckResourceAttr("data.kubernetes_service_account.test", "image_pull_secret.0.name", name+"-image-pull-secret"), resource.TestCheckResourceAttr("data.kubernetes_service_account.test", "automount_service_account_token", "true"), - resource.TestCheckResourceAttrSet("data.kubernetes_service_account.test", "default_secret_name"), ), }, }, @@ -49,18 +47,21 @@ func TestAccKubernetesDataSourceServiceAccount_default_secret(t *testing.T) { name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + skipIfClusterVersionGreaterThanOrEqual(t, "1.24.0") + }, ProviderFactories: testAccProviderFactories, Steps: []resource.TestStep{ { - Config: testAccKubernetesServiceAccountConfig_default_secret(name), + Config: testAccKubernetesDataSourceServiceAccountConfig_default_secret(name), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.name", name), resource.TestCheckResourceAttr("kubernetes_service_account.test", "secret.#", "1"), ), }, { - Config: testAccKubernetesServiceAccountConfig_default_secret(name) + + Config: testAccKubernetesDataSourceServiceAccountConfig_default_secret(name) + testAccKubernetesDataSourceServiceAccountConfig_default_secret_read(name), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("data.kubernetes_service_account.test", "metadata.0.name", name), @@ -117,7 +118,7 @@ func testAccKubernetesDataSourceServiceAccountConfig_read() string { `) } -func testAccKubernetesServiceAccountConfig_default_secret(name string) string { +func testAccKubernetesDataSourceServiceAccountConfig_default_secret(name string) string { return fmt.Sprintf(` variable "token_name" { default = "%s-token-test0" diff --git a/kubernetes/provider.go b/kubernetes/provider.go index 1ab078094e..912262c4de 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -11,6 +11,7 @@ import ( "strconv" "github.com/hashicorp/go-cty/cty" + gversion "github.com/hashicorp/go-version" "github.com/mitchellh/go-homedir" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -633,3 +634,26 @@ func useAdmissionregistrationV1beta1(conn *kubernetes.Clientset) (bool, error) { useadmissionregistrationv1beta1 = ptrToBool(true) return true, nil } + +func getServerVersion(connection *kubernetes.Clientset) (*gversion.Version, error) { + sv, err := connection.ServerVersion() + if err != nil { + return nil, err + } + + return gversion.NewVersion(sv.String()) +} + +func serverVersionGreaterThanOrEqual(connection *kubernetes.Clientset, version string) (bool, error) { + sv, err := getServerVersion(connection) + if err != nil { + return false, err + } + // server version that we need to compare with + cv, err := gversion.NewVersion(version) + if err != nil { + return false, err + } + + return sv.GreaterThanOrEqual(cv), nil +} diff --git a/kubernetes/resource_kubernetes_default_service_account.go b/kubernetes/resource_kubernetes_default_service_account.go index 5f6f98acb0..ce80196fb5 100644 --- a/kubernetes/resource_kubernetes_default_service_account.go +++ b/kubernetes/resource_kubernetes_default_service_account.go @@ -55,7 +55,6 @@ func resourceKubernetesDefaultServiceAccountCreate(ctx context.Context, d *schem log.Printf("[INFO] Default service account exists: %s", metadata.Namespace) return nil }) - if err != nil { return diag.FromErr(err) } diff --git a/kubernetes/resource_kubernetes_service.go b/kubernetes/resource_kubernetes_service.go index f870ba34c3..e5ba21db4e 100644 --- a/kubernetes/resource_kubernetes_service.go +++ b/kubernetes/resource_kubernetes_service.go @@ -442,7 +442,7 @@ func resourceKubernetesServiceUpdate(ctx context.Context, d *schema.ResourceData ops := patchMetadata("metadata.0.", "/metadata/", d) if d.HasChange("spec") { - serverVersion, err := conn.ServerVersion() + serverVersion, err := getServerVersion(conn) if err != nil { return diag.FromErr(err) } diff --git a/kubernetes/resource_kubernetes_service_account.go b/kubernetes/resource_kubernetes_service_account.go index 8463061578..647ad450ca 100644 --- a/kubernetes/resource_kubernetes_service_account.go +++ b/kubernetes/resource_kubernetes_service_account.go @@ -69,8 +69,9 @@ func resourceKubernetesServiceAccount() *schema.Resource { Default: true, }, "default_secret_name": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Computed: true, + Deprecated: "Starting from version 1.24.0 Kubernetes does not automatically generate a token for service accounts, in this case, `default_secret_name` will be empty and deprecated in future", }, }, } @@ -106,12 +107,21 @@ func resourceKubernetesServiceAccountCreate(ctx context.Context, d *schema.Resou if err != nil { return diag.FromErr(err) } + return resourceKubernetesServiceAccountRead(ctx, d, meta) } func getServiceAccountDefaultSecret(ctx context.Context, name string, config api.ServiceAccount, timeout time.Duration, conn *kubernetes.Clientset) (*api.Secret, error) { + b, err := serverVersionGreaterThanOrEqual(conn, "1.24.0") + if err != nil { + return &api.Secret{}, err + } + if b { + return &api.Secret{}, nil + } + var svcAccTokens []api.Secret - err := resource.RetryContext(ctx, timeout, func() *resource.RetryError { + err = resource.RetryContext(ctx, timeout, func() *resource.RetryError { resp, err := conn.CoreV1().ServiceAccounts(config.Namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return resource.NonRetryableError(err) @@ -167,6 +177,20 @@ func findDefaultServiceAccount(ctx context.Context, sa *api.ServiceAccount, conn */ ds := make([]string, 0) + b, err := serverVersionGreaterThanOrEqual(conn, "1.24.0") + if err != nil { + return "", diag.FromErr(err) + } + if b { + return "", diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "'default_secret_name' is no longer applicable for Kubernetes 'v1.24.0' and above", + Detail: "Starting from version 1.24.0 Kubernetes does not automatically generate a token for service accounts, in this case, `default_secret_name` will be empty", + }, + } + } + for _, saSecret := range sa.Secrets { if !strings.HasPrefix(saSecret.Name, fmt.Sprintf("%s-token-", sa.Name)) { log.Printf("[DEBUG] Skipping %s as it doesn't have the right name", saSecret.Name) @@ -237,6 +261,9 @@ func diffObjectReferences(origOrs []api.ObjectReference, ors []api.ObjectReferen } func resourceKubernetesServiceAccountRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + + var diagMessages []diag.Diagnostic + exists, err := resourceKubernetesServiceAccountExists(ctx, d, meta) if err != nil { return diag.FromErr(err) @@ -284,6 +311,17 @@ func resourceKubernetesServiceAccountRead(ctx context.Context, d *schema.Resourc } defaultSecretName := d.Get("default_secret_name").(string) + b, err := serverVersionGreaterThanOrEqual(conn, "1.24.0") + if err != nil { + return diag.FromErr(err) + } + if b { + diagMessages = append(diagMessages, diag.Diagnostic{ + Severity: diag.Warning, + Summary: "'default_secret_name' is no longer applicable for Kubernetes 'v1.24.0' and above", + Detail: "Starting from version 1.24.0 Kubernetes does not automatically generate a token for service accounts, in this case, `default_secret_name` will be empty", + }) + } log.Printf("[DEBUG] Default secret name is %q", defaultSecretName) secrets := flattenServiceAccountSecrets(svcAcc.Secrets, defaultSecretName) log.Printf("[DEBUG] Flattened secrets: %#v", secrets) @@ -292,7 +330,7 @@ func resourceKubernetesServiceAccountRead(ctx context.Context, d *schema.Resourc return diag.FromErr(err) } - return nil + return diagMessages } func resourceKubernetesServiceAccountUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -415,6 +453,7 @@ func resourceKubernetesServiceAccountImportState(ctx context.Context, d *schema. if err != nil { return nil, fmt.Errorf("Unable to set default_secret_name: %s", err) } + d.SetId(buildId(sa.ObjectMeta)) return []*schema.ResourceData{d}, nil diff --git a/kubernetes/resource_kubernetes_service_account_test.go b/kubernetes/resource_kubernetes_service_account_test.go index fb6105c207..c4a57a0e4e 100644 --- a/kubernetes/resource_kubernetes_service_account_test.go +++ b/kubernetes/resource_kubernetes_service_account_test.go @@ -63,6 +63,38 @@ func TestAccKubernetesServiceAccount_basic(t *testing.T) { }) } +func TestAccKubernetesServiceAccount_default_secret(t *testing.T) { + var conf api.ServiceAccount + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + resourceName := "kubernetes_service_account_v1.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + skipIfClusterVersionGreaterThanOrEqual(t, "1.24.0") + }, + IDRefreshName: resourceName, + ProviderFactories: testAccProviderFactories, + CheckDestroy: testAccCheckKubernetesServiceAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesServiceAccountConfig_default_secret(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesServiceAccountExists(resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "metadata.0.name", name), + resource.TestCheckResourceAttrSet(resourceName, "default_secret_name"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"metadata.0.resource_version", "automount_service_account_token"}, + }, + }, + }) +} + func TestAccKubernetesServiceAccount_automount(t *testing.T) { var conf api.ServiceAccount name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) @@ -187,7 +219,7 @@ func TestAccKubernetesServiceAccount_update(t *testing.T) { resource.TestCheckResourceAttr("kubernetes_service_account.test", "image_pull_secret.#", "0"), resource.TestCheckResourceAttr("kubernetes_service_account.test", "automount_service_account_token", "true"), testAccCheckServiceAccountImagePullSecrets(&conf, []*regexp.Regexp{}), - testAccCheckServiceAccountSecrets(&conf, []*regexp.Regexp{ + testAccCheckServiceAccountDefaultSecrets(&conf, []*regexp.Regexp{ regexp.MustCompile("^" + name + "-token-[a-z0-9]+$"), }), ), @@ -219,7 +251,7 @@ func TestAccKubernetesServiceAccount_generatedName(t *testing.T) { resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.uid"), resource.TestCheckResourceAttr("kubernetes_service_account.test", "automount_service_account_token", "true"), testAccCheckServiceAccountImagePullSecrets(&conf, []*regexp.Regexp{}), - testAccCheckServiceAccountSecrets(&conf, []*regexp.Regexp{ + testAccCheckServiceAccountDefaultSecrets(&conf, []*regexp.Regexp{ regexp.MustCompile("^" + prefix + "[a-z0-9]+-token-[a-z0-9]+$"), }), ), @@ -255,6 +287,15 @@ func matchLocalObjectReferenceName(lor []api.LocalObjectReference, expected []*r return false } +func testAccCheckServiceAccountDefaultSecrets(m *api.ServiceAccount, expected []*regexp.Regexp) resource.TestCheckFunc { + if clusterVersionGreaterThanOrEqual("1.24.0") { + return func(s *terraform.State) error { + return nil + } + } + return testAccCheckServiceAccountSecrets(m, expected) +} + func testAccCheckServiceAccountSecrets(m *api.ServiceAccount, expected []*regexp.Regexp) resource.TestCheckFunc { return func(s *terraform.State) error { if len(expected) == 0 && len(m.Secrets) == 0 { @@ -397,6 +438,14 @@ resource "kubernetes_secret" "four" { `, name, name, name, name, name) } +func testAccKubernetesServiceAccountConfig_default_secret(name string) string { + return fmt.Sprintf(`resource "kubernetes_service_account_v1" "test" { + metadata { + name = "%s" + } +}`, name) +} + func testAccKubernetesServiceAccountConfig_modified(name string) string { return fmt.Sprintf(`resource "kubernetes_service_account" "test" { metadata { diff --git a/kubernetes/structure_service_spec.go b/kubernetes/structure_service_spec.go index a35449ce31..8dbcd1c3e3 100644 --- a/kubernetes/structure_service_spec.go +++ b/kubernetes/structure_service_spec.go @@ -5,7 +5,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/version" ) // Flatteners @@ -284,7 +283,7 @@ func expandServiceSpec(l []interface{}) v1.ServiceSpec { // Patch Ops -func patchServiceSpec(keyPrefix, pathPrefix string, d *schema.ResourceData, v *version.Info) (PatchOperations, error) { +func patchServiceSpec(keyPrefix, pathPrefix string, d *schema.ResourceData, kv *gversion.Version) (PatchOperations, error) { ops := make([]PatchOperation, 0, 0) if d.HasChange(keyPrefix + "allocate_load_balancer_node_ports") { @@ -360,12 +359,8 @@ func patchServiceSpec(keyPrefix, pathPrefix string, d *schema.ResourceData, v *v }) } if d.HasChange(keyPrefix + "external_ips") { - k8sVersion, err := gversion.NewVersion(v.String()) - if err != nil { - return nil, err - } - v1_8_0, _ := gversion.NewVersion("1.8.0") - if k8sVersion.LessThan(v1_8_0) { + version, _ := gversion.NewVersion("1.8.0") + if kv.LessThan(version) { // If we haven't done this the deprecated field would have priority ops = append(ops, &ReplaceOperation{ Path: pathPrefix + "deprecatedPublicIPs",