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 }