From cc7dbf6092f9c6a111d67e0b9b2d95bf4c413acb Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Mon, 19 Oct 2020 05:07:13 -0700 Subject: [PATCH] Add consul_key function to integrate concul with hcl2 tempaltes. (#10119) * Add consul_key function to integrate concul with hcl2 tempaltes. * sidebar nav --- common/template/funcs.go | 25 +++++++++++ hcl2template/function/Consul.go | 25 +++++++++++ hcl2template/functions.go | 1 + template/interpolate/funcs.go | 25 +---------- website/data/docs-navigation.js | 1 + .../from-1.5/functions/contextual/consul.mdx | 42 +++++++++++++++++++ .../from-1.5/functions/contextual/vault.mdx | 2 +- 7 files changed, 97 insertions(+), 24 deletions(-) create mode 100644 hcl2template/function/Consul.go create mode 100644 website/pages/docs/from-1.5/functions/contextual/consul.mdx diff --git a/common/template/funcs.go b/common/template/funcs.go index f086cdf1c..fd86e1ade 100644 --- a/common/template/funcs.go +++ b/common/template/funcs.go @@ -8,6 +8,7 @@ import ( "strings" "sync" + consulapi "github.com/hashicorp/consul/api" vaultapi "github.com/hashicorp/vault/api" ) @@ -61,3 +62,27 @@ func Vault(path string, key string) (string, error) { } return "", errors.New("Vault path does not contain the requested key") } + +func Consul(k string) (string, error) { + consulConfig := consulapi.DefaultConfig() + client, err := consulapi.NewClient(consulConfig) + if err != nil { + return "", fmt.Errorf("error getting consul client: %s", err) + } + + q := &consulapi.QueryOptions{} + kv, _, err := client.KV().Get(k, q) + if err != nil { + return "", fmt.Errorf("error reading consul key: %s", err) + } + if kv == nil { + return "", fmt.Errorf("key does not exist at the given path: %s", k) + } + + value := string(kv.Value) + if value == "" { + return "", fmt.Errorf("value is empty at path %s", k) + } + + return value, nil +} diff --git a/hcl2template/function/Consul.go b/hcl2template/function/Consul.go new file mode 100644 index 000000000..4fc81a1e0 --- /dev/null +++ b/hcl2template/function/Consul.go @@ -0,0 +1,25 @@ +package function + +import ( + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" + + commontpl "github.com/hashicorp/packer/common/template" +) + +// ConsulFunc constructs a function that retrieves KV secrets from HC vault +var ConsulFunc = function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "key", + Type: cty.String, + }, + }, + Type: function.StaticReturnType(cty.String), + Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { + key := args[0].AsString() + val, err := commontpl.Consul(key) + + return cty.StringVal(val), err + }, +}) diff --git a/hcl2template/functions.go b/hcl2template/functions.go index 2c4b1396a..3f3e7a858 100644 --- a/hcl2template/functions.go +++ b/hcl2template/functions.go @@ -46,6 +46,7 @@ func Functions(basedir string) map[string]function.Function { "coalescelist": stdlib.CoalesceListFunc, "compact": stdlib.CompactFunc, "concat": stdlib.ConcatFunc, + "consul_key": pkrfunction.ConsulFunc, "contains": stdlib.ContainsFunc, "convert": typeexpr.ConvertFunc, "csvdecode": stdlib.CSVDecodeFunc, diff --git a/template/interpolate/funcs.go b/template/interpolate/funcs.go index 8fd68a821..f6fae509d 100644 --- a/template/interpolate/funcs.go +++ b/template/interpolate/funcs.go @@ -10,7 +10,6 @@ import ( "text/template" "time" - consulapi "github.com/hashicorp/consul/api" commontpl "github.com/hashicorp/packer/common/template" "github.com/hashicorp/packer/common/uuid" "github.com/hashicorp/packer/helper/common" @@ -250,34 +249,14 @@ func funcGenPackerVersion(ctx *Context) interface{} { } func funcGenConsul(ctx *Context) interface{} { - return func(k string) (string, error) { + return func(key string) (string, error) { if !ctx.EnableEnv { // The error message doesn't have to be that detailed since // semantic checks should catch this. return "", errors.New("consul_key is not allowed here") } - consulConfig := consulapi.DefaultConfig() - client, err := consulapi.NewClient(consulConfig) - if err != nil { - return "", fmt.Errorf("error getting consul client: %s", err) - } - - q := &consulapi.QueryOptions{} - kv, _, err := client.KV().Get(k, q) - if err != nil { - return "", fmt.Errorf("error reading consul key: %s", err) - } - if kv == nil { - return "", fmt.Errorf("key does not exist at the given path: %s", k) - } - - value := string(kv.Value) - if value == "" { - return "", fmt.Errorf("value is empty at path %s", k) - } - - return value, nil + return commontpl.Consul(key) } } diff --git a/website/data/docs-navigation.js b/website/data/docs-navigation.js index 922c47855..d2501f960 100644 --- a/website/data/docs-navigation.js +++ b/website/data/docs-navigation.js @@ -33,6 +33,7 @@ export default [ category: 'contextual', content: [ 'vault', + 'consul', ], }, { diff --git a/website/pages/docs/from-1.5/functions/contextual/consul.mdx b/website/pages/docs/from-1.5/functions/contextual/consul.mdx new file mode 100644 index 000000000..048593ebb --- /dev/null +++ b/website/pages/docs/from-1.5/functions/contextual/consul.mdx @@ -0,0 +1,42 @@ +--- +layout: docs +page_title: consul - Functions - Configuration Language +sidebar_title: consul +description: The consul function retrieves secrets from HashiCorp consul KV stores. +--- + + +# `consul_key` Function + +[Consul](https://www.consul.io) keys can be used within your template using the +`consul_key` function. + +You can either use this function in a locals block or directly inline where you +want to use the value. + +```hcl +locals { + my_version = "${consul_key("myservice/version")}" +} + +source "null" "first-example" { + communicator = "none" +} + +build { + name = "my-build-name" + sources = ["null.first-example"] + + provisioner "shell-local" { + environment_vars = ["TESTVAR=${build.PackerRunUUID}"] + inline = ["echo my_version is '${local.my_version}'", + "echo version is '${consul_key("myservice/version")}'."] + } +} +``` + +This will load the key stored at the path `myservice/version` from consul. + +The configuration for consul (address, tokens, ...) must be specified as +environment variables, as specified in the +[Documentation](https://www.consul.io/docs/commands#environment-variables). diff --git a/website/pages/docs/from-1.5/functions/contextual/vault.mdx b/website/pages/docs/from-1.5/functions/contextual/vault.mdx index 1bf1ff1be..477c2ec46 100644 --- a/website/pages/docs/from-1.5/functions/contextual/vault.mdx +++ b/website/pages/docs/from-1.5/functions/contextual/vault.mdx @@ -46,7 +46,7 @@ stored at the key `foo`, storing it as the local variable `local.foo`. In order for this to work, you must set the environment variables `VAULT_TOKEN` and `VAULT_ADDR` to valid values. --> **NOTE:** HCL functions can be used in local variable definitions or inline +-> **NOTE:** HCL functions can be used in local variable definitions or inline with a provisioner/post-processor. They cannot be used in global variable definitions. The api tool we use allows for more custom configuration of the Vault client via