github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/builtin/providers/terraform/data_source_state.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform/backend"
     8  	"github.com/hashicorp/terraform/configs/configschema"
     9  	"github.com/hashicorp/terraform/providers"
    10  	"github.com/hashicorp/terraform/tfdiags"
    11  	"github.com/zclconf/go-cty/cty"
    12  
    13  	backendInit "github.com/hashicorp/terraform/backend/init"
    14  )
    15  
    16  func dataSourceRemoteStateGetSchema() providers.Schema {
    17  	return providers.Schema{
    18  		Block: &configschema.Block{
    19  			Attributes: map[string]*configschema.Attribute{
    20  				"backend": {
    21  					Type:     cty.String,
    22  					Required: true,
    23  				},
    24  				"config": {
    25  					Type:     cty.DynamicPseudoType,
    26  					Optional: true,
    27  				},
    28  				"defaults": {
    29  					Type:     cty.DynamicPseudoType,
    30  					Optional: true,
    31  				},
    32  				"outputs": {
    33  					Type:     cty.DynamicPseudoType,
    34  					Computed: true,
    35  				},
    36  				"workspace": {
    37  					Type:     cty.String,
    38  					Optional: true,
    39  				},
    40  			},
    41  		},
    42  	}
    43  }
    44  
    45  func dataSourceRemoteStateValidate(cfg cty.Value) tfdiags.Diagnostics {
    46  	var diags tfdiags.Diagnostics
    47  
    48  	// Getting the backend implicitly validates the configuration for it,
    49  	// but we can only do that if it's all known already.
    50  	if cfg.GetAttr("config").IsWhollyKnown() && cfg.GetAttr("backend").IsKnown() {
    51  		_, moreDiags := getBackend(cfg)
    52  		diags = diags.Append(moreDiags)
    53  	} else {
    54  		// Otherwise we'll just type-check the config object itself.
    55  		configTy := cfg.GetAttr("config").Type()
    56  		if configTy != cty.DynamicPseudoType && !(configTy.IsObjectType() || configTy.IsMapType()) {
    57  			diags = diags.Append(tfdiags.AttributeValue(
    58  				tfdiags.Error,
    59  				"Invalid backend configuration",
    60  				"The configuration must be an object value.",
    61  				cty.GetAttrPath("config"),
    62  			))
    63  		}
    64  	}
    65  
    66  	{
    67  		defaultsTy := cfg.GetAttr("defaults").Type()
    68  		if defaultsTy != cty.DynamicPseudoType && !(defaultsTy.IsObjectType() || defaultsTy.IsMapType()) {
    69  			diags = diags.Append(tfdiags.AttributeValue(
    70  				tfdiags.Error,
    71  				"Invalid default values",
    72  				"Defaults must be given in an object value.",
    73  				cty.GetAttrPath("defaults"),
    74  			))
    75  		}
    76  	}
    77  
    78  	return diags
    79  }
    80  
    81  func dataSourceRemoteStateRead(d cty.Value) (cty.Value, tfdiags.Diagnostics) {
    82  	var diags tfdiags.Diagnostics
    83  
    84  	b, moreDiags := getBackend(d)
    85  	diags = diags.Append(moreDiags)
    86  	if diags.HasErrors() {
    87  		return cty.NilVal, diags
    88  	}
    89  
    90  	newState := make(map[string]cty.Value)
    91  	newState["backend"] = d.GetAttr("backend")
    92  	newState["config"] = d.GetAttr("config")
    93  
    94  	workspaceName := backend.DefaultStateName
    95  
    96  	if workspaceVal := d.GetAttr("workspace"); !workspaceVal.IsNull() {
    97  		newState["workspace"] = workspaceVal
    98  		workspaceName = workspaceVal.AsString()
    99  	}
   100  
   101  	newState["workspace"] = cty.StringVal(workspaceName)
   102  
   103  	state, err := b.StateMgrWithoutCheckVersion(workspaceName)
   104  	if err != nil {
   105  		diags = diags.Append(tfdiags.AttributeValue(
   106  			tfdiags.Error,
   107  			"Error loading state error",
   108  			fmt.Sprintf("error loading the remote state: %s", err),
   109  			cty.Path(nil).GetAttr("backend"),
   110  		))
   111  		return cty.NilVal, diags
   112  	}
   113  
   114  	if err := state.RefreshStateWithoutCheckVersion(); err != nil {
   115  		diags = diags.Append(err)
   116  		return cty.NilVal, diags
   117  	}
   118  
   119  	outputs := make(map[string]cty.Value)
   120  
   121  	if defaultsVal := d.GetAttr("defaults"); !defaultsVal.IsNull() {
   122  		newState["defaults"] = defaultsVal
   123  		it := defaultsVal.ElementIterator()
   124  		for it.Next() {
   125  			k, v := it.Element()
   126  			outputs[k.AsString()] = v
   127  		}
   128  	} else {
   129  		newState["defaults"] = cty.NullVal(cty.DynamicPseudoType)
   130  	}
   131  
   132  	remoteState := state.State()
   133  	if remoteState == nil {
   134  		diags = diags.Append(tfdiags.AttributeValue(
   135  			tfdiags.Error,
   136  			"Unable to find remote state",
   137  			"No stored state was found for the given workspace in the given backend.",
   138  			cty.Path(nil).GetAttr("workspace"),
   139  		))
   140  		newState["outputs"] = cty.EmptyObjectVal
   141  		return cty.ObjectVal(newState), diags
   142  	}
   143  	mod := remoteState.RootModule()
   144  	if mod != nil { // should always have a root module in any valid state
   145  		for k, os := range mod.OutputValues {
   146  			outputs[k] = os.Value
   147  		}
   148  	}
   149  
   150  	newState["outputs"] = cty.ObjectVal(outputs)
   151  
   152  	return cty.ObjectVal(newState), diags
   153  }
   154  
   155  func getBackend(cfg cty.Value) (backend.Backend, tfdiags.Diagnostics) {
   156  	var diags tfdiags.Diagnostics
   157  
   158  	backendType := cfg.GetAttr("backend").AsString()
   159  
   160  	// Don't break people using the old _local syntax - but note warning above
   161  	if backendType == "_local" {
   162  		log.Println(`[INFO] Switching old (unsupported) backend "_local" to "local"`)
   163  		backendType = "local"
   164  	}
   165  
   166  	// Create the client to access our remote state
   167  	log.Printf("[DEBUG] Initializing remote state backend: %s", backendType)
   168  	f := backendInit.Backend(backendType)
   169  	if f == nil {
   170  		diags = diags.Append(tfdiags.AttributeValue(
   171  			tfdiags.Error,
   172  			"Invalid backend configuration",
   173  			fmt.Sprintf("There is no backend type named %q.", backendType),
   174  			cty.Path(nil).GetAttr("backend"),
   175  		))
   176  		return nil, diags
   177  	}
   178  	b := f()
   179  
   180  	config := cfg.GetAttr("config")
   181  	if config.IsNull() {
   182  		// We'll treat this as an empty configuration and see if the backend's
   183  		// schema and validation code will accept it.
   184  		config = cty.EmptyObjectVal
   185  	}
   186  
   187  	if config.Type().IsMapType() { // The code below expects an object type, so we'll convert
   188  		config = cty.ObjectVal(config.AsValueMap())
   189  	}
   190  
   191  	schema := b.ConfigSchema()
   192  	// Try to coerce the provided value into the desired configuration type.
   193  	configVal, err := schema.CoerceValue(config)
   194  	if err != nil {
   195  		diags = diags.Append(tfdiags.AttributeValue(
   196  			tfdiags.Error,
   197  			"Invalid backend configuration",
   198  			fmt.Sprintf("The given configuration is not valid for backend %q: %s.", backendType,
   199  				tfdiags.FormatError(err)),
   200  			cty.Path(nil).GetAttr("config"),
   201  		))
   202  		return nil, diags
   203  	}
   204  
   205  	newVal, validateDiags := b.PrepareConfig(configVal)
   206  	diags = diags.Append(validateDiags)
   207  	if validateDiags.HasErrors() {
   208  		return nil, diags
   209  	}
   210  	configVal = newVal
   211  
   212  	configureDiags := b.Configure(configVal)
   213  	if configureDiags.HasErrors() {
   214  		diags = diags.Append(configureDiags.Err())
   215  		return nil, diags
   216  	}
   217  
   218  	return b, diags
   219  }