github.com/posener/terraform@v0.11.0-beta1.0.20171103235147-645df36af025/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 `