Skip to content
This repository was archived by the owner on Aug 1, 2023. It is now read-only.

List and get Compute image metadata #557

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions openstack/compute/v2/images/fixtures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// +build fixtures

package images

import (
"net/http"
"testing"

th "github.com/rackspace/gophercloud/testhelper"
"github.com/rackspace/gophercloud/testhelper/client"
)

var (
testMetadataMap = map[string]string{"foo": "bar"}
testMetadataOpts = MetadatumOpts{"foo": "bar"}
testMetadataString = `{"metadata": {"foo": "bar"}}`

testMetadataChangeOpts = MetadataOpts{"foo": "baz"}
testMetadataChangeString = `{"metadata": {"foo": "baz"}}`
testMetadataResetString = testMetadataChangeString
testMetadataResetMap = map[string]string{"foo": "baz"}
testMetadataUpdateString = `{"metadata": {"foo": "baz"}}`
testMetadataUpdateMap = map[string]string{"foo": "baz"}

testMetadatumMap = map[string]string{"foo": "bar"}
testMetadatumOpts = MetadatumOpts{"foo": "bar"}
testMetadatumString = `{"meta": {"foo": "bar"}}`

testMetadatumChangeOpts = MetadatumOpts{"foo": "bar"}
testMetadatumChangeString = `{"meta": {"foo": "bar"}}`
testMetadatumCreateMap = map[string]string{"foo": "bar"}
testMetadatumCreateString = testMetadatumChangeString
)

// HandleMetadataGetSuccessfully sets up the test server to respond to a metadata Get request.
func HandleMetadataGetSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/images/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestHeader(t, r, "Accept", "application/json")

w.WriteHeader(http.StatusOK)
w.Write([]byte(testMetadataString))
})
}

// HandleMetadataResetSuccessfully sets up the test server to respond to a metadata Create request.
func HandleMetadataResetSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/images/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, testMetadataResetString)

w.WriteHeader(http.StatusOK)
w.Header().Add("Content-Type", "application/json")
w.Write([]byte(testMetadataResetString))
})
}

// HandleMetadataUpdateSuccessfully sets up the test server to respond to a metadata Update request.
func HandleMetadataUpdateSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/images/1234asdf/metadata", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "POST")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, testMetadataResetString)

w.WriteHeader(http.StatusOK)
w.Header().Add("Content-Type", "application/json")
w.Write([]byte(testMetadataUpdateString))
})
}

// HandleMetadatumGetSuccessfully sets up the test server to respond to a metadatum Get request.
func HandleMetadatumGetSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/images/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestHeader(t, r, "Accept", "application/json")

w.WriteHeader(http.StatusOK)
w.Header().Add("Content-Type", "application/json")
w.Write([]byte(testMetadatumString))
})
}

// HandleMetadatumCreateSuccessfully sets up the test server to respond to a metadatum Create request.
func HandleMetadatumCreateSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/images/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "PUT")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
th.TestJSONRequest(t, r, testMetadatumChangeString)

w.WriteHeader(http.StatusOK)
w.Header().Add("Content-Type", "application/json")
w.Write([]byte(testMetadatumChangeString))
})
}

// HandleMetadatumDeleteSuccessfully sets up the test server to respond to a metadatum Delete request.
func HandleMetadatumDeleteSuccessfully(t *testing.T) {
th.Mux.HandleFunc("/images/1234asdf/metadata/foo", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "DELETE")
th.TestHeader(t, r, "X-Auth-Token", client.TokenID)

w.WriteHeader(http.StatusNoContent)
})
}
121 changes: 120 additions & 1 deletion openstack/compute/v2/images/requests.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package images

import (
"errors"
"fmt"

"github.com/rackspace/gophercloud"
Expand Down Expand Up @@ -59,7 +60,7 @@ func ListDetail(client *gophercloud.ServiceClient, opts ListOptsBuilder) paginat
}

// Get acquires additional detail about a specific image by ID.
// Use ExtractImage() to interpret the result as an openstack Image.
// Use GetResult.Extract() to interpret the result as an openstack Image.
func Get(client *gophercloud.ServiceClient, id string) GetResult {
var result GetResult
_, result.Err = client.Get(getURL(client, id), &result.Body, nil)
Expand Down Expand Up @@ -107,3 +108,121 @@ func IDFromName(client *gophercloud.ServiceClient, name string) (string, error)
return "", fmt.Errorf("Found %d images matching %s", imageCount, name)
}
}

// Metadata requests all the metadata for the given image ID.
func Metadata(client *gophercloud.ServiceClient, id string) GetMetadataResult {
var res GetMetadataResult
_, res.Err = client.Get(metadataURL(client, id), &res.Body, nil)
return res
}

// MetadataOpts is a map that contains key-value pairs.
type MetadataOpts map[string]string

// ToMetadataResetMap assembles a body for a Reset request based on the contents of a MetadataOpts.
func (opts MetadataOpts) ToMetadataResetMap() (map[string]interface{}, error) {
return map[string]interface{}{"metadata": opts}, nil
}

// ToMetadataUpdateMap assembles a body for an Update request based on the contents of a MetadataOpts.
func (opts MetadataOpts) ToMetadataUpdateMap() (map[string]interface{}, error) {
return map[string]interface{}{"metadata": opts}, nil
}

// ResetMetadataOptsBuilder allows extensions to add additional parameters to the
// Reset request.
type ResetMetadataOptsBuilder interface {
ToMetadataResetMap() (map[string]interface{}, error)
}

// ResetMetadata will create multiple new key-value pairs for the given image ID.
// Note: Using this operation will erase any already-existing metadata and create
// the new metadata provided. To keep any already-existing metadata, use the
// UpdateMetadatas or UpdateMetadata function.
func ResetMetadata(client *gophercloud.ServiceClient, id string, opts ResetMetadataOptsBuilder) ResetMetadataResult {
var res ResetMetadataResult
metadata, err := opts.ToMetadataResetMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Put(metadataURL(client, id), metadata, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}

// UpdateMetadataOptsBuilder allows extensions to add additional parameters to the
// Create request.
type UpdateMetadataOptsBuilder interface {
ToMetadataUpdateMap() (map[string]interface{}, error)
}

// UpdateMetadata updates (or creates) all the metadata specified by opts for the given image ID.
// This operation does not affect already-existing metadata that is not specified
// by opts.
func UpdateMetadata(client *gophercloud.ServiceClient, id string, opts UpdateMetadataOptsBuilder) UpdateMetadataResult {
var res UpdateMetadataResult
metadata, err := opts.ToMetadataUpdateMap()
if err != nil {
res.Err = err
return res
}
_, res.Err = client.Post(metadataURL(client, id), metadata, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}

// Metadatum requests the key-value pair with the given key for the given image ID.
func Metadatum(client *gophercloud.ServiceClient, id, key string) GetMetadatumResult {
var res GetMetadatumResult
_, res.Err = client.Request("GET", metadatumURL(client, id, key), gophercloud.RequestOpts{
JSONResponse: &res.Body,
})
return res
}

// MetadatumOpts is a map of length one that contains a key-value pair.
type MetadatumOpts map[string]interface{}

// ToMetadatumCreateMap assembles a body for a Create request based on the contents of a MetadataumOpts.
func (opts MetadatumOpts) ToMetadatumCreateMap() (map[string]interface{}, string, error) {
if len(opts) != 1 {
return nil, "", errors.New("CreateMetadatum operation must have 1 and only 1 key-value pair.")
}
metadatum := map[string]interface{}{"meta": opts}
var key string
for k := range metadatum["meta"].(MetadatumOpts) {
key = k
}
return metadatum, key, nil
}

// MetadatumOptsBuilder allows extensions to add additional parameters to the
// Create request.
type MetadatumOptsBuilder interface {
ToMetadatumCreateMap() (map[string]interface{}, string, error)
}

// CreateMetadatum will create or update the key-value pair with the given key for the given image ID.
func CreateMetadatum(client *gophercloud.ServiceClient, id string, opts MetadatumOptsBuilder) CreateMetadatumResult {
var res CreateMetadatumResult
metadatum, key, err := opts.ToMetadatumCreateMap()
if err != nil {
res.Err = err
return res
}

_, res.Err = client.Put(metadatumURL(client, id, key), metadatum, &res.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return res
}

// DeleteMetadatum will delete the key-value pair with the given key for the given image ID.
func DeleteMetadatum(client *gophercloud.ServiceClient, id, key string) DeleteMetadatumResult {
var res DeleteMetadatumResult
_, res.Err = client.Delete(metadatumURL(client, id, key), nil)
return res
}
70 changes: 70 additions & 0 deletions openstack/compute/v2/images/requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,73 @@ func TestDeleteImage(t *testing.T) {
res := Delete(fake.ServiceClient(), "12345678")
th.AssertNoErr(t, res.Err)
}

func TestGetMetadata(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()

HandleMetadataGetSuccessfully(t)

expected := testMetadataMap
actual, err := Metadata(fake.ServiceClient(), "1234asdf").Extract()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, expected, actual)
}

func TestResetMetadata(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()

HandleMetadataResetSuccessfully(t)

expected := testMetadataResetMap
actual, err := ResetMetadata(fake.ServiceClient(), "1234asdf", testMetadataChangeOpts).Extract()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, expected, actual)
}

func TestUpdateMetadata(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()

HandleMetadataUpdateSuccessfully(t)

expected := testMetadataUpdateMap
actual, err := UpdateMetadata(fake.ServiceClient(), "1234asdf", testMetadataChangeOpts).Extract()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, expected, actual)
}

func TestGetMetadatum(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()

HandleMetadatumGetSuccessfully(t)

expected := testMetadatumMap
actual, err := Metadatum(fake.ServiceClient(), "1234asdf", "foo").Extract()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, expected, actual)
}

func TestCreateMetadatum(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()

HandleMetadatumCreateSuccessfully(t)

expected := testMetadatumCreateMap
actual, err := CreateMetadatum(fake.ServiceClient(), "1234asdf", testMetadatumChangeOpts).Extract()
th.AssertNoErr(t, err)
th.AssertDeepEquals(t, expected, actual)
}

func TestDeleteMetadatum(t *testing.T) {
th.SetupHTTP()
defer th.TeardownHTTP()

HandleMetadatumDeleteSuccessfully(t)

err := DeleteMetadatum(fake.ServiceClient(), "1234asdf", "foo").ExtractErr()
th.AssertNoErr(t, err)
}
Loading