fileserviceclient.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. package storage
  2. import (
  3. "encoding/xml"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "strings"
  8. )
  9. // FileServiceClient contains operations for Microsoft Azure File Service.
  10. type FileServiceClient struct {
  11. client Client
  12. auth authentication
  13. }
  14. // ListSharesParameters defines the set of customizable parameters to make a
  15. // List Shares call.
  16. //
  17. // See https://msdn.microsoft.com/en-us/library/azure/dn167009.aspx
  18. type ListSharesParameters struct {
  19. Prefix string
  20. Marker string
  21. Include string
  22. MaxResults uint
  23. Timeout uint
  24. }
  25. // ShareListResponse contains the response fields from
  26. // ListShares call.
  27. //
  28. // See https://msdn.microsoft.com/en-us/library/azure/dn167009.aspx
  29. type ShareListResponse struct {
  30. XMLName xml.Name `xml:"EnumerationResults"`
  31. Xmlns string `xml:"xmlns,attr"`
  32. Prefix string `xml:"Prefix"`
  33. Marker string `xml:"Marker"`
  34. NextMarker string `xml:"NextMarker"`
  35. MaxResults int64 `xml:"MaxResults"`
  36. Shares []Share `xml:"Shares>Share"`
  37. }
  38. type compType string
  39. const (
  40. compNone compType = ""
  41. compList compType = "list"
  42. compMetadata compType = "metadata"
  43. compProperties compType = "properties"
  44. compRangeList compType = "rangelist"
  45. )
  46. func (ct compType) String() string {
  47. return string(ct)
  48. }
  49. type resourceType string
  50. const (
  51. resourceDirectory resourceType = "directory"
  52. resourceFile resourceType = ""
  53. resourceShare resourceType = "share"
  54. )
  55. func (rt resourceType) String() string {
  56. return string(rt)
  57. }
  58. func (p ListSharesParameters) getParameters() url.Values {
  59. out := url.Values{}
  60. if p.Prefix != "" {
  61. out.Set("prefix", p.Prefix)
  62. }
  63. if p.Marker != "" {
  64. out.Set("marker", p.Marker)
  65. }
  66. if p.Include != "" {
  67. out.Set("include", p.Include)
  68. }
  69. if p.MaxResults != 0 {
  70. out.Set("maxresults", fmt.Sprintf("%v", p.MaxResults))
  71. }
  72. if p.Timeout != 0 {
  73. out.Set("timeout", fmt.Sprintf("%v", p.Timeout))
  74. }
  75. return out
  76. }
  77. func (p ListDirsAndFilesParameters) getParameters() url.Values {
  78. out := url.Values{}
  79. if p.Marker != "" {
  80. out.Set("marker", p.Marker)
  81. }
  82. if p.MaxResults != 0 {
  83. out.Set("maxresults", fmt.Sprintf("%v", p.MaxResults))
  84. }
  85. if p.Timeout != 0 {
  86. out.Set("timeout", fmt.Sprintf("%v", p.Timeout))
  87. }
  88. return out
  89. }
  90. // returns url.Values for the specified types
  91. func getURLInitValues(comp compType, res resourceType) url.Values {
  92. values := url.Values{}
  93. if comp != compNone {
  94. values.Set("comp", comp.String())
  95. }
  96. if res != resourceFile {
  97. values.Set("restype", res.String())
  98. }
  99. return values
  100. }
  101. // GetShareReference returns a Share object for the specified share name.
  102. func (f FileServiceClient) GetShareReference(name string) Share {
  103. return Share{
  104. fsc: &f,
  105. Name: name,
  106. Properties: ShareProperties{
  107. Quota: -1,
  108. },
  109. }
  110. }
  111. // ListShares returns the list of shares in a storage account along with
  112. // pagination token and other response details.
  113. //
  114. // See https://msdn.microsoft.com/en-us/library/azure/dd179352.aspx
  115. func (f FileServiceClient) ListShares(params ListSharesParameters) (*ShareListResponse, error) {
  116. q := mergeParams(params.getParameters(), url.Values{"comp": {"list"}})
  117. var out ShareListResponse
  118. resp, err := f.listContent("", q, nil)
  119. if err != nil {
  120. return nil, err
  121. }
  122. defer resp.body.Close()
  123. err = xmlUnmarshal(resp.body, &out)
  124. // assign our client to the newly created Share objects
  125. for i := range out.Shares {
  126. out.Shares[i].fsc = &f
  127. }
  128. return &out, err
  129. }
  130. // GetServiceProperties gets the properties of your storage account's file service.
  131. // File service does not support logging
  132. // See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-file-service-properties
  133. func (f *FileServiceClient) GetServiceProperties() (*ServiceProperties, error) {
  134. return f.client.getServiceProperties(fileServiceName, f.auth)
  135. }
  136. // SetServiceProperties sets the properties of your storage account's file service.
  137. // File service does not support logging
  138. // See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-file-service-properties
  139. func (f *FileServiceClient) SetServiceProperties(props ServiceProperties) error {
  140. return f.client.setServiceProperties(props, fileServiceName, f.auth)
  141. }
  142. // retrieves directory or share content
  143. func (f FileServiceClient) listContent(path string, params url.Values, extraHeaders map[string]string) (*storageResponse, error) {
  144. if err := f.checkForStorageEmulator(); err != nil {
  145. return nil, err
  146. }
  147. uri := f.client.getEndpoint(fileServiceName, path, params)
  148. extraHeaders = f.client.protectUserAgent(extraHeaders)
  149. headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)
  150. resp, err := f.client.exec(http.MethodGet, uri, headers, nil, f.auth)
  151. if err != nil {
  152. return nil, err
  153. }
  154. if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
  155. readAndCloseBody(resp.body)
  156. return nil, err
  157. }
  158. return resp, nil
  159. }
  160. // returns true if the specified resource exists
  161. func (f FileServiceClient) resourceExists(path string, res resourceType) (bool, http.Header, error) {
  162. if err := f.checkForStorageEmulator(); err != nil {
  163. return false, nil, err
  164. }
  165. uri := f.client.getEndpoint(fileServiceName, path, getURLInitValues(compNone, res))
  166. headers := f.client.getStandardHeaders()
  167. resp, err := f.client.exec(http.MethodHead, uri, headers, nil, f.auth)
  168. if resp != nil {
  169. defer readAndCloseBody(resp.body)
  170. if resp.statusCode == http.StatusOK || resp.statusCode == http.StatusNotFound {
  171. return resp.statusCode == http.StatusOK, resp.headers, nil
  172. }
  173. }
  174. return false, nil, err
  175. }
  176. // creates a resource depending on the specified resource type
  177. func (f FileServiceClient) createResource(path string, res resourceType, urlParams url.Values, extraHeaders map[string]string, expectedResponseCodes []int) (http.Header, error) {
  178. resp, err := f.createResourceNoClose(path, res, urlParams, extraHeaders)
  179. if err != nil {
  180. return nil, err
  181. }
  182. defer readAndCloseBody(resp.body)
  183. return resp.headers, checkRespCode(resp.statusCode, expectedResponseCodes)
  184. }
  185. // creates a resource depending on the specified resource type, doesn't close the response body
  186. func (f FileServiceClient) createResourceNoClose(path string, res resourceType, urlParams url.Values, extraHeaders map[string]string) (*storageResponse, error) {
  187. if err := f.checkForStorageEmulator(); err != nil {
  188. return nil, err
  189. }
  190. values := getURLInitValues(compNone, res)
  191. combinedParams := mergeParams(values, urlParams)
  192. uri := f.client.getEndpoint(fileServiceName, path, combinedParams)
  193. extraHeaders = f.client.protectUserAgent(extraHeaders)
  194. headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)
  195. return f.client.exec(http.MethodPut, uri, headers, nil, f.auth)
  196. }
  197. // returns HTTP header data for the specified directory or share
  198. func (f FileServiceClient) getResourceHeaders(path string, comp compType, res resourceType, verb string) (http.Header, error) {
  199. resp, err := f.getResourceNoClose(path, comp, res, verb, nil)
  200. if err != nil {
  201. return nil, err
  202. }
  203. defer readAndCloseBody(resp.body)
  204. if err = checkRespCode(resp.statusCode, []int{http.StatusOK}); err != nil {
  205. return nil, err
  206. }
  207. return resp.headers, nil
  208. }
  209. // gets the specified resource, doesn't close the response body
  210. func (f FileServiceClient) getResourceNoClose(path string, comp compType, res resourceType, verb string, extraHeaders map[string]string) (*storageResponse, error) {
  211. if err := f.checkForStorageEmulator(); err != nil {
  212. return nil, err
  213. }
  214. params := getURLInitValues(comp, res)
  215. uri := f.client.getEndpoint(fileServiceName, path, params)
  216. extraHeaders = f.client.protectUserAgent(extraHeaders)
  217. headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)
  218. return f.client.exec(verb, uri, headers, nil, f.auth)
  219. }
  220. // deletes the resource and returns the response
  221. func (f FileServiceClient) deleteResource(path string, res resourceType) error {
  222. resp, err := f.deleteResourceNoClose(path, res)
  223. if err != nil {
  224. return err
  225. }
  226. defer readAndCloseBody(resp.body)
  227. return checkRespCode(resp.statusCode, []int{http.StatusAccepted})
  228. }
  229. // deletes the resource and returns the response, doesn't close the response body
  230. func (f FileServiceClient) deleteResourceNoClose(path string, res resourceType) (*storageResponse, error) {
  231. if err := f.checkForStorageEmulator(); err != nil {
  232. return nil, err
  233. }
  234. values := getURLInitValues(compNone, res)
  235. uri := f.client.getEndpoint(fileServiceName, path, values)
  236. return f.client.exec(http.MethodDelete, uri, f.client.getStandardHeaders(), nil, f.auth)
  237. }
  238. // merges metadata into extraHeaders and returns extraHeaders
  239. func mergeMDIntoExtraHeaders(metadata, extraHeaders map[string]string) map[string]string {
  240. if metadata == nil && extraHeaders == nil {
  241. return nil
  242. }
  243. if extraHeaders == nil {
  244. extraHeaders = make(map[string]string)
  245. }
  246. for k, v := range metadata {
  247. extraHeaders[userDefinedMetadataHeaderPrefix+k] = v
  248. }
  249. return extraHeaders
  250. }
  251. // merges extraHeaders into headers and returns headers
  252. func mergeHeaders(headers, extraHeaders map[string]string) map[string]string {
  253. for k, v := range extraHeaders {
  254. headers[k] = v
  255. }
  256. return headers
  257. }
  258. // sets extra header data for the specified resource
  259. func (f FileServiceClient) setResourceHeaders(path string, comp compType, res resourceType, extraHeaders map[string]string) (http.Header, error) {
  260. if err := f.checkForStorageEmulator(); err != nil {
  261. return nil, err
  262. }
  263. params := getURLInitValues(comp, res)
  264. uri := f.client.getEndpoint(fileServiceName, path, params)
  265. extraHeaders = f.client.protectUserAgent(extraHeaders)
  266. headers := mergeHeaders(f.client.getStandardHeaders(), extraHeaders)
  267. resp, err := f.client.exec(http.MethodPut, uri, headers, nil, f.auth)
  268. if err != nil {
  269. return nil, err
  270. }
  271. defer readAndCloseBody(resp.body)
  272. return resp.headers, checkRespCode(resp.statusCode, []int{http.StatusOK})
  273. }
  274. // gets metadata for the specified resource
  275. func (f FileServiceClient) getMetadata(path string, res resourceType) (map[string]string, error) {
  276. if err := f.checkForStorageEmulator(); err != nil {
  277. return nil, err
  278. }
  279. headers, err := f.getResourceHeaders(path, compMetadata, res, http.MethodGet)
  280. if err != nil {
  281. return nil, err
  282. }
  283. return getMetadataFromHeaders(headers), nil
  284. }
  285. // returns a map of custom metadata values from the specified HTTP header
  286. func getMetadataFromHeaders(header http.Header) map[string]string {
  287. metadata := make(map[string]string)
  288. for k, v := range header {
  289. // Can't trust CanonicalHeaderKey() to munge case
  290. // reliably. "_" is allowed in identifiers:
  291. // https://msdn.microsoft.com/en-us/library/azure/dd179414.aspx
  292. // https://msdn.microsoft.com/library/aa664670(VS.71).aspx
  293. // http://tools.ietf.org/html/rfc7230#section-3.2
  294. // ...but "_" is considered invalid by
  295. // CanonicalMIMEHeaderKey in
  296. // https://golang.org/src/net/textproto/reader.go?s=14615:14659#L542
  297. // so k can be "X-Ms-Meta-Foo" or "x-ms-meta-foo_bar".
  298. k = strings.ToLower(k)
  299. if len(v) == 0 || !strings.HasPrefix(k, strings.ToLower(userDefinedMetadataHeaderPrefix)) {
  300. continue
  301. }
  302. // metadata["foo"] = content of the last X-Ms-Meta-Foo header
  303. k = k[len(userDefinedMetadataHeaderPrefix):]
  304. metadata[k] = v[len(v)-1]
  305. }
  306. if len(metadata) == 0 {
  307. return nil
  308. }
  309. return metadata
  310. }
  311. //checkForStorageEmulator determines if the client is setup for use with
  312. //Azure Storage Emulator, and returns a relevant error
  313. func (f FileServiceClient) checkForStorageEmulator() error {
  314. if f.client.accountName == StorageEmulatorAccountName {
  315. return fmt.Errorf("Error: File service is not currently supported by Azure Storage Emulator")
  316. }
  317. return nil
  318. }