aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg/hooks/hooks.go2
-rw-r--r--pkg/hooks/monitor.go54
-rw-r--r--pkg/hooks/monitor_test.go195
3 files changed, 236 insertions, 15 deletions
diff --git a/pkg/hooks/hooks.go b/pkg/hooks/hooks.go
index 6413829ee..66e97d6bd 100644
--- a/pkg/hooks/hooks.go
+++ b/pkg/hooks/hooks.go
@@ -1,4 +1,4 @@
-// Package hooks implements CRI-O's hook handling.
+// Package hooks implements hook configuration and handling for CRI-O and libpod.
package hooks
import (
diff --git a/pkg/hooks/monitor.go b/pkg/hooks/monitor.go
index ba5e0f246..febe3483f 100644
--- a/pkg/hooks/monitor.go
+++ b/pkg/hooks/monitor.go
@@ -2,6 +2,7 @@ package hooks
import (
"context"
+ "os"
"path/filepath"
"github.com/fsnotify/fsnotify"
@@ -11,7 +12,7 @@ import (
// Monitor dynamically monitors hook directories for additions,
// updates, and removals.
//
-// This function write two empty structs to the sync channel: the
+// This function writes two empty structs to the sync channel: the
// first is written after the watchers are established and the second
// when this function exits. The expected usage is:
//
@@ -48,18 +49,47 @@ func (m *Manager) Monitor(ctx context.Context, sync chan<- error) {
for {
select {
case event := <-watcher.Events:
- if event.Op&fsnotify.Remove == fsnotify.Remove {
- ok := m.remove(filepath.Base(event.Name))
- if ok {
- logrus.Debugf("removed hook %s", event.Name)
+ filename := filepath.Base(event.Name)
+ if len(m.directories) <= 1 {
+ if event.Op&fsnotify.Remove == fsnotify.Remove {
+ ok := m.remove(filename)
+ if ok {
+ logrus.Debugf("removed hook %s", event.Name)
+ }
+ } else if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write {
+ err = m.add(event.Name)
+ if err == nil {
+ logrus.Debugf("added hook %s", event.Name)
+ } else if err != ErrNoJSONSuffix {
+ logrus.Errorf("failed to add hook %s: %v", event.Name, err)
+ }
}
- }
- if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write {
- err = m.add(event.Name)
- if err == nil {
- logrus.Debugf("added hook %s", event.Name)
- } else if err != ErrNoJSONSuffix {
- logrus.Errorf("failed to add hook %s: %v", event.Name, err)
+ } else if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Remove == fsnotify.Remove {
+ err = nil
+ found := false
+ for i := len(m.directories) - 1; i >= 0; i-- {
+ path := filepath.Join(m.directories[i], filename)
+ err = m.add(path)
+ if err == nil {
+ found = true
+ logrus.Debugf("(re)added hook %s (triggered activity on %s)", path, event.Name)
+ break
+ } else if err == ErrNoJSONSuffix {
+ found = true
+ break // this is not going to change for fallback directories
+ } else if os.IsNotExist(err) {
+ continue // move on to the next fallback directory
+ } else {
+ found = true
+ logrus.Errorf("failed to (re)add hook %s (triggered by activity on %s): %v", path, event.Name, err)
+ break
+ }
+ }
+ if (found || event.Op&fsnotify.Remove == fsnotify.Remove) && err != nil {
+ ok := m.remove(filename)
+ if ok {
+ logrus.Debugf("removed hook %s (triggered by activity on %s)", filename, event.Name)
+ }
}
}
case <-ctx.Done():
diff --git a/pkg/hooks/monitor_test.go b/pkg/hooks/monitor_test.go
index 78e8d3fe5..b3af4bb43 100644
--- a/pkg/hooks/monitor_test.go
+++ b/pkg/hooks/monitor_test.go
@@ -14,7 +14,7 @@ import (
"golang.org/x/text/language"
)
-func TestMonitorGood(t *testing.T) {
+func TestMonitorOneDirGood(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
dir, err := ioutil.TempDir("", "hooks-test-")
if err != nil {
@@ -92,7 +92,7 @@ func TestMonitorGood(t *testing.T) {
})
t.Run("bad-addition", func(t *testing.T) {
- err = ioutil.WriteFile(jsonPath, []byte("{\"version\": \"-1\"]}"), 0644)
+ err = ioutil.WriteFile(jsonPath, []byte("{\"version\": \"-1\"}"), 0644)
if err != nil {
t.Fatal(err)
}
@@ -118,6 +118,197 @@ func TestMonitorGood(t *testing.T) {
assert.Equal(t, context.Canceled, err)
}
+func TestMonitorTwoDirGood(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ primaryDir, err := ioutil.TempDir("", "hooks-test-primary-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(primaryDir)
+
+ fallbackDir, err := ioutil.TempDir("", "hooks-test-fallback-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(fallbackDir)
+
+ lang, err := language.Parse("und-u-va-posix")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ manager, err := New(ctx, []string{fallbackDir, primaryDir}, []string{}, lang)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ sync := make(chan error, 2)
+ go manager.Monitor(ctx, sync)
+ err = <-sync
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ fallbackPath := filepath.Join(fallbackDir, "a.json")
+ fallbackJSON := []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\"}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}", path))
+ fallbackInjected := &rspec.Hooks{
+ Prestart: []rspec.Hook{
+ {
+ Path: path,
+ },
+ },
+ }
+
+ t.Run("good-fallback-addition", func(t *testing.T) {
+ err = ioutil.WriteFile(fallbackPath, fallbackJSON, 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ time.Sleep(100 * time.Millisecond) // wait for monitor to notice
+
+ config := &rspec.Spec{}
+ _, err = manager.Hooks(config, map[string]string{}, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assert.Equal(t, fallbackInjected, config.Hooks)
+ })
+
+ primaryPath := filepath.Join(primaryDir, "a.json")
+ primaryJSON := []byte(fmt.Sprintf("{\"version\": \"1.0.0\", \"hook\": {\"path\": \"%s\", \"timeout\": 1}, \"when\": {\"always\": true}, \"stages\": [\"prestart\"]}", path))
+ one := 1
+ primaryInjected := &rspec.Hooks{
+ Prestart: []rspec.Hook{
+ {
+ Path: path,
+ Timeout: &one,
+ },
+ },
+ }
+
+ t.Run("good-primary-override", func(t *testing.T) {
+ err = ioutil.WriteFile(primaryPath, primaryJSON, 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ time.Sleep(100 * time.Millisecond) // wait for monitor to notice
+
+ config := &rspec.Spec{}
+ _, err = manager.Hooks(config, map[string]string{}, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assert.Equal(t, primaryInjected, config.Hooks)
+ })
+
+ t.Run("good-fallback-removal", func(t *testing.T) {
+ err = os.Remove(fallbackPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ time.Sleep(100 * time.Millisecond) // wait for monitor to notice
+
+ config := &rspec.Spec{}
+ _, err = manager.Hooks(config, map[string]string{}, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, primaryInjected, config.Hooks) // masked by primary
+ })
+
+ t.Run("good-fallback-restore", func(t *testing.T) {
+ err = ioutil.WriteFile(fallbackPath, fallbackJSON, 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ time.Sleep(100 * time.Millisecond) // wait for monitor to notice
+
+ config := &rspec.Spec{}
+ _, err = manager.Hooks(config, map[string]string{}, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, primaryInjected, config.Hooks) // masked by primary
+ })
+
+ t.Run("bad-primary-addition", func(t *testing.T) {
+ err = ioutil.WriteFile(primaryPath, []byte("{\"version\": \"-1\"}"), 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ time.Sleep(100 * time.Millisecond) // wait for monitor to notice
+
+ config := &rspec.Spec{}
+ expected := config.Hooks
+ _, err = manager.Hooks(config, map[string]string{}, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, expected, config.Hooks)
+ })
+
+ t.Run("good-primary-removal", func(t *testing.T) {
+ err = os.Remove(primaryPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ time.Sleep(100 * time.Millisecond) // wait for monitor to notice
+
+ config := &rspec.Spec{}
+ _, err = manager.Hooks(config, map[string]string{}, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, fallbackInjected, config.Hooks)
+ })
+
+ t.Run("good-non-json-addition", func(t *testing.T) {
+ err = ioutil.WriteFile(filepath.Join(fallbackDir, "README"), []byte("Hello"), 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ time.Sleep(100 * time.Millisecond) // wait for monitor to notice
+
+ config := &rspec.Spec{}
+ _, err = manager.Hooks(config, map[string]string{}, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ assert.Equal(t, fallbackInjected, config.Hooks)
+ })
+
+ t.Run("good-fallback-removal", func(t *testing.T) {
+ err = os.Remove(fallbackPath)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ time.Sleep(100 * time.Millisecond) // wait for monitor to notice
+
+ config := &rspec.Spec{}
+ expected := config.Hooks
+ _, err = manager.Hooks(config, map[string]string{}, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+ assert.Equal(t, expected, config.Hooks)
+ })
+
+ cancel()
+ err = <-sync
+ assert.Equal(t, context.Canceled, err)
+}
+
func TestMonitorBadWatcher(t *testing.T) {
ctx := context.Background()