Skip to content

Commit 1017f66

Browse files
authored
Merge pull request #81 from maysunfaisal/181-1
Migrate devfile specific vol code from odo to devfile/library
2 parents ffe3a0a + 294012e commit 1017f66

File tree

4 files changed

+443
-3
lines changed

4 files changed

+443
-3
lines changed

pkg/devfile/generator/generators.go

+53
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"k8s.io/apimachinery/pkg/api/resource"
1111
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1212

13+
v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
1314
"github.com/devfile/library/pkg/devfile/parser"
1415
"github.com/devfile/library/pkg/devfile/parser/data/v2/common"
1516
)
@@ -284,3 +285,55 @@ func GetImageStream(imageStreamParams ImageStreamParams) imagev1.ImageStream {
284285
}
285286
return imageStream
286287
}
288+
289+
// VolumeInfo is a struct to hold the pvc name and the volume name to create a volume.
290+
type VolumeInfo struct {
291+
PVCName string
292+
VolumeName string
293+
}
294+
295+
// VolumeParams is a struct that contains the required data to create Kubernetes Volumes and mount Volumes in Containers
296+
type VolumeParams struct {
297+
// Containers is a list of containers that needs to be updated for the volume mounts
298+
Containers []corev1.Container
299+
300+
// VolumeNameToVolumeInfo is a map of the devfile volume name to the volume info containing the pvc name and the volume name.
301+
VolumeNameToVolumeInfo map[string]VolumeInfo
302+
}
303+
304+
// GetVolumesAndVolumeMounts gets the PVC volumes and updates the containers with the volume mounts.
305+
func GetVolumesAndVolumeMounts(devfileObj parser.DevfileObj, volumeParams VolumeParams, options common.DevfileOptions) ([]corev1.Volume, error) {
306+
307+
containerComponents, err := devfileObj.Data.GetDevfileContainerComponents(options)
308+
if err != nil {
309+
return nil, err
310+
}
311+
312+
var pvcVols []corev1.Volume
313+
for volName, volInfo := range volumeParams.VolumeNameToVolumeInfo {
314+
pvcVols = append(pvcVols, getPVC(volInfo.VolumeName, volInfo.PVCName))
315+
316+
// containerNameToMountPaths is a map of the Devfile container name to their Devfile Volume Mount Paths for a given Volume Name
317+
containerNameToMountPaths := make(map[string][]string)
318+
for _, containerComp := range containerComponents {
319+
for _, volumeMount := range containerComp.Container.VolumeMounts {
320+
if volName == volumeMount.Name {
321+
containerNameToMountPaths[containerComp.Name] = append(containerNameToMountPaths[containerComp.Name], GetVolumeMountPath(volumeMount))
322+
}
323+
}
324+
}
325+
326+
addVolumeMountToContainers(volumeParams.Containers, volInfo.VolumeName, containerNameToMountPaths)
327+
}
328+
return pvcVols, nil
329+
}
330+
331+
// GetVolumeMountPath gets the volume mount's path.
332+
func GetVolumeMountPath(volumeMount v1.VolumeMount) string {
333+
// if there is no volume mount path, default to volume mount name as per devfile schema
334+
if volumeMount.Path == "" {
335+
volumeMount.Path = "/" + volumeMount.Name
336+
}
337+
338+
return volumeMount.Path
339+
}

pkg/devfile/generator/generators_test.go

+268-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"reflect"
55
"testing"
66

7-
"github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
87
v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
98
"github.com/devfile/api/v2/pkg/attributes"
109
"github.com/devfile/library/pkg/devfile/parser"
@@ -28,7 +27,7 @@ func TestGetContainers(t *testing.T) {
2827
trueMountSources := true
2928
falseMountSources := false
3029

31-
project := v1alpha2.Project{
30+
project := v1.Project{
3231
ClonePath: "test-project/",
3332
Name: "project0",
3433
ProjectSource: v1.ProjectSource{
@@ -189,7 +188,7 @@ func TestGetContainers(t *testing.T) {
189188
DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{
190189
DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{
191190
Components: tt.containerComponents,
192-
Projects: []v1alpha2.Project{
191+
Projects: []v1.Project{
193192
project,
194193
},
195194
},
@@ -228,3 +227,269 @@ func TestGetContainers(t *testing.T) {
228227
}
229228

230229
}
230+
231+
func TestGetVolumesAndVolumeMounts(t *testing.T) {
232+
233+
type testVolumeMountInfo struct {
234+
mountPath string
235+
volumeName string
236+
}
237+
238+
tests := []struct {
239+
name string
240+
components []v1.Component
241+
volumeNameToVolInfo map[string]VolumeInfo
242+
wantContainerToVol map[string][]testVolumeMountInfo
243+
wantErr bool
244+
}{
245+
{
246+
name: "One volume mounted",
247+
components: []v1.Component{testingutil.GetFakeContainerComponent("comp1"), testingutil.GetFakeContainerComponent("comp2")},
248+
volumeNameToVolInfo: map[string]VolumeInfo{
249+
"myvolume1": {
250+
PVCName: "volume1-pvc",
251+
VolumeName: "volume1-pvc-vol",
252+
},
253+
},
254+
wantContainerToVol: map[string][]testVolumeMountInfo{
255+
"comp1": {
256+
{
257+
mountPath: "/my/volume/mount/path1",
258+
volumeName: "volume1-pvc-vol",
259+
},
260+
},
261+
"comp2": {
262+
{
263+
mountPath: "/my/volume/mount/path1",
264+
volumeName: "volume1-pvc-vol",
265+
},
266+
},
267+
},
268+
wantErr: false,
269+
},
270+
{
271+
name: "One volume mounted at diff locations",
272+
components: []v1.Component{
273+
{
274+
Name: "container1",
275+
ComponentUnion: v1.ComponentUnion{
276+
Container: &v1.ContainerComponent{
277+
Container: v1.Container{
278+
VolumeMounts: []v1.VolumeMount{
279+
{
280+
Name: "volume1",
281+
Path: "/path1",
282+
},
283+
{
284+
Name: "volume1",
285+
Path: "/path2",
286+
},
287+
},
288+
},
289+
},
290+
},
291+
},
292+
},
293+
volumeNameToVolInfo: map[string]VolumeInfo{
294+
"volume1": {
295+
PVCName: "volume1-pvc",
296+
VolumeName: "volume1-pvc-vol",
297+
},
298+
},
299+
wantContainerToVol: map[string][]testVolumeMountInfo{
300+
"container1": {
301+
{
302+
mountPath: "/path1",
303+
volumeName: "volume1-pvc-vol",
304+
},
305+
{
306+
mountPath: "/path2",
307+
volumeName: "volume1-pvc-vol",
308+
},
309+
},
310+
},
311+
wantErr: false,
312+
},
313+
{
314+
name: "One volume mounted at diff container components",
315+
components: []v1.Component{
316+
{
317+
Name: "container1",
318+
ComponentUnion: v1.ComponentUnion{
319+
Container: &v1.ContainerComponent{
320+
Container: v1.Container{
321+
VolumeMounts: []v1.VolumeMount{
322+
{
323+
Name: "volume1",
324+
Path: "/path1",
325+
},
326+
},
327+
},
328+
},
329+
},
330+
},
331+
{
332+
Name: "container2",
333+
ComponentUnion: v1.ComponentUnion{
334+
Container: &v1.ContainerComponent{
335+
Container: v1.Container{
336+
VolumeMounts: []v1.VolumeMount{
337+
{
338+
Name: "volume1",
339+
Path: "/path2",
340+
},
341+
},
342+
},
343+
},
344+
},
345+
},
346+
},
347+
volumeNameToVolInfo: map[string]VolumeInfo{
348+
"volume1": {
349+
PVCName: "volume1-pvc",
350+
VolumeName: "volume1-pvc-vol",
351+
},
352+
},
353+
wantContainerToVol: map[string][]testVolumeMountInfo{
354+
"container1": {
355+
{
356+
mountPath: "/path1",
357+
volumeName: "volume1-pvc-vol",
358+
},
359+
},
360+
"container2": {
361+
{
362+
mountPath: "/path2",
363+
volumeName: "volume1-pvc-vol",
364+
},
365+
},
366+
},
367+
wantErr: false,
368+
},
369+
{
370+
name: "Invalid case",
371+
components: []v1.Component{
372+
{
373+
Name: "container1",
374+
Attributes: attributes.Attributes{}.FromStringMap(map[string]string{
375+
"firstString": "firstStringValue",
376+
}),
377+
ComponentUnion: v1.ComponentUnion{},
378+
},
379+
},
380+
wantErr: true,
381+
},
382+
}
383+
384+
for _, tt := range tests {
385+
t.Run(tt.name, func(t *testing.T) {
386+
387+
devObj := parser.DevfileObj{
388+
Data: &v2.DevfileV2{
389+
Devfile: v1.Devfile{
390+
DevWorkspaceTemplateSpec: v1.DevWorkspaceTemplateSpec{
391+
DevWorkspaceTemplateSpecContent: v1.DevWorkspaceTemplateSpecContent{
392+
Components: tt.components,
393+
},
394+
},
395+
},
396+
},
397+
}
398+
399+
containers, err := GetContainers(devObj, common.DevfileOptions{})
400+
if err != nil {
401+
t.Errorf("TestGetVolumesAndVolumeMounts error - %v", err)
402+
return
403+
}
404+
405+
var options common.DevfileOptions
406+
if tt.wantErr {
407+
options = common.DevfileOptions{
408+
Filter: map[string]interface{}{
409+
"firstString": "firstStringValue",
410+
},
411+
}
412+
}
413+
414+
volumeParams := VolumeParams{
415+
Containers: containers,
416+
VolumeNameToVolumeInfo: tt.volumeNameToVolInfo,
417+
}
418+
419+
pvcVols, err := GetVolumesAndVolumeMounts(devObj, volumeParams, options)
420+
if tt.wantErr == (err == nil) {
421+
t.Errorf("TestGetVolumesAndVolumeMounts() error = %v, wantErr %v", err, tt.wantErr)
422+
} else if err == nil {
423+
// check if the pvc volumes returned are correct
424+
for _, volInfo := range tt.volumeNameToVolInfo {
425+
matched := false
426+
for _, pvcVol := range pvcVols {
427+
if volInfo.VolumeName == pvcVol.Name && pvcVol.PersistentVolumeClaim != nil && volInfo.PVCName == pvcVol.PersistentVolumeClaim.ClaimName {
428+
matched = true
429+
}
430+
}
431+
432+
if !matched {
433+
t.Errorf("TestGetVolumesAndVolumeMounts error - could not find volume details %s in the actual result", volInfo.VolumeName)
434+
}
435+
}
436+
437+
// check the volume mounts of the containers
438+
for _, container := range containers {
439+
if volMounts, ok := tt.wantContainerToVol[container.Name]; !ok {
440+
t.Errorf("TestGetVolumesAndVolumeMounts error - did not find the expected container %s", container.Name)
441+
return
442+
} else {
443+
for _, expectedVolMount := range volMounts {
444+
matched := false
445+
for _, actualVolMount := range container.VolumeMounts {
446+
if expectedVolMount.volumeName == actualVolMount.Name && expectedVolMount.mountPath == actualVolMount.MountPath {
447+
matched = true
448+
}
449+
}
450+
451+
if !matched {
452+
t.Errorf("TestGetVolumesAndVolumeMounts error - could not find volume mount details for path %s in the actual result for container %s", expectedVolMount.mountPath, container.Name)
453+
}
454+
}
455+
}
456+
}
457+
}
458+
})
459+
}
460+
}
461+
462+
func TestGetVolumeMountPath(t *testing.T) {
463+
464+
tests := []struct {
465+
name string
466+
volumeMount v1.VolumeMount
467+
wantPath string
468+
}{
469+
{
470+
name: "Mount Path is present",
471+
volumeMount: v1.VolumeMount{
472+
Name: "name1",
473+
Path: "/path1",
474+
},
475+
wantPath: "/path1",
476+
},
477+
{
478+
name: "Mount Path is absent",
479+
volumeMount: v1.VolumeMount{
480+
Name: "name1",
481+
},
482+
wantPath: "/name1",
483+
},
484+
}
485+
for _, tt := range tests {
486+
t.Run(tt.name, func(t *testing.T) {
487+
path := GetVolumeMountPath(tt.volumeMount)
488+
489+
if path != tt.wantPath {
490+
t.Errorf("TestGetVolumeMountPath error: mount path mismatch, expected: %v got: %v", tt.wantPath, path)
491+
}
492+
})
493+
}
494+
495+
}

pkg/devfile/generator/utils.go

+32
Original file line numberDiff line numberDiff line change
@@ -419,3 +419,35 @@ func getBuildConfigSpec(buildConfigSpecParams BuildConfigSpecParams) *buildv1.Bu
419419
},
420420
}
421421
}
422+
423+
// getPVC gets a pvc type volume with the given volume name and pvc name.
424+
func getPVC(volumeName, pvcName string) corev1.Volume {
425+
426+
return corev1.Volume{
427+
Name: volumeName,
428+
VolumeSource: corev1.VolumeSource{
429+
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
430+
ClaimName: pvcName,
431+
},
432+
},
433+
}
434+
}
435+
436+
// addVolumeMountToContainers adds the Volume Mounts in containerNameToMountPaths to the containers for a given volumeName.
437+
// containerNameToMountPaths is a map of a container name to an array of its Mount Paths.
438+
func addVolumeMountToContainers(containers []corev1.Container, volumeName string, containerNameToMountPaths map[string][]string) {
439+
440+
for containerName, mountPaths := range containerNameToMountPaths {
441+
for i := range containers {
442+
if containers[i].Name == containerName {
443+
for _, mountPath := range mountPaths {
444+
containers[i].VolumeMounts = append(containers[i].VolumeMounts, corev1.VolumeMount{
445+
Name: volumeName,
446+
MountPath: mountPath,
447+
},
448+
)
449+
}
450+
}
451+
}
452+
}
453+
}

0 commit comments

Comments
 (0)