196 lines
4 KiB
Go
196 lines
4 KiB
Go
package gonf
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
var files []*FilePromise
|
|
|
|
func init() {
|
|
files = make([]*FilePromise, 0)
|
|
}
|
|
|
|
type FileType int
|
|
|
|
const (
|
|
FILE = iota
|
|
DIRECTORY
|
|
)
|
|
|
|
type FilePromise struct {
|
|
chain []Promise
|
|
contents Value
|
|
dirPermissions *Permissions
|
|
err error
|
|
filename Value
|
|
fileType FileType
|
|
permissions *Permissions
|
|
status Status
|
|
}
|
|
|
|
func Directory(filename any) *FilePromise {
|
|
return &FilePromise{
|
|
chain: nil,
|
|
contents: nil,
|
|
dirPermissions: nil,
|
|
err: nil,
|
|
filename: interfaceToTemplateValue(filename),
|
|
fileType: DIRECTORY,
|
|
permissions: nil,
|
|
status: DECLARED,
|
|
}
|
|
}
|
|
|
|
func File(filename any) *FilePromise {
|
|
return &FilePromise{
|
|
chain: nil,
|
|
contents: nil,
|
|
dirPermissions: nil,
|
|
err: nil,
|
|
filename: interfaceToTemplateValue(filename),
|
|
fileType: FILE,
|
|
permissions: nil,
|
|
status: DECLARED,
|
|
}
|
|
}
|
|
|
|
func (f *FilePromise) Contents(contents any) *FilePromise {
|
|
f.contents = interfaceToValue(contents)
|
|
return f
|
|
}
|
|
|
|
func (f *FilePromise) DirectoriesPermissions(p *Permissions) *FilePromise {
|
|
f.dirPermissions = p
|
|
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
|
|
}
|
|
|
|
func (f *FilePromise) IfRepaired(p ...Promise) Promise {
|
|
f.chain = append(f.chain, p...)
|
|
return f
|
|
}
|
|
|
|
func (f *FilePromise) Promise() *FilePromise {
|
|
if f.status == DECLARED {
|
|
f.status = PROMISED
|
|
files = append(files, f)
|
|
}
|
|
return f
|
|
}
|
|
|
|
func (f *FilePromise) Resolve() {
|
|
filename := f.filename.String()
|
|
if f.status, f.err = makeDirectoriesHierarchy(filepath.Dir(filename), f.dirPermissions); f.err != nil {
|
|
return
|
|
}
|
|
switch f.fileType {
|
|
case DIRECTORY:
|
|
if f.status, f.err = makeDirectoriesHierarchy(filepath.Clean(filename), f.dirPermissions); f.err != nil {
|
|
return
|
|
}
|
|
case FILE:
|
|
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
|
|
}
|
|
}
|
|
default:
|
|
panic(fmt.Errorf("unknown File type enum value %d", f.fileType))
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
func (f FilePromise) Status() Status {
|
|
return f.status
|
|
}
|
|
|
|
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
|
|
}
|