diff --git a/command/hcl2_upgrade.go b/command/hcl2_upgrade.go index 07eddaf07..c8a906f68 100644 --- a/command/hcl2_upgrade.go +++ b/command/hcl2_upgrade.go @@ -123,6 +123,7 @@ var ( localsVariableMap = map[string]string{} timestamp = false isotime = false + strftime = false ) type BlockParser interface { @@ -192,7 +193,7 @@ func (c *HCL2UpgradeCommand) RunContext(_ context.Context, cla *HCL2UpgradeArgs) builders := []*template.Builder{} { - // sort builders to avoid map's randomnes + // sort builders to avoid map's randomness for _, builder := range tpl.Builders { builders = append(builders, builder) } @@ -349,6 +350,14 @@ func transposeTemplatingCalls(s []byte) []byte { return fmt.Sprintf("${legacy_isotime(\"%s\")}", a[0]) }, + "strftime": func(a ...string) string { + if len(a) == 0 { + // returns rfc3339 formatted string. + return "${timestamp()}" + } + strftime = true + return fmt.Sprintf("${legacy_strftime(\"%s\")}", a[0]) + }, "user": func(in string) string { if _, ok := localsVariableMap[in]; ok { // variable is now a local @@ -491,6 +500,7 @@ func variableTransposeTemplatingCalls(s []byte) (isLocal bool, body []byte) { "aws_secretsmanager": setIsLocal, "timestamp": setIsLocal, "isotime": setIsLocal, + "strftime": setIsLocal, "user": setIsLocal, "env": func(in string) string { return fmt.Sprintf("${env(%q)}", in) @@ -562,6 +572,9 @@ func jsonBodyToHCL2Body(out *hclwrite.Body, kvs map[string]interface{}) { case map[string]interface{}: var mostComplexElem interface{} for _, randomElem := range value { + if k == "linux_options" || k == "network_interface" { + break + } // 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 ) @@ -830,6 +843,9 @@ func (p *LocalsParser) Write(out *bytes.Buffer) { 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.`) } + if strftime { + fmt.Fprintln(out, `# The "legacy_strftime" function has been provided for backwards compatability, but we recommend switching to the timestamp and formatdate functions.`) + } if len(p.LocalsOut) > 0 { if p.WithAnnotations { out.Write([]byte(localsVarHeader)) diff --git a/command/hcl2_upgrade_test.go b/command/hcl2_upgrade_test.go index fc5292f7c..30bc21b09 100644 --- a/command/hcl2_upgrade_test.go +++ b/command/hcl2_upgrade_test.go @@ -10,11 +10,6 @@ import ( ) func Test_hcl2_upgrade(t *testing.T) { - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("Getwd: %v", err) - } - _ = cwd tc := []struct { folder string @@ -33,6 +28,7 @@ func Test_hcl2_upgrade(t *testing.T) { {folder: "variables-with-variables", flags: []string{}}, {folder: "complete-variables-with-template-engine", flags: []string{}}, {folder: "escaping", flags: []string{}}, + {folder: "vsphere_linux_options_and_network_interface", exitCode: 1, flags: []string{}}, {folder: "inexistent", flags: []string{}, exitCode: 1, exitEarly: true}, } diff --git a/command/test-fixtures/hcl2_upgrade/variables-only/expected.pkr.hcl b/command/test-fixtures/hcl2_upgrade/variables-only/expected.pkr.hcl index 2b612eb57..a78a2ba64 100644 --- a/command/test-fixtures/hcl2_upgrade/variables-only/expected.pkr.hcl +++ b/command/test-fixtures/hcl2_upgrade/variables-only/expected.pkr.hcl @@ -34,6 +34,7 @@ data "amazon-secretsmanager" "autogenerated_2" { key = "api_key" name = "sample/app/passwords" } +# The "legacy_strftime" function has been provided for backwards compatability, but we recommend switching to the timestamp and formatdate functions. local "password" { sensitive = true @@ -41,5 +42,6 @@ local "password" { } locals { - password_key = "MY_KEY_${data.amazon-secretsmanager.autogenerated_2.value}" + ami_description = "AMI ${legacy_strftime("%Y-%m")}" + password_key = "MY_KEY_${data.amazon-secretsmanager.autogenerated_2.value}" } diff --git a/command/test-fixtures/hcl2_upgrade/variables-only/input.json b/command/test-fixtures/hcl2_upgrade/variables-only/input.json index 27cefb911..949d1772a 100644 --- a/command/test-fixtures/hcl2_upgrade/variables-only/input.json +++ b/command/test-fixtures/hcl2_upgrade/variables-only/input.json @@ -6,6 +6,7 @@ "aws_secret_key": "", "aws_access_key": "", "password": "{{ aws_secretsmanager `sample/app/password` }}", + "ami_description": "AMI {{strftime \"%Y-%m\"}}", "password_key": "MY_KEY_{{ aws_secretsmanager `sample/app/passwords` `api_key` }}" }, "sensitive-variables": [ diff --git a/command/test-fixtures/hcl2_upgrade/vsphere_linux_options_and_network_interface/expected.pkr.hcl b/command/test-fixtures/hcl2_upgrade/vsphere_linux_options_and_network_interface/expected.pkr.hcl new file mode 100644 index 000000000..a160d0c9e --- /dev/null +++ b/command/test-fixtures/hcl2_upgrade/vsphere_linux_options_and_network_interface/expected.pkr.hcl @@ -0,0 +1,41 @@ + +source "vsphere-clone" "autogenerated_1" { + RAM_reserve_all = false + cluster = "USER_VALUE" + convert_to_template = false + customize { + dns_server_list = ["192.168.254.1", "192.168.254.2"] + ipv4_gateway = "172.15.102.254" + linux_options { + domain = "test.internal" + host_name = "packer-test" + } + network_interface { + ipv4_address = "172.15.102.202" + ipv4_netmask = "24" + } + } + datacenter = "USER_VALUE" + datastore = "USER_VALUE" + disable_shutdown = true + disk_controller_type = ["pvscsi", "pvscsi"] + insecure_connection = "true" + network = "USER_VALUE" + notes = "Build via Packer" + password = "USER_VALUE" + ssh_password = "Aa123456" + ssh_username = "root" + storage { + disk_controller_index = 0 + disk_size = 100000 + } + template = "erez's template" + username = "USER_VALUE" + vcenter_server = "USER_VALUE" + vm_name = "USER_VALUE" +} + +build { + sources = ["source.vsphere-clone.autogenerated_1"] + +} diff --git a/command/test-fixtures/hcl2_upgrade/vsphere_linux_options_and_network_interface/input.json b/command/test-fixtures/hcl2_upgrade/vsphere_linux_options_and_network_interface/input.json new file mode 100644 index 000000000..2fc3f3a83 --- /dev/null +++ b/command/test-fixtures/hcl2_upgrade/vsphere_linux_options_and_network_interface/input.json @@ -0,0 +1,48 @@ +{ + "builders": [ + { + "disable_shutdown": true, + "type": "vsphere-clone", + "vcenter_server": "USER_VALUE", + "username": "USER_VALUE", + "password": "USER_VALUE", + "insecure_connection": "true", + "datacenter": "USER_VALUE", + "cluster": "USER_VALUE", + "network": "USER_VALUE", + "datastore": "USER_VALUE", + "vm_name": "USER_VALUE", + "notes": "Build via Packer", + "ssh_username": "root", + "ssh_password": "Aa123456", + "template": "erez's template", + "RAM_reserve_all": false, + "convert_to_template": false, + "disk_controller_type": [ + "pvscsi", + "pvscsi" + ], + "storage": [ + { + "disk_controller_index": 0, + "disk_size": 100000 + } + ], + "customize": { + "linux_options": { + "host_name": "packer-test", + "domain": "test.internal" + }, + "network_interface": { + "ipv4_address": "172.15.102.202", + "ipv4_netmask": "24" + }, + "ipv4_gateway": "172.15.102.254", + "dns_server_list": [ + "192.168.254.1", + "192.168.254.2" + ] + } + } + ] +} \ No newline at end of file diff --git a/go.mod b/go.mod index 8ee7046d8..4dd0454ba 100644 --- a/go.mod +++ b/go.mod @@ -57,6 +57,7 @@ require ( github.com/hashicorp/packer-plugin-vmware v1.0.0 github.com/hashicorp/packer-plugin-vsphere v1.0.0 github.com/hashicorp/packer-plugin-yandex v1.0.0 + github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect github.com/klauspost/pgzip v0.0.0-20151221113845-47f36e165cec github.com/masterzen/winrm v0.0.0-20210504160029-28ed956f5227 github.com/mattn/go-tty v0.0.0-20191112051231-74040eebce08 diff --git a/hcl2template/function/datetime.go b/hcl2template/function/datetime.go index e53454f98..8f9804ad0 100644 --- a/hcl2template/function/datetime.go +++ b/hcl2template/function/datetime.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/jehiah/go-strftime" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) @@ -55,6 +56,26 @@ var LegacyIsotimeFunc = function.New(&function.Spec{ }, }) +// LegacyStrftimeFunc constructs a function that returns a string representation +// of the current date and time using golang's strftime datetime formatting. +var LegacyStrftimeFunc = function.New(&function.Spec{ + Params: []function.Parameter{}, + VarParam: &function.Parameter{ + Name: "format", + Type: cty.String, + }, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + if len(args) > 1 { + return cty.StringVal(""), fmt.Errorf("too many values, 1 needed: %v", args) + } else if len(args) == 0 { + return cty.StringVal(InitTime.Format(time.RFC3339)), nil + } + format := args[0].AsString() + return cty.StringVal(strftime.Format(format, InitTime)), nil + }, +}) + // LegacyIsotimeFunc returns a string representation of the current date and // time using the given format string. The format string follows golang's // datetime formatting. See diff --git a/hcl2template/functions.go b/hcl2template/functions.go index a85f414f0..33da10257 100644 --- a/hcl2template/functions.go +++ b/hcl2template/functions.go @@ -69,6 +69,7 @@ func Functions(basedir string) map[string]function.Function { "jsonencode": stdlib.JSONEncodeFunc, "keys": stdlib.KeysFunc, "legacy_isotime": pkrfunction.LegacyIsotimeFunc, + "legacy_strftime": pkrfunction.LegacyStrftimeFunc, "length": pkrfunction.LengthFunc, "log": stdlib.LogFunc, "lookup": stdlib.LookupFunc, diff --git a/website/content/docs/templates/hcl_templates/functions/datetime/legacy_strftime.mdx b/website/content/docs/templates/hcl_templates/functions/datetime/legacy_strftime.mdx new file mode 100644 index 000000000..852b89add --- /dev/null +++ b/website/content/docs/templates/hcl_templates/functions/datetime/legacy_strftime.mdx @@ -0,0 +1,40 @@ +--- +page_title: legacy_strftime - Functions - Configuration Language +description: |- + The legacy_strftime function returns a string representation of the current date + and time. +--- + +# `legacy_strftime` — UTC time, formated using the ISO C standard format + +The `legacy_strftime` function returns the current date and time using the given +format string. The format string follows strftime's datetime formatting. + +This function has been provided to create backwards compatibility with Packer's +legacy JSON templates. However, we recommend that you upgrade your HCL Packer +template to use +[`timestamp`](/docs/templates/hcl_templates/functions/datetime/timestamp) and +[`formatdate`](/docs/templates/hcl_templates/functions/datetime/formatdate) +together as soon as is convenient. + +-> **Note:** If you are using a large number of builders, provisioners or +post-processors, the strftime may be slightly different for each one because it +is from when the plugin is launched not the initial Packer process. In order to +avoid this and make the timestamp consistent across all plugins, set it as a +user variable and then access the user variable within your plugins. + +## Examples + +```shell-session +> legacy_strftime("%Y-%m") +2021-08 +``` + +## Related Functions + +- [`format`](/docs/templates/hcl_templates/functions/string/format) is a more general formatting function for arbitrary +data. +- [`timestamp`](/docs/templates/hcl_templates/functions/datetime/timestamp) returns the current date and time in a format +suitable for input to `formatdate`. +- [`formatdate`](/docs/templates/hcl_templates/functions/datetime/formatdate) can convert the resulting timestamp to + other date and time formats. diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 0651dd42a..471da1596 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -482,6 +482,10 @@ { "title": "legacy_isotime", "path": "templates/hcl_templates/functions/datetime/legacy_isotime" + }, + { + "title": "legacy_strftime", + "path": "templates/hcl_templates/functions/datetime/legacy_strftime" } ] },