Implemented confi snapshots
This commit is contained in:
+138
-4
@@ -22,13 +22,18 @@ type Kind int
|
||||
const (
|
||||
KindSet Kind = iota
|
||||
KindInstance
|
||||
KindRule
|
||||
)
|
||||
|
||||
func (k Kind) sub() string {
|
||||
if k == KindInstance {
|
||||
switch k {
|
||||
case KindInstance:
|
||||
return "instances"
|
||||
case KindRule:
|
||||
return "rules"
|
||||
default:
|
||||
return "sets"
|
||||
}
|
||||
return "sets"
|
||||
}
|
||||
|
||||
// Meta is the lightweight listing representation.
|
||||
@@ -36,6 +41,10 @@ type Meta struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version int `json:"version"`
|
||||
// SetID is only populated for instances (the schema they belong to); it is
|
||||
// empty for sets. Lets clients group/filter instances by set without a GET
|
||||
// per instance.
|
||||
SetID string `json:"setId,omitempty"`
|
||||
}
|
||||
|
||||
// VersionMeta describes a single persisted revision.
|
||||
@@ -59,7 +68,7 @@ type Store struct {
|
||||
// New opens (and creates, if needed) the configs storage directories.
|
||||
func New(storageDir string) (*Store, error) {
|
||||
s := &Store{rootDir: storageDir, dirs: map[Kind]string{}}
|
||||
for _, k := range []Kind{KindSet, KindInstance} {
|
||||
for _, k := range []Kind{KindSet, KindInstance, KindRule} {
|
||||
dir := filepath.Join(storageDir, "configs", k.sub())
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return nil, fmt.Errorf("create %s dir: %w", k.sub(), err)
|
||||
@@ -75,6 +84,7 @@ type header struct {
|
||||
Name string `json:"name"`
|
||||
Version int `json:"version"`
|
||||
Tag string `json:"tag"`
|
||||
SetID string `json:"setId"`
|
||||
}
|
||||
|
||||
func validateID(id string) error {
|
||||
@@ -139,7 +149,7 @@ func (s *Store) List(k Kind) ([]Meta, error) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, Meta{ID: id, Name: h.Name, Version: h.Version})
|
||||
out = append(out, Meta{ID: id, Name: h.Name, Version: h.Version, SetID: h.SetID})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -462,6 +472,9 @@ func (s *Store) CreateInstance(inst ConfigInstance, tag string) (ConfigInstance,
|
||||
if err := inst.ValidateAgainst(set); err != nil {
|
||||
return ConfigInstance{}, err
|
||||
}
|
||||
if err := s.applyRules(&inst, set); err != nil {
|
||||
return ConfigInstance{}, err
|
||||
}
|
||||
obj, err := toMap(inst)
|
||||
if err != nil {
|
||||
return ConfigInstance{}, err
|
||||
@@ -483,6 +496,9 @@ func (s *Store) UpdateInstance(id string, inst ConfigInstance, tag string) (Conf
|
||||
if err := inst.ValidateAgainst(set); err != nil {
|
||||
return ConfigInstance{}, err
|
||||
}
|
||||
if err := s.applyRules(&inst, set); err != nil {
|
||||
return ConfigInstance{}, err
|
||||
}
|
||||
obj, err := toMap(inst)
|
||||
if err != nil {
|
||||
return ConfigInstance{}, err
|
||||
@@ -500,6 +516,124 @@ func (s *Store) SetForInstance(inst ConfigInstance) (ConfigSet, error) {
|
||||
return s.setForInstance(inst)
|
||||
}
|
||||
|
||||
// ── rules ────────────────────────────────────────────────────────────────────
|
||||
|
||||
// GetRule returns the current revision of a CUE validation/transformation rule.
|
||||
func (s *Store) GetRule(id string) (ConfigRule, error) {
|
||||
data, err := s.get(KindRule, id)
|
||||
if err != nil {
|
||||
return ConfigRule{}, err
|
||||
}
|
||||
var rule ConfigRule
|
||||
return rule, json.Unmarshal(data, &rule)
|
||||
}
|
||||
|
||||
// GetRuleVersion returns a specific revision of a rule.
|
||||
func (s *Store) GetRuleVersion(id string, version int) (ConfigRule, error) {
|
||||
data, err := s.getVersion(KindRule, id, version)
|
||||
if err != nil {
|
||||
return ConfigRule{}, err
|
||||
}
|
||||
var rule ConfigRule
|
||||
return rule, json.Unmarshal(data, &rule)
|
||||
}
|
||||
|
||||
// CreateRule validates the rule's CUE source and stores it.
|
||||
func (s *Store) CreateRule(rule ConfigRule, tag string) (ConfigRule, error) {
|
||||
if err := rule.Validate(); err != nil {
|
||||
return ConfigRule{}, err
|
||||
}
|
||||
obj, err := toMap(rule)
|
||||
if err != nil {
|
||||
return ConfigRule{}, err
|
||||
}
|
||||
_, data, err := s.create(KindRule, obj, tag)
|
||||
if err != nil {
|
||||
return ConfigRule{}, err
|
||||
}
|
||||
var out ConfigRule
|
||||
return out, json.Unmarshal(data, &out)
|
||||
}
|
||||
|
||||
// UpdateRule validates and stores a new revision of an existing rule.
|
||||
func (s *Store) UpdateRule(id string, rule ConfigRule, tag string) (ConfigRule, error) {
|
||||
if err := rule.Validate(); err != nil {
|
||||
return ConfigRule{}, err
|
||||
}
|
||||
obj, err := toMap(rule)
|
||||
if err != nil {
|
||||
return ConfigRule{}, err
|
||||
}
|
||||
data, err := s.update(KindRule, id, obj, tag)
|
||||
if err != nil {
|
||||
return ConfigRule{}, err
|
||||
}
|
||||
var out ConfigRule
|
||||
return out, json.Unmarshal(data, &out)
|
||||
}
|
||||
|
||||
// rulesForSet loads every current-revision rule bound to the given set.
|
||||
func (s *Store) rulesForSet(setID string) ([]ConfigRule, error) {
|
||||
metas, err := s.List(KindRule)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out []ConfigRule
|
||||
for _, m := range metas {
|
||||
if m.SetID != setID {
|
||||
continue
|
||||
}
|
||||
rule, err := s.GetRule(m.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, rule)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// applyRules runs every rule bound to inst's set against its values. On
|
||||
// success it merges rule-derived values back into inst (parameter keys only) so
|
||||
// the stored instance reflects the canonical, transformed configuration. A
|
||||
// violation is returned as *RuleError so the caller can surface the structured
|
||||
// details.
|
||||
func (s *Store) applyRules(inst *ConfigInstance, set ConfigSet) error {
|
||||
rules, err := s.rulesForSet(inst.SetID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(rules) == 0 {
|
||||
return nil
|
||||
}
|
||||
res := evaluateRules(rules, inst.Values)
|
||||
if !res.OK {
|
||||
return &RuleError{Result: res}
|
||||
}
|
||||
for k, v := range res.Transformed {
|
||||
if _, ok := set.param(k); ok {
|
||||
if inst.Values == nil {
|
||||
inst.Values = map[string]any{}
|
||||
}
|
||||
inst.Values[k] = v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateInstanceRules evaluates the stored rules for an instance without
|
||||
// persisting anything. It is the read-only counterpart used by the validate
|
||||
// endpoint.
|
||||
func (s *Store) ValidateInstanceRules(inst ConfigInstance) (RuleResult, error) {
|
||||
rules, err := s.rulesForSet(inst.SetID)
|
||||
if err != nil {
|
||||
return RuleResult{}, err
|
||||
}
|
||||
if len(rules) == 0 {
|
||||
return RuleResult{OK: true}, nil
|
||||
}
|
||||
return evaluateRules(rules, inst.Values), nil
|
||||
}
|
||||
|
||||
// ── JSON helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
func name(obj map[string]any) string {
|
||||
|
||||
Reference in New Issue
Block a user