cmd/import.go (346 lines of code) (raw):
// Copyright 2018 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"log"
"os"
"sort"
"strings"
"sync"
"github.com/GoogleCloudPlatform/terraformer/terraformutils/terraformerstring"
"github.com/GoogleCloudPlatform/terraformer/terraformutils/providerwrapper"
"github.com/spf13/pflag"
"github.com/GoogleCloudPlatform/terraformer/terraformutils"
"github.com/GoogleCloudPlatform/terraformer/terraformutils/terraformoutput"
"github.com/spf13/cobra"
)
type ImportOptions struct {
Resources []string
Excludes []string
PathPattern string
PathOutput string
State string
Bucket string
Profile string
Verbose bool
Zone string
Regions []string
Projects []string
ResourceGroup string
Connect bool
Compact bool
Filter []string
Plan bool `json:"-"`
Output string
NoSort bool
RetryCount int
RetrySleepMs int
}
const DefaultPathPattern = "{output}/{provider}/{service}/"
const DefaultPathOutput = "generated"
const DefaultState = "local"
func newImportCmd() *cobra.Command {
options := ImportOptions{}
cmd := &cobra.Command{
Use: "import",
Short: "Import current state to Terraform configuration",
Long: "Import current state to Terraform configuration",
SilenceUsage: true,
SilenceErrors: false,
//Version: version.String(),
}
cmd.AddCommand(newCmdPlanImporter(options))
cmd.AddCommand(&cobra.Command{
Use: "no-sort",
Short: "Don't sort resources",
Long: "Don't sort resources",
})
for _, subcommand := range providerImporterSubcommands() {
providerCommand := subcommand(options)
_ = providerCommand.MarkPersistentFlagRequired("resources")
cmd.AddCommand(providerCommand)
}
return cmd
}
func Import(provider terraformutils.ProviderGenerator, options ImportOptions, args []string) error {
providerWrapper, options, err := initOptionsAndWrapper(provider, options, args)
if err != nil {
return err
}
defer providerWrapper.Kill()
providerMapping := terraformutils.NewProvidersMapping(provider)
err = initAllServicesResources(providerMapping, options, args, providerWrapper)
if err != nil {
return err
}
err = terraformutils.RefreshResourcesByProvider(providerMapping, providerWrapper)
if err != nil {
return err
}
providerMapping.ConvertTFStates(providerWrapper)
// change structs with additional data for each resource
providerMapping.CleanupProviders()
err = importFromPlan(providerMapping, options, args)
return err
}
func initOptionsAndWrapper(provider terraformutils.ProviderGenerator, options ImportOptions, args []string) (*providerwrapper.ProviderWrapper, ImportOptions, error) {
err := provider.Init(args)
if err != nil {
return nil, options, err
}
if terraformerstring.ContainsString(options.Resources, "*") {
log.Println("Attempting an import of ALL resources in " + provider.GetName())
options.Resources = providerServices(provider)
}
if len(options.Excludes) > 0 {
localSlice := []string{}
for _, r := range options.Resources {
remove := false
for _, e := range options.Excludes {
if r == e {
remove = true
log.Println("Excluding resource " + e)
}
}
if !remove {
localSlice = append(localSlice, r)
}
}
options.Resources = localSlice
}
providerWrapper, err := providerwrapper.NewProviderWrapper(provider.GetName(), provider.GetConfig(), options.Verbose, map[string]int{"retryCount": options.RetryCount, "retrySleepMs": options.RetrySleepMs})
if err != nil {
return nil, options, err
}
return providerWrapper, options, nil
}
func initAllServicesResources(providersMapping *terraformutils.ProvidersMapping, options ImportOptions, args []string, providerWrapper *providerwrapper.ProviderWrapper) error {
numOfResources := len(options.Resources)
var wg sync.WaitGroup
wg.Add(numOfResources)
var failedServices []string
for _, service := range options.Resources {
serviceProvider := providersMapping.AddServiceToProvider(service)
err := serviceProvider.Init(args)
if err != nil {
return err
}
err = initServiceResources(service, serviceProvider, options, providerWrapper)
if err != nil {
failedServices = append(failedServices, service)
}
}
// remove providers that failed to init their service
providersMapping.RemoveServices(failedServices)
providersMapping.ProcessResources(false)
return nil
}
func importFromPlan(providerMapping *terraformutils.ProvidersMapping, options ImportOptions, args []string) error {
plan := &ImportPlan{
Provider: providerMapping.GetBaseProvider().GetName(),
Options: options,
Args: args,
ImportedResource: map[string][]terraformutils.Resource{},
}
resourcesByService := providerMapping.GetResourcesByService()
for service := range resourcesByService {
plan.ImportedResource[service] = append(plan.ImportedResource[service], resourcesByService[service]...)
}
if options.Plan {
path := Path(options.PathPattern, providerMapping.GetBaseProvider().GetName(), "terraformer", options.PathOutput)
return ExportPlanFile(plan, path, "plan.json")
}
return ImportFromPlan(providerMapping.GetBaseProvider(), plan)
}
func initServiceResources(service string, provider terraformutils.ProviderGenerator,
options ImportOptions, providerWrapper *providerwrapper.ProviderWrapper) error {
log.Println(provider.GetName() + " importing... " + service)
err := provider.InitService(service, options.Verbose)
if err != nil {
log.Printf("%s error importing %s, err: %s\n", provider.GetName(), service, err)
return err
}
provider.GetService().ParseFilters(options.Filter)
err = provider.GetService().InitResources()
if err != nil {
log.Printf("%s error initializing resources in service %s, err: %s\n", provider.GetName(), service, err)
return err
}
provider.GetService().PopulateIgnoreKeys(providerWrapper)
provider.GetService().InitialCleanup()
log.Println(provider.GetName() + " done importing " + service)
return nil
}
func ImportFromPlan(provider terraformutils.ProviderGenerator, plan *ImportPlan) error {
options := plan.Options
importedResource := plan.ImportedResource
isServicePath := strings.Contains(options.PathPattern, "{service}")
if options.Connect {
log.Println(provider.GetName() + " Connecting.... ")
importedResource = terraformutils.ConnectServices(importedResource, isServicePath, provider.GetResourceConnections())
}
if !isServicePath {
var compactedResources []terraformutils.Resource
for _, resources := range importedResource {
compactedResources = append(compactedResources, resources...)
}
e := printService(provider, "", options, compactedResources, importedResource)
if e != nil {
return e
}
} else {
for serviceName, resources := range importedResource {
e := printService(provider, serviceName, options, resources, importedResource)
if e != nil {
return e
}
}
}
return nil
}
func printService(provider terraformutils.ProviderGenerator, serviceName string, options ImportOptions, resources []terraformutils.Resource, importedResource map[string][]terraformutils.Resource) error {
log.Println(provider.GetName() + " save " + serviceName)
// Print HCL files for Resources
path := Path(options.PathPattern, provider.GetName(), serviceName, options.PathOutput)
err := terraformoutput.OutputHclFiles(resources, provider, path, serviceName, options.Compact, options.Output, !options.NoSort)
if err != nil {
return err
}
tfStateFile, err := terraformutils.PrintTfState(resources)
if err != nil {
return err
}
// print or upload State file
if options.State == "bucket" {
log.Println(provider.GetName() + " upload tfstate to bucket " + options.Bucket)
bucket := terraformoutput.BucketState{
Name: options.Bucket,
}
if err := bucket.BucketUpload(path, tfStateFile); err != nil {
return err
}
// create Bucket file
if bucketStateDataFile, err := terraformutils.Print(bucket.BucketGetTfData(path), map[string]struct{}{}, options.Output, !options.NoSort); err == nil {
terraformoutput.PrintFile(path+"/bucket.tf", bucketStateDataFile)
}
} else {
if serviceName == "" {
log.Println(provider.GetName() + " save tfstate")
} else {
log.Println(provider.GetName() + " save tfstate for " + serviceName)
}
if err := os.WriteFile(path+"/terraform.tfstate", tfStateFile, os.ModePerm); err != nil {
return err
}
}
// Print hcl variables.tf
if serviceName != "" {
if options.Connect && len(provider.GetResourceConnections()[serviceName]) > 0 {
variables := map[string]map[string]map[string]interface{}{}
variables["data"] = map[string]map[string]interface{}{}
variables["data"]["terraform_remote_state"] = map[string]interface{}{}
if options.State == "bucket" {
bucket := terraformoutput.BucketState{
Name: options.Bucket,
}
for k := range provider.GetResourceConnections()[serviceName] {
if _, exist := importedResource[k]; !exist {
continue
}
variables["data"]["terraform_remote_state"][k] = map[string]interface{}{
"backend": "gcs",
"config": bucket.BucketGetTfData(strings.ReplaceAll(path, serviceName, k)),
}
}
} else {
for k := range provider.GetResourceConnections()[serviceName] {
if _, exist := importedResource[k]; !exist {
continue
}
variables["data"]["terraform_remote_state"][k] = map[string]interface{}{
"backend": "local",
"config": map[string]interface{}{
"path": strings.Repeat("../", strings.Count(path, "/")) + strings.ReplaceAll(path, serviceName, k) + "terraform.tfstate",
},
}
}
}
// create variables file
if len(provider.GetResourceConnections()[serviceName]) > 0 && options.Connect && len(variables["data"]["terraform_remote_state"]) > 0 {
variablesFile, err := terraformutils.Print(variables, map[string]struct{}{"config": {}}, options.Output, !options.NoSort)
if err != nil {
return err
}
terraformoutput.PrintFile(path+"/variables."+terraformoutput.GetFileExtension(options.Output), variablesFile)
}
}
} else {
if options.Connect {
variables := map[string]map[string]map[string]interface{}{}
variables["data"] = map[string]map[string]interface{}{}
variables["data"]["terraform_remote_state"] = map[string]interface{}{}
if options.State == "bucket" {
bucket := terraformoutput.BucketState{
Name: options.Bucket,
}
variables["data"]["terraform_remote_state"]["local"] = map[string]interface{}{
"backend": "gcs",
"config": bucket.BucketGetTfData(path),
}
} else {
variables["data"]["terraform_remote_state"]["local"] = map[string]interface{}{
"backend": "local",
"config": map[string]interface{}{
"path": "terraform.tfstate",
},
}
}
// create variables file
if options.Connect {
variablesFile, err := terraformutils.Print(variables, map[string]struct{}{"config": {}}, options.Output, !options.NoSort)
if err != nil {
return err
}
terraformoutput.PrintFile(path+"/variables."+terraformoutput.GetFileExtension(options.Output), variablesFile)
}
}
}
return nil
}
func Path(pathPattern, providerName, serviceName, output string) string {
return strings.NewReplacer(
"{provider}", providerName,
"{service}", serviceName,
"{output}", output,
).Replace(pathPattern)
}
func listCmd(provider terraformutils.ProviderGenerator) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List supported resources for " + provider.GetName() + " provider",
Long: "List supported resources for " + provider.GetName() + " provider",
RunE: func(cmd *cobra.Command, args []string) error {
services := providerServices(provider)
for _, k := range services {
fmt.Println(k)
}
return nil
},
}
cmd.Flags().AddFlag(&pflag.Flag{Name: "resources"})
return cmd
}
func providerServices(provider terraformutils.ProviderGenerator) []string {
var services []string
for k := range provider.GetSupportedService() {
services = append(services, k)
}
sort.Strings(services)
return services
}
func baseProviderFlags(flag *pflag.FlagSet, options *ImportOptions, sampleRes, sampleFilters string) {
flag.BoolVarP(&options.Connect, "connect", "c", true, "")
flag.BoolVarP(&options.Compact, "compact", "C", false, "")
flag.StringSliceVarP(&options.Resources, "resources", "r", []string{}, sampleRes)
flag.StringSliceVarP(&options.Excludes, "excludes", "x", []string{}, sampleRes)
flag.StringVarP(&options.PathPattern, "path-pattern", "p", DefaultPathPattern, "{output}/{provider}/")
flag.StringVarP(&options.PathOutput, "path-output", "o", DefaultPathOutput, "")
flag.StringVarP(&options.State, "state", "s", DefaultState, "local or bucket")
flag.StringVarP(&options.Bucket, "bucket", "b", "", "gs://terraform-state")
flag.StringSliceVarP(&options.Filter, "filter", "f", []string{}, sampleFilters)
flag.BoolVarP(&options.Verbose, "verbose", "v", false, "")
flag.BoolVarP(&options.NoSort, "no-sort", "S", false, "set to disable sorting of HCL")
flag.StringVarP(&options.Output, "output", "O", "hcl", "output format hcl or json")
flag.IntVarP(&options.RetryCount, "retry-number", "n", 5, "number of retries to perform when refresh fails")
flag.IntVarP(&options.RetrySleepMs, "retry-sleep-ms", "m", 300, "time in ms to sleep between retries")
}