Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[glance]: handle various image struct member types #3159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
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
70 changes: 70 additions & 0 deletions internal/assert/assert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package assert
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These aren't assertions. These are format conversions. jsonhelpers maybe?


import (
"fmt"
"strconv"
)

const errf = "unknown type for %s: %T (value: %v)"

func Int(v any, name string) (int, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NumberOrString?

switch t := v.(type) {
case nil:
return 0, nil
case int:
return t, nil
case int64:
return int(t), nil
case float32:
return int(t), nil
case float64:
return int(t), nil
case string:
return strconv.Atoi(t)
}

return 0, fmt.Errorf(errf, name, v, v)
}

func Int64(v any, name string) (int64, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you made NumberOrString generic on the return type you wouldn't need this function at all.

type ConvertibleNumber interface { int | int64 | float32 | float64 }

switch t := v.(type) {
case nil:
return 0, nil
case int:
return int64(t), nil
case int64:
return t, nil
case float32:
return int64(t), nil
case float64:
return int64(t), nil
case string:
return strconv.ParseInt(t, 10, 64)
}

return 0, fmt.Errorf(errf, name, v, v)
}

func String(v any, name string) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is necessary.

switch t := v.(type) {
case nil:
return "", nil
case string:
return t, nil
}

return "", fmt.Errorf(errf, name, v, v)
}

func Bool(v any, name string) (bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BoolOrString?

switch t := v.(type) {
case nil:
return false, nil
case bool:
return t, nil
case string:
return strconv.ParseBool(t)
}

return false, fmt.Errorf(errf, name, v, v)
}
95 changes: 74 additions & 21 deletions openstack/image/v2/images/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ package images

import (
"encoding/json"
"fmt"
"reflect"
"strings"
"time"

"github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/v2/internal/assert"
"github.com/gophercloud/gophercloud/v2/pagination"
)

Expand All @@ -17,7 +16,7 @@ type Image struct {
ID string `json:"id"`

// Name is the human-readable display name for the image.
Name string `json:"name"`
Name string `json:"-"`

// Status is the image status. It can be "queued" or "active"
// See image/v2/images/type.go
Expand All @@ -29,23 +28,23 @@ type Image struct {

// ContainerFormat is the format of the container.
// Valid values are ami, ari, aki, bare, and ovf.
ContainerFormat string `json:"container_format"`
ContainerFormat string `json:"-"`

// 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"`
DiskFormat string `json:"-"`

// MinDiskGigabytes is the amount of disk space in GB that is required to
// boot the image.
MinDiskGigabytes int `json:"min_disk"`
MinDiskGigabytes int `json:"-"`

// MinRAMMegabytes [optional] is the amount of RAM in MB that is required to
// boot the image.
MinRAMMegabytes int `json:"min_ram"`
MinRAMMegabytes int `json:"-"`

// Owner is the tenant ID the image belongs to.
Owner string `json:"owner"`
Owner string `json:"-"`

// Protected is whether the image is deletable or not.
Protected bool `json:"protected"`
Expand All @@ -54,10 +53,10 @@ type Image struct {
Visibility ImageVisibility `json:"visibility"`

// Hidden is whether the image is listed in default image list or not.
Hidden bool `json:"os_hidden"`
Hidden bool `json:"-"`

// Checksum is the checksum of the data that's associated with the image.
Checksum string `json:"checksum"`
Checksum string `json:"-"`

// SizeBytes is the size of the data that's associated with the image.
SizeBytes int64 `json:"-"`
Expand All @@ -70,7 +69,7 @@ type Image struct {

// Properties is a set of key-value pairs, if any, that are associated with
// the image.
Properties map[string]any
Properties map[string]any `json:"-"`

// CreatedAt is the date when the image has been created.
CreatedAt time.Time `json:"created_at"`
Expand All @@ -88,7 +87,7 @@ type Image struct {
Schema string `json:"schema"`

// VirtualSize is the virtual size of the image
VirtualSize int64 `json:"virtual_size"`
VirtualSize int64 `json:"-"`

// OpenStackImageImportMethods is a slice listing the types of import
// methods available in the cloud.
Expand All @@ -102,7 +101,16 @@ func (r *Image) UnmarshalJSON(b []byte) error {
type tmp Image
var s struct {
tmp
Name any `json:"name"`
ContainerFormat any `json:"container_format"`
DiskFormat any `json:"disk_format"`
MinDiskGigabytes any `json:"min_disk"`
MinRAMMegabytes any `json:"min_ram"`
Owner any `json:"owner"`
Hidden any `json:"os_hidden"`
Checksum any `json:"checksum"`
SizeBytes any `json:"size"`
VirtualSize any `json:"virtual_size"`
OpenStackImageImportMethods string `json:"openstack-image-import-methods"`
OpenStackImageStoreIDs string `json:"openstack-image-store-ids"`
}
Expand All @@ -112,15 +120,48 @@ func (r *Image) UnmarshalJSON(b []byte) error {
}
*r = Image(s.tmp)

switch t := s.SizeBytes.(type) {
case nil:
r.SizeBytes = 0
case float32:
r.SizeBytes = int64(t)
case float64:
r.SizeBytes = int64(t)
default:
return fmt.Errorf("Unknown type for SizeBytes: %v (value: %v)", reflect.TypeOf(t), t)
// some of the values may be nil, or bool as a string format, or int as
// a string format so we need to assert them to the correct type using
// the assert package
r.Name, err = assert.String(s.Name, "Name")
if err != nil {
return err
}
Comment on lines +126 to +129
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? What's the difference between this and regular json unmarshalling with omitempty?

Ditto all other uses of String below.

r.ContainerFormat, err = assert.String(s.ContainerFormat, "ContainerFormat")
if err != nil {
return err
}
r.DiskFormat, err = assert.String(s.DiskFormat, "DiskFormat")
if err != nil {
return err
}
r.MinDiskGigabytes, err = assert.Int(s.MinDiskGigabytes, "MinDiskGigabytes")
if err != nil {
return err
}
r.MinRAMMegabytes, err = assert.Int(s.MinRAMMegabytes, "MinRAMMegabytes")
if err != nil {
return err
}
r.Owner, err = assert.String(s.Owner, "Owner")
if err != nil {
return err
}
r.Hidden, err = assert.Bool(s.Hidden, "Hidden")
if err != nil {
return err
}
r.Checksum, err = assert.String(s.Checksum, "Checksum")
if err != nil {
return err
}
r.SizeBytes, err = assert.Int64(s.SizeBytes, "SizeBytes")
if err != nil {
return err
}
r.VirtualSize, err = assert.Int64(s.VirtualSize, "VirtualSize")
if err != nil {
return err
}

// Bundle all other fields into Properties
Expand All @@ -131,10 +172,22 @@ func (r *Image) UnmarshalJSON(b []byte) error {
}
if resultMap, ok := result.(map[string]any); ok {
delete(resultMap, "self")
delete(resultMap, "name")
delete(resultMap, "container_format")
delete(resultMap, "disk_format")
delete(resultMap, "min_disk")
delete(resultMap, "min_ram")
delete(resultMap, "owner")
delete(resultMap, "os_hidden")
delete(resultMap, "checksum")
delete(resultMap, "size")
delete(resultMap, "virtual_size")
delete(resultMap, "openstack-image-import-methods")
delete(resultMap, "openstack-image-store-ids")
r.Properties = gophercloud.RemainingKeys(Image{}, resultMap)
if m, ok := resultMap["properties"]; ok {
r.Properties["properties"] = m
}
}

if v := strings.FieldsFunc(strings.TrimSpace(s.OpenStackImageImportMethods), splitFunc); len(v) > 0 {
Expand Down
48 changes: 43 additions & 5 deletions openstack/image/v2/images/testing/fixtures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ func HandleImageListSuccessfully(t *testing.T) {
"updated_at": "2015-07-15T11:43:35Z",
"visibility": "public",
"self": "/v2/images/07aa21a9-fa1a-430e-9a33-185be5982431",
"min_disk": 0,
"protected": false,
"min_disk": 4e-06,
"protected": null,
"id": "07aa21a9-fa1a-430e-9a33-185be5982431",
"size": 25165824,
"file": "/v2/images/07aa21a9-fa1a-430e-9a33-185be5982431/file",
"checksum": "eb9139e4942121f22bbc2afc0400b2a4",
"checksum": null,
"owner": "cba624273b8344e59dd1fd18685183b0",
"virtual_size": null,
"min_ram": 0,
Expand All @@ -60,12 +60,12 @@ func HandleImageListSuccessfully(t *testing.T) {
"updated_at": "2015-07-15T11:43:32Z",
"visibility": "public",
"self": "/v2/images/8c64f48a-45a3-4eaa-adff-a8106b6c005b",
"min_disk": 0,
"min_disk": null,
"protected": false,
"id": "8c64f48a-45a3-4eaa-adff-a8106b6c005b",
"file": "/v2/images/8c64f48a-45a3-4eaa-adff-a8106b6c005b/file",
"checksum": "be575a2b939972276ef675752936977f",
"owner": "cba624273b8344e59dd1fd18685183b0",
"owner": null,
"virtual_size": null,
"min_ram": 0,
"schema": "/v2/schemas/image",
Expand Down Expand Up @@ -271,6 +271,44 @@ func HandleImageGetSuccessfully(t *testing.T) {
"self": "/v2/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27",
"min_disk": 0,
"protected": false,
"properties": "{'hypervisor_type': 'qemu', 'architecture': 'x86_64'}",
"id": "1bea47ed-f6a9-463b-b423-14b9cca9ad27",
"file": "/v2/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27/file",
"checksum": "64d7c1cd2b6f60c92c14662941cb7913",
"owner": "5ef70662f8b34079a6eddb8da9d75fe8",
"size": 13167616,
"min_ram": 0,
"schema": "/v2/schemas/image",
"virtual_size": null,
"hw_disk_bus": "scsi",
"hw_disk_bus_model": "virtio-scsi",
"hw_scsi_model": "virtio-scsi"
}`)
})
}

// HandleImageGetSuccessfullyStringBool test setup
func HandleImageGetSuccessfullyStringBool(t *testing.T) {
th.Mux.HandleFunc("/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27", func(w http.ResponseWriter, r *http.Request) {
th.TestMethod(t, r, "GET")
th.TestHeader(t, r, "X-Auth-Token", fakeclient.TokenID)

w.Header().Add("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{
"status": "active",
"name": "cirros-0.3.2-x86_64-disk",
"tags": [],
"container_format": "bare",
"created_at": "2014-05-05T17:15:10Z",
"disk_format": "qcow2",
"updated_at": "2014-05-05T17:15:11Z",
"visibility": "public",
"os_hidden": "False",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could do with a note about what we're simulating here also...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

"self": "/v2/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27",
"min_disk": 0,
"protected": false,
"properties": "{'hypervisor_type': 'qemu', 'architecture': 'x86_64'}",
"id": "1bea47ed-f6a9-463b-b423-14b9cca9ad27",
"file": "/v2/images/1bea47ed-f6a9-463b-b423-14b9cca9ad27/file",
"checksum": "64d7c1cd2b6f60c92c14662941cb7913",
Expand Down
Loading
Loading