diff options
-rw-r--r-- | gonf/files.go | 51 | ||||
-rw-r--r-- | gonf/permissions.go | 89 | ||||
-rw-r--r-- | gonf/values.go | 38 |
3 files changed, 153 insertions, 25 deletions
diff --git a/gonf/files.go b/gonf/files.go index ac6a0aa..974c19c 100644 --- a/gonf/files.go +++ b/gonf/files.go @@ -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 { + 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 -------------------------------------------------------------- diff --git a/gonf/permissions.go b/gonf/permissions.go new file mode 100644 index 0000000..2bd73a9 --- /dev/null +++ b/gonf/permissions.go @@ -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 +} diff --git a/gonf/values.go b/gonf/values.go index b77e703..56743e2 100644 --- a/gonf/values.go +++ b/gonf/values.go @@ -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 } |