internal/tfimport/importer/resources.go (83 lines of code) (raw):

// Copyright 2021 Google LLC // // 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 importer defines resource-specific implementations for interface Importer. package importer import ( "bufio" "fmt" "io" "log" "os" "strings" ) // ConfigMap is a type alias for a map of resource config values. // It's supposed to hold values for keys used in determining a resource's ImportID. // May come from the provider block, the planned change values, manually provided defaults, or elsewhere. type ConfigMap map[string]interface{} // InsufficientInfoErr indicates that we do not have enough information to import a resource. type InsufficientInfoErr struct { MissingFields []string Msg string } func (e *InsufficientInfoErr) Error() string { err := fmt.Sprintf("missing fields: %v", strings.Join(e.MissingFields, ", ")) if e.Msg != "" { err = fmt.Sprintf("%v; additional info: %v", err, e.Msg) } return err } // SkipErr indicates that the user manually skipped the import. type SkipErr struct{} func (e *SkipErr) Error() string { return "Skipped resource" } // DoesNotExistErr indicates that a resources specifically determined that it doesn't exist. // Example: google_resource_manager_lien requires a name to import, but if it finds no liens, it should return this error. type DoesNotExistErr struct { Resource string } func (e *DoesNotExistErr) Error() string { return fmt.Sprintf("Resource %v does not exist", e.Resource) } // fromConfigValues returns the first matching config value for key, from the given config value maps cvs. func fromConfigValues(key string, cvs ...ConfigMap) (interface{}, error) { for _, cv := range cvs { if v, ok := cv[key]; ok { return v, nil } } return nil, fmt.Errorf("could not find key %q in resource change or provider config", key) } // Small helper functions to DRY the fromUser* functions. func showPrompt(fieldName, prompt string) { log.Printf("Could not determine %q automatically\n", fieldName) log.Println(prompt) } func parseUserVal(val string, err error) (string, error) { if val == "" { return "", &SkipErr{} } return val, err } // fromUser asks the user for the value. func fromUser(in io.Reader, fieldName string, prompt string) (val string, err error) { showPrompt(fieldName, prompt) val, err = userValue(in) return parseUserVal(val, err) } // userValue asks the user to fill in a value that the importer can't figure out. func userValue(in io.Reader) (string, error) { fmt.Println("Enter a value manually (blank to skip):") reader := bufio.NewReader(in) val, err := reader.ReadString('\n') if err != nil { return "", err } val = strings.TrimSpace(val) return val, nil } // loadFields returns a map of field names to values, taking into account interactivity and multiple ConfigMaps. // The result can be used in templates. func loadFields(fields []string, interactive bool, configValues ...ConfigMap) (fieldsMap map[string]interface{}, err error) { fieldsMap = make(map[string]interface{}) var missingFields []string for _, field := range fields { val, err := fromConfigValues(field, configValues...) // If interactive is set, try to get the field interactively. if err != nil && interactive { prompt := fmt.Sprintf("Please enter the exact value for %v", field) val, err = fromUser(os.Stdin, field, prompt) } // If err is still not nil, then user didn't provide value, treat this as a missing field. if err != nil { missingFields = append(missingFields, field) } // Leave the value as-is, to allow arbitrary nesting in the template, if it happens to not be a string. // For example, in the google_container_node_pool resource, `node_config` is a map but has potentially useful sub-fields. fieldsMap[field] = val } if len(missingFields) > 0 { return nil, &InsufficientInfoErr{missingFields, ""} } return fieldsMap, nil }