mirror of
https://github.com/hashicorp/packer.git
synced 2026-04-20 21:59:08 -04:00
hcl2template: add text(encode|decode)base64 funcs (#12997)
Compared to Terraform, Packer was lacking a capability to encode/decode strings to/from base64-encoded text encoded with another encoding. This could be problematic in some cases, mainly when working with Windows, as most of the OS uses UTF-16LE as its standard encoding for many operations. Therefore, we take a page from Terraform here, and add those functions to what Packer supports in an HCL2 context.
This commit is contained in:
parent
92aabc7c2e
commit
accbe97e1e
4 changed files with 161 additions and 0 deletions
|
|
@ -4,6 +4,8 @@
|
|||
package hcl2template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/go-cty-funcs/cidr"
|
||||
|
|
@ -19,6 +21,7 @@ import (
|
|||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
||||
"golang.org/x/text/encoding/ianaindex"
|
||||
)
|
||||
|
||||
// Functions returns the set of functions that should be used to when
|
||||
|
|
@ -102,6 +105,8 @@ func Functions(basedir string) map[string]function.Function {
|
|||
"split": stdlib.SplitFunc,
|
||||
"strrev": stdlib.ReverseFunc,
|
||||
"substr": stdlib.SubstrFunc,
|
||||
"textdecodebase64": TextDecodeBase64Func,
|
||||
"textencodebase64": TextEncodeBase64Func,
|
||||
"timestamp": pkrfunction.TimestampFunc,
|
||||
"timeadd": stdlib.TimeAddFunc,
|
||||
"title": stdlib.TitleFunc,
|
||||
|
|
@ -130,6 +135,98 @@ func Functions(basedir string) map[string]function.Function {
|
|||
return funcs
|
||||
}
|
||||
|
||||
// TextEncodeBase64Func constructs a function that encodes a string to a target encoding and then to a base64 sequence.
|
||||
var TextEncodeBase64Func = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "string",
|
||||
Type: cty.String,
|
||||
},
|
||||
{
|
||||
Name: "encoding",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Description: "Encodes the input string (UTF-8) to the destination encoding. The output is base64 to account for cty limiting strings to NFC normalised UTF-8 strings.",
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
RefineResult: func(rb *cty.RefinementBuilder) *cty.RefinementBuilder { return rb.NotNull() },
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
encoding, err := ianaindex.IANA.Encoding(args[1].AsString())
|
||||
if err != nil || encoding == nil {
|
||||
return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "%q is not a supported IANA encoding name or alias", args[1].AsString())
|
||||
}
|
||||
|
||||
encName, err := ianaindex.IANA.Name(encoding)
|
||||
if err != nil { // would be weird, since we just read this encoding out
|
||||
encName = args[1].AsString()
|
||||
}
|
||||
|
||||
encoder := encoding.NewEncoder()
|
||||
encodedInput, err := encoder.Bytes([]byte(args[0].AsString()))
|
||||
if err != nil {
|
||||
// The string representations of "err" disclose implementation
|
||||
// details of the underlying library, and the main error we might
|
||||
// like to return a special message for is unexported as
|
||||
// golang.org/x/text/encoding/internal.RepertoireError, so this
|
||||
// is just a generic error message for now.
|
||||
//
|
||||
// We also don't include the string itself in the message because
|
||||
// it can typically be very large, contain newline characters,
|
||||
// etc.
|
||||
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "the given string contains characters that cannot be represented in %s", encName)
|
||||
}
|
||||
|
||||
return cty.StringVal(base64.StdEncoding.EncodeToString(encodedInput)), nil
|
||||
},
|
||||
})
|
||||
|
||||
// TextDecodeBase64Func constructs a function that decodes a base64 sequence from the source encoding to UTF-8.
|
||||
var TextDecodeBase64Func = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "source",
|
||||
Type: cty.String,
|
||||
},
|
||||
{
|
||||
Name: "encoding",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Description: "Encodes the input base64 blob from an encoding to utf-8. The input is base64 to account for cty limiting strings to NFC normalised UTF-8 strings.",
|
||||
RefineResult: func(rb *cty.RefinementBuilder) *cty.RefinementBuilder { return rb.NotNull() },
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
encoding, err := ianaindex.IANA.Encoding(args[1].AsString())
|
||||
if err != nil || encoding == nil {
|
||||
return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "%q is not a supported IANA encoding name or alias", args[1].AsString())
|
||||
}
|
||||
|
||||
encName, err := ianaindex.IANA.Name(encoding)
|
||||
if err != nil { // would be weird, since we just read this encoding out
|
||||
encName = args[1].AsString()
|
||||
}
|
||||
|
||||
s := args[0].AsString()
|
||||
sDec, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case base64.CorruptInputError:
|
||||
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "the given value is has an invalid base64 symbol at offset %d", int(err))
|
||||
default:
|
||||
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid source string: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
decoder := encoding.NewDecoder()
|
||||
decoded, err := decoder.Bytes(sDec)
|
||||
if err != nil || bytes.ContainsRune(decoded, '<27>') {
|
||||
return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "the given string contains symbols that are not defined for %s", encName)
|
||||
}
|
||||
|
||||
return cty.StringVal(string(decoded)), nil
|
||||
},
|
||||
})
|
||||
|
||||
var unimplFunc = function.New(&function.Spec{
|
||||
Type: func([]cty.Value) (cty.Type, error) {
|
||||
return cty.DynamicPseudoType, fmt.Errorf("function not yet implemented")
|
||||
|
|
|
|||
28
website/content/docs/templates/hcl_templates/functions/encoding/textdecodebase64.mdx
vendored
Normal file
28
website/content/docs/templates/hcl_templates/functions/encoding/textdecodebase64.mdx
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
page_title: testdecodebase64 - Functions - Configuration Language
|
||||
description: The testdecodebase64 function converts a base64 encoded string, whose underlying encoding is the one specified as argument, into a UTF-8 string.
|
||||
---
|
||||
|
||||
# `textdecodebase64` Function
|
||||
|
||||
Encodes the input string from a speicified encoding into UTF-8.
|
||||
The input is base64-encoded to account for HCL's string encoding limitations: they must be UTF-8, NFC-normalised.
|
||||
|
||||
Packer uses the "standard" Base64 alphabet as defined in
|
||||
[RFC 4648 section 4](https://tools.ietf.org/html/rfc4648#section-4).
|
||||
|
||||
The `encoding_name` argument must contain one of the encoding names or aliases recorded in
|
||||
[the IANA character encoding registry](https://www.iana.org/assignments/character-sets/character-sets.xhtml).
|
||||
|
||||
## Examples
|
||||
|
||||
```shell-session
|
||||
# Usage: textencodebase64(input_base64, encoding_name)
|
||||
> textdecodebase64("SABlAGwAbABvACAAVwBvAHIAbABkAA==", "UTF-16LE")
|
||||
Hello World
|
||||
```
|
||||
|
||||
## Related Functions
|
||||
|
||||
- [`base64encode`](/packer/docs/templates/hcl_templates/functions/encoding/base64encode) performs the opposite operation,
|
||||
encoding the UTF-8 bytes for a string as Base64.
|
||||
28
website/content/docs/templates/hcl_templates/functions/encoding/textencodebase64.mdx
vendored
Normal file
28
website/content/docs/templates/hcl_templates/functions/encoding/textencodebase64.mdx
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
page_title: testencodebase64 - Functions - Configuration Language
|
||||
description: The testencodebase64 function converts a UTF-8 NFC input string to a base64 blob that encodes the target encoding's rendering of the input string.
|
||||
---
|
||||
|
||||
# `textencodebase64` Function
|
||||
|
||||
Encodes the input string to the destination encoding.
|
||||
The output is base64-encoded to account for HCL's string encoding limitations: they must be UTF-8, NFC-normalised.
|
||||
|
||||
Packer uses the "standard" Base64 alphabet as defined in
|
||||
[RFC 4648 section 4](https://tools.ietf.org/html/rfc4648#section-4).
|
||||
|
||||
The `encoding_name` argument must contain one of the encoding names or aliases recorded in
|
||||
[the IANA character encoding registry](https://www.iana.org/assignments/character-sets/character-sets.xhtml).
|
||||
|
||||
## Examples
|
||||
|
||||
```shell-session
|
||||
# Usage: textencodebase64(input_string, encoding_name)
|
||||
> textencodebase64("Hello World", "UTF-16LE")
|
||||
SABlAGwAbABvACAAVwBvAHIAbABkAA==
|
||||
```
|
||||
|
||||
## Related Functions
|
||||
|
||||
- [`base64encode`](/packer/docs/templates/hcl_templates/functions/encoding/base64encode) performs the opposite operation,
|
||||
encoding the UTF-8 bytes for a string as Base64.
|
||||
|
|
@ -443,6 +443,14 @@
|
|||
"title": "urlencode",
|
||||
"path": "templates/hcl_templates/functions/encoding/urlencode"
|
||||
},
|
||||
{
|
||||
"title": "textencodebase64",
|
||||
"path": "templates/hcl_templates/functions/encoding/textencodebase64"
|
||||
},
|
||||
{
|
||||
"title": "textdecodebase64",
|
||||
"path": "templates/hcl_templates/functions/encoding/textdecodebase64"
|
||||
},
|
||||
{
|
||||
"title": "yamldecode",
|
||||
"path": "templates/hcl_templates/functions/encoding/yamldecode"
|
||||
|
|
|
|||
Loading…
Reference in a new issue