github.com/rebelkathi/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 `