2018-03-10 20:44:05 +01:00
package configupgrade
import (
"bytes"
"fmt"
"github.com/hashicorp/terraform/tfdiags"
hcl2 "github.com/hashicorp/hcl2/hcl"
2018-06-21 02:02:29 +02:00
hcl2write "github.com/hashicorp/hcl2/hclwrite"
2018-03-10 20:44:05 +01:00
)
// Upgrade takes some input module sources and produces a new ModuleSources
// that should be equivalent to the input but use the configuration idioms
// associated with the new configuration loader.
//
// The result of this function will probably not be accepted by this function,
// because it will contain constructs that are known only to the new
// loader.
//
// The result may include additional files that were not present in the
// input. The result may also include nil entries for filenames that were
// present in the input, indicating that these files should be deleted.
// In particular, file renames are represented as a new entry accompanied
// by a nil entry for the old name.
//
// If the returned diagnostics contains errors, the caller should not write
// the resulting sources to disk since they will probably be incomplete. If
// only warnings are present then the files may be written to disk. Most
// warnings are also represented as "TF-UPGRADE-TODO:" comments in the
// generated source files so that users can visit them all and decide what to
// do with them.
2018-06-29 02:26:06 +02:00
func ( u * Upgrader ) Upgrade ( input ModuleSources ) ( ModuleSources , tfdiags . Diagnostics ) {
2018-03-10 20:44:05 +01:00
ret := make ( ModuleSources )
var diags tfdiags . Diagnostics
2018-06-29 02:26:06 +02:00
an , err := u . analyze ( input )
if err != nil {
diags = diags . Append ( err )
return ret , diags
}
2018-03-10 20:44:05 +01:00
for name , src := range input {
ext := fileExt ( name )
if ext == "" {
// This should never happen because we ignore files that don't
// have our conventional extensions during LoadModule, but we'll
// silently pass through such files assuming that the caller
// has been tampering with the sources map somehow.
ret [ name ] = src
continue
}
isJSON := ( ext == ".tf.json" )
// The legacy loader allowed JSON syntax inside files named just .tf,
// so we'll detect that case and rename them here so that the new
// loader will accept the JSON. However, JSON files are usually
// generated so we'll also generate a warning to the user to update
// whatever program generated the file to use the new name.
if ! isJSON {
trimSrc := bytes . TrimSpace ( src )
if len ( trimSrc ) > 0 && ( trimSrc [ 0 ] == '{' || trimSrc [ 0 ] == '[' ) {
isJSON = true
// Rename in the output
ret [ name ] = nil // mark for deletion
oldName := name
name = input . UnusedFilename ( name + ".json" )
2018-06-21 04:27:14 +02:00
ret [ name ] = src
2018-03-10 20:44:05 +01:00
diags = diags . Append ( & hcl2 . Diagnostic {
Severity : hcl2 . DiagWarning ,
Summary : "JSON configuration file was renamed" ,
Detail : fmt . Sprintf (
"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." ,
oldName , name ,
) ,
} )
2018-06-21 04:27:14 +02:00
continue
2018-03-10 20:44:05 +01:00
}
}
if isJSON {
// We don't do any automatic rewriting for JSON files, since they
// are usually generated and thus it's the generating program that
// needs to be updated, rather than its output.
diags = diags . Append ( & hcl2 . Diagnostic {
Severity : hcl2 . DiagWarning ,
Summary : "JSON configuration file was not rewritten" ,
Detail : fmt . Sprintf (
"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." ,
name ,
) ,
} )
continue
}
// TODO: Actually rewrite this .tf file.
2018-06-29 02:26:06 +02:00
result , fileDiags := u . upgradeNativeSyntaxFile ( name , src , an )
2018-06-21 02:02:29 +02:00
diags = diags . Append ( fileDiags )
if fileDiags . HasErrors ( ) {
// Leave unchanged, then.
ret [ name ] = src
continue
}
ret [ name ] = hcl2write . Format ( result . Content )
2018-03-10 20:44:05 +01:00
}
versionsName := ret . UnusedFilename ( "versions.tf" )
2018-06-21 02:02:29 +02:00
ret [ versionsName ] = [ ] byte ( newVersionConstraint )
2018-03-10 20:44:05 +01:00
return ret , diags
}
2018-06-21 02:02:29 +02:00
const newVersionConstraint = `
terraform {
required_version = ">= 0.12"
}
`