github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/012_config_upgrade.go (about) 1 package command 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/hashicorp/hcl/v2" 12 "github.com/hashicorp/terraform/configs/configupgrade" 13 "github.com/hashicorp/terraform/terraform" 14 "github.com/hashicorp/terraform/tfdiags" 15 ) 16 17 // ZeroTwelveUpgradeCommand is a Command implementation that can upgrade 18 // the configuration files for a module from pre-0.11 syntax to new 0.12 19 // idiom, while also flagging any suspicious constructs that will require 20 // human review. 21 type ZeroTwelveUpgradeCommand struct { 22 Meta 23 } 24 25 func (c *ZeroTwelveUpgradeCommand) Run(args []string) int { 26 args, err := c.Meta.process(args, true) 27 if err != nil { 28 return 1 29 } 30 31 var skipConfirm, force bool 32 33 flags := c.Meta.extendedFlagSet("0.12upgrade") 34 flags.BoolVar(&skipConfirm, "yes", false, "skip confirmation prompt") 35 flags.BoolVar(&force, "force", false, "override duplicate upgrade heuristic") 36 if err := flags.Parse(args); err != nil { 37 return 1 38 } 39 40 var diags tfdiags.Diagnostics 41 42 var dir string 43 args = flags.Args() 44 switch len(args) { 45 case 0: 46 dir = "." 47 case 1: 48 dir = args[0] 49 default: 50 diags = diags.Append(tfdiags.Sourceless( 51 tfdiags.Error, 52 "Too many arguments", 53 "The command 0.12upgrade expects only a single argument, giving the directory containing the module to upgrade.", 54 )) 55 c.showDiagnostics(diags) 56 return 1 57 } 58 59 // Check for user-supplied plugin path 60 if c.pluginPath, err = c.loadPluginPath(); err != nil { 61 c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err)) 62 return 1 63 } 64 65 dir = c.normalizePath(dir) 66 67 sources, err := configupgrade.LoadModule(dir) 68 if err != nil { 69 if os.IsNotExist(err) { 70 diags = diags.Append(tfdiags.Sourceless( 71 tfdiags.Error, 72 "Module directory not found", 73 fmt.Sprintf("The given directory %s does not exist.", dir), 74 )) 75 } else { 76 diags = diags.Append(err) 77 } 78 c.showDiagnostics(diags) 79 return 1 80 } 81 82 if len(sources) == 0 { 83 diags = diags.Append(tfdiags.Sourceless( 84 tfdiags.Error, 85 "Not a module directory", 86 fmt.Sprintf("The given directory %s does not contain any Terraform configuration files.", dir), 87 )) 88 c.showDiagnostics(diags) 89 return 1 90 } 91 92 // The config loader doesn't naturally populate our sources 93 // map, so we'll do it manually so our diagnostics can have 94 // source code snippets inside them. 95 // This is weird, but this whole upgrade codepath is pretty 96 // weird and temporary, so we'll accept it. 97 if loader, err := c.initConfigLoader(); err == nil { 98 parser := loader.Parser() 99 for name, src := range sources { 100 parser.ForceFileSource(filepath.Join(dir, name), src) 101 } 102 } 103 104 if !force { 105 // We'll check first if this directory already looks upgraded, so we 106 // don't waste the user's time dealing with an interactive prompt 107 // immediately followed by an error. 108 if already, rng := sources.MaybeAlreadyUpgraded(); already { 109 diags = diags.Append(&hcl.Diagnostic{ 110 Severity: hcl.DiagError, 111 Summary: "Module already upgraded", 112 Detail: fmt.Sprintf("The module in directory %s has a version constraint that suggests it has already been upgraded for v0.12. If this is incorrect, either remove this constraint or override this heuristic with the -force argument. Upgrading a module that was already upgraded may change the meaning of that module.", dir), 113 Subject: rng.ToHCL().Ptr(), 114 }) 115 c.showDiagnostics(diags) 116 return 1 117 } 118 } 119 120 if !skipConfirm { 121 c.Ui.Output(fmt.Sprintf(` 122 This command will rewrite the configuration files in the given directory so 123 that they use the new syntax features from Terraform v0.12, and will identify 124 any constructs that may need to be adjusted for correct operation with 125 Terraform v0.12. 126 127 We recommend using this command in a clean version control work tree, so that 128 you can easily see the proposed changes as a diff against the latest commit. 129 If you have uncommited changes already present, we recommend aborting this 130 command and dealing with them before running this command again. 131 `)) 132 133 query := "Would you like to upgrade the module in the current directory?" 134 if dir != "." { 135 query = fmt.Sprintf("Would you like to upgrade the module in %s?", dir) 136 } 137 v, err := c.UIInput().Input(context.Background(), &terraform.InputOpts{ 138 Id: "approve", 139 Query: query, 140 Description: `Only 'yes' will be accepted to confirm.`, 141 }) 142 if err != nil { 143 diags = diags.Append(err) 144 c.showDiagnostics(diags) 145 return 1 146 } 147 if v != "yes" { 148 c.Ui.Info("Upgrade cancelled.") 149 return 0 150 } 151 152 c.Ui.Output(`-----------------------------------------------------------------------------`) 153 } 154 155 upgrader := &configupgrade.Upgrader{ 156 Providers: c.providerResolver(), 157 Provisioners: c.provisionerFactories(), 158 } 159 newSources, upgradeDiags := upgrader.Upgrade(sources, dir) 160 diags = diags.Append(upgradeDiags) 161 if upgradeDiags.HasErrors() { 162 c.showDiagnostics(diags) 163 return 2 164 } 165 166 // Now we'll write the contents of newSources into the filesystem. 167 for name, src := range newSources { 168 fn := filepath.Join(dir, name) 169 if src == nil { 170 // indicates a file to be deleted 171 err := os.Remove(fn) 172 if err != nil { 173 diags = diags.Append(tfdiags.Sourceless( 174 tfdiags.Error, 175 "Failed to remove file", 176 fmt.Sprintf("The file %s must be renamed as part of the upgrade process, but the old file could not be deleted: %s.", fn, err), 177 )) 178 } 179 continue 180 } 181 182 err := ioutil.WriteFile(fn, src, 0644) 183 if err != nil { 184 diags = diags.Append(tfdiags.Sourceless( 185 tfdiags.Error, 186 "Failed to write file", 187 fmt.Sprintf("The file %s must be updated or created as part of the upgrade process, but there was an error while writing: %s.", fn, err), 188 )) 189 } 190 } 191 192 c.showDiagnostics(diags) 193 if diags.HasErrors() { 194 return 2 195 } 196 197 if !skipConfirm { 198 if len(diags) != 0 { 199 c.Ui.Output(`-----------------------------------------------------------------------------`) 200 } 201 c.Ui.Output(c.Colorize().Color(` 202 [bold][green]Upgrade complete![reset] 203 204 The configuration files were upgraded successfully. Use your version control 205 system to review the proposed changes, make any necessary adjustments, and 206 then commit. 207 `)) 208 if len(diags) != 0 { 209 // We checked for errors above, so these must be warnings. 210 c.Ui.Output(`Some warnings were generated during the upgrade, as shown above. These 211 indicate situations where Terraform could not decide on an appropriate course 212 of action without further human input. 213 214 Where possible, these have also been marked with TF-UPGRADE-TODO comments to 215 mark the locations where a decision must be made. After reviewing and adjusting 216 these, manually remove the TF-UPGRADE-TODO comment before continuing. 217 `) 218 } 219 220 } 221 return 0 222 } 223 224 func (c *ZeroTwelveUpgradeCommand) Help() string { 225 helpText := ` 226 Usage: terraform 0.12upgrade [module-dir] 227 228 Rewrites the .tf files for a single module that was written for a Terraform 229 version prior to v0.12 so that it uses new syntax features from v0.12 230 and later. 231 232 Also rewrites constructs that behave differently after v0.12, and flags any 233 suspicious constructs that require human review, 234 235 By default, 0.12upgrade rewrites the files in the current working directory. 236 However, a path to a different directory can be provided. The command will 237 prompt for confirmation interactively unless the -yes option is given. 238 239 Options: 240 241 -yes Skip the initial introduction messages and interactive 242 confirmation. This can be used to run this command in 243 batch from a script. 244 245 -force Override the heuristic that attempts to detect if a 246 configuration is already written for v0.12 or later. 247 Some of the transformations made by this command are 248 not idempotent, so re-running against the same module 249 may change the meanings expressions in the module. 250 ` 251 return strings.TrimSpace(helpText) 252 } 253 254 func (c *ZeroTwelveUpgradeCommand) Synopsis() string { 255 return "Rewrites pre-0.12 module source code for v0.12" 256 }