mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-04 07:49:35 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			239 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package pagination
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"reflect"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/rackspace/gophercloud"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist.
 | 
						|
	ErrPageNotAvailable = errors.New("The requested page does not exist.")
 | 
						|
)
 | 
						|
 | 
						|
// Page must be satisfied by the result type of any resource collection.
 | 
						|
// It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated.
 | 
						|
// Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs,
 | 
						|
// instead.
 | 
						|
// Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type
 | 
						|
// will need to implement.
 | 
						|
type Page interface {
 | 
						|
 | 
						|
	// NextPageURL generates the URL for the page of data that follows this collection.
 | 
						|
	// Return "" if no such page exists.
 | 
						|
	NextPageURL() (string, error)
 | 
						|
 | 
						|
	// IsEmpty returns true if this Page has no items in it.
 | 
						|
	IsEmpty() (bool, error)
 | 
						|
 | 
						|
	// GetBody returns the Page Body. This is used in the `AllPages` method.
 | 
						|
	GetBody() interface{}
 | 
						|
}
 | 
						|
 | 
						|
// Pager knows how to advance through a specific resource collection, one page at a time.
 | 
						|
type Pager struct {
 | 
						|
	client *gophercloud.ServiceClient
 | 
						|
 | 
						|
	initialURL string
 | 
						|
 | 
						|
	createPage func(r PageResult) Page
 | 
						|
 | 
						|
	Err error
 | 
						|
 | 
						|
	// Headers supplies additional HTTP headers to populate on each paged request.
 | 
						|
	Headers map[string]string
 | 
						|
}
 | 
						|
 | 
						|
// NewPager constructs a manually-configured pager.
 | 
						|
// Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page.
 | 
						|
func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager {
 | 
						|
	return Pager{
 | 
						|
		client:     client,
 | 
						|
		initialURL: initialURL,
 | 
						|
		createPage: createPage,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// WithPageCreator returns a new Pager that substitutes a different page creation function. This is
 | 
						|
// useful for overriding List functions in delegation.
 | 
						|
func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager {
 | 
						|
	return Pager{
 | 
						|
		client:     p.client,
 | 
						|
		initialURL: p.initialURL,
 | 
						|
		createPage: createPage,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (p Pager) fetchNextPage(url string) (Page, error) {
 | 
						|
	resp, err := Request(p.client, p.Headers, url)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	remembered, err := PageResultFrom(resp)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return p.createPage(remembered), nil
 | 
						|
}
 | 
						|
 | 
						|
// EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function.
 | 
						|
// Return "false" from the handler to prematurely stop iterating.
 | 
						|
func (p Pager) EachPage(handler func(Page) (bool, error)) error {
 | 
						|
	if p.Err != nil {
 | 
						|
		return p.Err
 | 
						|
	}
 | 
						|
	currentURL := p.initialURL
 | 
						|
	for {
 | 
						|
		currentPage, err := p.fetchNextPage(currentURL)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		empty, err := currentPage.IsEmpty()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if empty {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		ok, err := handler(currentPage)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if !ok {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		currentURL, err = currentPage.NextPageURL()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if currentURL == "" {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// AllPages returns all the pages from a `List` operation in a single page,
 | 
						|
// allowing the user to retrieve all the pages at once.
 | 
						|
func (p Pager) AllPages() (Page, error) {
 | 
						|
	// pagesSlice holds all the pages until they get converted into as Page Body.
 | 
						|
	var pagesSlice []interface{}
 | 
						|
	// body will contain the final concatenated Page body.
 | 
						|
	var body reflect.Value
 | 
						|
 | 
						|
	// Grab a test page to ascertain the page body type.
 | 
						|
	testPage, err := p.fetchNextPage(p.initialURL)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	// Store the page type so we can use reflection to create a new mega-page of
 | 
						|
	// that type.
 | 
						|
	pageType := reflect.TypeOf(testPage)
 | 
						|
 | 
						|
	// if it's a single page, just return the testPage (first page)
 | 
						|
	if _, found := pageType.FieldByName("SinglePageBase"); found {
 | 
						|
		return testPage, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Switch on the page body type. Recognized types are `map[string]interface{}`,
 | 
						|
	// `[]byte`, and `[]interface{}`.
 | 
						|
	switch testPage.GetBody().(type) {
 | 
						|
	case map[string]interface{}:
 | 
						|
		// key is the map key for the page body if the body type is `map[string]interface{}`.
 | 
						|
		var key string
 | 
						|
		// Iterate over the pages to concatenate the bodies.
 | 
						|
		err := p.EachPage(func(page Page) (bool, error) {
 | 
						|
			b := page.GetBody().(map[string]interface{})
 | 
						|
			for k := range b {
 | 
						|
				// If it's a linked page, we don't want the `links`, we want the other one.
 | 
						|
				if !strings.HasSuffix(k, "links") {
 | 
						|
					key = k
 | 
						|
				}
 | 
						|
			}
 | 
						|
			switch keyType := b[key].(type) {
 | 
						|
			case map[string]interface{}:
 | 
						|
				pagesSlice = append(pagesSlice, keyType)
 | 
						|
			case []interface{}:
 | 
						|
				pagesSlice = append(pagesSlice, b[key].([]interface{})...)
 | 
						|
			default:
 | 
						|
				return false, fmt.Errorf("Unsupported page body type: %+v", keyType)
 | 
						|
			}
 | 
						|
			return true, nil
 | 
						|
		})
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		// Set body to value of type `map[string]interface{}`
 | 
						|
		body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice)))
 | 
						|
		body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice))
 | 
						|
	case []byte:
 | 
						|
		// Iterate over the pages to concatenate the bodies.
 | 
						|
		err := p.EachPage(func(page Page) (bool, error) {
 | 
						|
			b := page.GetBody().([]byte)
 | 
						|
			pagesSlice = append(pagesSlice, b)
 | 
						|
			// seperate pages with a comma
 | 
						|
			pagesSlice = append(pagesSlice, []byte{10})
 | 
						|
			return true, nil
 | 
						|
		})
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		if len(pagesSlice) > 0 {
 | 
						|
			// Remove the trailing comma.
 | 
						|
			pagesSlice = pagesSlice[:len(pagesSlice)-1]
 | 
						|
		}
 | 
						|
		var b []byte
 | 
						|
		// Combine the slice of slices in to a single slice.
 | 
						|
		for _, slice := range pagesSlice {
 | 
						|
			b = append(b, slice.([]byte)...)
 | 
						|
		}
 | 
						|
		// Set body to value of type `bytes`.
 | 
						|
		body = reflect.New(reflect.TypeOf(b)).Elem()
 | 
						|
		body.SetBytes(b)
 | 
						|
	case []interface{}:
 | 
						|
		// Iterate over the pages to concatenate the bodies.
 | 
						|
		err := p.EachPage(func(page Page) (bool, error) {
 | 
						|
			b := page.GetBody().([]interface{})
 | 
						|
			pagesSlice = append(pagesSlice, b...)
 | 
						|
			return true, nil
 | 
						|
		})
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		// Set body to value of type `[]interface{}`
 | 
						|
		body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice))
 | 
						|
		for i, s := range pagesSlice {
 | 
						|
			body.Index(i).Set(reflect.ValueOf(s))
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		return nil, fmt.Errorf("Page body has unrecognized type.")
 | 
						|
	}
 | 
						|
 | 
						|
	// Each `Extract*` function is expecting a specific type of page coming back,
 | 
						|
	// otherwise the type assertion in those functions will fail. pageType is needed
 | 
						|
	// to create a type in this method that has the same type that the `Extract*`
 | 
						|
	// function is expecting and set the Body of that object to the concatenated
 | 
						|
	// pages.
 | 
						|
	page := reflect.New(pageType)
 | 
						|
	// Set the page body to be the concatenated pages.
 | 
						|
	page.Elem().FieldByName("Body").Set(body)
 | 
						|
	// Set any additional headers that were pass along. The `objectstorage` pacakge,
 | 
						|
	// for example, passes a Content-Type header.
 | 
						|
	h := make(http.Header)
 | 
						|
	for k, v := range p.Headers {
 | 
						|
		h.Add(k, v)
 | 
						|
	}
 | 
						|
	page.Elem().FieldByName("Header").Set(reflect.ValueOf(h))
 | 
						|
	// Type assert the page to a Page interface so that the type assertion in the
 | 
						|
	// `Extract*` methods will work.
 | 
						|
	return page.Elem().Interface().(Page), err
 | 
						|
}
 |