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  }