github.com/Jae-cisco/terraform@v0.11.12-beta1/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  	defer func() {
   188  		err := opReq.StateLocker.Unlock(nil)
   189  		if err != nil {
   190  			c.Ui.Error(err.Error())
   191  		}
   192  	}()
   193  
   194  	// Perform the import. Note that as you can see it is possible for this
   195  	// API to import more than one resource at once. For now, we only allow
   196  	// one while we stabilize this feature.
   197  	newState, err := ctx.Import(&terraform.ImportOpts{
   198  		Targets: []*terraform.ImportTarget{
   199  			&terraform.ImportTarget{
   200  				Addr:     args[0],
   201  				ID:       args[1],
   202  				Provider: c.Meta.provider,
   203  			},
   204  		},
   205  	})
   206  	if err != nil {
   207  		diags = diags.Append(err)
   208  		c.showDiagnostics(diags)
   209  		return 1
   210  	}
   211  
   212  	// Persist the final state
   213  	log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
   214  	if err := state.WriteState(newState); err != nil {
   215  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   216  		return 1
   217  	}
   218  	if err := state.PersistState(); err != nil {
   219  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   220  		return 1
   221  	}
   222  
   223  	c.Ui.Output(c.Colorize().Color("[reset][green]\n" + importCommandSuccessMsg))
   224  
   225  	if c.Meta.allowMissingConfig && rc == nil {
   226  		c.Ui.Output(c.Colorize().Color("[reset][yellow]\n" + importCommandAllowMissingResourceMsg))
   227  	}
   228  
   229  	c.showDiagnostics(diags)
   230  	if diags.HasErrors() {
   231  		return 1
   232  	}
   233  
   234  	return 0
   235  }
   236  
   237  func (c *ImportCommand) Help() string {
   238  	helpText := `
   239  Usage: terraform import [options] ADDR ID
   240  
   241    Import existing infrastructure into your Terraform state.
   242  
   243    This will find and import the specified resource into your Terraform
   244    state, allowing existing infrastructure to come under Terraform
   245    management without having to be initially created by Terraform.
   246  
   247    The ADDR specified is the address to import the resource to. Please
   248    see the documentation online for resource addresses. The ID is a
   249    resource-specific ID to identify that resource being imported. Please
   250    reference the documentation for the resource type you're importing to
   251    determine the ID syntax to use. It typically matches directly to the ID
   252    that the provider uses.
   253  
   254    The current implementation of Terraform import can only import resources
   255    into the state. It does not generate configuration. A future version of
   256    Terraform will also generate configuration.
   257  
   258    Because of this, prior to running terraform import it is necessary to write
   259    a resource configuration block for the resource manually, to which the
   260    imported object will be attached.
   261  
   262    This command will not modify your infrastructure, but it will make
   263    network requests to inspect parts of your infrastructure relevant to
   264    the resource being imported.
   265  
   266  Options:
   267  
   268    -backup=path            Path to backup the existing state file before
   269                            modifying. Defaults to the "-state-out" path with
   270                            ".backup" extension. Set to "-" to disable backup.
   271  
   272    -config=path            Path to a directory of Terraform configuration files
   273                            to use to configure the provider. Defaults to pwd.
   274                            If no config files are present, they must be provided
   275                            via the input prompts or env vars.
   276  
   277    -allow-missing-config   Allow import when no resource configuration block exists.
   278  
   279    -input=true             Ask for input for variables if not directly set.
   280  
   281    -lock=true              Lock the state file when locking is supported.
   282  
   283    -lock-timeout=0s        Duration to retry a state lock.
   284  
   285    -no-color               If specified, output won't contain any color.
   286  
   287    -provider=provider      Specific provider to use for import. This is used for
   288                            specifying aliases, such as "aws.eu". Defaults to the
   289                            normal provider prefix of the resource being imported.
   290  
   291    -state=PATH             Path to the source state file. Defaults to the configured
   292                            backend, or "terraform.tfstate"
   293  
   294    -state-out=PATH         Path to the destination state file to write to. If this
   295                            isn't specified, the source state file will be used. This
   296                            can be a new or existing path.
   297  
   298    -var 'foo=bar'          Set a variable in the Terraform configuration. This
   299                            flag can be set multiple times. This is only useful
   300                            with the "-config" flag.
   301  
   302    -var-file=foo           Set variables in the Terraform configuration from
   303                            a file. If "terraform.tfvars" or any ".auto.tfvars"
   304                            files are present, they will be automatically loaded.
   305  
   306  
   307  `
   308  	return strings.TrimSpace(helpText)
   309  }
   310  
   311  func (c *ImportCommand) Synopsis() string {
   312  	return "Import existing infrastructure into Terraform"
   313  }
   314  
   315  const importCommandInvalidAddressFmt = `Error: %s
   316  
   317  For information on valid syntax, see:
   318  https://www.terraform.io/docs/internals/resource-addressing.html
   319  `
   320  
   321  const importCommandMissingResourceSpecMsg = `Error: resource address must include a full resource spec
   322  
   323  For information on valid syntax, see:
   324  https://www.terraform.io/docs/internals/resource-addressing.html
   325  `
   326  
   327  const importCommandResourceModeMsg = `Error: resource address must refer to a managed resource.
   328  
   329  Data resources cannot be imported.
   330  `
   331  
   332  const importCommandMissingResourceFmt = `[reset][bold][red]Error:[reset][bold] resource address %q does not exist in the configuration.[reset]
   333  
   334  Before importing this resource, please create its configuration in %s. For example:
   335  
   336  resource %q %q {
   337    # (resource arguments)
   338  }
   339  `
   340  
   341  const importCommandSuccessMsg = `Import successful!
   342  
   343  The resources that were imported are shown above. These resources are now in
   344  your Terraform state and will henceforth be managed by Terraform.
   345  `
   346  
   347  const importCommandAllowMissingResourceMsg = `Import does not generate resource configuration, you must create a resource
   348  configuration block that matches the current or desired state manually.
   349  
   350  If there is no matching resource configuration block for the imported
   351  resource, Terraform will delete the resource on the next "terraform apply".
   352  It is recommended that you run "terraform plan" to verify that the
   353  configuration is correct and complete.
   354  `