Merge pull request #65 from rackspace/rebuild-server-for-real

Implement Rebuild server functionality.
diff --git a/acceptance/13-rebuild-server.go b/acceptance/13-rebuild-server.go
new file mode 100644
index 0000000..12aed26
--- /dev/null
+++ b/acceptance/13-rebuild-server.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+	"fmt"
+	"flag"
+	"github.com/rackspace/gophercloud"
+)
+
+var quiet = flag.Bool("quiet", false, "Quiet mode, for acceptance testing.  $? still indicates errors though.")
+
+func main() {
+	flag.Parse()
+	withIdentity(false, func(acc gophercloud.AccessProvider) {
+		withServerApi(acc, func(servers gophercloud.CloudServersProvider) {
+			log("Creating server")
+			id, err := createServer(servers, "", "", "", "")
+			if err != nil {
+				panic(err)
+			}
+			waitForServerState(servers, id, "ACTIVE")
+			defer servers.DeleteServerById(id)
+
+			log("Rebuilding server")
+			newDetails, err := servers.RebuildServer(id, gophercloud.NewServer{
+				Name: randomString("ACPTTEST", 32),
+				ImageRef: findAlternativeImage(),
+				FlavorRef: findAlternativeFlavor(),
+				AdminPass: randomString("", 16),
+			})
+			if err != nil {
+				panic(err)
+			}
+			waitForServerState(servers, newDetails.Id, "ACTIVE")
+
+			log("Done")
+		})
+	})
+}
+
+func log(s string) {
+	if !*quiet {
+		fmt.Println(s)
+	}
+}
diff --git a/acceptance/libargs.go b/acceptance/libargs.go
index 932e61e..5edb445 100644
--- a/acceptance/libargs.go
+++ b/acceptance/libargs.go
@@ -130,6 +130,12 @@
 	return "3"  // 1GB image, up from 512MB image
 }
 
+// findAlternativeImage locates an image to resize or rebuild a server with.  It is guaranteed to be
+// different than what aSuitableImage() returns.  If none could be found, this function will panic.
+func findAlternativeImage() string {
+	return "c6f9c411-e708-4952-91e5-62ded5ea4d3e"
+}
+
 // withIdentity authenticates the user against the provider's identity service, and provides an
 // accessor for additional services.
 func withIdentity(ar bool, f func(gophercloud.AccessProvider)) {
diff --git a/interfaces.go b/interfaces.go
index c8a6f96..626e531 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -117,6 +117,17 @@
 	// This function returns the new set of server details if successful.
 	UpdateServer(id string, newValues NewServerSettings) (*Server, error)
 
+	// RebuildServer reprovisions a server to the specifications given by the
+	// NewServer structure.  The following fields are guaranteed to be recognized:
+	//
+	//		Name (required)				AccessIPv4
+	//		imageRef (required)			AccessIPv6
+	//		AdminPass (required)		Metadata
+	//		Personality
+	//
+	// Other providers may reserve the right to act on additional fields.
+	RebuildServer(id string, ns NewServer) (*Server, error)
+
 	// Images
 
 	// ListImages yields the list of available operating system images.  This function
diff --git a/servers.go b/servers.go
index e127e05..85f49ff 100644
--- a/servers.go
+++ b/servers.go
@@ -267,6 +267,27 @@
 	return svr, err
 }
 
+// See the CloudServersProvider interface for details.
+func (gsp *genericServersProvider) RebuildServer (id string, ns NewServer) (*Server, error) {
+	var s *Server
+
+	err := gsp.context.WithReauth(gsp.access, func() error {
+		ep := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
+		return perigee.Post(ep, perigee.Options{
+			ReqBody: &struct {
+				Rebuild *NewServer `json:"rebuild"`
+			}{&ns},
+			Results: &struct{ Server **Server }{&s},
+			MoreHeaders: map[string]string{
+				"X-Auth-Token": gsp.access.AuthToken(),
+			},
+			OkCodes: []int{202},
+		})
+	})
+
+	return s, err
+}
+
 // RaxBandwidth provides measurement of server bandwidth consumed over a given audit interval.
 type RaxBandwidth struct {
 	AuditPeriodEnd    string `json:"audit_period_end"`