diff --git a/schema/module_schema.go b/schema/module_schema.go index 59f9c70a..5e827962 100644 --- a/schema/module_schema.go +++ b/schema/module_schema.go @@ -1,7 +1,9 @@ package schema import ( + "fmt" "sort" + "strings" "github.com/hashicorp/hcl-lang/lang" "github.com/hashicorp/hcl-lang/schema" @@ -11,7 +13,7 @@ import ( "github.com/zclconf/go-cty/cty" ) -func schemaForDependentModuleBlock(localName string, modMeta *module.Meta) (*schema.BodySchema, error) { +func schemaForDependentModuleBlock(module module.ModuleCall, modMeta *module.Meta) (*schema.BodySchema, error) { attributes := make(map[string]*schema.AttributeSchema, 0) for name, modVar := range modMeta.Variables { @@ -40,7 +42,7 @@ func schemaForDependentModuleBlock(localName string, modMeta *module.Meta) (*sch Attributes: attributes, } - if localName == "" { + if module.LocalName == "" { // avoid creating output refs if we don't have reference name return bodySchema, nil } @@ -52,7 +54,7 @@ func schemaForDependentModuleBlock(localName string, modMeta *module.Meta) (*sch for name, output := range modMeta.Outputs { addr := lang.Address{ lang.RootStep{Name: "module"}, - lang.AttrStep{Name: localName}, + lang.AttrStep{Name: module.LocalName}, lang.AttrStep{Name: name}, } @@ -82,7 +84,7 @@ func schemaForDependentModuleBlock(localName string, modMeta *module.Meta) (*sch addr := lang.Address{ lang.RootStep{Name: "module"}, - lang.AttrStep{Name: localName}, + lang.AttrStep{Name: module.LocalName}, } bodySchema.TargetableAs = append(bodySchema.TargetableAs, &schema.Targetable{ Address: addr, @@ -113,6 +115,17 @@ func schemaForDependentModuleBlock(localName string, modMeta *module.Meta) (*sch } } + if strings.HasPrefix(module.SourceAddr, "registry.terraform.io/") { + shortName := strings.TrimPrefix(module.SourceAddr, "registry.terraform.io/") + version := module.Version + if version == "" { + version = "latest" + } + bodySchema.DocsLink = &schema.DocsLink{ + URL: fmt.Sprintf(`https://registry.terraform.io/modules/%s/%s`, shortName, version), + } + } + return bodySchema, nil } diff --git a/schema/module_schema_test.go b/schema/module_schema_test.go index 1d3f1311..ffd19ff8 100644 --- a/schema/module_schema_test.go +++ b/schema/module_schema_test.go @@ -15,7 +15,10 @@ import ( func TestSchemaForDependentModuleBlock_emptyMeta(t *testing.T) { meta := &module.Meta{} - depSchema, err := schemaForDependentModuleBlock("refname", meta) + module := module.ModuleCall{ + LocalName: "refname", + } + depSchema, err := schemaForDependentModuleBlock(module, meta) if err != nil { t.Fatal(err) } @@ -63,7 +66,10 @@ func TestSchemaForDependentModuleBlock_basic(t *testing.T) { }, }, } - depSchema, err := schemaForDependentModuleBlock("refname", meta) + module := module.ModuleCall{ + LocalName: "refname", + } + depSchema, err := schemaForDependentModuleBlock(module, meta) if err != nil { t.Fatal(err) } @@ -263,9 +269,123 @@ func TestSchemaForDependentModuleBlock_Target(t *testing.T) { }, }, } + module := module.ModuleCall{ + LocalName: "refname", + } + + for _, tc := range testCases { + depSchema, err := schemaForDependentModuleBlock(module, tc.meta) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(tc.expectedSchema, depSchema, ctydebug.CmpOptions); diff != "" { + t.Fatalf("schema mismatch: %s", diff) + } + } +} + +func TestSchemaForDependentModuleBlock_DocsLink(t *testing.T) { + type testCase struct { + name string + meta *module.Meta + module module.ModuleCall + expectedSchema *schema.BodySchema + } + + testCases := []testCase{ + { + "local module", + &module.Meta{ + Path: "./local", + Variables: map[string]module.Variable{}, + Outputs: map[string]module.Output{}, + Filenames: nil, + }, + module.ModuleCall{ + LocalName: "refname", + SourceAddr: "./local", + }, + &schema.BodySchema{ + Attributes: map[string]*schema.AttributeSchema{}, + TargetableAs: []*schema.Targetable{ + { + Address: lang.Address{ + lang.RootStep{Name: "module"}, + lang.AttrStep{Name: "refname"}, + }, + ScopeId: refscope.ModuleScope, + AsType: cty.Object(map[string]cty.Type{}), + NestedTargetables: []*schema.Targetable{}, + }, + }, + Targets: nil, + }, + }, + { + "remote module", + &module.Meta{ + Path: "registry.terraform.io/terraform-aws-modules/vpc/aws", + Variables: map[string]module.Variable{}, + Outputs: map[string]module.Output{}, + Filenames: nil, + }, + module.ModuleCall{ + LocalName: "vpc", + SourceAddr: "registry.terraform.io/terraform-aws-modules/vpc/aws", + }, + &schema.BodySchema{ + Attributes: map[string]*schema.AttributeSchema{}, + TargetableAs: []*schema.Targetable{ + { + Address: lang.Address{ + lang.RootStep{Name: "module"}, + lang.AttrStep{Name: "vpc"}, + }, + ScopeId: refscope.ModuleScope, + AsType: cty.Object(map[string]cty.Type{}), + NestedTargetables: []*schema.Targetable{}, + }, + }, + DocsLink: &schema.DocsLink{ + URL: "https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest", + }, + }, + }, + { + "remote module with version", + &module.Meta{ + Path: "registry.terraform.io/terraform-aws-modules/vpc/aws", + Variables: map[string]module.Variable{}, + Outputs: map[string]module.Output{}, + Filenames: nil, + }, + module.ModuleCall{ + LocalName: "vpc", + SourceAddr: "registry.terraform.io/terraform-aws-modules/vpc/aws", + Version: "1.33.7", + }, + &schema.BodySchema{ + Attributes: map[string]*schema.AttributeSchema{}, + TargetableAs: []*schema.Targetable{ + { + Address: lang.Address{ + lang.RootStep{Name: "module"}, + lang.AttrStep{Name: "vpc"}, + }, + ScopeId: refscope.ModuleScope, + AsType: cty.Object(map[string]cty.Type{}), + NestedTargetables: []*schema.Targetable{}, + }, + }, + DocsLink: &schema.DocsLink{ + URL: "https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/1.33.7", + }, + }, + }, + } for _, tc := range testCases { - depSchema, err := schemaForDependentModuleBlock("refname", tc.meta) + depSchema, err := schemaForDependentModuleBlock(tc.module, tc.meta) if err != nil { t.Fatal(err) } diff --git a/schema/schema_merge.go b/schema/schema_merge.go index d03c36a1..c501c2d9 100644 --- a/schema/schema_merge.go +++ b/schema/schema_merge.go @@ -207,7 +207,7 @@ func (m *SchemaMerger) SchemaForModule(meta *module.Meta) (*schema.BodySchema, e }, } - depSchema, err := schemaForDependentModuleBlock(module.LocalName, modMeta) + depSchema, err := schemaForDependentModuleBlock(module, modMeta) if err == nil { mergedSchema.Blocks["module"].DependentBody[schema.NewSchemaKey(depKeys)] = depSchema } diff --git a/schema/schema_merge_v015_test.go b/schema/schema_merge_v015_test.go index 6c46a46b..3963053a 100644 --- a/schema/schema_merge_v015_test.go +++ b/schema/schema_merge_v015_test.go @@ -660,6 +660,7 @@ var expectedRemoteModuleSchema = &schema.BlockSchema{ }, }, }, + DocsLink: &schema.DocsLink{URL: "https://registry.terraform.io/modules/namespace/foobar/latest"}, }, schema.NewSchemaKey(schema.DependencyKeys{ Attributes: []schema.AttributeDependent{ @@ -705,6 +706,7 @@ var expectedRemoteModuleSchema = &schema.BlockSchema{ }, }, }, + DocsLink: &schema.DocsLink{URL: "https://registry.terraform.io/modules/namespace/foobar/latest"}, }, }, }