package file import ( "bytes" "crypto/sha256" "fmt" "io" "io/ioutil" "os" "os/user" "strconv" "syscall" ) var ( DefaultPerm os.FileMode = 0740 ) type ( F struct { Path string Mode os.FileMode IsDir bool // IsDir ensures the resource is a directory. Content []byte Owner string Group string } ) // Is will check if the file resource exists. func (f *F) Is() bool { if f.Path == "" { return false } if f.Mode == 0 { f.Mode = DefaultPerm } stat := syscall.Stat_t{} if err := syscall.Stat(f.Path, &stat); err != nil && os.IsNotExist(err) { return false } else if err != nil { return false } mode := (os.FileMode)(stat.Mode) if mode.IsDir() != f.IsDir { return false } else if f.Mode != 0 && mode.Perm() != f.Mode { return false } if f.Owner != "" { uid, err := getUID(f.Owner) if err != nil { return false } if int(stat.Uid) != uid { return false } } if f.Group != "" { gid, err := getGID(f.Group) if err != nil { return false } if int(stat.Gid) != gid { return false } } want := sha256.New() is := sha256.New() file, err := os.Open(f.Path) if err != nil { return false } defer file.Close() l, err := io.Copy(is, file) if l != int64(len(f.Content)) { return false } want.Write(f.Content) if bytes.Compare(want.Sum(nil), is.Sum(nil)) != 0 { return false } return true } // Ensure writes the file out onto the disk. func (f *F) Ensure() error { if f.Path == "" { return fmt.Errorf("path is empty") } if f.Mode == 0 { f.Mode = DefaultPerm } if f.IsDir && len(f.Content) > 0 { return fmt.Errorf("directory can't have content") } if f.IsDir { if err := os.Mkdir(f.Path, f.Mode); err != nil && !os.IsExist(err) { return fmt.Errorf("could not create directory '%s': %w", f.Path, err) } } else { if err := ioutil.WriteFile(f.Path, f.Content, f.Mode); err != nil { return fmt.Errorf("could not write file '%s': %w", f.Path, err) } } if stat, err := os.Stat(f.Path); err != nil { return fmt.Errorf("could not check resource '%s': %w", f.Path, err) } else if stat.Mode() != f.Mode { if err := os.Chmod(f.Path, os.FileMode(f.Mode)); err != nil { return fmt.Errorf("could not set resource '%s' mode: %w", f.Path, err) } } if f.Owner != "" { uid, err := getUID(f.Owner) if err != nil { return fmt.Errorf("could not get user id '%s': %w", f.Owner, err) } if err := os.Chown(f.Path, uid, -1); err != nil { return fmt.Errorf("could not set owner for '%s': %w", f.Path, err) } } if f.Group != "" { gid, err := getGID(f.Group) if err != nil { return fmt.Errorf("could not get group id '%s': %w", f.Group, err) } if err := os.Chown(f.Path, -1, gid); err != nil { return fmt.Errorf("could not set group for '%s': %w", f.Path, err) } } return nil } func getUID(name string) (int, error) { user, err := user.Lookup(name) if err != nil { return 0, fmt.Errorf("could not get user id: %w", err) } uid, err := strconv.Atoi(user.Uid) if err != nil { return 0, fmt.Errorf("could not get user id: %w", err) } return uid, nil } func getGID(name string) (int, error) { user, err := user.LookupGroup(name) if err != nil { return 0, fmt.Errorf("could not get user id: %w", err) } gid, err := strconv.Atoi(user.Gid) if err != nil { return 0, fmt.Errorf("could not get user id: %w", err) } return gid, nil }