github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/command/import.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/hcl2/hcl"
    10  
    11  	"github.com/hashicorp/terraform/backend"
    12  	"github.com/hashicorp/terraform/config"
    13  	"github.com/hashicorp/terraform/config/module"
    14  	"github.com/hashicorp/terraform/terraform"
    15  	"github.com/hashicorp/terraform/tfdiags"
    16  )
    17  
    18  // ImportCommand is a cli.Command implementation that imports resources
    19  // into the Terraform state.
    20  type ImportCommand struct {
    21  	Meta
    22  }
    23  
    24  func (c *ImportCommand) Run(args []string) int {
    25  	// Get the pwd since its our default -config flag value
    26  	pwd, err := os.Getwd()
    27  	if err != nil {
    28  		c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    29  		return 1
    30  	}
    31  
    32  	var configPath string
    33  	args, err = c.Meta.process(args, true)
    34  	if err != nil {
    35  		return 1
    36  	}
    37  
    38  	cmdFlags := c.Meta.flagSet("import")
    39  	cmdFlags.IntVar(&c.Meta.parallelism, "parallelism", 0, "parallelism")
    40  	cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
    41  	cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
    42  	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
    43  	cmdFlags.StringVar(&configPath, "config", pwd, "path")
    44  	cmdFlags.StringVar(&c.Meta.provider, "provider", "", "provider")
    45  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    46  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    47  	cmdFlags.BoolVar(&c.Meta.allowMissingConfig, "allow-missing-config", false, "allow missing config")
    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  	// Validate the provided resource address for syntax
    61  	addr, err := terraform.ParseResourceAddress(args[0])
    62  	if err != nil {
    63  		c.Ui.Error(fmt.Sprintf(importCommandInvalidAddressFmt, err))
    64  		return 1
    65  	}
    66  	if !addr.HasResourceSpec() {
    67  		// module.foo target isn't allowed for import
    68  		c.Ui.Error(importCommandMissingResourceSpecMsg)
    69  		return 1
    70  	}
    71  	if addr.Mode != config.ManagedResourceMode {
    72  		// can't import to a data resource address
    73  		c.Ui.Error(importCommandResourceModeMsg)
    74  		return 1
    75  	}
    76  
    77  	var diags tfdiags.Diagnostics
    78  
    79  	// Load the module
    80  	var mod *module.Tree
    81  	if configPath != "" {
    82  		if empty, _ := config.IsEmptyDir(configPath); empty {
    83  			diags = diags.Append(&hcl.Diagnostic{
    84  				Severity: hcl.DiagError,
    85  				Summary:  "No Terraform configuration files",
    86  				Detail: fmt.Sprintf(
    87  					"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.",
    88  					configPath,
    89  				),
    90  			})
    91  			c.showDiagnostics(diags)
    92  			return 1
    93  		}
    94  
    95  		var modDiags tfdiags.Diagnostics
    96  		mod, modDiags = c.Module(configPath)
    97  		diags = diags.Append(modDiags)
    98  		if modDiags.HasErrors() {
    99  			c.showDiagnostics(diags)
   100  			return 1
   101  		}
   102  	}
   103  
   104  	// Verify that the given address points to something that exists in config.
   105  	// This is to reduce the risk that a typo in the resource address will
   106  	// import something that Terraform will want to immediately destroy on
   107  	// the next plan, and generally acts as a reassurance of user intent.
   108  	targetMod := mod.Child(addr.Path)
   109  	if targetMod == nil {
   110  		modulePath := addr.WholeModuleAddress().String()
   111  		diags = diags.Append(&hcl.Diagnostic{
   112  			Severity: hcl.DiagError,
   113  			Summary:  "Import to non-existent module",
   114  			Detail: fmt.Sprintf(
   115  				"%s is not defined in the configuration. Please add configuration for this module before importing into it.",
   116  				modulePath,
   117  			),
   118  		})
   119  		c.showDiagnostics(diags)
   120  		return 1
   121  	}
   122  	rcs := targetMod.Config().Resources
   123  	var rc *config.Resource
   124  	for _, thisRc := range rcs {
   125  		if addr.MatchesConfig(targetMod, thisRc) {
   126  			rc = thisRc
   127  			break
   128  		}
   129  	}
   130  	if !c.Meta.allowMissingConfig && rc == nil {
   131  		modulePath := addr.WholeModuleAddress().String()
   132  		if modulePath == "" {
   133  			modulePath = "the root module"
   134  		}
   135  
   136  		c.showDiagnostics(diags)
   137  
   138  		// This is not a diagnostic because currently our diagnostics printer
   139  		// doesn't support having a code example in the detail, and there's
   140  		// a code example in this message.
   141  		// TODO: Improve the diagnostics printer so we can use it for this
   142  		// message.
   143  		c.Ui.Error(fmt.Sprintf(
   144  			importCommandMissingResourceFmt,
   145  			addr, modulePath, addr.Type, addr.Name,
   146  		))
   147  		return 1
   148  	}
   149  
   150  	// Check for user-supplied plugin path
   151  	if c.pluginPath, err = c.loadPluginPath(); err != nil {
   152  		c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
   153  		return 1
   154  	}
   155  
   156  	// Load the backend
   157  	b, err := c.Backend(&BackendOpts{
   158  		Config: mod.Config(),
   159  	})
   160  	if err != nil {
   161  		c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
   162  		return 1
   163  	}
   164  
   165  	// We require a backend.Local to build a context.
   166  	// This isn't necessarily a "local.Local" backend, which provides local
   167  	// operations, however that is the only current implementation. A
   168  	// "local.Local" backend also doesn't necessarily provide local state, as
   169  	// that may be delegated to a "remotestate.Backend".
   170  	local, ok := b.(backend.Local)
   171  	if !ok {
   172  		c.Ui.Error(ErrUnsupportedLocalOp)
   173  		return 1
   174  	}
   175  
   176  	// Build the operation
   177  	opReq := c.Operation()
   178  	opReq.Module = mod
   179  
   180  	// Get the context
   181  	ctx, state, err := local.Context(opReq)
   182  	if err != nil {
   183  		c.Ui.Error(err.Error())
   184  		return 1
   185  	}
   186  
   187  	// Perform the import. Note that as you can see it is possible for this
   188  	// API to import more than one resource at once. For now, we only allow
   189  	// one while we stabilize this feature.
   190  	newState, err := ctx.Import(&terraform.ImportOpts{
   191  		Targets: []*terraform.ImportTarget{
   192  			&terraform.ImportTarget{
   193  				Addr:     args[0],
   194  				ID:       args[1],
   195  				Provider: c.Meta.provider,
   196  			},
   197  		},
   198  	})
   199  	if err != nil {
   200  		diags = diags.Append(err)
   201  		c.showDiagnostics(diags)
   202  		return 1
   203  	}
   204  
   205  	// Persist the final state
   206  	log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
   207  	if err := state.WriteState(newState); err != nil {
   208  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   209  		return 1
   210  	}
   211  	if err := state.PersistState(); err != nil {
   212  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   213  		return 1
   214  	}
   215  
   216  	c.Ui.Output(c.Colorize().Color("[reset][green]\n" + importCommandSuccessMsg))
   217  
   218  	if c.Meta.allowMissingConfig && rc == nil {
   219  		c.Ui.Output(c.Colorize().Color("[reset][yellow]\n" + importCommandAllowMissingResourceMsg))
   220  	}
   221  
   222  	c.showDiagnostics(diags)
   223  	if diags.HasErrors() {
   224  		return 1
   225  	}
   226  
   227  	return 0
   228  }
   229  
   230  func (c *ImportCommand) Help() string {
   231  	helpText := `
   232  Usage: terraform import [options] ADDR ID
   233  
   234    Import existing infrastructure into your Terraform state.
   235  
   236    This will find and import the specified resource into your Terraform
   237    state, allowing existing infrastructure to come under Terraform
   238    management without having to be initially created by Terraform.
   239  
   240    The ADDR specified is the address to import the resource to. Please
   241    see the documentation online for resource addresses. The ID is a
   242    resource-specific ID to identify that resource being imported. Please
   243    reference the documentation for the resource type you're importing to
   244    determine the ID syntax to use. It typically matches directly to the ID
   245    that the provider uses.
   246  
   247    The current implementation of Terraform import can only import resources
   248    into the state. It does not generate configuration. A future version of
   249    Terraform will also generate configuration.
   250  
   251    Because of this, prior to running terraform import it is necessary to write
   252    a resource configuration block for the resource manually, to which the
   253    imported object will be attached.
   254  
   255    This command will not modify your infrastructure, but it will make
   256    network requests to inspect parts of your infrastructure relevant to
   257    the resource being imported.
   258  
   259  Options:
   260  
   261    -backup=path            Path to backup the existing state file before
   262                            modifying. Defaults to the "-state-out" path with
   263                            ".backup" extension. Set to "-" to disable backup.
   264  
   265    -config=path            Path to a directory of Terraform configuration files
   266                            to use to configure the provider. Defaults to pwd.
   267                            If no config files are present, they must be provided
   268                            via the input prompts or env vars.
   269  
   270    -allow-missing-config   Allow import when no resource configuration block exists.
   271  
   272    -input=true             Ask for input for variables if not directly set.
   273  
   274    -lock=true              Lock the state file when locking is supported.
   275  
   276    -lock-timeout=0s        Duration to retry a state lock.
   277  
   278    -no-color               If specified, output won't contain any color.
   279  
   280    -provider=provider      Specific provider to use for import. This is used for
   281                            specifying aliases, such as "aws.eu". Defaults to the
   282                            normal provider prefix of the resource being imported.
   283  
   284    -state=PATH             Path to the source state file. Defaults to the configured
   285                            backend, or "terraform.tfstate"
   286  
   287    -state-out=PATH         Path to the destination state file to write to. If this
   288                            isn't specified, the source state file will be used. This
   289                            can be a new or existing path.
   290  
   291    -var 'foo=bar'          Set a variable in the Terraform configuration. This
   292                            flag can be set multiple times. This is only useful
   293                            with the "-config" flag.
   294  
   295    -var-file=foo           Set variables in the Terraform configuration from
   296                            a file. If "terraform.tfvars" or any ".auto.tfvars"
   297                            files are present, they will be automatically loaded.
   298  
   299  
   300  `
   301  	return strings.TrimSpace(helpText)
   302  }
   303  
   304  func (c *ImportCommand) Synopsis() string {
   305  	return "Import existing infrastructure into Terraform"
   306  }
   307  
   308  const importCommandInvalidAddressFmt = `Error: %s
   309  
   310  For information on valid syntax, see:
   311  https://www.terraform.io/docs/internals/resource-addressing.html
   312  `
   313  
   314  const importCommandMissingResourceSpecMsg = `Error: resource address must include a full resource spec
   315  
   316  For information on valid syntax, see:
   317  https://www.terraform.io/docs/internals/resource-addressing.html
   318  `
   319  
   320  const importCommandResourceModeMsg = `Error: resource address must refer to a managed resource.
   321  
   322  Data resources cannot be imported.
   323  `
   324  
   325  const importCommandMissingResourceFmt = `[reset][bold][red]Error:[reset][bold] resource address %q does not exist in the configuration.[reset]
   326  
   327  Before importing this resource, please create its configuration in %s. For example:
   328  
   329  resource %q %q {
   330    # (resource arguments)
   331  }
   332  `
   333  
   334  const importCommandSuccessMsg = `Import successful!
   335  
   336  The resources that were imported are shown above. These resources are now in
   337  your Terraform state and will henceforth be managed by Terraform.
   338  `
   339  
   340  const importCommandAllowMissingResourceMsg = `Import does not generate resource configuration, you must create a resource
   341  configuration block that matches the current or desired state manually.
   342  
   343  If there is no matching resource configuration block for the imported
   344  resource, Terraform will delete the resource on the next "terraform apply".
   345  It is recommended that you run "terraform plan" to verify that the
   346  configuration is correct and complete.
   347  `