mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 18:10:30 -04:00
266 lines
9.6 KiB
Go
266 lines
9.6 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package stackmigrate
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hcldec"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/collections"
|
|
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
|
|
"github.com/hashicorp/terraform/internal/stacks/stackconfig"
|
|
"github.com/hashicorp/terraform/internal/stacks/stackstate"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
func (m *migration) migrateComponents(components collections.Map[Instance, collections.Set[*stackResource]]) {
|
|
// We need to calculate the dependencies between components, so we can
|
|
// populate the dependencies and dependents fields in the component instances.
|
|
dependencies, dependents := m.calculateDependencies(components)
|
|
|
|
for instance := range components.All() {
|
|
// We need to see the inputs and outputs from the component, so we can
|
|
// create the component instance with the correct values.
|
|
// ignore the diag because we already found this when loading the config.
|
|
config, _ := m.moduleConfig(m.Config.Component(stackaddrs.ConfigComponentForAbsInstance(instance)))
|
|
|
|
// We can put unknown values into the state for now, as Stacks should
|
|
// perform a refresh before actually using any of these anyway.
|
|
inputs := make(map[addrs.InputVariable]cty.Value, len(config.Module.Variables))
|
|
for name := range config.Module.Variables {
|
|
inputs[addrs.InputVariable{Name: name}] = cty.DynamicVal
|
|
}
|
|
outputs := make(map[addrs.OutputValue]cty.Value, len(config.Module.Outputs))
|
|
for name := range config.Module.Outputs {
|
|
outputs[addrs.OutputValue{Name: name}] = cty.DynamicVal
|
|
}
|
|
|
|
// We need this address to be able to look up dependencies and
|
|
// dependents later.
|
|
addr := AbsComponent{
|
|
Stack: instance.Stack,
|
|
Item: instance.Item.Component,
|
|
}
|
|
|
|
// We emit a change a change for each component instance
|
|
m.emit(&stackstate.AppliedChangeComponentInstance{
|
|
ComponentAddr: AbsComponent{
|
|
Stack: stackaddrs.RootStackInstance,
|
|
Item: instance.Item.Component,
|
|
},
|
|
ComponentInstanceAddr: instance,
|
|
|
|
OutputValues: outputs,
|
|
InputVariables: inputs,
|
|
|
|
// If a destroy plan, or a removed block, is executed before the
|
|
// next plan is applied, the component will break without this
|
|
// metadata.
|
|
Dependencies: dependencies.Get(addr),
|
|
Dependents: dependents.Get(addr),
|
|
})
|
|
}
|
|
}
|
|
|
|
func (m *migration) calculateDependencies(components collections.Map[Instance, collections.Set[*stackResource]]) (collections.Map[AbsComponent, collections.Set[AbsComponent]], collections.Map[AbsComponent, collections.Set[AbsComponent]]) {
|
|
// The dependency map cares only about config components rather than instances,
|
|
// so we need to convert the map to use the config component address.
|
|
cfgComponents := collections.NewMap[AbsComponent, collections.Set[*stackResource]]()
|
|
for in, cmpnts := range components.All() {
|
|
cfgComponents.Put(AbsComponent{
|
|
Stack: in.Stack,
|
|
Item: in.Item.Component,
|
|
}, cmpnts)
|
|
}
|
|
|
|
dependencies := collections.NewMap[AbsComponent, collections.Set[AbsComponent]]()
|
|
dependents := collections.NewMap[AbsComponent, collections.Set[AbsComponent]]()
|
|
|
|
// First, we're going to work out the dependencies between components.
|
|
for addr, cmpnts := range cfgComponents.All() {
|
|
for resource := range cmpnts.All() {
|
|
instance := resource.AbsResourceInstance.Component
|
|
|
|
compDepSet := collections.NewSet[AbsComponent]()
|
|
|
|
// We collect the component's dependencies, and also
|
|
// add the component to the dependent set of its dependencies.
|
|
addDependencies := func(dss collections.Set[AbsComponent]) {
|
|
compDepSet.AddAll(dss)
|
|
for cmpt := range dss.All() {
|
|
if !dependents.HasKey(cmpt) {
|
|
dependents.Put(cmpt, collections.NewSet[AbsComponent]())
|
|
}
|
|
dependents.Get(cmpt).Add(addr)
|
|
}
|
|
}
|
|
|
|
component := resource.ComponentConfig
|
|
stack := resource.StackConfig
|
|
// First, check the inputs.
|
|
inputDependencies, inputDiags := m.componentDependenciesFromExpression(component.Inputs, instance.Stack, cfgComponents)
|
|
m.emitDiags(inputDiags)
|
|
addDependencies(inputDependencies)
|
|
|
|
// Then, check the depends_on directly.
|
|
for _, traversal := range component.DependsOn {
|
|
dependsOnDependencies, dependsOnDiags := m.componentDependenciesFromTraversal(traversal, instance.Stack, cfgComponents)
|
|
m.emitDiags(dependsOnDiags)
|
|
addDependencies(dependsOnDependencies)
|
|
}
|
|
|
|
// Then, check the foreach.
|
|
forEachDependencies, forEachDiags := m.componentDependenciesFromExpression(component.ForEach, instance.Stack, cfgComponents)
|
|
m.emitDiags(forEachDiags)
|
|
addDependencies(forEachDependencies)
|
|
|
|
// Finally, we're going to look at the providers, and see if they
|
|
// depend on any other components.
|
|
for _, expr := range component.ProviderConfigs {
|
|
pds, diags := m.providerDependencies(expr, instance.Stack, stack, cfgComponents)
|
|
m.emitDiags(diags)
|
|
addDependencies(pds)
|
|
}
|
|
|
|
// We're happy we got all the dependencies for this component, so we
|
|
// can store them now.
|
|
dependencies.Put(addr, compDepSet)
|
|
}
|
|
}
|
|
return dependencies, dependents
|
|
}
|
|
|
|
// componentDependenciesFromExpression returns a set of components that are
|
|
// referenced in the given expression.
|
|
func (m *migration) componentDependenciesFromExpression(expr hcl.Expression, current stackaddrs.StackInstance, components collections.Map[AbsComponent, collections.Set[*stackResource]]) (ds collections.Set[AbsComponent], diags tfdiags.Diagnostics) {
|
|
ds = collections.NewSet[AbsComponent]()
|
|
if expr == nil {
|
|
return ds, diags
|
|
}
|
|
|
|
for _, v := range expr.Variables() {
|
|
dss, moreDiags := m.componentDependenciesFromTraversal(v, current, components)
|
|
ds.AddAll(dss)
|
|
diags = diags.Append(moreDiags)
|
|
}
|
|
return ds, diags
|
|
}
|
|
|
|
// componentDependenciesFromTraversal returns the component that is referenced
|
|
// in the given traversal, if it is a component reference.
|
|
func (m *migration) componentDependenciesFromTraversal(traversal hcl.Traversal, current stackaddrs.StackInstance, components collections.Map[AbsComponent, collections.Set[*stackResource]]) (deps collections.Set[AbsComponent], diags tfdiags.Diagnostics) {
|
|
deps = collections.NewSet[AbsComponent]()
|
|
|
|
parsed, _, moreDiags := stackaddrs.ParseReference(traversal)
|
|
diags = diags.Append(moreDiags)
|
|
if moreDiags.HasErrors() {
|
|
// Then the configuration is invalid, so we'll skip this variable.
|
|
// The user should have ran a separate validation step before
|
|
// performing the migration to catch this.
|
|
return deps, diags
|
|
}
|
|
|
|
switch ref := parsed.Target.(type) {
|
|
case stackaddrs.Component:
|
|
// We have a reference to a component in the current stack.
|
|
deps.Add(AbsComponent{
|
|
Stack: current,
|
|
Item: ref,
|
|
})
|
|
return deps, diags
|
|
case stackaddrs.StackCall:
|
|
targetStackAddress := append(current.ConfigAddr(), stackaddrs.StackStep(ref))
|
|
stack := m.Config.Stack(targetStackAddress)
|
|
|
|
if stack == nil {
|
|
// reference to a stack that does not exist in the configuration.
|
|
diags = diags.Append(hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Stack not found",
|
|
Detail: fmt.Sprintf("Stack %q not found in configuration.", targetStackAddress),
|
|
Subject: parsed.SourceRange.ToHCL().Ptr(),
|
|
})
|
|
return deps, diags
|
|
}
|
|
|
|
for name := range stack.Components {
|
|
// If the component in the stack call is part of the mapping, then it will
|
|
// be present in the map, and we will add it to the dependencies.
|
|
// Otherwise, we will ignore it.
|
|
componentAddr := AbsComponent{
|
|
Stack: current.Child(ref.Name, addrs.NoKey),
|
|
Item: stackaddrs.Component{Name: name},
|
|
}
|
|
|
|
if _, ok := components.GetOk(componentAddr); ok {
|
|
deps.Add(componentAddr)
|
|
}
|
|
}
|
|
return deps, diags
|
|
default:
|
|
// This is not a component reference, and we only care about
|
|
// component dependencies.
|
|
return deps, diags
|
|
}
|
|
}
|
|
|
|
func (m *migration) providerDependencies(expr hcl.Expression, current stackaddrs.StackInstance, stack *stackconfig.Stack, components collections.Map[AbsComponent, collections.Set[*stackResource]]) (ds collections.Set[AbsComponent], diags tfdiags.Diagnostics) {
|
|
ds = collections.NewSet[AbsComponent]()
|
|
for _, v := range expr.Variables() {
|
|
ref, _, moreDiags := stackaddrs.ParseReference(v)
|
|
diags = diags.Append(moreDiags)
|
|
if moreDiags.HasErrors() {
|
|
// Invalid configuration, so skip it.
|
|
continue
|
|
}
|
|
|
|
switch ref := ref.Target.(type) {
|
|
case stackaddrs.ProviderConfigRef:
|
|
config := stack.ProviderConfigs[addrs.LocalProviderConfig{
|
|
LocalName: ref.ProviderLocalName,
|
|
Alias: ref.Name,
|
|
}]
|
|
|
|
dss, moreDiags := m.componentDependenciesFromExpression(config.ForEach, current, components)
|
|
diags = diags.Append(moreDiags)
|
|
ds.AddAll(dss)
|
|
|
|
if config.Config == nil {
|
|
// if there is no configuration, then there won't be any
|
|
// dependencies.
|
|
break
|
|
}
|
|
|
|
addr, ok := stack.RequiredProviders.ProviderForLocalName(ref.ProviderLocalName)
|
|
if !ok {
|
|
diags = diags.Append(tfdiags.Sourceless(tfdiags.Error, "Provider not found", fmt.Sprintf("Provider %s not found in required_providers.", ref.ProviderLocalName)))
|
|
continue
|
|
}
|
|
|
|
provider, pDiags := m.provider(addr)
|
|
if pDiags.HasErrors() {
|
|
diags = diags.Append(pDiags)
|
|
continue // skip this provider if we can't get the schema
|
|
}
|
|
|
|
spec := provider.GetProviderSchema().Provider.Body.DecoderSpec()
|
|
traversals := hcldec.Variables(config.Config, spec)
|
|
for _, traversal := range traversals {
|
|
dss, moreDiags := m.componentDependenciesFromTraversal(traversal, current, components)
|
|
diags = diags.Append(moreDiags)
|
|
ds.AddAll(dss)
|
|
}
|
|
|
|
default:
|
|
// This is not a provider reference, and we only care about
|
|
// provider dependencies.
|
|
continue
|
|
}
|
|
}
|
|
return ds, diags
|
|
}
|