1
0
mirror of https://github.com/minio/mc.git synced 2025-11-13 12:22:45 +03:00

Squash minio package into s3 package

This commit is contained in:
Harshavardhana
2015-02-08 17:56:28 -08:00
parent e1c93116d3
commit 231f0c3efb
20 changed files with 100 additions and 1076 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
cover.out cover.out
mc mc
site/

View File

@@ -7,12 +7,14 @@ getdeps: checkdeps
@go get github.com/tools/godep && echo "Installed godep" @go get github.com/tools/godep && echo "Installed godep"
@go get golang.org/x/tools/cmd/cover && echo "Installed cover" @go get golang.org/x/tools/cmd/cover && echo "Installed cover"
install: pkgs uri build-all: getdeps
@echo "Building Libraries"
@godep go generate ./...
@godep go build ./...
test-all: build-all
@echo "Running Test Suites:"
@godep go test -race ./...
install: test-all
@godep go install github.com/minio-io/mc && echo "Installed mc" @godep go install github.com/minio-io/mc && echo "Installed mc"
pkgs:
@godep go test -race -coverprofile=cover.out github.com/minio-io/mc/pkg/s3
@godep go test -race -coverprofile=cover.out github.com/minio-io/mc/pkg/minio
uri:
@godep go test -race -coverprofile=cover.out github.com/minio-io/mc/pkg/uri

View File

@@ -22,7 +22,6 @@ import (
"path" "path"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/minio-io/mc/pkg/minio"
"github.com/minio-io/mc/pkg/s3" "github.com/minio-io/mc/pkg/s3"
) )
@@ -37,11 +36,6 @@ var Options = []cli.Command{
Usage: "", Usage: "",
Subcommands: subS3APIOptions, Subcommands: subS3APIOptions,
}, },
{
Name: "minio",
Usage: "",
Subcommands: subMinioApiOptions,
},
} }
var subS3Options = []cli.Command{ var subS3Options = []cli.Command{
@@ -59,63 +53,15 @@ var subS3APIOptions = []cli.Command{
Configure, Configure,
} }
var subMinioApiOptions = []cli.Command{ func getAuthFilePath() string {
MinioGetObject,
MinioPutObject,
MinioPutBucket,
MinioListObjects,
MinioListBuckets,
MinioConfigure,
}
func getAWSAuthFilePath() string {
home := os.Getenv("HOME") home := os.Getenv("HOME")
return path.Join(home, S3_AUTH) return path.Join(home, AUTH)
}
func getMinioAuthFilePath() string {
home := os.Getenv("HOME")
return path.Join(home, MINIO_AUTH)
}
func getMinioEnvironment() (auth *minio.Auth, err error) {
var accessKey, secretKey string
minioAuth, err := os.OpenFile(getMinioAuthFilePath(), os.O_RDWR, 0666)
defer minioAuth.Close()
if err != nil {
accessKey = os.Getenv("MINIO_ACCESS_KEY_ID")
secretKey = os.Getenv("MINIO_SECRET_ACCESS_KEY")
if accessKey == "" && secretKey == "" {
return nil, missingAccessSecretErr
}
if accessKey == "" {
return nil, missingAccessErr
}
if secretKey == "" {
return nil, missingSecretErr
}
auth = &minio.Auth{
AccessKey: accessKey,
SecretKey: secretKey,
Hostname: "127.0.0.1:8080",
}
} else {
var n int
minioAuthbytes := make([]byte, 1024)
n, err = minioAuth.Read(minioAuthbytes)
err = json.Unmarshal(minioAuthbytes[:n], &auth)
if err != nil {
return nil, err
}
}
return auth, nil
} }
func getAWSEnvironment() (auth *s3.Auth, err error) { func getAWSEnvironment() (auth *s3.Auth, err error) {
var s3Auth *os.File var s3Auth *os.File
var accessKey, secretKey string var accessKey, secretKey string
s3Auth, err = os.OpenFile(getAWSAuthFilePath(), os.O_RDWR, 0666) s3Auth, err = os.OpenFile(getAuthFilePath(), os.O_RDWR, 0666)
defer s3Auth.Close() defer s3Auth.Close()
if err != nil { if err != nil {
accessKey = os.Getenv("AWS_ACCESS_KEY_ID") accessKey = os.Getenv("AWS_ACCESS_KEY_ID")

View File

@@ -7,7 +7,6 @@ import (
"path" "path"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/minio-io/mc/pkg/minio"
"github.com/minio-io/mc/pkg/s3" "github.com/minio-io/mc/pkg/s3"
) )
@@ -23,58 +22,6 @@ func parseConfigureInput(c *cli.Context) (accessKey, secretKey string, err error
return accessKey, secretKey, nil return accessKey, secretKey, nil
} }
func doMinioConfigure(c *cli.Context) {
hostname := c.String("hostname")
if hostname == "" {
log.Fatal("Invalid hostname")
}
/*
certFile := c.String("cert")
if certFile == "" {
log.Fatal("invalid certificate")
}
keyFile := c.String("key")
if keyFile == "" {
log.Fatal("invalid key")
}
var accessKey, secretKey string
var err error
accessKey, secretKey, err = parseConfigureInput(c)
if err != nil {
log.Fatal(err)
}
*/
auth := &minio.Auth{
//AccessKey: accessKey,
//SecretKey: secretKey,
Hostname: hostname,
// CertPEM: certFile,
// KeyPEM: keyFile,
}
jAuth, err := json.Marshal(auth)
if err != nil {
log.Fatal(err)
}
var minioFile *os.File
home := os.Getenv("HOME")
minioFile, err = os.OpenFile(path.Join(home, MINIO_AUTH), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
defer minioFile.Close()
if err != nil {
log.Fatal(err)
}
_, err = minioFile.Write(jAuth)
if err != nil {
log.Fatal(err)
}
}
func doS3Configure(c *cli.Context) { func doS3Configure(c *cli.Context) {
var err error var err error
var jAuth []byte var jAuth []byte
@@ -92,7 +39,7 @@ func doS3Configure(c *cli.Context) {
var s3File *os.File var s3File *os.File
home := os.Getenv("HOME") home := os.Getenv("HOME")
s3File, err = os.OpenFile(path.Join(home, S3_AUTH), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) s3File, err = os.OpenFile(path.Join(home, AUTH), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
defer s3File.Close() defer s3File.Close()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

17
docs/index.md Normal file
View File

@@ -0,0 +1,17 @@
# Welcome to MkDocs
For full documentation visit [mkdocs.org](http://mkdocs.org).
## Commands
* `mkdocs new [dir-name]` - Create a new project.
* `mkdocs serve` - Start the live-reloading docs server.
* `mkdocs build` - Build the documentation site.
* `mkdocs help` - Print this help message.
## Project layout
mkdocs.yml # The configuration file.
docs/
index.md # The documentation homepage.
... # Other markdown pages, images and other files.

3
docs_linux.go Normal file
View File

@@ -0,0 +1,3 @@
package main
//go:generate mkdocs build --clean

View File

@@ -1,149 +0,0 @@
/*
* Mini Object Storage, (C) 2014,2015 Minio, Inc.
*
* 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
*
* 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 main
import (
"github.com/codegangsta/cli"
)
var MinioGetObject = cli.Command{
Name: "get-object",
Usage: "",
Description: "Retrieves objects from Amazon S3.",
Action: minioGetObject,
Flags: []cli.Flag{
cli.StringFlag{
Name: "bucket",
Value: "",
Usage: "bucket name",
},
cli.StringFlag{
Name: "key",
Value: "",
Usage: "path to Object",
},
},
}
var MinioPutBucket = cli.Command{
Name: "put-object",
Usage: "",
Description: "Adds an object to a bucket.",
Action: minioPutObject,
Flags: []cli.Flag{
cli.StringFlag{
Name: "bucket",
Value: "",
Usage: "bucket name",
},
cli.StringFlag{
Name: "key",
Value: "",
Usage: "Object name",
},
cli.StringFlag{
Name: "body",
Value: "",
Usage: "Object blob",
},
},
}
var MinioPutObject = cli.Command{
Name: "put-bucket",
Usage: "",
Description: "Creates a new bucket.",
Action: minioPutBucket,
Flags: []cli.Flag{
cli.StringFlag{
Name: "bucket",
Value: "",
Usage: "bucket name",
},
},
}
var MinioListObjects = cli.Command{
Name: "list-objects",
Usage: "",
Description: `Returns some or all (up to 1000) of the objects in a bucket.
You can use the request parameters as selection criteria to
return a subset of the objects in a bucket.`,
Action: minioListObjects,
Flags: []cli.Flag{
cli.StringFlag{
Name: "bucket",
Value: "",
Usage: "Bucket name",
},
},
}
var MinioListBuckets = cli.Command{
Name: "list-buckets",
Usage: "",
Description: `Returns a list of all buckets owned by the authenticated
sender of the request.`,
Action: minioListBuckets,
}
var MinioConfigure = cli.Command{
Name: "configure",
Usage: "",
Description: `Configure minio client configuration data. If your config
file does not exist (the default location is ~/.auth), it will be
automatically created for you. Note that the configure command only writes
values to the config file. It does not use any configuration values from
the environment variables.`,
Action: doMinioConfigure,
Flags: []cli.Flag{
cli.StringFlag{
Name: "hostname",
Value: "127.0.0.1:8080",
Usage: "Minio object server",
},
cli.StringFlag{
Name: "accesskey",
Value: "",
Usage: "Minio access key",
},
cli.StringFlag{
Name: "secretKey",
Value: "",
Usage: "Minio secret key",
},
cli.StringFlag{
Name: "cacert",
Value: "",
Usage: "CA authority cert",
},
cli.StringFlag{
Name: "cert",
Value: "",
Usage: "Minio server certificate",
},
cli.StringFlag{
Name: "key",
Value: "",
Usage: "Minio server private key",
},
},
}
const (
MINIO_AUTH = ".minioauth"
)

View File

@@ -1,59 +0,0 @@
/*
* Mini Object Storage, (C) 2014,2015 Minio, Inc.
*
* 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
*
* 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 main
import (
"io"
"log"
"os"
"github.com/codegangsta/cli"
"github.com/minio-io/mc/pkg/minio"
)
func minioGetObject(c *cli.Context) {
var bucket, key string
var err error
var objectReader io.ReadCloser
var objectSize int64
var auth *minio.Auth
auth, err = getMinioEnvironment()
if err != nil {
log.Fatal(err)
}
bucket = c.String("bucket")
key = c.String("key")
if bucket == "" {
log.Fatal(bucketNameErr)
}
if key == "" {
log.Fatal(objectNameErr)
}
minio, _ := minio.NewMinioClient(auth)
objectReader, objectSize, err = minio.Get(bucket, key)
if err != nil {
log.Fatal(err)
}
_, err = io.CopyN(os.Stdout, objectReader, objectSize)
if err != nil {
log.Fatal(err)
}
}

View File

@@ -1,30 +0,0 @@
package main
import (
"log"
"github.com/codegangsta/cli"
"github.com/minio-io/mc/pkg/minio"
)
func minioDumpBuckets(v []*minio.Bucket) {
for _, b := range v {
log.Printf("Bucket :%#v", b)
}
}
func minioListBuckets(c *cli.Context) {
auth, err := getMinioEnvironment()
if err != nil {
log.Fatal(err)
}
var buckets []*minio.Bucket
mc, _ := minio.NewMinioClient(auth)
buckets, err = mc.Buckets()
if err != nil {
log.Fatal(err)
}
minioDumpBuckets(buckets)
}

View File

@@ -1,60 +0,0 @@
/*
* Mini Object Storage, (C) 2014,2015 Minio, Inc.
*
* 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
*
* 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 main
import (
"log"
"github.com/codegangsta/cli"
"github.com/minio-io/mc/pkg/minio"
)
func minioParseListObjectsInput(c *cli.Context) (bucket string, err error) {
bucket = c.String("bucket")
if bucket == "" {
return "", bucketNameErr
}
return bucket, nil
}
func minioListObjects(c *cli.Context) {
var err error
var bucket string
var auth *minio.Auth
auth, err = getMinioEnvironment()
if err != nil {
log.Fatal(err)
}
bucket, err = minioParseListObjectsInput(c)
if err != nil {
log.Fatal(err)
}
var items []*minio.Item
mc, _ := minio.NewMinioClient(auth)
// Gets 1000 maxkeys supported with GET Bucket API
items, err = mc.GetBucket(bucket, "", minio.MAX_OBJECT_LIST)
if err != nil {
log.Fatal(err)
}
for _, item := range items {
log.Println(item)
}
}

View File

@@ -1,46 +0,0 @@
/*
* Mini Object Storage, (C) 2014,2015 Minio, Inc.
*
* 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
*
* 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 main
import (
"log"
"github.com/codegangsta/cli"
"github.com/minio-io/mc/pkg/minio"
)
func minioPutBucket(c *cli.Context) {
var bucket string
var auth *minio.Auth
var err error
auth, err = getMinioEnvironment()
if err != nil {
log.Fatal(err)
}
bucket = c.String("bucket")
if bucket == "" {
log.Fatal(bucketNameErr)
}
mc, _ := minio.NewMinioClient(auth)
err = mc.PutBucket(bucket)
if err != nil {
log.Fatal(err)
}
}

View File

@@ -1,104 +0,0 @@
/*
* Mini Object Storage, (C) 2014,2015 Minio, Inc.
*
* 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
*
* 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 main
import (
"bytes"
"io"
"log"
"os"
"github.com/codegangsta/cli"
"github.com/minio-io/mc/pkg/minio"
)
func minioPutMetadata(reader io.Reader) (bodyBuf io.Reader, size int64, err error) {
var length int
var bodyBuffer bytes.Buffer
for err == nil {
byteBuffer := make([]byte, 1024*1024)
length, err = reader.Read(byteBuffer)
// It is necessary for us to verify this and break
if length == 0 {
break
}
byteBuffer = byteBuffer[0:length]
_, err = bodyBuffer.Write(byteBuffer)
if err != nil {
break
}
}
if err != io.EOF {
return nil, 0, err
}
return &bodyBuffer, int64(bodyBuffer.Len()), nil
}
func minioParsePutObjectInput(c *cli.Context) (bucket, key, body string, err error) {
bucket = c.String("bucket")
key = c.String("key")
body = c.String("body")
if bucket == "" {
return "", "", "", bucketNameErr
}
if key == "" {
return "", "", "", objectNameErr
}
if body == "" {
return "", "", "", objectBlobErr
}
return bucket, key, body, nil
}
func minioPutObject(c *cli.Context) {
var err error
var bucket, key, body string
var auth *minio.Auth
auth, err = getMinioEnvironment()
if err != nil {
log.Fatal(err)
}
bucket, key, body, err = minioParsePutObjectInput(c)
if err != nil {
log.Fatal(err)
}
mc, _ := minio.NewMinioClient(auth)
var bodyFile *os.File
bodyFile, err = os.Open(body)
if err != nil {
log.Fatal(err)
}
var bodyBuffer io.Reader
var size int64
bodyBuffer, size, err = minioPutMetadata(bodyFile)
if err != nil {
log.Fatal(err)
}
err = mc.Put(bucket, key, nil, size, bodyBuffer)
if err != nil {
log.Fatal(err)
}
}

1
mkdocs.yml Normal file
View File

@@ -0,0 +1 @@
site_name: My Docs

View File

@@ -1 +0,0 @@
rootkey.csv

View File

@@ -1,69 +0,0 @@
package minio
import (
"crypto/tls"
"io/ioutil"
"net"
"net/http"
"time"
)
type Auth struct {
AccessKey string
SecretKey string
Hostname string
CertPEM string
KeyPEM string
}
type TlsConfig struct {
CertPEMBlock []byte
KeyPEMBlock []byte
}
func (a *Auth) loadKeys(cert string, key string) (*TlsConfig, error) {
certBlock, err := ioutil.ReadFile(cert)
if err != nil {
return nil, err
}
keyBlock, err := ioutil.ReadFile(key)
if err != nil {
return nil, err
}
t := &TlsConfig{}
t.CertPEMBlock = certBlock
t.KeyPEMBlock = keyBlock
return t, nil
}
func (a *Auth) getTlsTransport() (*http.Transport, error) {
if a.CertPEM == "" || a.KeyPEM == "" {
return &http.Transport{
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
}, nil
}
tlsconfig, err := a.loadKeys(a.CertPEM, a.KeyPEM)
if err != nil {
return nil, err
}
var cert tls.Certificate
cert, err = tls.X509KeyPair(tlsconfig.CertPEMBlock, tlsconfig.KeyPEMBlock)
if err != nil {
return nil, err
}
// Setup HTTPS client
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
}
tlsConfig.BuildNameToCertificate()
transport := &http.Transport{TLSClientConfig: tlsConfig}
return transport, nil
}

View File

@@ -1,380 +0,0 @@
/*
* Mini Object Storage, (C) 2014,2015 Minio, Inc.
*
* 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
*
* 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 minio implements a generic Minio client
package minio
import (
"bytes"
"encoding/base64"
"encoding/hex"
"encoding/xml"
"errors"
"fmt"
"hash"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
const (
MAX_OBJECT_LIST = 1000
)
// Client is an Minio MINIO client.
type Client struct {
Hostname string
Transport http.RoundTripper // or nil for the default
}
type Bucket struct {
Name string
CreationDate string // 2006-02-03T16:45:09.000Z
}
func (c *Client) transport() http.RoundTripper {
if c.Transport != nil {
return c.Transport
}
return http.DefaultTransport
}
func (c *Client) hostname() string {
if c.Hostname != "" {
return c.Hostname
}
return "localhost"
}
// bucketURL returns the URL prefix of the bucket, with trailing slash
func (c *Client) bucketURL(bucket string) string {
return fmt.Sprintf("http://%s/%s/", c.hostname(), bucket)
}
func (c *Client) keyURL(bucket, key string) string {
return c.bucketURL(bucket) + key
}
func newReq(url_ string) *http.Request {
req, err := http.NewRequest("GET", url_, nil)
if err != nil {
panic(fmt.Sprintf("minio client; invalid URL: %v", err))
}
req.Header.Set("User-Agent", "Minio")
return req
}
func (c *Client) Buckets() ([]*Bucket, error) {
req := newReq("http://" + c.hostname() + "/")
res, err := c.transport().RoundTrip(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("minio: Unexpected status code %d fetching bucket list", res.StatusCode)
}
return parseListAllMyBuckets(res.Body)
}
func parseListAllMyBuckets(r io.Reader) ([]*Bucket, error) {
type allMyBuckets struct {
Buckets struct {
Bucket []*Bucket
}
}
var res allMyBuckets
if err := xml.NewDecoder(r).Decode(&res); err != nil {
return nil, err
}
return res.Buckets.Bucket, nil
}
// Returns 0, os.ErrNotExist if not on MINIO, otherwise reterr is real.
func (c *Client) Stat(key, bucket string) (size int64, reterr error) {
req := newReq(c.keyURL(bucket, key))
req.Method = "HEAD"
res, err := c.transport().RoundTrip(req)
if err != nil {
return 0, err
}
if res.Body != nil {
defer res.Body.Close()
}
switch res.StatusCode {
case http.StatusNotFound:
return 0, os.ErrNotExist
case http.StatusOK:
return strconv.ParseInt(res.Header.Get("Content-Length"), 10, 64)
}
return 0, fmt.Errorf("minio: Unexpected status code %d statting object %v", res.StatusCode, key)
}
func (c *Client) PutBucket(bucket string) error {
req := newReq(c.bucketURL(bucket))
req.Method = "PUT"
res, err := c.transport().RoundTrip(req)
if res != nil && res.Body != nil {
defer res.Body.Close()
}
if err != nil {
return err
}
if res.StatusCode != http.StatusOK {
// res.Write(os.Stderr)
return fmt.Errorf("Got response code %d from minio", res.StatusCode)
}
return nil
}
func (c *Client) Put(bucket, key string, md5 hash.Hash, size int64, body io.Reader) error {
req := newReq(c.keyURL(bucket, key))
req.Method = "PUT"
req.ContentLength = size
if md5 != nil {
b64 := new(bytes.Buffer)
encoder := base64.NewEncoder(base64.StdEncoding, b64)
encoder.Write(md5.Sum(nil))
encoder.Close()
req.Header.Set("Content-MD5", b64.String())
}
req.Body = ioutil.NopCloser(body)
res, err := c.transport().RoundTrip(req)
if res != nil && res.Body != nil {
defer res.Body.Close()
}
if err != nil {
return err
}
if res.StatusCode != http.StatusOK {
// res.Write(os.Stderr)
return fmt.Errorf("Got response code %d from minio", res.StatusCode)
}
return nil
}
type Item struct {
Key string
LastModified string
Size int64
}
type listBucketResults struct {
Contents []*Item
IsTruncated bool
MaxKeys int
Name string // bucket name
Marker string
}
// GetBucket (List Objects) returns 0 to maxKeys (inclusive) items from the
// provided bucket. Keys before startAt will be skipped. (This is the MINIO
// 'marker' value). If the length of the returned items is equal to
// maxKeys, there is no indication whether or not the returned list is truncated.
func (c *Client) GetBucket(bucket string, startAt string, maxKeys int) (items []*Item, err error) {
if maxKeys < 0 {
return nil, errors.New("invalid negative maxKeys")
}
marker := startAt
for len(items) < maxKeys {
fetchN := maxKeys - len(items)
if fetchN > MAX_OBJECT_LIST {
fetchN = MAX_OBJECT_LIST
}
var bres listBucketResults
url_ := fmt.Sprintf("%s?marker=%s&max-keys=%d",
c.bucketURL(bucket), url.QueryEscape(marker), fetchN)
// Try the enumerate three times, since s3 likes to close
// https connections a lot, and Go sucks at dealing with it:
// https://code.google.com/p/go/issues/detail?id=3514
const maxTries = 5
for try := 1; try <= maxTries; try++ {
time.Sleep(time.Duration(try-1) * 100 * time.Millisecond)
req := newReq(url_)
res, err := c.transport().RoundTrip(req)
if err != nil {
if try < maxTries {
continue
}
return nil, err
}
if res.StatusCode != http.StatusOK {
if res.StatusCode < 500 {
body, _ := ioutil.ReadAll(io.LimitReader(res.Body, 1<<20))
aerr := &Error{
Op: "ListBucket",
Code: res.StatusCode,
Body: body,
Header: res.Header,
}
aerr.parseXML()
res.Body.Close()
return nil, aerr
}
} else {
bres = listBucketResults{}
var logbuf bytes.Buffer
err = xml.NewDecoder(io.TeeReader(res.Body, &logbuf)).Decode(&bres)
if err != nil {
log.Printf("Error parsing minio XML response: %v for %q", err, logbuf.Bytes())
} else if bres.MaxKeys != fetchN || bres.Name != bucket || bres.Marker != marker {
err = fmt.Errorf("Unexpected parse from server: %#v from: %s", bres, logbuf.Bytes())
log.Print(err)
}
}
res.Body.Close()
if err != nil {
if try < maxTries-1 {
continue
}
log.Print(err)
return nil, err
}
break
}
for _, it := range bres.Contents {
if it.Key == marker && it.Key != startAt {
// Skip first dup on pages 2 and higher.
continue
}
if it.Key < startAt {
return nil, fmt.Errorf("Unexpected response from Minio: item key %q but wanted greater than %q", it.Key, startAt)
}
items = append(items, it)
marker = it.Key
}
if !bres.IsTruncated {
// log.Printf("Not truncated. so breaking. items = %d; len Contents = %d, url = %s", len(items), len(bres.Contents), url_)
break
}
}
return items, nil
}
func (c *Client) Get(bucket, key string) (body io.ReadCloser, size int64, err error) {
req := newReq(c.keyURL(bucket, key))
res, err := c.transport().RoundTrip(req)
if err != nil {
return
}
switch res.StatusCode {
case http.StatusOK:
return res.Body, res.ContentLength, nil
case http.StatusNotFound:
res.Body.Close()
return nil, 0, os.ErrNotExist
default:
res.Body.Close()
return nil, 0, fmt.Errorf("Minio HTTP error on GET: %d", res.StatusCode)
}
}
// GetPartial fetches part of the minio key object in bucket.
// If length is negative, the rest of the object is returned.
// The caller must close rc.
func (c *Client) GetPartial(bucket, key string, offset, length int64) (rc io.ReadCloser, err error) {
if offset < 0 {
return nil, errors.New("invalid negative length")
}
req := newReq(c.keyURL(bucket, key))
if length >= 0 {
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1))
} else {
req.Header.Set("Range", fmt.Sprintf("bytes=%d-", offset))
}
res, err := c.transport().RoundTrip(req)
if err != nil {
return
}
switch res.StatusCode {
case http.StatusOK, http.StatusPartialContent:
return res.Body, nil
case http.StatusNotFound:
res.Body.Close()
return nil, os.ErrNotExist
default:
res.Body.Close()
return nil, fmt.Errorf("Minio HTTP error on GET: %d", res.StatusCode)
}
}
func NewMinioClient(auth *Auth) (client *Client, err error) {
transport, err := auth.getTlsTransport()
if err != nil {
return nil, err
}
client = &Client{auth.Hostname, transport}
return client, nil
}
// Error is the type returned by some API operations.
type Error struct {
Op string
Code int // HTTP status code
Body []byte // response body
Header http.Header // response headers
// UsedEndpoint and MinioCode are the XML response's Endpoint and
// Code fields, respectively.
UseEndpoint string // if a temporary redirect (wrong hostname)
MinioCode string
}
func (e *Error) Error() string {
if bytes.Contains(e.Body, []byte("<Error>")) {
return fmt.Sprintf("minio.%s: status %d: %s", e.Op, e.Code, e.Body)
}
return fmt.Sprintf("minio.%s: status %d", e.Op, e.Code)
}
func (e *Error) parseXML() {
var xe xmlError
_ = xml.NewDecoder(bytes.NewReader(e.Body)).Decode(&xe)
e.MinioCode = xe.Code
if xe.Code == "TemporaryRedirect" {
e.UseEndpoint = xe.Endpoint
}
if xe.Code == "SignatureDoesNotMatch" {
want, _ := hex.DecodeString(strings.Replace(xe.StringToSignBytes, " ", "", -1))
log.Printf("Minio SignatureDoesNotMatch. StringToSign should be %d bytes: %q (%x)", len(want), want, want)
}
}
// xmlError is the Error response from Minio.
type xmlError struct {
XMLName xml.Name `xml:"Error"`
Code string
Message string
RequestId string
Bucket string
Endpoint string
StringToSignBytes string
}

View File

@@ -1,51 +0,0 @@
/*
* Mini Object Storage, (C) 2014,2015 Minio, Inc.
*
* 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
*
* 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 minio
import (
"reflect"
"strings"
"testing"
)
var tc *Client
func TestParseBuckets(t *testing.T) {
res := "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ListAllMyBucketsResult xmlns=\"http://your-local.hostname.com/doc/2006-03-01/\"><Owner><ID>ownerIDField</ID><DisplayName>bobDisplayName</DisplayName></Owner><Buckets><Bucket><Name>bucketOne</Name><CreationDate>2006-06-21T07:04:31.000Z</CreationDate></Bucket><Bucket><Name>bucketTwo</Name><CreationDate>2006-06-21T07:04:32.000Z</CreationDate></Bucket></Buckets></ListAllMyBucketsResult>"
buckets, err := parseListAllMyBuckets(strings.NewReader(res))
if err != nil {
t.Fatal(err)
}
if g, w := len(buckets), 2; g != w {
t.Errorf("num parsed buckets = %d; want %d", g, w)
}
want := []*Bucket{
{Name: "bucketOne", CreationDate: "2006-06-21T07:04:31.000Z"},
{Name: "bucketTwo", CreationDate: "2006-06-21T07:04:32.000Z"},
}
dump := func(v []*Bucket) {
for i, b := range v {
t.Logf("Bucket #%d: %#v", i, b)
}
}
if !reflect.DeepEqual(buckets, want) {
t.Error("mismatch; GOT:")
dump(buckets)
t.Error("WANT:")
dump(want)
}
}

View File

@@ -1 +0,0 @@
package minio

View File

@@ -20,9 +20,12 @@ import (
"bytes" "bytes"
"crypto/hmac" "crypto/hmac"
"crypto/sha1" "crypto/sha1"
"crypto/tls"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net"
//"log" //"log"
"net/http" "net/http"
"net/url" "net/url"
@@ -37,13 +40,20 @@ type Auth struct {
AccessKey string AccessKey string
SecretKey string SecretKey string
// Hostname is the S3 hostname to use. // If empty, the standard US region of "s3.amazonaws.com" is used.
// If empty, the standard US region of "s3.amazonaws.com" is
// used.
Hostname string Hostname string
// Used for SSL transport layer
CertPEM string
KeyPEM string
} }
const standardUSRegionAWS = "https://s3.amazonaws.com" type TlsConfig struct {
CertPEMBlock []byte
KeyPEMBlock []byte
}
const standardUSRegionAWS = "s3.amazonaws.com"
func (a *Auth) hostname() string { func (a *Auth) hostname() string {
// Prefix with https for Amazon hostnames // Prefix with https for Amazon hostnames
@@ -54,7 +64,54 @@ func (a *Auth) hostname() string {
return "http://" + a.Hostname return "http://" + a.Hostname
} }
} }
return standardUSRegionAWS return "https://" + standardUSRegionAWS
}
func (a *Auth) loadKeys(cert string, key string) (*TlsConfig, error) {
certBlock, err := ioutil.ReadFile(cert)
if err != nil {
return nil, err
}
keyBlock, err := ioutil.ReadFile(key)
if err != nil {
return nil, err
}
t := &TlsConfig{}
t.CertPEMBlock = certBlock
t.KeyPEMBlock = keyBlock
return t, nil
}
func (a *Auth) getTlsTransport() (*http.Transport, error) {
if a.CertPEM == "" || a.KeyPEM == "" {
return &http.Transport{
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
}, nil
}
tlsconfig, err := a.loadKeys(a.CertPEM, a.KeyPEM)
if err != nil {
return nil, err
}
var cert tls.Certificate
cert, err = tls.X509KeyPair(tlsconfig.CertPEMBlock, tlsconfig.KeyPEMBlock)
if err != nil {
return nil, err
}
// Setup HTTPS client
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
}
tlsConfig.BuildNameToCertificate()
transport := &http.Transport{TLSClientConfig: tlsConfig}
return transport, nil
} }
func (a *Auth) SignRequest(req *http.Request) { func (a *Auth) SignRequest(req *http.Request) {

View File

@@ -111,5 +111,5 @@ var Configure = cli.Command{
} }
const ( const (
S3_AUTH = ".auth" AUTH = ".auth"
) )