Skip to content

Commit

Permalink
command/jsonprovider: export providers schemas to json (#20446)
Browse files Browse the repository at this point in the history
* command/jsonprovider: a new package for exporting providers schemas as JSON
  • Loading branch information
mildwonkey authored Feb 25, 2019
1 parent 2b9e2b4 commit 16823f4
Show file tree
Hide file tree
Showing 14 changed files with 790 additions and 0 deletions.
31 changes: 31 additions & 0 deletions command/jsonprovider/attribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package jsonprovider

import (
"encoding/json"

"github.com/hashicorp/terraform/configs/configschema"
)

type attribute struct {
AttributeType json.RawMessage `json:"type,omitempty"`
Description string `json:"description,omitempty"`
Required bool `json:"required,omitempty"`
Optional bool `json:"optional,omitempty"`
Computed bool `json:"computed,omitempty"`
Sensitive bool `json:"sensitive,omitempty"`
}

func marshalAttribute(attr *configschema.Attribute) *attribute {
// we're not concerned about errors because at this point the schema has
// already been checked and re-checked.
attrTy, _ := attr.Type.MarshalJSON()

return &attribute{
AttributeType: attrTy,
Description: attr.Description,
Required: attr.Required,
Optional: attr.Optional,
Computed: attr.Computed,
Sensitive: attr.Sensitive,
}
}
42 changes: 42 additions & 0 deletions command/jsonprovider/attribute_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package jsonprovider

import (
"encoding/json"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"

"github.com/hashicorp/terraform/configs/configschema"
)

func TestMarshalAttribute(t *testing.T) {
tests := []struct {
Input *configschema.Attribute
Want *attribute
}{
{
&configschema.Attribute{Type: cty.String, Optional: true, Computed: true},
&attribute{
AttributeType: json.RawMessage(`"string"`),
Optional: true,
Computed: true,
},
},
{ // collection types look a little odd.
&configschema.Attribute{Type: cty.Map(cty.String), Optional: true, Computed: true},
&attribute{
AttributeType: json.RawMessage(`["map","string"]`),
Optional: true,
Computed: true,
},
},
}

for _, test := range tests {
got := marshalAttribute(test.Input)
if !cmp.Equal(got, test.Want) {
t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, test.Want))
}
}
}
67 changes: 67 additions & 0 deletions command/jsonprovider/block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package jsonprovider

import (
"github.com/hashicorp/terraform/configs/configschema"
)

type block struct {
Attributes map[string]*attribute `json:"attributes,omitempty"`
BlockTypes map[string]*blockType `json:"block_types,omitempty"`
}

type blockType struct {
NestingMode string `json:"nesting_mode,omitempty"`
Block *block `json:"block,omitempty"`
MinItems uint64 `json:"min_items,omitempty"`
MaxItems uint64 `json:"max_items,omitempty"`
}

func marshalBlockTypes(nestedBlock *configschema.NestedBlock) *blockType {
if nestedBlock == nil {
return &blockType{}
}
ret := &blockType{
Block: marshalBlock(&nestedBlock.Block),
MinItems: uint64(nestedBlock.MinItems),
MaxItems: uint64(nestedBlock.MaxItems),
}

switch nestedBlock.Nesting {
case configschema.NestingSingle:
ret.NestingMode = "single"
case configschema.NestingList:
ret.NestingMode = "list"
case configschema.NestingSet:
ret.NestingMode = "set"
case configschema.NestingMap:
ret.NestingMode = "map"
default:
ret.NestingMode = "invalid"
}
return ret
}

func marshalBlock(configBlock *configschema.Block) *block {
if configBlock == nil {
return &block{}
}

var ret block
if len(configBlock.Attributes) > 0 {
attrs := make(map[string]*attribute, len(configBlock.Attributes))
for k, attr := range configBlock.Attributes {
attrs[k] = marshalAttribute(attr)
}
ret.Attributes = attrs
}

if len(configBlock.BlockTypes) > 0 {
blockTypes := make(map[string]*blockType, len(configBlock.BlockTypes))
for k, bt := range configBlock.BlockTypes {
blockTypes[k] = marshalBlockTypes(bt)
}
ret.BlockTypes = blockTypes
}

return &ret
}
66 changes: 66 additions & 0 deletions command/jsonprovider/block_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package jsonprovider

import (
"encoding/json"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"

"github.com/hashicorp/terraform/configs/configschema"
)

func TestMarshalBlock(t *testing.T) {
tests := []struct {
Input *configschema.Block
Want *block
}{
{
nil,
&block{},
},
{
Input: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"ami": {Type: cty.String, Optional: true},
},
BlockTypes: map[string]*configschema.NestedBlock{
"network_interface": {
Nesting: configschema.NestingList,
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"device_index": {Type: cty.String, Optional: true},
"description": {Type: cty.String, Optional: true},
},
},
},
},
},
Want: &block{
Attributes: map[string]*attribute{
"ami": {AttributeType: json.RawMessage(`"string"`), Optional: true},
"id": {AttributeType: json.RawMessage(`"string"`), Optional: true, Computed: true},
},
BlockTypes: map[string]*blockType{
"network_interface": {
NestingMode: "list",
Block: &block{
Attributes: map[string]*attribute{
"description": {AttributeType: json.RawMessage(`"string"`), Optional: true},
"device_index": {AttributeType: json.RawMessage(`"string"`), Optional: true},
},
},
},
},
},
},
}

for _, test := range tests {
got := marshalBlock(test.Input)
if !cmp.Equal(got, test.Want) {
t.Fatalf("wrong result:\n %v\n", cmp.Diff(got, test.Want))
}
}
}
3 changes: 3 additions & 0 deletions command/jsonprovider/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Package jsonprovider contains types and functions to marshal terraform
// provider schemas into a json formatted output.
package jsonprovider
75 changes: 75 additions & 0 deletions command/jsonprovider/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package jsonprovider

import (
"encoding/json"

"github.com/hashicorp/terraform/terraform"
)

// FormatVersion represents the version of the json format and will be
// incremented for any change to this format that requires changes to a
// consuming parser.
const FormatVersion = "0.1"

// providers is the top-level object returned when exporting provider schemas
type providers struct {
FormatVersion string `json:"format_version"`
Schemas map[string]Provider `json:"provider_schemas"`
}

type Provider struct {
Provider *schema `json:"provider,omitempty"`
ResourceSchemas map[string]*schema `json:"resource_schemas,omitempty"`
DataSourceSchemas map[string]*schema `json:"data_source_schemas,omitempty"`
}

func newProviders() *providers {
schemas := make(map[string]Provider)
return &providers{
FormatVersion: FormatVersion,
Schemas: schemas,
}
}

func Marshal(s *terraform.Schemas) ([]byte, error) {
if len(s.Providers) == 0 {
return nil, nil
}

providers := newProviders()

for k, v := range s.Providers {
providers.Schemas[k] = marshalProvider(v)
}

// add some polish for the human consumers
ret, err := json.MarshalIndent(providers, "", " ")
return ret, err
}

func marshalProvider(tps *terraform.ProviderSchema) Provider {
if tps == nil {
return Provider{}
}

var ps *schema
var rs, ds map[string]*schema

if tps.Provider != nil {
ps = marshalSchema(tps.Provider)
}

if tps.ResourceTypes != nil {
rs = marshalSchemas(tps.ResourceTypes, tps.ResourceTypeSchemaVersions)
}

if tps.DataSources != nil {
ds = marshalSchemas(tps.DataSources, tps.ResourceTypeSchemaVersions)
}

return Provider{
Provider: ps,
ResourceSchemas: rs,
DataSourceSchemas: ds,
}
}
Loading

0 comments on commit 16823f4

Please sign in to comment.