github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/builtin/providers/terraform/data_source_state.go (about)

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