images service v2 port from rackpsace/gophercloud (#171)

* CheckByteArrayEquals funcs

* direct port from rackspace/gophercloud with minor additions to get unit tests passing

* new package for uploading and downloading image data

* updates to make imageservice v2 consistent with the rest of gophercloud/gophercloud

* add image service v2 client
diff --git a/openstack/imageservice/v2/images/requests.go b/openstack/imageservice/v2/images/requests.go
new file mode 100644
index 0000000..32f09ee
--- /dev/null
+++ b/openstack/imageservice/v2/images/requests.go
@@ -0,0 +1,238 @@
+package images
+
+import (
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/pagination"
+)
+
+// ListOptsBuilder allows extensions to add additional parameters to the
+// List request.
+type ListOptsBuilder interface {
+	ToImageListQuery() (string, error)
+}
+
+// ListOpts allows the filtering and sorting of paginated collections through
+// the API. Filtering is achieved by passing in struct field values that map to
+// the server attributes you want to see returned. Marker and Limit are used
+// for pagination.
+//http://developer.openstack.org/api-ref-image-v2.html
+type ListOpts struct {
+	// Integer value for the limit of values to return.
+	Limit int `q:"limit"`
+
+	// UUID of the server at which you want to set a marker.
+	Marker string `q:"marker"`
+
+	Name         string            `q:"name"`
+	Visibility   ImageVisibility   `q:"visibility"`
+	MemberStatus ImageMemberStatus `q:"member_status"`
+	Owner        string            `q:"owner"`
+	Status       ImageStatus       `q:"status"`
+	SizeMin      int64             `q:"size_min"`
+	SizeMax      int64             `q:"size_max"`
+	SortKey      string            `q:"sort_key"`
+	SortDir      string            `q:"sort_dir"`
+	Tag          string            `q:"tag"`
+}
+
+// ToImageListQuery formats a ListOpts into a query string.
+func (opts ListOpts) ToImageListQuery() (string, error) {
+	q, err := gophercloud.BuildQueryString(opts)
+	return q.String(), err
+}
+
+// List implements image list request
+func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
+	url := listURL(c)
+	if opts != nil {
+		query, err := opts.ToImageListQuery()
+		if err != nil {
+			return pagination.Pager{Err: err}
+		}
+		url += query
+	}
+	return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
+		return ImagePage{pagination.LinkedPageBase{PageResult: r}}
+	})
+}
+
+// CreateOptsBuilder describes struct types that can be accepted by the Create call.
+// The CreateOpts struct in this package does.
+type CreateOptsBuilder interface {
+	// Returns value that can be passed to json.Marshal
+	ToImageCreateMap() (map[string]interface{}, error)
+}
+
+// CreateOpts implements CreateOptsBuilder
+type CreateOpts struct {
+	// Name is the name of the new image.
+	Name string `json:"name" required:"true"`
+
+	// Id is the the image ID.
+	ID string `json:"id,omitempty"`
+
+	// Visibility defines who can see/use the image.
+	Visibility *ImageVisibility `json:"visibility,omitempty"`
+
+	// Tags is a set of image tags.
+	Tags []string `json:"tags,omitempty"`
+
+	// ContainerFormat is the format of the
+	// container. Valid values are ami, ari, aki, bare, and ovf.
+	ContainerFormat string `json:"container_format,omitempty"`
+
+	// DiskFormat is the format of the disk. If set,
+	// valid values are ami, ari, aki, vhd, vmdk, raw, qcow2, vdi,
+	// and iso.
+	DiskFormat string `json:"disk_format,omitempty"`
+
+	// MinDisk is the amount of disk space in
+	// GB that is required to boot the image.
+	MinDisk int `json:"min_disk,omitempty"`
+
+	// MinRAM is the amount of RAM in MB that
+	// is required to boot the image.
+	MinRAM int `json:"min_ram,omitempty"`
+
+	// protected is whether the image is not deletable.
+	Protected *bool `json:"protected,omitempty"`
+
+	// properties is a set of properties, if any, that
+	// are associated with the image.
+	Properties map[string]string `json:"-,omitempty"`
+}
+
+// ToImageCreateMap assembles a request body based on the contents of
+// a CreateOpts.
+func (opts CreateOpts) ToImageCreateMap() (map[string]interface{}, error) {
+	b, err := gophercloud.BuildRequestBody(opts, "")
+	if err != nil {
+		return nil, err
+	}
+
+	if opts.Properties != nil {
+		for k, v := range opts.Properties {
+			b[k] = v
+		}
+	}
+	return b, nil
+}
+
+// Create implements create image request
+func Create(client *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
+	b, err := opts.ToImageCreateMap()
+	if err != nil {
+		r.Err = err
+		return r
+	}
+	_, r.Err = client.Post(createURL(client), b, &r.Body, &gophercloud.RequestOpts{OkCodes: []int{201}})
+	return
+}
+
+// Delete implements image delete request
+func Delete(client *gophercloud.ServiceClient, id string) (r DeleteResult) {
+	_, r.Err = client.Delete(deleteURL(client, id), nil)
+	return
+}
+
+// Get implements image get request
+func Get(client *gophercloud.ServiceClient, id string) (r GetResult) {
+	_, r.Err = client.Get(getURL(client, id), &r.Body, nil)
+	return
+}
+
+// Update implements image updated request
+func Update(client *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
+	b, err := opts.ToImageUpdateMap()
+	if err != nil {
+		r.Err = err
+		return r
+	}
+	_, r.Err = client.Patch(updateURL(client, id), b, &r.Body, &gophercloud.RequestOpts{
+		OkCodes:     []int{200},
+		MoreHeaders: map[string]string{"Content-Type": "application/openstack-images-v2.1-json-patch"},
+	})
+	return
+}
+
+// UpdateOptsBuilder implements UpdateOptsBuilder
+type UpdateOptsBuilder interface {
+	// returns value implementing json.Marshaler which when marshaled matches the patch schema:
+	// http://specs.openstack.org/openstack/glance-specs/specs/api/v2/http-patch-image-api-v2.html
+	ToImageUpdateMap() ([]interface{}, error)
+}
+
+// UpdateOpts implements UpdateOpts
+type UpdateOpts []Patch
+
+// ToImageUpdateMap builder
+func (opts UpdateOpts) ToImageUpdateMap() ([]interface{}, error) {
+	m := make([]interface{}, len(opts))
+	for i, patch := range opts {
+		patchJSON := patch.ToImagePatchMap()
+		m[i] = patchJSON
+	}
+	return m, nil
+}
+
+// Patch represents a single update to an existing image. Multiple updates to an image can be
+// submitted at the same time.
+type Patch interface {
+	ToImagePatchMap() map[string]interface{}
+}
+
+// UpdateVisibility updated visibility
+type UpdateVisibility struct {
+	Visibility ImageVisibility
+}
+
+// ToImagePatchMap builder
+func (u UpdateVisibility) ToImagePatchMap() map[string]interface{} {
+	return map[string]interface{}{
+		"op":    "replace",
+		"path":  "/visibility",
+		"value": u.Visibility,
+	}
+}
+
+// ReplaceImageName implements Patch
+type ReplaceImageName struct {
+	NewName string
+}
+
+// ToImagePatchMap builder
+func (r ReplaceImageName) ToImagePatchMap() map[string]interface{} {
+	return map[string]interface{}{
+		"op":    "replace",
+		"path":  "/name",
+		"value": r.NewName,
+	}
+}
+
+// ReplaceImageChecksum implements Patch
+type ReplaceImageChecksum struct {
+	Checksum string
+}
+
+// ReplaceImageChecksum builder
+func (rc ReplaceImageChecksum) ToImagePatchMap() map[string]interface{} {
+	return map[string]interface{}{
+		"op":    "replace",
+		"path":  "/checksum",
+		"value": rc.Checksum,
+	}
+}
+
+// ReplaceImageTags implements Patch
+type ReplaceImageTags struct {
+	NewTags []string
+}
+
+// ToImagePatchMap builder
+func (r ReplaceImageTags) ToImagePatchMap() map[string]interface{} {
+	return map[string]interface{}{
+		"op":    "replace",
+		"path":  "/tags",
+		"value": r.NewTags,
+	}
+}