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 }