github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/command/import.go (about) 1 package command 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "os" 8 "strings" 9 10 "github.com/hashicorp/hcl/v2" 11 "github.com/hashicorp/hcl/v2/hclsyntax" 12 13 "github.com/muratcelep/terraform/not-internal/addrs" 14 "github.com/muratcelep/terraform/not-internal/backend" 15 "github.com/muratcelep/terraform/not-internal/command/arguments" 16 "github.com/muratcelep/terraform/not-internal/command/views" 17 "github.com/muratcelep/terraform/not-internal/configs" 18 "github.com/muratcelep/terraform/not-internal/terraform" 19 "github.com/muratcelep/terraform/not-internal/tfdiags" 20 ) 21 22 // ImportCommand is a cli.Command implementation that imports resources 23 // into the Terraform state. 24 type ImportCommand struct { 25 Meta 26 } 27 28 func (c *ImportCommand) Run(args []string) int { 29 // Get the pwd since its our default -config flag value 30 pwd, err := os.Getwd() 31 if err != nil { 32 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 33 return 1 34 } 35 36 var configPath string 37 args = c.Meta.process(args) 38 39 cmdFlags := c.Meta.extendedFlagSet("import") 40 cmdFlags.BoolVar(&c.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible") 41 cmdFlags.IntVar(&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") 42 cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") 43 cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") 44 cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") 45 cmdFlags.StringVar(&configPath, "config", pwd, "path") 46 cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") 47 cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") 48 cmdFlags.BoolVar(&c.Meta.allowMissingConfig, "allow-missing-config", false, "allow missing config") 49 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 50 if err := cmdFlags.Parse(args); err != nil { 51 return 1 52 } 53 54 args = cmdFlags.Args() 55 if len(args) != 2 { 56 c.Ui.Error("The import command expects two arguments.") 57 cmdFlags.Usage() 58 return 1 59 } 60 61 var diags tfdiags.Diagnostics 62 63 // Parse the provided resource address. 64 traversalSrc := []byte(args[0]) 65 traversal, travDiags := hclsyntax.ParseTraversalAbs(traversalSrc, "<import-address>", hcl.Pos{Line: 1, Column: 1}) 66 diags = diags.Append(travDiags) 67 if travDiags.HasErrors() { 68 c.registerSynthConfigSource("<import-address>", traversalSrc) // so we can include a source snippet 69 c.showDiagnostics(diags) 70 c.Ui.Info(importCommandInvalidAddressReference) 71 return 1 72 } 73 addr, addrDiags := addrs.ParseAbsResourceInstance(traversal) 74 diags = diags.Append(addrDiags) 75 if addrDiags.HasErrors() { 76 c.registerSynthConfigSource("<import-address>", traversalSrc) // so we can include a source snippet 77 c.showDiagnostics(diags) 78 c.Ui.Info(importCommandInvalidAddressReference) 79 return 1 80 } 81 82 if addr.Resource.Resource.Mode != addrs.ManagedResourceMode { 83 diags = diags.Append(errors.New("A managed resource address is required. Importing into a data resource is not allowed.")) 84 c.showDiagnostics(diags) 85 return 1 86 } 87 88 if !c.dirIsConfigPath(configPath) { 89 diags = diags.Append(&hcl.Diagnostic{ 90 Severity: hcl.DiagError, 91 Summary: "No Terraform configuration files", 92 Detail: fmt.Sprintf( 93 "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.", 94 configPath, 95 ), 96 }) 97 c.showDiagnostics(diags) 98 return 1 99 } 100 101 // Load the full config, so we can verify that the target resource is 102 // already configured. 103 config, configDiags := c.loadConfig(configPath) 104 diags = diags.Append(configDiags) 105 if configDiags.HasErrors() { 106 c.showDiagnostics(diags) 107 return 1 108 } 109 110 // Verify that the given address points to something that exists in config. 111 // This is to reduce the risk that a typo in the resource address will 112 // import something that Terraform will want to immediately destroy on 113 // the next plan, and generally acts as a reassurance of user intent. 114 targetConfig := config.DescendentForInstance(addr.Module) 115 if targetConfig == nil { 116 modulePath := addr.Module.String() 117 diags = diags.Append(&hcl.Diagnostic{ 118 Severity: hcl.DiagError, 119 Summary: "Import to non-existent module", 120 Detail: fmt.Sprintf( 121 "%s is not defined in the configuration. Please add configuration for this module before importing into it.", 122 modulePath, 123 ), 124 }) 125 c.showDiagnostics(diags) 126 return 1 127 } 128 targetMod := targetConfig.Module 129 rcs := targetMod.ManagedResources 130 var rc *configs.Resource 131 resourceRelAddr := addr.Resource.Resource 132 for _, thisRc := range rcs { 133 if resourceRelAddr.Type == thisRc.Type && resourceRelAddr.Name == thisRc.Name { 134 rc = thisRc 135 break 136 } 137 } 138 if !c.Meta.allowMissingConfig && rc == nil { 139 modulePath := addr.Module.String() 140 if modulePath == "" { 141 modulePath = "the root module" 142 } 143 144 c.showDiagnostics(diags) 145 146 // This is not a diagnostic because currently our diagnostics printer 147 // doesn't support having a code example in the detail, and there's 148 // a code example in this message. 149 // TODO: Improve the diagnostics printer so we can use it for this 150 // message. 151 c.Ui.Error(fmt.Sprintf( 152 importCommandMissingResourceFmt, 153 addr, modulePath, resourceRelAddr.Type, resourceRelAddr.Name, 154 )) 155 return 1 156 } 157 158 // Check for user-supplied plugin path 159 if c.pluginPath, err = c.loadPluginPath(); err != nil { 160 c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err)) 161 return 1 162 } 163 164 // Load the backend 165 b, backendDiags := c.Backend(&BackendOpts{ 166 Config: config.Module.Backend, 167 }) 168 diags = diags.Append(backendDiags) 169 if backendDiags.HasErrors() { 170 c.showDiagnostics(diags) 171 return 1 172 } 173 174 // We require a backend.Local to build a context. 175 // This isn't necessarily a "local.Local" backend, which provides local 176 // operations, however that is the only current implementation. A 177 // "local.Local" backend also doesn't necessarily provide local state, as 178 // that may be delegated to a "remotestate.Backend". 179 local, ok := b.(backend.Local) 180 if !ok { 181 c.Ui.Error(ErrUnsupportedLocalOp) 182 return 1 183 } 184 185 // Build the operation 186 opReq := c.Operation(b) 187 opReq.ConfigDir = configPath 188 opReq.ConfigLoader, err = c.initConfigLoader() 189 if err != nil { 190 diags = diags.Append(err) 191 c.showDiagnostics(diags) 192 return 1 193 } 194 opReq.Hooks = []terraform.Hook{c.uiHook()} 195 { 196 var moreDiags tfdiags.Diagnostics 197 opReq.Variables, moreDiags = c.collectVariableValues() 198 diags = diags.Append(moreDiags) 199 if moreDiags.HasErrors() { 200 c.showDiagnostics(diags) 201 return 1 202 } 203 } 204 opReq.View = views.NewOperation(arguments.ViewHuman, c.RunningInAutomation, c.View) 205 206 // Check remote Terraform version is compatible 207 remoteVersionDiags := c.remoteVersionCheck(b, opReq.Workspace) 208 diags = diags.Append(remoteVersionDiags) 209 c.showDiagnostics(diags) 210 if diags.HasErrors() { 211 return 1 212 } 213 214 // Get the context 215 lr, state, ctxDiags := local.LocalRun(opReq) 216 diags = diags.Append(ctxDiags) 217 if ctxDiags.HasErrors() { 218 c.showDiagnostics(diags) 219 return 1 220 } 221 222 // Successfully creating the context can result in a lock, so ensure we release it 223 defer func() { 224 diags := opReq.StateLocker.Unlock() 225 if diags.HasErrors() { 226 c.showDiagnostics(diags) 227 } 228 }() 229 230 // Perform the import. Note that as you can see it is possible for this 231 // API to import more than one resource at once. For now, we only allow 232 // one while we stabilize this feature. 233 newState, importDiags := lr.Core.Import(lr.Config, lr.InputState, &terraform.ImportOpts{ 234 Targets: []*terraform.ImportTarget{ 235 { 236 Addr: addr, 237 ID: args[1], 238 }, 239 }, 240 241 // The LocalRun idea is designed around our primary operations, so 242 // the input variables end up represented as plan options even though 243 // this particular operation isn't really a plan. 244 SetVariables: lr.PlanOpts.SetVariables, 245 }) 246 diags = diags.Append(importDiags) 247 if diags.HasErrors() { 248 c.showDiagnostics(diags) 249 return 1 250 } 251 252 // Persist the final state 253 log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath()) 254 if err := state.WriteState(newState); err != nil { 255 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 256 return 1 257 } 258 if err := state.PersistState(); err != nil { 259 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 260 return 1 261 } 262 263 c.Ui.Output(c.Colorize().Color("[reset][green]\n" + importCommandSuccessMsg)) 264 265 if c.Meta.allowMissingConfig && rc == nil { 266 c.Ui.Output(c.Colorize().Color("[reset][yellow]\n" + importCommandAllowMissingResourceMsg)) 267 } 268 269 c.showDiagnostics(diags) 270 if diags.HasErrors() { 271 return 1 272 } 273 274 return 0 275 } 276 277 func (c *ImportCommand) Help() string { 278 helpText := ` 279 Usage: terraform [global options] import [options] ADDR ID 280 281 Import existing infrastructure into your Terraform state. 282 283 This will find and import the specified resource into your Terraform 284 state, allowing existing infrastructure to come under Terraform 285 management without having to be initially created by Terraform. 286 287 The ADDR specified is the address to import the resource to. Please 288 see the documentation online for resource addresses. The ID is a 289 resource-specific ID to identify that resource being imported. Please 290 reference the documentation for the resource type you're importing to 291 determine the ID syntax to use. It typically matches directly to the ID 292 that the provider uses. 293 294 The current implementation of Terraform import can only import resources 295 into the state. It does not generate configuration. A future version of 296 Terraform will also generate configuration. 297 298 Because of this, prior to running terraform import it is necessary to write 299 a resource configuration block for the resource manually, to which the 300 imported object will be attached. 301 302 This command will not modify your infrastructure, but it will make 303 network requests to inspect parts of your infrastructure relevant to 304 the resource being imported. 305 306 Options: 307 308 -config=path Path to a directory of Terraform configuration files 309 to use to configure the provider. Defaults to pwd. 310 If no config files are present, they must be provided 311 via the input prompts or env vars. 312 313 -allow-missing-config Allow import when no resource configuration block exists. 314 315 -input=false Disable interactive input prompts. 316 317 -lock=false Don't hold a state lock during the operation. This is 318 dangerous if others might concurrently run commands 319 against the same workspace. 320 321 -lock-timeout=0s Duration to retry a state lock. 322 323 -no-color If specified, output won't contain any color. 324 325 -var 'foo=bar' Set a variable in the Terraform configuration. This 326 flag can be set multiple times. This is only useful 327 with the "-config" flag. 328 329 -var-file=foo Set variables in the Terraform configuration from 330 a file. If "terraform.tfvars" or any ".auto.tfvars" 331 files are present, they will be automatically loaded. 332 333 -ignore-remote-version A rare option used for the remote backend only. See 334 the remote backend documentation for more information. 335 336 -state, state-out, and -backup are legacy options supported for the local 337 backend only. For more information, see the local backend's documentation. 338 339 ` 340 return strings.TrimSpace(helpText) 341 } 342 343 func (c *ImportCommand) Synopsis() string { 344 return "Associate existing infrastructure with a Terraform resource" 345 } 346 347 const importCommandInvalidAddressReference = `For information on valid syntax, see: 348 https://www.terraform.io/docs/cli/state/resource-addressing.html` 349 350 const importCommandMissingResourceFmt = `[reset][bold][red]Error:[reset][bold] resource address %q does not exist in the configuration.[reset] 351 352 Before importing this resource, please create its configuration in %s. For example: 353 354 resource %q %q { 355 # (resource arguments) 356 } 357 ` 358 359 const importCommandSuccessMsg = `Import successful! 360 361 The resources that were imported are shown above. These resources are now in 362 your Terraform state and will henceforth be managed by Terraform. 363 ` 364 365 const importCommandAllowMissingResourceMsg = `Import does not generate resource configuration, you must create a resource 366 configuration block that matches the current or desired state manually. 367 368 If there is no matching resource configuration block for the imported 369 resource, Terraform will delete the resource on the next "terraform apply". 370 It is recommended that you run "terraform plan" to verify that the 371 configuration is correct and complete. 372 `