Skip to content

Commit

Permalink
(TF-18960) Enable store references in deployment configuration (#390)
Browse files Browse the repository at this point in the history
* feat: early decode store blocks in deployment files

* feat: create targets for store blocks to allow references to dynamic values within

* refactor: use a specific example for the varset id
  • Loading branch information
ansgarm authored Aug 19, 2024
1 parent 824462a commit 27f3526
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 1 deletion.
6 changes: 6 additions & 0 deletions earlydecoder/stacks/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ func LoadStack(path string, files map[string]*hcl.File) (*stack.Meta, hcl.Diagno
deployments[key] = *deployment
}

stores := make(map[string]stack.Store)
for key, store := range mod.Stores {
stores[key] = *store
}

return &stack.Meta{
Path: path,
Filenames: filenames,
Expand All @@ -92,6 +97,7 @@ func LoadStack(path string, files map[string]*hcl.File) (*stack.Meta, hcl.Diagno
Outputs: outputs,
ProviderRequirements: providerRequirements,
Deployments: deployments,
Stores: stores,
}, diags
}

Expand Down
3 changes: 3 additions & 0 deletions earlydecoder/stacks/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func TestLoadStack(t *testing.T) {
Outputs: map[string]stack.Output{},
ProviderRequirements: map[string]stack.ProviderRequirement{},
Deployments: map[string]stack.Deployment{},
Stores: map[string]stack.Store{},
},
nil,
},
Expand All @@ -70,6 +71,7 @@ func TestLoadStack(t *testing.T) {
Outputs: map[string]stack.Output{},
ProviderRequirements: map[string]stack.ProviderRequirement{},
Deployments: map[string]stack.Deployment{},
Stores: map[string]stack.Store{},
},
nil,
},
Expand All @@ -96,6 +98,7 @@ func TestLoadStack(t *testing.T) {
"random": {Source: tfaddr.MustParseProviderSource("hashicorp/random"), VersionConstraints: version.MustConstraints(version.NewConstraint("~> 3.5.1"))},
},
Deployments: map[string]stack.Deployment{},
Stores: map[string]stack.Store{},
},
nil,
},
Expand Down
12 changes: 12 additions & 0 deletions earlydecoder/stacks/load_deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,19 @@ func loadDeployFromFile(file *hcl.File, ds *decodedStack) hcl.Diagnostics {
ds.Deployments[name] = &stack.Deployment{
Inputs: inputs,
}
case "store":
if len(block.Labels) != 2 || block.Labels[0] == "" || block.Labels[1] == "" {
continue
}

storeType := block.Labels[0]
storeName := block.Labels[1]

ds.Stores[storeName] = &stack.Store{
Type: storeType,
}
}

}

return diags
Expand Down
2 changes: 2 additions & 0 deletions earlydecoder/stacks/load_stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type decodedStack struct {
ProviderRequirements map[string]*providerRequirement

Deployments map[string]*stack.Deployment
Stores map[string]*stack.Store
}

func newDecodedStack() *decodedStack {
Expand All @@ -33,6 +34,7 @@ func newDecodedStack() *decodedStack {
Outputs: make(map[string]*stack.Output),
ProviderRequirements: make(map[string]*providerRequirement),
Deployments: make(map[string]*stack.Deployment),
Stores: make(map[string]*stack.Store),
}
}

Expand Down
4 changes: 4 additions & 0 deletions earlydecoder/stacks/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ var deploymentRootSchema = &hcl.BodySchema{
Type: "deployment",
LabelNames: []string{"name"},
},
{
Type: "store",
LabelNames: []string{"type", "name"},
},
},
}

Expand Down
1 change: 1 addition & 0 deletions internal/schema/refscope/scopes.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ var (

ComponentScope = lang.ScopeId("component")
IdentityTokenScope = lang.ScopeId("identity_token")
StoreScope = lang.ScopeId("store")
)
7 changes: 6 additions & 1 deletion internal/schema/stacks/1.9/store_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func storeBlockSchema() *schema.BlockSchema {
Name: "name",
SemanticTokenModifiers: lang.SemanticTokenModifiers{tokmod.Name},
Description: lang.PlainText("Store name"),
IsDepKey: true,
},
},
DependentBody: map[schema.SchemaKey]*schema.BodySchema{
Expand All @@ -50,9 +51,13 @@ func storeBlockSchema() *schema.BlockSchema {
"id": {
IsRequired: true,
Constraint: schema.LiteralType{Type: cty.String},
Description: lang.Markdown("The id of the varset. In the form of 'varset-nnnnnnnnnnnnnnnn'."),
Description: lang.Markdown("The id of the varset. In the form of 'varset-QKpocVOC3uQQxVrF'."),
},
},
AnyAttribute: &schema.AttributeSchema{
IsComputed: true,
Constraint: schema.AnyExpression{OfType: cty.DynamicPseudoType},
},
},
},
}
Expand Down
42 changes: 42 additions & 0 deletions schema/stacks/deploy_schema_merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,48 @@ func (m *DeploySchemaMerger) SchemaForDeployment(meta *stack.Meta) (*schema.Body
// TODO: isOptional should be set to true if at least one input is required
mergedSchema.Blocks["deployment"].Body.Attributes["inputs"].Constraint = constr

// We add specific Targetables with a dynamic type for each store as the AnyAttribute defined in the store schema
// is not picked up when reference targets are collected because we don't know all the available attributes that
// are eventually available in the varset and reference targets need a specific name to match against
// However, it is also possible to target the parent if its type is dynamic. This is why we add this target.
// We can't set the body to dynamic in the schema as that would remove the completions for the "id" attribute of the
// varset store block.
// If there's a better way to do this, we should consider it as this feels like a bit of a hack.
// TODO: once we parse tfvars files, do this differently for the tfvars type store block
for name, store := range meta.Stores {
key := schema.NewSchemaKey(schema.DependencyKeys{
Labels: []schema.LabelDependent{
{Index: 0, Value: store.Type},
{Index: 1, Value: name},
}})

// Copy the body of the store block for the specific store type to keep attributes and constraints
// as dependent bodies replace the original ones
newBody := mergedSchema.Blocks["store"].DependentBody[schema.NewSchemaKey(schema.DependencyKeys{
Labels: []schema.LabelDependent{
{Index: 0, Value: store.Type},
}})].Copy()

if newBody == nil {
newBody = mergedSchema.Blocks["store"].Body
}

newBody.TargetableAs = schema.Targetables{
&schema.Targetable{
Address: lang.Address{
lang.RootStep{Name: "store"},
lang.AttrStep{Name: store.Type},
lang.AttrStep{Name: name},
},
AsType: cty.DynamicPseudoType, // we need this type for the target for this store block as it matches every nested value
ScopeId: refscope.StoreScope,
FriendlyName: "store",
},
}

mergedSchema.Blocks["store"].DependentBody[key] = newBody
}

return mergedSchema, nil
}

Expand Down
1 change: 1 addition & 0 deletions stack/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ type Meta struct {
ProviderRequirements map[string]ProviderRequirement

Deployments map[string]Deployment
Stores map[string]Store
}
8 changes: 8 additions & 0 deletions stack/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package stack

type Store struct {
Type string
}

0 comments on commit 27f3526

Please sign in to comment.