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 ---------------------------------------------------------------- // ----- Public ----------------------------------------------------------------
type FilePromise struct { type FilePromise struct {
chain []Promise chain []Promise
contents Value contents Value
err error err error
filename Value filename Value
status Status permissions *Permissions
status Status
} }
func File(filename any) *FilePromise { func File(filename any) *FilePromise {
return &FilePromise{ return &FilePromise{
chain: nil, chain: nil,
contents: nil, contents: nil,
err: nil, err: nil,
filename: interfaceToTemplateValue(filename), filename: interfaceToTemplateValue(filename),
status: PROMISED, permissions: nil,
status: PROMISED,
} }
} }
@ -42,6 +44,11 @@ func (f *FilePromise) Contents(contents any) *FilePromise {
return f return f
} }
func (f *FilePromise) Permissions(p *Permissions) *FilePromise {
f.permissions = p
return f
}
func (f *FilePromise) Template(contents any) *FilePromise { func (f *FilePromise) Template(contents any) *FilePromise {
f.contents = interfaceToTemplateValue(contents) f.contents = interfaceToTemplateValue(contents)
return f return f
@ -79,16 +86,28 @@ func (f *FilePromise) Resolve() {
return return
} }
f.status = REPAIRED f.status = REPAIRED
slog.Info("file", "filename", f.filename, "status", f.status)
for _, p := range f.chain {
p.Resolve()
}
} }
} }
if f.status == PROMISED { if f.permissions != nil {
f.status = KEPT 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 -------------------------------------------------------------- // ----- 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 { func interfaceToValue(v any) Value {
if vv, ok := v.(string); ok {
return &StringValue{vv}
}
if vv, ok := v.([]byte); ok { if vv, ok := v.([]byte); ok {
return &BytesValue{vv} 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 { if vv, ok := v.(*VariablePromise); ok {
return vv return vv
} }
@ -25,12 +28,15 @@ func interfaceToValue(v any) Value {
} }
func interfaceToTemplateValue(v any) Value { func interfaceToTemplateValue(v any) Value {
if vv, ok := v.(string); ok {
return &TemplateValue{data: vv}
}
if vv, ok := v.([]byte); ok { if vv, ok := v.([]byte); ok {
return &TemplateValue{data: string(vv)} 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 { if vv, ok := v.(*VariablePromise); ok {
return vv 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)) panic(fmt.Sprintf("interfaceToTemplateValue cannot take type %T as argument. Value was %#v.", v, v))
} }
// ----- BytesValue ----------------------------------------------------------------- // ----- BytesValue ------------------------------------------------------------
type BytesValue struct { type BytesValue struct {
value []byte value []byte
} }
@ -46,12 +52,26 @@ type BytesValue struct {
func (b BytesValue) Bytes() []byte { func (b BytesValue) Bytes() []byte {
return b.value return b.value
} }
func (b BytesValue) String() string { func (b BytesValue) String() string {
return string(b.value[:]) 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 { type StringValue struct {
value string value string
} }