Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] command/jsonprovider: export providers schemas to json #20446

Merged
merged 5 commits into from
Feb 25, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
}
}
}
69 changes: 69 additions & 0 deletions command/jsonprovider/block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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"`
MaxItems uint64 `json:"max_items"`
mildwonkey marked this conversation as resolved.
Show resolved Hide resolved
}

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.String() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was surprised to see the .String() call here vs. switching directly on the nestedBlock.Nesting value. Could you add a comment explaining it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll just drop the .String() and check the nested mode directly - I had doubts when I wrote that but forgot to follow up.

case "nestingModeInvalid":
ret.NestingMode = "invalid"
case "NestingSingle":
ret.NestingMode = "single"
case "NestingList":
ret.NestingMode = "list"
case "NestingSet":
ret.NestingMode = "set"
case "NestingMap":
ret.NestingMode = "map"
default:
// unpossible.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move the statement from "nestingModeInvalid" down here and remove that case? I think it being literally nestingModeInvalid and it being some value that isn't in the enum at all are... both invalid, really.

}
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