github.com/sl1pm4t/terraform@v0.6.4-0.20170725213156-870617d22df3/command/import.go (about)

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