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 }