| package config_vars |
| |
| /* |
| Package config_vars provides helpers for configuration templates. |
| */ |
| |
| import ( |
| "bytes" |
| "context" |
| "encoding/json" |
| "html/template" |
| "strings" |
| "sync" |
| |
| "go.skia.org/infra/go/chrome_branch" |
| "go.skia.org/infra/go/skerr" |
| ) |
| |
| var ( |
| // These templates should not be used. |
| bannedTmpls = []string{ |
| "Chromium.Master.Number", |
| } |
| ) |
| |
| // Vars represents current values of variables which may be used in templates. |
| type Vars struct { |
| Branches *Branches `json:"branches"` |
| } |
| |
| // Validate returns an error if Vars is not valid. |
| func (v *Vars) Validate() error { |
| if v.Branches == nil { |
| return skerr.Fmt("Branches is required.") |
| } |
| if err := v.Branches.Validate(); err != nil { |
| return skerr.Wrap(err) |
| } |
| return nil |
| } |
| |
| // DummyVars returns an instance of Vars with arbitrary contents which may be |
| // used during validation and testing. |
| func DummyVars() *Vars { |
| return &Vars{ |
| Branches: &Branches{ |
| Chromium: &chrome_branch.Branches{ |
| Main: &chrome_branch.Branch{ |
| Milestone: 82, |
| Number: 0, |
| Ref: chrome_branch.RefMain, |
| }, |
| Beta: &chrome_branch.Branch{ |
| Milestone: 81, |
| Number: 4044, |
| Ref: chrome_branch.ReleaseBranchRef(4044), |
| }, |
| Stable: &chrome_branch.Branch{ |
| Milestone: 80, |
| Number: 3987, |
| Ref: chrome_branch.ReleaseBranchRef(4044), |
| }, |
| }, |
| }, |
| } |
| } |
| |
| // Branches represents named branches in git repositories. |
| type Branches struct { |
| // Chromium release branches. |
| Chromium *chrome_branch.Branches `json:"chromium"` |
| } |
| |
| // Validate returns an error if Branches is not valid. |
| func (b *Branches) Validate() error { |
| if b.Chromium == nil { |
| return skerr.Fmt("Chromium is required.") |
| } |
| if err := b.Chromium.Validate(); err != nil { |
| return skerr.Wrap(err) |
| } |
| return nil |
| } |
| |
| // Template is a text template which uses the contents of Vars. It is |
| // technically thread-safe, but users should take care to cache the value of |
| // Template.String() rather than calling it repeatedly, unless Update() is |
| // only ever called within the same thread as String(). |
| type Template struct { |
| mtx sync.RWMutex |
| raw string |
| tmpl *template.Template |
| value string |
| } |
| |
| // NewTemplate returns a Template instance. |
| func NewTemplate(rawTmpl string) (*Template, error) { |
| rv := &Template{} |
| if err := rv.init(rawTmpl); err != nil { |
| return nil, skerr.Wrap(err) |
| } |
| return rv, nil |
| } |
| |
| // init initializes the template. |
| func (t *Template) init(rawTmpl string) error { |
| for _, banned := range bannedTmpls { |
| if strings.Contains(rawTmpl, banned) { |
| return skerr.Fmt("Templates should not use %q", banned) |
| } |
| } |
| tmpl, err := template.New("").Parse(rawTmpl) |
| if err != nil { |
| return skerr.Wrap(err) |
| } |
| tmpl.Option("missingkey=error") |
| t.raw = rawTmpl |
| t.tmpl = tmpl |
| return skerr.Wrap(t.validate()) |
| } |
| |
| // UnmarshalJSON implements json.Unmarshaler. |
| func (t *Template) UnmarshalJSON(b []byte) error { |
| t.mtx.Lock() |
| defer t.mtx.Unlock() |
| var raw string |
| if err := json.Unmarshal(b, &raw); err != nil { |
| return skerr.Wrap(err) |
| } |
| return skerr.Wrap(t.init(raw)) |
| } |
| |
| // MarshalJSON implements json.Marshaler. |
| func (t *Template) MarshalJSON() ([]byte, error) { |
| t.mtx.RLock() |
| defer t.mtx.RUnlock() |
| return json.Marshal(t.raw) |
| } |
| |
| // Validate returns an error if the Template is not valid. |
| func (t *Template) Validate() error { |
| t.mtx.RLock() |
| defer t.mtx.RUnlock() |
| return skerr.Wrap(t.validate()) |
| } |
| |
| // validate returns an error if the Template is not valid. Assumes the caller |
| // holds t.mtx. |
| func (t *Template) validate() error { |
| if t == nil || t.raw == "" || t.tmpl == nil { |
| return skerr.Fmt("Template is missing.") |
| } |
| // Create dummy Vars to test the template. |
| v := DummyVars() |
| |
| // Self-check, in case any of the members of Vars change. |
| if err := v.Validate(); err != nil { |
| return skerr.Wrap(err) |
| } |
| var buf bytes.Buffer |
| if err := t.tmpl.Execute(&buf, v); err != nil { |
| return skerr.Wrap(err) |
| } |
| return nil |
| } |
| |
| // Update executes the Template using the given Vars and updates its value. |
| func (t *Template) Update(v *Vars) error { |
| t.mtx.Lock() |
| defer t.mtx.Unlock() |
| var buf bytes.Buffer |
| if err := t.tmpl.Execute(&buf, v); err != nil { |
| return skerr.Wrap(err) |
| } |
| t.value = buf.String() |
| return nil |
| } |
| |
| // String returns the current value of the Template. |
| func (t *Template) String() string { |
| t.mtx.RLock() |
| defer t.mtx.RUnlock() |
| return t.value |
| } |
| |
| // Equal returns true iff the given Template is equivalent to this one. This |
| // allows us to test equality using deepequal.DeepEqual, despite the fact that |
| // text.Templates are never DeepEqual. |
| func (t *Template) Equal(t2 *Template) bool { |
| t.mtx.RLock() |
| defer t.mtx.RUnlock() |
| t2.mtx.RLock() |
| defer t2.mtx.RUnlock() |
| if t.raw != t2.raw { |
| return false |
| } |
| if t.value != t2.value { |
| return false |
| } |
| return true |
| } |
| |
| // RawTemplate returns the raw string value of the Template. |
| func (t *Template) RawTemplate() string { |
| return t.raw |
| } |
| |
| // Registry manages a set of Templates, updating their values when Update is |
| // called. |
| type Registry struct { |
| cbc chrome_branch.Client |
| mtx sync.Mutex |
| templates []*Template |
| vars *Vars |
| } |
| |
| // NewRegistry returns a Registry instance. Implicitly runs Update(). |
| func NewRegistry(ctx context.Context, cbc chrome_branch.Client) (*Registry, error) { |
| r := &Registry{ |
| cbc: cbc, |
| } |
| if err := r.Update(ctx); err != nil { |
| return nil, err |
| } |
| return r, nil |
| } |
| |
| // Register the given Template. Implicitly Updates its value using the most |
| // recent Vars. |
| func (r *Registry) Register(t *Template) error { |
| r.mtx.Lock() |
| defer r.mtx.Unlock() |
| if err := t.Update(r.vars); err != nil { |
| return err |
| } |
| r.templates = append(r.templates, t) |
| return nil |
| } |
| |
| // updateFrom updates all of the Templates managed by this Registry using the |
| // given Vars. |
| func (r *Registry) updateFrom(v *Vars) error { |
| r.mtx.Lock() |
| defer r.mtx.Unlock() |
| for _, tmpl := range r.templates { |
| if err := tmpl.Update(v); err != nil { |
| return skerr.Wrap(err) |
| } |
| } |
| r.vars = v |
| return nil |
| } |
| |
| // Update all of the Templates managed by this Registry by loading new values. |
| func (r *Registry) Update(ctx context.Context) error { |
| chromeBranches, err := r.cbc.Get(ctx) |
| if err != nil { |
| return skerr.Wrap(err) |
| } |
| vars := &Vars{ |
| Branches: &Branches{ |
| Chromium: chromeBranches, |
| }, |
| } |
| return skerr.Wrap(r.updateFrom(vars)) |
| } |