Merge pull request #12372 from hashicorp/f-kubernetes

kubernetes: Add provider + namespace resource
This commit is contained in:
Radek Simko 2017-03-16 07:18:39 +00:00 committed by GitHub
commit 4448e45678
14 changed files with 1055 additions and 0 deletions

View File

@ -0,0 +1,171 @@
package kubernetes
import (
"bytes"
"fmt"
"log"
"os"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/go-homedir"
kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
)
func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"host": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_HOST", ""),
Description: "The hostname (in form of URI) of Kubernetes master.",
},
"username": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_USER", ""),
Description: "The username to use for HTTP basic authentication when accessing the Kubernetes master endpoint.",
},
"password": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_PASSWORD", ""),
Description: "The password to use for HTTP basic authentication when accessing the Kubernetes master endpoint.",
},
"insecure": {
Type: schema.TypeBool,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_INSECURE", false),
Description: "Whether server should be accessed without verifying the TLS certificate.",
},
"client_certificate": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLIENT_CERT_DATA", ""),
Description: "PEM-encoded client certificate for TLS authentication.",
},
"client_key": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLIENT_KEY_DATA", ""),
Description: "PEM-encoded client certificate key for TLS authentication.",
},
"cluster_ca_certificate": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLUSTER_CA_CERT_DATA", ""),
Description: "PEM-encoded root certificates bundle for TLS authentication.",
},
"config_path": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CONFIG", "~/.kube/config"),
Description: "Path to the kube config file, defaults to ~/.kube/config",
},
"config_context_auth_info": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX_AUTH_INFO", ""),
Description: "",
},
"config_context_cluster": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX_CLUSTER", ""),
Description: "",
},
},
ResourcesMap: map[string]*schema.Resource{
"kubernetes_namespace": resourceKubernetesNamespace(),
},
ConfigureFunc: providerConfigure,
}
}
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
// Config file loading
cfg, err := tryLoadingConfigFile(d)
if err != nil {
return nil, err
}
if cfg == nil {
cfg = &restclient.Config{}
}
// Overriding with static configuration
cfg.UserAgent = fmt.Sprintf("HashiCorp/1.0 Terraform/%s", terraform.VersionString())
if v, ok := d.GetOk("host"); ok {
cfg.Host = v.(string)
}
if v, ok := d.GetOk("username"); ok {
cfg.Username = v.(string)
}
if v, ok := d.GetOk("password"); ok {
cfg.Password = v.(string)
}
if v, ok := d.GetOk("insecure"); ok {
cfg.Insecure = v.(bool)
}
if v, ok := d.GetOk("cluster_ca_certificate"); ok {
cfg.CAData = bytes.NewBufferString(v.(string)).Bytes()
}
if v, ok := d.GetOk("client_certificate"); ok {
cfg.CertData = bytes.NewBufferString(v.(string)).Bytes()
}
if v, ok := d.GetOk("client_key"); ok {
cfg.KeyData = bytes.NewBufferString(v.(string)).Bytes()
}
k, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, fmt.Errorf("Failed to configure: %s", err)
}
return k, nil
}
func tryLoadingConfigFile(d *schema.ResourceData) (*restclient.Config, error) {
path, err := homedir.Expand(d.Get("config_path").(string))
if err != nil {
return nil, err
}
loader := &clientcmd.ClientConfigLoadingRules{
ExplicitPath: path,
}
overrides := &clientcmd.ConfigOverrides{}
ctxSuffix := "; no context"
authInfo, authInfoOk := d.GetOk("config_context_auth_info")
cluster, clusterOk := d.GetOk("config_context_cluster")
if authInfoOk || clusterOk {
overrides.Context = clientcmdapi.Context{}
if authInfoOk {
overrides.Context.AuthInfo = authInfo.(string)
}
if clusterOk {
overrides.Context.Cluster = cluster.(string)
}
ctxSuffix = fmt.Sprintf("; auth_info: %s, cluster: %s",
overrides.Context.AuthInfo, overrides.Context.Cluster)
}
log.Printf("[DEBUG] Using override context: %#v", *overrides)
cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides)
cfg, err := cc.ClientConfig()
if err != nil {
if pathErr, ok := err.(*os.PathError); ok && os.IsNotExist(pathErr.Err) {
log.Printf("[INFO] Unable to load config file as it doesn't exist at %q", path)
return nil, nil
}
return nil, fmt.Errorf("Failed to load config (%s%s): %s", path, ctxSuffix, err)
}
log.Printf("[INFO] Successfully loaded config file (%s%s)", path, ctxSuffix)
return cfg, nil
}

View File

@ -0,0 +1,53 @@
package kubernetes
import (
"os"
"strings"
"testing"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider
func init() {
testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{
"kubernetes": testAccProvider,
}
}
func TestProvider(t *testing.T) {
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestProvider_impl(t *testing.T) {
var _ terraform.ResourceProvider = Provider()
}
func testAccPreCheck(t *testing.T) {
hasFileCfg := (os.Getenv("KUBE_CTX_AUTH_INFO") != "" && os.Getenv("KUBE_CTX_CLUSTER") != "")
hasStaticCfg := (os.Getenv("KUBE_HOST") != "" &&
os.Getenv("KUBE_USER") != "" &&
os.Getenv("KUBE_PASSWORD") != "" &&
os.Getenv("KUBE_CLIENT_CERT_DATA") != "" &&
os.Getenv("KUBE_CLIENT_KEY_DATA") != "" &&
os.Getenv("KUBE_CLUSTER_CA_CERT_DATA") != "")
if !hasFileCfg && !hasStaticCfg {
t.Fatalf("File config (KUBE_CTX_AUTH_INFO and KUBE_CTX_CLUSTER) or static configuration"+
" (%s) must be set for acceptance tests",
strings.Join([]string{
"KUBE_HOST",
"KUBE_USER",
"KUBE_PASSWORD",
"KUBE_CLIENT_CERT_DATA",
"KUBE_CLIENT_KEY_DATA",
"KUBE_CLUSTER_CA_CERT_DATA",
}, ", "))
}
}

View File

@ -0,0 +1,143 @@
package kubernetes
import (
"fmt"
"log"
"time"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"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 resourceKubernetesNamespace() *schema.Resource {
return &schema.Resource{
Create: resourceKubernetesNamespaceCreate,
Read: resourceKubernetesNamespaceRead,
Exists: resourceKubernetesNamespaceExists,
Update: resourceKubernetesNamespaceUpdate,
Delete: resourceKubernetesNamespaceDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"metadata": metadataSchema("namespace"),
},
}
}
func resourceKubernetesNamespaceCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)
metadata := expandMetadata(d.Get("metadata").([]interface{}))
namespace := api.Namespace{
ObjectMeta: metadata,
}
log.Printf("[INFO] Creating new namespace: %#v", namespace)
out, err := conn.CoreV1().Namespaces().Create(&namespace)
if err != nil {
return err
}
log.Printf("[INFO] Submitted new namespace: %#v", out)
d.SetId(out.Name)
return resourceKubernetesNamespaceRead(d, meta)
}
func resourceKubernetesNamespaceRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)
name := d.Id()
log.Printf("[INFO] Reading namespace %s", name)
namespace, err := conn.CoreV1().Namespaces().Get(name)
if err != nil {
log.Printf("[DEBUG] Received error: %#v", err)
return err
}
log.Printf("[INFO] Received namespace: %#v", namespace)
err = d.Set("metadata", flattenMetadata(namespace.ObjectMeta))
if err != nil {
return err
}
return nil
}
func resourceKubernetesNamespaceUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)
metadata := expandMetadata(d.Get("metadata").([]interface{}))
// This is necessary in case the name is generated
metadata.Name = d.Id()
namespace := api.Namespace{
ObjectMeta: metadata,
}
log.Printf("[INFO] Updating namespace: %#v", namespace)
out, err := conn.CoreV1().Namespaces().Update(&namespace)
if err != nil {
return err
}
log.Printf("[INFO] Submitted updated namespace: %#v", out)
d.SetId(out.Name)
return resourceKubernetesNamespaceRead(d, meta)
}
func resourceKubernetesNamespaceDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*kubernetes.Clientset)
name := d.Id()
log.Printf("[INFO] Deleting namespace: %#v", name)
err := conn.CoreV1().Namespaces().Delete(name, &api.DeleteOptions{})
if err != nil {
return err
}
stateConf := &resource.StateChangeConf{
Target: []string{},
Pending: []string{"Terminating"},
Timeout: 5 * time.Minute,
Refresh: func() (interface{}, string, error) {
out, err := conn.CoreV1().Namespaces().Get(name)
if err != nil {
if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 {
return nil, "", nil
}
log.Printf("[ERROR] Received error: %#v", err)
return out, "Error", err
}
statusPhase := fmt.Sprintf("%v", out.Status.Phase)
log.Printf("[DEBUG] Namespace %s status received: %#v", out.Name, statusPhase)
return out, statusPhase, nil
},
}
_, err = stateConf.WaitForState()
if err != nil {
return err
}
log.Printf("[INFO] Namespace %s deleted", name)
d.SetId("")
return nil
}
func resourceKubernetesNamespaceExists(d *schema.ResourceData, meta interface{}) (bool, error) {
conn := meta.(*kubernetes.Clientset)
name := d.Id()
log.Printf("[INFO] Checking namespace %s", name)
_, err := conn.CoreV1().Namespaces().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)
}
log.Printf("[INFO] Namespace %s exists", name)
return true, err
}

View File

@ -0,0 +1,272 @@
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 TestAccKubernetesNamespace_basic(t *testing.T) {
var conf api.Namespace
nsName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "kubernetes_namespace.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckKubernetesNamespaceDestroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesNamespaceConfig_basic(nsName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesNamespaceExists("kubernetes_namespace.test", &conf),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.annotations.%", "2"),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.annotations.TestAnnotationOne", "one"),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.annotations.TestAnnotationTwo", "two"),
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "TestAnnotationTwo": "two"}),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.labels.%", "3"),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.labels.TestLabelOne", "one"),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.labels.TestLabelTwo", "two"),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.labels.TestLabelThree", "three"),
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.name", nsName),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.generation"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.resource_version"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.self_link"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.uid"),
),
},
{
Config: testAccKubernetesNamespaceConfig_smallerLists(nsName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesNamespaceExists("kubernetes_namespace.test", &conf),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.annotations.%", "2"),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.annotations.TestAnnotationOne", "one"),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.annotations.Different", "1234"),
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "Different": "1234"}),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.labels.%", "2"),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.labels.TestLabelOne", "one"),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.labels.TestLabelThree", "three"),
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelThree": "three"}),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.name", nsName),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.generation"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.resource_version"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.self_link"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.uid"),
),
},
{
Config: testAccKubernetesNamespaceConfig_noLists(nsName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesNamespaceExists("kubernetes_namespace.test", &conf),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.annotations.%", "0"),
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.labels.%", "0"),
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.name", nsName),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.generation"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.resource_version"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.self_link"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.uid"),
),
},
},
})
}
func TestAccKubernetesNamespace_importBasic(t *testing.T) {
resourceName := "kubernetes_namespace.test"
nsName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckKubernetesNamespaceDestroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesNamespaceConfig_basic(nsName),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func TestAccKubernetesNamespace_generatedName(t *testing.T) {
var conf api.Namespace
prefix := "tf-acc-test-gen-"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
IDRefreshName: "kubernetes_namespace.test",
Providers: testAccProviders,
CheckDestroy: testAccCheckKubernetesNamespaceDestroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesNamespaceConfig_generatedName(prefix),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckKubernetesNamespaceExists("kubernetes_namespace.test", &conf),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.annotations.%", "0"),
testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.labels.%", "0"),
testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}),
resource.TestCheckResourceAttr("kubernetes_namespace.test", "metadata.0.generate_name", prefix),
resource.TestMatchResourceAttr("kubernetes_namespace.test", "metadata.0.name", regexp.MustCompile("^"+prefix)),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.generation"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.resource_version"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.self_link"),
resource.TestCheckResourceAttrSet("kubernetes_namespace.test", "metadata.0.uid"),
),
},
},
})
}
func TestAccKubernetesNamespace_importGeneratedName(t *testing.T) {
resourceName := "kubernetes_namespace.test"
prefix := "tf-acc-test-gen-import-"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckKubernetesNamespaceDestroy,
Steps: []resource.TestStep{
{
Config: testAccKubernetesNamespaceConfig_generatedName(prefix),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}
func testAccCheckMetaAnnotations(om *api.ObjectMeta, expected map[string]string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if len(expected) == 0 && len(om.Annotations) == 0 {
return nil
}
if !reflect.DeepEqual(om.Annotations, expected) {
return fmt.Errorf("%s annotations don't match.\nExpected: %q\nGiven: %q",
om.Name, expected, om.Annotations)
}
return nil
}
}
func testAccCheckMetaLabels(om *api.ObjectMeta, expected map[string]string) resource.TestCheckFunc {
return func(s *terraform.State) error {
if len(expected) == 0 && len(om.Labels) == 0 {
return nil
}
if !reflect.DeepEqual(om.Labels, expected) {
return fmt.Errorf("%s labels don't match.\nExpected: %q\nGiven: %q",
om.Name, expected, om.Labels)
}
return nil
}
}
func testAccCheckKubernetesNamespaceDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*kubernetes.Clientset)
for _, rs := range s.RootModule().Resources {
if rs.Type != "kubernetes_namespace" {
continue
}
resp, err := conn.CoreV1().Namespaces().Get(rs.Primary.ID)
if err == nil {
if resp.Name == rs.Primary.ID {
return fmt.Errorf("Namespace still exists: %s", rs.Primary.ID)
}
}
}
return nil
}
func testAccCheckKubernetesNamespaceExists(n string, obj *api.Namespace) 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)
out, err := conn.CoreV1().Namespaces().Get(rs.Primary.ID)
if err != nil {
return err
}
*obj = *out
return nil
}
}
func testAccKubernetesNamespaceConfig_basic(nsName string) string {
return fmt.Sprintf(`
resource "kubernetes_namespace" "test" {
metadata {
annotations {
TestAnnotationOne = "one"
TestAnnotationTwo = "two"
}
labels {
TestLabelOne = "one"
TestLabelTwo = "two"
TestLabelThree = "three"
}
name = "%s"
}
}`, nsName)
}
func testAccKubernetesNamespaceConfig_smallerLists(nsName string) string {
return fmt.Sprintf(`
resource "kubernetes_namespace" "test" {
metadata {
annotations {
TestAnnotationOne = "one"
Different = "1234"
}
labels {
TestLabelOne = "one"
TestLabelThree = "three"
}
name = "%s"
}
}`, nsName)
}
func testAccKubernetesNamespaceConfig_noLists(nsName string) string {
return fmt.Sprintf(`
resource "kubernetes_namespace" "test" {
metadata {
name = "%s"
}
}`, nsName)
}
func testAccKubernetesNamespaceConfig_generatedName(prefix string) string {
return fmt.Sprintf(`
resource "kubernetes_namespace" "test" {
metadata {
generate_name = "%s"
}
}`, prefix)
}

View File

@ -0,0 +1,75 @@
package kubernetes
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
)
func metadataFields(objectName string) map[string]*schema.Schema {
return map[string]*schema.Schema{
"annotations": {
Type: schema.TypeMap,
Description: fmt.Sprintf("An unstructured key value map stored with the %s that may be used to store arbitrary metadata. More info: http://kubernetes.io/docs/user-guide/annotations", objectName),
Optional: true,
ValidateFunc: validateAnnotations,
},
"generation": {
Type: schema.TypeInt,
Description: "A sequence number representing a specific generation of the desired state.",
Computed: true,
},
"labels": {
Type: schema.TypeMap,
Description: fmt.Sprintf("Map of string keys and values that can be used to organize and categorize (scope and select) the %s. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels", objectName),
Optional: true,
ValidateFunc: validateLabels,
},
"name": {
Type: schema.TypeString,
Description: fmt.Sprintf("Name of the %s, must be unique. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names", objectName),
Optional: true,
ForceNew: true,
Computed: true,
ValidateFunc: validateName,
ConflictsWith: []string{"metadata.generate_name"},
},
"resource_version": {
Type: schema.TypeString,
Description: fmt.Sprintf("An opaque value that represents the internal version of this %s that can be used by clients to determine when %s has changed. Read more: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency", objectName, objectName),
Computed: true,
},
"self_link": {
Type: schema.TypeString,
Description: fmt.Sprintf("A URL representing this %s.", objectName),
Computed: true,
},
"uid": {
Type: schema.TypeString,
Description: fmt.Sprintf("The unique in time and space value for this %s. More info: http://kubernetes.io/docs/user-guide/identifiers#uids", objectName),
Computed: true,
},
}
}
func metadataSchema(objectName string) *schema.Schema {
fields := metadataFields(objectName)
fields["generate_name"] = &schema.Schema{
Type: schema.TypeString,
Description: "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",
Optional: true,
ForceNew: true,
ValidateFunc: validateGenerateName,
ConflictsWith: []string{"metadata.name"},
}
return &schema.Schema{
Type: schema.TypeList,
Description: fmt.Sprintf("Standard %s's metadata. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata", objectName),
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: fields,
},
}
}

View File

@ -0,0 +1,56 @@
package kubernetes
import (
"fmt"
api "k8s.io/kubernetes/pkg/api/v1"
)
func expandMetadata(in []interface{}) api.ObjectMeta {
meta := api.ObjectMeta{}
if len(in) < 1 {
return meta
}
m := in[0].(map[string]interface{})
meta.Annotations = expandStringMap(m["annotations"].(map[string]interface{}))
meta.Labels = expandStringMap(m["labels"].(map[string]interface{}))
if v, ok := m["generate_name"]; ok {
meta.GenerateName = v.(string)
}
if v, ok := m["name"]; ok {
meta.Name = v.(string)
}
if v, ok := m["namespace"]; ok {
meta.Namespace = v.(string)
}
return meta
}
func expandStringMap(m map[string]interface{}) map[string]string {
result := make(map[string]string)
for k, v := range m {
result[k] = v.(string)
}
return result
}
func flattenMetadata(meta api.ObjectMeta) []map[string]interface{} {
m := make(map[string]interface{})
m["annotations"] = meta.Annotations
m["generate_name"] = meta.GenerateName
m["labels"] = meta.Labels
m["name"] = meta.Name
m["resource_version"] = meta.ResourceVersion
m["self_link"] = meta.SelfLink
m["uid"] = fmt.Sprintf("%v", meta.UID)
m["generation"] = meta.Generation
if meta.Namespace != "" {
m["namespace"] = meta.Namespace
}
return []map[string]interface{}{m}
}

View File

@ -0,0 +1,63 @@
provider "google" {
// Provider settings to be provided via ENV variables
}
data "google_compute_zones" "available" {}
resource "random_id" "cluster_name" {
byte_length = 10
}
resource "random_id" "username" {
byte_length = 14
}
resource "random_id" "password" {
byte_length = 16
}
resource "google_container_cluster" "primary" {
name = "tf-acc-test-${random_id.cluster_name.hex}"
zone = "${data.google_compute_zones.available.names[0]}"
initial_node_count = 3
additional_zones = [
"${data.google_compute_zones.available.names[1]}"
]
master_auth {
username = "${random_id.username.hex}"
password = "${random_id.password.hex}"
}
node_config {
oauth_scopes = [
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/devstorage.read_only",
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring"
]
}
}
output "endpoint" {
value = "${google_container_cluster.primary.endpoint}"
}
output "username" {
value = "${google_container_cluster.primary.master_auth.0.username}"
}
output "password" {
value = "${google_container_cluster.primary.master_auth.0.password}"
}
output "client_certificate_b64" {
value = "${google_container_cluster.primary.master_auth.0.client_certificate}"
}
output "client_key_b64" {
value = "${google_container_cluster.primary.master_auth.0.client_key}"
}
output "cluster_ca_certificate_b64" {
value = "${google_container_cluster.primary.master_auth.0.cluster_ca_certificate}"
}

View File

@ -0,0 +1,60 @@
package kubernetes
import (
"fmt"
"strings"
apiValidation "k8s.io/kubernetes/pkg/api/validation"
utilValidation "k8s.io/kubernetes/pkg/util/validation"
)
func validateAnnotations(value interface{}, key string) (ws []string, es []error) {
m := value.(map[string]interface{})
for k, _ := range m {
errors := utilValidation.IsQualifiedName(strings.ToLower(k))
if len(errors) > 0 {
for _, e := range errors {
es = append(es, fmt.Errorf("%s (%q) %s", key, k, e))
}
}
}
return
}
func validateName(value interface{}, key string) (ws []string, es []error) {
v := value.(string)
errors := apiValidation.NameIsDNSLabel(v, false)
if len(errors) > 0 {
for _, err := range errors {
es = append(es, fmt.Errorf("%s %s", key, err))
}
}
return
}
func validateGenerateName(value interface{}, key string) (ws []string, es []error) {
v := value.(string)
errors := apiValidation.NameIsDNSLabel(v, true)
if len(errors) > 0 {
for _, err := range errors {
es = append(es, fmt.Errorf("%s %s", key, err))
}
}
return
}
func validateLabels(value interface{}, key string) (ws []string, es []error) {
m := value.(map[string]interface{})
for k, v := range m {
for _, msg := range utilValidation.IsQualifiedName(k) {
es = append(es, fmt.Errorf("%s (%q) %s", key, k, msg))
}
val := v.(string)
for _, msg := range utilValidation.IsValidLabelValue(val) {
es = append(es, fmt.Errorf("%s (%q) %s", key, val, msg))
}
}
return
}

View File

@ -37,6 +37,7 @@ import (
icinga2provider "github.com/hashicorp/terraform/builtin/providers/icinga2"
ignitionprovider "github.com/hashicorp/terraform/builtin/providers/ignition"
influxdbprovider "github.com/hashicorp/terraform/builtin/providers/influxdb"
kubernetesprovider "github.com/hashicorp/terraform/builtin/providers/kubernetes"
libratoprovider "github.com/hashicorp/terraform/builtin/providers/librato"
logentriesprovider "github.com/hashicorp/terraform/builtin/providers/logentries"
mailgunprovider "github.com/hashicorp/terraform/builtin/providers/mailgun"
@ -112,6 +113,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{
"icinga2": icinga2provider.Provider,
"ignition": ignitionprovider.Provider,
"influxdb": influxdbprovider.Provider,
"kubernetes": kubernetesprovider.Provider,
"librato": libratoprovider.Provider,
"logentries": logentriesprovider.Provider,
"mailgun": mailgunprovider.Provider,

View File

@ -40,6 +40,7 @@ body.layout-heroku,
body.layout-ignition,
body.layout-icinga2,
body.layout-influxdb,
body.layout-kubernetes,
body.layout-librato,
body.layout-logentries,
body.layout-mailgun,

View File

@ -0,0 +1,68 @@
---
layout: "kubernetes"
page_title: "Provider: Kubernetes"
sidebar_current: "docs-kubernetes-index"
description: |-
The Kubernetes (K8s) provider is used to interact with the resources supported by Kubernetes. The provider needs to be configured with the proper credentials before it can be used.
---
# Kubernetes Provider
The Kubernetes (K8S) provider is used to interact with the resources supported by Kubernetes. The provider needs to be configured with the proper credentials before it can be used.
Use the navigation to the left to read about the available resources.
-> **Note:** The Kubernetes provider is new as of Terraform 0.9. It is ready to be used but many features are still being added. If there is a Kubernetes feature missing, please report it in the GitHub repo.
## Example Usage
```
provider "kubernetes" {
config_context_auth_info = "ops"
config_context_cluster = "mycluster"
}
resource "kubernetes_namespace" "example" {
metadata {
name = "my-first-namespace"
}
}
```
## Authentication
There are generally two ways to configure the Kubernetes provider.
The provider always first tries to load **a config file** from a given
(or default) location - this requires valid `config_context_auth_info` & `config_context_cluster`.
The other way is **statically** define all the credentials:
```
provider "kubernetes" {
host = "https://104.196.242.174"
username = "ClusterMaster"
password = "MindTheGap"
client_certificate = "${file("~/.kube/client-cert.pem")}"
client_key = "${file("~/.kube/client-key.pem")}"
cluster_ca_certificate = "${file("~/.kube/cluster-ca-cert.pem")}"
}
```
If you have **both** valid configuration in a config file and static configuration, the static one is used as override.
i.e. any static field will override its counterpart loaded from the config.
## Argument Reference
The following arguments are supported:
* `host` - (Optional) The hostname (in form of URI) of Kubernetes master. Can be sourced from `KUBE_HOST`. Defaults to `https://localhost`.
* `username` - (Optional) The username to use for HTTP basic authentication when accessing the Kubernetes master endpoint. Can be sourced from `KUBE_USER`.
* `password` - (Optional) The password to use for HTTP basic authentication when accessing the Kubernetes master endpoint. Can be sourced from `KUBE_PASSWORD`.
* `insecure`- (Optional) Whether server should be accessed without verifying the TLS certificate. Can be sourced from `KUBE_INSECURE`. Defaults to `false`.
* `client_certificate` - (Optional) PEM-encoded client certificate for TLS authentication. Can be sourced from `KUBE_CLIENT_CERT_DATA`.
* `client_key` - (Optional) PEM-encoded client certificate key for TLS authentication. Can be sourced from `KUBE_CLIENT_KEY_DATA`.
* `cluster_ca_certificate` - (Optional) PEM-encoded root certificates bundle for TLS authentication. Can be sourced from `KUBE_CLUSTER_CA_CERT_DATA`.
* `config_path` - (Optional) Path to the kube config file. Can be sourced from `KUBE_CONFIG`. Defaults to `~/.kube/config`.
* `config_context_auth_info` - (Optional) Authentication info context of the kube config (name of the kubeconfig user, `--user` flag in `kubectl`). Can be sourced from `KUBE_CTX_AUTH_INFO`.
* `config_context_cluster` - (Optional) Cluster context of the kube config (name of the kubeconfig cluster, `--cluster` flag in `kubectl`). Can be sourced from `KUBE_CTX_CLUSTER`.

View File

@ -0,0 +1,61 @@
---
layout: "kubernetes"
page_title: "Kubernetes: kubernetes_namespace"
sidebar_current: "docs-kubernetes-resource-namespace"
description: |-
Kubernetes supports multiple virtual clusters backed by the same physical cluster. These virtual clusters are called namespaces.
---
# kubernetes_namespace
Kubernetes supports multiple virtual clusters backed by the same physical cluster. These virtual clusters are called namespaces.
Read more about namespaces at https://kubernetes.io/docs/user-guide/namespaces/
## Example Usage
```
resource "kubernetes_namespace" "example" {
metadata {
annotations {
name = "example-annotation"
}
labels {
mylabel = "label-value"
}
name = "TerraformExampleNamespace"
}
}
```
## Argument Reference
The following arguments are supported:
* `metadata` - (Required) Standard namespace's [metadata](https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata).
## Nested Blocks
### `metadata`
#### Arguments
* `annotations` - (Optional) An unstructured key value map stored with the namespace 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 about [name idempotency](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) namespaces. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels
* `name` - (Optional) Name of the namespace, must be unique. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names
#### 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 namespace that can be used by clients to determine when namespaces have changed. Read more about [concurrency control and consistency](https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency).
* `self_link` - A URL representing this namespace.
* `uid` - The unique in time and space value for this namespace. More info: http://kubernetes.io/docs/user-guide/identifiers#uids
## Import
Namespaces can be imported using their name, e.g.
```
$ terraform import kubernetes_namespace.n TerraformExampleNamespace
```

View File

@ -303,6 +303,10 @@
<a href="/docs/providers/influxdb/index.html">InfluxDB</a>
</li>
<li<%= sidebar_current("docs-providers-kubernetes") %>>
<a href="/docs/providers/kubernetes/index.html">Kubernetes</a>
</li>
<li<%= sidebar_current("docs-providers-librato") %>>
<a href="/docs/providers/librato/index.html">Librato</a>
</li>

View File

@ -0,0 +1,26 @@
<% wrap_layout :inner do %>
<% content_for :sidebar do %>
<div class="docs-sidebar hidden-print affix-top" role="complementary">
<ul class="nav docs-sidenav">
<li<%= sidebar_current("docs-home") %>>
<a href="/docs/providers/index.html">&laquo; Documentation Home</a>
</li>
<li<%= sidebar_current("docs-kubernetes-index") %>>
<a href="/docs/providers/kubernetes/index.html">Kubernetes Provider</a>
</li>
<li<%= sidebar_current(/^docs-kubernetes-resource/) %>>
<a href="#">Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-kubernetes-resource-namespace") %>>
<a href="/docs/providers/kubernetes/r/namespace.html">kubernetes_namespace</a>
</li>
</ul>
</li>
</ul>
</div>
<% end %>
<%= yield %>
<% end %>