diff --git a/earlydecoder/decoder_test.go b/earlydecoder/decoder_test.go index cdb2c11f..f9f14d93 100644 --- a/earlydecoder/decoder_test.go +++ b/earlydecoder/decoder_test.go @@ -53,6 +53,10 @@ resource "google_storage_bucket" "bucket" { name = "test-bucket" } +data "blah_foobar" "test" { + name = "something" +} + provider "grafana" { url = "http://grafana.example.com/" org_id = 1 @@ -62,11 +66,13 @@ provider "grafana" { Path: path, ProviderReferences: map[module.ProviderRef]tfaddr.Provider{ {LocalName: "aws"}: tfaddr.NewLegacyProvider("aws"), + {LocalName: "blah"}: tfaddr.NewLegacyProvider("blah"), {LocalName: "google"}: tfaddr.NewLegacyProvider("google"), {LocalName: "grafana"}: tfaddr.NewLegacyProvider("grafana"), }, ProviderRequirements: map[tfaddr.Provider]version.Constraints{ tfaddr.NewLegacyProvider("aws"): {}, + tfaddr.NewLegacyProvider("blah"): {}, tfaddr.NewLegacyProvider("google"): {}, tfaddr.NewLegacyProvider("grafana"): {}, }, diff --git a/schema/convert_json.go b/schema/convert_json.go new file mode 100644 index 00000000..5d57ba6e --- /dev/null +++ b/schema/convert_json.go @@ -0,0 +1,238 @@ +package schema + +import ( + "fmt" + + "github.com/hashicorp/go-version" + "github.com/hashicorp/hcl-lang/lang" + "github.com/hashicorp/hcl-lang/schema" + "github.com/hashicorp/terraform-json" + "github.com/hashicorp/terraform-registry-address" + "github.com/zclconf/go-cty/cty" +) + +func ProviderSchemaFromJson(jsonSchema *tfjson.ProviderSchema, pAddr tfaddr.Provider) *ProviderSchema { + ps := &ProviderSchema{ + Resources: map[string]*schema.BodySchema{}, + DataSources: map[string]*schema.BodySchema{}, + } + + if jsonSchema.ConfigSchema != nil { + ps.Provider = bodySchemaFromJson(jsonSchema.ConfigSchema.Block) + ps.Provider.Detail = detailForSrcAddr(pAddr, nil) + ps.Provider.DocsLink = docsLinkForProvider(pAddr, nil) + } + + for rName, rSchema := range jsonSchema.ResourceSchemas { + ps.Resources[rName] = bodySchemaFromJson(rSchema.Block) + ps.Resources[rName].Detail = detailForSrcAddr(pAddr, nil) + } + + for dsName, dsSchema := range jsonSchema.DataSourceSchemas { + ps.DataSources[dsName] = bodySchemaFromJson(dsSchema.Block) + ps.DataSources[dsName].Detail = detailForSrcAddr(pAddr, nil) + } + + return ps +} + +func (ps *ProviderSchema) SetProviderVersion(pAddr tfaddr.Provider, v *version.Version) { + if ps.Provider != nil { + ps.Provider.Detail = detailForSrcAddr(pAddr, v) + ps.Provider.DocsLink = docsLinkForProvider(pAddr, v) + } + for _, rSchema := range ps.Resources { + rSchema.Detail = detailForSrcAddr(pAddr, v) + } + for _, dsSchema := range ps.DataSources { + dsSchema.Detail = detailForSrcAddr(pAddr, v) + } +} + +func bodySchemaFromJson(schemaBlock *tfjson.SchemaBlock) *schema.BodySchema { + if schemaBlock == nil { + s := schema.NewBodySchema() + return s + } + + return &schema.BodySchema{ + Attributes: convertAttributesFromJson(schemaBlock.Attributes), + Blocks: convertBlocksFromJson(schemaBlock.NestedBlocks), + IsDeprecated: schemaBlock.Deprecated, + Description: markupContent(schemaBlock.Description, schemaBlock.DescriptionKind), + } +} + +func convertBlocksFromJson(blocks map[string]*tfjson.SchemaBlockType) map[string]*schema.BlockSchema { + cBlocks := make(map[string]*schema.BlockSchema, len(blocks)) + for name, jsonSchema := range blocks { + block := jsonSchema.Block + + blockType := schema.BlockTypeNil + labels := []*schema.LabelSchema{} + + switch jsonSchema.NestingMode { + case tfjson.SchemaNestingModeSingle: + blockType = schema.BlockTypeObject + case tfjson.SchemaNestingModeMap: + labels = []*schema.LabelSchema{ + {Name: "name"}, + } + blockType = schema.BlockTypeMap + case tfjson.SchemaNestingModeList: + blockType = schema.BlockTypeList + case tfjson.SchemaNestingModeSet: + blockType = schema.BlockTypeSet + } + + cBlocks[name] = &schema.BlockSchema{ + Description: markupContent(block.Description, block.DescriptionKind), + Type: blockType, + IsDeprecated: block.Deprecated, + MinItems: jsonSchema.MinItems, + MaxItems: jsonSchema.MaxItems, + Labels: labels, + Body: bodySchemaFromJson(block), + } + } + return cBlocks +} + +func convertAttributesFromJson(attributes map[string]*tfjson.SchemaAttribute) map[string]*schema.AttributeSchema { + cAttrs := make(map[string]*schema.AttributeSchema, len(attributes)) + for name, attr := range attributes { + cAttrs[name] = &schema.AttributeSchema{ + Description: markupContent(attr.Description, attr.DescriptionKind), + IsDeprecated: attr.Deprecated, + IsComputed: attr.Computed, + IsOptional: attr.Optional, + IsRequired: attr.Required, + Expr: exprConstraintsFromAttribute(attr), + } + } + return cAttrs +} + +func exprConstraintsFromAttribute(attr *tfjson.SchemaAttribute) schema.ExprConstraints { + var expr schema.ExprConstraints + if attr.AttributeType != cty.NilType { + expr = schema.LiteralTypeOnly(attr.AttributeType) + } + if attr.AttributeNestedType != nil { + switch attr.AttributeNestedType.NestingMode { + case tfjson.SchemaNestingModeSingle: + return schema.ExprConstraints{ + convertJsonAttributesToObjectExprAttr(attr.AttributeNestedType.Attributes), + } + case tfjson.SchemaNestingModeList: + return schema.ExprConstraints{ + schema.ListExpr{ + Elem: schema.ExprConstraints{ + convertJsonAttributesToObjectExprAttr(attr.AttributeNestedType.Attributes), + }, + MinItems: attr.AttributeNestedType.MinItems, + MaxItems: attr.AttributeNestedType.MaxItems, + }, + } + case tfjson.SchemaNestingModeSet: + return schema.ExprConstraints{ + schema.SetExpr{ + Elem: schema.ExprConstraints{ + convertJsonAttributesToObjectExprAttr(attr.AttributeNestedType.Attributes), + }, + MinItems: attr.AttributeNestedType.MinItems, + MaxItems: attr.AttributeNestedType.MaxItems, + }, + } + case tfjson.SchemaNestingModeMap: + return schema.ExprConstraints{ + schema.MapExpr{ + Elem: schema.ExprConstraints{ + convertJsonAttributesToObjectExprAttr(attr.AttributeNestedType.Attributes), + }, + MinItems: attr.AttributeNestedType.MinItems, + MaxItems: attr.AttributeNestedType.MaxItems, + }, + } + } + } + return expr +} + +func convertJsonAttributesToObjectExprAttr(attrs map[string]*tfjson.SchemaAttribute) schema.ObjectExpr { + attributes := make(schema.ObjectExprAttributes, len(attrs)) + for name, attr := range attrs { + attributes[name] = &schema.AttributeSchema{ + Description: markupContent(attr.Description, attr.DescriptionKind), + IsDeprecated: attr.Deprecated, + IsComputed: attr.Computed, + IsOptional: attr.Optional, + IsRequired: attr.Required, + Expr: exprConstraintsFromAttribute(attr), + } + } + return schema.ObjectExpr{ + Attributes: attributes, + } +} + +func markupContent(value string, kind tfjson.SchemaDescriptionKind) lang.MarkupContent { + if value == "" { + return lang.MarkupContent{} + } + switch kind { + case tfjson.SchemaDescriptionKindMarkdown: + return lang.Markdown(value) + case tfjson.SchemaDescriptionKindPlain: + return lang.PlainText(value) + } + + // backwards compatibility with v0.12 + return lang.PlainText(value) +} + +func docsLinkForProvider(addr tfaddr.Provider, v *version.Version) *schema.DocsLink { + if !providerHasDocs(addr) { + return nil + } + + ver := "latest" + if v != nil { + ver = v.String() + } + + return &schema.DocsLink{ + URL: fmt.Sprintf("https://registry.terraform.io/providers/%s/%s/%s/docs", + addr.Namespace, addr.Type, ver), + Tooltip: fmt.Sprintf("%s Documentation", addr.ForDisplay()), + } +} + +func providerHasDocs(addr tfaddr.Provider) bool { + if addr.IsBuiltIn() { + // Ideally this should point to versioned TF core docs + // but there aren't any for the built-in provider yet + return false + } + if addr.Hostname != "registry.terraform.io" { + // docs URLs outside of the official Registry aren't standardized yet + return false + } + return true +} + +func detailForSrcAddr(addr tfaddr.Provider, v *version.Version) string { + if addr.IsBuiltIn() { + if v == nil { + return "(builtin)" + } + return fmt.Sprintf("(builtin %s)", v.String()) + } + + detail := addr.ForDisplay() + if v != nil { + detail += " " + v.String() + } + + return detail +} diff --git a/schema/convert_json_test.go b/schema/convert_json_test.go new file mode 100644 index 00000000..9cb1d543 --- /dev/null +++ b/schema/convert_json_test.go @@ -0,0 +1,82 @@ +package schema + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/go-version" + "github.com/hashicorp/hcl-lang/schema" + tfaddr "github.com/hashicorp/terraform-registry-address" + "github.com/zclconf/go-cty-debug/ctydebug" + "github.com/zclconf/go-cty/cty" +) + +func TestProviderSchema_SetProviderVersion(t *testing.T) { + ps := &ProviderSchema{ + Provider: &schema.BodySchema{}, + Resources: map[string]*schema.BodySchema{ + "foo": { + Attributes: map[string]*schema.AttributeSchema{ + "str": { + Expr: schema.LiteralTypeOnly(cty.String), + IsOptional: true, + }, + }, + }, + }, + DataSources: map[string]*schema.BodySchema{ + "bar": { + Attributes: map[string]*schema.AttributeSchema{ + "num": { + Expr: schema.LiteralTypeOnly(cty.Number), + IsOptional: true, + }, + }, + }, + }, + } + expectedSchema := &ProviderSchema{ + Provider: &schema.BodySchema{ + Detail: "hashicorp/aws 1.2.5", + DocsLink: &schema.DocsLink{ + URL: "https://registry.terraform.io/providers/hashicorp/aws/1.2.5/docs", + Tooltip: "hashicorp/aws Documentation", + }, + }, + Resources: map[string]*schema.BodySchema{ + "foo": { + Detail: "hashicorp/aws 1.2.5", + Attributes: map[string]*schema.AttributeSchema{ + "str": { + Expr: schema.LiteralTypeOnly(cty.String), + IsOptional: true, + }, + }, + }, + }, + DataSources: map[string]*schema.BodySchema{ + "bar": { + Detail: "hashicorp/aws 1.2.5", + Attributes: map[string]*schema.AttributeSchema{ + "num": { + Expr: schema.LiteralTypeOnly(cty.Number), + IsOptional: true, + }, + }, + }, + }, + } + + pAddr := tfaddr.Provider{ + Hostname: tfaddr.DefaultRegistryHost, + Namespace: "hashicorp", + Type: "aws", + } + pv := version.Must(version.NewVersion("1.2.5")) + + ps.SetProviderVersion(pAddr, pv) + + if diff := cmp.Diff(expectedSchema, ps, ctydebug.CmpOptions); diff != "" { + t.Fatalf("unexpected schema: %s", diff) + } +} diff --git a/schema/schema_merge.go b/schema/schema_merge.go index 37286c52..06d16ebc 100644 --- a/schema/schema_merge.go +++ b/schema/schema_merge.go @@ -6,10 +6,8 @@ import ( "github.com/hashicorp/go-version" "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/schema" - tfjson "github.com/hashicorp/terraform-json" "github.com/hashicorp/terraform-registry-address" "github.com/hashicorp/terraform-schema/module" - "github.com/zclconf/go-cty/cty" ) type SchemaMerger struct { @@ -158,145 +156,3 @@ func (pr ProviderReferences) ReferencesOfProvider(addr tfaddr.Provider) []module return refs } - -func convertBodySchemaFromJson(detail string, schemaBlock *tfjson.SchemaBlock) *schema.BodySchema { - if schemaBlock == nil { - s := schema.NewBodySchema() - s.Detail = detail - return s - } - - return &schema.BodySchema{ - Attributes: convertAttributesFromJson(schemaBlock.Attributes), - Blocks: convertBlocksFromJson(schemaBlock.NestedBlocks), - IsDeprecated: schemaBlock.Deprecated, - Detail: detail, - Description: markupContent(schemaBlock.Description, schemaBlock.DescriptionKind), - } -} - -func convertBlocksFromJson(blocks map[string]*tfjson.SchemaBlockType) map[string]*schema.BlockSchema { - cBlocks := make(map[string]*schema.BlockSchema, len(blocks)) - for name, jsonSchema := range blocks { - block := jsonSchema.Block - - blockType := schema.BlockTypeNil - labels := []*schema.LabelSchema{} - - switch jsonSchema.NestingMode { - case tfjson.SchemaNestingModeMap: - labels = []*schema.LabelSchema{ - {Name: "name"}, - } - blockType = schema.BlockTypeMap - case tfjson.SchemaNestingModeList: - blockType = schema.BlockTypeList - case tfjson.SchemaNestingModeSet: - blockType = schema.BlockTypeSet - } - - cBlocks[name] = &schema.BlockSchema{ - Description: markupContent(block.Description, block.DescriptionKind), - Type: blockType, - IsDeprecated: block.Deprecated, - MinItems: jsonSchema.MinItems, - MaxItems: jsonSchema.MaxItems, - Labels: labels, - Body: convertBodySchemaFromJson("", block), - } - } - return cBlocks -} - -func convertAttributesFromJson(attributes map[string]*tfjson.SchemaAttribute) map[string]*schema.AttributeSchema { - cAttrs := make(map[string]*schema.AttributeSchema, len(attributes)) - for name, attr := range attributes { - cAttrs[name] = &schema.AttributeSchema{ - Description: markupContent(attr.Description, attr.DescriptionKind), - IsDeprecated: attr.Deprecated, - IsComputed: attr.Computed, - IsOptional: attr.Optional, - IsRequired: attr.Required, - Expr: exprConstraintsFromAttribute(attr), - } - } - return cAttrs -} - -func exprConstraintsFromAttribute(attr *tfjson.SchemaAttribute) schema.ExprConstraints { - var expr schema.ExprConstraints - if attr.AttributeType != cty.NilType { - expr = schema.LiteralTypeOnly(attr.AttributeType) - } - if attr.AttributeNestedType != nil { - switch attr.AttributeNestedType.NestingMode { - case tfjson.SchemaNestingModeSingle: - return schema.ExprConstraints{ - convertJsonAttributesToObjectExprAttr(attr.AttributeNestedType.Attributes), - } - case tfjson.SchemaNestingModeList: - return schema.ExprConstraints{ - schema.ListExpr{ - Elem: schema.ExprConstraints{ - convertJsonAttributesToObjectExprAttr(attr.AttributeNestedType.Attributes), - }, - MinItems: attr.AttributeNestedType.MinItems, - MaxItems: attr.AttributeNestedType.MaxItems, - }, - } - case tfjson.SchemaNestingModeSet: - return schema.ExprConstraints{ - schema.SetExpr{ - Elem: schema.ExprConstraints{ - convertJsonAttributesToObjectExprAttr(attr.AttributeNestedType.Attributes), - }, - MinItems: attr.AttributeNestedType.MinItems, - MaxItems: attr.AttributeNestedType.MaxItems, - }, - } - case tfjson.SchemaNestingModeMap: - return schema.ExprConstraints{ - schema.MapExpr{ - Elem: schema.ExprConstraints{ - convertJsonAttributesToObjectExprAttr(attr.AttributeNestedType.Attributes), - }, - MinItems: attr.AttributeNestedType.MinItems, - MaxItems: attr.AttributeNestedType.MaxItems, - }, - } - } - } - return expr -} - -func convertJsonAttributesToObjectExprAttr(attrs map[string]*tfjson.SchemaAttribute) schema.ObjectExpr { - attributes := make(schema.ObjectExprAttributes, len(attrs)) - for name, attr := range attrs { - attributes[name] = &schema.AttributeSchema{ - Description: markupContent(attr.Description, attr.DescriptionKind), - IsDeprecated: attr.Deprecated, - IsComputed: attr.Computed, - IsOptional: attr.Optional, - IsRequired: attr.Required, - Expr: exprConstraintsFromAttribute(attr), - } - } - return schema.ObjectExpr{ - Attributes: attributes, - } -} - -func markupContent(value string, kind tfjson.SchemaDescriptionKind) lang.MarkupContent { - if value == "" { - return lang.MarkupContent{} - } - switch kind { - case tfjson.SchemaDescriptionKindMarkdown: - return lang.Markdown(value) - case tfjson.SchemaDescriptionKindPlain: - return lang.PlainText(value) - } - - // backwards compatibility with v0.12 - return lang.PlainText(value) -} diff --git a/schema/schema_merge_test.go b/schema/schema_merge_test.go index ed1c10df..06d70961 100644 --- a/schema/schema_merge_test.go +++ b/schema/schema_merge_test.go @@ -3,22 +3,28 @@ package schema import ( "encoding/json" "errors" + "fmt" "io/ioutil" + "path/filepath" "testing" "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/go-version" "github.com/hashicorp/hcl-lang/schema" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" tfjson "github.com/hashicorp/terraform-json" + "github.com/hashicorp/terraform-registry-address" + "github.com/hashicorp/terraform-schema/earlydecoder" + "github.com/hashicorp/terraform-schema/module" + "github.com/zclconf/go-cty-debug/ctydebug" "github.com/zclconf/go-cty/cty" ) -func TestMergeWithJsonProviderSchemas_noCoreSchema(t *testing.T) { +func TestSchemaMerger_SchemaForModule_noCoreSchema(t *testing.T) { sm := NewSchemaMerger(nil) - _, err := sm.MergeWithJsonProviderSchemas(nil) + _, err := sm.SchemaForModule(nil) if err == nil { t.Fatal("expected error for nil core schema") } @@ -28,7 +34,7 @@ func TestMergeWithJsonProviderSchemas_noCoreSchema(t *testing.T) { } } -func TestMergeWithJsonProviderSchemas_noProviderSchema(t *testing.T) { +func TestSchemaMerger_SchemaForModule_noProviderSchema(t *testing.T) { testCoreSchema := &schema.BodySchema{ Blocks: map[string]*schema.BlockSchema{ "provider": { @@ -67,99 +73,80 @@ func TestMergeWithJsonProviderSchemas_noProviderSchema(t *testing.T) { } sm := NewSchemaMerger(testCoreSchema) - _, err := sm.MergeWithJsonProviderSchemas(nil) + _, err := sm.SchemaForModule(&module.Meta{}) if err != nil { t.Fatal(err) } } func TestMergeWithJsonProviderSchemas_v012(t *testing.T) { - b, err := ioutil.ReadFile("testdata/test-config-0.12.tf") + sm := NewSchemaMerger(testCoreSchema()) + sr := testSchemaReader(t, filepath.Join("testdata", "provider-schemas-0.12.json"), true) + sm.SetSchemaReader(sr) + meta := testModuleMeta(t, "testdata/test-config-0.12.tf") + mergedSchema, err := sm.SchemaForModule(meta) if err != nil { t.Fatal(err) } - f, diags := hclsyntax.ParseConfig(b, "test.tf", hcl.InitialPos) - if len(diags) > 0 { - t.Fatal(diags) - } - ps := &tfjson.ProviderSchemas{} - b, err = ioutil.ReadFile("testdata/provider-schemas-0.12.json") - if err != nil { - t.Fatal(err) + if diff := cmp.Diff(expectedMergedSchema_v012, mergedSchema, ctydebug.CmpOptions); diff != "" { + t.Fatalf("schema differs: %s", diff) } - err = json.Unmarshal(b, ps) +} + +func TestMergeWithJsonProviderSchemas_v013(t *testing.T) { + sm := NewSchemaMerger(testCoreSchema()) + sr := testSchemaReader(t, filepath.Join("testdata", "provider-schemas-0.13.json"), false) + sm.SetSchemaReader(sr) + meta := testModuleMeta(t, "testdata/test-config-0.13.tf") + mergedSchema, err := sm.SchemaForModule(meta) if err != nil { t.Fatal(err) } - testCoreSchema := &schema.BodySchema{ - Blocks: map[string]*schema.BlockSchema{ - "provider": { - Labels: []*schema.LabelSchema{ - {Name: "name"}, - }, - Body: &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "alias": {Expr: schema.LiteralTypeOnly(cty.String), IsOptional: true}, - }, - }, - }, - "resource": { - Labels: []*schema.LabelSchema{ - {Name: "type"}, - {Name: "name"}, - }, - Body: &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "count": {Expr: schema.LiteralTypeOnly(cty.Number), IsOptional: true}, - }, - }, - }, - "data": { - Labels: []*schema.LabelSchema{ - {Name: "type"}, - {Name: "name"}, - }, - Body: &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "count": {Expr: schema.LiteralTypeOnly(cty.Number), IsOptional: true}, - }, - }, - }, - }, + if diff := cmp.Diff(expectedMergedSchema_v013, mergedSchema, ctydebug.CmpOptions); diff != "" { + t.Fatalf("schema differs: %s", diff) } - sm := NewSchemaMerger(testCoreSchema) - sm.SetParsedFiles(map[string]*hcl.File{ - "test.tf": f, - }) +} - mergedSchema, err := sm.MergeWithJsonProviderSchemas(ps) +func TestMergeWithJsonProviderSchemas_v015(t *testing.T) { + sm := NewSchemaMerger(testCoreSchema()) + sr := testSchemaReader(t, filepath.Join("testdata", "provider-schemas-0.15.json"), false) + sm.SetSchemaReader(sr) + meta := testModuleMeta(t, "testdata/test-config-0.15.tf") + mergedSchema, err := sm.SchemaForModule(meta) if err != nil { t.Fatal(err) } - opts := cmp.Options{ - cmpopts.IgnoreUnexported(cty.Type{}), - } - - if diff := cmp.Diff(expectedMergedSchema_v012, mergedSchema, opts); diff != "" { + if diff := cmp.Diff(expectedMergedSchema_v015, mergedSchema, ctydebug.CmpOptions); diff != "" { t.Fatalf("schema differs: %s", diff) } } -func TestMergeWithJsonProviderSchemas_v013(t *testing.T) { - b, err := ioutil.ReadFile("testdata/test-config-0.13.tf") +func testModuleMeta(t *testing.T, path string) *module.Meta { + b, err := ioutil.ReadFile(path) if err != nil { t.Fatal(err) } - f, diags := hclsyntax.ParseConfig(b, "test.tf", hcl.InitialPos) + filename := filepath.Base(path) + + f, diags := hclsyntax.ParseConfig(b, filename, hcl.InitialPos) if len(diags) > 0 { t.Fatal(diags) } + meta, diags := earlydecoder.LoadModule("testdata", map[string]*hcl.File{ + filename: f, + }) + if diags.HasErrors() { + t.Fatal(diags) + } + return meta +} +func testSchemaReader(t *testing.T, jsonPath string, legacyStyle bool) SchemaReader { ps := &tfjson.ProviderSchemas{} - b, err = ioutil.ReadFile("testdata/provider-schemas-0.13.json") + b, err := ioutil.ReadFile(jsonPath) if err != nil { t.Fatal(err) } @@ -168,82 +155,52 @@ func TestMergeWithJsonProviderSchemas_v013(t *testing.T) { t.Fatal(err) } - testCoreSchema := &schema.BodySchema{ - Blocks: map[string]*schema.BlockSchema{ - "provider": { - Labels: []*schema.LabelSchema{ - {Name: "name"}, - }, - Body: &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "alias": {Expr: schema.LiteralTypeOnly(cty.String), IsOptional: true}, - }, - }, - }, - "resource": { - Labels: []*schema.LabelSchema{ - {Name: "type"}, - {Name: "name"}, - }, - Body: &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "count": {Expr: schema.LiteralTypeOnly(cty.Number), IsOptional: true}, - }, - }, - }, - "data": { - Labels: []*schema.LabelSchema{ - {Name: "type"}, - {Name: "name"}, - }, - Body: &schema.BodySchema{ - Attributes: map[string]*schema.AttributeSchema{ - "count": {Expr: schema.LiteralTypeOnly(cty.Number), IsOptional: true}, - }, - }, + if legacyStyle { + return &testJsonSchemaReader{ + ps: ps, + useTypeOnly: true, + migrations: map[tfaddr.Provider]tfaddr.Provider{ + tfaddr.NewLegacyProvider("null"): tfaddr.NewDefaultProvider("null"), + tfaddr.NewLegacyProvider("random"): tfaddr.NewDefaultProvider("random"), + tfaddr.NewLegacyProvider("terraform"): tfaddr.NewBuiltInProvider("terraform"), }, + } + } + return &testJsonSchemaReader{ + ps: ps, + migrations: map[tfaddr.Provider]tfaddr.Provider{ + // the builtin provider doesn't have entry in required_providers + tfaddr.NewLegacyProvider("terraform"): tfaddr.NewBuiltInProvider("terraform"), }, } - sm := NewSchemaMerger(testCoreSchema) - sm.SetParsedFiles(map[string]*hcl.File{ - "test.tf": f, - }) +} - mergedSchema, err := sm.MergeWithJsonProviderSchemas(ps) - if err != nil { - t.Fatal(err) - } +type testJsonSchemaReader struct { + ps *tfjson.ProviderSchemas + useTypeOnly bool + migrations map[tfaddr.Provider]tfaddr.Provider +} - opts := cmp.Options{ - cmpopts.IgnoreUnexported(cty.Type{}), +func (r *testJsonSchemaReader) ProviderSchema(_ string, pAddr tfaddr.Provider, _ version.Constraints) (*ProviderSchema, error) { + if newAddr, ok := r.migrations[pAddr]; ok { + pAddr = newAddr } - if diff := cmp.Diff(expectedMergedSchema_v013, mergedSchema, opts); diff != "" { - t.Fatalf("schema differs: %s", diff) + addr := pAddr.String() + if r.useTypeOnly { + addr = pAddr.Type } -} -func TestMergeWithJsonProviderSchemas_v015(t *testing.T) { - b, err := ioutil.ReadFile("testdata/test-config-0.15.tf") - if err != nil { - t.Fatal(err) - } - f, diags := hclsyntax.ParseConfig(b, "test.tf", hcl.InitialPos) - if len(diags) > 0 { - t.Fatal(diags) + jsonSchema, ok := r.ps.Schemas[addr] + if !ok { + return nil, fmt.Errorf("%s: schema not found", pAddr.String()) } - ps := &tfjson.ProviderSchemas{} - b, err = ioutil.ReadFile("testdata/provider-schemas-0.15.json") - if err != nil { - t.Fatal(err) - } - err = json.Unmarshal(b, ps) - if err != nil { - t.Fatal(err) - } + return ProviderSchemaFromJson(jsonSchema, pAddr), nil +} - testCoreSchema := &schema.BodySchema{ +func testCoreSchema() *schema.BodySchema { + return &schema.BodySchema{ Blocks: map[string]*schema.BlockSchema{ "provider": { Labels: []*schema.LabelSchema{ @@ -279,21 +236,4 @@ func TestMergeWithJsonProviderSchemas_v015(t *testing.T) { }, }, } - sm := NewSchemaMerger(testCoreSchema) - sm.SetParsedFiles(map[string]*hcl.File{ - "test.tf": f, - }) - - mergedSchema, err := sm.MergeWithJsonProviderSchemas(ps) - if err != nil { - t.Fatal(err) - } - - opts := cmp.Options{ - cmpopts.IgnoreUnexported(cty.Type{}), - } - - if diff := cmp.Diff(expectedMergedSchema_v015, mergedSchema, opts); diff != "" { - t.Fatalf("schema differs: %s", diff) - } } diff --git a/schema/schema_merge_v012_test.go b/schema/schema_merge_v012_test.go index 80bec82e..3d0c11b4 100644 --- a/schema/schema_merge_v012_test.go +++ b/schema/schema_merge_v012_test.go @@ -36,11 +36,6 @@ var expectedMergedSchema_v012 = &schema.BodySchema{ Tooltip: "hashicorp/random Documentation", }, }, - `{"labels":[{"index":0,"value":"terraform"}]}`: { - Blocks: map[string]*schema.BlockSchema{}, - Attributes: map[string]*schema.AttributeSchema{}, - Detail: "(builtin)", - }, }, }, "resource": { diff --git a/schema/schema_merge_v013_test.go b/schema/schema_merge_v013_test.go index 75248fad..7dffddca 100644 --- a/schema/schema_merge_v013_test.go +++ b/schema/schema_merge_v013_test.go @@ -62,11 +62,6 @@ var expectedMergedSchema_v013 = &schema.BodySchema{ Blocks: map[string]*schema.BlockSchema{}, Attributes: map[string]*schema.AttributeSchema{}, }, - `{"labels":[{"index":0,"value":"terraform"}]}`: { - Detail: "(builtin)", - Blocks: map[string]*schema.BlockSchema{}, - Attributes: map[string]*schema.AttributeSchema{}, - }, }, }, "resource": { diff --git a/schema/schema_merge_v015_test.go b/schema/schema_merge_v015_test.go index eb124839..e2ed849a 100644 --- a/schema/schema_merge_v015_test.go +++ b/schema/schema_merge_v015_test.go @@ -1,7 +1,6 @@ package schema import ( - //"github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/schema" "github.com/zclconf/go-cty/cty" ) diff --git a/schema/testdata/provider-schemas-0.15.json b/schema/testdata/provider-schemas-0.15.json index b76aac3e..ac409b28 100644 --- a/schema/testdata/provider-schemas-0.15.json +++ b/schema/testdata/provider-schemas-0.15.json @@ -2,6 +2,13 @@ "format_version": "0.1", "provider_schemas": { "registry.terraform.io/hashicorp/hashicup": { + "provider": { + "version": 0, + "block": { + "attributes": {}, + "description_kind": "plain" + } + }, "data_source_schemas": { "hashicup_test": { "version": 0,