Repository: gophercloud/gophercloud

Issue:
blockstorage: IsPublic list option is missing
The `IsPublic` query parameter to list private and public volume types is currently missing.
The support for this option is listed in the documentation: https://docs.openstack.org/api-ref/block-storage/v3/index.html#volume-types-types

Only public volume types are listed by default. To query private volume types, the IsPublic parameter must be provided in the OpenStack query.

Hints from issue comments:
@github-actions: Thank you for reporting your first issue! Be sure that we will be looking at it, but keep in mind
this sometimes takes a while.
Please let the maintainers know if your issue has not got enough attention after a few days.
If any doubt, please consult our issue [tutorial](https://github.com/gophercloud/gophercloud/blob/main/docs/contributor-tutorial/step-02-issues.md).

Solution patch:
diff --git a/openstack/blockstorage/v3/volumetypes/requests.go b/openstack/blockstorage/v3/volumetypes/requests.go
index d419d75d9..8849c394f 100644
--- a/openstack/blockstorage/v3/volumetypes/requests.go
+++ b/openstack/blockstorage/v3/volumetypes/requests.go
@@ -73,6 +73,9 @@ type ListOptsBuilder interface {
 // ListOpts holds options for listing Volume Types. It is passed to the volumetypes.List
 // function.
 type ListOpts struct {
+	// Specifies whether the query should include public or private Volume Types.
+	// By default, it queries both types.
+	IsPublic visibility `q:"is_public"`
 	// Comma-separated list of sort keys and optional sort directions in the
 	// form of <key>[:<direction>].
 	Sort string `q:"sort"`
@@ -84,8 +87,22 @@ type ListOpts struct {
 	Marker string `q:"marker"`
 }
 
+type visibility string
+
+const (
+	// VisibilityDefault enables querying both public and private Volume Types.
+	VisibilityDefault visibility = "None"
+	// VisibilityPublic restricts the query to only public Volume Types.
+	VisibilityPublic visibility = "true"
+	// VisibilityPrivate restricts the query to only private Volume Types.
+	VisibilityPrivate visibility = "false"
+)
+
 // ToVolumeTypeListQuery formats a ListOpts into a query string.
 func (opts ListOpts) ToVolumeTypeListQuery() (string, error) {
+	if opts.IsPublic == "" {
+		opts.IsPublic = VisibilityDefault
+	}
 	q, err := gophercloud.BuildQueryString(opts)
 	return q.String(), err
 }
@@ -93,14 +110,14 @@ func (opts ListOpts) ToVolumeTypeListQuery() (string, error) {
 // List returns Volume types.
 func List(client *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager {
 	url := listURL(client)
-
-	if opts != nil {
-		query, err := opts.ToVolumeTypeListQuery()
-		if err != nil {
-			return pagination.Pager{Err: err}
-		}
-		url += query
+	if opts == nil {
+		opts = ListOpts{}
+	}
+	query, err := opts.ToVolumeTypeListQuery()
+	if err != nil {
+		return pagination.Pager{Err: err}
 	}
+	url += query
 
 	return pagination.NewPager(client, url, func(r pagination.PageResult) pagination.Page {
 		return VolumeTypePage{pagination.LinkedPageBase{PageResult: r}}



Test patch:
diff --git a/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go b/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go
index d8f5786bb..a03209f19 100644
--- a/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go
+++ b/openstack/blockstorage/v3/volumetypes/testing/fixtures_test.go
@@ -195,6 +195,18 @@ var UpdatedExtraSpec = map[string]string{
 	"capabilities": "gpu-2",
 }
 
+func HandleListIsPublicParam(t *testing.T, fakeServer th.FakeServer, values map[string]string) {
+	fakeServer.Mux.HandleFunc("/types", func(w http.ResponseWriter, r *http.Request) {
+		th.TestMethod(t, r, "GET")
+		th.TestHeader(t, r, "X-Auth-Token", client.TokenID)
+		th.TestFormValues(t, r, values)
+
+		w.Header().Add("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		fmt.Fprint(w, `{"volume_types": []}`)
+	})
+}
+
 func HandleExtraSpecsListSuccessfully(t *testing.T, fakeServer th.FakeServer) {
 	fakeServer.Mux.HandleFunc("/types/1/extra_specs", func(w http.ResponseWriter, r *http.Request) {
 		th.TestMethod(t, r, "GET")
diff --git a/openstack/blockstorage/v3/volumetypes/testing/requests_test.go b/openstack/blockstorage/v3/volumetypes/testing/requests_test.go
index 920714d72..2ed84d144 100644
--- a/openstack/blockstorage/v3/volumetypes/testing/requests_test.go
+++ b/openstack/blockstorage/v3/volumetypes/testing/requests_test.go
@@ -122,6 +122,46 @@ func TestUpdate(t *testing.T) {
 	th.CheckEquals(t, true, v.IsPublic)
 }
 
+func TestListIsPublicParam(t *testing.T) {
+	fakeServer := th.SetupHTTP()
+	defer fakeServer.Teardown()
+	result := make(map[string]string)
+	HandleListIsPublicParam(t, fakeServer, result)
+
+	// An empty ListOpts should query both public and private volume types by default.
+	result["is_public"] = "None"
+	err := volumetypes.List(client.ServiceClient(fakeServer), nil).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) {
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+
+	err = volumetypes.List(client.ServiceClient(fakeServer), volumetypes.ListOpts{IsPublic: volumetypes.VisibilityDefault}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) {
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+
+	// Specific visibility queries
+	result["is_public"] = "true"
+	err = volumetypes.List(client.ServiceClient(fakeServer), volumetypes.ListOpts{IsPublic: volumetypes.VisibilityPublic}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) {
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+
+	result["is_public"] = "false"
+	err = volumetypes.List(client.ServiceClient(fakeServer), volumetypes.ListOpts{IsPublic: volumetypes.VisibilityPrivate}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) {
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+
+	// Specific visibility query while ensuring other options are still considered.
+	result["is_public"] = "None"
+	result["sort"] = "asc"
+	err = volumetypes.List(client.ServiceClient(fakeServer), volumetypes.ListOpts{IsPublic: volumetypes.VisibilityDefault, Sort: "asc"}).EachPage(context.TODO(), func(_ context.Context, page pagination.Page) (bool, error) {
+		return true, nil
+	})
+	th.AssertNoErr(t, err)
+}
+
 func TestVolumeTypeExtraSpecsList(t *testing.T) {
 	fakeServer := th.SetupHTTP()
 	defer fakeServer.Teardown()