Merge pull request #82 from markpeek/markpeek-deleteimage

Add DeleteImageById and acceptance test for CreateImage and DeleteImageById
diff --git a/acceptance/17-create-delete-image.go b/acceptance/17-create-delete-image.go
new file mode 100644
index 0000000..0ad9b14
--- /dev/null
+++ b/acceptance/17-create-delete-image.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"github.com/rackspace/gophercloud"
+)
+
+var quiet = flag.Bool("quiet", false, "Quiet mode for acceptance testing.  $? non-zero on error though.")
+var rgn = flag.String("r", "DFW", "Datacenter region to interrogate.")
+
+func main() {
+	flag.Parse()
+
+	withIdentity(false, func(auth gophercloud.AccessProvider) {
+		withServerApi(auth, func(servers gophercloud.CloudServersProvider) {
+			log("Creating server")
+			serverId, err := createServer(servers, "", "", "", "")
+			if err != nil {
+				panic(err)
+			}
+			waitForServerState(servers, serverId, "ACTIVE")
+
+			log("Creating image")
+			name := randomString("ACPTTEST", 16)
+			createImage := gophercloud.CreateImage{
+				Name: name,
+			}
+			imageId, err := servers.CreateImage(serverId, createImage)
+			if err != nil {
+				panic(err)
+			}
+			waitForImageState(servers, imageId, "ACTIVE")
+
+			log("Deleting server")
+			servers.DeleteServerById(serverId)
+
+			log("Deleting image")
+			servers.DeleteImageById(imageId)
+
+			log("Done")
+		})
+	})
+}
+
+func log(s string) {
+        if !*quiet {
+                fmt.Println(s)
+        }
+}
diff --git a/acceptance/libargs.go b/acceptance/libargs.go
index 5edb445..b1e79ff 100644
--- a/acceptance/libargs.go
+++ b/acceptance/libargs.go
@@ -1,10 +1,10 @@
 package main
 
 import (
-	"fmt"
-	"os"
 	"crypto/rand"
+	"fmt"
 	"github.com/rackspace/gophercloud"
+	"os"
 	"time"
 )
 
@@ -32,13 +32,13 @@
 // (Implementation from Even Shaw's contribution on
 // http://stackoverflow.com/questions/12771930/what-is-the-fastest-way-to-generate-a-long-random-string-in-go).
 func randomString(prefix string, n int) string {
-    const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
-    var bytes = make([]byte, n)
-    rand.Read(bytes)
-    for i, b := range bytes {
-        bytes[i] = alphanum[b % byte(len(alphanum))]
-    }
-    return prefix + string(bytes)
+	const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+	var bytes = make([]byte, n)
+	rand.Read(bytes)
+	for i, b := range bytes {
+		bytes[i] = alphanum[b%byte(len(alphanum))]
+	}
+	return prefix + string(bytes)
 }
 
 // aSuitableImage finds a minimal image for use in dynamically creating servers.
@@ -127,7 +127,7 @@
 // findAlternativeFlavor locates a flavor to resize a server to.  It is guaranteed to be different
 // than what aSuitableFlavor() returns.  If none could be found, this function will panic.
 func findAlternativeFlavor() string {
-	return "3"  // 1GB image, up from 512MB image
+	return "3" // 1GB image, up from 512MB image
 }
 
 // findAlternativeImage locates an image to resize or rebuild a server with.  It is guaranteed to be
@@ -143,8 +143,8 @@
 	acc, err := gophercloud.Authenticate(
 		provider,
 		gophercloud.AuthOptions{
-			Username: username,
-			Password: password,
+			Username:    username,
+			Password:    password,
 			AllowReauth: ar,
 		},
 	)
@@ -185,4 +185,21 @@
 		time.Sleep(10 * time.Second)
 	}
 	panic("Impossible")
-}
\ No newline at end of file
+}
+
+// waitForImageState polls, every 10 seconds, for a given image to appear in the indicated state.
+// This call will block forever if it never appears in the desired state, so if a timeout is required,
+// make sure to call this function in a goroutine.
+func waitForImageState(api gophercloud.CloudServersProvider, id, state string) error {
+	for {
+		s, err := api.ImageById(id)
+		if err != nil {
+			return err
+		}
+		if s.Status == state {
+			return nil
+		}
+		time.Sleep(10 * time.Second)
+	}
+	panic("Impossible")
+}
diff --git a/images.go b/images.go
index 61a3369..a23e0bb 100644
--- a/images.go
+++ b/images.go
@@ -37,6 +37,20 @@
 	return is, err
 }
 
+func (gsp *genericServersProvider) DeleteImageById(id string) error {
+	err := gsp.context.WithReauth(gsp.access, func() error {
+		url := gsp.endpoint + "/images/" + id
+		_, err := perigee.Request("DELETE", url, perigee.Options{
+			CustomClient: gsp.context.httpClient,
+			MoreHeaders: map[string]string{
+				"X-Auth-Token": gsp.access.AuthToken(),
+			},
+		})
+		return err
+	})
+	return err
+}
+
 // ImageLink provides a reference to a image by either ID or by direct URL.
 // Some services use just the ID, others use just the URL.
 // This structure provides a common means of expressing both in a single field.
diff --git a/interfaces.go b/interfaces.go
index 6786d39..4982937 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -148,6 +148,9 @@
 	// ImageById yields details about a specific image.
 	ImageById(id string) (*Image, error)
 
+	// DeleteImageById will delete the specific image.
+	DeleteImageById(id string) error
+
 	// Flavors
 
 	// ListFlavors yields the list of available system flavors.  This function
diff --git a/servers.go b/servers.go
index 798666a..6ca8d24 100644
--- a/servers.go
+++ b/servers.go
@@ -326,8 +326,7 @@
 			MoreHeaders: map[string]string{
 				"X-Auth-Token": gsp.access.AuthToken(),
 			},
-			OkCodes:     []int{200, 202},
-			DumpReqJson: true,
+			OkCodes: []int{200, 202},
 		})
 	})