github.com/opentofu/opentofu@v1.7.1/internal/plugin/convert/schema.go (about)

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