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

Skip to content

dns: implement shared zones list #3390

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

Merged
merged 1 commit into from
May 22, 2025
Merged
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
35 changes: 35 additions & 0 deletions internal/acceptance/openstack/dns/v2/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,41 @@ func DeleteTransferRequest(t *testing.T, client *gophercloud.ServiceClient, tr *
t.Logf("Deleted zone transfer request: %s", tr.ID)
}

// CreateShare will create a zone share. An error will be returned if the
// zone share was unable to be created.
func CreateShare(t *testing.T, client *gophercloud.ServiceClient, zone *zones.Zone, targetProjectID string) (*zones.ZoneShare, error) {
t.Logf("Attempting to share zone %s with project %s", zone.ID, targetProjectID)

createOpts := zones.ShareZoneOpts{
TargetProjectID: targetProjectID,
}

share, err := zones.Share(context.TODO(), client, zone.ID, createOpts).Extract()
if err != nil {
return share, err
}

t.Logf("Created share for zone: %s", zone.ID)

th.AssertEquals(t, share.ZoneID, zone.ID)
th.AssertEquals(t, share.TargetProjectID, targetProjectID)

return share, nil
}

// UnshareZone will unshare a zone. An error will be returned if the
// zone unshare was unable to be created.
func UnshareZone(t *testing.T, client *gophercloud.ServiceClient, share *zones.ZoneShare) {
t.Logf("Attempting to unshare zone %s with project %s", share.ZoneID, share.TargetProjectID)

err := zones.Unshare(context.TODO(), client, share.ZoneID, share.ID).ExtractErr()
if err != nil {
t.Fatalf("Unable to unshare zone %s: %v", share.ZoneID, err)
}

t.Logf("Unshared zone: %s", share.ZoneID)
}

// DeleteRecordSet will delete a specified record set. A fatal error will occur if
// the record set failed to be deleted. This works best when used as a deferred
// function.
Expand Down
66 changes: 66 additions & 0 deletions internal/acceptance/openstack/dns/v2/shares_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//go:build acceptance || dns || zone_shares

package v2

import (
"context"
"testing"

"github.com/gophercloud/gophercloud/v2/internal/acceptance/clients"
identity "github.com/gophercloud/gophercloud/v2/internal/acceptance/openstack/identity/v3"
"github.com/gophercloud/gophercloud/v2/internal/acceptance/tools"
"github.com/gophercloud/gophercloud/v2/openstack/dns/v2/zones"
th "github.com/gophercloud/gophercloud/v2/testhelper"
)

func TestShareCRD(t *testing.T) {
// Create new project
identityClient, err := clients.NewIdentityV3Client()
th.AssertNoErr(t, err)

project, err := identity.CreateProject(t, identityClient, nil)
th.AssertNoErr(t, err)
defer identity.DeleteProject(t, identityClient, project.ID)

// Create new Zone
client, err := clients.NewDNSV2Client()
th.AssertNoErr(t, err)

zone, err := CreateZone(t, client)
th.AssertNoErr(t, err)
defer DeleteZone(t, client, zone)

// Create a zone share to new tenant
share, err := CreateShare(t, client, zone, project.ID)
th.AssertNoErr(t, err)
tools.PrintResource(t, share)
defer UnshareZone(t, client, share)

// Get the share
getShare, err := zones.GetShare(context.TODO(), client, share.ZoneID, share.ID).Extract()
th.AssertNoErr(t, err)
tools.PrintResource(t, getShare)
th.AssertDeepEquals(t, *share, *getShare)

// List shares
allPages, err := zones.ListShares(client, share.ZoneID, nil).AllPages(context.TODO())
th.AssertNoErr(t, err)

allShares, err := zones.ExtractZoneShares(allPages)
th.AssertNoErr(t, err)
tools.PrintResource(t, allShares)

foundShare := -1
for i, s := range allShares {
tools.PrintResource(t, &s)
if share.ID == s.ID {
foundShare = i
break
}
}
if foundShare == -1 {
t.Fatalf("Share %s not found in list", share.ID)
}

th.AssertDeepEquals(t, *share, allShares[foundShare])
}
48 changes: 45 additions & 3 deletions openstack/dns/v2/zones/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,48 @@ func Delete(ctx context.Context, client *gophercloud.ServiceClient, zoneID strin
return
}

// ListSharesOptsBuilder allows extensions to add additional parameters to the List
// request.
type ListSharesOptsBuilder interface {
ToZoneListSharesHeadersMap() (map[string]string, error)
}

// ListSharesOpts is a structure that holds parameters for listing zone shares.
type ListSharesOpts struct {
AllProjects bool `h:"X-Auth-All-Projects"`
}

// ToZoneListSharesHeadersMap formats a ListSharesOpts into header parameters.
func (opts ListSharesOpts) ToZoneListSharesHeadersMap() (map[string]string, error) {
return gophercloud.BuildHeaders(opts)
}

// ListShares implements a zone list shares request.
func ListShares(client *gophercloud.ServiceClient, zoneID string, opts ListSharesOptsBuilder) pagination.Pager {
var h map[string]string
var err error

if opts != nil {
h, err = opts.ToZoneListSharesHeadersMap()
if err != nil {
return pagination.Pager{Err: err}
}
}

pager := pagination.NewPager(client, sharesBaseURL(client, zoneID), func(r pagination.PageResult) pagination.Page {
return ZoneSharePage{pagination.LinkedPageBase{PageResult: r}}
})
pager.Headers = h
return pager
}

// GetShare returns information about a shared zone, given its ID.
func GetShare(ctx context.Context, client *gophercloud.ServiceClient, zoneID, shareID string) (r ZoneShareResult) {
resp, err := client.Get(ctx, shareURL(client, zoneID, shareID), &r.Body, nil)
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
return
}

// request body for sharing a zone.
type ShareOptsBuilder interface {
ToShareMap() (map[string]interface{}, error)
Expand All @@ -198,14 +240,14 @@ func (opts ShareZoneOpts) ToShareMap() (map[string]interface{}, error) {
}

// Share shares a zone with another project.
func Share(ctx context.Context, client *gophercloud.ServiceClient, zoneID string, opts ShareOptsBuilder) (r gophercloud.ErrResult) {
func Share(ctx context.Context, client *gophercloud.ServiceClient, zoneID string, opts ShareOptsBuilder) (r ZoneShareResult) {
body, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
r.Err = err
return
}

resp, err := client.Post(ctx, zoneShareURL(client, zoneID), body, nil, &gophercloud.RequestOpts{
resp, err := client.Post(ctx, sharesBaseURL(client, zoneID), body, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{201},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
Expand All @@ -214,7 +256,7 @@ func Share(ctx context.Context, client *gophercloud.ServiceClient, zoneID string

// Unshare removes a share for a zone.
func Unshare(ctx context.Context, client *gophercloud.ServiceClient, zoneID, shareID string) (r gophercloud.ErrResult) {
resp, err := client.Delete(ctx, zoneUnshareURL(client, zoneID, shareID), &gophercloud.RequestOpts{
resp, err := client.Delete(ctx, shareURL(client, zoneID, shareID), &gophercloud.RequestOpts{
OkCodes: []int{204},
})
_, r.Header, r.Err = gophercloud.ParseResponse(resp, err)
Expand Down
78 changes: 78 additions & 0 deletions openstack/dns/v2/zones/results.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,81 @@ func (r *Zone) UnmarshalJSON(b []byte) error {

return err
}

// ZoneShare represents a shared zone.
type ZoneShare struct {
// ID uniquely identifies this zone share.
ID string `json:"id"`

// ZoneID is the ID of the zone being shared.
ZoneID string `json:"zone_id"`

// ProjectID is the ID of the project with which the zone is shared.
ProjectID string `json:"project_id"`

// TargetProjectID is the ID of the project with which the zone is shared.
TargetProjectID string `json:"target_project_id"`

// CreatedAt is the date when the zone share was created.
CreatedAt time.Time `json:"-"`

// UpdatedAt is the date when the zone share was last updated.
UpdatedAt time.Time `json:"-"`
}

func (r *ZoneShare) UnmarshalJSON(b []byte) error {
type tmp ZoneShare
var s struct {
tmp
CreatedAt gophercloud.JSONRFC3339MilliNoZ `json:"created_at"`
UpdatedAt gophercloud.JSONRFC3339MilliNoZ `json:"updated_at"`
}

err := json.Unmarshal(b, &s)
if err != nil {
return err
}
*r = ZoneShare(s.tmp)

r.CreatedAt = time.Time(s.CreatedAt)
r.UpdatedAt = time.Time(s.UpdatedAt)

return nil
}

// ZoneShareResult is the result of a GetZoneShare request.
type ZoneShareResult struct {
gophercloud.Result
}

// Extract interprets a GetResult, CreateResult or UpdateResult as a Zone.
// An error is returned if the original call or the extraction failed.
func (r ZoneShareResult) Extract() (*ZoneShare, error) {
var s *ZoneShare
err := r.ExtractInto(&s)
return s, err
}

// ZoneSharePage is a single page of ZoneShare results.
type ZoneSharePage struct {
pagination.LinkedPageBase
}

// IsEmpty returns true if the page contains no results.
func (r ZoneSharePage) IsEmpty() (bool, error) {
if r.StatusCode == 204 {
return true, nil
}

s, err := ExtractZoneShares(r)
return len(s) == 0, err
}

// ExtractZoneShares extracts a slice of ZoneShares from a List result.
func ExtractZoneShares(r pagination.Page) ([]ZoneShare, error) {
var s struct {
ZoneShares []ZoneShare `json:"shared_zones"`
}
err := (r.(ZoneSharePage)).ExtractInto(&s)
return s.ZoneShares, err
}
54 changes: 54 additions & 0 deletions openstack/dns/v2/zones/testing/fixtures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,57 @@ func HandleDeleteSuccessfully(t *testing.T, fakeServer th.FakeServer) {
fmt.Fprint(w, DeleteZoneResponse)
})
}

// ShareZoneResponse is a sample response to share a zone.
const ShareZoneResponse = `
{
"id": "fd40b017-bf97-461c-8d30-d4e922b28edd",
"zone_id": "a3365b47-ee93-43ad-9a60-2b2ca96b1898",
"project_id": "16ade46c85a1435bb86d9138d37da57e",
"target_project_id": "232e37df46af42089710e2ae39111c2f",
"created_at": "2022-11-30T22:20:27.000000",
"updated_at": null,
"links": {
"self": "http://127.0.0.1:60053/v2/zones/a3365b47-ee93-43ad-9a60-2b2ca96b1898/shares/fd40b017-bf97-461c-8d30-d4e922b28edd",
"zone": "http://127.0.0.1:60053/v2/zones/a3365b47-ee93-43ad-9a60-2b2ca96b1898"
}
}
`

// ShareZoneCreatedAt is the expected created at time for the shared zone
var ShareZoneCreatedAt, _ = time.Parse(gophercloud.RFC3339MilliNoZ, "2022-11-30T22:20:27.000000")

// ShareZone is the expected shared zone
var ShareZone = zones.ZoneShare{
ID: "fd40b017-bf97-461c-8d30-d4e922b28edd",
ZoneID: "a3365b47-ee93-43ad-9a60-2b2ca96b1898",
ProjectID: "16ade46c85a1435bb86d9138d37da57e",
TargetProjectID: "232e37df46af42089710e2ae39111c2f",
CreatedAt: ShareZoneCreatedAt,
}

// ListSharesResponse is a sample response to list zone shares.
const ListSharesResponse = `
{
"shared_zones": [
{
"id": "fd40b017-bf97-461c-8d30-d4e922b28edd",
"zone_id": "a3365b47-ee93-43ad-9a60-2b2ca96b1898",
"project_id": "16ade46c85a1435bb86d9138d37da57e",
"target_project_id": "232e37df46af42089710e2ae39111c2f",
"created_at": "2022-11-30T22:20:27.000000",
"updated_at": null,
"links": {
"self": "http://127.0.0.1:60053/v2/zones/a3365b47-ee93-43ad-9a60-2b2ca96b1898/shares/fd40b017-bf97-461c-8d30-d4e922b28edd",
"zone": "http://127.0.0.1:60053/v2/zones/a3365b47-ee93-43ad-9a60-2b2ca96b1898"
}
}
],
"links": {
"self": "http://127.0.0.1:60053/v2/zones/a3365b47-ee93-43ad-9a60-2b2ca96b1898/shares"
}
}
`

// ListZoneShares is the expected list of shared zones
var ListZoneShares = []zones.ZoneShare{ShareZone}
28 changes: 27 additions & 1 deletion openstack/dns/v2/zones/testing/requests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package testing
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"testing"
Expand Down Expand Up @@ -127,11 +128,14 @@ func TestShare(t *testing.T) {
th.CheckDeepEquals(t, expectedBody, reqBody)

w.WriteHeader(http.StatusCreated)
w.Header().Add("Content-Type", "application/json")
fmt.Fprint(w, ShareZoneResponse)
})

opts := zones.ShareZoneOpts{TargetProjectID: "project-id"}
err := zones.Share(context.TODO(), client.ServiceClient(fakeServer), "zone-id", opts).ExtractErr()
zone, err := zones.Share(context.TODO(), client.ServiceClient(fakeServer), "zone-id", opts).Extract()
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ShareZone, *zone)
}

func TestUnshare(t *testing.T) {
Expand All @@ -146,3 +150,25 @@ func TestUnshare(t *testing.T) {
err := zones.Unshare(context.TODO(), client.ServiceClient(fakeServer), "zone-id", "share-id").ExtractErr()
th.AssertNoErr(t, err)
}

func TestListShares(t *testing.T) {
fakeServer := th.SetupHTTP()
defer fakeServer.Teardown()

fakeServer.Mux.HandleFunc("/zones/zone-id/shares", func(w http.ResponseWriter, r *http.Request) {
th.AssertEquals(t, r.Method, "GET")
th.AssertEquals(t, "true", r.Header.Get("X-Auth-All-Projects"))

w.Header().Add("Content-Type", "application/json")
fmt.Fprint(w, ListSharesResponse)
})

opts := zones.ListSharesOpts{
AllProjects: true,
}
pages, err := zones.ListShares(client.ServiceClient(fakeServer), "zone-id", opts).AllPages(context.TODO())
th.AssertNoErr(t, err)
actual, err := zones.ExtractZoneShares(pages)
th.AssertNoErr(t, err)
th.CheckDeepEquals(t, ListZoneShares, actual)
}
10 changes: 5 additions & 5 deletions openstack/dns/v2/zones/urls.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ func zoneURL(c *gophercloud.ServiceClient, zoneID string) string {
return c.ServiceURL("zones", zoneID)
}

// zoneShareURL returns the URL for sharing a zone.
func zoneShareURL(c *gophercloud.ServiceClient, zoneID string) string {
// sharesBaseURL returns the URL for shared zones.
func sharesBaseURL(c *gophercloud.ServiceClient, zoneID string) string {
return c.ServiceURL("zones", zoneID, "shares")
}

// zoneUnshareURL returns the URL for unsharing a zone.
func zoneUnshareURL(c *gophercloud.ServiceClient, zoneID, shareID string) string {
return c.ServiceURL("zones", zoneID, "shares", shareID)
// shareURL returns the URL for a shared zone.
func shareURL(c *gophercloud.ServiceClient, zoneID, sharedZoneID string) string {
return c.ServiceURL("zones", zoneID, "shares", sharedZoneID)
}
Loading