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