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