2020-08-25 04:51:43 -04:00
package command
import (
"bytes"
"context"
"fmt"
"io"
"os"
"path/filepath"
2021-01-19 04:21:39 -05:00
"reflect"
2020-08-25 04:51:43 -04:00
"sort"
"strings"
texttemplate "text/template"
"github.com/hashicorp/hcl/v2/hclwrite"
2021-01-20 04:37:16 -05:00
hcl2shim "github.com/hashicorp/packer-plugin-sdk/hcl2helper"
2020-12-17 16:29:25 -05:00
"github.com/hashicorp/packer-plugin-sdk/template"
2021-01-19 04:21:39 -05:00
"github.com/mitchellh/mapstructure"
2020-08-25 04:51:43 -04: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 09:08:04 -05:00
hcl2UpgradeFileHeader = ` # This file was autogenerated by the ' packer hcl2_upgrade ' command . We
2020-08-25 04:51:43 -04: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 06:39:54 -05:00
# Avoid mixing go templating calls ( for example ` + " ` ` ` { { upper ( ` string ` ) } } ` ` ` " + ` )
2020-11-10 04:46:20 -05: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 04:51:43 -04:00
# constraints documentation
2021-02-08 05:28:26 -05: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 04:46:20 -05:00
packerBlockHeader = `
2021-01-14 17:55:19 -05:00
# See https : //www.packer.io/docs/templates/hcl_templates/blocks/packer for more info
2020-08-25 04:51:43 -04: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 04:51:43 -04:00
# source . Read the documentation for source blocks here :
2021-01-14 17:55:19 -05:00
# https : //www.packer.io/docs/templates/hcl_templates/blocks/source`
2020-08-25 04:51:43 -04: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 04:51:43 -04:00
# documentation for build blocks can be found here :
2021-01-14 17:55:19 -05:00
# https : //www.packer.io/docs/templates/hcl_templates/blocks/build
2020-08-25 04:51:43 -04:00
build {
`
2021-02-08 05:28:26 -05:00
2021-01-19 04:21:39 -05: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 05:28:26 -05:00
# https : //www.packer.io/docs/templates/hcl_templates/blocks/data
# Read the documentation for the Amazon AMI Data Source here :
# https : //www.packer.io/docs/datasources/amazon/ami`
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 :
# https : //www.packer.io/docs/datasources/amazon/secretsmanager`
2020-08-25 04:51:43 -04:00
)
2021-02-10 08:54:19 -05:00
var (
amazonSecretsManagerMap = map [ string ] map [ string ] interface { } { }
localsVariableMap = map [ string ] string { }
timestamp = false
)
2021-02-08 05:28:26 -05:00
2021-02-10 08:54:19 -05:00
func ( c * HCL2UpgradeCommand ) RunContext ( _ context . Context , cla * HCL2UpgradeArgs ) int {
2020-08-25 04:51:43 -04: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
}
if _ , err := output . Write ( [ ] byte ( hcl2UpgradeFileHeader ) ) ; err != nil {
c . Ui . Error ( fmt . Sprintf ( "Failed to write to file: %v" , err ) )
return 1
}
hdl , ret := c . GetConfigFromJSON ( & cla . MetaArgs )
if ret != 0 {
return ret
}
core := hdl . ( * CoreWrapper ) . Core
if err := core . Initialize ( ) ; err != nil {
2020-11-10 04:46:20 -05:00
c . Ui . Error ( fmt . Sprintf ( "Ignoring following initialization error: %v" , err ) )
2020-08-25 04:51:43 -04:00
}
tpl := core . Template
2021-02-08 05:28:26 -05:00
// OutPut Locals and Local blocks
localsContent := hclwrite . NewEmptyFile ( )
localsBody := localsContent . Body ( )
localsBody . AppendNewline ( )
localBody := localsBody . AppendNewBlock ( "locals" , nil ) . Body ( )
2020-11-10 04:46:20 -05:00
2021-02-08 05:28:26 -05:00
localsOut := [ ] byte { }
2020-11-10 04:46:20 -05:00
2020-08-25 04:51:43 -04:00
// Output variables section
2021-02-08 05:28:26 -05:00
variablesOut := [ ] byte { }
2020-08-25 04:51:43 -04:00
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
} )
}
2021-02-09 08:57:42 -05:00
hasLocals := false
2020-08-25 04:51:43 -04:00
for _ , variable := range variables {
variablesContent := hclwrite . NewEmptyFile ( )
variablesBody := variablesContent . Body ( )
2021-02-08 05:28:26 -05:00
variablesBody . AppendNewline ( )
2020-08-25 04:51:43 -04:00
variableBody := variablesBody . AppendNewBlock ( "variable" , [ ] string { variable . Key } ) . Body ( )
variableBody . SetAttributeRaw ( "type" , hclwrite . Tokens { & hclwrite . Token { Bytes : [ ] byte ( "string" ) } } )
if variable . Default != "" || ! variable . Required {
variableBody . SetAttributeValue ( "default" , hcl2shim . HCL2ValueFromConfigValue ( variable . Default ) )
}
2021-02-08 05:28:26 -05:00
sensitive := false
2020-08-25 04:51:43 -04:00
if isSensitiveVariable ( variable . Key , tpl . SensitiveVariables ) {
2021-02-08 05:28:26 -05:00
sensitive = true
2020-08-25 04:51:43 -04:00
variableBody . SetAttributeValue ( "sensitive" , cty . BoolVal ( true ) )
}
2021-02-08 05:28:26 -05:00
isLocal , out := variableTransposeTemplatingCalls ( variablesContent . Bytes ( ) )
if isLocal {
if sensitive {
// Create Local block because this is sensitive
localContent := hclwrite . NewEmptyFile ( )
body := localContent . Body ( )
body . AppendNewline ( )
localBody := body . AppendNewBlock ( "local" , [ ] string { variable . Key } ) . Body ( )
localBody . SetAttributeValue ( "sensitive" , cty . BoolVal ( true ) )
localBody . SetAttributeValue ( "expression" , hcl2shim . HCL2ValueFromConfigValue ( variable . Default ) )
localsOut = append ( localsOut , transposeTemplatingCalls ( localContent . Bytes ( ) ) ... )
localsVariableMap [ variable . Key ] = "local"
continue
}
localBody . SetAttributeValue ( variable . Key , hcl2shim . HCL2ValueFromConfigValue ( variable . Default ) )
localsVariableMap [ variable . Key ] = "locals"
2021-02-09 08:57:42 -05:00
hasLocals = true
2021-02-08 05:28:26 -05:00
continue
}
variablesOut = append ( variablesOut , out ... )
2020-08-25 04:51:43 -04:00
}
2021-02-09 08:57:42 -05:00
if hasLocals {
localsOut = append ( localsOut , transposeTemplatingCalls ( localsContent . Bytes ( ) ) ... )
}
2020-08-25 04:51:43 -04:00
builders := [ ] * template . Builder { }
{
// sort builders to avoid map's randomnes
for _ , builder := range tpl . Builders {
builders = append ( builders , builder )
}
}
2021-02-08 05:28:26 -05:00
// Output amazon-ami data source section
amazonAmiOut , err := c . writeAmazonAmiDatasource ( builders )
if err != nil {
2021-01-19 04:21:39 -05:00
return 1
}
sort . Slice ( builders , func ( i , j int ) bool {
return builders [ i ] . Type + builders [ i ] . Name < builders [ j ] . Type + builders [ j ] . Name
} )
2021-02-08 05:28:26 -05:00
// Output sources section
sourcesOut := [ ] byte { }
2020-08-25 04:51:43 -04:00
for i , builderCfg := range builders {
sourcesContent := hclwrite . NewEmptyFile ( )
body := sourcesContent . Body ( )
body . AppendNewline ( )
2021-02-02 12:05:04 -05:00
if ! c . Meta . CoreConfig . Components . PluginConfig . Builders . Has ( builderCfg . Type ) {
2020-08-25 04:51:43 -04:00
c . Ui . Error ( fmt . Sprintf ( "unknown builder type: %q\n" , builderCfg . Type ) )
return 1
}
if builderCfg . Name == "" || builderCfg . Name == builderCfg . Type {
builderCfg . Name = fmt . Sprintf ( "autogenerated_%d" , i + 1 )
}
2021-02-09 08:57:42 -05:00
builderCfg . Name = strings . ReplaceAll ( strings . TrimSpace ( builderCfg . Name ) , " " , "_" )
2020-08-25 04:51:43 -04:00
sourceBody := body . AppendNewBlock ( "source" , [ ] string { builderCfg . Type , builderCfg . Name } ) . Body ( )
jsonBodyToHCL2Body ( sourceBody , builderCfg . Config )
2021-02-08 05:28:26 -05:00
sourcesOut = append ( sourcesOut , transposeTemplatingCalls ( sourcesContent . Bytes ( ) ) ... )
2020-08-25 04:51:43 -04:00
}
// Output build section
buildContent := hclwrite . NewEmptyFile ( )
buildBody := buildContent . Body ( )
if tpl . Description != "" {
buildBody . SetAttributeValue ( "description" , cty . StringVal ( tpl . Description ) )
buildBody . AppendNewline ( )
}
sourceNames := [ ] string { }
for _ , builder := range builders {
sourceNames = append ( sourceNames , fmt . Sprintf ( "source.%s.%s" , builder . Type , builder . Name ) )
}
buildBody . SetAttributeValue ( "sources" , hcl2shim . HCL2ValueFromConfigValue ( sourceNames ) )
buildBody . AppendNewline ( )
2021-02-08 05:28:26 -05:00
buildOut := buildContent . Bytes ( )
2020-08-25 04:51:43 -04:00
2021-02-08 05:28:26 -05:00
// Output provisioners section
provisionersOut := [ ] byte { }
2020-08-25 04:51:43 -04:00
for _ , provisioner := range tpl . Provisioners {
buildBody . AppendNewline ( )
2021-02-11 04:23:15 -05:00
contentBytes := c . writeProvisioner ( "provisioner" , provisioner )
provisionersOut = append ( provisionersOut , transposeTemplatingCalls ( contentBytes ) ... )
}
2020-08-25 04:51:43 -04:00
2021-02-11 04:23:15 -05:00
if tpl . CleanupProvisioner != nil {
buildBody . AppendNewline ( )
contentBytes := c . writeProvisioner ( "error-cleanup-provisioner" , tpl . CleanupProvisioner )
provisionersOut = append ( provisionersOut , transposeTemplatingCalls ( contentBytes ) ... )
2020-08-25 04:51:43 -04:00
}
2021-02-08 05:28:26 -05:00
// Output post-processors section
postProcessorsOut := [ ] byte { }
2020-08-25 04:51:43 -04:00
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
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 )
}
2021-02-08 05:28:26 -05:00
postProcessorsOut = append ( postProcessorsOut , transposeTemplatingCalls ( postProcessorContent . Bytes ( ) ) ... )
2020-08-25 04:51:43 -04:00
}
2021-02-08 05:28:26 -05:00
// Output amazon-secretsmanager data source section
keys := make ( [ ] string , 0 , len ( amazonSecretsManagerMap ) )
for k := range amazonSecretsManagerMap {
keys = append ( keys , k )
}
sort . Strings ( keys )
amazonSecretsDataOut := [ ] byte { }
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 ] )
amazonSecretsDataOut = append ( amazonSecretsDataOut , datasourceContent . Bytes ( ) ... )
}
// Write file
out := & bytes . Buffer { }
// Packer section
if tpl . MinVersion != "" {
out . Write ( [ ] byte ( packerBlockHeader ) )
fileContent := hclwrite . NewEmptyFile ( )
body := fileContent . Body ( )
packerBody := body . AppendNewBlock ( "packer" , nil ) . Body ( )
packerBody . SetAttributeValue ( "required_version" , cty . StringVal ( fmt . Sprintf ( ">= %s" , tpl . MinVersion ) ) )
out . Write ( fileContent . Bytes ( ) )
}
2021-02-11 04:23:15 -05:00
if len ( variablesOut ) > 0 {
out . Write ( [ ] byte ( inputVarHeader ) )
out . Write ( variablesOut )
}
2021-02-08 05:28:26 -05:00
if len ( amazonSecretsManagerMap ) > 0 {
out . Write ( [ ] byte ( amazonSecretsManagerDataHeader ) )
out . Write ( amazonSecretsDataOut )
}
if len ( amazonAmiOut ) > 0 {
out . Write ( [ ] byte ( amazonAmiDataHeader ) )
out . Write ( amazonAmiOut )
}
2021-02-10 08:54:19 -05:00
if timestamp {
_ , _ = out . Write ( [ ] byte ( "\n" ) )
fmt . Fprintln ( out , ` # "timestamp" template function replacement ` )
fmt . Fprintln ( out , ` locals { timestamp = regex_replace(timestamp(), "[- TZ:]", "") } ` )
}
2021-02-08 05:28:26 -05:00
if len ( localsOut ) > 0 {
out . Write ( [ ] byte ( localsVarHeader ) )
out . Write ( localsOut )
}
out . Write ( [ ] byte ( sourcesHeader ) )
out . Write ( sourcesOut )
out . Write ( [ ] byte ( buildHeader ) )
out . Write ( buildOut )
out . Write ( provisionersOut )
out . Write ( postProcessorsOut )
2020-08-25 04:51:43 -04:00
_ , _ = out . Write ( [ ] byte ( "}\n" ) )
_ , _ = output . Write ( hclwrite . Format ( out . Bytes ( ) ) )
c . Ui . Say ( fmt . Sprintf ( "Successfully created %s " , cla . OutputFile ) )
return 0
}
2021-02-11 04:23:15 -05:00
func ( c * HCL2UpgradeCommand ) writeProvisioner ( typeName string , provisioner * template . Provisioner ) [ ] byte {
provisionerContent := hclwrite . NewEmptyFile ( )
body := provisionerContent . Body ( )
block := body . AppendNewBlock ( typeName , [ ] string { provisioner . Type } )
cfg := provisioner . Config
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 ( )
}
jsonBodyToHCL2Body ( block . Body ( ) , cfg )
return provisionerContent . Bytes ( )
}
2021-02-08 05:28:26 -05:00
func ( c * HCL2UpgradeCommand ) writeAmazonAmiDatasource ( builders [ ] * template . Builder ) ( [ ] byte , error ) {
amazonAmiOut := [ ] byte { }
2021-01-19 04:21:39 -05:00
amazonAmiFilters := [ ] map [ string ] interface { } { }
i := 1
for _ , builder := range builders {
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 {
c . Ui . Error ( fmt . Sprintf ( "Failed to write amazon-ami data source: %v" , err ) )
2021-02-08 05:28:26 -05:00
return nil , err
2021-01-19 04:21:39 -05:00
}
duplicate := false
dataSourceName := fmt . Sprintf ( "autogenerated_%d" , i )
for j , filter := range amazonAmiFilters {
if reflect . DeepEqual ( filter , sourceAmiFilter ) {
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 )
2021-02-08 05:28:26 -05:00
amazonAmiOut = append ( amazonAmiOut , transposeTemplatingCalls ( datasourceContent . Bytes ( ) ) ... )
2021-01-19 04:21:39 -05:00
}
}
}
2021-02-08 05:28:26 -05:00
return amazonAmiOut , nil
2021-01-19 04:21:39 -05:00
}
2020-11-10 04:46:20 -05: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 )
}
2020-08-25 04:51:43 -04: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 {
fallbackReturn := func ( err error ) [ ] byte {
2020-11-10 04:46:20 -05:00
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 04:51:43 -04:00
}
2021-02-08 05:28:26 -05:00
funcMap := templateCommonFunctionMap ( )
tpl , err := texttemplate . New ( "hcl2_upgrade" ) .
Funcs ( funcMap ) .
Parse ( string ( s ) )
if err != nil {
return fallbackReturn ( err )
}
str := & bytes . Buffer { }
v := struct {
HTTPIP string
HTTPPort string
} {
HTTPIP : "{{ .HTTPIP }}" ,
HTTPPort : "{{ .HTTPPort }}" ,
}
if err := tpl . Execute ( str , v ) ; err != nil {
return fallbackReturn ( err )
}
return str . Bytes ( )
}
func templateCommonFunctionMap ( ) texttemplate . FuncMap {
return texttemplate . FuncMap {
"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 )
} , "timestamp" : func ( ) string {
2021-02-10 08:54:19 -05:00
timestamp = true
2020-08-25 04:51:43 -04:00
return "${local.timestamp}"
} ,
"isotime" : func ( ) string {
2021-02-10 08:54:19 -05:00
timestamp = true
2020-08-25 04:51:43 -04:00
return "${local.timestamp}"
} ,
"user" : func ( in string ) string {
2021-02-08 05:28:26 -05:00
if _ , ok := localsVariableMap [ in ] ; ok {
// variable is now a local
return fmt . Sprintf ( "${local.%s}" , in )
}
2020-08-25 04:51:43 -04:00
return fmt . Sprintf ( "${var.%s}" , in )
} ,
"env" : func ( in string ) string {
2020-11-11 14:54:22 -05:00
return fmt . Sprintf ( "${env(%q)}" , in )
2020-08-25 04:51:43 -04:00
} ,
"build" : func ( a string ) string {
return fmt . Sprintf ( "${build.%s}" , a )
} ,
2021-01-19 04:21:39 -05:00
"data" : func ( a string ) string {
return fmt . Sprintf ( "${data.%s}" , a )
} ,
2020-11-10 04:46:20 -05: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()}" )
} ,
"lower" : func ( _ string ) ( string , error ) {
return "" , UnhandleableArgumentError {
"lower" ,
"`lower(var.example)`" ,
2021-01-14 17:55:19 -05:00
"https://www.packer.io/docs/templates/hcl_templates/functions/string/lower" ,
2020-11-10 04:46:20 -05:00
}
} ,
"upper" : func ( _ string ) ( string , error ) {
return "" , UnhandleableArgumentError {
"upper" ,
"`upper(var.example)`" ,
2021-01-14 17:55:19 -05:00
"https://www.packer.io/docs/templates/hcl_templates/functions/string/upper" ,
2020-11-10 04:46:20 -05:00
}
} ,
"split" : func ( _ , _ string , _ int ) ( string , error ) {
return "" , UnhandleableArgumentError {
"split" ,
"`split(separator, string)`" ,
2021-01-14 17:55:19 -05:00
"https://www.packer.io/docs/templates/hcl_templates/functions/string/split" ,
2020-11-10 04:46:20 -05:00
}
} ,
"replace" : func ( _ , _ , _ string , _ int ) ( string , error ) {
return "" , UnhandleableArgumentError {
"replace" ,
"`replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`" ,
2021-01-14 17:55:19 -05: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" ,
2020-11-10 04:46:20 -05:00
}
} ,
"replace_all" : func ( _ , _ , _ string ) ( string , error ) {
return "" , UnhandleableArgumentError {
"replace_all" ,
"`replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`" ,
2021-01-14 17:55:19 -05: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" ,
2020-11-10 04:46:20 -05:00
}
} ,
"clean_resource_name" : func ( _ string ) ( string , error ) {
return "" , UnhandleableArgumentError {
"clean_resource_name" ,
"use custom validation rules, `replace(string, substring, replacement)` or `regex_replace(string, substring, replacement)`" ,
2021-01-14 17:55:19 -05: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" ,
2020-11-10 04:46:20 -05:00
}
} ,
"build_name" : func ( ) string {
return fmt . Sprintf ( "${build.name}" )
} ,
"build_type" : func ( ) string {
return fmt . Sprintf ( "${build.type}" )
} ,
2020-08-25 04:51:43 -04:00
}
2021-02-08 05:28:26 -05: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 ) {
fallbackReturn := func ( err error ) [ ] 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 ... )
}
funcMap := templateCommonFunctionMap ( )
funcMap [ "aws_secretsmanager" ] = func ( a ... string ) string {
isLocal = true
return ""
}
2020-08-25 04:51:43 -04:00
2020-11-10 04:46:20 -05:00
tpl , err := texttemplate . New ( "hcl2_upgrade" ) .
2020-08-25 04:51:43 -04:00
Funcs ( funcMap ) .
Parse ( string ( s ) )
if err != nil {
2021-02-08 05:28:26 -05:00
return isLocal , fallbackReturn ( err )
2020-08-25 04:51:43 -04:00
}
str := & bytes . Buffer { }
v := struct {
HTTPIP string
HTTPPort string
} {
HTTPIP : "{{ .HTTPIP }}" ,
HTTPPort : "{{ .HTTPPort }}" ,
}
if err := tpl . Execute ( str , v ) ; err != nil {
2021-02-08 05:28:26 -05:00
return isLocal , fallbackReturn ( err )
2020-08-25 04:51:43 -04:00
}
2021-02-08 05:28:26 -05:00
return isLocal , str . Bytes ( )
2020-08-25 04:51:43 -04: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 10:02:05 -04: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 04:51:43 -04:00
}
2020-08-27 10:02:05 -04:00
switch mostComplexElem . ( type ) {
case string , int , float64 , bool :
2020-08-25 04:51:43 -04:00
out . SetAttributeValue ( k , hcl2shim . HCL2ValueFromConfigValue ( value ) )
default :
nestedBlockBody := out . AppendNewBlock ( k , nil ) . Body ( )
jsonBodyToHCL2Body ( nestedBlockBody , value )
}
2020-08-27 10:02:05 -04:00
case map [ string ] string , map [ string ] int , map [ string ] float64 :
out . SetAttributeValue ( k , hcl2shim . HCL2ValueFromConfigValue ( value ) )
2020-08-25 04:51:43 -04:00
case [ ] interface { } :
if len ( value ) == 0 {
continue
}
2020-08-27 10:02:05 -04: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 04:51:43 -04:00
case map [ string ] interface { } :
2020-08-27 10:02:05 -04: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 04:51:43 -04: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 10:02:05 -04:00
out . SetAttributeValue ( k , hcl2shim . HCL2ValueFromConfigValue ( value ) )
2020-08-25 04:51:43 -04: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 := `
Usage : packer hcl2_upgrade - output - file = JSON_TEMPLATE . pkr . hcl JSON_TEMPLATE ...
2020-10-14 14:52:17 -04:00
Will transform your JSON template into an HCL2 configuration .
2020-08-25 04:51:43 -04: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 04:51:43 -04:00
}
func ( * HCL2UpgradeCommand ) AutocompleteArgs ( ) complete . Predictor {
return complete . PredictNothing
}
func ( * HCL2UpgradeCommand ) AutocompleteFlags ( ) complete . Flags {
return complete . Flags { }
}