Skip to content

Commit c7cde43

Browse files
BBBmauJHeilCoveo
andauthored
Add ephemeral volumes to Pod Spec (#2183)
* Working implementation of ephemeral volumes * Add acceptance test for ephemeral volume * add changelog * Rename 2145.txt to 2185.txt * Rename 2185.txt to 2183.txt * add docs * make tests-lint-fix * revert metadataFields() --------- Co-authored-by: jheil <[email protected]> Co-authored-by: JHeil <[email protected]>
1 parent 98b02ed commit c7cde43

7 files changed

+235
-4
lines changed

.changelog/2183.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:feature
2+
`kubernetes/resource_kubernetes_pod.go`: add `ephemeral` volume type to pod specification [GH-2032]
3+
```

kubernetes/resource_kubernetes_persistent_volume_claim_test.go

+37
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,43 @@ func testAccCheckKubernetesPersistentVolumeClaimExists(n string, obj *api.Persis
572572
}
573573
}
574574

575+
func testAccCheckKubernetesPersistentVolumeClaimCreated(namespace, name string, obj *api.PersistentVolumeClaim) resource.TestCheckFunc {
576+
return func(s *terraform.State) error {
577+
conn, err := testAccProvider.Meta().(KubeClientsets).MainClientset()
578+
if err != nil {
579+
return err
580+
}
581+
ctx := context.TODO()
582+
out, err := conn.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, name, metav1.GetOptions{})
583+
if err != nil {
584+
return err
585+
}
586+
587+
*obj = *out
588+
return nil
589+
}
590+
}
591+
592+
func testAccCheckKubernetesPersistentVolumeClaimIsDestroyed(obj *api.PersistentVolumeClaim) resource.TestCheckFunc {
593+
return func(s *terraform.State) error {
594+
meta := obj.GetObjectMeta()
595+
conn, err := testAccProvider.Meta().(KubeClientsets).MainClientset()
596+
if err != nil {
597+
return err
598+
}
599+
ctx := context.TODO()
600+
out, err := conn.CoreV1().PersistentVolumeClaims(meta.GetNamespace()).Get(ctx, meta.GetName(), metav1.GetOptions{})
601+
if err != nil {
602+
if errors.IsNotFound(err) {
603+
return nil
604+
}
605+
return err
606+
}
607+
608+
return fmt.Errorf("Expected no PVC but still found %q", out.GetObjectMeta().GetName())
609+
}
610+
}
611+
575612
func testAccCheckClaimRef(pv *api.PersistentVolume, expected *ObjectRefStatic) resource.TestCheckFunc {
576613
return func(s *terraform.State) error {
577614
or := pv.Spec.ClaimRef

kubernetes/resource_kubernetes_pod_test.go

+105
Original file line numberDiff line numberDiff line change
@@ -1495,6 +1495,36 @@ func TestAccKubernetesPod_runtimeClassName(t *testing.T) {
14951495
})
14961496
}
14971497

1498+
func TestAccKubernetesPod_with_ephemeral_storage(t *testing.T) {
1499+
var pod api.Pod
1500+
var pvc api.PersistentVolumeClaim
1501+
1502+
testName := fmt.Sprintf("tf-acc-test-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))
1503+
imageName := nginxImageVersion
1504+
1505+
resource.Test(t, resource.TestCase{
1506+
PreCheck: func() { testAccPreCheck(t) },
1507+
ProviderFactories: testAccProviderFactories,
1508+
CheckDestroy: testAccCheckKubernetesPodDestroy,
1509+
Steps: []resource.TestStep{
1510+
{
1511+
Config: testAccKubernetesPodEphemeralStorage(testName, imageName),
1512+
Check: resource.ComposeAggregateTestCheckFunc(
1513+
testAccCheckKubernetesPodExists("kubernetes_pod_v1.test", &pod),
1514+
testAccCheckKubernetesPersistentVolumeClaimCreated("default", testName+"-ephemeral", &pvc),
1515+
resource.TestCheckResourceAttr("kubernetes_pod_v1.test", "spec.0.volume.0.name", "ephemeral"),
1516+
resource.TestCheckResourceAttr("kubernetes_pod_v1.test", "spec.0.volume.0.ephemeral.0.spec.0.storage_class_name", testName),
1517+
),
1518+
},
1519+
// Do a second test with only the storage class and check that the PVC has been deleted by the ephemeral volume
1520+
{
1521+
Config: testAccKubernetesPodEphemeralStorageWithoutPod(testName),
1522+
Check: testAccCheckKubernetesPersistentVolumeClaimIsDestroyed(&pvc),
1523+
},
1524+
},
1525+
})
1526+
}
1527+
14981528
func createRuncRuntimeClass(rn string) error {
14991529
conn, err := testAccProvider.Meta().(KubeClientsets).MainClientset()
15001530
if err != nil {
@@ -3292,3 +3322,78 @@ resource "kubernetes_pod_v1" "scheduler" {
32923322
}
32933323
`, name)
32943324
}
3325+
3326+
func testAccKubernetesPodEphemeralStorage(name, imageName string) string {
3327+
return fmt.Sprintf(`resource "kubernetes_storage_class" "test" {
3328+
metadata {
3329+
name = %[1]q
3330+
}
3331+
storage_provisioner = "pd.csi.storage.gke.io"
3332+
reclaim_policy = "Delete"
3333+
volume_binding_mode = "WaitForFirstConsumer"
3334+
3335+
parameters = {
3336+
type = "pd-standard"
3337+
}
3338+
}
3339+
3340+
resource "kubernetes_pod_v1" "test" {
3341+
metadata {
3342+
name = %[1]q
3343+
labels = {
3344+
Test = "TfAcceptanceTest"
3345+
}
3346+
}
3347+
spec {
3348+
priority_class_name = "default"
3349+
container {
3350+
name = "containername"
3351+
image = %[2]q
3352+
3353+
volume_mount {
3354+
mount_path = "/ephemeral"
3355+
name = "ephemeral"
3356+
}
3357+
}
3358+
3359+
volume {
3360+
name = "ephemeral"
3361+
3362+
ephemeral {
3363+
metadata {
3364+
labels = {
3365+
Test = "TfAcceptanceTest"
3366+
}
3367+
}
3368+
spec {
3369+
access_modes = ["ReadWriteOnce"]
3370+
storage_class_name = %[1]q
3371+
3372+
resources {
3373+
requests = {
3374+
storage = "5Gi"
3375+
}
3376+
}
3377+
}
3378+
}
3379+
}
3380+
}
3381+
}
3382+
`, name, imageName)
3383+
}
3384+
3385+
func testAccKubernetesPodEphemeralStorageWithoutPod(name string) string {
3386+
return fmt.Sprintf(`resource "kubernetes_storage_class" "test" {
3387+
metadata {
3388+
name = %[1]q
3389+
}
3390+
storage_provisioner = "pd.csi.storage.gke.io"
3391+
reclaim_policy = "Delete"
3392+
volume_binding_mode = "WaitForFirstConsumer"
3393+
3394+
parameters = {
3395+
type = "pd-standard"
3396+
}
3397+
}
3398+
`, name)
3399+
}

kubernetes/schema_pod_spec.go

+48-4
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,50 @@ func volumeSchema(isUpdatable bool) *schema.Resource {
687687
},
688688
}
689689

690+
v["ephemeral"] = &schema.Schema{
691+
Type: schema.TypeList,
692+
Description: "Represents an ephemeral volume that is handled by a normal storage driver. More info: https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/#generic-ephemeral-volumes",
693+
Optional: true,
694+
MaxItems: 1,
695+
Elem: &schema.Resource{
696+
Schema: map[string]*schema.Schema{
697+
"metadata": {
698+
Type: schema.TypeList,
699+
Description: "May contain labels and annotations that will be copied into the PVC when creating it.",
700+
Required: true,
701+
MaxItems: 1,
702+
Elem: &schema.Resource{
703+
Schema: map[string]*schema.Schema{
704+
"annotations": {
705+
Type: schema.TypeMap,
706+
Description: "An unstructured key value map stored with the persistent volume claim that may be used to store arbitrary metadata. More info: http://kubernetes.io/docs/user-guide/annotations",
707+
Optional: true,
708+
Elem: &schema.Schema{Type: schema.TypeString},
709+
ValidateFunc: validateAnnotations,
710+
},
711+
"labels": {
712+
Type: schema.TypeMap,
713+
Description: "Map of string keys and values that can be used to organize and categorize (scope and select) the persistent volume claim. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels",
714+
Optional: true,
715+
Elem: &schema.Schema{Type: schema.TypeString},
716+
ValidateFunc: validateLabels,
717+
},
718+
},
719+
},
720+
},
721+
"spec": {
722+
Type: schema.TypeList,
723+
Description: "The specification for the PersistentVolumeClaim. The entire content is copied unchanged into the PVC that gets created from this template. The same fields as in a PersistentVolumeClaim are also valid here.",
724+
Required: true,
725+
MaxItems: 1,
726+
Elem: &schema.Resource{
727+
Schema: persistentVolumeClaimSpecFields(),
728+
},
729+
},
730+
},
731+
},
732+
}
733+
690734
v["persistent_volume_claim"] = &schema.Schema{
691735
Type: schema.TypeList,
692736
Description: "The specification of a persistent volume.",
@@ -789,7 +833,7 @@ func volumeSchema(isUpdatable bool) *schema.Resource {
789833
Elem: &schema.Resource{
790834
Schema: map[string]*schema.Schema{
791835
// identical to SecretVolumeSource but without the default mode and uses a local object reference as name instead of a secret name.
792-
"secret": &schema.Schema{
836+
"secret": {
793837
Type: schema.TypeList,
794838
Description: "Secret represents a secret that should populate this volume. More info: http://kubernetes.io/docs/user-guide/volumes#secrets",
795839
Optional: true,
@@ -835,7 +879,7 @@ func volumeSchema(isUpdatable bool) *schema.Resource {
835879
},
836880
},
837881
// identical to ConfigMapVolumeSource but without the default mode and uses a local object reference as name instead of a secret name.
838-
"config_map": &schema.Schema{
882+
"config_map": {
839883
Type: schema.TypeList,
840884
Description: "ConfigMap represents a configMap that should populate this volume",
841885
Optional: true,
@@ -881,7 +925,7 @@ func volumeSchema(isUpdatable bool) *schema.Resource {
881925
},
882926
},
883927
// identical to DownwardAPIVolumeSource but without the default mode.
884-
"downward_api": &schema.Schema{
928+
"downward_api": {
885929
Type: schema.TypeList,
886930
Description: "DownwardAPI represents downward API about the pod that should populate this volume",
887931
Optional: true,
@@ -959,7 +1003,7 @@ func volumeSchema(isUpdatable bool) *schema.Resource {
9591003
},
9601004
},
9611005
},
962-
"service_account_token": &schema.Schema{
1006+
"service_account_token": {
9631007
Type: schema.TypeList,
9641008
Description: "A projected service account token volume",
9651009
Optional: true,

kubernetes/structure_persistent_volume_spec.go

+31
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,18 @@ func flattenVsphereVirtualDiskVolumeSource(in *v1.VsphereVirtualDiskVolumeSource
545545
return []interface{}{att}
546546
}
547547

548+
func flattenEphemeralVolumeSource(in *v1.EphemeralVolumeSource) []interface{} {
549+
att := make(map[string]interface{})
550+
551+
metadata := make(map[string]interface{})
552+
metadata["labels"] = in.VolumeClaimTemplate.ObjectMeta.GetLabels()
553+
metadata["annotations"] = in.VolumeClaimTemplate.ObjectMeta.GetAnnotations()
554+
555+
att["metadata"] = []interface{}{metadata}
556+
att["spec"] = flattenPersistentVolumeClaimSpec(in.VolumeClaimTemplate.Spec)
557+
return []interface{}{att}
558+
}
559+
548560
// Expanders
549561

550562
func expandAWSElasticBlockStoreVolumeSource(l []interface{}) *v1.AWSElasticBlockStoreVolumeSource {
@@ -1233,6 +1245,25 @@ func expandVsphereVirtualDiskVolumeSource(l []interface{}) *v1.VsphereVirtualDis
12331245
return obj
12341246
}
12351247

1248+
func expandEphemeralVolumeSource(l []interface{}) (*v1.EphemeralVolumeSource, error) {
1249+
if len(l) == 0 || l[0] == nil {
1250+
return &v1.EphemeralVolumeSource{}, nil
1251+
}
1252+
in := l[0].(map[string]interface{})
1253+
pvc_claim, err := expandPersistentVolumeClaimSpec(in["spec"].([]interface{}))
1254+
if err != nil {
1255+
return &v1.EphemeralVolumeSource{}, err
1256+
}
1257+
1258+
obj := &v1.EphemeralVolumeSource{
1259+
VolumeClaimTemplate: &v1.PersistentVolumeClaimTemplate{
1260+
ObjectMeta: expandMetadata(in["metadata"].([]interface{})),
1261+
Spec: *pvc_claim,
1262+
},
1263+
}
1264+
return obj, nil
1265+
}
1266+
12361267
func patchPersistentVolumeSpec(pathPrefix, prefix string, d *schema.ResourceData) (PatchOperations, error) {
12371268
ops := make([]PatchOperation, 0)
12381269
prefix += ".0."

kubernetes/structures_pod.go

+10
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,9 @@ func flattenVolumes(volumes []v1.Volume) ([]interface{}, error) {
422422
if v.PhotonPersistentDisk != nil {
423423
obj["photon_persistent_disk"] = flattenPhotonPersistentDiskVolumeSource(v.PhotonPersistentDisk)
424424
}
425+
if v.Ephemeral != nil {
426+
obj["ephemeral"] = flattenEphemeralVolumeSource(v.Ephemeral)
427+
}
425428
att[i] = obj
426429
}
427430
return att, nil
@@ -1514,6 +1517,13 @@ func expandVolumes(volumes []interface{}) ([]v1.Volume, error) {
15141517
if v, ok := m["photon_persistent_disk"].([]interface{}); ok && len(v) > 0 {
15151518
vl[i].PhotonPersistentDisk = expandPhotonPersistentDiskVolumeSource(v)
15161519
}
1520+
if v, ok := m["ephemeral"].([]interface{}); ok && len(v) > 0 {
1521+
ephemeral, err := expandEphemeralVolumeSource(v)
1522+
if err != nil {
1523+
return vl, err
1524+
}
1525+
vl[i].Ephemeral = ephemeral
1526+
}
15171527
}
15181528
return vl, nil
15191529
}

website/docs/r/pod.html.markdown

+1
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,7 @@ The `items` block supports the following:
907907
* `csi` - (Optional) CSI represents storage that is handled by an external CSI driver. For more info see [Kubernetes reference](https://kubernetes.io/docs/concepts/storage/volumes/#csi)
908908
* `downward_api` - (Optional) DownwardAPI represents downward API about the pod that should populate this volume
909909
* `empty_dir` - (Optional) EmptyDir represents a temporary directory that shares a pod's lifetime. For more info see [Kubernetes reference](http://kubernetes.io/docs/user-guide/volumes#emptydir)
910+
* `ephemeral` - (Optional) Represents an ephemeral volume that is handled by a normal storage driver. More info: https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/#generic-ephemeral-volumes.
910911
* `fc` - (Optional) Represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.
911912
* `flex_volume` - (Optional) Represents a generic volume resource that is provisioned/attached using an exec based plugin. This is an alpha feature and may change in future.
912913
* `flocker` - (Optional) Represents a Flocker volume attached to a kubelet's host machine and exposed to the pod for its usage. This depends on the Flocker control service being running

0 commit comments

Comments
 (0)