259 lines
7.6 KiB
Go
259 lines
7.6 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/hashicorp/terraform/internal/backend"
|
|
"github.com/hashicorp/terraform/internal/backend/remote"
|
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
backendInit "github.com/hashicorp/terraform/internal/backend/init"
|
|
)
|
|
|
|
func dataSourceRemoteStateGetSchema() providers.Schema {
|
|
return providers.Schema{
|
|
Block: &configschema.Block{
|
|
Attributes: map[string]*configschema.Attribute{
|
|
"backend": {
|
|
Type: cty.String,
|
|
Description: "The remote backend to use, e.g. `remote` or `http`.",
|
|
DescriptionKind: configschema.StringMarkdown,
|
|
Required: true,
|
|
},
|
|
"config": {
|
|
Type: cty.DynamicPseudoType,
|
|
Description: "The configuration of the remote backend. " +
|
|
"Although this is optional, most backends require " +
|
|
"some configuration.\n\n" +
|
|
"The object can use any arguments that would be valid " +
|
|
"in the equivalent `terraform { backend \"<TYPE>\" { ... } }` " +
|
|
"block.",
|
|
DescriptionKind: configschema.StringMarkdown,
|
|
Optional: true,
|
|
},
|
|
"defaults": {
|
|
Type: cty.DynamicPseudoType,
|
|
Description: "Default values for outputs, in case " +
|
|
"the state file is empty or lacks a required output.",
|
|
DescriptionKind: configschema.StringMarkdown,
|
|
Optional: true,
|
|
},
|
|
"outputs": {
|
|
Type: cty.DynamicPseudoType,
|
|
Description: "An object containing every root-level " +
|
|
"output in the remote state.",
|
|
DescriptionKind: configschema.StringMarkdown,
|
|
Computed: true,
|
|
},
|
|
"workspace": {
|
|
Type: cty.String,
|
|
Description: "The Terraform workspace to use, if " +
|
|
"the backend supports workspaces.",
|
|
DescriptionKind: configschema.StringMarkdown,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func dataSourceRemoteStateValidate(cfg cty.Value) tfdiags.Diagnostics {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
// Getting the backend implicitly validates the configuration for it,
|
|
// but we can only do that if it's all known already.
|
|
if cfg.GetAttr("config").IsWhollyKnown() && cfg.GetAttr("backend").IsKnown() {
|
|
_, _, moreDiags := getBackend(cfg)
|
|
diags = diags.Append(moreDiags)
|
|
} else {
|
|
// Otherwise we'll just type-check the config object itself.
|
|
configTy := cfg.GetAttr("config").Type()
|
|
if configTy != cty.DynamicPseudoType && !(configTy.IsObjectType() || configTy.IsMapType()) {
|
|
diags = diags.Append(tfdiags.AttributeValue(
|
|
tfdiags.Error,
|
|
"Invalid backend configuration",
|
|
"The configuration must be an object value.",
|
|
cty.GetAttrPath("config"),
|
|
))
|
|
}
|
|
}
|
|
|
|
{
|
|
defaultsTy := cfg.GetAttr("defaults").Type()
|
|
if defaultsTy != cty.DynamicPseudoType && !(defaultsTy.IsObjectType() || defaultsTy.IsMapType()) {
|
|
diags = diags.Append(tfdiags.AttributeValue(
|
|
tfdiags.Error,
|
|
"Invalid default values",
|
|
"Defaults must be given in an object value.",
|
|
cty.GetAttrPath("defaults"),
|
|
))
|
|
}
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
func dataSourceRemoteStateRead(d cty.Value) (cty.Value, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
b, cfg, moreDiags := getBackend(d)
|
|
diags = diags.Append(moreDiags)
|
|
if moreDiags.HasErrors() {
|
|
return cty.NilVal, diags
|
|
}
|
|
|
|
configureDiags := b.Configure(cfg)
|
|
if configureDiags.HasErrors() {
|
|
diags = diags.Append(configureDiags.Err())
|
|
return cty.NilVal, diags
|
|
}
|
|
|
|
newState := make(map[string]cty.Value)
|
|
newState["backend"] = d.GetAttr("backend")
|
|
newState["config"] = d.GetAttr("config")
|
|
|
|
workspaceVal := d.GetAttr("workspace")
|
|
// This attribute is not computed, so we always have to store the state
|
|
// value, even if we implicitly use a default.
|
|
newState["workspace"] = workspaceVal
|
|
|
|
workspaceName := backend.DefaultStateName
|
|
if !workspaceVal.IsNull() {
|
|
workspaceName = workspaceVal.AsString()
|
|
}
|
|
|
|
state, err := b.StateMgr(workspaceName)
|
|
if err != nil {
|
|
diags = diags.Append(tfdiags.AttributeValue(
|
|
tfdiags.Error,
|
|
"Error loading state error",
|
|
fmt.Sprintf("error loading the remote state: %s", err),
|
|
cty.Path(nil).GetAttr("backend"),
|
|
))
|
|
return cty.NilVal, diags
|
|
}
|
|
|
|
if err := state.RefreshState(); err != nil {
|
|
diags = diags.Append(err)
|
|
return cty.NilVal, diags
|
|
}
|
|
|
|
outputs := make(map[string]cty.Value)
|
|
|
|
if defaultsVal := d.GetAttr("defaults"); !defaultsVal.IsNull() {
|
|
newState["defaults"] = defaultsVal
|
|
it := defaultsVal.ElementIterator()
|
|
for it.Next() {
|
|
k, v := it.Element()
|
|
outputs[k.AsString()] = v
|
|
}
|
|
} else {
|
|
newState["defaults"] = cty.NullVal(cty.DynamicPseudoType)
|
|
}
|
|
|
|
remoteState := state.State()
|
|
if remoteState == nil {
|
|
diags = diags.Append(tfdiags.AttributeValue(
|
|
tfdiags.Error,
|
|
"Unable to find remote state",
|
|
"No stored state was found for the given workspace in the given backend.",
|
|
cty.Path(nil).GetAttr("workspace"),
|
|
))
|
|
newState["outputs"] = cty.EmptyObjectVal
|
|
return cty.ObjectVal(newState), diags
|
|
}
|
|
mod := remoteState.RootModule()
|
|
if mod != nil { // should always have a root module in any valid state
|
|
for k, os := range mod.OutputValues {
|
|
outputs[k] = os.Value
|
|
}
|
|
}
|
|
|
|
newState["outputs"] = cty.ObjectVal(outputs)
|
|
|
|
return cty.ObjectVal(newState), diags
|
|
}
|
|
|
|
func getBackend(cfg cty.Value) (backend.Backend, cty.Value, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
backendType := cfg.GetAttr("backend").AsString()
|
|
|
|
// Don't break people using the old _local syntax - but note warning above
|
|
if backendType == "_local" {
|
|
log.Println(`[INFO] Switching old (unsupported) backend "_local" to "local"`)
|
|
backendType = "local"
|
|
}
|
|
|
|
// Create the client to access our remote state
|
|
log.Printf("[DEBUG] Initializing remote state backend: %s", backendType)
|
|
f := getBackendFactory(backendType)
|
|
if f == nil {
|
|
diags = diags.Append(tfdiags.AttributeValue(
|
|
tfdiags.Error,
|
|
"Invalid backend configuration",
|
|
fmt.Sprintf("There is no backend type named %q.", backendType),
|
|
cty.Path(nil).GetAttr("backend"),
|
|
))
|
|
return nil, cty.NilVal, diags
|
|
}
|
|
b := f()
|
|
|
|
config := cfg.GetAttr("config")
|
|
if config.IsNull() {
|
|
// We'll treat this as an empty configuration and see if the backend's
|
|
// schema and validation code will accept it.
|
|
config = cty.EmptyObjectVal
|
|
}
|
|
|
|
if config.Type().IsMapType() { // The code below expects an object type, so we'll convert
|
|
config = cty.ObjectVal(config.AsValueMap())
|
|
}
|
|
|
|
schema := b.ConfigSchema()
|
|
// Try to coerce the provided value into the desired configuration type.
|
|
configVal, err := schema.CoerceValue(config)
|
|
if err != nil {
|
|
diags = diags.Append(tfdiags.AttributeValue(
|
|
tfdiags.Error,
|
|
"Invalid backend configuration",
|
|
fmt.Sprintf("The given configuration is not valid for backend %q: %s.", backendType,
|
|
tfdiags.FormatError(err)),
|
|
cty.Path(nil).GetAttr("config"),
|
|
))
|
|
return nil, cty.NilVal, diags
|
|
}
|
|
|
|
newVal, validateDiags := b.PrepareConfig(configVal)
|
|
diags = diags.Append(validateDiags)
|
|
if validateDiags.HasErrors() {
|
|
return nil, cty.NilVal, diags
|
|
}
|
|
|
|
// If this is the enhanced remote backend, we want to disable the version
|
|
// check, because this is a read-only operation
|
|
if rb, ok := b.(*remote.Remote); ok {
|
|
rb.IgnoreVersionConflict()
|
|
}
|
|
|
|
return b, newVal, diags
|
|
}
|
|
|
|
// overrideBackendFactories allows test cases to control the set of available
|
|
// backends to allow for more self-contained tests. This should never be set
|
|
// in non-test code.
|
|
var overrideBackendFactories map[string]backend.InitFn
|
|
|
|
func getBackendFactory(backendType string) backend.InitFn {
|
|
if len(overrideBackendFactories) > 0 {
|
|
// Tests may override the set of backend factories.
|
|
return overrideBackendFactories[backendType]
|
|
}
|
|
|
|
return backendInit.Backend(backendType)
|
|
}
|