github.com/pachyderm/pachyderm@v1.13.4/src/server/pfs/s3/bucket.go (about)

     1  package s3
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strings"
     7  
     8  	"github.com/gogo/protobuf/types"
     9  	glob "github.com/pachyderm/ohmyglob"
    10  	pfsClient "github.com/pachyderm/pachyderm/src/client/pfs"
    11  	pfsServer "github.com/pachyderm/pachyderm/src/server/pfs"
    12  	"github.com/pachyderm/pachyderm/src/server/pkg/ancestry"
    13  	"github.com/pachyderm/pachyderm/src/server/pkg/errutil"
    14  	"github.com/pachyderm/s2"
    15  )
    16  
    17  func newContents(fileInfo *pfsClient.FileInfo) (s2.Contents, error) {
    18  	t, err := types.TimestampFromProto(fileInfo.Committed)
    19  	if err != nil {
    20  		return s2.Contents{}, err
    21  	}
    22  
    23  	return s2.Contents{
    24  		Key:          fileInfo.File.Path,
    25  		LastModified: t,
    26  		ETag:         fmt.Sprintf("%x", fileInfo.Hash),
    27  		Size:         fileInfo.SizeBytes,
    28  		StorageClass: globalStorageClass,
    29  		Owner:        defaultUser,
    30  	}, nil
    31  }
    32  
    33  func (c *controller) GetLocation(r *http.Request, bucketName string) (string, error) {
    34  	c.logger.Debugf("GetLocation: %+v", bucketName)
    35  
    36  	pc, err := c.requestClient(r)
    37  	if err != nil {
    38  		return "", err
    39  	}
    40  
    41  	bucket, err := c.driver.bucket(pc, r, bucketName)
    42  	if err != nil {
    43  		return "", err
    44  	}
    45  	_, err = c.driver.bucketCapabilities(pc, r, bucket)
    46  	if err != nil {
    47  		return "", err
    48  	}
    49  
    50  	return globalLocation, nil
    51  }
    52  
    53  func (c *controller) ListObjects(r *http.Request, bucketName, prefix, marker, delimiter string, maxKeys int) (*s2.ListObjectsResult, error) {
    54  	c.logger.Debugf("ListObjects: bucketName=%+v, prefix=%+v, marker=%+v, delimiter=%+v, maxKeys=%+v", bucketName, prefix, marker, delimiter, maxKeys)
    55  
    56  	// Strip / from prefix to normalize: "/" means "all objects" and "/foo"
    57  	// means the same as "foo"
    58  	prefix = strings.TrimPrefix(prefix, "/")
    59  
    60  	pc, err := c.requestClient(r)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	if delimiter != "" && delimiter != "/" {
    66  		return nil, invalidDelimiterError(r)
    67  	}
    68  
    69  	bucket, err := c.driver.bucket(pc, r, bucketName)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	bucketCaps, err := c.driver.bucketCapabilities(pc, r, bucket)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	result := s2.ListObjectsResult{
    79  		Contents:       []*s2.Contents{},
    80  		CommonPrefixes: []*s2.CommonPrefixes{},
    81  	}
    82  
    83  	if !bucketCaps.readable {
    84  		// serve empty results if we can't read the bucket; this helps with s3
    85  		// conformance
    86  		return &result, nil
    87  	}
    88  
    89  	recursive := delimiter == ""
    90  	var pattern string
    91  	if recursive {
    92  		pattern = fmt.Sprintf("%s**", glob.QuoteMeta(prefix))
    93  	} else {
    94  		pattern = fmt.Sprintf("%s*", glob.QuoteMeta(prefix))
    95  	}
    96  
    97  	err = pc.GlobFileF(bucket.Repo, bucket.Commit, pattern, func(fileInfo *pfsClient.FileInfo) error {
    98  		if fileInfo.FileType == pfsClient.FileType_DIR {
    99  			if fileInfo.File.Path == "/" {
   100  				// skip the root directory
   101  				return nil
   102  			}
   103  			if recursive {
   104  				// skip directories if recursing
   105  				return nil
   106  			}
   107  		} else if fileInfo.FileType != pfsClient.FileType_FILE {
   108  			// skip anything that isn't a file or dir
   109  			return nil
   110  		}
   111  
   112  		fileInfo.File.Path = fileInfo.File.Path[1:] // strip leading slash
   113  
   114  		if !strings.HasPrefix(fileInfo.File.Path, prefix) {
   115  			return nil
   116  		}
   117  		if fileInfo.File.Path <= marker {
   118  			return nil
   119  		}
   120  
   121  		if len(result.Contents)+len(result.CommonPrefixes) >= maxKeys {
   122  			if maxKeys > 0 {
   123  				result.IsTruncated = true
   124  			}
   125  			return errutil.ErrBreak
   126  		}
   127  		if fileInfo.FileType == pfsClient.FileType_FILE {
   128  			c, err := newContents(fileInfo)
   129  			if err != nil {
   130  				return err
   131  			}
   132  
   133  			result.Contents = append(result.Contents, &c)
   134  		} else {
   135  			result.CommonPrefixes = append(result.CommonPrefixes, &s2.CommonPrefixes{
   136  				Prefix: fmt.Sprintf("%s/", fileInfo.File.Path),
   137  				Owner:  defaultUser,
   138  			})
   139  		}
   140  
   141  		return nil
   142  	})
   143  
   144  	return &result, err
   145  }
   146  
   147  func (c *controller) CreateBucket(r *http.Request, bucketName string) error {
   148  	c.logger.Debugf("CreateBucket: %+v", bucketName)
   149  
   150  	if !c.driver.canModifyBuckets() {
   151  		return s2.NotImplementedError(r)
   152  	}
   153  
   154  	pc, err := c.requestClient(r)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	bucket, err := c.driver.bucket(pc, r, bucketName)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	err = pc.CreateRepo(bucket.Repo)
   165  	if err != nil {
   166  		if errutil.IsAlreadyExistError(err) {
   167  			// Bucket already exists - this is not an error so long as the
   168  			// branch being created is new. Verify if that is the case now,
   169  			// since PFS' `CreateBranch` won't error out.
   170  			_, err := pc.InspectBranch(bucket.Repo, bucket.Commit)
   171  			if err != nil {
   172  				if !pfsServer.IsBranchNotFoundErr(err) {
   173  					return s2.InternalError(r, err)
   174  				}
   175  			} else {
   176  				return s2.BucketAlreadyOwnedByYouError(r)
   177  			}
   178  		} else if ancestry.IsInvalidNameError(err) {
   179  			return s2.InvalidBucketNameError(r)
   180  		} else {
   181  			return s2.InternalError(r, err)
   182  		}
   183  	}
   184  
   185  	err = pc.CreateBranch(bucket.Repo, bucket.Commit, "", nil)
   186  	if err != nil {
   187  		if ancestry.IsInvalidNameError(err) {
   188  			return s2.InvalidBucketNameError(r)
   189  		}
   190  		return s2.InternalError(r, err)
   191  	}
   192  
   193  	return nil
   194  }
   195  
   196  func (c *controller) DeleteBucket(r *http.Request, bucketName string) error {
   197  	c.logger.Debugf("DeleteBucket: %+v", bucketName)
   198  
   199  	if !c.driver.canModifyBuckets() {
   200  		return s2.NotImplementedError(r)
   201  	}
   202  
   203  	pc, err := c.requestClient(r)
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	bucket, err := c.driver.bucket(pc, r, bucketName)
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	// `DeleteBranch` does not return an error if a non-existing branch is
   214  	// deleting. So first, we verify that the branch exists so we can
   215  	// otherwise return a 404.
   216  	branchInfo, err := pc.InspectBranch(bucket.Repo, bucket.Commit)
   217  	if err != nil {
   218  		return maybeNotFoundError(r, err)
   219  	}
   220  
   221  	if branchInfo.Head != nil {
   222  		hasFiles := false
   223  		err = pc.Walk(branchInfo.Branch.Repo.Name, branchInfo.Head.ID, "", func(fileInfo *pfsClient.FileInfo) error {
   224  			if fileInfo.FileType == pfsClient.FileType_FILE {
   225  				hasFiles = true
   226  				return errutil.ErrBreak
   227  			}
   228  			return nil
   229  		})
   230  		if err != nil {
   231  			return s2.InternalError(r, err)
   232  		}
   233  
   234  		if hasFiles {
   235  			return s2.BucketNotEmptyError(r)
   236  		}
   237  	}
   238  
   239  	err = pc.DeleteBranch(bucket.Repo, bucket.Commit, false)
   240  	if err != nil {
   241  		return s2.InternalError(r, err)
   242  	}
   243  
   244  	repoInfo, err := pc.InspectRepo(bucket.Repo)
   245  	if err != nil {
   246  		return s2.InternalError(r, err)
   247  	}
   248  
   249  	// delete the repo if this was the last branch
   250  	if len(repoInfo.Branches) == 0 {
   251  		err = pc.DeleteRepo(bucket.Repo, false)
   252  		if err != nil {
   253  			return s2.InternalError(r, err)
   254  		}
   255  	}
   256  
   257  	return nil
   258  }
   259  
   260  func (c *controller) ListObjectVersions(r *http.Request, bucketName, prefix, keyMarker, versionIDMarker string, delimiter string, maxKeys int) (*s2.ListObjectVersionsResult, error) {
   261  	// NOTE: because this endpoint isn't implemented, conformance tests will
   262  	// fail on teardown. It's nevertheless unimplemented because it's too
   263  	// expensive to pull off with PFS until this is implemented:
   264  	// https://github.com/pachyderm/pachyderm/issues/3896
   265  	c.logger.Debugf("ListObjectVersions: bucketName=%+v, prefix=%+v, keyMarker=%+v, versionIDMarker=%+v, delimiter=%+v, maxKeys=%+v", bucketName, prefix, keyMarker, versionIDMarker, delimiter, maxKeys)
   266  	return nil, s2.NotImplementedError(r)
   267  }
   268  
   269  func (c *controller) GetBucketVersioning(r *http.Request, bucketName string) (string, error) {
   270  	c.logger.Debugf("GetBucketVersioning: %+v", bucketName)
   271  
   272  	pc, err := c.requestClient(r)
   273  	if err != nil {
   274  		return "", err
   275  	}
   276  
   277  	bucket, err := c.driver.bucket(pc, r, bucketName)
   278  	if err != nil {
   279  		return "", err
   280  	}
   281  	bucketCaps, err := c.driver.bucketCapabilities(pc, r, bucket)
   282  	if err != nil {
   283  		return "", err
   284  	}
   285  
   286  	if bucketCaps.historicVersions {
   287  		return s2.VersioningEnabled, nil
   288  	}
   289  	return s2.VersioningDisabled, nil
   290  }
   291  
   292  func (c *controller) SetBucketVersioning(r *http.Request, bucketName, status string) error {
   293  	c.logger.Debugf("SetBucketVersioning: bucketName=%+v, status=%+v", bucketName, status)
   294  
   295  	pc, err := c.requestClient(r)
   296  	if err != nil {
   297  		return err
   298  	}
   299  
   300  	bucket, err := c.driver.bucket(pc, r, bucketName)
   301  	if err != nil {
   302  		return err
   303  	}
   304  	bucketCaps, err := c.driver.bucketCapabilities(pc, r, bucket)
   305  	if err != nil {
   306  		return err
   307  	}
   308  
   309  	if bucketCaps.historicVersions {
   310  		if status != s2.VersioningEnabled {
   311  			return s2.NotImplementedError(r)
   312  		}
   313  	} else {
   314  		if status != s2.VersioningDisabled {
   315  			return s2.NotImplementedError(r)
   316  		}
   317  	}
   318  	return nil
   319  }