// Copyright 2018 The Go Cloud Development Kit Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package driver defines interfaces to be implemented by blob drivers, which // will be used by the blob package to interact with the underlying services. // Application code should use package blob. package driver // import "gocloud.dev/blob/driver" import ( "context" "errors" "io" "strings" "time" "gocloud.dev/gcerrors" ) // ReaderOptions controls Reader behaviors. type ReaderOptions struct { // BeforeRead is a callback that must be called exactly once before // any data is read, unless NewRangeReader returns an error before then, in // which case it should not be called at all. // asFunc allows drivers to expose driver-specific types; // see Bucket.As for more details. BeforeRead func(asFunc func(interface{}) bool) error } // Reader reads an object from the blob. type Reader interface { io.ReadCloser // Attributes returns a subset of attributes about the blob. // The portable type will not modify the returned ReaderAttributes. Attributes() *ReaderAttributes // As allows drivers to expose driver-specific types; // see Bucket.As for more details. As(interface{}) bool } // Writer writes an object to the blob. type Writer interface { io.WriteCloser } // WriterOptions controls behaviors of Writer. type WriterOptions struct { // BufferSize changes the default size in byte of the maximum part Writer can // write in a single request, if supported. Larger objects will be split into // multiple requests. BufferSize int // CacheControl specifies caching attributes that services may use // when serving the blob. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control CacheControl string // ContentDisposition specifies whether the blob content is expected to be // displayed inline or as an attachment. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition ContentDisposition string // ContentEncoding specifies the encoding used for the blob's content, if any. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding ContentEncoding string // ContentLanguage specifies the language used in the blob's content, if any. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language ContentLanguage string // ContentMD5 is used as a message integrity check. // The portable type checks that the MD5 hash of the bytes written matches // ContentMD5. // If len(ContentMD5) > 0, driver implementations may pass it to their // underlying network service to guarantee the integrity of the bytes in // transit. ContentMD5 []byte // Metadata holds key/value strings to be associated with the blob. // Keys are guaranteed to be non-empty and lowercased. Metadata map[string]string // BeforeWrite is a callback that must be called exactly once before // any data is written, unless NewTypedWriter returns an error, in // which case it should not be called. // asFunc allows drivers to expose driver-specific types; // see Bucket.As for more details. BeforeWrite func(asFunc func(interface{}) bool) error } // CopyOptions controls options for Copy. type CopyOptions struct { // BeforeCopy is a callback that must be called before initiating the Copy. // asFunc allows drivers to expose driver-specific types; // see Bucket.As for more details. BeforeCopy func(asFunc func(interface{}) bool) error } // ReaderAttributes contains a subset of attributes about a blob that are // accessible from Reader. type ReaderAttributes struct { // ContentType is the MIME type of the blob object. It must not be empty. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type ContentType string // ModTime is the time the blob object was last modified. ModTime time.Time // Size is the size of the object in bytes. Size int64 } // Attributes contains attributes about a blob. type Attributes struct { // CacheControl specifies caching attributes that services may use // when serving the blob. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control CacheControl string // ContentDisposition specifies whether the blob content is expected to be // displayed inline or as an attachment. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition ContentDisposition string // ContentEncoding specifies the encoding used for the blob's content, if any. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding ContentEncoding string // ContentLanguage specifies the language used in the blob's content, if any. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Language ContentLanguage string // ContentType is the MIME type of the blob object. It must not be empty. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type ContentType string // Metadata holds key/value pairs associated with the blob. // Keys will be lowercased by the portable type before being returned // to the user. If there are duplicate case-insensitive keys (e.g., // "foo" and "FOO"), only one value will be kept, and it is undefined // which one. Metadata map[string]string // ModTime is the time the blob object was last modified. ModTime time.Time // Size is the size of the object in bytes. Size int64 // MD5 is an MD5 hash of the blob contents or nil if not available. MD5 []byte // AsFunc allows drivers to expose driver-specific types; // see Bucket.As for more details. // If not set, no driver-specific types are supported. AsFunc func(interface{}) bool } // ListOptions sets options for listing objects in the bucket. type ListOptions struct { // Prefix indicates that only results with the given prefix should be // returned. Prefix string // Delimiter sets the delimiter used to define a hierarchical namespace, // like a filesystem with "directories". // // An empty delimiter means that the bucket is treated as a single flat // namespace. // // A non-empty delimiter means that any result with the delimiter in its key // after Prefix is stripped will be returned with ListObject.IsDir = true, // ListObject.Key truncated after the delimiter, and zero values for other // ListObject fields. These results represent "directories". Multiple results // in a "directory" are returned as a single result. Delimiter string // PageSize sets the maximum number of objects to be returned. // 0 means no maximum; driver implementations should choose a reasonable // max. It is guaranteed to be >= 0. PageSize int // PageToken may be filled in with the NextPageToken from a previous // ListPaged call. PageToken []byte // BeforeList is a callback that must be called exactly once during ListPaged, // before the underlying service's list is executed. // asFunc allows drivers to expose driver-specific types; // see Bucket.As for more details. BeforeList func(asFunc func(interface{}) bool) error } // ListObject represents a specific blob object returned from ListPaged. type ListObject struct { // Key is the key for this blob. Key string // ModTime is the time the blob object was last modified. ModTime time.Time // Size is the size of the object in bytes. Size int64 // MD5 is an MD5 hash of the blob contents or nil if not available. MD5 []byte // IsDir indicates that this result represents a "directory" in the // hierarchical namespace, ending in ListOptions.Delimiter. Key can be // passed as ListOptions.Prefix to list items in the "directory". // Fields other than Key and IsDir will not be set if IsDir is true. IsDir bool // AsFunc allows drivers to expose driver-specific types; // see Bucket.As for more details. // If not set, no driver-specific types are supported. AsFunc func(interface{}) bool } // ListPage represents a page of results return from ListPaged. type ListPage struct { // Objects is the slice of objects found. If ListOptions.PageSize > 0, // it should have at most ListOptions.PageSize entries. // // Objects should be returned in lexicographical order of UTF-8 encoded keys, // including across pages. I.e., all objects returned from a ListPage request // made using a PageToken from a previous ListPage request's NextPageToken // should have Key >= the Key for all objects from the previous request. Objects []*ListObject // NextPageToken should be left empty unless there are more objects // to return. The value may be returned as ListOptions.PageToken on a // subsequent ListPaged call, to fetch the next page of results. // It can be an arbitrary []byte; it need not be a valid key. NextPageToken []byte } // Bucket provides read, write and delete operations on objects within it on the // blob service. type Bucket interface { // ErrorCode should return a code that describes the error, which was returned by // one of the other methods in this interface. ErrorCode(error) gcerrors.ErrorCode // As converts i to driver-specific types. // See https://gocloud.dev/concepts/as/ for background information. As(i interface{}) bool // ErrorAs allows drivers to expose driver-specific types for returned // errors. // See https://gocloud.dev/concepts/as/ for background information. ErrorAs(error, interface{}) bool // Attributes returns attributes for the blob. If the specified object does // not exist, Attributes must return an error for which ErrorCode returns // gcerrors.NotFound. // The portable type will not modify the returned Attributes. Attributes(ctx context.Context, key string) (*Attributes, error) // ListPaged lists objects in the bucket, in lexicographical order by // UTF-8-encoded key, returning pages of objects at a time. // Services are only required to be eventually consistent with respect // to recently written or deleted objects. That is to say, there is no // guarantee that an object that's been written will immediately be returned // from ListPaged. // opts is guaranteed to be non-nil. ListPaged(ctx context.Context, opts *ListOptions) (*ListPage, error) // NewRangeReader returns a Reader that reads part of an object, reading at // most length bytes starting at the given offset. If length is negative, it // will read until the end of the object. If the specified object does not // exist, NewRangeReader must return an error for which ErrorCode returns // gcerrors.NotFound. // opts is guaranteed to be non-nil. NewRangeReader(ctx context.Context, key string, offset, length int64, opts *ReaderOptions) (Reader, error) // NewTypedWriter returns Writer that writes to an object associated with key. // // A new object will be created unless an object with this key already exists. // Otherwise any previous object with the same key will be replaced. // The object may not be available (and any previous object will remain) // until Close has been called. // // contentType sets the MIME type of the object to be written. It must not be // empty. opts is guaranteed to be non-nil. // // The caller must call Close on the returned Writer when done writing. // // Implementations should abort an ongoing write if ctx is later canceled, // and do any necessary cleanup in Close. Close should then return ctx.Err(). NewTypedWriter(ctx context.Context, key, contentType string, opts *WriterOptions) (Writer, error) // Copy copies the object associated with srcKey to dstKey. // // If the source object does not exist, Copy must return an error for which // ErrorCode returns gcerrors.NotFound. // // If the destination object already exists, it should be overwritten. // // opts is guaranteed to be non-nil. Copy(ctx context.Context, dstKey, srcKey string, opts *CopyOptions) error // Delete deletes the object associated with key. If the specified object does // not exist, Delete must return an error for which ErrorCode returns // gcerrors.NotFound. Delete(ctx context.Context, key string) error // SignedURL returns a URL that can be used to GET the blob for the duration // specified in opts.Expiry. opts is guaranteed to be non-nil. // If not supported, return an error for which ErrorCode returns // gcerrors.Unimplemented. SignedURL(ctx context.Context, key string, opts *SignedURLOptions) (string, error) // Close cleans up any resources used by the Bucket. Once Close is called, // there will be no method calls to the Bucket other than As, ErrorAs, and // ErrorCode. There may be open readers or writers that will receive calls. // It is up to the driver as to how these will be handled. Close() error } // SignedURLOptions sets options for SignedURL. type SignedURLOptions struct { // Expiry sets how long the returned URL is valid for. It is guaranteed to be > 0. Expiry time.Duration // Method is the HTTP method that can be used on the URL; one of "GET", "PUT", // or "DELETE". Drivers must implement all 3. Method string } // prefixedBucket implements Bucket by prepending prefix to all keys. type prefixedBucket struct { base Bucket prefix string } // NewPrefixedBucket returns a Bucket based on b with all keys modified to have // prefix. func NewPrefixedBucket(b Bucket, prefix string) Bucket { return &prefixedBucket{base: b, prefix: prefix} } func (b *prefixedBucket) ErrorCode(err error) gcerrors.ErrorCode { return b.base.ErrorCode(err) } func (b *prefixedBucket) As(i interface{}) bool { return b.base.As(i) } func (b *prefixedBucket) ErrorAs(err error, i interface{}) bool { return b.base.ErrorAs(err, i) } func (b *prefixedBucket) Attributes(ctx context.Context, key string) (*Attributes, error) { return b.base.Attributes(ctx, b.prefix+key) } func (b *prefixedBucket) ListPaged(ctx context.Context, opts *ListOptions) (*ListPage, error) { var myopts ListOptions if opts != nil { myopts = *opts } myopts.Prefix = b.prefix + myopts.Prefix page, err := b.base.ListPaged(ctx, &myopts) if err != nil { return nil, err } for _, p := range page.Objects { p.Key = strings.TrimPrefix(p.Key, b.prefix) } return page, nil } func (b *prefixedBucket) NewRangeReader(ctx context.Context, key string, offset, length int64, opts *ReaderOptions) (Reader, error) { return b.base.NewRangeReader(ctx, b.prefix+key, offset, length, opts) } func (b *prefixedBucket) NewTypedWriter(ctx context.Context, key, contentType string, opts *WriterOptions) (Writer, error) { if key == "" { return nil, errors.New("invalid key (empty string)") } return b.base.NewTypedWriter(ctx, b.prefix+key, contentType, opts) } func (b *prefixedBucket) Copy(ctx context.Context, dstKey, srcKey string, opts *CopyOptions) error { return b.base.Copy(ctx, b.prefix+dstKey, b.prefix+srcKey, opts) } func (b *prefixedBucket) Delete(ctx context.Context, key string) error { return b.base.Delete(ctx, b.prefix+key) } func (b *prefixedBucket) SignedURL(ctx context.Context, key string, opts *SignedURLOptions) (string, error) { return b.base.SignedURL(ctx, b.prefix+key, opts) } func (b *prefixedBucket) Close() error { return b.base.Close() }