go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/config/vars/vars.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package vars implements a registry of ${var} placeholders. 16 // 17 // They can be used in config validation rule patterns and config set names 18 // and paths in place of a not-yet-known values. 19 package vars 20 21 import ( 22 "context" 23 "fmt" 24 "regexp" 25 "sync" 26 27 "go.chromium.org/luci/common/errors" 28 ) 29 30 // Vars is the default set of placeholder vars used by the process. 31 // 32 // Individual packages may register vars here during init() time. 33 var Vars VarSet 34 35 // VarSet holds a registry ${var} -> callback that extracts it from a context. 36 // 37 // It is used by the config client to render config validation patterns like 38 // "services/${appid}". By using placeholder vars it is possible to register 39 // validation rules during process startup time before values of all vars 40 // are actually known. 41 type VarSet struct { 42 l sync.RWMutex 43 v map[string]func(context.Context) (string, error) 44 } 45 46 // Register registers a variable that can be used in templates as `${name}`. 47 // 48 // Such placeholder variable is rendered into an actual value via the given 49 // callback in RenderTemplate. 50 // 51 // The primary use case for this mechanism is to allow to register config 52 // validation rule patterns that depend on not-yet known values during init() 53 // time. 54 // 55 // Panics if such variable is already registered. 56 func (vs *VarSet) Register(name string, value func(context.Context) (string, error)) { 57 vs.l.Lock() 58 defer vs.l.Unlock() 59 if vs.v == nil { 60 vs.v = make(map[string]func(context.Context) (string, error), 1) 61 } 62 if vs.v[name] != nil { 63 panic(fmt.Sprintf("variable %q is already registered", name)) 64 } 65 vs.v[name] = value 66 } 67 68 var placeholderRe = regexp.MustCompile(`\${[^}]*}`) 69 70 // RenderTemplate replaces all `${var}` references in the string with their 71 // values by calling registered callbacks. 72 func (vs *VarSet) RenderTemplate(ctx context.Context, templ string) (string, error) { 73 vs.l.RLock() 74 defer vs.l.RUnlock() 75 76 var errs errors.MultiError 77 78 out := placeholderRe.ReplaceAllStringFunc(templ, func(match string) string { 79 name := match[2 : len(match)-1] // strip ${...} 80 81 var val string 82 var err error 83 84 if cb := vs.v[name]; cb != nil { 85 val, err = cb(ctx) 86 } else { 87 err = fmt.Errorf("no placeholder named %q is registered", name) 88 } 89 90 if err != nil { 91 errs = append(errs, err) 92 } 93 return val 94 }) 95 96 if len(errs) != 0 { 97 return "", errs 98 } 99 return out, nil 100 }