mirror of
https://github.com/minio/minio.git
synced 2025-04-22 11:26:36 -04:00
Merge remote-tracking branch 'origin/master' into HEAD
Conflicts: pkg/drivers/donut/donut.go pkg/storage/donut/bucket.go pkg/storage/donut/donut.go pkg/storage/donut/donut_test.go pkg/storage/donut/donutdriver.go
This commit is contained in:
commit
dd5ba865ff
10
Godeps/Godeps.json
generated
10
Godeps/Godeps.json
generated
@ -5,11 +5,6 @@
|
|||||||
"./..."
|
"./..."
|
||||||
],
|
],
|
||||||
"Deps": [
|
"Deps": [
|
||||||
{
|
|
||||||
"ImportPath": "github.com/codegangsta/cli",
|
|
||||||
"Comment": "1.2.0-42-gfbda1ce",
|
|
||||||
"Rev": "fbda1ce02d5dabcee952040e5f4025753b6c9ce0"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/gorilla/context",
|
"ImportPath": "github.com/gorilla/context",
|
||||||
"Rev": "50c25fb3b2b3b3cc724e9b6ac75fb44b3bccd0da"
|
"Rev": "50c25fb3b2b3b3cc724e9b6ac75fb44b3bccd0da"
|
||||||
@ -18,6 +13,11 @@
|
|||||||
"ImportPath": "github.com/gorilla/mux",
|
"ImportPath": "github.com/gorilla/mux",
|
||||||
"Rev": "e444e69cbd2e2e3e0749a2f3c717cec491552bbf"
|
"Rev": "e444e69cbd2e2e3e0749a2f3c717cec491552bbf"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/minio-io/cli",
|
||||||
|
"Comment": "1.2.0-96-gfcc23e2",
|
||||||
|
"Rev": "fcc23e23a705c0d95fce2a446c364ac31a1c73a5"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "gopkg.in/check.v1",
|
"ImportPath": "gopkg.in/check.v1",
|
||||||
"Rev": "64131543e7896d5bcc6bd5a76287eb75ea96c673"
|
"Rev": "64131543e7896d5bcc6bd5a76287eb75ea96c673"
|
||||||
|
@ -210,7 +210,7 @@ Subcommands can be defined for a more git-like command line app.
|
|||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
{
|
{
|
||||||
Name: "add",
|
Name: "add",
|
||||||
ShortName: "a",
|
Aliases: []string{"a"},
|
||||||
Usage: "add a task to the list",
|
Usage: "add a task to the list",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) {
|
||||||
println("added task: ", c.Args().First())
|
println("added task: ", c.Args().First())
|
||||||
@ -218,7 +218,7 @@ app.Commands = []cli.Command{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "complete",
|
Name: "complete",
|
||||||
ShortName: "c",
|
Aliases: []string{"c"},
|
||||||
Usage: "complete a task on the list",
|
Usage: "complete a task on the list",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) {
|
||||||
println("completed task: ", c.Args().First())
|
println("completed task: ", c.Args().First())
|
||||||
@ -226,7 +226,7 @@ app.Commands = []cli.Command{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "template",
|
Name: "template",
|
||||||
ShortName: "r",
|
Aliases: []string{"r"},
|
||||||
Usage: "options for task templates",
|
Usage: "options for task templates",
|
||||||
Subcommands: []cli.Command{
|
Subcommands: []cli.Command{
|
||||||
{
|
{
|
||||||
@ -244,7 +244,7 @@ app.Commands = []cli.Command{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
@ -262,8 +262,8 @@ app := cli.NewApp()
|
|||||||
app.EnableBashCompletion = true
|
app.EnableBashCompletion = true
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
{
|
{
|
||||||
Name: "complete",
|
Name: "complete",
|
||||||
ShortName: "c",
|
Aliases: []string{"c"},
|
||||||
Usage: "complete a task on the list",
|
Usage: "complete a task on the list",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) {
|
||||||
println("completed task: ", c.Args().First())
|
println("completed task: ", c.Args().First())
|
||||||
@ -293,6 +293,6 @@ setting the `PROG` variable to the name of your program:
|
|||||||
## Contribution Guidelines
|
## Contribution Guidelines
|
||||||
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
|
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
|
||||||
|
|
||||||
If you are have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
|
If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together.
|
||||||
|
|
||||||
If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out.
|
If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out.
|
@ -2,8 +2,12 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,16 +35,23 @@ type App struct {
|
|||||||
// An action to execute before any subcommands are run, but after the context is ready
|
// An action to execute before any subcommands are run, but after the context is ready
|
||||||
// If a non-nil error is returned, no subcommands are run
|
// If a non-nil error is returned, no subcommands are run
|
||||||
Before func(context *Context) error
|
Before func(context *Context) error
|
||||||
|
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||||
|
// It is run even if Action() panics
|
||||||
|
After func(context *Context) error
|
||||||
// The action to execute when no subcommands are specified
|
// The action to execute when no subcommands are specified
|
||||||
Action func(context *Context)
|
Action func(context *Context)
|
||||||
// Execute this function if the proper command cannot be found
|
// Execute this function if the proper command cannot be found
|
||||||
CommandNotFound func(context *Context, command string)
|
CommandNotFound func(context *Context, command string)
|
||||||
// Compilation date
|
// Compilation date
|
||||||
Compiled time.Time
|
Compiled time.Time
|
||||||
// Author
|
// List of all authors who contributed
|
||||||
|
Authors []Author
|
||||||
|
// Name of Author (Note: Use App.Authors, this is deprecated)
|
||||||
Author string
|
Author string
|
||||||
// Author e-mail
|
// Email of Author (Note: Use App.Authors, this is deprecated)
|
||||||
Email string
|
Email string
|
||||||
|
// Writer writer to write output to
|
||||||
|
Writer io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tries to find out when this binary was compiled.
|
// Tries to find out when this binary was compiled.
|
||||||
@ -62,15 +73,42 @@ func NewApp() *App {
|
|||||||
BashComplete: DefaultAppComplete,
|
BashComplete: DefaultAppComplete,
|
||||||
Action: helpCommand.Action,
|
Action: helpCommand.Action,
|
||||||
Compiled: compileTime(),
|
Compiled: compileTime(),
|
||||||
|
Writer: os.Stdout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
|
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
|
||||||
func (a *App) Run(arguments []string) error {
|
func (a *App) Run(arguments []string) (err error) {
|
||||||
|
if a.Author != "" || a.Email != "" {
|
||||||
|
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
|
||||||
|
}
|
||||||
|
|
||||||
|
if HelpPrinter == nil {
|
||||||
|
defer func() {
|
||||||
|
HelpPrinter = nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
HelpPrinter = func(templ string, data interface{}) {
|
||||||
|
funcMap := template.FuncMap{
|
||||||
|
"join": strings.Join,
|
||||||
|
}
|
||||||
|
|
||||||
|
w := tabwriter.NewWriter(a.Writer, 0, 8, 1, '\t', 0)
|
||||||
|
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
||||||
|
err := t.Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// append help to commands
|
// append help to commands
|
||||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||||
a.Commands = append(a.Commands, helpCommand)
|
a.Commands = append(a.Commands, helpCommand)
|
||||||
a.appendFlag(HelpFlag)
|
if (HelpFlag != BoolFlag{}) {
|
||||||
|
a.appendFlag(HelpFlag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//append version/help flags
|
//append version/help flags
|
||||||
@ -85,21 +123,21 @@ func (a *App) Run(arguments []string) error {
|
|||||||
// parse flags
|
// parse flags
|
||||||
set := flagSet(a.Name, a.Flags)
|
set := flagSet(a.Name, a.Flags)
|
||||||
set.SetOutput(ioutil.Discard)
|
set.SetOutput(ioutil.Discard)
|
||||||
err := set.Parse(arguments[1:])
|
err = set.Parse(arguments[1:])
|
||||||
nerr := normalizeFlags(a.Flags, set)
|
nerr := normalizeFlags(a.Flags, set)
|
||||||
if nerr != nil {
|
if nerr != nil {
|
||||||
fmt.Println(nerr)
|
fmt.Fprintln(a.Writer, nerr)
|
||||||
context := NewContext(a, set, set)
|
context := NewContext(a, set, set)
|
||||||
ShowAppHelp(context)
|
ShowAppHelp(context)
|
||||||
fmt.Println("")
|
fmt.Fprintln(a.Writer)
|
||||||
return nerr
|
return nerr
|
||||||
}
|
}
|
||||||
context := NewContext(a, set, set)
|
context := NewContext(a, set, set)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Incorrect Usage.\n\n")
|
fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
|
||||||
ShowAppHelp(context)
|
ShowAppHelp(context)
|
||||||
fmt.Println("")
|
fmt.Fprintln(a.Writer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +153,15 @@ func (a *App) Run(arguments []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.After != nil {
|
||||||
|
defer func() {
|
||||||
|
// err is always nil here.
|
||||||
|
// There is a check to see if it is non-nil
|
||||||
|
// just few lines before.
|
||||||
|
err = a.After(context)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if a.Before != nil {
|
if a.Before != nil {
|
||||||
err := a.Before(context)
|
err := a.Before(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -139,18 +186,20 @@ func (a *App) Run(arguments []string) error {
|
|||||||
// Another entry point to the cli app, takes care of passing arguments and error handling
|
// Another entry point to the cli app, takes care of passing arguments and error handling
|
||||||
func (a *App) RunAndExitOnError() {
|
func (a *App) RunAndExitOnError() {
|
||||||
if err := a.Run(os.Args); err != nil {
|
if err := a.Run(os.Args); err != nil {
|
||||||
os.Stderr.WriteString(fmt.Sprintln(err))
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
|
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
|
||||||
func (a *App) RunAsSubcommand(ctx *Context) error {
|
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||||
// append help to commands
|
// append help to commands
|
||||||
if len(a.Commands) > 0 {
|
if len(a.Commands) > 0 {
|
||||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||||
a.Commands = append(a.Commands, helpCommand)
|
a.Commands = append(a.Commands, helpCommand)
|
||||||
a.appendFlag(HelpFlag)
|
if (HelpFlag != BoolFlag{}) {
|
||||||
|
a.appendFlag(HelpFlag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,23 +211,23 @@ func (a *App) RunAsSubcommand(ctx *Context) error {
|
|||||||
// parse flags
|
// parse flags
|
||||||
set := flagSet(a.Name, a.Flags)
|
set := flagSet(a.Name, a.Flags)
|
||||||
set.SetOutput(ioutil.Discard)
|
set.SetOutput(ioutil.Discard)
|
||||||
err := set.Parse(ctx.Args().Tail())
|
err = set.Parse(ctx.Args().Tail())
|
||||||
nerr := normalizeFlags(a.Flags, set)
|
nerr := normalizeFlags(a.Flags, set)
|
||||||
context := NewContext(a, set, ctx.globalSet)
|
context := NewContext(a, set, ctx.globalSet)
|
||||||
|
|
||||||
if nerr != nil {
|
if nerr != nil {
|
||||||
fmt.Println(nerr)
|
fmt.Fprintln(a.Writer, nerr)
|
||||||
if len(a.Commands) > 0 {
|
if len(a.Commands) > 0 {
|
||||||
ShowSubcommandHelp(context)
|
ShowSubcommandHelp(context)
|
||||||
} else {
|
} else {
|
||||||
ShowCommandHelp(ctx, context.Args().First())
|
ShowCommandHelp(ctx, context.Args().First())
|
||||||
}
|
}
|
||||||
fmt.Println("")
|
fmt.Fprintln(a.Writer)
|
||||||
return nerr
|
return nerr
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Incorrect Usage.\n\n")
|
fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
|
||||||
ShowSubcommandHelp(context)
|
ShowSubcommandHelp(context)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -197,6 +246,15 @@ func (a *App) RunAsSubcommand(ctx *Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if a.After != nil {
|
||||||
|
defer func() {
|
||||||
|
// err is always nil here.
|
||||||
|
// There is a check to see if it is non-nil
|
||||||
|
// just few lines before.
|
||||||
|
err = a.After(context)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if a.Before != nil {
|
if a.Before != nil {
|
||||||
err := a.Before(context)
|
err := a.Before(context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -214,11 +272,7 @@ func (a *App) RunAsSubcommand(ctx *Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run default Action
|
// Run default Action
|
||||||
if len(a.Commands) > 0 {
|
a.Action(context)
|
||||||
a.Action(context)
|
|
||||||
} else {
|
|
||||||
a.Action(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -249,3 +303,19 @@ func (a *App) appendFlag(flag Flag) {
|
|||||||
a.Flags = append(a.Flags, flag)
|
a.Flags = append(a.Flags, flag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Author represents someone who has contributed to a cli project.
|
||||||
|
type Author struct {
|
||||||
|
Name string // The Authors name
|
||||||
|
Email string // The Authors email
|
||||||
|
}
|
||||||
|
|
||||||
|
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
|
||||||
|
func (a Author) String() string {
|
||||||
|
e := ""
|
||||||
|
if a.Email != "" {
|
||||||
|
e = "<" + a.Email + "> "
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%v %v", a.Name, e)
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package cli_test
|
package cli_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@ -20,6 +21,9 @@ func ExampleApp() {
|
|||||||
app.Action = func(c *cli.Context) {
|
app.Action = func(c *cli.Context) {
|
||||||
fmt.Printf("Hello %v\n", c.String("name"))
|
fmt.Printf("Hello %v\n", c.String("name"))
|
||||||
}
|
}
|
||||||
|
app.Author = "Harrison"
|
||||||
|
app.Email = "harrison@lolwut.com"
|
||||||
|
app.Authors = []cli.Author{cli.Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}}
|
||||||
app.Run(os.Args)
|
app.Run(os.Args)
|
||||||
// Output:
|
// Output:
|
||||||
// Hello Jeremy
|
// Hello Jeremy
|
||||||
@ -33,13 +37,13 @@ func ExampleAppSubcommand() {
|
|||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
{
|
{
|
||||||
Name: "hello",
|
Name: "hello",
|
||||||
ShortName: "hi",
|
Aliases: []string{"hi"},
|
||||||
Usage: "use it to see a description",
|
Usage: "use it to see a description",
|
||||||
Description: "This is how we describe hello the function",
|
Description: "This is how we describe hello the function",
|
||||||
Subcommands: []cli.Command{
|
Subcommands: []cli.Command{
|
||||||
{
|
{
|
||||||
Name: "english",
|
Name: "english",
|
||||||
ShortName: "en",
|
Aliases: []string{"en"},
|
||||||
Usage: "sends a greeting in english",
|
Usage: "sends a greeting in english",
|
||||||
Description: "greets someone in english",
|
Description: "greets someone in english",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
@ -74,7 +78,7 @@ func ExampleAppHelp() {
|
|||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
{
|
{
|
||||||
Name: "describeit",
|
Name: "describeit",
|
||||||
ShortName: "d",
|
Aliases: []string{"d"},
|
||||||
Usage: "use it to see a description",
|
Usage: "use it to see a description",
|
||||||
Description: "This is how we describe describeit the function",
|
Description: "This is how we describe describeit the function",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) {
|
||||||
@ -104,7 +108,7 @@ func ExampleAppBashComplete() {
|
|||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
{
|
{
|
||||||
Name: "describeit",
|
Name: "describeit",
|
||||||
ShortName: "d",
|
Aliases: []string{"d"},
|
||||||
Usage: "use it to see a description",
|
Usage: "use it to see a description",
|
||||||
Description: "This is how we describe describeit the function",
|
Description: "This is how we describe describeit the function",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) {
|
||||||
@ -158,8 +162,8 @@ var commandAppTests = []struct {
|
|||||||
|
|
||||||
func TestApp_Command(t *testing.T) {
|
func TestApp_Command(t *testing.T) {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
fooCommand := cli.Command{Name: "foobar", ShortName: "f"}
|
fooCommand := cli.Command{Name: "foobar", Aliases: []string{"f"}}
|
||||||
batCommand := cli.Command{Name: "batbaz", ShortName: "b"}
|
batCommand := cli.Command{Name: "batbaz", Aliases: []string{"b"}}
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
fooCommand,
|
fooCommand,
|
||||||
batCommand,
|
batCommand,
|
||||||
@ -192,6 +196,76 @@ func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
|
|||||||
expect(t, firstArg, "my-arg")
|
expect(t, firstArg, "my-arg")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
||||||
|
var context *cli.Context
|
||||||
|
|
||||||
|
a := cli.NewApp()
|
||||||
|
a.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
context = c
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "lang",
|
||||||
|
Value: "english",
|
||||||
|
Usage: "language for the greeting",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Before: func(_ *cli.Context) error { return nil },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})
|
||||||
|
|
||||||
|
expect(t, context.Args().Get(0), "abcd")
|
||||||
|
expect(t, context.String("lang"), "spanish")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
|
||||||
|
var parsedOption string
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "option", Value: "", Usage: "some option"},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
parsedOption = c.String("option")
|
||||||
|
args = c.Args()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"})
|
||||||
|
|
||||||
|
expect(t, parsedOption, "my-option")
|
||||||
|
expect(t, args[0], "my-arg")
|
||||||
|
expect(t, args[1], "--")
|
||||||
|
expect(t, args[2], "--notARealFlag")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
args = c.Args()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
|
||||||
|
|
||||||
|
expect(t, args[0], "my-arg")
|
||||||
|
expect(t, args[1], "--")
|
||||||
|
expect(t, args[2], "notAFlagAtAll")
|
||||||
|
}
|
||||||
|
|
||||||
func TestApp_Float64Flag(t *testing.T) {
|
func TestApp_Float64Flag(t *testing.T) {
|
||||||
var meters float64
|
var meters float64
|
||||||
|
|
||||||
@ -265,6 +339,50 @@ func TestApp_ParseSliceFlags(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApp_DefaultStdout(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
|
||||||
|
if app.Writer != os.Stdout {
|
||||||
|
t.Error("Default output writer not set.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockWriter struct {
|
||||||
|
written []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *mockWriter) Write(p []byte) (n int, err error) {
|
||||||
|
if fw.written == nil {
|
||||||
|
fw.written = p
|
||||||
|
} else {
|
||||||
|
fw.written = append(fw.written, p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *mockWriter) GetWritten() (b []byte) {
|
||||||
|
return fw.written
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_SetStdout(t *testing.T) {
|
||||||
|
w := &mockWriter{}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "test"
|
||||||
|
app.Writer = w
|
||||||
|
|
||||||
|
err := app.Run([]string{"help"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(w.written) == 0 {
|
||||||
|
t.Error("App did not write output to desired writer.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApp_BeforeFunc(t *testing.T) {
|
func TestApp_BeforeFunc(t *testing.T) {
|
||||||
beforeRun, subcommandRun := false, false
|
beforeRun, subcommandRun := false, false
|
||||||
beforeError := fmt.Errorf("fail")
|
beforeError := fmt.Errorf("fail")
|
||||||
@ -331,6 +449,87 @@ func TestApp_BeforeFunc(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApp_AfterFunc(t *testing.T) {
|
||||||
|
afterRun, subcommandRun := false, false
|
||||||
|
afterError := fmt.Errorf("fail")
|
||||||
|
var err error
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
|
||||||
|
app.After = func(c *cli.Context) error {
|
||||||
|
afterRun = true
|
||||||
|
s := c.String("opt")
|
||||||
|
if s == "fail" {
|
||||||
|
return afterError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
cli.Command{
|
||||||
|
Name: "sub",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
subcommandRun = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "opt"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// run with the After() func succeeding
|
||||||
|
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if afterRun == false {
|
||||||
|
t.Errorf("After() not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subcommandRun == false {
|
||||||
|
t.Errorf("Subcommand not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
afterRun, subcommandRun = false, false
|
||||||
|
|
||||||
|
// run with the Before() func failing
|
||||||
|
err = app.Run([]string{"command", "--opt", "fail", "sub"})
|
||||||
|
|
||||||
|
// should be the same error produced by the Before func
|
||||||
|
if err != afterError {
|
||||||
|
t.Errorf("Run error expected, but not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
if afterRun == false {
|
||||||
|
t.Errorf("After() not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subcommandRun == false {
|
||||||
|
t.Errorf("Subcommand not executed when expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppNoHelpFlag(t *testing.T) {
|
||||||
|
oldFlag := cli.HelpFlag
|
||||||
|
defer func() {
|
||||||
|
cli.HelpFlag = oldFlag
|
||||||
|
}()
|
||||||
|
|
||||||
|
cli.HelpFlag = cli.BoolFlag{}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
err := app.Run([]string{"test", "-h"})
|
||||||
|
|
||||||
|
if err != flag.ErrHelp {
|
||||||
|
t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAppHelpPrinter(t *testing.T) {
|
func TestAppHelpPrinter(t *testing.T) {
|
||||||
oldPrinter := cli.HelpPrinter
|
oldPrinter := cli.HelpPrinter
|
||||||
defer func() {
|
defer func() {
|
@ -12,17 +12,17 @@ func Example() {
|
|||||||
app.Usage = "task list on the command line"
|
app.Usage = "task list on the command line"
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
{
|
{
|
||||||
Name: "add",
|
Name: "add",
|
||||||
ShortName: "a",
|
Aliases: []string{"a"},
|
||||||
Usage: "add a task to the list",
|
Usage: "add a task to the list",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) {
|
||||||
println("added task: ", c.Args().First())
|
println("added task: ", c.Args().First())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "complete",
|
Name: "complete",
|
||||||
ShortName: "c",
|
Aliases: []string{"c"},
|
||||||
Usage: "complete a task on the list",
|
Usage: "complete a task on the list",
|
||||||
Action: func(c *cli.Context) {
|
Action: func(c *cli.Context) {
|
||||||
println("completed task: ", c.Args().First())
|
println("completed task: ", c.Args().First())
|
||||||
},
|
},
|
||||||
@ -38,13 +38,13 @@ func ExampleSubcommand() {
|
|||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
{
|
{
|
||||||
Name: "hello",
|
Name: "hello",
|
||||||
ShortName: "hi",
|
Aliases: []string{"hi"},
|
||||||
Usage: "use it to see a description",
|
Usage: "use it to see a description",
|
||||||
Description: "This is how we describe hello the function",
|
Description: "This is how we describe hello the function",
|
||||||
Subcommands: []cli.Command{
|
Subcommands: []cli.Command{
|
||||||
{
|
{
|
||||||
Name: "english",
|
Name: "english",
|
||||||
ShortName: "en",
|
Aliases: []string{"en"},
|
||||||
Usage: "sends a greeting in english",
|
Usage: "sends a greeting in english",
|
||||||
Description: "greets someone in english",
|
Description: "greets someone in english",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
@ -58,9 +58,9 @@ func ExampleSubcommand() {
|
|||||||
println("Hello, ", c.String("name"))
|
println("Hello, ", c.String("name"))
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
Name: "spanish",
|
Name: "spanish",
|
||||||
ShortName: "sp",
|
Aliases: []string{"sp"},
|
||||||
Usage: "sends a greeting in spanish",
|
Usage: "sends a greeting in spanish",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "surname",
|
Name: "surname",
|
||||||
@ -72,9 +72,9 @@ func ExampleSubcommand() {
|
|||||||
println("Hola, ", c.String("surname"))
|
println("Hola, ", c.String("surname"))
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
Name: "french",
|
Name: "french",
|
||||||
ShortName: "fr",
|
Aliases: []string{"fr"},
|
||||||
Usage: "sends a greeting in french",
|
Usage: "sends a greeting in french",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "nickname",
|
Name: "nickname",
|
@ -10,8 +10,10 @@ import (
|
|||||||
type Command struct {
|
type Command struct {
|
||||||
// The name of the command
|
// The name of the command
|
||||||
Name string
|
Name string
|
||||||
// short name of the command. Typically one character
|
// short name of the command. Typically one character (deprecated, use `Aliases`)
|
||||||
ShortName string
|
ShortName string
|
||||||
|
// A list of aliases for the command
|
||||||
|
Aliases []string
|
||||||
// A short description of the usage of this command
|
// A short description of the usage of this command
|
||||||
Usage string
|
Usage string
|
||||||
// A longer explanation of how the command works
|
// A longer explanation of how the command works
|
||||||
@ -21,6 +23,9 @@ type Command struct {
|
|||||||
// An action to execute before any sub-subcommands are run, but after the context is ready
|
// An action to execute before any sub-subcommands are run, but after the context is ready
|
||||||
// If a non-nil error is returned, no sub-subcommands are run
|
// If a non-nil error is returned, no sub-subcommands are run
|
||||||
Before func(context *Context) error
|
Before func(context *Context) error
|
||||||
|
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||||
|
// It is run even if Action() panics
|
||||||
|
After func(context *Context) error
|
||||||
// The function to call when this command is invoked
|
// The function to call when this command is invoked
|
||||||
Action func(context *Context)
|
Action func(context *Context)
|
||||||
// List of child commands
|
// List of child commands
|
||||||
@ -36,11 +41,11 @@ type Command struct {
|
|||||||
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
||||||
func (c Command) Run(ctx *Context) error {
|
func (c Command) Run(ctx *Context) error {
|
||||||
|
|
||||||
if len(c.Subcommands) > 0 || c.Before != nil {
|
if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil {
|
||||||
return c.startApp(ctx)
|
return c.startApp(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.HideHelp {
|
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
|
||||||
// append help to flags
|
// append help to flags
|
||||||
c.Flags = append(
|
c.Flags = append(
|
||||||
c.Flags,
|
c.Flags,
|
||||||
@ -56,36 +61,48 @@ func (c Command) Run(ctx *Context) error {
|
|||||||
set.SetOutput(ioutil.Discard)
|
set.SetOutput(ioutil.Discard)
|
||||||
|
|
||||||
firstFlagIndex := -1
|
firstFlagIndex := -1
|
||||||
|
terminatorIndex := -1
|
||||||
for index, arg := range ctx.Args() {
|
for index, arg := range ctx.Args() {
|
||||||
if strings.HasPrefix(arg, "-") {
|
if arg == "--" {
|
||||||
firstFlagIndex = index
|
terminatorIndex = index
|
||||||
break
|
break
|
||||||
|
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
||||||
|
firstFlagIndex = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if firstFlagIndex > -1 && !c.SkipFlagParsing {
|
if firstFlagIndex > -1 && !c.SkipFlagParsing {
|
||||||
args := ctx.Args()
|
args := ctx.Args()
|
||||||
regularArgs := args[1:firstFlagIndex]
|
regularArgs := make([]string, len(args[1:firstFlagIndex]))
|
||||||
flagArgs := args[firstFlagIndex:]
|
copy(regularArgs, args[1:firstFlagIndex])
|
||||||
|
|
||||||
|
var flagArgs []string
|
||||||
|
if terminatorIndex > -1 {
|
||||||
|
flagArgs = args[firstFlagIndex:terminatorIndex]
|
||||||
|
regularArgs = append(regularArgs, args[terminatorIndex:]...)
|
||||||
|
} else {
|
||||||
|
flagArgs = args[firstFlagIndex:]
|
||||||
|
}
|
||||||
|
|
||||||
err = set.Parse(append(flagArgs, regularArgs...))
|
err = set.Parse(append(flagArgs, regularArgs...))
|
||||||
} else {
|
} else {
|
||||||
err = set.Parse(ctx.Args().Tail())
|
err = set.Parse(ctx.Args().Tail())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Incorrect Usage.\n\n")
|
fmt.Fprint(ctx.App.Writer, "Incorrect Usage.\n\n")
|
||||||
ShowCommandHelp(ctx, c.Name)
|
ShowCommandHelp(ctx, c.Name)
|
||||||
fmt.Println("")
|
fmt.Fprintln(ctx.App.Writer)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
nerr := normalizeFlags(c.Flags, set)
|
nerr := normalizeFlags(c.Flags, set)
|
||||||
if nerr != nil {
|
if nerr != nil {
|
||||||
fmt.Println(nerr)
|
fmt.Fprintln(ctx.App.Writer, nerr)
|
||||||
fmt.Println("")
|
fmt.Fprintln(ctx.App.Writer)
|
||||||
ShowCommandHelp(ctx, c.Name)
|
ShowCommandHelp(ctx, c.Name)
|
||||||
fmt.Println("")
|
fmt.Fprintln(ctx.App.Writer)
|
||||||
return nerr
|
return nerr
|
||||||
}
|
}
|
||||||
context := NewContext(ctx.App, set, ctx.globalSet)
|
context := NewContext(ctx.App, set, ctx.globalSet)
|
||||||
@ -102,9 +119,24 @@ func (c Command) Run(ctx *Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Command) Names() []string {
|
||||||
|
names := []string{c.Name}
|
||||||
|
|
||||||
|
if c.ShortName != "" {
|
||||||
|
names = append(names, c.ShortName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return append(names, c.Aliases...)
|
||||||
|
}
|
||||||
|
|
||||||
// Returns true if Command.Name or Command.ShortName matches given name
|
// Returns true if Command.Name or Command.ShortName matches given name
|
||||||
func (c Command) HasName(name string) bool {
|
func (c Command) HasName(name string) bool {
|
||||||
return c.Name == name || c.ShortName == name
|
for _, n := range c.Names() {
|
||||||
|
if n == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Command) startApp(ctx *Context) error {
|
func (c Command) startApp(ctx *Context) error {
|
||||||
@ -134,6 +166,7 @@ func (c Command) startApp(ctx *Context) error {
|
|||||||
|
|
||||||
// set the actions
|
// set the actions
|
||||||
app.Before = c.Before
|
app.Before = c.Before
|
||||||
|
app.After = c.After
|
||||||
if c.Action != nil {
|
if c.Action != nil {
|
||||||
app.Action = c.Action
|
app.Action = c.Action
|
||||||
} else {
|
} else {
|
@ -17,7 +17,7 @@ func TestCommandDoNotIgnoreFlags(t *testing.T) {
|
|||||||
|
|
||||||
command := cli.Command{
|
command := cli.Command{
|
||||||
Name: "test-cmd",
|
Name: "test-cmd",
|
||||||
ShortName: "tc",
|
Aliases: []string{"tc"},
|
||||||
Usage: "this is for testing",
|
Usage: "this is for testing",
|
||||||
Description: "testing",
|
Description: "testing",
|
||||||
Action: func(_ *cli.Context) {},
|
Action: func(_ *cli.Context) {},
|
||||||
@ -37,7 +37,7 @@ func TestCommandIgnoreFlags(t *testing.T) {
|
|||||||
|
|
||||||
command := cli.Command{
|
command := cli.Command{
|
||||||
Name: "test-cmd",
|
Name: "test-cmd",
|
||||||
ShortName: "tc",
|
Aliases: []string{"tc"},
|
||||||
Usage: "this is for testing",
|
Usage: "this is for testing",
|
||||||
Description: "testing",
|
Description: "testing",
|
||||||
Action: func(_ *cli.Context) {},
|
Action: func(_ *cli.Context) {},
|
@ -106,6 +106,11 @@ func (c *Context) GlobalGeneric(name string) interface{} {
|
|||||||
return lookupGeneric(name, c.globalSet)
|
return lookupGeneric(name, c.globalSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the number of flags set
|
||||||
|
func (c *Context) NumFlags() int {
|
||||||
|
return c.flagSet.NFlag()
|
||||||
|
}
|
||||||
|
|
||||||
// Determines if the flag was actually set
|
// Determines if the flag was actually set
|
||||||
func (c *Context) IsSet(name string) bool {
|
func (c *Context) IsSet(name string) bool {
|
||||||
if c.setFlags == nil {
|
if c.setFlags == nil {
|
@ -97,3 +97,15 @@ func TestContext_GlobalIsSet(t *testing.T) {
|
|||||||
expect(t, c.GlobalIsSet("myflagGlobalUnset"), false)
|
expect(t, c.GlobalIsSet("myflagGlobalUnset"), false)
|
||||||
expect(t, c.GlobalIsSet("bogusGlobal"), false)
|
expect(t, c.GlobalIsSet("bogusGlobal"), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext_NumFlags(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
set.String("otherflag", "hello world", "doc")
|
||||||
|
globalSet := flag.NewFlagSet("test", 0)
|
||||||
|
globalSet.Bool("myflagGlobal", true, "doc")
|
||||||
|
c := cli.NewContext(nil, set, globalSet)
|
||||||
|
set.Parse([]string{"--myflag", "--otherflag=foo"})
|
||||||
|
globalSet.Parse([]string{"--myflagGlobal"})
|
||||||
|
expect(t, c.NumFlags(), 2)
|
||||||
|
}
|
@ -12,6 +12,7 @@ import (
|
|||||||
// This flag enables bash-completion for all commands and subcommands
|
// This flag enables bash-completion for all commands and subcommands
|
||||||
var BashCompletionFlag = BoolFlag{
|
var BashCompletionFlag = BoolFlag{
|
||||||
Name: "generate-bash-completion",
|
Name: "generate-bash-completion",
|
||||||
|
Hide: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This flag prints the version for the application
|
// This flag prints the version for the application
|
||||||
@ -21,6 +22,8 @@ var VersionFlag = BoolFlag{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This flag prints the help for all commands and subcommands
|
// This flag prints the help for all commands and subcommands
|
||||||
|
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
||||||
|
// unless HideHelp is set to true)
|
||||||
var HelpFlag = BoolFlag{
|
var HelpFlag = BoolFlag{
|
||||||
Name: "help, h",
|
Name: "help, h",
|
||||||
Usage: "show help",
|
Usage: "show help",
|
||||||
@ -34,6 +37,7 @@ type Flag interface {
|
|||||||
// Apply Flag settings to the given flag set
|
// Apply Flag settings to the given flag set
|
||||||
Apply(*flag.FlagSet)
|
Apply(*flag.FlagSet)
|
||||||
getName() string
|
getName() string
|
||||||
|
isNotHidden() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func flagSet(name string, flags []Flag) *flag.FlagSet {
|
func flagSet(name string, flags []Flag) *flag.FlagSet {
|
||||||
@ -65,12 +69,18 @@ type GenericFlag struct {
|
|||||||
Value Generic
|
Value Generic
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
Hide bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the string representation of the generic flag to display the
|
||||||
|
// help text to the user (uses the String() method of the generic flag to show
|
||||||
|
// the value)
|
||||||
func (f GenericFlag) String() string {
|
func (f GenericFlag) String() string {
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s %v\t`%v` %s", prefixFor(f.Name), f.Name, f.Value, "-"+f.Name+" option -"+f.Name+" option", f.Usage))
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||||
|
// provided by the user for parsing by the flag
|
||||||
func (f GenericFlag) Apply(set *flag.FlagSet) {
|
func (f GenericFlag) Apply(set *flag.FlagSet) {
|
||||||
val := f.Value
|
val := f.Value
|
||||||
if f.EnvVar != "" {
|
if f.EnvVar != "" {
|
||||||
@ -92,6 +102,10 @@ func (f GenericFlag) getName() string {
|
|||||||
return f.Name
|
return f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f GenericFlag) isNotHidden() bool {
|
||||||
|
return !f.Hide
|
||||||
|
}
|
||||||
|
|
||||||
type StringSlice []string
|
type StringSlice []string
|
||||||
|
|
||||||
func (f *StringSlice) Set(value string) error {
|
func (f *StringSlice) Set(value string) error {
|
||||||
@ -112,12 +126,13 @@ type StringSliceFlag struct {
|
|||||||
Value *StringSlice
|
Value *StringSlice
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
Hide bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f StringSliceFlag) String() string {
|
func (f StringSliceFlag) String() string {
|
||||||
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
||||||
pref := prefixFor(firstName)
|
pref := prefixFor(firstName)
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||||
@ -145,6 +160,10 @@ func (f StringSliceFlag) getName() string {
|
|||||||
return f.Name
|
return f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f StringSliceFlag) isNotHidden() bool {
|
||||||
|
return !f.Hide
|
||||||
|
}
|
||||||
|
|
||||||
type IntSlice []int
|
type IntSlice []int
|
||||||
|
|
||||||
func (f *IntSlice) Set(value string) error {
|
func (f *IntSlice) Set(value string) error {
|
||||||
@ -171,12 +190,13 @@ type IntSliceFlag struct {
|
|||||||
Value *IntSlice
|
Value *IntSlice
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
Hide bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f IntSliceFlag) String() string {
|
func (f IntSliceFlag) String() string {
|
||||||
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
||||||
pref := prefixFor(firstName)
|
pref := prefixFor(firstName)
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||||
@ -207,10 +227,15 @@ func (f IntSliceFlag) getName() string {
|
|||||||
return f.Name
|
return f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f IntSliceFlag) isNotHidden() bool {
|
||||||
|
return !f.Hide
|
||||||
|
}
|
||||||
|
|
||||||
type BoolFlag struct {
|
type BoolFlag struct {
|
||||||
Name string
|
Name string
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
Hide bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f BoolFlag) String() string {
|
func (f BoolFlag) String() string {
|
||||||
@ -241,10 +266,15 @@ func (f BoolFlag) getName() string {
|
|||||||
return f.Name
|
return f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f BoolFlag) isNotHidden() bool {
|
||||||
|
return !f.Hide
|
||||||
|
}
|
||||||
|
|
||||||
type BoolTFlag struct {
|
type BoolTFlag struct {
|
||||||
Name string
|
Name string
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
Hide bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f BoolTFlag) String() string {
|
func (f BoolTFlag) String() string {
|
||||||
@ -275,11 +305,16 @@ func (f BoolTFlag) getName() string {
|
|||||||
return f.Name
|
return f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f BoolTFlag) isNotHidden() bool {
|
||||||
|
return !f.Hide
|
||||||
|
}
|
||||||
|
|
||||||
type StringFlag struct {
|
type StringFlag struct {
|
||||||
Name string
|
Name string
|
||||||
Value string
|
Value string
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
Hide bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f StringFlag) String() string {
|
func (f StringFlag) String() string {
|
||||||
@ -287,11 +322,10 @@ func (f StringFlag) String() string {
|
|||||||
fmtString = "%s %v\t%v"
|
fmtString = "%s %v\t%v"
|
||||||
|
|
||||||
if len(f.Value) > 0 {
|
if len(f.Value) > 0 {
|
||||||
fmtString = "%s '%v'\t%v"
|
fmtString = "%s \"%v\"\t%v"
|
||||||
} else {
|
} else {
|
||||||
fmtString = "%s %v\t%v"
|
fmtString = "%s %v\t%v"
|
||||||
}
|
}
|
||||||
|
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
|
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,15 +349,20 @@ func (f StringFlag) getName() string {
|
|||||||
return f.Name
|
return f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f StringFlag) isNotHidden() bool {
|
||||||
|
return !f.Hide
|
||||||
|
}
|
||||||
|
|
||||||
type IntFlag struct {
|
type IntFlag struct {
|
||||||
Name string
|
Name string
|
||||||
Value int
|
Value int
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
Hide bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f IntFlag) String() string {
|
func (f IntFlag) String() string {
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f IntFlag) Apply(set *flag.FlagSet) {
|
func (f IntFlag) Apply(set *flag.FlagSet) {
|
||||||
@ -331,7 +370,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) {
|
|||||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||||
envVar = strings.TrimSpace(envVar)
|
envVar = strings.TrimSpace(envVar)
|
||||||
if envVal := os.Getenv(envVar); envVal != "" {
|
if envVal := os.Getenv(envVar); envVal != "" {
|
||||||
envValInt, err := strconv.ParseUint(envVal, 10, 64)
|
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
f.Value = int(envValInt)
|
f.Value = int(envValInt)
|
||||||
break
|
break
|
||||||
@ -349,15 +388,20 @@ func (f IntFlag) getName() string {
|
|||||||
return f.Name
|
return f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f IntFlag) isNotHidden() bool {
|
||||||
|
return !f.Hide
|
||||||
|
}
|
||||||
|
|
||||||
type DurationFlag struct {
|
type DurationFlag struct {
|
||||||
Name string
|
Name string
|
||||||
Value time.Duration
|
Value time.Duration
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
Hide bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f DurationFlag) String() string {
|
func (f DurationFlag) String() string {
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f DurationFlag) Apply(set *flag.FlagSet) {
|
func (f DurationFlag) Apply(set *flag.FlagSet) {
|
||||||
@ -383,15 +427,20 @@ func (f DurationFlag) getName() string {
|
|||||||
return f.Name
|
return f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f DurationFlag) isNotHidden() bool {
|
||||||
|
return !f.Hide
|
||||||
|
}
|
||||||
|
|
||||||
type Float64Flag struct {
|
type Float64Flag struct {
|
||||||
Name string
|
Name string
|
||||||
Value float64
|
Value float64
|
||||||
Usage string
|
Usage string
|
||||||
EnvVar string
|
EnvVar string
|
||||||
|
Hide bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Float64Flag) String() string {
|
func (f Float64Flag) String() string {
|
||||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s '%v'\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f Float64Flag) Apply(set *flag.FlagSet) {
|
func (f Float64Flag) Apply(set *flag.FlagSet) {
|
||||||
@ -416,6 +465,10 @@ func (f Float64Flag) getName() string {
|
|||||||
return f.Name
|
return f.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f Float64Flag) isNotHidden() bool {
|
||||||
|
return !f.Hide
|
||||||
|
}
|
||||||
|
|
||||||
func prefixFor(name string) (prefix string) {
|
func prefixFor(name string) (prefix string) {
|
||||||
if len(name) == 1 {
|
if len(name) == 1 {
|
||||||
prefix = "-"
|
prefix = "-"
|
@ -38,7 +38,7 @@ var stringFlagTests = []struct {
|
|||||||
{"help", "", "--help \t"},
|
{"help", "", "--help \t"},
|
||||||
{"h", "", "-h \t"},
|
{"h", "", "-h \t"},
|
||||||
{"h", "", "-h \t"},
|
{"h", "", "-h \t"},
|
||||||
{"test", "Something", "--test 'Something'\t"},
|
{"test", "Something", "--test \"Something\"\t"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringFlagHelpOutput(t *testing.T) {
|
func TestStringFlagHelpOutput(t *testing.T) {
|
||||||
@ -75,22 +75,22 @@ var stringSliceFlagTests = []struct {
|
|||||||
s := &cli.StringSlice{}
|
s := &cli.StringSlice{}
|
||||||
s.Set("")
|
s.Set("")
|
||||||
return s
|
return s
|
||||||
}(), "--help '--help option --help option'\t"},
|
}(), "--help [--help option --help option]\t"},
|
||||||
{"h", func() *cli.StringSlice {
|
{"h", func() *cli.StringSlice {
|
||||||
s := &cli.StringSlice{}
|
s := &cli.StringSlice{}
|
||||||
s.Set("")
|
s.Set("")
|
||||||
return s
|
return s
|
||||||
}(), "-h '-h option -h option'\t"},
|
}(), "-h [-h option -h option]\t"},
|
||||||
{"h", func() *cli.StringSlice {
|
{"h", func() *cli.StringSlice {
|
||||||
s := &cli.StringSlice{}
|
s := &cli.StringSlice{}
|
||||||
s.Set("")
|
s.Set("")
|
||||||
return s
|
return s
|
||||||
}(), "-h '-h option -h option'\t"},
|
}(), "-h [-h option -h option]\t"},
|
||||||
{"test", func() *cli.StringSlice {
|
{"test", func() *cli.StringSlice {
|
||||||
s := &cli.StringSlice{}
|
s := &cli.StringSlice{}
|
||||||
s.Set("Something")
|
s.Set("Something")
|
||||||
return s
|
return s
|
||||||
}(), "--test '--test option --test option'\t"},
|
}(), "--test [--test option --test option]\t"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStringSliceFlagHelpOutput(t *testing.T) {
|
func TestStringSliceFlagHelpOutput(t *testing.T) {
|
||||||
@ -122,8 +122,8 @@ var intFlagTests = []struct {
|
|||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"help", "--help '0'\t"},
|
{"help", "--help \"0\"\t"},
|
||||||
{"h", "-h '0'\t"},
|
{"h", "-h \"0\"\t"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntFlagHelpOutput(t *testing.T) {
|
func TestIntFlagHelpOutput(t *testing.T) {
|
||||||
@ -155,8 +155,8 @@ var durationFlagTests = []struct {
|
|||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"help", "--help '0'\t"},
|
{"help", "--help \"0\"\t"},
|
||||||
{"h", "-h '0'\t"},
|
{"h", "-h \"0\"\t"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDurationFlagHelpOutput(t *testing.T) {
|
func TestDurationFlagHelpOutput(t *testing.T) {
|
||||||
@ -189,14 +189,14 @@ var intSliceFlagTests = []struct {
|
|||||||
value *cli.IntSlice
|
value *cli.IntSlice
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"help", &cli.IntSlice{}, "--help '--help option --help option'\t"},
|
{"help", &cli.IntSlice{}, "--help [--help option --help option]\t"},
|
||||||
{"h", &cli.IntSlice{}, "-h '-h option -h option'\t"},
|
{"h", &cli.IntSlice{}, "-h [-h option -h option]\t"},
|
||||||
{"h", &cli.IntSlice{}, "-h '-h option -h option'\t"},
|
{"h", &cli.IntSlice{}, "-h [-h option -h option]\t"},
|
||||||
{"test", func() *cli.IntSlice {
|
{"test", func() *cli.IntSlice {
|
||||||
i := &cli.IntSlice{}
|
i := &cli.IntSlice{}
|
||||||
i.Set("9")
|
i.Set("9")
|
||||||
return i
|
return i
|
||||||
}(), "--test '--test option --test option'\t"},
|
}(), "--test [--test option --test option]\t"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntSliceFlagHelpOutput(t *testing.T) {
|
func TestIntSliceFlagHelpOutput(t *testing.T) {
|
||||||
@ -228,8 +228,8 @@ var float64FlagTests = []struct {
|
|||||||
name string
|
name string
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"help", "--help '0'\t"},
|
{"help", "--help \"0\"\t"},
|
||||||
{"h", "-h '0'\t"},
|
{"h", "-h \"0\"\t"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFloat64FlagHelpOutput(t *testing.T) {
|
func TestFloat64FlagHelpOutput(t *testing.T) {
|
||||||
@ -262,15 +262,14 @@ var genericFlagTests = []struct {
|
|||||||
value cli.Generic
|
value cli.Generic
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{"help", &Parser{}, "--help <nil>\t`-help option -help option` "},
|
{"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"},
|
||||||
{"h", &Parser{}, "-h <nil>\t`-h option -h option` "},
|
{"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"},
|
||||||
{"test", &Parser{}, "--test <nil>\t`-test option -test option` "},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenericFlagHelpOutput(t *testing.T) {
|
func TestGenericFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range genericFlagTests {
|
for _, test := range genericFlagTests {
|
||||||
flag := cli.GenericFlag{Name: test.name}
|
flag := cli.GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"}
|
||||||
output := flag.String()
|
output := flag.String()
|
||||||
|
|
||||||
if output != test.expected {
|
if output != test.expected {
|
@ -1,11 +1,6 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import "fmt"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"text/tabwriter"
|
|
||||||
"text/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The text template for the Default help topic.
|
// The text template for the Default help topic.
|
||||||
// cli.go uses text/template to render templates. You can
|
// cli.go uses text/template to render templates. You can
|
||||||
@ -17,14 +12,13 @@ USAGE:
|
|||||||
{{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...]
|
{{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...]
|
||||||
|
|
||||||
VERSION:
|
VERSION:
|
||||||
{{.Version}}{{if or .Author .Email}}
|
{{.Version}}
|
||||||
|
|
||||||
AUTHOR:{{if .Author}}
|
AUTHOR(S):
|
||||||
{{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}}
|
{{range .Authors}}{{ . }} {{end}}
|
||||||
{{.Email}}{{end}}{{end}}
|
|
||||||
|
|
||||||
COMMANDS:
|
COMMANDS:
|
||||||
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
|
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
||||||
{{end}}{{if .Flags}}
|
{{end}}{{if .Flags}}
|
||||||
GLOBAL OPTIONS:
|
GLOBAL OPTIONS:
|
||||||
{{range .Flags}}{{.}}
|
{{range .Flags}}{{.}}
|
||||||
@ -58,7 +52,7 @@ USAGE:
|
|||||||
{{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...]
|
{{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...]
|
||||||
|
|
||||||
COMMANDS:
|
COMMANDS:
|
||||||
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
|
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
||||||
{{end}}{{if .Flags}}
|
{{end}}{{if .Flags}}
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
{{range .Flags}}{{.}}
|
{{range .Flags}}{{.}}
|
||||||
@ -66,9 +60,9 @@ OPTIONS:
|
|||||||
`
|
`
|
||||||
|
|
||||||
var helpCommand = Command{
|
var helpCommand = Command{
|
||||||
Name: "help",
|
Name: "help",
|
||||||
ShortName: "h",
|
Aliases: []string{"h"},
|
||||||
Usage: "Shows a list of commands or help for one command",
|
Usage: "Shows a list of commands or help for one command",
|
||||||
Action: func(c *Context) {
|
Action: func(c *Context) {
|
||||||
args := c.Args()
|
args := c.Args()
|
||||||
if args.Present() {
|
if args.Present() {
|
||||||
@ -80,9 +74,9 @@ var helpCommand = Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var helpSubcommand = Command{
|
var helpSubcommand = Command{
|
||||||
Name: "help",
|
Name: "help",
|
||||||
ShortName: "h",
|
Aliases: []string{"h"},
|
||||||
Usage: "Shows a list of commands or help for one command",
|
Usage: "Shows a list of commands or help for one command",
|
||||||
Action: func(c *Context) {
|
Action: func(c *Context) {
|
||||||
args := c.Args()
|
args := c.Args()
|
||||||
if args.Present() {
|
if args.Present() {
|
||||||
@ -94,30 +88,61 @@ var helpSubcommand = Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prints help for the App
|
// Prints help for the App
|
||||||
var HelpPrinter = printHelp
|
type helpPrinter func(templ string, data interface{})
|
||||||
|
|
||||||
|
var HelpPrinter helpPrinter = nil
|
||||||
|
|
||||||
// Prints version for the App
|
// Prints version for the App
|
||||||
var VersionPrinter = printVersion
|
var VersionPrinter = printVersion
|
||||||
|
|
||||||
func ShowAppHelp(c *Context) {
|
func ShowAppHelp(c *Context) {
|
||||||
HelpPrinter(AppHelpTemplate, c.App)
|
// Make a copy of c.App context
|
||||||
|
app := *c.App
|
||||||
|
app.Flags = make([]Flag, 0)
|
||||||
|
for _, flag := range c.App.Flags {
|
||||||
|
if flag.isNotHidden() {
|
||||||
|
app.Flags = append(app.Flags, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HelpPrinter(AppHelpTemplate, app)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints the list of subcommands as the default app completion method
|
// Prints the list of subcommands as the default app completion method
|
||||||
func DefaultAppComplete(c *Context) {
|
func DefaultAppComplete(c *Context) {
|
||||||
for _, command := range c.App.Commands {
|
for _, command := range c.App.Commands {
|
||||||
fmt.Println(command.Name)
|
for _, name := range command.Names() {
|
||||||
if command.ShortName != "" {
|
fmt.Fprintln(c.App.Writer, name)
|
||||||
fmt.Println(command.ShortName)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints help for the given command
|
// Prints help for the given command
|
||||||
func ShowCommandHelp(c *Context, command string) {
|
func ShowCommandHelp(c *Context, command string) {
|
||||||
|
// show the subcommand help for a command with subcommands
|
||||||
|
if command == "" {
|
||||||
|
// Make a copy of c.App context
|
||||||
|
app := *c.App
|
||||||
|
app.Flags = make([]Flag, 0)
|
||||||
|
for _, flag := range c.App.Flags {
|
||||||
|
if flag.isNotHidden() {
|
||||||
|
app.Flags = append(app.Flags, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HelpPrinter(SubcommandHelpTemplate, app)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range c.App.Commands {
|
for _, c := range c.App.Commands {
|
||||||
if c.HasName(command) {
|
if c.HasName(command) {
|
||||||
HelpPrinter(CommandHelpTemplate, c)
|
// Make a copy of command context
|
||||||
|
c0 := c
|
||||||
|
c0.Flags = make([]Flag, 0)
|
||||||
|
for _, flag := range c.Flags {
|
||||||
|
if flag.isNotHidden() {
|
||||||
|
c0.Flags = append(c0.Flags, flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HelpPrinter(CommandHelpTemplate, c0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,13 +150,13 @@ func ShowCommandHelp(c *Context, command string) {
|
|||||||
if c.App.CommandNotFound != nil {
|
if c.App.CommandNotFound != nil {
|
||||||
c.App.CommandNotFound(c, command)
|
c.App.CommandNotFound(c, command)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("No help topic for '%v'\n", command)
|
fmt.Fprintf(c.App.Writer, "No help topic for '%v'\n", command)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints help for the given subcommand
|
// Prints help for the given subcommand
|
||||||
func ShowSubcommandHelp(c *Context) {
|
func ShowSubcommandHelp(c *Context) {
|
||||||
HelpPrinter(SubcommandHelpTemplate, c.App)
|
ShowCommandHelp(c, c.Command.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints the version number of the App
|
// Prints the version number of the App
|
||||||
@ -140,7 +165,7 @@ func ShowVersion(c *Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func printVersion(c *Context) {
|
func printVersion(c *Context) {
|
||||||
fmt.Printf("%v version %v\n", c.App.Name, c.App.Version)
|
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prints the lists of commands within a given context
|
// Prints the lists of commands within a given context
|
||||||
@ -159,16 +184,6 @@ func ShowCommandCompletions(ctx *Context, command string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func printHelp(templ string, data interface{}) {
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
|
|
||||||
t := template.Must(template.New("help").Parse(templ))
|
|
||||||
err := t.Execute(w, data)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkVersion(c *Context) bool {
|
func checkVersion(c *Context) bool {
|
||||||
if c.GlobalBool("version") {
|
if c.GlobalBool("version") {
|
||||||
ShowVersion(c)
|
ShowVersion(c)
|
87
main.go
87
main.go
@ -19,12 +19,47 @@ package main
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/minio-io/cli"
|
||||||
"github.com/minio-io/minio/pkg/server"
|
"github.com/minio-io/minio/pkg/server"
|
||||||
"github.com/minio-io/minio/pkg/utils/log"
|
"github.com/minio-io/minio/pkg/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getStorageType(input string) server.StorageType {
|
var flags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "domain,d",
|
||||||
|
Value: "",
|
||||||
|
Usage: "domain used for routing incoming API requests",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "api-address,a",
|
||||||
|
Value: ":9000",
|
||||||
|
Usage: "address for incoming API requests",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "web-address,w",
|
||||||
|
Value: ":9001",
|
||||||
|
Usage: "address for incoming Management UI requests",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "cert,c",
|
||||||
|
Hide: true,
|
||||||
|
Value: "",
|
||||||
|
Usage: "cert.pem",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "key,k",
|
||||||
|
Hide: true,
|
||||||
|
Value: "",
|
||||||
|
Usage: "key.pem",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "driver-type,t",
|
||||||
|
Value: "donut",
|
||||||
|
Usage: "valid entries: file,inmemory,donut",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDriverType(input string) server.DriverType {
|
||||||
switch {
|
switch {
|
||||||
case input == "file":
|
case input == "file":
|
||||||
return server.File
|
return server.File
|
||||||
@ -34,15 +69,15 @@ func getStorageType(input string) server.StorageType {
|
|||||||
return server.Donut
|
return server.Donut
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
log.Println("Unknown storage type:", input)
|
log.Println("Unknown driver type:", input)
|
||||||
log.Println("Choosing default storage type as 'file'..")
|
log.Println("Choosing default driver type as 'file'..")
|
||||||
return server.File
|
return server.File
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCmd(c *cli.Context) {
|
func runCmd(c *cli.Context) {
|
||||||
storageTypeStr := c.String("storage-type")
|
driverTypeStr := c.String("driver-type")
|
||||||
domain := c.String("domain")
|
domain := c.String("domain")
|
||||||
apiaddress := c.String("api-address")
|
apiaddress := c.String("api-address")
|
||||||
webaddress := c.String("web-address")
|
webaddress := c.String("web-address")
|
||||||
@ -52,7 +87,7 @@ func runCmd(c *cli.Context) {
|
|||||||
log.Fatal("Both certificate and key must be provided to enable https")
|
log.Fatal("Both certificate and key must be provided to enable https")
|
||||||
}
|
}
|
||||||
tls := (certFile != "" && keyFile != "")
|
tls := (certFile != "" && keyFile != "")
|
||||||
storageType := getStorageType(storageTypeStr)
|
driverType := getDriverType(driverTypeStr)
|
||||||
var serverConfigs []server.Config
|
var serverConfigs []server.Config
|
||||||
apiServerConfig := server.Config{
|
apiServerConfig := server.Config{
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
@ -61,7 +96,7 @@ func runCmd(c *cli.Context) {
|
|||||||
CertFile: certFile,
|
CertFile: certFile,
|
||||||
KeyFile: keyFile,
|
KeyFile: keyFile,
|
||||||
APIType: server.MinioAPI{
|
APIType: server.MinioAPI{
|
||||||
StorageType: storageType,
|
DriverType: driverType,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
webUIServerConfig := server.Config{
|
webUIServerConfig := server.Config{
|
||||||
@ -82,39 +117,11 @@ func runCmd(c *cli.Context) {
|
|||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "minio"
|
app.Name = "minio"
|
||||||
app.Usage = ""
|
app.Version = "0.1.0"
|
||||||
app.Flags = []cli.Flag{
|
app.Author = "Minio.io"
|
||||||
cli.StringFlag{
|
app.Usage = "Minimalist Object Storage"
|
||||||
Name: "domain,d",
|
app.EnableBashCompletion = true
|
||||||
Value: "",
|
app.Flags = flags
|
||||||
Usage: "domain used for routing incoming API requests",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "api-address,a",
|
|
||||||
Value: ":9000",
|
|
||||||
Usage: "address for incoming API requests",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "web-address,w",
|
|
||||||
Value: ":9001",
|
|
||||||
Usage: "address for incoming Management UI requests",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "cert,c",
|
|
||||||
Value: "",
|
|
||||||
Usage: "cert.pem",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "key,k",
|
|
||||||
Value: "",
|
|
||||||
Usage: "key.pem",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "storage-type,s",
|
|
||||||
Value: "donut",
|
|
||||||
Usage: "valid entries: file,inmemory,donut",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
app.Action = runCmd
|
app.Action = runCmd
|
||||||
app.Run(os.Args)
|
app.Run(os.Args)
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
"github.com/minio-io/minio/pkg/utils/log"
|
"github.com/minio-io/minio/pkg/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,21 +45,21 @@ func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
acceptsContentType := getContentType(req)
|
acceptsContentType := getContentType(req)
|
||||||
objects, resources, err := server.storage.ListObjects(bucket, resources)
|
objects, resources, err := server.driver.ListObjects(bucket, resources)
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case nil: // success
|
case nil: // success
|
||||||
{
|
{
|
||||||
response := generateObjectsListResult(bucket, objects, resources)
|
response := generateObjectsListResult(bucket, objects, resources)
|
||||||
w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType))
|
w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BucketNotFound:
|
case drivers.BucketNotFound:
|
||||||
{
|
{
|
||||||
error := errorCodeError(NoSuchBucket)
|
error := errorCodeError(NoSuchBucket)
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.ImplementationError:
|
case drivers.ImplementationError:
|
||||||
{
|
{
|
||||||
// Embed error log on server side
|
// Embed error log on server side
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
@ -68,14 +68,14 @@ func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Requ
|
|||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BucketNameInvalid:
|
case drivers.BucketNameInvalid:
|
||||||
{
|
{
|
||||||
error := errorCodeError(InvalidBucketName)
|
error := errorCodeError(InvalidBucketName)
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.ObjectNameInvalid:
|
case drivers.ObjectNameInvalid:
|
||||||
{
|
{
|
||||||
error := errorCodeError(NoSuchKey)
|
error := errorCodeError(NoSuchKey)
|
||||||
errorResponse := getErrorResponse(error, resources.Prefix)
|
errorResponse := getErrorResponse(error, resources.Prefix)
|
||||||
@ -91,14 +91,14 @@ func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Requ
|
|||||||
// owned by the authenticated sender of the request.
|
// owned by the authenticated sender of the request.
|
||||||
func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Request) {
|
func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
acceptsContentType := getContentType(req)
|
acceptsContentType := getContentType(req)
|
||||||
buckets, err := server.storage.ListBuckets()
|
buckets, err := server.driver.ListBuckets()
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
{
|
{
|
||||||
response := generateBucketsListResult(buckets)
|
response := generateBucketsListResult(buckets)
|
||||||
w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType))
|
w.Write(writeObjectHeadersAndResponse(w, response, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.ImplementationError:
|
case drivers.ImplementationError:
|
||||||
{
|
{
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
error := errorCodeError(InternalError)
|
error := errorCodeError(InternalError)
|
||||||
@ -106,7 +106,7 @@ func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Requ
|
|||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BackendCorrupted:
|
case drivers.BackendCorrupted:
|
||||||
{
|
{
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
error := errorCodeError(InternalError)
|
error := errorCodeError(InternalError)
|
||||||
@ -123,7 +123,7 @@ func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Requ
|
|||||||
func (server *minioAPI) putBucketHandler(w http.ResponseWriter, req *http.Request) {
|
func (server *minioAPI) putBucketHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
vars := mux.Vars(req)
|
vars := mux.Vars(req)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
err := server.storage.CreateBucket(bucket)
|
err := server.driver.CreateBucket(bucket)
|
||||||
|
|
||||||
resources := getBucketResources(req.URL.Query())
|
resources := getBucketResources(req.URL.Query())
|
||||||
if resources.Policy == true {
|
if resources.Policy == true {
|
||||||
@ -138,21 +138,21 @@ func (server *minioAPI) putBucketHandler(w http.ResponseWriter, req *http.Reques
|
|||||||
w.Header().Set("Server", "Minio")
|
w.Header().Set("Server", "Minio")
|
||||||
w.Header().Set("Connection", "close")
|
w.Header().Set("Connection", "close")
|
||||||
}
|
}
|
||||||
case mstorage.BucketNameInvalid:
|
case drivers.BucketNameInvalid:
|
||||||
{
|
{
|
||||||
error := errorCodeError(InvalidBucketName)
|
error := errorCodeError(InvalidBucketName)
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BucketExists:
|
case drivers.BucketExists:
|
||||||
{
|
{
|
||||||
error := errorCodeError(BucketAlreadyExists)
|
error := errorCodeError(BucketAlreadyExists)
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.ImplementationError:
|
case drivers.ImplementationError:
|
||||||
{
|
{
|
||||||
// Embed errors log on server side
|
// Embed errors log on server side
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
"github.com/minio-io/minio/pkg/utils/log"
|
"github.com/minio-io/minio/pkg/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Reques
|
|||||||
bucket = vars["bucket"]
|
bucket = vars["bucket"]
|
||||||
object = vars["object"]
|
object = vars["object"]
|
||||||
|
|
||||||
metadata, err := server.storage.GetObjectMetadata(bucket, object, "")
|
metadata, err := server.driver.GetObjectMetadata(bucket, object, "")
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case nil: // success
|
case nil: // success
|
||||||
{
|
{
|
||||||
@ -52,7 +52,7 @@ func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Reques
|
|||||||
switch httpRange.start == 0 && httpRange.length == 0 {
|
switch httpRange.start == 0 && httpRange.length == 0 {
|
||||||
case true:
|
case true:
|
||||||
writeObjectHeaders(w, metadata)
|
writeObjectHeaders(w, metadata)
|
||||||
if _, err := server.storage.GetObject(w, bucket, object); err != nil {
|
if _, err := server.driver.GetObject(w, bucket, object); err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
error := errorCodeError(InternalError)
|
error := errorCodeError(InternalError)
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
@ -64,7 +64,7 @@ func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Reques
|
|||||||
metadata.Size = httpRange.length
|
metadata.Size = httpRange.length
|
||||||
writeRangeObjectHeaders(w, metadata, httpRange.getContentRange())
|
writeRangeObjectHeaders(w, metadata, httpRange.getContentRange())
|
||||||
w.WriteHeader(http.StatusPartialContent)
|
w.WriteHeader(http.StatusPartialContent)
|
||||||
_, err := server.storage.GetPartialObject(w, bucket, object, httpRange.start, httpRange.length)
|
_, err := server.driver.GetPartialObject(w, bucket, object, httpRange.start, httpRange.length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
error := errorCodeError(InternalError)
|
error := errorCodeError(InternalError)
|
||||||
@ -76,28 +76,28 @@ func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Reques
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case mstorage.ObjectNotFound:
|
case drivers.ObjectNotFound:
|
||||||
{
|
{
|
||||||
error := errorCodeError(NoSuchKey)
|
error := errorCodeError(NoSuchKey)
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.ObjectNameInvalid:
|
case drivers.ObjectNameInvalid:
|
||||||
{
|
{
|
||||||
error := errorCodeError(NoSuchKey)
|
error := errorCodeError(NoSuchKey)
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BucketNameInvalid:
|
case drivers.BucketNameInvalid:
|
||||||
{
|
{
|
||||||
error := errorCodeError(InvalidBucketName)
|
error := errorCodeError(InvalidBucketName)
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.ImplementationError:
|
case drivers.ImplementationError:
|
||||||
{
|
{
|
||||||
// Embed errors log on serve side
|
// Embed errors log on serve side
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
@ -119,25 +119,25 @@ func (server *minioAPI) headObjectHandler(w http.ResponseWriter, req *http.Reque
|
|||||||
bucket = vars["bucket"]
|
bucket = vars["bucket"]
|
||||||
object = vars["object"]
|
object = vars["object"]
|
||||||
|
|
||||||
metadata, err := server.storage.GetObjectMetadata(bucket, object, "")
|
metadata, err := server.driver.GetObjectMetadata(bucket, object, "")
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
writeObjectHeaders(w, metadata)
|
writeObjectHeaders(w, metadata)
|
||||||
case mstorage.ObjectNotFound:
|
case drivers.ObjectNotFound:
|
||||||
{
|
{
|
||||||
error := errorCodeError(NoSuchKey)
|
error := errorCodeError(NoSuchKey)
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.ObjectNameInvalid:
|
case drivers.ObjectNameInvalid:
|
||||||
{
|
{
|
||||||
error := errorCodeError(NoSuchKey)
|
error := errorCodeError(NoSuchKey)
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.ImplementationError:
|
case drivers.ImplementationError:
|
||||||
{
|
{
|
||||||
// Embed error log on server side
|
// Embed error log on server side
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
@ -167,12 +167,12 @@ func (server *minioAPI) putObjectHandler(w http.ResponseWriter, req *http.Reques
|
|||||||
|
|
||||||
// get Content-MD5 sent by client
|
// get Content-MD5 sent by client
|
||||||
md5 := req.Header.Get("Content-MD5")
|
md5 := req.Header.Get("Content-MD5")
|
||||||
err := server.storage.CreateObject(bucket, object, "", md5, req.Body)
|
err := server.driver.CreateObject(bucket, object, "", md5, req.Body)
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
w.Header().Set("Server", "Minio")
|
w.Header().Set("Server", "Minio")
|
||||||
w.Header().Set("Connection", "close")
|
w.Header().Set("Connection", "close")
|
||||||
case mstorage.ImplementationError:
|
case drivers.ImplementationError:
|
||||||
{
|
{
|
||||||
// Embed error log on server side
|
// Embed error log on server side
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
@ -181,35 +181,35 @@ func (server *minioAPI) putObjectHandler(w http.ResponseWriter, req *http.Reques
|
|||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BucketNotFound:
|
case drivers.BucketNotFound:
|
||||||
{
|
{
|
||||||
error := errorCodeError(NoSuchBucket)
|
error := errorCodeError(NoSuchBucket)
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BucketNameInvalid:
|
case drivers.BucketNameInvalid:
|
||||||
{
|
{
|
||||||
error := errorCodeError(InvalidBucketName)
|
error := errorCodeError(InvalidBucketName)
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.ObjectExists:
|
case drivers.ObjectExists:
|
||||||
{
|
{
|
||||||
error := errorCodeError(NotImplemented)
|
error := errorCodeError(NotImplemented)
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BadDigest:
|
case drivers.BadDigest:
|
||||||
{
|
{
|
||||||
error := errorCodeError(BadDigest)
|
error := errorCodeError(BadDigest)
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.InvalidDigest:
|
case drivers.InvalidDigest:
|
||||||
{
|
{
|
||||||
error := errorCodeError(InvalidDigest)
|
error := errorCodeError(InvalidDigest)
|
||||||
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
errorResponse := getErrorResponse(error, "/"+bucket+"/"+object)
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
"github.com/minio-io/minio/pkg/utils/log"
|
"github.com/minio-io/minio/pkg/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ func (server *minioAPI) putBucketPolicyHandler(w http.ResponseWriter, req *http.
|
|||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
acceptsContentType := getContentType(req)
|
acceptsContentType := getContentType(req)
|
||||||
|
|
||||||
policy, ok := mstorage.Parsepolicy(req.Body)
|
policy, ok := drivers.Parsepolicy(req.Body)
|
||||||
if ok == false {
|
if ok == false {
|
||||||
error := errorCodeError(InvalidPolicyDocument)
|
error := errorCodeError(InvalidPolicyDocument)
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
@ -43,7 +43,7 @@ func (server *minioAPI) putBucketPolicyHandler(w http.ResponseWriter, req *http.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := server.storage.CreateBucketPolicy(bucket, policy)
|
err := server.driver.CreateBucketPolicy(bucket, policy)
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
{
|
{
|
||||||
@ -51,21 +51,21 @@ func (server *minioAPI) putBucketPolicyHandler(w http.ResponseWriter, req *http.
|
|||||||
writeCommonHeaders(w, getContentString(acceptsContentType))
|
writeCommonHeaders(w, getContentString(acceptsContentType))
|
||||||
w.Header().Set("Connection", "keep-alive")
|
w.Header().Set("Connection", "keep-alive")
|
||||||
}
|
}
|
||||||
case mstorage.BucketNameInvalid:
|
case drivers.BucketNameInvalid:
|
||||||
{
|
{
|
||||||
error := errorCodeError(InvalidBucketName)
|
error := errorCodeError(InvalidBucketName)
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BucketNotFound:
|
case drivers.BucketNotFound:
|
||||||
{
|
{
|
||||||
error := errorCodeError(NoSuchBucket)
|
error := errorCodeError(NoSuchBucket)
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BackendCorrupted:
|
case drivers.BackendCorrupted:
|
||||||
{
|
{
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
error := errorCodeError(InternalError)
|
error := errorCodeError(InternalError)
|
||||||
@ -73,7 +73,7 @@ func (server *minioAPI) putBucketPolicyHandler(w http.ResponseWriter, req *http.
|
|||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.ImplementationError:
|
case drivers.ImplementationError:
|
||||||
{
|
{
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
error := errorCodeError(InternalError)
|
error := errorCodeError(InternalError)
|
||||||
@ -93,7 +93,7 @@ func (server *minioAPI) getBucketPolicyHandler(w http.ResponseWriter, req *http.
|
|||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
acceptsContentType := getContentType(req)
|
acceptsContentType := getContentType(req)
|
||||||
|
|
||||||
p, err := server.storage.GetBucketPolicy(bucket)
|
p, err := server.driver.GetBucketPolicy(bucket)
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
{
|
{
|
||||||
@ -108,28 +108,28 @@ func (server *minioAPI) getBucketPolicyHandler(w http.ResponseWriter, req *http.
|
|||||||
w.Header().Set("Connection", "keep-alive")
|
w.Header().Set("Connection", "keep-alive")
|
||||||
w.Write(responsePolicy)
|
w.Write(responsePolicy)
|
||||||
}
|
}
|
||||||
case mstorage.BucketNameInvalid:
|
case drivers.BucketNameInvalid:
|
||||||
{
|
{
|
||||||
error := errorCodeError(InvalidBucketName)
|
error := errorCodeError(InvalidBucketName)
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BucketNotFound:
|
case drivers.BucketNotFound:
|
||||||
{
|
{
|
||||||
error := errorCodeError(NoSuchBucket)
|
error := errorCodeError(NoSuchBucket)
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BucketPolicyNotFound:
|
case drivers.BucketPolicyNotFound:
|
||||||
{
|
{
|
||||||
error := errorCodeError(NoSuchBucketPolicy)
|
error := errorCodeError(NoSuchBucketPolicy)
|
||||||
errorResponse := getErrorResponse(error, bucket)
|
errorResponse := getErrorResponse(error, bucket)
|
||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.BackendCorrupted:
|
case drivers.BackendCorrupted:
|
||||||
{
|
{
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
error := errorCodeError(InternalError)
|
error := errorCodeError(InternalError)
|
||||||
@ -137,7 +137,7 @@ func (server *minioAPI) getBucketPolicyHandler(w http.ResponseWriter, req *http.
|
|||||||
w.WriteHeader(error.HTTPStatusCode)
|
w.WriteHeader(error.HTTPStatusCode)
|
||||||
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
w.Write(writeErrorResponse(w, errorResponse, acceptsContentType))
|
||||||
}
|
}
|
||||||
case mstorage.ImplementationError:
|
case drivers.ImplementationError:
|
||||||
{
|
{
|
||||||
log.Errorln(err)
|
log.Errorln(err)
|
||||||
error := errorCodeError(InternalError)
|
error := errorCodeError(InternalError)
|
||||||
|
@ -19,7 +19,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Reply date format
|
// Reply date format
|
||||||
@ -33,7 +33,7 @@ const (
|
|||||||
//
|
//
|
||||||
// output:
|
// output:
|
||||||
// populated struct that can be serialized to match xml and json api spec output
|
// populated struct that can be serialized to match xml and json api spec output
|
||||||
func generateBucketsListResult(buckets []mstorage.BucketMetadata) BucketListResponse {
|
func generateBucketsListResult(buckets []drivers.BucketMetadata) BucketListResponse {
|
||||||
var listbuckets []*Bucket
|
var listbuckets []*Bucket
|
||||||
var data = BucketListResponse{}
|
var data = BucketListResponse{}
|
||||||
var owner = Owner{}
|
var owner = Owner{}
|
||||||
@ -73,7 +73,7 @@ func (b itemKey) Less(i, j int) bool { return b[i].Key < b[j].Key }
|
|||||||
//
|
//
|
||||||
// output:
|
// output:
|
||||||
// populated struct that can be serialized to match xml and json api spec output
|
// populated struct that can be serialized to match xml and json api spec output
|
||||||
func generateObjectsListResult(bucket string, objects []mstorage.ObjectMetadata, bucketResources mstorage.BucketResourcesMetadata) ObjectListResponse {
|
func generateObjectsListResult(bucket string, objects []drivers.ObjectMetadata, bucketResources drivers.BucketResourcesMetadata) ObjectListResponse {
|
||||||
var contents []*Item
|
var contents []*Item
|
||||||
var prefixes []*Prefix
|
var prefixes []*Prefix
|
||||||
var owner = Owner{}
|
var owner = Owner{}
|
||||||
|
@ -22,13 +22,13 @@ import (
|
|||||||
|
|
||||||
router "github.com/gorilla/mux"
|
router "github.com/gorilla/mux"
|
||||||
"github.com/minio-io/minio/pkg/api/config"
|
"github.com/minio-io/minio/pkg/api/config"
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// private use
|
// private use
|
||||||
type minioAPI struct {
|
type minioAPI struct {
|
||||||
domain string
|
domain string
|
||||||
storage mstorage.Storage
|
driver drivers.Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path based routing
|
// Path based routing
|
||||||
@ -72,10 +72,10 @@ func getMux(api minioAPI, mux *router.Router) *router.Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HTTPHandler - http wrapper handler
|
// HTTPHandler - http wrapper handler
|
||||||
func HTTPHandler(domain string, storage mstorage.Storage) http.Handler {
|
func HTTPHandler(domain string, driver drivers.Driver) http.Handler {
|
||||||
var mux *router.Router
|
var mux *router.Router
|
||||||
var api = minioAPI{}
|
var api = minioAPI{}
|
||||||
api.storage = storage
|
api.driver = driver
|
||||||
api.domain = domain
|
api.domain = domain
|
||||||
|
|
||||||
r := router.NewRouter()
|
r := router.NewRouter()
|
||||||
|
@ -29,8 +29,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio-io/minio/pkg/api"
|
"github.com/minio-io/minio/pkg/api"
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
"github.com/minio-io/minio/pkg/storage/memory"
|
"github.com/minio-io/minio/pkg/drivers/memory"
|
||||||
|
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
@ -42,8 +42,8 @@ type MySuite struct{}
|
|||||||
var _ = Suite(&MySuite{})
|
var _ = Suite(&MySuite{})
|
||||||
|
|
||||||
func (s *MySuite) TestNonExistantObject(c *C) {
|
func (s *MySuite) TestNonExistantObject(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
@ -54,14 +54,14 @@ func (s *MySuite) TestNonExistantObject(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MySuite) TestEmptyObject(c *C) {
|
func (s *MySuite) TestEmptyObject(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
buffer := bytes.NewBufferString("")
|
buffer := bytes.NewBufferString("")
|
||||||
storage.CreateBucket("bucket")
|
driver.CreateBucket("bucket")
|
||||||
storage.CreateObject("bucket", "object", "", "", buffer)
|
driver.CreateObject("bucket", "object", "", "", buffer)
|
||||||
|
|
||||||
response, err := http.Get(testServer.URL + "/bucket/object")
|
response, err := http.Get(testServer.URL + "/bucket/object")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@ -71,7 +71,7 @@ func (s *MySuite) TestEmptyObject(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(true, Equals, bytes.Equal(responseBody, buffer.Bytes()))
|
c.Assert(true, Equals, bytes.Equal(responseBody, buffer.Bytes()))
|
||||||
|
|
||||||
metadata, err := storage.GetObjectMetadata("bucket", "object", "")
|
metadata, err := driver.GetObjectMetadata("bucket", "object", "")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
verifyHeaders(c, response.Header, metadata.Created, 0, "application/octet-stream", metadata.Md5)
|
verifyHeaders(c, response.Header, metadata.Created, 0, "application/octet-stream", metadata.Md5)
|
||||||
|
|
||||||
@ -79,14 +79,14 @@ func (s *MySuite) TestEmptyObject(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MySuite) TestObject(c *C) {
|
func (s *MySuite) TestObject(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
buffer := bytes.NewBufferString("hello world")
|
buffer := bytes.NewBufferString("hello world")
|
||||||
storage.CreateBucket("bucket")
|
driver.CreateBucket("bucket")
|
||||||
storage.CreateObject("bucket", "object", "", "", buffer)
|
driver.CreateObject("bucket", "object", "", "", buffer)
|
||||||
|
|
||||||
response, err := http.Get(testServer.URL + "/bucket/object")
|
response, err := http.Get(testServer.URL + "/bucket/object")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@ -96,14 +96,14 @@ func (s *MySuite) TestObject(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello world")))
|
c.Assert(true, Equals, bytes.Equal(responseBody, []byte("hello world")))
|
||||||
|
|
||||||
metadata, err := storage.GetObjectMetadata("bucket", "object", "")
|
metadata, err := driver.GetObjectMetadata("bucket", "object", "")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
verifyHeaders(c, response.Header, metadata.Created, len("hello world"), "application/octet-stream", metadata.Md5)
|
verifyHeaders(c, response.Header, metadata.Created, len("hello world"), "application/octet-stream", metadata.Md5)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MySuite) TestMultipleObjects(c *C) {
|
func (s *MySuite) TestMultipleObjects(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
@ -111,10 +111,10 @@ func (s *MySuite) TestMultipleObjects(c *C) {
|
|||||||
buffer2 := bytes.NewBufferString("hello two")
|
buffer2 := bytes.NewBufferString("hello two")
|
||||||
buffer3 := bytes.NewBufferString("hello three")
|
buffer3 := bytes.NewBufferString("hello three")
|
||||||
|
|
||||||
storage.CreateBucket("bucket")
|
driver.CreateBucket("bucket")
|
||||||
storage.CreateObject("bucket", "object1", "", "", buffer1)
|
driver.CreateObject("bucket", "object1", "", "", buffer1)
|
||||||
storage.CreateObject("bucket", "object2", "", "", buffer2)
|
driver.CreateObject("bucket", "object2", "", "", buffer2)
|
||||||
storage.CreateObject("bucket", "object3", "", "", buffer3)
|
driver.CreateObject("bucket", "object3", "", "", buffer3)
|
||||||
|
|
||||||
// test non-existant object
|
// test non-existant object
|
||||||
response, err := http.Get(testServer.URL + "/bucket/object")
|
response, err := http.Get(testServer.URL + "/bucket/object")
|
||||||
@ -129,7 +129,7 @@ func (s *MySuite) TestMultipleObjects(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
// get metadata
|
// get metadata
|
||||||
metadata, err := storage.GetObjectMetadata("bucket", "object1", "")
|
metadata, err := driver.GetObjectMetadata("bucket", "object1", "")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ func (s *MySuite) TestMultipleObjects(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
// get metadata
|
// get metadata
|
||||||
metadata, err = storage.GetObjectMetadata("bucket", "object2", "")
|
metadata, err = driver.GetObjectMetadata("bucket", "object2", "")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ func (s *MySuite) TestMultipleObjects(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
// get metadata
|
// get metadata
|
||||||
metadata, err = storage.GetObjectMetadata("bucket", "object3", "")
|
metadata, err = driver.GetObjectMetadata("bucket", "object3", "")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
@ -182,8 +182,8 @@ func (s *MySuite) TestMultipleObjects(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MySuite) TestNotImplemented(c *C) {
|
func (s *MySuite) TestNotImplemented(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
@ -193,8 +193,8 @@ func (s *MySuite) TestNotImplemented(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MySuite) TestHeader(c *C) {
|
func (s *MySuite) TestHeader(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
@ -203,25 +203,25 @@ func (s *MySuite) TestHeader(c *C) {
|
|||||||
c.Assert(response.StatusCode, Equals, http.StatusNotFound)
|
c.Assert(response.StatusCode, Equals, http.StatusNotFound)
|
||||||
|
|
||||||
buffer := bytes.NewBufferString("hello world")
|
buffer := bytes.NewBufferString("hello world")
|
||||||
storage.CreateBucket("bucket")
|
driver.CreateBucket("bucket")
|
||||||
storage.CreateObject("bucket", "object", "", "", buffer)
|
driver.CreateObject("bucket", "object", "", "", buffer)
|
||||||
|
|
||||||
response, err = http.Get(testServer.URL + "/bucket/object")
|
response, err = http.Get(testServer.URL + "/bucket/object")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
metadata, err := storage.GetObjectMetadata("bucket", "object", "")
|
metadata, err := driver.GetObjectMetadata("bucket", "object", "")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
verifyHeaders(c, response.Header, metadata.Created, len("hello world"), "application/octet-stream", metadata.Md5)
|
verifyHeaders(c, response.Header, metadata.Created, len("hello world"), "application/octet-stream", metadata.Md5)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MySuite) TestPutBucket(c *C) {
|
func (s *MySuite) TestPutBucket(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
buckets, err := storage.ListBuckets()
|
buckets, err := driver.ListBuckets()
|
||||||
c.Assert(len(buckets), Equals, 0)
|
c.Assert(len(buckets), Equals, 0)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
@ -234,23 +234,23 @@ func (s *MySuite) TestPutBucket(c *C) {
|
|||||||
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
c.Assert(response.StatusCode, Equals, http.StatusOK)
|
||||||
|
|
||||||
// check bucket exists
|
// check bucket exists
|
||||||
buckets, err = storage.ListBuckets()
|
buckets, err = driver.ListBuckets()
|
||||||
c.Assert(len(buckets), Equals, 1)
|
c.Assert(len(buckets), Equals, 1)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(buckets[0].Name, Equals, "bucket")
|
c.Assert(buckets[0].Name, Equals, "bucket")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MySuite) TestPutObject(c *C) {
|
func (s *MySuite) TestPutObject(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
resources := mstorage.BucketResourcesMetadata{}
|
resources := drivers.BucketResourcesMetadata{}
|
||||||
|
|
||||||
resources.Maxkeys = 1000
|
resources.Maxkeys = 1000
|
||||||
resources.Prefix = ""
|
resources.Prefix = ""
|
||||||
objects, resources, err := storage.ListObjects("bucket", resources)
|
objects, resources, err := driver.ListObjects("bucket", resources)
|
||||||
c.Assert(len(objects), Equals, 0)
|
c.Assert(len(objects), Equals, 0)
|
||||||
c.Assert(resources.IsTruncated, Equals, false)
|
c.Assert(resources.IsTruncated, Equals, false)
|
||||||
c.Assert(err, Not(IsNil))
|
c.Assert(err, Not(IsNil))
|
||||||
@ -278,18 +278,18 @@ func (s *MySuite) TestPutObject(c *C) {
|
|||||||
resources.Maxkeys = 1000
|
resources.Maxkeys = 1000
|
||||||
resources.Prefix = ""
|
resources.Prefix = ""
|
||||||
|
|
||||||
objects, resources, err = storage.ListObjects("bucket", resources)
|
objects, resources, err = driver.ListObjects("bucket", resources)
|
||||||
c.Assert(len(objects), Equals, 1)
|
c.Assert(len(objects), Equals, 1)
|
||||||
c.Assert(resources.IsTruncated, Equals, false)
|
c.Assert(resources.IsTruncated, Equals, false)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
var writer bytes.Buffer
|
var writer bytes.Buffer
|
||||||
|
|
||||||
storage.GetObject(&writer, "bucket", "two")
|
driver.GetObject(&writer, "bucket", "two")
|
||||||
|
|
||||||
c.Assert(bytes.Equal(writer.Bytes(), []byte("hello world")), Equals, true)
|
c.Assert(bytes.Equal(writer.Bytes(), []byte("hello world")), Equals, true)
|
||||||
|
|
||||||
metadata, err := storage.GetObjectMetadata("bucket", "two", "")
|
metadata, err := driver.GetObjectMetadata("bucket", "two", "")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
lastModified := metadata.Created
|
lastModified := metadata.Created
|
||||||
|
|
||||||
@ -298,8 +298,8 @@ func (s *MySuite) TestPutObject(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MySuite) TestListBuckets(c *C) {
|
func (s *MySuite) TestListBuckets(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
@ -312,7 +312,7 @@ func (s *MySuite) TestListBuckets(c *C) {
|
|||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
c.Assert(len(listResponse.Buckets.Bucket), Equals, 0)
|
c.Assert(len(listResponse.Buckets.Bucket), Equals, 0)
|
||||||
|
|
||||||
storage.CreateBucket("foo")
|
driver.CreateBucket("foo")
|
||||||
|
|
||||||
response, err = http.Get(testServer.URL + "/")
|
response, err = http.Get(testServer.URL + "/")
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
@ -324,7 +324,7 @@ func (s *MySuite) TestListBuckets(c *C) {
|
|||||||
c.Assert(len(listResponse.Buckets.Bucket), Equals, 1)
|
c.Assert(len(listResponse.Buckets.Bucket), Equals, 1)
|
||||||
c.Assert(listResponse.Buckets.Bucket[0].Name, Equals, "foo")
|
c.Assert(listResponse.Buckets.Bucket[0].Name, Equals, "foo")
|
||||||
|
|
||||||
storage.CreateBucket("bar")
|
driver.CreateBucket("bar")
|
||||||
|
|
||||||
response, err = http.Get(testServer.URL + "/")
|
response, err = http.Get(testServer.URL + "/")
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
@ -377,12 +377,12 @@ func verifyHeaders(c *C, header http.Header, date time.Time, size int, contentTy
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MySuite) TestXMLNameNotInBucketListJson(c *C) {
|
func (s *MySuite) TestXMLNameNotInBucketListJson(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
err := storage.CreateBucket("foo")
|
err := driver.CreateBucket("foo")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
request, err := http.NewRequest("GET", testServer.URL+"/", bytes.NewBufferString(""))
|
request, err := http.NewRequest("GET", testServer.URL+"/", bytes.NewBufferString(""))
|
||||||
@ -402,12 +402,12 @@ func (s *MySuite) TestXMLNameNotInBucketListJson(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MySuite) TestXMLNameNotInObjectListJson(c *C) {
|
func (s *MySuite) TestXMLNameNotInObjectListJson(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
err := storage.CreateBucket("foo")
|
err := driver.CreateBucket("foo")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
request, err := http.NewRequest("GET", testServer.URL+"/foo", bytes.NewBufferString(""))
|
request, err := http.NewRequest("GET", testServer.URL+"/foo", bytes.NewBufferString(""))
|
||||||
@ -427,12 +427,12 @@ func (s *MySuite) TestXMLNameNotInObjectListJson(c *C) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *MySuite) TestContentTypePersists(c *C) {
|
func (s *MySuite) TestContentTypePersists(c *C) {
|
||||||
_, _, storage := memory.Start()
|
_, _, driver := memory.Start()
|
||||||
httpHandler := api.HTTPHandler("", storage)
|
httpHandler := api.HTTPHandler("", driver)
|
||||||
testServer := httptest.NewServer(httpHandler)
|
testServer := httptest.NewServer(httpHandler)
|
||||||
defer testServer.Close()
|
defer testServer.Close()
|
||||||
|
|
||||||
err := storage.CreateBucket("bucket")
|
err := driver.CreateBucket("bucket")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// No encoder interface exists, so we create one.
|
// No encoder interface exists, so we create one.
|
||||||
@ -58,7 +58,7 @@ func writeErrorResponse(w http.ResponseWriter, response interface{}, acceptsType
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write object header
|
// Write object header
|
||||||
func writeObjectHeaders(w http.ResponseWriter, metadata mstorage.ObjectMetadata) {
|
func writeObjectHeaders(w http.ResponseWriter, metadata drivers.ObjectMetadata) {
|
||||||
lastModified := metadata.Created.Format(time.RFC1123)
|
lastModified := metadata.Created.Format(time.RFC1123)
|
||||||
// common headers
|
// common headers
|
||||||
writeCommonHeaders(w, metadata.ContentType)
|
writeCommonHeaders(w, metadata.ContentType)
|
||||||
@ -69,7 +69,7 @@ func writeObjectHeaders(w http.ResponseWriter, metadata mstorage.ObjectMetadata)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write range object header
|
// Write range object header
|
||||||
func writeRangeObjectHeaders(w http.ResponseWriter, metadata mstorage.ObjectMetadata, ra string) {
|
func writeRangeObjectHeaders(w http.ResponseWriter, metadata drivers.ObjectMetadata, ra string) {
|
||||||
lastModified := metadata.Created.Format(time.RFC1123)
|
lastModified := metadata.Created.Format(time.RFC1123)
|
||||||
// common headers
|
// common headers
|
||||||
writeCommonHeaders(w, metadata.ContentType)
|
writeCommonHeaders(w, metadata.ContentType)
|
||||||
|
@ -20,11 +20,11 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// parse bucket url queries
|
// parse bucket url queries
|
||||||
func getBucketResources(values url.Values) (v mstorage.BucketResourcesMetadata) {
|
func getBucketResources(values url.Values) (v drivers.BucketResourcesMetadata) {
|
||||||
for key, value := range values {
|
for key, value := range values {
|
||||||
switch true {
|
switch true {
|
||||||
case key == "prefix":
|
case key == "prefix":
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package storage
|
package drivers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -26,7 +26,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// APITestSuite - collection of API tests
|
// APITestSuite - collection of API tests
|
||||||
func APITestSuite(c *check.C, create func() Storage) {
|
func APITestSuite(c *check.C, create func() Driver) {
|
||||||
testCreateBucket(c, create)
|
testCreateBucket(c, create)
|
||||||
testMultipleObjectCreation(c, create)
|
testMultipleObjectCreation(c, create)
|
||||||
testPaging(c, create)
|
testPaging(c, create)
|
||||||
@ -43,16 +43,16 @@ func APITestSuite(c *check.C, create func() Storage) {
|
|||||||
//testContentMd5Set(c, create) TODO
|
//testContentMd5Set(c, create) TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCreateBucket(c *check.C, create func() Storage) {
|
func testCreateBucket(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
err := storage.CreateBucket("bucket")
|
err := drivers.CreateBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMultipleObjectCreation(c *check.C, create func() Storage) {
|
func testMultipleObjectCreation(c *check.C, create func() Driver) {
|
||||||
objects := make(map[string][]byte)
|
objects := make(map[string][]byte)
|
||||||
storage := create()
|
drivers := create()
|
||||||
err := storage.CreateBucket("bucket")
|
err := drivers.CreateBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
randomPerm := rand.Perm(10)
|
randomPerm := rand.Perm(10)
|
||||||
@ -62,7 +62,7 @@ func testMultipleObjectCreation(c *check.C, create func() Storage) {
|
|||||||
}
|
}
|
||||||
key := "obj" + strconv.Itoa(i)
|
key := "obj" + strconv.Itoa(i)
|
||||||
objects[key] = []byte(randomString)
|
objects[key] = []byte(randomString)
|
||||||
err := storage.CreateObject("bucket", key, "", "", bytes.NewBufferString(randomString))
|
err := drivers.CreateObject("bucket", key, "", "", bytes.NewBufferString(randomString))
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,11 +70,11 @@ func testMultipleObjectCreation(c *check.C, create func() Storage) {
|
|||||||
etags := make(map[string]string)
|
etags := make(map[string]string)
|
||||||
for key, value := range objects {
|
for key, value := range objects {
|
||||||
var byteBuffer bytes.Buffer
|
var byteBuffer bytes.Buffer
|
||||||
_, err := storage.GetObject(&byteBuffer, "bucket", key)
|
_, err := drivers.GetObject(&byteBuffer, "bucket", key)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(byteBuffer.Bytes(), check.DeepEquals, value)
|
c.Assert(byteBuffer.Bytes(), check.DeepEquals, value)
|
||||||
|
|
||||||
metadata, err := storage.GetObjectMetadata("bucket", key, "")
|
metadata, err := drivers.GetObjectMetadata("bucket", key, "")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(metadata.Size, check.Equals, int64(len(value)))
|
c.Assert(metadata.Size, check.Equals, int64(len(value)))
|
||||||
|
|
||||||
@ -84,20 +84,20 @@ func testMultipleObjectCreation(c *check.C, create func() Storage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPaging(c *check.C, create func() Storage) {
|
func testPaging(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
storage.CreateBucket("bucket")
|
drivers.CreateBucket("bucket")
|
||||||
resources := BucketResourcesMetadata{}
|
resources := BucketResourcesMetadata{}
|
||||||
objects, resources, err := storage.ListObjects("bucket", resources)
|
objects, resources, err := drivers.ListObjects("bucket", resources)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(len(objects), check.Equals, 0)
|
c.Assert(len(objects), check.Equals, 0)
|
||||||
c.Assert(resources.IsTruncated, check.Equals, false)
|
c.Assert(resources.IsTruncated, check.Equals, false)
|
||||||
// check before paging occurs
|
// check before paging occurs
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
key := "obj" + strconv.Itoa(i)
|
key := "obj" + strconv.Itoa(i)
|
||||||
storage.CreateObject("bucket", key, "", "", bytes.NewBufferString(key))
|
drivers.CreateObject("bucket", key, "", "", bytes.NewBufferString(key))
|
||||||
resources.Maxkeys = 5
|
resources.Maxkeys = 5
|
||||||
objects, resources, err = storage.ListObjects("bucket", resources)
|
objects, resources, err = drivers.ListObjects("bucket", resources)
|
||||||
c.Assert(len(objects), check.Equals, i+1)
|
c.Assert(len(objects), check.Equals, i+1)
|
||||||
c.Assert(resources.IsTruncated, check.Equals, false)
|
c.Assert(resources.IsTruncated, check.Equals, false)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
@ -105,20 +105,20 @@ func testPaging(c *check.C, create func() Storage) {
|
|||||||
// check after paging occurs pages work
|
// check after paging occurs pages work
|
||||||
for i := 6; i <= 10; i++ {
|
for i := 6; i <= 10; i++ {
|
||||||
key := "obj" + strconv.Itoa(i)
|
key := "obj" + strconv.Itoa(i)
|
||||||
storage.CreateObject("bucket", key, "", "", bytes.NewBufferString(key))
|
drivers.CreateObject("bucket", key, "", "", bytes.NewBufferString(key))
|
||||||
resources.Maxkeys = 5
|
resources.Maxkeys = 5
|
||||||
objects, resources, err = storage.ListObjects("bucket", resources)
|
objects, resources, err = drivers.ListObjects("bucket", resources)
|
||||||
c.Assert(len(objects), check.Equals, 5)
|
c.Assert(len(objects), check.Equals, 5)
|
||||||
c.Assert(resources.IsTruncated, check.Equals, true)
|
c.Assert(resources.IsTruncated, check.Equals, true)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
// check paging with prefix at end returns less objects
|
// check paging with prefix at end returns less objects
|
||||||
{
|
{
|
||||||
storage.CreateObject("bucket", "newPrefix", "", "", bytes.NewBufferString("prefix1"))
|
drivers.CreateObject("bucket", "newPrefix", "", "", bytes.NewBufferString("prefix1"))
|
||||||
storage.CreateObject("bucket", "newPrefix2", "", "", bytes.NewBufferString("prefix2"))
|
drivers.CreateObject("bucket", "newPrefix2", "", "", bytes.NewBufferString("prefix2"))
|
||||||
resources.Prefix = "new"
|
resources.Prefix = "new"
|
||||||
resources.Maxkeys = 5
|
resources.Maxkeys = 5
|
||||||
objects, resources, err = storage.ListObjects("bucket", resources)
|
objects, resources, err = drivers.ListObjects("bucket", resources)
|
||||||
c.Assert(len(objects), check.Equals, 2)
|
c.Assert(len(objects), check.Equals, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ func testPaging(c *check.C, create func() Storage) {
|
|||||||
{
|
{
|
||||||
resources.Prefix = ""
|
resources.Prefix = ""
|
||||||
resources.Maxkeys = 1000
|
resources.Maxkeys = 1000
|
||||||
objects, resources, err = storage.ListObjects("bucket", resources)
|
objects, resources, err = drivers.ListObjects("bucket", resources)
|
||||||
c.Assert(objects[0].Key, check.Equals, "newPrefix")
|
c.Assert(objects[0].Key, check.Equals, "newPrefix")
|
||||||
c.Assert(objects[1].Key, check.Equals, "newPrefix2")
|
c.Assert(objects[1].Key, check.Equals, "newPrefix2")
|
||||||
c.Assert(objects[2].Key, check.Equals, "obj0")
|
c.Assert(objects[2].Key, check.Equals, "obj0")
|
||||||
@ -136,14 +136,14 @@ func testPaging(c *check.C, create func() Storage) {
|
|||||||
|
|
||||||
// check delimited results with delimiter and prefix
|
// check delimited results with delimiter and prefix
|
||||||
{
|
{
|
||||||
storage.CreateObject("bucket", "this/is/delimited", "", "", bytes.NewBufferString("prefix1"))
|
drivers.CreateObject("bucket", "this/is/delimited", "", "", bytes.NewBufferString("prefix1"))
|
||||||
storage.CreateObject("bucket", "this/is/also/a/delimited/file", "", "", bytes.NewBufferString("prefix2"))
|
drivers.CreateObject("bucket", "this/is/also/a/delimited/file", "", "", bytes.NewBufferString("prefix2"))
|
||||||
var prefixes []string
|
var prefixes []string
|
||||||
resources.CommonPrefixes = prefixes // allocate new everytime
|
resources.CommonPrefixes = prefixes // allocate new everytime
|
||||||
resources.Delimiter = "/"
|
resources.Delimiter = "/"
|
||||||
resources.Prefix = "this/is/"
|
resources.Prefix = "this/is/"
|
||||||
resources.Maxkeys = 10
|
resources.Maxkeys = 10
|
||||||
objects, resources, err = storage.ListObjects("bucket", resources)
|
objects, resources, err = drivers.ListObjects("bucket", resources)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(len(objects), check.Equals, 1)
|
c.Assert(len(objects), check.Equals, 1)
|
||||||
c.Assert(resources.CommonPrefixes[0], check.Equals, "also/")
|
c.Assert(resources.CommonPrefixes[0], check.Equals, "also/")
|
||||||
@ -157,7 +157,7 @@ func testPaging(c *check.C, create func() Storage) {
|
|||||||
resources.Delimiter = "/"
|
resources.Delimiter = "/"
|
||||||
resources.Prefix = ""
|
resources.Prefix = ""
|
||||||
resources.Maxkeys = 1000
|
resources.Maxkeys = 1000
|
||||||
objects, resources, err = storage.ListObjects("bucket", resources)
|
objects, resources, err = drivers.ListObjects("bucket", resources)
|
||||||
c.Assert(objects[0].Key, check.Equals, "newPrefix")
|
c.Assert(objects[0].Key, check.Equals, "newPrefix")
|
||||||
c.Assert(objects[1].Key, check.Equals, "newPrefix2")
|
c.Assert(objects[1].Key, check.Equals, "newPrefix2")
|
||||||
c.Assert(objects[2].Key, check.Equals, "obj0")
|
c.Assert(objects[2].Key, check.Equals, "obj0")
|
||||||
@ -171,7 +171,7 @@ func testPaging(c *check.C, create func() Storage) {
|
|||||||
resources.Prefix = "obj"
|
resources.Prefix = "obj"
|
||||||
resources.Delimiter = ""
|
resources.Delimiter = ""
|
||||||
resources.Maxkeys = 1000
|
resources.Maxkeys = 1000
|
||||||
objects, resources, err = storage.ListObjects("bucket", resources)
|
objects, resources, err = drivers.ListObjects("bucket", resources)
|
||||||
c.Assert(objects[0].Key, check.Equals, "obj0")
|
c.Assert(objects[0].Key, check.Equals, "obj0")
|
||||||
c.Assert(objects[1].Key, check.Equals, "obj1")
|
c.Assert(objects[1].Key, check.Equals, "obj1")
|
||||||
c.Assert(objects[2].Key, check.Equals, "obj10")
|
c.Assert(objects[2].Key, check.Equals, "obj10")
|
||||||
@ -182,95 +182,95 @@ func testPaging(c *check.C, create func() Storage) {
|
|||||||
{
|
{
|
||||||
resources.Prefix = "new"
|
resources.Prefix = "new"
|
||||||
resources.Maxkeys = 5
|
resources.Maxkeys = 5
|
||||||
objects, resources, err = storage.ListObjects("bucket", resources)
|
objects, resources, err = drivers.ListObjects("bucket", resources)
|
||||||
c.Assert(objects[0].Key, check.Equals, "newPrefix")
|
c.Assert(objects[0].Key, check.Equals, "newPrefix")
|
||||||
c.Assert(objects[1].Key, check.Equals, "newPrefix2")
|
c.Assert(objects[1].Key, check.Equals, "newPrefix2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testObjectOverwriteFails(c *check.C, create func() Storage) {
|
func testObjectOverwriteFails(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
storage.CreateBucket("bucket")
|
drivers.CreateBucket("bucket")
|
||||||
err := storage.CreateObject("bucket", "object", "", "", bytes.NewBufferString("one"))
|
err := drivers.CreateObject("bucket", "object", "", "", bytes.NewBufferString("one"))
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
err = storage.CreateObject("bucket", "object", "", "", bytes.NewBufferString("three"))
|
err = drivers.CreateObject("bucket", "object", "", "", bytes.NewBufferString("three"))
|
||||||
c.Assert(err, check.Not(check.IsNil))
|
c.Assert(err, check.Not(check.IsNil))
|
||||||
var bytesBuffer bytes.Buffer
|
var bytesBuffer bytes.Buffer
|
||||||
length, err := storage.GetObject(&bytesBuffer, "bucket", "object")
|
length, err := drivers.GetObject(&bytesBuffer, "bucket", "object")
|
||||||
c.Assert(length, check.Equals, int64(len("one")))
|
c.Assert(length, check.Equals, int64(len("one")))
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(string(bytesBuffer.Bytes()), check.Equals, "one")
|
c.Assert(string(bytesBuffer.Bytes()), check.Equals, "one")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNonExistantBucketOperations(c *check.C, create func() Storage) {
|
func testNonExistantBucketOperations(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
err := storage.CreateObject("bucket", "object", "", "", bytes.NewBufferString("one"))
|
err := drivers.CreateObject("bucket", "object", "", "", bytes.NewBufferString("one"))
|
||||||
c.Assert(err, check.Not(check.IsNil))
|
c.Assert(err, check.Not(check.IsNil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBucketRecreateFails(c *check.C, create func() Storage) {
|
func testBucketRecreateFails(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
err := storage.CreateBucket("string")
|
err := drivers.CreateBucket("string")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
err = storage.CreateBucket("string")
|
err = drivers.CreateBucket("string")
|
||||||
c.Assert(err, check.Not(check.IsNil))
|
c.Assert(err, check.Not(check.IsNil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPutObjectInSubdir(c *check.C, create func() Storage) {
|
func testPutObjectInSubdir(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
err := storage.CreateBucket("bucket")
|
err := drivers.CreateBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
err = storage.CreateObject("bucket", "dir1/dir2/object", "", "", bytes.NewBufferString("hello world"))
|
err = drivers.CreateObject("bucket", "dir1/dir2/object", "", "", bytes.NewBufferString("hello world"))
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
var bytesBuffer bytes.Buffer
|
var bytesBuffer bytes.Buffer
|
||||||
length, err := storage.GetObject(&bytesBuffer, "bucket", "dir1/dir2/object")
|
length, err := drivers.GetObject(&bytesBuffer, "bucket", "dir1/dir2/object")
|
||||||
c.Assert(len(bytesBuffer.Bytes()), check.Equals, len("hello world"))
|
c.Assert(len(bytesBuffer.Bytes()), check.Equals, len("hello world"))
|
||||||
c.Assert(int64(len(bytesBuffer.Bytes())), check.Equals, length)
|
c.Assert(int64(len(bytesBuffer.Bytes())), check.Equals, length)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testListBuckets(c *check.C, create func() Storage) {
|
func testListBuckets(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
|
|
||||||
// test empty list
|
// test empty list
|
||||||
buckets, err := storage.ListBuckets()
|
buckets, err := drivers.ListBuckets()
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(len(buckets), check.Equals, 0)
|
c.Assert(len(buckets), check.Equals, 0)
|
||||||
|
|
||||||
// add one and test exists
|
// add one and test exists
|
||||||
err = storage.CreateBucket("bucket1")
|
err = drivers.CreateBucket("bucket1")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
buckets, err = storage.ListBuckets()
|
buckets, err = drivers.ListBuckets()
|
||||||
c.Assert(len(buckets), check.Equals, 1)
|
c.Assert(len(buckets), check.Equals, 1)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// add two and test exists
|
// add two and test exists
|
||||||
err = storage.CreateBucket("bucket2")
|
err = drivers.CreateBucket("bucket2")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
buckets, err = storage.ListBuckets()
|
buckets, err = drivers.ListBuckets()
|
||||||
c.Assert(len(buckets), check.Equals, 2)
|
c.Assert(len(buckets), check.Equals, 2)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// add three and test exists + prefix
|
// add three and test exists + prefix
|
||||||
err = storage.CreateBucket("bucket22")
|
err = drivers.CreateBucket("bucket22")
|
||||||
|
|
||||||
buckets, err = storage.ListBuckets()
|
buckets, err = drivers.ListBuckets()
|
||||||
c.Assert(len(buckets), check.Equals, 3)
|
c.Assert(len(buckets), check.Equals, 3)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testListBucketsOrder(c *check.C, create func() Storage) {
|
func testListBucketsOrder(c *check.C, create func() Driver) {
|
||||||
// if implementation contains a map, order of map keys will vary.
|
// if implementation contains a map, order of map keys will vary.
|
||||||
// this ensures they return in the same order each time
|
// this ensures they return in the same order each time
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
storage := create()
|
drivers := create()
|
||||||
// add one and test exists
|
// add one and test exists
|
||||||
storage.CreateBucket("bucket1")
|
drivers.CreateBucket("bucket1")
|
||||||
storage.CreateBucket("bucket2")
|
drivers.CreateBucket("bucket2")
|
||||||
|
|
||||||
buckets, err := storage.ListBuckets()
|
buckets, err := drivers.ListBuckets()
|
||||||
c.Assert(len(buckets), check.Equals, 2)
|
c.Assert(len(buckets), check.Equals, 2)
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(buckets[0].Name, check.Equals, "bucket1")
|
c.Assert(buckets[0].Name, check.Equals, "bucket1")
|
||||||
@ -278,22 +278,22 @@ func testListBucketsOrder(c *check.C, create func() Storage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testListObjectsTestsForNonExistantBucket(c *check.C, create func() Storage) {
|
func testListObjectsTestsForNonExistantBucket(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
resources := BucketResourcesMetadata{Prefix: "", Maxkeys: 1000}
|
resources := BucketResourcesMetadata{Prefix: "", Maxkeys: 1000}
|
||||||
objects, resources, err := storage.ListObjects("bucket", resources)
|
objects, resources, err := drivers.ListObjects("bucket", resources)
|
||||||
c.Assert(err, check.Not(check.IsNil))
|
c.Assert(err, check.Not(check.IsNil))
|
||||||
c.Assert(resources.IsTruncated, check.Equals, false)
|
c.Assert(resources.IsTruncated, check.Equals, false)
|
||||||
c.Assert(len(objects), check.Equals, 0)
|
c.Assert(len(objects), check.Equals, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNonExistantObjectInBucket(c *check.C, create func() Storage) {
|
func testNonExistantObjectInBucket(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
err := storage.CreateBucket("bucket")
|
err := drivers.CreateBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
var byteBuffer bytes.Buffer
|
var byteBuffer bytes.Buffer
|
||||||
length, err := storage.GetObject(&byteBuffer, "bucket", "dir1")
|
length, err := drivers.GetObject(&byteBuffer, "bucket", "dir1")
|
||||||
c.Assert(length, check.Equals, int64(0))
|
c.Assert(length, check.Equals, int64(0))
|
||||||
c.Assert(err, check.Not(check.IsNil))
|
c.Assert(err, check.Not(check.IsNil))
|
||||||
c.Assert(len(byteBuffer.Bytes()), check.Equals, 0)
|
c.Assert(len(byteBuffer.Bytes()), check.Equals, 0)
|
||||||
@ -309,16 +309,16 @@ func testNonExistantObjectInBucket(c *check.C, create func() Storage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Storage) {
|
func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
err := storage.CreateBucket("bucket")
|
err := drivers.CreateBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
err = storage.CreateObject("bucket", "dir1/dir2/object", "", "", bytes.NewBufferString("hello world"))
|
err = drivers.CreateObject("bucket", "dir1/dir2/object", "", "", bytes.NewBufferString("hello world"))
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
var byteBuffer bytes.Buffer
|
var byteBuffer bytes.Buffer
|
||||||
length, err := storage.GetObject(&byteBuffer, "bucket", "dir1")
|
length, err := drivers.GetObject(&byteBuffer, "bucket", "dir1")
|
||||||
c.Assert(length, check.Equals, int64(0))
|
c.Assert(length, check.Equals, int64(0))
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case ObjectNotFound:
|
case ObjectNotFound:
|
||||||
@ -335,7 +335,7 @@ func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Storage) {
|
|||||||
c.Assert(len(byteBuffer.Bytes()), check.Equals, 0)
|
c.Assert(len(byteBuffer.Bytes()), check.Equals, 0)
|
||||||
|
|
||||||
var byteBuffer2 bytes.Buffer
|
var byteBuffer2 bytes.Buffer
|
||||||
length, err = storage.GetObject(&byteBuffer, "bucket", "dir1/")
|
length, err = drivers.GetObject(&byteBuffer, "bucket", "dir1/")
|
||||||
c.Assert(length, check.Equals, int64(0))
|
c.Assert(length, check.Equals, int64(0))
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case ObjectNotFound:
|
case ObjectNotFound:
|
||||||
@ -352,40 +352,40 @@ func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Storage) {
|
|||||||
c.Assert(len(byteBuffer2.Bytes()), check.Equals, 0)
|
c.Assert(len(byteBuffer2.Bytes()), check.Equals, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDefaultContentType(c *check.C, create func() Storage) {
|
func testDefaultContentType(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
err := storage.CreateBucket("bucket")
|
err := drivers.CreateBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// test empty
|
// test empty
|
||||||
err = storage.CreateObject("bucket", "one", "", "", bytes.NewBufferString("one"))
|
err = drivers.CreateObject("bucket", "one", "", "", bytes.NewBufferString("one"))
|
||||||
metadata, err := storage.GetObjectMetadata("bucket", "one", "")
|
metadata, err := drivers.GetObjectMetadata("bucket", "one", "")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(metadata.ContentType, check.Equals, "application/octet-stream")
|
c.Assert(metadata.ContentType, check.Equals, "application/octet-stream")
|
||||||
|
|
||||||
// test custom
|
// test custom
|
||||||
storage.CreateObject("bucket", "two", "application/text", "", bytes.NewBufferString("two"))
|
drivers.CreateObject("bucket", "two", "application/text", "", bytes.NewBufferString("two"))
|
||||||
metadata, err = storage.GetObjectMetadata("bucket", "two", "")
|
metadata, err = drivers.GetObjectMetadata("bucket", "two", "")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(metadata.ContentType, check.Equals, "application/text")
|
c.Assert(metadata.ContentType, check.Equals, "application/text")
|
||||||
|
|
||||||
// test trim space
|
// test trim space
|
||||||
storage.CreateObject("bucket", "three", "\tapplication/json ", "", bytes.NewBufferString("three"))
|
drivers.CreateObject("bucket", "three", "\tapplication/json ", "", bytes.NewBufferString("three"))
|
||||||
metadata, err = storage.GetObjectMetadata("bucket", "three", "")
|
metadata, err = drivers.GetObjectMetadata("bucket", "three", "")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(metadata.ContentType, check.Equals, "application/json")
|
c.Assert(metadata.ContentType, check.Equals, "application/json")
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
func testContentMd5Set(c *check.C, create func() Storage) {
|
func testContentMd5Set(c *check.C, create func() Driver) {
|
||||||
storage := create()
|
drivers := create()
|
||||||
err := storage.CreateBucket("bucket")
|
err := drivers.CreateBucket("bucket")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// test md5 invalid
|
// test md5 invalid
|
||||||
err = storage.CreateObject("bucket", "one", "", "NWJiZjVhNTIzMjhlNzQzOWFlNmU3MTlkZmU3MTIyMDA", bytes.NewBufferString("one"))
|
err = drivers.CreateObject("bucket", "one", "", "NWJiZjVhNTIzMjhlNzQzOWFlNmU3MTlkZmU3MTIyMDA", bytes.NewBufferString("one"))
|
||||||
c.Assert(err, check.Not(check.IsNil))
|
c.Assert(err, check.Not(check.IsNil))
|
||||||
err = storage.CreateObject("bucket", "two", "", "NWJiZjVhNTIzMjhlNzQzOWFlNmU3MTlkZmU3MTIyMDA=", bytes.NewBufferString("one"))
|
err = drivers.CreateObject("bucket", "two", "", "NWJiZjVhNTIzMjhlNzQzOWFlNmU3MTlkZmU3MTIyMDA=", bytes.NewBufferString("one"))
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
}
|
}
|
||||||
*/
|
*/
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package storage
|
package drivers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package storage
|
package drivers
|
||||||
|
|
||||||
// This file implements compatability layer for AWS clients
|
// This file implements compatability layer for AWS clients
|
||||||
|
|
@ -1,4 +1,20 @@
|
|||||||
package storage
|
/*
|
||||||
|
* Minimalist Object Storage, (C) 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 drivers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
@ -14,21 +14,22 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package donutstorage
|
package donut
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/minio-io/minio/pkg/storage"
|
|
||||||
"github.com/minio-io/minio/pkg/storage/donut"
|
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
|
"github.com/minio-io/minio/pkg/storage/donut"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Storage creates a new single disk storage driver using donut without encoding.
|
// donutDriver - creates a new single disk drivers driver using donut
|
||||||
type Storage struct {
|
type donutDriver struct {
|
||||||
donut donut.Donut
|
donut donut.Donut
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,15 +38,14 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Start a single disk subsystem
|
// Start a single disk subsystem
|
||||||
func Start(path string) (chan<- string, <-chan error, storage.Storage) {
|
func Start(path string) (chan<- string, <-chan error, drivers.Driver) {
|
||||||
|
|
||||||
ctrlChannel := make(chan string)
|
ctrlChannel := make(chan string)
|
||||||
errorChannel := make(chan error)
|
errorChannel := make(chan error)
|
||||||
s := new(Storage)
|
s := new(donutDriver)
|
||||||
|
|
||||||
// TODO donut driver should be passed in as Start param and driven by config
|
// TODO donut driver should be passed in as Start param and driven by config
|
||||||
var err error
|
var err error
|
||||||
s.donut, err = donut.NewDonutDriver(path)
|
s.donut, err = donut.NewDonut(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorChannel <- err
|
errorChannel <- err
|
||||||
}
|
}
|
||||||
@ -54,13 +54,13 @@ func Start(path string) (chan<- string, <-chan error, storage.Storage) {
|
|||||||
return ctrlChannel, errorChannel, s
|
return ctrlChannel, errorChannel, s
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(ctrlChannel <-chan string, errorChannel chan<- error, s *Storage) {
|
func start(ctrlChannel <-chan string, errorChannel chan<- error, s *donutDriver) {
|
||||||
close(errorChannel)
|
close(errorChannel)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListBuckets returns a list of buckets
|
// ListBuckets returns a list of buckets
|
||||||
func (donutStorage Storage) ListBuckets() (results []storage.BucketMetadata, err error) {
|
func (d donutDriver) ListBuckets() (results []drivers.BucketMetadata, err error) {
|
||||||
buckets, err := donutStorage.donut.ListBuckets()
|
buckets, err := d.donut.ListBuckets()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ func (donutStorage Storage) ListBuckets() (results []storage.BucketMetadata, err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result := storage.BucketMetadata{
|
result := drivers.BucketMetadata{
|
||||||
Name: bucket,
|
Name: bucket,
|
||||||
// TODO Add real created date
|
// TODO Add real created date
|
||||||
Created: time.Now(),
|
Created: time.Now(),
|
||||||
@ -79,30 +79,30 @@ func (donutStorage Storage) ListBuckets() (results []storage.BucketMetadata, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateBucket creates a new bucket
|
// CreateBucket creates a new bucket
|
||||||
func (donutStorage Storage) CreateBucket(bucket string) error {
|
func (d donutDriver) CreateBucket(bucket string) error {
|
||||||
return donutStorage.donut.CreateBucket(bucket)
|
return d.donut.CreateBucket(bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketMetadata retrieves an bucket's metadata
|
// GetBucketMetadata retrieves an bucket's metadata
|
||||||
func (donutStorage Storage) GetBucketMetadata(bucket string) (storage.BucketMetadata, error) {
|
func (d donutDriver) GetBucketMetadata(bucket string) (drivers.BucketMetadata, error) {
|
||||||
return storage.BucketMetadata{}, errors.New("Not Implemented")
|
return drivers.BucketMetadata{}, errors.New("Not Implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBucketPolicy sets a bucket's access policy
|
// CreateBucketPolicy sets a bucket's access policy
|
||||||
func (donutStorage Storage) CreateBucketPolicy(bucket string, p storage.BucketPolicy) error {
|
func (d donutDriver) CreateBucketPolicy(bucket string, p drivers.BucketPolicy) error {
|
||||||
return errors.New("Not Implemented")
|
return errors.New("Not Implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketPolicy returns a bucket's access policy
|
// GetBucketPolicy returns a bucket's access policy
|
||||||
func (donutStorage Storage) GetBucketPolicy(bucket string) (storage.BucketPolicy, error) {
|
func (d donutDriver) GetBucketPolicy(bucket string) (drivers.BucketPolicy, error) {
|
||||||
return storage.BucketPolicy{}, errors.New("Not Implemented")
|
return drivers.BucketPolicy{}, errors.New("Not Implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObject retrieves an object and writes it to a writer
|
// GetObject retrieves an object and writes it to a writer
|
||||||
func (donutStorage Storage) GetObject(target io.Writer, bucket, key string) (int64, error) {
|
func (d donutDriver) GetObject(target io.Writer, bucket, key string) (int64, error) {
|
||||||
reader, err := donutStorage.donut.GetObject(bucket, key)
|
reader, err := d.donut.GetObjectReader(bucket, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, storage.ObjectNotFound{
|
return 0, drivers.ObjectNotFound{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Object: key,
|
Object: key,
|
||||||
}
|
}
|
||||||
@ -110,23 +110,23 @@ func (donutStorage Storage) GetObject(target io.Writer, bucket, key string) (int
|
|||||||
return io.Copy(target, reader)
|
return io.Copy(target, reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPartialObject retrieves an object and writes it to a writer
|
// GetPartialObject retrieves an object range and writes it to a writer
|
||||||
func (donutStorage Storage) GetPartialObject(w io.Writer, bucket, object string, start, length int64) (int64, error) {
|
func (d donutDriver) GetPartialObject(w io.Writer, bucket, object string, start, length int64) (int64, error) {
|
||||||
return 0, errors.New("Not Implemented")
|
return 0, errors.New("Not Implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectMetadata retrieves an object's metadata
|
// GetObjectMetadata retrieves an object's metadata
|
||||||
func (donutStorage Storage) GetObjectMetadata(bucket, key string, prefix string) (storage.ObjectMetadata, error) {
|
func (d donutDriver) GetObjectMetadata(bucket, key string, prefix string) (drivers.ObjectMetadata, error) {
|
||||||
metadata, err := donutStorage.donut.GetObjectMetadata(bucket, key)
|
metadata, err := d.donut.GetObjectMetadata(bucket, key)
|
||||||
created, err := time.Parse(time.RFC3339Nano, metadata["sys.created"])
|
created, err := time.Parse(time.RFC3339Nano, metadata["sys.created"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return storage.ObjectMetadata{}, err
|
return drivers.ObjectMetadata{}, err
|
||||||
}
|
}
|
||||||
size, err := strconv.ParseInt(metadata["sys.size"], 10, 64)
|
size, err := strconv.ParseInt(metadata["sys.size"], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return storage.ObjectMetadata{}, err
|
return drivers.ObjectMetadata{}, err
|
||||||
}
|
}
|
||||||
objectMetadata := storage.ObjectMetadata{
|
objectMetadata := drivers.ObjectMetadata{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
|
|
||||||
@ -138,12 +138,12 @@ func (donutStorage Storage) GetObjectMetadata(bucket, key string, prefix string)
|
|||||||
return objectMetadata, nil
|
return objectMetadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListObjects lists objects
|
// ListObjects - returns list of objects
|
||||||
func (donutStorage Storage) ListObjects(bucket string, resources storage.BucketResourcesMetadata) ([]storage.ObjectMetadata, storage.BucketResourcesMetadata, error) {
|
func (d donutDriver) ListObjects(bucket string, resources drivers.BucketResourcesMetadata) ([]drivers.ObjectMetadata, drivers.BucketResourcesMetadata, error) {
|
||||||
// TODO Fix IsPrefixSet && IsDelimiterSet and use them
|
// TODO Fix IsPrefixSet && IsDelimiterSet and use them
|
||||||
objects, err := donutStorage.donut.ListObjects(bucket)
|
objects, err := d.donut.ListObjects(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, storage.BucketResourcesMetadata{}, err
|
return nil, drivers.BucketResourcesMetadata{}, err
|
||||||
}
|
}
|
||||||
sort.Strings(objects)
|
sort.Strings(objects)
|
||||||
if resources.Prefix != "" {
|
if resources.Prefix != "" {
|
||||||
@ -166,15 +166,15 @@ func (donutStorage Storage) ListObjects(bucket string, resources storage.BucketR
|
|||||||
actualObjects = objects
|
actualObjects = objects
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []storage.ObjectMetadata
|
var results []drivers.ObjectMetadata
|
||||||
for _, object := range actualObjects {
|
for _, object := range actualObjects {
|
||||||
if len(results) >= resources.Maxkeys {
|
if len(results) >= resources.Maxkeys {
|
||||||
resources.IsTruncated = true
|
resources.IsTruncated = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
metadata, err := donutStorage.GetObjectMetadata(bucket, resources.Prefix+object, "")
|
metadata, err := d.GetObjectMetadata(bucket, resources.Prefix+object, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, storage.BucketResourcesMetadata{}, err
|
return nil, drivers.BucketResourcesMetadata{}, err
|
||||||
}
|
}
|
||||||
results = append(results, metadata)
|
results = append(results, metadata)
|
||||||
}
|
}
|
||||||
@ -241,8 +241,8 @@ func uniqueObjects(objects []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateObject creates a new object
|
// CreateObject creates a new object
|
||||||
func (donutStorage Storage) CreateObject(bucketKey, objectKey, contentType, expectedMd5sum string, reader io.Reader) error {
|
func (d donutDriver) CreateObject(bucketKey, objectKey, contentType, expectedMd5sum string, reader io.Reader) error {
|
||||||
writer, err := donutStorage.donut.GetObjectWriter(bucketKey, objectKey)
|
writer, err := d.donut.GetObjectWriter(bucketKey, objectKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
@ -14,14 +14,14 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package donutstorage
|
package donut
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
|
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
@ -35,14 +35,14 @@ var _ = Suite(&MySuite{})
|
|||||||
func (s *MySuite) TestAPISuite(c *C) {
|
func (s *MySuite) TestAPISuite(c *C) {
|
||||||
// c.Skip("Not Implemented")
|
// c.Skip("Not Implemented")
|
||||||
var storageList []string
|
var storageList []string
|
||||||
create := func() mstorage.Storage {
|
create := func() drivers.Driver {
|
||||||
path, err := ioutil.TempDir(os.TempDir(), "minio-fs-")
|
path, err := ioutil.TempDir(os.TempDir(), "minio-fs-")
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
storageList = append(storageList, path)
|
storageList = append(storageList, path)
|
||||||
_, _, store := Start(path) // TODO Make InMemory driver
|
_, _, store := Start(path) // TODO Make InMemory driver
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
mstorage.APITestSuite(c, create)
|
drivers.APITestSuite(c, create)
|
||||||
removeRoots(c, storageList)
|
removeRoots(c, storageList)
|
||||||
}
|
}
|
||||||
|
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package storage
|
package drivers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
@ -23,8 +23,8 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Storage - generic API interface
|
// Driver - generic API interface for various drivers - donut, file, memory
|
||||||
type Storage interface {
|
type Driver interface {
|
||||||
// Bucket Operations
|
// Bucket Operations
|
||||||
ListBuckets() ([]BucketMetadata, error)
|
ListBuckets() ([]BucketMetadata, error)
|
||||||
CreateBucket(bucket string) error
|
CreateBucket(bucket string) error
|
@ -14,7 +14,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package storage
|
package drivers
|
||||||
|
|
||||||
// BackendError - generic disk backend error
|
// BackendError - generic disk backend error
|
||||||
type BackendError struct {
|
type BackendError struct {
|
@ -19,20 +19,22 @@ package file
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Start filesystem channel
|
// Start filesystem channel
|
||||||
func Start(root string) (chan<- string, <-chan error, *Storage) {
|
func Start(root string) (chan<- string, <-chan error, drivers.Driver) {
|
||||||
ctrlChannel := make(chan string)
|
ctrlChannel := make(chan string)
|
||||||
errorChannel := make(chan error)
|
errorChannel := make(chan error)
|
||||||
s := Storage{}
|
s := new(fileDriver)
|
||||||
s.root = root
|
s.root = root
|
||||||
s.lock = new(sync.Mutex)
|
s.lock = new(sync.Mutex)
|
||||||
go start(ctrlChannel, errorChannel, &s)
|
go start(ctrlChannel, errorChannel, s)
|
||||||
return ctrlChannel, errorChannel, &s
|
return ctrlChannel, errorChannel, s
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(ctrlChannel <-chan string, errorChannel chan<- error, s *Storage) {
|
func start(ctrlChannel <-chan string, errorChannel chan<- error, s *fileDriver) {
|
||||||
err := os.MkdirAll(s.root, 0700)
|
err := os.MkdirAll(s.root, 0700)
|
||||||
errorChannel <- err
|
errorChannel <- err
|
||||||
close(errorChannel)
|
close(errorChannel)
|
133
pkg/drivers/file/file_bucket.go
Normal file
133
pkg/drivers/file/file_bucket.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* Minimalist Object File, (C) 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 file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
|
)
|
||||||
|
|
||||||
|
/// Bucket Operations
|
||||||
|
|
||||||
|
// ListBuckets - Get service
|
||||||
|
func (file *fileDriver) ListBuckets() ([]drivers.BucketMetadata, error) {
|
||||||
|
files, err := ioutil.ReadDir(file.root)
|
||||||
|
if err != nil {
|
||||||
|
return []drivers.BucketMetadata{}, drivers.EmbedError("bucket", "", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadataList []drivers.BucketMetadata
|
||||||
|
for _, fileName := range files {
|
||||||
|
// Skip policy files
|
||||||
|
if strings.HasSuffix(fileName.Name(), "_policy.json") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !fileName.IsDir() {
|
||||||
|
return []drivers.BucketMetadata{}, drivers.BackendCorrupted{Path: file.root}
|
||||||
|
}
|
||||||
|
metadata := drivers.BucketMetadata{
|
||||||
|
Name: fileName.Name(),
|
||||||
|
Created: fileName.ModTime(), // TODO - provide real created time
|
||||||
|
}
|
||||||
|
metadataList = append(metadataList, metadata)
|
||||||
|
}
|
||||||
|
return metadataList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBucket - PUT Bucket
|
||||||
|
func (file *fileDriver) CreateBucket(bucket string) error {
|
||||||
|
file.lock.Lock()
|
||||||
|
defer file.lock.Unlock()
|
||||||
|
|
||||||
|
// verify bucket path legal
|
||||||
|
if drivers.IsValidBucket(bucket) == false {
|
||||||
|
return drivers.BucketNameInvalid{Bucket: bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get bucket path
|
||||||
|
bucketDir := path.Join(file.root, bucket)
|
||||||
|
|
||||||
|
// check if bucket exists
|
||||||
|
if _, err := os.Stat(bucketDir); err == nil {
|
||||||
|
return drivers.BucketExists{
|
||||||
|
Bucket: bucket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make bucket
|
||||||
|
err := os.Mkdir(bucketDir, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return drivers.EmbedError(bucket, "", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListObjects - GET bucket (list objects)
|
||||||
|
func (file *fileDriver) ListObjects(bucket string, resources drivers.BucketResourcesMetadata) ([]drivers.ObjectMetadata, drivers.BucketResourcesMetadata, error) {
|
||||||
|
p := bucketDir{}
|
||||||
|
p.files = make(map[string]os.FileInfo)
|
||||||
|
|
||||||
|
if drivers.IsValidBucket(bucket) == false {
|
||||||
|
return []drivers.ObjectMetadata{}, resources, drivers.BucketNameInvalid{Bucket: bucket}
|
||||||
|
}
|
||||||
|
if resources.Prefix != "" && drivers.IsValidObject(resources.Prefix) == false {
|
||||||
|
return []drivers.ObjectMetadata{}, resources, drivers.ObjectNameInvalid{Bucket: bucket, Object: resources.Prefix}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootPrefix := path.Join(file.root, bucket)
|
||||||
|
// check bucket exists
|
||||||
|
if _, err := os.Stat(rootPrefix); os.IsNotExist(err) {
|
||||||
|
return []drivers.ObjectMetadata{}, resources, drivers.BucketNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.root = rootPrefix
|
||||||
|
err := filepath.Walk(rootPrefix, p.getAllFiles)
|
||||||
|
if err != nil {
|
||||||
|
return []drivers.ObjectMetadata{}, resources, drivers.EmbedError(bucket, "", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadataList []drivers.ObjectMetadata
|
||||||
|
var metadata drivers.ObjectMetadata
|
||||||
|
|
||||||
|
// Populate filtering mode
|
||||||
|
resources.Mode = drivers.GetMode(resources)
|
||||||
|
|
||||||
|
for name, f := range p.files {
|
||||||
|
if len(metadataList) >= resources.Maxkeys {
|
||||||
|
resources.IsTruncated = true
|
||||||
|
goto ret
|
||||||
|
}
|
||||||
|
metadata, resources, err = file.filter(bucket, name, f, resources)
|
||||||
|
if err != nil {
|
||||||
|
return []drivers.ObjectMetadata{}, resources, drivers.EmbedError(bucket, "", err)
|
||||||
|
}
|
||||||
|
if metadata.Bucket != "" {
|
||||||
|
metadataList = append(metadataList, metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret:
|
||||||
|
sort.Sort(byObjectKey(metadataList))
|
||||||
|
return metadataList, resources, nil
|
||||||
|
}
|
@ -23,17 +23,17 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Storage - file local variables
|
// fileDriver - file local variables
|
||||||
type Storage struct {
|
type fileDriver struct {
|
||||||
root string
|
root string
|
||||||
lock *sync.Mutex
|
lock *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata - carries metadata about object
|
// fileMetadata - carries metadata about object
|
||||||
type Metadata struct {
|
type fileMetadata struct {
|
||||||
Md5sum []byte
|
Md5sum []byte
|
||||||
ContentType string
|
ContentType string
|
||||||
}
|
}
|
||||||
@ -77,7 +77,7 @@ func delimiter(object, delimiter string) string {
|
|||||||
return delimitedStr
|
return delimitedStr
|
||||||
}
|
}
|
||||||
|
|
||||||
type byObjectKey []mstorage.ObjectMetadata
|
type byObjectKey []drivers.ObjectMetadata
|
||||||
|
|
||||||
// Len
|
// Len
|
||||||
func (b byObjectKey) Len() int { return len(b) }
|
func (b byObjectKey) Len() int { return len(b) }
|
@ -20,13 +20,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO handle resources.Marker
|
// TODO handle resources.Marker
|
||||||
func (storage *Storage) filter(bucket, name string, file os.FileInfo, resources mstorage.BucketResourcesMetadata) (mstorage.ObjectMetadata, mstorage.BucketResourcesMetadata, error) {
|
func (file *fileDriver) filter(bucket, name string, f os.FileInfo, resources drivers.BucketResourcesMetadata) (drivers.ObjectMetadata, drivers.BucketResourcesMetadata, error) {
|
||||||
var err error
|
var err error
|
||||||
var metadata mstorage.ObjectMetadata
|
var metadata drivers.ObjectMetadata
|
||||||
|
|
||||||
switch true {
|
switch true {
|
||||||
// Both delimiter and Prefix is present
|
// Both delimiter and Prefix is present
|
||||||
@ -37,15 +37,15 @@ func (storage *Storage) filter(bucket, name string, file os.FileInfo, resources
|
|||||||
switch true {
|
switch true {
|
||||||
case name == resources.Prefix:
|
case name == resources.Prefix:
|
||||||
// Use resources.Prefix to filter out delimited files
|
// Use resources.Prefix to filter out delimited files
|
||||||
metadata, err = storage.GetObjectMetadata(bucket, name, resources.Prefix)
|
metadata, err = file.GetObjectMetadata(bucket, name, resources.Prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.ObjectMetadata{}, resources, mstorage.EmbedError(bucket, "", err)
|
return drivers.ObjectMetadata{}, resources, drivers.EmbedError(bucket, "", err)
|
||||||
}
|
}
|
||||||
case delimitedName == file.Name():
|
case delimitedName == f.Name():
|
||||||
// Use resources.Prefix to filter out delimited files
|
// Use resources.Prefix to filter out delimited files
|
||||||
metadata, err = storage.GetObjectMetadata(bucket, name, resources.Prefix)
|
metadata, err = file.GetObjectMetadata(bucket, name, resources.Prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.ObjectMetadata{}, resources, mstorage.EmbedError(bucket, "", err)
|
return drivers.ObjectMetadata{}, resources, drivers.EmbedError(bucket, "", err)
|
||||||
}
|
}
|
||||||
case delimitedName != "":
|
case delimitedName != "":
|
||||||
if delimitedName == resources.Delimiter {
|
if delimitedName == resources.Delimiter {
|
||||||
@ -61,15 +61,15 @@ func (storage *Storage) filter(bucket, name string, file os.FileInfo, resources
|
|||||||
switch true {
|
switch true {
|
||||||
case delimitedName == "":
|
case delimitedName == "":
|
||||||
// Do not strip prefix object output
|
// Do not strip prefix object output
|
||||||
metadata, err = storage.GetObjectMetadata(bucket, name, "")
|
metadata, err = file.GetObjectMetadata(bucket, name, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.ObjectMetadata{}, resources, mstorage.EmbedError(bucket, "", err)
|
return drivers.ObjectMetadata{}, resources, drivers.EmbedError(bucket, "", err)
|
||||||
}
|
}
|
||||||
case delimitedName == file.Name():
|
case delimitedName == f.Name():
|
||||||
// Do not strip prefix object output
|
// Do not strip prefix object output
|
||||||
metadata, err = storage.GetObjectMetadata(bucket, name, "")
|
metadata, err = file.GetObjectMetadata(bucket, name, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.ObjectMetadata{}, resources, mstorage.EmbedError(bucket, "", err)
|
return drivers.ObjectMetadata{}, resources, drivers.EmbedError(bucket, "", err)
|
||||||
}
|
}
|
||||||
case delimitedName != "":
|
case delimitedName != "":
|
||||||
resources.CommonPrefixes = appendUniq(resources.CommonPrefixes, delimitedName)
|
resources.CommonPrefixes = appendUniq(resources.CommonPrefixes, delimitedName)
|
||||||
@ -78,15 +78,15 @@ func (storage *Storage) filter(bucket, name string, file os.FileInfo, resources
|
|||||||
case resources.IsPrefixSet():
|
case resources.IsPrefixSet():
|
||||||
if strings.HasPrefix(name, resources.Prefix) {
|
if strings.HasPrefix(name, resources.Prefix) {
|
||||||
// Do not strip prefix object output
|
// Do not strip prefix object output
|
||||||
metadata, err = storage.GetObjectMetadata(bucket, name, "")
|
metadata, err = file.GetObjectMetadata(bucket, name, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.ObjectMetadata{}, resources, mstorage.EmbedError(bucket, "", err)
|
return drivers.ObjectMetadata{}, resources, drivers.EmbedError(bucket, "", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case resources.IsDefault():
|
case resources.IsDefault():
|
||||||
metadata, err = storage.GetObjectMetadata(bucket, name, "")
|
metadata, err = file.GetObjectMetadata(bucket, name, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.ObjectMetadata{}, resources, mstorage.EmbedError(bucket, "", err)
|
return drivers.ObjectMetadata{}, resources, drivers.EmbedError(bucket, "", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -28,136 +28,136 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
/// Object Operations
|
/// Object Operations
|
||||||
|
|
||||||
// GetPartialObject - GET object from range
|
// GetPartialObject - GET object from range
|
||||||
func (storage *Storage) GetPartialObject(w io.Writer, bucket, object string, start, length int64) (int64, error) {
|
func (file *fileDriver) GetPartialObject(w io.Writer, bucket, object string, start, length int64) (int64, error) {
|
||||||
// validate bucket
|
// validate bucket
|
||||||
if mstorage.IsValidBucket(bucket) == false {
|
if drivers.IsValidBucket(bucket) == false {
|
||||||
return 0, mstorage.BucketNameInvalid{Bucket: bucket}
|
return 0, drivers.BucketNameInvalid{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate object
|
// validate object
|
||||||
if mstorage.IsValidObject(object) == false {
|
if drivers.IsValidObject(object) == false {
|
||||||
return 0, mstorage.ObjectNameInvalid{Bucket: bucket, Object: object}
|
return 0, drivers.ObjectNameInvalid{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
|
|
||||||
objectPath := path.Join(storage.root, bucket, object)
|
objectPath := path.Join(file.root, bucket, object)
|
||||||
filestat, err := os.Stat(objectPath)
|
filestat, err := os.Stat(objectPath)
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
{
|
{
|
||||||
if filestat.IsDir() {
|
if filestat.IsDir() {
|
||||||
return 0, mstorage.ObjectNotFound{Bucket: bucket, Object: object}
|
return 0, drivers.ObjectNotFound{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return 0, mstorage.ObjectNotFound{Bucket: bucket, Object: object}
|
return 0, drivers.ObjectNotFound{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
return 0, mstorage.EmbedError(bucket, object, err)
|
return 0, drivers.EmbedError(bucket, object, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file, err := os.Open(objectPath)
|
f, err := os.Open(objectPath)
|
||||||
defer file.Close()
|
defer f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, mstorage.EmbedError(bucket, object, err)
|
return 0, drivers.EmbedError(bucket, object, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = file.Seek(start, os.SEEK_SET)
|
_, err = f.Seek(start, os.SEEK_SET)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, mstorage.EmbedError(bucket, object, err)
|
return 0, drivers.EmbedError(bucket, object, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := io.CopyN(w, file, length)
|
count, err := io.CopyN(w, f, length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return count, mstorage.EmbedError(bucket, object, err)
|
return count, drivers.EmbedError(bucket, object, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObject - GET object from key
|
// GetObject - GET object from key
|
||||||
func (storage *Storage) GetObject(w io.Writer, bucket string, object string) (int64, error) {
|
func (file *fileDriver) GetObject(w io.Writer, bucket string, object string) (int64, error) {
|
||||||
// validate bucket
|
// validate bucket
|
||||||
if mstorage.IsValidBucket(bucket) == false {
|
if drivers.IsValidBucket(bucket) == false {
|
||||||
return 0, mstorage.BucketNameInvalid{Bucket: bucket}
|
return 0, drivers.BucketNameInvalid{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate object
|
// validate object
|
||||||
if mstorage.IsValidObject(object) == false {
|
if drivers.IsValidObject(object) == false {
|
||||||
return 0, mstorage.ObjectNameInvalid{Bucket: bucket, Object: object}
|
return 0, drivers.ObjectNameInvalid{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
|
|
||||||
objectPath := path.Join(storage.root, bucket, object)
|
objectPath := path.Join(file.root, bucket, object)
|
||||||
|
|
||||||
filestat, err := os.Stat(objectPath)
|
filestat, err := os.Stat(objectPath)
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
{
|
{
|
||||||
if filestat.IsDir() {
|
if filestat.IsDir() {
|
||||||
return 0, mstorage.ObjectNotFound{Bucket: bucket, Object: object}
|
return 0, drivers.ObjectNotFound{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return 0, mstorage.ObjectNotFound{Bucket: bucket, Object: object}
|
return 0, drivers.ObjectNotFound{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
return 0, mstorage.EmbedError(bucket, object, err)
|
return 0, drivers.EmbedError(bucket, object, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file, err := os.Open(objectPath)
|
f, err := os.Open(objectPath)
|
||||||
defer file.Close()
|
defer f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, mstorage.EmbedError(bucket, object, err)
|
return 0, drivers.EmbedError(bucket, object, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := io.Copy(w, file)
|
count, err := io.Copy(w, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return count, mstorage.EmbedError(bucket, object, err)
|
return count, drivers.EmbedError(bucket, object, err)
|
||||||
}
|
}
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectMetadata - HEAD object
|
// GetObjectMetadata - HEAD object
|
||||||
func (storage *Storage) GetObjectMetadata(bucket, object, prefix string) (mstorage.ObjectMetadata, error) {
|
func (file *fileDriver) GetObjectMetadata(bucket, object, prefix string) (drivers.ObjectMetadata, error) {
|
||||||
if mstorage.IsValidBucket(bucket) == false {
|
if drivers.IsValidBucket(bucket) == false {
|
||||||
return mstorage.ObjectMetadata{}, mstorage.BucketNameInvalid{Bucket: bucket}
|
return drivers.ObjectMetadata{}, drivers.BucketNameInvalid{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mstorage.IsValidObject(object) == false {
|
if drivers.IsValidObject(object) == false {
|
||||||
return mstorage.ObjectMetadata{}, mstorage.ObjectNameInvalid{Bucket: bucket, Object: bucket}
|
return drivers.ObjectMetadata{}, drivers.ObjectNameInvalid{Bucket: bucket, Object: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not use path.Join() since path.Join strips off any object names with '/', use them as is
|
// Do not use path.Join() since path.Join strips off any object names with '/', use them as is
|
||||||
// in a static manner so that we can send a proper 'ObjectNotFound' reply back upon os.Stat()
|
// in a static manner so that we can send a proper 'ObjectNotFound' reply back upon os.Stat()
|
||||||
objectPath := storage.root + "/" + bucket + "/" + object
|
objectPath := file.root + "/" + bucket + "/" + object
|
||||||
stat, err := os.Stat(objectPath)
|
stat, err := os.Stat(objectPath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return mstorage.ObjectMetadata{}, mstorage.ObjectNotFound{Bucket: bucket, Object: object}
|
return drivers.ObjectMetadata{}, drivers.ObjectNotFound{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = os.Stat(objectPath + "$metadata")
|
_, err = os.Stat(objectPath + "$metadata")
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return mstorage.ObjectMetadata{}, mstorage.ObjectNotFound{Bucket: bucket, Object: object}
|
return drivers.ObjectMetadata{}, drivers.ObjectNotFound{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(objectPath + "$metadata")
|
f, err := os.Open(objectPath + "$metadata")
|
||||||
defer file.Close()
|
defer f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.ObjectMetadata{}, mstorage.EmbedError(bucket, object, err)
|
return drivers.ObjectMetadata{}, drivers.EmbedError(bucket, object, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var deserializedMetadata Metadata
|
var deserializedMetadata fileMetadata
|
||||||
decoder := gob.NewDecoder(file)
|
decoder := gob.NewDecoder(f)
|
||||||
err = decoder.Decode(&deserializedMetadata)
|
err = decoder.Decode(&deserializedMetadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.ObjectMetadata{}, mstorage.EmbedError(bucket, object, err)
|
return drivers.ObjectMetadata{}, drivers.EmbedError(bucket, object, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentType := "application/octet-stream"
|
contentType := "application/octet-stream"
|
||||||
@ -171,7 +171,7 @@ func (storage *Storage) GetObjectMetadata(bucket, object, prefix string) (mstora
|
|||||||
etag = hex.EncodeToString(deserializedMetadata.Md5sum)
|
etag = hex.EncodeToString(deserializedMetadata.Md5sum)
|
||||||
}
|
}
|
||||||
trimmedObject := strings.TrimPrefix(object, prefix)
|
trimmedObject := strings.TrimPrefix(object, prefix)
|
||||||
metadata := mstorage.ObjectMetadata{
|
metadata := drivers.ObjectMetadata{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: trimmedObject,
|
Key: trimmedObject,
|
||||||
Created: stat.ModTime(),
|
Created: stat.ModTime(),
|
||||||
@ -184,24 +184,24 @@ func (storage *Storage) GetObjectMetadata(bucket, object, prefix string) (mstora
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateObject - PUT object
|
// CreateObject - PUT object
|
||||||
func (storage *Storage) CreateObject(bucket, key, contentType, md5sum string, data io.Reader) error {
|
func (file *fileDriver) CreateObject(bucket, key, contentType, md5sum string, data io.Reader) error {
|
||||||
// TODO Commits should stage then move instead of writing directly
|
// TODO Commits should stage then move instead of writing directly
|
||||||
storage.lock.Lock()
|
file.lock.Lock()
|
||||||
defer storage.lock.Unlock()
|
defer file.lock.Unlock()
|
||||||
|
|
||||||
// check bucket name valid
|
// check bucket name valid
|
||||||
if mstorage.IsValidBucket(bucket) == false {
|
if drivers.IsValidBucket(bucket) == false {
|
||||||
return mstorage.BucketNameInvalid{Bucket: bucket}
|
return drivers.BucketNameInvalid{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check bucket exists
|
// check bucket exists
|
||||||
if _, err := os.Stat(path.Join(storage.root, bucket)); os.IsNotExist(err) {
|
if _, err := os.Stat(path.Join(file.root, bucket)); os.IsNotExist(err) {
|
||||||
return mstorage.BucketNotFound{Bucket: bucket}
|
return drivers.BucketNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify object path legal
|
// verify object path legal
|
||||||
if mstorage.IsValidObject(key) == false {
|
if drivers.IsValidObject(key) == false {
|
||||||
return mstorage.ObjectNameInvalid{Bucket: bucket, Object: key}
|
return drivers.ObjectNameInvalid{Bucket: bucket, Object: key}
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify content type
|
// verify content type
|
||||||
@ -211,54 +211,54 @@ func (storage *Storage) CreateObject(bucket, key, contentType, md5sum string, da
|
|||||||
contentType = strings.TrimSpace(contentType)
|
contentType = strings.TrimSpace(contentType)
|
||||||
|
|
||||||
// get object path
|
// get object path
|
||||||
objectPath := path.Join(storage.root, bucket, key)
|
objectPath := path.Join(file.root, bucket, key)
|
||||||
objectDir := path.Dir(objectPath)
|
objectDir := path.Dir(objectPath)
|
||||||
if _, err := os.Stat(objectDir); os.IsNotExist(err) {
|
if _, err := os.Stat(objectDir); os.IsNotExist(err) {
|
||||||
err = os.MkdirAll(objectDir, 0700)
|
err = os.MkdirAll(objectDir, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.EmbedError(bucket, key, err)
|
return drivers.EmbedError(bucket, key, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if object exists
|
// check if object exists
|
||||||
if _, err := os.Stat(objectPath); !os.IsNotExist(err) {
|
if _, err := os.Stat(objectPath); !os.IsNotExist(err) {
|
||||||
return mstorage.ObjectExists{
|
return drivers.ObjectExists{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Object: key,
|
Object: key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// write object
|
// write object
|
||||||
file, err := os.OpenFile(objectPath, os.O_WRONLY|os.O_CREATE, 0600)
|
f, err := os.OpenFile(objectPath, os.O_WRONLY|os.O_CREATE, 0600)
|
||||||
defer file.Close()
|
defer f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.EmbedError(bucket, key, err)
|
return drivers.EmbedError(bucket, key, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := md5.New()
|
h := md5.New()
|
||||||
mw := io.MultiWriter(file, h)
|
mw := io.MultiWriter(f, h)
|
||||||
|
|
||||||
_, err = io.Copy(mw, data)
|
_, err = io.Copy(mw, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.EmbedError(bucket, key, err)
|
return drivers.EmbedError(bucket, key, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
file, err = os.OpenFile(objectPath+"$metadata", os.O_WRONLY|os.O_CREATE, 0600)
|
f, err = os.OpenFile(objectPath+"$metadata", os.O_WRONLY|os.O_CREATE, 0600)
|
||||||
defer file.Close()
|
defer f.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.EmbedError(bucket, key, err)
|
return drivers.EmbedError(bucket, key, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata := &Metadata{
|
metadata := &fileMetadata{
|
||||||
ContentType: contentType,
|
ContentType: contentType,
|
||||||
Md5sum: h.Sum(nil),
|
Md5sum: h.Sum(nil),
|
||||||
}
|
}
|
||||||
// serialize metadata to gob
|
// serialize metadata to gob
|
||||||
encoder := gob.NewEncoder(file)
|
encoder := gob.NewEncoder(f)
|
||||||
err = encoder.Encode(metadata)
|
err = encoder.Encode(metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.EmbedError(bucket, key, err)
|
return drivers.EmbedError(bucket, key, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify data received to be correct, Content-MD5 received
|
// Verify data received to be correct, Content-MD5 received
|
||||||
@ -266,10 +266,10 @@ func (storage *Storage) CreateObject(bucket, key, contentType, md5sum string, da
|
|||||||
var data []byte
|
var data []byte
|
||||||
data, err = base64.StdEncoding.DecodeString(md5sum)
|
data, err = base64.StdEncoding.DecodeString(md5sum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mstorage.InvalidDigest{Bucket: bucket, Key: key, Md5: md5sum}
|
return drivers.InvalidDigest{Bucket: bucket, Key: key, Md5: md5sum}
|
||||||
}
|
}
|
||||||
if !bytes.Equal(metadata.Md5sum, data) {
|
if !bytes.Equal(metadata.Md5sum, data) {
|
||||||
return mstorage.BadDigest{Bucket: bucket, Key: key, Md5: md5sum}
|
return drivers.BadDigest{Bucket: bucket, Key: key, Md5: md5sum}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
111
pkg/drivers/file/file_policy.go
Normal file
111
pkg/drivers/file/file_policy.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Minimalist Object Storage, (C) 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 file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetBucketPolicy - GET bucket policy
|
||||||
|
func (file *fileDriver) GetBucketPolicy(bucket string) (drivers.BucketPolicy, error) {
|
||||||
|
file.lock.Lock()
|
||||||
|
defer file.lock.Unlock()
|
||||||
|
|
||||||
|
var p drivers.BucketPolicy
|
||||||
|
// verify bucket path legal
|
||||||
|
if drivers.IsValidBucket(bucket) == false {
|
||||||
|
return drivers.BucketPolicy{}, drivers.BucketNameInvalid{Bucket: bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get bucket path
|
||||||
|
bucketDir := path.Join(file.root, bucket)
|
||||||
|
// check if bucket exists
|
||||||
|
if _, err := os.Stat(bucketDir); err != nil {
|
||||||
|
return drivers.BucketPolicy{}, drivers.BucketNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get policy path
|
||||||
|
bucketPolicy := path.Join(file.root, bucket+"_policy.json")
|
||||||
|
filestat, err := os.Stat(bucketPolicy)
|
||||||
|
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return drivers.BucketPolicy{}, drivers.BucketPolicyNotFound{Bucket: bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
if filestat.IsDir() {
|
||||||
|
return drivers.BucketPolicy{}, drivers.BackendCorrupted{Path: bucketPolicy}
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(bucketPolicy, os.O_RDONLY, 0666)
|
||||||
|
defer f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return drivers.BucketPolicy{}, drivers.EmbedError(bucket, "", err)
|
||||||
|
}
|
||||||
|
encoder := json.NewDecoder(f)
|
||||||
|
err = encoder.Decode(&p)
|
||||||
|
if err != nil {
|
||||||
|
return drivers.BucketPolicy{}, drivers.EmbedError(bucket, "", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBucketPolicy - PUT bucket policy
|
||||||
|
func (file *fileDriver) CreateBucketPolicy(bucket string, p drivers.BucketPolicy) error {
|
||||||
|
file.lock.Lock()
|
||||||
|
defer file.lock.Unlock()
|
||||||
|
|
||||||
|
// verify bucket path legal
|
||||||
|
if drivers.IsValidBucket(bucket) == false {
|
||||||
|
return drivers.BucketNameInvalid{Bucket: bucket}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get bucket path
|
||||||
|
bucketDir := path.Join(file.root, bucket)
|
||||||
|
// check if bucket exists
|
||||||
|
if _, err := os.Stat(bucketDir); err != nil {
|
||||||
|
return drivers.BucketNotFound{
|
||||||
|
Bucket: bucket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get policy path
|
||||||
|
bucketPolicy := path.Join(file.root, bucket+"_policy.json")
|
||||||
|
filestat, ret := os.Stat(bucketPolicy)
|
||||||
|
if !os.IsNotExist(ret) {
|
||||||
|
if filestat.IsDir() {
|
||||||
|
return drivers.BackendCorrupted{Path: bucketPolicy}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.OpenFile(bucketPolicy, os.O_WRONLY|os.O_CREATE, 0600)
|
||||||
|
defer f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return drivers.EmbedError(bucket, "", err)
|
||||||
|
}
|
||||||
|
encoder := json.NewEncoder(f)
|
||||||
|
err = encoder.Encode(p)
|
||||||
|
if err != nil {
|
||||||
|
return drivers.EmbedError(bucket, "", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -21,7 +21,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
|
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
@ -34,14 +34,14 @@ var _ = Suite(&MySuite{})
|
|||||||
|
|
||||||
func (s *MySuite) TestAPISuite(c *C) {
|
func (s *MySuite) TestAPISuite(c *C) {
|
||||||
var storageList []string
|
var storageList []string
|
||||||
create := func() mstorage.Storage {
|
create := func() drivers.Driver {
|
||||||
path, err := ioutil.TempDir(os.TempDir(), "minio-file-")
|
path, err := ioutil.TempDir(os.TempDir(), "minio-file-")
|
||||||
c.Check(err, IsNil)
|
c.Check(err, IsNil)
|
||||||
storageList = append(storageList, path)
|
storageList = append(storageList, path)
|
||||||
_, _, store := Start(path)
|
_, _, store := Start(path)
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
mstorage.APITestSuite(c, create)
|
drivers.APITestSuite(c, create)
|
||||||
removeRoots(c, storageList)
|
removeRoots(c, storageList)
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Minio 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 file except in compliance with the License.
|
||||||
@ -19,7 +19,6 @@ package memory
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@ -28,37 +27,40 @@ import (
|
|||||||
|
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
|
||||||
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Storage - local variables
|
// memoryDriver - local variables
|
||||||
type Storage struct {
|
type memoryDriver struct {
|
||||||
bucketdata map[string]storedBucket
|
bucketdata map[string]storedBucket
|
||||||
objectdata map[string]storedObject
|
objectdata map[string]storedObject
|
||||||
lock *sync.RWMutex
|
lock *sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type storedBucket struct {
|
type storedBucket struct {
|
||||||
metadata mstorage.BucketMetadata
|
metadata drivers.BucketMetadata
|
||||||
// owner string // TODO
|
// owner string // TODO
|
||||||
// id string // TODO
|
// id string // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
type storedObject struct {
|
type storedObject struct {
|
||||||
metadata mstorage.ObjectMetadata
|
metadata drivers.ObjectMetadata
|
||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start memory object server
|
// Start memory object server
|
||||||
func Start() (chan<- string, <-chan error, *Storage) {
|
func Start() (chan<- string, <-chan error, drivers.Driver) {
|
||||||
ctrlChannel := make(chan string)
|
ctrlChannel := make(chan string)
|
||||||
errorChannel := make(chan error)
|
errorChannel := make(chan error)
|
||||||
|
|
||||||
|
memory := new(memoryDriver)
|
||||||
|
memory.bucketdata = make(map[string]storedBucket)
|
||||||
|
memory.objectdata = make(map[string]storedObject)
|
||||||
|
memory.lock = new(sync.RWMutex)
|
||||||
|
|
||||||
go start(ctrlChannel, errorChannel)
|
go start(ctrlChannel, errorChannel)
|
||||||
return ctrlChannel, errorChannel, &Storage{
|
return ctrlChannel, errorChannel, memory
|
||||||
bucketdata: make(map[string]storedBucket),
|
|
||||||
objectdata: make(map[string]storedObject),
|
|
||||||
lock: new(sync.RWMutex),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(ctrlChannel <-chan string, errorChannel chan<- error) {
|
func start(ctrlChannel <-chan string, errorChannel chan<- error) {
|
||||||
@ -66,43 +68,43 @@ func start(ctrlChannel <-chan string, errorChannel chan<- error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetObject - GET object from memory buffer
|
// GetObject - GET object from memory buffer
|
||||||
func (storage *Storage) GetObject(w io.Writer, bucket string, object string) (int64, error) {
|
func (memory memoryDriver) GetObject(w io.Writer, bucket string, object string) (int64, error) {
|
||||||
// get object
|
// get object
|
||||||
key := object
|
key := object
|
||||||
if val, ok := storage.objectdata[key]; ok {
|
if val, ok := memory.objectdata[key]; ok {
|
||||||
objectBuffer := bytes.NewBuffer(val.data)
|
objectBuffer := bytes.NewBuffer(val.data)
|
||||||
written, err := io.Copy(w, objectBuffer)
|
written, err := io.Copy(w, objectBuffer)
|
||||||
return written, err
|
return written, err
|
||||||
}
|
}
|
||||||
return 0, mstorage.ObjectNotFound{Bucket: bucket, Object: object}
|
return 0, drivers.ObjectNotFound{Bucket: bucket, Object: object}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPartialObject - GET object from memory buffer range
|
// GetPartialObject - GET object from memory buffer range
|
||||||
func (storage *Storage) GetPartialObject(w io.Writer, bucket, object string, start, end int64) (int64, error) {
|
func (memory memoryDriver) GetPartialObject(w io.Writer, bucket, object string, start, end int64) (int64, error) {
|
||||||
return 0, mstorage.APINotImplemented{API: "GetPartialObject"}
|
return 0, drivers.APINotImplemented{API: "GetPartialObject"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBucketPolicy - Not implemented
|
// CreateBucketPolicy - Not implemented
|
||||||
func (storage *Storage) CreateBucketPolicy(bucket string, policy mstorage.BucketPolicy) error {
|
func (memory memoryDriver) CreateBucketPolicy(bucket string, policy drivers.BucketPolicy) error {
|
||||||
return mstorage.APINotImplemented{API: "PutBucketPolicy"}
|
return drivers.APINotImplemented{API: "PutBucketPolicy"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBucketPolicy - Not implemented
|
// GetBucketPolicy - Not implemented
|
||||||
func (storage *Storage) GetBucketPolicy(bucket string) (mstorage.BucketPolicy, error) {
|
func (memory memoryDriver) GetBucketPolicy(bucket string) (drivers.BucketPolicy, error) {
|
||||||
return mstorage.BucketPolicy{}, mstorage.APINotImplemented{API: "GetBucketPolicy"}
|
return drivers.BucketPolicy{}, drivers.APINotImplemented{API: "GetBucketPolicy"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateObject - PUT object to memory buffer
|
// CreateObject - PUT object to memory buffer
|
||||||
func (storage *Storage) CreateObject(bucket, key, contentType, md5sum string, data io.Reader) error {
|
func (memory memoryDriver) CreateObject(bucket, key, contentType, md5sum string, data io.Reader) error {
|
||||||
storage.lock.Lock()
|
memory.lock.Lock()
|
||||||
defer storage.lock.Unlock()
|
defer memory.lock.Unlock()
|
||||||
|
|
||||||
if _, ok := storage.bucketdata[bucket]; ok == false {
|
if _, ok := memory.bucketdata[bucket]; ok == false {
|
||||||
return mstorage.BucketNotFound{Bucket: bucket}
|
return drivers.BucketNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := storage.objectdata[key]; ok == true {
|
if _, ok := memory.objectdata[key]; ok == true {
|
||||||
return mstorage.ObjectExists{Bucket: bucket, Object: key}
|
return drivers.ObjectExists{Bucket: bucket, Object: key}
|
||||||
}
|
}
|
||||||
|
|
||||||
if contentType == "" {
|
if contentType == "" {
|
||||||
@ -117,7 +119,7 @@ func (storage *Storage) CreateObject(bucket, key, contentType, md5sum string, da
|
|||||||
size := bytesBuffer.Len()
|
size := bytesBuffer.Len()
|
||||||
md5SumBytes := md5.Sum(bytesBuffer.Bytes())
|
md5SumBytes := md5.Sum(bytesBuffer.Bytes())
|
||||||
md5Sum := hex.EncodeToString(md5SumBytes[:])
|
md5Sum := hex.EncodeToString(md5SumBytes[:])
|
||||||
newObject.metadata = mstorage.ObjectMetadata{
|
newObject.metadata = drivers.ObjectMetadata{
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
|
|
||||||
@ -128,27 +130,27 @@ func (storage *Storage) CreateObject(bucket, key, contentType, md5sum string, da
|
|||||||
}
|
}
|
||||||
newObject.data = bytesBuffer.Bytes()
|
newObject.data = bytesBuffer.Bytes()
|
||||||
}
|
}
|
||||||
storage.objectdata[key] = newObject
|
memory.objectdata[key] = newObject
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBucket - create bucket in memory
|
// CreateBucket - create bucket in memory
|
||||||
func (storage *Storage) CreateBucket(bucketName string) error {
|
func (memory memoryDriver) CreateBucket(bucketName string) error {
|
||||||
storage.lock.Lock()
|
memory.lock.Lock()
|
||||||
defer storage.lock.Unlock()
|
defer memory.lock.Unlock()
|
||||||
if !mstorage.IsValidBucket(bucketName) {
|
if !drivers.IsValidBucket(bucketName) {
|
||||||
return mstorage.BucketNameInvalid{Bucket: bucketName}
|
return drivers.BucketNameInvalid{Bucket: bucketName}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := storage.bucketdata[bucketName]; ok == true {
|
if _, ok := memory.bucketdata[bucketName]; ok == true {
|
||||||
return mstorage.BucketExists{Bucket: bucketName}
|
return drivers.BucketExists{Bucket: bucketName}
|
||||||
}
|
}
|
||||||
|
|
||||||
var newBucket = storedBucket{}
|
var newBucket = storedBucket{}
|
||||||
newBucket.metadata = mstorage.BucketMetadata{}
|
newBucket.metadata = drivers.BucketMetadata{}
|
||||||
newBucket.metadata.Name = bucketName
|
newBucket.metadata.Name = bucketName
|
||||||
newBucket.metadata.Created = time.Now()
|
newBucket.metadata.Created = time.Now()
|
||||||
storage.bucketdata[bucketName] = newBucket
|
memory.bucketdata[bucketName] = newBucket
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -172,13 +174,13 @@ func appendUniq(slice []string, i string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListObjects - list objects from memory
|
// ListObjects - list objects from memory
|
||||||
func (storage *Storage) ListObjects(bucket string, resources mstorage.BucketResourcesMetadata) ([]mstorage.ObjectMetadata, mstorage.BucketResourcesMetadata, error) {
|
func (memory memoryDriver) ListObjects(bucket string, resources drivers.BucketResourcesMetadata) ([]drivers.ObjectMetadata, drivers.BucketResourcesMetadata, error) {
|
||||||
if _, ok := storage.bucketdata[bucket]; ok == false {
|
if _, ok := memory.bucketdata[bucket]; ok == false {
|
||||||
return []mstorage.ObjectMetadata{}, mstorage.BucketResourcesMetadata{IsTruncated: false}, mstorage.BucketNotFound{Bucket: bucket}
|
return []drivers.ObjectMetadata{}, drivers.BucketResourcesMetadata{IsTruncated: false}, drivers.BucketNotFound{Bucket: bucket}
|
||||||
}
|
}
|
||||||
var results []mstorage.ObjectMetadata
|
var results []drivers.ObjectMetadata
|
||||||
var keys []string
|
var keys []string
|
||||||
for key := range storage.objectdata {
|
for key := range memory.objectdata {
|
||||||
switch true {
|
switch true {
|
||||||
// Prefix absent, delimit object key based on delimiter
|
// Prefix absent, delimit object key based on delimiter
|
||||||
case resources.Delimiter != "" && resources.Prefix == "":
|
case resources.Delimiter != "" && resources.Prefix == "":
|
||||||
@ -193,7 +195,6 @@ func (storage *Storage) ListObjects(bucket string, resources mstorage.BucketReso
|
|||||||
case resources.Delimiter != "" && resources.Prefix != "" && strings.HasPrefix(key, resources.Prefix):
|
case resources.Delimiter != "" && resources.Prefix != "" && strings.HasPrefix(key, resources.Prefix):
|
||||||
trimmedName := strings.TrimPrefix(key, resources.Prefix)
|
trimmedName := strings.TrimPrefix(key, resources.Prefix)
|
||||||
delimitedName := delimiter(trimmedName, resources.Delimiter)
|
delimitedName := delimiter(trimmedName, resources.Delimiter)
|
||||||
fmt.Println(trimmedName, delimitedName, key, resources.Prefix)
|
|
||||||
switch true {
|
switch true {
|
||||||
case key == resources.Prefix:
|
case key == resources.Prefix:
|
||||||
keys = appendUniq(keys, key)
|
keys = appendUniq(keys, key)
|
||||||
@ -219,9 +220,9 @@ func (storage *Storage) ListObjects(bucket string, resources mstorage.BucketReso
|
|||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if len(results) == resources.Maxkeys {
|
if len(results) == resources.Maxkeys {
|
||||||
return results, mstorage.BucketResourcesMetadata{IsTruncated: true}, nil
|
return results, drivers.BucketResourcesMetadata{IsTruncated: true}, nil
|
||||||
}
|
}
|
||||||
object := storage.objectdata[key]
|
object := memory.objectdata[key]
|
||||||
if bucket == object.metadata.Bucket {
|
if bucket == object.metadata.Bucket {
|
||||||
results = append(results, object.metadata)
|
results = append(results, object.metadata)
|
||||||
}
|
}
|
||||||
@ -230,7 +231,7 @@ func (storage *Storage) ListObjects(bucket string, resources mstorage.BucketReso
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ByBucketName is a type for sorting bucket metadata by bucket name
|
// ByBucketName is a type for sorting bucket metadata by bucket name
|
||||||
type ByBucketName []mstorage.BucketMetadata
|
type ByBucketName []drivers.BucketMetadata
|
||||||
|
|
||||||
// Len of bucket name
|
// Len of bucket name
|
||||||
func (b ByBucketName) Len() int { return len(b) }
|
func (b ByBucketName) Len() int { return len(b) }
|
||||||
@ -242,9 +243,9 @@ func (b ByBucketName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
|||||||
func (b ByBucketName) Less(i, j int) bool { return b[i].Name < b[j].Name }
|
func (b ByBucketName) Less(i, j int) bool { return b[i].Name < b[j].Name }
|
||||||
|
|
||||||
// ListBuckets - List buckets from memory
|
// ListBuckets - List buckets from memory
|
||||||
func (storage *Storage) ListBuckets() ([]mstorage.BucketMetadata, error) {
|
func (memory memoryDriver) ListBuckets() ([]drivers.BucketMetadata, error) {
|
||||||
var results []mstorage.BucketMetadata
|
var results []drivers.BucketMetadata
|
||||||
for _, bucket := range storage.bucketdata {
|
for _, bucket := range memory.bucketdata {
|
||||||
results = append(results, bucket.metadata)
|
results = append(results, bucket.metadata)
|
||||||
}
|
}
|
||||||
sort.Sort(ByBucketName(results))
|
sort.Sort(ByBucketName(results))
|
||||||
@ -252,9 +253,9 @@ func (storage *Storage) ListBuckets() ([]mstorage.BucketMetadata, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetObjectMetadata - get object metadata from memory
|
// GetObjectMetadata - get object metadata from memory
|
||||||
func (storage *Storage) GetObjectMetadata(bucket, key, prefix string) (mstorage.ObjectMetadata, error) {
|
func (memory memoryDriver) GetObjectMetadata(bucket, key, prefix string) (drivers.ObjectMetadata, error) {
|
||||||
if object, ok := storage.objectdata[key]; ok == true {
|
if object, ok := memory.objectdata[key]; ok == true {
|
||||||
return object.metadata, nil
|
return object.metadata, nil
|
||||||
}
|
}
|
||||||
return mstorage.ObjectMetadata{}, mstorage.ObjectNotFound{Bucket: bucket, Object: key}
|
return drivers.ObjectMetadata{}, drivers.ObjectNotFound{Bucket: bucket, Object: key}
|
||||||
}
|
}
|
@ -19,7 +19,7 @@ package memory
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
|
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
@ -31,10 +31,9 @@ type MySuite struct{}
|
|||||||
var _ = Suite(&MySuite{})
|
var _ = Suite(&MySuite{})
|
||||||
|
|
||||||
func (s *MySuite) TestAPISuite(c *C) {
|
func (s *MySuite) TestAPISuite(c *C) {
|
||||||
create := func() mstorage.Storage {
|
create := func() drivers.Driver {
|
||||||
_, _, store := Start()
|
_, _, store := Start()
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
drivers.APITestSuite(c, create)
|
||||||
mstorage.APITestSuite(c, create)
|
|
||||||
}
|
}
|
@ -24,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Integer to Int conversion
|
// Integer to Int conversion
|
||||||
func int2cInt(src_err_list []int) *C.int32_t {
|
func int2CInt(src_err_list []int) *C.int32_t {
|
||||||
var sizeErrInt = int(unsafe.Sizeof(src_err_list[0]))
|
var sizeErrInt = int(unsafe.Sizeof(src_err_list[0]))
|
||||||
switch sizeInt {
|
switch sizeInt {
|
||||||
case sizeErrInt:
|
case sizeErrInt:
|
||||||
|
@ -32,47 +32,47 @@ import (
|
|||||||
//
|
//
|
||||||
// Decoded data is exactly similar in length and content as the original data.
|
// Decoded data is exactly similar in length and content as the original data.
|
||||||
func (e *Encoder) Decode(chunks [][]byte, length int) ([]byte, error) {
|
func (e *Encoder) Decode(chunks [][]byte, length int) ([]byte, error) {
|
||||||
var decode_matrix *C.uint8_t
|
var decodeMatrix *C.uint8_t
|
||||||
var decode_tbls *C.uint8_t
|
var decodeTbls *C.uint8_t
|
||||||
var decode_index *C.uint32_t
|
var decodeIndex *C.uint32_t
|
||||||
var source, target **C.uint8_t
|
var source, target **C.uint8_t
|
||||||
|
|
||||||
k := e.params.K
|
k := int(e.params.K)
|
||||||
m := e.params.M
|
m := int(e.params.M)
|
||||||
n := k + m
|
n := k + m
|
||||||
if len(chunks) != int(n) {
|
if len(chunks) != n {
|
||||||
return nil, errors.New(fmt.Sprintf("chunks length must be %d", n))
|
return nil, errors.New(fmt.Sprintf("chunks length must be %d", n))
|
||||||
}
|
}
|
||||||
chunk_size := GetEncodedChunkLen(length, uint8(k))
|
chunkLen := GetEncodedBlockLen(length, uint8(k))
|
||||||
|
|
||||||
error_index := make([]int, n+1)
|
errorIndex := make([]int, n+1)
|
||||||
var err_count int = 0
|
var errCount int = 0
|
||||||
|
|
||||||
for i := range chunks {
|
for i := range chunks {
|
||||||
// Check of chunks are really null
|
// Check of chunks are really null
|
||||||
if chunks[i] == nil || len(chunks[i]) == 0 {
|
if chunks[i] == nil || len(chunks[i]) == 0 {
|
||||||
error_index[err_count] = i
|
errorIndex[errCount] = i
|
||||||
err_count++
|
errCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
error_index[err_count] = -1
|
errorIndex[errCount] = -1
|
||||||
err_count++
|
errCount++
|
||||||
|
|
||||||
// Too many missing chunks, cannot be more than parity `m`
|
// Too many missing chunks, cannot be more than parity `m`
|
||||||
if err_count-1 > int(n-k) {
|
if errCount-1 > int(n-k) {
|
||||||
return nil, errors.New("too many erasures requested, can't decode")
|
return nil, errors.New("too many erasures requested, can't decode")
|
||||||
}
|
}
|
||||||
|
|
||||||
error_index_ptr := int2cInt(error_index[:err_count])
|
errorIndex_ptr := int2CInt(errorIndex[:errCount])
|
||||||
|
|
||||||
for i := range chunks {
|
for i := range chunks {
|
||||||
if chunks[i] == nil || len(chunks[i]) == 0 {
|
if chunks[i] == nil || len(chunks[i]) == 0 {
|
||||||
chunks[i] = make([]byte, chunk_size)
|
chunks[i] = make([]byte, chunkLen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
C.minio_init_decoder(error_index_ptr, C.int(k), C.int(n), C.int(err_count-1),
|
C.minio_init_decoder(errorIndex_ptr, C.int(k), C.int(n), C.int(errCount-1),
|
||||||
e.encode_matrix, &decode_matrix, &decode_tbls, &decode_index)
|
e.encodeMatrix, &decodeMatrix, &decodeTbls, &decodeIndex)
|
||||||
|
|
||||||
pointers := make([]*byte, n)
|
pointers := make([]*byte, n)
|
||||||
for i := range chunks {
|
for i := range chunks {
|
||||||
@ -81,24 +81,24 @@ func (e *Encoder) Decode(chunks [][]byte, length int) ([]byte, error) {
|
|||||||
|
|
||||||
data := (**C.uint8_t)(unsafe.Pointer(&pointers[0]))
|
data := (**C.uint8_t)(unsafe.Pointer(&pointers[0]))
|
||||||
|
|
||||||
ret := C.minio_get_source_target(C.int(err_count-1), C.int(k), C.int(m), error_index_ptr,
|
ret := C.minio_get_source_target(C.int(errCount-1), C.int(k), C.int(m), errorIndex_ptr,
|
||||||
decode_index, data, &source, &target)
|
decodeIndex, data, &source, &target)
|
||||||
|
|
||||||
if int(ret) == -1 {
|
if int(ret) == -1 {
|
||||||
return nil, errors.New("Decoding source target failed")
|
return nil, errors.New("Decoding source target failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
C.ec_encode_data(C.int(chunk_size), C.int(k), C.int(err_count-1), decode_tbls,
|
C.ec_encode_data(C.int(chunkLen), C.int(k), C.int(errCount-1), decodeTbls,
|
||||||
source, target)
|
source, target)
|
||||||
|
|
||||||
recovered_output := make([]byte, 0, chunk_size*int(k))
|
recoveredOutput := make([]byte, 0, chunkLen*int(k))
|
||||||
for i := 0; i < int(k); i++ {
|
for i := 0; i < int(k); i++ {
|
||||||
recovered_output = append(recovered_output, chunks[i]...)
|
recoveredOutput = append(recoveredOutput, chunks[i]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO cache this if necessary
|
// TODO cache this if necessary
|
||||||
e.decode_matrix = decode_matrix
|
e.decodeMatrix = decodeMatrix
|
||||||
e.decode_tbls = decode_tbls
|
e.decodeTbls = decodeTbls
|
||||||
|
|
||||||
return recovered_output[:length], nil
|
return recoveredOutput[:length], nil
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,12 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Technique int
|
type Technique uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Vandermonde Technique = iota
|
Vandermonde Technique = iota
|
||||||
Cauchy
|
Cauchy
|
||||||
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -52,10 +53,10 @@ type EncoderParams struct {
|
|||||||
// Encoder is an object used to encode and decode data.
|
// Encoder is an object used to encode and decode data.
|
||||||
type Encoder struct {
|
type Encoder struct {
|
||||||
params *EncoderParams
|
params *EncoderParams
|
||||||
encode_matrix,
|
encodeMatrix,
|
||||||
encode_tbls,
|
encodeTbls,
|
||||||
decode_matrix,
|
decodeMatrix,
|
||||||
decode_tbls *C.uint8_t
|
decodeTbls *C.uint8_t
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseEncoderParams creates an EncoderParams object.
|
// ParseEncoderParams creates an EncoderParams object.
|
||||||
@ -94,30 +95,30 @@ func ParseEncoderParams(k, m uint8, technique Technique) (*EncoderParams, error)
|
|||||||
|
|
||||||
// NewEncoder creates an encoder object with a given set of parameters.
|
// NewEncoder creates an encoder object with a given set of parameters.
|
||||||
func NewEncoder(ep *EncoderParams) *Encoder {
|
func NewEncoder(ep *EncoderParams) *Encoder {
|
||||||
var encode_matrix *C.uint8_t
|
var k = C.int(ep.K)
|
||||||
var encode_tbls *C.uint8_t
|
var m = C.int(ep.M)
|
||||||
|
|
||||||
k := C.int(ep.K)
|
var encodeMatrix *C.uint8_t
|
||||||
m := C.int(ep.M)
|
var encodeTbls *C.uint8_t
|
||||||
|
|
||||||
C.minio_init_encoder(C.int(ep.Technique), k, m, &encode_matrix,
|
C.minio_init_encoder(C.int(ep.Technique), k, m, &encodeMatrix,
|
||||||
&encode_tbls)
|
&encodeTbls)
|
||||||
|
|
||||||
return &Encoder{
|
return &Encoder{
|
||||||
params: ep,
|
params: ep,
|
||||||
encode_matrix: encode_matrix,
|
encodeMatrix: encodeMatrix,
|
||||||
encode_tbls: encode_tbls,
|
encodeTbls: encodeTbls,
|
||||||
decode_matrix: nil,
|
decodeMatrix: nil,
|
||||||
decode_tbls: nil,
|
decodeTbls: nil,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetEncodedLen(inputLen int, k, m uint8) (outputLen int) {
|
func GetEncodedBlocksLen(inputLen int, k, m uint8) (outputLen int) {
|
||||||
outputLen = GetEncodedChunkLen(inputLen, k) * int(k+m)
|
outputLen = GetEncodedBlockLen(inputLen, k) * int(k+m)
|
||||||
return outputLen
|
return outputLen
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetEncodedChunkLen(inputLen int, k uint8) (outputChunkLen int) {
|
func GetEncodedBlockLen(inputLen int, k uint8) (encodedOutputLen int) {
|
||||||
alignment := int(k) * SIMDAlign
|
alignment := int(k) * SIMDAlign
|
||||||
remainder := inputLen % alignment
|
remainder := inputLen % alignment
|
||||||
|
|
||||||
@ -125,55 +126,66 @@ func GetEncodedChunkLen(inputLen int, k uint8) (outputChunkLen int) {
|
|||||||
if remainder != 0 {
|
if remainder != 0 {
|
||||||
paddedInputLen = inputLen + (alignment - remainder)
|
paddedInputLen = inputLen + (alignment - remainder)
|
||||||
}
|
}
|
||||||
outputChunkLen = paddedInputLen / int(k)
|
encodedOutputLen = paddedInputLen / int(k)
|
||||||
return outputChunkLen
|
return encodedOutputLen
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode encodes a block of data. The input is the original data. The output
|
// Encode erasure codes a block of data in "k" data blocks and "m" parity blocks.
|
||||||
// is a 2 tuple containing (k + m) chunks of erasure encoded data and the
|
// Output is [k+m][]blocks of data and parity slices.
|
||||||
// length of the original object.
|
func (e *Encoder) Encode(inputData []byte) (encodedBlocks [][]byte, err error) {
|
||||||
func (e *Encoder) Encode(input []byte) ([][]byte, error) {
|
k := int(e.params.K) // "k" data blocks
|
||||||
inputLen := len(input)
|
m := int(e.params.M) // "m" parity blocks
|
||||||
k := C.int(e.params.K)
|
n := k + m // "n" total encoded blocks
|
||||||
m := C.int(e.params.M)
|
|
||||||
n := k + m
|
|
||||||
|
|
||||||
chunkLen := GetEncodedChunkLen(inputLen, e.params.K)
|
// Length of a single encoded chunk.
|
||||||
encodedDataLen := chunkLen * int(k)
|
// Total number of encoded chunks = "k" data + "m" parity blocks
|
||||||
paddedDataLen := int(encodedDataLen) - inputLen
|
encodedBlockLen := GetEncodedBlockLen(len(inputData), uint8(k))
|
||||||
|
|
||||||
if paddedDataLen > 0 {
|
// Length of total number of "k" data chunks
|
||||||
s := make([]byte, paddedDataLen)
|
encodedDataBlocksLen := encodedBlockLen * k
|
||||||
|
|
||||||
|
// Length of extra padding required for the data blocks.
|
||||||
|
encodedDataBlocksPadLen := encodedDataBlocksLen - len(inputData)
|
||||||
|
|
||||||
|
// Extend inputData buffer to accommodate coded data blocks if necesssary
|
||||||
|
if encodedDataBlocksPadLen > 0 {
|
||||||
|
padding := make([]byte, encodedDataBlocksPadLen)
|
||||||
// Expand with new padded blocks to the byte array
|
// Expand with new padded blocks to the byte array
|
||||||
input = append(input, s...)
|
inputData = append(inputData, padding...)
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedParityLen := chunkLen * int(e.params.M)
|
// Extend inputData buffer to accommodate coded parity blocks
|
||||||
c := make([]byte, encodedParityLen)
|
if true { // create a temporary scope to trigger garbage collect
|
||||||
input = append(input, c...)
|
encodedParityBlocksLen := encodedBlockLen * m
|
||||||
|
parityBlocks := make([]byte, encodedParityBlocksLen)
|
||||||
// encodedOutLen := encodedDataLen + encodedParityLen
|
inputData = append(inputData, parityBlocks...)
|
||||||
|
|
||||||
// Allocate chunks
|
|
||||||
chunks := make([][]byte, k+m)
|
|
||||||
pointers := make([]*byte, k+m)
|
|
||||||
|
|
||||||
var i int
|
|
||||||
// Add data blocks to chunks
|
|
||||||
for i = 0; i < int(k); i++ {
|
|
||||||
chunks[i] = input[i*chunkLen : (i+1)*chunkLen]
|
|
||||||
pointers[i] = &chunks[i][0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for i = int(k); i < int(n); i++ {
|
// Allocate memory to the "encoded blocks" return buffer
|
||||||
chunks[i] = make([]byte, chunkLen)
|
encodedBlocks = make([][]byte, n) // Return buffer
|
||||||
pointers[i] = &chunks[i][0]
|
|
||||||
|
// Nessary to bridge Go to the C world. C requires 2D arry of pointers to
|
||||||
|
// byte array. "encodedBlocks" is a 2D slice.
|
||||||
|
pointersToEncodedBlock := make([]*byte, n) // Pointers to encoded blocks.
|
||||||
|
|
||||||
|
// Copy data block slices to encoded block buffer
|
||||||
|
for i := 0; i < k; i++ {
|
||||||
|
encodedBlocks[i] = inputData[i*encodedBlockLen : (i+1)*encodedBlockLen]
|
||||||
|
pointersToEncodedBlock[i] = &encodedBlocks[i][0]
|
||||||
}
|
}
|
||||||
|
|
||||||
data := (**C.uint8_t)(unsafe.Pointer(&pointers[:k][0]))
|
// Copy erasure block slices to encoded block buffer
|
||||||
coding := (**C.uint8_t)(unsafe.Pointer(&pointers[k:][0]))
|
for i := k; i < n; i++ {
|
||||||
|
encodedBlocks[i] = make([]byte, encodedBlockLen)
|
||||||
|
pointersToEncodedBlock[i] = &encodedBlocks[i][0]
|
||||||
|
}
|
||||||
|
|
||||||
C.ec_encode_data(C.int(chunkLen), k, m, e.encode_tbls, data,
|
// Erasure code the data into K data blocks and M parity
|
||||||
coding)
|
// blocks. Only the parity blocks are filled. Data blocks remain
|
||||||
return chunks, nil
|
// intact.
|
||||||
|
C.ec_encode_data(C.int(encodedBlockLen), C.int(k), C.int(m), e.encodeTbls,
|
||||||
|
(**C.uint8_t)(unsafe.Pointer(&pointersToEncodedBlock[:k][0])), // Pointers to data blocks
|
||||||
|
(**C.uint8_t)(unsafe.Pointer(&pointersToEncodedBlock[k:][0]))) // Pointers to parity blocks
|
||||||
|
|
||||||
|
return encodedBlocks, nil
|
||||||
}
|
}
|
||||||
|
@ -24,11 +24,11 @@ import (
|
|||||||
|
|
||||||
"github.com/minio-io/minio/pkg/api"
|
"github.com/minio-io/minio/pkg/api"
|
||||||
"github.com/minio-io/minio/pkg/api/web"
|
"github.com/minio-io/minio/pkg/api/web"
|
||||||
|
"github.com/minio-io/minio/pkg/drivers"
|
||||||
|
"github.com/minio-io/minio/pkg/drivers/donut"
|
||||||
|
"github.com/minio-io/minio/pkg/drivers/file"
|
||||||
|
"github.com/minio-io/minio/pkg/drivers/memory"
|
||||||
"github.com/minio-io/minio/pkg/server/httpserver"
|
"github.com/minio-io/minio/pkg/server/httpserver"
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
|
||||||
"github.com/minio-io/minio/pkg/storage/donutstorage"
|
|
||||||
"github.com/minio-io/minio/pkg/storage/file"
|
|
||||||
"github.com/minio-io/minio/pkg/storage/memory"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config - http server parameters
|
// Config - http server parameters
|
||||||
@ -41,9 +41,9 @@ type Config struct {
|
|||||||
APIType interface{}
|
APIType interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MinioAPI - storage type donut, file, memory
|
// MinioAPI - driver type donut, file, memory
|
||||||
type MinioAPI struct {
|
type MinioAPI struct {
|
||||||
StorageType StorageType
|
DriverType DriverType
|
||||||
}
|
}
|
||||||
|
|
||||||
// Web - web related
|
// Web - web related
|
||||||
@ -51,12 +51,12 @@ type Web struct {
|
|||||||
Websocket bool // TODO
|
Websocket bool // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageType - different storage types supported by minio
|
// DriverType - different driver types supported by minio
|
||||||
type StorageType int
|
type DriverType int
|
||||||
|
|
||||||
// Storage types
|
// Driver types
|
||||||
const (
|
const (
|
||||||
Memory = iota
|
Memory DriverType = iota
|
||||||
File
|
File
|
||||||
Donut
|
Donut
|
||||||
)
|
)
|
||||||
@ -71,7 +71,7 @@ func getHTTPChannels(configs []Config) (ctrlChans []chan<- string, statusChans [
|
|||||||
case MinioAPI:
|
case MinioAPI:
|
||||||
{
|
{
|
||||||
// configure web server
|
// configure web server
|
||||||
var storage mstorage.Storage
|
var driver drivers.Driver
|
||||||
var httpConfig = httpserver.Config{}
|
var httpConfig = httpserver.Config{}
|
||||||
httpConfig.Address = config.Address
|
httpConfig.Address = config.Address
|
||||||
httpConfig.Websocket = false
|
httpConfig.Websocket = false
|
||||||
@ -84,9 +84,9 @@ func getHTTPChannels(configs []Config) (ctrlChans []chan<- string, statusChans [
|
|||||||
httpConfig.KeyFile = config.KeyFile
|
httpConfig.KeyFile = config.KeyFile
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrlChans, statusChans, storage = getStorageChannels(k.StorageType)
|
ctrlChans, statusChans, driver = getDriverChannels(k.DriverType)
|
||||||
// start minio api in a web server, pass storage driver into it
|
// start minio api in a web server, pass driver driver into it
|
||||||
ctrlChan, statusChan, _ = httpserver.Start(api.HTTPHandler(config.Domain, storage), httpConfig)
|
ctrlChan, statusChan, _ = httpserver.Start(api.HTTPHandler(config.Domain, driver), httpConfig)
|
||||||
|
|
||||||
ctrlChans = append(ctrlChans, ctrlChan)
|
ctrlChans = append(ctrlChans, ctrlChan)
|
||||||
statusChans = append(statusChans, statusChan)
|
statusChans = append(statusChans, statusChan)
|
||||||
@ -113,50 +113,50 @@ func getHTTPChannels(configs []Config) (ctrlChans []chan<- string, statusChans [
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStorageChannels(storageType StorageType) (ctrlChans []chan<- string, statusChans []<-chan error, storage mstorage.Storage) {
|
func getDriverChannels(driverType DriverType) (ctrlChans []chan<- string, statusChans []<-chan error, driver drivers.Driver) {
|
||||||
// a pair of control channels, we use these primarily to add to the lists above
|
// a pair of control channels, we use these primarily to add to the lists above
|
||||||
var ctrlChan chan<- string
|
var ctrlChan chan<- string
|
||||||
var statusChan <-chan error
|
var statusChan <-chan error
|
||||||
|
|
||||||
// instantiate storage
|
// instantiate driver
|
||||||
// preconditions:
|
// preconditions:
|
||||||
// - storage type specified
|
// - driver type specified
|
||||||
// - any configuration for storage is populated
|
// - any configuration for driver is populated
|
||||||
// postconditions:
|
// postconditions:
|
||||||
// - storage driver is initialized
|
// - driver driver is initialized
|
||||||
// - ctrlChans has channel to communicate to storage
|
// - ctrlChans has channel to communicate to driver
|
||||||
// - statusChans has channel for messages coming from storage
|
// - statusChans has channel for messages coming from driver
|
||||||
switch {
|
switch {
|
||||||
case storageType == Memory:
|
case driverType == Memory:
|
||||||
{
|
{
|
||||||
ctrlChan, statusChan, storage = memory.Start()
|
ctrlChan, statusChan, driver = memory.Start()
|
||||||
ctrlChans = append(ctrlChans, ctrlChan)
|
ctrlChans = append(ctrlChans, ctrlChan)
|
||||||
statusChans = append(statusChans, statusChan)
|
statusChans = append(statusChans, statusChan)
|
||||||
}
|
}
|
||||||
case storageType == File:
|
case driverType == File:
|
||||||
{
|
{
|
||||||
u, err := user.Current()
|
u, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
root := path.Join(u.HomeDir, "minio-storage", "file")
|
root := path.Join(u.HomeDir, "minio-storage", "file")
|
||||||
ctrlChan, statusChan, storage = file.Start(root)
|
ctrlChan, statusChan, driver = file.Start(root)
|
||||||
ctrlChans = append(ctrlChans, ctrlChan)
|
ctrlChans = append(ctrlChans, ctrlChan)
|
||||||
statusChans = append(statusChans, statusChan)
|
statusChans = append(statusChans, statusChan)
|
||||||
}
|
}
|
||||||
case storageType == Donut:
|
case driverType == Donut:
|
||||||
{
|
{
|
||||||
u, err := user.Current()
|
u, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
root := path.Join(u.HomeDir, "minio-storage", "donut")
|
root := path.Join(u.HomeDir, "minio-driver", "donut")
|
||||||
ctrlChan, statusChan, storage = donutstorage.Start(root)
|
ctrlChan, statusChan, driver = donut.Start(root)
|
||||||
ctrlChans = append(ctrlChans, ctrlChan)
|
ctrlChans = append(ctrlChans, ctrlChan)
|
||||||
statusChans = append(statusChans, statusChan)
|
statusChans = append(statusChans, statusChan)
|
||||||
}
|
}
|
||||||
default: // should never happen
|
default: // should never happen
|
||||||
log.Fatal("No storage driver found")
|
log.Fatal("No driver found")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bucketDriver struct {
|
type donutBucket struct {
|
||||||
nodes []string
|
nodes []string
|
||||||
|
objects map[string][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bucketDriver) GetNodes() ([]string, error) {
|
// GetNodes - get list of associated nodes for a given bucket
|
||||||
|
func (b donutBucket) GetNodes() ([]string, error) {
|
||||||
var nodes []string
|
var nodes []string
|
||||||
for _, node := range b.nodes {
|
for _, node := range b.nodes {
|
||||||
nodes = append(nodes, node)
|
nodes = append(nodes, node)
|
||||||
@ -18,7 +20,7 @@ func (b bucketDriver) GetNodes() ([]string, error) {
|
|||||||
return nodes, nil
|
return nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bucketDriver) AddNode(nodeID, bucketID string) error {
|
func (b donutBucket) AddNode(nodeID, bucketID string) error {
|
||||||
tokens := strings.Split(bucketID, ":")
|
tokens := strings.Split(bucketID, ":")
|
||||||
if len(tokens) != 3 {
|
if len(tokens) != 3 {
|
||||||
return errors.New("Bucket ID malformed: " + bucketID)
|
return errors.New("Bucket ID malformed: " + bucketID)
|
@ -1,51 +1,181 @@
|
|||||||
package donut
|
package donut
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// INTERFACES
|
type donut struct {
|
||||||
|
buckets map[string]Bucket
|
||||||
// Donut interface
|
nodes map[string]Node
|
||||||
type Donut interface {
|
|
||||||
CreateBucket(bucket string) error
|
|
||||||
GetObject(bucket, object string) (io.ReadCloser, error)
|
|
||||||
GetObjectMetadata(bucket, object string) (map[string]string, error)
|
|
||||||
GetObjectWriter(bucket, object string) (ObjectWriter, error)
|
|
||||||
ListBuckets() ([]string, error)
|
|
||||||
ListObjects(bucket string) ([]string, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bucket interface
|
// NewDonut - instantiate new donut driver
|
||||||
type Bucket interface {
|
func NewDonut(root string) (Donut, error) {
|
||||||
GetNodes() ([]string, error)
|
nodes := make(map[string]Node)
|
||||||
AddNode(nodeID, bucketID string) error
|
nodes["localhost"] = &localDirectoryNode{root: root}
|
||||||
|
driver := &donut{
|
||||||
|
buckets: make(map[string]Bucket),
|
||||||
|
nodes: nodes,
|
||||||
|
}
|
||||||
|
for nodeID, node := range nodes {
|
||||||
|
bucketIDs, err := node.GetBuckets()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, bucketID := range bucketIDs {
|
||||||
|
tokens := strings.Split(bucketID, ":")
|
||||||
|
if _, ok := driver.buckets[tokens[0]]; ok {
|
||||||
|
// found bucket, skip creating
|
||||||
|
} else {
|
||||||
|
bucket := donutBucket{
|
||||||
|
nodes: make([]string, 16),
|
||||||
|
}
|
||||||
|
// TODO catch errors
|
||||||
|
driver.buckets[tokens[0]] = bucket
|
||||||
|
}
|
||||||
|
if err = driver.buckets[tokens[0]].AddNode(nodeID, bucketID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return driver, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node interface
|
// CreateBucket - create a new bucket
|
||||||
type Node interface {
|
func (d donut) CreateBucket(bucketName string) error {
|
||||||
CreateBucket(bucket string) error
|
if _, ok := d.buckets[bucketName]; ok == false {
|
||||||
GetBuckets() ([]string, error)
|
bucketName = strings.TrimSpace(bucketName)
|
||||||
GetDonutMetadata(bucket, object string) (map[string]string, error)
|
if bucketName == "" {
|
||||||
GetMetadata(bucket, object string) (map[string]string, error)
|
return errors.New("Cannot create bucket with no name")
|
||||||
GetReader(bucket, object string) (io.ReadCloser, error)
|
}
|
||||||
GetWriter(bucket, object string) (Writer, error)
|
// assign nodes
|
||||||
ListObjects(bucket string) ([]string, error)
|
// TODO assign other nodes
|
||||||
|
nodes := make([]string, 16)
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
nodes[i] = "localhost"
|
||||||
|
if node, ok := d.nodes["localhost"]; ok {
|
||||||
|
node.CreateBucket(bucketName + ":0:" + strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bucket := donutBucket{
|
||||||
|
nodes: nodes,
|
||||||
|
}
|
||||||
|
d.buckets[bucketName] = bucket
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("Bucket exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObjectWriter interface
|
// ListBuckets - list all buckets
|
||||||
type ObjectWriter interface {
|
func (d donut) ListBuckets() ([]string, error) {
|
||||||
Close() error
|
var buckets []string
|
||||||
CloseWithError(error) error
|
for bucket := range d.buckets {
|
||||||
GetMetadata() (map[string]string, error)
|
buckets = append(buckets, bucket)
|
||||||
SetMetadata(map[string]string) error
|
}
|
||||||
Write([]byte) (int, error)
|
sort.Strings(buckets)
|
||||||
|
return buckets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writer interface
|
// GetObjectWriter - get a new writer interface for a new object
|
||||||
type Writer interface {
|
func (d donut) GetObjectWriter(bucketName, objectName string) (ObjectWriter, error) {
|
||||||
ObjectWriter
|
if bucket, ok := d.buckets[bucketName]; ok == true {
|
||||||
|
writers := make([]Writer, 16)
|
||||||
GetDonutMetadata() (map[string]string, error)
|
nodes, err := bucket.GetNodes()
|
||||||
SetDonutMetadata(map[string]string) error
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i, nodeID := range nodes {
|
||||||
|
if node, ok := d.nodes[nodeID]; ok == true {
|
||||||
|
writer, err := node.GetWriter(bucketName+":0:"+strconv.Itoa(i), objectName)
|
||||||
|
if err != nil {
|
||||||
|
for _, writerToClose := range writers {
|
||||||
|
if writerToClose != nil {
|
||||||
|
writerToClose.CloseWithError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
writers[i] = writer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newErasureWriter(writers), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("Bucket not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetObjectReader - get a new reader interface for a new object
|
||||||
|
func (d donut) GetObjectReader(bucketName, objectName string) (io.ReadCloser, error) {
|
||||||
|
r, w := io.Pipe()
|
||||||
|
if bucket, ok := d.buckets[bucketName]; ok == true {
|
||||||
|
readers := make([]io.ReadCloser, 16)
|
||||||
|
nodes, err := bucket.GetNodes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var metadata map[string]string
|
||||||
|
for i, nodeID := range nodes {
|
||||||
|
if node, ok := d.nodes[nodeID]; ok == true {
|
||||||
|
bucketID := bucketName + ":0:" + strconv.Itoa(i)
|
||||||
|
reader, err := node.GetReader(bucketID, objectName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
readers[i] = reader
|
||||||
|
if metadata == nil {
|
||||||
|
metadata, err = node.GetDonutMetadata(bucketID, objectName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go erasureReader(readers, metadata, w)
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("Bucket not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetObjectMetadata returns metadata for a given object in a bucket
|
||||||
|
func (d donut) GetObjectMetadata(bucketName, object string) (map[string]string, error) {
|
||||||
|
if bucket, ok := d.buckets[bucketName]; ok {
|
||||||
|
nodes, err := bucket.GetNodes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if node, ok := d.nodes[nodes[0]]; ok {
|
||||||
|
bucketID := bucketName + ":0:0"
|
||||||
|
metadata, err := node.GetMetadata(bucketID, object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
donutMetadata, err := node.GetDonutMetadata(bucketID, object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
metadata["sys.created"] = donutMetadata["created"]
|
||||||
|
metadata["sys.md5"] = donutMetadata["md5"]
|
||||||
|
metadata["sys.size"] = donutMetadata["size"]
|
||||||
|
return metadata, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("Cannot connect to node: " + nodes[0])
|
||||||
|
}
|
||||||
|
return nil, errors.New("Bucket not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListObjects - list all the available objects in a bucket
|
||||||
|
func (d donut) ListObjects(bucketName string) ([]string, error) {
|
||||||
|
if bucket, ok := d.buckets[bucketName]; ok {
|
||||||
|
nodes, err := bucket.GetNodes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if node, ok := d.nodes[nodes[0]]; ok {
|
||||||
|
return node.ListObjects(bucketName + ":0:0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("Bucket not found")
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package donut
|
package donut
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
|
||||||
|
|
||||||
"bytes"
|
"bytes"
|
||||||
. "gopkg.in/check.v1"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
. "gopkg.in/check.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test(t *testing.T) { TestingT(t) }
|
func Test(t *testing.T) { TestingT(t) }
|
||||||
@ -21,7 +21,7 @@ func (s *MySuite) TestEmptyBucket(c *C) {
|
|||||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
donut, err := NewDonutDriver(root)
|
donut, err := NewDonut(root)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
// check buckets are empty
|
// check buckets are empty
|
||||||
@ -34,7 +34,7 @@ func (s *MySuite) TestBucketWithoutNameFails(c *C) {
|
|||||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
donut, err := NewDonutDriver(root)
|
donut, err := NewDonut(root)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
// fail to create new bucket without a name
|
// fail to create new bucket without a name
|
||||||
err = donut.CreateBucket("")
|
err = donut.CreateBucket("")
|
||||||
@ -48,7 +48,7 @@ func (s *MySuite) TestCreateBucketAndList(c *C) {
|
|||||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
donut, err := NewDonutDriver(root)
|
donut, err := NewDonut(root)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
// create bucket
|
// create bucket
|
||||||
err = donut.CreateBucket("foo")
|
err = donut.CreateBucket("foo")
|
||||||
@ -64,7 +64,7 @@ func (s *MySuite) TestCreateBucketWithSameNameFails(c *C) {
|
|||||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
donut, err := NewDonutDriver(root)
|
donut, err := NewDonut(root)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
err = donut.CreateBucket("foo")
|
err = donut.CreateBucket("foo")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
@ -77,7 +77,7 @@ func (s *MySuite) TestCreateMultipleBucketsAndList(c *C) {
|
|||||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
donut, err := NewDonutDriver(root)
|
donut, err := NewDonut(root)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
// add a second bucket
|
// add a second bucket
|
||||||
err = donut.CreateBucket("foo")
|
err = donut.CreateBucket("foo")
|
||||||
@ -102,7 +102,7 @@ func (s *MySuite) TestNewObjectFailsWithoutBucket(c *C) {
|
|||||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
donut, err := NewDonutDriver(root)
|
donut, err := NewDonut(root)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
writer, err := donut.GetObjectWriter("foo", "obj")
|
writer, err := donut.GetObjectWriter("foo", "obj")
|
||||||
@ -114,7 +114,7 @@ func (s *MySuite) TestNewObjectFailsWithEmptyName(c *C) {
|
|||||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
donut, err := NewDonutDriver(root)
|
donut, err := NewDonut(root)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
writer, err := donut.GetObjectWriter("foo", "")
|
writer, err := donut.GetObjectWriter("foo", "")
|
||||||
@ -130,7 +130,7 @@ func (s *MySuite) TestNewObjectCanBeWritten(c *C) {
|
|||||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
donut, err := NewDonutDriver(root)
|
donut, err := NewDonut(root)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
err = donut.CreateBucket("foo")
|
err = donut.CreateBucket("foo")
|
||||||
@ -161,7 +161,7 @@ func (s *MySuite) TestNewObjectCanBeWritten(c *C) {
|
|||||||
|
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
reader, err := donut.GetObject("foo", "obj")
|
reader, err := donut.GetObjectReader("foo", "obj")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
var actualData bytes.Buffer
|
var actualData bytes.Buffer
|
||||||
@ -183,7 +183,7 @@ func (s *MySuite) TestMultipleNewObjects(c *C) {
|
|||||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
donut, err := NewDonutDriver(root)
|
donut, err := NewDonut(root)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
c.Assert(donut.CreateBucket("foo"), IsNil)
|
c.Assert(donut.CreateBucket("foo"), IsNil)
|
||||||
@ -199,7 +199,7 @@ func (s *MySuite) TestMultipleNewObjects(c *C) {
|
|||||||
|
|
||||||
// c.Skip("not complete")
|
// c.Skip("not complete")
|
||||||
|
|
||||||
reader, err := donut.GetObject("foo", "obj1")
|
reader, err := donut.GetObjectReader("foo", "obj1")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
var readerBuffer1 bytes.Buffer
|
var readerBuffer1 bytes.Buffer
|
||||||
_, err = io.Copy(&readerBuffer1, reader)
|
_, err = io.Copy(&readerBuffer1, reader)
|
||||||
@ -207,7 +207,7 @@ func (s *MySuite) TestMultipleNewObjects(c *C) {
|
|||||||
// c.Skip("Not Implemented")
|
// c.Skip("Not Implemented")
|
||||||
c.Assert(readerBuffer1.Bytes(), DeepEquals, []byte("one"))
|
c.Assert(readerBuffer1.Bytes(), DeepEquals, []byte("one"))
|
||||||
|
|
||||||
reader, err = donut.GetObject("foo", "obj2")
|
reader, err = donut.GetObjectReader("foo", "obj2")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
var readerBuffer2 bytes.Buffer
|
var readerBuffer2 bytes.Buffer
|
||||||
_, err = io.Copy(&readerBuffer2, reader)
|
_, err = io.Copy(&readerBuffer2, reader)
|
||||||
@ -224,7 +224,7 @@ func (s *MySuite) TestSysPrefixShouldFail(c *C) {
|
|||||||
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
root, err := ioutil.TempDir(os.TempDir(), "donut-")
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
defer os.RemoveAll(root)
|
defer os.RemoveAll(root)
|
||||||
donut, err := NewDonutDriver(root)
|
donut, err := NewDonut(root)
|
||||||
c.Assert(err, IsNil)
|
c.Assert(err, IsNil)
|
||||||
|
|
||||||
c.Assert(donut.CreateBucket("foo"), IsNil)
|
c.Assert(donut.CreateBucket("foo"), IsNil)
|
@ -1,176 +0,0 @@
|
|||||||
package donut
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type donutDriver struct {
|
|
||||||
buckets map[string]Bucket
|
|
||||||
nodes map[string]Node
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDonutDriver - instantiate new donut driver
|
|
||||||
func NewDonutDriver(root string) (Donut, error) {
|
|
||||||
nodes := make(map[string]Node)
|
|
||||||
nodes["localhost"] = &localDirectoryNode{root: root}
|
|
||||||
driver := &donutDriver{
|
|
||||||
buckets: make(map[string]Bucket),
|
|
||||||
nodes: nodes,
|
|
||||||
}
|
|
||||||
for nodeID, node := range nodes {
|
|
||||||
bucketIDs, err := node.GetBuckets()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, bucketID := range bucketIDs {
|
|
||||||
tokens := strings.Split(bucketID, ":")
|
|
||||||
if _, ok := driver.buckets[tokens[0]]; ok {
|
|
||||||
// found bucket, skip creating
|
|
||||||
} else {
|
|
||||||
bucket := bucketDriver{
|
|
||||||
nodes: make([]string, 16),
|
|
||||||
}
|
|
||||||
// TODO catch errors
|
|
||||||
driver.buckets[tokens[0]] = bucket
|
|
||||||
}
|
|
||||||
if err = driver.buckets[tokens[0]].AddNode(nodeID, bucketID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return driver, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver donutDriver) CreateBucket(bucketName string) error {
|
|
||||||
if _, ok := driver.buckets[bucketName]; ok == false {
|
|
||||||
bucketName = strings.TrimSpace(bucketName)
|
|
||||||
if bucketName == "" {
|
|
||||||
return errors.New("Cannot create bucket with no name")
|
|
||||||
}
|
|
||||||
// assign nodes
|
|
||||||
// TODO assign other nodes
|
|
||||||
nodes := make([]string, 16)
|
|
||||||
for i := 0; i < 16; i++ {
|
|
||||||
nodes[i] = "localhost"
|
|
||||||
if node, ok := driver.nodes["localhost"]; ok {
|
|
||||||
node.CreateBucket(bucketName + ":0:" + strconv.Itoa(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bucket := bucketDriver{
|
|
||||||
nodes: nodes,
|
|
||||||
}
|
|
||||||
driver.buckets[bucketName] = bucket
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("Bucket exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver donutDriver) ListBuckets() ([]string, error) {
|
|
||||||
var buckets []string
|
|
||||||
for bucket := range driver.buckets {
|
|
||||||
buckets = append(buckets, bucket)
|
|
||||||
}
|
|
||||||
sort.Strings(buckets)
|
|
||||||
return buckets, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver donutDriver) GetObjectWriter(bucketName, objectName string) (ObjectWriter, error) {
|
|
||||||
if bucket, ok := driver.buckets[bucketName]; ok == true {
|
|
||||||
writers := make([]Writer, 16)
|
|
||||||
nodes, err := bucket.GetNodes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for i, nodeID := range nodes {
|
|
||||||
if node, ok := driver.nodes[nodeID]; ok == true {
|
|
||||||
writer, err := node.GetWriter(bucketName+":0:"+strconv.Itoa(i), objectName)
|
|
||||||
if err != nil {
|
|
||||||
for _, writerToClose := range writers {
|
|
||||||
if writerToClose != nil {
|
|
||||||
writerToClose.CloseWithError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
writers[i] = writer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newErasureWriter(writers), nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("Bucket not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver donutDriver) GetObject(bucketName, objectName string) (io.ReadCloser, error) {
|
|
||||||
r, w := io.Pipe()
|
|
||||||
if bucket, ok := driver.buckets[bucketName]; ok == true {
|
|
||||||
readers := make([]io.ReadCloser, 16)
|
|
||||||
nodes, err := bucket.GetNodes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var metadata map[string]string
|
|
||||||
for i, nodeID := range nodes {
|
|
||||||
if node, ok := driver.nodes[nodeID]; ok == true {
|
|
||||||
bucketID := bucketName + ":0:" + strconv.Itoa(i)
|
|
||||||
reader, err := node.GetReader(bucketID, objectName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
readers[i] = reader
|
|
||||||
if metadata == nil {
|
|
||||||
metadata, err = node.GetDonutMetadata(bucketID, objectName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
go erasureReader(readers, metadata, w)
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("Bucket not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetObjectMetadata returns metadata for a given object in a bucket
|
|
||||||
func (driver donutDriver) GetObjectMetadata(bucketName, object string) (map[string]string, error) {
|
|
||||||
if bucket, ok := driver.buckets[bucketName]; ok {
|
|
||||||
nodes, err := bucket.GetNodes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if node, ok := driver.nodes[nodes[0]]; ok {
|
|
||||||
bucketID := bucketName + ":0:0"
|
|
||||||
metadata, err := node.GetMetadata(bucketID, object)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
donutMetadata, err := node.GetDonutMetadata(bucketID, object)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
metadata["sys.created"] = donutMetadata["created"]
|
|
||||||
metadata["sys.md5"] = donutMetadata["md5"]
|
|
||||||
metadata["sys.size"] = donutMetadata["size"]
|
|
||||||
return metadata, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("Cannot connect to node: " + nodes[0])
|
|
||||||
}
|
|
||||||
return nil, errors.New("Bucket not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver donutDriver) ListObjects(bucketName string) ([]string, error) {
|
|
||||||
if bucket, ok := driver.buckets[bucketName]; ok {
|
|
||||||
nodes, err := bucket.GetNodes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if node, ok := driver.nodes[nodes[0]]; ok {
|
|
||||||
return node.ListObjects(bucketName + ":0:0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("Bucket not found")
|
|
||||||
}
|
|
@ -2,29 +2,70 @@ package donut
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"github.com/minio-io/minio/pkg/encoding/erasure"
|
"github.com/minio-io/minio/pkg/encoding/erasure"
|
||||||
"github.com/minio-io/minio/pkg/utils/split"
|
"github.com/minio-io/minio/pkg/utils/split"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// getErasureTechnique - convert technique string into Technique type
|
||||||
|
func getErasureTechnique(technique string) (erasure.Technique, error) {
|
||||||
|
switch true {
|
||||||
|
case technique == "Cauchy":
|
||||||
|
return erasure.Cauchy, nil
|
||||||
|
case technique == "Vandermonde":
|
||||||
|
return erasure.Cauchy, nil
|
||||||
|
default:
|
||||||
|
return erasure.None, errors.New("Invalid erasure technique")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// erasureReader - returns aligned streaming reads over a PipeWriter
|
||||||
func erasureReader(readers []io.ReadCloser, donutMetadata map[string]string, writer *io.PipeWriter) {
|
func erasureReader(readers []io.ReadCloser, donutMetadata map[string]string, writer *io.PipeWriter) {
|
||||||
// TODO handle errors
|
totalChunks, err := strconv.Atoi(donutMetadata["chunkCount"])
|
||||||
totalChunks, _ := strconv.Atoi(donutMetadata["chunkCount"])
|
if err != nil {
|
||||||
totalLeft, _ := strconv.Atoi(donutMetadata["size"])
|
writer.CloseWithError(err)
|
||||||
blockSize, _ := strconv.Atoi(donutMetadata["blockSize"])
|
return
|
||||||
k, _ := strconv.Atoi(donutMetadata["erasureK"])
|
}
|
||||||
m, _ := strconv.Atoi(donutMetadata["erasureM"])
|
totalLeft, err := strconv.Atoi(donutMetadata["size"])
|
||||||
expectedMd5sum, _ := hex.DecodeString(donutMetadata["md5"])
|
if err != nil {
|
||||||
|
writer.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
blockSize, err := strconv.Atoi(donutMetadata["blockSize"])
|
||||||
|
if err != nil {
|
||||||
|
writer.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
k, err := strconv.Atoi(donutMetadata["erasureK"])
|
||||||
|
if err != nil {
|
||||||
|
writer.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m, err := strconv.Atoi(donutMetadata["erasureM"])
|
||||||
|
if err != nil {
|
||||||
|
writer.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
expectedMd5sum, err := hex.DecodeString(donutMetadata["md5"])
|
||||||
|
if err != nil {
|
||||||
|
writer.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
technique, err := getErasureTechnique(donutMetadata["erasureTechnique"])
|
||||||
|
if err != nil {
|
||||||
|
writer.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
summer := md5.New()
|
summer := md5.New()
|
||||||
// TODO select technique properly
|
params, _ := erasure.ParseEncoderParams(uint8(k), uint8(m), technique)
|
||||||
params, _ := erasure.ParseEncoderParams(uint8(k), uint8(m), erasure.Cauchy)
|
|
||||||
encoder := erasure.NewEncoder(params)
|
encoder := erasure.NewEncoder(params)
|
||||||
for _, reader := range readers {
|
for _, reader := range readers {
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
@ -34,13 +75,17 @@ func erasureReader(readers []io.ReadCloser, donutMetadata map[string]string, wri
|
|||||||
if blockSize < totalLeft {
|
if blockSize < totalLeft {
|
||||||
curBlockSize = blockSize
|
curBlockSize = blockSize
|
||||||
}
|
}
|
||||||
curChunkSize := erasure.GetEncodedChunkLen(curBlockSize, uint8(k))
|
|
||||||
|
curChunkSize := erasure.GetEncodedBlockLen(curBlockSize, uint8(k))
|
||||||
|
|
||||||
encodedBytes := make([][]byte, 16)
|
encodedBytes := make([][]byte, 16)
|
||||||
for i, reader := range readers {
|
for i, reader := range readers {
|
||||||
var bytesBuffer bytes.Buffer
|
var bytesBuffer bytes.Buffer
|
||||||
// TODO watch for errors
|
_, err := io.CopyN(&bytesBuffer, reader, int64(curChunkSize))
|
||||||
io.CopyN(&bytesBuffer, reader, int64(curChunkSize))
|
if err != nil {
|
||||||
|
writer.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
encodedBytes[i] = bytesBuffer.Bytes()
|
encodedBytes[i] = bytesBuffer.Bytes()
|
||||||
}
|
}
|
||||||
decodedData, err := encoder.Decode(encodedBytes, curBlockSize)
|
decodedData, err := encoder.Decode(encodedBytes, curBlockSize)
|
||||||
@ -49,7 +94,11 @@ func erasureReader(readers []io.ReadCloser, donutMetadata map[string]string, wri
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
summer.Write(decodedData)
|
summer.Write(decodedData)
|
||||||
io.Copy(writer, bytes.NewBuffer(decodedData))
|
_, err = io.Copy(writer, bytes.NewBuffer(decodedData))
|
||||||
|
if err != nil {
|
||||||
|
writer.CloseWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
totalLeft = totalLeft - blockSize
|
totalLeft = totalLeft - blockSize
|
||||||
}
|
}
|
||||||
actualMd5sum := summer.Sum(nil)
|
actualMd5sum := summer.Sum(nil)
|
||||||
@ -58,6 +107,7 @@ func erasureReader(readers []io.ReadCloser, donutMetadata map[string]string, wri
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
writer.Close()
|
writer.Close()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// erasure writer
|
// erasure writer
|
||||||
@ -70,6 +120,7 @@ type erasureWriter struct {
|
|||||||
isClosed <-chan bool
|
isClosed <-chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newErasureWriter - get a new writer
|
||||||
func newErasureWriter(writers []Writer) ObjectWriter {
|
func newErasureWriter(writers []Writer) ObjectWriter {
|
||||||
r, w := io.Pipe()
|
r, w := io.Pipe()
|
||||||
isClosed := make(chan bool)
|
isClosed := make(chan bool)
|
||||||
|
51
pkg/storage/donut/interfaces.go
Normal file
51
pkg/storage/donut/interfaces.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package donut
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Collection of Donut specification interfaces
|
||||||
|
|
||||||
|
// Donut interface
|
||||||
|
type Donut interface {
|
||||||
|
CreateBucket(bucket string) error
|
||||||
|
GetObjectReader(bucket, object string) (io.ReadCloser, error)
|
||||||
|
GetObjectWriter(bucket, object string) (ObjectWriter, error)
|
||||||
|
GetObjectMetadata(bucket, object string) (map[string]string, error)
|
||||||
|
ListBuckets() ([]string, error)
|
||||||
|
ListObjects(bucket string) ([]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bucket interface
|
||||||
|
type Bucket interface {
|
||||||
|
GetNodes() ([]string, error)
|
||||||
|
AddNode(nodeID, bucketID string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node interface
|
||||||
|
type Node interface {
|
||||||
|
CreateBucket(bucket string) error
|
||||||
|
GetBuckets() ([]string, error)
|
||||||
|
GetDonutMetadata(bucket, object string) (map[string]string, error)
|
||||||
|
GetMetadata(bucket, object string) (map[string]string, error)
|
||||||
|
GetReader(bucket, object string) (io.ReadCloser, error)
|
||||||
|
GetWriter(bucket, object string) (Writer, error)
|
||||||
|
ListObjects(bucket string) ([]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectWriter interface
|
||||||
|
type ObjectWriter interface {
|
||||||
|
Close() error
|
||||||
|
CloseWithError(error) error
|
||||||
|
GetMetadata() (map[string]string, error)
|
||||||
|
SetMetadata(map[string]string) error
|
||||||
|
Write([]byte) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer interface
|
||||||
|
type Writer interface {
|
||||||
|
ObjectWriter
|
||||||
|
|
||||||
|
GetDonutMetadata() (map[string]string, error)
|
||||||
|
SetDonutMetadata(map[string]string) error
|
||||||
|
}
|
@ -41,7 +41,7 @@ func (d localDirectoryNode) GetWriter(bucket, object string) (Writer, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newDonutFileWriter(objectPath)
|
return newDonutObjectWriter(objectPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d localDirectoryNode) GetReader(bucket, object string) (io.ReadCloser, error) {
|
func (d localDirectoryNode) GetReader(bucket, object string) (io.ReadCloser, error) {
|
@ -7,12 +7,12 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDonutFileWriter(objectDir string) (Writer, error) {
|
func newDonutObjectWriter(objectDir string) (Writer, error) {
|
||||||
dataFile, err := os.OpenFile(path.Join(objectDir, "data"), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
dataFile, err := os.OpenFile(path.Join(objectDir, "data"), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return donutFileWriter{
|
return donutObjectWriter{
|
||||||
root: objectDir,
|
root: objectDir,
|
||||||
file: dataFile,
|
file: dataFile,
|
||||||
metadata: make(map[string]string),
|
metadata: make(map[string]string),
|
||||||
@ -20,7 +20,7 @@ func newDonutFileWriter(objectDir string) (Writer, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type donutFileWriter struct {
|
type donutObjectWriter struct {
|
||||||
root string
|
root string
|
||||||
file *os.File
|
file *os.File
|
||||||
metadata map[string]string
|
metadata map[string]string
|
||||||
@ -28,11 +28,11 @@ type donutFileWriter struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d donutFileWriter) Write(data []byte) (int, error) {
|
func (d donutObjectWriter) Write(data []byte) (int, error) {
|
||||||
return d.file.Write(data)
|
return d.file.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d donutFileWriter) Close() error {
|
func (d donutObjectWriter) Close() error {
|
||||||
if d.err != nil {
|
if d.err != nil {
|
||||||
return d.err
|
return d.err
|
||||||
}
|
}
|
||||||
@ -44,14 +44,14 @@ func (d donutFileWriter) Close() error {
|
|||||||
return d.file.Close()
|
return d.file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d donutFileWriter) CloseWithError(err error) error {
|
func (d donutObjectWriter) CloseWithError(err error) error {
|
||||||
if d.err != nil {
|
if d.err != nil {
|
||||||
d.err = err
|
d.err = err
|
||||||
}
|
}
|
||||||
return d.Close()
|
return d.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d donutFileWriter) SetMetadata(metadata map[string]string) error {
|
func (d donutObjectWriter) SetMetadata(metadata map[string]string) error {
|
||||||
for k := range d.metadata {
|
for k := range d.metadata {
|
||||||
delete(d.metadata, k)
|
delete(d.metadata, k)
|
||||||
}
|
}
|
||||||
@ -61,7 +61,7 @@ func (d donutFileWriter) SetMetadata(metadata map[string]string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d donutFileWriter) GetMetadata() (map[string]string, error) {
|
func (d donutObjectWriter) GetMetadata() (map[string]string, error) {
|
||||||
metadata := make(map[string]string)
|
metadata := make(map[string]string)
|
||||||
for k, v := range d.metadata {
|
for k, v := range d.metadata {
|
||||||
metadata[k] = v
|
metadata[k] = v
|
||||||
@ -69,7 +69,7 @@ func (d donutFileWriter) GetMetadata() (map[string]string, error) {
|
|||||||
return metadata, nil
|
return metadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d donutFileWriter) SetDonutMetadata(metadata map[string]string) error {
|
func (d donutObjectWriter) SetDonutMetadata(metadata map[string]string) error {
|
||||||
for k := range d.donutMetadata {
|
for k := range d.donutMetadata {
|
||||||
delete(d.donutMetadata, k)
|
delete(d.donutMetadata, k)
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ func (d donutFileWriter) SetDonutMetadata(metadata map[string]string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d donutFileWriter) GetDonutMetadata() (map[string]string, error) {
|
func (d donutObjectWriter) GetDonutMetadata() (map[string]string, error) {
|
||||||
donutMetadata := make(map[string]string)
|
donutMetadata := make(map[string]string)
|
||||||
for k, v := range d.donutMetadata {
|
for k, v := range d.donutMetadata {
|
||||||
donutMetadata[k] = v
|
donutMetadata[k] = v
|
@ -1,39 +0,0 @@
|
|||||||
package donut
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type objectWriter struct {
|
|
||||||
metadata map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (obj objectWriter) Write(data []byte) (length int, err error) {
|
|
||||||
return 11, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (obj objectWriter) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (obj objectWriter) CloseWithError(err error) error {
|
|
||||||
return errors.New("Not Implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (obj objectWriter) SetMetadata(metadata map[string]string) error {
|
|
||||||
for k := range obj.metadata {
|
|
||||||
delete(obj.metadata, k)
|
|
||||||
}
|
|
||||||
for k, v := range metadata {
|
|
||||||
obj.metadata[k] = v
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (obj objectWriter) GetMetadata() (map[string]string, error) {
|
|
||||||
ret := make(map[string]string)
|
|
||||||
for k, v := range obj.metadata {
|
|
||||||
ret[k] = v
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
@ -1,133 +0,0 @@
|
|||||||
/*
|
|
||||||
* Minimalist Object Storage, (C) 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 file
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
/// Bucket Operations
|
|
||||||
|
|
||||||
// ListBuckets - Get service
|
|
||||||
func (storage *Storage) ListBuckets() ([]mstorage.BucketMetadata, error) {
|
|
||||||
files, err := ioutil.ReadDir(storage.root)
|
|
||||||
if err != nil {
|
|
||||||
return []mstorage.BucketMetadata{}, mstorage.EmbedError("bucket", "", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadataList []mstorage.BucketMetadata
|
|
||||||
for _, file := range files {
|
|
||||||
// Skip policy files
|
|
||||||
if strings.HasSuffix(file.Name(), "_policy.json") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return []mstorage.BucketMetadata{}, mstorage.BackendCorrupted{Path: storage.root}
|
|
||||||
}
|
|
||||||
metadata := mstorage.BucketMetadata{
|
|
||||||
Name: file.Name(),
|
|
||||||
Created: file.ModTime(), // TODO - provide real created time
|
|
||||||
}
|
|
||||||
metadataList = append(metadataList, metadata)
|
|
||||||
}
|
|
||||||
return metadataList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateBucket - PUT Bucket
|
|
||||||
func (storage *Storage) CreateBucket(bucket string) error {
|
|
||||||
storage.lock.Lock()
|
|
||||||
defer storage.lock.Unlock()
|
|
||||||
|
|
||||||
// verify bucket path legal
|
|
||||||
if mstorage.IsValidBucket(bucket) == false {
|
|
||||||
return mstorage.BucketNameInvalid{Bucket: bucket}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get bucket path
|
|
||||||
bucketDir := path.Join(storage.root, bucket)
|
|
||||||
|
|
||||||
// check if bucket exists
|
|
||||||
if _, err := os.Stat(bucketDir); err == nil {
|
|
||||||
return mstorage.BucketExists{
|
|
||||||
Bucket: bucket,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make bucket
|
|
||||||
err := os.Mkdir(bucketDir, 0700)
|
|
||||||
if err != nil {
|
|
||||||
return mstorage.EmbedError(bucket, "", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListObjects - GET bucket (list objects)
|
|
||||||
func (storage *Storage) ListObjects(bucket string, resources mstorage.BucketResourcesMetadata) ([]mstorage.ObjectMetadata, mstorage.BucketResourcesMetadata, error) {
|
|
||||||
p := bucketDir{}
|
|
||||||
p.files = make(map[string]os.FileInfo)
|
|
||||||
|
|
||||||
if mstorage.IsValidBucket(bucket) == false {
|
|
||||||
return []mstorage.ObjectMetadata{}, resources, mstorage.BucketNameInvalid{Bucket: bucket}
|
|
||||||
}
|
|
||||||
if resources.Prefix != "" && mstorage.IsValidObject(resources.Prefix) == false {
|
|
||||||
return []mstorage.ObjectMetadata{}, resources, mstorage.ObjectNameInvalid{Bucket: bucket, Object: resources.Prefix}
|
|
||||||
}
|
|
||||||
|
|
||||||
rootPrefix := path.Join(storage.root, bucket)
|
|
||||||
// check bucket exists
|
|
||||||
if _, err := os.Stat(rootPrefix); os.IsNotExist(err) {
|
|
||||||
return []mstorage.ObjectMetadata{}, resources, mstorage.BucketNotFound{Bucket: bucket}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.root = rootPrefix
|
|
||||||
err := filepath.Walk(rootPrefix, p.getAllFiles)
|
|
||||||
if err != nil {
|
|
||||||
return []mstorage.ObjectMetadata{}, resources, mstorage.EmbedError(bucket, "", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadataList []mstorage.ObjectMetadata
|
|
||||||
var metadata mstorage.ObjectMetadata
|
|
||||||
|
|
||||||
// Populate filtering mode
|
|
||||||
resources.Mode = mstorage.GetMode(resources)
|
|
||||||
|
|
||||||
for name, file := range p.files {
|
|
||||||
if len(metadataList) >= resources.Maxkeys {
|
|
||||||
resources.IsTruncated = true
|
|
||||||
goto ret
|
|
||||||
}
|
|
||||||
metadata, resources, err = storage.filter(bucket, name, file, resources)
|
|
||||||
if err != nil {
|
|
||||||
return []mstorage.ObjectMetadata{}, resources, mstorage.EmbedError(bucket, "", err)
|
|
||||||
}
|
|
||||||
if metadata.Bucket != "" {
|
|
||||||
metadataList = append(metadataList, metadata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret:
|
|
||||||
sort.Sort(byObjectKey(metadataList))
|
|
||||||
return metadataList, resources, nil
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
/*
|
|
||||||
* Minimalist Object Storage, (C) 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 file
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"encoding/json"
|
|
||||||
mstorage "github.com/minio-io/minio/pkg/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetBucketPolicy - GET bucket policy
|
|
||||||
func (storage *Storage) GetBucketPolicy(bucket string) (mstorage.BucketPolicy, error) {
|
|
||||||
storage.lock.Lock()
|
|
||||||
defer storage.lock.Unlock()
|
|
||||||
|
|
||||||
var p mstorage.BucketPolicy
|
|
||||||
// verify bucket path legal
|
|
||||||
if mstorage.IsValidBucket(bucket) == false {
|
|
||||||
return mstorage.BucketPolicy{}, mstorage.BucketNameInvalid{Bucket: bucket}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get bucket path
|
|
||||||
bucketDir := path.Join(storage.root, bucket)
|
|
||||||
// check if bucket exists
|
|
||||||
if _, err := os.Stat(bucketDir); err != nil {
|
|
||||||
return mstorage.BucketPolicy{}, mstorage.BucketNotFound{Bucket: bucket}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get policy path
|
|
||||||
bucketPolicy := path.Join(storage.root, bucket+"_policy.json")
|
|
||||||
filestat, err := os.Stat(bucketPolicy)
|
|
||||||
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return mstorage.BucketPolicy{}, mstorage.BucketPolicyNotFound{Bucket: bucket}
|
|
||||||
}
|
|
||||||
|
|
||||||
if filestat.IsDir() {
|
|
||||||
return mstorage.BucketPolicy{}, mstorage.BackendCorrupted{Path: bucketPolicy}
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.OpenFile(bucketPolicy, os.O_RDONLY, 0666)
|
|
||||||
defer file.Close()
|
|
||||||
if err != nil {
|
|
||||||
return mstorage.BucketPolicy{}, mstorage.EmbedError(bucket, "", err)
|
|
||||||
}
|
|
||||||
encoder := json.NewDecoder(file)
|
|
||||||
err = encoder.Decode(&p)
|
|
||||||
if err != nil {
|
|
||||||
return mstorage.BucketPolicy{}, mstorage.EmbedError(bucket, "", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateBucketPolicy - PUT bucket policy
|
|
||||||
func (storage *Storage) CreateBucketPolicy(bucket string, p mstorage.BucketPolicy) error {
|
|
||||||
storage.lock.Lock()
|
|
||||||
defer storage.lock.Unlock()
|
|
||||||
|
|
||||||
// verify bucket path legal
|
|
||||||
if mstorage.IsValidBucket(bucket) == false {
|
|
||||||
return mstorage.BucketNameInvalid{Bucket: bucket}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get bucket path
|
|
||||||
bucketDir := path.Join(storage.root, bucket)
|
|
||||||
// check if bucket exists
|
|
||||||
if _, err := os.Stat(bucketDir); err != nil {
|
|
||||||
return mstorage.BucketNotFound{
|
|
||||||
Bucket: bucket,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get policy path
|
|
||||||
bucketPolicy := path.Join(storage.root, bucket+"_policy.json")
|
|
||||||
filestat, ret := os.Stat(bucketPolicy)
|
|
||||||
if !os.IsNotExist(ret) {
|
|
||||||
if filestat.IsDir() {
|
|
||||||
return mstorage.BackendCorrupted{Path: bucketPolicy}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.OpenFile(bucketPolicy, os.O_WRONLY|os.O_CREATE, 0600)
|
|
||||||
defer file.Close()
|
|
||||||
if err != nil {
|
|
||||||
return mstorage.EmbedError(bucket, "", err)
|
|
||||||
}
|
|
||||||
encoder := json.NewEncoder(file)
|
|
||||||
err = encoder.Encode(p)
|
|
||||||
if err != nil {
|
|
||||||
return mstorage.EmbedError(bucket, "", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user