diff --git a/go.mod b/go.mod index 69dca74c..0ebb5d24 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,13 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hc-install v0.6.3 - github.com/hashicorp/hcl-lang v0.0.0-20240122101040-f43c27231c10 - github.com/hashicorp/hcl/v2 v2.19.1 + github.com/hashicorp/hcl-lang v0.0.0-20240326153306-49d737897778 + github.com/hashicorp/hcl/v2 v2.20.1 github.com/hashicorp/terraform-exec v0.20.0 github.com/hashicorp/terraform-json v0.21.0 github.com/hashicorp/terraform-registry-address v0.2.3 github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5 - github.com/zclconf/go-cty v1.14.2 + github.com/zclconf/go-cty v1.14.4 github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b ) @@ -26,9 +26,10 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/mod v0.15.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/mod v0.16.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.19.0 // indirect ) diff --git a/go.sum b/go.sum index e1353b0d..c349cdf1 100644 --- a/go.sum +++ b/go.sum @@ -41,10 +41,10 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hc-install v0.6.3 h1:yE/r1yJvWbtrJ0STwScgEnCanb0U9v7zp0Gbkmcoxqs= github.com/hashicorp/hc-install v0.6.3/go.mod h1:KamGdbodYzlufbWh4r9NRo8y6GLHWZP2GBtdnms1Ln0= -github.com/hashicorp/hcl-lang v0.0.0-20240122101040-f43c27231c10 h1:tXisNKDNqOGkcRf8NeM4b0Br4Q5SFrpbDj36qSQimjA= -github.com/hashicorp/hcl-lang v0.0.0-20240122101040-f43c27231c10/go.mod h1:HWlBK8JoI8P4yp2reyTc267YoDpypd5fK3OYAPLyUXM= -github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= -github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= +github.com/hashicorp/hcl-lang v0.0.0-20240326153306-49d737897778 h1:Yw/5Lno+YiOAEQQ6krRpiJ4lCv+x0k7Q6/SxH5AIsus= +github.com/hashicorp/hcl-lang v0.0.0-20240326153306-49d737897778/go.mod h1:2Vwxuf2nQi/095HYVqo0kWhlBLKhwEi/jTf5TxB61MI= +github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= +github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8JyYF3vpnuEo= github.com/hashicorp/terraform-exec v0.20.0/go.mod h1:ckKGkJWbsNqFKV1itgMnE0hY9IYf1HoiekpuN0eWoDw= github.com/hashicorp/terraform-json v0.21.0 h1:9NQxbLNqPbEMze+S6+YluEdXgJmhQykRyRNd+zTI05U= @@ -57,12 +57,9 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5 h1:shw+DWUaHIyW64Tv30ASCbC6QO6fLy+M5SJb5pJVEI4= github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5/go.mod h1:nHPoxaBUc5CDAMIv0MNmn5PBjWbTs9BI/eh30/n0U6g= @@ -78,25 +75,27 @@ github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6Ac github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.14.2 h1:kTG7lqmBou0Zkx35r6HJHUQTvaRPr5bIAf3AoHS0izI= -github.com/zclconf/go-cty v1.14.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= +github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= diff --git a/schema/convert_json.go b/schema/convert_json.go index a758b87c..31ca83b5 100644 --- a/schema/convert_json.go +++ b/schema/convert_json.go @@ -12,12 +12,14 @@ import ( tfjson "github.com/hashicorp/terraform-json" tfaddr "github.com/hashicorp/terraform-registry-address" "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" ) func ProviderSchemaFromJson(jsonSchema *tfjson.ProviderSchema, pAddr tfaddr.Provider) *ProviderSchema { ps := &ProviderSchema{ Resources: map[string]*schema.BodySchema{}, DataSources: map[string]*schema.BodySchema{}, + Functions: map[string]*schema.FunctionSignature{}, } if jsonSchema.ConfigSchema != nil { @@ -37,6 +39,11 @@ func ProviderSchemaFromJson(jsonSchema *tfjson.ProviderSchema, pAddr tfaddr.Prov ps.DataSources[dsName].Detail = detailForSrcAddr(pAddr, nil) } + for fnName, fnSig := range jsonSchema.Functions { + ps.Functions[fnName] = functionSignatureFromJson(fnSig) + ps.Functions[fnName].Detail = detailForSrcAddr(pAddr, nil) + } + return ps } @@ -396,3 +403,35 @@ func detailForSrcAddr(addr tfaddr.Provider, v *version.Version) string { return detail } + +func functionSignatureFromJson(fnSig *tfjson.FunctionSignature) *schema.FunctionSignature { + if fnSig == nil { + return &schema.FunctionSignature{} + } + + varParam := convertParameterFromJson(fnSig.VariadicParameter) + params := make([]function.Parameter, len(fnSig.Parameters)) + for i, param := range fnSig.Parameters { + params[i] = *convertParameterFromJson(param) + } + + return &schema.FunctionSignature{ + Description: fnSig.Description, + ReturnType: fnSig.ReturnType, + Params: params, + VarParam: varParam, + } +} + +func convertParameterFromJson(param *tfjson.FunctionParameter) *function.Parameter { + if param == nil { + return nil + } + + return &function.Parameter{ + Name: param.Name, + Type: param.Type, + Description: param.Description, + AllowNull: param.IsNullable, + } +} diff --git a/schema/convert_json_test.go b/schema/convert_json_test.go index 15253474..f356ea5e 100644 --- a/schema/convert_json_test.go +++ b/schema/convert_json_test.go @@ -5,6 +5,7 @@ package schema import ( "encoding/json" + "fmt" "testing" "github.com/google/go-cmp/cmp" @@ -13,6 +14,7 @@ import ( "github.com/hashicorp/terraform-schema/internal/addr" "github.com/zclconf/go-cty-debug/ctydebug" "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" ) func TestProviderSchemaFromJson_empty(t *testing.T) { @@ -23,6 +25,7 @@ func TestProviderSchemaFromJson_empty(t *testing.T) { expectedPs := &ProviderSchema{ Resources: map[string]*schema.BodySchema{}, DataSources: map[string]*schema.BodySchema{}, + Functions: map[string]*schema.FunctionSignature{}, } if diff := cmp.Diff(expectedPs, ps, ctydebug.CmpOptions); diff != "" { @@ -291,6 +294,7 @@ func TestProviderSchemaFromJson_basic(t *testing.T) { }, }, DataSources: map[string]*schema.BodySchema{}, + Functions: map[string]*schema.FunctionSignature{}, } if diff := cmp.Diff(expectedPs, ps, ctydebug.CmpOptions); diff != "" { @@ -498,9 +502,148 @@ func TestProviderSchemaFromJson_nested_set_list(t *testing.T) { }, }, DataSources: map[string]*schema.BodySchema{}, + Functions: map[string]*schema.FunctionSignature{}, } if diff := cmp.Diff(expectedPs, ps, ctydebug.CmpOptions); diff != "" { t.Fatalf("provider schema mismatch: %s", diff) } } + +func TestProviderSchemaFromJson_function(t *testing.T) { + testCases := []struct { + testName string + rawSchema string + expectedSchema ProviderSchema + }{ + { + "basic", + `{ + "functions": { + "example": { + "description": "Echoes given argument as result", + "summary": "Example function", + "return_type": "string", + "parameters": [ + { + "name": "input", + "description": "String to echo", + "type": "string" + } + ] + } + } + }`, + ProviderSchema{ + Resources: map[string]*schema.BodySchema{}, + DataSources: map[string]*schema.BodySchema{}, + Functions: map[string]*schema.FunctionSignature{ + "example": { + Description: "Echoes given argument as result", + Detail: "hashicorp/aws", + ReturnType: cty.String, + Params: []function.Parameter{ + { + Name: "input", + Description: "String to echo", + Type: cty.String, + }, + }, + VarParam: nil, + }, + }, + }, + }, + { + "no parameters", + `{ + "functions": { + "example": { + "description": "Returns a string", + "summary": "Example function", + "return_type": "string", + "parameters": [] + } + } + }`, + ProviderSchema{ + Resources: map[string]*schema.BodySchema{}, + DataSources: map[string]*schema.BodySchema{}, + Functions: map[string]*schema.FunctionSignature{ + "example": { + Description: "Returns a string", + Detail: "hashicorp/aws", + ReturnType: cty.String, + Params: []function.Parameter{}, + VarParam: nil, + }, + }, + }, + }, + { + "with variadic parameter", + `{ + "functions": { + "example": { + "description": "Echoes given argument as result", + "summary": "Example function", + "return_type": "string", + "parameters": [ + { + "name": "input", + "description": "String to echo", + "type": "string" + } + ], + "variadic_parameter": { + "name": "vars", + "description": "Optional additional arguments", + "type": "string" + } + } + } + }`, + ProviderSchema{ + Resources: map[string]*schema.BodySchema{}, + DataSources: map[string]*schema.BodySchema{}, + Functions: map[string]*schema.FunctionSignature{ + "example": { + Description: "Echoes given argument as result", + Detail: "hashicorp/aws", + ReturnType: cty.String, + Params: []function.Parameter{ + { + Name: "input", + Description: "String to echo", + Type: cty.String, + }, + }, + VarParam: &function.Parameter{ + Name: "vars", + Type: cty.String, + Description: "Optional additional arguments", + }, + }, + }, + }, + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%2d-%s", i, tc.testName), func(t *testing.T) { + + jsonSchema := &tfjson.ProviderSchema{} + err := json.Unmarshal([]byte(tc.rawSchema), jsonSchema) + if err != nil { + t.Fatal(err) + } + providerAddr := addr.NewDefaultProvider("aws") + + ps := ProviderSchemaFromJson(jsonSchema, providerAddr) + if diff := cmp.Diff(&tc.expectedSchema, ps, ctydebug.CmpOptions); diff != "" { + t.Fatalf("provider schema mismatch: %s", diff) + } + }) + } + +} diff --git a/schema/errors.go b/schema/errors.go index 28892184..e7e134d6 100644 --- a/schema/errors.go +++ b/schema/errors.go @@ -15,6 +15,12 @@ func (e coreSchemaRequiredErr) Error() string { return "core schema required (none provided)" } +type coreFunctionsRequiredErr struct{} + +func (e coreFunctionsRequiredErr) Error() string { + return "core functions required (none provided)" +} + type NoCompatibleSchemaErr struct { Version *version.Version Constraints version.Constraints diff --git a/schema/functions_merge.go b/schema/functions_merge.go new file mode 100644 index 00000000..c68dc634 --- /dev/null +++ b/schema/functions_merge.go @@ -0,0 +1,62 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "fmt" + + "github.com/hashicorp/hcl-lang/schema" + tfmod "github.com/hashicorp/terraform-schema/module" +) + +type FunctionsMerger struct { + coreFunctions map[string]schema.FunctionSignature + schemaReader SchemaReader +} + +func NewFunctionsMerger(coreFunctions map[string]schema.FunctionSignature) *FunctionsMerger { + return &FunctionsMerger{ + coreFunctions: coreFunctions, + } +} + +func (m *FunctionsMerger) SetSchemaReader(sr SchemaReader) { + m.schemaReader = sr +} + +func (m *FunctionsMerger) FunctionsForModule(meta *tfmod.Meta) (map[string]schema.FunctionSignature, error) { + if m.coreFunctions == nil { + return nil, coreFunctionsRequiredErr{} + } + + if meta == nil { + return m.coreFunctions, nil + } + + mergedFunctions := make(map[string]schema.FunctionSignature, len(m.coreFunctions)) + for fName, fSig := range m.coreFunctions { + mergedFunctions[fName] = *fSig.Copy() + } + + providerRefs := ProviderReferences(meta.ProviderReferences) + + if m.schemaReader != nil { + for pAddr, pVersionCons := range meta.ProviderRequirements { + pSchema, err := m.schemaReader.ProviderSchema(meta.Path, pAddr, pVersionCons) + if err != nil { + continue + } + + refs := providerRefs.ReferencesOfProvider(pAddr) + + for _, localRef := range refs { + for fName, fSig := range pSchema.Functions { + mergedFunctions[fmt.Sprintf("provider::%s::%s", localRef.LocalName, fName)] = *fSig.Copy() + } + } + } + } + + return mergedFunctions, nil +} diff --git a/schema/functions_merge_test.go b/schema/functions_merge_test.go new file mode 100644 index 00000000..411637ef --- /dev/null +++ b/schema/functions_merge_test.go @@ -0,0 +1,151 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "errors" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/go-version" + "github.com/hashicorp/hcl-lang/schema" + tfjson "github.com/hashicorp/terraform-json" + tfaddr "github.com/hashicorp/terraform-registry-address" + "github.com/hashicorp/terraform-schema/internal/addr" + tfmod "github.com/hashicorp/terraform-schema/module" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" +) + +func TestFunctionsMerger_FunctionsForModule_noCoreFunctions(t *testing.T) { + sm := NewFunctionsMerger(nil) + + _, err := sm.FunctionsForModule(nil) + if err == nil { + t.Fatal("expected error for nil core schema") + } + + if !errors.Is(err, coreFunctionsRequiredErr{}) { + t.Fatalf("unexpected error: %#v", err) + } +} + +func TestFunctionsMerger_FunctionsForModule_noMeta(t *testing.T) { + coreFunctions := map[string]schema.FunctionSignature{ + "foo": { + Params: []function.Parameter{ + {Name: "bar", Type: cty.String, Description: "bar function"}, + }, + }, + } + + sm := NewFunctionsMerger(coreFunctions) + + functions, err := sm.FunctionsForModule(nil) + if err != nil { + t.Fatalf("unexpected error: %#v", err) + } + + if len(functions) != 1 { + t.Fatalf("unexpected functions: %#v", functions) + } + + if functions["foo"].Params[0].Name != "bar" { + t.Fatalf("unexpected function: %#v", functions) + } +} + +func TestFunctionsMerger_FunctionsForModule(t *testing.T) { + coreFunctions := map[string]schema.FunctionSignature{ + "foo": { + Params: []function.Parameter{ + {Name: "bar", Type: cty.String, Description: "bar param"}, + }, + Description: "foo function", + }, + } + + fm := NewFunctionsMerger(coreFunctions) + fm.SetSchemaReader(&testJsonSchemaReader{ + ps: &tfjson.ProviderSchemas{ + FormatVersion: "1.0", + Schemas: map[string]*tfjson.ProviderSchema{ + "registry.terraform.io/hashicorp/test": { + ConfigSchema: &tfjson.Schema{}, + DataSourceSchemas: map[string]*tfjson.Schema{}, + ResourceSchemas: map[string]*tfjson.Schema{}, + Functions: map[string]*tfjson.FunctionSignature{ + "bar": { + Parameters: []*tfjson.FunctionParameter{ + {Name: "baz", Type: cty.String, Description: "baz param"}, + }, + Description: "bar function", + ReturnType: cty.String, + }, + "alleven": { + VariadicParameter: &tfjson.FunctionParameter{ + Name: "numbers", + Type: cty.List(cty.Number), + }, + Description: "Returns true if all passed arguments are even numbers", + ReturnType: cty.Bool, + }, + }, + }, + }, + }, + }) + + testProvider := addr.NewDefaultProvider("test") + versionConstraints, err := version.NewConstraint("1.0.0") + + if err != nil { + t.Fatalf("unexpected error: %#v", err) + } + + meta := &tfmod.Meta{ + ProviderReferences: map[tfmod.ProviderRef]tfaddr.Provider{ + {LocalName: "localtest"}: testProvider, + }, + ProviderRequirements: tfmod.ProviderRequirements{ + testProvider: versionConstraints, + }, + } + + expectedFunctions := map[string]schema.FunctionSignature{ + "foo": { + Params: []function.Parameter{ + {Name: "bar", Type: cty.String, Description: "bar param"}, + }, + Description: "foo function", + }, + "provider::localtest::bar": { + Params: []function.Parameter{ + {Name: "baz", Type: cty.String, Description: "baz param"}, + }, + Description: "bar function", + Detail: "hashicorp/test", + ReturnType: cty.String, + }, + "provider::localtest::alleven": { + Params: []function.Parameter{}, + VarParam: &function.Parameter{ + Name: "numbers", Type: cty.List(cty.Number), + }, + Description: "Returns true if all passed arguments are even numbers", + Detail: "hashicorp/test", + ReturnType: cty.Bool, + }, + } + + givenFunctions, err := fm.FunctionsForModule(meta) + if err != nil { + t.Fatalf("unexpected error: %#v", err) + } + + if diff := cmp.Diff(expectedFunctions, givenFunctions, ctydebug.CmpOptions); diff != "" { + t.Fatalf("functions mismatch: %s", diff) + } +} diff --git a/schema/provider_schema.go b/schema/provider_schema.go index 0b01b75d..da2d21a7 100644 --- a/schema/provider_schema.go +++ b/schema/provider_schema.go @@ -13,6 +13,7 @@ type ProviderSchema struct { Provider *schema.BodySchema Resources map[string]*schema.BodySchema DataSources map[string]*schema.BodySchema + Functions map[string]*schema.FunctionSignature } func (ps *ProviderSchema) Copy() *ProviderSchema { @@ -38,6 +39,13 @@ func (ps *ProviderSchema) Copy() *ProviderSchema { } } + if ps.Functions != nil { + newPs.Functions = make(map[string]*schema.FunctionSignature, len(ps.Functions)) + for name, fSig := range ps.Functions { + newPs.Functions[name] = fSig.Copy() + } + } + return newPs } @@ -53,4 +61,7 @@ func (ps *ProviderSchema) SetProviderVersion(pAddr tfaddr.Provider, v *version.V for _, dsSchema := range ps.DataSources { dsSchema.Detail = detailForSrcAddr(pAddr, v) } + for _, fSig := range ps.Functions { + fSig.Detail = detailForSrcAddr(pAddr, v) + } } diff --git a/schema/provider_schema_test.go b/schema/provider_schema_test.go index 7a34c930..86d78c71 100644 --- a/schema/provider_schema_test.go +++ b/schema/provider_schema_test.go @@ -12,6 +12,7 @@ import ( tfaddr "github.com/hashicorp/terraform-registry-address" "github.com/zclconf/go-cty-debug/ctydebug" "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" ) func TestProviderSchema_SetProviderVersion(t *testing.T) { @@ -37,6 +38,18 @@ func TestProviderSchema_SetProviderVersion(t *testing.T) { }, }, }, + Functions: map[string]*schema.FunctionSignature{ + "baz": { + Params: []function.Parameter{ + { + Name: "a", + Type: cty.String, + Description: "first parameter", + }, + }, + Description: "baz", + }, + }, } expectedSchema := &ProviderSchema{ Provider: &schema.BodySchema{ @@ -69,6 +82,15 @@ func TestProviderSchema_SetProviderVersion(t *testing.T) { }, }, }, + Functions: map[string]*schema.FunctionSignature{ + "baz": { + Description: "baz", + Detail: "hashicorp/aws 1.2.5", + Params: []function.Parameter{ + {Name: "a", Type: cty.String, Description: "first parameter"}, + }, + }, + }, } pAddr := tfaddr.Provider{