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

schema: Account for short (Registry) module source address #94

Merged
merged 1 commit into from
Feb 24, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
166 changes: 98 additions & 68 deletions schema/schema_merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (m *SchemaMerger) SchemaForModule(meta *module.Meta) (*schema.BodySchema, e
return nil, coreSchemaRequiredErr{}
}

if meta == nil || m.schemaReader == nil {
if meta == nil {
Copy link
Member Author

Choose a reason for hiding this comment

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

This just makes testing a bit easier as less tests require provider schema.

return m.coreSchema, nil
}

Expand All @@ -72,95 +72,97 @@ func (m *SchemaMerger) SchemaForModule(meta *module.Meta) (*schema.BodySchema, e

providerRefs := ProviderReferences(meta.ProviderReferences)

for pAddr, pVersionCons := range meta.ProviderRequirements {
pSchema, err := m.schemaReader.ProviderSchema(meta.Path, pAddr, pVersionCons)
if err != nil {
continue
}

refs := providerRefs.ReferencesOfProvider(pAddr)
for _, localRef := range refs {
if pSchema.Provider != nil {
mergedSchema.Blocks["provider"].DependentBody[schema.NewSchemaKey(schema.DependencyKeys{
Labels: []schema.LabelDependent{
{Index: 0, Value: localRef.LocalName},
},
})] = pSchema.Provider
}

providerAddr := lang.Address{
lang.RootStep{Name: localRef.LocalName},
}
if localRef.Alias != "" {
providerAddr = append(providerAddr, lang.AttrStep{Name: localRef.Alias})
if m.schemaReader != nil {
for pAddr, pVersionCons := range meta.ProviderRequirements {
pSchema, err := m.schemaReader.ProviderSchema(meta.Path, pAddr, pVersionCons)
if err != nil {
continue
}

for rName, rSchema := range pSchema.Resources {
depKeys := schema.DependencyKeys{
Labels: []schema.LabelDependent{
{Index: 0, Value: rName},
},
Attributes: []schema.AttributeDependent{
{
Name: "provider",
Expr: schema.ExpressionValue{
Address: providerAddr,
},
refs := providerRefs.ReferencesOfProvider(pAddr)
for _, localRef := range refs {
if pSchema.Provider != nil {
mergedSchema.Blocks["provider"].DependentBody[schema.NewSchemaKey(schema.DependencyKeys{
Labels: []schema.LabelDependent{
{Index: 0, Value: localRef.LocalName},
},
},
})] = pSchema.Provider
}

providerAddr := lang.Address{
lang.RootStep{Name: localRef.LocalName},
}
if localRef.Alias != "" {
providerAddr = append(providerAddr, lang.AttrStep{Name: localRef.Alias})
}
mergedSchema.Blocks["resource"].DependentBody[schema.NewSchemaKey(depKeys)] = rSchema

// No explicit association is required
// if the resource prefix matches provider name
if strings.HasPrefix(rName, localRef.LocalName+"_") {
for rName, rSchema := range pSchema.Resources {
depKeys := schema.DependencyKeys{
Labels: []schema.LabelDependent{
{Index: 0, Value: rName},
},
Attributes: []schema.AttributeDependent{
{
Name: "provider",
Expr: schema.ExpressionValue{
Address: providerAddr,
},
},
},
}
mergedSchema.Blocks["resource"].DependentBody[schema.NewSchemaKey(depKeys)] = rSchema
}
}

for dsName, dsSchema := range pSchema.DataSources {
depKeys := schema.DependencyKeys{
Labels: []schema.LabelDependent{
{Index: 0, Value: dsName},
},
Attributes: []schema.AttributeDependent{
{
Name: "provider",
Expr: schema.ExpressionValue{
Address: providerAddr,
// No explicit association is required
// if the resource prefix matches provider name
if strings.HasPrefix(rName, localRef.LocalName+"_") {
depKeys := schema.DependencyKeys{
Labels: []schema.LabelDependent{
{Index: 0, Value: rName},
},
},
},
}

// Add backend-related core bits of schema
if isRemoteStateDataSource(pAddr, dsName) {
dsSchema.Attributes["backend"].IsDepKey = true
dsSchema.Attributes["backend"].Expr = backends.BackendTypesAsExprConstraints(m.terraformVersion)

delete(dsSchema.Attributes, "config")
depBodies := m.dependentBodyForRemoteStateDataSource(providerAddr, localRef)
for key, depBody := range depBodies {
mergedSchema.Blocks["data"].DependentBody[key] = depBody
}
mergedSchema.Blocks["resource"].DependentBody[schema.NewSchemaKey(depKeys)] = rSchema
}
}

mergedSchema.Blocks["data"].DependentBody[schema.NewSchemaKey(depKeys)] = dsSchema

// No explicit association is required
// if the resource prefix matches provider name
if strings.HasPrefix(dsName, localRef.LocalName+"_") {
for dsName, dsSchema := range pSchema.DataSources {
depKeys := schema.DependencyKeys{
Labels: []schema.LabelDependent{
{Index: 0, Value: dsName},
},
Attributes: []schema.AttributeDependent{
{
Name: "provider",
Expr: schema.ExpressionValue{
Address: providerAddr,
},
},
},
}

// Add backend-related core bits of schema
if isRemoteStateDataSource(pAddr, dsName) {
dsSchema.Attributes["backend"].IsDepKey = true
dsSchema.Attributes["backend"].Expr = backends.BackendTypesAsExprConstraints(m.terraformVersion)

delete(dsSchema.Attributes, "config")
depBodies := m.dependentBodyForRemoteStateDataSource(providerAddr, localRef)
for key, depBody := range depBodies {
mergedSchema.Blocks["data"].DependentBody[key] = depBody
}
}

mergedSchema.Blocks["data"].DependentBody[schema.NewSchemaKey(depKeys)] = dsSchema

// No explicit association is required
// if the resource prefix matches provider name
if strings.HasPrefix(dsName, localRef.LocalName+"_") {
depKeys := schema.DependencyKeys{
Labels: []schema.LabelDependent{
{Index: 0, Value: dsName},
},
}
mergedSchema.Blocks["data"].DependentBody[schema.NewSchemaKey(depKeys)] = dsSchema
}
}
}
}
Expand All @@ -176,17 +178,20 @@ func (m *SchemaMerger) SchemaForModule(meta *module.Meta) (*schema.BodySchema, e
}
mergedSchema.Blocks["variable"].DependentBody = variableDependentBody(meta.Variables)
}

if m.moduleReader != nil {
reader := m.moduleReader
modules, err := reader.ModuleCalls(meta.Path)
if err != nil {
return mergedSchema, nil
}

for _, module := range modules {
modMeta, err := reader.ModuleMeta(module.Path)
if err != nil {
continue
}

depKeys := schema.DependencyKeys{
// Fetching based only on the source can cause conflicts for multiple versions of the same module
// specially if they have different versions or the source of those modules have been modified
Expand All @@ -205,6 +210,31 @@ func (m *SchemaMerger) SchemaForModule(meta *module.Meta) (*schema.BodySchema, e
if err == nil {
mergedSchema.Blocks["module"].DependentBody[schema.NewSchemaKey(depKeys)] = depSchema
}

// There's likely more edge cases with how source address can be represented in config
// vs in module manifest, but for now we at least account for the common case of TF Registry
if strings.HasPrefix(module.SourceAddr, "registry.terraform.io/") {
dbanck marked this conversation as resolved.
Show resolved Hide resolved
shortName := strings.TrimPrefix(module.SourceAddr, "registry.terraform.io/")

depKeys := schema.DependencyKeys{
// Fetching based only on the source can cause conflicts for multiple versions of the same module
// specially if they have different versions or the source of those modules have been modified
// inside the .terraform folder. This is a compromise that we made in this moment since it would impact only auto completion
Attributes: []schema.AttributeDependent{
{
Name: "source",
Expr: schema.ExpressionValue{
Static: cty.StringVal(shortName),
},
},
},
}

depSchema, err := schemaForDependentModuleBlock(module.LocalName, modMeta)
Copy link
Member

Choose a reason for hiding this comment

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

Can we remove the schemaForDependentModuleBlock call here as we already fetched depSchema in line 209?

if err == nil {
mergedSchema.Blocks["module"].DependentBody[schema.NewSchemaKey(depKeys)] = depSchema
}
}
}
}
return mergedSchema, nil
Expand Down
50 changes: 50 additions & 0 deletions schema/schema_merge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,24 @@ func TestMergeWithJsonProviderSchemasAndModuleVariables_v015(t *testing.T) {
}
}

func TestMergeWithJsonProviderSchemasAndModuleVariables_registryModule(t *testing.T) {
sm := NewSchemaMerger(testCoreSchema())
sm.SetModuleReader(testRegistryModuleReader())
sm.SetTerraformVersion(v0_15_0)
meta := testModuleMeta(t, "testdata/test-config-remote-module.tf")
t.Logf("meta: %#v", meta)
mergedSchema, err := sm.SchemaForModule(meta)
if err != nil {
t.Fatal(err)
}

moduleSchema := mergedSchema.Blocks["module"]

if diff := cmp.Diff(expectedRemoteModuleSchema, moduleSchema, ctydebug.CmpOptions); diff != "" {
t.Fatalf("schema differs: %s", diff)
}
}

func testModuleMeta(t *testing.T, path string) *module.Meta {
b, err := ioutil.ReadFile(path)
if err != nil {
Expand Down Expand Up @@ -368,6 +386,38 @@ func (m *testModuleReaderStruct) ModuleMeta(modPath string) (*module.Meta, error
return nil, fmt.Errorf("invalid source")
}

func testRegistryModuleReader() ModuleReader {
return &testRegistryModuleReaderStruct{}
}

type testRegistryModuleReaderStruct struct {
}

func (m *testRegistryModuleReaderStruct) ModuleCalls(modPath string) ([]module.ModuleCall, error) {
return []module.ModuleCall{
{
LocalName: "remote-example",
SourceAddr: "registry.terraform.io/namespace/foobar",
Path: ".terraform/modules/remote-example",
},
}, nil
}

func (m *testRegistryModuleReaderStruct) ModuleMeta(modPath string) (*module.Meta, error) {
if modPath == ".terraform/modules/remote-example" {
return &module.Meta{
Path: ".terraform/modules/remote-example",
Variables: map[string]module.Variable{
"test": {
Type: cty.String,
Description: "test var",
},
},
}, nil
}
return nil, fmt.Errorf("invalid source")
}

func (r *testJsonSchemaReader) ProviderSchema(_ string, pAddr tfaddr.Provider, _ version.Constraints) (*ProviderSchema, error) {
if newAddr, ok := r.migrations[pAddr]; ok {
pAddr = newAddr
Expand Down
Loading