github.com/pulumi/terraform@v1.4.0/pkg/command/import.go (about)

     1  package command
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/hcl/v2"
    11  	"github.com/hashicorp/hcl/v2/hclsyntax"
    12  
    13  	"github.com/pulumi/terraform/pkg/addrs"
    14  	"github.com/pulumi/terraform/pkg/backend"
    15  	"github.com/pulumi/terraform/pkg/command/arguments"
    16  	"github.com/pulumi/terraform/pkg/command/views"
    17  	"github.com/pulumi/terraform/pkg/configs"
    18  	"github.com/pulumi/terraform/pkg/terraform"
    19  	"github.com/pulumi/terraform/pkg/tfdiags"
    20  )
    21  
    22  // ImportCommand is a cli.Command implementation that imports resources
    23  // into the Terraform state.
    24  type ImportCommand struct {
    25  	Meta
    26  }
    27  
    28  func (c *ImportCommand) Run(args []string) int {
    29  	// Get the pwd since its our default -config flag value
    30  	pwd, err := os.Getwd()
    31  	if err != nil {
    32  		c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    33  		return 1
    34  	}
    35  
    36  	var configPath string
    37  	args = c.Meta.process(args)
    38  
    39  	cmdFlags := c.Meta.extendedFlagSet("import")
    40  	cmdFlags.BoolVar(&c.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
    41  	cmdFlags.IntVar(&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
    42  	cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
    43  	cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
    44  	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
    45  	cmdFlags.StringVar(&configPath, "config", pwd, "path")
    46  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    47  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    48  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    49  	if err := cmdFlags.Parse(args); err != nil {
    50  		return 1
    51  	}
    52  
    53  	args = cmdFlags.Args()
    54  	if len(args) != 2 {
    55  		c.Ui.Error("The import command expects two arguments.")
    56  		cmdFlags.Usage()
    57  		return 1
    58  	}
    59  
    60  	var diags tfdiags.Diagnostics
    61  
    62  	// Parse the provided resource address.
    63  	traversalSrc := []byte(args[0])
    64  	traversal, travDiags := hclsyntax.ParseTraversalAbs(traversalSrc, "<import-address>", hcl.Pos{Line: 1, Column: 1})
    65  	diags = diags.Append(travDiags)
    66  	if travDiags.HasErrors() {
    67  		c.registerSynthConfigSource("<import-address>", traversalSrc) // so we can include a source snippet
    68  		c.showDiagnostics(diags)
    69  		c.Ui.Info(importCommandInvalidAddressReference)
    70  		return 1
    71  	}
    72  	addr, addrDiags := addrs.ParseAbsResourceInstance(traversal)
    73  	diags = diags.Append(addrDiags)
    74  	if addrDiags.HasErrors() {
    75  		c.registerSynthConfigSource("<import-address>", traversalSrc) // so we can include a source snippet
    76  		c.showDiagnostics(diags)
    77  		c.Ui.Info(importCommandInvalidAddressReference)
    78  		return 1
    79  	}
    80  
    81  	if addr.Resource.Resource.Mode != addrs.ManagedResourceMode {
    82  		diags = diags.Append(errors.New("A managed resource address is required. Importing into a data resource is not allowed."))
    83  		c.showDiagnostics(diags)
    84  		return 1
    85  	}
    86  
    87  	if !c.dirIsConfigPath(configPath) {
    88  		diags = diags.Append(&hcl.Diagnostic{
    89  			Severity: hcl.DiagError,
    90  			Summary:  "No Terraform configuration files",
    91  			Detail: fmt.Sprintf(
    92  				"The directory %s does not contain any Terraform configuration files (.tf or .tf.json). To specify a different configuration directory, use the -config=\"...\" command line option.",
    93  				configPath,
    94  			),
    95  		})
    96  		c.showDiagnostics(diags)
    97  		return 1
    98  	}
    99  
   100  	// Load the full config, so we can verify that the target resource is
   101  	// already configured.
   102  	config, configDiags := c.loadConfig(configPath)
   103  	diags = diags.Append(configDiags)
   104  	if configDiags.HasErrors() {
   105  		c.showDiagnostics(diags)
   106  		return 1
   107  	}
   108  
   109  	// Verify that the given address points to something that exists in config.
   110  	// This is to reduce the risk that a typo in the resource address will
   111  	// import something that Terraform will want to immediately destroy on
   112  	// the next plan, and generally acts as a reassurance of user intent.
   113  	targetConfig := config.DescendentForInstance(addr.Module)
   114  	if targetConfig == nil {
   115  		modulePath := addr.Module.String()
   116  		diags = diags.Append(&hcl.Diagnostic{
   117  			Severity: hcl.DiagError,
   118  			Summary:  "Import to non-existent module",
   119  			Detail: fmt.Sprintf(
   120  				"%s is not defined in the configuration. Please add configuration for this module before importing into it.",
   121  				modulePath,
   122  			),
   123  		})
   124  		c.showDiagnostics(diags)
   125  		return 1
   126  	}
   127  	targetMod := targetConfig.Module
   128  	rcs := targetMod.ManagedResources
   129  	var rc *configs.Resource
   130  	resourceRelAddr := addr.Resource.Resource
   131  	for _, thisRc := range rcs {
   132  		if resourceRelAddr.Type == thisRc.Type && resourceRelAddr.Name == thisRc.Name {
   133  			rc = thisRc
   134  			break
   135  		}
   136  	}
   137  	if rc == nil {
   138  		modulePath := addr.Module.String()
   139  		if modulePath == "" {
   140  			modulePath = "the root module"
   141  		}
   142  
   143  		c.showDiagnostics(diags)
   144  
   145  		// This is not a diagnostic because currently our diagnostics printer
   146  		// doesn't support having a code example in the detail, and there's
   147  		// a code example in this message.
   148  		// TODO: Improve the diagnostics printer so we can use it for this
   149  		// message.
   150  		c.Ui.Error(fmt.Sprintf(
   151  			importCommandMissingResourceFmt,
   152  			addr, modulePath, resourceRelAddr.Type, resourceRelAddr.Name,
   153  		))
   154  		return 1
   155  	}
   156  
   157  	// Check for user-supplied plugin path
   158  	if c.pluginPath, err = c.loadPluginPath(); err != nil {
   159  		c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
   160  		return 1
   161  	}
   162  
   163  	// Load the backend
   164  	b, backendDiags := c.Backend(&BackendOpts{
   165  		Config: config.Module.Backend,
   166  	})
   167  	diags = diags.Append(backendDiags)
   168  	if backendDiags.HasErrors() {
   169  		c.showDiagnostics(diags)
   170  		return 1
   171  	}
   172  
   173  	// We require a backend.Local to build a context.
   174  	// This isn't necessarily a "local.Local" backend, which provides local
   175  	// operations, however that is the only current implementation. A
   176  	// "local.Local" backend also doesn't necessarily provide local state, as
   177  	// that may be delegated to a "remotestate.Backend".
   178  	local, ok := b.(backend.Local)
   179  	if !ok {
   180  		c.Ui.Error(ErrUnsupportedLocalOp)
   181  		return 1
   182  	}
   183  
   184  	// Build the operation
   185  	opReq := c.Operation(b, arguments.ViewHuman)
   186  	opReq.ConfigDir = configPath
   187  	opReq.ConfigLoader, err = c.initConfigLoader()
   188  	if err != nil {
   189  		diags = diags.Append(err)
   190  		c.showDiagnostics(diags)
   191  		return 1
   192  	}
   193  	opReq.Hooks = []terraform.Hook{c.uiHook()}
   194  	{
   195  		var moreDiags tfdiags.Diagnostics
   196  		opReq.Variables, moreDiags = c.collectVariableValues()
   197  		diags = diags.Append(moreDiags)
   198  		if moreDiags.HasErrors() {
   199  			c.showDiagnostics(diags)
   200  			return 1
   201  		}
   202  	}
   203  	opReq.View = views.NewOperation(arguments.ViewHuman, c.RunningInAutomation, c.View)
   204  
   205  	// Check remote Terraform version is compatible
   206  	remoteVersionDiags := c.remoteVersionCheck(b, opReq.Workspace)
   207  	diags = diags.Append(remoteVersionDiags)
   208  	c.showDiagnostics(diags)
   209  	if diags.HasErrors() {
   210  		return 1
   211  	}
   212  
   213  	// Get the context
   214  	lr, state, ctxDiags := local.LocalRun(opReq)
   215  	diags = diags.Append(ctxDiags)
   216  	if ctxDiags.HasErrors() {
   217  		c.showDiagnostics(diags)
   218  		return 1
   219  	}
   220  
   221  	// Successfully creating the context can result in a lock, so ensure we release it
   222  	defer func() {
   223  		diags := opReq.StateLocker.Unlock()
   224  		if diags.HasErrors() {
   225  			c.showDiagnostics(diags)
   226  		}
   227  	}()
   228  
   229  	// Perform the import. Note that as you can see it is possible for this
   230  	// API to import more than one resource at once. For now, we only allow
   231  	// one while we stabilize this feature.
   232  	newState, importDiags := lr.Core.Import(lr.Config, lr.InputState, &terraform.ImportOpts{
   233  		Targets: []*terraform.ImportTarget{
   234  			{
   235  				Addr: addr,
   236  				ID:   args[1],
   237  			},
   238  		},
   239  
   240  		// The LocalRun idea is designed around our primary operations, so
   241  		// the input variables end up represented as plan options even though
   242  		// this particular operation isn't really a plan.
   243  		SetVariables: lr.PlanOpts.SetVariables,
   244  	})
   245  	diags = diags.Append(importDiags)
   246  	if diags.HasErrors() {
   247  		c.showDiagnostics(diags)
   248  		return 1
   249  	}
   250  
   251  	// Get schemas, if possible, before writing state
   252  	var schemas *terraform.Schemas
   253  	if isCloudMode(b) {
   254  		var schemaDiags tfdiags.Diagnostics
   255  		schemas, schemaDiags = c.MaybeGetSchemas(newState, nil)
   256  		diags = diags.Append(schemaDiags)
   257  	}
   258  
   259  	// Persist the final state
   260  	log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
   261  	if err := state.WriteState(newState); err != nil {
   262  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   263  		return 1
   264  	}
   265  	if err := state.PersistState(schemas); err != nil {
   266  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   267  		return 1
   268  	}
   269  
   270  	c.Ui.Output(c.Colorize().Color("[reset][green]\n" + importCommandSuccessMsg))
   271  
   272  	c.showDiagnostics(diags)
   273  	if diags.HasErrors() {
   274  		return 1
   275  	}
   276  
   277  	return 0
   278  }
   279  
   280  func (c *ImportCommand) Help() string {
   281  	helpText := `
   282  Usage: terraform [global options] import [options] ADDR ID
   283  
   284    Import existing infrastructure into your Terraform state.
   285  
   286    This will find and import the specified resource into your Terraform
   287    state, allowing existing infrastructure to come under Terraform
   288    management without having to be initially created by Terraform.
   289  
   290    The ADDR specified is the address to import the resource to. Please
   291    see the documentation online for resource addresses. The ID is a
   292    resource-specific ID to identify that resource being imported. Please
   293    reference the documentation for the resource type you're importing to
   294    determine the ID syntax to use. It typically matches directly to the ID
   295    that the provider uses.
   296  
   297    The current implementation of Terraform import can only import resources
   298    into the state. It does not generate configuration. A future version of
   299    Terraform will also generate configuration.
   300  
   301    Because of this, prior to running terraform import it is necessary to write
   302    a resource configuration block for the resource manually, to which the
   303    imported object will be attached.
   304  
   305    This command will not modify your infrastructure, but it will make
   306    network requests to inspect parts of your infrastructure relevant to
   307    the resource being imported.
   308  
   309  Options:
   310  
   311    -config=path            Path to a directory of Terraform configuration files
   312                            to use to configure the provider. Defaults to pwd.
   313                            If no config files are present, they must be provided
   314                            via the input prompts or env vars.
   315  
   316    -input=false            Disable interactive input prompts.
   317  
   318    -lock=false             Don't hold a state lock during the operation. This is
   319                            dangerous if others might concurrently run commands
   320                            against the same workspace.
   321  
   322    -lock-timeout=0s        Duration to retry a state lock.
   323  
   324    -no-color               If specified, output won't contain any color.
   325  
   326    -var 'foo=bar'          Set a variable in the Terraform configuration. This
   327                            flag can be set multiple times. This is only useful
   328                            with the "-config" flag.
   329  
   330    -var-file=foo           Set variables in the Terraform configuration from
   331                            a file. If "terraform.tfvars" or any ".auto.tfvars"
   332                            files are present, they will be automatically loaded.
   333  
   334    -ignore-remote-version  A rare option used for the remote backend only. See
   335                            the remote backend documentation for more information.
   336  
   337    -state, state-out, and -backup are legacy options supported for the local
   338    backend only. For more information, see the local backend's documentation.
   339  
   340  `
   341  	return strings.TrimSpace(helpText)
   342  }
   343  
   344  func (c *ImportCommand) Synopsis() string {
   345  	return "Associate existing infrastructure with a Terraform resource"
   346  }
   347  
   348  const importCommandInvalidAddressReference = `For information on valid syntax, see:
   349  https://www.terraform.io/docs/cli/state/resource-addressing.html`
   350  
   351  const importCommandMissingResourceFmt = `[reset][bold][red]Error:[reset][bold] resource address %q does not exist in the configuration.[reset]
   352  
   353  Before importing this resource, please create its configuration in %s. For example:
   354  
   355  resource %q %q {
   356    # (resource arguments)
   357  }
   358  `
   359  
   360  const importCommandSuccessMsg = `Import successful!
   361  
   362  The resources that were imported are shown above. These resources are now in
   363  your Terraform state and will henceforth be managed by Terraform.
   364  `