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 }