2020-08-25 10:51:43 +02:00
package command
import (
"bytes"
"context"
"fmt"
"io"
"os"
"path/filepath"
2021-01-19 10:21:39 +01:00
"reflect"
2021-03-22 02:56:30 -07:00
"regexp"
2020-08-25 10:51:43 +02:00
"sort"
2021-03-22 02:56:30 -07:00
"strconv"
2020-08-25 10:51:43 +02:00
"strings"
texttemplate "text/template"
2021-02-23 16:51:13 +01:00
"github.com/hashicorp/go-multierror"
2020-08-25 10:51:43 +02:00
"github.com/hashicorp/hcl/v2/hclwrite"
2021-03-25 13:37:48 +01:00
awscommon "github.com/hashicorp/packer-plugin-amazon/builder/common"
2021-01-20 10:37:16 +01:00
hcl2shim "github.com/hashicorp/packer-plugin-sdk/hcl2helper"
2020-12-17 13:29:25 -08:00
"github.com/hashicorp/packer-plugin-sdk/template"
2021-02-12 15:37:41 +01:00
"github.com/hashicorp/packer/packer"
2021-01-19 10:21:39 +01:00
"github.com/mitchellh/mapstructure"
2020-08-25 10:51:43 +02:00
"github.com/posener/complete"
"github.com/zclconf/go-cty/cty"
)
type HCL2UpgradeCommand struct {
Meta
}
func ( c * HCL2UpgradeCommand ) Run ( args [] string ) int {
ctx , cleanup := handleTermInterrupt ( c . Ui )
defer cleanup ()
cfg , ret := c . ParseArgs ( args )
if ret != 0 {
return ret
}
return c . RunContext ( ctx , cfg )
}
func ( c * HCL2UpgradeCommand ) ParseArgs ( args [] string ) ( * HCL2UpgradeArgs , int ) {
var cfg HCL2UpgradeArgs
flags := c . Meta . FlagSet ( "hcl2_upgrade" , FlagSetNone )
flags . Usage = func () { c . Ui . Say ( c . Help ()) }
cfg . AddFlagSets ( flags )
if err := flags . Parse ( args ); err != nil {
return & cfg , 1
}
args = flags . Args ()
if len ( args ) != 1 {
flags . Usage ()
return & cfg , 1
}
cfg . Path = args [ 0 ]
if cfg . OutputFile == "" {
cfg . OutputFile = cfg . Path + ".pkr.hcl"
}
return & cfg , 0
}
const (
2021-01-18 06:08:04 -08:00
hcl2UpgradeFileHeader = `# This file was autogenerated by the 'packer hcl2_upgrade' command. We
2020-08-25 10:51:43 +02:00
# recommend double checking that everything is correct before going forward. We
# also recommend treating this file as disposable. The HCL2 blocks in this
# file can be moved to other files. For example, the variable blocks could be
# moved to their own 'variables.pkr.hcl' file, etc. Those files need to be
# suffixed with '.pkr.hcl' to be visible to Packer. To use multiple files at
# once they also need to be in the same folder. 'packer inspect folder/'
# will describe to you what is in that folder.
2020-12-09 03:39:54 -08:00
# Avoid mixing go templating calls ( for example ` + "```{{ upper(`string`) }}```" + ` )
2020-11-10 10:46:20 +01:00
# and HCL2 calls (for example '${ var.string_value_example }' ). They won't be
# executed together and the outcome will be unknown.
`
inputVarHeader = `
2020-10-14 14:52:17 -04:00
# All generated input variables will be of 'string' type as this is how Packer JSON
# views them; you can change their type later on. Read the variables type
2020-08-25 10:51:43 +02:00
# constraints documentation
2021-02-08 11:28:26 +01:00
# https://www.packer.io/docs/templates/hcl_templates/variables#type-constraints for more info.`
localsVarHeader = `
# All locals variables are generated from variables that uses expressions
# that are not allowed in HCL2 variables.
# Read the documentation for locals blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/locals`
2020-11-10 10:46:20 +01:00
packerBlockHeader = `
2021-01-14 14:55:19 -08:00
# See https://www.packer.io/docs/templates/hcl_templates/blocks/packer for more info
2020-08-25 10:51:43 +02:00
`
sourcesHeader = `
# source blocks are generated from your builders; a source can be referenced in
2020-10-14 14:52:17 -04:00
# build blocks. A build block runs provisioner and post-processors on a
2020-08-25 10:51:43 +02:00
# source. Read the documentation for source blocks here:
2021-01-14 14:55:19 -08:00
# https://www.packer.io/docs/templates/hcl_templates/blocks/source`
2020-08-25 10:51:43 +02:00
buildHeader = `
2020-10-14 14:52:17 -04:00
# a build block invokes sources and runs provisioning steps on them. The
2020-08-25 10:51:43 +02:00
# documentation for build blocks can be found here:
2021-01-14 14:55:19 -08:00
# https://www.packer.io/docs/templates/hcl_templates/blocks/build
2020-08-25 10:51:43 +02:00
`
2021-02-08 11:28:26 +01:00
2021-01-19 10:21:39 +01:00
amazonAmiDataHeader = `
# The amazon-ami data block is generated from your amazon builder source_ami_filter; a data
# from this block can be referenced in source and locals blocks.
# Read the documentation for data blocks here:
2021-02-08 11:28:26 +01:00
# https://www.packer.io/docs/templates/hcl_templates/blocks/data
# Read the documentation for the Amazon AMI Data Source here:
2021-03-25 14:02:21 +01:00
# https://www.packer.io/docs/datasources/amazon/ami`
2021-02-08 11:28:26 +01:00
amazonSecretsManagerDataHeader = `
# The amazon-secretsmanager data block is generated from your aws_secretsmanager template function; a data
# from this block can be referenced in source and locals blocks.
# Read the documentation for data blocks here:
# https://www.packer.io/docs/templates/hcl_templates/blocks/data
# Read the documentation for the Amazon Secrets Manager Data Source here:
2021-03-25 14:02:21 +01:00
# https://www.packer.io/docs/datasources/amazon/secretsmanager`
2020-08-25 10:51:43 +02:00
)
2021-02-10 14:54:19 +01:00
var (
amazonSecretsManagerMap = map [ string ] map [ string ] interface {}{}
localsVariableMap = map [ string ] string {}
timestamp = false
2021-03-17 16:06:19 -07:00
isotime = false
2021-02-10 14:54:19 +01:00
)
2021-02-08 11:28:26 +01:00
2021-02-12 15:18:53 +01:00
type BlockParser interface {
Parse ( * template . Template ) error
Write ( * bytes . Buffer )
}
2021-02-10 14:54:19 +01:00
func ( c * HCL2UpgradeCommand ) RunContext ( _ context . Context , cla * HCL2UpgradeArgs ) int {
2020-08-25 10:51:43 +02:00
var output io . Writer
if err := os . MkdirAll ( filepath . Dir ( cla . OutputFile ), 0 ); err != nil {
c . Ui . Error ( fmt . Sprintf ( "Failed to create output directory: %v" , err ))
return 1
}
if f , err := os . Create ( cla . OutputFile ); err == nil {
output = f
defer f . Close ()
} else {
c . Ui . Error ( fmt . Sprintf ( "Failed to create output file: %v" , err ))
return 1
}
2021-02-12 15:18:53 +01:00
if cla . WithAnnotations {
if _ , err := output . Write ([] byte ( hcl2UpgradeFileHeader )); err != nil {
c . Ui . Error ( fmt . Sprintf ( "Failed to write to file: %v" , err ))
return 1
}
2020-08-25 10:51:43 +02:00
}
hdl , ret := c . GetConfigFromJSON ( & cla . MetaArgs )
if ret != 0 {
return ret
}
core := hdl .( * CoreWrapper ). Core
if err := core . Initialize (); err != nil {
2020-11-10 10:46:20 +01:00
c . Ui . Error ( fmt . Sprintf ( "Ignoring following initialization error: %v" , err ))
2020-08-25 10:51:43 +02:00
}
tpl := core . Template
2021-02-12 15:18:53 +01:00
// Parse blocks
2020-11-10 10:46:20 +01:00
2021-02-12 15:18:53 +01:00
packerBlock := & PackerParser {
2021-02-12 15:37:41 +01:00
WithAnnotations : cla . WithAnnotations ,
2021-02-12 15:18:53 +01:00
}
if err := packerBlock . Parse ( tpl ); err != nil {
c . Ui . Error ( err . Error ())
return 1
2020-08-25 10:51:43 +02:00
}
2021-02-12 15:18:53 +01:00
variables := & VariableParser {
2021-02-12 15:37:41 +01:00
WithAnnotations : cla . WithAnnotations ,
2021-02-12 15:18:53 +01:00
}
if err := variables . Parse ( tpl ); err != nil {
c . Ui . Error ( err . Error ())
return 1
2020-08-25 10:51:43 +02:00
}
2021-02-12 15:18:53 +01:00
locals := & LocalsParser {
LocalsOut : variables . localsOut ,
2021-02-12 15:37:41 +01:00
WithAnnotations : cla . WithAnnotations ,
2021-02-12 15:18:53 +01:00
}
if err := locals . Parse ( tpl ); err != nil {
c . Ui . Error ( err . Error ())
return 1
2021-02-09 14:57:42 +01:00
}
2020-08-25 10:51:43 +02:00
builders := [] * template . Builder {}
{
// sort builders to avoid map's randomnes
for _ , builder := range tpl . Builders {
builders = append ( builders , builder )
}
}
2021-02-15 17:35:03 +01:00
sort . Slice ( builders , func ( i , j int ) bool {
return builders [ i ]. Type + builders [ i ]. Name < builders [ j ]. Type + builders [ j ]. Name
})
2020-08-25 10:51:43 +02:00
2021-02-12 15:18:53 +01:00
amazonAmiDatasource := & AmazonAmiDatasourceParser {
2021-02-12 15:37:41 +01:00
Builders : builders ,
WithAnnotations : cla . WithAnnotations ,
2021-02-12 15:18:53 +01:00
}
if err := amazonAmiDatasource . Parse ( tpl ); err != nil {
c . Ui . Error ( err . Error ())
2021-01-19 10:21:39 +01:00
return 1
}
2021-02-12 15:18:53 +01:00
sources := & SourceParser {
Builders : builders ,
BuilderPlugins : c . Meta . CoreConfig . Components . PluginConfig . Builders ,
2021-02-12 15:37:41 +01:00
WithAnnotations : cla . WithAnnotations ,
2021-02-12 15:18:53 +01:00
}
if err := sources . Parse ( tpl ); err != nil {
c . Ui . Error ( err . Error ())
return 1
2020-08-25 10:51:43 +02:00
}
2021-02-12 15:18:53 +01:00
build := & BuildParser {
Builders : builders ,
2021-02-12 15:37:41 +01:00
WithAnnotations : cla . WithAnnotations ,
2021-02-12 15:18:53 +01:00
}
if err := build . Parse ( tpl ); err != nil {
c . Ui . Error ( err . Error ())
return 1
2020-08-25 10:51:43 +02:00
}
2021-02-12 15:18:53 +01:00
amazonSecretsDatasource := & AmazonSecretsDatasourceParser {
2021-02-12 15:37:41 +01:00
WithAnnotations : cla . WithAnnotations ,
2020-08-25 10:51:43 +02:00
}
2021-02-12 15:18:53 +01:00
if err := amazonSecretsDatasource . Parse ( tpl ); err != nil {
c . Ui . Error ( err . Error ())
return 1
2021-02-08 11:28:26 +01:00
}
// Write file
out := & bytes . Buffer {}
2021-02-15 17:35:03 +01:00
for _ , block := range [] BlockParser {
packerBlock ,
variables ,
amazonSecretsDatasource ,
amazonAmiDatasource ,
locals ,
sources ,
build ,
} {
block . Write ( out )
2021-02-08 11:28:26 +01:00
}
2021-02-12 15:18:53 +01:00
if _ , err := output . Write ( hclwrite . Format ( out . Bytes ())); err != nil {
c . Ui . Error ( fmt . Sprintf ( "Failed to write to file: %v" , err ))
return 1
2021-02-11 10:23:15 +01:00
}
2021-02-08 11:28:26 +01:00
2020-08-25 10:51:43 +02:00
c . Ui . Say ( fmt . Sprintf ( "Successfully created %s " , cla . OutputFile ))
return 0
}
2020-11-10 10:46:20 +01:00
type UnhandleableArgumentError struct {
Call string
Correspondance string
Docs string
}
func ( uc UnhandleableArgumentError ) Error () string {
return fmt . Sprintf ( `unhandled %q call:
# there is no way to automatically upgrade the %[1]q call.
# Please manually upgrade to %s
# Visit %s for more infos.` , uc . Call , uc . Correspondance , uc . Docs )
}
2021-03-19 02:24:49 -07:00
func fallbackReturn ( err error , s [] byte ) [] byte {
if strings . Contains ( err . Error (), "unhandled" ) {
return append ([] byte ( fmt . Sprintf ( "\n# %s\n" , err )), s ... )
}
return append ([] byte ( fmt . Sprintf ( "\n# could not parse template for following block: %q\n" , err )), s ... )
}
2020-08-25 10:51:43 +02:00
// transposeTemplatingCalls executes parts of blocks as go template files and replaces
// their result with their hcl2 variant. If something goes wrong the template
// containing the go template string is returned.
func transposeTemplatingCalls ( s [] byte ) [] byte {
2021-02-23 16:51:13 +01:00
funcErrors := & multierror . Error {
ErrorFormat : func ( es [] error ) string {
if len ( es ) == 1 {
return fmt . Sprintf ( "# 1 error occurred upgrading the following block:\n\t# %s\n" , es [ 0 ])
}
2021-02-08 11:28:26 +01:00
2021-02-23 16:51:13 +01:00
points := make ([] string , len ( es ))
for i , err := range es {
if i == len ( es ) - 1 {
points [ i ] = fmt . Sprintf ( "# %s" , err )
continue
}
points [ i ] = fmt . Sprintf ( "# %s\n" , err )
}
return fmt . Sprintf (
"# %d errors occurred upgrading the following block:\n\t%s" ,
len ( es ), strings . Join ( points , "\n\t" ))
},
2021-02-08 11:28:26 +01:00
}
2021-02-23 16:51:13 +01:00
funcMap := texttemplate . FuncMap {
2021-02-08 11:28:26 +01:00
"aws_secretsmanager" : func ( a ... string ) string {
if len ( a ) == 2 {
for key , config := range amazonSecretsManagerMap {
nameOk := config [ "name" ] == a [ 0 ]
keyOk := config [ "key" ] == a [ 1 ]
if nameOk && keyOk {
return fmt . Sprintf ( "${data.amazon-secretsmanager.%s.value}" , key )
}
}
id := fmt . Sprintf ( "autogenerated_%d" , len ( amazonSecretsManagerMap ) + 1 )
amazonSecretsManagerMap [ id ] = map [ string ] interface {}{
"name" : a [ 0 ],
"key" : a [ 1 ],
}
return fmt . Sprintf ( "${data.amazon-secretsmanager.%s.value}" , id )
}
for key , config := range amazonSecretsManagerMap {
nameOk := config [ "name" ] == a [ 0 ]
if nameOk {
return fmt . Sprintf ( "${data.amazon-secretsmanager.%s.value}" , key )
}
}
id := fmt . Sprintf ( "autogenerated_%d" , len ( amazonSecretsManagerMap ) + 1 )
amazonSecretsManagerMap [ id ] = map [ string ] interface {}{
"name" : a [ 0 ],
}
return fmt . Sprintf ( "${data.amazon-secretsmanager.%s.value}" , id )
2021-02-23 14:18:43 +01:00
},
"timestamp" : func () string {
2021-02-10 14:54:19 +01:00
timestamp = true
2020-08-25 10:51:43 +02:00
return "${local.timestamp}"
},
2021-02-23 14:18:43 +01:00
"isotime" : func ( a ... string ) string {
2021-03-17 14:35:30 -07:00
if len ( a ) == 0 {
2021-03-17 16:06:19 -07:00
// returns rfc3339 formatted string.
return "${timestamp()}"
2021-03-17 14:35:30 -07:00
}
// otherwise a valid isotime func has one input.
2021-03-17 16:06:19 -07:00
isotime = true
2021-03-17 14:35:30 -07:00
return fmt . Sprintf ( "${legacy_isotime(\"%s\")}" , a [ 0 ])
2020-08-25 10:51:43 +02:00
},
"user" : func ( in string ) string {
2021-02-08 11:28:26 +01:00
if _ , ok := localsVariableMap [ in ]; ok {
// variable is now a local
return fmt . Sprintf ( "${local.%s}" , in )
}
2020-08-25 10:51:43 +02:00
return fmt . Sprintf ( "${var.%s}" , in )
},
"env" : func ( in string ) string {
2020-11-11 20:54:22 +01:00
return fmt . Sprintf ( "${env(%q)}" , in )
2020-08-25 10:51:43 +02:00
},
"build" : func ( a string ) string {
return fmt . Sprintf ( "${build.%s}" , a )
},
2021-01-19 10:21:39 +01:00
"data" : func ( a string ) string {
return fmt . Sprintf ( "${data.%s}" , a )
},
2020-11-10 10:46:20 +01:00
"template_dir" : func () string {
return fmt . Sprintf ( "${path.root}" )
},
"pwd" : func () string {
return fmt . Sprintf ( "${path.cwd}" )
},
"packer_version" : func () string {
return fmt . Sprintf ( "${packer.version}" )
},
"uuid" : func () string {
return fmt . Sprintf ( "${uuidv4()}" )
},
2021-02-23 16:51:13 +01:00
"lower" : func ( a string ) ( string , error ) {
funcErrors = multierror . Append ( funcErrors , UnhandleableArgumentError {
2020-11-10 10:46:20 +01:00
"lower" ,
"`lower(var.example)`" ,
2021-01-14 14:55:19 -08:00
"https://www.packer.io/docs/templates/hcl_templates/functions/string/lower" ,
2021-02-23 16:51:13 +01:00
})
return fmt . Sprintf ( "{{ lower `%s` }}" , a ), nil
2020-11-10 10:46:20 +01:00
},
2021-02-23 16:51:13 +01:00
"upper" : func ( a string ) ( string , error ) {
funcErrors = multierror . Append ( funcErrors , UnhandleableArgumentError {
2020-11-10 10:46:20 +01:00
"upper" ,
"`upper(var.example)`" ,
2021-01-14 14:55:19 -08:00
"https://www.packer.io/docs/templates/hcl_templates/functions/string/upper" ,
2021-02-23 16:51:13 +01:00
})
return fmt . Sprintf ( "{{ upper `%s` }}" , a ), nil
2020-11-10 10:46:20 +01:00
},
2021-02-23 16:51:13 +01:00
"split" : func ( a , b string , n int ) ( string , error ) {
funcErrors = multierror . Append ( funcErrors , UnhandleableArgumentError {
2020-11-10 10:46:20 +01:00
"split" ,
"`split(separator, string)`" ,
2021-01-14 14:55:19 -08:00
"https://www.packer.io/docs/templates/hcl_templates/functions/string/split" ,
2021-02-23 16:51:13 +01:00
})
return fmt . Sprintf ( "{{ split `%s` `%s` %d }}" , a , b , n ), nil
2020-11-10 10:46:20 +01:00
},
2021-02-23 16:51:13 +01:00
"replace" : func ( a , b string , n int , c string ) ( string , error ) {
funcErrors = multierror . Append ( funcErrors , UnhandleableArgumentError {
2020-11-10 10:46:20 +01:00
"replace" ,
"`replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`" ,
2021-01-14 14:55:19 -08:00
"https://www.packer.io/docs/templates/hcl_templates/functions/string/replace or https://www.packer.io/docs/templates/hcl_templates/functions/string/regex_replace" ,
2021-02-23 16:51:13 +01:00
})
return fmt . Sprintf ( "{{ replace `%s` `%s` `%s` %d }}" , a , b , c , n ), nil
2020-11-10 10:46:20 +01:00
},
2021-02-23 16:51:13 +01:00
"replace_all" : func ( a , b , c string ) ( string , error ) {
funcErrors = multierror . Append ( funcErrors , UnhandleableArgumentError {
2020-11-10 10:46:20 +01:00
"replace_all" ,
"`replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`" ,
2021-01-14 14:55:19 -08:00
"https://www.packer.io/docs/templates/hcl_templates/functions/string/replace or https://www.packer.io/docs/templates/hcl_templates/functions/string/regex_replace" ,
2021-02-23 16:51:13 +01:00
})
return fmt . Sprintf ( "{{ replace_all `%s` `%s` `%s` }}" , a , b , c ), nil
2020-11-10 10:46:20 +01:00
},
2021-02-23 16:51:13 +01:00
"clean_resource_name" : func ( a string ) ( string , error ) {
funcErrors = multierror . Append ( funcErrors , UnhandleableArgumentError {
2020-11-10 10:46:20 +01:00
"clean_resource_name" ,
"use custom validation rules, `replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`" ,
2021-01-14 14:55:19 -08:00
"https://packer.io/docs/templates/hcl_templates/variables#custom-validation-rules" +
" , https://www.packer.io/docs/templates/hcl_templates/functions/string/replace" +
" or https://www.packer.io/docs/templates/hcl_templates/functions/string/regex_replace" ,
2021-02-23 16:51:13 +01:00
})
return fmt . Sprintf ( "{{ clean_resource_name `%s` }}" , a ), nil
2020-11-10 10:46:20 +01:00
},
"build_name" : func () string {
return fmt . Sprintf ( "${build.name}" )
},
"build_type" : func () string {
return fmt . Sprintf ( "${build.type}" )
},
2020-08-25 10:51:43 +02:00
}
2021-02-23 16:51:13 +01:00
tpl , err := texttemplate . New ( "hcl2_upgrade" ).
Funcs ( funcMap ).
Parse ( string ( s ))
if err != nil {
2021-03-22 02:56:30 -07:00
if strings . Contains ( err . Error (), "unexpected \"\\\\\" in operand" ) {
// This error occurs if the operand in the text template used
// escaped quoting \" instead of bactick quoting `
// Create a regex to do a string replace on this block, to fix
// quoting.
q := fixQuoting ( string ( s ))
unquoted := [] byte ( q )
tpl , err = texttemplate . New ( "hcl2_upgrade" ).
Funcs ( funcMap ).
Parse ( string ( unquoted ))
if err != nil {
return fallbackReturn ( err , unquoted )
}
} else {
return fallbackReturn ( err , s )
}
2021-02-23 16:51:13 +01:00
}
str := & bytes . Buffer {}
// PASSTHROUGHS is a map of variable-specific golang text template fields
// that should remain in the text template format.
if err := tpl . Execute ( str , PASSTHROUGHS ); err != nil {
2021-03-19 02:24:49 -07:00
return fallbackReturn ( err , s )
2021-02-23 16:51:13 +01:00
}
out := str . Bytes ()
if funcErrors . Len () > 0 {
return append ([] byte ( fmt . Sprintf ( "\n%s" , funcErrors )), out ... )
}
return out
2021-02-08 11:28:26 +01:00
}
// variableTransposeTemplatingCalls executes parts of blocks as go template files and replaces
// their result with their hcl2 variant for variables block only. If something goes wrong the template
// containing the go template string is returned.
// In variableTransposeTemplatingCalls the definition of aws_secretsmanager function will create a data source
// with the same name as the variable.
func variableTransposeTemplatingCalls ( s [] byte ) ( isLocal bool , body [] byte ) {
2021-02-23 14:18:43 +01:00
setIsLocal := func ( a ... string ) string {
2021-02-08 11:28:26 +01:00
isLocal = true
return ""
}
2021-02-23 14:18:43 +01:00
// Make locals from variables using valid template engine,
// expect the ones using only 'env'
// ref: https://www.packer.io/docs/templates/legacy_json_templates/engine#template-engine
2021-02-23 16:51:13 +01:00
funcMap := texttemplate . FuncMap {
"aws_secretsmanager" : setIsLocal ,
"timestamp" : setIsLocal ,
"isotime" : setIsLocal ,
"user" : setIsLocal ,
"env" : func ( in string ) string {
return fmt . Sprintf ( "${env(%q)}" , in )
},
"template_dir" : setIsLocal ,
"pwd" : setIsLocal ,
"packer_version" : setIsLocal ,
"uuid" : setIsLocal ,
"lower" : setIsLocal ,
"upper" : setIsLocal ,
"split" : func ( _ , _ string , _ int ) ( string , error ) {
isLocal = true
return "" , nil
},
"replace" : func ( _ , _ string , _ int , _ string ) ( string , error ) {
isLocal = true
return "" , nil
},
"replace_all" : func ( _ , _ , _ string ) ( string , error ) {
isLocal = true
return "" , nil
},
2021-02-22 17:14:01 +01:00
}
2020-08-25 10:51:43 +02:00
2020-11-10 10:46:20 +01:00
tpl , err := texttemplate . New ( "hcl2_upgrade" ).
2020-08-25 10:51:43 +02:00
Funcs ( funcMap ).
Parse ( string ( s ))
if err != nil {
2021-03-22 02:56:30 -07:00
if strings . Contains ( err . Error (), "unexpected \"\\\\\" in operand" ) {
// This error occurs if the operand in the text template used
// escaped quoting \" instead of bactick quoting `
// Create a regex to do a string replace on this block, to fix
// quoting.
q := fixQuoting ( string ( s ))
unquoted := [] byte ( q )
tpl , err = texttemplate . New ( "hcl2_upgrade" ).
Funcs ( funcMap ).
Parse ( string ( unquoted ))
if err != nil {
return isLocal , fallbackReturn ( err , unquoted )
}
} else {
return isLocal , fallbackReturn ( err , s )
}
2020-08-25 10:51:43 +02:00
}
str := & bytes . Buffer {}
2021-02-15 01:40:21 -08:00
// PASSTHROUGHS is a map of variable-specific golang text template fields
// that should remain in the text template format.
if err := tpl . Execute ( str , PASSTHROUGHS ); err != nil {
2021-03-19 02:24:49 -07:00
return isLocal , fallbackReturn ( err , s )
2020-08-25 10:51:43 +02:00
}
2021-02-08 11:28:26 +01:00
return isLocal , str . Bytes ()
2020-08-25 10:51:43 +02:00
}
func jsonBodyToHCL2Body ( out * hclwrite . Body , kvs map [ string ] interface {}) {
ks := [] string {}
for k := range kvs {
ks = append ( ks , k )
}
sort . Strings ( ks )
for _ , k := range ks {
value := kvs [ k ]
switch value := value .( type ) {
case map [ string ] interface {}:
2020-08-27 16:02:05 +02:00
var mostComplexElem interface {}
for _ , randomElem := range value {
// HACK: we take the most complex element of that map because
// in HCL2, map of objects can be bodies, for example:
// map containing object: source_ami_filter {} ( body )
// simple string/string map: tags = {} ) ( attribute )
//
// if we could not find an object in this map then it's most
// likely a plain map and so we guess it should be and
// attribute. Though now if value refers to something that is
// an object but only contains a string or a bool; we could
// generate a faulty object. For example a (somewhat invalid)
// source_ami_filter where only `most_recent` is set.
switch randomElem .( type ) {
case string , int , float64 , bool :
if mostComplexElem != nil {
continue
}
mostComplexElem = randomElem
default :
mostComplexElem = randomElem
}
2020-08-25 10:51:43 +02:00
}
2020-08-27 16:02:05 +02:00
switch mostComplexElem .( type ) {
case string , int , float64 , bool :
2020-08-25 10:51:43 +02:00
out . SetAttributeValue ( k , hcl2shim . HCL2ValueFromConfigValue ( value ))
default :
nestedBlockBody := out . AppendNewBlock ( k , nil ). Body ()
jsonBodyToHCL2Body ( nestedBlockBody , value )
}
2020-08-27 16:02:05 +02:00
case map [ string ] string , map [ string ] int , map [ string ] float64 :
out . SetAttributeValue ( k , hcl2shim . HCL2ValueFromConfigValue ( value ))
2020-08-25 10:51:43 +02:00
case [] interface {}:
if len ( value ) == 0 {
continue
}
2020-08-27 16:02:05 +02:00
var mostComplexElem interface {}
for _ , randomElem := range value {
// HACK: we take the most complex element of that slice because
// in hcl2 slices of plain types can be arrays, for example:
// simple string type: owners = ["0000000000"]
// object: launch_block_device_mappings {}
switch randomElem .( type ) {
case string , int , float64 , bool :
if mostComplexElem != nil {
continue
}
mostComplexElem = randomElem
default :
mostComplexElem = randomElem
}
}
switch mostComplexElem .( type ) {
2020-08-25 10:51:43 +02:00
case map [ string ] interface {}:
2020-08-27 16:02:05 +02:00
// this is an object in a slice; so we unwrap it. We
// could try to remove any 's' suffix in the key, but
// this might not work everywhere.
2020-08-25 10:51:43 +02:00
for i := range value {
value := value [ i ].( map [ string ] interface {})
nestedBlockBody := out . AppendNewBlock ( k , nil ). Body ()
jsonBodyToHCL2Body ( nestedBlockBody , value )
}
continue
default :
2020-08-27 16:02:05 +02:00
out . SetAttributeValue ( k , hcl2shim . HCL2ValueFromConfigValue ( value ))
2020-08-25 10:51:43 +02:00
}
default :
out . SetAttributeValue ( k , hcl2shim . HCL2ValueFromConfigValue ( value ))
}
}
}
func isSensitiveVariable ( key string , vars [] * template . Variable ) bool {
for _ , v := range vars {
if v . Key == key {
return true
}
}
return false
}
func ( * HCL2UpgradeCommand ) Help () string {
helpText := `
2021-02-15 02:03:44 -08:00
Usage: packer hcl2_upgrade [options] TEMPLATE
2020-08-25 10:51:43 +02:00
2020-10-14 14:52:17 -04:00
Will transform your JSON template into an HCL2 configuration.
2021-02-15 02:03:44 -08:00
Options:
-output-file=path Set output file name. By default this will be the
TEMPLATE name with ".pkr.hcl" appended to it. To be a
valid Packer HCL template, it must have the suffix
".pkr.hcl"
-with-annotations Add helper annotation comments to the file to help new
HCL2 users understand the template format.
2020-08-25 10:51:43 +02:00
`
return strings . TrimSpace ( helpText )
}
func ( * HCL2UpgradeCommand ) Synopsis () string {
2020-10-14 14:52:17 -04:00
return "transform a JSON template into an HCL2 configuration"
2020-08-25 10:51:43 +02:00
}
func ( * HCL2UpgradeCommand ) AutocompleteArgs () complete . Predictor {
return complete . PredictNothing
}
func ( * HCL2UpgradeCommand ) AutocompleteFlags () complete . Flags {
return complete . Flags {}
}
2021-02-12 15:18:53 +01:00
2021-02-12 15:37:41 +01:00
// Specific blocks parser responsible to parse and write the block
2021-02-12 15:18:53 +01:00
type PackerParser struct {
2021-02-12 15:37:41 +01:00
WithAnnotations bool
2021-02-12 15:18:53 +01:00
out [] byte
}
func ( p * PackerParser ) Parse ( tpl * template . Template ) error {
if tpl . MinVersion != "" {
fileContent := hclwrite . NewEmptyFile ()
body := fileContent . Body ()
packerBody := body . AppendNewBlock ( "packer" , nil ). Body ()
packerBody . SetAttributeValue ( "required_version" , cty . StringVal ( fmt . Sprintf ( ">= %s" , tpl . MinVersion )))
p . out = fileContent . Bytes ()
}
return nil
}
func ( p * PackerParser ) Write ( out * bytes . Buffer ) {
if len ( p . out ) > 0 {
2021-02-12 15:37:41 +01:00
if p . WithAnnotations {
2021-02-12 15:18:53 +01:00
out . Write ([] byte ( packerBlockHeader ))
}
out . Write ( p . out )
}
}
type VariableParser struct {
2021-02-12 15:37:41 +01:00
WithAnnotations bool
2021-02-12 15:18:53 +01:00
variablesOut [] byte
localsOut [] byte
}
2021-03-19 02:24:49 -07:00
func makeLocal ( variable * template . Variable , sensitive bool , localBody * hclwrite . Body , localsContent * hclwrite . File , hasLocals * bool ) [] byte {
if sensitive {
// Create Local block because this is sensitive
sensitiveLocalContent := hclwrite . NewEmptyFile ()
body := sensitiveLocalContent . Body ()
body . AppendNewline ()
sensitiveLocalBody := body . AppendNewBlock ( "local" , [] string { variable . Key }). Body ()
sensitiveLocalBody . SetAttributeValue ( "sensitive" , cty . BoolVal ( true ))
sensitiveLocalBody . SetAttributeValue ( "expression" , hcl2shim . HCL2ValueFromConfigValue ( variable . Default ))
localsVariableMap [ variable . Key ] = "local"
return sensitiveLocalContent . Bytes ()
}
localBody . SetAttributeValue ( variable . Key , hcl2shim . HCL2ValueFromConfigValue ( variable . Default ))
localsVariableMap [ variable . Key ] = "locals"
* hasLocals = true
return [] byte {}
}
func makeVariable ( variable * template . Variable , sensitive bool ) [] byte {
variablesContent := hclwrite . NewEmptyFile ()
variablesBody := variablesContent . Body ()
variablesBody . AppendNewline ()
variableBody := variablesBody . AppendNewBlock ( "variable" , [] string { variable . Key }). Body ()
variableBody . SetAttributeRaw ( "type" , hclwrite . Tokens { & hclwrite . Token { Bytes : [] byte ( "string" )}})
if variable . Default != "" || ! variable . Required {
shimmed := hcl2shim . HCL2ValueFromConfigValue ( variable . Default )
variableBody . SetAttributeValue ( "default" , shimmed )
}
if sensitive {
variableBody . SetAttributeValue ( "sensitive" , cty . BoolVal ( true ))
}
return variablesContent . Bytes ()
}
2021-02-12 15:18:53 +01:00
func ( p * VariableParser ) Parse ( tpl * template . Template ) error {
2021-03-19 02:24:49 -07:00
// Output Locals and Local blocks
2021-02-12 15:18:53 +01:00
localsContent := hclwrite . NewEmptyFile ()
localsBody := localsContent . Body ()
localsBody . AppendNewline ()
localBody := localsBody . AppendNewBlock ( "locals" , nil ). Body ()
2021-03-19 02:24:49 -07:00
hasLocals := false
2021-02-12 15:18:53 +01:00
if len ( p . variablesOut ) == 0 {
p . variablesOut = [] byte {}
}
if len ( p . localsOut ) == 0 {
p . localsOut = [] byte {}
}
variables := [] * template . Variable {}
{
// sort variables to avoid map's randomness
for _ , variable := range tpl . Variables {
variables = append ( variables , variable )
}
sort . Slice ( variables , func ( i , j int ) bool {
return variables [ i ]. Key < variables [ j ]. Key
})
}
for _ , variable := range variables {
2021-03-19 02:24:49 -07:00
// Create new HCL2 "variables" block, and populate the "value"
// field with the "Default" value from the JSON variable.
2021-02-12 15:18:53 +01:00
2021-03-19 02:24:49 -07:00
// Interpolate Jsonval first as an hcl variable to determine if it is
// a local.
isLocal , _ := variableTransposeTemplatingCalls ([] byte ( variable . Default ))
2021-02-12 15:18:53 +01:00
sensitive := false
if isSensitiveVariable ( variable . Key , tpl . SensitiveVariables ) {
sensitive = true
}
2021-03-19 02:24:49 -07:00
// Create final HCL block and append.
2021-02-12 15:18:53 +01:00
if isLocal {
2021-03-19 02:24:49 -07:00
sensitiveBlocks := makeLocal ( variable , sensitive , localBody , localsContent , & hasLocals )
if len ( sensitiveBlocks ) > 0 {
p . localsOut = append ( p . localsOut , transposeTemplatingCalls ( sensitiveBlocks ) ... )
2021-02-12 15:18:53 +01:00
}
continue
}
2021-03-19 02:24:49 -07:00
varbytes := makeVariable ( variable , sensitive )
_ , out := variableTransposeTemplatingCalls ( varbytes )
2021-02-12 15:18:53 +01:00
p . variablesOut = append ( p . variablesOut , out ... )
}
2021-03-19 02:24:49 -07:00
if hasLocals == true {
2021-02-12 15:18:53 +01:00
p . localsOut = append ( p . localsOut , transposeTemplatingCalls ( localsContent . Bytes ()) ... )
}
2021-03-19 02:24:49 -07:00
2021-02-12 15:18:53 +01:00
return nil
}
func ( p * VariableParser ) Write ( out * bytes . Buffer ) {
if len ( p . variablesOut ) > 0 {
2021-02-12 15:37:41 +01:00
if p . WithAnnotations {
2021-02-12 15:18:53 +01:00
out . Write ([] byte ( inputVarHeader ))
}
out . Write ( p . variablesOut )
}
}
type LocalsParser struct {
2021-02-12 15:37:41 +01:00
WithAnnotations bool
2021-02-12 15:18:53 +01:00
LocalsOut [] byte
}
func ( p * LocalsParser ) Parse ( tpl * template . Template ) error {
// Locals where parsed with Variables
return nil
}
func ( p * LocalsParser ) Write ( out * bytes . Buffer ) {
if timestamp {
_ , _ = out . Write ([] byte ( "\n" ))
2021-02-12 15:37:41 +01:00
if p . WithAnnotations {
2021-02-12 15:18:53 +01:00
fmt . Fprintln ( out , `# "timestamp" template function replacement` )
}
fmt . Fprintln ( out , `locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") }` )
}
2021-03-17 16:06:19 -07:00
if isotime {
fmt . Fprintln ( out , `# The "legacy_isotime" function has been provided for backwards compatability, but we recommend switching to the timestamp and formatdate functions.` )
}
2021-02-12 15:18:53 +01:00
if len ( p . LocalsOut ) > 0 {
2021-02-12 15:37:41 +01:00
if p . WithAnnotations {
2021-02-12 15:18:53 +01:00
out . Write ([] byte ( localsVarHeader ))
}
out . Write ( p . LocalsOut )
}
}
type AmazonSecretsDatasourceParser struct {
2021-02-12 15:37:41 +01:00
WithAnnotations bool
2021-02-12 15:18:53 +01:00
out [] byte
}
func ( p * AmazonSecretsDatasourceParser ) Parse ( _ * template . Template ) error {
if p . out == nil {
p . out = [] byte {}
}
keys := make ([] string , 0 , len ( amazonSecretsManagerMap ))
for k := range amazonSecretsManagerMap {
keys = append ( keys , k )
}
sort . Strings ( keys )
for _ , dataSourceName := range keys {
datasourceContent := hclwrite . NewEmptyFile ()
body := datasourceContent . Body ()
body . AppendNewline ()
datasourceBody := body . AppendNewBlock ( "data" , [] string { "amazon-secretsmanager" , dataSourceName }). Body ()
jsonBodyToHCL2Body ( datasourceBody , amazonSecretsManagerMap [ dataSourceName ])
p . out = append ( p . out , datasourceContent . Bytes () ... )
}
return nil
}
func ( p * AmazonSecretsDatasourceParser ) Write ( out * bytes . Buffer ) {
if len ( p . out ) > 0 {
2021-02-12 15:37:41 +01:00
if p . WithAnnotations {
2021-02-12 15:18:53 +01:00
out . Write ([] byte ( amazonSecretsManagerDataHeader ))
}
out . Write ( p . out )
}
}
type AmazonAmiDatasourceParser struct {
2021-02-12 15:37:41 +01:00
Builders [] * template . Builder
WithAnnotations bool
2021-02-12 15:18:53 +01:00
out [] byte
}
func ( p * AmazonAmiDatasourceParser ) Parse ( _ * template . Template ) error {
if p . out == nil {
p . out = [] byte {}
}
amazonAmiFilters := [] map [ string ] interface {}{}
i := 1
2021-02-12 15:37:41 +01:00
for _ , builder := range p . Builders {
2021-02-12 15:18:53 +01:00
if strings . HasPrefix ( builder . Type , "amazon-" ) {
if sourceAmiFilter , ok := builder . Config [ "source_ami_filter" ]; ok {
sourceAmiFilterCfg := map [ string ] interface {}{}
if err := mapstructure . Decode ( sourceAmiFilter , & sourceAmiFilterCfg ); err != nil {
return fmt . Errorf ( "Failed to write amazon-ami data source: %v" , err )
}
2021-02-15 17:35:03 +01:00
sourceAmiFilterCfg , err := copyAWSAccessConfig ( sourceAmiFilterCfg , builder . Config )
if err != nil {
return err
}
2021-02-12 15:18:53 +01:00
duplicate := false
dataSourceName := fmt . Sprintf ( "autogenerated_%d" , i )
for j , filter := range amazonAmiFilters {
2021-02-15 17:35:03 +01:00
if reflect . DeepEqual ( filter , sourceAmiFilterCfg ) {
2021-02-12 15:18:53 +01:00
duplicate = true
dataSourceName = fmt . Sprintf ( "autogenerated_%d" , j + 1 )
continue
}
}
// This is a hack...
// Use templating so that it could be correctly transformed later into a data resource
sourceAmiDataRef := fmt . Sprintf ( "{{ data `amazon-ami.%s.id` }}" , dataSourceName )
if duplicate {
delete ( builder . Config , "source_ami_filter" )
builder . Config [ "source_ami" ] = sourceAmiDataRef
continue
}
amazonAmiFilters = append ( amazonAmiFilters , sourceAmiFilterCfg )
delete ( builder . Config , "source_ami_filter" )
builder . Config [ "source_ami" ] = sourceAmiDataRef
i ++
datasourceContent := hclwrite . NewEmptyFile ()
body := datasourceContent . Body ()
body . AppendNewline ()
sourceBody := body . AppendNewBlock ( "data" , [] string { "amazon-ami" , dataSourceName }). Body ()
jsonBodyToHCL2Body ( sourceBody , sourceAmiFilterCfg )
p . out = append ( p . out , transposeTemplatingCalls ( datasourceContent . Bytes ()) ... )
}
}
}
return nil
}
2021-02-15 17:35:03 +01:00
func copyAWSAccessConfig ( sourceAmi map [ string ] interface {}, builder map [ string ] interface {}) ( map [ string ] interface {}, error ) {
// Transform access config to a map
accessConfigMap := map [ string ] interface {}{}
if err := mapstructure . Decode ( awscommon . AccessConfig {}, & accessConfigMap ); err != nil {
return sourceAmi , err
}
for k := range accessConfigMap {
// Copy only access config present in the builder
if v , ok := builder [ k ]; ok {
sourceAmi [ k ] = v
}
}
return sourceAmi , nil
}
2021-02-12 15:18:53 +01:00
func ( p * AmazonAmiDatasourceParser ) Write ( out * bytes . Buffer ) {
if len ( p . out ) > 0 {
2021-02-12 15:37:41 +01:00
if p . WithAnnotations {
2021-02-12 15:18:53 +01:00
out . Write ([] byte ( amazonAmiDataHeader ))
}
out . Write ( p . out )
}
}
type SourceParser struct {
2021-02-12 15:37:41 +01:00
Builders [] * template . Builder
BuilderPlugins packer . BuilderSet
WithAnnotations bool
2021-02-12 15:18:53 +01:00
out [] byte
}
func ( p * SourceParser ) Parse ( tpl * template . Template ) error {
if p . out == nil {
p . out = [] byte {}
}
for i , builderCfg := range p . Builders {
sourcesContent := hclwrite . NewEmptyFile ()
body := sourcesContent . Body ()
body . AppendNewline ()
if ! p . BuilderPlugins . Has ( builderCfg . Type ) {
return fmt . Errorf ( "unknown builder type: %q\n" , builderCfg . Type )
}
if builderCfg . Name == "" || builderCfg . Name == builderCfg . Type {
builderCfg . Name = fmt . Sprintf ( "autogenerated_%d" , i + 1 )
}
builderCfg . Name = strings . ReplaceAll ( strings . TrimSpace ( builderCfg . Name ), " " , "_" )
sourceBody := body . AppendNewBlock ( "source" , [] string { builderCfg . Type , builderCfg . Name }). Body ()
jsonBodyToHCL2Body ( sourceBody , builderCfg . Config )
p . out = append ( p . out , transposeTemplatingCalls ( sourcesContent . Bytes ()) ... )
}
return nil
}
func ( p * SourceParser ) Write ( out * bytes . Buffer ) {
if len ( p . out ) > 0 {
2021-02-12 15:37:41 +01:00
if p . WithAnnotations {
2021-02-12 15:18:53 +01:00
out . Write ([] byte ( sourcesHeader ))
}
out . Write ( p . out )
}
}
type BuildParser struct {
2021-02-12 15:37:41 +01:00
Builders [] * template . Builder
WithAnnotations bool
2021-02-12 15:18:53 +01:00
provisioners BlockParser
postProcessors BlockParser
2021-02-12 15:37:41 +01:00
out [] byte
2021-02-12 15:18:53 +01:00
}
func ( p * BuildParser ) Parse ( tpl * template . Template ) error {
2021-02-22 16:07:37 +01:00
if len ( p . Builders ) == 0 {
return nil
}
2021-02-12 15:18:53 +01:00
buildContent := hclwrite . NewEmptyFile ()
buildBody := buildContent . Body ()
if tpl . Description != "" {
buildBody . SetAttributeValue ( "description" , cty . StringVal ( tpl . Description ))
buildBody . AppendNewline ()
}
sourceNames := [] string {}
for _ , builder := range p . Builders {
sourceNames = append ( sourceNames , fmt . Sprintf ( "source.%s.%s" , builder . Type , builder . Name ))
}
buildBody . SetAttributeValue ( "sources" , hcl2shim . HCL2ValueFromConfigValue ( sourceNames ))
buildBody . AppendNewline ()
p . out = buildContent . Bytes ()
p . provisioners = & ProvisionerParser {
2021-02-12 15:37:41 +01:00
WithAnnotations : p . WithAnnotations ,
2021-02-12 15:18:53 +01:00
}
if err := p . provisioners . Parse ( tpl ); err != nil {
return err
}
p . postProcessors = & PostProcessorParser {
2021-02-12 15:37:41 +01:00
WithAnnotations : p . WithAnnotations ,
2021-02-12 15:18:53 +01:00
}
if err := p . postProcessors . Parse ( tpl ); err != nil {
return err
}
return nil
}
func ( p * BuildParser ) Write ( out * bytes . Buffer ) {
if len ( p . out ) > 0 {
2021-02-12 15:37:41 +01:00
if p . WithAnnotations {
2021-02-12 15:18:53 +01:00
out . Write ([] byte ( buildHeader ))
} else {
_ , _ = out . Write ([] byte ( "\n" ))
}
_ , _ = out . Write ([] byte ( "build {\n" ))
out . Write ( p . out )
p . provisioners . Write ( out )
p . postProcessors . Write ( out )
_ , _ = out . Write ([] byte ( "}\n" ))
}
}
type ProvisionerParser struct {
2021-02-12 15:37:41 +01:00
WithAnnotations bool
2021-02-12 15:18:53 +01:00
out [] byte
}
func ( p * ProvisionerParser ) Parse ( tpl * template . Template ) error {
if p . out == nil {
p . out = [] byte {}
}
for _ , provisioner := range tpl . Provisioners {
contentBytes := writeProvisioner ( "provisioner" , provisioner )
p . out = append ( p . out , transposeTemplatingCalls ( contentBytes ) ... )
}
if tpl . CleanupProvisioner != nil {
contentBytes := writeProvisioner ( "error-cleanup-provisioner" , tpl . CleanupProvisioner )
p . out = append ( p . out , transposeTemplatingCalls ( contentBytes ) ... )
}
return nil
}
func writeProvisioner ( typeName string , provisioner * template . Provisioner ) [] byte {
provisionerContent := hclwrite . NewEmptyFile ()
body := provisionerContent . Body ()
block := body . AppendNewBlock ( typeName , [] string { provisioner . Type })
2021-03-05 11:10:00 +01:00
2021-02-12 15:18:53 +01:00
cfg := provisioner . Config
2021-03-05 11:10:00 +01:00
if cfg == nil {
cfg = map [ string ] interface {}{}
}
2021-02-12 15:18:53 +01:00
if len ( provisioner . Except ) > 0 {
cfg [ "except" ] = provisioner . Except
}
if len ( provisioner . Only ) > 0 {
cfg [ "only" ] = provisioner . Only
}
if provisioner . MaxRetries != "" {
cfg [ "max_retries" ] = provisioner . MaxRetries
}
if provisioner . Timeout > 0 {
cfg [ "timeout" ] = provisioner . Timeout . String ()
}
2021-02-18 15:05:12 +01:00
if provisioner . PauseBefore > 0 {
cfg [ "pause_before" ] = provisioner . PauseBefore . String ()
}
2021-02-12 15:18:53 +01:00
body . AppendNewline ()
jsonBodyToHCL2Body ( block . Body (), cfg )
return provisionerContent . Bytes ()
}
func ( p * ProvisionerParser ) Write ( out * bytes . Buffer ) {
if len ( p . out ) > 0 {
out . Write ( p . out )
}
}
type PostProcessorParser struct {
2021-02-12 15:37:41 +01:00
WithAnnotations bool
2021-02-12 15:18:53 +01:00
out [] byte
}
func ( p * PostProcessorParser ) Parse ( tpl * template . Template ) error {
if p . out == nil {
p . out = [] byte {}
}
for _ , pps := range tpl . PostProcessors {
postProcessorContent := hclwrite . NewEmptyFile ()
body := postProcessorContent . Body ()
switch len ( pps ) {
case 0 :
continue
case 1 :
default :
body = body . AppendNewBlock ( "post-processors" , nil ). Body ()
}
for _ , pp := range pps {
ppBody := body . AppendNewBlock ( "post-processor" , [] string { pp . Type }). Body ()
if pp . KeepInputArtifact != nil {
ppBody . SetAttributeValue ( "keep_input_artifact" , cty . BoolVal ( * pp . KeepInputArtifact ))
}
cfg := pp . Config
2021-03-05 11:10:00 +01:00
if cfg == nil {
cfg = map [ string ] interface {}{}
}
2021-02-12 15:18:53 +01:00
if len ( pp . Except ) > 0 {
cfg [ "except" ] = pp . Except
}
if len ( pp . Only ) > 0 {
cfg [ "only" ] = pp . Only
}
if pp . Name != "" && pp . Name != pp . Type {
cfg [ "name" ] = pp . Name
}
jsonBodyToHCL2Body ( ppBody , cfg )
}
p . out = append ( p . out , transposeTemplatingCalls ( postProcessorContent . Bytes ()) ... )
}
return nil
}
func ( p * PostProcessorParser ) Write ( out * bytes . Buffer ) {
if len ( p . out ) > 0 {
out . Write ( p . out )
}
}
2021-02-15 01:40:21 -08:00
var PASSTHROUGHS = map [ string ] string { "NVME_Present" : "{{ .NVME_Present }}" ,
"Usb_Present" : "{{ .Usb_Present }}" ,
"Serial_Type" : "{{ .Serial_Type }}" ,
"MapKey" : "{{ .MapKey }}" ,
"HostAlias" : "{{ .HostAlias }}" ,
"BoxName" : "{{ .BoxName }}" ,
"Port" : "{{ .Port }}" ,
"Header" : "{{ .Header }}" ,
"HTTPIP" : "{{ .HTTPIP }}" ,
"Host" : "{{ .Host }}" ,
"PACKER_TEST_TEMP" : "{{ .PACKER_TEST_TEMP }}" ,
"SCSI_diskAdapterType" : "{{ .SCSI_diskAdapterType }}" ,
"VHDBlockSizeBytes" : "{{ .VHDBlockSizeBytes }}" ,
"Parallel_Auto" : "{{ .Parallel_Auto }}" ,
"KTyp" : "{{ .KTyp }}" ,
"MemorySize" : "{{ .MemorySize }}" ,
"APIURL" : "{{ .APIURL }}" ,
"SourcePath" : "{{ .SourcePath }}" ,
"CDROMType" : "{{ .CDROMType }}" ,
"Parallel_Present" : "{{ .Parallel_Present }}" ,
"HTTPPort" : "{{ .HTTPPort }}" ,
"BuildName" : "{{ .BuildName }}" ,
"Network_Device" : "{{ .Network_Device }}" ,
"Flavor" : "{{ .Flavor }}" ,
"Image" : "{{ .Image }}" ,
"Os" : "{{ .Os }}" ,
"Network_Type" : "{{ .Network_Type }}" ,
"SourceOMIName" : "{{ .SourceOMIName }}" ,
"Serial_Yield" : "{{ .Serial_Yield }}" ,
"SourceAMI" : "{{ .SourceAMI }}" ,
"SSHHostPort" : "{{ .SSHHostPort }}" ,
"Vars" : "{{ .Vars }}" ,
"Slice" : "{{ .Slice }}" ,
"Version" : "{{ .Version }}" ,
"Parallel_Bidirectional" : "{{ .Parallel_Bidirectional }}" ,
"Serial_Auto" : "{{ .Serial_Auto }}" ,
"VHDX" : "{{ .VHDX }}" ,
"WinRMPassword" : "{{ .WinRMPassword }}" ,
"DefaultOrganizationID" : "{{ .DefaultOrganizationID }}" ,
"HTTPDir" : "{{ .HTTPDir }}" ,
2021-03-23 12:31:13 +01:00
"HTTPContent" : "{{ .HTTPContent }}" ,
2021-02-15 01:40:21 -08:00
"SegmentPath" : "{{ .SegmentPath }}" ,
"NewVHDSizeBytes" : "{{ .NewVHDSizeBytes }}" ,
"CTyp" : "{{ .CTyp }}" ,
"VMName" : "{{ .VMName }}" ,
"Serial_Present" : "{{ .Serial_Present }}" ,
"Varname" : "{{ .Varname }}" ,
"DiskNumber" : "{{ .DiskNumber }}" ,
"SecondID" : "{{ .SecondID }}" ,
"Typ" : "{{ .Typ }}" ,
"SourceAMIName" : "{{ .SourceAMIName }}" ,
"ActiveProfile" : "{{ .ActiveProfile }}" ,
"Primitive" : "{{ .Primitive }}" ,
"Elem" : "{{ .Elem }}" ,
"Network_Adapter" : "{{ .Network_Adapter }}" ,
"Minor" : "{{ .Minor }}" ,
"ProjectName" : "{{ .ProjectName }}" ,
"Generation" : "{{ .Generation }}" ,
"User" : "{{ .User }}" ,
"Size" : "{{ .Size }}" ,
"Parallel_Filename" : "{{ .Parallel_Filename }}" ,
"ID" : "{{ .ID }}" ,
"FastpathLen" : "{{ .FastpathLen }}" ,
"Tag" : "{{ .Tag }}" ,
"Serial_Endpoint" : "{{ .Serial_Endpoint }}" ,
"GuestOS" : "{{ .GuestOS }}" ,
"Major" : "{{ .Major }}" ,
"Serial_Filename" : "{{ .Serial_Filename }}" ,
"Name" : "{{ .Name }}" ,
"SourceOMI" : "{{ .SourceOMI }}" ,
"SCSI_Present" : "{{ .SCSI_Present }}" ,
"CpuCount" : "{{ .CpuCount }}" ,
"DefaultProjectID" : "{{ .DefaultProjectID }}" ,
"CDROMType_PrimarySecondary" : "{{ .CDROMType_PrimarySecondary }}" ,
"Arch" : "{{ .Arch }}" ,
"ImageFile" : "{{ .ImageFile }}" ,
"SATA_Present" : "{{ .SATA_Present }}" ,
"Serial_Host" : "{{ .Serial_Host }}" ,
"BuildRegion" : "{{ .BuildRegion }}" ,
"Id" : "{{ .Id }}" ,
"SyncedFolder" : "{{ .SyncedFolder }}" ,
"Network_Name" : "{{ .Network_Name }}" ,
"AccountID" : "{{ .AccountID }}" ,
"OPTION" : "{{ .OPTION }}" ,
"Type" : "{{ .Type }}" ,
"CustomVagrantfile" : "{{ .CustomVagrantfile }}" ,
"SendTelemetry" : "{{ .SendTelemetry }}" ,
"DiskType" : "{{ .DiskType }}" ,
"Password" : "{{ .Password }}" ,
"HardDrivePath" : "{{ .HardDrivePath }}" ,
"ISOPath" : "{{ .ISOPath }}" ,
"Insecure" : "{{ .Insecure }}" ,
"Region" : "{{ .Region }}" ,
"SecretKey" : "{{ .SecretKey }}" ,
"DefaultRegion" : "{{ .DefaultRegion }}" ,
"MemoryStartupBytes" : "{{ .MemoryStartupBytes }}" ,
"SwitchName" : "{{ .SwitchName }}" ,
"Path" : "{{ .Path }}" ,
"Username" : "{{ .Username }}" ,
"OutputDir" : "{{ .OutputDir }}" ,
"DiskName" : "{{ .DiskName }}" ,
"ProviderVagrantfile" : "{{ .ProviderVagrantfile }}" ,
"Sound_Present" : "{{ .Sound_Present }}" ,
}
2021-03-22 02:56:30 -07:00
func fixQuoting ( old string ) string {
// This regex captures golang template functions that use escaped quotes:
// {{ env \"myvar\" }}
re := regexp . MustCompile ( ` {{ \ s *\ w *\ s * ( \\ ".*\\" ) \ s * }} ` )
body := re . ReplaceAllFunc ([] byte ( old ), func ( s [] byte ) [] byte {
// Get the capture group
group := re . ReplaceAllString ( string ( s ), `$1` )
unquoted , err := strconv . Unquote ( fmt . Sprintf ( "\"%s\"" , group ))
if err != nil {
return s
}
return [] byte ( strings . Replace ( string ( s ), group , unquoted , 1 ))
})
return string ( body )
}