Ignore exist/not-exist errors on plugin remove
| ... | ... |
@@ -639,14 +639,10 @@ func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
|
| 639 | 639 |
return errors.Wrap(err, "error unmounting plugin data") |
| 640 | 640 |
} |
| 641 | 641 |
|
| 642 |
- removeDir := pluginDir + "-removing" |
|
| 643 |
- if err := os.Rename(pluginDir, removeDir); err != nil {
|
|
| 644 |
- return errors.Wrap(err, "error performing atomic remove of plugin dir") |
|
| 642 |
+ if err := atomicRemoveAll(pluginDir); err != nil {
|
|
| 643 |
+ return err |
|
| 645 | 644 |
} |
| 646 | 645 |
|
| 647 |
- if err := system.EnsureRemoveAll(removeDir); err != nil {
|
|
| 648 |
- return errors.Wrap(err, "error removing plugin dir") |
|
| 649 |
- } |
|
| 650 | 646 |
pm.config.Store.Remove(p) |
| 651 | 647 |
pm.config.LogPluginEvent(id, name, "remove") |
| 652 | 648 |
pm.publisher.Publish(EventRemove{Plugin: p.PluginObj})
|
| ... | ... |
@@ -835,3 +831,35 @@ func splitConfigRootFSFromTar(in io.ReadCloser, config *[]byte) io.ReadCloser {
|
| 835 | 835 |
}() |
| 836 | 836 |
return pr |
| 837 | 837 |
} |
| 838 |
+ |
|
| 839 |
+func atomicRemoveAll(dir string) error {
|
|
| 840 |
+ renamed := dir + "-removing" |
|
| 841 |
+ |
|
| 842 |
+ err := os.Rename(dir, renamed) |
|
| 843 |
+ switch {
|
|
| 844 |
+ case os.IsNotExist(err), err == nil: |
|
| 845 |
+ // even if `dir` doesn't exist, we can still try and remove `renamed` |
|
| 846 |
+ case os.IsExist(err): |
|
| 847 |
+ // Some previous remove failed, check if the origin dir exists |
|
| 848 |
+ if e := system.EnsureRemoveAll(renamed); e != nil {
|
|
| 849 |
+ return errors.Wrap(err, "rename target already exists and could not be removed") |
|
| 850 |
+ } |
|
| 851 |
+ if _, err := os.Stat(dir); os.IsNotExist(err) {
|
|
| 852 |
+ // origin doesn't exist, nothing left to do |
|
| 853 |
+ return nil |
|
| 854 |
+ } |
|
| 855 |
+ |
|
| 856 |
+ // attempt to rename again |
|
| 857 |
+ if err := os.Rename(dir, renamed); err != nil {
|
|
| 858 |
+ return errors.Wrap(err, "failed to rename dir for atomic removal") |
|
| 859 |
+ } |
|
| 860 |
+ default: |
|
| 861 |
+ return errors.Wrap(err, "failed to rename dir for atomic removal") |
|
| 862 |
+ } |
|
| 863 |
+ |
|
| 864 |
+ if err := system.EnsureRemoveAll(renamed); err != nil {
|
|
| 865 |
+ os.Rename(renamed, dir) |
|
| 866 |
+ return err |
|
| 867 |
+ } |
|
| 868 |
+ return nil |
|
| 869 |
+} |
| 838 | 870 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,81 @@ |
| 0 |
+package plugin |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io/ioutil" |
|
| 4 |
+ "os" |
|
| 5 |
+ "path/filepath" |
|
| 6 |
+ "testing" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func TestAtomicRemoveAllNormal(t *testing.T) {
|
|
| 10 |
+ dir, err := ioutil.TempDir("", "atomic-remove-with-normal")
|
|
| 11 |
+ if err != nil {
|
|
| 12 |
+ t.Fatal(err) |
|
| 13 |
+ } |
|
| 14 |
+ defer os.RemoveAll(dir) // just try to make sure this gets cleaned up |
|
| 15 |
+ |
|
| 16 |
+ if err := atomicRemoveAll(dir); err != nil {
|
|
| 17 |
+ t.Fatal(err) |
|
| 18 |
+ } |
|
| 19 |
+ |
|
| 20 |
+ if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
|
| 21 |
+ t.Fatalf("dir should be gone: %v", err)
|
|
| 22 |
+ } |
|
| 23 |
+ if _, err := os.Stat(dir + "-removing"); !os.IsNotExist(err) {
|
|
| 24 |
+ t.Fatalf("dir should be gone: %v", err)
|
|
| 25 |
+ } |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+func TestAtomicRemoveAllAlreadyExists(t *testing.T) {
|
|
| 29 |
+ dir, err := ioutil.TempDir("", "atomic-remove-already-exists")
|
|
| 30 |
+ if err != nil {
|
|
| 31 |
+ t.Fatal(err) |
|
| 32 |
+ } |
|
| 33 |
+ defer os.RemoveAll(dir) // just try to make sure this gets cleaned up |
|
| 34 |
+ |
|
| 35 |
+ if err := os.MkdirAll(dir+"-removing", 0755); err != nil {
|
|
| 36 |
+ t.Fatal(err) |
|
| 37 |
+ } |
|
| 38 |
+ defer os.RemoveAll(dir + "-removing") |
|
| 39 |
+ |
|
| 40 |
+ if err := atomicRemoveAll(dir); err != nil {
|
|
| 41 |
+ t.Fatal(err) |
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ if _, err := os.Stat(dir); !os.IsNotExist(err) {
|
|
| 45 |
+ t.Fatalf("dir should be gone: %v", err)
|
|
| 46 |
+ } |
|
| 47 |
+ if _, err := os.Stat(dir + "-removing"); !os.IsNotExist(err) {
|
|
| 48 |
+ t.Fatalf("dir should be gone: %v", err)
|
|
| 49 |
+ } |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+func TestAtomicRemoveAllNotExist(t *testing.T) {
|
|
| 53 |
+ if err := atomicRemoveAll("/not-exist"); err != nil {
|
|
| 54 |
+ t.Fatal(err) |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ dir, err := ioutil.TempDir("", "atomic-remove-already-exists")
|
|
| 58 |
+ if err != nil {
|
|
| 59 |
+ t.Fatal(err) |
|
| 60 |
+ } |
|
| 61 |
+ defer os.RemoveAll(dir) // just try to make sure this gets cleaned up |
|
| 62 |
+ |
|
| 63 |
+ // create the removing dir, but not the "real" one |
|
| 64 |
+ foo := filepath.Join(dir, "foo") |
|
| 65 |
+ removing := dir + "-removing" |
|
| 66 |
+ if err := os.MkdirAll(removing, 0755); err != nil {
|
|
| 67 |
+ t.Fatal(err) |
|
| 68 |
+ } |
|
| 69 |
+ |
|
| 70 |
+ if err := atomicRemoveAll(dir); err != nil {
|
|
| 71 |
+ t.Fatal(err) |
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ if _, err := os.Stat(foo); !os.IsNotExist(err) {
|
|
| 75 |
+ t.Fatalf("dir should be gone: %v", err)
|
|
| 76 |
+ } |
|
| 77 |
+ if _, err := os.Stat(removing); !os.IsNotExist(err) {
|
|
| 78 |
+ t.Fatalf("dir should be gone: %v", err)
|
|
| 79 |
+ } |
|
| 80 |
+} |