github.com/pgray/terraform@v0.5.4-0.20170822184730-b6a464c5214d/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  	})
   128  	if err != nil {
   129  		c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
   130  		return 1
   131  	}
   132  
   133  	// We require a backend.Local to build a context.
   134  	// This isn't necessarily a "local.Local" backend, which provides local
   135  	// operations, however that is the only current implementation. A
   136  	// "local.Local" backend also doesn't necessarily provide local state, as
   137  	// that may be delegated to a "remotestate.Backend".
   138  	local, ok := b.(backend.Local)
   139  	if !ok {
   140  		c.Ui.Error(ErrUnsupportedLocalOp)
   141  		return 1
   142  	}
   143  
   144  	// Build the operation
   145  	opReq := c.Operation()
   146  	opReq.Module = mod
   147  
   148  	// Get the context
   149  	ctx, state, err := local.Context(opReq)
   150  	if err != nil {
   151  		c.Ui.Error(err.Error())
   152  		return 1
   153  	}
   154  
   155  	// Perform the import. Note that as you can see it is possible for this
   156  	// API to import more than one resource at once. For now, we only allow
   157  	// one while we stabilize this feature.
   158  	newState, err := ctx.Import(&terraform.ImportOpts{
   159  		Targets: []*terraform.ImportTarget{
   160  			&terraform.ImportTarget{
   161  				Addr:     args[0],
   162  				ID:       args[1],
   163  				Provider: c.Meta.provider,
   164  			},
   165  		},
   166  	})
   167  	if err != nil {
   168  		c.Ui.Error(fmt.Sprintf("Error importing: %s", err))
   169  		return 1
   170  	}
   171  
   172  	// Persist the final state
   173  	log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
   174  	if err := state.WriteState(newState); err != nil {
   175  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   176  		return 1
   177  	}
   178  	if err := state.PersistState(); err != nil {
   179  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   180  		return 1
   181  	}
   182  
   183  	c.Ui.Output(c.Colorize().Color("[reset][green]\n" + importCommandSuccessMsg))
   184  
   185  	return 0
   186  }
   187  
   188  func (c *ImportCommand) Help() string {
   189  	helpText := `
   190  Usage: terraform import [options] ADDR ID
   191  
   192    Import existing infrastructure into your Terraform state.
   193  
   194    This will find and import the specified resource into your Terraform
   195    state, allowing existing infrastructure to come under Terraform
   196    management without having to be initially created by Terraform.
   197  
   198    The ADDR specified is the address to import the resource to. Please
   199    see the documentation online for resource addresses. The ID is a
   200    resource-specific ID to identify that resource being imported. Please
   201    reference the documentation for the resource type you're importing to
   202    determine the ID syntax to use. It typically matches directly to the ID
   203    that the provider uses.
   204  
   205    In the current state of Terraform import, the resource is only imported
   206    into your state file. Once it is imported, you must manually write
   207    configuration for the new resource or Terraform will mark it for destruction.
   208    Future versions of Terraform will expand the functionality of Terraform
   209    import.
   210  
   211    This command will not modify your infrastructure, but it will make
   212    network requests to inspect parts of your infrastructure relevant to
   213    the resource being imported.
   214  
   215  Options:
   216  
   217    -backup=path        Path to backup the existing state file before
   218                        modifying. Defaults to the "-state-out" path with
   219                        ".backup" extension. Set to "-" to disable backup.
   220  
   221    -config=path        Path to a directory of Terraform configuration files
   222                        to use to configure the provider. Defaults to pwd.
   223                        If no config files are present, they must be provided
   224                        via the input prompts or env vars.
   225  
   226    -input=true         Ask for input for variables if not directly set.
   227  
   228    -lock=true          Lock the state file when locking is supported.
   229  
   230    -lock-timeout=0s    Duration to retry a state lock.
   231  
   232    -no-color           If specified, output won't contain any color.
   233  
   234    -provider=provider  Specific provider to use for import. This is used for
   235                        specifying aliases, such as "aws.eu". Defaults to the
   236                        normal provider prefix of the resource being imported.
   237  
   238    -state=PATH         Path to the source state file. Defaults to the configured
   239                        backend, or "terraform.tfstate"
   240  
   241    -state-out=PATH     Path to the destination state file to write to. If this
   242                        isn't specified, the source state file will be used. This
   243                        can be a new or existing path.
   244  
   245    -var 'foo=bar'      Set a variable in the Terraform configuration. This
   246                        flag can be set multiple times. This is only useful
   247                        with the "-config" flag.
   248  
   249    -var-file=foo       Set variables in the Terraform configuration from
   250                        a file. If "terraform.tfvars" or any ".auto.tfvars"
   251                        files are present, they will be automatically loaded.
   252  
   253  
   254  `
   255  	return strings.TrimSpace(helpText)
   256  }
   257  
   258  func (c *ImportCommand) Synopsis() string {
   259  	return "Import existing infrastructure into Terraform"
   260  }
   261  
   262  const importCommandInvalidAddressFmt = `Error: %s
   263  
   264  For information on valid syntax, see:
   265  https://www.terraform.io/docs/internals/resource-addressing.html
   266  `
   267  
   268  const importCommandMissingResourceSpecMsg = `Error: resource address must include a full resource spec
   269  
   270  For information on valid syntax, see:
   271  https://www.terraform.io/docs/internals/resource-addressing.html
   272  `
   273  
   274  const importCommandResourceModeMsg = `Error: resource address must refer to a managed resource.
   275  
   276  Data resources cannot be imported.
   277  `
   278  
   279  const importCommandMissingConfigMsg = `Error: no configuration files in this directory.
   280  
   281  "terraform import" can only be run in a Terraform configuration directory.
   282  Create one or more .tf files in this directory to import here.
   283  `
   284  
   285  const importCommandMissingModuleFmt = `Error: %s does not exist in the configuration.
   286  
   287  Please add the configuration for the module before importing resources into it.
   288  `
   289  
   290  const importCommandMissingResourceFmt = `Error: resource address %q does not exist in the configuration.
   291  
   292  Before importing this resource, please create its configuration in %s. For example:
   293  
   294  resource %q %q {
   295    # (resource arguments)
   296  }
   297  `
   298  
   299  const importCommandSuccessMsg = `Import successful!
   300  
   301  The resources that were imported are shown above. These resources are now in
   302  your Terraform state and will henceforth be managed by Terraform.
   303  
   304  Import does not generate configuration, so the next step is to ensure that
   305  the resource configurations match the current (or desired) state of the
   306  imported resources. You can use the output from "terraform plan" to verify that
   307  the configuration is correct and complete.
   308  `