Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a readonly backend #236

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions readonly/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package readonly

import (
"errors"
"net/url"

"github.com/graymeta/stow"
)

const (
// ConfigWrappedKind is a key whose value is the type of backend being wrapped.
ConfigWrappedKind = "wrapped_kind"
// ConfigWrapped is the key whose value is the config for the backend being wrapped.
ConfigWrapped = "wrapped"
)

// Kind is the kid of Location this package provides
const Kind = "readonly"

func init() {
validatefn := func(config stow.Config) error {
wrappedKind, ok := config.Config(ConfigWrappedKind)
if !ok || wrappedKind == "" {
return errors.New("missing wrapped kind")
}
wrappedConfig, ok := config.NestedConfig(ConfigWrapped)
if !ok {
return errors.New("missing config for wrapped")
}
return stow.Validate(wrappedKind, wrappedConfig)
}
makefn := func(config stow.Config) (stow.Location, error) {
wrappedKind, ok := config.Config(ConfigWrappedKind)
if !ok || wrappedKind == "" {
return nil, errors.New("missing wrapped kind")
}
wrappedConfig, ok := config.NestedConfig(ConfigWrapped)
if !ok {
return nil, errors.New("missing config for wrapped")
}

w, err := stow.Dial(wrappedKind, wrappedConfig)
if err != nil {
return nil, err
}

return &location{wrapped: w}, nil
}
kindfn := func(u *url.URL) bool {
return u.Scheme == Kind
}

stow.Register(Kind, makefn, kindfn, validatefn)
}
66 changes: 66 additions & 0 deletions readonly/container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package readonly

import (
"errors"
"io"

"github.com/graymeta/stow"
)

type container struct {
wrapped stow.Container
}

// ID gets a unique string describing this Container.
func (c *container) ID() string {
return c.wrapped.ID()
}

// Name gets a human-readable name describing this Container.
func (c *container) Name() string {
return c.wrapped.Name()
}

// Item gets an item by its ID.
func (c *container) Item(id string) (stow.Item, error) {
it, err := c.wrapped.Item(id)
if err != nil {
return nil, err
}

return &item{wrapped: it}, nil
}

// Items gets a page of items with the specified
// prefix for this Container.
// The specified cursor is a pointer to the start of
// the items to get. It it obtained from a previous
// call to this method, or should be CursorStart for the
// first page.
// count is the number of items to return per page.
// The returned cursor can be checked with IsCursorEnd to
// decide if there are any more items or not.
func (c *container) Items(prefix, cursor string, count int) ([]stow.Item, string, error) {
its, startAfter, err := c.wrapped.Items(prefix, cursor, count)
if err != nil {
return nil, "", err
}

wrapped := make([]stow.Item, len(its))
for i, val := range its {
wrapped[i] = &item{wrapped: val}
}

return wrapped, startAfter, nil
}

// RemoveItem removes the Item with the specified ID.
func (c *container) RemoveItem(id string) error {
return errors.New("readonly")
}

// Put creates a new Item with the specified name, and contents
// read from the reader.
func (c *container) Put(name string, r io.Reader, size int64, metadata map[string]interface{}) (stow.Item, error) {
return nil, errors.New("readonly")
}
61 changes: 61 additions & 0 deletions readonly/item.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package readonly

import (
"io"
"net/url"
"time"

"github.com/graymeta/stow"
)

type item struct {
wrapped stow.Item
}

// ID gets a unique string describing this Item.
func (i *item) ID() string {
return i.wrapped.ID()
}

// Name gets a human-readable name describing this Item.
func (i *item) Name() string {
return i.wrapped.Name()
}

// URL gets a URL for this item.
// For example:
// local: file:///path/to/something
// azure: azure://host:port/api/something
// s3: s3://host:post/etc
func (i *item) URL() *url.URL {
return i.wrapped.URL()
}

// Size gets the size of the Item's contents in bytes.
func (i *item) Size() (int64, error) {
return i.wrapped.Size()
}

// Open opens the Item for reading.
// Calling code must close the io.ReadCloser.
func (i *item) Open() (io.ReadCloser, error) {
return i.wrapped.Open()
}

// ETag is a string that is different when the Item is
// different, and the same when the item is the same.
// Usually this is the last modified datetime.
func (i *item) ETag() (string, error) {
return i.wrapped.ETag()
}

// LastMod returns the last modified date of the file.
func (i *item) LastMod() (time.Time, error) {
return i.wrapped.LastMod()
}

// Metadata gets a map of key/values that belong
// to this Item.
func (i *item) Metadata() (map[string]interface{}, error) {
return i.wrapped.Metadata()
}
73 changes: 73 additions & 0 deletions readonly/location.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package readonly

import (
"errors"
"net/url"

"github.com/graymeta/stow"
)

type location struct {
wrapped stow.Location
}

// Closes the underlying location.
func (l *location) Close() error {
return l.wrapped.Close()
}

// CreateContainer creates a new Container with the
// specified name.
func (l *location) CreateContainer(name string) (stow.Container, error) {
return nil, errors.New("readonly")
}

// Containers gets a page of containers
// with the specified prefix from this Location.
// The specified cursor is a pointer to the start of
// the containers to get. It it obtained from a previous
// call to this method, or should be CursorStart for the
// first page.
// count is the number of items to return per page.
// The returned cursor can be checked with IsCursorEnd to
// decide if there are any more items or not.
func (l *location) Containers(prefix string, cursor string, count int) ([]stow.Container, string, error) {
cs, startAfter, err := l.wrapped.Containers(prefix, cursor, count)
if err != nil {
return nil, "", err
}

wrapped := make([]stow.Container, len(cs))
for i, val := range cs {
wrapped[i] = &container{wrapped: val}
}

return wrapped, startAfter, nil
}

// Container gets the Container with the specified
// identifier.
func (l *location) Container(id string) (stow.Container, error) {
c, err := l.wrapped.Container(id)
if err != nil {
return nil, err
}

return &container{wrapped: c}, nil
}

// RemoveContainer removes the container with the specified ID.
func (l *location) RemoveContainer(id string) error {
return errors.New("readonly")
}

// ItemByURL gets an Item at this location with the
// specified URL.
func (l *location) ItemByURL(url *url.URL) (stow.Item, error) {
i, err := l.wrapped.ItemByURL(url)
if err != nil {
return nil, err
}

return &item{wrapped: i}, nil
}
23 changes: 21 additions & 2 deletions stow.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,14 @@ type Config interface {
// Config gets a string configuration value and a
// bool indicating whether the value was present or not.
Config(name string) (string, bool)
// NestedConfig gets a key/value configuration and a
// bool indicating whehter the value was present or not.
NestedConfig(name string) (Config, bool)
// Set sets the configuration name to specified value
Set(name, value string)
// SetNestedConfig sets the configuration name to the
// specified key/value configuration.
SetNestedConfig(name string, value Config)
}

// Register adds a Location implementation, with two helper functions.
Expand Down Expand Up @@ -221,12 +227,19 @@ func KindByURL(u *url.URL) (string, error) {

// ConfigMap is a map[string]string that implements
// the Config method.
type ConfigMap map[string]string
type ConfigMap map[string]interface{}

// Config gets a string configuration value and a
// bool indicating whether the value was present or not.
func (c ConfigMap) Config(name string) (string, bool) {
val, ok := c[name]
val, ok := c[name].(string)
return val, ok
}

// NestedConfig gets a key/value configuration and a
// bool indicating whehter the value was present or not.
func (c ConfigMap) NestedConfig(name string) (Config, bool) {
val, ok := c[name].(Config)
return val, ok
}

Expand All @@ -235,6 +248,12 @@ func (c ConfigMap) Set(name, value string) {
c[name] = value
}

// SetNestedConfig sets the configuration name to the
// specified key/value configuration.
func (c ConfigMap) SetNestedConfig(name string, value Config) {
c[name] = value
}

// errUnknownKind indicates that a kind is unknown.
type errUnknownKind string

Expand Down