opentofu/plugin/convert/schema.go
Paddy e6592dc710
Add support for provider metadata to modules. (#22583)
Implement a new provider_meta block in the terraform block of modules, allowing provider-keyed metadata to be communicated from HCL to provider binaries.

Bundled in this change for minimal protocol version bumping is the addition of markdown support for attribute descriptions and the ability to indicate when an attribute is deprecated, so this information can be shown in the schema dump.

Co-authored-by: Paul Tyng <paul@paultyng.net>
2020-03-05 16:53:24 -08:00

186 lines
4.9 KiB
Go

package convert
import (
"encoding/json"
"reflect"
"sort"
"github.com/hashicorp/terraform/configs/configschema"
proto "github.com/hashicorp/terraform/internal/tfplugin5"
"github.com/hashicorp/terraform/providers"
)
// ConfigSchemaToProto takes a *configschema.Block and converts it to a
// proto.Schema_Block for a grpc response.
func ConfigSchemaToProto(b *configschema.Block) *proto.Schema_Block {
block := &proto.Schema_Block{
Description: b.Description,
DescriptionKind: protoStringKind(b.DescriptionKind),
Deprecated: b.Deprecated,
}
for _, name := range sortedKeys(b.Attributes) {
a := b.Attributes[name]
attr := &proto.Schema_Attribute{
Name: name,
Description: a.Description,
DescriptionKind: protoStringKind(a.DescriptionKind),
Optional: a.Optional,
Computed: a.Computed,
Required: a.Required,
Sensitive: a.Sensitive,
Deprecated: a.Deprecated,
}
ty, err := json.Marshal(a.Type)
if err != nil {
panic(err)
}
attr.Type = ty
block.Attributes = append(block.Attributes, attr)
}
for _, name := range sortedKeys(b.BlockTypes) {
b := b.BlockTypes[name]
block.BlockTypes = append(block.BlockTypes, protoSchemaNestedBlock(name, b))
}
return block
}
func protoStringKind(k configschema.StringKind) proto.StringKind {
switch k {
default:
return proto.StringKind_PLAIN
case configschema.StringMarkdown:
return proto.StringKind_MARKDOWN
}
}
func protoSchemaNestedBlock(name string, b *configschema.NestedBlock) *proto.Schema_NestedBlock {
var nesting proto.Schema_NestedBlock_NestingMode
switch b.Nesting {
case configschema.NestingSingle:
nesting = proto.Schema_NestedBlock_SINGLE
case configschema.NestingGroup:
nesting = proto.Schema_NestedBlock_GROUP
case configschema.NestingList:
nesting = proto.Schema_NestedBlock_LIST
case configschema.NestingSet:
nesting = proto.Schema_NestedBlock_SET
case configschema.NestingMap:
nesting = proto.Schema_NestedBlock_MAP
default:
nesting = proto.Schema_NestedBlock_INVALID
}
return &proto.Schema_NestedBlock{
TypeName: name,
Block: ConfigSchemaToProto(&b.Block),
Nesting: nesting,
MinItems: int64(b.MinItems),
MaxItems: int64(b.MaxItems),
}
}
// ProtoToProviderSchema takes a proto.Schema and converts it to a providers.Schema.
func ProtoToProviderSchema(s *proto.Schema) providers.Schema {
return providers.Schema{
Version: s.Version,
Block: ProtoToConfigSchema(s.Block),
}
}
// ProtoToConfigSchema takes the GetSchcema_Block from a grpc response and converts it
// to a terraform *configschema.Block.
func ProtoToConfigSchema(b *proto.Schema_Block) *configschema.Block {
block := &configschema.Block{
Attributes: make(map[string]*configschema.Attribute),
BlockTypes: make(map[string]*configschema.NestedBlock),
Description: b.Description,
DescriptionKind: schemaStringKind(b.DescriptionKind),
Deprecated: b.Deprecated,
}
for _, a := range b.Attributes {
attr := &configschema.Attribute{
Description: a.Description,
DescriptionKind: schemaStringKind(a.DescriptionKind),
Required: a.Required,
Optional: a.Optional,
Computed: a.Computed,
Sensitive: a.Sensitive,
Deprecated: a.Deprecated,
}
if err := json.Unmarshal(a.Type, &attr.Type); err != nil {
panic(err)
}
block.Attributes[a.Name] = attr
}
for _, b := range b.BlockTypes {
block.BlockTypes[b.TypeName] = schemaNestedBlock(b)
}
return block
}
func schemaStringKind(k proto.StringKind) configschema.StringKind {
switch k {
default:
return configschema.StringPlain
case proto.StringKind_MARKDOWN:
return configschema.StringMarkdown
}
}
func schemaNestedBlock(b *proto.Schema_NestedBlock) *configschema.NestedBlock {
var nesting configschema.NestingMode
switch b.Nesting {
case proto.Schema_NestedBlock_SINGLE:
nesting = configschema.NestingSingle
case proto.Schema_NestedBlock_GROUP:
nesting = configschema.NestingGroup
case proto.Schema_NestedBlock_LIST:
nesting = configschema.NestingList
case proto.Schema_NestedBlock_MAP:
nesting = configschema.NestingMap
case proto.Schema_NestedBlock_SET:
nesting = configschema.NestingSet
default:
// In all other cases we'll leave it as the zero value (invalid) and
// let the caller validate it and deal with this.
}
nb := &configschema.NestedBlock{
Nesting: nesting,
MinItems: int(b.MinItems),
MaxItems: int(b.MaxItems),
}
nested := ProtoToConfigSchema(b.Block)
nb.Block = *nested
return nb
}
// sortedKeys returns the lexically sorted keys from the given map. This is
// used to make schema conversions are deterministic. This panics if map keys
// are not a string.
func sortedKeys(m interface{}) []string {
v := reflect.ValueOf(m)
keys := make([]string, v.Len())
mapKeys := v.MapKeys()
for i, k := range mapKeys {
keys[i] = k.Interface().(string)
}
sort.Strings(keys)
return keys
}