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 }