github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/configs/configupgrade/upgrade.go (about) 1 package configupgrade 2 3 import ( 4 "bytes" 5 "fmt" 6 7 "github.com/hashicorp/terraform/tfdiags" 8 9 hcl2 "github.com/hashicorp/hcl/v2" 10 hcl2write "github.com/hashicorp/hcl/v2/hclwrite" 11 ) 12 13 // Upgrade takes some input module sources and produces a new ModuleSources 14 // that should be equivalent to the input but use the configuration idioms 15 // associated with the new configuration loader. 16 // 17 // The result of this function will probably not be accepted by this function, 18 // because it will contain constructs that are known only to the new 19 // loader. 20 // 21 // The result may include additional files that were not present in the 22 // input. The result may also include nil entries for filenames that were 23 // present in the input, indicating that these files should be deleted. 24 // In particular, file renames are represented as a new entry accompanied 25 // by a nil entry for the old name. 26 // 27 // If the returned diagnostics contains errors, the caller should not write 28 // the resulting sources to disk since they will probably be incomplete. If 29 // only warnings are present then the files may be written to disk. Most 30 // warnings are also represented as "TF-UPGRADE-TODO:" comments in the 31 // generated source files so that users can visit them all and decide what to 32 // do with them. 33 func (u *Upgrader) Upgrade(input ModuleSources, dir string) (ModuleSources, tfdiags.Diagnostics) { 34 ret := make(ModuleSources) 35 var diags tfdiags.Diagnostics 36 37 an, err := u.analyze(input) 38 if err != nil { 39 diags = diags.Append(err) 40 return ret, diags 41 } 42 an.ModuleDir = dir 43 44 for name, src := range input { 45 ext := fileExt(name) 46 if ext == "" { 47 // This should never happen because we ignore files that don't 48 // have our conventional extensions during LoadModule, but we'll 49 // silently pass through such files assuming that the caller 50 // has been tampering with the sources map somehow. 51 ret[name] = src 52 continue 53 } 54 55 isJSON := (ext == ".tf.json") 56 57 // The legacy loader allowed JSON syntax inside files named just .tf, 58 // so we'll detect that case and rename them here so that the new 59 // loader will accept the JSON. However, JSON files are usually 60 // generated so we'll also generate a warning to the user to update 61 // whatever program generated the file to use the new name. 62 if !isJSON { 63 trimSrc := bytes.TrimSpace(src) 64 if len(trimSrc) > 0 && (trimSrc[0] == '{' || trimSrc[0] == '[') { 65 isJSON = true 66 67 // Rename in the output 68 ret[name] = nil // mark for deletion 69 oldName := name 70 name = input.UnusedFilename(name + ".json") 71 ret[name] = src 72 73 diags = diags.Append(&hcl2.Diagnostic{ 74 Severity: hcl2.DiagWarning, 75 Summary: "JSON configuration file was renamed", 76 Detail: fmt.Sprintf( 77 "The file %q appears to be in JSON format, so it was renamed to %q. If this file is generated by another program, that program must be updated to use this new name.", 78 oldName, name, 79 ), 80 }) 81 continue 82 } 83 } 84 85 if isJSON { 86 // We don't do any automatic rewriting for JSON files, since they 87 // are usually generated and thus it's the generating program that 88 // needs to be updated, rather than its output. 89 diags = diags.Append(&hcl2.Diagnostic{ 90 Severity: hcl2.DiagWarning, 91 Summary: "JSON configuration file was not rewritten", 92 Detail: fmt.Sprintf( 93 "The JSON configuration file %q was skipped, because JSON files are assumed to be generated. The program that generated this file may need to be updated for changes to the configuration language.", 94 name, 95 ), 96 }) 97 ret[name] = src // unchanged 98 continue 99 } 100 101 // TODO: Actually rewrite this .tf file. 102 result, fileDiags := u.upgradeNativeSyntaxFile(name, src, an) 103 diags = diags.Append(fileDiags) 104 if fileDiags.HasErrors() { 105 // Leave unchanged, then. 106 ret[name] = src 107 continue 108 } 109 110 ret[name] = hcl2write.Format(result.Content) 111 } 112 113 versionsName := ret.UnusedFilename("versions.tf") 114 ret[versionsName] = []byte(newVersionConstraint) 115 116 return ret, diags 117 } 118 119 const newVersionConstraint = ` 120 terraform { 121 required_version = ">= 0.12" 122 } 123 `