Merge pull request #81 from markpeek/markpeek-image

Image changes to support packer.io
diff --git a/images.go b/images.go
index 38c73e1..61a3369 100644
--- a/images.go
+++ b/images.go
@@ -21,6 +21,22 @@
 	return is, err
 }
 
+func (gsp *genericServersProvider) ImageById(id string) (*Image, error) {
+	var is *Image
+
+	err := gsp.context.WithReauth(gsp.access, func() error {
+		url := gsp.endpoint + "/images/" + id
+		return perigee.Get(url, perigee.Options{
+			CustomClient: gsp.context.httpClient,
+			Results:      &struct{ Image **Image }{&is},
+			MoreHeaders: map[string]string{
+				"X-Auth-Token": gsp.access.AuthToken(),
+			},
+		})
+	})
+	return is, 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 725b602..6786d39 100644
--- a/interfaces.go
+++ b/interfaces.go
@@ -128,6 +128,9 @@
 	// Other providers may reserve the right to act on additional fields.
 	RebuildServer(id string, ns NewServer) (*Server, error)
 
+	// CreateImage will create a new image from the specified server id returning the id of the new image.
+	CreateImage(id string, ci CreateImage) (string, error)
+
 	// Addresses
 
 	// ListAddresses yields the list of available addresses for the server.
@@ -142,6 +145,9 @@
 	// returns full details for each image, if available.
 	ListImages() ([]Image, error)
 
+	// ImageById yields details about a specific image.
+	ImageById(id string) (*Image, error)
+
 	// Flavors
 
 	// ListFlavors yields the list of available system flavors.  This function
diff --git a/reauth.go b/reauth.go
index eb9ac1e..342aca4 100644
--- a/reauth.go
+++ b/reauth.go
@@ -21,3 +21,16 @@
 	}
 	return err
 }
+
+// This is like WithReauth above but returns a perigee Response object
+func (c *Context) ResponseWithReauth(ap AccessProvider, f func() (*perigee.Response, error)) (*perigee.Response, error) {
+	response, err := f()
+	cause, ok := err.(*perigee.UnexpectedResponseCodeError)
+	if ok && cause.Actual == 401 {
+		err = c.reauthHandler(ap)
+		if err == nil {
+			response, err = f()
+		}
+	}
+	return response, err
+}
diff --git a/servers.go b/servers.go
index 6de42b0..798666a 100644
--- a/servers.go
+++ b/servers.go
@@ -6,6 +6,7 @@
 import (
 	"fmt"
 	"github.com/racker/perigee"
+	"strings"
 )
 
 // genericServersProvider structures provide the implementation for generic OpenStack-compatible
@@ -314,6 +315,35 @@
 	return *pas, err
 }
 
+// See the CloudServersProvider interface for details.
+func (gsp *genericServersProvider) CreateImage(id string, ci CreateImage) (string, error) {
+	response, err := gsp.context.ResponseWithReauth(gsp.access, func() (*perigee.Response, error) {
+		ep := fmt.Sprintf("%s/servers/%s/action", gsp.endpoint, id)
+		return perigee.Request("POST", ep, perigee.Options{
+			ReqBody: &struct {
+				CreateImage *CreateImage `json:"createImage"`
+			}{&ci},
+			MoreHeaders: map[string]string{
+				"X-Auth-Token": gsp.access.AuthToken(),
+			},
+			OkCodes:     []int{200, 202},
+			DumpReqJson: true,
+		})
+	})
+
+	if err != nil {
+		return "", err
+	}
+	location, err := response.HttpResponse.Location()
+	if err != nil {
+		return "", err
+	}
+
+	// Return the last element of the location which is the image id
+	locationArr := strings.Split(location.Path, "/")
+	return locationArr[len(locationArr)-1], err
+}
+
 // RaxBandwidth provides measurement of server bandwidth consumed over a given audit interval.
 type RaxBandwidth struct {
 	AuditPeriodEnd    string `json:"audit_period_end"`
@@ -355,7 +385,7 @@
 // The HostId field represents the host your server runs on and
 // can be used to determine this scenario if it is relevant to your application.
 // Note that HostId is unique only per account; it is not globally unique.
-// 
+//
 // Id provides the server's unique identifier.
 // This field must be treated opaquely.
 //
@@ -416,27 +446,27 @@
 // http://docs.rackspace.com/servers/api/v2/cs-devguide/content/ch_extensions.html#ext_status
 // for more details.  It's too lengthy to include here.
 type Server struct {
-	AccessIPv4         string         `json:"accessIPv4"`
-	AccessIPv6         string         `json:"accessIPv6"`
-	Addresses          AddressSet     `json:"addresses"`
-	Created            string         `json:"created"`
-	Flavor             FlavorLink     `json:"flavor"`
-	HostId             string         `json:"hostId"`
-	Id                 string         `json:"id"`
-	Image              ImageLink      `json:"image"`
-	Links              []Link         `json:"links"`
-	Metadata           interface{}    `json:"metadata"`
-	Name               string         `json:"name"`
-	Progress           int            `json:"progress"`
-	Status             string         `json:"status"`
-	TenantId           string         `json:"tenant_id"`
-	Updated            string         `json:"updated"`
-	UserId             string         `json:"user_id"`
-	OsDcfDiskConfig    string         `json:"OS-DCF:diskConfig"`
-	RaxBandwidth       []RaxBandwidth `json:"rax-bandwidth:bandwidth"`
-	OsExtStsPowerState int            `json:"OS-EXT-STS:power_state"`
-	OsExtStsTaskState  string         `json:"OS-EXT-STS:task_state"`
-	OsExtStsVmState    string         `json:"OS-EXT-STS:vm_state"`
+	AccessIPv4         string            `json:"accessIPv4"`
+	AccessIPv6         string            `json:"accessIPv6"`
+	Addresses          AddressSet        `json:"addresses"`
+	Created            string            `json:"created"`
+	Flavor             FlavorLink        `json:"flavor"`
+	HostId             string            `json:"hostId"`
+	Id                 string            `json:"id"`
+	Image              ImageLink         `json:"image"`
+	Links              []Link            `json:"links"`
+	Metadata           map[string]string `json:"metadata"`
+	Name               string            `json:"name"`
+	Progress           int               `json:"progress"`
+	Status             string            `json:"status"`
+	TenantId           string            `json:"tenant_id"`
+	Updated            string            `json:"updated"`
+	UserId             string            `json:"user_id"`
+	OsDcfDiskConfig    string            `json:"OS-DCF:diskConfig"`
+	RaxBandwidth       []RaxBandwidth    `json:"rax-bandwidth:bandwidth"`
+	OsExtStsPowerState int               `json:"OS-EXT-STS:power_state"`
+	OsExtStsTaskState  string            `json:"OS-EXT-STS:task_state"`
+	OsExtStsVmState    string            `json:"OS-EXT-STS:vm_state"`
 }
 
 // NewServerSettings structures record those fields of the Server structure to change
@@ -493,16 +523,17 @@
 // Any Links provided are used to refer to the server specifically by URL.
 // These links are useful for making additional REST calls not explicitly supported by Gorax.
 type NewServer struct {
-	Name            string          `json:"name,omitempty"`
-	ImageRef        string          `json:"imageRef,omitempty"`
-	FlavorRef       string          `json:"flavorRef,omitempty"`
-	Metadata        interface{}     `json:"metadata,omitempty"`
-	Personality     []FileConfig    `json:"personality,omitempty"`
-	Networks        []NetworkConfig `json:"networks,omitempty"`
-	AdminPass       string          `json:"adminPass,omitempty"`
-	Id              string          `json:"id,omitempty"`
-	Links           []Link          `json:"links,omitempty"`
-	OsDcfDiskConfig string          `json:"OS-DCF:diskConfig,omitempty"`
+	Name            string            `json:"name,omitempty"`
+	ImageRef        string            `json:"imageRef,omitempty"`
+	FlavorRef       string            `json:"flavorRef,omitempty"`
+	Metadata        map[string]string `json:"metadata,omitempty"`
+	Personality     []FileConfig      `json:"personality,omitempty"`
+	Networks        []NetworkConfig   `json:"networks,omitempty"`
+	AdminPass       string            `json:"adminPass,omitempty"`
+	KeyPairName     string            `json:"key_name,omitempty"`
+	Id              string            `json:"id,omitempty"`
+	Links           []Link            `json:"links,omitempty"`
+	OsDcfDiskConfig string            `json:"OS-DCF:diskConfig,omitempty"`
 }
 
 // ResizeRequest structures are used internally to encode to JSON the parameters required to resize a server instance.
@@ -513,3 +544,8 @@
 	FlavorRef  string `json:"flavorRef"`
 	DiskConfig string `json:"OS-DCF:diskConfig,omitempty"`
 }
+
+type CreateImage struct {
+	Name     string            `json:"name"`
+	Metadata map[string]string `json:"metadata,omitempty"`
+}