Adds StorageDriverFactory, unifying creation of StorageDrivers
Custom storage drivers can register a factory to create the driver by name, similar to the database/sql package's Register and Open factory.Create returns an in-process driver if registered or an IPC driver if one can be found, erroring otherwise This standardizes parameter passing for creation of storage drivers Also adds documentation for storagedriver package and children
This commit is contained in:
parent
ff81f3a719
commit
ca0084fad1
15 changed files with 290 additions and 79 deletions
|
@ -8,19 +8,14 @@ import (
|
|||
"github.com/docker/docker-registry/storagedriver/ipc"
|
||||
)
|
||||
|
||||
// An out-of-process filesystem driver, intended to be run by ipc.NewDriverClient
|
||||
func main() {
|
||||
parametersBytes := []byte(os.Args[1])
|
||||
var parameters map[string]interface{}
|
||||
var parameters map[string]string
|
||||
err := json.Unmarshal(parametersBytes, ¶meters)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rootDirectory := "/tmp/registry"
|
||||
if parameters != nil {
|
||||
rootDirParam, ok := parameters["RootDirectory"].(string)
|
||||
if ok && rootDirParam != "" {
|
||||
rootDirectory = rootDirParam
|
||||
}
|
||||
}
|
||||
ipc.Server(filesystem.NewDriver(rootDirectory))
|
||||
|
||||
ipc.StorageDriverServer(filesystem.FromParameters(parameters))
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"github.com/docker/docker-registry/storagedriver/ipc"
|
||||
)
|
||||
|
||||
// An out-of-process inmemory driver, intended to be run by ipc.NewDriverClient
|
||||
// This exists primarily for example and testing purposes
|
||||
func main() {
|
||||
ipc.Server(inmemory.NewDriver())
|
||||
ipc.StorageDriverServer(inmemory.New())
|
||||
}
|
||||
|
|
|
@ -3,55 +3,24 @@ package main
|
|||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/crowdmob/goamz/aws"
|
||||
"github.com/docker/docker-registry/storagedriver/ipc"
|
||||
"github.com/docker/docker-registry/storagedriver/s3"
|
||||
)
|
||||
|
||||
// An out-of-process S3 driver, intended to be run by ipc.NewDriverClient
|
||||
func main() {
|
||||
parametersBytes := []byte(os.Args[1])
|
||||
var parameters map[string]interface{}
|
||||
var parameters map[string]string
|
||||
err := json.Unmarshal(parametersBytes, ¶meters)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
accessKey, ok := parameters["accessKey"].(string)
|
||||
if !ok || accessKey == "" {
|
||||
panic("No accessKey parameter")
|
||||
}
|
||||
|
||||
secretKey, ok := parameters["secretKey"].(string)
|
||||
if !ok || secretKey == "" {
|
||||
panic("No secretKey parameter")
|
||||
}
|
||||
|
||||
region, ok := parameters["region"].(string)
|
||||
if !ok || region == "" {
|
||||
panic("No region parameter")
|
||||
}
|
||||
|
||||
bucket, ok := parameters["bucket"].(string)
|
||||
if !ok || bucket == "" {
|
||||
panic("No bucket parameter")
|
||||
}
|
||||
|
||||
encrypt, ok := parameters["encrypt"].(string)
|
||||
if !ok {
|
||||
panic("No encrypt parameter")
|
||||
}
|
||||
|
||||
encryptBool, err := strconv.ParseBool(encrypt)
|
||||
driver, err := s3.FromParameters(parameters)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
driver, err := s3.NewDriver(accessKey, secretKey, aws.GetRegion(region), encryptBool, bucket)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ipc.Server(driver)
|
||||
ipc.StorageDriverServer(driver)
|
||||
}
|
||||
|
|
64
storagedriver/factory/factory.go
Normal file
64
storagedriver/factory/factory.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package factory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker-registry/storagedriver"
|
||||
"github.com/docker/docker-registry/storagedriver/ipc"
|
||||
)
|
||||
|
||||
// Internal mapping between storage driver names and their respective factories
|
||||
var driverFactories = make(map[string]StorageDriverFactory)
|
||||
|
||||
// Factory interface for the storagedriver.StorageDriver interface
|
||||
// Storage drivers should call Register() with a factory to make the driver available by name
|
||||
type StorageDriverFactory interface {
|
||||
// Creates and returns a new storagedriver.StorageDriver with the given parameters
|
||||
// Parameters will vary by driver and may be ignored
|
||||
// Each parameter key must only consist of lowercase letters and numbers
|
||||
Create(parameters map[string]string) (storagedriver.StorageDriver, error)
|
||||
}
|
||||
|
||||
// Register makes a storage driver available by the provided name.
|
||||
// If Register is called twice with the same name or if driver factory is nil, it panics.
|
||||
func Register(name string, factory StorageDriverFactory) {
|
||||
if factory == nil {
|
||||
panic("Must not provide nil StorageDriverFactory")
|
||||
}
|
||||
_, registered := driverFactories[name]
|
||||
if registered {
|
||||
panic(fmt.Sprintf("StorageDriverFactory named %s already registered", name))
|
||||
}
|
||||
|
||||
driverFactories[name] = factory
|
||||
}
|
||||
|
||||
// Create a new storagedriver.StorageDriver with the given name and parameters
|
||||
// To run in-process, the StorageDriverFactory must first be registered with the given name
|
||||
// If no in-process drivers are found with the given name, this attempts to create an IPC driver
|
||||
// If no in-process or external drivers are found, an InvalidStorageDriverError is returned
|
||||
func Create(name string, parameters map[string]string) (storagedriver.StorageDriver, error) {
|
||||
driverFactory, ok := driverFactories[name]
|
||||
if !ok {
|
||||
// No registered StorageDriverFactory found, try ipc
|
||||
driverClient, err := ipc.NewDriverClient(name, parameters)
|
||||
if err != nil {
|
||||
return nil, InvalidStorageDriverError{name}
|
||||
}
|
||||
err = driverClient.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driverClient, nil
|
||||
}
|
||||
return driverFactory.Create(parameters)
|
||||
}
|
||||
|
||||
// Error returned when attempting to construct an unregistered storage driver
|
||||
type InvalidStorageDriverError struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (err InvalidStorageDriverError) Error() string {
|
||||
return fmt.Sprintf("StorageDriver not registered: %s", err.Name)
|
||||
}
|
|
@ -8,13 +8,45 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/docker/docker-registry/storagedriver"
|
||||
"github.com/docker/docker-registry/storagedriver/factory"
|
||||
)
|
||||
|
||||
const DriverName = "filesystem"
|
||||
const DefaultRootDirectory = "/tmp/registry/storage"
|
||||
|
||||
func init() {
|
||||
factory.Register(DriverName, &filesystemDriverFactory{})
|
||||
}
|
||||
|
||||
// Implements the factory.StorageDriverFactory interface
|
||||
type filesystemDriverFactory struct{}
|
||||
|
||||
func (factory *filesystemDriverFactory) Create(parameters map[string]string) (storagedriver.StorageDriver, error) {
|
||||
return FromParameters(parameters), nil
|
||||
}
|
||||
|
||||
// Storage Driver backed by a local filesystem
|
||||
// All provided paths will be subpaths of the RootDirectory
|
||||
type FilesystemDriver struct {
|
||||
rootDirectory string
|
||||
}
|
||||
|
||||
func NewDriver(rootDirectory string) *FilesystemDriver {
|
||||
// Constructs a new FilesystemDriver with a given parameters map
|
||||
// Optional Parameters:
|
||||
// - rootdirectory
|
||||
func FromParameters(parameters map[string]string) *FilesystemDriver {
|
||||
var rootDirectory = DefaultRootDirectory
|
||||
if parameters != nil {
|
||||
rootDir, ok := parameters["rootdirectory"]
|
||||
if ok {
|
||||
rootDirectory = rootDir
|
||||
}
|
||||
}
|
||||
return New(rootDirectory)
|
||||
}
|
||||
|
||||
// Constructs a new FilesystemDriver with a given rootDirectory
|
||||
func New(rootDirectory string) *FilesystemDriver {
|
||||
return &FilesystemDriver{rootDirectory}
|
||||
}
|
||||
|
||||
|
@ -22,6 +54,8 @@ func (d *FilesystemDriver) subPath(subPath string) string {
|
|||
return path.Join(d.rootDirectory, subPath)
|
||||
}
|
||||
|
||||
// Implement the storagedriver.StorageDriver interface
|
||||
|
||||
func (d *FilesystemDriver) GetContent(path string) ([]byte, error) {
|
||||
contents, err := ioutil.ReadFile(d.subPath(path))
|
||||
if err != nil {
|
||||
|
|
|
@ -17,8 +17,8 @@ func init() {
|
|||
os.RemoveAll(rootDirectory)
|
||||
|
||||
filesystemDriverConstructor := func() (storagedriver.StorageDriver, error) {
|
||||
return NewDriver(rootDirectory), nil
|
||||
return New(rootDirectory), nil
|
||||
}
|
||||
testsuites.RegisterInProcessSuite(filesystemDriverConstructor, testsuites.NeverSkip)
|
||||
testsuites.RegisterIPCSuite("filesystem", map[string]string{"RootDirectory": rootDirectory}, testsuites.NeverSkip)
|
||||
testsuites.RegisterIPCSuite(DriverName, map[string]string{"rootdirectory": rootDirectory}, testsuites.NeverSkip)
|
||||
}
|
||||
|
|
|
@ -10,14 +10,31 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/docker/docker-registry/storagedriver"
|
||||
"github.com/docker/docker-registry/storagedriver/factory"
|
||||
)
|
||||
|
||||
const DriverName = "inmemory"
|
||||
|
||||
func init() {
|
||||
factory.Register(DriverName, &inMemoryDriverFactory{})
|
||||
}
|
||||
|
||||
// Implements the factory.StorageDriverFactory interface
|
||||
type inMemoryDriverFactory struct{}
|
||||
|
||||
func (factory *inMemoryDriverFactory) Create(parameters map[string]string) (storagedriver.StorageDriver, error) {
|
||||
return New(), nil
|
||||
}
|
||||
|
||||
// InMemory Storage Driver backed by a map
|
||||
// Intended solely for example and testing purposes
|
||||
type InMemoryDriver struct {
|
||||
storage map[string][]byte
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewDriver() *InMemoryDriver {
|
||||
// Constructs a new InMemoryDriver
|
||||
func New() *InMemoryDriver {
|
||||
return &InMemoryDriver{storage: make(map[string][]byte)}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,8 @@ func Test(t *testing.T) { TestingT(t) }
|
|||
|
||||
func init() {
|
||||
inmemoryDriverConstructor := func() (storagedriver.StorageDriver, error) {
|
||||
return NewDriver(), nil
|
||||
return New(), nil
|
||||
}
|
||||
testsuites.RegisterInProcessSuite(inmemoryDriverConstructor, testsuites.NeverSkip)
|
||||
testsuites.RegisterIPCSuite("inmemory", nil, testsuites.NeverSkip)
|
||||
testsuites.RegisterIPCSuite(DriverName, nil, testsuites.NeverSkip)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/docker/libchan/spdy"
|
||||
)
|
||||
|
||||
// Storage Driver implementation using a managed child process communicating over IPC
|
||||
type StorageDriverClient struct {
|
||||
subprocess *exec.Cmd
|
||||
socket *os.File
|
||||
|
@ -22,6 +23,13 @@ type StorageDriverClient struct {
|
|||
sender libchan.Sender
|
||||
}
|
||||
|
||||
// Constructs a new out-of-process storage driver using the driver name and configuration parameters
|
||||
// Must call Start() on this driver client before remote method calls can be made
|
||||
//
|
||||
// Looks for drivers in the following locations in order:
|
||||
// - Storage drivers directory (to be determined, yet not implemented)
|
||||
// - $GOPATH/bin
|
||||
// - $PATH
|
||||
func NewDriverClient(name string, parameters map[string]string) (*StorageDriverClient, error) {
|
||||
paramsBytes, err := json.Marshal(parameters)
|
||||
if err != nil {
|
||||
|
@ -46,6 +54,7 @@ func NewDriverClient(name string, parameters map[string]string) (*StorageDriverC
|
|||
}, nil
|
||||
}
|
||||
|
||||
// Starts the designated child process storage driver and binds a socket to this process for IPC
|
||||
func (driver *StorageDriverClient) Start() error {
|
||||
fileDescriptors, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0)
|
||||
if err != nil {
|
||||
|
@ -93,6 +102,8 @@ func (driver *StorageDriverClient) Start() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Stops the child process storage driver
|
||||
// storagedriver.StorageDriver methods called after Stop() will fail
|
||||
func (driver *StorageDriverClient) Stop() error {
|
||||
closeSenderErr := driver.sender.Close()
|
||||
closeTransportErr := driver.transport.Close()
|
||||
|
@ -109,6 +120,8 @@ func (driver *StorageDriverClient) Stop() error {
|
|||
return killErr
|
||||
}
|
||||
|
||||
// Implement the storagedriver.StorageDriver interface over IPC
|
||||
|
||||
func (driver *StorageDriverClient) GetContent(path string) ([]byte, error) {
|
||||
receiver, remoteSender := libchan.Pipe()
|
||||
|
||||
|
|
|
@ -10,12 +10,16 @@ import (
|
|||
"github.com/docker/libchan"
|
||||
)
|
||||
|
||||
// Defines a remote method call request
|
||||
// A return value struct is to be sent over the ResponseChannel
|
||||
type Request struct {
|
||||
Type string
|
||||
Parameters map[string]interface{}
|
||||
ResponseChannel libchan.Sender
|
||||
}
|
||||
|
||||
// A simple wrapper around an io.ReadCloser that implements the io.ReadWriteCloser interface
|
||||
// Writes are disallowed and will return an error if ever called
|
||||
type noWriteReadWriteCloser struct {
|
||||
io.ReadCloser
|
||||
}
|
||||
|
@ -24,6 +28,8 @@ func (r noWriteReadWriteCloser) Write(p []byte) (n int, err error) {
|
|||
return 0, errors.New("Write unsupported")
|
||||
}
|
||||
|
||||
// Wraps an io.Reader as an io.ReadWriteCloser with a nop Close and unsupported Write method
|
||||
// Has no effect when an io.ReadWriteCloser is passed in
|
||||
func WrapReader(reader io.Reader) io.ReadWriteCloser {
|
||||
if readWriteCloser, ok := reader.(io.ReadWriteCloser); ok {
|
||||
return readWriteCloser
|
||||
|
@ -39,6 +45,7 @@ type responseError struct {
|
|||
Message string
|
||||
}
|
||||
|
||||
// Wraps an error in a serializable struct containing the error's type and message
|
||||
func ResponseError(err error) *responseError {
|
||||
if err == nil {
|
||||
return nil
|
||||
|
@ -53,29 +60,37 @@ func (err *responseError) Error() string {
|
|||
return fmt.Sprintf("%s: %s", err.Type, err.Message)
|
||||
}
|
||||
|
||||
// IPC method call response object definitions
|
||||
|
||||
// Response for a ReadStream request
|
||||
type ReadStreamResponse struct {
|
||||
Reader io.ReadWriteCloser
|
||||
Error *responseError
|
||||
}
|
||||
|
||||
// Response for a WriteStream request
|
||||
type WriteStreamResponse struct {
|
||||
Error *responseError
|
||||
}
|
||||
|
||||
// Response for a ResumeWritePosition request
|
||||
type ResumeWritePositionResponse struct {
|
||||
Position uint64
|
||||
Error *responseError
|
||||
}
|
||||
|
||||
// Response for a List request
|
||||
type ListResponse struct {
|
||||
Keys []string
|
||||
Error *responseError
|
||||
}
|
||||
|
||||
// Response for a Move request
|
||||
type MoveResponse struct {
|
||||
Error *responseError
|
||||
}
|
||||
|
||||
// Response for a Delete request
|
||||
type DeleteResponse struct {
|
||||
Error *responseError
|
||||
}
|
||||
|
|
|
@ -6,13 +6,18 @@ import (
|
|||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"github.com/docker/docker-registry/storagedriver"
|
||||
"github.com/docker/libchan"
|
||||
"github.com/docker/libchan/spdy"
|
||||
)
|
||||
|
||||
func Server(driver storagedriver.StorageDriver) error {
|
||||
// Construct a new IPC server handling requests for the given storagedriver.StorageDriver
|
||||
// This explicitly uses file descriptor 3 for IPC communication, as storage drivers are spawned in client.go
|
||||
//
|
||||
// To create a new out-of-process driver, create a main package which calls StorageDriverServer with a storagedriver.StorageDriver
|
||||
func StorageDriverServer(driver storagedriver.StorageDriver) error {
|
||||
childSocket := os.NewFile(3, "childSocket")
|
||||
defer childSocket.Close()
|
||||
conn, err := net.FileConn(childSocket)
|
||||
|
@ -34,6 +39,9 @@ func Server(driver storagedriver.StorageDriver) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Receives new storagedriver.StorageDriver method requests and creates a new goroutine to handle each request
|
||||
//
|
||||
// Requests are expected to be of type ipc.Request as the parameters are unknown until the request type is deserialized
|
||||
func receive(driver storagedriver.StorageDriver, receiver libchan.Receiver) {
|
||||
for {
|
||||
var request Request
|
||||
|
@ -45,6 +53,8 @@ func receive(driver storagedriver.StorageDriver, receiver libchan.Receiver) {
|
|||
}
|
||||
}
|
||||
|
||||
// Handles storagedriver.StorageDriver method requests as defined in client.go
|
||||
// Responds to requests using the Request.ResponseChannel
|
||||
func handleRequest(driver storagedriver.StorageDriver, request Request) {
|
||||
switch request.Type {
|
||||
case "GetContent":
|
||||
|
@ -76,14 +86,9 @@ func handleRequest(driver storagedriver.StorageDriver, request Request) {
|
|||
panic(err)
|
||||
}
|
||||
case "ReadStream":
|
||||
var offset uint64
|
||||
|
||||
path, _ := request.Parameters["Path"].(string)
|
||||
offset, ok := request.Parameters["Offset"].(uint64)
|
||||
if !ok {
|
||||
offsetSigned, _ := request.Parameters["Offset"].(int64)
|
||||
offset = uint64(offsetSigned)
|
||||
}
|
||||
// Depending on serialization method, Offset may be convereted to any int/uint type
|
||||
offset := reflect.ValueOf(request.Parameters["Offset"]).Convert(reflect.TypeOf(uint64(0))).Uint()
|
||||
reader, err := driver.ReadStream(path, offset)
|
||||
var response ReadStreamResponse
|
||||
if err != nil {
|
||||
|
@ -96,19 +101,11 @@ func handleRequest(driver storagedriver.StorageDriver, request Request) {
|
|||
panic(err)
|
||||
}
|
||||
case "WriteStream":
|
||||
var offset uint64
|
||||
|
||||
path, _ := request.Parameters["Path"].(string)
|
||||
offset, ok := request.Parameters["Offset"].(uint64)
|
||||
if !ok {
|
||||
offsetSigned, _ := request.Parameters["Offset"].(int64)
|
||||
offset = uint64(offsetSigned)
|
||||
}
|
||||
size, ok := request.Parameters["Size"].(uint64)
|
||||
if !ok {
|
||||
sizeSigned, _ := request.Parameters["Size"].(int64)
|
||||
size = uint64(sizeSigned)
|
||||
}
|
||||
// Depending on serialization method, Offset may be convereted to any int/uint type
|
||||
offset := reflect.ValueOf(request.Parameters["Offset"]).Convert(reflect.TypeOf(uint64(0))).Uint()
|
||||
// Depending on serialization method, Size may be convereted to any int/uint type
|
||||
size := reflect.ValueOf(request.Parameters["Size"]).Convert(reflect.TypeOf(uint64(0))).Uint()
|
||||
reader, _ := request.Parameters["Reader"].(io.ReadCloser)
|
||||
err := driver.WriteStream(path, offset, size, reader)
|
||||
response := WriteStreamResponse{
|
||||
|
|
|
@ -2,6 +2,7 @@ package s3
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
@ -9,21 +10,82 @@ import (
|
|||
"github.com/crowdmob/goamz/aws"
|
||||
"github.com/crowdmob/goamz/s3"
|
||||
"github.com/docker/docker-registry/storagedriver"
|
||||
"github.com/docker/docker-registry/storagedriver/factory"
|
||||
)
|
||||
|
||||
/* Chunks need to be at least 5MB to store with a multipart upload on S3 */
|
||||
const DriverName = "s3"
|
||||
|
||||
// Chunks need to be at least 5MB to store with a multipart upload on S3
|
||||
const minChunkSize = uint64(5 * 1024 * 1024)
|
||||
|
||||
/* The largest amount of parts you can request from S3 */
|
||||
// The largest amount of parts you can request from S3
|
||||
const listPartsMax = 1000
|
||||
|
||||
func init() {
|
||||
factory.Register(DriverName, &s3DriverFactory{})
|
||||
}
|
||||
|
||||
// Implements the factory.StorageDriverFactory interface
|
||||
type s3DriverFactory struct{}
|
||||
|
||||
func (factory *s3DriverFactory) Create(parameters map[string]string) (storagedriver.StorageDriver, error) {
|
||||
return FromParameters(parameters)
|
||||
}
|
||||
|
||||
// Storage Driver backed by Amazon S3
|
||||
// Objects are stored at absolute keys in the provided bucket
|
||||
type S3Driver struct {
|
||||
S3 *s3.S3
|
||||
Bucket *s3.Bucket
|
||||
Encrypt bool
|
||||
}
|
||||
|
||||
func NewDriver(accessKey string, secretKey string, region aws.Region, encrypt bool, bucketName string) (*S3Driver, error) {
|
||||
// Constructs a new S3Driver with a given parameters map
|
||||
// Required parameters:
|
||||
// - accesskey
|
||||
// - secretkey
|
||||
// - region
|
||||
// - bucket
|
||||
// - encrypt
|
||||
func FromParameters(parameters map[string]string) (*S3Driver, error) {
|
||||
accessKey, ok := parameters["accesskey"]
|
||||
if !ok || accessKey == "" {
|
||||
return nil, fmt.Errorf("No accesskey parameter provided")
|
||||
}
|
||||
|
||||
secretKey, ok := parameters["secretkey"]
|
||||
if !ok || secretKey == "" {
|
||||
return nil, fmt.Errorf("No secretkey parameter provided")
|
||||
}
|
||||
|
||||
regionName, ok := parameters["region"]
|
||||
if !ok || regionName == "" {
|
||||
return nil, fmt.Errorf("No region parameter provided")
|
||||
}
|
||||
region := aws.GetRegion(regionName)
|
||||
if region.Name == "" {
|
||||
return nil, fmt.Errorf("Invalid region provided: %s", region)
|
||||
}
|
||||
|
||||
bucket, ok := parameters["bucket"]
|
||||
if !ok || bucket == "" {
|
||||
return nil, fmt.Errorf("No bucket parameter provided")
|
||||
}
|
||||
|
||||
encrypt, ok := parameters["encrypt"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("No encrypt parameter provided")
|
||||
}
|
||||
|
||||
encryptBool, err := strconv.ParseBool(encrypt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to parse the encrypt parameter: %v", err)
|
||||
}
|
||||
return New(accessKey, secretKey, region, encryptBool, bucket)
|
||||
}
|
||||
|
||||
// Constructs a new S3Driver with the given AWS credentials, region, encryption flag, and bucketName
|
||||
func New(accessKey string, secretKey string, region aws.Region, encrypt bool, bucketName string) (*S3Driver, error) {
|
||||
auth := aws.Auth{AccessKey: accessKey, SecretKey: secretKey}
|
||||
s3obj := s3.New(auth, region)
|
||||
bucket := s3obj.Bucket(bucketName)
|
||||
|
|
|
@ -2,6 +2,7 @@ package s3
|
|||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/crowdmob/goamz/aws"
|
||||
|
@ -14,23 +15,34 @@ import (
|
|||
func Test(t *testing.T) { TestingT(t) }
|
||||
|
||||
func init() {
|
||||
accessKey := os.Getenv("ACCESS_KEY")
|
||||
secretKey := os.Getenv("SECRET_KEY")
|
||||
accessKey := os.Getenv("AWS_ACCESS_KEY")
|
||||
secretKey := os.Getenv("AWS_SECRET_KEY")
|
||||
region := os.Getenv("AWS_REGION")
|
||||
bucket := os.Getenv("S3_BUCKET")
|
||||
encrypt := os.Getenv("S3_ENCRYPT")
|
||||
|
||||
s3DriverConstructor := func() (storagedriver.StorageDriver, error) {
|
||||
return NewDriver(accessKey, secretKey, aws.GetRegion(region), true, bucket)
|
||||
shouldEncrypt, err := strconv.ParseBool(encrypt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return New(accessKey, secretKey, aws.GetRegion(region), shouldEncrypt, bucket)
|
||||
}
|
||||
|
||||
// Skip S3 storage driver tests if environment variable parameters are not provided
|
||||
skipCheck := func() string {
|
||||
if accessKey == "" || secretKey == "" || region == "" || bucket == "" || encrypt == "" {
|
||||
return "Must set ACCESS_KEY, SECRET_KEY, AWS_REGION, S3_BUCKET, and S3_ENCRYPT to run S3 tests"
|
||||
return "Must set AWS_ACCESS_KEY, AWS_SECRET_KEY, AWS_REGION, S3_BUCKET, and S3_ENCRYPT to run S3 tests"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
testsuites.RegisterInProcessSuite(s3DriverConstructor, skipCheck)
|
||||
testsuites.RegisterIPCSuite("s3", map[string]string{"accessKey": accessKey, "secretKey": secretKey, "region": region, "bucket": bucket, "encrypt": encrypt}, skipCheck)
|
||||
testsuites.RegisterIPCSuite(DriverName, map[string]string{
|
||||
"accesskey": accessKey,
|
||||
"secretkey": secretKey,
|
||||
"region": region,
|
||||
"bucket": bucket,
|
||||
"encrypt": encrypt,
|
||||
}, skipCheck)
|
||||
}
|
||||
|
|
|
@ -5,17 +5,41 @@ import (
|
|||
"io"
|
||||
)
|
||||
|
||||
// Defines methods that a Storage Driver must implement for a filesystem-like key/value object storage
|
||||
type StorageDriver interface {
|
||||
// Retrieve the content stored at "path" as a []byte
|
||||
// Should primarily be used for small objects
|
||||
GetContent(path string) ([]byte, error)
|
||||
|
||||
// Store the []byte content at a location designated by "path"
|
||||
// Should primarily be used for small objects
|
||||
PutContent(path string, content []byte) error
|
||||
|
||||
// Retrieve an io.ReadCloser for the content stored at "path" with a given byte offset
|
||||
// May be used to resume reading a stream by providing a nonzero offset
|
||||
ReadStream(path string, offset uint64) (io.ReadCloser, error)
|
||||
|
||||
// Store the contents of the provided io.ReadCloser at a location designated by "path"
|
||||
// The driver will know it has received the full contents when it has read "size" bytes
|
||||
// May be used to resume writing a stream by providing a nonzero offset
|
||||
// The offset must be no larger than the number of bytes already written to this path
|
||||
WriteStream(path string, offset, size uint64, readCloser io.ReadCloser) error
|
||||
|
||||
// Retrieve the byte offset at which it is safe to continue writing at "path"
|
||||
ResumeWritePosition(path string) (uint64, error)
|
||||
|
||||
// Recursively lists the objects stored at a subpath of the given prefix
|
||||
List(prefix string) ([]string, error)
|
||||
|
||||
// Moves an object stored at sourcePath to destPath, removing the original object
|
||||
// Note: This may be no more efficient than a copy followed by a delete for many implementations
|
||||
Move(sourcePath string, destPath string) error
|
||||
|
||||
// Recursively deletes all objects stored at "path" and its subpaths
|
||||
Delete(path string) error
|
||||
}
|
||||
|
||||
// Error returned when operating on a nonexistent path
|
||||
type PathNotFoundError struct {
|
||||
Path string
|
||||
}
|
||||
|
@ -24,6 +48,7 @@ func (err PathNotFoundError) Error() string {
|
|||
return fmt.Sprintf("Path not found: %s", err.Path)
|
||||
}
|
||||
|
||||
// Error returned when attempting to read or write from an invalid offset
|
||||
type InvalidOffsetError struct {
|
||||
Path string
|
||||
Offset uint64
|
||||
|
|
|
@ -17,6 +17,9 @@ import (
|
|||
// Hook up gocheck into the "go test" runner
|
||||
func Test(t *testing.T) { TestingT(t) }
|
||||
|
||||
// Registers an in-process storage driver test suite with the go test runner
|
||||
//
|
||||
// If skipCheck returns a non-empty skip reason, the suite is skipped with the given reason
|
||||
func RegisterInProcessSuite(driverConstructor DriverConstructor, skipCheck SkipCheck) {
|
||||
Suite(&DriverSuite{
|
||||
Constructor: driverConstructor,
|
||||
|
@ -24,6 +27,9 @@ func RegisterInProcessSuite(driverConstructor DriverConstructor, skipCheck SkipC
|
|||
})
|
||||
}
|
||||
|
||||
// Registers a storage driver test suite which runs the named driver as a child process with the given parameters
|
||||
//
|
||||
// If skipCheck returns a non-empty skip reason, the suite is skipped with the given reason
|
||||
func RegisterIPCSuite(driverName string, ipcParams map[string]string, skipCheck SkipCheck) {
|
||||
suite := &DriverSuite{
|
||||
Constructor: func() (storagedriver.StorageDriver, error) {
|
||||
|
|
Loading…
Reference in a new issue