github.com/upcmd/up@v0.8.1-0.20230108151705-ad8b797bf04f/biz/impl/dvar.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  	"bufio"
    12  	"github.com/howeyc/gopass"
    13  	"github.com/mohae/deepcopy"
    14  	"github.com/upcmd/up/model/core"
    15  	u "github.com/upcmd/up/utils"
    16  	"gopkg.in/yaml.v2"
    17  	"io/ioutil"
    18  	"os"
    19  	"path"
    20  	"strings"
    21  )
    22  
    23  type Dvars []Dvar
    24  type EnvVars []EnvVar
    25  
    26  type Dvar struct {
    27  	Name         string
    28  	Value        string
    29  	Desc         string
    30  	Expand       int
    31  	Flags        []string //supported: vvvv, toObj,envVar,
    32  	Rendered     string
    33  	Secure       *u.SecureSetting
    34  	Ref          string
    35  	RefDir       string
    36  	DataKey      string
    37  	DataPath     string
    38  	DataTemplate string
    39  }
    40  
    41  type EnvVar struct {
    42  	Name  string
    43  	Value string
    44  }
    45  
    46  func (dvars *Dvars) ValidateAndLoading(contextVars *core.Cache) {
    47  	var identified bool
    48  	for idx, dvar := range *dvars {
    49  
    50  		if strings.Contains(dvar.Name, "-") {
    51  			identified = true
    52  			u.InvalidAndPanic("validating dvar name", "dvar name can not contain '-', please use '_' instead")
    53  		}
    54  		if u.CharIsNum(dvar.Name[0:1]) != -1 {
    55  			identified = true
    56  			u.InvalidAndPanic("validating dvar name", "dvar name can not start with number")
    57  		}
    58  
    59  		if dvar.Ref != "" && dvar.Value != "" {
    60  			u.InvalidAndPanic("validating dvar ref and value", "ref and value can not both exist at the same time")
    61  		}
    62  
    63  		refdir := ConfigRuntime().RefDir
    64  		if dvar.Ref != "" {
    65  			if dvar.RefDir != "" {
    66  				rawdir := dvar.RefDir
    67  				refdir = Render(rawdir, contextVars)
    68  			}
    69  
    70  			rawref := dvar.Ref
    71  			ref := Render(rawref, contextVars)
    72  
    73  			data, err := ioutil.ReadFile(path.Join(refdir, ref))
    74  			u.LogErrorAndPanic("load dvar value from ref file", err, "please fix file loading problem")
    75  			(*dvars)[idx].Value = string(data)
    76  		}
    77  	}
    78  
    79  	if identified {
    80  		u.InvalidAndPanic("dvar validate", "the dvar name identified above should be fixed before continue")
    81  	}
    82  
    83  }
    84  
    85  //return false means not in context of dvar scope
    86  type TransientSyncFunc func(key string, val interface{}) bool
    87  
    88  //given a dvars with the vars context, it expands with rendered result
    89  func (dvars *Dvars) Expand(mark string, contextVars *core.Cache) *core.Cache {
    90  
    91  	dvars.ValidateAndLoading(contextVars)
    92  	var expandedVars *core.Cache = core.NewCache()
    93  	var secretVarList []string = []string{}
    94  
    95  	if *contextVars == nil {
    96  		contextVars = core.NewCache()
    97  	}
    98  
    99  	var tmpVars core.Cache = deepcopy.Copy(*contextVars).(core.Cache)
   100  	var tmpDvars Dvars
   101  	tmpDvars = deepcopy.Copy(*dvars).(Dvars)
   102  
   103  	//this is to ensure data consistency of the one way return and overriding from dvar expand to step vars(and context vars)
   104  	transientSync := func(key string, val interface{}) bool {
   105  		//ensure all template reg change is carried over
   106  		tmpVars.Put(key, val)
   107  		expandedVars.Put(key, val)
   108  		return true
   109  	}
   110  	transientSyncVoid := func(key string, val interface{}) bool { return false }
   111  
   112  	stepRuntime := StepRuntime()
   113  	if stepRuntime != nil {
   114  		stepRuntime.DataSyncInDvarExpand = transientSync
   115  	}
   116  
   117  	var datasource interface{}
   118  
   119  	for idx, dvar := range tmpDvars {
   120  		dvarRaw := tmpDvars[idx].Value
   121  		if dvar.Expand == 0 {
   122  			tmpDvars[idx].Expand = 1
   123  		}
   124  		for i := 0; i < tmpDvars[idx].Expand; i++ {
   125  			tval := tmpDvars[idx].Value
   126  			tmpDvars[idx].Value = Render(tval, tmpVars)
   127  		}
   128  
   129  		var rval string
   130  
   131  		if dvar.DataKey != "" && dvar.DataPath != "" && dvar.DataTemplate != "" {
   132  			u.InvalidAndPanic("validating datasource", "datakey, datapath and datatemplate can not coexist at the same time")
   133  		}
   134  
   135  		//the rendering using the datakey is the post rendering process
   136  		if dvar.DataKey != "" {
   137  			datakey := Render(dvar.DataKey, tmpVars)
   138  			datasource = tmpVars.Get(datakey)
   139  			rval = Render(dvarRaw, datasource)
   140  		} else {
   141  			rval = tmpDvars[idx].Value
   142  		}
   143  
   144  		if dvar.DataPath != "" {
   145  			datapath := Render(dvar.DataPath, tmpVars)
   146  			datasource = core.GetSubObjectFromCache(&tmpVars, datapath, false, ConfigRuntime().Verbose)
   147  			rval = Render(dvarRaw, datasource)
   148  		} else {
   149  			rval = tmpDvars[idx].Value
   150  		}
   151  
   152  		if dvar.DataTemplate != "" {
   153  			datatemplate := Render(dvar.DataTemplate, tmpVars)
   154  			datasource = core.YamlToObj(datatemplate)
   155  			rval = Render(dvarRaw, datasource)
   156  		} else {
   157  			rval = tmpDvars[idx].Value
   158  		}
   159  
   160  		if rval == "" {
   161  			rval = NONE_VALUE
   162  		}
   163  
   164  		tmpVars.Put(dvar.Name, rval)
   165  		(*dvars)[idx].Rendered = rval
   166  
   167  		if dvar.Name != "void" {
   168  			expandedVars.Put(dvar.Name, rval)
   169  		}
   170  
   171  		func() {
   172  			dvar := (*dvars)[idx]
   173  			mergeTarget := &tmpVars
   174  			vlevels := []string{"v", "vv", "vvv", "vvvv", "vvvvv", "vvvvv"}
   175  			var dvarObjName string
   176  			var dvarNameKept bool
   177  			var objConverted = new(interface{})
   178  			if dvar.Flags != nil && len(dvar.Flags) != 0 {
   179  				if u.Contains(dvar.Flags, "prompt") {
   180  					u.Pprompt(dvar.Name, func() string {
   181  						if dvar.Desc != "" {
   182  							return Render(dvar.Desc, tmpVars)
   183  						} else {
   184  							return u.Spf("This will be saved as %s's value", dvar.Name)
   185  						}
   186  					}())
   187  
   188  					var saneValue string
   189  					if u.Contains(dvar.Flags, "masked") {
   190  						maskedPassword, _ := gopass.GetPasswdMasked()
   191  						saneValue = string(maskedPassword)
   192  					} else {
   193  
   194  						reader := bufio.NewReader(os.Stdin)
   195  						dvarInputValue, _ := reader.ReadString('\n')
   196  						saneValue = u.RemoveCr(dvarInputValue)
   197  					}
   198  					pval := func() (v string) {
   199  						if saneValue != "" {
   200  							v = saneValue
   201  						} else if saneValue == "" && dvar.Value == "" {
   202  							v = NONE_VALUE
   203  						} else {
   204  							v = dvar.Value
   205  						}
   206  						return
   207  					}()
   208  
   209  					(*mergeTarget).Put(dvar.Name, pval)
   210  					(*expandedVars).Put(dvar.Name, pval)
   211  					dvar.Rendered = saneValue
   212  				}
   213  
   214  				if u.Contains(dvar.Flags, "secret") {
   215  					GetVault().Put(dvar.Name, dvar.Rendered)
   216  					secretVarList = append(secretVarList, dvar.Name)
   217  				}
   218  
   219  				for _, vlevel := range vlevels {
   220  					if u.Contains(dvar.Flags, vlevel) {
   221  						u.PpmsgHintHighPermitted("v", "dvar> "+dvar.Name, dvar.Rendered)
   222  						u.Pln("-")
   223  						u.PlnInfo(dvar.Rendered)
   224  					}
   225  				}
   226  
   227  				if u.Contains(dvar.Flags, "toObj") {
   228  					rawyml := dvar.Rendered
   229  
   230  					err := yaml.Unmarshal([]byte(rawyml), objConverted)
   231  					u.LogErrorAndPanic("dvar conversion to object:", err, u.ContentWithLineNumber(rawyml))
   232  
   233  					dvarObjName = func() (dvarname string) {
   234  						if u.Contains(dvar.Flags, "keepName") {
   235  							dvarname = dvar.Name
   236  							dvarNameKept = true
   237  						} else {
   238  							dvarname = u.Spf("%s_%s", dvar.Name, "object")
   239  						}
   240  						return
   241  					}()
   242  
   243  					if dvar.Name != "void" {
   244  						(*mergeTarget).Put(dvarObjName, *objConverted)
   245  						(*expandedVars).Put(dvarObjName, *objConverted)
   246  					}
   247  					if TaskerRuntime().Tasker.TaskStack.GetLen() > 0 {
   248  						if u.Contains(dvar.Flags, "reg") {
   249  							if dvar.Name != "void" {
   250  								TaskRuntime().ExecbaseVars.Put(dvarObjName, *objConverted)
   251  							} else {
   252  								u.LogWarn("?reg a void", "you can't register a object with void name, use a proper name instead or split to multiple steps")
   253  							}
   254  						}
   255  					}
   256  
   257  					for _, vlevel := range vlevels {
   258  						if u.Contains(dvar.Flags, vlevel) {
   259  							u.PpmsgHintHighPermitted("v", "dvar[object]> "+dvarObjName, *objConverted)
   260  						}
   261  					}
   262  				}
   263  
   264  				if TaskerRuntime().Tasker.TaskStack.GetLen() > 0 {
   265  					if u.Contains(dvar.Flags, "reg") {
   266  						if dvar.Name != "void" {
   267  							//assume keepname indicates toObj: this is to ensure the object is not overridden by the dvar string value
   268  							if !dvarNameKept {
   269  								TaskRuntime().ExecbaseVars.Put(dvar.Name, dvar.Rendered)
   270  							}
   271  						}
   272  					}
   273  				}
   274  
   275  				if u.Contains(dvar.Flags, "secure") {
   276  					DecryptAndRegister(ConfigRuntime().Secure, &dvar, mergeTarget, expandedVars)
   277  				}
   278  
   279  				if u.Contains(dvar.Flags, "envVar") {
   280  					var secureValue string
   281  					envvarName := u.Spf("%s_%s", "envVar", dvar.Name)
   282  					if u.Contains(dvar.Flags, "secure") {
   283  						secureValue = Decrypt(ConfigRuntime().Secure, &dvar, mergeTarget)
   284  						(*mergeTarget).Put(envvarName, secureValue)
   285  						(*expandedVars).Put(envvarName, secureValue)
   286  						os.Setenv(dvar.Name, secureValue)
   287  					} else {
   288  						(*mergeTarget).Put(envvarName, dvar.Rendered)
   289  						(*expandedVars).Put(envvarName, dvar.Rendered)
   290  						os.Setenv(dvar.Name, dvar.Rendered)
   291  					}
   292  				}
   293  
   294  				if u.Contains(dvar.Flags, "taskScope") {
   295  					if !dvarNameKept {
   296  						TaskRuntime().TaskVars.Put(dvar.Name, dvar.Rendered)
   297  					}
   298  					if dvarObjName != "" {
   299  						//keepname only applies to toObj case
   300  						if dvarNameKept {
   301  							TaskRuntime().TaskVars.Put(dvar.Name, *objConverted)
   302  						} else {
   303  							TaskRuntime().TaskVars.Put(dvarObjName, *objConverted)
   304  						}
   305  					}
   306  				}
   307  			}
   308  
   309  			if dvar.Secure != nil {
   310  				DecryptAndRegister(ConfigRuntime().Secure, &dvar, mergeTarget, expandedVars)
   311  			}
   312  
   313  		}()
   314  
   315  	}
   316  
   317  	u.Pfvvvvv("[%s] dvar expanded result:\n%s\n", mark, u.Sppmsg(*expandedVars))
   318  	if stepRuntime != nil {
   319  		stepRuntime.DataSyncInDvarExpand = transientSyncVoid
   320  	}
   321  
   322  	for _, x := range secretVarList {
   323  		expandedVars.Delete(x)
   324  	}
   325  
   326  	return expandedVars
   327  }