mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 18:10:30 -04:00
139 lines
4.5 KiB
Go
139 lines
4.5 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package stackmigrate
|
|
|
|
import (
|
|
"fmt"
|
|
"iter"
|
|
|
|
"github.com/hashicorp/go-slug/sourceaddrs"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
"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/states"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
// Migration is a struct that aids in migrating a terraform state to a stack configuration.
|
|
type Migration struct {
|
|
// Providers is a map of provider addresses available to the stack.
|
|
Providers map[addrs.Provider]providers.Factory
|
|
|
|
// PreviousState is the terraform core state that we are migrating from.
|
|
PreviousState *states.State
|
|
Config *stackconfig.Config
|
|
}
|
|
|
|
// Alias common types to make the code more readable.
|
|
type (
|
|
// ConfigComponent is the definition of a component in a stack configuration,
|
|
// and therefore is unique for all instances of a component in a stack.
|
|
Config = stackaddrs.ConfigComponent
|
|
|
|
// Every instance of a component in a stack instance has a unique address.
|
|
Instance = stackaddrs.AbsComponentInstance
|
|
|
|
// Every instance of a component in a stack has the same AbsComponent address.
|
|
AbsComponent = stackaddrs.AbsComponent
|
|
)
|
|
|
|
func (m *Migration) Migrate(resources map[string]string, modules map[string]string, emit func(change stackstate.AppliedChange), emitDiag func(diagnostic tfdiags.Diagnostic)) {
|
|
|
|
migration := &migration{
|
|
Migration: m,
|
|
emit: emit,
|
|
emitDiag: emitDiag,
|
|
providers: make(map[addrs.Provider]providers.Interface),
|
|
parser: configs.NewSourceBundleParser(m.Config.Sources),
|
|
configs: make(map[sourceaddrs.FinalSource]*configs.Config),
|
|
}
|
|
|
|
defer migration.close() // cleanup any opened providers.
|
|
|
|
components := migration.migrateResources(resources, modules)
|
|
migration.migrateComponents(components)
|
|
|
|
// Everything is migrated!
|
|
}
|
|
|
|
type migration struct {
|
|
*Migration
|
|
|
|
emit func(change stackstate.AppliedChange)
|
|
emitDiag func(diagnostic tfdiags.Diagnostic)
|
|
|
|
providers map[addrs.Provider]providers.Interface
|
|
parser *configs.SourceBundleParser
|
|
configs map[sourceaddrs.FinalSource]*configs.Config
|
|
}
|
|
|
|
func (m *migration) stateResources() iter.Seq2[addrs.AbsResource, *states.Resource] {
|
|
return func(yield func(addrs.AbsResource, *states.Resource) bool) {
|
|
for _, module := range m.PreviousState.Modules {
|
|
for _, resource := range module.Resources {
|
|
if !yield(resource.Addr, resource) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// moduleConfig returns the module configuration for the component. If the configuration
|
|
// has already been loaded, it will be returned from the cache.
|
|
func (m *migration) moduleConfig(component *stackconfig.Component) (*configs.Config, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
if component.FinalSourceAddr == nil {
|
|
// if there is no final source address, then the configuration was likely
|
|
// loaded via a shallow load, but we need the full configuration.
|
|
panic("component has no final source address")
|
|
}
|
|
if cfg, ok := m.configs[component.FinalSourceAddr]; ok {
|
|
return cfg, diags
|
|
}
|
|
moduleConfig, diags := component.ModuleConfig(m.parser.Bundle())
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
m.configs[component.FinalSourceAddr] = moduleConfig
|
|
return moduleConfig, diags
|
|
}
|
|
|
|
func (m *migration) emitDiags(diags tfdiags.Diagnostics) {
|
|
for _, diag := range diags {
|
|
m.emitDiag(diag)
|
|
}
|
|
}
|
|
|
|
func (m *migration) provider(provider addrs.Provider) (providers.Interface, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
if p, ok := m.providers[provider]; ok {
|
|
return p, diags
|
|
}
|
|
|
|
factory, ok := m.Migration.Providers[provider]
|
|
if !ok {
|
|
return nil, tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "Provider not found", fmt.Sprintf("Provider %s not found in required_providers.", provider.ForDisplay()))}
|
|
}
|
|
|
|
p, err := factory()
|
|
if err != nil {
|
|
return nil, tfdiags.Diagnostics{tfdiags.Sourceless(tfdiags.Error, "Provider initialization failed", fmt.Sprintf("Failed to initialize provider %s: %s", provider.ForDisplay(), err.Error()))}
|
|
}
|
|
|
|
m.providers[provider] = p
|
|
return p, diags
|
|
}
|
|
|
|
func (m *migration) close() {
|
|
for addr, provider := range m.providers {
|
|
if err := provider.Close(); err != nil {
|
|
m.emitDiag(tfdiags.Sourceless(tfdiags.Error, "Provider cleanup failed", fmt.Sprintf("Failed to close provider %s: %s", addr.ForDisplay(), err.Error())))
|
|
}
|
|
}
|
|
}
|