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

feat: Report usage of write-only attributes for public providers #1926

Merged
merged 4 commits into from
Jan 17, 2025
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
6 changes: 6 additions & 0 deletions .changes/unreleased/ENHANCEMENTS-20250117-164515.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: ENHANCEMENTS
body: Report usage of write-only attributes for public providers
time: 2025-01-17T16:45:15.722924+01:00
custom:
Issue: "1926"
Repository: terraform-ls
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ require (
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/hc-install v0.9.1
github.com/hashicorp/hcl-lang v0.0.0-20241209140757-4f7c1c9bbc32
github.com/hashicorp/hcl-lang v0.0.0-20250117153936-66cdc97e9d3b
github.com/hashicorp/hcl/v2 v2.23.0
github.com/hashicorp/terraform-exec v0.21.0
github.com/hashicorp/terraform-json v0.24.0
github.com/hashicorp/terraform-registry-address v0.2.4
github.com/hashicorp/terraform-schema v0.0.0-20241212141216-b4693e6bc465
github.com/hashicorp/terraform-schema v0.0.0-20250117153811-3c4991466f2c
github.com/mcuadros/go-defaults v1.2.0
github.com/mh-cbon/go-fmt-fail v0.0.0-20160815164508-67765b3fbcb5
github.com/mitchellh/cli v1.1.5
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ github.com/hashicorp/hc-install v0.9.1 h1:gkqTfE3vVbafGQo6VZXcy2v5yoz2bE0+nhZXru
github.com/hashicorp/hc-install v0.9.1/go.mod h1:pWWvN/IrfeBK4XPeXXYkL6EjMufHkCK5DvwxeLKuBf0=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl-lang v0.0.0-20241209140757-4f7c1c9bbc32 h1:7vZlQmXm2ypWJyRBPeX1Mson/dsReRDw7yIoLQGL/8w=
github.com/hashicorp/hcl-lang v0.0.0-20241209140757-4f7c1c9bbc32/go.mod h1:IZQIEGz+2WgWElRh8Tkc8gxT9AzPXMrRBjn2+iBkqdc=
github.com/hashicorp/hcl-lang v0.0.0-20250117153936-66cdc97e9d3b h1:JWLbh10Hji/SYrBGwaWmvmqvbbOxQzuFZ0CplYCwCM4=
github.com/hashicorp/hcl-lang v0.0.0-20250117153936-66cdc97e9d3b/go.mod h1:7aFvdIfHocBadjQ6j5RbxV0rSEasCPj0RTj/ujGCmi8=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ=
Expand All @@ -237,8 +237,8 @@ github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4
github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow=
github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA=
github.com/hashicorp/terraform-registry-address v0.2.4/go.mod h1:tUNYTVyCtU4OIGXXMDp7WNcJ+0W1B4nmstVDgHMjfAU=
github.com/hashicorp/terraform-schema v0.0.0-20241212141216-b4693e6bc465 h1:W1KHI7/MaoHT7wKFLj6eqDX0rQYoHky/TqJUPOj9s1o=
github.com/hashicorp/terraform-schema v0.0.0-20241212141216-b4693e6bc465/go.mod h1:3vDqHlpaMuTeBXSC4LWDM/m2QdEe9DmC90IgyuhdgZw=
github.com/hashicorp/terraform-schema v0.0.0-20250117153811-3c4991466f2c h1:g/Y0BUI5Gk1hgMWcI5PpeXtvvmzvQruW6az0yPhFFKk=
github.com/hashicorp/terraform-schema v0.0.0-20250117153811-3c4991466f2c/go.mod h1:+fQEDxf+c6PnG7/3ZF26K69zWLnIp/uTmsMffCsuw6o=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
github.com/hexops/autogold v1.3.1 h1:YgxF9OHWbEIUjhDbpnLhgVsjUDsiHDTyDfy2lrfdlzo=
Expand Down
14 changes: 14 additions & 0 deletions internal/features/modules/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,20 @@ func (f *ModulesFeature) decodeModule(ctx context.Context, dir document.DirHandl
}
}

woAttributesId, err := f.stateStore.JobStore.EnqueueJob(ctx, job.Job{
Dir: dir,
Func: func(ctx context.Context) error {
return jobs.DecodeWriteOnlyAttributes(ctx, f.Store, f.rootFeature, path)
},
Type: op.OpTypeDecodeWriteOnlyAttributes.String(),
DependsOn: append(modCalls, eSchemaId),
IgnoreState: ignoreState,
})
if err != nil {
return deferIds, err
}
deferIds = append(deferIds, woAttributesId)

return deferIds, nil
},
})
Expand Down
101 changes: 101 additions & 0 deletions internal/features/modules/jobs/write_only_attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package jobs

import (
"context"

"github.com/hashicorp/hcl-lang/decoder"
"github.com/hashicorp/hcl-lang/lang"
idecoder "github.com/hashicorp/terraform-ls/internal/decoder"
"github.com/hashicorp/terraform-ls/internal/document"
fdecoder "github.com/hashicorp/terraform-ls/internal/features/modules/decoder"
"github.com/hashicorp/terraform-ls/internal/features/modules/state"
"github.com/hashicorp/terraform-ls/internal/job"
ilsp "github.com/hashicorp/terraform-ls/internal/lsp"
op "github.com/hashicorp/terraform-ls/internal/terraform/module/operation"
tfaddr "github.com/hashicorp/terraform-registry-address"
tfschema "github.com/hashicorp/terraform-schema/schema"
)

// DecodeWriteOnlyAttributes collects usages of write only attributes,
// using previously parsed AST (via [ParseModuleConfiguration]),
// core schema of appropriate version (as obtained via [GetTerraformVersion])
// and provider schemas ([PreloadEmbeddedSchema] or [ObtainSchema]).
func DecodeWriteOnlyAttributes(ctx context.Context, modStore *state.ModuleStore, rootFeature fdecoder.RootReader, modPath string) error {
mod, err := modStore.ModuleRecordByPath(modPath)
if err != nil {
return err
}

// TODO: Avoid collection if upstream jobs reported no changes

// Avoid collection if it is already in progress or already done
if mod.WriteOnlyAttributesState != op.OpStateUnknown && !job.IgnoreState(ctx) {
return job.StateNotChangedErr{Dir: document.DirHandleFromPath(modPath)}
}

err = modStore.SetWriteOnlyAttributesState(modPath, op.OpStateLoading)
if err != nil {
return err
}

d := decoder.NewDecoder(&fdecoder.PathReader{
StateReader: modStore,
RootReader: rootFeature,
})
d.SetContext(idecoder.DecoderContext(ctx))

pd, err := d.Path(lang.Path{
Path: modPath,
LanguageID: ilsp.Terraform.String(),
})
if err != nil {
return err
}

// input list of write only attributes
woAttrs, rErr := pd.CollectWriteOnlyAttributes()

if rErr != nil {
return rErr
}

findProviderAddr := func(resourceName string) *tfaddr.Provider {
for localRef, addr := range mod.Meta.ProviderReferences {
if tfschema.TypeBelongsToProvider(resourceName, localRef) {
return &addr
}
}
return nil
}

// output counts of write only attributes aggregated by provider, resource and attribute
woAttrsMap := make(state.WriteOnlyAttributes)

// count usages and resolve provider
for _, attr := range woAttrs {
providerAddr := findProviderAddr(attr.Resource)
if providerAddr == nil {
continue
}

if _, ok := woAttrsMap[*providerAddr]; !ok {
woAttrsMap[*providerAddr] = make(map[state.ResourceName]map[state.AttributeName]int)
}

if _, ok := woAttrsMap[*providerAddr][attr.Resource]; !ok {
woAttrsMap[*providerAddr][attr.Resource] = make(map[state.AttributeName]int)
}

woAttrsMap[*providerAddr][attr.Resource][attr.Name]++
}

sErr := modStore.UpdateWriteOnlyAttributes(modPath, woAttrsMap, rErr)
if sErr != nil {
return sErr
}

return rErr
}
12 changes: 12 additions & 0 deletions internal/features/modules/modules_feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,18 @@ func (f *ModulesFeature) Telemetry(path string) map[string]interface{} {
properties["providerRequirements"] = reqs
}

if len(mod.WriteOnlyAttributes) > 0 {
woAttrs := make(map[string]map[string]map[string]int)

for pAddr, stats := range mod.WriteOnlyAttributes {
if telemetry.IsPublicProvider(pAddr) {
woAttrs[pAddr.String()] = stats
}
}

properties["writeOnlyAttributes"] = woAttrs
}

modId, err := f.Store.GetModuleID(mod.Path())
if err != nil {
return properties
Expand Down
9 changes: 9 additions & 0 deletions internal/features/modules/state/module_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ type ModuleRecord struct {
MetaErr error
MetaState op.OpState

WriteOnlyAttributes WriteOnlyAttributes
WriteOnlyAttributesErr error
WriteOnlyAttributesState op.OpState

ModuleDiagnostics ast.SourceModDiags
ModuleDiagnosticsState globalAst.DiagnosticSourceState
}
Expand Down Expand Up @@ -63,6 +67,10 @@ func (m *ModuleRecord) Copy() *ModuleRecord {
MetaErr: m.MetaErr,
MetaState: m.MetaState,

WriteOnlyAttributes: m.WriteOnlyAttributes,
WriteOnlyAttributesErr: m.WriteOnlyAttributesErr,
WriteOnlyAttributesState: m.WriteOnlyAttributesState,

ModuleDiagnosticsState: m.ModuleDiagnosticsState.Copy(),
}

Expand Down Expand Up @@ -101,6 +109,7 @@ func newModule(modPath string) *ModuleRecord {
RefOriginsState: op.OpStateUnknown,
RefTargetsState: op.OpStateUnknown,
MetaState: op.OpStateUnknown,
WriteOnlyAttributesState: op.OpStateUnknown,
ModuleDiagnosticsState: globalAst.DiagnosticSourceState{
globalAst.HCLParsingSource: op.OpStateUnknown,
globalAst.SchemaValidationSource: op.OpStateUnknown,
Expand Down
43 changes: 43 additions & 0 deletions internal/features/modules/state/module_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,49 @@ func (s *ModuleStore) UpdateReferenceOrigins(path string, origins reference.Orig
return nil
}

func (s *ModuleStore) SetWriteOnlyAttributesState(path string, state op.OpState) error {
txn := s.db.Txn(true)
defer txn.Abort()

mod, err := moduleCopyByPath(txn, path)
if err != nil {
return err
}

mod.WriteOnlyAttributesState = state
err = txn.Insert(s.tableName, mod)
if err != nil {
return err
}

txn.Commit()
return nil
}

func (s *ModuleStore) UpdateWriteOnlyAttributes(path string, woAttrs WriteOnlyAttributes, woAttrsErr error) error {
txn := s.db.Txn(true)
txn.Defer(func() {
s.SetWriteOnlyAttributesState(path, op.OpStateLoaded)
})
defer txn.Abort()

mod, err := moduleCopyByPath(txn, path)
if err != nil {
return err
}

mod.WriteOnlyAttributes = woAttrs
mod.WriteOnlyAttributesErr = woAttrsErr

err = txn.Insert(s.tableName, mod)
if err != nil {
return err
}

txn.Commit()
return nil
}

func (s *ModuleStore) RegistryModuleMeta(addr tfaddr.Module, cons version.Constraints) (*registry.ModuleData, error) {
return s.registryModuleStore.RegistryModuleMeta(addr, cons)
}
Expand Down
13 changes: 13 additions & 0 deletions internal/features/modules/state/write_only_attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package state

import (
tfaddr "github.com/hashicorp/terraform-registry-address"
)

type ResourceName = string
type AttributeName = string

type WriteOnlyAttributes map[tfaddr.Provider]map[ResourceName]map[AttributeName]int
7 changes: 4 additions & 3 deletions internal/terraform/module/operation/op_type_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions internal/terraform/module/operation/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@ const (
OpTypeLoadTestMetadata
OpTypeDecodeTestReferenceTargets
OpTypeDecodeTestReferenceOrigins
OpTypeDecodeWriteOnlyAttributes
OpTypeSchemaTestValidation
)
Loading