github.com/hooklift/terraform@v0.11.0-beta1.0.20171117000744-6786c1361ffe/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.BoolVar(&c.Meta.allowMissingConfig, "allow-missing-config", false, "allow missing config")
    45  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    46  	if err := cmdFlags.Parse(args); err != nil {
    47  		return 1
    48  	}
    49  
    50  	args = cmdFlags.Args()
    51  	if len(args) != 2 {
    52  		c.Ui.Error("The import command expects two arguments.")
    53  		cmdFlags.Usage()
    54  		return 1
    55  	}
    56  
    57  	// Validate the provided resource address for syntax
    58  	addr, err := terraform.ParseResourceAddress(args[0])
    59  	if err != nil {
    60  		c.Ui.Error(fmt.Sprintf(importCommandInvalidAddressFmt, err))
    61  		return 1
    62  	}
    63  	if !addr.HasResourceSpec() {
    64  		// module.foo target isn't allowed for import
    65  		c.Ui.Error(importCommandMissingResourceSpecMsg)
    66  		return 1
    67  	}
    68  	if addr.Mode != config.ManagedResourceMode {
    69  		// can't import to a data resource address
    70  		c.Ui.Error(importCommandResourceModeMsg)
    71  		return 1
    72  	}
    73  
    74  	// Load the module
    75  	var mod *module.Tree
    76  	if configPath != "" {
    77  		var err error
    78  		mod, err = c.Module(configPath)
    79  		if err != nil {
    80  			c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err))
    81  			return 1
    82  		}
    83  	}
    84  
    85  	// Verify that the given address points to something that exists in config.
    86  	// This is to reduce the risk that a typo in the resource address will
    87  	// import something that Terraform will want to immediately destroy on
    88  	// the next plan, and generally acts as a reassurance of user intent.
    89  	targetMod := mod.Child(addr.Path)
    90  	if targetMod == nil {
    91  		modulePath := addr.WholeModuleAddress().String()
    92  		if modulePath == "" {
    93  			c.Ui.Error(importCommandMissingConfigMsg)
    94  		} else {
    95  			c.Ui.Error(fmt.Sprintf(importCommandMissingModuleFmt, modulePath))
    96  		}
    97  		return 1
    98  	}
    99  	rcs := targetMod.Config().Resources
   100  	var rc *config.Resource
   101  	for _, thisRc := range rcs {
   102  		if addr.MatchesConfig(targetMod, thisRc) {
   103  			rc = thisRc
   104  			break
   105  		}
   106  	}
   107  	if !c.Meta.allowMissingConfig && rc == nil {
   108  		modulePath := addr.WholeModuleAddress().String()
   109  		if modulePath == "" {
   110  			modulePath = "the root module"
   111  		}
   112  		c.Ui.Error(fmt.Sprintf(
   113  			importCommandMissingResourceFmt,
   114  			addr, modulePath, addr.Type, addr.Name,
   115  		))
   116  		return 1
   117  	}
   118  
   119  	// Check for user-supplied plugin path
   120  	if c.pluginPath, err = c.loadPluginPath(); err != nil {
   121  		c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
   122  		return 1
   123  	}
   124  
   125  	// Load the backend
   126  	b, err := c.Backend(&BackendOpts{
   127  		Config: mod.Config(),
   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 backend.Local to build a context.
   135  	// This isn't necessarily a "local.Local" backend, which provides local
   136  	// operations, however that is the only current implementation. A
   137  	// "local.Local" backend also doesn't necessarily provide local state, as
   138  	// that may be delegated to a "remotestate.Backend".
   139  	local, ok := b.(backend.Local)
   140  	if !ok {
   141  		c.Ui.Error(ErrUnsupportedLocalOp)
   142  		return 1
   143  	}
   144  
   145  	// Build the operation
   146  	opReq := c.Operation()
   147  	opReq.Module = mod
   148  
   149  	// Get the context
   150  	ctx, state, err := local.Context(opReq)
   151  	if err != nil {
   152  		c.Ui.Error(err.Error())
   153  		return 1
   154  	}
   155  
   156  	// Perform the import. Note that as you can see it is possible for this
   157  	// API to import more than one resource at once. For now, we only allow
   158  	// one while we stabilize this feature.
   159  	newState, err := ctx.Import(&terraform.ImportOpts{
   160  		Targets: []*terraform.ImportTarget{
   161  			&terraform.ImportTarget{
   162  				Addr:     args[0],
   163  				ID:       args[1],
   164  				Provider: c.Meta.provider,
   165  			},
   166  		},
   167  	})
   168  	if err != nil {
   169  		c.Ui.Error(fmt.Sprintf("Error importing: %s", err))
   170  		return 1
   171  	}
   172  
   173  	// Persist the final state
   174  	log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
   175  	if err := state.WriteState(newState); err != nil {
   176  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   177  		return 1
   178  	}
   179  	if err := state.PersistState(); err != nil {
   180  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   181  		return 1
   182  	}
   183  
   184  	c.Ui.Output(c.Colorize().Color("[reset][green]\n" + importCommandSuccessMsg))
   185  
   186  	if c.Meta.allowMissingConfig && rc == nil {
   187  		c.Ui.Output(c.Colorize().Color("[reset][yellow]\n" + importCommandAllowMissingResourceMsg))
   188  	}
   189  
   190  	return 0
   191  }
   192  
   193  func (c *ImportCommand) Help() string {
   194  	helpText := `
   195  Usage: terraform import [options] ADDR ID
   196  
   197    Import existing infrastructure into your Terraform state.
   198  
   199    This will find and import the specified resource into your Terraform
   200    state, allowing existing infrastructure to come under Terraform
   201    management without having to be initially created by Terraform.
   202  
   203    The ADDR specified is the address to import the resource to. Please
   204    see the documentation online for resource addresses. The ID is a
   205    resource-specific ID to identify that resource being imported. Please
   206    reference the documentation for the resource type you're importing to
   207    determine the ID syntax to use. It typically matches directly to the ID
   208    that the provider uses.
   209  
   210    The current implementation of Terraform import can only import resources
   211    into the state. It does not generate configuration. A future version of
   212    Terraform will also generate configuration.
   213  
   214    Because of this, prior to running terraform import it is necessary to write
   215    a resource configuration block for the resource manually, to which the
   216    imported object will be attached.
   217  
   218    This command will not modify your infrastructure, but it will make
   219    network requests to inspect parts of your infrastructure relevant to
   220    the resource being imported.
   221  
   222  Options:
   223  
   224    -backup=path            Path to backup the existing state file before
   225                            modifying. Defaults to the "-state-out" path with
   226                            ".backup" extension. Set to "-" to disable backup.
   227  
   228    -config=path            Path to a directory of Terraform configuration files
   229                            to use to configure the provider. Defaults to pwd.
   230                            If no config files are present, they must be provided
   231                            via the input prompts or env vars.
   232  
   233    -allow-missing-config   Allow import when no resource configuration block exists.
   234  
   235    -input=true             Ask for input for variables if not directly set.
   236  
   237    -lock=true              Lock the state file when locking is supported.
   238  
   239    -lock-timeout=0s        Duration to retry a state lock.
   240  
   241    -no-color               If specified, output won't contain any color.
   242  
   243    -provider=provider      Specific provider to use for import. This is used for
   244                            specifying aliases, such as "aws.eu". Defaults to the
   245                            normal provider prefix of the resource being imported.
   246  
   247    -state=PATH             Path to the source state file. Defaults to the configured
   248                            backend, or "terraform.tfstate"
   249  
   250    -state-out=PATH         Path to the destination state file to write to. If this
   251                            isn't specified, the source state file will be used. This
   252                            can be a new or existing path.
   253  
   254    -var 'foo=bar'          Set a variable in the Terraform configuration. This
   255                            flag can be set multiple times. This is only useful
   256                            with the "-config" flag.
   257  
   258    -var-file=foo           Set variables in the Terraform configuration from
   259                            a file. If "terraform.tfvars" or any ".auto.tfvars"
   260                            files are present, they will be automatically loaded.
   261  
   262  
   263  `
   264  	return strings.TrimSpace(helpText)
   265  }
   266  
   267  func (c *ImportCommand) Synopsis() string {
   268  	return "Import existing infrastructure into Terraform"
   269  }
   270  
   271  const importCommandInvalidAddressFmt = `Error: %s
   272  
   273  For information on valid syntax, see:
   274  https://www.terraform.io/docs/internals/resource-addressing.html
   275  `
   276  
   277  const importCommandMissingResourceSpecMsg = `Error: resource address must include a full resource spec
   278  
   279  For information on valid syntax, see:
   280  https://www.terraform.io/docs/internals/resource-addressing.html
   281  `
   282  
   283  const importCommandResourceModeMsg = `Error: resource address must refer to a managed resource.
   284  
   285  Data resources cannot be imported.
   286  `
   287  
   288  const importCommandMissingConfigMsg = `Error: no configuration files in this directory.
   289  
   290  "terraform import" can only be run in a Terraform configuration directory.
   291  Create one or more .tf files in this directory to import here.
   292  `
   293  
   294  const importCommandMissingModuleFmt = `Error: %s does not exist in the configuration.
   295  
   296  Please add the configuration for the module before importing resources into it.
   297  `
   298  
   299  const importCommandMissingResourceFmt = `Error: resource address %q does not exist in the configuration.
   300  
   301  Before importing this resource, please create its configuration in %s. For example:
   302  
   303  resource %q %q {
   304    # (resource arguments)
   305  }
   306  `
   307  
   308  const importCommandSuccessMsg = `Import successful!
   309  
   310  The resources that were imported are shown above. These resources are now in
   311  your Terraform state and will henceforth be managed by Terraform.
   312  `
   313  
   314  const importCommandAllowMissingResourceMsg = `Import does not generate resource configuration, you must create a resource
   315  configuration block that matches the current or desired state manually.
   316  
   317  If there is no matching resource configuration block for the imported
   318  resource, Terraform will delete the resource on the next "terraform apply".
   319  It is recommended that you run "terraform plan" to verify that the
   320  configuration is correct and complete.
   321  `