gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package storage
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net/url"
  10. "os"
  11. "path"
  12. "strings"
  13. "time"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/setting"
  16. "code.gitea.io/gitea/modules/util"
  17. "github.com/Azure/azure-sdk-for-go/sdk/azcore"
  18. "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
  19. "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
  20. "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
  21. "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob"
  22. "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
  23. "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
  24. )
  25. var _ Object = &azureBlobObject{}
  26. type azureBlobObject struct {
  27. blobClient *blob.Client
  28. Context context.Context
  29. Name string
  30. Size int64
  31. ModTime *time.Time
  32. offset int64
  33. }
  34. func (a *azureBlobObject) Read(p []byte) (int, error) {
  35. // TODO: improve the performance, we can implement another interface, maybe implement io.WriteTo
  36. if a.offset >= a.Size {
  37. return 0, io.EOF
  38. }
  39. count := min(int64(len(p)), a.Size-a.offset)
  40. res, err := a.blobClient.DownloadBuffer(a.Context, p, &blob.DownloadBufferOptions{
  41. Range: blob.HTTPRange{
  42. Offset: a.offset,
  43. Count: count,
  44. },
  45. })
  46. if err != nil {
  47. return 0, convertAzureBlobErr(err)
  48. }
  49. a.offset += res
  50. return int(res), nil
  51. }
  52. func (a *azureBlobObject) Close() error {
  53. a.offset = 0
  54. return nil
  55. }
  56. func (a *azureBlobObject) Seek(offset int64, whence int) (int64, error) {
  57. switch whence {
  58. case io.SeekStart:
  59. case io.SeekCurrent:
  60. offset += a.offset
  61. case io.SeekEnd:
  62. offset = a.Size + offset
  63. default:
  64. return 0, errors.New("Seek: invalid whence")
  65. }
  66. if offset > a.Size {
  67. return 0, errors.New("Seek: invalid offset")
  68. } else if offset < 0 {
  69. return 0, errors.New("Seek: invalid offset")
  70. }
  71. a.offset = offset
  72. return a.offset, nil
  73. }
  74. func (a *azureBlobObject) Stat() (os.FileInfo, error) {
  75. return &azureBlobFileInfo{
  76. a.Name,
  77. a.Size,
  78. *a.ModTime,
  79. }, nil
  80. }
  81. var _ ObjectStorage = &AzureBlobStorage{}
  82. // AzureStorage returns a azure blob storage
  83. type AzureBlobStorage struct {
  84. cfg *setting.AzureBlobStorageConfig
  85. ctx context.Context
  86. credential *azblob.SharedKeyCredential
  87. client *azblob.Client
  88. }
  89. func convertAzureBlobErr(err error) error {
  90. if err == nil {
  91. return nil
  92. }
  93. if bloberror.HasCode(err, bloberror.BlobNotFound) {
  94. return os.ErrNotExist
  95. }
  96. var respErr *azcore.ResponseError
  97. if !errors.As(err, &respErr) {
  98. return err
  99. }
  100. return fmt.Errorf("%s", respErr.ErrorCode)
  101. }
  102. // NewAzureBlobStorage returns a azure blob storage
  103. func NewAzureBlobStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage, error) {
  104. config := cfg.AzureBlobConfig
  105. log.Info("Creating Azure Blob storage at %s:%s with base path %s", config.Endpoint, config.Container, config.BasePath)
  106. cred, err := azblob.NewSharedKeyCredential(config.AccountName, config.AccountKey)
  107. if err != nil {
  108. return nil, convertAzureBlobErr(err)
  109. }
  110. client, err := azblob.NewClientWithSharedKeyCredential(config.Endpoint, cred, &azblob.ClientOptions{})
  111. if err != nil {
  112. return nil, convertAzureBlobErr(err)
  113. }
  114. _, err = client.CreateContainer(ctx, config.Container, &container.CreateOptions{})
  115. if err != nil {
  116. // Check to see if we already own this container (which happens if you run this twice)
  117. if !bloberror.HasCode(err, bloberror.ContainerAlreadyExists) {
  118. return nil, convertMinioErr(err)
  119. }
  120. }
  121. return &AzureBlobStorage{
  122. cfg: &config,
  123. ctx: ctx,
  124. credential: cred,
  125. client: client,
  126. }, nil
  127. }
  128. func (a *AzureBlobStorage) buildAzureBlobPath(p string) string {
  129. p = util.PathJoinRelX(a.cfg.BasePath, p)
  130. if p == "." || p == "/" {
  131. p = "" // azure uses prefix, so path should be empty as relative path
  132. }
  133. return p
  134. }
  135. func (a *AzureBlobStorage) getObjectNameFromPath(path string) string {
  136. s := strings.Split(path, "/")
  137. return s[len(s)-1]
  138. }
  139. // Open opens a file
  140. func (a *AzureBlobStorage) Open(path string) (Object, error) {
  141. blobClient := a.getBlobClient(path)
  142. res, err := blobClient.GetProperties(a.ctx, &blob.GetPropertiesOptions{})
  143. if err != nil {
  144. return nil, convertAzureBlobErr(err)
  145. }
  146. return &azureBlobObject{
  147. Context: a.ctx,
  148. blobClient: blobClient,
  149. Name: a.getObjectNameFromPath(path),
  150. Size: *res.ContentLength,
  151. ModTime: res.LastModified,
  152. }, nil
  153. }
  154. // Save saves a file to azure blob storage
  155. func (a *AzureBlobStorage) Save(path string, r io.Reader, size int64) (int64, error) {
  156. rd := util.NewCountingReader(r)
  157. _, err := a.client.UploadStream(
  158. a.ctx,
  159. a.cfg.Container,
  160. a.buildAzureBlobPath(path),
  161. rd,
  162. // TODO: support set block size and concurrency
  163. &blockblob.UploadStreamOptions{},
  164. )
  165. if err != nil {
  166. return 0, convertAzureBlobErr(err)
  167. }
  168. return int64(rd.Count()), nil
  169. }
  170. type azureBlobFileInfo struct {
  171. name string
  172. size int64
  173. modTime time.Time
  174. }
  175. func (a azureBlobFileInfo) Name() string {
  176. return path.Base(a.name)
  177. }
  178. func (a azureBlobFileInfo) Size() int64 {
  179. return a.size
  180. }
  181. func (a azureBlobFileInfo) ModTime() time.Time {
  182. return a.modTime
  183. }
  184. func (a azureBlobFileInfo) IsDir() bool {
  185. return strings.HasSuffix(a.name, "/")
  186. }
  187. func (a azureBlobFileInfo) Mode() os.FileMode {
  188. return os.ModePerm
  189. }
  190. func (a azureBlobFileInfo) Sys() any {
  191. return nil
  192. }
  193. // Stat returns the stat information of the object
  194. func (a *AzureBlobStorage) Stat(path string) (os.FileInfo, error) {
  195. blobClient := a.getBlobClient(path)
  196. res, err := blobClient.GetProperties(a.ctx, &blob.GetPropertiesOptions{})
  197. if err != nil {
  198. return nil, convertAzureBlobErr(err)
  199. }
  200. s := strings.Split(path, "/")
  201. return &azureBlobFileInfo{
  202. s[len(s)-1],
  203. *res.ContentLength,
  204. *res.LastModified,
  205. }, nil
  206. }
  207. // Delete delete a file
  208. func (a *AzureBlobStorage) Delete(path string) error {
  209. blobClient := a.getBlobClient(path)
  210. _, err := blobClient.Delete(a.ctx, nil)
  211. return convertAzureBlobErr(err)
  212. }
  213. // URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
  214. func (a *AzureBlobStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) {
  215. blobClient := a.getBlobClient(path)
  216. startTime := time.Now()
  217. u, err := blobClient.GetSASURL(sas.BlobPermissions{
  218. Read: true,
  219. }, time.Now().Add(5*time.Minute), &blob.GetSASURLOptions{
  220. StartTime: &startTime,
  221. })
  222. if err != nil {
  223. return nil, convertAzureBlobErr(err)
  224. }
  225. return url.Parse(u)
  226. }
  227. // IterateObjects iterates across the objects in the azureblobstorage
  228. func (a *AzureBlobStorage) IterateObjects(dirName string, fn func(path string, obj Object) error) error {
  229. dirName = a.buildAzureBlobPath(dirName)
  230. if dirName != "" {
  231. dirName += "/"
  232. }
  233. pager := a.client.NewListBlobsFlatPager(a.cfg.Container, &container.ListBlobsFlatOptions{
  234. Prefix: &dirName,
  235. })
  236. for pager.More() {
  237. resp, err := pager.NextPage(a.ctx)
  238. if err != nil {
  239. return convertAzureBlobErr(err)
  240. }
  241. for _, object := range resp.Segment.BlobItems {
  242. blobClient := a.getBlobClient(*object.Name)
  243. object := &azureBlobObject{
  244. Context: a.ctx,
  245. blobClient: blobClient,
  246. Name: *object.Name,
  247. Size: *object.Properties.ContentLength,
  248. ModTime: object.Properties.LastModified,
  249. }
  250. if err := func(object *azureBlobObject, fn func(path string, obj Object) error) error {
  251. defer object.Close()
  252. return fn(strings.TrimPrefix(object.Name, a.cfg.BasePath), object)
  253. }(object, fn); err != nil {
  254. return convertAzureBlobErr(err)
  255. }
  256. }
  257. }
  258. return nil
  259. }
  260. // Delete delete a file
  261. func (a *AzureBlobStorage) getBlobClient(path string) *blob.Client {
  262. return a.client.ServiceClient().NewContainerClient(a.cfg.Container).NewBlobClient(a.buildAzureBlobPath(path))
  263. }
  264. func init() {
  265. RegisterStorageType(setting.AzureBlobStorageType, NewAzureBlobStorage)
  266. }