From f589081fd8cd9d40292059b1074a89a32f18185d Mon Sep 17 00:00:00 2001 From: Wenfeng Pan Date: Sat, 10 Aug 2024 02:26:51 +0000 Subject: [PATCH 1/2] backport of commit 3198acddeee0ab580eb83de87c7a3f91efe8e59f --- hcl2template/function/encoding.go | 54 +++++++++++++++++++ hcl2template/function/encoding_test.go | 49 +++++++++++++++++ hcl2template/functions.go | 1 + .../functions/encoding/base64gzip.mdx | 32 +++++++++++ website/data/docs-nav-data.json | 4 ++ 5 files changed, 140 insertions(+) create mode 100644 hcl2template/function/encoding.go create mode 100644 hcl2template/function/encoding_test.go create mode 100644 website/content/docs/templates/hcl_templates/functions/encoding/base64gzip.mdx diff --git a/hcl2template/function/encoding.go b/hcl2template/function/encoding.go new file mode 100644 index 00000000000..b2b8020b8e3 --- /dev/null +++ b/hcl2template/function/encoding.go @@ -0,0 +1,54 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package function + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "fmt" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" +) + +// Base64GzipFunc constructs a function that compresses a string with gzip and then encodes the result in +// Base64 encoding. +var Base64GzipFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "str", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + s := args[0].AsString() + + var b bytes.Buffer + gz := gzip.NewWriter(&b) + if _, err := gz.Write([]byte(s)); err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("failed to write gzip raw data: %w", err) + } + if err := gz.Flush(); err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("failed to flush gzip writer: %w", err) + } + if err := gz.Close(); err != nil { + return cty.UnknownVal(cty.String), fmt.Errorf("failed to close gzip writer: %w", err) + } + return cty.StringVal(base64.StdEncoding.EncodeToString(b.Bytes())), nil + }, +}) + +// Base64Gzip compresses a string with gzip and then encodes the result in +// Base64 encoding. +// +// Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4. +// +// Strings in the Terraform language are sequences of unicode characters rather +// than bytes, so this function will first encode the characters from the string +// as UTF-8, then apply gzip compression, and then finally apply Base64 encoding. +func Base64Gzip(str cty.Value) (cty.Value, error) { + return Base64GzipFunc.Call([]cty.Value{str}) +} diff --git a/hcl2template/function/encoding_test.go b/hcl2template/function/encoding_test.go new file mode 100644 index 00000000000..ebab0a4d1f1 --- /dev/null +++ b/hcl2template/function/encoding_test.go @@ -0,0 +1,49 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package function + +import ( + "fmt" + "testing" + + "github.com/zclconf/go-cty/cty" +) + +func TestBase64Gzip(t *testing.T) { + tests := []struct { + String cty.Value + Want cty.Value + Err bool + }{ + { + cty.StringVal("test"), + cty.StringVal("H4sIAAAAAAAA/ypJLS4BAAAA//8BAAD//wx+f9gEAAAA"), + false, + }, + { + cty.StringVal("helloworld"), + cty.StringVal("H4sIAAAAAAAA/8pIzcnJL88vykkBAAAA//8BAAD//60g6/kKAAAA"), + false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("base64gzip(%#v)", test.String), func(t *testing.T) { + got, err := Base64Gzip(test.String) + + if test.Err { + if err == nil { + t.Fatal("succeeded; want error") + } + return + } else if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !got.RawEquals(test.Want) { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) + } + }) + } +} diff --git a/hcl2template/functions.go b/hcl2template/functions.go index 7089201bc2d..3736d12f580 100644 --- a/hcl2template/functions.go +++ b/hcl2template/functions.go @@ -38,6 +38,7 @@ func Functions(basedir string) map[string]function.Function { "basename": filesystem.BasenameFunc, "base64decode": encoding.Base64DecodeFunc, "base64encode": encoding.Base64EncodeFunc, + "base64gzip": pkrfunction.Base64GzipFunc, "bcrypt": crypto.BcryptFunc, "can": tryfunc.CanFunc, "ceil": stdlib.CeilFunc, diff --git a/website/content/docs/templates/hcl_templates/functions/encoding/base64gzip.mdx b/website/content/docs/templates/hcl_templates/functions/encoding/base64gzip.mdx new file mode 100644 index 00000000000..c015077dfa3 --- /dev/null +++ b/website/content/docs/templates/hcl_templates/functions/encoding/base64gzip.mdx @@ -0,0 +1,32 @@ +--- +page_title: base64gzip - Functions - Configuration Language +description: The base64encode function compresses the given string with gzip and then + encodes the result in Base64. +--- + +# `base64gzip` Function + +`base64gzip` compresses a string with gzip and then encodes the result in +Base64 encoding. + +Packer uses the "standard" Base64 alphabet as defined in +[RFC 4648 section 4](https://tools.ietf.org/html/rfc4648#section-4). + +Strings in the Packer language are sequences of unicode characters rather +than bytes, so this function will first encode the characters from the string +as UTF-8, and then apply Base64 encoding to the result. + +The Packer language applies Unicode normalization to all strings, and so +passing a string through `base64decode` and then `base64encode` may not yield +the original result exactly. + +While we do not recommend manipulating large, raw binary data in the Packer +language, this function can be used to compress reasonably sized text strings +generated within the Packer language. For example, the result of this +function can be used to create a compressed object in Amazon S3 as part of +an S3 website. + +## Related Functions + +- [`base64decode`](/packer/docs/templates/hcl_templates/functions/encoding/base64decode) performs the opposite operation, + decoding Base64 data and interpreting it as a UTF-8 string. diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 5e491ff3adf..0a7242a98ab 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -427,6 +427,10 @@ "title": "base64encode", "path": "templates/hcl_templates/functions/encoding/base64encode" }, + { + "title": "base64gzip", + "path": "templates/hcl_templates/functions/encoding/base64gzip" + }, { "title": "csvdecode", "path": "templates/hcl_templates/functions/encoding/csvdecode" From b408cffd555404157fedfebbc3d0b08059a469a4 Mon Sep 17 00:00:00 2001 From: Wenfeng Pan Date: Tue, 13 Aug 2024 18:01:57 +0000 Subject: [PATCH 2/2] backport of commit 77e35b9216a6550e79cdc7ccf62f9556cfc65a05 --- hcl2template/function/encoding.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hcl2template/function/encoding.go b/hcl2template/function/encoding.go index b2b8020b8e3..08c92f2d78b 100644 --- a/hcl2template/function/encoding.go +++ b/hcl2template/function/encoding.go @@ -44,9 +44,9 @@ var Base64GzipFunc = function.New(&function.Spec{ // Base64Gzip compresses a string with gzip and then encodes the result in // Base64 encoding. // -// Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4. +// Packer uses the "standard" Base64 alphabet as defined in RFC 4648 section 4. // -// Strings in the Terraform language are sequences of unicode characters rather +// Strings in the Packer language are sequences of unicode characters rather // than bytes, so this function will first encode the characters from the string // as UTF-8, then apply gzip compression, and then finally apply Base64 encoding. func Base64Gzip(str cty.Value) (cty.Value, error) {