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 }