kubernetes: Add secret resource (#12960)

This commit is contained in:
Marc Rooding 2017-03-30 10:24:40 +02:00 committed by Radek Simko
parent 1dca12201a
commit c2b657a039
6 changed files with 570 additions and 0 deletions

View File

@ -83,6 +83,7 @@ func Provider() terraform.ResourceProvider {
ResourcesMap: map[string]*schema.Resource{
"kubernetes_config_map": resourceKubernetesConfigMap(),
"kubernetes_namespace": resourceKubernetesNamespace(),
"kubernetes_secret": resourceKubernetesSecret(),
},
ConfigureFunc: providerConfigure,
}

View File

@ -0,0 +1,159 @@
package kubernetes
import (
"log"
"fmt"
"github.com/hashicorp/terraform/helper/schema"
pkgApi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
api "k8s.io/kubernetes/pkg/api/v1"
kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
)
func resourceKubernetesSecret() *schema.Resource {
return &schema.Resource{
Create: resourceKubernetesSecretCreate,
Read: resourceKubernetesSecretRead,
Exists: resourceKubernetesSecretExists,
Update: resourceKubernetesSecretUpdate,
Delete: resourceKubernetesSecretDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"metadata": namespacedMetadataSchema("secret", true),
"data": {
Type: schema.TypeMap,
Description: "A map of the secret data.",
Optional: true,
Sensitive: true,
},
"type": {
Type: schema.TypeString,
Description: "Type of secret",
Default: "Opaque",
Optional: true,
ForceNew: true,
},
},
}
}
func resourceKubernetesSecretCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)
metadata := expandMetadata(d.Get("metadata").([]interface{}))
secret := api.Secret{
ObjectMeta: metadata,
StringData: expandStringMap(d.Get("data").(map[string]interface{})),
}
if v, ok := d.GetOk("type"); ok {
secret.Type = api.SecretType(v.(string))
}
log.Printf("[INFO] Creating new secret: %#v", secret)
out, err := conn.CoreV1().Secrets(metadata.Namespace).Create(&secret)
if err != nil {
return err
}
log.Printf("[INFO] Submitting new secret: %#v", out)
d.SetId(buildId(out.ObjectMeta))
return resourceKubernetesSecretRead(d, meta)
}
func resourceKubernetesSecretRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)
namespace, name := idParts(d.Id())
log.Printf("[INFO] Reading secret %s", name)
secret, err := conn.CoreV1().Secrets(namespace).Get(name)
if err != nil {
return err
}
log.Printf("[INFO] Received secret: %#v", secret)
err = d.Set("metadata", flattenMetadata(secret.ObjectMeta))
if err != nil {
return err
}
d.Set("data", byteMapToStringMap(secret.Data))
d.Set("type", secret.Type)
return nil
}
func resourceKubernetesSecretUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)
namespace, name := idParts(d.Id())
ops := patchMetadata("metadata.0.", "/metadata/", d)
if d.HasChange("data") {
oldV, newV := d.GetChange("data")
oldV = base64EncodeStringMap(oldV.(map[string]interface{}))
newV = base64EncodeStringMap(newV.(map[string]interface{}))
diffOps := diffStringMap("/data/", oldV.(map[string]interface{}), newV.(map[string]interface{}))
ops = append(ops, diffOps...)
}
data, err := ops.MarshalJSON()
if err != nil {
return fmt.Errorf("Failed to marshal update operations: %s", err)
}
log.Printf("[INFO] Updating secret %q: %v", name, data)
out, err := conn.CoreV1().Secrets(namespace).Patch(name, pkgApi.JSONPatchType, data)
if err != nil {
return fmt.Errorf("Failed to update secret: %s", err)
}
log.Printf("[INFO] Submitting updated secret: %#v", out)
d.SetId(buildId(out.ObjectMeta))
return resourceKubernetesSecretRead(d, meta)
}
func resourceKubernetesSecretDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)
namespace, name := idParts(d.Id())
log.Printf("[INFO] Deleting secret: %q", name)
err := conn.CoreV1().Secrets(namespace).Delete(name, &api.DeleteOptions{})
if err != nil {
return err
}
log.Printf("[INFO] Secret %s deleted", name)
d.SetId("")
return nil
}
func resourceKubernetesSecretExists(d *schema.ResourceData, meta interface{}) (bool, error) {
conn := meta.(*kubernetes.Clientset)
namespace, name := idParts(d.Id())
log.Printf("[INFO] Checking secret %s", name)
_, err := conn.CoreV1().Secrets(namespace).Get(name)
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
}

View File

@ -0,0 +1,320 @@
package kubernetes
import (
"fmt"
"reflect"
"regexp"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
api "k8s.io/kubernetes/pkg/api/v1"
kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
)
func TestAccKubernetesSecret_basic(t *testing.T) {
var conf api.Secret
name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "kubernetes_secret.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckKubernetesSecretDestroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesSecretConfig_basic(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesSecretExists("kubernetes_secret.test", &conf),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.annotations.%", "2"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.annotations.TestAnnotationOne", "one"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.annotations.TestAnnotationTwo", "two"),
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "TestAnnotationTwo": "two"}),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.labels.%", "3"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.labels.TestLabelOne", "one"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.labels.TestLabelTwo", "two"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.labels.TestLabelThree", "three"),
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.name", name),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.generation"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.resource_version"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.self_link"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.uid"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "data.%", "2"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "data.one", "first"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "data.two", "second"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "type", "Opaque"),
testAccCheckSecretData(&conf, map[string]string{"one": "first", "two": "second"}),
),
},
{
Config: testAccKubernetesSecretConfig_modified(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesSecretExists("kubernetes_secret.test", &conf),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.annotations.%", "2"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.annotations.TestAnnotationOne", "one"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.annotations.Different", "1234"),
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "Different": "1234"}),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.labels.%", "2"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.labels.TestLabelOne", "one"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.labels.TestLabelThree", "three"),
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelThree": "three"}),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.name", name),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.generation"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.resource_version"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.self_link"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.uid"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "data.%", "3"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "data.one", "first"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "data.two", "second"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "data.nine", "ninth"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "type", "Opaque"),
testAccCheckSecretData(&conf, map[string]string{"one": "first", "two": "second", "nine": "ninth"}),
),
},
{
Config: testAccKubernetesSecretConfig_noData(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesSecretExists("kubernetes_secret.test", &conf),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.annotations.%", "0"),
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.labels.%", "0"),
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.name", name),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.generation"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.resource_version"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.self_link"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.uid"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "data.%", "0"),
testAccCheckSecretData(&conf, map[string]string{}),
),
},
{
Config: testAccKubernetesSecretConfig_typeSpecified(name),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesSecretExists("kubernetes_secret.test", &conf),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.annotations.%", "0"),
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.labels.%", "0"),
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.name", name),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.generation"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.resource_version"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.self_link"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.uid"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "data.%", "2"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "data.username", "admin"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "data.password", "password"),
resource.TestCheckResourceAttr("kubernetes_secret.test", "type", "kubernetes.io/basic-auth"),
testAccCheckSecretData(&conf, map[string]string{"username": "admin", "password": "password"}),
),
},
},
})
}
func TestAccKubernetesSecret_importBasic(t *testing.T) {
resourceName := "kubernetes_secret.test"
name := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckKubernetesSecretDestroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesSecretConfig_basic(name),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccKubernetesSecret_generatedName(t *testing.T) {
var conf api.Secret
prefix := "tf-acc-test-gen-"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "kubernetes_secret.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckKubernetesSecretDestroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesSecretConfig_generatedName(prefix),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesSecretExists("kubernetes_secret.test", &conf),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.annotations.%", "0"),
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.labels.%", "0"),
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}),
resource.TestCheckResourceAttr("kubernetes_secret.test", "metadata.0.generate_name", prefix),
resource.TestMatchResourceAttr("kubernetes_secret.test", "metadata.0.name", regexp.MustCompile("^"+prefix)),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.generation"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.resource_version"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.self_link"),
resource.TestCheckResourceAttrSet("kubernetes_secret.test", "metadata.0.uid"),
),
},
},
})
}
func TestAccKubernetesSecret_importGeneratedName(t *testing.T) {
resourceName := "kubernetes_secret.test"
prefix := "tf-acc-test-gen-import-"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckKubernetesSecretDestroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesSecretConfig_generatedName(prefix),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func testAccCheckSecretData(m *api.Secret, expected map[string]string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if len(expected) == 0 && len(m.Data) == 0 {
return nil
}
if !reflect.DeepEqual(byteMapToStringMap(m.Data), expected) {
return fmt.Errorf("%s data don't match.\nExpected: %q\nGiven: %q",
m.Name, expected, m.Data)
}
return nil
}
}
func testAccCheckKubernetesSecretDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*kubernetes.Clientset)
for _, rs := range s.RootModule().Resources {
if rs.Type != "kubernetes_secret" {
continue
}
namespace, name := idParts(rs.Primary.ID)
resp, err := conn.CoreV1().Secrets(namespace).Get(name)
if err == nil {
if resp.Name == rs.Primary.ID {
return fmt.Errorf("Secret still exists: %s", rs.Primary.ID)
}
}
}
return nil
}
func testAccCheckKubernetesSecretExists(n string, obj *api.Secret) 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().Secrets(namespace).Get(name)
if err != nil {
return err
}
*obj = *out
return nil
}
}
func testAccKubernetesSecretConfig_basic(name string) string {
return fmt.Sprintf(`
resource "kubernetes_secret" "test" {
metadata {
annotations {
TestAnnotationOne = "one"
TestAnnotationTwo = "two"
}
labels {
TestLabelOne = "one"
TestLabelTwo = "two"
TestLabelThree = "three"
}
name = "%s"
}
data {
one = "first"
two = "second"
}
}`, name)
}
func testAccKubernetesSecretConfig_modified(name string) string {
return fmt.Sprintf(`
resource "kubernetes_secret" "test" {
metadata {
annotations {
TestAnnotationOne = "one"
Different = "1234"
}
labels {
TestLabelOne = "one"
TestLabelThree = "three"
}
name = "%s"
}
data {
one = "first"
two = "second"
nine = "ninth"
}
}`, name)
}
func testAccKubernetesSecretConfig_noData(name string) string {
return fmt.Sprintf(`
resource "kubernetes_secret" "test" {
metadata {
name = "%s"
}
}`, name)
}
func testAccKubernetesSecretConfig_typeSpecified(name string) string {
return fmt.Sprintf(`
resource "kubernetes_secret" "test" {
metadata {
name = "%s"
}
data {
username = "admin"
password = "password"
}
type = "kubernetes.io/basic-auth"
}`, name)
}
func testAccKubernetesSecretConfig_generatedName(prefix string) string {
return fmt.Sprintf(`
resource "kubernetes_secret" "test" {
metadata {
generate_name = "%s"
}
data {
one = "first"
two = "second"
}
}`, prefix)
}

View File

@ -5,6 +5,7 @@ import (
"net/url"
"strings"
"encoding/base64"
"github.com/hashicorp/terraform/helper/schema"
api "k8s.io/kubernetes/pkg/api/v1"
)
@ -99,3 +100,20 @@ func isInternalAnnotationKey(annotationKey string) bool {
return false
}
func byteMapToStringMap(m map[string][]byte) map[string]string {
result := make(map[string]string)
for k, v := range m {
result[k] = string(v)
}
return result
}
func base64EncodeStringMap(m map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for k, v := range m {
value := v.(string)
result[k] = (base64.StdEncoding.EncodeToString([]byte(value)))
}
return result
}

View File

@ -0,0 +1,69 @@
---
layout: "kubernetes"
page_title: "Kubernetes: kubernetes_secret"
sidebar_current: "docs-kubernetes-resource-secret"
description: |-
The resource provides mechanisms to inject containers with sensitive information while keeping containers agnostic of Kubernetes.
---
# kubernetes_secret
The resource provides mechanisms to inject containers with sensitive information, such as passwords, while keeping containers agnostic of Kubernetes.
Secrets can be used to store sensitive information either as individual properties or coarse-grained entries like entire files or JSON blobs.
The resource will by default create a secret which is available to any pod in the specified (or default) namespace.
~> Read more about security properties and risks involved with using Kubernetes secrets: https://kubernetes.io/docs/user-guide/secrets/#security-properties
~> **Note:** All arguments including the secret data will be stored in the raw state as plain-text. [Read more about sensitive data in state](/docs/state/sensitive-data.html).
## Example Usage
```
resource "kubernetes_secret" "example" {
metadata {
name = "basic-auth"
}
data {
username = "admin"
password = "P4ssw0rd"
}
type = "kubernetes.io/basic-auth"
}
```
## Argument Reference
The following arguments are supported:
* `data` - (Optional) A map of the secret data.
* `metadata` - (Required) Standard secret's metadata. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata
* `type` - (Optional) The secret type. Defaults to `Opaque`. More info: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/secrets.md#proposed-design
## Nested Blocks
### `metadata`
#### Arguments
* `annotations` - (Optional) An unstructured key value map stored with the secret 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 secret. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels
* `name` - (Optional) Name of the secret, 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 secret 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 secret that can be used by clients to determine when secret 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 secret.
* `uid` - The unique in time and space value for this secret. More info: http://kubernetes.io/docs/user-guide/identifiers#uids
## Import
Secret can be imported using its name, e.g.
```
$ terraform import kubernetes_secret.example my-secret
```

View File

@ -19,6 +19,9 @@
<li<%= sidebar_current("docs-kubernetes-resource-namespace") %>>
<a href="/docs/providers/kubernetes/r/namespace.html">kubernetes_namespace</a>
</li>
<li<%= sidebar_current("docs-kubernetes-resource-secret") %>>
<a href="/docs/providers/kubernetes/r/secret.html">kubernetes_secret</a>
</li>
</ul>
</li>
</ul>