~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
123 lines · 2.8 KB Raw History
package baosync

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/vault/openbao"
)

type Report struct {
	Applied   []Op
	Conflicts []string
	Err       error
}

func (r Report) Changed() bool { return len(r.Applied) > 0 }

func (r Report) Summary() string {
	if len(r.Applied) == 0 && len(r.Conflicts) == 0 && r.Err == nil {
		return "in sync"
	}
	var b strings.Builder
	counts := map[OpKind]int{}
	var order []OpKind
	for _, o := range r.Applied {
		if counts[o.Kind] == 0 {
			order = append(order, o.Kind)
		}
		counts[o.Kind]++
	}
	for i, k := range order {
		if i > 0 {
			b.WriteString(", ")
		}
		fmt.Fprintf(&b, "%s x%d", k, counts[k])
	}
	if len(r.Conflicts) > 0 {
		if b.Len() > 0 {
			b.WriteString("; ")
		}
		fmt.Fprintf(&b, "%d conflict(s): %s", len(r.Conflicts), strings.Join(r.Conflicts, "; "))
	}
	if r.Err != nil {
		if b.Len() > 0 {
			b.WriteString("; ")
		}
		b.WriteString("errors: " + r.Err.Error())
	}
	return b.String()
}

func enableSecret(ctx context.Context, c *openbao.Client, o Op) error {
	var err error
	switch o.Type {
	case MountKV2:
		err = c.EnableKV(ctx, o.Target, 2)
	case MountKV:
		err = c.EnableKV(ctx, o.Target, 1)
	case MountPKI:
		err = c.MountPKI(ctx, o.Target)
	default:
		err = fmt.Errorf("baosync: no way to enable a %s mount", o.Type)
	}
	if err == nil && len(o.Body) > 0 {
		err = c.TuneMount(ctx, o.Target, o.Body)
	}
	return err
}

func applyOp(ctx context.Context, c *openbao.Client, o Op) error {
	switch o.Kind {
	case OpEnableSecret:
		return enableSecret(ctx, c, o)
	case OpEnableAuth:
		return c.EnableAuth(ctx, o.Target, o.Type)
	case OpDisableAuth:
		return c.DisableAuth(ctx, o.Target)
	case OpPutPolicy:
		return c.PutPolicy(ctx, o.Target, o.HCL)
	case OpWriteObject:
		return c.Write(ctx, o.Target, o.Body)
	case OpDeleteObject, OpDeleteKV:
		return c.Delete(ctx, o.Target)
	case OpDeletePolicy:
		return c.DeletePolicy(ctx, o.Target)
	case OpDisableSecret:
		return c.DisableMount(ctx, o.Target)
	case OpRegister:
		return c.Write(ctx, "sys/plugins/catalog/"+o.Target, o.Body)
	case OpReload:
		return c.Write(ctx, "sys/plugins/reload/backend", o.Body)
	case OpDeregister:
		return c.Delete(ctx, "sys/plugins/catalog/"+o.Target)
	}
	return fmt.Errorf("baosync: unknown op %s", o.Kind)
}

func Apply(ctx context.Context, c *openbao.Client, p Plan) Report {
	r := Report{Conflicts: p.Conflicts}
	var errs []error
	for _, o := range p.Ops {
		if err := applyOp(ctx, c, o); err != nil {
			errs = append(errs, fmt.Errorf("%s: %w", o, err))
			continue
		}
		r.Applied = append(r.Applied, o)
	}
	r.Err = errors.Join(errs...)
	return r
}

func Sync(ctx context.Context, c *openbao.Client, d Desired) (Report, error) {
	if err := d.Validate(); err != nil {
		return Report{}, err
	}
	a, err := Read(ctx, c, d)
	if err != nil {
		return Report{}, err
	}
	return Apply(ctx, c, Diff(d, a)), nil
}