diff --git a/go.mod b/go.mod index 367eb9fb..a02ef022 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/devfile/library go 1.15 require ( - github.com/devfile/api/v2 v2.0.0-20210917193329-089a48011460 + github.com/devfile/api/v2 v2.0.0-20211012194348-adf74d82f446 github.com/fatih/color v1.7.0 github.com/gobwas/glob v0.2.3 github.com/golang/mock v1.5.0 diff --git a/go.sum b/go.sum index 11acfdcb..faa61676 100644 --- a/go.sum +++ b/go.sum @@ -83,8 +83,8 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devfile/api/v2 v2.0.0-20210917193329-089a48011460 h1:cmd+3poyUwevcWchYdvE02YT1nQU4SJpA5/wrdLrpWE= -github.com/devfile/api/v2 v2.0.0-20210917193329-089a48011460/go.mod h1:kLX/nW93gigOHXK3NLeJL2fSS/sgEe+OHu8bo3aoOi4= +github.com/devfile/api/v2 v2.0.0-20211012194348-adf74d82f446 h1:4/lT2y37QMUQpqF0JnYPy2JkJ58X/dHEwr3goiWbzD4= +github.com/devfile/api/v2 v2.0.0-20211012194348-adf74d82f446/go.mod h1:d99eTN6QxgzihOOFyOZA+VpUyD4Q1pYRYHZ/ci9J96Q= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= diff --git a/pkg/devfile/generator/utils.go b/pkg/devfile/generator/utils.go index f6583bb5..8487a2cf 100644 --- a/pkg/devfile/generator/utils.go +++ b/pkg/devfile/generator/utils.go @@ -2,7 +2,9 @@ package generator import ( "fmt" + "github.com/hashicorp/go-multierror" "path/filepath" + "reflect" "strings" v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2" @@ -58,17 +60,56 @@ func convertPorts(endpoints []v1.Endpoint) []corev1.ContainerPort { } // getResourceReqs creates a kubernetes ResourceRequirements object based on resource requirements set in the devfile -func getResourceReqs(comp v1.Component) corev1.ResourceRequirements { +func getResourceReqs(comp v1.Component) (corev1.ResourceRequirements, error) { reqs := corev1.ResourceRequirements{} limits := make(corev1.ResourceList) - if comp.Container != nil && comp.Container.MemoryLimit != "" { - memoryLimit, err := resource.ParseQuantity(comp.Container.MemoryLimit) - if err == nil { - limits[corev1.ResourceMemory] = memoryLimit + requests := make(corev1.ResourceList) + var returnedErr error + if comp.Container != nil { + if comp.Container.MemoryLimit != "" { + memoryLimit, err := resource.ParseQuantity(comp.Container.MemoryLimit) + if err != nil { + errMsg := fmt.Errorf("error parsing memoryLimit requirement for component %s: %v", comp.Name, err.Error()) + returnedErr = multierror.Append(returnedErr, errMsg) + } else { + limits[corev1.ResourceMemory] = memoryLimit + } + } + if comp.Container.CpuLimit != "" { + cpuLimit, err := resource.ParseQuantity(comp.Container.CpuLimit) + if err != nil { + errMsg := fmt.Errorf("error parsing cpuLimit requirement for component %s: %v", comp.Name, err.Error()) + returnedErr = multierror.Append(returnedErr, errMsg) + } else { + limits[corev1.ResourceCPU] = cpuLimit + } + } + if comp.Container.MemoryRequest != "" { + memoryRequest, err := resource.ParseQuantity(comp.Container.MemoryRequest) + if err != nil { + errMsg := fmt.Errorf("error parsing memoryRequest requirement for component %s: %v", comp.Name, err.Error()) + returnedErr = multierror.Append(returnedErr, errMsg) + } else { + requests[corev1.ResourceMemory] = memoryRequest + } + } + if comp.Container.CpuRequest != "" { + cpuRequest, err := resource.ParseQuantity(comp.Container.CpuRequest) + if err != nil { + errMsg := fmt.Errorf("error parsing cpuRequest requirement for component %s: %v", comp.Name, err.Error()) + returnedErr = multierror.Append(returnedErr, errMsg) + } else { + requests[corev1.ResourceCPU] = cpuRequest + } + } + if !reflect.DeepEqual(limits, corev1.ResourceList{}) { + reqs.Limits = limits + } + if !reflect.DeepEqual(requests, corev1.ResourceList{}) { + reqs.Requests = requests } - reqs.Limits = limits } - return reqs + return reqs, returnedErr } // addSyncRootFolder adds the sync root folder to the container env @@ -517,7 +558,10 @@ func getAllContainers(devfileObj parser.DevfileObj, options common.DevfileOption } for _, comp := range containerComponents { envVars := convertEnvs(comp.Container.Env) - resourceReqs := getResourceReqs(comp) + resourceReqs, err := getResourceReqs(comp) + if err != nil { + return containers, err + } ports := convertPorts(comp.Container.Endpoints) containerParams := containerParams{ Name: comp.Name, diff --git a/pkg/devfile/generator/utils_test.go b/pkg/devfile/generator/utils_test.go index 15f0353b..b547c5f5 100644 --- a/pkg/devfile/generator/utils_test.go +++ b/pkg/devfile/generator/utils_test.go @@ -1,6 +1,7 @@ package generator import ( + "github.com/hashicorp/go-multierror" "github.com/stretchr/testify/assert" "path/filepath" "reflect" @@ -162,8 +163,15 @@ func TestConvertPorts(t *testing.T) { } func TestGetResourceReqs(t *testing.T) { - limit := "1024Mi" - quantity, err := resource.ParseQuantity(limit) + memoryLimit := "1024Mi" + memoryRequest := "1Gi" + cpuRequest := "1m" + cpuLimit := "1m" + + memoryLimitQuantity, err := resource.ParseQuantity(memoryLimit) + memoryRequestQuantity, err := resource.ParseQuantity(memoryRequest) + cpuRequestQuantity, err := resource.ParseQuantity(cpuRequest) + cpuLimitQuantity, err := resource.ParseQuantity(cpuLimit) if err != nil { t.Errorf("TestGetResourceReqs() unexpected error: %v", err) } @@ -171,22 +179,31 @@ func TestGetResourceReqs(t *testing.T) { name string component v1.Component want corev1.ResourceRequirements + wantErr []string }{ { - name: "One Endpoint", + name: "generate resource limit", component: v1.Component{ Name: "testcomponent", ComponentUnion: v1.ComponentUnion{ Container: &v1.ContainerComponent{ Container: v1.Container{ - MemoryLimit: "1024Mi", + MemoryLimit: memoryLimit, + MemoryRequest: memoryRequest, + CpuRequest: cpuRequest, + CpuLimit: cpuLimit, }, }, }, }, want: corev1.ResourceRequirements{ Limits: corev1.ResourceList{ - corev1.ResourceMemory: quantity, + corev1.ResourceMemory: memoryLimitQuantity, + corev1.ResourceCPU: cpuLimitQuantity, + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: memoryRequestQuantity, + corev1.ResourceCPU: cpuRequestQuantity, }, }, }, @@ -209,13 +226,40 @@ func TestGetResourceReqs(t *testing.T) { }, want: corev1.ResourceRequirements{}, }, + { + name: "test error case", + component: v1.Component{ + Name: "testcomponent", + ComponentUnion: v1.ComponentUnion{ + Container: &v1.ContainerComponent{ + Container: v1.Container{ + MemoryLimit: "invalid", + MemoryRequest: "invalid", + CpuRequest: "invalid", + CpuLimit: "invalid", + }, + }, + }, + }, + wantErr: []string{ + "error parsing memoryLimit requirement.*", + "error parsing cpuLimit requirement.*", + "error parsing memoryRequest requirement.*", + "error parsing cpuRequest requirement.*", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - req := getResourceReqs(tt.component) - if !reflect.DeepEqual(tt.want, req) { - t.Errorf("TestGetResourceReqs() error: expected %v, wanted %v", req, tt.want) + req, err := getResourceReqs(tt.component) + if merr, ok := err.(*multierror.Error); ok && tt.wantErr != nil { + assert.Equal(t, len(tt.wantErr), len(merr.Errors), "Error list length should match") + for i := 0; i < len(merr.Errors); i++ { + assert.Regexp(t, tt.wantErr[i], merr.Errors[i].Error(), "Error message should match") + } + } else if !reflect.DeepEqual(tt.want, req) { + assert.Equal(t, tt.want, req, "TestGetResourceReqs(): The two values should be the same.") } }) }