376 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			376 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
| 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
 | |
| }
 |