github.com/GoogleCloudPlatform/terraformer@v0.8.18/cmd/import.go (about) 1 // Copyright 2018 The Terraformer Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package cmd 15 16 import ( 17 "fmt" 18 "io/ioutil" 19 "log" 20 "os" 21 "sort" 22 "strings" 23 "sync" 24 25 "github.com/GoogleCloudPlatform/terraformer/terraformutils/terraformerstring" 26 27 "github.com/GoogleCloudPlatform/terraformer/terraformutils/providerwrapper" 28 29 "github.com/spf13/pflag" 30 31 "github.com/GoogleCloudPlatform/terraformer/terraformutils" 32 "github.com/GoogleCloudPlatform/terraformer/terraformutils/terraformoutput" 33 34 "github.com/spf13/cobra" 35 ) 36 37 type ImportOptions struct { 38 Resources []string 39 Excludes []string 40 PathPattern string 41 PathOutput string 42 State string 43 Bucket string 44 Profile string 45 Verbose bool 46 Zone string 47 Regions []string 48 Projects []string 49 ResourceGroup string 50 Connect bool 51 Compact bool 52 Filter []string 53 Plan bool `json:"-"` 54 Output string 55 RetryCount int 56 RetrySleepMs int 57 } 58 59 const DefaultPathPattern = "{output}/{provider}/{service}/" 60 const DefaultPathOutput = "generated" 61 const DefaultState = "local" 62 63 func newImportCmd() *cobra.Command { 64 options := ImportOptions{} 65 cmd := &cobra.Command{ 66 Use: "import", 67 Short: "Import current state to Terraform configuration", 68 Long: "Import current state to Terraform configuration", 69 SilenceUsage: true, 70 SilenceErrors: false, 71 //Version: version.String(), 72 } 73 74 cmd.AddCommand(newCmdPlanImporter(options)) 75 for _, subcommand := range providerImporterSubcommands() { 76 providerCommand := subcommand(options) 77 _ = providerCommand.MarkPersistentFlagRequired("resources") 78 cmd.AddCommand(providerCommand) 79 } 80 return cmd 81 } 82 83 func Import(provider terraformutils.ProviderGenerator, options ImportOptions, args []string) error { 84 85 providerWrapper, options, err := initOptionsAndWrapper(provider, options, args) 86 if err != nil { 87 return err 88 } 89 defer providerWrapper.Kill() 90 providerMapping := terraformutils.NewProvidersMapping(provider) 91 92 err = initAllServicesResources(providerMapping, options, args, providerWrapper) 93 if err != nil { 94 return err 95 } 96 97 err = terraformutils.RefreshResourcesByProvider(providerMapping, providerWrapper) 98 if err != nil { 99 return err 100 } 101 102 providerMapping.ConvertTFStates(providerWrapper) 103 // change structs with additional data for each resource 104 providerMapping.CleanupProviders() 105 106 err = importFromPlan(providerMapping, options, args) 107 108 return err 109 } 110 111 func initOptionsAndWrapper(provider terraformutils.ProviderGenerator, options ImportOptions, args []string) (*providerwrapper.ProviderWrapper, ImportOptions, error) { 112 err := provider.Init(args) 113 if err != nil { 114 return nil, options, err 115 } 116 117 if terraformerstring.ContainsString(options.Resources, "*") { 118 log.Println("Attempting an import of ALL resources in " + provider.GetName()) 119 options.Resources = providerServices(provider) 120 } 121 122 if options.Excludes != nil { 123 localSlice := []string{} 124 for _, r := range options.Resources { 125 remove := false 126 for _, e := range options.Excludes { 127 if r == e { 128 remove = true 129 log.Println("Excluding resource " + e) 130 } 131 } 132 if !remove { 133 localSlice = append(localSlice, r) 134 } 135 } 136 options.Resources = localSlice 137 } 138 139 providerWrapper, err := providerwrapper.NewProviderWrapper(provider.GetName(), provider.GetConfig(), options.Verbose, map[string]int{"retryCount": options.RetryCount, "retrySleepMs": options.RetrySleepMs}) 140 if err != nil { 141 return nil, options, err 142 } 143 144 return providerWrapper, options, nil 145 } 146 147 func initAllServicesResources(providersMapping *terraformutils.ProvidersMapping, options ImportOptions, args []string, providerWrapper *providerwrapper.ProviderWrapper) error { 148 numOfResources := len(options.Resources) 149 var wg sync.WaitGroup 150 wg.Add(numOfResources) 151 152 var failedServices []string 153 154 for _, service := range options.Resources { 155 serviceProvider := providersMapping.AddServiceToProvider(service) 156 err := serviceProvider.Init(args) 157 if err != nil { 158 return err 159 } 160 err = initServiceResources(service, serviceProvider, options, providerWrapper) 161 if err != nil { 162 failedServices = append(failedServices, service) 163 } 164 } 165 166 // remove providers that failed to init their service 167 providersMapping.RemoveServices(failedServices) 168 providersMapping.ProcessResources(false) 169 170 return nil 171 } 172 173 func importFromPlan(providerMapping *terraformutils.ProvidersMapping, options ImportOptions, args []string) error { 174 plan := &ImportPlan{ 175 Provider: providerMapping.GetBaseProvider().GetName(), 176 Options: options, 177 Args: args, 178 ImportedResource: map[string][]terraformutils.Resource{}, 179 } 180 181 resourcesByService := providerMapping.GetResourcesByService() 182 for service := range resourcesByService { 183 plan.ImportedResource[service] = append(plan.ImportedResource[service], resourcesByService[service]...) 184 } 185 186 if options.Plan { 187 path := Path(options.PathPattern, providerMapping.GetBaseProvider().GetName(), "terraformer", options.PathOutput) 188 return ExportPlanFile(plan, path, "plan.json") 189 } 190 191 return ImportFromPlan(providerMapping.GetBaseProvider(), plan) 192 } 193 194 func initServiceResources(service string, provider terraformutils.ProviderGenerator, 195 options ImportOptions, providerWrapper *providerwrapper.ProviderWrapper) error { 196 log.Println(provider.GetName() + " importing... " + service) 197 err := provider.InitService(service, options.Verbose) 198 if err != nil { 199 log.Printf("%s error importing %s, err: %s\n", provider.GetName(), service, err) 200 return err 201 } 202 provider.GetService().ParseFilters(options.Filter) 203 err = provider.GetService().InitResources() 204 if err != nil { 205 log.Printf("%s error initializing resources in service %s, err: %s\n", provider.GetName(), service, err) 206 return err 207 } 208 209 provider.GetService().PopulateIgnoreKeys(providerWrapper) 210 provider.GetService().InitialCleanup() 211 log.Println(provider.GetName() + " done importing " + service) 212 213 return nil 214 } 215 216 func ImportFromPlan(provider terraformutils.ProviderGenerator, plan *ImportPlan) error { 217 options := plan.Options 218 importedResource := plan.ImportedResource 219 isServicePath := strings.Contains(options.PathPattern, "{service}") 220 221 if options.Connect { 222 log.Println(provider.GetName() + " Connecting.... ") 223 importedResource = terraformutils.ConnectServices(importedResource, isServicePath, provider.GetResourceConnections()) 224 } 225 226 if !isServicePath { 227 var compactedResources []terraformutils.Resource 228 for _, resources := range importedResource { 229 compactedResources = append(compactedResources, resources...) 230 } 231 e := printService(provider, "", options, compactedResources, importedResource) 232 if e != nil { 233 return e 234 } 235 } else { 236 for serviceName, resources := range importedResource { 237 e := printService(provider, serviceName, options, resources, importedResource) 238 if e != nil { 239 return e 240 } 241 } 242 } 243 return nil 244 } 245 246 func printService(provider terraformutils.ProviderGenerator, serviceName string, options ImportOptions, resources []terraformutils.Resource, importedResource map[string][]terraformutils.Resource) error { 247 log.Println(provider.GetName() + " save " + serviceName) 248 // Print HCL files for Resources 249 path := Path(options.PathPattern, provider.GetName(), serviceName, options.PathOutput) 250 err := terraformoutput.OutputHclFiles(resources, provider, path, serviceName, options.Compact, options.Output) 251 if err != nil { 252 return err 253 } 254 tfStateFile, err := terraformutils.PrintTfState(resources) 255 if err != nil { 256 return err 257 } 258 // print or upload State file 259 if options.State == "bucket" { 260 log.Println(provider.GetName() + " upload tfstate to bucket " + options.Bucket) 261 bucket := terraformoutput.BucketState{ 262 Name: options.Bucket, 263 } 264 if err := bucket.BucketUpload(path, tfStateFile); err != nil { 265 return err 266 } 267 // create Bucket file 268 if bucketStateDataFile, err := terraformutils.Print(bucket.BucketGetTfData(path), map[string]struct{}{}, options.Output); err == nil { 269 terraformoutput.PrintFile(path+"/bucket.tf", bucketStateDataFile) 270 } 271 } else { 272 if serviceName == "" { 273 log.Println(provider.GetName() + " save tfstate") 274 } else { 275 log.Println(provider.GetName() + " save tfstate for " + serviceName) 276 } 277 if err := ioutil.WriteFile(path+"/terraform.tfstate", tfStateFile, os.ModePerm); err != nil { 278 return err 279 } 280 } 281 // Print hcl variables.tf 282 if serviceName != "" { 283 if options.Connect && len(provider.GetResourceConnections()[serviceName]) > 0 { 284 variables := map[string]map[string]map[string]interface{}{} 285 variables["data"] = map[string]map[string]interface{}{} 286 variables["data"]["terraform_remote_state"] = map[string]interface{}{} 287 if options.State == "bucket" { 288 bucket := terraformoutput.BucketState{ 289 Name: options.Bucket, 290 } 291 for k := range provider.GetResourceConnections()[serviceName] { 292 if _, exist := importedResource[k]; !exist { 293 continue 294 } 295 variables["data"]["terraform_remote_state"][k] = map[string]interface{}{ 296 "backend": "gcs", 297 "config": bucket.BucketGetTfData(strings.ReplaceAll(path, serviceName, k)), 298 } 299 } 300 } else { 301 for k := range provider.GetResourceConnections()[serviceName] { 302 if _, exist := importedResource[k]; !exist { 303 continue 304 } 305 variables["data"]["terraform_remote_state"][k] = map[string]interface{}{ 306 "backend": "local", 307 "config": map[string]interface{}{ 308 "path": strings.Repeat("../", strings.Count(path, "/")) + strings.ReplaceAll(path, serviceName, k) + "terraform.tfstate", 309 }, 310 } 311 } 312 } 313 // create variables file 314 if len(provider.GetResourceConnections()[serviceName]) > 0 && options.Connect && len(variables["data"]["terraform_remote_state"]) > 0 { 315 variablesFile, err := terraformutils.Print(variables, map[string]struct{}{"config": {}}, options.Output) 316 if err != nil { 317 return err 318 } 319 terraformoutput.PrintFile(path+"/variables."+terraformoutput.GetFileExtension(options.Output), variablesFile) 320 } 321 } 322 } else { 323 if options.Connect { 324 variables := map[string]map[string]map[string]interface{}{} 325 variables["data"] = map[string]map[string]interface{}{} 326 variables["data"]["terraform_remote_state"] = map[string]interface{}{} 327 if options.State == "bucket" { 328 bucket := terraformoutput.BucketState{ 329 Name: options.Bucket, 330 } 331 variables["data"]["terraform_remote_state"]["local"] = map[string]interface{}{ 332 "backend": "gcs", 333 "config": bucket.BucketGetTfData(path), 334 } 335 } else { 336 variables["data"]["terraform_remote_state"]["local"] = map[string]interface{}{ 337 "backend": "local", 338 "config": map[string]interface{}{ 339 "path": "terraform.tfstate", 340 }, 341 } 342 } 343 // create variables file 344 if options.Connect { 345 variablesFile, err := terraformutils.Print(variables, map[string]struct{}{"config": {}}, options.Output) 346 if err != nil { 347 return err 348 } 349 terraformoutput.PrintFile(path+"/variables."+terraformoutput.GetFileExtension(options.Output), variablesFile) 350 } 351 } 352 } 353 return nil 354 } 355 356 func Path(pathPattern, providerName, serviceName, output string) string { 357 return strings.NewReplacer( 358 "{provider}", providerName, 359 "{service}", serviceName, 360 "{output}", output, 361 ).Replace(pathPattern) 362 } 363 364 func listCmd(provider terraformutils.ProviderGenerator) *cobra.Command { 365 cmd := &cobra.Command{ 366 Use: "list", 367 Short: "List supported resources for " + provider.GetName() + " provider", 368 Long: "List supported resources for " + provider.GetName() + " provider", 369 RunE: func(cmd *cobra.Command, args []string) error { 370 services := providerServices(provider) 371 for _, k := range services { 372 fmt.Println(k) 373 } 374 return nil 375 }, 376 } 377 cmd.Flags().AddFlag(&pflag.Flag{Name: "resources"}) 378 return cmd 379 } 380 381 func providerServices(provider terraformutils.ProviderGenerator) []string { 382 var services []string 383 for k := range provider.GetSupportedService() { 384 services = append(services, k) 385 } 386 sort.Strings(services) 387 return services 388 } 389 390 func baseProviderFlags(flag *pflag.FlagSet, options *ImportOptions, sampleRes, sampleFilters string) { 391 flag.BoolVarP(&options.Connect, "connect", "c", true, "") 392 flag.BoolVarP(&options.Compact, "compact", "C", false, "") 393 flag.StringSliceVarP(&options.Resources, "resources", "r", []string{}, sampleRes) 394 flag.StringSliceVarP(&options.Excludes, "excludes", "x", []string{}, sampleRes) 395 flag.StringVarP(&options.PathPattern, "path-pattern", "p", DefaultPathPattern, "{output}/{provider}/") 396 flag.StringVarP(&options.PathOutput, "path-output", "o", DefaultPathOutput, "") 397 flag.StringVarP(&options.State, "state", "s", DefaultState, "local or bucket") 398 flag.StringVarP(&options.Bucket, "bucket", "b", "", "gs://terraform-state") 399 flag.StringSliceVarP(&options.Filter, "filter", "f", []string{}, sampleFilters) 400 flag.BoolVarP(&options.Verbose, "verbose", "v", false, "") 401 flag.StringVarP(&options.Output, "output", "O", "hcl", "output format hcl or json") 402 flag.IntVarP(&options.RetryCount, "retry-number", "n", 5, "number of retries to perform when refresh fails") 403 flag.IntVarP(&options.RetrySleepMs, "retry-sleep-ms", "m", 300, "time in ms to sleep between retries") 404 }