github.phpd.cn/hashicorp/packer@v1.3.2/template/interpolate/funcs.go (about)

     1  package interpolate
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strconv"
     9  	"strings"
    10  	"text/template"
    11  	"time"
    12  
    13  	consulapi "github.com/hashicorp/consul/api"
    14  	"github.com/hashicorp/packer/common/uuid"
    15  	"github.com/hashicorp/packer/version"
    16  	vaultapi "github.com/hashicorp/vault/api"
    17  )
    18  
    19  // InitTime is the UTC time when this package was initialized. It is
    20  // used as the timestamp for all configuration templates so that they
    21  // match for a single build.
    22  var InitTime time.Time
    23  
    24  func init() {
    25  	InitTime = time.Now().UTC()
    26  }
    27  
    28  // Funcs are the interpolation funcs that are available within interpolations.
    29  var FuncGens = map[string]FuncGenerator{
    30  	"build_name":     funcGenBuildName,
    31  	"build_type":     funcGenBuildType,
    32  	"env":            funcGenEnv,
    33  	"isotime":        funcGenIsotime,
    34  	"pwd":            funcGenPwd,
    35  	"split":          funcGenSplitter,
    36  	"template_dir":   funcGenTemplateDir,
    37  	"timestamp":      funcGenTimestamp,
    38  	"uuid":           funcGenUuid,
    39  	"user":           funcGenUser,
    40  	"packer_version": funcGenPackerVersion,
    41  	"consul_key":     funcGenConsul,
    42  	"vault":          funcGenVault,
    43  
    44  	"upper": funcGenPrimitive(strings.ToUpper),
    45  	"lower": funcGenPrimitive(strings.ToLower),
    46  }
    47  
    48  // FuncGenerator is a function that given a context generates a template
    49  // function for the template.
    50  type FuncGenerator func(*Context) interface{}
    51  
    52  // Funcs returns the functions that can be used for interpolation given
    53  // a context.
    54  func Funcs(ctx *Context) template.FuncMap {
    55  	result := make(map[string]interface{})
    56  	for k, v := range FuncGens {
    57  		result[k] = v(ctx)
    58  	}
    59  	if ctx != nil {
    60  		for k, v := range ctx.Funcs {
    61  			result[k] = v
    62  		}
    63  	}
    64  
    65  	return template.FuncMap(result)
    66  }
    67  
    68  func funcGenSplitter(ctx *Context) interface{} {
    69  	return func(k string, s string, i int) (string, error) {
    70  		// return func(s string) (string, error) {
    71  		split := strings.Split(k, s)
    72  		if len(split) <= i {
    73  			return "", fmt.Errorf("the substring %d was unavailable using the separator value, %s, only %d values were found", i, s, len(split))
    74  		}
    75  		return split[i], nil
    76  	}
    77  }
    78  
    79  func funcGenBuildName(ctx *Context) interface{} {
    80  	return func() (string, error) {
    81  		if ctx == nil || ctx.BuildName == "" {
    82  			return "", errors.New("build_name not available")
    83  		}
    84  
    85  		return ctx.BuildName, nil
    86  	}
    87  }
    88  
    89  func funcGenBuildType(ctx *Context) interface{} {
    90  	return func() (string, error) {
    91  		if ctx == nil || ctx.BuildType == "" {
    92  			return "", errors.New("build_type not available")
    93  		}
    94  
    95  		return ctx.BuildType, nil
    96  	}
    97  }
    98  
    99  func funcGenEnv(ctx *Context) interface{} {
   100  	return func(k string) (string, error) {
   101  		if !ctx.EnableEnv {
   102  			// The error message doesn't have to be that detailed since
   103  			// semantic checks should catch this.
   104  			return "", errors.New("env vars are not allowed here")
   105  		}
   106  
   107  		return os.Getenv(k), nil
   108  	}
   109  }
   110  
   111  func funcGenIsotime(ctx *Context) interface{} {
   112  	return func(format ...string) (string, error) {
   113  		if len(format) == 0 {
   114  			return InitTime.Format(time.RFC3339), nil
   115  		}
   116  
   117  		if len(format) > 1 {
   118  			return "", fmt.Errorf("too many values, 1 needed: %v", format)
   119  		}
   120  
   121  		return InitTime.Format(format[0]), nil
   122  	}
   123  }
   124  
   125  func funcGenPrimitive(value interface{}) FuncGenerator {
   126  	return func(ctx *Context) interface{} {
   127  		return value
   128  	}
   129  }
   130  
   131  func funcGenPwd(ctx *Context) interface{} {
   132  	return func() (string, error) {
   133  		return os.Getwd()
   134  	}
   135  }
   136  
   137  func funcGenTemplateDir(ctx *Context) interface{} {
   138  	return func() (string, error) {
   139  		if ctx == nil || ctx.TemplatePath == "" {
   140  			return "", errors.New("template path not available")
   141  		}
   142  
   143  		path, err := filepath.Abs(filepath.Dir(ctx.TemplatePath))
   144  		if err != nil {
   145  			return "", err
   146  		}
   147  
   148  		return path, nil
   149  	}
   150  }
   151  
   152  func funcGenTimestamp(ctx *Context) interface{} {
   153  	return func() string {
   154  		return strconv.FormatInt(InitTime.Unix(), 10)
   155  	}
   156  }
   157  
   158  func funcGenUser(ctx *Context) interface{} {
   159  	return func(k string) (string, error) {
   160  		if ctx == nil || ctx.UserVariables == nil {
   161  			return "", errors.New("test")
   162  		}
   163  
   164  		return ctx.UserVariables[k], nil
   165  	}
   166  }
   167  
   168  func funcGenUuid(ctx *Context) interface{} {
   169  	return func() string {
   170  		return uuid.TimeOrderedUUID()
   171  	}
   172  }
   173  
   174  func funcGenPackerVersion(ctx *Context) interface{} {
   175  	return func() string {
   176  		return version.FormattedVersion()
   177  	}
   178  }
   179  
   180  func funcGenConsul(ctx *Context) interface{} {
   181  	return func(k string) (string, error) {
   182  		if !ctx.EnableEnv {
   183  			// The error message doesn't have to be that detailed since
   184  			// semantic checks should catch this.
   185  			return "", errors.New("consul_key is not allowed here")
   186  		}
   187  
   188  		consulConfig := consulapi.DefaultConfig()
   189  		client, err := consulapi.NewClient(consulConfig)
   190  		if err != nil {
   191  			return "", fmt.Errorf("error getting consul client: %s", err)
   192  		}
   193  
   194  		q := &consulapi.QueryOptions{}
   195  		kv, _, err := client.KV().Get(k, q)
   196  		if err != nil {
   197  			return "", fmt.Errorf("error reading consul key: %s", err)
   198  		}
   199  		if kv == nil {
   200  			return "", fmt.Errorf("key does not exist at the given path: %s", k)
   201  		}
   202  
   203  		value := string(kv.Value)
   204  		if value == "" {
   205  			return "", fmt.Errorf("value is empty at path %s", k)
   206  		}
   207  
   208  		return value, nil
   209  	}
   210  }
   211  
   212  func funcGenVault(ctx *Context) interface{} {
   213  	return func(path string, key string) (string, error) {
   214  		// Only allow interpolation from Vault when env vars are being read.
   215  		if !ctx.EnableEnv {
   216  			// The error message doesn't have to be that detailed since
   217  			// semantic checks should catch this.
   218  			return "", errors.New("Vault vars are only allowed in the variables section")
   219  		}
   220  		if token := os.Getenv("VAULT_TOKEN"); token == "" {
   221  			return "", errors.New("Must set VAULT_TOKEN env var in order to " +
   222  				"use vault template function")
   223  		}
   224  		// const EnvVaultAddress = "VAULT_ADDR"
   225  		// const EnvVaultToken = "VAULT_TOKEN"
   226  		vaultConfig := vaultapi.DefaultConfig()
   227  		cli, err := vaultapi.NewClient(vaultConfig)
   228  		if err != nil {
   229  			return "", errors.New(fmt.Sprintf("Error getting Vault client: %s", err))
   230  		}
   231  		secret, err := cli.Logical().Read(path)
   232  		if err != nil {
   233  			return "", errors.New(fmt.Sprintf("Error reading vault secret: %s", err))
   234  		}
   235  		if secret == nil {
   236  			return "", errors.New(fmt.Sprintf("Vault Secret does not exist at the given path."))
   237  		}
   238  
   239  		data, ok := secret.Data["data"]
   240  		if !ok {
   241  			// maybe ths is v1, not v2 kv store
   242  			value, ok := secret.Data[key]
   243  			if ok {
   244  				return value.(string), nil
   245  			}
   246  
   247  			// neither v1 nor v2 proudced a valid value
   248  			return "", errors.New(fmt.Sprintf("Vault data was empty at the "+
   249  				"given path. Warnings: %s", strings.Join(secret.Warnings, "; ")))
   250  		}
   251  
   252  		value := data.(map[string]interface{})[key].(string)
   253  		return value, nil
   254  	}
   255  }