chore(repo): renamed gonf subfolder to something more traditional in go land

This commit is contained in:
Julien Dessaux 2024-03-07 00:54:35 +01:00
parent 3b9af43738
commit 560becfd32
Signed by: adyxax
GPG key ID: F92E51B86E07177E
14 changed files with 2 additions and 2 deletions

95
pkg/commands.go Normal file
View file

@ -0,0 +1,95 @@
package gonf
import (
"bytes"
"log/slog"
"os/exec"
)
// ----- Globals ---------------------------------------------------------------
var commands []*CommandPromise
// ----- Init ------------------------------------------------------------------
func init() {
commands = make([]*CommandPromise, 0)
}
// ----- Public ----------------------------------------------------------------
func Command(cmd string, args ...string) *CommandPromise {
return CommandWithEnv([]string{}, cmd, args...)
}
func CommandWithEnv(env []string, cmd string, args ...string) *CommandPromise {
return &CommandPromise{
args: args,
chain: nil,
cmd: cmd,
env: env,
err: nil,
Status: PROMISED,
}
}
type CommandPromise struct {
args []string
chain []Promise
cmd string
env []string
err error
Status Status
Stdout bytes.Buffer
Stderr bytes.Buffer
}
func (c *CommandPromise) IfRepaired(p ...Promise) Promise {
c.chain = p
return c
}
func (c *CommandPromise) Promise() Promise {
commands = append(commands, c)
return c
}
func (c *CommandPromise) Resolve() {
cmd := exec.Command(c.cmd, c.args...)
for _, e := range c.env {
cmd.Env = append(cmd.Environ(), e)
}
cmd.Stdout = &c.Stdout
cmd.Stderr = &c.Stderr
if c.err = cmd.Run(); c.err != nil {
c.Status = BROKEN
slog.Error("command", "args", c.args, "cmd", c.cmd, "env", c.env, "err", c.err, "stdout", c.Stdout.String(), "stderr", c.Stderr.String(), "status", c.Status)
return
}
if c.Stdout.Len() == 0 && c.Stderr.Len() > 0 {
c.Status = BROKEN
slog.Error("command", "args", c.args, "cmd", c.cmd, "env", c.env, "stdout", c.Stdout.String(), "stderr", c.Stderr.String(), "status", c.Status)
return
}
c.Status = REPAIRED
slog.Info("command", "args", c.args, "cmd", c.cmd, "env", c.env, "stderr", c.Stderr.String(), "status", c.Status)
// TODO add a notion of repaired?
for _, p := range c.chain {
p.Resolve()
}
}
// ----- Internal --------------------------------------------------------------
func resolveCommands() (status Status) {
status = KEPT
for _, c := range commands {
if c.Status == PROMISED {
c.Resolve()
switch c.Status {
case BROKEN:
return BROKEN
case REPAIRED:
status = REPAIRED
}
}
}
return
}

151
pkg/files.go Normal file
View file

@ -0,0 +1,151 @@
package gonf
import (
"bytes"
"crypto/sha256"
"errors"
"io"
"io/fs"
"log/slog"
"os"
)
// ----- Globals ---------------------------------------------------------------
var files []*FilePromise
// ----- Init ------------------------------------------------------------------
func init() {
files = make([]*FilePromise, 0)
}
// ----- Public ----------------------------------------------------------------
type FilePromise struct {
chain []Promise
contents Value
err error
filename Value
permissions *Permissions
status Status
}
func File(filename any) *FilePromise {
return &FilePromise{
chain: nil,
contents: nil,
err: nil,
filename: interfaceToTemplateValue(filename),
permissions: nil,
status: PROMISED,
}
}
func (f *FilePromise) Contents(contents any) *FilePromise {
f.contents = interfaceToValue(contents)
return f
}
func (f *FilePromise) Permissions(p *Permissions) *FilePromise {
f.permissions = p
return f
}
func (f *FilePromise) Template(contents any) *FilePromise {
f.contents = interfaceToTemplateValue(contents)
return f
}
// We want to satisfy the Promise interface
func (f *FilePromise) IfRepaired(p ...Promise) Promise {
f.chain = p
return f
}
func (f *FilePromise) Promise() Promise {
files = append(files, f)
return f
}
func (f *FilePromise) Resolve() {
filename := f.filename.String()
if f.contents != nil {
var sumFile []byte
sumFile, f.err = sha256sumOfFile(filename)
if f.err != nil {
if !errors.Is(f.err, fs.ErrNotExist) {
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
f.status = BROKEN
return
}
}
contents := f.contents.Bytes()
sumContents := sha256sum(contents)
if !bytes.Equal(sumFile, sumContents) {
if f.err = writeFile(filename, contents); f.err != nil {
f.status = BROKEN
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
return
}
f.status = REPAIRED
}
}
if f.permissions != nil {
var status Status
status, f.err = f.permissions.resolve(filename)
if f.status == PROMISED || status == BROKEN {
f.status = status
}
if f.err != nil {
slog.Error("file", "filename", f.filename, "status", f.status, "error", f.err)
return
}
}
if f.status == REPAIRED {
slog.Info("file", "filename", f.filename, "status", f.status)
for _, p := range f.chain {
p.Resolve()
}
} else {
f.status = KEPT
slog.Debug("file", "filename", f.filename, "status", f.status)
}
}
// ----- Internal --------------------------------------------------------------
func resolveFiles() (status Status) {
status = KEPT
for _, f := range files {
if f.status == PROMISED {
f.Resolve()
switch f.status {
case BROKEN:
return BROKEN
case REPAIRED:
status = REPAIRED
}
}
}
return
}
func sha256sumOfFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
func writeFile(filename string, contents []byte) error {
f, err := os.Create(filename)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(contents)
return err
}

52
pkg/gonf.go Normal file
View file

@ -0,0 +1,52 @@
package gonf
import (
"log/slog"
"os"
)
func EnableDebugLogs() {
h := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
slog.SetDefault(slog.New(h))
}
func Resolve() (status Status) {
for {
// ----- Files -------------------------------------------------
status = resolveFiles()
switch status {
case BROKEN:
return BROKEN
case REPAIRED:
continue
}
// ----- Packages ----------------------------------------------
status = resolvePackages()
switch status {
case BROKEN:
return BROKEN
case REPAIRED:
packages_list_function()
continue
}
// ----- Services ----------------------------------------------
status = resolveServices()
switch status {
case BROKEN:
return BROKEN
case REPAIRED:
continue
}
// ----- Commands ----------------------------------------------
status = resolveCommands()
switch status {
case BROKEN:
return BROKEN
case REPAIRED:
continue
}
return
}
}

81
pkg/packages.go Normal file
View file

@ -0,0 +1,81 @@
package gonf
import "log/slog"
// ----- Globals ---------------------------------------------------------------
var packages []*PackagePromise
// packages management functions
var packages_install_function func([]string) (Status, []string)
var packages_list_function func()
var packages_update_function *CommandPromise
// ----- Init ------------------------------------------------------------------
func init() {
packages = make([]*PackagePromise, 0)
}
// ----- Public ----------------------------------------------------------------
func SetPackagesConfiguration(install func([]string) (Status, []string), list func(), update *CommandPromise) {
packages_install_function = install
packages_list_function = list
packages_update_function = update
}
func Package(names ...string) *PackagePromise {
return &PackagePromise{
chain: nil,
err: nil,
names: names,
status: PROMISED,
}
}
type PackagePromise struct {
chain []Promise
err error
names []string
status Status
}
func (p *PackagePromise) IfRepaired(ps ...Promise) Promise {
p.chain = ps
return p
}
func (p *PackagePromise) Promise() Promise {
packages = append(packages, p)
return p
}
func (p *PackagePromise) Resolve() {
status, affected := packages_install_function(p.names)
switch status {
case BROKEN:
slog.Error("package", "names", p.names, "status", status, "broke", affected)
case KEPT:
slog.Debug("package", "names", p.names, "status", status)
case REPAIRED:
slog.Info("package", "names", p.names, "status", status, "repaired", affected)
for _, pp := range p.chain {
pp.Resolve()
}
}
}
// ----- Internal --------------------------------------------------------------
func resolvePackages() (status Status) {
status = KEPT
for _, c := range packages {
if c.status == PROMISED {
c.Resolve()
switch c.status {
case BROKEN:
return BROKEN
case REPAIRED:
status = REPAIRED
}
}
}
return
}

89
pkg/permissions.go Normal file
View file

@ -0,0 +1,89 @@
package gonf
import (
"errors"
"io/fs"
"os"
"os/user"
"strconv"
"syscall"
)
type Permissions struct {
group Value
mode Value
user Value
}
func ModeUserGroup(mode, user, group interface{}) *Permissions {
return &Permissions{
group: interfaceToTemplateValue(group),
mode: interfaceToTemplateValue(mode),
user: interfaceToTemplateValue(user),
}
}
func (p *Permissions) resolve(filename string) (Status, error) {
g, ok := p.group.(*IntValue)
if !ok {
if group, err := user.LookupGroup(p.group.String()); err != nil {
return BROKEN, err
} else {
if groupId, err := strconv.Atoi(group.Gid); err != nil {
return BROKEN, err
} else {
g = &IntValue{groupId}
p.group = g
}
}
}
m, ok := p.mode.(*IntValue)
if !ok {
if i, err := strconv.Atoi(p.mode.String()); err != nil {
return BROKEN, err
} else {
m = &IntValue{i}
p.mode = m
}
}
u, ok := p.user.(*IntValue)
if !ok {
if user, err := user.Lookup(p.user.String()); err != nil {
return BROKEN, err
} else {
if userId, err := strconv.Atoi(user.Uid); err != nil {
return BROKEN, err
} else {
u = &IntValue{userId}
p.group = u
}
}
}
var status Status = KEPT
if fileInfo, err := os.Lstat(filename); err != nil {
return BROKEN, err
} else {
gv := g.Int()
mv := fs.FileMode(m.Int())
uv := u.Int()
if fileInfo.Mode() != mv {
if err := os.Chmod(filename, mv); err != nil {
return BROKEN, err
}
status = REPAIRED
}
if stat, ok := fileInfo.Sys().(*syscall.Stat_t); ok {
if stat.Gid != uint32(gv) || stat.Uid != uint32(uv) {
if err := os.Chown(filename, uv, gv); err != nil {
return BROKEN, err
}
status = REPAIRED
}
} else {
return BROKEN, errors.New("Unsupported operating system")
}
_ = gv
_ = uv
}
return status, nil
}

50
pkg/promises.go Normal file
View file

@ -0,0 +1,50 @@
package gonf
type Promise interface {
IfRepaired(...Promise) Promise
Promise() Promise
Resolve()
}
//type Operation int
//
//const (
// AND = iota
// OR
// NOT
//)
//
//func (o Operation) String() string {
// switch o {
// case AND:
// return "and"
// case OR:
// return "or"
// case NOT:
// return "not"
// }
// panic("unknown")
//}
type Status int
const (
PROMISED = iota
BROKEN
KEPT
REPAIRED
)
func (s Status) String() string {
switch s {
case PROMISED:
return "promised"
case BROKEN:
return "broken"
case KEPT:
return "kept"
case REPAIRED:
return "repaired"
}
panic("unknown")
}

96
pkg/services.go Normal file
View file

@ -0,0 +1,96 @@
package gonf
import "log/slog"
// ----- Globals ---------------------------------------------------------------
var services []*ServicePromise
// service management function
var serviceFunction func(string, string) (Status, error)
// ----- Init ------------------------------------------------------------------
func init() {
services = make([]*ServicePromise, 0)
}
// ----- Public ----------------------------------------------------------------
func SetServiceFunction(f func(string, string) (Status, error)) {
serviceFunction = f
}
func Service(names ...string) *ServicePromise {
return &ServicePromise{
chain: nil,
err: nil,
names: names,
states: nil,
status: PROMISED,
}
}
func (s *ServicePromise) State(states ...string) *ServicePromise {
s.states = states
return s
}
type ServicePromise struct {
chain []Promise
err error
names []string
states []string
status Status
}
func (s *ServicePromise) IfRepaired(ps ...Promise) Promise {
s.chain = ps
return s
}
func (s *ServicePromise) Promise() Promise {
services = append(services, s)
return s
}
func (s *ServicePromise) Resolve() {
for _, name := range s.names {
var repaired = false
for _, state := range s.states {
s.status, s.err = serviceFunction(name, state)
if s.status == BROKEN {
slog.Error("service", "name", name, "state", state, "status", s.status, "error", s.err)
return
} else if s.status == REPAIRED {
repaired = true
}
}
if repaired {
s.status = REPAIRED
slog.Info("service", "name", name, "state", s.states, "status", s.status)
} else {
s.status = KEPT
slog.Debug("service", "name", name, "state", s.states, "status", s.status)
}
}
if s.status == REPAIRED {
for _, pp := range s.chain {
pp.Resolve()
}
}
}
// ----- Internal --------------------------------------------------------------
func resolveServices() (status Status) {
status = KEPT
for _, c := range services {
if c.status == PROMISED {
c.Resolve()
switch c.status {
case BROKEN:
return BROKEN
case REPAIRED:
status = REPAIRED
}
}
}
return
}

44
pkg/templates.go Normal file
View file

@ -0,0 +1,44 @@
package gonf
import (
"bytes"
"log/slog"
"text/template"
)
// ----- Globals ---------------------------------------------------------------
var templates *template.Template
// ----- Init ------------------------------------------------------------------
func init() {
templates = template.New("")
templates.Option("missingkey=error")
templates.Funcs(builtinTemplateFunctions)
}
// ----- Public ----------------------------------------------------------------
type TemplateValue struct {
contents []byte
data string
}
func (t *TemplateValue) Bytes() []byte {
if t.contents == nil {
tpl := templates.New("")
if _, err := tpl.Parse(t.data); err != nil {
slog.Error("template", "step", "Parse", "data", t.data, "error", err)
return nil
}
var buff bytes.Buffer
if err := tpl.Execute(&buff, nil /* no data needed */); err != nil {
slog.Error("template", "step", "Execute", "data", t.data, "error", err)
return nil
}
t.contents = buff.Bytes()
}
return t.contents
}
func (t TemplateValue) String() string {
return string(t.Bytes()[:])
}

11
pkg/triggers.go Normal file
View file

@ -0,0 +1,11 @@
package gonf
type simpleTrigger struct {
fact string
status Status
}
//type compositeTrigger struct {
// triggers []simpleTrigger
// operation Operation
//}

27
pkg/utils.go Normal file
View file

@ -0,0 +1,27 @@
package gonf
import (
"crypto/sha256"
)
var builtinTemplateFunctions = map[string]any{
//"encodeURIQueryParameter": url.QueryEscape,
"var": getVariable,
}
func FilterSlice[T any](slice *[]T, predicate func(T) bool) {
i := 0
for _, element := range *slice {
if predicate(element) { // if the element matches the predicate function
(*slice)[i] = element // then we keep it in the slice
i++
} // otherwise the element will get overwritten
}
*slice = (*slice)[:i] // or truncated out of the slice
}
func sha256sum(contents []byte) []byte {
h := sha256.New()
h.Write(contents)
return h.Sum(nil)
}

104
pkg/values.go Normal file
View file

@ -0,0 +1,104 @@
package gonf
import (
"fmt"
"log/slog"
"strings"
)
type Value interface {
Bytes() []byte
String() string
}
func interfaceToValue(v any) Value {
if vv, ok := v.([]byte); ok {
return &BytesValue{vv}
}
if vv, ok := v.(int); ok {
return &IntValue{vv}
}
if vv, ok := v.(string); ok {
return &StringValue{vv}
}
if vv, ok := v.(*VariablePromise); ok {
return vv
}
slog.Error("interfaceToValue", "value", v, "error", "Not Implemented")
panic(fmt.Sprintf("interfaceToValue cannot take type %T as argument. Value was %#v.", v, v))
}
func interfaceToTemplateValue(v any) Value {
if vv, ok := v.([]byte); ok {
return &TemplateValue{data: string(vv)}
}
if vv, ok := v.(int); ok {
return &IntValue{vv}
}
if vv, ok := v.(string); ok {
return &TemplateValue{data: vv}
}
if vv, ok := v.(*VariablePromise); ok {
return vv
}
slog.Error("interfaceToTemplateValue", "value", v, "error", "Not Implemented")
panic(fmt.Sprintf("interfaceToTemplateValue cannot take type %T as argument. Value was %#v.", v, v))
}
// ----- BytesValue ------------------------------------------------------------
type BytesValue struct {
value []byte
}
func (b BytesValue) Bytes() []byte {
return b.value
}
func (b BytesValue) String() string {
return string(b.value[:])
}
// ----- IntValue --------------------------------------------------------------
type IntValue struct {
value int
}
func (i IntValue) Bytes() []byte {
return []byte(string(i.value))
}
func (i IntValue) Int() int {
return i.value
}
func (i IntValue) String() string {
return string(i.value)
}
// ----- StringsListValue ------------------------------------------------------
type StringsListValue struct {
value []string
}
func (s *StringsListValue) Append(v ...string) {
s.value = append(s.value, v...)
}
func (s StringsListValue) Bytes() []byte {
return []byte(s.String())
}
func (s StringsListValue) String() string {
return strings.Join(s.value, "\n")
}
// ----- StringValue -----------------------------------------------------------
type StringValue struct {
value string
}
func (s StringValue) Bytes() []byte {
return []byte(s.value)
}
func (s StringValue) String() string {
return s.value
}
// TODO maps
// TODO what else?

85
pkg/variables.go Normal file
View file

@ -0,0 +1,85 @@
package gonf
import (
"fmt"
"log/slog"
)
// ----- Globals ---------------------------------------------------------------
var variables map[string]*VariablePromise
// ----- Init ------------------------------------------------------------------
func init() {
variables = make(map[string]*VariablePromise)
}
// ----- Public ----------------------------------------------------------------
func AppendVariable(name string, values ...string) *VariablePromise {
if v, ok := variables[name]; ok {
if l, ok := v.value.(*StringsListValue); ok {
l.Append(values...)
}
return v
}
v := &VariablePromise{
isDefault: false,
name: name,
value: &StringsListValue{values},
}
variables[name] = v
return v
}
func Default(name string, value string) *VariablePromise {
if v, ok := variables[name]; ok {
if !v.isDefault {
slog.Debug("default would overwrite a variable, ignoring", "name", name, "old_value", v.value, "new_value", value)
return nil
}
slog.Error("default is being overwritten", "name", name, "old_value", v.value, "new_value", value)
}
v := &VariablePromise{
isDefault: true,
name: name,
value: interfaceToTemplateValue(value),
}
variables[name] = v
return v
}
func Variable(name string, value string) *VariablePromise {
if v, ok := variables[name]; ok && !v.isDefault {
slog.Error("variable is being overwritten", "name", name, "old_value", v, "new_value", value)
}
v := &VariablePromise{
isDefault: false,
name: name,
value: interfaceToTemplateValue(value),
}
variables[name] = v
return v
}
type VariablePromise struct {
isDefault bool
name string
value Value
}
// We want VariablePromise to satisfy the Value interface
func (s VariablePromise) Bytes() []byte {
return s.value.Bytes()
}
func (s VariablePromise) String() string {
return s.value.String()
}
// ----- Internal --------------------------------------------------------------
func getVariable(name string) string {
if v, ok := variables[name]; ok {
return v.value.String()
} else {
slog.Error("undefined variable or default", "name", name)
panic(fmt.Sprintf("undefined variable or default %s", name))
}
}