Skip to content

Commit

Permalink
Get type from schema
Browse files Browse the repository at this point in the history
  • Loading branch information
feloy committed Sep 16, 2021
1 parent 5088b22 commit 818ea6b
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 45 deletions.
56 changes: 35 additions & 21 deletions pkg/odo/cli/service/operator_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,23 @@ func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) (err error) {

// CRD is valid. We can use it further to create a service from it.
b.CustomResourceDefinition = d.OriginalCRD

// Validate spec
hasCR, cr := o.KClient.CheckCustomResourceInCSV(b.CustomResource, &csv)
if !hasCR {
return fmt.Errorf("the %q resource doesn't exist in specified %q operator", b.CustomResource, b.group)
}

crd, err := o.KClient.GetCRDSpec(cr, b.group, b.CustomResource)
if err != nil {
return err
}

err = validate.AgainstSchema(crd, d.OriginalCRD["spec"], strfmt.Default)
if err != nil {
return err
}

} else if b.CustomResource != "" {
// make sure that CSV of the specified ServiceType exists
csv, err = o.KClient.GetClusterServiceVersion(o.ServiceType)
Expand All @@ -138,8 +155,18 @@ func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) (err error) {
o.ServiceName = strings.ToLower(b.CustomResource)
}

hasCR, cr := o.KClient.CheckCustomResourceInCSV(b.CustomResource, &csv)
if !hasCR {
return fmt.Errorf("the %q resource doesn't exist in specified %q operator", b.CustomResource, b.group)
}

crd, err := o.KClient.GetCRDSpec(cr, b.group, b.CustomResource)
if err != nil {
return err
}

if len(o.parameters) != 0 {
builtCRD, err := b.buildCRDfromParams(o, b.group, b.version, b.CustomResource)
builtCRD, err := svc.BuildCRDFromParams(o.ParametersMap, crd, b.group, b.version, b.CustomResource)
if err != nil {
return err
}
Expand Down Expand Up @@ -182,6 +209,13 @@ func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) (err error) {
return err
}
}

// Validate spec
err = validate.AgainstSchema(crd, d.OriginalCRD["spec"], strfmt.Default)
if err != nil {
return err
}

} else {
// This block is executed only when user has neither provided a
// file nor a valid `odo service create <operator-name>` to start
Expand All @@ -192,22 +226,6 @@ func (b *OperatorBackend) ValidateServiceCreate(o *CreateOptions) (err error) {
return fmt.Errorf("please use a valid command to start an Operator backed service; desired format: %q", "odo service create <operator-name>/<crd-name>")
}

// Validate spec
hasCR, cr := o.KClient.CheckCustomResourceInCSV(b.CustomResource, &csv)
if !hasCR {
return fmt.Errorf("the %q resource doesn't exist in specified %q operator", b.CustomResource, b.group)
}

crd, err := o.KClient.GetCRDSpec(cr, b.group, b.CustomResource)
if err != nil {
return err
}

err = validate.AgainstSchema(crd, d.OriginalCRD["spec"], strfmt.Default)
if err != nil {
return err
}

return nil
}

Expand Down Expand Up @@ -279,10 +297,6 @@ func (b *OperatorBackend) DeleteService(o *DeleteOptions, name string, applicati
return nil
}

func (b *OperatorBackend) buildCRDfromParams(o *CreateOptions, group, version, kind string) (map[string]interface{}, error) {
return svc.BuildCRDFromParams(o.ParametersMap, group, version, kind)
}

func (b *OperatorBackend) DescribeService(o *DescribeOptions, serviceName, app string) error {

clusterList, _, err := svc.ListOperatorServices(o.KClient)
Expand Down
82 changes: 61 additions & 21 deletions pkg/service/crd_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import (
"strconv"
"strings"

"github.com/go-openapi/spec"
"github.com/pkg/errors"
)

// BuildCRDFromParams iterates over the parameter maps provided by the user and builds the CRD
func BuildCRDFromParams(paramMap map[string]string, group, version, kind string) (map[string]interface{}, error) {
func BuildCRDFromParams(paramMap map[string]string, crd *spec.Schema, group, version, kind string) (map[string]interface{}, error) {
spec := map[string]interface{}{}
for k, v := range paramMap {
err := addParam(spec, k, v)
err := addParam(spec, crd, k, v)
if err != nil {
return nil, err
}
Expand All @@ -25,18 +26,24 @@ func BuildCRDFromParams(paramMap map[string]string, group, version, kind string)
return result, nil
}

func addParam(m map[string]interface{}, key string, value string) error {
func addParam(m map[string]interface{}, crd *spec.Schema, key string, value string) error {
if strings.Contains(key, ".") {
parts := strings.SplitN(key, ".", 2)
_, found := m[parts[0]]
property := parts[0]
_, found := m[property]
if !found {
m[parts[0]] = map[string]interface{}{}
m[property] = map[string]interface{}{}
}
submap, ok := m[parts[0]].(map[string]interface{})
submap, ok := m[property].(map[string]interface{})
if !ok {
return errors.New("already defined")
}
err := addParam(submap, parts[1], value)
var subCRD *spec.Schema
if crd != nil {
s := crd.Properties[property]
subCRD = &s
}
err := addParam(submap, subCRD, parts[1], value)
if err != nil {
return err
}
Expand All @@ -45,26 +52,59 @@ func addParam(m map[string]interface{}, key string, value string) error {
return errors.New("already defined")
}
// TODO(feloy) convert based on declared type in schema
m[key] = convertType(value)
var subCRD *spec.Schema
if crd != nil {
s := crd.Properties[key]
subCRD = &s
}
m[key] = convertType(subCRD, value)
}
return nil
}

func convertType(value string) interface{} {
intv, err := strconv.ParseInt(value, 10, 64)
if err == nil {
return int64(intv)
}
floatv, err := strconv.ParseFloat(value, 64)
func convertType(crd *spec.Schema, value string) interface{} {
if crd != nil {
// do not use 'else' as the Schema can accept several types
// the first matching type will be used
if crd.Type.Contains("string") {
return value
}
if crd.Type.Contains("integer") {
intv, err := strconv.ParseInt(value, 10, 64)
if err == nil {
return int64(intv)
}
}
if crd.Type.Contains("number") {
floatv, err := strconv.ParseFloat(value, 64)
if err == nil {
return floatv
}
}
if crd.Type.Contains("boolean") {
boolv, err := strconv.ParseBool(value)
if err == nil {
return boolv
}
}
} else {
// no crd information available, guess the type depending on the value
intv, err := strconv.ParseInt(value, 10, 64)
if err == nil {
return int64(intv)
}

if err == nil {
return floatv
}
floatv, err := strconv.ParseFloat(value, 64)
if err == nil {
return floatv
}

boolv, err := strconv.ParseBool(value)
if err == nil {
return boolv
boolv, err := strconv.ParseBool(value)
if err == nil {
return boolv
}
}
// if there were errors for everything else we return the string value

// as a last resort return the string value
return value
}
100 changes: 97 additions & 3 deletions pkg/service/crd_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import (
"encoding/json"
"reflect"
"testing"

"github.com/go-openapi/spec"
)

func TestBuildCRDFromParams(t *testing.T) {
tests := []struct {
name string
crd *spec.Schema
params map[string]string
want map[string]interface{}
wantErr bool
}{
{
name: "params ok",
name: "params ok without crd",
params: map[string]string{
"u": "1",
"a.b.c": "2",
Expand All @@ -34,7 +37,7 @@ func TestBuildCRDFromParams(t *testing.T) {
wantErr: false,
},
{
name: "typed params",
name: "typed params without crd",
params: map[string]string{
"a.bool": "true",
"a.string": "foobar",
Expand All @@ -49,6 +52,97 @@ func TestBuildCRDFromParams(t *testing.T) {
},
wantErr: false,
},
{
name: "typed params with crd",
params: map[string]string{
"a.bool": "true",
"a.string1": "foobar",
"a.string2": "true",
"a.string3": "1.234",
"a.string4": "11",
"a.float": "1.234",
"a.int": "11",
},
crd: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{
"object",
},
Properties: map[string]spec.Schema{
"a": {
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{
"object",
},
Properties: map[string]spec.Schema{
"bool": {
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{
"boolean",
},
},
},
"int": {
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{
"integer",
},
},
},
"float": {
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{
"number",
},
},
},
"string1": {
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{
"string",
},
},
},
"string2": {
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{
"string",
},
},
},
"string3": {
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{
"string",
},
},
},
"string4": {
SchemaProps: spec.SchemaProps{
Type: spec.StringOrArray{
"string",
},
},
},
},
},
},
},
},
},
want: map[string]interface{}{
"a": map[string]interface{}{
"bool": true,
"string1": "foobar",
"string2": "true",
"string3": "1.234",
"string4": "11",
"float": 1.234,
"int": int64(11),
},
},
wantErr: false,
},
{
name: "params error map defined before value",
params: map[string]string{
Expand All @@ -75,7 +169,7 @@ func TestBuildCRDFromParams(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, gotErr := BuildCRDFromParams(tt.params, "a group", "a version", "a kind")
got, gotErr := BuildCRDFromParams(tt.params, tt.crd, "a group", "a version", "a kind")
if gotErr != nil != tt.wantErr {
t.Errorf("got err: %v, expected err: %v\n", gotErr != nil, tt.wantErr)
}
Expand Down

0 comments on commit 818ea6b

Please sign in to comment.