feat(files): implemented file permission promises

This commit is contained in:
Julien Dessaux 2024-02-15 00:48:48 +01:00
parent d7aa6c514b
commit c1f22b97e6
Signed by: adyxax
GPG key ID: F92E51B86E07177E
3 changed files with 154 additions and 26 deletions

View file

@ -20,20 +20,22 @@ func init() {
// ----- Public ----------------------------------------------------------------
type FilePromise struct {
chain []Promise
contents Value
err error
filename Value
status Status
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),
status: PROMISED,
chain: nil,
contents: nil,
err: nil,
filename: interfaceToTemplateValue(filename),
permissions: nil,
status: PROMISED,
}
}
@ -42,6 +44,11 @@ func (f *FilePromise) Contents(contents any) *FilePromise {
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
@ -79,16 +86,28 @@ func (f *FilePromise) Resolve() {
return
}
f.status = REPAIRED
slog.Info("file", "filename", f.filename, "status", f.status)
for _, p := range f.chain {
p.Resolve()
}
}
}
if f.status == PROMISED {
f.status = KEPT
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)
}
slog.Debug("file", "filename", f.filename, "status", f.status)
}
// ----- Internal --------------------------------------------------------------

89
gonf/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
}

View file

@ -11,12 +11,15 @@ type Value interface {
}
func interfaceToValue(v any) Value {
if vv, ok := v.(string); ok {
return &StringValue{vv}
}
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
}
@ -25,12 +28,15 @@ func interfaceToValue(v any) Value {
}
func interfaceToTemplateValue(v any) Value {
if vv, ok := v.(string); ok {
return &TemplateValue{data: vv}
}
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
}
@ -38,7 +44,7 @@ func interfaceToTemplateValue(v any) Value {
panic(fmt.Sprintf("interfaceToTemplateValue cannot take type %T as argument. Value was %#v.", v, v))
}
// ----- BytesValue -----------------------------------------------------------------
// ----- BytesValue ------------------------------------------------------------
type BytesValue struct {
value []byte
}
@ -46,12 +52,26 @@ type BytesValue struct {
func (b BytesValue) Bytes() []byte {
return b.value
}
func (b BytesValue) String() string {
return string(b.value[:])
}
// ----- StringValue ----------------------------------------------------------------
// ----- 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)
}
// ----- StringValue -----------------------------------------------------------
type StringValue struct {
value string
}