From 3cd34f1039bc6de584e94f7ca74e0d9581f6a042 Mon Sep 17 00:00:00 2001 From: creack Date: Thu, 14 Mar 2013 09:27:06 -0700 Subject: [PATCH 1/9] Issue #79, add regexp to the CmdRmi command --- commands.go | 26 ++++++++++++-------------- fs/store.go | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/commands.go b/commands.go index 03125687a..3e03f3d76 100644 --- a/commands.go +++ b/commands.go @@ -356,29 +356,27 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string } // 'docker rmi NAME' removes all images with the name NAME -func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) error { +func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) { cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image") fl_all := cmd.Bool("a", false, "Use IMAGE as a path and remove ALL images in this path") - if err := cmd.Parse(args); err != nil { - cmd.Usage() - return nil - } - if cmd.NArg() < 1 { + fl_regexp := cmd.Bool("r", false, "Use IMAGE as a regular expression instead of an exact name") + if cmd.Parse(args) != nil || cmd.NArg() < 1 { cmd.Usage() return nil } for _, name := range cmd.Args() { - var err error - if *fl_all { + if *fl_regexp { + err = srv.images.RemoveRegexp(name) + } else if *fl_all { err = srv.images.RemoveInPath(name) } else { - image, err := srv.images.Get(name) - if err != nil { - return err - } else if image == nil { - return errors.New("No such image: " + name) + if image, err1 := srv.images.Find(name); err1 != nil { + err = err1 + } else if err1 == nil && image == nil { + err = fmt.Errorf("No such image: %s", name) + } else { + err = srv.images.Remove(image) } - err = srv.images.Remove(image) } if err != nil { return err diff --git a/fs/store.go b/fs/store.go index 1243694fe..7fa5ddb3e 100644 --- a/fs/store.go +++ b/fs/store.go @@ -11,6 +11,7 @@ import ( "os" "path" "path/filepath" + "regexp" "strings" "syscall" "time" @@ -105,6 +106,28 @@ func (store *Store) RemoveInPath(pth string) error { return nil } +// DeleteMatch deletes all images whose name matches `pattern` +func (store *Store) RemoveRegexp(pattern string) error { + // Retrieve all the paths + paths, err := store.Paths() + if err != nil { + return err + } + // Check the pattern on each elements + for _, pth := range paths { + if match, err := regexp.MatchString(pattern, pth); err != nil { + return err + } else if match { + // If there is a match, remove it + fmt.Printf("Match: %s %s\n", pth, pattern) + if err := store.RemoveInPath(pth); err != nil { + return nil + } + } + } + return nil +} + func (store *Store) Remove(img *Image) error { _, err := store.orm.Delete(img) return err From c0b9afbf0109b25d8bd6159a1b9fbb00a0de42d6 Mon Sep 17 00:00:00 2001 From: creack Date: Thu, 14 Mar 2013 18:07:33 -0700 Subject: [PATCH 2/9] Add image removal unit tests --- fs/remove_test.go | 132 ++++++++++++++++++++++++++++++++++++++++++++++ fs/store_test.go | 6 +++ 2 files changed, 138 insertions(+) create mode 100644 fs/remove_test.go diff --git a/fs/remove_test.go b/fs/remove_test.go new file mode 100644 index 000000000..90356a861 --- /dev/null +++ b/fs/remove_test.go @@ -0,0 +1,132 @@ +package fs + +import ( + "github.com/dotcloud/docker/fake" + "testing" +) + +func countImages(store *Store) int { + paths, err := store.Images() + if err != nil { + panic(err) + } + return len(paths) +} + +func TestRemoveInPath(t *testing.T) { + store, err := TempStore("test-remove-in-path") + if err != nil { + t.Fatal(err) + } + defer nuke(store) + archive, err := fake.FakeTar() + if err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 0 { + t.Fatalf("Expected 0 images, %d found", c) + } + images := make([]*Image, 10) + for i := 0; i < 10; i++ { + if image, err := store.Create(archive, nil, "foo", "Testing"); err != nil { + t.Fatal(err) + } else { + images[i] = image + } + } + if c := countImages(store); c != 10 { + t.Fatalf("Expected 10 images, %d found", c) + } + if err := store.RemoveInPath("foo"); err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 0 { + t.Fatalf("Expected 0 images, %d found", c) + } +} + +func TestRemove(t *testing.T) { + store, err := TempStore("test-remove") + if err != nil { + t.Fatal(err) + } + defer nuke(store) + archive, err := fake.FakeTar() + if err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 0 { + t.Fatalf("Expected 0 images, %d found", c) + } + + // Test 1 create / 1 delete + img, err := store.Create(archive, nil, "foo", "Testing") + if err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 1 { + t.Fatalf("Expected 1 images, %d found", c) + } + if err := store.Remove(img); err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 0 { + t.Fatalf("Expected 0 images, %d found", c) + } + + // Test 2 create (same name) / 1 delete + img1, err := store.Create(archive, nil, "foo", "Testing") + if err != nil { + t.Fatal(err) + } + img2, err := store.Create(archive, nil, "foo", "Testing") + if err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 2 { + t.Fatalf("Expected 2 images, %d found", c) + } + if err := store.Remove(img1); err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 1 { + t.Fatalf("Expected 1 images, %d found", c) + } + + // Test delete wrong name + // Note: If we change orm and Delete of non existing return error, we will need to change this test + if err := store.Remove(&Image{Id: "Not_foo", store: img2.store}); err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 1 { + t.Fatalf("Expected 1 images, %d found", c) + } + + // Test delete last one + if err := store.Remove(img2); err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 0 { + t.Fatalf("Expected 0 images, %d found", c) + } +} + +// FIXME: Do more extensive test (ex: with full name, wrong name, with Id, etc) +func TestRemoveRegexp(t *testing.T) { + store, err := TempStore("test-remove-regexp") + if err != nil { + t.Fatal(err) + } + defer nuke(store) + archive, err := fake.FakeTar() + if err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 0 { + t.Fatalf("Expected 0 images, %d found", c) + } + _, err = store.Create(archive, nil, "foo", "Testing") + if err != nil { + t.Fatal(err) + } +} diff --git a/fs/store_test.go b/fs/store_test.go index b8b251670..50f27b7b3 100644 --- a/fs/store_test.go +++ b/fs/store_test.go @@ -11,6 +11,8 @@ import ( "time" ) +// FIXME: Remove the Fake package + func TestInit(t *testing.T) { store, err := TempStore("testinit") if err != nil { @@ -26,6 +28,8 @@ func TestInit(t *testing.T) { } } +// FIXME: Do more extensive tests (ex: create multiple, delete, recreate; +// create multiple, check the amount of images and paths, etc..) func TestCreate(t *testing.T) { store, err := TempStore("testcreate") if err != nil { @@ -229,6 +233,8 @@ func TestMountpointDuplicateRoot(t *testing.T) { } } +// FIXME: Move this function somewhere else to allow go test to run as non-root +// FIXME: Nuke everything afterward (currently leave 2 dir in /tmp) func TestMount(t *testing.T) { store, err := TempStore("test-mount") if err != nil { From cb7819cbc509a78d627584d18a65a4557875b516 Mon Sep 17 00:00:00 2001 From: creack Date: Fri, 15 Mar 2013 00:47:02 -0700 Subject: [PATCH 3/9] Correct the help --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 3e03f3d76..4d8e25206 100644 --- a/commands.go +++ b/commands.go @@ -54,7 +54,7 @@ func (srv *Server) Help() string { {"reset", "Reset changes to a container's filesystem"}, {"restart", "Restart a running container"}, {"rm", "Remove a container"}, - {"rmimage", "Remove an image"}, + {"rmi", "Remove an image"}, {"run", "Run a command in a new container"}, {"start", "Start a stopped container"}, {"stop", "Stop a running container"}, From 4d80958b016b8055c166393cb5e0e69f7a30441e Mon Sep 17 00:00:00 2001 From: creack Date: Fri, 15 Mar 2013 01:28:09 -0700 Subject: [PATCH 4/9] Add entries to the gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 686ac8342..0eb69f772 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ docker/docker .*.swp a.out *.orig +command-line-arguments.test +.flymake* From 82188b9c2cdbdb097e8ffb52e5604a116150c970 Mon Sep 17 00:00:00 2001 From: creack Date: Fri, 15 Mar 2013 02:01:48 -0700 Subject: [PATCH 5/9] Issue #92 Run go test on fs package as non-root --- fs/store_test.go | 59 ------------------------------------ mount_test.go | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 59 deletions(-) create mode 100644 mount_test.go diff --git a/fs/store_test.go b/fs/store_test.go index 50f27b7b3..5d7fa0d0d 100644 --- a/fs/store_test.go +++ b/fs/store_test.go @@ -233,65 +233,6 @@ func TestMountpointDuplicateRoot(t *testing.T) { } } -// FIXME: Move this function somewhere else to allow go test to run as non-root -// FIXME: Nuke everything afterward (currently leave 2 dir in /tmp) -func TestMount(t *testing.T) { - store, err := TempStore("test-mount") - if err != nil { - t.Fatal(err) - } - defer nuke(store) - archive, err := fake.FakeTar() - if err != nil { - t.Fatal(err) - } - image, err := store.Create(archive, nil, "foo", "Testing") - if err != nil { - t.Fatal(err) - } - // Create mount targets - root, err := ioutil.TempDir("", "docker-fs-test") - if err != nil { - t.Fatal(err) - } - rw, err := ioutil.TempDir("", "docker-fs-test") - if err != nil { - t.Fatal(err) - } - mountpoint, err := image.Mount(root, rw) - if err != nil { - t.Fatal(err) - } - defer mountpoint.Umount() - // Mountpoint should be marked as mounted - if !mountpoint.Mounted() { - t.Fatal("Mountpoint not mounted") - } - // There should be one mountpoint registered - if mps, err := image.Mountpoints(); err != nil { - t.Fatal(err) - } else if len(mps) != 1 { - t.Fatal("Wrong number of mountpoints registered (should be %d, not %d)", 1, len(mps)) - } - // Unmounting should work - if err := mountpoint.Umount(); err != nil { - t.Fatal(err) - } - // De-registering should work - if err := mountpoint.Deregister(); err != nil { - t.Fatal(err) - } - if mps, err := image.Mountpoints(); err != nil { - t.Fatal(err) - } else if len(mps) != 0 { - t.Fatal("Wrong number of mountpoints registered (should be %d, not %d)", 0, len(mps)) - } - // General health check - if err := healthCheck(store); err != nil { - t.Fatal(err) - } -} - func TempStore(prefix string) (*Store, error) { dir, err := ioutil.TempDir("", "docker-fs-test-"+prefix) if err != nil { diff --git a/mount_test.go b/mount_test.go new file mode 100644 index 000000000..c3c9c5393 --- /dev/null +++ b/mount_test.go @@ -0,0 +1,79 @@ +package docker + +import ( + "github.com/dotcloud/docker/fake" + "github.com/dotcloud/docker/fs" + "io/ioutil" + "os" + "testing" +) + +// Note: This test is in the docker package because he needs to be run as root +func TestMount(t *testing.T) { + dir, err := ioutil.TempDir("", "docker-fs-test-mount") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + store, err := fs.New(dir) + if err != nil { + t.Fatal(err) + } + + archive, err := fake.FakeTar() + if err != nil { + t.Fatal(err) + } + + image, err := store.Create(archive, nil, "foo", "Testing") + if err != nil { + t.Fatal(err) + } + + // Create mount targets + root, err := ioutil.TempDir("", "docker-fs-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + + rw, err := ioutil.TempDir("", "docker-fs-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(rw) + + mountpoint, err := image.Mount(root, rw) + if err != nil { + t.Fatal(err) + } + defer mountpoint.Umount() + // Mountpoint should be marked as mounted + if !mountpoint.Mounted() { + t.Fatal("Mountpoint not mounted") + } + // There should be one mountpoint registered + if mps, err := image.Mountpoints(); err != nil { + t.Fatal(err) + } else if len(mps) != 1 { + t.Fatal("Wrong number of mountpoints registered (should be %d, not %d)", 1, len(mps)) + } + // Unmounting should work + if err := mountpoint.Umount(); err != nil { + t.Fatal(err) + } + // De-registering should work + if err := mountpoint.Deregister(); err != nil { + t.Fatal(err) + } + if mps, err := image.Mountpoints(); err != nil { + t.Fatal(err) + } else if len(mps) != 0 { + t.Fatal("Wrong number of mountpoints registered (should be %d, not %d)", 0, len(mps)) + } + // General health check + // if err := healthCheck(store); err != nil { + // t.Fatal(err) + // } +} From 2839a590187be2c7ac56f6582bcd897a2a2f1efb Mon Sep 17 00:00:00 2001 From: creack Date: Fri, 15 Mar 2013 02:03:41 -0700 Subject: [PATCH 6/9] Add a test to make sure we are root before starting the tests on docker package --- docker_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker_test.go b/docker_test.go index 003ef5fa9..735e15baf 100644 --- a/docker_test.go +++ b/docker_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "os" "os/exec" + "os/user" "testing" ) @@ -42,6 +43,12 @@ func init() { return } + if usr, err := user.Current(); err != nil { + panic(err) + } else if usr.Uid != "0" { + panic("docker tests needs to be run as root") + } + // Create a temp directory root, err := ioutil.TempDir("", "docker-test") if err != nil { From 030a33aa23ddeb3046fea50d889dd04933276294 Mon Sep 17 00:00:00 2001 From: creack Date: Fri, 15 Mar 2013 02:22:50 -0700 Subject: [PATCH 7/9] Implement more extensive tests on the image deletion --- fs/remove_test.go | 105 ++++++++++++++++++++++++++++++++++++++++++---- fs/store.go | 1 - 2 files changed, 98 insertions(+), 8 deletions(-) diff --git a/fs/remove_test.go b/fs/remove_test.go index 90356a861..d4601f1b7 100644 --- a/fs/remove_test.go +++ b/fs/remove_test.go @@ -1,6 +1,7 @@ package fs import ( + "fmt" "github.com/dotcloud/docker/fake" "testing" ) @@ -26,12 +27,11 @@ func TestRemoveInPath(t *testing.T) { if c := countImages(store); c != 0 { t.Fatalf("Expected 0 images, %d found", c) } - images := make([]*Image, 10) + + // Test 10 create / Delete all for i := 0; i < 10; i++ { - if image, err := store.Create(archive, nil, "foo", "Testing"); err != nil { + if _, err := store.Create(archive, nil, "foo", "Testing"); err != nil { t.Fatal(err) - } else { - images[i] = image } } if c := countImages(store); c != 10 { @@ -43,6 +43,30 @@ func TestRemoveInPath(t *testing.T) { if c := countImages(store); c != 0 { t.Fatalf("Expected 0 images, %d found", c) } + + // Test 10 create / Delete 1 + for i := 0; i < 10; i++ { + if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil { + t.Fatal(err) + } + } + if c := countImages(store); c != 10 { + t.Fatalf("Expected 10 images, %d found", c) + } + if err := store.RemoveInPath("foo-0"); err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 9 { + t.Fatalf("Expected 9 images, %d found", c) + } + + // Delete failure + if err := store.RemoveInPath("Not_Foo"); err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 9 { + t.Fatalf("Expected 9 images, %d found", c) + } } func TestRemove(t *testing.T) { @@ -111,7 +135,6 @@ func TestRemove(t *testing.T) { } } -// FIXME: Do more extensive test (ex: with full name, wrong name, with Id, etc) func TestRemoveRegexp(t *testing.T) { store, err := TempStore("test-remove-regexp") if err != nil { @@ -125,8 +148,76 @@ func TestRemoveRegexp(t *testing.T) { if c := countImages(store); c != 0 { t.Fatalf("Expected 0 images, %d found", c) } - _, err = store.Create(archive, nil, "foo", "Testing") - if err != nil { + + // Test 10 create with different names / Delete all good regexp + for i := 0; i < 10; i++ { + if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil { + t.Fatal(err) + } + } + if c := countImages(store); c != 10 { + t.Fatalf("Expected 10 images, %d found", c) + } + if err := store.RemoveRegexp("foo"); err != nil { t.Fatal(err) } + if c := countImages(store); c != 0 { + t.Fatalf("Expected 0 images, %d found", c) + } + + // Test 10 create with different names / Delete all good regexp globing + for i := 0; i < 10; i++ { + if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil { + t.Fatal(err) + } + } + if c := countImages(store); c != 10 { + t.Fatalf("Expected 10 images, %d found", c) + } + if err := store.RemoveRegexp("foo-*"); err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 0 { + t.Fatalf("Expected 0 images, %d found", c) + } + + // Test 10 create with different names / Delete all bad regexp + for i := 0; i < 10; i++ { + if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil { + t.Fatal(err) + } + } + if c := countImages(store); c != 10 { + t.Fatalf("Expected 10 images, %d found", c) + } + if err := store.RemoveRegexp("oo-*"); err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 0 { + t.Fatalf("Expected 0 images, %d found", c) + } + + // Test 10 create with different names / Delete none strict regexp + for i := 0; i < 10; i++ { + if _, err := store.Create(archive, nil, fmt.Sprintf("foo-%d", i), "Testing"); err != nil { + t.Fatal(err) + } + } + if c := countImages(store); c != 10 { + t.Fatalf("Expected 10 images, %d found", c) + } + if err := store.RemoveRegexp("^oo-"); err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 10 { + t.Fatalf("Expected 10 images, %d found", c) + } + + // Test delete 2 + if err := store.RemoveRegexp("^foo-[1,2]$"); err != nil { + t.Fatal(err) + } + if c := countImages(store); c != 8 { + t.Fatalf("Expected 8 images, %d found", c) + } } diff --git a/fs/store.go b/fs/store.go index 7fa5ddb3e..d7fcb3542 100644 --- a/fs/store.go +++ b/fs/store.go @@ -119,7 +119,6 @@ func (store *Store) RemoveRegexp(pattern string) error { return err } else if match { // If there is a match, remove it - fmt.Printf("Match: %s %s\n", pth, pattern) if err := store.RemoveInPath(pth); err != nil { return nil } From 137af244ee561bd350797c8ce6e159fbd46b6a02 Mon Sep 17 00:00:00 2001 From: creack Date: Fri, 15 Mar 2013 02:33:37 -0700 Subject: [PATCH 8/9] Add the health check to mount_test --- mount_test.go | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/mount_test.go b/mount_test.go index c3c9c5393..98354875e 100644 --- a/mount_test.go +++ b/mount_test.go @@ -8,6 +8,41 @@ import ( "testing" ) +// Look for inconsistencies in a store. +func healthCheck(store *Store) error { + parents := make(map[string]bool) + paths, err := store.Paths() + if err != nil { + return err + } + for _, path := range paths { + images, err := store.List(path) + if err != nil { + return err + } + IDs := make(map[string]bool) // All IDs for this path + for _, img := range images { + // Check for duplicate IDs per path + if _, exists := IDs[img.Id]; exists { + return errors.New(fmt.Sprintf("Duplicate ID: %s", img.Id)) + } else { + IDs[img.Id] = true + } + // Store parent for 2nd pass + if parent := img.Parent; parent != "" { + parents[parent] = true + } + } + } + // Check non-existing parents + for parent := range parents { + if _, exists := parents[parent]; !exists { + return errors.New("Reference to non-registered parent: " + parent) + } + } + return nil +} + // Note: This test is in the docker package because he needs to be run as root func TestMount(t *testing.T) { dir, err := ioutil.TempDir("", "docker-fs-test-mount") @@ -73,7 +108,7 @@ func TestMount(t *testing.T) { t.Fatal("Wrong number of mountpoints registered (should be %d, not %d)", 0, len(mps)) } // General health check - // if err := healthCheck(store); err != nil { - // t.Fatal(err) - // } + if err := healthCheck(store); err != nil { + t.Fatal(err) + } } From b2cf5041cd9a491b40b0a15c152655264f854d6b Mon Sep 17 00:00:00 2001 From: creack Date: Fri, 15 Mar 2013 03:58:43 -0700 Subject: [PATCH 9/9] Change the errors.New() with fmt.Errof --- fs/store_test.go | 5 ++--- mount_test.go | 7 ++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/store_test.go b/fs/store_test.go index 5d7fa0d0d..ceefc5e13 100644 --- a/fs/store_test.go +++ b/fs/store_test.go @@ -1,7 +1,6 @@ package fs import ( - "errors" "fmt" "github.com/dotcloud/docker/fake" "github.com/dotcloud/docker/future" @@ -261,7 +260,7 @@ func healthCheck(store *Store) error { for _, img := range images { // Check for duplicate IDs per path if _, exists := IDs[img.Id]; exists { - return errors.New(fmt.Sprintf("Duplicate ID: %s", img.Id)) + return fmt.Errorf("Duplicate ID: %s", img.Id) } else { IDs[img.Id] = true } @@ -274,7 +273,7 @@ func healthCheck(store *Store) error { // Check non-existing parents for parent := range parents { if _, exists := parents[parent]; !exists { - return errors.New("Reference to non-registered parent: " + parent) + return fmt.Errorf("Reference to non-registered parent: %s", parent) } } return nil diff --git a/mount_test.go b/mount_test.go index 98354875e..f5bd9e09e 100644 --- a/mount_test.go +++ b/mount_test.go @@ -1,6 +1,7 @@ package docker import ( + "fmt" "github.com/dotcloud/docker/fake" "github.com/dotcloud/docker/fs" "io/ioutil" @@ -9,7 +10,7 @@ import ( ) // Look for inconsistencies in a store. -func healthCheck(store *Store) error { +func healthCheck(store *fs.Store) error { parents := make(map[string]bool) paths, err := store.Paths() if err != nil { @@ -24,7 +25,7 @@ func healthCheck(store *Store) error { for _, img := range images { // Check for duplicate IDs per path if _, exists := IDs[img.Id]; exists { - return errors.New(fmt.Sprintf("Duplicate ID: %s", img.Id)) + return fmt.Errorf("Duplicate ID: %s", img.Id) } else { IDs[img.Id] = true } @@ -37,7 +38,7 @@ func healthCheck(store *Store) error { // Check non-existing parents for parent := range parents { if _, exists := parents[parent]; !exists { - return errors.New("Reference to non-registered parent: " + parent) + return fmt.Errorf("Reference to non-registered parent: %s", parent) } } return nil