2015-05-02 01:59:16 +02:00
package template
import (
"crypto/sha256"
"encoding/hex"
"fmt"
2015-05-21 22:01:28 +02:00
"os"
"path/filepath"
2016-07-11 22:39:34 +02:00
"strings"
2015-05-02 01:59:16 +02:00
2016-01-31 08:45:15 +01:00
"github.com/hashicorp/hil"
"github.com/hashicorp/hil/ast"
2015-05-02 01:59:16 +02:00
"github.com/hashicorp/terraform/config"
2015-11-13 18:07:02 +01:00
"github.com/hashicorp/terraform/helper/pathorcontents"
2015-05-02 01:59:16 +02:00
"github.com/hashicorp/terraform/helper/schema"
)
2016-05-22 00:09:55 +02:00
func dataSourceFile ( ) * schema . Resource {
2015-05-02 01:59:16 +02:00
return & schema . Resource {
2016-05-22 00:09:55 +02:00
Read : dataSourceFileRead ,
2015-05-02 01:59:16 +02:00
Schema : map [ string ] * schema . Schema {
2015-11-13 18:07:02 +01:00
"template" : & schema . Schema {
Type : schema . TypeString ,
Optional : true ,
Description : "Contents of the template" ,
ConflictsWith : [ ] string { "filename" } ,
2016-03-10 19:34:56 +01:00
ValidateFunc : validateTemplateAttribute ,
2015-11-13 18:07:02 +01:00
} ,
2015-05-02 01:59:16 +02:00
"filename" : & schema . Schema {
Type : schema . TypeString ,
2015-11-13 18:07:02 +01:00
Optional : true ,
2015-05-02 01:59:16 +02:00
Description : "file to read template from" ,
2015-05-21 22:01:28 +02:00
// Make a "best effort" attempt to relativize the file path.
StateFunc : func ( v interface { } ) string {
2015-11-13 18:07:02 +01:00
if v == nil || v . ( string ) == "" {
return ""
}
2015-05-21 22:01:28 +02:00
pwd , err := os . Getwd ( )
if err != nil {
return v . ( string )
}
rel , err := filepath . Rel ( pwd , v . ( string ) )
if err != nil {
return v . ( string )
}
return rel
} ,
2015-11-13 18:07:02 +01:00
Deprecated : "Use the 'template' attribute instead." ,
ConflictsWith : [ ] string { "template" } ,
2015-05-02 01:59:16 +02:00
} ,
"vars" : & schema . Schema {
2016-07-11 22:39:34 +02:00
Type : schema . TypeMap ,
Optional : true ,
Default : make ( map [ string ] interface { } ) ,
Description : "variables to substitute" ,
ValidateFunc : validateVarsAttribute ,
2015-05-02 01:59:16 +02:00
} ,
"rendered" : & schema . Schema {
Type : schema . TypeString ,
Computed : true ,
Description : "rendered template" ,
} ,
} ,
}
}
2016-05-22 00:09:55 +02:00
func dataSourceFileRead ( d * schema . ResourceData , meta interface { } ) error {
2015-10-06 01:08:33 +02:00
rendered , err := renderFile ( d )
2015-05-09 00:46:37 +02:00
if err != nil {
return err
}
d . Set ( "rendered" , rendered )
d . SetId ( hash ( rendered ) )
return nil
}
2015-06-17 20:58:01 +02:00
type templateRenderError error
2015-10-06 01:08:33 +02:00
func renderFile ( d * schema . ResourceData ) ( string , error ) {
2015-11-13 18:07:02 +01:00
template := d . Get ( "template" ) . ( string )
2015-05-02 01:59:16 +02:00
filename := d . Get ( "filename" ) . ( string )
vars := d . Get ( "vars" ) . ( map [ string ] interface { } )
2015-11-13 18:07:02 +01:00
if template == "" && filename != "" {
template = filename
2015-05-04 19:26:17 +02:00
}
2015-11-13 18:07:02 +01:00
contents , _ , err := pathorcontents . Read ( template )
2015-05-02 01:59:16 +02:00
if err != nil {
2015-05-09 00:46:37 +02:00
return "" , err
2015-05-02 01:59:16 +02:00
}
2015-11-13 18:07:02 +01:00
rendered , err := execute ( contents , vars )
2015-05-02 01:59:16 +02:00
if err != nil {
2015-06-17 20:58:01 +02:00
return "" , templateRenderError (
fmt . Errorf ( "failed to render %v: %v" , filename , err ) ,
)
2015-05-02 01:59:16 +02:00
}
2015-05-09 00:46:37 +02:00
return rendered , nil
2015-05-02 01:59:16 +02:00
}
// execute parses and executes a template using vars.
func execute ( s string , vars map [ string ] interface { } ) ( string , error ) {
2016-01-31 08:45:15 +01:00
root , err := hil . Parse ( s )
2015-05-02 01:59:16 +02:00
if err != nil {
return "" , err
}
varmap := make ( map [ string ] ast . Variable )
for k , v := range vars {
// As far as I can tell, v is always a string.
// If it's not, tell the user gracefully.
s , ok := v . ( string )
if ! ok {
return "" , fmt . Errorf ( "unexpected type for variable %q: %T" , k , v )
}
varmap [ k ] = ast . Variable {
2016-09-02 22:20:03 +02:00
Value : s ,
Type : ast . TypeString ,
2015-05-02 01:59:16 +02:00
}
}
2016-01-31 08:45:15 +01:00
cfg := hil . EvalConfig {
2015-05-02 01:59:16 +02:00
GlobalScope : & ast . BasicScope {
VarMap : varmap ,
2016-01-15 22:28:47 +01:00
FuncMap : config . Funcs ( ) ,
2015-05-02 01:59:16 +02:00
} ,
}
2016-04-09 03:23:36 +02:00
result , err := hil . Eval ( root , & cfg )
2015-05-02 01:59:16 +02:00
if err != nil {
return "" , err
}
2016-04-09 03:23:36 +02:00
if result . Type != hil . TypeString {
return "" , fmt . Errorf ( "unexpected output hil.Type: %v" , result . Type )
2015-05-02 01:59:16 +02:00
}
2016-04-09 03:23:36 +02:00
return result . Value . ( string ) , nil
2015-05-02 01:59:16 +02:00
}
func hash ( s string ) string {
sha := sha256 . Sum256 ( [ ] byte ( s ) )
2015-05-09 00:46:37 +02:00
return hex . EncodeToString ( sha [ : ] )
2015-05-02 01:59:16 +02:00
}
2016-03-10 19:34:56 +01:00
func validateTemplateAttribute ( v interface { } , key string ) ( ws [ ] string , es [ ] error ) {
_ , wasPath , err := pathorcontents . Read ( v . ( string ) )
if err != nil {
es = append ( es , err )
return
}
if wasPath {
ws = append ( ws , fmt . Sprintf ( "%s: looks like you specified a path instead of file contents. Use `file()` to load this path. Specifying a path directly is deprecated and will be removed in a future version." , key ) )
}
return
}
2016-07-11 22:39:34 +02:00
func validateVarsAttribute ( v interface { } , key string ) ( ws [ ] string , es [ ] error ) {
// vars can only be primitives right now
var badVars [ ] string
for k , v := range v . ( map [ string ] interface { } ) {
switch v . ( type ) {
case [ ] interface { } :
badVars = append ( badVars , fmt . Sprintf ( "%s (list)" , k ) )
case map [ string ] interface { } :
badVars = append ( badVars , fmt . Sprintf ( "%s (map)" , k ) )
}
}
if len ( badVars ) > 0 {
es = append ( es , fmt . Errorf (
"%s: cannot contain non-primitives; bad keys: %s" ,
key , strings . Join ( badVars , ", " ) ) )
}
return
}