-
Notifications
You must be signed in to change notification settings - Fork 879
Adds prototype NG driver apis #630
Changes from all commits
9143fdf
1d06ffc
6e12249
a639c0d
8ef5c07
167806b
e4d8e39
d93c46e
a5fd05e
f5d695b
114354d
dc8dd81
8904675
ad68f52
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
FROM golang:1.3.3 | ||
|
||
WORKDIR /go/src/ | ||
|
||
RUN ["mkdir", "-p", "github.com/docker/"] | ||
|
||
VOLUME ["/Users/bbland/code/docker/docker-registry:github.com/docker/docker-registry"] | ||
|
||
# WORKDIR github.com/docker/docker-registry/contrib/golang_impl_ng | ||
|
||
RUN ["go", "get", "-d", "-v", "./..."] | ||
RUN ["go", "fmt", "./..."] | ||
RUN ["go", "install", "./..."] | ||
ENTRYPOINT ["/go/bin/driverclient", "-driver"] | ||
CMD ["inmemory"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package driver | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
) | ||
|
||
type Driver interface { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
GetContent(path string) ([]byte, error) | ||
PutContent(path string, content []byte) error | ||
ReadStream(path string, offset uint64) (io.ReadCloser, error) | ||
WriteStream(path string, offset uint64, readCloser io.ReadCloser) error | ||
ResumeWritePosition(path string) (uint64, error) | ||
List(prefix string) ([]string, error) | ||
Move(sourcePath string, destPath string) error | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To keep consistency, should this be changed to |
||
Delete(path string) error | ||
} | ||
|
||
type PathNotFoundError struct { | ||
Path string | ||
} | ||
|
||
func (err PathNotFoundError) Error() string { | ||
return fmt.Sprintf("Path not found: %s", err.Path) | ||
} | ||
|
||
type InvalidOffsetError struct { | ||
Path string | ||
Offset uint64 | ||
} | ||
|
||
func (err InvalidOffsetError) Error() string { | ||
return fmt.Sprintf("Invalid offset: %d for path: %s", err.Offset, err.Path) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
package filesystem | ||
|
||
import ( | ||
"io" | ||
"io/ioutil" | ||
"os" | ||
"path" | ||
"strings" | ||
|
||
"github.com/docker/docker-registry/contrib/golang_impl_ng/driver" | ||
) | ||
|
||
type FilesystemDriver struct { | ||
rootDirectory string | ||
} | ||
|
||
func NewDriver(rootDirectory string) *FilesystemDriver { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In terms of previous filesystem driver configuration, there was a lot more than just the root directory. For example, in the S3 driver, you had to set up your access key/secret key to connect to the S3 bucket. For the swift driver, you needed to specify your tenant name, region name, your keystone API key, etc. How will this fit in with this implementation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current intention is that each driver will have a main() method that handles (json) serialized parameters when run out-of-process over ipc. If you reference contrib/golang_impl_ng/main/driver/filesystem/filesystem.go, you can see that we're pulling the RootDirectory parameter out of the first command line argument, which is a json blob of all required configuration params. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On Mon, Oct 13, 2014 at 04:43:12PM -0700, Matthew Fisher wrote:
Detached libchan storage drivers can just read their own config files |
||
return &FilesystemDriver{rootDirectory} | ||
} | ||
|
||
func (d *FilesystemDriver) subPath(subPath string) string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to add additional cruft, as it's just a one-liner function. There's no true difference between calling |
||
return path.Join(d.rootDirectory, subPath) | ||
} | ||
|
||
func (d *FilesystemDriver) GetContent(path string) ([]byte, error) { | ||
contents, err := ioutil.ReadFile(d.subPath(path)) | ||
if err != nil { | ||
return nil, driver.PathNotFoundError{path} | ||
} | ||
return contents, nil | ||
} | ||
|
||
func (d *FilesystemDriver) PutContent(subPath string, contents []byte) error { | ||
fullPath := d.subPath(subPath) | ||
parentDir := path.Dir(fullPath) | ||
err := os.MkdirAll(parentDir, 0755) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = ioutil.WriteFile(fullPath, contents, 0644) | ||
return err | ||
} | ||
|
||
func (d *FilesystemDriver) ReadStream(path string, offset uint64) (io.ReadCloser, error) { | ||
file, err := os.OpenFile(d.subPath(path), os.O_RDONLY, 0644) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
seekPos, err := file.Seek(int64(offset), os.SEEK_SET) | ||
if err != nil { | ||
file.Close() | ||
return nil, err | ||
} else if seekPos < int64(offset) { | ||
file.Close() | ||
return nil, driver.InvalidOffsetError{path, offset} | ||
} | ||
|
||
return file, nil | ||
} | ||
|
||
func (d *FilesystemDriver) WriteStream(subPath string, offset uint64, reader io.ReadCloser) error { | ||
defer reader.Close() | ||
|
||
resumableOffset, err := d.ResumeWritePosition(subPath) | ||
if _, pathNotFound := err.(driver.PathNotFoundError); err != nil && !pathNotFound { | ||
return err | ||
} | ||
|
||
if offset > resumableOffset { | ||
return driver.InvalidOffsetError{subPath, offset} | ||
} | ||
|
||
fullPath := d.subPath(subPath) | ||
parentDir := path.Dir(fullPath) | ||
err = os.MkdirAll(parentDir, 0755) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var file *os.File | ||
if offset == 0 { | ||
file, err = os.Create(fullPath) | ||
} else { | ||
file, err = os.OpenFile(fullPath, os.O_WRONLY|os.O_APPEND, 0) | ||
} | ||
|
||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
buf := make([]byte, 32*1024) | ||
for { | ||
bytesRead, er := reader.Read(buf) | ||
if bytesRead > 0 { | ||
bytesWritten, ew := file.WriteAt(buf[0:bytesRead], int64(offset)) | ||
if bytesWritten > 0 { | ||
offset += uint64(bytesWritten) | ||
} | ||
if ew != nil { | ||
err = ew | ||
break | ||
} | ||
if bytesRead != bytesWritten { | ||
err = io.ErrShortWrite | ||
break | ||
} | ||
} | ||
if er == io.EOF { | ||
break | ||
} | ||
if er != nil { | ||
err = er | ||
break | ||
} | ||
} | ||
return err | ||
} | ||
|
||
func (d *FilesystemDriver) ResumeWritePosition(subPath string) (uint64, error) { | ||
fullPath := d.subPath(subPath) | ||
|
||
fileInfo, err := os.Stat(fullPath) | ||
if err != nil && !os.IsNotExist(err) { | ||
return 0, err | ||
} else if err != nil { | ||
return 0, driver.PathNotFoundError{subPath} | ||
} | ||
return uint64(fileInfo.Size()), nil | ||
} | ||
|
||
func (d *FilesystemDriver) List(prefix string) ([]string, error) { | ||
prefix = strings.TrimRight(prefix, "/") | ||
fullPath := d.subPath(prefix) | ||
|
||
dir, err := os.Open(fullPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
fileNames, err := dir.Readdirnames(0) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
keys := make([]string, len(fileNames)) | ||
for i := 0; i < len(fileNames); i++ { | ||
keys[i] = prefix + "/" + fileNames[i] | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. // It doesn't fill the slice with empty strings
// and allocates enough memory
keys := make([]string, 0, len(fileNames))
for _, fileName := range fileNames {
keys = append(keys, prefix + "/" + fileName) // or path.Join(prefix, fileName)
} |
||
return keys, nil | ||
} | ||
|
||
func (d *FilesystemDriver) Move(sourcePath string, destPath string) error { | ||
err := os.Rename(d.subPath(sourcePath), d.subPath(destPath)) | ||
return err | ||
} | ||
|
||
func (d *FilesystemDriver) Delete(subPath string) error { | ||
fullPath := d.subPath(subPath) | ||
|
||
_, err := os.Stat(fullPath) | ||
if err != nil && !os.IsNotExist(err) { | ||
return err | ||
} else if err != nil { | ||
return driver.PathNotFoundError{subPath} | ||
} | ||
|
||
err = os.RemoveAll(fullPath) | ||
return err | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package filesystem | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"github.com/docker/docker-registry/contrib/golang_impl_ng/driver" | ||
"github.com/docker/docker-registry/contrib/golang_impl_ng/driver/ipc" | ||
. "gopkg.in/check.v1" | ||
) | ||
|
||
// Hook up gocheck into the "go test" runner. | ||
func Test(t *testing.T) { TestingT(t) } | ||
|
||
var rootDirectory = "/tmp/driver" | ||
var _ = os.RemoveAll(rootDirectory) | ||
|
||
var filesystemDriverConstructor = func() (driver.Driver, error) { | ||
return NewDriver(rootDirectory), nil | ||
} | ||
|
||
var InProcessSuite = Suite(&driver.InProcessDriverSuite{ | ||
DriverConstructor: filesystemDriverConstructor, | ||
}) | ||
|
||
var IPCSuite = Suite(&ipc.IPCDriverSuite{ | ||
TestDriverConfig: &ipc.TestDriverConfig{ | ||
"filesystem", | ||
map[string]string{"RootDirectory": rootDirectory}, | ||
}, | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package inmemory | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/docker/docker-registry/contrib/golang_impl_ng/driver" | ||
) | ||
|
||
type InMemoryDriver struct { | ||
storage map[string][]byte | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to |
||
} | ||
|
||
func NewDriver() *InMemoryDriver { | ||
return &InMemoryDriver{make(map[string][]byte)} | ||
} | ||
|
||
func (d *InMemoryDriver) GetContent(path string) ([]byte, error) { | ||
contents, ok := d.storage[path] | ||
if !ok { | ||
return nil, driver.PathNotFoundError{path} | ||
} | ||
return contents, nil | ||
} | ||
|
||
func (d *InMemoryDriver) PutContent(path string, contents []byte) error { | ||
d.storage[path] = contents | ||
return nil | ||
} | ||
|
||
func (d *InMemoryDriver) ReadStream(path string, offset uint64) (io.ReadCloser, error) { | ||
contents, err := d.GetContent(path) | ||
if err != nil { | ||
return nil, err | ||
} else if len(contents) < int(offset) { | ||
return nil, driver.InvalidOffsetError{path, offset} | ||
} | ||
|
||
return ioutil.NopCloser(bytes.NewReader(contents[offset:])), nil | ||
} | ||
|
||
func (d *InMemoryDriver) WriteStream(path string, offset uint64, reader io.ReadCloser) error { | ||
defer reader.Close() | ||
|
||
resumableOffset, err := d.ResumeWritePosition(path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if offset > resumableOffset { | ||
return driver.InvalidOffsetError{path, offset} | ||
} | ||
|
||
contents, err := ioutil.ReadAll(reader) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if offset > 0 { | ||
contents = append(d.storage[path][0:offset], contents...) | ||
} | ||
|
||
d.storage[path] = contents | ||
return nil | ||
} | ||
|
||
func (d *InMemoryDriver) ResumeWritePosition(path string) (uint64, error) { | ||
contents, ok := d.storage[path] | ||
if !ok { | ||
return 0, nil | ||
} | ||
return uint64(len(contents)), nil | ||
} | ||
|
||
func (d *InMemoryDriver) List(prefix string) ([]string, error) { | ||
subPathMatcher, err := regexp.Compile(fmt.Sprintf("^%s/[^/]+", prefix)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
keySet := make(map[string]bool) | ||
for k := range d.storage { | ||
if key := subPathMatcher.FindString(k); key != "" { | ||
keySet[key] = true | ||
} | ||
} | ||
|
||
i := 0 | ||
keys := make([]string, len(keySet)) | ||
for k := range keySet { | ||
keys[i] = k | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's better to get rid of keys := make([]string, 0, len(keySet))
for k := range keySet {
keys = append(keys, key)
} |
||
i++ | ||
} | ||
return keys, nil | ||
} | ||
|
||
func (d *InMemoryDriver) Move(sourcePath string, destPath string) error { | ||
contents, ok := d.storage[sourcePath] | ||
if !ok { | ||
return driver.PathNotFoundError{sourcePath} | ||
} | ||
d.storage[destPath] = contents | ||
delete(d.storage, sourcePath) | ||
return nil | ||
} | ||
|
||
func (d *InMemoryDriver) Delete(path string) error { | ||
subPaths := make([]string, 0) | ||
for k := range d.storage { | ||
if strings.HasPrefix(k, path) { | ||
subPaths = append(subPaths, k) | ||
} | ||
} | ||
|
||
if len(subPaths) == 0 { | ||
return driver.PathNotFoundError{path} | ||
} | ||
|
||
for _, subPath := range subPaths { | ||
delete(d.storage, subPath) | ||
} | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small mini-rant, but I think there's potential in breaking this package out of the registry completely as a filesystem API package to contribute back to the Go community, similar to other projects in the pkg/ directory in docker/docker: https://github.com/docker/docker/tree/master/pkg