storj.io/uplink@v1.13.0/private/object/object.go (about)

     1  // Copyright (C) 2020 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package object
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	_ "unsafe" // for go:linkname
    10  
    11  	"github.com/spacemonkeygo/monkit/v3"
    12  	"github.com/zeebo/errs"
    13  
    14  	"storj.io/common/errs2"
    15  	"storj.io/common/rpc/rpcstatus"
    16  	"storj.io/common/storj"
    17  	"storj.io/uplink"
    18  	"storj.io/uplink/internal/expose"
    19  	"storj.io/uplink/private/metaclient"
    20  )
    21  
    22  var mon = monkit.Package()
    23  
    24  // Error is default error class for uplink.
    25  var packageError = errs.Class("object")
    26  
    27  // ErrMethodNotAllowed is returned when method is not allowed against specified entity (e.g. object).
    28  var ErrMethodNotAllowed = errors.New("method not allowed")
    29  
    30  // IPSummary contains information about the object IP-s.
    31  type IPSummary = metaclient.GetObjectIPsResponse
    32  
    33  // VersionedObject represents object with version.
    34  // TODO find better place of name for this and related things.
    35  type VersionedObject struct {
    36  	uplink.Object
    37  	Version        []byte
    38  	IsDeleteMarker bool
    39  	Retention      *metaclient.Retention
    40  }
    41  
    42  // VersionedUpload represents upload which returnes object version at the end.
    43  type VersionedUpload struct {
    44  	upload *uplink.Upload
    45  }
    46  
    47  // ListObjectVersionsOptions defines listing options for versioned objects.
    48  type ListObjectVersionsOptions struct {
    49  	Prefix        string
    50  	Cursor        string
    51  	VersionCursor []byte
    52  	Recursive     bool
    53  	System        bool
    54  	Custom        bool
    55  	Limit         int
    56  }
    57  
    58  // Info returns the last information about the uploaded object.
    59  func (upload *VersionedUpload) Info() *VersionedObject {
    60  	info := upload.upload.Info()
    61  	return convertUplinkObject(info)
    62  }
    63  
    64  // Write uploads len(p) bytes from p to the object's data stream.
    65  // It returns the number of bytes written from p (0 <= n <= len(p))
    66  // and any error encountered that caused the write to stop early.
    67  func (upload *VersionedUpload) Write(p []byte) (n int, err error) {
    68  	return upload.upload.Write(p)
    69  }
    70  
    71  // Commit commits data to the store.
    72  //
    73  // Returns ErrUploadDone when either Abort or Commit has already been called.
    74  func (upload *VersionedUpload) Commit() error {
    75  	return upload.upload.Commit()
    76  }
    77  
    78  // SetCustomMetadata updates custom metadata to be included with the object.
    79  // If it is nil, it won't be modified.
    80  func (upload *VersionedUpload) SetCustomMetadata(ctx context.Context, custom uplink.CustomMetadata) error {
    81  	return upload.upload.SetCustomMetadata(ctx, custom)
    82  }
    83  
    84  // Abort aborts the upload.
    85  //
    86  // Returns ErrUploadDone when either Abort or Commit has already been called.
    87  func (upload *VersionedUpload) Abort() error {
    88  	return upload.upload.Abort()
    89  }
    90  
    91  // VersionedDownload is a download from Storj Network.
    92  type VersionedDownload struct {
    93  	download *uplink.Download
    94  }
    95  
    96  // Info returns the last information about the object.
    97  func (download *VersionedDownload) Info() *VersionedObject {
    98  	return convertObject(download_getMetaclientObject(download.download))
    99  }
   100  
   101  // Read downloads up to len(p) bytes into p from the object's data stream.
   102  // It returns the number of bytes read (0 <= n <= len(p)) and any error encountered.
   103  func (download *VersionedDownload) Read(p []byte) (n int, err error) {
   104  	return download.download.Read(p)
   105  }
   106  
   107  // Close closes the reader of the download.
   108  func (download *VersionedDownload) Close() error {
   109  	return download.download.Close()
   110  }
   111  
   112  // GetObjectIPs returns the IP-s for a given object.
   113  //
   114  // TODO: delete, once we have stopped using it.
   115  func GetObjectIPs(ctx context.Context, config uplink.Config, access *uplink.Access, bucket, key string) (_ [][]byte, err error) {
   116  	summary, err := GetObjectIPSummary(ctx, config, access, bucket, key)
   117  	if err != nil {
   118  		return nil, packageError.Wrap(err)
   119  	}
   120  	return summary.IPPorts, nil
   121  }
   122  
   123  // GetObjectIPSummary returns the object IP summary.
   124  func GetObjectIPSummary(ctx context.Context, config uplink.Config, access *uplink.Access, bucket, key string) (_ *IPSummary, err error) {
   125  	defer mon.Task()(&ctx)(&err)
   126  
   127  	dialer, err := expose.ConfigGetDialer(config, ctx)
   128  	if err != nil {
   129  		return nil, packageError.Wrap(err)
   130  	}
   131  	defer func() { err = errs.Combine(err, dialer.Pool.Close()) }()
   132  
   133  	metainfoClient, err := metaclient.DialNodeURL(ctx, dialer, access.SatelliteAddress(), expose.AccessGetAPIKey(access), config.UserAgent)
   134  	if err != nil {
   135  		return nil, packageError.Wrap(err)
   136  	}
   137  	defer func() { err = errs.Combine(err, metainfoClient.Close()) }()
   138  
   139  	db := metaclient.New(metainfoClient, storj.EncryptionParameters{}, expose.AccessGetEncAccess(access).Store)
   140  
   141  	summary, err := db.GetObjectIPs(ctx, metaclient.Bucket{Name: bucket}, key)
   142  	return summary, packageError.Wrap(err)
   143  }
   144  
   145  // StatObject returns information about an object at the specific key and version.
   146  func StatObject(ctx context.Context, project *uplink.Project, bucket, key string, version []byte) (info *VersionedObject, err error) {
   147  	defer mon.Task()(&ctx)(&err)
   148  
   149  	db, err := dialMetainfoDB(ctx, project)
   150  	if err != nil {
   151  		return nil, packageConvertKnownErrors(err, bucket, key)
   152  	}
   153  	defer func() { err = errs.Combine(err, db.Close()) }()
   154  
   155  	obj, err := db.GetObject(ctx, bucket, key, version)
   156  	if err != nil {
   157  		return nil, packageConvertKnownErrors(err, bucket, key)
   158  	}
   159  
   160  	return convertObject(&obj), nil
   161  }
   162  
   163  // DeleteObject deletes the object at the specific key.
   164  // Returned deleted is not nil when the access grant has read permissions and
   165  // the object was deleted.
   166  // TODO(ver) currently we are returning object that was returned by satellite
   167  // if its regular object (status != delete marker) it means no delete marker
   168  // was created.
   169  func DeleteObject(ctx context.Context, project *uplink.Project, bucket, key string, version []byte) (info *VersionedObject, err error) {
   170  	defer mon.Task()(&ctx)(&err)
   171  
   172  	db, err := dialMetainfoDB(ctx, project)
   173  	if err != nil {
   174  		return nil, packageConvertKnownErrors(err, bucket, key)
   175  	}
   176  	defer func() { err = errs.Combine(err, db.Close()) }()
   177  
   178  	obj, err := db.DeleteObject(ctx, bucket, key, version)
   179  	if err != nil {
   180  		return nil, packageConvertKnownErrors(err, bucket, key)
   181  	}
   182  
   183  	return convertObject(&obj), nil
   184  }
   185  
   186  // ListObjectVersions returns a list of objects and their versions.
   187  func ListObjectVersions(ctx context.Context, project *uplink.Project, bucket string, options *ListObjectVersionsOptions) (_ []*VersionedObject, more bool, err error) {
   188  	defer mon.Task()(&ctx)(&err)
   189  
   190  	db, err := dialMetainfoDB(ctx, project)
   191  	if err != nil {
   192  		return nil, false, convertKnownErrors(err, bucket, "")
   193  	}
   194  	defer func() { err = errs.Combine(err, db.Close()) }()
   195  
   196  	opts := metaclient.ListOptions{
   197  		Direction:          metaclient.After,
   198  		IncludeAllVersions: true,
   199  	}
   200  
   201  	if options != nil {
   202  		opts.Prefix = options.Prefix
   203  		opts.Cursor = options.Cursor
   204  		opts.VersionCursor = options.VersionCursor
   205  		opts.Recursive = options.Recursive
   206  		opts.IncludeCustomMetadata = options.Custom
   207  		opts.IncludeSystemMetadata = options.System
   208  		opts.Limit = options.Limit
   209  	}
   210  
   211  	obj, err := db.ListObjects(ctx, bucket, opts)
   212  	if err != nil {
   213  		return nil, false, convertKnownErrors(err, bucket, "")
   214  	}
   215  
   216  	var versions []*VersionedObject
   217  	for _, o := range obj.Items {
   218  		versions = append(versions, convertObject(&o))
   219  	}
   220  
   221  	return versions, obj.More, nil
   222  }
   223  
   224  // UploadObject starts an upload to the specific key.
   225  //
   226  // It is not guaranteed that the uncommitted object is visible through ListUploads while uploading.
   227  func UploadObject(ctx context.Context, project *uplink.Project, bucket, key string, options *uplink.UploadOptions) (_ *VersionedUpload, err error) {
   228  	defer mon.Task()(&ctx)(&err)
   229  
   230  	upload, err := project.UploadObject(ctx, bucket, key, options)
   231  	if err != nil {
   232  		return
   233  	}
   234  	return &VersionedUpload{
   235  		upload: upload,
   236  	}, nil
   237  }
   238  
   239  // DownloadObject starts a download from the specific key and version. If version is empty latest object will be downloaded.
   240  func DownloadObject(ctx context.Context, project *uplink.Project, bucket, key string, version []byte, options *uplink.DownloadOptions) (_ *VersionedDownload, err error) {
   241  	defer mon.Task()(&ctx)(&err)
   242  
   243  	download, err := downloadObjectWithVersion(ctx, project, bucket, key, version, options)
   244  	if err != nil {
   245  		return nil, packageConvertKnownErrors(err, bucket, key)
   246  	}
   247  	return &VersionedDownload{
   248  		download: download,
   249  	}, nil
   250  }
   251  
   252  // CommitUpload commits a multipart upload to bucket and key started with BeginUpload.
   253  //
   254  // uploadID is an upload identifier returned by BeginUpload.
   255  func CommitUpload(ctx context.Context, project *uplink.Project, bucket, key, uploadID string, opts *uplink.CommitUploadOptions) (info *VersionedObject, err error) {
   256  	defer mon.Task()(&ctx)(&err)
   257  
   258  	obj, err := project.CommitUpload(ctx, bucket, key, uploadID, opts)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  
   263  	return convertUplinkObject(obj), nil
   264  }
   265  
   266  // CopyObject atomically copies object to a different bucket or/and key.
   267  func CopyObject(ctx context.Context, project *uplink.Project, sourceBucket, sourceKey string, sourceVersion []byte, targetBucket, targetKey string, options *uplink.CopyObjectOptions) (_ *VersionedObject, err error) {
   268  	defer mon.Task()(&ctx)(&err)
   269  
   270  	db, err := dialMetainfoDB(ctx, project)
   271  	if err != nil {
   272  		return nil, packageConvertKnownErrors(err, sourceBucket, sourceKey)
   273  	}
   274  	defer func() { err = errs.Combine(err, db.Close()) }()
   275  
   276  	obj, err := db.CopyObject(ctx, sourceBucket, sourceKey, sourceVersion, targetBucket, targetKey)
   277  	if err != nil {
   278  		return nil, packageConvertKnownErrors(err, sourceBucket, sourceKey)
   279  	}
   280  
   281  	return convertObject(obj), nil
   282  }
   283  
   284  // convertObject converts metainfo.Object to Version.
   285  func convertObject(obj *metaclient.Object) *VersionedObject {
   286  	if obj == nil || obj.Bucket.Name == "" { // nil or zero object
   287  		return nil
   288  	}
   289  
   290  	return &VersionedObject{
   291  		Object: uplink.Object{
   292  			Key:      obj.Path,
   293  			IsPrefix: obj.IsPrefix,
   294  			System: uplink.SystemMetadata{
   295  				Created:       obj.Created,
   296  				Expires:       obj.Expires,
   297  				ContentLength: obj.Size,
   298  			},
   299  			Custom: obj.Metadata,
   300  		},
   301  		Version:        obj.Version,
   302  		IsDeleteMarker: obj.IsDeleteMarker,
   303  		Retention:      obj.Retention,
   304  	}
   305  }
   306  
   307  // convertObject converts metainfo.Object to Version.
   308  func convertUplinkObject(obj *uplink.Object) *VersionedObject {
   309  	if obj == nil {
   310  		return nil
   311  	}
   312  
   313  	return &VersionedObject{
   314  		Object:  *obj,
   315  		Version: objectVersion(obj),
   316  	}
   317  }
   318  
   319  func packageConvertKnownErrors(err error, bucket, key string) error {
   320  	if errs2.IsRPC(err, rpcstatus.MethodNotAllowed) {
   321  		return ErrMethodNotAllowed
   322  	}
   323  	return convertKnownErrors(err, bucket, key)
   324  }
   325  
   326  //go:linkname convertKnownErrors storj.io/uplink.convertKnownErrors
   327  func convertKnownErrors(err error, bucket, key string) error
   328  
   329  //go:linkname dialMetainfoDB storj.io/uplink.dialMetainfoDB
   330  func dialMetainfoDB(ctx context.Context, project *uplink.Project) (_ *metaclient.DB, err error)
   331  
   332  //go:linkname encryptionParameters storj.io/uplink.encryptionParameters
   333  func encryptionParameters(project *uplink.Project) storj.EncryptionParameters
   334  
   335  //go:linkname objectVersion storj.io/uplink.objectVersion
   336  func objectVersion(object *uplink.Object) []byte
   337  
   338  //go:linkname downloadObjectWithVersion storj.io/uplink.downloadObjectWithVersion
   339  func downloadObjectWithVersion(ctx context.Context, project *uplink.Project, bucket, key string, version []byte, options *uplink.DownloadOptions) (_ *uplink.Download, err error)
   340  
   341  //go:linkname download_getMetaclientObject storj.io/uplink.download_getMetaclientObject
   342  func download_getMetaclientObject(dl *uplink.Download) *metaclient.Object