github.com/opentofu/opentofu@v1.7.1/internal/plugin6/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/tfplugin6"
    16  	"github.com/zclconf/go-cty/cty"
    17  )
    18  
    19  // ConfigSchemaToProto takes a *configschema.Block and converts it to a
    20  // proto.Schema_Block for a grpc response.
    21  func ConfigSchemaToProto(b *configschema.Block) *proto.Schema_Block {
    22  	block := &proto.Schema_Block{
    23  		Description:     b.Description,
    24  		DescriptionKind: protoStringKind(b.DescriptionKind),
    25  		Deprecated:      b.Deprecated,
    26  	}
    27  
    28  	for _, name := range sortedKeys(b.Attributes) {
    29  		a := b.Attributes[name]
    30  
    31  		attr := &proto.Schema_Attribute{
    32  			Name:            name,
    33  			Description:     a.Description,
    34  			DescriptionKind: protoStringKind(a.DescriptionKind),
    35  			Optional:        a.Optional,
    36  			Computed:        a.Computed,
    37  			Required:        a.Required,
    38  			Sensitive:       a.Sensitive,
    39  			Deprecated:      a.Deprecated,
    40  		}
    41  
    42  		if a.Type != cty.NilType {
    43  			ty, err := json.Marshal(a.Type)
    44  			if err != nil {
    45  				panic(err)
    46  			}
    47  			attr.Type = ty
    48  		}
    49  
    50  		if a.NestedType != nil {
    51  			attr.NestedType = configschemaObjectToProto(a.NestedType)
    52  		}
    53  
    54  		block.Attributes = append(block.Attributes, attr)
    55  	}
    56  
    57  	for _, name := range sortedKeys(b.BlockTypes) {
    58  		b := b.BlockTypes[name]
    59  		block.BlockTypes = append(block.BlockTypes, protoSchemaNestedBlock(name, b))
    60  	}
    61  
    62  	return block
    63  }
    64  
    65  func protoStringKind(k configschema.StringKind) proto.StringKind {
    66  	switch k {
    67  	default:
    68  		return proto.StringKind_PLAIN
    69  	case configschema.StringMarkdown:
    70  		return proto.StringKind_MARKDOWN
    71  	}
    72  }
    73  
    74  func protoSchemaNestedBlock(name string, b *configschema.NestedBlock) *proto.Schema_NestedBlock {
    75  	var nesting proto.Schema_NestedBlock_NestingMode
    76  	switch b.Nesting {
    77  	case configschema.NestingSingle:
    78  		nesting = proto.Schema_NestedBlock_SINGLE
    79  	case configschema.NestingGroup:
    80  		nesting = proto.Schema_NestedBlock_GROUP
    81  	case configschema.NestingList:
    82  		nesting = proto.Schema_NestedBlock_LIST
    83  	case configschema.NestingSet:
    84  		nesting = proto.Schema_NestedBlock_SET
    85  	case configschema.NestingMap:
    86  		nesting = proto.Schema_NestedBlock_MAP
    87  	default:
    88  		nesting = proto.Schema_NestedBlock_INVALID
    89  	}
    90  	return &proto.Schema_NestedBlock{
    91  		TypeName: name,
    92  		Block:    ConfigSchemaToProto(&b.Block),
    93  		Nesting:  nesting,
    94  		MinItems: int64(b.MinItems),
    95  		MaxItems: int64(b.MaxItems),
    96  	}
    97  }
    98  
    99  // ProtoToProviderSchema takes a proto.Schema and converts it to a providers.Schema.
   100  func ProtoToProviderSchema(s *proto.Schema) providers.Schema {
   101  	return providers.Schema{
   102  		Version: s.Version,
   103  		Block:   ProtoToConfigSchema(s.Block),
   104  	}
   105  }
   106  
   107  // ProtoToConfigSchema takes the GetSchcema_Block from a grpc response and converts it
   108  // to a tofu *configschema.Block.
   109  func ProtoToConfigSchema(b *proto.Schema_Block) *configschema.Block {
   110  	block := &configschema.Block{
   111  		Attributes: make(map[string]*configschema.Attribute),
   112  		BlockTypes: make(map[string]*configschema.NestedBlock),
   113  
   114  		Description:     b.Description,
   115  		DescriptionKind: schemaStringKind(b.DescriptionKind),
   116  		Deprecated:      b.Deprecated,
   117  	}
   118  
   119  	for _, a := range b.Attributes {
   120  		attr := &configschema.Attribute{
   121  			Description:     a.Description,
   122  			DescriptionKind: schemaStringKind(a.DescriptionKind),
   123  			Required:        a.Required,
   124  			Optional:        a.Optional,
   125  			Computed:        a.Computed,
   126  			Sensitive:       a.Sensitive,
   127  			Deprecated:      a.Deprecated,
   128  		}
   129  
   130  		if a.Type != nil {
   131  			if err := json.Unmarshal(a.Type, &attr.Type); err != nil {
   132  				panic(err)
   133  			}
   134  		}
   135  
   136  		if a.NestedType != nil {
   137  			attr.NestedType = protoObjectToConfigSchema(a.NestedType)
   138  		}
   139  
   140  		block.Attributes[a.Name] = attr
   141  	}
   142  
   143  	for _, b := range b.BlockTypes {
   144  		block.BlockTypes[b.TypeName] = schemaNestedBlock(b)
   145  	}
   146  
   147  	return block
   148  }
   149  
   150  func schemaStringKind(k proto.StringKind) configschema.StringKind {
   151  	switch k {
   152  	default:
   153  		return configschema.StringPlain
   154  	case proto.StringKind_MARKDOWN:
   155  		return configschema.StringMarkdown
   156  	}
   157  }
   158  
   159  func schemaNestedBlock(b *proto.Schema_NestedBlock) *configschema.NestedBlock {
   160  	var nesting configschema.NestingMode
   161  	switch b.Nesting {
   162  	case proto.Schema_NestedBlock_SINGLE:
   163  		nesting = configschema.NestingSingle
   164  	case proto.Schema_NestedBlock_GROUP:
   165  		nesting = configschema.NestingGroup
   166  	case proto.Schema_NestedBlock_LIST:
   167  		nesting = configschema.NestingList
   168  	case proto.Schema_NestedBlock_MAP:
   169  		nesting = configschema.NestingMap
   170  	case proto.Schema_NestedBlock_SET:
   171  		nesting = configschema.NestingSet
   172  	default:
   173  		// In all other cases we'll leave it as the zero value (invalid) and
   174  		// let the caller validate it and deal with this.
   175  	}
   176  
   177  	nb := &configschema.NestedBlock{
   178  		Nesting:  nesting,
   179  		MinItems: int(b.MinItems),
   180  		MaxItems: int(b.MaxItems),
   181  	}
   182  
   183  	nested := ProtoToConfigSchema(b.Block)
   184  	nb.Block = *nested
   185  	return nb
   186  }
   187  
   188  func protoObjectToConfigSchema(b *proto.Schema_Object) *configschema.Object {
   189  	var nesting configschema.NestingMode
   190  	switch b.Nesting {
   191  	case proto.Schema_Object_SINGLE:
   192  		nesting = configschema.NestingSingle
   193  	case proto.Schema_Object_LIST:
   194  		nesting = configschema.NestingList
   195  	case proto.Schema_Object_MAP:
   196  		nesting = configschema.NestingMap
   197  	case proto.Schema_Object_SET:
   198  		nesting = configschema.NestingSet
   199  	default:
   200  		// In all other cases we'll leave it as the zero value (invalid) and
   201  		// let the caller validate it and deal with this.
   202  	}
   203  
   204  	object := &configschema.Object{
   205  		Attributes: make(map[string]*configschema.Attribute),
   206  		Nesting:    nesting,
   207  	}
   208  
   209  	for _, a := range b.Attributes {
   210  		attr := &configschema.Attribute{
   211  			Description:     a.Description,
   212  			DescriptionKind: schemaStringKind(a.DescriptionKind),
   213  			Required:        a.Required,
   214  			Optional:        a.Optional,
   215  			Computed:        a.Computed,
   216  			Sensitive:       a.Sensitive,
   217  			Deprecated:      a.Deprecated,
   218  		}
   219  
   220  		if a.Type != nil {
   221  			if err := json.Unmarshal(a.Type, &attr.Type); err != nil {
   222  				panic(err)
   223  			}
   224  		}
   225  
   226  		if a.NestedType != nil {
   227  			attr.NestedType = protoObjectToConfigSchema(a.NestedType)
   228  		}
   229  
   230  		object.Attributes[a.Name] = attr
   231  	}
   232  
   233  	return object
   234  }
   235  
   236  // sortedKeys returns the lexically sorted keys from the given map. This is
   237  // used to make schema conversions are deterministic. This panics if map keys
   238  // are not a string.
   239  func sortedKeys(m interface{}) []string {
   240  	v := reflect.ValueOf(m)
   241  	keys := make([]string, v.Len())
   242  
   243  	mapKeys := v.MapKeys()
   244  	for i, k := range mapKeys {
   245  		keys[i] = k.Interface().(string)
   246  	}
   247  
   248  	sort.Strings(keys)
   249  	return keys
   250  }
   251  
   252  func configschemaObjectToProto(b *configschema.Object) *proto.Schema_Object {
   253  	var nesting proto.Schema_Object_NestingMode
   254  	switch b.Nesting {
   255  	case configschema.NestingSingle:
   256  		nesting = proto.Schema_Object_SINGLE
   257  	case configschema.NestingList:
   258  		nesting = proto.Schema_Object_LIST
   259  	case configschema.NestingSet:
   260  		nesting = proto.Schema_Object_SET
   261  	case configschema.NestingMap:
   262  		nesting = proto.Schema_Object_MAP
   263  	default:
   264  		nesting = proto.Schema_Object_INVALID
   265  	}
   266  
   267  	attributes := make([]*proto.Schema_Attribute, 0, len(b.Attributes))
   268  
   269  	for _, name := range sortedKeys(b.Attributes) {
   270  		a := b.Attributes[name]
   271  
   272  		attr := &proto.Schema_Attribute{
   273  			Name:            name,
   274  			Description:     a.Description,
   275  			DescriptionKind: protoStringKind(a.DescriptionKind),
   276  			Optional:        a.Optional,
   277  			Computed:        a.Computed,
   278  			Required:        a.Required,
   279  			Sensitive:       a.Sensitive,
   280  			Deprecated:      a.Deprecated,
   281  		}
   282  
   283  		if a.Type != cty.NilType {
   284  			ty, err := json.Marshal(a.Type)
   285  			if err != nil {
   286  				panic(err)
   287  			}
   288  			attr.Type = ty
   289  		}
   290  
   291  		if a.NestedType != nil {
   292  			attr.NestedType = configschemaObjectToProto(a.NestedType)
   293  		}
   294  
   295  		attributes = append(attributes, attr)
   296  	}
   297  
   298  	return &proto.Schema_Object{
   299  		Attributes: attributes,
   300  		Nesting:    nesting,
   301  	}
   302  }