github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/builtin/providers/kubernetes/resource_kubernetes_persistent_volume_claim.go (about)

     1  package kubernetes
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"github.com/hashicorp/terraform/helper/resource"
     9  	"github.com/hashicorp/terraform/helper/schema"
    10  	"k8s.io/apimachinery/pkg/api/errors"
    11  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  	"k8s.io/apimachinery/pkg/fields"
    13  	pkgApi "k8s.io/apimachinery/pkg/types"
    14  	api "k8s.io/kubernetes/pkg/api/v1"
    15  	kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
    16  )
    17  
    18  func resourceKubernetesPersistentVolumeClaim() *schema.Resource {
    19  	return &schema.Resource{
    20  		Create: resourceKubernetesPersistentVolumeClaimCreate,
    21  		Read:   resourceKubernetesPersistentVolumeClaimRead,
    22  		Exists: resourceKubernetesPersistentVolumeClaimExists,
    23  		Update: resourceKubernetesPersistentVolumeClaimUpdate,
    24  		Delete: resourceKubernetesPersistentVolumeClaimDelete,
    25  		Importer: &schema.ResourceImporter{
    26  			State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
    27  				d.Set("wait_until_bound", true)
    28  				return []*schema.ResourceData{d}, nil
    29  			},
    30  		},
    31  
    32  		Timeouts: &schema.ResourceTimeout{
    33  			Create: schema.DefaultTimeout(5 * time.Minute),
    34  		},
    35  
    36  		Schema: map[string]*schema.Schema{
    37  			"metadata": namespacedMetadataSchema("persistent volume claim", true),
    38  			"spec": {
    39  				Type:        schema.TypeList,
    40  				Description: "Spec defines the desired characteristics of a volume requested by a pod author. More info: http://kubernetes.io/docs/user-guide/persistent-volumes#persistentvolumeclaims",
    41  				Required:    true,
    42  				ForceNew:    true,
    43  				MaxItems:    1,
    44  				Elem: &schema.Resource{
    45  					Schema: map[string]*schema.Schema{
    46  						"access_modes": {
    47  							Type:        schema.TypeSet,
    48  							Description: "A set of the desired access modes the volume should have. More info: http://kubernetes.io/docs/user-guide/persistent-volumes#access-modes-1",
    49  							Required:    true,
    50  							ForceNew:    true,
    51  							Elem:        &schema.Schema{Type: schema.TypeString},
    52  							Set:         schema.HashString,
    53  						},
    54  						"resources": {
    55  							Type:        schema.TypeList,
    56  							Description: "A list of the minimum resources the volume should have. More info: http://kubernetes.io/docs/user-guide/persistent-volumes#resources",
    57  							Required:    true,
    58  							ForceNew:    true,
    59  							MaxItems:    1,
    60  							Elem: &schema.Resource{
    61  								Schema: map[string]*schema.Schema{
    62  									"limits": {
    63  										Type:        schema.TypeMap,
    64  										Description: "Map describing the maximum amount of compute resources allowed. More info: http://kubernetes.io/docs/user-guide/compute-resources/",
    65  										Optional:    true,
    66  										ForceNew:    true,
    67  									},
    68  									"requests": {
    69  										Type:        schema.TypeMap,
    70  										Description: "Map describing the minimum amount of compute resources required. If this is omitted for a container, it defaults to `limits` if that is explicitly specified, otherwise to an implementation-defined value. More info: http://kubernetes.io/docs/user-guide/compute-resources/",
    71  										Optional:    true,
    72  										ForceNew:    true,
    73  									},
    74  								},
    75  							},
    76  						},
    77  						"selector": {
    78  							Type:        schema.TypeList,
    79  							Description: "A label query over volumes to consider for binding.",
    80  							Optional:    true,
    81  							ForceNew:    true,
    82  							MaxItems:    1,
    83  							Elem: &schema.Resource{
    84  								Schema: map[string]*schema.Schema{
    85  									"match_expressions": {
    86  										Type:        schema.TypeList,
    87  										Description: "A list of label selector requirements. The requirements are ANDed.",
    88  										Optional:    true,
    89  										ForceNew:    true,
    90  										Elem: &schema.Resource{
    91  											Schema: map[string]*schema.Schema{
    92  												"key": {
    93  													Type:        schema.TypeString,
    94  													Description: "The label key that the selector applies to.",
    95  													Optional:    true,
    96  													ForceNew:    true,
    97  												},
    98  												"operator": {
    99  													Type:        schema.TypeString,
   100  													Description: "A key's relationship to a set of values. Valid operators ard `In`, `NotIn`, `Exists` and `DoesNotExist`.",
   101  													Optional:    true,
   102  													ForceNew:    true,
   103  												},
   104  												"values": {
   105  													Type:        schema.TypeSet,
   106  													Description: "An array of string values. If the operator is `In` or `NotIn`, the values array must be non-empty. If the operator is `Exists` or `DoesNotExist`, the values array must be empty. This array is replaced during a strategic merge patch.",
   107  													Optional:    true,
   108  													ForceNew:    true,
   109  													Elem:        &schema.Schema{Type: schema.TypeString},
   110  													Set:         schema.HashString,
   111  												},
   112  											},
   113  										},
   114  									},
   115  									"match_labels": {
   116  										Type:        schema.TypeMap,
   117  										Description: "A map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of `match_expressions`, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.",
   118  										Optional:    true,
   119  										ForceNew:    true,
   120  									},
   121  								},
   122  							},
   123  						},
   124  						"volume_name": {
   125  							Type:        schema.TypeString,
   126  							Description: "The binding reference to the PersistentVolume backing this claim.",
   127  							Optional:    true,
   128  							ForceNew:    true,
   129  							Computed:    true,
   130  						},
   131  					},
   132  				},
   133  			},
   134  			"wait_until_bound": {
   135  				Type:        schema.TypeBool,
   136  				Description: "Whether to wait for the claim to reach `Bound` state (to find volume in which to claim the space)",
   137  				Optional:    true,
   138  				Default:     true,
   139  			},
   140  		},
   141  	}
   142  }
   143  
   144  func resourceKubernetesPersistentVolumeClaimCreate(d *schema.ResourceData, meta interface{}) error {
   145  	conn := meta.(*kubernetes.Clientset)
   146  
   147  	metadata := expandMetadata(d.Get("metadata").([]interface{}))
   148  	spec, err := expandPersistentVolumeClaimSpec(d.Get("spec").([]interface{}))
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	claim := api.PersistentVolumeClaim{
   154  		ObjectMeta: metadata,
   155  		Spec:       spec,
   156  	}
   157  
   158  	log.Printf("[INFO] Creating new persistent volume claim: %#v", claim)
   159  	out, err := conn.CoreV1().PersistentVolumeClaims(metadata.Namespace).Create(&claim)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	log.Printf("[INFO] Submitted new persistent volume claim: %#v", out)
   164  
   165  	d.SetId(buildId(out.ObjectMeta))
   166  	name := out.ObjectMeta.Name
   167  
   168  	if d.Get("wait_until_bound").(bool) {
   169  		var lastEvent api.Event
   170  		stateConf := &resource.StateChangeConf{
   171  			Target:  []string{"Bound"},
   172  			Pending: []string{"Pending"},
   173  			Timeout: d.Timeout(schema.TimeoutCreate),
   174  			Refresh: func() (interface{}, string, error) {
   175  				out, err := conn.CoreV1().PersistentVolumeClaims(metadata.Namespace).Get(name, meta_v1.GetOptions{})
   176  				if err != nil {
   177  					log.Printf("[ERROR] Received error: %#v", err)
   178  					return out, "", err
   179  				}
   180  
   181  				events, err := conn.CoreV1().Events(metadata.Namespace).List(meta_v1.ListOptions{
   182  					FieldSelector: fields.Set(map[string]string{
   183  						"involvedObject.name":      metadata.Name,
   184  						"involvedObject.namespace": metadata.Namespace,
   185  						"involvedObject.kind":      "PersistentVolumeClaim",
   186  					}).String(),
   187  				})
   188  				if err != nil {
   189  					return out, "", err
   190  				}
   191  				if len(events.Items) > 0 {
   192  					lastEvent = events.Items[0]
   193  				}
   194  
   195  				statusPhase := fmt.Sprintf("%v", out.Status.Phase)
   196  				log.Printf("[DEBUG] Persistent volume claim %s status received: %#v", out.Name, statusPhase)
   197  				return out, statusPhase, nil
   198  			},
   199  		}
   200  		_, err = stateConf.WaitForState()
   201  		if err != nil {
   202  			reason := ""
   203  			if lastEvent.Reason != "" {
   204  				reason = fmt.Sprintf(". Reason: %s: %s", lastEvent.Reason, lastEvent.Message)
   205  			}
   206  			return fmt.Errorf("%s%s", err, reason)
   207  		}
   208  	}
   209  	log.Printf("[INFO] Persistent volume claim %s created", out.Name)
   210  
   211  	return resourceKubernetesPersistentVolumeClaimRead(d, meta)
   212  }
   213  
   214  func resourceKubernetesPersistentVolumeClaimRead(d *schema.ResourceData, meta interface{}) error {
   215  	conn := meta.(*kubernetes.Clientset)
   216  
   217  	namespace, name := idParts(d.Id())
   218  	log.Printf("[INFO] Reading persistent volume claim %s", name)
   219  	claim, err := conn.CoreV1().PersistentVolumeClaims(namespace).Get(name, meta_v1.GetOptions{})
   220  	if err != nil {
   221  		log.Printf("[DEBUG] Received error: %#v", err)
   222  		return err
   223  	}
   224  	log.Printf("[INFO] Received persistent volume claim: %#v", claim)
   225  	err = d.Set("metadata", flattenMetadata(claim.ObjectMeta))
   226  	if err != nil {
   227  		return err
   228  	}
   229  	err = d.Set("spec", flattenPersistentVolumeClaimSpec(claim.Spec))
   230  	if err != nil {
   231  		return err
   232  	}
   233  
   234  	return nil
   235  }
   236  
   237  func resourceKubernetesPersistentVolumeClaimUpdate(d *schema.ResourceData, meta interface{}) error {
   238  	conn := meta.(*kubernetes.Clientset)
   239  	namespace, name := idParts(d.Id())
   240  
   241  	ops := patchMetadata("metadata.0.", "/metadata/", d)
   242  	// The whole spec is ForceNew = nothing to update there
   243  	data, err := ops.MarshalJSON()
   244  	if err != nil {
   245  		return fmt.Errorf("Failed to marshal update operations: %s", err)
   246  	}
   247  
   248  	log.Printf("[INFO] Updating persistent volume claim: %s", ops)
   249  	out, err := conn.CoreV1().PersistentVolumeClaims(namespace).Patch(name, pkgApi.JSONPatchType, data)
   250  	if err != nil {
   251  		return err
   252  	}
   253  	log.Printf("[INFO] Submitted updated persistent volume claim: %#v", out)
   254  
   255  	return resourceKubernetesPersistentVolumeClaimRead(d, meta)
   256  }
   257  
   258  func resourceKubernetesPersistentVolumeClaimDelete(d *schema.ResourceData, meta interface{}) error {
   259  	conn := meta.(*kubernetes.Clientset)
   260  
   261  	namespace, name := idParts(d.Id())
   262  	log.Printf("[INFO] Deleting persistent volume claim: %#v", name)
   263  	err := conn.CoreV1().PersistentVolumeClaims(namespace).Delete(name, &meta_v1.DeleteOptions{})
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	log.Printf("[INFO] Persistent volume claim %s deleted", name)
   269  
   270  	d.SetId("")
   271  	return nil
   272  }
   273  
   274  func resourceKubernetesPersistentVolumeClaimExists(d *schema.ResourceData, meta interface{}) (bool, error) {
   275  	conn := meta.(*kubernetes.Clientset)
   276  
   277  	namespace, name := idParts(d.Id())
   278  	log.Printf("[INFO] Checking persistent volume claim %s", name)
   279  	_, err := conn.CoreV1().PersistentVolumeClaims(namespace).Get(name, meta_v1.GetOptions{})
   280  	if err != nil {
   281  		if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 {
   282  			return false, nil
   283  		}
   284  		log.Printf("[DEBUG] Received error: %#v", err)
   285  	}
   286  	return true, err
   287  }