Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Enhance file watching #612

Merged
merged 4 commits into from
Oct 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
## 2.3.1 (unreleased)

FEATURES:
- Allow `-v` and `--version` to print version [#612](https://github.com/nanobox-io/nanobox/pull/612)
- Make nanobox-server service able to survive reboots [#612](https://github.com/nanobox-io/nanobox/pull/612)
- Add dns alias' to run and dry-run containers [#592](https://github.com/nanobox-io/nanobox/pull/592)
- Support service configuration enhancements [#586](https://github.com/nanobox-io/nanobox/pull/586)
- Support password protected ssh keys [#576](https://github.com/nanobox-io/nanobox/pull/576)[#580](https://github.com/nanobox-io/nanobox/pull/580)[#582](https://github.com/nanobox-io/nanobox/pull/582)
- Untrack vendored files [#567](https://github.com/nanobox-io/nanobox/pull/567)

BUG FIXES:
- Fix linux fs_watch issues [#612](https://github.com/nanobox-io/nanobox/pull/612)
- Refactor local engine configuration [#604](https://github.com/nanobox-io/nanobox/pull/604)
- Fix panic on linux if nfs isn't running [#601](https://github.com/nanobox-io/nanobox/pull/601)
- Handle `nanobox-update` not being in global path [#596](https://github.com/nanobox-io/nanobox/pull/596)
Expand Down
15 changes: 8 additions & 7 deletions commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package commands

import (
"fmt"
"os"
"path/filepath"
"strings"
Expand All @@ -19,7 +20,6 @@ import (
"github.com/nanobox-io/nanobox/util/update"
)

//
var (
// debug mode
debugMode bool
Expand All @@ -30,11 +30,9 @@ var (
// display level trace
displayTraceMode bool

//
internalCommand bool

//
endpoint string
showVersion bool
endpoint string

// NanoboxCmd ...
NanoboxCmd = &cobra.Command{
Expand Down Expand Up @@ -101,9 +99,11 @@ var (
}
},

//
Run: func(ccmd *cobra.Command, args []string) {

if displayDebugMode || showVersion {
fmt.Println(models.VersionString())
return
}
// fall back on default help if no args/flags are passed
ccmd.HelpFunc()(ccmd, args)
},
Expand All @@ -120,6 +120,7 @@ func init() {
NanoboxCmd.PersistentFlags().MarkHidden("internal")
NanoboxCmd.PersistentFlags().BoolVarP(&debugMode, "debug", "", false, "In the event of a failure, drop into debug context")
NanoboxCmd.PersistentFlags().BoolVarP(&displayDebugMode, "verbose", "v", false, "Increases display output and sets level to debug")
NanoboxCmd.PersistentFlags().BoolVarP(&showVersion, "version", "", false, "Print version information and exit")
NanoboxCmd.PersistentFlags().BoolVarP(&displayTraceMode, "trace", "t", false, "Increases display output and sets level to trace")

// log specific flags
Expand Down
4 changes: 2 additions & 2 deletions util/provider/share/share_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,15 @@ func Remove(path string) error {
shareRPC := &ShareRPC{}
err := shareRPC.Remove(req, resp)
if err != nil || !resp.Success {
err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
err = fmt.Errorf("failed to remove share %v %v", err, resp.Message)
}
return err
}

// have the server run the share command
err = server.ClientRun("ShareRPC.Remove", req, resp)
if err != nil || !resp.Success {
err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
err = fmt.Errorf("failed to remove share %v %v", err, resp.Message)
}
return err

Expand Down
4 changes: 2 additions & 2 deletions util/provider/share/share_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,15 @@ func Remove(path string) error {
shareRPC := &ShareRPC{}
err := shareRPC.Remove(req, resp)
if err != nil || !resp.Success {
err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
err = fmt.Errorf("failed to remove share %v %v", err, resp.Message)
}
return err
}

// have the server run the share command
err = server.ClientRun("ShareRPC.Remove", req, resp)
if err != nil || !resp.Success {
err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
err = fmt.Errorf("failed to remove share %v %v", err, resp.Message)
}
return err
}
Expand Down
4 changes: 2 additions & 2 deletions util/provider/share/share_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,15 @@ func Remove(path string) error {
shareRPC := &ShareRPC{}
err := shareRPC.Remove(req, resp)
if err != nil || !resp.Success {
err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
err = fmt.Errorf("failed to remove share %v %v", err, resp.Message)
}
return err
}

// have the server run the share command
err := server.ClientRun("ShareRPC.Remove", req, resp)
if err != nil || !resp.Success {
err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
err = fmt.Errorf("failed to remove share %v %v", err, resp.Message)
}
return err

Expand Down
4 changes: 3 additions & 1 deletion util/service/create_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ After=network.target
[Service]
Type=simple
EnvironmentFile=-/etc/sysconfig/network
ExecStart=%s
[Install]
WantedBy=multi-user.target
`, name, strings.Join(command, " "))

case "upstart":
Expand Down
170 changes: 101 additions & 69 deletions util/watch/notify.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package watch

import (
"fmt"
"os"
"path/filepath"
"strings"
Expand All @@ -9,99 +10,130 @@ import (
"github.com/jcelliott/lumber"
)

type notify struct {
path string
events chan event
watcher *fsnotify.Watcher
type event struct {
file string
error error
fsnotify.Event
}

func newNotifyWatcher(path string) Watcher {
return &notify{
path: path,
events: make(chan event, 10),
}
type Watcher interface {
watch() error
eventChan() chan event
close() error
}

// start the watching process and return an error if we cant watch all the files
func (n *notify) watch() (err error) {
type notify struct {
events chan event // separate event channel so we don't send on all fsnotify.Events
*fsnotify.Watcher
}

n.watcher, err = fsnotify.NewWatcher()
if err != nil {
return err
func newRecursiveWatcher(path string) (Watcher, error) {
folders := subfolders(path)
if len(folders) == 0 {
return nil, fmt.Errorf("No folders to watch")
}

err = filepath.Walk(n.path, n.walkFunc)
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
return nil, err
}

go n.EventHandler()
notifyWatcher := &notify{Watcher: watcher, events: make(chan event)}

return
for i := range folders {
lumber.Info("Adding %s", folders[i])
err = notifyWatcher.Add(folders[i])
if err != nil {
return nil, err
}
}
return notifyWatcher, nil
}

func (n *notify) eventChan() chan event {
return n.events
}
func run(watcher *notify) {
for {
select {
case evnt := <-watcher.Events:
if shouldIgnoreFile(filepath.Base(evnt.Name)) {
continue
}

// close the watcher
func (n *notify) close() error {
return n.watcher.Close()
}
if evnt.Op&fsnotify.Create == fsnotify.Create {
fi, err := os.Stat(evnt.Name)
if err != nil {
// stat ./4913: no such file or directory
lumber.Error("Failed to stat on event - %s", err.Error())
} else if fi.IsDir() {
lumber.Info("Detected dir creation: %s", evnt.Name) // todo: Debug doesn't work
if !shouldIgnoreFile(filepath.Base(evnt.Name)) {
err = watcher.Add(evnt.Name)
if err != nil {
lumber.Error("ERROR - %s", err.Error())
}
}
} else {
lumber.Info("Detected creation: %s", evnt.Name) // todo: Debug doesn't work
watcher.events <- event{file: evnt.Name}
}
}

// add a file that is being walked to the watch system
func (n *notify) walkFunc(path string, info os.FileInfo, err error) error {
if err != nil {
return err
if evnt.Op&fsnotify.Write == fsnotify.Write {
lumber.Info("Detected modification: %s", evnt.Name) // todo: Debug doesn't work
watcher.events <- event{file: evnt.Name}
}

case err := <-watcher.Errors:
lumber.Error("Watcher error encountered - %s", err.Error())
}
}
}

for _, ignoreName := range ignoreFile {
if strings.HasSuffix(path, ignoreName) {
lumber.Info("watcher: skipping %s", path)
if info.IsDir() {
// if the thing we are ignoring is a directory
// subfolders returns a slice of subfolders (recursive), including the folder provided.
func subfolders(path string) (paths []string) {
filepath.Walk(path, func(newPath string, info os.FileInfo, err error) error {
if err != nil {
return err
}

if info.IsDir() {
name := info.Name()
// skip folders that begin with a dot
if shouldIgnoreFile(name) && name != "." && name != ".." {
return filepath.SkipDir
}
// if its not just skip the file
return nil
paths = append(paths, newPath)
}
return nil
})
return paths
}

// shouldIgnoreFile determines if a file should be ignored.
// Ignore files that start with `.` or `_` or end with `~`.
func shouldIgnoreFile(name string) bool {
for i := range ignoreFile {
if name == ignoreFile[i] {
return true
}
}

return n.watcher.Add(path)
return strings.HasPrefix(name, ".") ||
strings.HasPrefix(name, "_") ||
strings.HasSuffix(name, ".swp") ||
strings.HasSuffix(name, "~")
}

func (n *notify) EventHandler() {
for {
select {
case e := <-n.watcher.Events:
lumber.Debug("e: %+v", e)
switch {
case e.Op&fsnotify.Create == fsnotify.Create:
// a new file/folder was created.. add it
n.watcher.Add(e.Name)
// send an event
n.events <- event{file: e.Name}

case e.Op&fsnotify.Write == fsnotify.Write:
// a file was written to.. send the event
n.events <- event{file: e.Name}

case e.Op&fsnotify.Remove == fsnotify.Remove:
// a file was removed. remove it
n.watcher.Remove(e.Name)

case e.Op&fsnotify.Rename == fsnotify.Rename:
// remove from watcher because we no longer need to watch
n.watcher.Remove(e.Name)

case e.Op&fsnotify.Chmod == fsnotify.Chmod:
// ignore anything that is just changing modes
// mostlikely it was just a touch
// start the watching process and return an error if we cant watch all the files
func (n *notify) watch() (err error) {
go run(n)
return nil
}

}
case err := <-n.watcher.Errors:
n.events <- event{file: "", error: err}
}
}
func (n *notify) eventChan() chan event {
return n.events
}

// close the watcher
func (n *notify) close() error {
return n.Close()
}
6 changes: 3 additions & 3 deletions util/watch/notify_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ import (

func TestNotifyFiles(t *testing.T) {
os.MkdirAll("/tmp/nanobox/", 0777)
notifyWatcher := newNotifyWatcher("/tmp/nanobox/")
err := notifyWatcher.watch()
notifyWatcher, err := newRecursiveWatcher("/tmp/nanobox/")
defer notifyWatcher.close()
if err != nil {
t.Fatalf("failed to watch: %s", err)
}
notifyWatcher.watch()

defer notifyWatcher.close()
<-time.After(time.Second)
ioutil.WriteFile("/tmp/nanobox/notify.tmp", []byte("hi"), 0777)

Expand Down
Loading