jx/internal/resource/file.go
Matthew Rich b57de00464
All checks were successful
Declarative Tests / test (push) Successful in 47s
test file not exist error
2024-03-24 22:06:08 -07:00

223 lines
4.9 KiB
Go

// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
//
package resource
import (
"context"
"errors"
"fmt"
"os"
"os/user"
"io"
"syscall"
"gopkg.in/yaml.v3"
"strconv"
"path/filepath"
"net/url"
"time"
)
type FileType string
const (
RegularFile FileType = "regular"
DirectoryFile FileType = "directory"
BlockDeviceFile FileType = "block"
CharacterDeviceFile FileType = "char"
NamedPipeFile FileType = "pipe"
SymbolicLinkFile FileType = "symlink"
SocketFile FileType = "socket"
)
var ErrInvalidResourceURI error = errors.New("Invalid resource URI")
func init() {
ResourceTypes.Register("file", func(u *url.URL) Resource {
f := NewFile()
f.Path = filepath.Join(u.Hostname(), u.Path)
return f
})
}
// Manage the state of file system objects
type File struct {
loader YamlLoader
Path string `yaml:"path"`
Owner string `yaml:"owner"`
Group string `yaml:"group"`
Mode string `yaml:"mode"`
Atime time.Time `yaml:"atime",omitempty`
Ctime time.Time `yaml:"ctime",omitempty`
Mtime time.Time `yaml:"mtime",omitempty`
Content string `yaml:"content",omitempty`
FileType FileType `yaml:"filetype"`
State string `yaml:"state"`
}
func NewFile() *File {
return &File{ loader: YamlLoadDecl, FileType: RegularFile }
}
func (f *File) URI() string {
return fmt.Sprintf("file://%s", f.Path)
}
func (f *File) SetURI(uri string) error {
resourceUri, e := url.Parse(uri)
if resourceUri.Scheme == "file" {
f.Path, e = filepath.Abs(filepath.Join(resourceUri.Hostname(), resourceUri.RequestURI()))
} else {
e = fmt.Errorf("%w: %s is not a file", ErrInvalidResourceURI, uri)
}
return e
}
func (f *File) Apply() error {
switch f.State {
case "absent":
removeErr := os.Remove(f.Path)
if removeErr != nil {
return removeErr
}
case "present": {
uid,uidErr := LookupUID(f.Owner)
if uidErr != nil {
return uidErr
}
gid,gidErr := LookupGID(f.Group)
if gidErr != nil {
return gidErr
}
mode,modeErr := strconv.ParseInt(f.Mode, 8, 64)
if modeErr != nil {
return modeErr
}
//e := os.Stat(f.path)
//if os.IsNotExist(e) {
switch f.FileType {
case DirectoryFile:
os.MkdirAll(f.Path, os.FileMode(mode))
default:
fallthrough
case RegularFile:
createdFile,e := os.Create(f.Path)
if e != nil {
return e
}
defer createdFile.Close()
if chmodErr := createdFile.Chmod(os.FileMode(mode)); chmodErr != nil {
return chmodErr
}
_,writeErr := createdFile.Write([]byte(f.Content))
if writeErr != nil {
return writeErr
}
if ! f.Mtime.IsZero() && ! f.Atime.IsZero() {
if chtimesErr := os.Chtimes(f.Path, f.Atime, f.Mtime); chtimesErr != nil {
return chtimesErr
}
}
}
if chownErr := os.Chown(f.Path, uid, gid); chownErr != nil {
return chownErr
}
}
}
return nil
}
func (f *File) LoadDecl(yamlFileResourceDeclaration string) error {
return f.loader(yamlFileResourceDeclaration, f)
}
func (f *File) ResolveId(ctx context.Context) string {
filePath, fileAbsErr := filepath.Abs(f.Path)
if fileAbsErr != nil {
panic(fileAbsErr)
}
f.Path = filePath
return filePath
}
func (f *File) Read(ctx context.Context) ([]byte, error) {
filePath, fileAbsErr := filepath.Abs(f.Path)
if fileAbsErr != nil {
panic(fileAbsErr)
}
f.Path = filePath
info, e := os.Stat(f.Path)
if e != nil {
f.State = "absent"
return nil, e
}
f.Mtime = info.ModTime()
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
f.Atime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
f.Ctime = time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
userId := strconv.Itoa(int(stat.Uid))
groupId := strconv.Itoa(int(stat.Gid))
fileUser, userErr := user.LookupId(userId)
if userErr != nil { //UnknownUserIdError
//panic(userErr)
f.Owner = userId
} else {
f.Owner = fileUser.Name
}
fileGroup, groupErr := user.LookupGroupId(groupId)
if groupErr != nil {
//panic(groupErr)
f.Group = groupId
} else {
f.Group = fileGroup.Name
}
}
f.Mode = fmt.Sprintf("%04o", info.Mode().Perm())
file, fileErr := os.Open(f.Path)
if fileErr != nil {
panic(fileErr)
}
fileContent, ioErr := io.ReadAll(file)
if ioErr != nil {
panic(ioErr)
}
f.Content = string(fileContent)
f.State = "present"
return yaml.Marshal(f)
}
func (f *File) Type() string { return "file" }
func (f *FileType) UnmarshalYAML(value *yaml.Node) error {
var s string
if err := value.Decode(&s); err != nil {
return err
}
switch s {
case string(RegularFile), string(DirectoryFile), string(BlockDeviceFile), string(CharacterDeviceFile), string(NamedPipeFile), string(SymbolicLinkFile), string(SocketFile):
*f = FileType(s)
return nil
default:
return errors.New("invalid FileType value")
}
}