From 4be2c38bab8877d69d5f8f4a5f0370b6672fe0e6 Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Mon, 10 Aug 2020 10:48:01 -0400 Subject: [PATCH] Add experimental mode Signed-off-by: Dave Henderson --- cmd/gomplate/config.go | 8 +++ cmd/gomplate/config_test.go | 19 +++++++ cmd/gomplate/main.go | 9 ++++ data/datasource_git.go | 2 +- docs-src/content/functions/crypto.yml | 5 ++ docs-src/content/functions/func_doc.md.tmpl | 18 ++++++- docs/content/config.md | 12 +++++ docs/content/functions/crypto.md | 25 +++++++-- docs/content/usage.md | 6 +++ funcs.go | 59 +++++++++++++-------- funcs/aws.go | 24 +++++++-- funcs/base64.go | 16 +++++- funcs/coll.go | 16 +++++- funcs/conv.go | 24 +++++++-- funcs/crypto.go | 37 ++++++++++++- funcs/crypto_test.go | 22 ++++++-- funcs/data.go | 43 ++++++++++----- funcs/env.go | 20 +++++-- funcs/file.go | 17 ++++-- funcs/file_test.go | 6 +-- funcs/filepath.go | 13 ++++- funcs/funcs.go | 15 ++++++ funcs/gcp.go | 17 +++++- funcs/math.go | 30 ++++++++--- funcs/net.go | 12 ++++- funcs/path.go | 13 ++++- funcs/random.go | 16 +++++- funcs/regexp.go | 16 +++++- funcs/sockaddr.go | 12 ++++- funcs/strings.go | 36 +++++++++---- funcs/test.go | 28 +++++++--- funcs/time.go | 14 ++++- funcs/uuid.go | 16 +++++- gomplate.go | 2 +- internal/config/configfile.go | 24 +++++++++ internal/config/configfile_test.go | 20 +++++++ internal/tests/integration/crypto_test.go | 2 + 37 files changed, 564 insertions(+), 110 deletions(-) create mode 100644 funcs/funcs.go diff --git a/cmd/gomplate/config.go b/cmd/gomplate/config.go index 02278618..caaf4963 100644 --- a/cmd/gomplate/config.go +++ b/cmd/gomplate/config.go @@ -156,6 +156,10 @@ func cobraConfig(cmd *cobra.Command, args []string) (cfg *config.Config, err err if err != nil { return nil, err } + cfg.Experimental, err = getBool(cmd, "experimental") + if err != nil { + return nil, err + } cfg.LDelim, err = getString(cmd, "left-delim") if err != nil { @@ -249,5 +253,9 @@ func applyEnvVars(ctx context.Context, cfg *config.Config) (*config.Config, erro cfg.SuppressEmpty = true } + if !cfg.Experimental && conv.ToBool(env.Getenv("GOMPLATE_EXPERIMENTAL", "false")) { + cfg.Experimental = true + } + return cfg, nil } diff --git a/cmd/gomplate/config_test.go b/cmd/gomplate/config_test.go index 24178423..97b9a89f 100644 --- a/cmd/gomplate/config_test.go +++ b/cmd/gomplate/config_test.go @@ -233,6 +233,25 @@ func TestApplyEnvVars(t *testing.T) { &config.Config{SuppressEmpty: true}, &config.Config{SuppressEmpty: true}, }, + + { + "GOMPLATE_EXPERIMENTAL", "bogus", + false, + &config.Config{}, + &config.Config{Experimental: false}, + }, + { + "GOMPLATE_EXPERIMENTAL", "true", + false, + &config.Config{}, + &config.Config{Experimental: true}, + }, + { + "GOMPLATE_EXPERIMENTAL", "false", + false, + &config.Config{Experimental: true}, + &config.Config{Experimental: true}, + }, } for i, d := range data { diff --git a/cmd/gomplate/main.go b/cmd/gomplate/main.go index cbd3f744..ab52c630 100644 --- a/cmd/gomplate/main.go +++ b/cmd/gomplate/main.go @@ -78,6 +78,13 @@ func newGomplateCmd() *cobra.Command { if err != nil { return err } + ctx = config.ContextWithConfig(ctx, cfg) + if cfg.Experimental { + log.UpdateContext(func(c zerolog.Context) zerolog.Context { + return c.Bool("experimental", true) + }) + log.Info().Msg("experimental functions and features enabled!") + } log.Debug().Msgf("starting %s", cmd.Name()) log.Debug(). @@ -135,6 +142,8 @@ func initFlags(command *cobra.Command) { command.Flags().String("left-delim", ldDefault, "override the default left-`delimiter` [$GOMPLATE_LEFT_DELIM]") command.Flags().String("right-delim", rdDefault, "override the default right-`delimiter` [$GOMPLATE_RIGHT_DELIM]") + command.Flags().Bool("experimental", false, "enable experimental features [$GOMPLATE_EXPERIMENTAL]") + command.Flags().BoolP("verbose", "V", false, "output extra information about what gomplate is doing") command.Flags().String("config", defaultConfigFile, "config file (overridden by commandline flags)") diff --git a/data/datasource_git.go b/data/datasource_git.go index 3c614863..f4b6670d 100644 --- a/data/datasource_git.go +++ b/data/datasource_git.go @@ -26,7 +26,7 @@ import ( ) func readGit(source *Source, args ...string) ([]byte, error) { - ctx := context.Background() + ctx := context.TODO() g := gitsource{} u := source.URL diff --git a/docs-src/content/functions/crypto.yml b/docs-src/content/functions/crypto.yml index bf31163e..1602b5d9 100644 --- a/docs-src/content/functions/crypto.yml +++ b/docs-src/content/functions/crypto.yml @@ -55,6 +55,7 @@ funcs: $ gomplate -i '{{ crypto.PBKDF2 "foo" "bar" 1024 8 }}' 32c4907c3c80792b - name: crypto.RSADecrypt + experimental: true description: | Decrypt an RSA-encrypted input and print the output as a string. Note that this may result in unreadable text if the decrypted payload is binary. See @@ -87,6 +88,7 @@ funcs: -i '{{ base64.DecodeBytes .ciphertext | crypto.RSADecrypt .privKey }}' hello - name: crypto.RSADecryptBytes + experimental: true description: | Decrypt an RSA-encrypted input and output the decrypted byte array. @@ -120,6 +122,7 @@ funcs: {{ crypto.RSADecryptBytes .privKey $enc | conv.ToString }}' hello - name: crypto.RSAEncrypt + experimental: true description: | Encrypt the input with RSA and the padding scheme from PKCS#1 v1.5. @@ -158,6 +161,7 @@ funcs: Ciphertext in hex: {{ printf "%x" $enc }}' 71729b87cccabb248b9e0e5173f0b12c01d9d2a0565bad18aef9d332ce984bde06acb8bb69334a01446f7f6430077f269e6fbf2ccacd972fe5856dd4719252ebddf599948d937d96ea41540dad291b868f6c0cf647dffdb5acb22cd33557f9a1ddd0ee6c1ad2bbafc910ba8f817b66ea0569afc06e5c7858fd9dc2638861fe7c97391b2f190e4c682b4aa2c9b0050081efe18b10aa8c2b2b5f5b68a42dcc06c9da35b37fca9b1509fddc940eb99f516a2e0195405bcb3993f0fa31bc038d53d2e7231dff08cc39448105ed2d0ac52d375cb543ca8a399f807cc5d007e2c44c69876d189667eee66361a393c4916826af77479382838cd4e004b8baa05636805a - name: crypto.RSAGenerateKey + experimental: true description: | Generate a new RSA Private Key and output in PEM-encoded PKCS#1 ASN.1 DER form. @@ -184,6 +188,7 @@ funcs: {{ crypto.RSADecrypt $key $enc }}' hello - name: crypto.RSADerivePublicKey + experimental: true description: | Derive a public key from an RSA private key and output in PKIX ASN.1 DER form. diff --git a/docs-src/content/functions/func_doc.md.tmpl b/docs-src/content/functions/func_doc.md.tmpl index c66b0d09..0e8668de 100644 --- a/docs-src/content/functions/func_doc.md.tmpl +++ b/docs-src/content/functions/func_doc.md.tmpl @@ -24,9 +24,23 @@ menu: {{ $data.preamble -}} +{{- define "annotations" -}} +{{ if has . "deprecated" }} _(deprecated)_{{ end -}} +{{ if and (has . "experimental") (index . "experimental") }} _(experimental)_{{ end -}} +{{ end -}} + {{ range $_, $f := $data.funcs }} -## {{ if has $f "rawName" }}{{ $f.rawName }}{{ else }}`{{ $f.name }}`{{ if has $f "deprecated" }} _(deprecated)_ -**Deprecation Notice:** {{ $f.deprecated }}{{ end }}{{ end }} +## {{ if has $f "rawName" -}} +{{ $f.rawName }}{{ else }}`{{ $f.name }}`{{ end }}{{ template "annotations" $f }} +{{ if has $f "deprecated" -}} +**Deprecation Notice:** {{ $f.deprecated }} +{{ end -}} +{{ if and (has . "experimental") (index . "experimental") -}} +**Experimental:** This function is [_experimental_][experimental] and may be enabled with the [`--experimental`][experimental] flag. + +[experimental]: ../config/#experimental +{{ end -}} + {{ if has $f "alias" }} **Alias:** `{{$f.alias}}` {{ end }} diff --git a/docs/content/config.md b/docs/content/config.md index 7857e452..c1179f63 100644 --- a/docs/content/config.md +++ b/docs/content/config.md @@ -148,6 +148,18 @@ Use the rendered output as the [`postExec`](#postexec) command's standard input. Must be used in conjuction with [`postExec`](#postexec), and will override any [`outputFiles`](#outputfiles) settings. +## `experimental` + +See [`--experimental`](../usage/#--experimental). Can also be set with the `GOMPLATE_EXPERIMENTAL=true` environment variable. + +Some functions and features are provided for early feedback behind the `experimental` configuration option. These features may change before being permanently enabled, and [feedback](https://github.com/hairyhenderson/gomplate/issues/new) is requested from early adopters! + +Experimental functions are marked in the documentation with an _"(experimental)"_ annotation. + +```yaml +experimental: true +``` + ## `in` See [`--in`/`-i`](../usage/#--file-f---in-i-and---out-o). diff --git a/docs/content/functions/crypto.md b/docs/content/functions/crypto.md index 3af44c08..18654d62 100644 --- a/docs/content/functions/crypto.md +++ b/docs/content/functions/crypto.md @@ -75,7 +75,10 @@ $ gomplate -i '{{ crypto.PBKDF2 "foo" "bar" 1024 8 }}' 32c4907c3c80792b ``` -## `crypto.RSADecrypt` +## `crypto.RSADecrypt` _(experimental)_ +**Experimental:** This function is [_experimental_][experimental] and may be enabled with the [`--experimental`][experimental] flag. + +[experimental]: ../config/#experimental Decrypt an RSA-encrypted input and print the output as a string. Note that this may result in unreadable text if the decrypted payload is binary. See @@ -120,7 +123,10 @@ $ gomplate -c ciphertext=env:///ENCRYPTED -c privKey=./testPrivKey \ hello ``` -## `crypto.RSADecryptBytes` +## `crypto.RSADecryptBytes` _(experimental)_ +**Experimental:** This function is [_experimental_][experimental] and may be enabled with the [`--experimental`][experimental] flag. + +[experimental]: ../config/#experimental Decrypt an RSA-encrypted input and output the decrypted byte array. @@ -166,7 +172,10 @@ $ gomplate -c pubKey=./testPubKey -c privKey=./testPrivKey \ hello ``` -## `crypto.RSAEncrypt` +## `crypto.RSAEncrypt` _(experimental)_ +**Experimental:** This function is [_experimental_][experimental] and may be enabled with the [`--experimental`][experimental] flag. + +[experimental]: ../config/#experimental Encrypt the input with RSA and the padding scheme from PKCS#1 v1.5. @@ -217,7 +226,10 @@ $ gomplate -c pubKey=./testPubKey \ 71729b87cccabb248b9e0e5173f0b12c01d9d2a0565bad18aef9d332ce984bde06acb8bb69334a01446f7f6430077f269e6fbf2ccacd972fe5856dd4719252ebddf599948d937d96ea41540dad291b868f6c0cf647dffdb5acb22cd33557f9a1ddd0ee6c1ad2bbafc910ba8f817b66ea0569afc06e5c7858fd9dc2638861fe7c97391b2f190e4c682b4aa2c9b0050081efe18b10aa8c2b2b5f5b68a42dcc06c9da35b37fca9b1509fddc940eb99f516a2e0195405bcb3993f0fa31bc038d53d2e7231dff08cc39448105ed2d0ac52d375cb543ca8a399f807cc5d007e2c44c69876d189667eee66361a393c4916826af77479382838cd4e004b8baa05636805a ``` -## `crypto.RSAGenerateKey` +## `crypto.RSAGenerateKey` _(experimental)_ +**Experimental:** This function is [_experimental_][experimental] and may be enabled with the [`--experimental`][experimental] flag. + +[experimental]: ../config/#experimental Generate a new RSA Private Key and output in PEM-encoded PKCS#1 ASN.1 DER form. @@ -258,7 +270,10 @@ $ gomplate -i '{{ $key := crypto.RSAGenerateKey 2048 -}} hello ``` -## `crypto.RSADerivePublicKey` +## `crypto.RSADerivePublicKey` _(experimental)_ +**Experimental:** This function is [_experimental_][experimental] and may be enabled with the [`--experimental`][experimental] flag. + +[experimental]: ../config/#experimental Derive a public key from an RSA private key and output in PKIX ASN.1 DER form. diff --git a/docs/content/usage.md b/docs/content/usage.md index 3966054d..823fc0fb 100644 --- a/docs/content/usage.md +++ b/docs/content/usage.md @@ -274,6 +274,12 @@ HELLO WORLD Note that multiple inputs are not yet supported when using this option. +### `--experimental` + +Use this flag to enable experimental functionality. See the docs for the +[`experimental`](../config/#experimental) configuration option for more +information. + ## Post-template command execution Gomplate can launch other commands when template execution is successful. Simply diff --git a/funcs.go b/funcs.go index 2c7e70f1..97c1cc18 100644 --- a/funcs.go +++ b/funcs.go @@ -1,34 +1,51 @@ package gomplate import ( + "context" "text/template" "github.com/hairyhenderson/gomplate/v3/data" "github.com/hairyhenderson/gomplate/v3/funcs" + "github.com/hairyhenderson/gomplate/v3/internal/config" ) -// Funcs - The function mappings are defined here! +// Funcs - +// Deprecated: use CreateFuncs instead func Funcs(d *data.Data) template.FuncMap { + ctx := context.Background() + cfg := config.FromContext(ctx) + return CreateFuncs(config.ContextWithConfig(ctx, cfg), d) +} + +// CreateFuncs - function mappings are created here +func CreateFuncs(ctx context.Context, d *data.Data) template.FuncMap { f := template.FuncMap{} - funcs.AddDataFuncs(f, d) - funcs.AWSFuncs(f) - funcs.AddGCPFuncs(f) - funcs.AddBase64Funcs(f) - funcs.AddNetFuncs(f) - funcs.AddReFuncs(f) - funcs.AddStringFuncs(f) - funcs.AddEnvFuncs(f) - funcs.AddConvFuncs(f) - funcs.AddTimeFuncs(f) - funcs.AddMathFuncs(f) - funcs.AddCryptoFuncs(f) - funcs.AddFileFuncs(f) - funcs.AddFilePathFuncs(f) - funcs.AddPathFuncs(f) - funcs.AddSockaddrFuncs(f) - funcs.AddTestFuncs(f) - funcs.AddCollFuncs(f) - funcs.AddUUIDFuncs(f) - funcs.AddRandomFuncs(f) + addToMap(f, funcs.CreateDataFuncs(ctx, d)) + addToMap(f, funcs.CreateAWSFuncs(ctx)) + addToMap(f, funcs.CreateGCPFuncs(ctx)) + addToMap(f, funcs.CreateBase64Funcs(ctx)) + addToMap(f, funcs.CreateNetFuncs(ctx)) + addToMap(f, funcs.CreateReFuncs(ctx)) + addToMap(f, funcs.CreateStringFuncs(ctx)) + addToMap(f, funcs.CreateEnvFuncs(ctx)) + addToMap(f, funcs.CreateConvFuncs(ctx)) + addToMap(f, funcs.CreateTimeFuncs(ctx)) + addToMap(f, funcs.CreateMathFuncs(ctx)) + addToMap(f, funcs.CreateCryptoFuncs(ctx)) + addToMap(f, funcs.CreateFileFuncs(ctx)) + addToMap(f, funcs.CreateFilePathFuncs(ctx)) + addToMap(f, funcs.CreatePathFuncs(ctx)) + addToMap(f, funcs.CreateSockaddrFuncs(ctx)) + addToMap(f, funcs.CreateTestFuncs(ctx)) + addToMap(f, funcs.CreateCollFuncs(ctx)) + addToMap(f, funcs.CreateUUIDFuncs(ctx)) + addToMap(f, funcs.CreateRandomFuncs(ctx)) return f } + +// addToMap - add src's entries to dst +func addToMap(dst, src map[string]interface{}) { + for k, v := range src { + dst[k] = v + } +} diff --git a/funcs/aws.go b/funcs/aws.go index 5a1556c0..e7359ce9 100644 --- a/funcs/aws.go +++ b/funcs/aws.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "sync" "github.com/hairyhenderson/gomplate/v3/aws" @@ -24,17 +25,32 @@ func AWSNS() *Funcs { // AWSFuncs - func AWSFuncs(f map[string]interface{}) { + f2 := CreateAWSFuncs(context.Background()) + for k, v := range f2 { + f[k] = v + } +} + +// CreateAWSFuncs - +func CreateAWSFuncs(ctx context.Context) map[string]interface{} { + f := map[string]interface{}{} + ns := AWSNS() + ns.ctx = ctx + f["aws"] = AWSNS // global aliases - for backwards compatibility - f["ec2meta"] = AWSNS().EC2Meta - f["ec2dynamic"] = AWSNS().EC2Dynamic - f["ec2tag"] = AWSNS().EC2Tag - f["ec2region"] = AWSNS().EC2Region + f["ec2meta"] = ns.EC2Meta + f["ec2dynamic"] = ns.EC2Dynamic + f["ec2tag"] = ns.EC2Tag + f["ec2region"] = ns.EC2Region + return f } // Funcs - type Funcs struct { + ctx context.Context + meta *aws.Ec2Meta info *aws.Ec2Info kms *aws.KMS diff --git a/funcs/base64.go b/funcs/base64.go index da9b315e..0b913008 100644 --- a/funcs/base64.go +++ b/funcs/base64.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "sync" "github.com/hairyhenderson/gomplate/v3/base64" @@ -20,11 +21,22 @@ func Base64NS() *Base64Funcs { // AddBase64Funcs - func AddBase64Funcs(f map[string]interface{}) { - f["base64"] = Base64NS + for k, v := range CreateBase64Funcs(context.Background()) { + f[k] = v + } +} + +// CreateBase64Funcs - +func CreateBase64Funcs(ctx context.Context) map[string]interface{} { + ns := Base64NS() + ns.ctx = ctx + return map[string]interface{}{"base64": Base64NS} } // Base64Funcs - -type Base64Funcs struct{} +type Base64Funcs struct { + ctx context.Context +} // Encode - func (f *Base64Funcs) Encode(in interface{}) (string, error) { diff --git a/funcs/coll.go b/funcs/coll.go index 570b6544..ae36fbd7 100644 --- a/funcs/coll.go +++ b/funcs/coll.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "sync" "github.com/hairyhenderson/gomplate/v3/conv" @@ -22,6 +23,16 @@ func CollNS() *CollFuncs { // AddCollFuncs - func AddCollFuncs(f map[string]interface{}) { + for k, v := range CreateCollFuncs(context.Background()) { + f[k] = v + } +} + +// CreateCollFuncs - +func CreateCollFuncs(ctx context.Context) map[string]interface{} { + ns := CollNS() + ns.ctx = ctx + f := map[string]interface{}{} f["coll"] = CollNS f["has"] = CollNS().Has @@ -37,10 +48,13 @@ func AddCollFuncs(f map[string]interface{}) { f["sort"] = CollNS().Sort f["jsonpath"] = CollNS().JSONPath f["flatten"] = CollNS().Flatten + return f } // CollFuncs - -type CollFuncs struct{} +type CollFuncs struct { + ctx context.Context +} // Slice - func (f *CollFuncs) Slice(args ...interface{}) []interface{} { diff --git a/funcs/conv.go b/funcs/conv.go index 210e1be6..986d4833 100644 --- a/funcs/conv.go +++ b/funcs/conv.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "net/url" "sync" "text/template" @@ -22,16 +23,29 @@ func ConvNS() *ConvFuncs { // AddConvFuncs - func AddConvFuncs(f map[string]interface{}) { + for k, v := range CreateConvFuncs(context.Background()) { + f[k] = v + } +} + +// CreateConvFuncs - +func CreateConvFuncs(ctx context.Context) map[string]interface{} { + ns := ConvNS() + ns.ctx = ctx + f := map[string]interface{}{} f["conv"] = ConvNS - f["urlParse"] = ConvNS().URL - f["bool"] = ConvNS().Bool - f["join"] = ConvNS().Join - f["default"] = ConvNS().Default + f["urlParse"] = ns.URL + f["bool"] = ns.Bool + f["join"] = ns.Join + f["default"] = ns.Default + return f } // ConvFuncs - -type ConvFuncs struct{} +type ConvFuncs struct { + ctx context.Context +} // Bool - func (f *ConvFuncs) Bool(s interface{}) bool { diff --git a/funcs/crypto.go b/funcs/crypto.go index 59516ded..1937e84e 100644 --- a/funcs/crypto.go +++ b/funcs/crypto.go @@ -1,6 +1,7 @@ package funcs import ( + "context" gcrypto "crypto" "crypto/sha1" //nolint: gosec "crypto/sha256" @@ -29,11 +30,23 @@ func CryptoNS() *CryptoFuncs { // AddCryptoFuncs - func AddCryptoFuncs(f map[string]interface{}) { - f["crypto"] = CryptoNS + for k, v := range CreateCryptoFuncs(context.Background()) { + f[k] = v + } +} + +// CreateCryptoFuncs - +func CreateCryptoFuncs(ctx context.Context) map[string]interface{} { + ns := CryptoNS() + ns.ctx = ctx + + return map[string]interface{}{"crypto": CryptoNS} } // CryptoFuncs - -type CryptoFuncs struct{} +type CryptoFuncs struct { + ctx context.Context +} // PBKDF2 - Run the Password-Based Key Derivation Function #2 as defined in // RFC 2898 (PKCS #5 v2.0). This function outputs the binary result in hex @@ -133,25 +146,41 @@ func (f *CryptoFuncs) Bcrypt(args ...interface{}) (string, error) { } // RSAEncrypt - +// Experimental! func (f *CryptoFuncs) RSAEncrypt(key string, in interface{}) ([]byte, error) { + if err := checkExperimental(f.ctx); err != nil { + return nil, err + } msg := toBytes(in) return crypto.RSAEncrypt(key, msg) } // RSADecrypt - +// Experimental! func (f *CryptoFuncs) RSADecrypt(key string, in []byte) (string, error) { + if err := checkExperimental(f.ctx); err != nil { + return "", err + } out, err := crypto.RSADecrypt(key, in) return string(out), err } // RSADecryptBytes - +// Experimental! func (f *CryptoFuncs) RSADecryptBytes(key string, in []byte) ([]byte, error) { + if err := checkExperimental(f.ctx); err != nil { + return nil, err + } out, err := crypto.RSADecrypt(key, in) return out, err } // RSAGenerateKey - +// Experimental! func (f *CryptoFuncs) RSAGenerateKey(args ...interface{}) (string, error) { + if err := checkExperimental(f.ctx); err != nil { + return "", err + } bits := 4096 if len(args) == 1 { bits = conv.ToInt(args[0]) @@ -163,7 +192,11 @@ func (f *CryptoFuncs) RSAGenerateKey(args ...interface{}) (string, error) { } // RSADerivePublicKey - +// Experimental! func (f *CryptoFuncs) RSADerivePublicKey(privateKey string) (string, error) { + if err := checkExperimental(f.ctx); err != nil { + return "", err + } out, err := crypto.RSADerivePublicKey([]byte(privateKey)) return string(out), err } diff --git a/funcs/crypto_test.go b/funcs/crypto_test.go index 4d2b483f..b61643f7 100644 --- a/funcs/crypto_test.go +++ b/funcs/crypto_test.go @@ -1,14 +1,26 @@ package funcs import ( + "context" "strings" "testing" + "github.com/hairyhenderson/gomplate/v3/internal/config" "github.com/stretchr/testify/assert" ) -func TestPBKDF2(t *testing.T) { +func testCryptoNS() *CryptoFuncs { + ctx := context.Background() + cfg := config.FromContext(ctx) + cfg.Experimental = true + ctx = config.ContextWithConfig(ctx, cfg) c := CryptoNS() + c.ctx = ctx + return c +} + +func TestPBKDF2(t *testing.T) { + c := testCryptoNS() dk, err := cryptoNS.PBKDF2("password", []byte("IEEE"), "4096", 32) assert.Equal(t, "f42c6fc52df0ebef9ebb4b90b38a5f902e83fe1b135a70e23aed762e9710a12e", dk) assert.NoError(t, err) @@ -36,7 +48,7 @@ func TestSHA(t *testing.T) { sha512 := "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f" sha512_224 := "4634270f707b6a54daae7530460842e20e37ed265ceee9a43e8924aa" sha512_256 := "53048e2681941ef99b2e29b76b4c7dabe4c2d0c634fc6d46e0e2f13107e7af23" - c := CryptoNS() + c := testCryptoNS() assert.Equal(t, sha1, c.SHA1(in)) assert.Equal(t, sha224, c.SHA224(in)) assert.Equal(t, sha256, c.SHA256(in)) @@ -48,7 +60,7 @@ func TestSHA(t *testing.T) { func TestBcrypt(t *testing.T) { in := "foo" - c := CryptoNS() + c := testCryptoNS() actual, err := c.Bcrypt(in) assert.NoError(t, err) assert.True(t, strings.HasPrefix(actual, "$2a$10$")) @@ -66,7 +78,7 @@ func TestBcrypt(t *testing.T) { } func TestRSAGenerateKey(t *testing.T) { - c := CryptoNS() + c := testCryptoNS() _, err := c.RSAGenerateKey(0) assert.Error(t, err) @@ -82,7 +94,7 @@ func TestRSAGenerateKey(t *testing.T) { } func TestRSACrypt(t *testing.T) { - c := CryptoNS() + c := testCryptoNS() key, err := c.RSAGenerateKey() assert.NoError(t, err) pub, err := c.RSADerivePublicKey(key) diff --git a/funcs/data.go b/funcs/data.go index b13619e3..0a9c4a8a 100644 --- a/funcs/data.go +++ b/funcs/data.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "sync" "github.com/hairyhenderson/gomplate/v3/conv" @@ -20,6 +21,14 @@ func DataNS() *DataFuncs { // AddDataFuncs - func AddDataFuncs(f map[string]interface{}, d *data.Data) { + for k, v := range CreateDataFuncs(context.Background(), d) { + f[k] = v + } +} + +// CreateDataFuncs - +func CreateDataFuncs(ctx context.Context, d *data.Data) map[string]interface{} { + f := map[string]interface{}{} f["datasource"] = d.Datasource f["ds"] = d.Datasource f["datasourceExists"] = d.DatasourceExists @@ -27,25 +36,31 @@ func AddDataFuncs(f map[string]interface{}, d *data.Data) { f["defineDatasource"] = d.DefineDatasource f["include"] = d.Include + ns := DataNS() + ns.ctx = ctx + f["data"] = DataNS - f["json"] = DataNS().JSON - f["jsonArray"] = DataNS().JSONArray - f["yaml"] = DataNS().YAML - f["yamlArray"] = DataNS().YAMLArray - f["toml"] = DataNS().TOML - f["csv"] = DataNS().CSV - f["csvByRow"] = DataNS().CSVByRow - f["csvByColumn"] = DataNS().CSVByColumn - f["toJSON"] = DataNS().ToJSON - f["toJSONPretty"] = DataNS().ToJSONPretty - f["toYAML"] = DataNS().ToYAML - f["toTOML"] = DataNS().ToTOML - f["toCSV"] = DataNS().ToCSV + f["json"] = ns.JSON + f["jsonArray"] = ns.JSONArray + f["yaml"] = ns.YAML + f["yamlArray"] = ns.YAMLArray + f["toml"] = ns.TOML + f["csv"] = ns.CSV + f["csvByRow"] = ns.CSVByRow + f["csvByColumn"] = ns.CSVByColumn + f["toJSON"] = ns.ToJSON + f["toJSONPretty"] = ns.ToJSONPretty + f["toYAML"] = ns.ToYAML + f["toTOML"] = ns.ToTOML + f["toCSV"] = ns.ToCSV + return f } // DataFuncs - -type DataFuncs struct{} +type DataFuncs struct { + ctx context.Context +} // JSON - func (f *DataFuncs) JSON(in interface{}) (map[string]interface{}, error) { diff --git a/funcs/env.go b/funcs/env.go index 2a6866c9..3188eb96 100644 --- a/funcs/env.go +++ b/funcs/env.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "sync" "github.com/hairyhenderson/gomplate/v3/conv" @@ -20,14 +21,25 @@ func EnvNS() *EnvFuncs { // AddEnvFuncs - func AddEnvFuncs(f map[string]interface{}) { - f["env"] = EnvNS + for k, v := range CreateEnvFuncs(context.Background()) { + f[k] = v + } +} - // global aliases - for backwards compatibility - f["getenv"] = EnvNS().Getenv +// CreateEnvFuncs - +func CreateEnvFuncs(ctx context.Context) map[string]interface{} { + ns := EnvNS() + ns.ctx = ctx + return map[string]interface{}{ + "env": EnvNS, + "getenv": ns.Getenv, + } } // EnvFuncs - -type EnvFuncs struct{} +type EnvFuncs struct { + ctx context.Context +} // Getenv - func (f *EnvFuncs) Getenv(key interface{}, def ...string) string { diff --git a/funcs/file.go b/funcs/file.go index 5831ffd2..0f063b6e 100644 --- a/funcs/file.go +++ b/funcs/file.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "os" "sync" @@ -16,18 +17,28 @@ var ( // FileNS - the File namespace func FileNS() *FileFuncs { - ffInit.Do(func() { ff = &FileFuncs{afero.NewOsFs()} }) + ffInit.Do(func() { ff = &FileFuncs{fs: afero.NewOsFs()} }) return ff } // AddFileFuncs - func AddFileFuncs(f map[string]interface{}) { - f["file"] = FileNS + for k, v := range CreateFileFuncs(context.Background()) { + f[k] = v + } +} + +// CreateFileFuncs - +func CreateFileFuncs(ctx context.Context) map[string]interface{} { + ns := FileNS() + ns.ctx = ctx + return map[string]interface{}{"file": FileNS} } // FileFuncs - type FileFuncs struct { - fs afero.Fs + ctx context.Context + fs afero.Fs } // Read - diff --git a/funcs/file_test.go b/funcs/file_test.go index 393fcef0..1202854c 100644 --- a/funcs/file_test.go +++ b/funcs/file_test.go @@ -10,7 +10,7 @@ import ( func TestFileExists(t *testing.T) { fs := afero.NewMemMapFs() - ff := &FileFuncs{fs} + ff := &FileFuncs{fs: fs} _ = fs.Mkdir("/tmp", 0777) f, _ := fs.Create("/tmp/foo") @@ -22,7 +22,7 @@ func TestFileExists(t *testing.T) { func TestFileIsDir(t *testing.T) { fs := afero.NewMemMapFs() - ff := &FileFuncs{fs} + ff := &FileFuncs{fs: fs} _ = fs.Mkdir("/tmp", 0777) f, _ := fs.Create("/tmp/foo") @@ -34,7 +34,7 @@ func TestFileIsDir(t *testing.T) { func TestFileWalk(t *testing.T) { fs := afero.NewMemMapFs() - ff := &FileFuncs{fs} + ff := &FileFuncs{fs: fs} _ = fs.Mkdir("/tmp", 0777) _ = fs.Mkdir("/tmp/bar", 0777) diff --git a/funcs/filepath.go b/funcs/filepath.go index afaeff73..a86c4742 100644 --- a/funcs/filepath.go +++ b/funcs/filepath.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "path/filepath" "sync" @@ -20,11 +21,21 @@ func FilePathNS() *FilePathFuncs { // AddFilePathFuncs - func AddFilePathFuncs(f map[string]interface{}) { - f["filepath"] = FilePathNS + for k, v := range CreateFilePathFuncs(context.Background()) { + f[k] = v + } +} + +// CreateFilePathFuncs - +func CreateFilePathFuncs(ctx context.Context) map[string]interface{} { + ns := FilePathNS() + ns.ctx = ctx + return map[string]interface{}{"filepath": FilePathNS} } // FilePathFuncs - type FilePathFuncs struct { + ctx context.Context } // Base - diff --git a/funcs/funcs.go b/funcs/funcs.go new file mode 100644 index 00000000..6b5da5cb --- /dev/null +++ b/funcs/funcs.go @@ -0,0 +1,15 @@ +package funcs + +import ( + "context" + "fmt" + + "github.com/hairyhenderson/gomplate/v3/internal/config" +) + +func checkExperimental(ctx context.Context) error { + if !config.FromContext(ctx).Experimental { + return fmt.Errorf("experimental function, but experimental mode not enabled") + } + return nil +} diff --git a/funcs/gcp.go b/funcs/gcp.go index 5227909d..8b479506 100644 --- a/funcs/gcp.go +++ b/funcs/gcp.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "sync" "github.com/hairyhenderson/gomplate/v3/gcp" @@ -22,12 +23,26 @@ func GCPNS() *GcpFuncs { } // AddGCPFuncs - +// Deprecated: use CreateGCPFuncs func AddGCPFuncs(f map[string]interface{}) { + for k, v := range CreateGCPFuncs(context.Background()) { + f[k] = v + } +} + +// CreateGCPFuncs - +func CreateGCPFuncs(ctx context.Context) map[string]interface{} { + f := map[string]interface{}{} + ns := GCPNS() + ns.ctx = ctx f["gcp"] = GCPNS + return f } -// Funcs - +// GcpFuncs - type GcpFuncs struct { + ctx context.Context + meta *gcp.MetaClient metaInit sync.Once gcpopts gcp.ClientOptions diff --git a/funcs/math.go b/funcs/math.go index e0acdceb..b3ec70b8 100644 --- a/funcs/math.go +++ b/funcs/math.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "fmt" gmath "math" "strconv" @@ -24,19 +25,32 @@ func MathNS() *MathFuncs { // AddMathFuncs - func AddMathFuncs(f map[string]interface{}) { + for k, v := range CreateMathFuncs(context.Background()) { + f[k] = v + } +} + +// CreateMathFuncs - +func CreateMathFuncs(ctx context.Context) map[string]interface{} { + f := map[string]interface{}{} + ns := MathNS() + ns.ctx = ctx f["math"] = MathNS - f["add"] = MathNS().Add - f["sub"] = MathNS().Sub - f["mul"] = MathNS().Mul - f["div"] = MathNS().Div - f["rem"] = MathNS().Rem - f["pow"] = MathNS().Pow - f["seq"] = MathNS().Seq + f["add"] = ns.Add + f["sub"] = ns.Sub + f["mul"] = ns.Mul + f["div"] = ns.Div + f["rem"] = ns.Rem + f["pow"] = ns.Pow + f["seq"] = ns.Seq + return f } // MathFuncs - -type MathFuncs struct{} +type MathFuncs struct { + ctx context.Context +} // IsInt - func (f *MathFuncs) IsInt(n interface{}) bool { diff --git a/funcs/net.go b/funcs/net.go index fba46045..922289b7 100644 --- a/funcs/net.go +++ b/funcs/net.go @@ -1,6 +1,7 @@ package funcs import ( + "context" stdnet "net" "sync" @@ -25,8 +26,17 @@ func AddNetFuncs(f map[string]interface{}) { f["net"] = NetNS } +// CreateNetFuncs - +func CreateNetFuncs(ctx context.Context) map[string]interface{} { + ns := NetNS() + ns.ctx = ctx + return map[string]interface{}{"net": NetNS} +} + // NetFuncs - -type NetFuncs struct{} +type NetFuncs struct { + ctx context.Context +} // LookupIP - func (f *NetFuncs) LookupIP(name interface{}) (string, error) { diff --git a/funcs/path.go b/funcs/path.go index 4a6ad870..419114cf 100644 --- a/funcs/path.go +++ b/funcs/path.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "path" "sync" @@ -20,11 +21,21 @@ func PathNS() *PathFuncs { // AddPathFuncs - func AddPathFuncs(f map[string]interface{}) { - f["path"] = PathNS + for k, v := range CreatePathFuncs(context.Background()) { + f[k] = v + } +} + +// CreatePathFuncs - +func CreatePathFuncs(ctx context.Context) map[string]interface{} { + ns := PathNS() + ns.ctx = ctx + return map[string]interface{}{"path": PathNS} } // PathFuncs - type PathFuncs struct { + ctx context.Context } // Base - diff --git a/funcs/random.go b/funcs/random.go index aea2787f..44a6e67c 100644 --- a/funcs/random.go +++ b/funcs/random.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "strconv" "sync" "unicode/utf8" @@ -24,11 +25,22 @@ func RandomNS() *RandomFuncs { // AddRandomFuncs - func AddRandomFuncs(f map[string]interface{}) { - f["random"] = RandomNS + for k, v := range CreateRandomFuncs(context.Background()) { + f[k] = v + } +} + +// CreateRandomFuncs - +func CreateRandomFuncs(ctx context.Context) map[string]interface{} { + ns := RandomNS() + ns.ctx = ctx + return map[string]interface{}{"random": RandomNS} } // RandomFuncs - -type RandomFuncs struct{} +type RandomFuncs struct { + ctx context.Context +} // ASCII - func (f *RandomFuncs) ASCII(count interface{}) (string, error) { diff --git a/funcs/regexp.go b/funcs/regexp.go index ce7feec9..5aa1bdfb 100644 --- a/funcs/regexp.go +++ b/funcs/regexp.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "sync" "github.com/pkg/errors" @@ -22,11 +23,22 @@ func ReNS() *ReFuncs { // AddReFuncs - func AddReFuncs(f map[string]interface{}) { - f["regexp"] = ReNS + for k, v := range CreateReFuncs(context.Background()) { + f[k] = v + } +} + +// CreateReFuncs - +func CreateReFuncs(ctx context.Context) map[string]interface{} { + ns := ReNS() + ns.ctx = ctx + return map[string]interface{}{"regexp": ReNS} } // ReFuncs - -type ReFuncs struct{} +type ReFuncs struct { + ctx context.Context +} // Find - func (f *ReFuncs) Find(re, input interface{}) (string, error) { diff --git a/funcs/sockaddr.go b/funcs/sockaddr.go index e8a94507..f1bb4561 100644 --- a/funcs/sockaddr.go +++ b/funcs/sockaddr.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "sync" "github.com/hashicorp/go-sockaddr" @@ -23,8 +24,17 @@ func AddSockaddrFuncs(f map[string]interface{}) { f["sockaddr"] = SockaddrNS } +// CreateSockaddrFuncs - +func CreateSockaddrFuncs(ctx context.Context) map[string]interface{} { + ns := SockaddrNS() + ns.ctx = ctx + return map[string]interface{}{"sockaddr": SockaddrNS} +} + // SockaddrFuncs - -type SockaddrFuncs struct{} +type SockaddrFuncs struct { + ctx context.Context +} // GetAllInterfaces - func (f *SockaddrFuncs) GetAllInterfaces() (sockaddr.IfAddrs, error) { diff --git a/funcs/strings.go b/funcs/strings.go index 9023012a..a3ac7ddd 100644 --- a/funcs/strings.go +++ b/funcs/strings.go @@ -6,6 +6,7 @@ package funcs // in templates easier. import ( + "context" "fmt" "reflect" "sync" @@ -34,17 +35,28 @@ func StrNS() *StringFuncs { // AddStringFuncs - func AddStringFuncs(f map[string]interface{}) { + for k, v := range CreateStringFuncs(context.Background()) { + f[k] = v + } +} + +// CreateStringFuncs - +func CreateStringFuncs(ctx context.Context) map[string]interface{} { + f := map[string]interface{}{} + ns := StrNS() + ns.ctx = ctx + f["strings"] = StrNS - f["replaceAll"] = StrNS().ReplaceAll - f["title"] = StrNS().Title - f["toUpper"] = StrNS().ToUpper - f["toLower"] = StrNS().ToLower - f["trimSpace"] = StrNS().TrimSpace - f["indent"] = StrNS().Indent - f["quote"] = StrNS().Quote - f["shellQuote"] = StrNS().ShellQuote - f["squote"] = StrNS().Squote + f["replaceAll"] = ns.ReplaceAll + f["title"] = ns.Title + f["toUpper"] = ns.ToUpper + f["toLower"] = ns.ToLower + f["trimSpace"] = ns.TrimSpace + f["indent"] = ns.Indent + f["quote"] = ns.Quote + f["shellQuote"] = ns.ShellQuote + f["squote"] = ns.Squote // these are legacy aliases with non-pipelinable arg order f["contains"] = strings.Contains @@ -53,10 +65,14 @@ func AddStringFuncs(f map[string]interface{}) { f["split"] = strings.Split f["splitN"] = strings.SplitN f["trim"] = strings.Trim + + return f } // StringFuncs - -type StringFuncs struct{} +type StringFuncs struct { + ctx context.Context +} // Abbrev - func (f *StringFuncs) Abbrev(args ...interface{}) (string, error) { diff --git a/funcs/test.go b/funcs/test.go index 820d4f17..1ceef651 100644 --- a/funcs/test.go +++ b/funcs/test.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "reflect" "sync" @@ -23,18 +24,31 @@ func TestNS() *TestFuncs { // AddTestFuncs - func AddTestFuncs(f map[string]interface{}) { + for k, v := range CreateTestFuncs(context.Background()) { + f[k] = v + } +} + +// CreateTestFuncs - +func CreateTestFuncs(ctx context.Context) map[string]interface{} { + f := map[string]interface{}{} + ns := TestNS() + ns.ctx = ctx f["test"] = TestNS - f["assert"] = TestNS().Assert - f["fail"] = TestNS().Fail - f["required"] = TestNS().Required - f["ternary"] = TestNS().Ternary - f["kind"] = TestNS().Kind - f["isKind"] = TestNS().IsKind + f["assert"] = ns.Assert + f["fail"] = ns.Fail + f["required"] = ns.Required + f["ternary"] = ns.Ternary + f["kind"] = ns.Kind + f["isKind"] = ns.IsKind + return f } // TestFuncs - -type TestFuncs struct{} +type TestFuncs struct { + ctx context.Context +} // Assert - func (f *TestFuncs) Assert(args ...interface{}) (string, error) { diff --git a/funcs/time.go b/funcs/time.go index f56a8e30..2074a6a9 100644 --- a/funcs/time.go +++ b/funcs/time.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "fmt" "strconv" "strings" @@ -42,11 +43,22 @@ func TimeNS() *TimeFuncs { // AddTimeFuncs - func AddTimeFuncs(f map[string]interface{}) { - f["time"] = TimeNS + for k, v := range CreateTimeFuncs(context.Background()) { + f[k] = v + } +} + +// CreateTimeFuncs - +func CreateTimeFuncs(ctx context.Context) map[string]interface{} { + ns := TimeNS() + ns.ctx = ctx + + return map[string]interface{}{"time": TimeNS} } // TimeFuncs - type TimeFuncs struct { + ctx context.Context ANSIC string UnixDate string RubyDate string diff --git a/funcs/uuid.go b/funcs/uuid.go index 3634a837..3799334e 100644 --- a/funcs/uuid.go +++ b/funcs/uuid.go @@ -1,6 +1,7 @@ package funcs import ( + "context" "sync" "github.com/hairyhenderson/gomplate/v3/conv" @@ -23,11 +24,22 @@ func UUIDNS() *UUIDFuncs { // AddUUIDFuncs - func AddUUIDFuncs(f map[string]interface{}) { - f["uuid"] = UUIDNS + for k, v := range CreateUUIDFuncs(context.Background()) { + f[k] = v + } +} + +// CreateUUIDFuncs - +func CreateUUIDFuncs(ctx context.Context) map[string]interface{} { + ns := UUIDNS() + ns.ctx = ctx + return map[string]interface{}{"uuid": UUIDNS} } // UUIDFuncs - -type UUIDFuncs struct{} +type UUIDFuncs struct { + ctx context.Context +} // V1 - return a version 1 UUID (based on the current MAC Address and the // current date/time). Use V4 instead in most cases. diff --git a/gomplate.go b/gomplate.go index 8922611e..d0fb3959 100644 --- a/gomplate.go +++ b/gomplate.go @@ -140,7 +140,7 @@ func Run(ctx context.Context, cfg *config.Config) error { if err != nil { return err } - funcMap := Funcs(d) + funcMap := CreateFuncs(ctx, d) err = bindPlugins(ctx, cfg, funcMap) if err != nil { return err diff --git a/internal/config/configfile.go b/internal/config/configfile.go index 19105633..bd0eaec6 100644 --- a/internal/config/configfile.go +++ b/internal/config/configfile.go @@ -2,6 +2,7 @@ package config import ( "bytes" + "context" "fmt" "io" "net/http" @@ -38,6 +39,8 @@ type Config struct { OutputDir string `yaml:"outputDir,omitempty"` OutputMap string `yaml:"outputMap,omitempty"` + Experimental bool `yaml:"experimental,omitempty"` + SuppressEmpty bool `yaml:"suppressEmpty,omitempty"` ExecPipe bool `yaml:"execPipe,omitempty"` PostExec []string `yaml:"postExec,omitempty,flow"` @@ -60,6 +63,27 @@ type Config struct { OutWriter io.Writer `yaml:"-"` } +var cfgContextKey = struct{}{} + +// ContextWithConfig returns a new context with a reference to the config. +func ContextWithConfig(ctx context.Context, cfg *Config) context.Context { + return context.WithValue(ctx, cfgContextKey, cfg) +} + +// FromContext returns a config from the given context, if any. If no +// config is present a new default configuration will be returned. +func FromContext(ctx context.Context) (cfg *Config) { + ok := ctx != nil + if ok { + cfg, ok = ctx.Value(cfgContextKey).(*Config) + } + if !ok { + cfg = &Config{} + cfg.ApplyDefaults() + } + return cfg +} + // mergeDataSources - use d as defaults, and override with values from o func mergeDataSources(d, o map[string]DataSource) map[string]DataSource { for k, v := range o { diff --git a/internal/config/configfile_test.go b/internal/config/configfile_test.go index 039467e3..7ca07450 100644 --- a/internal/config/configfile_test.go +++ b/internal/config/configfile_test.go @@ -1,6 +1,7 @@ package config import ( + "context" "net/http" "net/url" "os" @@ -735,3 +736,22 @@ func TestParseDatasourceArgWithAlias(t *testing.T) { assert.Equal(t, "merge", ds.URL.Scheme) assert.Equal(t, "./foo.yaml|http://example.com/bar.json%3Ffoo=bar", ds.URL.Opaque) } + +func TestContextWithConfig(t *testing.T) { + ctx := context.Background() + cfg := &Config{} + ctx = ContextWithConfig(ctx, cfg) + assert.Equal(t, cfg, ctx.Value(cfgContextKey)) +} + +func TestFromContext(t *testing.T) { + cfg := &Config{} + ctx := context.WithValue(context.Background(), + cfgContextKey, cfg) + assert.Equal(t, cfg, FromContext(ctx)) + + ctx = context.Background() + cfg = FromContext(ctx) + // assert that the returned config looks like a default one + assert.Equal(t, "{{", cfg.LDelim) +} diff --git a/internal/tests/integration/crypto_test.go b/internal/tests/integration/crypto_test.go index 18f0b6d2..68fd0806 100644 --- a/internal/tests/integration/crypto_test.go +++ b/internal/tests/integration/crypto_test.go @@ -52,6 +52,7 @@ func (s *CryptoSuite) TearDownTest(c *check.C) { func (s *CryptoSuite) TestRSACrypt(c *check.C) { result := icmd.RunCmd(icmd.Command(GomplateBin, + "--experimental", "-i", `{{ crypto.RSAGenerateKey 2048 -}}`, "-o", `key.pem`), func(cmd *icmd.Cmd) { cmd.Dir = s.tmpDir.Path() @@ -59,6 +60,7 @@ func (s *CryptoSuite) TestRSACrypt(c *check.C) { result.Assert(c, icmd.Expected{ExitCode: 0}) result = icmd.RunCmd(icmd.Command(GomplateBin, + "--experimental", "-c", "privKey=./key.pem", "-i", `{{ $pub := crypto.RSADerivePublicKey .privKey -}} {{ $enc := "hello" | crypto.RSAEncrypt $pub -}}