diff --git a/kubernetes/provider.go b/kubernetes/provider.go index e3f69f7026..3bdba74b2b 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -103,6 +103,7 @@ func Provider() terraform.ResourceProvider { "kubernetes_resource_quota": resourceKubernetesResourceQuota(), "kubernetes_secret": resourceKubernetesSecret(), "kubernetes_service": resourceKubernetesService(), + "kubernetes_service_account": resourceKubernetesServiceAccount(), }, ConfigureFunc: providerConfigure, } diff --git a/kubernetes/resource_kubernetes_service_account.go b/kubernetes/resource_kubernetes_service_account.go new file mode 100644 index 0000000000..b6d0f2e997 --- /dev/null +++ b/kubernetes/resource_kubernetes_service_account.go @@ -0,0 +1,220 @@ +package kubernetes + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + pkgApi "k8s.io/apimachinery/pkg/types" + api "k8s.io/kubernetes/pkg/api/v1" + kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" +) + +func resourceKubernetesServiceAccount() *schema.Resource { + return &schema.Resource{ + Create: resourceKubernetesServiceAccountCreate, + Read: resourceKubernetesServiceAccountRead, + Exists: resourceKubernetesServiceAccountExists, + Update: resourceKubernetesServiceAccountUpdate, + Delete: resourceKubernetesServiceAccountDelete, + + // This resource is not importable because the API doesn't offer + // any way to differentiate between default & user-defined secret + // after the account was created. + + Schema: map[string]*schema.Schema{ + "metadata": namespacedMetadataSchema("service account", true), + "image_pull_secret": { + Type: schema.TypeSet, + Description: "A list of references to secrets in the same namespace to use for pulling any images in pods that reference this Service Account. More info: http://kubernetes.io/docs/user-guide/secrets#manually-specifying-an-imagepullsecret", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names", + Optional: true, + }, + }, + }, + }, + "secret": { + Type: schema.TypeSet, + Description: "A list of secrets allowed to be used by pods running using this Service Account. More info: http://kubernetes.io/docs/user-guide/secrets", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names", + Optional: true, + }, + }, + }, + }, + "default_secret_name": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceKubernetesServiceAccountCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + metadata := expandMetadata(d.Get("metadata").([]interface{})) + svcAcc := api.ServiceAccount{ + AutomountServiceAccountToken: ptrToBool(false), + ObjectMeta: metadata, + ImagePullSecrets: expandLocalObjectReferenceArray(d.Get("image_pull_secret").(*schema.Set).List()), + Secrets: expandServiceAccountSecrets(d.Get("secret").(*schema.Set).List(), ""), + } + log.Printf("[INFO] Creating new service account: %#v", svcAcc) + out, err := conn.CoreV1().ServiceAccounts(metadata.Namespace).Create(&svcAcc) + if err != nil { + return err + } + log.Printf("[INFO] Submitted new service account: %#v", out) + d.SetId(buildId(out.ObjectMeta)) + + // Here we get the only chance to identify and store default secret name + // so we can avoid showing it in diff as it's not managed by Terraform + var resp *api.ServiceAccount + err = resource.Retry(30*time.Second, func() *resource.RetryError { + var err error + resp, err = conn.CoreV1().ServiceAccounts(out.Namespace).Get(out.Name, metav1.GetOptions{}) + if err != nil { + return resource.NonRetryableError(err) + } + if len(resp.Secrets) > len(svcAcc.Secrets) { + return nil + } + return resource.RetryableError(fmt.Errorf("Waiting for default secret of %q to appear", d.Id())) + }) + + diff := diffObjectReferences(svcAcc.Secrets, resp.Secrets) + if len(diff) > 1 { + return fmt.Errorf("Expected 1 generated default secret, %d found: %s", len(diff), diff) + } + + defaultSecret := diff[0] + d.Set("default_secret_name", defaultSecret.Name) + + return resourceKubernetesServiceAccountRead(d, meta) +} + +func diffObjectReferences(origOrs []api.ObjectReference, ors []api.ObjectReference) []api.ObjectReference { + var diff []api.ObjectReference + uniqueRefs := make(map[string]*api.ObjectReference, 0) + for _, or := range origOrs { + uniqueRefs[or.Name] = &or + } + + for _, or := range ors { + _, found := uniqueRefs[or.Name] + if !found { + diff = append(diff, or) + } + } + + return diff +} + +func resourceKubernetesServiceAccountRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Reading service account %s", name) + svcAcc, err := conn.CoreV1().ServiceAccounts(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + log.Printf("[DEBUG] Received error: %#v", err) + return err + } + log.Printf("[INFO] Received service account: %#v", svcAcc) + err = d.Set("metadata", flattenMetadata(svcAcc.ObjectMeta)) + if err != nil { + return err + } + d.Set("image_pull_secret", flattenLocalObjectReferenceArray(svcAcc.ImagePullSecrets)) + + defaultSecretName := d.Get("default_secret_name").(string) + log.Printf("[DEBUG] Default secret name is %q", defaultSecretName) + secrets := flattenServiceAccountSecrets(svcAcc.Secrets, defaultSecretName) + log.Printf("[DEBUG] Flattened secrets: %#v", secrets) + d.Set("secret", secrets) + + return nil +} + +func resourceKubernetesServiceAccountUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + + ops := patchMetadata("metadata.0.", "/metadata/", d) + if d.HasChange("image_pull_secret") { + v := d.Get("image_pull_secret").(*schema.Set).List() + ops = append(ops, &ReplaceOperation{ + Path: "/imagePullSecrets", + Value: expandLocalObjectReferenceArray(v), + }) + } + if d.HasChange("secret") { + v := d.Get("secret").(*schema.Set).List() + defaultSecretName := d.Get("default_secret_name").(string) + + ops = append(ops, &ReplaceOperation{ + Path: "/secrets", + Value: expandServiceAccountSecrets(v, defaultSecretName), + }) + } + data, err := ops.MarshalJSON() + if err != nil { + return fmt.Errorf("Failed to marshal update operations: %s", err) + } + log.Printf("[INFO] Updating service account %q: %v", name, string(data)) + out, err := conn.CoreV1().ServiceAccounts(namespace).Patch(name, pkgApi.JSONPatchType, data) + if err != nil { + return fmt.Errorf("Failed to update service account: %s", err) + } + log.Printf("[INFO] Submitted updated service account: %#v", out) + d.SetId(buildId(out.ObjectMeta)) + + return resourceKubernetesServiceAccountRead(d, meta) +} + +func resourceKubernetesServiceAccountDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Deleting service account: %#v", name) + err := conn.CoreV1().ServiceAccounts(namespace).Delete(name, &metav1.DeleteOptions{}) + if err != nil { + return err + } + + log.Printf("[INFO] Service account %s deleted", name) + + d.SetId("") + return nil +} + +func resourceKubernetesServiceAccountExists(d *schema.ResourceData, meta interface{}) (bool, error) { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Checking service account %s", name) + _, err := conn.CoreV1().ServiceAccounts(namespace).Get(name, metav1.GetOptions{}) + if err != nil { + if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 { + return false, nil + } + log.Printf("[DEBUG] Received error: %#v", err) + } + return true, err +} diff --git a/kubernetes/resource_kubernetes_service_account_test.go b/kubernetes/resource_kubernetes_service_account_test.go new file mode 100644 index 0000000000..c0fa5444bc --- /dev/null +++ b/kubernetes/resource_kubernetes_service_account_test.go @@ -0,0 +1,405 @@ +package kubernetes + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + api "k8s.io/kubernetes/pkg/api/v1" + kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/clientset" +) + +func TestAccKubernetesServiceAccount_basic(t *testing.T) { + var conf api.ServiceAccount + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_service_account.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesServiceAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesServiceAccountConfig_basic(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesServiceAccountExists("kubernetes_service_account.test", &conf), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.annotations.%", "2"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.annotations.TestAnnotationOne", "one"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.annotations.TestAnnotationTwo", "two"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "TestAnnotationTwo": "two"}), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.%", "3"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.TestLabelOne", "one"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.TestLabelTwo", "two"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.TestLabelThree", "three"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "secret.#", "2"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "image_pull_secret.#", "2"), + testAccCheckServiceAccountImagePullSecrets(&conf, []*regexp.Regexp{ + regexp.MustCompile("^" + name + "-three$"), + regexp.MustCompile("^" + name + "-four$"), + }), + testAccCheckServiceAccountSecrets(&conf, []*regexp.Regexp{ + regexp.MustCompile("^" + name + "-one$"), + regexp.MustCompile("^" + name + "-two$"), + regexp.MustCompile("^" + name + "-token-[a-z0-9]+$"), + }), + ), + }, + }, + }) +} + +func TestAccKubernetesServiceAccount_update(t *testing.T) { + var conf api.ServiceAccount + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_service_account.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesServiceAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesServiceAccountConfig_basic(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesServiceAccountExists("kubernetes_service_account.test", &conf), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.annotations.%", "2"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.annotations.TestAnnotationOne", "one"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.annotations.TestAnnotationTwo", "two"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "TestAnnotationTwo": "two"}), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.%", "3"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.TestLabelOne", "one"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.TestLabelTwo", "two"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.TestLabelThree", "three"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "secret.#", "2"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "image_pull_secret.#", "2"), + testAccCheckServiceAccountImagePullSecrets(&conf, []*regexp.Regexp{ + regexp.MustCompile("^" + name + "-three$"), + regexp.MustCompile("^" + name + "-four$"), + }), + testAccCheckServiceAccountSecrets(&conf, []*regexp.Regexp{ + regexp.MustCompile("^" + name + "-one$"), + regexp.MustCompile("^" + name + "-two$"), + regexp.MustCompile("^" + name + "-token-[a-z0-9]+$"), + }), + ), + }, + { + Config: testAccKubernetesServiceAccountConfig_modified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesServiceAccountExists("kubernetes_service_account.test", &conf), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.annotations.%", "2"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.annotations.TestAnnotationOne", "one"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.annotations.Different", "1234"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "Different": "1234"}), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.%", "2"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.TestLabelOne", "one"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.TestLabelThree", "three"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelThree": "three"}), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "secret.#", "1"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "image_pull_secret.#", "3"), + testAccCheckServiceAccountImagePullSecrets(&conf, []*regexp.Regexp{ + regexp.MustCompile("^" + name + "-three$"), + regexp.MustCompile("^" + name + "-four$"), + }), + testAccCheckServiceAccountSecrets(&conf, []*regexp.Regexp{ + regexp.MustCompile("^" + name + "-one$"), + regexp.MustCompile("^" + name + "-two$"), + regexp.MustCompile("^" + name + "-token-[a-z0-9]+$"), + }), + ), + }, + { + Config: testAccKubernetesServiceAccountConfig_noAttributes(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesServiceAccountExists("kubernetes_service_account.test", &conf), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.annotations.%", "0"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "secret.#", "0"), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "image_pull_secret.#", "0"), + testAccCheckServiceAccountImagePullSecrets(&conf, []*regexp.Regexp{}), + testAccCheckServiceAccountSecrets(&conf, []*regexp.Regexp{ + regexp.MustCompile("^" + name + "-token-[a-z0-9]+$"), + }), + ), + }, + }, + }) +} + +func TestAccKubernetesServiceAccount_generatedName(t *testing.T) { + var conf api.ServiceAccount + prefix := "tf-acc-test-gen-" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_service_account.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesServiceAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesServiceAccountConfig_generatedName(prefix), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesServiceAccountExists("kubernetes_service_account.test", &conf), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_service_account.test", "metadata.0.generate_name", prefix), + resource.TestMatchResourceAttr("kubernetes_service_account.test", "metadata.0.name", regexp.MustCompile("^"+prefix)), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_service_account.test", "metadata.0.uid"), + testAccCheckServiceAccountImagePullSecrets(&conf, []*regexp.Regexp{}), + testAccCheckServiceAccountSecrets(&conf, []*regexp.Regexp{ + regexp.MustCompile("^" + prefix + "[a-z0-9]+-token-[a-z0-9]+$"), + }), + ), + }, + }, + }) +} + +func testAccCheckServiceAccountImagePullSecrets(m *api.ServiceAccount, expected []*regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(expected) == 0 && len(m.ImagePullSecrets) == 0 { + return nil + } + + if !matchLocalObjectReferenceName(m.ImagePullSecrets, expected) { + return fmt.Errorf("%s image pull secrets don't match.\nExpected: %q\nGiven: %q", + m.Name, expected, m.ImagePullSecrets) + } + + return nil + } +} + +func matchLocalObjectReferenceName(lor []api.LocalObjectReference, expected []*regexp.Regexp) bool { + for _, r := range expected { + for _, ps := range lor { + matched := r.MatchString(ps.Name) + if matched { + return true + } + } + } + return false +} + +func testAccCheckServiceAccountSecrets(m *api.ServiceAccount, expected []*regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + if len(expected) == 0 && len(m.Secrets) == 0 { + return nil + } + if !matchObjectReferenceName(m.Secrets, expected) { + return fmt.Errorf("%s secrets don't match.\nExpected: %q\nGiven: %q", + m.Name, expected, m.Secrets) + } + return nil + } +} + +func matchObjectReferenceName(lor []api.ObjectReference, expected []*regexp.Regexp) bool { + for _, r := range expected { + for _, ps := range lor { + matched := r.MatchString(ps.Name) + if matched { + return true + } + } + } + return false +} + +func testAccCheckKubernetesServiceAccountDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*kubernetes.Clientset) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "kubernetes_service_account" { + continue + } + namespace, name := idParts(rs.Primary.ID) + resp, err := conn.CoreV1().ServiceAccounts(namespace).Get(name, meta_v1.GetOptions{}) + if err == nil { + if resp.Name == rs.Primary.ID { + return fmt.Errorf("Service Account still exists: %s", rs.Primary.ID) + } + } + } + + return nil +} + +func testAccCheckKubernetesServiceAccountExists(n string, obj *api.ServiceAccount) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := testAccProvider.Meta().(*kubernetes.Clientset) + namespace, name := idParts(rs.Primary.ID) + out, err := conn.CoreV1().ServiceAccounts(namespace).Get(name, meta_v1.GetOptions{}) + if err != nil { + return err + } + + *obj = *out + return nil + } +} + +func testAccKubernetesServiceAccountConfig_basic(name string) string { + return fmt.Sprintf(` +resource "kubernetes_service_account" "test" { + metadata { + annotations { + TestAnnotationOne = "one" + TestAnnotationTwo = "two" + } + labels { + TestLabelOne = "one" + TestLabelTwo = "two" + TestLabelThree = "three" + } + name = "%s" + } + secret { + name = "${kubernetes_secret.one.metadata.0.name}" + } + secret { + name = "${kubernetes_secret.two.metadata.0.name}" + } + image_pull_secret { + name = "${kubernetes_secret.three.metadata.0.name}" + } + image_pull_secret { + name = "${kubernetes_secret.four.metadata.0.name}" + } +} + +resource "kubernetes_secret" "one" { + metadata { + name = "%s-one" + } +} + +resource "kubernetes_secret" "two" { + metadata { + name = "%s-two" + } +} + +resource "kubernetes_secret" "three" { + metadata { + name = "%s-three" + } +} + +resource "kubernetes_secret" "four" { + metadata { + name = "%s-four" + } +} +`, name, name, name, name, name) +} + +func testAccKubernetesServiceAccountConfig_modified(name string) string { + return fmt.Sprintf(` +resource "kubernetes_service_account" "test" { + metadata { + annotations { + TestAnnotationOne = "one" + Different = "1234" + } + labels { + TestLabelOne = "one" + TestLabelThree = "three" + } + name = "%s" + } + secret { + name = "${kubernetes_secret.one.metadata.0.name}" + } + image_pull_secret { + name = "${kubernetes_secret.two.metadata.0.name}" + } + image_pull_secret { + name = "${kubernetes_secret.three.metadata.0.name}" + } + image_pull_secret { + name = "${kubernetes_secret.four.metadata.0.name}" + } +} + +resource "kubernetes_secret" "one" { + metadata { + name = "%s-one" + } +} + +resource "kubernetes_secret" "two" { + metadata { + name = "%s-two" + } +} + +resource "kubernetes_secret" "three" { + metadata { + name = "%s-three" + } +} + +resource "kubernetes_secret" "four" { + metadata { + name = "%s-four" + } +} +`, name, name, name, name, name) +} + +func testAccKubernetesServiceAccountConfig_noAttributes(name string) string { + return fmt.Sprintf(` +resource "kubernetes_service_account" "test" { + metadata { + name = "%s" + } +}`, name) +} + +func testAccKubernetesServiceAccountConfig_generatedName(prefix string) string { + return fmt.Sprintf(` +resource "kubernetes_service_account" "test" { + metadata { + generate_name = "%s" + } +}`, prefix) +} diff --git a/kubernetes/structures.go b/kubernetes/structures.go index 8c18551760..e6c5bacaf7 100644 --- a/kubernetes/structures.go +++ b/kubernetes/structures.go @@ -426,6 +426,7 @@ func flattenLocalObjectReferenceArray(in []api.LocalObjectReference) []interface } return att } + func expandLocalObjectReferenceArray(in []interface{}) []api.LocalObjectReference { att := []api.LocalObjectReference{} if len(in) < 1 { @@ -440,3 +441,34 @@ func expandLocalObjectReferenceArray(in []interface{}) []api.LocalObjectReferenc } return att } + +func flattenServiceAccountSecrets(in []api.ObjectReference, defaultSecretName string) []interface{} { + att := make([]interface{}, 0) + for _, v := range in { + if v.Name == defaultSecretName { + continue + } + m := map[string]interface{}{} + if v.Name != "" { + m["name"] = v.Name + } + att = append(att, m) + } + return att +} + +func expandServiceAccountSecrets(in []interface{}, defaultSecretName string) []api.ObjectReference { + att := make([]api.ObjectReference, 0) + + for _, c := range in { + p := c.(map[string]interface{}) + if name, ok := p["name"]; ok { + att = append(att, api.ObjectReference{Name: name.(string)}) + } + } + if defaultSecretName != "" { + att = append(att, api.ObjectReference{Name: defaultSecretName}) + } + + return att +} diff --git a/website/docs/r/service.html.markdown b/website/docs/r/service.html.markdown index 39d69fd75c..b6cbd3e0cc 100644 --- a/website/docs/r/service.html.markdown +++ b/website/docs/r/service.html.markdown @@ -1,7 +1,7 @@ --- layout: "kubernetes" page_title: "Kubernetes: kubernetes_service" -sidebar_current: "docs-kubernetes-resource-service" +sidebar_current: "docs-kubernetes-resource-service-x" description: |- A Service is an abstraction which defines a logical set of pods and a policy by which to access them - sometimes called a micro-service. --- diff --git a/website/docs/r/service_account.html.markdown b/website/docs/r/service_account.html.markdown new file mode 100644 index 0000000000..95a05d491e --- /dev/null +++ b/website/docs/r/service_account.html.markdown @@ -0,0 +1,78 @@ +--- +layout: "kubernetes" +page_title: "Kubernetes: kubernetes_service_account" +sidebar_current: "docs-kubernetes-resource-service-account" +description: |- + A service account provides an identity for processes that run in a Pod. +--- + +# kubernetes_service_account + +A service account provides an identity for processes that run in a Pod. + +Read more at https://kubernetes.io/docs/admin/service-accounts-admin/ + +## Example Usage + +```hcl +resource "kubernetes_service_account" "example" { + metadata { + name = "terraform-example" + } + secret { + name = "${kubernetes_secret.example.metadata.0.name}" + } +} + +resource "kubernetes_secret" "example" { + metadata { + name = "terraform-example" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `metadata` - (Required) Standard service account's metadata. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata +* `image_pull_secret` - (Optional) A list of references to secrets in the same namespace to use for pulling any images in pods that reference this Service Account. More info: http://kubernetes.io/docs/user-guide/secrets#manually-specifying-an-imagepullsecret +* `secret` - (Optional) A list of secrets allowed to be used by pods running using this Service Account. More info: http://kubernetes.io/docs/user-guide/secrets + +## Nested Blocks + +### `metadata` + +#### Arguments + +* `annotations` - (Optional) An unstructured key value map stored with the service account that may be used to store arbitrary metadata. More info: http://kubernetes.io/docs/user-guide/annotations +* `generate_name` - (Optional) Prefix, used by the server, to generate a unique name ONLY IF the `name` field has not been provided. This value will also be combined with a unique suffix. Read more: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#idempotency +* `labels` - (Optional) Map of string keys and values that can be used to organize and categorize (scope and select) the service account. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels +* `name` - (Optional) Name of the service account, must be unique. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names +* `namespace` - (Optional) Namespace defines the space within which name of the service account must be unique. + +#### Attributes + +* `generation` - A sequence number representing a specific generation of the desired state. +* `resource_version` - An opaque value that represents the internal version of this service account that can be used by clients to determine when service account has changed. Read more: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency +* `self_link` - A URL representing this service account. +* `uid` - The unique in time and space value for this service account. More info: http://kubernetes.io/docs/user-guide/identifiers#uids + +### `image_pull_secret` + +#### Arguments + +* `name` - (Optional) Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names + +### `secret` + +#### Arguments + +* `name` - (Optional) Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `default_secret_name` - Name of the default secret the is created & managed by the service diff --git a/website/kubernetes.erb b/website/kubernetes.erb index 8629b42e01..225367abe0 100644 --- a/website/kubernetes.erb +++ b/website/kubernetes.erb @@ -43,9 +43,12 @@