mirror of
https://github.com/minio/mc.git
synced 2025-11-13 12:22:45 +03:00
Move from file to fs and finish ListObjects() - add test stubs
This commit is contained in:
@@ -17,12 +17,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"path"
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cheggaaa/pb"
|
"github.com/cheggaaa/pb"
|
||||||
"github.com/minio-io/mc/pkg/client"
|
"github.com/minio-io/mc/pkg/client"
|
||||||
|
"github.com/minio-io/mc/pkg/client/fs"
|
||||||
"github.com/minio-io/mc/pkg/client/s3"
|
"github.com/minio-io/mc/pkg/client/s3"
|
||||||
"github.com/minio-io/mc/pkg/console"
|
"github.com/minio-io/mc/pkg/console"
|
||||||
"github.com/minio-io/minio/pkg/iodine"
|
"github.com/minio-io/minio/pkg/iodine"
|
||||||
@@ -60,27 +60,23 @@ func mustGetMcBashCompletionFilename() string {
|
|||||||
// NewClient - get new client
|
// NewClient - get new client
|
||||||
// TODO refactor this to be more testable
|
// TODO refactor this to be more testable
|
||||||
func getNewClient(debug bool, urlStr string) (clnt client.Client, err error) {
|
func getNewClient(debug bool, urlStr string) (clnt client.Client, err error) {
|
||||||
hostCfg, err := getHostConfig(urlStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, iodine.New(err, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
var auth s3.Auth
|
|
||||||
auth.AccessKeyID = hostCfg.Auth.AccessKeyID
|
|
||||||
auth.SecretAccessKey = hostCfg.Auth.SecretAccessKey
|
|
||||||
uType, err := getURLType(urlStr)
|
uType, err := getURLType(urlStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, iodine.New(err, nil)
|
return nil, iodine.New(err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch uType {
|
switch uType {
|
||||||
case urlObjectStorage: // Minio and S3 compatible object storage
|
case urlObjectStorage: // Minio and S3 compatible object storage
|
||||||
traceTransport := s3.GetNewTraceTransport(s3.NewTrace(false, true, nil), http.DefaultTransport)
|
hostCfg, err := getHostConfig(urlStr)
|
||||||
if debug {
|
if err != nil {
|
||||||
clnt = s3.GetNewClient(&auth, urlStr, traceTransport)
|
return nil, iodine.New(err, nil)
|
||||||
} else {
|
|
||||||
clnt = s3.GetNewClient(&auth, urlStr, http.DefaultTransport)
|
|
||||||
}
|
}
|
||||||
|
var auth s3.Auth
|
||||||
|
auth.AccessKeyID = hostCfg.Auth.AccessKeyID
|
||||||
|
auth.SecretAccessKey = hostCfg.Auth.SecretAccessKey
|
||||||
|
clnt = s3.GetNewClient(urlStr, &auth, debug)
|
||||||
|
return clnt, nil
|
||||||
|
case urlFile:
|
||||||
|
clnt = fs.GetNewClient(urlStr)
|
||||||
return clnt, nil
|
return clnt, nil
|
||||||
default:
|
default:
|
||||||
return nil, iodine.New(errUnsupportedScheme, nil)
|
return nil, iodine.New(errUnsupportedScheme, nil)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Minimalist Object Storage, (C) 2015 Minio, Inc.
|
* Minimalist Object Storage, (C) 2015 Minio, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this fs except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package file
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -29,20 +30,17 @@ import (
|
|||||||
"github.com/minio-io/minio/pkg/iodine"
|
"github.com/minio-io/minio/pkg/iodine"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fileClient struct {
|
type fsClient struct {
|
||||||
// Supports URL in following formats
|
|
||||||
// - http://<ipaddress>/<bucketname>/<object>
|
|
||||||
// - http://<bucketname>.<domain>/<object>
|
|
||||||
*url.URL
|
*url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNewClient -
|
// GetNewClient - instantiate a new fs client
|
||||||
func GetNewClient(path string) client.Client {
|
func GetNewClient(path string) client.Client {
|
||||||
u, err := url.Parse(path)
|
u, err := url.Parse(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &fileClient{u}
|
return &fsClient{u}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Object operations
|
/// Object operations
|
||||||
@@ -67,7 +65,7 @@ func isValidObject(bucket, object string) (string, os.FileInfo, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Put - upload new object to bucket
|
// Put - upload new object to bucket
|
||||||
func (f *fileClient) Put(bucket, object, md5HexString string, size int64, contents io.Reader) error {
|
func (f *fsClient) Put(bucket, object, md5HexString string, size int64, contents io.Reader) error {
|
||||||
// handle md5HexString match internally
|
// handle md5HexString match internally
|
||||||
if bucket == "" || object == "" {
|
if bucket == "" || object == "" {
|
||||||
return iodine.New(client.InvalidArgument{}, nil)
|
return iodine.New(client.InvalidArgument{}, nil)
|
||||||
@@ -76,14 +74,14 @@ func (f *fileClient) Put(bucket, object, md5HexString string, size int64, conten
|
|||||||
if size < 0 {
|
if size < 0 {
|
||||||
return iodine.New(client.InvalidArgument{}, nil)
|
return iodine.New(client.InvalidArgument{}, nil)
|
||||||
}
|
}
|
||||||
file, err := os.OpenFile(objectPath, os.O_CREATE|os.O_EXCL, 0600)
|
fs, err := os.Create(objectPath)
|
||||||
if os.IsExist(err) {
|
if os.IsExist(err) {
|
||||||
return iodine.New(client.ObjectExists{Bucket: bucket, Object: object}, nil)
|
return iodine.New(client.ObjectExists{Bucket: bucket, Object: object}, nil)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return iodine.New(err, nil)
|
return iodine.New(err, nil)
|
||||||
}
|
}
|
||||||
_, err = io.CopyN(file, contents, size)
|
_, err = io.CopyN(fs, contents, size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return iodine.New(err, nil)
|
return iodine.New(err, nil)
|
||||||
}
|
}
|
||||||
@@ -91,12 +89,12 @@ func (f *fileClient) Put(bucket, object, md5HexString string, size int64, conten
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get - download an object from bucket
|
// Get - download an object from bucket
|
||||||
func (f *fileClient) Get(bucket, object string) (body io.ReadCloser, size int64, md5 string, err error) {
|
func (f *fsClient) Get(bucket, object string) (body io.ReadCloser, size int64, md5 string, err error) {
|
||||||
objectPath, st, err := isValidObject(bucket, object)
|
objectPath, st, err := isValidObject(bucket, object)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, "", iodine.New(err, nil)
|
return nil, 0, "", iodine.New(err, nil)
|
||||||
}
|
}
|
||||||
body, err = os.OpenFile(objectPath, os.O_RDONLY, 0600)
|
body, err = os.Open(objectPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, "", iodine.New(err, nil)
|
return nil, 0, "", iodine.New(err, nil)
|
||||||
}
|
}
|
||||||
@@ -106,7 +104,7 @@ func (f *fileClient) Get(bucket, object string) (body io.ReadCloser, size int64,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPartial - download a partial object from bucket
|
// GetPartial - download a partial object from bucket
|
||||||
func (f *fileClient) GetPartial(bucket, key string, offset, length int64) (body io.ReadCloser, size int64, md5 string, err error) {
|
func (f *fsClient) GetPartial(bucket, key string, offset, length int64) (body io.ReadCloser, size int64, md5 string, err error) {
|
||||||
if offset < 0 {
|
if offset < 0 {
|
||||||
return nil, 0, "", iodine.New(client.InvalidRange{Offset: offset}, nil)
|
return nil, 0, "", iodine.New(client.InvalidRange{Offset: offset}, nil)
|
||||||
}
|
}
|
||||||
@@ -114,7 +112,7 @@ func (f *fileClient) GetPartial(bucket, key string, offset, length int64) (body
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StatObject -
|
// StatObject -
|
||||||
func (f *fileClient) StatObject(bucket, object string) (size int64, date time.Time, reterr error) {
|
func (f *fsClient) StatObject(bucket, object string) (size int64, date time.Time, reterr error) {
|
||||||
_, st, err := isValidObject(bucket, object)
|
_, st, err := isValidObject(bucket, object)
|
||||||
if size < 0 {
|
if size < 0 {
|
||||||
return 0, date, iodine.New(client.InvalidArgument{}, nil)
|
return 0, date, iodine.New(client.InvalidArgument{}, nil)
|
||||||
@@ -128,7 +126,7 @@ func (f *fileClient) StatObject(bucket, object string) (size int64, date time.Ti
|
|||||||
/// Bucket operations
|
/// Bucket operations
|
||||||
|
|
||||||
// ListBuckets - get list of buckets
|
// ListBuckets - get list of buckets
|
||||||
func (f *fileClient) ListBuckets() ([]*client.Bucket, error) {
|
func (f *fsClient) ListBuckets() ([]*client.Bucket, error) {
|
||||||
buckets, err := ioutil.ReadDir(f.Path)
|
buckets, err := ioutil.ReadDir(f.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, iodine.New(err, nil)
|
return nil, iodine.New(err, nil)
|
||||||
@@ -145,12 +143,42 @@ func (f *fileClient) ListBuckets() ([]*client.Bucket, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListObjects - get a list of objects
|
// ListObjects - get a list of objects
|
||||||
func (f *fileClient) ListObjects(bucket, keyPrefix string) (items []*client.Item, err error) {
|
func (f *fsClient) ListObjects(bucket, prefix string) (items []*client.Item, err error) {
|
||||||
return nil, iodine.New(client.APINotImplemented{API: "ListObjects"}, nil)
|
visitFS := func(fp string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err // fatal
|
||||||
|
}
|
||||||
|
if fi.IsDir() {
|
||||||
|
return nil // not a fs skip
|
||||||
|
}
|
||||||
|
if prefix != "" {
|
||||||
|
if strings.Contains(fp, prefix) {
|
||||||
|
item := &client.Item{
|
||||||
|
Key: fp,
|
||||||
|
LastModified: fi.ModTime(),
|
||||||
|
Size: fi.Size(),
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item := &client.Item{
|
||||||
|
Key: fp,
|
||||||
|
LastModified: fi.ModTime(),
|
||||||
|
Size: fi.Size(),
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = filepath.Walk(bucket, visitFS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, iodine.New(err, nil)
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutBucket - create a new bucket
|
// PutBucket - create a new bucket
|
||||||
func (f *fileClient) PutBucket(bucket string) error {
|
func (f *fsClient) PutBucket(bucket string) error {
|
||||||
if bucket == "" || strings.TrimSpace(bucket) == "" {
|
if bucket == "" || strings.TrimSpace(bucket) == "" {
|
||||||
return iodine.New(client.InvalidArgument{}, nil)
|
return iodine.New(client.InvalidArgument{}, nil)
|
||||||
}
|
}
|
||||||
@@ -168,7 +196,7 @@ func (f *fileClient) PutBucket(bucket string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StatBucket -
|
// StatBucket -
|
||||||
func (f *fileClient) StatBucket(bucket string) error {
|
func (f *fsClient) StatBucket(bucket string) error {
|
||||||
if bucket == "" {
|
if bucket == "" {
|
||||||
return iodine.New(client.InvalidArgument{}, nil)
|
return iodine.New(client.InvalidArgument{}, nil)
|
||||||
}
|
}
|
||||||
76
pkg/client/fs/fs_test.go
Normal file
76
pkg/client/fs/fs_test.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Minimalist Object Storage, (C) 2015 Minio, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this fs except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://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 fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/minio-io/check"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test(t *testing.T) { TestingT(t) }
|
||||||
|
|
||||||
|
type MySuite struct{}
|
||||||
|
|
||||||
|
var _ = Suite(&MySuite{})
|
||||||
|
|
||||||
|
// TODO - implement these steps
|
||||||
|
|
||||||
|
func (s *MySuite) TestListBuckets(c *C) {
|
||||||
|
root, err := ioutil.TempDir(os.TempDir(), "fs-")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MySuite) TestListObjects(c *C) {
|
||||||
|
root, err := ioutil.TempDir(os.TempDir(), "fs-")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MySuite) TestPutBucket(c *C) {
|
||||||
|
root, err := ioutil.TempDir(os.TempDir(), "fs-")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MySuite) TestPutObject(c *C) {
|
||||||
|
root, err := ioutil.TempDir(os.TempDir(), "fs-")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MySuite) TestGetObject(c *C) {
|
||||||
|
root, err := ioutil.TempDir(os.TempDir(), "fs-")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MySuite) TestStatObject(c *C) {
|
||||||
|
root, err := ioutil.TempDir(os.TempDir(), "fs-")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MySuite) TestStatBucket(c *C) {
|
||||||
|
root, err := ioutil.TempDir(os.TempDir(), "fs-")
|
||||||
|
c.Assert(err, IsNil)
|
||||||
|
defer os.RemoveAll(root)
|
||||||
|
}
|
||||||
@@ -94,15 +94,24 @@ type s3Client struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetNewClient returns an initialized s3Client structure.
|
// GetNewClient returns an initialized s3Client structure.
|
||||||
func GetNewClient(auth *Auth, urlStr string, transport http.RoundTripper) client.Client {
|
// if debug use a internal trace transport
|
||||||
u, err := url.Parse(urlStr)
|
func GetNewClient(hostURL string, auth *Auth, debug bool) client.Client {
|
||||||
|
u, err := url.Parse(hostURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
var traceTransport RoundTripTrace
|
||||||
|
var transport http.RoundTripper
|
||||||
|
if debug {
|
||||||
|
traceTransport = GetNewTraceTransport(NewTrace(false, true, nil), http.DefaultTransport)
|
||||||
|
transport = GetNewTraceTransport(s3Verify{}, traceTransport)
|
||||||
|
} else {
|
||||||
|
transport = http.DefaultTransport
|
||||||
|
}
|
||||||
s3c := &s3Client{
|
s3c := &s3Client{
|
||||||
&Meta{
|
&Meta{
|
||||||
Auth: auth,
|
Auth: auth,
|
||||||
Transport: GetNewTraceTransport(s3Verify{}, transport),
|
Transport: transport,
|
||||||
}, u,
|
}, u,
|
||||||
}
|
}
|
||||||
return s3c
|
return s3c
|
||||||
|
|||||||
@@ -61,13 +61,16 @@ func (c *s3Client) Put(bucket, key, md5HexString string, size int64, contents io
|
|||||||
req := newReq(c.keyURL(bucket, key))
|
req := newReq(c.keyURL(bucket, key))
|
||||||
req.Method = "PUT"
|
req.Method = "PUT"
|
||||||
req.ContentLength = size
|
req.ContentLength = size
|
||||||
|
|
||||||
req.Body = ioutil.NopCloser(contents)
|
req.Body = ioutil.NopCloser(contents)
|
||||||
md5, err := hex.DecodeString(md5HexString)
|
|
||||||
if err != nil {
|
// set Content-MD5 only if md5 is provided
|
||||||
return iodine.New(err, nil)
|
if strings.TrimSpace(md5HexString) != "" {
|
||||||
|
md5, err := hex.DecodeString(md5HexString)
|
||||||
|
if err != nil {
|
||||||
|
return iodine.New(err, nil)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(md5))
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-MD5", base64.StdEncoding.EncodeToString(md5))
|
|
||||||
c.signRequest(req, c.Host)
|
c.signRequest(req, c.Host)
|
||||||
|
|
||||||
res, err := c.Transport.RoundTrip(req)
|
res, err := c.Transport.RoundTrip(req)
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ func fixFileURL(urlStr string) (fixedURL string, err error) {
|
|||||||
if urlStr == "" {
|
if urlStr == "" {
|
||||||
return "", iodine.New(errEmptyURL, nil)
|
return "", iodine.New(errEmptyURL, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
utype, e := getURLType(urlStr)
|
utype, e := getURLType(urlStr)
|
||||||
if e != nil || utype != urlFile {
|
if e != nil || utype != urlFile {
|
||||||
return "", iodine.New(e, nil)
|
return "", iodine.New(e, nil)
|
||||||
@@ -95,14 +94,10 @@ func fixFileURL(urlStr string) (fixedURL string, err error) {
|
|||||||
// Not really a file URL. Host is not empty.
|
// Not really a file URL. Host is not empty.
|
||||||
return "", iodine.New(errInvalidURL, nil)
|
return "", iodine.New(errInvalidURL, nil)
|
||||||
}
|
}
|
||||||
|
// do not use u.Scheme since that would construct a path in the form
|
||||||
// fill missing scheme
|
// file:// which is an invalid file but url Parse doesn't report error
|
||||||
if u.Scheme == "" {
|
// so we construct manually instead
|
||||||
// Set it to file
|
fixedURL = "file:///" + u.Path
|
||||||
u.Scheme = "file"
|
|
||||||
}
|
|
||||||
|
|
||||||
fixedURL = u.String()
|
|
||||||
return fixedURL, nil
|
return fixedURL, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -146,11 +141,11 @@ func parseURL(arg string, aliases map[string]string) (urlStr string, err error)
|
|||||||
if !isValidURL(urlStr) {
|
if !isValidURL(urlStr) {
|
||||||
return "", iodine.New(errUnsupportedScheme, nil)
|
return "", iodine.New(errUnsupportedScheme, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it is a file URL, rewrite to file:///path/to form
|
// If it is a file URL, rewrite to file:///path/to form
|
||||||
if isValidFileURL(urlStr) {
|
if isValidFileURL(urlStr) {
|
||||||
return fixFileURL(urlStr)
|
return fixFileURL(urlStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlStr, nil
|
return urlStr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user