github.com/kubri/kubri@v0.5.1-0.20240317001612-bda2aaef967e/internal/blob/source.go (about) 1 package blob 2 3 import ( 4 "context" 5 "io" 6 "math" 7 "mime" 8 "net/url" 9 "path" 10 "strings" 11 12 "gocloud.dev/blob" 13 14 "github.com/kubri/kubri/source" 15 ) 16 17 type blobSource struct { 18 bucket *blob.Bucket 19 prefix string 20 baseURL string 21 } 22 23 func NewSource(url, prefix, baseURL string) (*source.Source, error) { 24 b, err := blob.OpenBucket(context.Background(), url) 25 if err != nil { 26 return nil, err 27 } 28 29 if prefix != "" { 30 prefix = strings.Trim(prefix, "/") + "/" 31 } 32 33 s := &blobSource{ 34 bucket: b, 35 prefix: prefix, 36 baseURL: baseURL, 37 } 38 39 return source.New(s), nil 40 } 41 42 func (s *blobSource) ListReleases(ctx context.Context) ([]*source.Release, error) { 43 releases := []*source.Release{} 44 iter := s.bucket.List(&blob.ListOptions{Prefix: s.prefix, Delimiter: "/"}) 45 46 for { 47 object, err := iter.Next(ctx) 48 if err == io.EOF { 49 break 50 } 51 if err != nil { 52 return nil, err 53 } 54 55 if !object.IsDir { 56 continue 57 } 58 59 r, err := s.GetRelease(ctx, path.Base(object.Key)) 60 if err != nil && err != source.ErrNoReleaseFound { 61 return nil, err 62 } 63 64 if r != nil { 65 releases = append(releases, r) 66 } 67 } 68 69 return releases, nil 70 } 71 72 func (s *blobSource) GetRelease(ctx context.Context, version string) (*source.Release, error) { 73 r := source.Release{Version: version} 74 75 iter := s.bucket.List(&blob.ListOptions{Prefix: path.Join(s.prefix, version) + "/", Delimiter: "/"}) 76 for { 77 object, err := iter.Next(ctx) 78 if err == io.EOF { 79 break 80 } 81 if err != nil { 82 return nil, err 83 } 84 85 if object.IsDir { 86 continue 87 } 88 89 if r.Date.IsZero() || r.Date.After(object.ModTime) { 90 r.Date = object.ModTime 91 } 92 93 var u string 94 if s.baseURL != "" { 95 u, err = url.JoinPath(s.baseURL, object.Key) 96 if err != nil { 97 return nil, err 98 } 99 } else { 100 u, err = s.bucket.SignedURL(ctx, object.Key, &blob.SignedURLOptions{Expiry: math.MaxInt64}) 101 if err != nil { 102 return nil, err 103 } 104 } 105 106 attr, err := s.bucket.Attributes(ctx, object.Key) 107 if err != nil { 108 return nil, err 109 } 110 111 r.Assets = append(r.Assets, &source.Asset{ 112 Name: path.Base(object.Key), 113 URL: u, 114 Size: int(attr.Size), 115 }) 116 } 117 118 if len(r.Assets) == 0 { 119 return nil, source.ErrNoReleaseFound 120 } 121 122 return &r, nil 123 } 124 125 func (s *blobSource) UploadAsset(ctx context.Context, version, name string, data []byte) error { 126 opt := &blob.WriterOptions{ContentType: mime.TypeByExtension(path.Ext(name))} 127 return s.bucket.WriteAll(ctx, path.Join(s.prefix, version, name), data, opt) 128 } 129 130 func (s *blobSource) DownloadAsset(ctx context.Context, version, name string) ([]byte, error) { 131 return s.bucket.ReadAll(ctx, path.Join(s.prefix, version, name)) 132 }