From da06110cc3302bafa00f5ab2788969324e5f7121 Mon Sep 17 00:00:00 2001 From: Lucas Bajolet Date: Tue, 5 Nov 2024 14:19:51 -0500 Subject: [PATCH 1/3] hcl2template: add filebase64 function The filebase64 function aims to read and encode a file's contents into base64. This is mostly to support reading the content of a file that is not valid UTF-8, as is the case with the `file` function. --- hcl2template/function/filebase64.go | 40 +++++++++++++++ hcl2template/function/filebase64_test.go | 62 ++++++++++++++++++++++++ hcl2template/functions.go | 1 + 3 files changed, 103 insertions(+) create mode 100644 hcl2template/function/filebase64.go create mode 100644 hcl2template/function/filebase64_test.go diff --git a/hcl2template/function/filebase64.go b/hcl2template/function/filebase64.go new file mode 100644 index 000000000..1f92e704d --- /dev/null +++ b/hcl2template/function/filebase64.go @@ -0,0 +1,40 @@ +package function + +import ( + "encoding/base64" + "fmt" + "os" + "strings" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" +) + +var Filebase64 = function.New(&function.Spec{ + Params: []function.Parameter{ + function.Parameter{ + Name: "path", + Description: "Read a file and encode it as a base64 string", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + RefineResult: refineNotNull, + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + path := args[0].AsString() + content, err := os.ReadFile(path) + if err != nil { + return cty.NullVal(cty.String), fmt.Errorf("failed to read file %q: %s", path, err) + } + + out := &strings.Builder{} + enc := base64.NewEncoder(base64.StdEncoding, out) + _, err = enc.Write(content) + if err != nil { + return cty.NullVal(cty.String), fmt.Errorf("failed to write file %q as base64: %s", path, err) + } + _ = enc.Close() + + return cty.StringVal(out.String()), nil + }, +}) diff --git a/hcl2template/function/filebase64_test.go b/hcl2template/function/filebase64_test.go new file mode 100644 index 000000000..a865060c5 --- /dev/null +++ b/hcl2template/function/filebase64_test.go @@ -0,0 +1,62 @@ +package function + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty/cty" +) + +func TestFilebase64(t *testing.T) { + tests := []struct { + name string + file string + expectedOutput string + expectError bool + }{ + { + "file exists, return base64'd contents, no error", + "./testdata/list.tmpl", + "JXsgZm9yIHggaW4gbGlzdCB+fQotICR7eH0KJXsgZW5kZm9yIH59Cg==", + false, + }, + { + "file doesn't exist, return nilval and an error", + "./testdata/no_file", + "", + true, + }, + { + "directory passed as arg, should error", + "./testdata", + "", + true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res, err := Filebase64.Call([]cty.Value{ + cty.StringVal(tt.file), + }) + + if tt.expectError && err == nil { + t.Fatal("succeeded; want error") + } + + if !tt.expectError && err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if err != nil { + return + } + + retVal := res.AsString() + diff := cmp.Diff(retVal, tt.expectedOutput) + if diff != "" { + t.Errorf("expected output and returned are different: %s", diff) + } + }) + } +} diff --git a/hcl2template/functions.go b/hcl2template/functions.go index 3736d12f5..8d50ecf49 100644 --- a/hcl2template/functions.go +++ b/hcl2template/functions.go @@ -60,6 +60,7 @@ func Functions(basedir string) map[string]function.Function { "distinct": stdlib.DistinctFunc, "element": stdlib.ElementFunc, "file": filesystem.MakeFileFunc(basedir, false), + "filebase64": pkrfunction.Filebase64, "fileexists": filesystem.MakeFileExistsFunc(basedir), "fileset": filesystem.MakeFileSetFunc(basedir), "flatten": stdlib.FlattenFunc, From 5bfd778aa732cf0855ef1d01c498f073a6dfb4b0 Mon Sep 17 00:00:00 2001 From: karthik P Date: Thu, 24 Apr 2025 23:05:04 +0530 Subject: [PATCH 2/3] adding docs for filebase64 function --- .../functions/file/filebase64.mdx | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 website/content/docs/templates/hcl_templates/functions/file/filebase64.mdx diff --git a/website/content/docs/templates/hcl_templates/functions/file/filebase64.mdx b/website/content/docs/templates/hcl_templates/functions/file/filebase64.mdx new file mode 100644 index 000000000..1b60fcc7f --- /dev/null +++ b/website/content/docs/templates/hcl_templates/functions/file/filebase64.mdx @@ -0,0 +1,46 @@ +--- +page_title: filebase64 - Functions - Configuration Language +description: |- + The filebase64 function reads the contents of the file at the given path and + returns them as a base64-encoded string. +--- + +# `filebase64` Function + +`filebase64` reads the contents of a file at the given path and returns them as +a base64-encoded string. + +```hcl +filebase64(path) +``` + +The result is a Base64 representation of the raw bytes in the given file. +Strings in the Packer language are sequences of Unicode characters, so +Base64 is the standard way to represent raw binary data that cannot be +interpreted as Unicode characters. Resource types that operate on binary +data will accept this data encoded in Base64, thus avoiding the need to +decode the result of this function. + +Packer uses the "standard" Base64 alphabet as defined in +[RFC 4648 section 4](https://tools.ietf.org/html/rfc4648#section-4). + +This function can be used only with functions that already exist as static +files on disk at the beginning of a Packer run. Language functions do not +participate in the dependency graph, so this function cannot be used with +files that are generated dynamically during a Packer operation. + +## Examples + +``` +> filebase64("${path.module}/hello.txt") +SGVsbG8gV29ybGQ= +``` + +## Related Functions + +* [`file`](/packer/docs/templates/hcl_templates/functions/file/file) also reads the contents of a given file, + but interprets the data as UTF-8 text and returns the result directly + as a string, without any further encoding. +* [`base64decode`](/packer/docs/templates/hcl_templates/functions/encoding/base64decode) can decode a Base64 string representing + bytes in UTF-8, but in practice `base64decode(filebase64(...))` is equivalent + to the shorter expression `file(...)`. From 6ef741d5f5c70110af1e668b1f7f882fb0a50ddb Mon Sep 17 00:00:00 2001 From: karthik P Date: Mon, 28 Apr 2025 10:51:47 +0530 Subject: [PATCH 3/3] adding fn to nav data json --- website/data/docs-nav-data.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index b80e72fef..d878ec5e3 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -508,6 +508,10 @@ "title": "file", "path": "templates/hcl_templates/functions/file/file" }, + { + "title": "filebase64", + "path": "templates/hcl_templates/functions/file/filebase64" + }, { "title": "fileexists", "path": "templates/hcl_templates/functions/file/fileexists"