github.com/opentofu/opentofu@v1.7.1/internal/builtin/providers/tf/data_source_state.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tf
     7  
     8  import (
     9  	"fmt"
    10  	"log"
    11  
    12  	"github.com/opentofu/opentofu/internal/backend"
    13  	"github.com/opentofu/opentofu/internal/backend/remote"
    14  	"github.com/opentofu/opentofu/internal/configs/configschema"
    15  	"github.com/opentofu/opentofu/internal/encryption"
    16  	"github.com/opentofu/opentofu/internal/providers"
    17  	"github.com/opentofu/opentofu/internal/tfdiags"
    18  	"github.com/zclconf/go-cty/cty"
    19  
    20  	backendInit "github.com/opentofu/opentofu/internal/backend/init"
    21  )
    22  
    23  func dataSourceRemoteStateGetSchema() providers.Schema {
    24  	return providers.Schema{
    25  		Block: &configschema.Block{
    26  			Attributes: map[string]*configschema.Attribute{
    27  				"backend": {
    28  					Type:            cty.String,
    29  					Description:     "The remote backend to use, e.g. `remote` or `http`.",
    30  					DescriptionKind: configschema.StringMarkdown,
    31  					Required:        true,
    32  				},
    33  				"config": {
    34  					Type: cty.DynamicPseudoType,
    35  					Description: "The configuration of the remote backend. " +
    36  						"Although this is optional, most backends require " +
    37  						"some configuration.\n\n" +
    38  						"The object can use any arguments that would be valid " +
    39  						"in the equivalent `terraform { backend \"<TYPE>\" { ... } }` " +
    40  						"block.",
    41  					DescriptionKind: configschema.StringMarkdown,
    42  					Optional:        true,
    43  				},
    44  				"defaults": {
    45  					Type: cty.DynamicPseudoType,
    46  					Description: "Default values for outputs, in case " +
    47  						"the state file is empty or lacks a required output.",
    48  					DescriptionKind: configschema.StringMarkdown,
    49  					Optional:        true,
    50  				},
    51  				"outputs": {
    52  					Type: cty.DynamicPseudoType,
    53  					Description: "An object containing every root-level " +
    54  						"output in the remote state.",
    55  					DescriptionKind: configschema.StringMarkdown,
    56  					Computed:        true,
    57  				},
    58  				"workspace": {
    59  					Type: cty.String,
    60  					Description: "The OpenTofu workspace to use, if " +
    61  						"the backend supports workspaces.",
    62  					DescriptionKind: configschema.StringMarkdown,
    63  					Optional:        true,
    64  				},
    65  			},
    66  		},
    67  	}
    68  }
    69  
    70  func dataSourceRemoteStateValidate(cfg cty.Value) tfdiags.Diagnostics {
    71  	var diags tfdiags.Diagnostics
    72  
    73  	// Getting the backend implicitly validates the configuration for it,
    74  	// but we can only do that if it's all known already.
    75  	if cfg.GetAttr("config").IsWhollyKnown() && cfg.GetAttr("backend").IsKnown() {
    76  		_, _, moreDiags := getBackend(cfg, nil) // Don't need the encryption for validation here
    77  		diags = diags.Append(moreDiags)
    78  	} else {
    79  		// Otherwise we'll just type-check the config object itself.
    80  		configTy := cfg.GetAttr("config").Type()
    81  		if configTy != cty.DynamicPseudoType && !(configTy.IsObjectType() || configTy.IsMapType()) {
    82  			diags = diags.Append(tfdiags.AttributeValue(
    83  				tfdiags.Error,
    84  				"Invalid backend configuration",
    85  				"The configuration must be an object value.",
    86  				cty.GetAttrPath("config"),
    87  			))
    88  		}
    89  	}
    90  
    91  	{
    92  		defaultsTy := cfg.GetAttr("defaults").Type()
    93  		if defaultsTy != cty.DynamicPseudoType && !(defaultsTy.IsObjectType() || defaultsTy.IsMapType()) {
    94  			diags = diags.Append(tfdiags.AttributeValue(
    95  				tfdiags.Error,
    96  				"Invalid default values",
    97  				"Defaults must be given in an object value.",
    98  				cty.GetAttrPath("defaults"),
    99  			))
   100  		}
   101  	}
   102  
   103  	return diags
   104  }
   105  
   106  func dataSourceRemoteStateRead(d cty.Value, enc encryption.StateEncryption) (cty.Value, tfdiags.Diagnostics) {
   107  	var diags tfdiags.Diagnostics
   108  
   109  	b, cfg, moreDiags := getBackend(d, enc)
   110  	diags = diags.Append(moreDiags)
   111  	if moreDiags.HasErrors() {
   112  		return cty.NilVal, diags
   113  	}
   114  
   115  	configureDiags := b.Configure(cfg)
   116  	if configureDiags.HasErrors() {
   117  		diags = diags.Append(configureDiags.Err())
   118  		return cty.NilVal, diags
   119  	}
   120  
   121  	newState := make(map[string]cty.Value)
   122  	newState["backend"] = d.GetAttr("backend")
   123  	newState["config"] = d.GetAttr("config")
   124  
   125  	workspaceVal := d.GetAttr("workspace")
   126  	// This attribute is not computed, so we always have to store the state
   127  	// value, even if we implicitly use a default.
   128  	newState["workspace"] = workspaceVal
   129  
   130  	workspaceName := backend.DefaultStateName
   131  	if !workspaceVal.IsNull() {
   132  		workspaceName = workspaceVal.AsString()
   133  	}
   134  
   135  	state, err := b.StateMgr(workspaceName)
   136  	if err != nil {
   137  		diags = diags.Append(tfdiags.AttributeValue(
   138  			tfdiags.Error,
   139  			"Error loading state error",
   140  			fmt.Sprintf("error loading the remote state: %s", err),
   141  			cty.Path(nil).GetAttr("backend"),
   142  		))
   143  		return cty.NilVal, diags
   144  	}
   145  
   146  	if err := state.RefreshState(); err != nil {
   147  		diags = diags.Append(err)
   148  		return cty.NilVal, diags
   149  	}
   150  
   151  	outputs := make(map[string]cty.Value)
   152  
   153  	if defaultsVal := d.GetAttr("defaults"); !defaultsVal.IsNull() {
   154  		newState["defaults"] = defaultsVal
   155  		it := defaultsVal.ElementIterator()
   156  		for it.Next() {
   157  			k, v := it.Element()
   158  			outputs[k.AsString()] = v
   159  		}
   160  	} else {
   161  		newState["defaults"] = cty.NullVal(cty.DynamicPseudoType)
   162  	}
   163  
   164  	remoteState := state.State()
   165  	if remoteState == nil {
   166  		diags = diags.Append(tfdiags.AttributeValue(
   167  			tfdiags.Error,
   168  			"Unable to find remote state",
   169  			"No stored state was found for the given workspace in the given backend.",
   170  			cty.Path(nil).GetAttr("workspace"),
   171  		))
   172  		newState["outputs"] = cty.EmptyObjectVal
   173  		return cty.ObjectVal(newState), diags
   174  	}
   175  	mod := remoteState.RootModule()
   176  	if mod != nil { // should always have a root module in any valid state
   177  		for k, os := range mod.OutputValues {
   178  			outputs[k] = os.Value
   179  		}
   180  	}
   181  
   182  	newState["outputs"] = cty.ObjectVal(outputs)
   183  
   184  	return cty.ObjectVal(newState), diags
   185  }
   186  
   187  func getBackend(cfg cty.Value, enc encryption.StateEncryption) (backend.Backend, cty.Value, tfdiags.Diagnostics) {
   188  	var diags tfdiags.Diagnostics
   189  
   190  	backendType := cfg.GetAttr("backend").AsString()
   191  
   192  	// Don't break people using the old _local syntax - but note warning above
   193  	if backendType == "_local" {
   194  		log.Println(`[INFO] Switching old (unsupported) backend "_local" to "local"`)
   195  		backendType = "local"
   196  	}
   197  
   198  	// Create the client to access our remote state
   199  	log.Printf("[DEBUG] Initializing remote state backend: %s", backendType)
   200  	f := getBackendFactory(backendType)
   201  	if f == nil {
   202  		detail := fmt.Sprintf("There is no backend type named %q.", backendType)
   203  		if msg, removed := backendInit.RemovedBackends[backendType]; removed {
   204  			detail = msg
   205  		}
   206  
   207  		diags = diags.Append(tfdiags.AttributeValue(
   208  			tfdiags.Error,
   209  			"Invalid backend configuration",
   210  			detail,
   211  			cty.Path(nil).GetAttr("backend"),
   212  		))
   213  		return nil, cty.NilVal, diags
   214  	}
   215  	b := f(enc)
   216  
   217  	config := cfg.GetAttr("config")
   218  	if config.IsNull() {
   219  		// We'll treat this as an empty configuration and see if the backend's
   220  		// schema and validation code will accept it.
   221  		config = cty.EmptyObjectVal
   222  	}
   223  
   224  	if config.Type().IsMapType() { // The code below expects an object type, so we'll convert
   225  		config = cty.ObjectVal(config.AsValueMap())
   226  	}
   227  
   228  	schema := b.ConfigSchema()
   229  	// Try to coerce the provided value into the desired configuration type.
   230  	configVal, err := schema.CoerceValue(config)
   231  	if err != nil {
   232  		diags = diags.Append(tfdiags.AttributeValue(
   233  			tfdiags.Error,
   234  			"Invalid backend configuration",
   235  			fmt.Sprintf("The given configuration is not valid for backend %q: %s.", backendType,
   236  				tfdiags.FormatError(err)),
   237  			cty.Path(nil).GetAttr("config"),
   238  		))
   239  		return nil, cty.NilVal, diags
   240  	}
   241  
   242  	newVal, validateDiags := b.PrepareConfig(configVal)
   243  	diags = diags.Append(validateDiags)
   244  	if validateDiags.HasErrors() {
   245  		return nil, cty.NilVal, diags
   246  	}
   247  
   248  	// If this is the enhanced remote backend, we want to disable the version
   249  	// check, because this is a read-only operation
   250  	if rb, ok := b.(*remote.Remote); ok {
   251  		rb.IgnoreVersionConflict()
   252  	}
   253  
   254  	return b, newVal, diags
   255  }
   256  
   257  // overrideBackendFactories allows test cases to control the set of available
   258  // backends to allow for more self-contained tests. This should never be set
   259  // in non-test code.
   260  var overrideBackendFactories map[string]backend.InitFn
   261  
   262  func getBackendFactory(backendType string) backend.InitFn {
   263  	if len(overrideBackendFactories) > 0 {
   264  		// Tests may override the set of backend factories.
   265  		return overrideBackendFactories[backendType]
   266  	}
   267  
   268  	return backendInit.Backend(backendType)
   269  }