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 `