From 5411d4ae134346ed2fd5eb14297bdfaafc02c680 Mon Sep 17 00:00:00 2001 From: kayrus Date: Fri, 26 Jul 2024 14:56:09 +0200 Subject: [PATCH] [glance]: handle various image struct member types --- internal/assert/assert.go | 70 ++++++++++++++ openstack/image/v2/images/results.go | 95 +++++++++++++++---- .../image/v2/images/testing/fixtures_test.go | 48 +++++++++- .../image/v2/images/testing/requests_test.go | 64 ++++++++++++- 4 files changed, 250 insertions(+), 27 deletions(-) create mode 100644 internal/assert/assert.go diff --git a/internal/assert/assert.go b/internal/assert/assert.go new file mode 100644 index 0000000000..aa90d07d70 --- /dev/null +++ b/internal/assert/assert.go @@ -0,0 +1,70 @@ +package assert + +import ( + "fmt" + "strconv" +) + +const errf = "unknown type for %s: %T (value: %v)" + +func Int(v any, name string) (int, error) { + 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) { + 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) { + 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) { + 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) +} diff --git a/openstack/image/v2/images/results.go b/openstack/image/v2/images/results.go index 6652f0e791..538d992f70 100644 --- a/openstack/image/v2/images/results.go +++ b/openstack/image/v2/images/results.go @@ -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" ) @@ -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 @@ -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"` @@ -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:"-"` @@ -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"` @@ -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. @@ -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"` } @@ -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 + } + 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 @@ -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 { diff --git a/openstack/image/v2/images/testing/fixtures_test.go b/openstack/image/v2/images/testing/fixtures_test.go index 0c9a7b0318..746b99f8fe 100644 --- a/openstack/image/v2/images/testing/fixtures_test.go +++ b/openstack/image/v2/images/testing/fixtures_test.go @@ -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, @@ -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", @@ -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", + "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", diff --git a/openstack/image/v2/images/testing/requests_test.go b/openstack/image/v2/images/testing/requests_test.go index 8b857871af..16bc4b0c81 100644 --- a/openstack/image/v2/images/testing/requests_test.go +++ b/openstack/image/v2/images/testing/requests_test.go @@ -239,10 +239,72 @@ func TestGetImage(t *testing.T) { "hw_disk_bus": "scsi", "hw_disk_bus_model": "virtio-scsi", "hw_scsi_model": "virtio-scsi", + "properties": "{'hypervisor_type': 'qemu', 'architecture': 'x86_64'}", }, } - th.AssertDeepEquals(t, &expectedImage, actualImage) + th.AssertDeepEquals(t, expectedImage, *actualImage) + th.AssertEquals(t, "{'hypervisor_type': 'qemu', 'architecture': 'x86_64'}", actualImage.Properties["properties"]) +} + +func TestGetImageStringBool(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + HandleImageGetSuccessfullyStringBool(t) + + actualImage, err := images.Get(context.TODO(), fakeclient.ServiceClient(), "1bea47ed-f6a9-463b-b423-14b9cca9ad27").Extract() + + th.AssertNoErr(t, err) + + checksum := "64d7c1cd2b6f60c92c14662941cb7913" + sizeBytes := int64(13167616) + containerFormat := "bare" + diskFormat := "qcow2" + minDiskGigabytes := 0 + minRAMMegabytes := 0 + owner := "5ef70662f8b34079a6eddb8da9d75fe8" + file := actualImage.File + createdDate := actualImage.CreatedAt + lastUpdate := actualImage.UpdatedAt + schema := "/v2/schemas/image" + + expectedImage := images.Image{ + ID: "1bea47ed-f6a9-463b-b423-14b9cca9ad27", + Name: "cirros-0.3.2-x86_64-disk", + Tags: []string{}, + + Status: images.ImageStatusActive, + + ContainerFormat: containerFormat, + DiskFormat: diskFormat, + + MinDiskGigabytes: minDiskGigabytes, + MinRAMMegabytes: minRAMMegabytes, + + Owner: owner, + + Protected: false, + Visibility: images.ImageVisibilityPublic, + Hidden: false, + + Checksum: checksum, + SizeBytes: sizeBytes, + File: file, + CreatedAt: createdDate, + UpdatedAt: lastUpdate, + Schema: schema, + VirtualSize: 0, + Properties: map[string]any{ + "hw_disk_bus": "scsi", + "hw_disk_bus_model": "virtio-scsi", + "hw_scsi_model": "virtio-scsi", + "properties": "{'hypervisor_type': 'qemu', 'architecture': 'x86_64'}", + }, + } + + th.AssertDeepEquals(t, expectedImage, *actualImage) + th.AssertEquals(t, "{'hypervisor_type': 'qemu', 'architecture': 'x86_64'}", actualImage.Properties["properties"]) } func TestDeleteImage(t *testing.T) {