github.com/upcmd/up@v0.8.1-0.20230108151705-ad8b797bf04f/biz/impl/template.go (about)

     1  // Ultimate Provisioner: UP cmd
     2  // Copyright (c) 2019 Stephen Cheng and contributors
     3  
     4  /* This Source Code Form is subject to the terms of the Mozilla Public
     5   * License, v. 2.0. If a copy of the MPL was not distributed with this
     6   * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
     7  
     8  package impl
     9  
    10  import (
    11  	"bytes"
    12  	"github.com/Masterminds/sprig/v3"
    13  	"github.com/upcmd/up/model/core"
    14  	u "github.com/upcmd/up/utils"
    15  	"io/ioutil"
    16  	"os"
    17  	"path/filepath"
    18  	"runtime"
    19  	"strings"
    20  	"time"
    21  
    22  	"github.com/leekchan/gtf"
    23  	"text/template"
    24  )
    25  
    26  var (
    27  	templateFuncs template.FuncMap
    28  	taskFuncs     template.FuncMap
    29  )
    30  
    31  func safeReg(varname string, object interface{}) string {
    32  	//if this is in dvar processing:
    33  	//need to one way sync the var to the returning var
    34  	TaskRuntime().ExecbaseVars.Put(varname, object)
    35  
    36  	//remove this as it will cause dirty data due to dvar processing
    37  	//StepRuntime().ContextVars.Put(varname, object)
    38  	//instead we do a callback to save it to dvar processing scope
    39  	if !StepRuntime().DataSyncInDvarExpand(varname, object) {
    40  		StepRuntime().ContextVars.Put(varname, object)
    41  	}
    42  
    43  	return core.ObjToYaml(object)
    44  }
    45  
    46  func FuncMapInit() {
    47  	taskFuncs = template.FuncMap{
    48  		"OS":   func() string { return runtime.GOOS },
    49  		"ARCH": func() string { return runtime.GOARCH },
    50  		"catLines": func(s string) string {
    51  			s = strings.Replace(s, "\r\n", " ", -1)
    52  			return strings.Replace(s, "\n", " ", -1)
    53  		},
    54  		"splitLines": func(s string) []string {
    55  			s = strings.Replace(s, "\r\n", "\n", -1)
    56  			return strings.Split(s, "\n")
    57  		},
    58  		"fromSlash": func(path string) string {
    59  			return filepath.FromSlash(path)
    60  		},
    61  		"toSlash": func(path string) string {
    62  			return filepath.ToSlash(path)
    63  		},
    64  		//--------------------------------------------------------
    65  		"now": func() string {
    66  			t := time.Now()
    67  			return t.Format("2006-01-02T15:04:05+11:00")
    68  		},
    69  		"printObj": func(obj interface{}) string {
    70  			u.Ppmsg(obj)
    71  			return u.Sppmsg(obj)
    72  		},
    73  		"objToYml": func(obj interface{}) string {
    74  			yml := core.ObjToYaml(obj)
    75  			u.PpmsgvvvvvHigh("objToYml", yml)
    76  			return yml
    77  		},
    78  		"ymlToObj": func(yml string) interface{} {
    79  			obj := core.YamlToObj(yml)
    80  			u.PpmsgvvvvvHigh("ymlToObj", obj)
    81  			return obj
    82  		},
    83  		//use the encryption key stored in vault instead of plain value in general cache
    84  		"encrypteAesWithVault": func(enckeyName string, plain string) string {
    85  			var encryptionkey string
    86  			if enckeyName != "" {
    87  				//use vault as first priority
    88  				opt := GetVault().Get(enckeyName)
    89  				if opt == nil {
    90  					opt = (StepRuntime().ContextVars).Get(enckeyName)
    91  				}
    92  				encryptionkey = opt.(string)
    93  			}
    94  
    95  			encrypted := Render(u.Spf(`{{encryptAES "%s" "%s"}}`, encryptionkey, plain), "")
    96  
    97  			return encrypted
    98  		},
    99  		//retrieve value from vault
   100  		"fromVault": func(varname string) string {
   101  			var val string = "NotExistInVault"
   102  			opt := GetVault().Get(varname)
   103  			if opt != nil {
   104  				val = opt.(string)
   105  			}
   106  
   107  			return val
   108  		},
   109  		//regObj will keep the golang object intact and register it to cache
   110  		//same effect of: '{{ func_return_a_obj arg | objToYml|ymlToObj|reg "instances" }}'
   111  		"regObj": func(varname string, obj interface{}) interface{} {
   112  			yml := core.ObjToYaml(obj)
   113  			regObj := core.YamlToObj(yml)
   114  			safeReg(varname, regObj)
   115  			u.PpmsgvvvvvHigh("ymlToObj", regObj)
   116  			return obj
   117  		},
   118  		"loopRange": func(start, end int64, regname string) string {
   119  			var looplist []int64 = []int64{}
   120  			for i := start; i <= end; i++ {
   121  				looplist = append(looplist, i)
   122  			}
   123  			safeReg(regname, looplist)
   124  			return regname
   125  		},
   126  		//reg do not return any value, so do not expect the dvar value will be something other than empty
   127  		"reg": safeReg,
   128  		"deReg": func(varname string) string {
   129  			TaskRuntime().ExecbaseVars.Delete(varname)
   130  			return ""
   131  		},
   132  		//keep envExport only in template func as to be carried to any type of func implementation
   133  		"envExport": func(expType, fileToSave string) string {
   134  			var expStr string
   135  			switch expType {
   136  			case "exec_base_env_vars_configured":
   137  				expStr = TaskerRuntime().Tasker.reportContextualEnvVars(TaskRuntime().ExecbaseVars)
   138  			case "exec_env_vars_configured":
   139  				expStr = TaskerRuntime().Tasker.reportContextualEnvVars(StepRuntime().ContextVars)
   140  			}
   141  
   142  			if fileToSave != "" {
   143  				ioutil.WriteFile(fileToSave, []byte(expStr), 0644)
   144  			}
   145  
   146  			return expStr
   147  		},
   148  		"pathExisted": func(path string) bool {
   149  			pathtstr := u.Spf("{{.%s}}", path)
   150  			return ElementValid(pathtstr, StepRuntime().ContextVars)
   151  		},
   152  		"fileContent": func(filepath string) string {
   153  			if _, err := os.Stat(filepath); os.IsNotExist(err) {
   154  				u.LogWarn("fileContent readFile", u.Spf("please fix file path: %s", filepath))
   155  				return ""
   156  			} else {
   157  				content, err := ioutil.ReadFile(filepath)
   158  				if err != nil {
   159  					u.LogWarn("fileContent readFile", u.Spf("please fix file read error, path: %s", filepath))
   160  				}
   161  				return string(content)
   162  			}
   163  		},
   164  		"validateMandatoryFailIfNone": func(varname, varvalue string) string {
   165  			if varvalue == "" {
   166  				u.InvalidAndPanic("validateMandatoryFailIfNone", u.Spf("Required var:(%s) must not be empty, please fix it", varname))
   167  			}
   168  			return varvalue
   169  		},
   170  	}
   171  
   172  	templateFuncs = sprig.TxtFuncMap()
   173  
   174  	for k, v := range gtf.GtfTextFuncMap {
   175  		if _, ok := templateFuncs[k]; !ok {
   176  			//does not exit, add it to funcmap now
   177  			templateFuncs[k] = v
   178  		}
   179  	}
   180  
   181  	for k, v := range taskFuncs {
   182  		templateFuncs[k] = v
   183  	}
   184  }
   185  
   186  func ToJson(str string) string {
   187  	return Render("{{toJson .}}", str)
   188  }
   189  
   190  func Render(tstr string, obj interface{}) string {
   191  	tname := "."
   192  	t, err := template.New(tname).Funcs(templateFuncs).Parse(tstr)
   193  
   194  	u.LogErrorAndPanic("template creating", err, u.ContentWithLineNumber(tstr))
   195  
   196  	var result bytes.Buffer
   197  	err = t.Execute(&result, obj)
   198  	u.LogErrorAndContinue("template rendering", err, u.ContentWithLineNumber(tstr))
   199  
   200  	val := result.String()
   201  	if "<no value>" == val {
   202  		val = NONE_VALUE
   203  	}
   204  
   205  	return val
   206  }
   207  
   208  func ElementValid(path string, obj interface{}) bool {
   209  
   210  	t, err := template.New("validator").Funcs(templateFuncs).Parse(path)
   211  
   212  	u.LogErrorAndContinue("template element validating", err, "Please fix the template issue and try again")
   213  
   214  	var result bytes.Buffer
   215  	err = t.Execute(&result, obj)
   216  	u.LogErrorAndContinue("element validating problem", err, u.ContentWithLineNumber(path))
   217  
   218  	if err != nil {
   219  		return false
   220  	} else if result.String() == "<no value>" {
   221  		return false
   222  	} else {
   223  		return true
   224  	}
   225  }
   226  
   227  func ListAllFuncs() {
   228  
   229  	var builtins = map[string]string{
   230  		"and":      "and",
   231  		"call":     "call",
   232  		"html":     "HTMLEscaper",
   233  		"index":    "index",
   234  		"js":       "JSEscaper",
   235  		"len":      "length",
   236  		"not":      "not",
   237  		"or":       "or",
   238  		"print":    "fmt.Sprint",
   239  		"printf":   "fmt.Sprintf",
   240  		"println":  "fmt.Sprintln",
   241  		"urlquery": "URLQueryEscaper",
   242  
   243  		// Comparisons
   244  		"eq": "eq", // ==
   245  		"ge": "ge", // >=
   246  		"gt": "gt", // >
   247  		"le": "le", // <=
   248  		"lt": "lt", // <
   249  		"ne": "ne", // !=
   250  	}
   251  
   252  	for k, v := range builtins {
   253  		u.Pf("%30s : %#v\n", k, v)
   254  	}
   255  
   256  	for k, v := range templateFuncs {
   257  		u.Pf("%30s : %#v\n", k, v)
   258  	}
   259  
   260  }
   261  func ListUpcmdFuncs() {
   262  	for k, v := range taskFuncs {
   263  		u.Pf("%30s : %#v\n", k, v)
   264  	}
   265  
   266  }