From 95612a422c2a659946fd6081a6cf1ccb3ebe0146 Mon Sep 17 00:00:00 2001 From: kaidaguerre Date: Wed, 25 Jan 2023 13:48:26 +0000 Subject: [PATCH] Remove need for connection config schema - support hcl tags on connection config struct. Closes #482 --- go.mod | 1 + go.sum | 2 + ..._config.go => connection_config_schema.go} | 55 +- ...st.go => connection_config_schema_test.go} | 473 ++++++++++++++++-- ...o => connection_config_schema_validate.go} | 15 +- ...connection_config_schema_validate_test.go} | 0 6 files changed, 473 insertions(+), 73 deletions(-) rename plugin/{connection_config.go => connection_config_schema.go} (67%) rename plugin/{connection_config_test.go => connection_config_schema_test.go} (53%) rename plugin/{connection_config_validate.go => connection_config_schema_validate.go} (85%) rename plugin/{connection_config_validate_test.go => connection_config_schema_validate_test.go} (100%) diff --git a/go.mod b/go.mod index b67fa17a..e2dd0b88 100644 --- a/go.mod +++ b/go.mod @@ -61,6 +61,7 @@ require ( github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/google/go-cmp v0.5.8 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect diff --git a/go.sum b/go.sum index 69302858..df38afc4 100644 --- a/go.sum +++ b/go.sum @@ -210,6 +210,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= @@ -374,6 +375,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= diff --git a/plugin/connection_config.go b/plugin/connection_config_schema.go similarity index 67% rename from plugin/connection_config.go rename to plugin/connection_config_schema.go index 1e0cdade..6a6897c8 100644 --- a/plugin/connection_config.go +++ b/plugin/connection_config_schema.go @@ -2,15 +2,18 @@ package plugin import ( "fmt" - "log" - + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/gohcl" "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/hcl/v2/hclparse" "github.com/turbot/go-kit/helpers" "github.com/turbot/steampipe-plugin-sdk/v5/plugin/schema" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" "github.com/zclconf/go-cty/cty/gocty" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "log" ) /* @@ -36,7 +39,7 @@ Usage: }, } - func ConfigInstance() interface{} { + func ConfigInstance() any { return &hackernewsConfig{} } @@ -56,7 +59,7 @@ ConnectionConfigInstanceFunc is a function type which returns 'any'. It is used to implement [plugin.ConnectionConfigSchema.NewInstance]. */ -type ConnectionConfigInstanceFunc func() interface{} +type ConnectionConfigInstanceFunc func() any /* Connection is a struct which is used to store connection config. @@ -74,13 +77,13 @@ type Connection struct { Name string // the connection config // NOTE: we always pass and store connection config BY VALUE - Config interface{} + Config any } // parse function parses the hcl config string into a connection config struct. // // The schema and the struct to parse into are provided by the plugin -func (c *ConnectionConfigSchema) parse(configString string) (config interface{}, err error) { +func (c *ConnectionConfigSchema) parse(configString string) (config any, err error) { defer func() { if r := recover(); r != nil { log.Printf("[WARN] ConnectionConfigSchema parse caught a panic: %v\n", r) @@ -88,13 +91,14 @@ func (c *ConnectionConfigSchema) parse(configString string) (config interface{}, } }() - // ensure a schema is set - if len(c.Schema) == 0 { - return nil, fmt.Errorf("cannot parse connection config as no config schema is set in the connection config") - } - if c.NewInstance == nil { - return nil, fmt.Errorf("cannot parse connection config as no NewInstance function is specified in the connection config") + if c.Schema == nil { + return c.parseConfigWithHclTags(configString) } + return c.parseConfigWithCtyTags(configString) +} + +// parse for legacy format config struct using cty tags +func (c *ConnectionConfigSchema) parseConfigWithCtyTags(configString string) (any, error) { configStruct := c.NewInstance() spec := schema.SchemaToObjectSpec(c.Schema) parser := hclparse.NewParser() @@ -110,9 +114,34 @@ func (c *ConnectionConfigSchema) parse(configString string) (config interface{}, // decode into the provided struct if err := gocty.FromCtyValue(value, configStruct); err != nil { - return nil, fmt.Errorf("Failed to marshal parsed config into config struct: %v", err) + return nil, fmt.Errorf("failed to marshal parsed config into config struct: %v", err) + } + // return the struct by value + return helpers.DereferencePointer(configStruct), nil +} + +func (c *ConnectionConfigSchema) parseConfigWithHclTags(configString string) (_ any, err error) { + configStruct := c.NewInstance() + parser := hclparse.NewParser() + file, diags := parser.ParseHCL([]byte(configString), "") + if diags.HasErrors() { + return nil, DiagsToError("failed to parse connection config", diags) + } + _, body, diags := file.Body.PartialContent(&hcl.BodySchema{}) + if diags.HasErrors() { + return nil, DiagsToError("failed to parse connection config", diags) + } + + evalCtx := &hcl.EvalContext{ + Variables: make(map[string]cty.Value), + Functions: make(map[string]function.Function), } + moreDiags := gohcl.DecodeBody(body, evalCtx, configStruct) + diags = append(diags, moreDiags...) + if diags.HasErrors() { + return nil, DiagsToError("failed to parse connection config", diags) + } // return the struct by value return helpers.DereferencePointer(configStruct), nil } diff --git a/plugin/connection_config_test.go b/plugin/connection_config_schema_test.go similarity index 53% rename from plugin/connection_config_test.go rename to plugin/connection_config_schema_test.go index aa108999..d12e53ea 100644 --- a/plugin/connection_config_test.go +++ b/plugin/connection_config_schema_test.go @@ -2,9 +2,7 @@ package plugin import ( "fmt" - "github.com/turbot/steampipe-plugin-sdk/v5/plugin/schema" - "reflect" "testing" ) @@ -16,41 +14,59 @@ type parseConfigTest struct { expectedFunc func(interface{}) bool } -type arrayProperty struct { +// legacy struct versions using cty tags +type childStructCty struct { + Name string `cty:"name" cty:"name"` +} +type structPropertyCty struct { + Name childStructCty `cty:"name"` +} +type structSlicePropertyCty struct { + Tables []configTableCty `cty:"tables"` +} +type configColumnCty struct { + Name string `cty:"name"` + Type string `cty:"type"` +} +type configTableCty struct { + Name string `cty:"name"` + Columns []configColumnCty `cty:"columns"` +} +type arrayPropertyCty struct { Regions []string `cty:"regions"` } -type stringProperty struct { +type stringPropertyCty struct { Region string `cty:"region"` } -type stringPtrProperty struct { +type stringPtrPropertyCty struct { Region *string `cty:"region"` Count int `cty:"count"` } -type intProperty struct { +type intPropertyCty struct { Count int `cty:"count"` } -type floatProperty struct { +type floatPropertyCty struct { Pi float64 `cty:"pi"` } -type allTypes struct { +type allTypesCty struct { Regions []string `cty:"regions"` Region string `cty:"region"` Count int `cty:"count"` Pi float64 `cty:"pi"` } -type allTypesMissingProperty struct { +type allTypesMissingPropertyCty struct { Region string `cty:"region"` Count int `cty:"count"` Pi float64 `cty:"pi"` } -type extraPropertyNoAnnotation struct { +type extraPropertyNoAnnotationCty struct { Foo int Regions []string `cty:"regions"` Region string `cty:"region"` Count int `cty:"count"` Pi float64 `cty:"pi"` } -type extraPropertyWithAnnotation struct { +type extraPropertyWithAnnotationCty struct { Foo int `cty:"foo"` Regions []string `cty:"regions"` Region string `cty:"region"` @@ -58,13 +74,150 @@ type extraPropertyWithAnnotation struct { Pi float64 `cty:"pi"` } +// hcl struct versions + +type childStruct struct { + Name string `hcl:"name" cty:"name"` +} +type structProperty struct { + Name childStruct `hcl:"name"` +} +type structSliceProperty struct { + Tables []configTable `hcl:"tables"` +} +type configColumn struct { + Name string `hcl:"name" cty:"name"` + Type string `hcl:"type" cty:"type"` +} +type configTable struct { + Name string `hcl:"name" cty:"name"` + Columns []configColumn `hcl:"columns" cty:"columns"` +} +type arrayProperty struct { + Regions []string `hcl:"regions"` +} +type stringProperty struct { + Region string `hcl:"region"` +} +type stringPtrProperty struct { + Region *string `hcl:"region"` + Count int `hcl:"count"` +} +type intProperty struct { + Count int `hcl:"count"` +} +type floatProperty struct { + Pi float64 `hcl:"pi"` +} +type allTypes struct { + Regions []string `hcl:"regions,optional"` + Region string `hcl:"region"` + Count int `hcl:"count"` + Pi float64 `hcl:"pi"` +} +type allTypesMissingProperty struct { + Region string `hcl:"region"` + Count int `hcl:"count"` + Pi float64 `hcl:"pi"` +} +type extraPropertyNoAnnotation struct { + Foo int + Regions []string `hcl:"regions"` + Region string `hcl:"region"` + Count int `hcl:"count"` + Pi float64 `hcl:"pi"` +} +type extraPropertyWithAnnotation struct { + Foo int `hcl:"foo"` + Regions []string `hcl:"regions"` + Region string `hcl:"region"` + Count int `hcl:"count"` + Pi float64 `hcl:"pi"` +} + var testCasesParseConfig = map[string]parseConfigTest{ - "array property": { + // cty tag implementation + "struct property cty": { + source: ` + name = { name = "foo"} + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &structPropertyCty{} }, + Schema: map[string]*schema.Attribute{ + "name": { + Type: schema.TypeMap, + AttrTypes: map[string]*schema.Attribute{ + "name": { + Type: schema.TypeString, + }, + }, + }, + }, + }, + expected: structPropertyCty{ + childStructCty{"foo"}}, + }, + "struct slice property cty": { + source: ` + tables = [ + { + name = "test1" + columns = [ + { + name = "c1" + type = "string" + }, + { + name = "c2" + type = "string" + }, + ] + } + ] + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &structSlicePropertyCty{} }, + Schema: map[string]*schema.Attribute{ + "tables": { + Type: schema.TypeList, + Elem: &schema.Attribute{ + Type: schema.TypeMap, + AttrTypes: map[string]*schema.Attribute{ + "name": { + Type: schema.TypeString, + }, + "columns": { + Type: schema.TypeList, + Elem: &schema.Attribute{ + + Type: schema.TypeMap, + AttrTypes: map[string]*schema.Attribute{ + "name": { + Type: schema.TypeString, + }, + "type": { + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: structSlicePropertyCty{ + Tables: []configTableCty{{ + Name: "test1", + Columns: []configColumnCty{{"c1", "string"}, {"c2", "string"}}, + }}}, + }, + "array property cty": { source: ` regions = ["us-east-1","us-west-2"] `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &arrayProperty{} }, + NewInstance: func() interface{} { return &arrayPropertyCty{} }, Schema: map[string]*schema.Attribute{ "regions": { Type: schema.TypeList, @@ -73,16 +226,16 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, }, }, - expected: arrayProperty{ + expected: arrayPropertyCty{ Regions: []string{"us-east-1", "us-west-2"}, }, }, - "string property": { + "string property cty": { source: ` region = "us-east-1" `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &stringProperty{} }, + NewInstance: func() interface{} { return &stringPropertyCty{} }, Schema: map[string]*schema.Attribute{ "region": { Type: schema.TypeString, @@ -90,17 +243,17 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, }, }, - expected: stringProperty{ + expected: stringPropertyCty{ Region: "us-east-1", }, }, - "string pointer property": { + "string pointer property cty": { source: ` region = "us-east-1" count = 100 `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &stringPtrProperty{} }, + NewInstance: func() interface{} { return &stringPtrPropertyCty{} }, Schema: map[string]*schema.Attribute{ "region": { Type: schema.TypeString, @@ -112,16 +265,15 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, }, expectedFunc: func(res interface{}) bool { - return *(res.(stringPtrProperty).Region) == "us-east-1" + return *(res.(stringPtrPropertyCty).Region) == "us-east-1" }, }, - - "count property": { + "count property cty": { source: ` count = 100 `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &intProperty{} }, + NewInstance: func() interface{} { return &intPropertyCty{} }, Schema: map[string]*schema.Attribute{ "count": { Type: schema.TypeInt, @@ -129,16 +281,16 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, }, }, - expected: intProperty{ + expected: intPropertyCty{ Count: 100, }, }, - "float property": { + "float property cty": { source: ` pi = 3.14 `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &floatProperty{} }, + NewInstance: func() interface{} { return &floatPropertyCty{} }, Schema: map[string]*schema.Attribute{ "pi": { Type: schema.TypeFloat, @@ -146,11 +298,11 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, }, }, - expected: floatProperty{ + expected: floatPropertyCty{ Pi: 3.14, }, }, - "all types": { + "all types cty": { source: ` regions = ["us-east-1","us-west-2"] region = "us-east-1" @@ -158,7 +310,7 @@ var testCasesParseConfig = map[string]parseConfigTest{ pi = 3.14 `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &allTypes{} }, + NewInstance: func() interface{} { return &allTypesCty{} }, Schema: map[string]*schema.Attribute{ "regions": { Type: schema.TypeList, @@ -179,14 +331,14 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, }, }, - expected: allTypes{ + expected: allTypesCty{ Regions: []string{"us-east-1", "us-west-2"}, Region: "us-east-1", Count: 100, Pi: 3.14, }, }, - "all types - extra hcl property: EXPECTED ERROR": { + "all types - extra hcl property: EXPECTED ERROR cty": { source: ` regions = ["us-east-1","us-west-2"] region = "us-east-1" @@ -195,7 +347,7 @@ var testCasesParseConfig = map[string]parseConfigTest{ foo = "bar" `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &allTypes{} }, + NewInstance: func() interface{} { return &allTypesCty{} }, Schema: map[string]*schema.Attribute{ "regions": { Type: schema.TypeList, @@ -218,7 +370,7 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, expected: "ERROR", }, - "all types - struct missing property: EXPECTED ERROR": { + "all types - struct missing property: EXPECTED ERROR cty": { source: ` regions = ["us-east-1","us-west-2"] region = "us-east-1" @@ -249,7 +401,7 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, expected: "ERROR", }, - "all types - struct has extra property no annotation": { + "all types - struct has extra property no annotation cty": { source: ` regions = ["us-east-1","us-west-2"] region = "us-east-1" @@ -257,7 +409,7 @@ var testCasesParseConfig = map[string]parseConfigTest{ pi = 3.14 `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &extraPropertyNoAnnotation{} }, + NewInstance: func() interface{} { return &extraPropertyNoAnnotationCty{} }, Schema: map[string]*schema.Attribute{ "regions": { Type: schema.TypeList, @@ -278,7 +430,7 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, }, }, - expected: extraPropertyNoAnnotation{ + expected: extraPropertyNoAnnotationCty{ Foo: 0, Regions: []string{"us-east-1", "us-west-2"}, Region: "us-east-1", @@ -286,7 +438,7 @@ var testCasesParseConfig = map[string]parseConfigTest{ Pi: 3.14, }, }, - "all types - struct has extra property with annotation: EXPECTED ERROR": { + "all types - struct has extra property with annotation: EXPECTED ERROR cty": { source: ` regions = ["us-east-1","us-west-2"] region = "us-east-1" @@ -294,7 +446,7 @@ var testCasesParseConfig = map[string]parseConfigTest{ pi = 3.14 `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &extraPropertyWithAnnotation{} }, + NewInstance: func() interface{} { return &extraPropertyWithAnnotationCty{} }, Schema: map[string]*schema.Attribute{ "regions": { Type: schema.TypeList, @@ -317,14 +469,14 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, expected: "ERROR", }, - "all types - missing optional array": { + "all types - missing optional array cty": { source: ` region = "us-east-1" count = 100 pi = 3.14 `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &allTypes{} }, + NewInstance: func() interface{} { return &allTypesCty{} }, Schema: map[string]*schema.Attribute{ "regions": { Type: schema.TypeList, @@ -344,21 +496,21 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, }, }, - expected: allTypes{ + expected: allTypesCty{ Regions: nil, Region: "us-east-1", Count: 100, Pi: 3.14, }, }, - "all types - missing optional string": { + "all types - missing optional string cty": { source: ` regions = ["us-east-1"] count = 100 pi = 3.14 `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &allTypes{} }, + NewInstance: func() interface{} { return &allTypesCty{} }, Schema: map[string]*schema.Attribute{ "regions": { Type: schema.TypeList, @@ -379,12 +531,12 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, expected: "ERROR", }, - "all types - missing optional string pointer": { + "all types - missing optional string pointer cty": { source: ` count = 100 `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &stringPtrProperty{} }, + NewInstance: func() interface{} { return &stringPtrPropertyCty{} }, Schema: map[string]*schema.Attribute{ "region": { Type: schema.TypeString, @@ -394,23 +546,22 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, }, }, - expected: stringPtrProperty{ + expected: stringPtrPropertyCty{ Count: 100, }, }, - "all types - missing required: EXPECTED ERROR": { + "all types - missing required: EXPECTED ERROR cty": { source: ` - region = "us-east-1" + count = 100 pi = 3.14 `, connectionConfigSchema: &ConnectionConfigSchema{ - NewInstance: func() interface{} { return &allTypes{} }, + NewInstance: func() interface{} { return &allTypesCty{} }, Schema: map[string]*schema.Attribute{ "regions": { - Type: schema.TypeList, - Elem: &schema.Attribute{Type: schema.TypeString}, - Required: true, + Type: schema.TypeList, + Elem: &schema.Attribute{Type: schema.TypeString}, }, "region": { Type: schema.TypeString, @@ -428,13 +579,227 @@ var testCasesParseConfig = map[string]parseConfigTest{ }, expected: "ERROR", }, + + // hcl tag implementation + "struct property": { + source: ` + name = { name = "foo"} + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &structProperty{} }, + }, + expected: structProperty{childStruct{"foo"}}, + }, + "struct slice property": { + source: ` + tables = [ + { + name = "test1" + columns = [ + { + name = "c1" + type = "string" + }, + { + name = "c2" + type = "string" + }, + ] + } + ] + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &structSliceProperty{} }, + }, + expected: structSliceProperty{ + Tables: []configTable{{ + Name: "test1", + Columns: []configColumn{{"c1", "string"}, {"c2", "string"}}, + }}}, + }, + "array property": { + source: ` + regions = ["us-east-1","us-west-2"] + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &arrayProperty{} }, + }, + expected: arrayProperty{ + Regions: []string{"us-east-1", "us-west-2"}, + }, + }, + "string property": { + source: ` + region = "us-east-1" + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &stringProperty{} }, + }, + expected: stringProperty{ + Region: "us-east-1", + }, + }, + "string pointer property": { + source: ` + region = "us-east-1" + count = 100 + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &stringPtrProperty{} }, + }, + expectedFunc: func(res interface{}) bool { + return *(res.(stringPtrProperty).Region) == "us-east-1" + }, + }, + "count property": { + source: ` + count = 100 + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &intProperty{} }, + }, + expected: intProperty{ + Count: 100, + }, + }, + "float property": { + source: ` + pi = 3.14 + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &floatProperty{} }, + }, + expected: floatProperty{ + Pi: 3.14, + }, + }, + "all types": { + source: ` + regions = ["us-east-1","us-west-2"] + region = "us-east-1" + count = 100 + pi = 3.14 + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &allTypes{} }, + }, + expected: allTypes{ + Regions: []string{"us-east-1", "us-west-2"}, + Region: "us-east-1", + Count: 100, + Pi: 3.14, + }, + }, + "all types - extra hcl property: EXPECTED ERROR": { + source: ` + regions = ["us-east-1","us-west-2"] + region = "us-east-1" + count = 100 + pi = 3.14 + foo = "bar" + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &allTypes{} }, + }, + expected: "ERROR", + }, + "all types - struct missing property: EXPECTED ERROR": { + source: ` + regions = ["us-east-1","us-west-2"] + region = "us-east-1" + count = 100 + pi = 3.14 + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &allTypesMissingProperty{} }, + }, + expected: "ERROR", + }, + "all types - struct has extra property no annotation": { + source: ` + regions = ["us-east-1","us-west-2"] + region = "us-east-1" + count = 100 + pi = 3.14 + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &extraPropertyNoAnnotation{} }, + }, + expected: extraPropertyNoAnnotation{ + Foo: 0, + Regions: []string{"us-east-1", "us-west-2"}, + Region: "us-east-1", + Count: 100, + Pi: 3.14, + }, + }, + "all types - struct has extra property with annotation: EXPECTED ERROR": { + source: ` + regions = ["us-east-1","us-west-2"] + region = "us-east-1" + count = 100 + pi = 3.14 + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &extraPropertyWithAnnotation{} }, + }, + expected: "ERROR", + }, + "all types - missing optional array": { + source: ` + region = "us-east-1" + count = 100 + pi = 3.14 + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &allTypes{} }, + }, + expected: allTypes{ + Regions: nil, + Region: "us-east-1", + Count: 100, + Pi: 3.14, + }, + }, + "all types - missing optional string": { + source: ` + regions = ["us-east-1"] + count = 100 + pi = 3.14 + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &allTypes{} }, + }, + expected: "ERROR", + }, + "all types - missing optional string pointer": { + source: ` + count = 100 + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &stringPtrProperty{} }, + }, + expected: stringPtrProperty{ + Count: 100, + }, + }, + "all types - missing required: EXPECTED ERROR": { + source: ` + + count = 100 + pi = 3.14 + `, + connectionConfigSchema: &ConnectionConfigSchema{ + NewInstance: func() interface{} { return &allTypes{} }, + }, + expected: "ERROR", + }, } func TestParseConnectionConfig(t *testing.T) { - for name, test := range testCasesParseConfig { + for name, test := range testCasesParseConfig { config, err := test.connectionConfigSchema.parse(test.source) - if err != nil { if test.expected != "ERROR" { t.Errorf("test %s failed with unexpected error: %v", name, err) diff --git a/plugin/connection_config_validate.go b/plugin/connection_config_schema_validate.go similarity index 85% rename from plugin/connection_config_validate.go rename to plugin/connection_config_schema_validate.go index 7a00ee3d..cae53047 100644 --- a/plugin/connection_config_validate.go +++ b/plugin/connection_config_schema_validate.go @@ -23,14 +23,17 @@ func (c *ConnectionConfigSchema) Validate() []string { validationErrors = append(validationErrors, fmt.Sprintf("NewInstance function must return a pointer to a struct instance, got %v", kind)) } - for name, attr := range c.Schema { - if attr.Type != schema.TypeList && attr.Elem != nil { - validationErrors = append(validationErrors, fmt.Sprintf("attribute %s has 'Elem' set but is Type is not TypeList", name)) - } + if c.Schema != nil { + for name, attr := range c.Schema { + if attr.Type != schema.TypeList && attr.Elem != nil { + validationErrors = append(validationErrors, fmt.Sprintf("attribute %s has 'Elem' set but is Type is not TypeList", name)) + } - // find a property in the struct which is tagged with this field - validationErrors = append(validationErrors, c.validateConfigStruct(name, attr, instance)...) + // find a property in the struct which is tagged with this field + validationErrors = append(validationErrors, c.validateConfigStruct(name, attr, instance)...) + } } + return validationErrors } diff --git a/plugin/connection_config_validate_test.go b/plugin/connection_config_schema_validate_test.go similarity index 100% rename from plugin/connection_config_validate_test.go rename to plugin/connection_config_schema_validate_test.go