github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/plugin/convert/schema.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package convert
     5  
     6  import (
     7  	"encoding/json"
     8  	"reflect"
     9  	"sort"
    10  
    11  	"github.com/terramate-io/tf/configs/configschema"
    12  	"github.com/terramate-io/tf/providers"
    13  	proto "github.com/terramate-io/tf/tfplugin5"
    14  )
    15  
    16  // ConfigSchemaToProto takes a *configschema.Block and converts it to a
    17  // proto.Schema_Block for a grpc response.
    18  func ConfigSchemaToProto(b *configschema.Block) *proto.Schema_Block {
    19  	block := &proto.Schema_Block{
    20  		Description:     b.Description,
    21  		DescriptionKind: protoStringKind(b.DescriptionKind),
    22  		Deprecated:      b.Deprecated,
    23  	}
    24  
    25  	for _, name := range sortedKeys(b.Attributes) {
    26  		a := b.Attributes[name]
    27  
    28  		attr := &proto.Schema_Attribute{
    29  			Name:            name,
    30  			Description:     a.Description,
    31  			DescriptionKind: protoStringKind(a.DescriptionKind),
    32  			Optional:        a.Optional,
    33  			Computed:        a.Computed,
    34  			Required:        a.Required,
    35  			Sensitive:       a.Sensitive,
    36  			Deprecated:      a.Deprecated,
    37  		}
    38  
    39  		ty, err := json.Marshal(a.Type)
    40  		if err != nil {
    41  			panic(err)
    42  		}
    43  
    44  		attr.Type = ty
    45  
    46  		block.Attributes = append(block.Attributes, attr)
    47  	}
    48  
    49  	for _, name := range sortedKeys(b.BlockTypes) {
    50  		b := b.BlockTypes[name]
    51  		block.BlockTypes = append(block.BlockTypes, protoSchemaNestedBlock(name, b))
    52  	}
    53  
    54  	return block
    55  }
    56  
    57  func protoStringKind(k configschema.StringKind) proto.StringKind {
    58  	switch k {
    59  	default:
    60  		return proto.StringKind_PLAIN
    61  	case configschema.StringMarkdown:
    62  		return proto.StringKind_MARKDOWN
    63  	}
    64  }
    65  
    66  func protoSchemaNestedBlock(name string, b *configschema.NestedBlock) *proto.Schema_NestedBlock {
    67  	var nesting proto.Schema_NestedBlock_NestingMode
    68  	switch b.Nesting {
    69  	case configschema.NestingSingle:
    70  		nesting = proto.Schema_NestedBlock_SINGLE
    71  	case configschema.NestingGroup:
    72  		nesting = proto.Schema_NestedBlock_GROUP
    73  	case configschema.NestingList:
    74  		nesting = proto.Schema_NestedBlock_LIST
    75  	case configschema.NestingSet:
    76  		nesting = proto.Schema_NestedBlock_SET
    77  	case configschema.NestingMap:
    78  		nesting = proto.Schema_NestedBlock_MAP
    79  	default:
    80  		nesting = proto.Schema_NestedBlock_INVALID
    81  	}
    82  	return &proto.Schema_NestedBlock{
    83  		TypeName: name,
    84  		Block:    ConfigSchemaToProto(&b.Block),
    85  		Nesting:  nesting,
    86  		MinItems: int64(b.MinItems),
    87  		MaxItems: int64(b.MaxItems),
    88  	}
    89  }
    90  
    91  // ProtoToProviderSchema takes a proto.Schema and converts it to a providers.Schema.
    92  func ProtoToProviderSchema(s *proto.Schema) providers.Schema {
    93  	return providers.Schema{
    94  		Version: s.Version,
    95  		Block:   ProtoToConfigSchema(s.Block),
    96  	}
    97  }
    98  
    99  // ProtoToConfigSchema takes the GetSchcema_Block from a grpc response and converts it
   100  // to a terraform *configschema.Block.
   101  func ProtoToConfigSchema(b *proto.Schema_Block) *configschema.Block {
   102  	block := &configschema.Block{
   103  		Attributes: make(map[string]*configschema.Attribute),
   104  		BlockTypes: make(map[string]*configschema.NestedBlock),
   105  
   106  		Description:     b.Description,
   107  		DescriptionKind: schemaStringKind(b.DescriptionKind),
   108  		Deprecated:      b.Deprecated,
   109  	}
   110  
   111  	for _, a := range b.Attributes {
   112  		attr := &configschema.Attribute{
   113  			Description:     a.Description,
   114  			DescriptionKind: schemaStringKind(a.DescriptionKind),
   115  			Required:        a.Required,
   116  			Optional:        a.Optional,
   117  			Computed:        a.Computed,
   118  			Sensitive:       a.Sensitive,
   119  			Deprecated:      a.Deprecated,
   120  		}
   121  
   122  		if err := json.Unmarshal(a.Type, &attr.Type); err != nil {
   123  			panic(err)
   124  		}
   125  
   126  		block.Attributes[a.Name] = attr
   127  	}
   128  
   129  	for _, b := range b.BlockTypes {
   130  		block.BlockTypes[b.TypeName] = schemaNestedBlock(b)
   131  	}
   132  
   133  	return block
   134  }
   135  
   136  func schemaStringKind(k proto.StringKind) configschema.StringKind {
   137  	switch k {
   138  	default:
   139  		return configschema.StringPlain
   140  	case proto.StringKind_MARKDOWN:
   141  		return configschema.StringMarkdown
   142  	}
   143  }
   144  
   145  func schemaNestedBlock(b *proto.Schema_NestedBlock) *configschema.NestedBlock {
   146  	var nesting configschema.NestingMode
   147  	switch b.Nesting {
   148  	case proto.Schema_NestedBlock_SINGLE:
   149  		nesting = configschema.NestingSingle
   150  	case proto.Schema_NestedBlock_GROUP:
   151  		nesting = configschema.NestingGroup
   152  	case proto.Schema_NestedBlock_LIST:
   153  		nesting = configschema.NestingList
   154  	case proto.Schema_NestedBlock_MAP:
   155  		nesting = configschema.NestingMap
   156  	case proto.Schema_NestedBlock_SET:
   157  		nesting = configschema.NestingSet
   158  	default:
   159  		// In all other cases we'll leave it as the zero value (invalid) and
   160  		// let the caller validate it and deal with this.
   161  	}
   162  
   163  	nb := &configschema.NestedBlock{
   164  		Nesting:  nesting,
   165  		MinItems: int(b.MinItems),
   166  		MaxItems: int(b.MaxItems),
   167  	}
   168  
   169  	nested := ProtoToConfigSchema(b.Block)
   170  	nb.Block = *nested
   171  	return nb
   172  }
   173  
   174  // sortedKeys returns the lexically sorted keys from the given map. This is
   175  // used to make schema conversions are deterministic. This panics if map keys
   176  // are not a string.
   177  func sortedKeys(m interface{}) []string {
   178  	v := reflect.ValueOf(m)
   179  	keys := make([]string, v.Len())
   180  
   181  	mapKeys := v.MapKeys()
   182  	for i, k := range mapKeys {
   183  		keys[i] = k.Interface().(string)
   184  	}
   185  
   186  	sort.Strings(keys)
   187  	return keys
   188  }