aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/Azure/azure-storage-go/fileserviceclient.go
blob: d68bd7f64e98b8ee9ec1d0f34460586775128aef (plain) (tree)






















































































































































































































































































































































































                                                                                                                                                                                  
package storage

import (
    "encoding/xml"
    "fmt"
    "net/http"
    "net/url"
    "strings"
)

// FileServiceClient contains operations for Microsoft Azure File Service.
type FileServiceClient struct {
    client Client
    auth   authentication
}

// ListSharesParameters defines the set of customizable parameters to make a
// List Shares call.
//
// See https://msdn.microsoft.com/en-us/library/azure/dn167009.aspx
type ListSharesParameters struct {
    Prefix     string
    Marker     string
    Include    string
    MaxResults uint
    Timeout    uint
}

// ShareListResponse contains the response fields from
// ListShares call.
//
// See https://msdn.microsoft.com/en-us/library/azure/dn167009.aspx
type ShareListResponse struct {
    XMLName    xml.Name `xml:"EnumerationResults"`
    Xmlns      string   `xml:"xmlns,attr"`
    Prefix     string   `xml:"Prefix"`
    Marker     string   `xml:"Marker"`
    NextMarker string   `xml:"NextMarker"`
    MaxResults int64    `xml:"MaxResults"`
    Shares     []Share  `xml:"Shares>Share"`
}

type compType string

const (
    compNone       compType = ""
    compList       compType = "list"
    compMetadata   compType = "metadata"
    compProperties compType = "properties"
    compRangeList  compType = "rangelist"
)

func (ct compType) String() string {
    return string(ct)
}

type resourceType string

const (
    resourceDirectory resourceType = "directory"
    resourceFile      resourceType = ""
    resourceShare     resourceType = "share"
)

func (rt resourceType) String() string {
    return string(rt)
}

func (p ListSharesParameters) getParameters() url.Values {
    out := url.Values{}

    if p.Prefix != "" {
        out.Set("prefix", p.Prefix)
    }
    if p.Marker != "" {
        out.Set("marker", p.Marker)
    }
    if p.Include != "" {
        out.Set("include", p.Include)
    }
    if p.MaxResults != 0 {
        out.Set("maxresults", fmt.Sprintf("%v", p.MaxResults))
    }
    if p.Timeout != 0 {
        out.Set("timeout", fmt.Sprintf("%v", p.Timeout))
    }

    return out
}

func (p ListDirsAndFilesParameters) getParameters() url.Values {
    out := url.Values{}

    if p.Marker != "" {
        out.Set("marker", p.Marker)
    }
    if p.MaxResults != 0 {
        out.Set("maxresults", fmt.Sprintf("%v", p.MaxResults))
    }
    if p.Timeout != 0 {
        out.Set("timeout", fmt.Sprintf("%v", p.Timeout))
    }

    return out
}

// returns url.Values for the specified types
func getURLInitValues(comp compType, res resourceType) url.Values {
    values := url.Values{}
    if comp != compNone {
        values.Set("comp", comp.String())
    }
    if res != resourceFile {
        values.Set("restype", res.String())
    }
    return values
}

// GetShareReference returns a Share object for the specified share name.
func (f FileServiceClient) GetShareReference(name string) Share {
    return Share{
        fsc:  &f,
        Name: name,
        Properties: ShareProperties{
            Quota: -1,
        },
    }
}

// ListShares returns the list of shares in a storage account along with
// pagination token and other response details.
//
// See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx
func (f FileServiceClient) ListShares(params ListSharesParameters) (*ShareListResponse, error) {
    q := mergeParams(params.getParameters(), url.Values{"comp": {"list"}})

    var out ShareListResponse
    resp, err := f.listContent("", q, nil)
    if err != nil {
        return nil, err
    }
    defer resp.body.Close()
    err = xmlUnmarshal(resp.body, &out)

    // assign our client to the newly created Share objects
    for i := range out.Shares {
        out.Shares[i].fsc = &f
    }
    return &out, err
}

// GetServiceProperties gets the properties of your storage account's file service.
// File service does not support logging
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-file-service-properties
func (f *FileServiceClient) GetServiceProperties() (*ServiceProperties, error) {
    return f.client.getServiceProperties(fileServiceName, f.auth)
}

// SetServiceProperties sets the properties of your storage account's file service.
// File service does not support logging
// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-file-service-properties
func (f *FileServiceClient) SetServiceProperties(props ServiceProperties) error {
    return f.client.setServiceProperties(props, fileServiceName, f.auth)
}

// retrieves directory or share content
func (f FileServiceClient) listContent(path string, params url.Values, extraHeaders map[string]string) (*storageResponse, error) {
    if err := f.checkForStorageEmulator(); err != nil {
        return nil, err
    }

    uri := f.client.getEndpoint(fileServiceName, path, params)
    extraHeaders = f.client.protectUserAgent(extraHeaders)
    headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)

    resp, err := f.client.exec(http.MethodGet, uri, headers, nil, f.auth)
    if err != nil {
        return nil, err
    }

    if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
        readAndCloseBody(resp.body)
        return nil, err
    }

    return resp, nil
}

// returns true if the specified resource exists
func (f FileServiceClient) resourceExists(path string, res resourceType) (bool, http.Header, error) {
    if err := f.checkForStorageEmulator(); err != nil {
        return false, nil, err
    }

    uri := f.client.getEndpoint(fileServiceName, path, getURLInitValues(compNone, res))
    headers := f.client.getStandardHeaders()

    resp, err := f.client.exec(http.MethodHead, uri, headers, nil, f.auth)
    if resp != nil {
        defer readAndCloseBody(resp.body)
        if resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound {
            return resp.statusCode == http.StatusOK, resp.headers, nil
        }
    }
    return false, nil, err
}

// creates a resource depending on the specified resource type
func (f FileServiceClient) createResource(path string, res resourceType, urlParams url.Values, extraHeaders map[string]string, expectedResponseCodes []int) (http.Header, error) {
    resp, err := f.createResourceNoClose(path, res, urlParams, extraHeaders)
    if err != nil {
        return nil, err
    }
    defer readAndCloseBody(resp.body)
    return resp.headers, checkRespCode(resp.statusCode, expectedResponseCodes)
}

// creates a resource depending on the specified resource type, doesn't close the response body
func (f FileServiceClient) createResourceNoClose(path string, res resourceType, urlParams url.Values, extraHeaders map[string]string) (*storageResponse, error) {
    if err := f.checkForStorageEmulator(); err != nil {
        return nil, err
    }

    values := getURLInitValues(compNone, res)
    combinedParams := mergeParams(values, urlParams)
    uri := f.client.getEndpoint(fileServiceName, path, combinedParams)
    extraHeaders = f.client.protectUserAgent(extraHeaders)
    headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)

    return f.client.exec(http.MethodPut, uri, headers, nil, f.auth)
}

// returns HTTP header data for the specified directory or share
func (f FileServiceClient) getResourceHeaders(path string, comp compType, res resourceType, verb string) (http.Header, error) {
    resp, err := f.getResourceNoClose(path, comp, res, verb, nil)
    if err != nil {
        return nil, err
    }
    defer readAndCloseBody(resp.body)

    if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
        return nil, err
    }

    return resp.headers, nil
}

// gets the specified resource, doesn't close the response body
func (f FileServiceClient) getResourceNoClose(path string, comp compType, res resourceType, verb string, extraHeaders map[string]string) (*storageResponse, error) {
    if err := f.checkForStorageEmulator(); err != nil {
        return nil, err
    }

    params := getURLInitValues(comp, res)
    uri := f.client.getEndpoint(fileServiceName, path, params)
    extraHeaders = f.client.protectUserAgent(extraHeaders)
    headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)

    return f.client.exec(verb, uri, headers, nil, f.auth)
}

// deletes the resource and returns the response
func (f FileServiceClient) deleteResource(path string, res resourceType) error {
    resp, err := f.deleteResourceNoClose(path, res)
    if err != nil {
        return err
    }
    defer readAndCloseBody(resp.body)
    return checkRespCode(resp.statusCode, []int{http.StatusAccepted})
}

// deletes the resource and returns the response, doesn't close the response body
func (f FileServiceClient) deleteResourceNoClose(path string, res resourceType) (*storageResponse, error) {
    if err := f.checkForStorageEmulator(); err != nil {
        return nil, err
    }

    values := getURLInitValues(compNone, res)
    uri := f.client.getEndpoint(fileServiceName, path, values)
    return f.client.exec(http.MethodDelete, uri, f.client.getStandardHeaders(), nil, f.auth)
}

// merges metadata into extraHeaders and returns extraHeaders
func mergeMDIntoExtraHeaders(metadata, extraHeaders map[string]string) map[string]string {
    if metadata == nil && extraHeaders == nil {
        return nil
    }
    if extraHeaders == nil {
        extraHeaders = make(map[string]string)
    }
    for k, v := range metadata {
        extraHeaders[userDefinedMetadataHeaderPrefix+k] = v
    }
    return extraHeaders
}

// merges extraHeaders into headers and returns headers
func mergeHeaders(headers, extraHeaders map[string]string) map[string]string {
    for k, v := range extraHeaders {
        headers[k] = v
    }
    return headers
}

// sets extra header data for the specified resource
func (f FileServiceClient) setResourceHeaders(path string, comp compType, res resourceType, extraHeaders map[string]string) (http.Header, error) {
    if err := f.checkForStorageEmulator(); err != nil {
        return nil, err
    }

    params := getURLInitValues(comp, res)
    uri := f.client.getEndpoint(fileServiceName, path, params)
    extraHeaders = f.client.protectUserAgent(extraHeaders)
    headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)

    resp, err := f.client.exec(http.MethodPut, uri, headers, nil, f.auth)
    if err != nil {
        return nil, err
    }
    defer readAndCloseBody(resp.body)

    return resp.headers, checkRespCode(resp.statusCode, []int{http.StatusOK})
}

// gets metadata for the specified resource
func (f FileServiceClient) getMetadata(path string, res resourceType) (map[string]string, error) {
    if err := f.checkForStorageEmulator(); err != nil {
        return nil, err
    }

    headers, err := f.getResourceHeaders(path, compMetadata, res, http.MethodGet)
    if err != nil {
        return nil, err
    }

    return getMetadataFromHeaders(headers), nil
}

// returns a map of custom metadata values from the specified HTTP header
func getMetadataFromHeaders(header http.Header) map[string]string {
    metadata := make(map[string]string)
    for k, v := range header {
        // Can't trust CanonicalHeaderKey() to munge case
        // reliably. "_" is allowed in identifiers:
        // https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
        // https://msdn.microsoft.com/library/aa664670(VS.71).aspx
        // http://tools.ietf.org/html/rfc7230#section-3.2
        // ...but "_" is considered invalid by
        // CanonicalMIMEHeaderKey in
        // https://golang.org/src/net/textproto/reader.go?s=14615:14659#L542
        // so k can be "X-Ms-Meta-Foo" or "x-ms-meta-foo_bar".
        k = strings.ToLower(k)
        if len(v) == 0 || !strings.HasPrefix(k, strings.ToLower(userDefinedMetadataHeaderPrefix)) {
            continue
        }
        // metadata["foo"] = content of the last X-Ms-Meta-Foo header
        k = k[len(userDefinedMetadataHeaderPrefix):]
        metadata[k] = v[len(v)-1]
    }

    if len(metadata) == 0 {
        return nil
    }

    return metadata
}

//checkForStorageEmulator determines if the client is setup for use with
//Azure Storage Emulator, and returns a relevant error
func (f FileServiceClient) checkForStorageEmulator() error {
    if f.client.accountName == StorageEmulatorAccountName {
        return fmt.Errorf("Error: File service is not currently supported by Azure Storage Emulator")
    }
    return nil
}