forked from TrueCloudLab/restic
190 lines
5.4 KiB
Go
190 lines
5.4 KiB
Go
package storage
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
)
|
|
|
|
const (
|
|
headerAccept = "Accept"
|
|
headerEtag = "Etag"
|
|
headerPrefer = "Prefer"
|
|
headerXmsContinuation = "x-ms-Continuation-NextTableName"
|
|
)
|
|
|
|
// TableServiceClient contains operations for Microsoft Azure Table Storage
|
|
// Service.
|
|
type TableServiceClient struct {
|
|
client Client
|
|
auth authentication
|
|
}
|
|
|
|
// TableOptions includes options for some table operations
|
|
type TableOptions struct {
|
|
RequestID string
|
|
}
|
|
|
|
func (options *TableOptions) addToHeaders(h map[string]string) map[string]string {
|
|
if options != nil {
|
|
h = addToHeaders(h, "x-ms-client-request-id", options.RequestID)
|
|
}
|
|
return h
|
|
}
|
|
|
|
// QueryNextLink includes information for getting the next page of
|
|
// results in query operations
|
|
type QueryNextLink struct {
|
|
NextLink *string
|
|
ml MetadataLevel
|
|
}
|
|
|
|
// GetServiceProperties gets the properties of your storage account's table service.
|
|
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-table-service-properties
|
|
func (t *TableServiceClient) GetServiceProperties() (*ServiceProperties, error) {
|
|
return t.client.getServiceProperties(tableServiceName, t.auth)
|
|
}
|
|
|
|
// SetServiceProperties sets the properties of your storage account's table service.
|
|
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-table-service-properties
|
|
func (t *TableServiceClient) SetServiceProperties(props ServiceProperties) error {
|
|
return t.client.setServiceProperties(props, tableServiceName, t.auth)
|
|
}
|
|
|
|
// GetTableReference returns a Table object for the specified table name.
|
|
func (t *TableServiceClient) GetTableReference(name string) *Table {
|
|
return &Table{
|
|
tsc: t,
|
|
Name: name,
|
|
}
|
|
}
|
|
|
|
// QueryTablesOptions includes options for some table operations
|
|
type QueryTablesOptions struct {
|
|
Top uint
|
|
Filter string
|
|
RequestID string
|
|
}
|
|
|
|
func (options *QueryTablesOptions) getParameters() (url.Values, map[string]string) {
|
|
query := url.Values{}
|
|
headers := map[string]string{}
|
|
if options != nil {
|
|
if options.Top > 0 {
|
|
query.Add(OdataTop, strconv.FormatUint(uint64(options.Top), 10))
|
|
}
|
|
if options.Filter != "" {
|
|
query.Add(OdataFilter, options.Filter)
|
|
}
|
|
headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID)
|
|
}
|
|
return query, headers
|
|
}
|
|
|
|
// QueryTables returns the tables in the storage account.
|
|
// You can use query options defined by the OData Protocol specification.
|
|
//
|
|
// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/query-tables
|
|
func (t *TableServiceClient) QueryTables(ml MetadataLevel, options *QueryTablesOptions) (*TableQueryResult, error) {
|
|
query, headers := options.getParameters()
|
|
uri := t.client.getEndpoint(tableServiceName, tablesURIPath, query)
|
|
return t.queryTables(uri, headers, ml)
|
|
}
|
|
|
|
// NextResults returns the next page of results
|
|
// from a QueryTables or a NextResults operation.
|
|
//
|
|
// See https://docs.microsoft.com/rest/api/storageservices/fileservices/query-tables
|
|
// See https://docs.microsoft.com/rest/api/storageservices/fileservices/query-timeout-and-pagination
|
|
func (tqr *TableQueryResult) NextResults(options *TableOptions) (*TableQueryResult, error) {
|
|
if tqr == nil {
|
|
return nil, errNilPreviousResult
|
|
}
|
|
if tqr.NextLink == nil {
|
|
return nil, errNilNextLink
|
|
}
|
|
headers := options.addToHeaders(map[string]string{})
|
|
|
|
return tqr.tsc.queryTables(*tqr.NextLink, headers, tqr.ml)
|
|
}
|
|
|
|
// TableQueryResult contains the response from
|
|
// QueryTables and QueryTablesNextResults functions.
|
|
type TableQueryResult struct {
|
|
OdataMetadata string `json:"odata.metadata"`
|
|
Tables []Table `json:"value"`
|
|
QueryNextLink
|
|
tsc *TableServiceClient
|
|
}
|
|
|
|
func (t *TableServiceClient) queryTables(uri string, headers map[string]string, ml MetadataLevel) (*TableQueryResult, error) {
|
|
if ml == EmptyPayload {
|
|
return nil, errEmptyPayload
|
|
}
|
|
headers = mergeHeaders(headers, t.client.getStandardHeaders())
|
|
headers[headerAccept] = string(ml)
|
|
|
|
resp, err := t.client.exec(http.MethodGet, uri, headers, nil, t.auth)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.body.Close()
|
|
|
|
if err := checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
respBody, err := ioutil.ReadAll(resp.body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var out TableQueryResult
|
|
err = json.Unmarshal(respBody, &out)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i := range out.Tables {
|
|
out.Tables[i].tsc = t
|
|
}
|
|
out.tsc = t
|
|
|
|
nextLink := resp.headers.Get(http.CanonicalHeaderKey(headerXmsContinuation))
|
|
if nextLink == "" {
|
|
out.NextLink = nil
|
|
} else {
|
|
originalURI, err := url.Parse(uri)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v := originalURI.Query()
|
|
v.Set(nextTableQueryParameter, nextLink)
|
|
newURI := t.client.getEndpoint(tableServiceName, tablesURIPath, v)
|
|
out.NextLink = &newURI
|
|
out.ml = ml
|
|
}
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
func addBodyRelatedHeaders(h map[string]string, length int) map[string]string {
|
|
h[headerContentType] = "application/json"
|
|
h[headerContentLength] = fmt.Sprintf("%v", length)
|
|
h[headerAcceptCharset] = "UTF-8"
|
|
return h
|
|
}
|
|
|
|
func addReturnContentHeaders(h map[string]string, ml MetadataLevel) map[string]string {
|
|
if ml != EmptyPayload {
|
|
h[headerPrefer] = "return-content"
|
|
h[headerAccept] = string(ml)
|
|
} else {
|
|
h[headerPrefer] = "return-no-content"
|
|
// From API version 2015-12-11 onwards, Accept header is required
|
|
h[headerAccept] = string(NoMetadata)
|
|
}
|
|
return h
|
|
}
|