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 }