mirror of https://github.com/docker/docs.git
Move towards using external binaries / RPC plugins
- First RPC steps - Work on some flaws in RPC model - Remove unused TLS settings from Engine and Swarm options - Add code to correctly encode data over the network - Add client driver for RPC - Rename server driver file - Start to make marshal make sense - Fix silly RPC method args and add client - Fix some issues with RPC calls, and marshaling - Simplify plugin main.go - Move towards 100% plugin in CLI - Ensure that plugin servers are cleaned up properly - Make flag parsing for driver flags work properly Includes some work carried from @dmp42 updating the build process and tests to use the new method. Signed-off-by: Nathan LeClaire <nathan.leclaire@gmail.com>
This commit is contained in:
parent
a63f26f438
commit
c8edb33ecd
|
@ -2,5 +2,5 @@ docker-machine*
|
|||
*.log
|
||||
*.iml
|
||||
.idea/
|
||||
bin
|
||||
cover
|
||||
./bin
|
||||
cover
|
||||
|
|
10
Makefile
10
Makefile
|
@ -13,11 +13,11 @@ DOCKER_CONTAINER_NAME := "docker-machine-build-container"
|
|||
build:
|
||||
test: build
|
||||
%:
|
||||
@docker build -t $(DOCKER_IMAGE_NAME) .
|
||||
docker build -t $(DOCKER_IMAGE_NAME) .
|
||||
|
||||
@test -z '$(shell docker ps -a | grep $(DOCKER_CONTAINER_NAME))' || docker rm -f $(DOCKER_CONTAINER_NAME)
|
||||
test -z '$(shell docker ps -a | grep $(DOCKER_CONTAINER_NAME))' || docker rm -f $(DOCKER_CONTAINER_NAME)
|
||||
|
||||
@docker run --name $(DOCKER_CONTAINER_NAME) \
|
||||
docker run --name $(DOCKER_CONTAINER_NAME) \
|
||||
-e DEBUG \
|
||||
-e STATIC \
|
||||
-e VERBOSE \
|
||||
|
@ -30,7 +30,7 @@ test: build
|
|||
$(DOCKER_IMAGE_NAME) \
|
||||
make $@
|
||||
|
||||
@test ! -d bin || rm -Rf bin
|
||||
@test -z "$(findstring build,$(patsubst cross,build,$@))" || docker cp $(DOCKER_CONTAINER_NAME):/go/src/github.com/docker/machine/bin bin
|
||||
test ! -d bin || rm -Rf bin
|
||||
test -z "$(findstring build,$(patsubst cross,build,$@))" || docker cp $(DOCKER_CONTAINER_NAME):/go/src/github.com/docker/machine/bin bin
|
||||
|
||||
endif
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# Project name, used to name the binaries
|
||||
PKG_NAME := docker-machine
|
||||
GH_USER ?= docker
|
||||
GH_REPO ?= machine
|
||||
# Github infos
|
||||
GITHUB_INFO := $(shell git config --get remote.origin.url | sed -e 's/.*:\(.*\).git/\1/')
|
||||
GH_USER ?= $(shell echo $(GITHUB_INFO) | cut -d \/ -f 1)
|
||||
GH_REPO ?= $(shell echo $(GITHUB_INFO) | cut -d \/ -f 2)
|
||||
|
||||
# If true, disable optimizations and does NOT strip the binary
|
||||
DEBUG ?=
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.0.3
|
||||
- 1.1.2
|
||||
- 1.2.2
|
||||
- 1.3.3
|
||||
- 1.4.2
|
||||
|
||||
script:
|
||||
- go vet ./...
|
||||
- go test -race -v ./libmachine/... ./commands/... ./drivers/...
|
|
@ -0,0 +1,21 @@
|
|||
Copyright (C) 2013 Jeremy Saenz
|
||||
All Rights Reserved.
|
||||
|
||||
MIT LICENSE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,316 @@
|
|||
[](https://travis-ci.org/codegangsta/cli)
|
||||
|
||||
# cli.go
|
||||
`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
|
||||
|
||||
You can view the API docs here:
|
||||
http://godoc.org/github.com/codegangsta/cli
|
||||
|
||||
## Overview
|
||||
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
|
||||
|
||||
**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive!
|
||||
|
||||
## Installation
|
||||
Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html).
|
||||
|
||||
To install `cli.go`, simply run:
|
||||
```
|
||||
$ go get github.com/codegangsta/cli
|
||||
```
|
||||
|
||||
Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used:
|
||||
```
|
||||
export PATH=$PATH:$GOPATH/bin
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`.
|
||||
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cli.NewApp().Run(os.Args)
|
||||
}
|
||||
```
|
||||
|
||||
This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation:
|
||||
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "boom"
|
||||
app.Usage = "make an explosive entrance"
|
||||
app.Action = func(c *cli.Context) {
|
||||
println("boom! I say!")
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
||||
```
|
||||
|
||||
Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below.
|
||||
|
||||
## Example
|
||||
|
||||
Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness!
|
||||
|
||||
Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it:
|
||||
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "greet"
|
||||
app.Usage = "fight the loneliness!"
|
||||
app.Action = func(c *cli.Context) {
|
||||
println("Hello friend!")
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
||||
```
|
||||
|
||||
Install our command to the `$GOPATH/bin` directory:
|
||||
|
||||
```
|
||||
$ go install
|
||||
```
|
||||
|
||||
Finally run our new command:
|
||||
|
||||
```
|
||||
$ greet
|
||||
Hello friend!
|
||||
```
|
||||
|
||||
`cli.go` also generates some bitchass help text:
|
||||
|
||||
```
|
||||
$ greet help
|
||||
NAME:
|
||||
greet - fight the loneliness!
|
||||
|
||||
USAGE:
|
||||
greet [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
0.0.0
|
||||
|
||||
COMMANDS:
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS
|
||||
--version Shows version information
|
||||
```
|
||||
|
||||
### Arguments
|
||||
You can lookup arguments by calling the `Args` function on `cli.Context`.
|
||||
|
||||
``` go
|
||||
...
|
||||
app.Action = func(c *cli.Context) {
|
||||
println("Hello", c.Args()[0])
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
### Flags
|
||||
Setting and querying flags is simple.
|
||||
``` go
|
||||
...
|
||||
app.Flags = []cli.Flag {
|
||||
cli.StringFlag{
|
||||
Name: "lang",
|
||||
Value: "english",
|
||||
Usage: "language for the greeting",
|
||||
},
|
||||
}
|
||||
app.Action = func(c *cli.Context) {
|
||||
name := "someone"
|
||||
if len(c.Args()) > 0 {
|
||||
name = c.Args()[0]
|
||||
}
|
||||
if c.String("lang") == "spanish" {
|
||||
println("Hola", name)
|
||||
} else {
|
||||
println("Hello", name)
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
See full list of flags at http://godoc.org/github.com/codegangsta/cli
|
||||
|
||||
#### Alternate Names
|
||||
|
||||
You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
|
||||
|
||||
``` go
|
||||
app.Flags = []cli.Flag {
|
||||
cli.StringFlag{
|
||||
Name: "lang, l",
|
||||
Value: "english",
|
||||
Usage: "language for the greeting",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error.
|
||||
|
||||
#### Values from the Environment
|
||||
|
||||
You can also have the default value set from the environment via `EnvVar`. e.g.
|
||||
|
||||
``` go
|
||||
app.Flags = []cli.Flag {
|
||||
cli.StringFlag{
|
||||
Name: "lang, l",
|
||||
Value: "english",
|
||||
Usage: "language for the greeting",
|
||||
EnvVar: "APP_LANG",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default.
|
||||
|
||||
``` go
|
||||
app.Flags = []cli.Flag {
|
||||
cli.StringFlag{
|
||||
Name: "lang, l",
|
||||
Value: "english",
|
||||
Usage: "language for the greeting",
|
||||
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Subcommands
|
||||
|
||||
Subcommands can be defined for a more git-like command line app.
|
||||
```go
|
||||
...
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "add a task to the list",
|
||||
Action: func(c *cli.Context) {
|
||||
println("added task: ", c.Args().First())
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "complete",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "complete a task on the list",
|
||||
Action: func(c *cli.Context) {
|
||||
println("completed task: ", c.Args().First())
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "template",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "options for task templates",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
Usage: "add a new template",
|
||||
Action: func(c *cli.Context) {
|
||||
println("new task template: ", c.Args().First())
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "remove an existing template",
|
||||
Action: func(c *cli.Context) {
|
||||
println("removed task template: ", c.Args().First())
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
### Bash Completion
|
||||
|
||||
You can enable completion commands by setting the `EnableBashCompletion`
|
||||
flag on the `App` object. By default, this setting will only auto-complete to
|
||||
show an app's subcommands, but you can write your own completion methods for
|
||||
the App or its subcommands.
|
||||
```go
|
||||
...
|
||||
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
|
||||
app := cli.NewApp()
|
||||
app.EnableBashCompletion = true
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "complete",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "complete a task on the list",
|
||||
Action: func(c *cli.Context) {
|
||||
println("completed task: ", c.Args().First())
|
||||
},
|
||||
BashComplete: func(c *cli.Context) {
|
||||
// This will complete if no args are passed
|
||||
if len(c.Args()) > 0 {
|
||||
return
|
||||
}
|
||||
for _, t := range tasks {
|
||||
fmt.Println(t)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
#### To Enable
|
||||
|
||||
Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while
|
||||
setting the `PROG` variable to the name of your program:
|
||||
|
||||
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete`
|
||||
|
||||
#### To Distribute
|
||||
|
||||
Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename
|
||||
it to the name of the program you wish to add autocomplete support for (or
|
||||
automatically install it there if you are distributing a package). Don't forget
|
||||
to source the file to make it active in the current shell.
|
||||
|
||||
```
|
||||
sudo cp src/bash_autocomplete /etc/bash_completion.d/<myprogram>
|
||||
source /etc/bash_completion.d/<myprogram>
|
||||
```
|
||||
|
||||
Alternatively, you can just document that users should source the generic
|
||||
`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set
|
||||
to the name of their program (as above).
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
|
@ -0,0 +1,308 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// App is the main structure of a cli application. It is recomended that
|
||||
// an app be created with the cli.NewApp() function
|
||||
type App struct {
|
||||
// The name of the program. Defaults to os.Args[0]
|
||||
Name string
|
||||
// Description of the program.
|
||||
Usage string
|
||||
// Version of the program
|
||||
Version string
|
||||
// List of commands to execute
|
||||
Commands []Command
|
||||
// List of flags to parse
|
||||
Flags []Flag
|
||||
// Boolean to enable bash completion commands
|
||||
EnableBashCompletion bool
|
||||
// Boolean to hide built-in help command
|
||||
HideHelp bool
|
||||
// Boolean to hide built-in version flag
|
||||
HideVersion bool
|
||||
// An action to execute when the bash-completion flag is set
|
||||
BashComplete func(context *Context)
|
||||
// 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
|
||||
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
|
||||
Action func(context *Context)
|
||||
// Execute this function if the proper command cannot be found
|
||||
CommandNotFound func(context *Context, command string)
|
||||
// Compilation date
|
||||
Compiled time.Time
|
||||
// List of all authors who contributed
|
||||
Authors []Author
|
||||
// Copyright of the binary if any
|
||||
Copyright string
|
||||
// Name of Author (Note: Use App.Authors, this is deprecated)
|
||||
Author string
|
||||
// Email of Author (Note: Use App.Authors, this is deprecated)
|
||||
Email string
|
||||
// Writer writer to write output to
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
// Tries to find out when this binary was compiled.
|
||||
// Returns the current time if it fails to find it.
|
||||
func compileTime() time.Time {
|
||||
info, err := os.Stat(os.Args[0])
|
||||
if err != nil {
|
||||
return time.Now()
|
||||
}
|
||||
return info.ModTime()
|
||||
}
|
||||
|
||||
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
|
||||
func NewApp() *App {
|
||||
return &App{
|
||||
Name: os.Args[0],
|
||||
Usage: "A new cli application",
|
||||
Version: "0.0.0",
|
||||
BashComplete: DefaultAppComplete,
|
||||
Action: helpCommand.Action,
|
||||
Compiled: compileTime(),
|
||||
Writer: os.Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
|
||||
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})
|
||||
}
|
||||
|
||||
// append help to commands
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
a.Commands = append(a.Commands, helpCommand)
|
||||
if (HelpFlag != BoolFlag{}) {
|
||||
a.appendFlag(HelpFlag)
|
||||
}
|
||||
}
|
||||
|
||||
//append version/help flags
|
||||
if a.EnableBashCompletion {
|
||||
a.appendFlag(BashCompletionFlag)
|
||||
}
|
||||
|
||||
if !a.HideVersion {
|
||||
a.appendFlag(VersionFlag)
|
||||
}
|
||||
|
||||
// parse flags
|
||||
set := flagSet(a.Name, a.Flags)
|
||||
set.SetOutput(ioutil.Discard)
|
||||
err = set.Parse(arguments[1:])
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
if nerr != nil {
|
||||
fmt.Fprintln(a.Writer, nerr)
|
||||
context := NewContext(a, set, nil)
|
||||
ShowAppHelp(context)
|
||||
return nerr
|
||||
}
|
||||
context := NewContext(a, set, nil)
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintln(a.Writer, "Incorrect Usage.")
|
||||
fmt.Fprintln(a.Writer)
|
||||
ShowAppHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if checkCompletions(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if checkHelp(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if checkVersion(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if a.After != nil {
|
||||
defer func() {
|
||||
afterErr := a.After(context)
|
||||
if afterErr != nil {
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if a.Before != nil {
|
||||
err := a.Before(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args := context.Args()
|
||||
if args.Present() {
|
||||
name := args.First()
|
||||
c := a.Command(name)
|
||||
if c != nil {
|
||||
return c.Run(context)
|
||||
}
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
a.Action(context)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Another entry point to the cli app, takes care of passing arguments and error handling
|
||||
func (a *App) RunAndExitOnError() {
|
||||
if err := a.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
|
||||
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
// append help to commands
|
||||
if len(a.Commands) > 0 {
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
a.Commands = append(a.Commands, helpCommand)
|
||||
if (HelpFlag != BoolFlag{}) {
|
||||
a.appendFlag(HelpFlag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// append flags
|
||||
if a.EnableBashCompletion {
|
||||
a.appendFlag(BashCompletionFlag)
|
||||
}
|
||||
|
||||
// parse flags
|
||||
set := flagSet(a.Name, a.Flags)
|
||||
set.SetOutput(ioutil.Discard)
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
nerr := normalizeFlags(a.Flags, set)
|
||||
context := NewContext(a, set, ctx)
|
||||
|
||||
if nerr != nil {
|
||||
fmt.Fprintln(a.Writer, nerr)
|
||||
fmt.Fprintln(a.Writer)
|
||||
if len(a.Commands) > 0 {
|
||||
ShowSubcommandHelp(context)
|
||||
} else {
|
||||
ShowCommandHelp(ctx, context.Args().First())
|
||||
}
|
||||
return nerr
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintln(a.Writer, "Incorrect Usage.")
|
||||
fmt.Fprintln(a.Writer)
|
||||
ShowSubcommandHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if checkCompletions(context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(a.Commands) > 0 {
|
||||
if checkSubcommandHelp(context) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if checkCommandHelp(ctx, context.Args().First()) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if a.After != nil {
|
||||
defer func() {
|
||||
afterErr := a.After(context)
|
||||
if afterErr != nil {
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
err = afterErr
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if a.Before != nil {
|
||||
err := a.Before(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args := context.Args()
|
||||
if args.Present() {
|
||||
name := args.First()
|
||||
c := a.Command(name)
|
||||
if c != nil {
|
||||
return c.Run(context)
|
||||
}
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
a.Action(context)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the named command on App. Returns nil if the command does not exist
|
||||
func (a *App) Command(name string) *Command {
|
||||
for _, c := range a.Commands {
|
||||
if c.HasName(name) {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) hasFlag(flag Flag) bool {
|
||||
for _, f := range a.Flags {
|
||||
if flag == f {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *App) appendFlag(flag Flag) {
|
||||
if !a.hasFlag(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)
|
||||
}
|
|
@ -0,0 +1,867 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func ExampleApp() {
|
||||
// set args for examples sake
|
||||
os.Args = []string{"greet", "--name", "Jeremy"}
|
||||
|
||||
app := NewApp()
|
||||
app.Name = "greet"
|
||||
app.Flags = []Flag{
|
||||
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
||||
}
|
||||
app.Action = func(c *Context) {
|
||||
fmt.Printf("Hello %v\n", c.String("name"))
|
||||
}
|
||||
app.Author = "Harrison"
|
||||
app.Email = "harrison@lolwut.com"
|
||||
app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}}
|
||||
app.Run(os.Args)
|
||||
// Output:
|
||||
// Hello Jeremy
|
||||
}
|
||||
|
||||
func ExampleAppSubcommand() {
|
||||
// set args for examples sake
|
||||
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
|
||||
app := NewApp()
|
||||
app.Name = "say"
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "hello",
|
||||
Aliases: []string{"hi"},
|
||||
Usage: "use it to see a description",
|
||||
Description: "This is how we describe hello the function",
|
||||
Subcommands: []Command{
|
||||
{
|
||||
Name: "english",
|
||||
Aliases: []string{"en"},
|
||||
Usage: "sends a greeting in english",
|
||||
Description: "greets someone in english",
|
||||
Flags: []Flag{
|
||||
StringFlag{
|
||||
Name: "name",
|
||||
Value: "Bob",
|
||||
Usage: "Name of the person to greet",
|
||||
},
|
||||
},
|
||||
Action: func(c *Context) {
|
||||
fmt.Println("Hello,", c.String("name"))
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
// Output:
|
||||
// Hello, Jeremy
|
||||
}
|
||||
|
||||
func ExampleAppHelp() {
|
||||
// set args for examples sake
|
||||
os.Args = []string{"greet", "h", "describeit"}
|
||||
|
||||
app := NewApp()
|
||||
app.Name = "greet"
|
||||
app.Flags = []Flag{
|
||||
StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
||||
}
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "describeit",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "use it to see a description",
|
||||
Description: "This is how we describe describeit the function",
|
||||
Action: func(c *Context) {
|
||||
fmt.Printf("i like to describe things")
|
||||
},
|
||||
},
|
||||
}
|
||||
app.Run(os.Args)
|
||||
// Output:
|
||||
// NAME:
|
||||
// describeit - use it to see a description
|
||||
//
|
||||
// USAGE:
|
||||
// command describeit [arguments...]
|
||||
//
|
||||
// DESCRIPTION:
|
||||
// This is how we describe describeit the function
|
||||
}
|
||||
|
||||
func ExampleAppBashComplete() {
|
||||
// set args for examples sake
|
||||
os.Args = []string{"greet", "--generate-bash-completion"}
|
||||
|
||||
app := NewApp()
|
||||
app.Name = "greet"
|
||||
app.EnableBashCompletion = true
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "describeit",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "use it to see a description",
|
||||
Description: "This is how we describe describeit the function",
|
||||
Action: func(c *Context) {
|
||||
fmt.Printf("i like to describe things")
|
||||
},
|
||||
}, {
|
||||
Name: "next",
|
||||
Usage: "next example",
|
||||
Description: "more stuff to see when generating bash completion",
|
||||
Action: func(c *Context) {
|
||||
fmt.Printf("the next example")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
// Output:
|
||||
// describeit
|
||||
// d
|
||||
// next
|
||||
// help
|
||||
// h
|
||||
}
|
||||
|
||||
func TestApp_Run(t *testing.T) {
|
||||
s := ""
|
||||
|
||||
app := NewApp()
|
||||
app.Action = func(c *Context) {
|
||||
s = s + c.Args().First()
|
||||
}
|
||||
|
||||
err := app.Run([]string{"command", "foo"})
|
||||
expect(t, err, nil)
|
||||
err = app.Run([]string{"command", "bar"})
|
||||
expect(t, err, nil)
|
||||
expect(t, s, "foobar")
|
||||
}
|
||||
|
||||
var commandAppTests = []struct {
|
||||
name string
|
||||
expected bool
|
||||
}{
|
||||
{"foobar", true},
|
||||
{"batbaz", true},
|
||||
{"b", true},
|
||||
{"f", true},
|
||||
{"bat", false},
|
||||
{"nothing", false},
|
||||
}
|
||||
|
||||
func TestApp_Command(t *testing.T) {
|
||||
app := NewApp()
|
||||
fooCommand := Command{Name: "foobar", Aliases: []string{"f"}}
|
||||
batCommand := Command{Name: "batbaz", Aliases: []string{"b"}}
|
||||
app.Commands = []Command{
|
||||
fooCommand,
|
||||
batCommand,
|
||||
}
|
||||
|
||||
for _, test := range commandAppTests {
|
||||
expect(t, app.Command(test.name) != nil, test.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
|
||||
var parsedOption, firstArg string
|
||||
|
||||
app := NewApp()
|
||||
command := Command{
|
||||
Name: "cmd",
|
||||
Flags: []Flag{
|
||||
StringFlag{Name: "option", Value: "", Usage: "some option"},
|
||||
},
|
||||
Action: func(c *Context) {
|
||||
parsedOption = c.String("option")
|
||||
firstArg = c.Args().First()
|
||||
},
|
||||
}
|
||||
app.Commands = []Command{command}
|
||||
|
||||
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"})
|
||||
|
||||
expect(t, parsedOption, "my-option")
|
||||
expect(t, firstArg, "my-arg")
|
||||
}
|
||||
|
||||
func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
|
||||
var context *Context
|
||||
|
||||
a := NewApp()
|
||||
a.Commands = []Command{
|
||||
{
|
||||
Name: "foo",
|
||||
Action: func(c *Context) {
|
||||
context = c
|
||||
},
|
||||
Flags: []Flag{
|
||||
StringFlag{
|
||||
Name: "lang",
|
||||
Value: "english",
|
||||
Usage: "language for the greeting",
|
||||
},
|
||||
},
|
||||
Before: func(_ *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 := NewApp()
|
||||
command := Command{
|
||||
Name: "cmd",
|
||||
Flags: []Flag{
|
||||
StringFlag{Name: "option", Value: "", Usage: "some option"},
|
||||
},
|
||||
Action: func(c *Context) {
|
||||
parsedOption = c.String("option")
|
||||
args = c.Args()
|
||||
},
|
||||
}
|
||||
app.Commands = []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 := NewApp()
|
||||
command := Command{
|
||||
Name: "cmd",
|
||||
Action: func(c *Context) {
|
||||
args = c.Args()
|
||||
},
|
||||
}
|
||||
app.Commands = []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) {
|
||||
var meters float64
|
||||
|
||||
app := NewApp()
|
||||
app.Flags = []Flag{
|
||||
Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
|
||||
}
|
||||
app.Action = func(c *Context) {
|
||||
meters = c.Float64("height")
|
||||
}
|
||||
|
||||
app.Run([]string{"", "--height", "1.93"})
|
||||
expect(t, meters, 1.93)
|
||||
}
|
||||
|
||||
func TestApp_ParseSliceFlags(t *testing.T) {
|
||||
var parsedOption, firstArg string
|
||||
var parsedIntSlice []int
|
||||
var parsedStringSlice []string
|
||||
|
||||
app := NewApp()
|
||||
command := Command{
|
||||
Name: "cmd",
|
||||
Flags: []Flag{
|
||||
IntSliceFlag{Name: "p", Value: &IntSlice{}, Usage: "set one or more ip addr"},
|
||||
StringSliceFlag{Name: "ip", Value: &StringSlice{}, Usage: "set one or more ports to open"},
|
||||
},
|
||||
Action: func(c *Context) {
|
||||
parsedIntSlice = c.IntSlice("p")
|
||||
parsedStringSlice = c.StringSlice("ip")
|
||||
parsedOption = c.String("option")
|
||||
firstArg = c.Args().First()
|
||||
},
|
||||
}
|
||||
app.Commands = []Command{command}
|
||||
|
||||
app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"})
|
||||
|
||||
IntsEquals := func(a, b []int) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
StrsEquals := func(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
var expectedIntSlice = []int{22, 80}
|
||||
var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"}
|
||||
|
||||
if !IntsEquals(parsedIntSlice, expectedIntSlice) {
|
||||
t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice)
|
||||
}
|
||||
|
||||
if !StrsEquals(parsedStringSlice, expectedStringSlice) {
|
||||
t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) {
|
||||
var parsedIntSlice []int
|
||||
var parsedStringSlice []string
|
||||
|
||||
app := NewApp()
|
||||
command := Command{
|
||||
Name: "cmd",
|
||||
Flags: []Flag{
|
||||
IntSliceFlag{Name: "a", Usage: "set numbers"},
|
||||
StringSliceFlag{Name: "str", Usage: "set strings"},
|
||||
},
|
||||
Action: func(c *Context) {
|
||||
parsedIntSlice = c.IntSlice("a")
|
||||
parsedStringSlice = c.StringSlice("str")
|
||||
},
|
||||
}
|
||||
app.Commands = []Command{command}
|
||||
|
||||
app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"})
|
||||
|
||||
var expectedIntSlice = []int{2}
|
||||
var expectedStringSlice = []string{"A"}
|
||||
|
||||
if parsedIntSlice[0] != expectedIntSlice[0] {
|
||||
t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0])
|
||||
}
|
||||
|
||||
if parsedStringSlice[0] != expectedStringSlice[0] {
|
||||
t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_DefaultStdout(t *testing.T) {
|
||||
app := 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 := 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) {
|
||||
beforeRun, subcommandRun := false, false
|
||||
beforeError := fmt.Errorf("fail")
|
||||
var err error
|
||||
|
||||
app := NewApp()
|
||||
|
||||
app.Before = func(c *Context) error {
|
||||
beforeRun = true
|
||||
s := c.String("opt")
|
||||
if s == "fail" {
|
||||
return beforeError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "sub",
|
||||
Action: func(c *Context) {
|
||||
subcommandRun = true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Flags = []Flag{
|
||||
StringFlag{Name: "opt"},
|
||||
}
|
||||
|
||||
// run with the Before() func succeeding
|
||||
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Run error: %s", err)
|
||||
}
|
||||
|
||||
if beforeRun == false {
|
||||
t.Errorf("Before() not executed when expected")
|
||||
}
|
||||
|
||||
if subcommandRun == false {
|
||||
t.Errorf("Subcommand not executed when expected")
|
||||
}
|
||||
|
||||
// reset
|
||||
beforeRun, 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 != beforeError {
|
||||
t.Errorf("Run error expected, but not received")
|
||||
}
|
||||
|
||||
if beforeRun == false {
|
||||
t.Errorf("Before() not executed when expected")
|
||||
}
|
||||
|
||||
if subcommandRun == true {
|
||||
t.Errorf("Subcommand executed when NOT expected")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestApp_AfterFunc(t *testing.T) {
|
||||
afterRun, subcommandRun := false, false
|
||||
afterError := fmt.Errorf("fail")
|
||||
var err error
|
||||
|
||||
app := NewApp()
|
||||
|
||||
app.After = func(c *Context) error {
|
||||
afterRun = true
|
||||
s := c.String("opt")
|
||||
if s == "fail" {
|
||||
return afterError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "sub",
|
||||
Action: func(c *Context) {
|
||||
subcommandRun = true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Flags = []Flag{
|
||||
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 := HelpFlag
|
||||
defer func() {
|
||||
HelpFlag = oldFlag
|
||||
}()
|
||||
|
||||
HelpFlag = BoolFlag{}
|
||||
|
||||
app := 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) {
|
||||
oldPrinter := HelpPrinter
|
||||
defer func() {
|
||||
HelpPrinter = oldPrinter
|
||||
}()
|
||||
|
||||
var wasCalled = false
|
||||
HelpPrinter = func(w io.Writer, template string, data interface{}) {
|
||||
wasCalled = true
|
||||
}
|
||||
|
||||
app := NewApp()
|
||||
app.Run([]string{"-h"})
|
||||
|
||||
if wasCalled == false {
|
||||
t.Errorf("Help printer expected to be called, but was not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppVersionPrinter(t *testing.T) {
|
||||
oldPrinter := VersionPrinter
|
||||
defer func() {
|
||||
VersionPrinter = oldPrinter
|
||||
}()
|
||||
|
||||
var wasCalled = false
|
||||
VersionPrinter = func(c *Context) {
|
||||
wasCalled = true
|
||||
}
|
||||
|
||||
app := NewApp()
|
||||
ctx := NewContext(app, nil, nil)
|
||||
ShowVersion(ctx)
|
||||
|
||||
if wasCalled == false {
|
||||
t.Errorf("Version printer expected to be called, but was not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppCommandNotFound(t *testing.T) {
|
||||
beforeRun, subcommandRun := false, false
|
||||
app := NewApp()
|
||||
|
||||
app.CommandNotFound = func(c *Context, command string) {
|
||||
beforeRun = true
|
||||
}
|
||||
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "bar",
|
||||
Action: func(c *Context) {
|
||||
subcommandRun = true
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run([]string{"command", "foo"})
|
||||
|
||||
expect(t, beforeRun, true)
|
||||
expect(t, subcommandRun, false)
|
||||
}
|
||||
|
||||
func TestGlobalFlag(t *testing.T) {
|
||||
var globalFlag string
|
||||
var globalFlagSet bool
|
||||
app := NewApp()
|
||||
app.Flags = []Flag{
|
||||
StringFlag{Name: "global, g", Usage: "global"},
|
||||
}
|
||||
app.Action = func(c *Context) {
|
||||
globalFlag = c.GlobalString("global")
|
||||
globalFlagSet = c.GlobalIsSet("global")
|
||||
}
|
||||
app.Run([]string{"command", "-g", "foo"})
|
||||
expect(t, globalFlag, "foo")
|
||||
expect(t, globalFlagSet, true)
|
||||
|
||||
}
|
||||
|
||||
func TestGlobalFlagsInSubcommands(t *testing.T) {
|
||||
subcommandRun := false
|
||||
parentFlag := false
|
||||
app := NewApp()
|
||||
|
||||
app.Flags = []Flag{
|
||||
BoolFlag{Name: "debug, d", Usage: "Enable debugging"},
|
||||
}
|
||||
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "foo",
|
||||
Flags: []Flag{
|
||||
BoolFlag{Name: "parent, p", Usage: "Parent flag"},
|
||||
},
|
||||
Subcommands: []Command{
|
||||
{
|
||||
Name: "bar",
|
||||
Action: func(c *Context) {
|
||||
if c.GlobalBool("debug") {
|
||||
subcommandRun = true
|
||||
}
|
||||
if c.GlobalBool("parent") {
|
||||
parentFlag = true
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run([]string{"command", "-d", "foo", "-p", "bar"})
|
||||
|
||||
expect(t, subcommandRun, true)
|
||||
expect(t, parentFlag, true)
|
||||
}
|
||||
|
||||
func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) {
|
||||
var subcommandHelpTopics = [][]string{
|
||||
{"command", "foo", "--help"},
|
||||
{"command", "foo", "-h"},
|
||||
{"command", "foo", "help"},
|
||||
}
|
||||
|
||||
for _, flagSet := range subcommandHelpTopics {
|
||||
t.Logf("==> checking with flags %v", flagSet)
|
||||
|
||||
app := NewApp()
|
||||
buf := new(bytes.Buffer)
|
||||
app.Writer = buf
|
||||
|
||||
subCmdBar := Command{
|
||||
Name: "bar",
|
||||
Usage: "does bar things",
|
||||
}
|
||||
subCmdBaz := Command{
|
||||
Name: "baz",
|
||||
Usage: "does baz things",
|
||||
}
|
||||
cmd := Command{
|
||||
Name: "foo",
|
||||
Description: "descriptive wall of text about how it does foo things",
|
||||
Subcommands: []Command{subCmdBar, subCmdBaz},
|
||||
}
|
||||
|
||||
app.Commands = []Command{cmd}
|
||||
err := app.Run(flagSet)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
t.Logf("output: %q\n", buf.Bytes())
|
||||
|
||||
if strings.Contains(output, "No help topic for") {
|
||||
t.Errorf("expect a help topic, got none: \n%q", output)
|
||||
}
|
||||
|
||||
for _, shouldContain := range []string{
|
||||
cmd.Name, cmd.Description,
|
||||
subCmdBar.Name, subCmdBar.Usage,
|
||||
subCmdBaz.Name, subCmdBaz.Usage,
|
||||
} {
|
||||
if !strings.Contains(output, shouldContain) {
|
||||
t.Errorf("want help to contain %q, did not: \n%q", shouldContain, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_Run_SubcommandFullPath(t *testing.T) {
|
||||
app := NewApp()
|
||||
buf := new(bytes.Buffer)
|
||||
app.Writer = buf
|
||||
|
||||
subCmd := Command{
|
||||
Name: "bar",
|
||||
Usage: "does bar things",
|
||||
}
|
||||
cmd := Command{
|
||||
Name: "foo",
|
||||
Description: "foo commands",
|
||||
Subcommands: []Command{subCmd},
|
||||
}
|
||||
app.Commands = []Command{cmd}
|
||||
|
||||
err := app.Run([]string{"command", "foo", "bar", "--help"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
if !strings.Contains(output, "foo bar - does bar things") {
|
||||
t.Errorf("expected full path to subcommand: %s", output)
|
||||
}
|
||||
if !strings.Contains(output, "command foo bar [arguments...]") {
|
||||
t.Errorf("expected full path to subcommand: %s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_Run_Help(t *testing.T) {
|
||||
var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}}
|
||||
|
||||
for _, args := range helpArguments {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
t.Logf("==> checking with arguments %v", args)
|
||||
|
||||
app := NewApp()
|
||||
app.Name = "boom"
|
||||
app.Usage = "make an explosive entrance"
|
||||
app.Writer = buf
|
||||
app.Action = func(c *Context) {
|
||||
buf.WriteString("boom I say!")
|
||||
}
|
||||
|
||||
err := app.Run(args)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
t.Logf("output: %q\n", buf.Bytes())
|
||||
|
||||
if !strings.Contains(output, "boom - make an explosive entrance") {
|
||||
t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_Run_Version(t *testing.T) {
|
||||
var versionArguments = [][]string{{"boom", "--version"}, {"boom", "-v"}}
|
||||
|
||||
for _, args := range versionArguments {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
t.Logf("==> checking with arguments %v", args)
|
||||
|
||||
app := NewApp()
|
||||
app.Name = "boom"
|
||||
app.Usage = "make an explosive entrance"
|
||||
app.Version = "0.1.0"
|
||||
app.Writer = buf
|
||||
app.Action = func(c *Context) {
|
||||
buf.WriteString("boom I say!")
|
||||
}
|
||||
|
||||
err := app.Run(args)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
output := buf.String()
|
||||
t.Logf("output: %q\n", buf.Bytes())
|
||||
|
||||
if !strings.Contains(output, "0.1.0") {
|
||||
t.Errorf("want version to contain %q, did not: \n%q", "0.1.0", output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
|
||||
app := NewApp()
|
||||
app.Action = func(c *Context) {}
|
||||
app.Before = func(c *Context) error { return fmt.Errorf("before error") }
|
||||
app.After = func(c *Context) error { return fmt.Errorf("after error") }
|
||||
|
||||
err := app.Run([]string{"foo"})
|
||||
if err == nil {
|
||||
t.Fatalf("expected to recieve error from Run, got none")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "before error") {
|
||||
t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "after error") {
|
||||
t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) {
|
||||
app := NewApp()
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "bar",
|
||||
Before: func(c *Context) error { return fmt.Errorf("before error") },
|
||||
After: func(c *Context) error { return fmt.Errorf("after error") },
|
||||
},
|
||||
}
|
||||
|
||||
err := app.Run([]string{"foo", "bar"})
|
||||
if err == nil {
|
||||
t.Fatalf("expected to recieve error from Run, got none")
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "before error") {
|
||||
t.Errorf("expected text of error from Before method, but got none in \"%v\"", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "after error") {
|
||||
t.Errorf("expected text of error from After method, but got none in \"%v\"", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
#! /bin/bash
|
||||
|
||||
: ${PROG:=$(basename ${BASH_SOURCE})}
|
||||
|
||||
_cli_bash_autocomplete() {
|
||||
local cur prev opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _cli_bash_autocomplete $PROG
|
|
@ -0,0 +1,5 @@
|
|||
autoload -U compinit && compinit
|
||||
autoload -U bashcompinit && bashcompinit
|
||||
|
||||
script_dir=$(dirname $0)
|
||||
source ${script_dir}/bash_autocomplete
|
|
@ -0,0 +1,40 @@
|
|||
// Package cli provides a minimal framework for creating and organizing command line
|
||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
||||
// cli application can be written as follows:
|
||||
// func main() {
|
||||
// cli.NewApp().Run(os.Args)
|
||||
// }
|
||||
//
|
||||
// Of course this application does not do much, so let's make this an actual application:
|
||||
// func main() {
|
||||
// app := cli.NewApp()
|
||||
// app.Name = "greet"
|
||||
// app.Usage = "say a greeting"
|
||||
// app.Action = func(c *cli.Context) {
|
||||
// println("Greetings")
|
||||
// }
|
||||
//
|
||||
// app.Run(os.Args)
|
||||
// }
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MultiError struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func NewMultiError(err ...error) MultiError {
|
||||
return MultiError{Errors: err}
|
||||
}
|
||||
|
||||
func (m MultiError) Error() string {
|
||||
errs := make([]string, len(m.Errors))
|
||||
for i, err := range m.Errors {
|
||||
errs[i] = err.Error()
|
||||
}
|
||||
|
||||
return strings.Join(errs, "\n")
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
app := NewApp()
|
||||
app.Name = "todo"
|
||||
app.Usage = "task list on the command line"
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "add",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "add a task to the list",
|
||||
Action: func(c *Context) {
|
||||
println("added task: ", c.Args().First())
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "complete",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "complete a task on the list",
|
||||
Action: func(c *Context) {
|
||||
println("completed task: ", c.Args().First())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
func ExampleSubcommand() {
|
||||
app := NewApp()
|
||||
app.Name = "say"
|
||||
app.Commands = []Command{
|
||||
{
|
||||
Name: "hello",
|
||||
Aliases: []string{"hi"},
|
||||
Usage: "use it to see a description",
|
||||
Description: "This is how we describe hello the function",
|
||||
Subcommands: []Command{
|
||||
{
|
||||
Name: "english",
|
||||
Aliases: []string{"en"},
|
||||
Usage: "sends a greeting in english",
|
||||
Description: "greets someone in english",
|
||||
Flags: []Flag{
|
||||
StringFlag{
|
||||
Name: "name",
|
||||
Value: "Bob",
|
||||
Usage: "Name of the person to greet",
|
||||
},
|
||||
},
|
||||
Action: func(c *Context) {
|
||||
println("Hello, ", c.String("name"))
|
||||
},
|
||||
}, {
|
||||
Name: "spanish",
|
||||
Aliases: []string{"sp"},
|
||||
Usage: "sends a greeting in spanish",
|
||||
Flags: []Flag{
|
||||
StringFlag{
|
||||
Name: "surname",
|
||||
Value: "Jones",
|
||||
Usage: "Surname of the person to greet",
|
||||
},
|
||||
},
|
||||
Action: func(c *Context) {
|
||||
println("Hola, ", c.String("surname"))
|
||||
},
|
||||
}, {
|
||||
Name: "french",
|
||||
Aliases: []string{"fr"},
|
||||
Usage: "sends a greeting in french",
|
||||
Flags: []Flag{
|
||||
StringFlag{
|
||||
Name: "nickname",
|
||||
Value: "Stevie",
|
||||
Usage: "Nickname of the person to greet",
|
||||
},
|
||||
},
|
||||
Action: func(c *Context) {
|
||||
println("Bonjour, ", c.String("nickname"))
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Name: "bye",
|
||||
Usage: "says goodbye",
|
||||
Action: func(c *Context) {
|
||||
println("bye")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Command is a subcommand for a cli.App.
|
||||
type Command struct {
|
||||
// The name of the command
|
||||
Name string
|
||||
// short name of the command. Typically one character (deprecated, use `Aliases`)
|
||||
ShortName string
|
||||
// A list of aliases for the command
|
||||
Aliases []string
|
||||
// A short description of the usage of this command
|
||||
Usage string
|
||||
// A longer explanation of how the command works
|
||||
Description string
|
||||
// The function to call when checking for bash command completions
|
||||
BashComplete func(context *Context)
|
||||
// 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
|
||||
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
|
||||
Action func(context *Context)
|
||||
// List of child commands
|
||||
Subcommands []Command
|
||||
// List of flags to parse
|
||||
Flags []Flag
|
||||
// Treat all flags as normal arguments if true
|
||||
SkipFlagParsing bool
|
||||
// Boolean to hide built-in help command
|
||||
HideHelp bool
|
||||
|
||||
commandNamePath []string
|
||||
}
|
||||
|
||||
// Returns the full name of the command.
|
||||
// For subcommands this ensures that parent commands are part of the command path
|
||||
func (c Command) FullName() string {
|
||||
if c.commandNamePath == nil {
|
||||
return c.Name
|
||||
}
|
||||
return strings.Join(c.commandNamePath, " ")
|
||||
}
|
||||
|
||||
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
||||
func (c Command) Run(ctx *Context) error {
|
||||
if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil {
|
||||
return c.startApp(ctx)
|
||||
}
|
||||
|
||||
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
|
||||
// append help to flags
|
||||
c.Flags = append(
|
||||
c.Flags,
|
||||
HelpFlag,
|
||||
)
|
||||
}
|
||||
|
||||
if ctx.App.EnableBashCompletion {
|
||||
c.Flags = append(c.Flags, BashCompletionFlag)
|
||||
}
|
||||
|
||||
set := flagSet(c.Name, c.Flags)
|
||||
set.SetOutput(ioutil.Discard)
|
||||
|
||||
firstFlagIndex := -1
|
||||
terminatorIndex := -1
|
||||
for index, arg := range ctx.Args() {
|
||||
if arg == "--" {
|
||||
terminatorIndex = index
|
||||
break
|
||||
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
||||
firstFlagIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if firstFlagIndex > -1 && !c.SkipFlagParsing {
|
||||
args := ctx.Args()
|
||||
regularArgs := make([]string, len(args[1: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...))
|
||||
} else {
|
||||
err = set.Parse(ctx.Args().Tail())
|
||||
|
||||
// Work around issue where if the first arg in ctx.Args.Tail()
|
||||
// is a flag, set.Parse returns an error
|
||||
if c.SkipFlagParsing {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
nerr := normalizeFlags(c.Flags, set)
|
||||
if nerr != nil {
|
||||
fmt.Fprintln(ctx.App.Writer, nerr)
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return nerr
|
||||
}
|
||||
context := NewContext(ctx.App, set, ctx)
|
||||
|
||||
if checkCommandCompletions(context, c.Name) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if checkCommandHelp(context, c.Name) {
|
||||
return nil
|
||||
}
|
||||
context.Command = c
|
||||
c.Action(context)
|
||||
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
|
||||
func (c Command) HasName(name string) bool {
|
||||
for _, n := range c.Names() {
|
||||
if n == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c Command) startApp(ctx *Context) error {
|
||||
app := NewApp()
|
||||
|
||||
// set the name and usage
|
||||
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
|
||||
if c.Description != "" {
|
||||
app.Usage = c.Description
|
||||
} else {
|
||||
app.Usage = c.Usage
|
||||
}
|
||||
|
||||
// set CommandNotFound
|
||||
app.CommandNotFound = ctx.App.CommandNotFound
|
||||
|
||||
// set the flags and commands
|
||||
app.Commands = c.Subcommands
|
||||
app.Flags = c.Flags
|
||||
app.HideHelp = c.HideHelp
|
||||
|
||||
app.Version = ctx.App.Version
|
||||
app.HideVersion = ctx.App.HideVersion
|
||||
app.Compiled = ctx.App.Compiled
|
||||
app.Author = ctx.App.Author
|
||||
app.Email = ctx.App.Email
|
||||
app.Writer = ctx.App.Writer
|
||||
|
||||
// bash completion
|
||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
||||
if c.BashComplete != nil {
|
||||
app.BashComplete = c.BashComplete
|
||||
}
|
||||
|
||||
// set the actions
|
||||
app.Before = c.Before
|
||||
app.After = c.After
|
||||
if c.Action != nil {
|
||||
app.Action = c.Action
|
||||
} else {
|
||||
app.Action = helpSubcommand.Action
|
||||
}
|
||||
|
||||
var newCmds []Command
|
||||
for _, cc := range app.Commands {
|
||||
cc.commandNamePath = []string{c.Name, cc.Name}
|
||||
newCmds = append(newCmds, cc)
|
||||
}
|
||||
app.Commands = newCmds
|
||||
|
||||
return app.RunAsSubcommand(ctx)
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommandDoNotIgnoreFlags(t *testing.T) {
|
||||
app := NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
test := []string{"blah", "blah", "-break"}
|
||||
set.Parse(test)
|
||||
|
||||
c := NewContext(app, set, nil)
|
||||
|
||||
command := Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(_ *Context) {},
|
||||
}
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err.Error(), "flag provided but not defined: -break")
|
||||
}
|
||||
|
||||
func TestCommandIgnoreFlags(t *testing.T) {
|
||||
app := NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
test := []string{"blah", "blah"}
|
||||
set.Parse(test)
|
||||
|
||||
c := NewContext(app, set, nil)
|
||||
|
||||
command := Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(_ *Context) {},
|
||||
SkipFlagParsing: true,
|
||||
}
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
// Fix bug with ignoring flag parsing that would still parse the first flag
|
||||
func TestCommandIgnoreFlagsIncludingFirstArgument(t *testing.T) {
|
||||
app := NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
test := []string{"blah", "-break"}
|
||||
set.Parse(test)
|
||||
|
||||
c := NewContext(app, set, nil)
|
||||
|
||||
command := Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(_ *Context) {},
|
||||
SkipFlagParsing: true,
|
||||
}
|
||||
err := command.Run(c)
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
|
@ -0,0 +1,388 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Context is a type that is passed through to
|
||||
// each Handler action in a cli application. Context
|
||||
// can be used to retrieve context-specific Args and
|
||||
// parsed command-line options.
|
||||
type Context struct {
|
||||
App *App
|
||||
Command Command
|
||||
flagSet *flag.FlagSet
|
||||
setFlags map[string]bool
|
||||
globalSetFlags map[string]bool
|
||||
parentContext *Context
|
||||
}
|
||||
|
||||
// Creates a new context. For use in when invoking an App or Command action.
|
||||
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
|
||||
return &Context{App: app, flagSet: set, parentContext: parentCtx}
|
||||
}
|
||||
|
||||
// Looks up the value of a local int flag, returns 0 if no int flag exists
|
||||
func (c *Context) Int(name string) int {
|
||||
return lookupInt(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists
|
||||
func (c *Context) Duration(name string) time.Duration {
|
||||
return lookupDuration(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
|
||||
func (c *Context) Float64(name string) float64 {
|
||||
return lookupFloat64(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local bool flag, returns false if no bool flag exists
|
||||
func (c *Context) Bool(name string) bool {
|
||||
return lookupBool(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local boolT flag, returns false if no bool flag exists
|
||||
func (c *Context) BoolT(name string) bool {
|
||||
return lookupBoolT(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local string flag, returns "" if no string flag exists
|
||||
func (c *Context) String(name string) string {
|
||||
return lookupString(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local string slice flag, returns nil if no string slice flag exists
|
||||
func (c *Context) StringSlice(name string) []string {
|
||||
return lookupStringSlice(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local int slice flag, returns nil if no int slice flag exists
|
||||
func (c *Context) IntSlice(name string) []int {
|
||||
return lookupIntSlice(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local generic flag, returns nil if no generic flag exists
|
||||
func (c *Context) Generic(name string) interface{} {
|
||||
return lookupGeneric(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a global int flag, returns 0 if no int flag exists
|
||||
func (c *Context) GlobalInt(name string) int {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupInt(name, fs)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists
|
||||
func (c *Context) GlobalDuration(name string) time.Duration {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupDuration(name, fs)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Looks up the value of a global bool flag, returns false if no bool flag exists
|
||||
func (c *Context) GlobalBool(name string) bool {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupBool(name, fs)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Looks up the value of a global string flag, returns "" if no string flag exists
|
||||
func (c *Context) GlobalString(name string) string {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupString(name, fs)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Looks up the value of a global string slice flag, returns nil if no string slice flag exists
|
||||
func (c *Context) GlobalStringSlice(name string) []string {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupStringSlice(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Looks up the value of a global int slice flag, returns nil if no int slice flag exists
|
||||
func (c *Context) GlobalIntSlice(name string) []int {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupIntSlice(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Looks up the value of a global generic flag, returns nil if no generic flag exists
|
||||
func (c *Context) GlobalGeneric(name string) interface{} {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupGeneric(name, fs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the number of flags set
|
||||
func (c *Context) NumFlags() int {
|
||||
return c.flagSet.NFlag()
|
||||
}
|
||||
|
||||
// Determines if the flag was actually set
|
||||
func (c *Context) IsSet(name string) bool {
|
||||
if c.setFlags == nil {
|
||||
c.setFlags = make(map[string]bool)
|
||||
c.flagSet.Visit(func(f *flag.Flag) {
|
||||
c.setFlags[f.Name] = true
|
||||
})
|
||||
}
|
||||
return c.setFlags[name] == true
|
||||
}
|
||||
|
||||
// Determines if the global flag was actually set
|
||||
func (c *Context) GlobalIsSet(name string) bool {
|
||||
if c.globalSetFlags == nil {
|
||||
c.globalSetFlags = make(map[string]bool)
|
||||
ctx := c
|
||||
if ctx.parentContext != nil {
|
||||
ctx = ctx.parentContext
|
||||
}
|
||||
for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext {
|
||||
ctx.flagSet.Visit(func(f *flag.Flag) {
|
||||
c.globalSetFlags[f.Name] = true
|
||||
})
|
||||
}
|
||||
}
|
||||
return c.globalSetFlags[name]
|
||||
}
|
||||
|
||||
// Returns a slice of flag names used in this context.
|
||||
func (c *Context) FlagNames() (names []string) {
|
||||
for _, flag := range c.Command.Flags {
|
||||
name := strings.Split(flag.getName(), ",")[0]
|
||||
if name == "help" {
|
||||
continue
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a slice of global flag names used by the app.
|
||||
func (c *Context) GlobalFlagNames() (names []string) {
|
||||
for _, flag := range c.App.Flags {
|
||||
name := strings.Split(flag.getName(), ",")[0]
|
||||
if name == "help" || name == "version" {
|
||||
continue
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the parent context, if any
|
||||
func (c *Context) Parent() *Context {
|
||||
return c.parentContext
|
||||
}
|
||||
|
||||
type Args []string
|
||||
|
||||
// Returns the command line arguments associated with the context.
|
||||
func (c *Context) Args() Args {
|
||||
args := Args(c.flagSet.Args())
|
||||
return args
|
||||
}
|
||||
|
||||
// Returns the nth argument, or else a blank string
|
||||
func (a Args) Get(n int) string {
|
||||
if len(a) > n {
|
||||
return a[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Returns the first argument, or else a blank string
|
||||
func (a Args) First() string {
|
||||
return a.Get(0)
|
||||
}
|
||||
|
||||
// Return the rest of the arguments (not the first one)
|
||||
// or else an empty string slice
|
||||
func (a Args) Tail() []string {
|
||||
if len(a) >= 2 {
|
||||
return []string(a)[1:]
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Checks if there are any arguments present
|
||||
func (a Args) Present() bool {
|
||||
return len(a) != 0
|
||||
}
|
||||
|
||||
// Swaps arguments at the given indexes
|
||||
func (a Args) Swap(from, to int) error {
|
||||
if from >= len(a) || to >= len(a) {
|
||||
return errors.New("index out of range")
|
||||
}
|
||||
a[from], a[to] = a[to], a[from]
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
|
||||
if ctx.parentContext != nil {
|
||||
ctx = ctx.parentContext
|
||||
}
|
||||
for ; ctx != nil; ctx = ctx.parentContext {
|
||||
if f := ctx.flagSet.Lookup(name); f != nil {
|
||||
return ctx.flagSet
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupInt(name string, set *flag.FlagSet) int {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
val, err := strconv.Atoi(f.Value.String())
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
val, err := time.ParseDuration(f.Value.String())
|
||||
if err == nil {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func lookupFloat64(name string, set *flag.FlagSet) float64 {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
val, err := strconv.ParseFloat(f.Value.String(), 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func lookupString(name string, set *flag.FlagSet) string {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
return f.Value.String()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func lookupStringSlice(name string, set *flag.FlagSet) []string {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
return (f.Value.(*StringSlice)).Value()
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupIntSlice(name string, set *flag.FlagSet) []int {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
return (f.Value.(*IntSlice)).Value()
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
return f.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupBool(name string, set *flag.FlagSet) bool {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
val, err := strconv.ParseBool(f.Value.String())
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func lookupBoolT(name string, set *flag.FlagSet) bool {
|
||||
f := set.Lookup(name)
|
||||
if f != nil {
|
||||
val, err := strconv.ParseBool(f.Value.String())
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
|
||||
switch ff.Value.(type) {
|
||||
case *StringSlice:
|
||||
default:
|
||||
set.Set(name, ff.Value.String())
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
|
||||
visited := make(map[string]bool)
|
||||
set.Visit(func(f *flag.Flag) {
|
||||
visited[f.Name] = true
|
||||
})
|
||||
for _, f := range flags {
|
||||
parts := strings.Split(f.getName(), ",")
|
||||
if len(parts) == 1 {
|
||||
continue
|
||||
}
|
||||
var ff *flag.Flag
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
if visited[name] {
|
||||
if ff != nil {
|
||||
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
|
||||
}
|
||||
ff = set.Lookup(name)
|
||||
}
|
||||
}
|
||||
if ff == nil {
|
||||
continue
|
||||
}
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
if !visited[name] {
|
||||
copyFlag(name, ff, set)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewContext(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Int("myflag", 12, "doc")
|
||||
globalSet := flag.NewFlagSet("test", 0)
|
||||
globalSet.Int("myflag", 42, "doc")
|
||||
globalCtx := NewContext(nil, globalSet, nil)
|
||||
command := Command{Name: "mycommand"}
|
||||
c := NewContext(nil, set, globalCtx)
|
||||
c.Command = command
|
||||
expect(t, c.Int("myflag"), 12)
|
||||
expect(t, c.GlobalInt("myflag"), 42)
|
||||
expect(t, c.Command.Name, "mycommand")
|
||||
}
|
||||
|
||||
func TestContext_Int(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Int("myflag", 12, "doc")
|
||||
c := NewContext(nil, set, nil)
|
||||
expect(t, c.Int("myflag"), 12)
|
||||
}
|
||||
|
||||
func TestContext_Duration(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Duration("myflag", time.Duration(12*time.Second), "doc")
|
||||
c := NewContext(nil, set, nil)
|
||||
expect(t, c.Duration("myflag"), time.Duration(12*time.Second))
|
||||
}
|
||||
|
||||
func TestContext_String(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.String("myflag", "hello world", "doc")
|
||||
c := NewContext(nil, set, nil)
|
||||
expect(t, c.String("myflag"), "hello world")
|
||||
}
|
||||
|
||||
func TestContext_Bool(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Bool("myflag", false, "doc")
|
||||
c := NewContext(nil, set, nil)
|
||||
expect(t, c.Bool("myflag"), false)
|
||||
}
|
||||
|
||||
func TestContext_BoolT(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Bool("myflag", true, "doc")
|
||||
c := NewContext(nil, set, nil)
|
||||
expect(t, c.BoolT("myflag"), true)
|
||||
}
|
||||
|
||||
func TestContext_Args(t *testing.T) {
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Bool("myflag", false, "doc")
|
||||
c := NewContext(nil, set, nil)
|
||||
set.Parse([]string{"--myflag", "bat", "baz"})
|
||||
expect(t, len(c.Args()), 2)
|
||||
expect(t, c.Bool("myflag"), true)
|
||||
}
|
||||
|
||||
func TestContext_IsSet(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")
|
||||
globalCtx := NewContext(nil, globalSet, nil)
|
||||
c := NewContext(nil, set, globalCtx)
|
||||
set.Parse([]string{"--myflag", "bat", "baz"})
|
||||
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
|
||||
expect(t, c.IsSet("myflag"), true)
|
||||
expect(t, c.IsSet("otherflag"), false)
|
||||
expect(t, c.IsSet("bogusflag"), false)
|
||||
expect(t, c.IsSet("myflagGlobal"), false)
|
||||
}
|
||||
|
||||
func TestContext_GlobalIsSet(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")
|
||||
globalSet.Bool("myflagGlobalUnset", true, "doc")
|
||||
globalCtx := NewContext(nil, globalSet, nil)
|
||||
c := NewContext(nil, set, globalCtx)
|
||||
set.Parse([]string{"--myflag", "bat", "baz"})
|
||||
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"})
|
||||
expect(t, c.GlobalIsSet("myflag"), false)
|
||||
expect(t, c.GlobalIsSet("otherflag"), false)
|
||||
expect(t, c.GlobalIsSet("bogusflag"), false)
|
||||
expect(t, c.GlobalIsSet("myflagGlobal"), true)
|
||||
expect(t, c.GlobalIsSet("myflagGlobalUnset"), 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")
|
||||
globalCtx := NewContext(nil, globalSet, nil)
|
||||
c := NewContext(nil, set, globalCtx)
|
||||
set.Parse([]string{"--myflag", "--otherflag=foo"})
|
||||
globalSet.Parse([]string{"--myflagGlobal"})
|
||||
expect(t, c.NumFlags(), 2)
|
||||
}
|
|
@ -0,0 +1,497 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This flag enables bash-completion for all commands and subcommands
|
||||
var BashCompletionFlag = BoolFlag{
|
||||
Name: "generate-bash-completion",
|
||||
}
|
||||
|
||||
// This flag prints the version for the application
|
||||
var VersionFlag = BoolFlag{
|
||||
Name: "version, v",
|
||||
Usage: "print the version",
|
||||
}
|
||||
|
||||
// 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{
|
||||
Name: "help, h",
|
||||
Usage: "show help",
|
||||
}
|
||||
|
||||
// Flag is a common interface related to parsing flags in cli.
|
||||
// For more advanced flag parsing techniques, it is recomended that
|
||||
// this interface be implemented.
|
||||
type Flag interface {
|
||||
fmt.Stringer
|
||||
// Apply Flag settings to the given flag set
|
||||
Apply(*flag.FlagSet)
|
||||
getName() string
|
||||
}
|
||||
|
||||
func flagSet(name string, flags []Flag) *flag.FlagSet {
|
||||
set := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
|
||||
for _, f := range flags {
|
||||
f.Apply(set)
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
func eachName(longName string, fn func(string)) {
|
||||
parts := strings.Split(longName, ",")
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
fn(name)
|
||||
}
|
||||
}
|
||||
|
||||
// Generic is a generic parseable type identified by a specific flag
|
||||
type Generic interface {
|
||||
Set(value string) error
|
||||
String() string
|
||||
}
|
||||
|
||||
// GenericFlag is the flag type for types implementing Generic
|
||||
type GenericFlag struct {
|
||||
Name string
|
||||
Value Generic
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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) {
|
||||
val := f.Value
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
val.Set(envVal)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f GenericFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// StringSlice is an opaque type for []string to satisfy flag.Value
|
||||
type StringSlice []string
|
||||
|
||||
// Set appends the string value to the list of values
|
||||
func (f *StringSlice) Set(value string) error {
|
||||
*f = append(*f, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f *StringSlice) String() string {
|
||||
return fmt.Sprintf("%s", *f)
|
||||
}
|
||||
|
||||
// Value returns the slice of strings set by this flag
|
||||
func (f *StringSlice) Value() []string {
|
||||
return *f
|
||||
}
|
||||
|
||||
// StringSlice is a string flag that can be specified multiple times on the
|
||||
// command-line
|
||||
type StringSliceFlag struct {
|
||||
Name string
|
||||
Value *StringSlice
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f StringSliceFlag) String() string {
|
||||
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
||||
pref := prefixFor(firstName)
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
newVal := &StringSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
newVal.Set(s)
|
||||
}
|
||||
f.Value = newVal
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Value == nil {
|
||||
f.Value = &StringSlice{}
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f StringSliceFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// StringSlice is an opaque type for []int to satisfy flag.Value
|
||||
type IntSlice []int
|
||||
|
||||
// Set parses the value into an integer and appends it to the list of values
|
||||
func (f *IntSlice) Set(value string) error {
|
||||
tmp, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
*f = append(*f, tmp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f *IntSlice) String() string {
|
||||
return fmt.Sprintf("%d", *f)
|
||||
}
|
||||
|
||||
// Value returns the slice of ints set by this flag
|
||||
func (f *IntSlice) Value() []int {
|
||||
return *f
|
||||
}
|
||||
|
||||
// IntSliceFlag is an int flag that can be specified multiple times on the
|
||||
// command-line
|
||||
type IntSliceFlag struct {
|
||||
Name string
|
||||
Value *IntSlice
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f IntSliceFlag) String() string {
|
||||
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
||||
pref := prefixFor(firstName)
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
newVal := &IntSlice{}
|
||||
for _, s := range strings.Split(envVal, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
err := newVal.Set(s)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
f.Value = newVal
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
if f.Value == nil {
|
||||
f.Value = &IntSlice{}
|
||||
}
|
||||
set.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f IntSliceFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// BoolFlag is a switch that defaults to false
|
||||
type BoolFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f BoolFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f BoolFlag) Apply(set *flag.FlagSet) {
|
||||
val := false
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err == nil {
|
||||
val = envValBool
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f BoolFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// BoolTFlag this represents a boolean flag that is true by default, but can
|
||||
// still be set to false by --some-flag=false
|
||||
type BoolTFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f BoolTFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
||||
val := true
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
envValBool, err := strconv.ParseBool(envVal)
|
||||
if err == nil {
|
||||
val = envValBool
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Bool(name, val, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f BoolTFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// StringFlag represents a flag that takes as string value
|
||||
type StringFlag struct {
|
||||
Name string
|
||||
Value string
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f StringFlag) String() string {
|
||||
var fmtString string
|
||||
fmtString = "%s %v\t%v"
|
||||
|
||||
if len(f.Value) > 0 {
|
||||
fmtString = "%s \"%v\"\t%v"
|
||||
} else {
|
||||
fmtString = "%s %v\t%v"
|
||||
}
|
||||
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage))
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f StringFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
f.Value = envVal
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.String(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f StringFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// IntFlag is a flag that takes an integer
|
||||
// Errors if the value provided cannot be parsed
|
||||
type IntFlag struct {
|
||||
Name string
|
||||
Value int
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f IntFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f IntFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64)
|
||||
if err == nil {
|
||||
f.Value = int(envValInt)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Int(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f IntFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// DurationFlag is a flag that takes a duration specified in Go's duration
|
||||
// format: https://golang.org/pkg/time/#ParseDuration
|
||||
type DurationFlag struct {
|
||||
Name string
|
||||
Value time.Duration
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f DurationFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f DurationFlag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
envValDuration, err := time.ParseDuration(envVal)
|
||||
if err == nil {
|
||||
f.Value = envValDuration
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Duration(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f DurationFlag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// Float64Flag is a flag that takes an float value
|
||||
// Errors if the value provided cannot be parsed
|
||||
type Float64Flag struct {
|
||||
Name string
|
||||
Value float64
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f Float64Flag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
func (f Float64Flag) Apply(set *flag.FlagSet) {
|
||||
if f.EnvVar != "" {
|
||||
for _, envVar := range strings.Split(f.EnvVar, ",") {
|
||||
envVar = strings.TrimSpace(envVar)
|
||||
if envVal := os.Getenv(envVar); envVal != "" {
|
||||
envValFloat, err := strconv.ParseFloat(envVal, 10)
|
||||
if err == nil {
|
||||
f.Value = float64(envValFloat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eachName(f.Name, func(name string) {
|
||||
set.Float64(name, f.Value, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
func (f Float64Flag) getName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func prefixFor(name string) (prefix string) {
|
||||
if len(name) == 1 {
|
||||
prefix = "-"
|
||||
} else {
|
||||
prefix = "--"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func prefixedNames(fullName string) (prefixed string) {
|
||||
parts := strings.Split(fullName, ",")
|
||||
for i, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
prefixed += prefixFor(name) + name
|
||||
if i < len(parts)-1 {
|
||||
prefixed += ", "
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func withEnvHint(envVar, str string) string {
|
||||
envText := ""
|
||||
if envVar != "" {
|
||||
envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $"))
|
||||
}
|
||||
return str + envText
|
||||
}
|
|
@ -0,0 +1,740 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var boolFlagTests = []struct {
|
||||
name string
|
||||
expected string
|
||||
}{
|
||||
{"help", "--help\t"},
|
||||
{"h", "-h\t"},
|
||||
}
|
||||
|
||||
func TestBoolFlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range boolFlagTests {
|
||||
flag := BoolFlag{Name: test.name}
|
||||
output := flag.String()
|
||||
|
||||
if output != test.expected {
|
||||
t.Errorf("%s does not match %s", output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var stringFlagTests = []struct {
|
||||
name string
|
||||
value string
|
||||
expected string
|
||||
}{
|
||||
{"help", "", "--help \t"},
|
||||
{"h", "", "-h \t"},
|
||||
{"h", "", "-h \t"},
|
||||
{"test", "Something", "--test \"Something\"\t"},
|
||||
}
|
||||
|
||||
func TestStringFlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range stringFlagTests {
|
||||
flag := StringFlag{Name: test.name, Value: test.value}
|
||||
output := flag.String()
|
||||
|
||||
if output != test.expected {
|
||||
t.Errorf("%s does not match %s", output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_FOO", "derp")
|
||||
for _, test := range stringFlagTests {
|
||||
flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"}
|
||||
output := flag.String()
|
||||
|
||||
if !strings.HasSuffix(output, " [$APP_FOO]") {
|
||||
t.Errorf("%s does not end with [$APP_FOO]", output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var stringSliceFlagTests = []struct {
|
||||
name string
|
||||
value *StringSlice
|
||||
expected string
|
||||
}{
|
||||
{"help", func() *StringSlice {
|
||||
s := &StringSlice{}
|
||||
s.Set("")
|
||||
return s
|
||||
}(), "--help [--help option --help option]\t"},
|
||||
{"h", func() *StringSlice {
|
||||
s := &StringSlice{}
|
||||
s.Set("")
|
||||
return s
|
||||
}(), "-h [-h option -h option]\t"},
|
||||
{"h", func() *StringSlice {
|
||||
s := &StringSlice{}
|
||||
s.Set("")
|
||||
return s
|
||||
}(), "-h [-h option -h option]\t"},
|
||||
{"test", func() *StringSlice {
|
||||
s := &StringSlice{}
|
||||
s.Set("Something")
|
||||
return s
|
||||
}(), "--test [--test option --test option]\t"},
|
||||
}
|
||||
|
||||
func TestStringSliceFlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range stringSliceFlagTests {
|
||||
flag := StringSliceFlag{Name: test.name, Value: test.value}
|
||||
output := flag.String()
|
||||
|
||||
if output != test.expected {
|
||||
t.Errorf("%q does not match %q", output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_QWWX", "11,4")
|
||||
for _, test := range stringSliceFlagTests {
|
||||
flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"}
|
||||
output := flag.String()
|
||||
|
||||
if !strings.HasSuffix(output, " [$APP_QWWX]") {
|
||||
t.Errorf("%q does not end with [$APP_QWWX]", output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var intFlagTests = []struct {
|
||||
name string
|
||||
expected string
|
||||
}{
|
||||
{"help", "--help \"0\"\t"},
|
||||
{"h", "-h \"0\"\t"},
|
||||
}
|
||||
|
||||
func TestIntFlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range intFlagTests {
|
||||
flag := IntFlag{Name: test.name}
|
||||
output := flag.String()
|
||||
|
||||
if output != test.expected {
|
||||
t.Errorf("%s does not match %s", output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_BAR", "2")
|
||||
for _, test := range intFlagTests {
|
||||
flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"}
|
||||
output := flag.String()
|
||||
|
||||
if !strings.HasSuffix(output, " [$APP_BAR]") {
|
||||
t.Errorf("%s does not end with [$APP_BAR]", output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var durationFlagTests = []struct {
|
||||
name string
|
||||
expected string
|
||||
}{
|
||||
{"help", "--help \"0\"\t"},
|
||||
{"h", "-h \"0\"\t"},
|
||||
}
|
||||
|
||||
func TestDurationFlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range durationFlagTests {
|
||||
flag := DurationFlag{Name: test.name}
|
||||
output := flag.String()
|
||||
|
||||
if output != test.expected {
|
||||
t.Errorf("%s does not match %s", output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_BAR", "2h3m6s")
|
||||
for _, test := range durationFlagTests {
|
||||
flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"}
|
||||
output := flag.String()
|
||||
|
||||
if !strings.HasSuffix(output, " [$APP_BAR]") {
|
||||
t.Errorf("%s does not end with [$APP_BAR]", output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var intSliceFlagTests = []struct {
|
||||
name string
|
||||
value *IntSlice
|
||||
expected string
|
||||
}{
|
||||
{"help", &IntSlice{}, "--help [--help option --help option]\t"},
|
||||
{"h", &IntSlice{}, "-h [-h option -h option]\t"},
|
||||
{"h", &IntSlice{}, "-h [-h option -h option]\t"},
|
||||
{"test", func() *IntSlice {
|
||||
i := &IntSlice{}
|
||||
i.Set("9")
|
||||
return i
|
||||
}(), "--test [--test option --test option]\t"},
|
||||
}
|
||||
|
||||
func TestIntSliceFlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range intSliceFlagTests {
|
||||
flag := IntSliceFlag{Name: test.name, Value: test.value}
|
||||
output := flag.String()
|
||||
|
||||
if output != test.expected {
|
||||
t.Errorf("%q does not match %q", output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_SMURF", "42,3")
|
||||
for _, test := range intSliceFlagTests {
|
||||
flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"}
|
||||
output := flag.String()
|
||||
|
||||
if !strings.HasSuffix(output, " [$APP_SMURF]") {
|
||||
t.Errorf("%q does not end with [$APP_SMURF]", output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var float64FlagTests = []struct {
|
||||
name string
|
||||
expected string
|
||||
}{
|
||||
{"help", "--help \"0\"\t"},
|
||||
{"h", "-h \"0\"\t"},
|
||||
}
|
||||
|
||||
func TestFloat64FlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range float64FlagTests {
|
||||
flag := Float64Flag{Name: test.name}
|
||||
output := flag.String()
|
||||
|
||||
if output != test.expected {
|
||||
t.Errorf("%s does not match %s", output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_BAZ", "99.4")
|
||||
for _, test := range float64FlagTests {
|
||||
flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"}
|
||||
output := flag.String()
|
||||
|
||||
if !strings.HasSuffix(output, " [$APP_BAZ]") {
|
||||
t.Errorf("%s does not end with [$APP_BAZ]", output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var genericFlagTests = []struct {
|
||||
name string
|
||||
value Generic
|
||||
expected string
|
||||
}{
|
||||
{"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"},
|
||||
{"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"},
|
||||
}
|
||||
|
||||
func TestGenericFlagHelpOutput(t *testing.T) {
|
||||
|
||||
for _, test := range genericFlagTests {
|
||||
flag := GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"}
|
||||
output := flag.String()
|
||||
|
||||
if output != test.expected {
|
||||
t.Errorf("%q does not match %q", output, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_ZAP", "3")
|
||||
for _, test := range genericFlagTests {
|
||||
flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"}
|
||||
output := flag.String()
|
||||
|
||||
if !strings.HasSuffix(output, " [$APP_ZAP]") {
|
||||
t.Errorf("%s does not end with [$APP_ZAP]", output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMultiString(t *testing.T) {
|
||||
(&App{
|
||||
Flags: []Flag{
|
||||
StringFlag{Name: "serve, s"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.String("serve") != "10" {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.String("s") != "10" {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}).Run([]string{"run", "-s", "10"})
|
||||
}
|
||||
|
||||
func TestParseMultiStringFromEnv(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_COUNT", "20")
|
||||
(&App{
|
||||
Flags: []Flag{
|
||||
StringFlag{Name: "count, c", EnvVar: "APP_COUNT"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.String("count") != "20" {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.String("c") != "20" {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}).Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiStringFromEnvCascade(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_COUNT", "20")
|
||||
(&App{
|
||||
Flags: []Flag{
|
||||
StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.String("count") != "20" {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.String("c") != "20" {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}).Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiStringSlice(t *testing.T) {
|
||||
(&App{
|
||||
Flags: []Flag{
|
||||
StringSliceFlag{Name: "serve, s", Value: &StringSlice{}},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}).Run([]string{"run", "-s", "10", "-s", "20"})
|
||||
}
|
||||
|
||||
func TestParseMultiStringSliceFromEnv(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||
|
||||
(&App{
|
||||
Flags: []Flag{
|
||||
StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "APP_INTERVALS"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
|
||||
t.Errorf("main name not set from env")
|
||||
}
|
||||
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
|
||||
t.Errorf("short name not set from env")
|
||||
}
|
||||
},
|
||||
}).Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiStringSliceFromEnvCascade(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||
|
||||
(&App{
|
||||
Flags: []Flag{
|
||||
StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) {
|
||||
t.Errorf("main name not set from env")
|
||||
}
|
||||
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) {
|
||||
t.Errorf("short name not set from env")
|
||||
}
|
||||
},
|
||||
}).Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiInt(t *testing.T) {
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
IntFlag{Name: "serve, s"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.Int("serve") != 10 {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.Int("s") != 10 {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run", "-s", "10"})
|
||||
}
|
||||
|
||||
func TestParseMultiIntFromEnv(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_TIMEOUT_SECONDS", "10")
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.Int("timeout") != 10 {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.Int("t") != 10 {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiIntFromEnvCascade(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_TIMEOUT_SECONDS", "10")
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.Int("timeout") != 10 {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.Int("t") != 10 {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiIntSlice(t *testing.T) {
|
||||
(&App{
|
||||
Flags: []Flag{
|
||||
IntSliceFlag{Name: "serve, s", Value: &IntSlice{}},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}).Run([]string{"run", "-s", "10", "-s", "20"})
|
||||
}
|
||||
|
||||
func TestParseMultiIntSliceFromEnv(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||
|
||||
(&App{
|
||||
Flags: []Flag{
|
||||
IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "APP_INTERVALS"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
|
||||
t.Errorf("main name not set from env")
|
||||
}
|
||||
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
|
||||
t.Errorf("short name not set from env")
|
||||
}
|
||||
},
|
||||
}).Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiIntSliceFromEnvCascade(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_INTERVALS", "20,30,40")
|
||||
|
||||
(&App{
|
||||
Flags: []Flag{
|
||||
IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) {
|
||||
t.Errorf("main name not set from env")
|
||||
}
|
||||
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) {
|
||||
t.Errorf("short name not set from env")
|
||||
}
|
||||
},
|
||||
}).Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiFloat64(t *testing.T) {
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
Float64Flag{Name: "serve, s"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.Float64("serve") != 10.2 {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.Float64("s") != 10.2 {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run", "-s", "10.2"})
|
||||
}
|
||||
|
||||
func TestParseMultiFloat64FromEnv(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.Float64("timeout") != 15.5 {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.Float64("t") != 15.5 {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiFloat64FromEnvCascade(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.Float64("timeout") != 15.5 {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.Float64("t") != 15.5 {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiBool(t *testing.T) {
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
BoolFlag{Name: "serve, s"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.Bool("serve") != true {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.Bool("s") != true {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run", "--serve"})
|
||||
}
|
||||
|
||||
func TestParseMultiBoolFromEnv(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_DEBUG", "1")
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.Bool("debug") != true {
|
||||
t.Errorf("main name not set from env")
|
||||
}
|
||||
if ctx.Bool("d") != true {
|
||||
t.Errorf("short name not set from env")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiBoolFromEnvCascade(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_DEBUG", "1")
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.Bool("debug") != true {
|
||||
t.Errorf("main name not set from env")
|
||||
}
|
||||
if ctx.Bool("d") != true {
|
||||
t.Errorf("short name not set from env")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiBoolT(t *testing.T) {
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
BoolTFlag{Name: "serve, s"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.BoolT("serve") != true {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if ctx.BoolT("s") != true {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run", "--serve"})
|
||||
}
|
||||
|
||||
func TestParseMultiBoolTFromEnv(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_DEBUG", "0")
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.BoolT("debug") != false {
|
||||
t.Errorf("main name not set from env")
|
||||
}
|
||||
if ctx.BoolT("d") != false {
|
||||
t.Errorf("short name not set from env")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseMultiBoolTFromEnvCascade(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_DEBUG", "0")
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if ctx.BoolT("debug") != false {
|
||||
t.Errorf("main name not set from env")
|
||||
}
|
||||
if ctx.BoolT("d") != false {
|
||||
t.Errorf("short name not set from env")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run"})
|
||||
}
|
||||
|
||||
type Parser [2]string
|
||||
|
||||
func (p *Parser) Set(value string) error {
|
||||
parts := strings.Split(value, ",")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid format")
|
||||
}
|
||||
|
||||
(*p)[0] = parts[0]
|
||||
(*p)[1] = parts[1]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) String() string {
|
||||
return fmt.Sprintf("%s,%s", p[0], p[1])
|
||||
}
|
||||
|
||||
func TestParseGeneric(t *testing.T) {
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
GenericFlag{Name: "serve, s", Value: &Parser{}},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) {
|
||||
t.Errorf("main name not set")
|
||||
}
|
||||
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) {
|
||||
t.Errorf("short name not set")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run", "-s", "10,20"})
|
||||
}
|
||||
|
||||
func TestParseGenericFromEnv(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_SERVE", "20,30")
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) {
|
||||
t.Errorf("main name not set from env")
|
||||
}
|
||||
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) {
|
||||
t.Errorf("short name not set from env")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run"})
|
||||
}
|
||||
|
||||
func TestParseGenericFromEnvCascade(t *testing.T) {
|
||||
os.Clearenv()
|
||||
os.Setenv("APP_FOO", "99,2000")
|
||||
a := App{
|
||||
Flags: []Flag{
|
||||
GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"},
|
||||
},
|
||||
Action: func(ctx *Context) {
|
||||
if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) {
|
||||
t.Errorf("value not set from env")
|
||||
}
|
||||
},
|
||||
}
|
||||
a.Run([]string{"run"})
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// The text template for the Default help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var AppHelpTemplate = `NAME:
|
||||
{{.Name}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.Name}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} [arguments...]
|
||||
{{if .Version}}
|
||||
VERSION:
|
||||
{{.Version}}
|
||||
{{end}}{{if len .Authors}}
|
||||
AUTHOR(S):
|
||||
{{range .Authors}}{{ . }}{{end}}
|
||||
{{end}}{{if .Commands}}
|
||||
COMMANDS:
|
||||
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
||||
{{end}}{{end}}{{if .Flags}}
|
||||
GLOBAL OPTIONS:
|
||||
{{range .Flags}}{{.}}
|
||||
{{end}}{{end}}{{if .Copyright }}
|
||||
COPYRIGHT:
|
||||
{{.Copyright}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
// The text template for the command help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var CommandHelpTemplate = `NAME:
|
||||
{{.FullName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
command {{.FullName}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if .Flags}}
|
||||
|
||||
OPTIONS:
|
||||
{{range .Flags}}{{.}}
|
||||
{{end}}{{ end }}
|
||||
`
|
||||
|
||||
// The text template for the subcommand help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var SubcommandHelpTemplate = `NAME:
|
||||
{{.Name}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...]
|
||||
|
||||
COMMANDS:
|
||||
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
||||
{{end}}{{if .Flags}}
|
||||
OPTIONS:
|
||||
{{range .Flags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
var helpCommand = Command{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
Action: func(c *Context) {
|
||||
args := c.Args()
|
||||
if args.Present() {
|
||||
ShowCommandHelp(c, args.First())
|
||||
} else {
|
||||
ShowAppHelp(c)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var helpSubcommand = Command{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
Action: func(c *Context) {
|
||||
args := c.Args()
|
||||
if args.Present() {
|
||||
ShowCommandHelp(c, args.First())
|
||||
} else {
|
||||
ShowSubcommandHelp(c)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Prints help for the App or Command
|
||||
type helpPrinter func(w io.Writer, templ string, data interface{})
|
||||
|
||||
var HelpPrinter helpPrinter = printHelp
|
||||
|
||||
// Prints version for the App
|
||||
var VersionPrinter = printVersion
|
||||
|
||||
func ShowAppHelp(c *Context) {
|
||||
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
|
||||
}
|
||||
|
||||
// Prints the list of subcommands as the default app completion method
|
||||
func DefaultAppComplete(c *Context) {
|
||||
for _, command := range c.App.Commands {
|
||||
for _, name := range command.Names() {
|
||||
fmt.Fprintln(c.App.Writer, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prints help for the given command
|
||||
func ShowCommandHelp(ctx *Context, command string) {
|
||||
// show the subcommand help for a command with subcommands
|
||||
if command == "" {
|
||||
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
|
||||
return
|
||||
}
|
||||
|
||||
for _, c := range ctx.App.Commands {
|
||||
if c.HasName(command) {
|
||||
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.App.CommandNotFound != nil {
|
||||
ctx.App.CommandNotFound(ctx, command)
|
||||
} else {
|
||||
fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command)
|
||||
}
|
||||
}
|
||||
|
||||
// Prints help for the given subcommand
|
||||
func ShowSubcommandHelp(c *Context) {
|
||||
ShowCommandHelp(c, c.Command.Name)
|
||||
}
|
||||
|
||||
// Prints the version number of the App
|
||||
func ShowVersion(c *Context) {
|
||||
VersionPrinter(c)
|
||||
}
|
||||
|
||||
func printVersion(c *Context) {
|
||||
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
|
||||
}
|
||||
|
||||
// Prints the lists of commands within a given context
|
||||
func ShowCompletions(c *Context) {
|
||||
a := c.App
|
||||
if a != nil && a.BashComplete != nil {
|
||||
a.BashComplete(c)
|
||||
}
|
||||
}
|
||||
|
||||
// Prints the custom completions for a given command
|
||||
func ShowCommandCompletions(ctx *Context, command string) {
|
||||
c := ctx.App.Command(command)
|
||||
if c != nil && c.BashComplete != nil {
|
||||
c.BashComplete(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func printHelp(out io.Writer, templ string, data interface{}) {
|
||||
funcMap := template.FuncMap{
|
||||
"join": strings.Join,
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(out, 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()
|
||||
}
|
||||
|
||||
func checkVersion(c *Context) bool {
|
||||
if c.GlobalBool("version") || c.GlobalBool("v") || c.Bool("version") || c.Bool("v") {
|
||||
ShowVersion(c)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkHelp(c *Context) bool {
|
||||
if c.GlobalBool("h") || c.GlobalBool("help") || c.Bool("h") || c.Bool("help") {
|
||||
ShowAppHelp(c)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkCommandHelp(c *Context, name string) bool {
|
||||
if c.Bool("h") || c.Bool("help") {
|
||||
ShowCommandHelp(c, name)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkSubcommandHelp(c *Context) bool {
|
||||
if c.GlobalBool("h") || c.GlobalBool("help") {
|
||||
ShowSubcommandHelp(c)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkCompletions(c *Context) bool {
|
||||
if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion {
|
||||
ShowCompletions(c)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func checkCommandCompletions(c *Context, name string) bool {
|
||||
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion {
|
||||
ShowCommandCompletions(c, name)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_ShowAppHelp_NoAuthor(t *testing.T) {
|
||||
output := new(bytes.Buffer)
|
||||
app := NewApp()
|
||||
app.Writer = output
|
||||
|
||||
c := NewContext(app, nil, nil)
|
||||
|
||||
ShowAppHelp(c)
|
||||
|
||||
if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 {
|
||||
t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ShowAppHelp_NoVersion(t *testing.T) {
|
||||
output := new(bytes.Buffer)
|
||||
app := NewApp()
|
||||
app.Writer = output
|
||||
|
||||
app.Version = ""
|
||||
|
||||
c := NewContext(app, nil, nil)
|
||||
|
||||
ShowAppHelp(c)
|
||||
|
||||
if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 {
|
||||
t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
/* Test Helpers */
|
||||
func expect(t *testing.T, a interface{}, b interface{}) {
|
||||
if a != b {
|
||||
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||
}
|
||||
}
|
||||
|
||||
func refute(t *testing.T, a interface{}, b interface{}) {
|
||||
if a == b {
|
||||
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/amazonec2"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(amazonec2.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/azure"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(azure.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/digitalocean"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(digitalocean.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/exoscale"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(exoscale.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/generic"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(generic.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/google"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(google.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/hyperv"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(hyperv.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/none"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(none.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/openstack"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(openstack.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/rackspace"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(rackspace.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/softlayer"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(softlayer.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/virtualbox"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(virtualbox.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/vmwarefusion"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(vmwarefusion.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/vmwarevcloudair"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(vmwarevcloudair.NewDriver("", ""))
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/drivers/vmwarevsphere"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.RegisterDriver(vmwarevsphere.NewDriver("", ""))
|
||||
}
|
|
@ -3,10 +3,11 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
"github.com/docker/machine/commands"
|
||||
"github.com/docker/machine/commands/mcndirs"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
|
@ -84,11 +85,14 @@ func main() {
|
|||
mcndirs.BaseDir = c.GlobalString("storage-path")
|
||||
return nil
|
||||
}
|
||||
|
||||
app.Commands = commands.Commands
|
||||
app.CommandNotFound = cmdNotFound
|
||||
app.Usage = "Create and manage machines running Docker."
|
||||
app.Version = version.Version + " (" + version.GitCommit + ")"
|
||||
|
||||
log.Debug("Docker Machine Version: ", app.Version)
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug, D",
|
||||
|
@ -137,7 +141,30 @@ func main() {
|
|||
},
|
||||
}
|
||||
|
||||
go commands.DeferClosePluginServers()
|
||||
|
||||
// Cleanup to run in case the user sends an interrupt (CTRL+C) to the
|
||||
// Machine program. Ensure that we do not leave dangling OS processes.
|
||||
signalCh := make(chan os.Signal, 1)
|
||||
signal.Notify(signalCh, os.Interrupt)
|
||||
|
||||
// Atypical exit condition -- write to the cleanup done channel after
|
||||
// ensuring that we have closed all exec-ed plugin servers.
|
||||
go func() {
|
||||
for range signalCh {
|
||||
log.Info("\nReceieved an interrupt, performing cleanup work...")
|
||||
close(commands.RpcClientDriversCh)
|
||||
<-commands.RpcDriversClosedCh
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO: Close plugin servers in case of client panic.
|
||||
|
||||
app.Run(os.Args)
|
||||
|
||||
close(commands.RpcClientDriversCh)
|
||||
<-commands.RpcDriversClosedCh
|
||||
}
|
||||
|
||||
func cmdNotFound(c *cli.Context, command string) {
|
||||
|
@ -146,6 +173,6 @@ func cmdNotFound(c *cli.Context, command string) {
|
|||
c.App.Name,
|
||||
command,
|
||||
c.App.Name,
|
||||
c.App.Name,
|
||||
os.Args[0],
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,19 +3,18 @@ package commands
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/cli"
|
||||
)
|
||||
|
||||
func cmdActive(c *cli.Context) {
|
||||
if len(c.Args()) > 0 {
|
||||
log.Fatal("Error: Too many arguments given.")
|
||||
fatal("Error: Too many arguments given.")
|
||||
}
|
||||
|
||||
store := getStore(c)
|
||||
host, err := getActiveHost(store)
|
||||
if err != nil {
|
||||
log.Fatalf("Error getting active host: %s", err)
|
||||
fatalf("Error getting active host: %s", err)
|
||||
}
|
||||
|
||||
if host != nil {
|
||||
|
|
|
@ -8,25 +8,10 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
"github.com/docker/machine/commands/mcndirs"
|
||||
_ "github.com/docker/machine/drivers/amazonec2"
|
||||
_ "github.com/docker/machine/drivers/azure"
|
||||
_ "github.com/docker/machine/drivers/digitalocean"
|
||||
_ "github.com/docker/machine/drivers/exoscale"
|
||||
_ "github.com/docker/machine/drivers/generic"
|
||||
_ "github.com/docker/machine/drivers/google"
|
||||
_ "github.com/docker/machine/drivers/hyperv"
|
||||
_ "github.com/docker/machine/drivers/none"
|
||||
_ "github.com/docker/machine/drivers/openstack"
|
||||
_ "github.com/docker/machine/drivers/rackspace"
|
||||
_ "github.com/docker/machine/drivers/softlayer"
|
||||
_ "github.com/docker/machine/drivers/virtualbox"
|
||||
_ "github.com/docker/machine/drivers/vmwarefusion"
|
||||
_ "github.com/docker/machine/drivers/vmwarevcloudair"
|
||||
_ "github.com/docker/machine/drivers/vmwarevsphere"
|
||||
"github.com/docker/machine/libmachine/cert"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/drivers/rpc"
|
||||
"github.com/docker/machine/libmachine/host"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/persist"
|
||||
|
@ -36,16 +21,68 @@ var (
|
|||
ErrUnknownShell = errors.New("Error: Unknown shell")
|
||||
ErrNoMachineSpecified = errors.New("Error: Expected to get one or more machine names as arguments.")
|
||||
ErrExpectedOneMachine = errors.New("Error: Expected one machine name as an argument.")
|
||||
|
||||
RpcClientDriversCh = make(chan *rpcdriver.RpcClientDriver)
|
||||
RpcDriversClosedCh = make(chan bool)
|
||||
)
|
||||
|
||||
func newPluginDriver(driverName string, rawContent []byte) (*rpcdriver.RpcClientDriver, error) {
|
||||
d, err := rpcdriver.NewRpcClientDriver(rawContent, driverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
RpcClientDriversCh <- d
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func DeferClosePluginServers() {
|
||||
rpcClientDrivers := []*rpcdriver.RpcClientDriver{}
|
||||
|
||||
for d := range RpcClientDriversCh {
|
||||
rpcClientDrivers = append(rpcClientDrivers, d)
|
||||
}
|
||||
|
||||
doneCh := make(chan bool)
|
||||
|
||||
for _, d := range rpcClientDrivers {
|
||||
d := d
|
||||
go func() {
|
||||
if err := d.Close(); err != nil {
|
||||
log.Debugf("Error closing connection to plugin server: %s", err)
|
||||
}
|
||||
|
||||
doneCh <- true
|
||||
}()
|
||||
}
|
||||
|
||||
for range rpcClientDrivers {
|
||||
<-doneCh
|
||||
}
|
||||
|
||||
RpcDriversClosedCh <- true
|
||||
}
|
||||
|
||||
func fatal(args ...interface{}) {
|
||||
close(RpcClientDriversCh)
|
||||
<-RpcDriversClosedCh
|
||||
log.Fatal(args...)
|
||||
}
|
||||
|
||||
func fatalf(fmtString string, args ...interface{}) {
|
||||
close(RpcClientDriversCh)
|
||||
<-RpcDriversClosedCh
|
||||
log.Fatalf(fmtString, args...)
|
||||
}
|
||||
|
||||
func confirmInput(msg string) bool {
|
||||
fmt.Printf("%s (y/n): ", msg)
|
||||
var resp string
|
||||
_, err := fmt.Scanln(&resp)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if strings.Index(strings.ToLower(resp), "y") == 0 {
|
||||
|
@ -65,16 +102,72 @@ func getStore(c *cli.Context) persist.Store {
|
|||
}
|
||||
}
|
||||
|
||||
func listHosts(store persist.Store) ([]*host.Host, error) {
|
||||
cliHosts := []*host.Host{}
|
||||
|
||||
hosts, err := store.List()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error attempting to list hosts from store: %s", err)
|
||||
}
|
||||
|
||||
for _, h := range hosts {
|
||||
d, err := newPluginDriver(h.DriverName, h.RawDriver)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error attempting to invoke binary for plugin: %s", err)
|
||||
}
|
||||
|
||||
h.Driver = d
|
||||
|
||||
cliHosts = append(cliHosts, h)
|
||||
}
|
||||
|
||||
return cliHosts, nil
|
||||
}
|
||||
|
||||
func loadHost(store persist.Store, hostName string) (*host.Host, error) {
|
||||
h, err := store.Load(hostName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Loading host from store failed: %s", err)
|
||||
}
|
||||
|
||||
d, err := newPluginDriver(h.DriverName, h.RawDriver)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error attempting to invoke binary for plugin: %s", err)
|
||||
}
|
||||
|
||||
h.Driver = d
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func saveHost(store persist.Store, h *host.Host) error {
|
||||
if err := store.Save(h); err != nil {
|
||||
return fmt.Errorf("Error attempting to save host to store: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFirstArgHost(c *cli.Context) *host.Host {
|
||||
store := getStore(c)
|
||||
hostName := c.Args().First()
|
||||
h, err := store.Load(hostName)
|
||||
|
||||
exists, err := store.Exists(hostName)
|
||||
if err != nil {
|
||||
fatalf("Error checking if host %q exists: %s", hostName, err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
fatalf("Host %q does not exist", hostName)
|
||||
}
|
||||
|
||||
h, err := loadHost(store, hostName)
|
||||
if err != nil {
|
||||
// I guess I feel OK with bailing here since if we can't get
|
||||
// the host reliably we're definitely not going to be able to
|
||||
// do anything else interesting, but also this premature exit
|
||||
// feels wrong to me. Let's revisit it later.
|
||||
log.Fatalf("Error trying to get host %q: %s", hostName, err)
|
||||
fatalf("Error trying to get host %q: %s", hostName, err)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
@ -84,7 +177,7 @@ func getHostsFromContext(c *cli.Context) ([]*host.Host, error) {
|
|||
hosts := []*host.Host{}
|
||||
|
||||
for _, hostName := range c.Args() {
|
||||
h, err := store.Load(hostName)
|
||||
h, err := loadHost(store, hostName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not load host %q: %s", hostName, err)
|
||||
}
|
||||
|
@ -94,91 +187,6 @@ func getHostsFromContext(c *cli.Context) ([]*host.Host, error) {
|
|||
return hosts, nil
|
||||
}
|
||||
|
||||
var sharedCreateFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "driver, d",
|
||||
Usage: fmt.Sprintf(
|
||||
"Driver to create machine with. Available drivers: %s",
|
||||
strings.Join(drivers.GetDriverNames(), ", "),
|
||||
),
|
||||
Value: "none",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "engine-install-url",
|
||||
Usage: "Custom URL to use for engine installation",
|
||||
Value: "https://get.docker.com",
|
||||
EnvVar: "MACHINE_DOCKER_INSTALL_URL",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "engine-opt",
|
||||
Usage: "Specify arbitrary flags to include with the created engine in the form flag=value",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "engine-insecure-registry",
|
||||
Usage: "Specify insecure registries to allow with the created engine",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "engine-registry-mirror",
|
||||
Usage: "Specify registry mirrors to use",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "engine-label",
|
||||
Usage: "Specify labels for the created engine",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "engine-storage-driver",
|
||||
Usage: "Specify a storage driver to use with the engine",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "engine-env",
|
||||
Usage: "Specify environment variables to set in the engine",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "swarm",
|
||||
Usage: "Configure Machine with Swarm",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "swarm-image",
|
||||
Usage: "Specify Docker image to use for Swarm",
|
||||
Value: "swarm:latest",
|
||||
EnvVar: "MACHINE_SWARM_IMAGE",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "swarm-master",
|
||||
Usage: "Configure Machine to be a Swarm master",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "swarm-discovery",
|
||||
Usage: "Discovery service to use with Swarm",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "swarm-strategy",
|
||||
Usage: "Define a default scheduling strategy for Swarm",
|
||||
Value: "spread",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "swarm-opt",
|
||||
Usage: "Define arbitrary flags for swarm",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "swarm-host",
|
||||
Usage: "ip/socket to listen on for Swarm master",
|
||||
Value: "tcp://0.0.0.0:3376",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "swarm-addr",
|
||||
Usage: "addr to advertise for Swarm (default: detect and use the machine IP)",
|
||||
Value: "",
|
||||
},
|
||||
}
|
||||
|
||||
var Commands = []cli.Command{
|
||||
{
|
||||
Name: "active",
|
||||
|
@ -198,13 +206,11 @@ var Commands = []cli.Command{
|
|||
},
|
||||
},
|
||||
{
|
||||
Flags: append(
|
||||
drivers.GetCreateFlags(),
|
||||
sharedCreateFlags...,
|
||||
),
|
||||
Name: "create",
|
||||
Usage: "Create a machine",
|
||||
Action: cmdCreate,
|
||||
Flags: sharedCreateFlags,
|
||||
Name: "create",
|
||||
Usage: "Create a machine.\n\nSpecify a driver with --driver to include the create flags for that driver in the help text.",
|
||||
Action: cmdCreateOuter,
|
||||
SkipFlagParsing: true,
|
||||
},
|
||||
{
|
||||
Name: "env",
|
||||
|
@ -379,20 +385,16 @@ func machineCommand(actionName string, host *host.Host, errorChan chan<- error)
|
|||
|
||||
log.Debugf("command=%s machine=%s", actionName, host.Name)
|
||||
|
||||
if err := commands[actionName](); err != nil {
|
||||
errorChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
errorChan <- nil
|
||||
errorChan <- commands[actionName]()
|
||||
}
|
||||
|
||||
// runActionForeachMachine will run the command across multiple machines
|
||||
func runActionForeachMachine(actionName string, machines []*host.Host) {
|
||||
func runActionForeachMachine(actionName string, machines []*host.Host) []error {
|
||||
var (
|
||||
numConcurrentActions = 0
|
||||
serialMachines = []*host.Host{}
|
||||
errorChan = make(chan error)
|
||||
errs = []error{}
|
||||
)
|
||||
|
||||
for _, machine := range machines {
|
||||
|
@ -427,10 +429,22 @@ func runActionForeachMachine(actionName string, machines []*host.Host) {
|
|||
for i := 0; i < numConcurrentActions; i++ {
|
||||
if err := <-errorChan; err != nil {
|
||||
log.Errorln(err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
close(errorChan)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func consolidateErrs(errs []error) error {
|
||||
finalErr := ""
|
||||
for _, err := range errs {
|
||||
finalErr = fmt.Sprintf("%s\n%s", finalErr, err)
|
||||
}
|
||||
|
||||
return errors.New(strings.TrimSpace(finalErr))
|
||||
}
|
||||
|
||||
func runActionWithContext(actionName string, c *cli.Context) error {
|
||||
|
@ -442,13 +456,15 @@ func runActionWithContext(actionName string, c *cli.Context) error {
|
|||
}
|
||||
|
||||
if len(hosts) == 0 {
|
||||
log.Fatal(ErrNoMachineSpecified)
|
||||
return ErrNoMachineSpecified
|
||||
}
|
||||
|
||||
runActionForeachMachine(actionName, hosts)
|
||||
if errs := runActionForeachMachine(actionName, hosts); len(errs) > 0 {
|
||||
return consolidateErrs(errs)
|
||||
}
|
||||
|
||||
for _, h := range hosts {
|
||||
if err := store.Save(h); err != nil {
|
||||
if err := saveHost(store, h); err != nil {
|
||||
return fmt.Errorf("Error saving host to store: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -19,14 +20,14 @@ func TestRunActionForeachMachine(t *testing.T) {
|
|||
{
|
||||
Name: "foo",
|
||||
DriverName: "fakedriver",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Running,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "bar",
|
||||
DriverName: "fakedriver",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Stopped,
|
||||
},
|
||||
},
|
||||
|
@ -37,28 +38,28 @@ func TestRunActionForeachMachine(t *testing.T) {
|
|||
// virtualbox... (to test serial actions)
|
||||
// It's actually FakeDriver!
|
||||
DriverName: "virtualbox",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Stopped,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "spam",
|
||||
DriverName: "virtualbox",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Running,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "eggs",
|
||||
DriverName: "fakedriver",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Stopped,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ham",
|
||||
DriverName: "fakedriver",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Running,
|
||||
},
|
||||
},
|
||||
|
@ -117,7 +118,7 @@ func TestPrintIPEmptyGivenLocalEngine(t *testing.T) {
|
|||
func TestPrintIPPrintsGivenRemoteEngine(t *testing.T) {
|
||||
defer cleanup()
|
||||
host, _ := hosttest.GetDefaultTestHost()
|
||||
host.Driver = &fakedriver.FakeDriver{}
|
||||
host.Driver = &fakedriver.Driver{}
|
||||
|
||||
out, w := captureStdout()
|
||||
|
||||
|
@ -127,3 +128,28 @@ func TestPrintIPPrintsGivenRemoteEngine(t *testing.T) {
|
|||
|
||||
assert.Equal(t, "1.2.3.4", strings.TrimSpace(<-out))
|
||||
}
|
||||
|
||||
func TestConsolidateError(t *testing.T) {
|
||||
cases := []struct {
|
||||
inputErrs []error
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
inputErrs: []error{
|
||||
errors.New("Couldn't remove host 'bar'"),
|
||||
},
|
||||
expectedErr: errors.New("Couldn't remove host 'bar'"),
|
||||
},
|
||||
{
|
||||
inputErrs: []error{
|
||||
errors.New("Couldn't remove host 'bar'"),
|
||||
errors.New("Couldn't remove host 'foo'"),
|
||||
},
|
||||
expectedErr: errors.New("Couldn't remove host 'bar'\nCouldn't remove host 'foo'"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
assert.Equal(t, c.expectedErr, consolidateErrs(c.inputErrs))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
"github.com/docker/machine/libmachine/auth"
|
||||
"github.com/docker/machine/libmachine/cert"
|
||||
"github.com/docker/machine/libmachine/host"
|
||||
|
@ -15,14 +15,14 @@ import (
|
|||
|
||||
func cmdConfig(c *cli.Context) {
|
||||
if len(c.Args()) != 1 {
|
||||
log.Fatal(ErrExpectedOneMachine)
|
||||
fatal(ErrExpectedOneMachine)
|
||||
}
|
||||
|
||||
h := getFirstArgHost(c)
|
||||
|
||||
dockerHost, authOptions, err := runConnectionBoilerplate(h, c)
|
||||
if err != nil {
|
||||
log.Fatalf("Error running connection boilerplate: %s", err)
|
||||
fatalf("Error running connection boilerplate: %s", err)
|
||||
}
|
||||
|
||||
log.Debug(dockerHost)
|
||||
|
|
|
@ -1,70 +1,167 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
"github.com/docker/machine/commands/mcndirs"
|
||||
"github.com/docker/machine/drivers/driverfactory"
|
||||
"github.com/docker/machine/libmachine"
|
||||
"github.com/docker/machine/libmachine/auth"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/drivers/rpc"
|
||||
"github.com/docker/machine/libmachine/engine"
|
||||
"github.com/docker/machine/libmachine/host"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnerror"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/persist"
|
||||
"github.com/docker/machine/libmachine/swarm"
|
||||
)
|
||||
|
||||
func cmdCreate(c *cli.Context) {
|
||||
var (
|
||||
driver drivers.Driver
|
||||
)
|
||||
var (
|
||||
sharedCreateFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "driver, d",
|
||||
Usage: fmt.Sprintf(
|
||||
"Driver to create machine with.",
|
||||
),
|
||||
Value: "none",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "engine-install-url",
|
||||
Usage: "Custom URL to use for engine installation",
|
||||
Value: "https://get.docker.com",
|
||||
EnvVar: "MACHINE_DOCKER_INSTALL_URL",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "engine-opt",
|
||||
Usage: "Specify arbitrary flags to include with the created engine in the form flag=value",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "engine-insecure-registry",
|
||||
Usage: "Specify insecure registries to allow with the created engine",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "engine-registry-mirror",
|
||||
Usage: "Specify registry mirrors to use",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "engine-label",
|
||||
Usage: "Specify labels for the created engine",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "engine-storage-driver",
|
||||
Usage: "Specify a storage driver to use with the engine",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "engine-env",
|
||||
Usage: "Specify environment variables to set in the engine",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "swarm",
|
||||
Usage: "Configure Machine with Swarm",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "swarm-image",
|
||||
Usage: "Specify Docker image to use for Swarm",
|
||||
Value: "swarm:latest",
|
||||
EnvVar: "MACHINE_SWARM_IMAGE",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "swarm-master",
|
||||
Usage: "Configure Machine to be a Swarm master",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "swarm-discovery",
|
||||
Usage: "Discovery service to use with Swarm",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "swarm-strategy",
|
||||
Usage: "Define a default scheduling strategy for Swarm",
|
||||
Value: "spread",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "swarm-opt",
|
||||
Usage: "Define arbitrary flags for swarm",
|
||||
Value: &cli.StringSlice{},
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "swarm-host",
|
||||
Usage: "ip/socket to listen on for Swarm master",
|
||||
Value: "tcp://0.0.0.0:3376",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "swarm-addr",
|
||||
Usage: "addr to advertise for Swarm (default: detect and use the machine IP)",
|
||||
Value: "",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func cmdCreateInner(c *cli.Context) {
|
||||
if len(c.Args()) > 1 {
|
||||
fatalf("Invalid command line. Found extra arguments %v", c.Args()[1:])
|
||||
}
|
||||
|
||||
driverName := c.String("driver")
|
||||
name := c.Args().First()
|
||||
driverName := c.String("driver")
|
||||
certInfo := getCertPathInfoFromContext(c)
|
||||
|
||||
storePath := c.GlobalString("storage-path")
|
||||
|
||||
store := &persist.Filestore{
|
||||
Path: storePath,
|
||||
CaCertPath: certInfo.CaCertPath,
|
||||
CaPrivateKeyPath: certInfo.CaPrivateKeyPath,
|
||||
}
|
||||
|
||||
// TODO: Not really a fan of "none" as the default driver...
|
||||
if driverName != "none" {
|
||||
var err error
|
||||
|
||||
c.App.Commands, err = trimDriverFlags(driverName, c.App.Commands)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
cli.ShowCommandHelp(c, "create")
|
||||
log.Fatal("You must specify a machine name")
|
||||
}
|
||||
|
||||
if len(c.Args()) > 1 {
|
||||
log.Fatalf("Invalid command line. Found extra arguments %v", c.Args()[1:])
|
||||
fatal("Error: No machine name specified.")
|
||||
}
|
||||
|
||||
validName := host.ValidateHostName(name)
|
||||
if !validName {
|
||||
log.Fatal("Error creating machine: ", mcnerror.ErrInvalidHostname)
|
||||
fatal("Error creating machine: ", mcnerror.ErrInvalidHostname)
|
||||
}
|
||||
|
||||
if err := validateSwarmDiscovery(c.String("swarm-discovery")); err != nil {
|
||||
log.Fatalf("Error parsing swarm discovery: %s", err)
|
||||
fatalf("Error parsing swarm discovery: %s", err)
|
||||
}
|
||||
|
||||
hostOptions := &host.HostOptions{
|
||||
// TODO: Fix hacky JSON solution
|
||||
bareDriverData, err := json.Marshal(&drivers.BaseDriver{
|
||||
MachineName: name,
|
||||
StorePath: c.GlobalString("storage-path"),
|
||||
})
|
||||
if err != nil {
|
||||
fatalf("Error attempting to marshal bare driver data: %s", err)
|
||||
}
|
||||
|
||||
driver, err := newPluginDriver(driverName, bareDriverData)
|
||||
if err != nil {
|
||||
fatalf("Error loading driver %q: %s", driverName, err)
|
||||
}
|
||||
|
||||
h, err := store.NewHost(driver)
|
||||
if err != nil {
|
||||
fatalf("Error getting new host: %s", err)
|
||||
}
|
||||
|
||||
h.HostOptions = &host.HostOptions{
|
||||
AuthOptions: &auth.AuthOptions{
|
||||
CertDir: mcndirs.GetMachineCertDir(),
|
||||
CaCertPath: certInfo.CaCertPath,
|
||||
|
@ -97,59 +194,213 @@ func cmdCreate(c *cli.Context) {
|
|||
},
|
||||
}
|
||||
|
||||
driver, err := driverfactory.NewDriver(driverName, name, storePath)
|
||||
if err != nil {
|
||||
log.Fatalf("Error trying to get driver: %s", err)
|
||||
}
|
||||
|
||||
h, err := store.NewHost(driver)
|
||||
if err != nil {
|
||||
log.Fatalf("Error getting new host: %s", err)
|
||||
}
|
||||
|
||||
h.HostOptions = hostOptions
|
||||
|
||||
exists, err := store.Exists(h.Name)
|
||||
if err != nil {
|
||||
log.Fatalf("Error checking if host exists: %s", err)
|
||||
fatalf("Error checking if host exists: %s", err)
|
||||
}
|
||||
if exists {
|
||||
log.Fatal(mcnerror.ErrHostAlreadyExists{
|
||||
fatal(mcnerror.ErrHostAlreadyExists{
|
||||
Name: h.Name,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: This should be moved out of the driver and done in the
|
||||
// commands module.
|
||||
if err := h.Driver.SetConfigFromFlags(c); err != nil {
|
||||
log.Fatalf("Error setting machine configuration from flags provided: %s", err)
|
||||
// driverOpts is the actual data we send over the wire to set the
|
||||
// driver parameters (an interface fulfilling drivers.DriverOptions,
|
||||
// concrete type rpcdriver.RpcFlags).
|
||||
mcnFlags := driver.GetCreateFlags()
|
||||
driverOpts := getDriverOpts(c, mcnFlags)
|
||||
|
||||
if err := h.Driver.SetConfigFromFlags(driverOpts); err != nil {
|
||||
fatalf("Error setting machine configuration from flags provided: %s", err)
|
||||
}
|
||||
|
||||
if err := libmachine.Create(store, h); err != nil {
|
||||
log.Fatalf("Error creating machine: %s", err)
|
||||
fatalf("Error creating machine: %s", err)
|
||||
}
|
||||
|
||||
info := fmt.Sprintf("%s env %s", os.Args[0], name)
|
||||
log.Infof("To see how to connect Docker to this machine, run: %s", info)
|
||||
if err := saveHost(store, h); err != nil {
|
||||
fatalf("Error attempting to save store: %s", err)
|
||||
}
|
||||
|
||||
log.Infof("To see how to connect Docker to this machine, run: %s", fmt.Sprintf("%s env %s", os.Args[0], name))
|
||||
}
|
||||
|
||||
// If the user has specified a driver, they should not see the flags for all
|
||||
// of the drivers in `docker-machine create`. This method replaces the 100+
|
||||
// create flags with only the ones applicable to the driver specified
|
||||
func trimDriverFlags(driver string, cmds []cli.Command) ([]cli.Command, error) {
|
||||
filteredCmds := cmds
|
||||
driverFlags, err := drivers.GetCreateFlagsForDriver(driver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The following function is needed because the CLI acrobatics that we're doing
|
||||
// (with having an "outer" and "inner" function each with their own custom
|
||||
// settings and flag parsing needs) are not well supported by codegangsta/cli.
|
||||
//
|
||||
// Instead of trying to make a convoluted series of flag parsing and relying on
|
||||
// codegangsta/cli internals work well, we simply read the flags we're
|
||||
// interested in from the outer function into module-level variables, and then
|
||||
// use them from the "inner" function.
|
||||
//
|
||||
// I'm not very pleased about this, but it seems to be the only decent
|
||||
// compromise without drastically modifying codegangsta/cli internals or our
|
||||
// own CLI.
|
||||
func flagHackLookup(flagName string) string {
|
||||
// e.g. "-d" for "--driver"
|
||||
flagPrefix := flagName[1:3]
|
||||
|
||||
for i, cmd := range cmds {
|
||||
if cmd.HasName("create") {
|
||||
filteredCmds[i].Flags = append(driverFlags, sharedCreateFlags...)
|
||||
// TODO: Should we support -flag-name (single hyphen) syntax as well?
|
||||
for i, arg := range os.Args {
|
||||
if strings.Contains(arg, flagPrefix) {
|
||||
// format '--driver foo' or '-d foo'
|
||||
if arg == flagPrefix || arg == flagName {
|
||||
if i+1 < len(os.Args) {
|
||||
return os.Args[i+1]
|
||||
}
|
||||
}
|
||||
|
||||
// format '--driver=foo' or '-d=foo'
|
||||
if strings.HasPrefix(arg, flagPrefix+"=") || strings.HasPrefix(arg, flagName+"=") {
|
||||
return strings.Split(arg, "=")[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredCmds, nil
|
||||
return ""
|
||||
}
|
||||
|
||||
func cmdCreateOuter(c *cli.Context) {
|
||||
driverName := flagHackLookup("--driver")
|
||||
|
||||
// We didn't recognize the driver name.
|
||||
if driverName == "" {
|
||||
cli.ShowCommandHelp(c, "create")
|
||||
return
|
||||
}
|
||||
|
||||
name := c.Args().First()
|
||||
|
||||
// TODO: Fix hacky JSON solution
|
||||
bareDriverData, err := json.Marshal(&drivers.BaseDriver{
|
||||
MachineName: name,
|
||||
})
|
||||
if err != nil {
|
||||
fatalf("Error attempting to marshal bare driver data: %s", err)
|
||||
}
|
||||
|
||||
driver, err := newPluginDriver(driverName, bareDriverData)
|
||||
if err != nil {
|
||||
fatalf("Error loading driver %q: %s", driverName, err)
|
||||
}
|
||||
|
||||
// TODO: So much flag manipulation and voodoo here, it seems to be
|
||||
// asking for trouble.
|
||||
//
|
||||
// mcnFlags is the data we get back over the wire (type mcnflag.Flag)
|
||||
// to indicate which parameters are available.
|
||||
mcnFlags := driver.GetCreateFlags()
|
||||
|
||||
// This bit will actually make "create" display the correct flags based
|
||||
// on the requested driver.
|
||||
cliFlags, err := convertMcnFlagsToCliFlags(mcnFlags)
|
||||
if err != nil {
|
||||
fatalf("Error trying to convert provided driver flags to cli flags: %s", err)
|
||||
}
|
||||
for i := range c.App.Commands {
|
||||
cmd := &c.App.Commands[i]
|
||||
if cmd.HasName("create") {
|
||||
cmd = addDriverFlagsToCommand(cliFlags, cmd)
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.App.Run(os.Args); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getDriverOpts(c *cli.Context, mcnflags []mcnflag.Flag) drivers.DriverOptions {
|
||||
// TODO: This function is pretty damn YOLO and would benefit from some
|
||||
// sanity checking around types and assertions.
|
||||
//
|
||||
// But, we need it so that we can actually send the flags for creating
|
||||
// a machine over the wire (cli.Context is a no go since there is so
|
||||
// much stuff in it).
|
||||
driverOpts := rpcdriver.RpcFlags{
|
||||
Values: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
for _, f := range mcnflags {
|
||||
driverOpts.Values[f.String()] = f.Default()
|
||||
|
||||
// Hardcoded logic for boolean... :(
|
||||
if f.Default() == nil {
|
||||
driverOpts.Values[f.String()] = false
|
||||
}
|
||||
}
|
||||
|
||||
for _, name := range c.FlagNames() {
|
||||
getter, ok := c.Generic(name).(flag.Getter)
|
||||
if !ok {
|
||||
// TODO: This is pretty hacky. StringSlice is the only
|
||||
// type so far we have to worry about which is not a
|
||||
// Getter, though.
|
||||
driverOpts.Values[name] = c.StringSlice(name)
|
||||
continue
|
||||
}
|
||||
driverOpts.Values[name] = getter.Get()
|
||||
}
|
||||
|
||||
return driverOpts
|
||||
}
|
||||
|
||||
func convertMcnFlagsToCliFlags(mcnFlags []mcnflag.Flag) ([]cli.Flag, error) {
|
||||
cliFlags := []cli.Flag{}
|
||||
for _, f := range mcnFlags {
|
||||
switch t := f.(type) {
|
||||
// TODO: It seems pretty wrong to just default "nil" to this,
|
||||
// but cli.BoolFlag doesn't have a "Value" field (false is
|
||||
// always the default)
|
||||
case *mcnflag.BoolFlag:
|
||||
f := f.(*mcnflag.BoolFlag)
|
||||
cliFlags = append(cliFlags, cli.BoolFlag{
|
||||
Name: f.Name,
|
||||
EnvVar: f.EnvVar,
|
||||
Usage: f.Usage,
|
||||
})
|
||||
case *mcnflag.IntFlag:
|
||||
f := f.(*mcnflag.IntFlag)
|
||||
cliFlags = append(cliFlags, cli.IntFlag{
|
||||
Name: f.Name,
|
||||
EnvVar: f.EnvVar,
|
||||
Usage: f.Usage,
|
||||
Value: f.Value,
|
||||
})
|
||||
case *mcnflag.StringFlag:
|
||||
f := f.(*mcnflag.StringFlag)
|
||||
cliFlags = append(cliFlags, cli.StringFlag{
|
||||
Name: f.Name,
|
||||
EnvVar: f.EnvVar,
|
||||
Usage: f.Usage,
|
||||
Value: f.Value,
|
||||
})
|
||||
case *mcnflag.StringSliceFlag:
|
||||
f := f.(*mcnflag.StringSliceFlag)
|
||||
cliFlags = append(cliFlags, cli.StringSliceFlag{
|
||||
Name: f.Name,
|
||||
EnvVar: f.EnvVar,
|
||||
Usage: f.Usage,
|
||||
|
||||
//TODO: Is this used with defaults? Can we convert the literal []string to cli.StringSlice properly?
|
||||
Value: &cli.StringSlice{},
|
||||
})
|
||||
default:
|
||||
log.Warn("Flag is ", f)
|
||||
return nil, fmt.Errorf("Flag is unrecognized flag type: %T", t)
|
||||
}
|
||||
}
|
||||
|
||||
return cliFlags, nil
|
||||
}
|
||||
|
||||
func addDriverFlagsToCommand(cliFlags []cli.Flag, cmd *cli.Command) *cli.Command {
|
||||
cmd.Flags = append(sharedCreateFlags, cliFlags...)
|
||||
cmd.SkipFlagParsing = false
|
||||
cmd.Action = cmdCreateInner
|
||||
sort.Sort(ByFlagName(cmd.Flags))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func validateSwarmDiscovery(discovery string) error {
|
||||
|
|
|
@ -7,8 +7,7 @@ import (
|
|||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/cli"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -34,21 +33,21 @@ type ShellConfig struct {
|
|||
|
||||
func cmdEnv(c *cli.Context) {
|
||||
if len(c.Args()) != 1 && !c.Bool("unset") {
|
||||
log.Fatal(improperEnvArgsError)
|
||||
fatal(improperEnvArgsError)
|
||||
}
|
||||
|
||||
h := getFirstArgHost(c)
|
||||
|
||||
dockerHost, authOptions, err := runConnectionBoilerplate(h, c)
|
||||
if err != nil {
|
||||
log.Fatalf("Error running connection boilerplate: %s", err)
|
||||
fatalf("Error running connection boilerplate: %s", err)
|
||||
}
|
||||
|
||||
userShell := c.String("shell")
|
||||
if userShell == "" {
|
||||
shell, err := detectShell()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
userShell = shell
|
||||
}
|
||||
|
@ -68,7 +67,7 @@ func cmdEnv(c *cli.Context) {
|
|||
if c.Bool("no-proxy") {
|
||||
ip, err := h.Driver.GetIP()
|
||||
if err != nil {
|
||||
log.Fatalf("Error getting host IP: %s", err)
|
||||
fatalf("Error getting host IP: %s", err)
|
||||
}
|
||||
|
||||
// first check for an existing lower case no_proxy var
|
||||
|
@ -122,11 +121,11 @@ func cmdEnv(c *cli.Context) {
|
|||
|
||||
tmpl, err := t.Parse(envTmpl)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(os.Stdout, shellCfg); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -152,11 +151,11 @@ func cmdEnv(c *cli.Context) {
|
|||
|
||||
tmpl, err := t.Parse(envTmpl)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(os.Stdout, shellCfg); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package drivers
|
||||
package commands
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/docker/machine/cli"
|
||||
|
||||
type ByFlagName []cli.Flag
|
||||
|
|
@ -6,9 +6,7 @@ import (
|
|||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
)
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
|
@ -25,7 +23,7 @@ var funcMap = template.FuncMap{
|
|||
func cmdInspect(c *cli.Context) {
|
||||
if len(c.Args()) == 0 {
|
||||
cli.ShowCommandHelp(c, "inspect")
|
||||
log.Fatal("You must specify a machine name")
|
||||
fatal("You must specify a machine name")
|
||||
}
|
||||
|
||||
tmplString := c.String("format")
|
||||
|
@ -33,26 +31,26 @@ func cmdInspect(c *cli.Context) {
|
|||
var tmpl *template.Template
|
||||
var err error
|
||||
if tmpl, err = template.New("").Funcs(funcMap).Parse(tmplString); err != nil {
|
||||
log.Fatalf("Template parsing error: %v\n", err)
|
||||
fatalf("Template parsing error: %v\n", err)
|
||||
}
|
||||
|
||||
jsonHost, err := json.Marshal(getFirstArgHost(c))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
obj := make(map[string]interface{})
|
||||
if err := json.Unmarshal(jsonHost, &obj); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(os.Stdout, obj); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
os.Stdout.Write([]byte{'\n'})
|
||||
} else {
|
||||
prettyJSON, err := json.MarshalIndent(getFirstArgHost(c), "", " ")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(prettyJSON))
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
)
|
||||
import "github.com/docker/machine/cli"
|
||||
|
||||
func cmdIp(c *cli.Context) {
|
||||
if err := runActionWithContext("ip", c); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/docker/machine/cli"
|
||||
|
||||
func cmdKill(c *cli.Context) {
|
||||
if err := runActionWithContext("kill", c); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/host"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
|
@ -45,13 +45,13 @@ func cmdLs(c *cli.Context) {
|
|||
quiet := c.Bool("quiet")
|
||||
filters, err := parseFilters(c.StringSlice("filter"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
store := getStore(c)
|
||||
hostList, err := store.List()
|
||||
hostList, err := listHosts(store)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
hostList = filterHosts(hostList, filters)
|
||||
|
@ -219,7 +219,7 @@ func matchesName(host *host.Host, names []string) bool {
|
|||
for _, n := range names {
|
||||
r, err := regexp.Compile(n)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
if r.MatchString(host.Driver.GetMachineName()) {
|
||||
return true
|
||||
|
@ -229,7 +229,7 @@ func matchesName(host *host.Host, names []string) bool {
|
|||
}
|
||||
|
||||
func getActiveHost(store persist.Store) (*host.Host, error) {
|
||||
hosts, err := store.List()
|
||||
hosts, err := listHosts(store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ func getActiveHost(store persist.Store) (*host.Host, error) {
|
|||
|
||||
for _, item := range hostListItems {
|
||||
if item.Active {
|
||||
h, err := store.Load(item.Name)
|
||||
h, err := loadHost(store, item.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -250,21 +250,38 @@ func getActiveHost(store persist.Store) (*host.Host, error) {
|
|||
}
|
||||
|
||||
func attemptGetHostState(h *host.Host, stateQueryChan chan<- HostListItem) {
|
||||
currentState, err := h.Driver.GetState()
|
||||
if err != nil {
|
||||
log.Errorf("error getting state for host %s: %s", h.Name, err)
|
||||
}
|
||||
stateCh := make(chan state.State)
|
||||
urlCh := make(chan string)
|
||||
|
||||
url, err := h.GetURL()
|
||||
if err != nil {
|
||||
if err == drivers.ErrHostIsNotRunning {
|
||||
url = ""
|
||||
} else {
|
||||
log.Errorf("error getting URL for host %s: %s", h.Name, err)
|
||||
go func() {
|
||||
currentState, err := h.Driver.GetState()
|
||||
if err != nil {
|
||||
log.Errorf("error getting state for host %s: %s", h.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
active, err := isActive(h)
|
||||
stateCh <- currentState
|
||||
}()
|
||||
|
||||
go func() {
|
||||
url, err := h.GetURL()
|
||||
if err != nil {
|
||||
if err.Error() == drivers.ErrHostIsNotRunning.Error() {
|
||||
url = ""
|
||||
} else {
|
||||
log.Errorf("error getting URL for host %s: %s", h.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
urlCh <- url
|
||||
}()
|
||||
|
||||
currentState := <-stateCh
|
||||
url := <-urlCh
|
||||
|
||||
close(stateCh)
|
||||
close(urlCh)
|
||||
|
||||
active, err := isActive(h, currentState, url)
|
||||
if err != nil {
|
||||
log.Errorf("error determining if host is active for host %s: %s",
|
||||
h.Name, err)
|
||||
|
@ -321,25 +338,7 @@ func getHostListItems(hostList []*host.Host) []HostListItem {
|
|||
|
||||
// IsActive provides a single function for determining if a host is active
|
||||
// based on both the url and if the host is stopped.
|
||||
func isActive(h *host.Host) (bool, error) {
|
||||
currentState, err := h.Driver.GetState()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("error getting state for host %s: %s", h.Name, err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
url, err := h.GetURL()
|
||||
|
||||
if err != nil {
|
||||
if err == drivers.ErrHostIsNotRunning {
|
||||
url = ""
|
||||
} else {
|
||||
log.Errorf("error getting URL for host %s: %s", h.Name, err)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
func isActive(h *host.Host, currentState state.State, url string) (bool, error) {
|
||||
dockerHost := os.Getenv("DOCKER_HOST")
|
||||
|
||||
running := currentState == state.Running
|
||||
|
|
|
@ -170,21 +170,21 @@ func TestFilterHostsByState(t *testing.T) {
|
|||
Name: "node1",
|
||||
DriverName: "fakedriver",
|
||||
HostOptions: &host.HostOptions{},
|
||||
Driver: &fakedriver.FakeDriver{MockState: state.Paused},
|
||||
Driver: &fakedriver.Driver{MockState: state.Paused},
|
||||
}
|
||||
node2 :=
|
||||
&host.Host{
|
||||
Name: "node2",
|
||||
DriverName: "virtualbox",
|
||||
HostOptions: &host.HostOptions{},
|
||||
Driver: &fakedriver.FakeDriver{MockState: state.Stopped},
|
||||
Driver: &fakedriver.Driver{MockState: state.Stopped},
|
||||
}
|
||||
node3 :=
|
||||
&host.Host{
|
||||
Name: "node3",
|
||||
DriverName: "fakedriver",
|
||||
HostOptions: &host.HostOptions{},
|
||||
Driver: &fakedriver.FakeDriver{MockState: state.Running},
|
||||
Driver: &fakedriver.Driver{MockState: state.Running},
|
||||
}
|
||||
hosts := []*host.Host{node1, node2, node3}
|
||||
expected := []*host.Host{node1, node2}
|
||||
|
@ -201,28 +201,28 @@ func TestFilterHostsByName(t *testing.T) {
|
|||
Name: "fire",
|
||||
DriverName: "fakedriver",
|
||||
HostOptions: &host.HostOptions{},
|
||||
Driver: &fakedriver.FakeDriver{MockState: state.Paused, MockName: "fire"},
|
||||
Driver: &fakedriver.Driver{MockState: state.Paused, MockName: "fire"},
|
||||
}
|
||||
node2 :=
|
||||
&host.Host{
|
||||
Name: "ice",
|
||||
DriverName: "adriver",
|
||||
HostOptions: &host.HostOptions{},
|
||||
Driver: &fakedriver.FakeDriver{MockState: state.Paused, MockName: "ice"},
|
||||
Driver: &fakedriver.Driver{MockState: state.Paused, MockName: "ice"},
|
||||
}
|
||||
node3 :=
|
||||
&host.Host{
|
||||
Name: "air",
|
||||
DriverName: "nodriver",
|
||||
HostOptions: &host.HostOptions{},
|
||||
Driver: &fakedriver.FakeDriver{MockState: state.Paused, MockName: "air"},
|
||||
Driver: &fakedriver.Driver{MockState: state.Paused, MockName: "air"},
|
||||
}
|
||||
node4 :=
|
||||
&host.Host{
|
||||
Name: "water",
|
||||
DriverName: "falsedriver",
|
||||
HostOptions: &host.HostOptions{},
|
||||
Driver: &fakedriver.FakeDriver{MockState: state.Paused, MockName: "water"},
|
||||
Driver: &fakedriver.Driver{MockState: state.Paused, MockName: "water"},
|
||||
}
|
||||
hosts := []*host.Host{node1, node2, node3, node4}
|
||||
expected := []*host.Host{node1, node2, node3}
|
||||
|
@ -269,21 +269,21 @@ func TestFilterHostsDifferentFlagsProduceAND(t *testing.T) {
|
|||
Name: "node1",
|
||||
DriverName: "fakedriver",
|
||||
HostOptions: &host.HostOptions{},
|
||||
Driver: &fakedriver.FakeDriver{MockState: state.Paused},
|
||||
Driver: &fakedriver.Driver{MockState: state.Paused},
|
||||
}
|
||||
node2 :=
|
||||
&host.Host{
|
||||
Name: "node2",
|
||||
DriverName: "virtualbox",
|
||||
HostOptions: &host.HostOptions{},
|
||||
Driver: &fakedriver.FakeDriver{MockState: state.Stopped},
|
||||
Driver: &fakedriver.Driver{MockState: state.Stopped},
|
||||
}
|
||||
node3 :=
|
||||
&host.Host{
|
||||
Name: "node3",
|
||||
DriverName: "fakedriver",
|
||||
HostOptions: &host.HostOptions{},
|
||||
Driver: &fakedriver.FakeDriver{MockState: state.Running},
|
||||
Driver: &fakedriver.Driver{MockState: state.Running},
|
||||
}
|
||||
hosts := []*host.Host{node1, node2, node3}
|
||||
expected := []*host.Host{}
|
||||
|
@ -314,7 +314,7 @@ func TestGetHostListItems(t *testing.T) {
|
|||
{
|
||||
Name: "foo",
|
||||
DriverName: "fakedriver",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Running,
|
||||
},
|
||||
HostOptions: &host.HostOptions{
|
||||
|
@ -328,7 +328,7 @@ func TestGetHostListItems(t *testing.T) {
|
|||
{
|
||||
Name: "bar",
|
||||
DriverName: "fakedriver",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Stopped,
|
||||
},
|
||||
HostOptions: &host.HostOptions{
|
||||
|
@ -342,7 +342,7 @@ func TestGetHostListItems(t *testing.T) {
|
|||
{
|
||||
Name: "baz",
|
||||
DriverName: "fakedriver",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Running,
|
||||
},
|
||||
HostOptions: &host.HostOptions{
|
||||
|
@ -396,7 +396,7 @@ func TestGetHostListItemsEnvDockerHostUnset(t *testing.T) {
|
|||
{
|
||||
Name: "foo",
|
||||
DriverName: "fakedriver",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Running,
|
||||
MockURL: "tcp://120.0.0.1:2376",
|
||||
},
|
||||
|
@ -411,7 +411,7 @@ func TestGetHostListItemsEnvDockerHostUnset(t *testing.T) {
|
|||
{
|
||||
Name: "bar",
|
||||
DriverName: "fakedriver",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Stopped,
|
||||
},
|
||||
HostOptions: &host.HostOptions{
|
||||
|
@ -425,7 +425,7 @@ func TestGetHostListItemsEnvDockerHostUnset(t *testing.T) {
|
|||
{
|
||||
Name: "baz",
|
||||
DriverName: "fakedriver",
|
||||
Driver: &fakedriver.FakeDriver{
|
||||
Driver: &fakedriver.Driver{
|
||||
MockState: state.Saved,
|
||||
},
|
||||
HostOptions: &host.HostOptions{
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
)
|
||||
|
||||
|
@ -10,7 +10,7 @@ func cmdRegenerateCerts(c *cli.Context) {
|
|||
if force || confirmInput("Regenerate TLS machine certs? Warning: this is irreversible.") {
|
||||
log.Infof("Regenerating TLS certificates")
|
||||
if err := runActionWithContext("configureAuth", c); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ package commands
|
|||
import (
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
)
|
||||
|
||||
func cmdRestart(c *cli.Context) {
|
||||
if err := runActionWithContext("restart", c); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
log.Info("Restarted machines may have new IP addresses. You may need to re-run the `docker-machine env` command.")
|
||||
}
|
||||
|
|
|
@ -1,31 +1,35 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
)
|
||||
|
||||
func cmdRm(c *cli.Context) {
|
||||
if len(c.Args()) == 0 {
|
||||
cli.ShowCommandHelp(c, "rm")
|
||||
log.Fatal("You must specify a machine name")
|
||||
fatal("You must specify a machine name")
|
||||
}
|
||||
|
||||
force := c.Bool("force")
|
||||
|
||||
isError := false
|
||||
|
||||
store := getStore(c)
|
||||
|
||||
for _, hostName := range c.Args() {
|
||||
if err := store.Remove(hostName, force); err != nil {
|
||||
log.Errorf("Error removing machine %s: %s", hostName, err)
|
||||
isError = true
|
||||
h, err := loadHost(store, hostName)
|
||||
if err != nil {
|
||||
fatalf("Error removing host %q: %s", hostName, err)
|
||||
}
|
||||
if err := h.Driver.Remove(); err != nil {
|
||||
if !force {
|
||||
log.Errorf("Provider error removing machine %q: %s", hostName, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if err := store.Remove(hostName); err != nil {
|
||||
log.Errorf("Error removing machine %q from store: %s", hostName, err)
|
||||
} else {
|
||||
log.Infof("Successfully removed %s", hostName)
|
||||
}
|
||||
}
|
||||
if isError {
|
||||
log.Fatal("There was an error removing a machine. To force remove it, pass the -f option. Warning: this might leave it running on the provider.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
"github.com/docker/machine/libmachine/host"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/persist"
|
||||
|
@ -25,8 +25,22 @@ var (
|
|||
"-o", "UserKnownHostsFile=/dev/null",
|
||||
"-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts."
|
||||
}
|
||||
|
||||
hostLoader HostLoader
|
||||
)
|
||||
|
||||
// TODO: Remove this hack in favor of better strategy. Currently the
|
||||
// HostLoader interface wraps the loadHost() function for easier testing.
|
||||
type HostLoader interface {
|
||||
LoadHost(persist.Store, string) (*host.Host, error)
|
||||
}
|
||||
|
||||
type ScpHostLoader struct{}
|
||||
|
||||
func (s *ScpHostLoader) LoadHost(store persist.Store, name string) (*host.Host, error) {
|
||||
return loadHost(store, name)
|
||||
}
|
||||
|
||||
func getInfoForScpArg(hostAndPath string, store persist.Store) (*host.Host, string, []string, error) {
|
||||
// TODO: What to do about colon in filepath?
|
||||
splitInfo := strings.Split(hostAndPath, ":")
|
||||
|
@ -39,7 +53,7 @@ func getInfoForScpArg(hostAndPath string, store persist.Store) (*host.Host, stri
|
|||
// Remote path. e.g. "machinename:/usr/bin/cmatrix"
|
||||
if len(splitInfo) == 2 {
|
||||
path := splitInfo[1]
|
||||
host, err := store.Load(splitInfo[0])
|
||||
host, err := hostLoader.LoadHost(store, splitInfo[0])
|
||||
if err != nil {
|
||||
return nil, "", nil, fmt.Errorf("Error loading host: %s", err)
|
||||
}
|
||||
|
@ -107,16 +121,18 @@ func runCmdWithStdIo(cmd exec.Cmd) error {
|
|||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdScp(c *cli.Context) {
|
||||
hostLoader = &ScpHostLoader{}
|
||||
|
||||
args := c.Args()
|
||||
if len(args) != 2 {
|
||||
cli.ShowCommandHelp(c, "scp")
|
||||
log.Fatal("Improper number of arguments.")
|
||||
fatal("Improper number of arguments.")
|
||||
}
|
||||
|
||||
// TODO: Check that "-3" flag is available in user's version of scp.
|
||||
|
@ -134,9 +150,9 @@ func cmdScp(c *cli.Context) {
|
|||
cmd, err := getScpCmd(src, dest, sshArgs, store)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
if err := runCmdWithStdIo(*cmd); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/host"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/persist"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
)
|
||||
|
||||
|
@ -18,6 +20,12 @@ type ScpFakeDriver struct {
|
|||
|
||||
type ScpFakeStore struct{}
|
||||
|
||||
type ScpFakeHostLoader struct{}
|
||||
|
||||
func (d ScpFakeDriver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{}
|
||||
}
|
||||
|
||||
func (d ScpFakeDriver) DriverName() string {
|
||||
return "fake"
|
||||
}
|
||||
|
@ -123,16 +131,10 @@ func (s ScpFakeStore) List() ([]*host.Host, error) {
|
|||
}
|
||||
|
||||
func (s ScpFakeStore) Load(name string) (*host.Host, error) {
|
||||
if name == "myfunhost" {
|
||||
return &host.Host{
|
||||
Name: "myfunhost",
|
||||
Driver: ScpFakeDriver{},
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("Host not found")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s ScpFakeStore) Remove(name string, force bool) error {
|
||||
func (s ScpFakeStore) Remove(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -144,8 +146,19 @@ func (s ScpFakeStore) NewHost(driver drivers.Driver) (*host.Host, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (fshl *ScpFakeHostLoader) LoadHost(store persist.Store, name string) (*host.Host, error) {
|
||||
if name == "myfunhost" {
|
||||
return &host.Host{
|
||||
Name: "myfunhost",
|
||||
Driver: ScpFakeDriver{},
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("Host not found")
|
||||
}
|
||||
|
||||
func TestGetInfoForScpArg(t *testing.T) {
|
||||
store := ScpFakeStore{}
|
||||
hostLoader = &ScpFakeHostLoader{}
|
||||
|
||||
expectedPath := "/tmp/foo"
|
||||
host, path, opts, err := getInfoForScpArg("/tmp/foo", store)
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/cli"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func cmdSsh(c *cli.Context) {
|
||||
|
@ -12,30 +10,30 @@ func cmdSsh(c *cli.Context) {
|
|||
name := args.First()
|
||||
|
||||
if name == "" {
|
||||
log.Fatal("Error: Please specify a machine name.")
|
||||
fatal("Error: Please specify a machine name.")
|
||||
}
|
||||
|
||||
store := getStore(c)
|
||||
host, err := store.Load(name)
|
||||
host, err := loadHost(store, name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
currentState, err := host.Driver.GetState()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if currentState != state.Running {
|
||||
log.Fatalf("Error: Cannot run SSH command: Host %q is not running", host.Name)
|
||||
fatalf("Error: Cannot run SSH command: Host %q is not running", host.Name)
|
||||
}
|
||||
|
||||
client, err := host.CreateSSHClient()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
if err := client.Shell(c.Args().Tail()...); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ package commands
|
|||
import (
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
)
|
||||
|
||||
func cmdStart(c *cli.Context) {
|
||||
if err := runActionWithContext("start", c); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
log.Info("Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.")
|
||||
}
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/cli"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
)
|
||||
|
||||
func cmdStatus(c *cli.Context) {
|
||||
if len(c.Args()) != 1 {
|
||||
log.Fatal(ErrExpectedOneMachine)
|
||||
fatal(ErrExpectedOneMachine)
|
||||
}
|
||||
|
||||
host := getFirstArgHost(c)
|
||||
currentState, err := host.Driver.GetState()
|
||||
if err != nil {
|
||||
log.Errorf("error getting state for host %s: %s", host.Name, err)
|
||||
}
|
||||
|
||||
log.Info(currentState)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/docker/machine/cli"
|
||||
|
||||
func cmdStop(c *cli.Context) {
|
||||
if err := runActionWithContext("stop", c); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/docker/machine/cli"
|
||||
|
||||
func cmdUpgrade(c *cli.Context) {
|
||||
if err := runActionWithContext("upgrade", c); err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,17 +3,16 @@ package commands
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/cli"
|
||||
)
|
||||
|
||||
func cmdUrl(c *cli.Context) {
|
||||
if len(c.Args()) != 1 {
|
||||
log.Fatal(ErrExpectedOneMachine)
|
||||
fatal(ErrExpectedOneMachine)
|
||||
}
|
||||
url, err := getFirstArgHost(c).GetURL()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(url)
|
||||
|
|
|
@ -11,12 +11,6 @@ weight=3
|
|||
|
||||
# Install Docker Machine
|
||||
|
||||
Docker Machine is supported on Windows, OS X, and Linux operating systems. You
|
||||
can install using one of Docker's automated installation methods or you can
|
||||
download and install via a binary. This page details each of those methods.
|
||||
|
||||
## OS X and Windows
|
||||
|
||||
On OS X and Windows, Machine is installed along with other Docker products when
|
||||
you install the Docker Toolbox. For details on installing Docker Toolbox, see
|
||||
the <a href="https://docs.docker.com/installation/mac/" target="_blank">Mac OS X
|
||||
|
@ -24,53 +18,52 @@ installation</a> instructions or <a
|
|||
href="https://docs.docker.com/installation/windows" target="_blank">Windows
|
||||
installation</a> instructions.
|
||||
|
||||
If you only want Docker Machine, you can install [the Machine binaries
|
||||
directly](https://github.com/docker/machine/releases/). Alternatively, OS X
|
||||
users have the option to follow the Linux installation instructions.
|
||||
If you only want Docker Machine, you can install the Machine binaries (the
|
||||
latest versions of which are located at
|
||||
https://github.com/docker/machine/releases/) directly by following the
|
||||
instructions in the next section.
|
||||
|
||||
## On Linux
|
||||
|
||||
To install on Linux, do the following:
|
||||
## Installing Machine Directly
|
||||
|
||||
1. Install <a href="https://docs.docker.com/installation/"
|
||||
target="_blank">Docker version 1.7.1 or greater</a>:
|
||||
target="_blank">the Docker binary</a>.
|
||||
|
||||
2. Download the Machine binary to somewhere in your `PATH` (for example,
|
||||
`/usr/local/bin`).
|
||||
2. Download the archive containing the Docker Machine binaries and extract them
|
||||
to your PATH.
|
||||
|
||||
$ curl -L https://github.com/docker/machine/releases/download/v0.4.1/docker-machine_linux-amd64 > /usr/local/bin/docker-machine
|
||||
Linux:
|
||||
|
||||
3. Apply executable permissions to the binary:
|
||||
```
|
||||
$ curl -L https://github.com/docker/machine/releases/download/v0.5.0/docker-machine_linux-amd64.zip >machine.zip && \
|
||||
unzip machine.zip && \
|
||||
rm machine.zip && \
|
||||
mv docker-machine* /usr/local/bin
|
||||
```
|
||||
|
||||
$ chmod +x /usr/local/bin/docker-machine
|
||||
OSX:
|
||||
|
||||
4. Check the installation by displaying the Machine version:
|
||||
```
|
||||
$ curl -L https://github.com/docker/machine/releases/download/v0.5.0/docker-machine_darwin-amd64.zip >machine.zip && \
|
||||
unzip machine.zip && \
|
||||
rm machine.zip && \
|
||||
mv docker-machine* /usr/local/bin
|
||||
```
|
||||
|
||||
$ docker-machine -v
|
||||
machine version 0.4.1 (e2c88d6)
|
||||
Windows (using Git Bash):
|
||||
|
||||
## Install from binary
|
||||
```
|
||||
$ curl -L https://github.com/docker/machine/releases/download/v0.5.0/docker-machine_windows-amd64.zip >machine.zip && \
|
||||
unzip machine.zip && \
|
||||
rm machine.zip && \
|
||||
mv docker-machine* /usr/local/bin
|
||||
```
|
||||
|
||||
The Docker Machine team compiles binaries for several platforms and
|
||||
architectures and makes them available from [the Machine release page on
|
||||
Github](https://github.com/docker/machine/releases/). To install from a binary:
|
||||
3. Check the installation by displaying the Machine version:
|
||||
|
||||
1. Download the binary you want.
|
||||
2. Rename the binary to `docker-machine`.
|
||||
3. Move the `docker-machine` file to an appropriate directory on your system.
|
||||
|
||||
For example, on an OS X machine, you might move it to the `/usr/local/bin`
|
||||
directory.
|
||||
|
||||
4. Ensure the file's executable permissions are correct.
|
||||
5. Apply executable permissions to the binary:
|
||||
|
||||
$ chmod +x /usr/local/bin/docker-machine
|
||||
|
||||
6. Check the installation by displaying the Machine version:
|
||||
|
||||
$ docker-machine -v
|
||||
machine version 0.4.1 (e2c88d6)
|
||||
```
|
||||
$ docker-machine -v
|
||||
machine version 0.5.0 (3e06852)
|
||||
```
|
||||
|
||||
## Where to go next
|
||||
|
||||
|
|
|
@ -11,10 +11,10 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/drivers/amazonec2/amz"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
"github.com/docker/machine/libmachine/ssh"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
|
@ -68,103 +68,97 @@ type Driver struct {
|
|||
Monitoring bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers.Register(driverName, &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-access-key",
|
||||
Usage: "AWS Access Key",
|
||||
EnvVar: "AWS_ACCESS_KEY_ID",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-secret-key",
|
||||
Usage: "AWS Secret Key",
|
||||
EnvVar: "AWS_SECRET_ACCESS_KEY",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-session-token",
|
||||
Usage: "AWS Session Token",
|
||||
EnvVar: "AWS_SESSION_TOKEN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-ami",
|
||||
Usage: "AWS machine image",
|
||||
EnvVar: "AWS_AMI",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-region",
|
||||
Usage: "AWS region",
|
||||
Value: defaultRegion,
|
||||
EnvVar: "AWS_DEFAULT_REGION",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-vpc-id",
|
||||
Usage: "AWS VPC id",
|
||||
EnvVar: "AWS_VPC_ID",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-zone",
|
||||
Usage: "AWS zone for instance (i.e. a,b,c,d,e)",
|
||||
Value: defaultZone,
|
||||
EnvVar: "AWS_ZONE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-subnet-id",
|
||||
Usage: "AWS VPC subnet id",
|
||||
EnvVar: "AWS_SUBNET_ID",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-security-group",
|
||||
Usage: "AWS VPC security group",
|
||||
Value: defaultSecurityGroup,
|
||||
EnvVar: "AWS_SECURITY_GROUP",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-instance-type",
|
||||
Usage: "AWS instance type",
|
||||
Value: defaultInstanceType,
|
||||
EnvVar: "AWS_INSTANCE_TYPE",
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
Name: "amazonec2-root-size",
|
||||
Usage: "AWS root disk size (in GB)",
|
||||
Value: defaultRootSize,
|
||||
EnvVar: "AWS_ROOT_SIZE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-iam-instance-profile",
|
||||
Usage: "AWS IAM Instance Profile",
|
||||
EnvVar: "AWS_INSTANCE_PROFILE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-ssh-user",
|
||||
Usage: "set the name of the ssh user",
|
||||
Value: defaultSSHUser,
|
||||
EnvVar: "AWS_SSH_USER",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
Name: "amazonec2-request-spot-instance",
|
||||
Usage: "Set this flag to request spot instance",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-spot-price",
|
||||
Usage: "AWS spot instance bid price (in dollar)",
|
||||
Value: defaultSpotPrice,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "amazonec2-private-address-only",
|
||||
Usage: "Only use a private IP address",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
Name: "amazonec2-use-private-address",
|
||||
Usage: "Force the usage of private IP address",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
Name: "amazonec2-monitoring",
|
||||
Usage: "Set this flag to enable CloudWatch monitoring",
|
||||
},
|
||||
|
@ -724,7 +718,7 @@ func generateId() string {
|
|||
rb := make([]byte, 10)
|
||||
_, err := rand.Read(rb)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to generate id: %s", err)
|
||||
log.Warn("Unable to generate id: %s", err)
|
||||
}
|
||||
|
||||
h := md5.New()
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
azure "github.com/MSOpenTech/azure-sdk-for-go"
|
||||
"github.com/MSOpenTech/azure-sdk-for-go/clients/vmClient"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
"github.com/docker/machine/libmachine/ssh"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
|
@ -40,69 +40,62 @@ const (
|
|||
defaultSSHUsername = "ubuntu"
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("azure", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCreateFlags registers the flags this d adds to
|
||||
// "docker hosts create"
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.IntFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.IntFlag{
|
||||
Name: "azure-docker-port",
|
||||
Usage: "Azure Docker port",
|
||||
Value: defaultDockerPort,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
Name: "azure-docker-swarm-master-port",
|
||||
Usage: "Azure Docker Swarm master port",
|
||||
Value: defaultSwarmMasterPort,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "AZURE_IMAGE",
|
||||
Name: "azure-image",
|
||||
Usage: "Azure image name. Default is Ubuntu 14.04 LTS x64",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "AZURE_LOCATION",
|
||||
Name: "azure-location",
|
||||
Usage: "Azure location",
|
||||
Value: defaultLocation,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "azure-password",
|
||||
Usage: "Azure user password",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "AZURE_PUBLISH_SETTINGS_FILE",
|
||||
Name: "azure-publish-settings-file",
|
||||
Usage: "Azure publish settings file",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "AZURE_SIZE",
|
||||
Name: "azure-size",
|
||||
Usage: "Azure size",
|
||||
Value: defaultSize,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
Name: "azure-ssh-port",
|
||||
Usage: "Azure SSH port",
|
||||
Value: defaultSSHPort,
|
||||
},
|
||||
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "AZURE_SUBSCRIPTION_CERT",
|
||||
Name: "azure-subscription-cert",
|
||||
Usage: "Azure subscription cert",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "AZURE_SUBSCRIPTION_ID",
|
||||
Name: "azure-subscription-id",
|
||||
Usage: "Azure subscription ID",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "azure-username",
|
||||
Usage: "Azure username",
|
||||
Value: defaultSSHUsername,
|
||||
|
|
|
@ -6,10 +6,10 @@ import (
|
|||
"time"
|
||||
|
||||
"code.google.com/p/goauth2/oauth"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/ssh"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
)
|
||||
|
@ -34,56 +34,50 @@ const (
|
|||
defaultSize = "512mb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("digitalocean", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCreateFlags registers the flags this driver adds to
|
||||
// "docker hosts create"
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "DIGITALOCEAN_ACCESS_TOKEN",
|
||||
Name: "digitalocean-access-token",
|
||||
Usage: "Digital Ocean access token",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "DIGITALOCEAN_SSH_USER",
|
||||
Name: "digitalocean-ssh-user",
|
||||
Usage: "Digital Ocean SSH username",
|
||||
Value: "root",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "DIGITALOCEAN_IMAGE",
|
||||
Name: "digitalocean-image",
|
||||
Usage: "Digital Ocean Image",
|
||||
Value: defaultImage,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "DIGITALOCEAN_REGION",
|
||||
Name: "digitalocean-region",
|
||||
Usage: "Digital Ocean region",
|
||||
Value: defaultRegion,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "DIGITALOCEAN_SIZE",
|
||||
Name: "digitalocean-size",
|
||||
Usage: "Digital Ocean size",
|
||||
Value: defaultSize,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
EnvVar: "DIGITALOCEAN_IPV6",
|
||||
Name: "digitalocean-ipv6",
|
||||
Usage: "enable ipv6 for droplet",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
EnvVar: "DIGITALOCEAN_PRIVATE_NETWORKING",
|
||||
Name: "digitalocean-private-networking",
|
||||
Usage: "enable private networking for droplet",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
EnvVar: "DIGITALOCEAN_BACKUPS",
|
||||
Name: "digitalocean-backups",
|
||||
Usage: "enable backups for droplet",
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
package driverfactory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/machine/drivers/amazonec2"
|
||||
"github.com/docker/machine/drivers/azure"
|
||||
"github.com/docker/machine/drivers/digitalocean"
|
||||
"github.com/docker/machine/drivers/exoscale"
|
||||
"github.com/docker/machine/drivers/generic"
|
||||
"github.com/docker/machine/drivers/google"
|
||||
"github.com/docker/machine/drivers/hyperv"
|
||||
"github.com/docker/machine/drivers/none"
|
||||
"github.com/docker/machine/drivers/openstack"
|
||||
"github.com/docker/machine/drivers/rackspace"
|
||||
"github.com/docker/machine/drivers/softlayer"
|
||||
"github.com/docker/machine/drivers/virtualbox"
|
||||
"github.com/docker/machine/drivers/vmwarefusion"
|
||||
"github.com/docker/machine/drivers/vmwarevcloudair"
|
||||
"github.com/docker/machine/drivers/vmwarevsphere"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
)
|
||||
|
||||
func NewDriver(driverName, hostName, storePath string) (drivers.Driver, error) {
|
||||
var (
|
||||
driver drivers.Driver
|
||||
)
|
||||
|
||||
switch driverName {
|
||||
case "virtualbox":
|
||||
driver = virtualbox.NewDriver(hostName, storePath)
|
||||
case "digitalocean":
|
||||
driver = digitalocean.NewDriver(hostName, storePath)
|
||||
case "amazonec2":
|
||||
driver = amazonec2.NewDriver(hostName, storePath)
|
||||
case "azure":
|
||||
driver = azure.NewDriver(hostName, storePath)
|
||||
case "exoscale":
|
||||
driver = exoscale.NewDriver(hostName, storePath)
|
||||
case "generic":
|
||||
driver = generic.NewDriver(hostName, storePath)
|
||||
case "google":
|
||||
driver = google.NewDriver(hostName, storePath)
|
||||
case "hyperv":
|
||||
driver = hyperv.NewDriver(hostName, storePath)
|
||||
case "openstack":
|
||||
driver = openstack.NewDriver(hostName, storePath)
|
||||
case "rackspace":
|
||||
driver = rackspace.NewDriver(hostName, storePath)
|
||||
case "softlayer":
|
||||
driver = softlayer.NewDriver(hostName, storePath)
|
||||
case "vmwarefusion":
|
||||
driver = vmwarefusion.NewDriver(hostName, storePath)
|
||||
case "vmwarevcloudair":
|
||||
driver = vmwarevcloudair.NewDriver(hostName, storePath)
|
||||
case "vmwarevsphere":
|
||||
driver = vmwarevsphere.NewDriver(hostName, storePath)
|
||||
case "none":
|
||||
driver = none.NewDriver(hostName, storePath)
|
||||
default:
|
||||
return nil, fmt.Errorf("Driver %q not recognized", driverName)
|
||||
}
|
||||
|
||||
return driver, nil
|
||||
}
|
|
@ -8,9 +8,9 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
"github.com/pyr/egoscale/src/egoscale"
|
||||
|
@ -38,56 +38,50 @@ const (
|
|||
defaultAvailabilityZone = "ch-gva-2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("exoscale", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterCreateFlags registers the flags this driver adds to
|
||||
// "docker hosts create"
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "EXOSCALE_ENDPOINT",
|
||||
Name: "exoscale-url",
|
||||
Usage: "exoscale API endpoint",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "EXOSCALE_API_KEY",
|
||||
Name: "exoscale-api-key",
|
||||
Usage: "exoscale API key",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "EXOSCALE_API_SECRET",
|
||||
Name: "exoscale-api-secret-key",
|
||||
Usage: "exoscale API secret key",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "EXOSCALE_INSTANCE_PROFILE",
|
||||
Name: "exoscale-instance-profile",
|
||||
Value: defaultInstanceProfile,
|
||||
Usage: "exoscale instance profile (small, medium, large, ...)",
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "EXOSCALE_DISK_SIZE",
|
||||
Name: "exoscale-disk-size",
|
||||
Value: defaultDiskSize,
|
||||
Usage: "exoscale disk size (10, 50, 100, 200, 400)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "EXSOCALE_IMAGE",
|
||||
Name: "exoscale-image",
|
||||
Value: defaultImage,
|
||||
Usage: "exoscale image template",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
mcnflag.StringSliceFlag{
|
||||
EnvVar: "EXOSCALE_SECURITY_GROUP",
|
||||
Name: "exoscale-security-group",
|
||||
Value: &cli.StringSlice{},
|
||||
Value: []string{},
|
||||
Usage: "exoscale security group",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "EXOSCALE_AVAILABILITY_ZONE",
|
||||
Name: "exoscale-availability-zone",
|
||||
Value: defaultAvailabilityZone,
|
||||
|
|
|
@ -2,86 +2,91 @@ package fakedriver
|
|||
|
||||
import (
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
)
|
||||
|
||||
type FakeDriver struct {
|
||||
type Driver struct {
|
||||
*drivers.BaseDriver
|
||||
MockState state.State
|
||||
MockURL string
|
||||
MockName string
|
||||
}
|
||||
|
||||
func (d *FakeDriver) DriverName() string {
|
||||
return "fakedriver"
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{}
|
||||
}
|
||||
|
||||
func (d *FakeDriver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
||||
func (d *Driver) DriverName() string {
|
||||
return "Driver"
|
||||
}
|
||||
|
||||
func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) GetURL() (string, error) {
|
||||
func (d *Driver) GetURL() (string, error) {
|
||||
return d.MockURL, nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) GetMachineName() string {
|
||||
func (d *Driver) GetMachineName() string {
|
||||
return d.MockName
|
||||
}
|
||||
|
||||
func (d *FakeDriver) GetIP() (string, error) {
|
||||
func (d *Driver) GetIP() (string, error) {
|
||||
return "1.2.3.4", nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) GetSSHHostname() (string, error) {
|
||||
func (d *Driver) GetSSHHostname() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) GetSSHKeyPath() string {
|
||||
func (d *Driver) GetSSHKeyPath() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (d *FakeDriver) GetSSHPort() (int, error) {
|
||||
func (d *Driver) GetSSHPort() (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) GetSSHUsername() string {
|
||||
func (d *Driver) GetSSHUsername() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (d *FakeDriver) GetState() (state.State, error) {
|
||||
func (d *Driver) GetState() (state.State, error) {
|
||||
return d.MockState, nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) PreCreateCheck() error {
|
||||
func (d *Driver) PreCreateCheck() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) Create() error {
|
||||
func (d *Driver) Create() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) Remove() error {
|
||||
func (d *Driver) Remove() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) Start() error {
|
||||
func (d *Driver) Start() error {
|
||||
d.MockState = state.Running
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) Stop() error {
|
||||
func (d *Driver) Stop() error {
|
||||
d.MockState = state.Stopped
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) Restart() error {
|
||||
func (d *Driver) Restart() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) Kill() error {
|
||||
func (d *Driver) Kill() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *FakeDriver) Upgrade() error {
|
||||
func (d *Driver) Upgrade() error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ import (
|
|||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
)
|
||||
|
@ -29,31 +29,25 @@ var (
|
|||
defaultSSHKey = filepath.Join(mcnutils.GetHomeDir(), ".ssh", "id_rsa")
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("generic", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCreateFlags registers the flags this driver adds to
|
||||
// "docker hosts create"
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "generic-ip-address",
|
||||
Usage: "IP Address of machine",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "generic-ssh-user",
|
||||
Usage: "SSH user",
|
||||
Value: defaultSSHUser,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "generic-ssh-key",
|
||||
Usage: "SSH private key path",
|
||||
Value: defaultSSHKey,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
Name: "generic-ssh-port",
|
||||
Usage: "SSH port",
|
||||
Value: defaultSSHPort,
|
||||
|
|
|
@ -92,7 +92,7 @@ func tokenFromWeb(config *oauth.Config) *oauth.Token {
|
|||
}
|
||||
_, err := t.Exchange(code)
|
||||
if err != nil {
|
||||
log.Fatalf("Token exchange error: %v", err)
|
||||
log.Warnf("Token exchange error: %v", err)
|
||||
}
|
||||
return t.Token
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/ssh"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
)
|
||||
|
@ -35,76 +35,71 @@ const (
|
|||
defaultDiskSize = 10
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("google", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCreateFlags registers the flags this driver adds to
|
||||
// "docker hosts create"
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "google-zone",
|
||||
Usage: "GCE Zone",
|
||||
Value: defaultZone,
|
||||
EnvVar: "GOOGLE_ZONE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "google-machine-type",
|
||||
Usage: "GCE Machine Type",
|
||||
Value: defaultMachineType,
|
||||
EnvVar: "GOOGLE_MACHINE_TYPE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "google-username",
|
||||
Usage: "GCE User Name",
|
||||
Value: defaultUser,
|
||||
EnvVar: "GOOGLE_USERNAME",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "google-project",
|
||||
Usage: "GCE Project",
|
||||
EnvVar: "GOOGLE_PROJECT",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "google-auth-token",
|
||||
Usage: "GCE oAuth token",
|
||||
EnvVar: "GOOGLE_AUTH_TOKEN",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "google-scopes",
|
||||
Usage: "GCE Scopes (comma-separated if multiple scopes)",
|
||||
Value: defaultScopes,
|
||||
EnvVar: "GOOGLE_SCOPES",
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
Name: "google-disk-size",
|
||||
Usage: "GCE Instance Disk Size (in GB)",
|
||||
Value: defaultDiskSize,
|
||||
EnvVar: "GOOGLE_DISK_SIZE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "google-disk-type",
|
||||
Usage: "GCE Instance Disk type",
|
||||
Value: defaultDiskType,
|
||||
EnvVar: "GOOGLE_DISK_TYPE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "google-address",
|
||||
Usage: "GCE Instance External IP",
|
||||
EnvVar: "GOOGLE_ADDRESS",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
Name: "google-preemptible",
|
||||
Usage: "GCE Instance Preemptibility",
|
||||
EnvVar: "GOOGLE_PREEMPTIBLE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "google-tags",
|
||||
Usage: "GCE Instance Tags (comma-separated)",
|
||||
EnvVar: "GOOGLE_TAGS",
|
||||
Value: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
"github.com/docker/machine/libmachine/ssh"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
|
@ -31,12 +31,6 @@ const (
|
|||
defaultMemory = 1024
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("hyper-v", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
func NewDriver(hostName, storePath string) drivers.Driver {
|
||||
return &Driver{
|
||||
DiskSize: defaultDiskSize,
|
||||
|
@ -50,26 +44,26 @@ func NewDriver(hostName, storePath string) drivers.Driver {
|
|||
|
||||
// GetCreateFlags registers the flags this driver adds to
|
||||
// "docker hosts create"
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "hyper-v-boot2docker-url",
|
||||
Usage: "Hyper-V URL of the boot2docker image. Defaults to the latest available version.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "hyper-v-boot2docker-location",
|
||||
Usage: "Hyper-V local boot2docker iso. Overrides URL.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "hyper-v-virtual-switch",
|
||||
Usage: "Hyper-V virtual switch name. Defaults to first found.",
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
Name: "hyper-v-disk-size",
|
||||
Usage: "Hyper-V disk size for host in MB.",
|
||||
Value: defaultDiskSize,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
Name: "hyper-v-memory",
|
||||
Usage: "Hyper-V memory size for host in MB.",
|
||||
Value: defaultMemory,
|
||||
|
|
|
@ -4,11 +4,17 @@ import (
|
|||
"fmt"
|
||||
neturl "net/url"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
)
|
||||
|
||||
const drivername = "none"
|
||||
|
||||
// func main() {
|
||||
// plugin.RegisterDriver(new(Driver))
|
||||
// }
|
||||
|
||||
// Driver is the driver used when no driver is selected. It is used to
|
||||
// connect to existing Docker hosts by specifying the URL of the host as
|
||||
// an option.
|
||||
|
@ -17,22 +23,6 @@ type Driver struct {
|
|||
URL string
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers.Register("none", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "url",
|
||||
Usage: "URL of host when no driver is selected",
|
||||
Value: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewDriver(hostName, storePath string) *Driver {
|
||||
return &Driver{
|
||||
BaseDriver: &drivers.BaseDriver{
|
||||
|
@ -42,12 +32,22 @@ func NewDriver(hostName, storePath string) *Driver {
|
|||
}
|
||||
}
|
||||
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "url",
|
||||
Usage: "URL of host when no driver is selected",
|
||||
Value: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Driver) Create() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) DriverName() string {
|
||||
return "none"
|
||||
return drivername
|
||||
}
|
||||
|
||||
func (d *Driver) GetIP() (string, error) {
|
|
@ -6,9 +6,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
"github.com/docker/machine/libmachine/ssh"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
|
@ -49,146 +49,150 @@ const (
|
|||
defaultActiveTimeout = 200
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("openstack", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_AUTH_URL",
|
||||
Name: "openstack-auth-url",
|
||||
Usage: "OpenStack authentication URL",
|
||||
Value: "",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
EnvVar: "OS_INSECURE",
|
||||
Name: "openstack-insecure",
|
||||
Usage: "Disable TLS credential checking.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_DOMAIN_ID",
|
||||
Name: "openstack-domain-id",
|
||||
Usage: "OpenStack domain ID (identity v3 only)",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_DOMAIN_NAME",
|
||||
Name: "openstack-domain-name",
|
||||
Usage: "OpenStack domain name (identity v3 only)",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_USERNAME",
|
||||
Name: "openstack-username",
|
||||
Usage: "OpenStack username",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_PASSWORD",
|
||||
Name: "openstack-password",
|
||||
Usage: "OpenStack password",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_TENANT_NAME",
|
||||
Name: "openstack-tenant-name",
|
||||
Usage: "OpenStack tenant name",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_TENANT_ID",
|
||||
Name: "openstack-tenant-id",
|
||||
Usage: "OpenStack tenant id",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_REGION_NAME",
|
||||
Name: "openstack-region",
|
||||
Usage: "OpenStack region name",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_AVAILABILITY_ZONE",
|
||||
Name: "openstack-availability-zone",
|
||||
Usage: "OpenStack availability zone",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_ENDPOINT_TYPE",
|
||||
Name: "openstack-endpoint-type",
|
||||
Usage: "OpenStack endpoint type (adminURL, internalURL or publicURL)",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_FLAVOR_ID",
|
||||
Name: "openstack-flavor-id",
|
||||
Usage: "OpenStack flavor id to use for the instance",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_FLAVOR_NAME",
|
||||
Name: "openstack-flavor-name",
|
||||
Usage: "OpenStack flavor name to use for the instance",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_IMAGE_ID",
|
||||
Name: "openstack-image-id",
|
||||
Usage: "OpenStack image id to use for the instance",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_IMAGE_NAME",
|
||||
Name: "openstack-image-name",
|
||||
Usage: "OpenStack image name to use for the instance",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_NETWORK_ID",
|
||||
Name: "openstack-net-id",
|
||||
Usage: "OpenStack network id the machine will be connected on",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_NETWORK_NAME",
|
||||
Name: "openstack-net-name",
|
||||
Usage: "OpenStack network name the machine will be connected on",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_SECURITY_GROUPS",
|
||||
Name: "openstack-sec-groups",
|
||||
Usage: "OpenStack comma separated security groups for the machine",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_FLOATINGIP_POOL",
|
||||
Name: "openstack-floatingip-pool",
|
||||
Usage: "OpenStack floating IP pool to get an IP from to assign to the instance",
|
||||
Value: "",
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "OS_IP_VERSION",
|
||||
Name: "openstack-ip-version",
|
||||
Usage: "OpenStack version of IP address assigned for the machine",
|
||||
Value: 4,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_SSH_USER",
|
||||
Name: "openstack-ssh-user",
|
||||
Usage: "OpenStack SSH user",
|
||||
Value: defaultSSHUser,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "OS_SSH_PORT",
|
||||
Name: "openstack-ssh-port",
|
||||
Usage: "OpenStack SSH port",
|
||||
Value: defaultSSHPort,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "openstack-ssh-user",
|
||||
Usage: "OpenStack SSH user",
|
||||
Value: defaultSSHUser,
|
||||
},
|
||||
mcnflag.IntFlag{
|
||||
Name: "openstack-ssh-port",
|
||||
Usage: "OpenStack SSH port",
|
||||
Value: defaultSSHPort,
|
||||
},
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "OS_ACTIVE_TIMEOUT",
|
||||
Name: "openstack-active-timeout",
|
||||
Usage: "OpenStack active timeout",
|
||||
|
|
|
@ -3,10 +3,10 @@ package rackspace
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/drivers/openstack"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
)
|
||||
|
||||
// Driver is a machine driver for Rackspace. It's a specialization of the generic OpenStack one.
|
||||
|
@ -24,61 +24,55 @@ const (
|
|||
defaultDockerInstall = "true"
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("rackspace", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCreateFlags registers the "machine create" flags recognized by this driver, including
|
||||
// their help text and defaults.
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_USERNAME",
|
||||
Name: "rackspace-username",
|
||||
Usage: "Rackspace account username",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_API_KEY",
|
||||
Name: "rackspace-api-key",
|
||||
Usage: "Rackspace API key",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_REGION_NAME",
|
||||
Name: "rackspace-region",
|
||||
Usage: "Rackspace region name",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "OS_ENDPOINT_TYPE",
|
||||
Name: "rackspace-endpoint-type",
|
||||
Usage: "Rackspace endpoint type (adminURL, internalURL or the default publicURL)",
|
||||
Value: defaultEndpointType,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "rackspace-image-id",
|
||||
Usage: "Rackspace image ID. Default: Ubuntu 14.04 LTS (Trusty Tahr) (PVHVM)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "rackspace-flavor-id",
|
||||
Usage: "Rackspace flavor ID. Default: General Purpose 1GB",
|
||||
Value: defaultFlavorId,
|
||||
EnvVar: "OS_FLAVOR_ID",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "rackspace-ssh-user",
|
||||
Usage: "SSH user for the newly booted machine. Set to root by default",
|
||||
Value: defaultSSHUser,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
Name: "rackspace-ssh-port",
|
||||
Usage: "SSH port for the newly booted machine. Set to 22 by default",
|
||||
Value: defaultSSHPort,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "rackspace-docker-install",
|
||||
Usage: "Set if docker have to be installed on the machine",
|
||||
Value: defaultDockerInstall,
|
||||
|
|
|
@ -7,9 +7,9 @@ import (
|
|||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/ssh"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
)
|
||||
|
@ -51,12 +51,6 @@ const (
|
|||
defaultPrivateVLANIP = 0
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("softlayer", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
func NewDriver(hostName, storePath string) drivers.Driver {
|
||||
return &Driver{
|
||||
Client: &Client{
|
||||
|
@ -83,89 +77,89 @@ func (d *Driver) GetSSHHostname() (string, error) {
|
|||
return d.GetIP()
|
||||
}
|
||||
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
// Set hourly billing to true by default since codegangsta cli doesn't take default bool values
|
||||
if os.Getenv("SOFTLAYER_HOURLY_BILLING") == "" {
|
||||
os.Setenv("SOFTLAYER_HOURLY_BILLING", "true")
|
||||
}
|
||||
return []cli.Flag{
|
||||
cli.IntFlag{
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "SOFTLAYER_MEMORY",
|
||||
Name: "softlayer-memory",
|
||||
Usage: "Memory in MB for machine",
|
||||
Value: defaultMemory,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "SOFTLAYER_DISK_SIZE",
|
||||
Name: "softlayer-disk-size",
|
||||
Usage: "Disk size for machine, a value of 0 uses the default size on softlayer",
|
||||
Value: defaultDiskSize,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "SOFTLAYER_USER",
|
||||
Name: "softlayer-user",
|
||||
Usage: "softlayer user account name",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "SOFTLAYER_API_KEY",
|
||||
Name: "softlayer-api-key",
|
||||
Usage: "softlayer user API key",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "SOFTLAYER_REGION",
|
||||
Name: "softlayer-region",
|
||||
Usage: "softlayer region for machine",
|
||||
Value: defaultRegion,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "SOFTLAYER_CPU",
|
||||
Name: "softlayer-cpu",
|
||||
Usage: "number of CPU's for the machine",
|
||||
Value: defaultCpus,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "SOFTLAYER_HOSTNAME",
|
||||
Name: "softlayer-hostname",
|
||||
Usage: "hostname for the machine - defaults to machine name",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "SOFTLAYER_DOMAIN",
|
||||
Name: "softlayer-domain",
|
||||
Usage: "domain name for machine",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "SOFTLAYER_API_ENDPOINT",
|
||||
Name: "softlayer-api-endpoint",
|
||||
Usage: "softlayer api endpoint to use",
|
||||
Value: ApiEndpoint,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
EnvVar: "SOFTLAYER_HOURLY_BILLING",
|
||||
Name: "softlayer-hourly-billing",
|
||||
Usage: "set hourly billing for machine - on by default",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
EnvVar: "SOFTLAYER_LOCAL_DISK",
|
||||
Name: "softlayer-local-disk",
|
||||
Usage: "use machine local disk instead of softlayer SAN",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
EnvVar: "SOFTLAYER_PRIVATE_NET",
|
||||
Name: "softlayer-private-net-only",
|
||||
Usage: "Use only private networking",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "SOFTLAYER_IMAGE",
|
||||
Name: "softlayer-image",
|
||||
Usage: "OS image for machine",
|
||||
Value: defaultImage,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "SOFTLAYER_PUBLIC_VLAN_ID",
|
||||
Name: "softlayer-public-vlan-id",
|
||||
Usage: "",
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "SOFTLAYER_PRIVATE_VLAN_ID",
|
||||
Name: "softlayer-private-vlan-id",
|
||||
Usage: "",
|
||||
|
|
|
@ -18,9 +18,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
"github.com/docker/machine/libmachine/ssh"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
|
@ -56,12 +56,6 @@ type Driver struct {
|
|||
NoShare bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
drivers.Register("virtualbox", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
func NewDriver(hostName, storePath string) *Driver {
|
||||
return &Driver{
|
||||
BaseDriver: &drivers.BaseDriver{
|
||||
|
@ -77,58 +71,56 @@ func NewDriver(hostName, storePath string) *Driver {
|
|||
}
|
||||
}
|
||||
|
||||
// RegisterCreateFlags registers the flags this driver adds to
|
||||
// "docker hosts create"
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.IntFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "VIRTUALBOX_MEMORY_SIZE",
|
||||
Name: "virtualbox-memory",
|
||||
Usage: "Size of memory for host in MB",
|
||||
Value: defaultMemory,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "VIRTUALBOX_CPU_COUNT",
|
||||
Name: "virtualbox-cpu-count",
|
||||
Usage: "number of CPUs for the machine (-1 to use the number of CPUs available)",
|
||||
Value: defaultCPU,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "VIRTUALBOX_DISK_SIZE",
|
||||
Name: "virtualbox-disk-size",
|
||||
Usage: "Size of disk for host in MB",
|
||||
Value: defaultDiskSize,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VIRTUALBOX_BOOT2DOCKER_URL",
|
||||
Name: "virtualbox-boot2docker-url",
|
||||
Usage: "The URL of the boot2docker image. Defaults to the latest available version",
|
||||
Value: defaultBoot2DockerURL,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "virtualbox-import-boot2docker-vm",
|
||||
Usage: "The name of a Boot2Docker VM to import",
|
||||
Value: defaultBoot2DockerImportVM,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "virtualbox-hostonly-cidr",
|
||||
Usage: "Specify the Host Only CIDR",
|
||||
Value: defaultHostOnlyCIDR,
|
||||
EnvVar: "VIRTUALBOX_HOSTONLY_CIDR",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "virtualbox-hostonly-nictype",
|
||||
Usage: "Specify the Host Only Network Adapter Type",
|
||||
Value: defaultHostOnlyNictype,
|
||||
EnvVar: "VIRTUALBOX_HOSTONLY_NIC_TYPE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
Name: "virtualbox-hostonly-nicpromisc",
|
||||
Usage: "Specify the Host Only Network Adapter Promiscuous Mode",
|
||||
Value: defaultHostOnlyPromiscMode,
|
||||
EnvVar: "VIRTUALBOX_HOSTONLY_NIC_PROMISC",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
mcnflag.BoolFlag{
|
||||
Name: "virtualbox-no-share",
|
||||
Usage: "Disable the mount of your home directory",
|
||||
},
|
||||
|
|
|
@ -17,9 +17,9 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
"github.com/docker/machine/libmachine/ssh"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
|
@ -56,51 +56,47 @@ const (
|
|||
defaultMemory = 1024
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("vmwarefusion", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCreateFlags registers the flags this driver adds to
|
||||
// "docker hosts create"
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "FUSION_BOOT2DOCKER_URL",
|
||||
Name: "vmwarefusion-boot2docker-url",
|
||||
Usage: "Fusion URL for boot2docker image",
|
||||
Value: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "FUSION_CONFIGDRIVE_URL",
|
||||
Name: "vmwarefusion-configdrive-url",
|
||||
Usage: "Fusion URL for cloud-init configdrive",
|
||||
Value: "",
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "FUSION_CPU_COUNT",
|
||||
Name: "vmwarefusion-cpu-count",
|
||||
Usage: "number of CPUs for the machine (-1 to use the number of CPUs available)",
|
||||
Value: defaultCpus,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "FUSION_MEMORY_SIZE",
|
||||
Name: "vmwarefusion-memory-size",
|
||||
Usage: "Fusion size of memory for host VM (in MB)",
|
||||
Value: defaultMemory,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "FUSION_DISK_SIZE",
|
||||
Name: "vmwarefusion-disk-size",
|
||||
Usage: "Fusion size of disk for host VM (in MB)",
|
||||
Value: defaultDiskSize,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "FUSION_SSH_USER",
|
||||
Name: "vmwarefusion-ssh-user",
|
||||
Usage: "SSH user",
|
||||
Value: defaultSSHUser,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "FUSION_SSH_PASSWORD",
|
||||
Name: "vmwarefusion-ssh-password",
|
||||
Usage: "SSH password",
|
||||
|
|
|
@ -11,9 +11,9 @@ import (
|
|||
|
||||
"github.com/vmware/govcloudair"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
"github.com/docker/machine/libmachine/ssh"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
|
@ -31,7 +31,6 @@ type Driver struct {
|
|||
Catalog string
|
||||
CatalogItem string
|
||||
DockerPort int
|
||||
Provision bool
|
||||
CPUCount int
|
||||
MemorySize int
|
||||
VAppID string
|
||||
|
@ -46,90 +45,76 @@ const (
|
|||
defaultDockerPort = 2376
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("vmwarevcloudair", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCreateFlags registers the flags this driver adds to
|
||||
// "docker hosts create"
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VCLOUDAIR_USERNAME",
|
||||
Name: "vmwarevcloudair-username",
|
||||
Usage: "vCloud Air username",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VCLOUDAIR_PASSWORD",
|
||||
Name: "vmwarevcloudair-password",
|
||||
Usage: "vCloud Air password",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VCLOUDAIR_COMPUTEID",
|
||||
Name: "vmwarevcloudair-computeid",
|
||||
Usage: "vCloud Air Compute ID (if using Dedicated Cloud)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VCLOUDAIR_VDCID",
|
||||
Name: "vmwarevcloudair-vdcid",
|
||||
Usage: "vCloud Air VDC ID",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VCLOUDAIR_ORGVDCNETWORK",
|
||||
Name: "vmwarevcloudair-orgvdcnetwork",
|
||||
Usage: "vCloud Air Org VDC Network (Default is <vdcid>-default-routed)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VCLOUDAIR_EDGEGATEWAY",
|
||||
Name: "vmwarevcloudair-edgegateway",
|
||||
Usage: "vCloud Air Org Edge Gateway (Default is <vdcid>)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VCLOUDAIR_PUBLICIP",
|
||||
Name: "vmwarevcloudair-publicip",
|
||||
Usage: "vCloud Air Org Public IP to use",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VCLOUDAIR_CATALOG",
|
||||
Name: "vmwarevcloudair-catalog",
|
||||
Usage: "vCloud Air Catalog (default is Public Catalog)",
|
||||
Value: defaultCatalog,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VCLOUDAIR_CATALOGITEM",
|
||||
Name: "vmwarevcloudair-catalogitem",
|
||||
Usage: "vCloud Air Catalog Item (default is Ubuntu Precise)",
|
||||
Value: defaultCatalogItem,
|
||||
},
|
||||
|
||||
// BoolTFlag is true by default.
|
||||
cli.BoolTFlag{
|
||||
EnvVar: "VCLOUDAIR_PROVISION",
|
||||
Name: "vmwarevcloudair-provision",
|
||||
Usage: "vCloud Air Install Docker binaries (default is true)",
|
||||
},
|
||||
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "VCLOUDAIR_CPU_COUNT",
|
||||
Name: "vmwarevcloudair-cpu-count",
|
||||
Usage: "vCloud Air VM Cpu Count (default 1)",
|
||||
Value: defaultCpus,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "VCLOUDAIR_MEMORY_SIZE",
|
||||
Name: "vmwarevcloudair-memory-size",
|
||||
Usage: "vCloud Air VM Memory Size in MB (default 2048)",
|
||||
Value: defaultMemory,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "VCLOUDAIR_SSH_PORT",
|
||||
Name: "vmwarevcloudair-ssh-port",
|
||||
Usage: "vCloud Air SSH port",
|
||||
Value: defaultSSHPort,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "VCLOUDAIR_DOCKER_PORT",
|
||||
Name: "vmwarevcloudair-docker-port",
|
||||
Usage: "vCloud Air Docker port",
|
||||
|
@ -204,7 +189,6 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
|||
d.DockerPort = flags.Int("vmwarevcloudair-docker-port")
|
||||
d.SSHUser = "root"
|
||||
d.SSHPort = flags.Int("vmwarevcloudair-ssh-port")
|
||||
d.Provision = flags.Bool("vmwarevcloudair-provision")
|
||||
d.CPUCount = flags.Int("vmwarevcloudair-cpu-count")
|
||||
d.MemorySize = flags.Int("vmwarevcloudair-memory-size")
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@ import (
|
|||
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/drivers/vmwarevsphere/errors"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
"github.com/docker/machine/libmachine/ssh"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
|
@ -53,75 +53,69 @@ const (
|
|||
defaultDiskSize = 20000
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers.Register("vmwarevsphere", &drivers.RegisteredDriver{
|
||||
GetCreateFlags: GetCreateFlags,
|
||||
})
|
||||
}
|
||||
|
||||
// GetCreateFlags registers the flags this driver adds to
|
||||
// "docker hosts create"
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.IntFlag{
|
||||
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
|
||||
return []mcnflag.Flag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "VSPHERE_CPU_COUNT",
|
||||
Name: "vmwarevsphere-cpu-count",
|
||||
Usage: "vSphere CPU number for docker VM",
|
||||
Value: defaultCpus,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "VSPHERE_MEMORY_SIZE",
|
||||
Name: "vmwarevsphere-memory-size",
|
||||
Usage: "vSphere size of memory for docker VM (in MB)",
|
||||
Value: defaultMemory,
|
||||
},
|
||||
cli.IntFlag{
|
||||
mcnflag.IntFlag{
|
||||
EnvVar: "VSPHERE_DISK_SIZE",
|
||||
Name: "vmwarevsphere-disk-size",
|
||||
Usage: "vSphere size of disk for docker VM (in MB)",
|
||||
Value: defaultDiskSize,
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VSPHERE_BOOT2DOCKER_URL",
|
||||
Name: "vmwarevsphere-boot2docker-url",
|
||||
Usage: "vSphere URL for boot2docker image",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VSPHERE_VCENTER",
|
||||
Name: "vmwarevsphere-vcenter",
|
||||
Usage: "vSphere IP/hostname for vCenter",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VSPHERE_USERNAME",
|
||||
Name: "vmwarevsphere-username",
|
||||
Usage: "vSphere username",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VSPHERE_PASSWORD",
|
||||
Name: "vmwarevsphere-password",
|
||||
Usage: "vSphere password",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VSPHERE_NETWORK",
|
||||
Name: "vmwarevsphere-network",
|
||||
Usage: "vSphere network where the docker VM will be attached",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VSPHERE_DATASTORE",
|
||||
Name: "vmwarevsphere-datastore",
|
||||
Usage: "vSphere datastore for docker VM",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VSPHERE_DATACENTER",
|
||||
Name: "vmwarevsphere-datacenter",
|
||||
Usage: "vSphere datacenter for docker VM",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VSPHERE_POOL",
|
||||
Name: "vmwarevsphere-pool",
|
||||
Usage: "vSphere resource pool for docker VM",
|
||||
},
|
||||
cli.StringFlag{
|
||||
mcnflag.StringFlag{
|
||||
EnvVar: "VSPHERE_COMPUTE_IP",
|
||||
Name: "vmwarevsphere-compute-ip",
|
||||
Usage: "vSphere compute host IP where the docker VM will be instantiated",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package cert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/machine/libmachine/auth"
|
||||
|
@ -25,10 +27,10 @@ func BootstrapCertificates(authOptions *auth.AuthOptions) error {
|
|||
if _, err := os.Stat(certDir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(certDir, 0700); err != nil {
|
||||
log.Fatalf("Error creating machine certificate dir: %s", err)
|
||||
return fmt.Errorf("Creating machine certificate dir failed: %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,11 +39,11 @@ func BootstrapCertificates(authOptions *auth.AuthOptions) error {
|
|||
|
||||
// check if the key path exists; if so, error
|
||||
if _, err := os.Stat(caPrivateKeyPath); err == nil {
|
||||
log.Fatalf("The CA key already exists. Please remove it or specify a different key/cert.")
|
||||
return errors.New("The CA key already exists. Please remove it or specify a different key/cert.")
|
||||
}
|
||||
|
||||
if err := GenerateCACertificate(caCertPath, caPrivateKeyPath, org, bits); err != nil {
|
||||
log.Infof("Error generating CA certificate: %s", err)
|
||||
return fmt.Errorf("Generating CA certificate failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,20 +53,20 @@ func BootstrapCertificates(authOptions *auth.AuthOptions) error {
|
|||
if _, err := os.Stat(certDir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.Mkdir(certDir, 0700); err != nil {
|
||||
log.Fatalf("Error creating machine client cert dir: %s", err)
|
||||
return fmt.Errorf("Creating machine client cert dir failed: %s", err)
|
||||
}
|
||||
} else {
|
||||
log.Fatal(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// check if the key path exists; if so, error
|
||||
if _, err := os.Stat(clientKeyPath); err == nil {
|
||||
log.Fatalf("The client key already exists. Please remove it or specify a different key/cert.")
|
||||
return errors.New("The client key already exists. Please remove it or specify a different key/cert.")
|
||||
}
|
||||
|
||||
if err := GenerateCert([]string{""}, clientCertPath, clientKeyPath, caCertPath, caPrivateKeyPath, org, bits); err != nil {
|
||||
log.Fatalf("Error generating client certificate: %s", err)
|
||||
return fmt.Errorf("Generating client certificate failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,9 @@ package drivers
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
)
|
||||
|
||||
|
@ -59,9 +57,13 @@ type Driver interface {
|
|||
// have any special restart behaviour.
|
||||
Restart() error
|
||||
|
||||
// GetCreateFlags returns the mcnflag.Flag slice representing the flags
|
||||
// that can be set, their descriptions and defaults.
|
||||
GetCreateFlags() []mcnflag.Flag
|
||||
|
||||
// SetConfigFromFlags configures the driver with the object that was returned
|
||||
// by RegisterCreateFlags
|
||||
SetConfigFromFlags(flags DriverOptions) error
|
||||
SetConfigFromFlags(opts DriverOptions) error
|
||||
|
||||
// Start a host
|
||||
Start() error
|
||||
|
@ -70,75 +72,8 @@ type Driver interface {
|
|||
Stop() error
|
||||
}
|
||||
|
||||
// RegisteredDriver is a wrapper struct used to "register" drivers so that
|
||||
// information about them can be displayed in the CLI.
|
||||
type RegisteredDriver struct {
|
||||
// GetCreateFlags gets the FlagSet for "docker-machine create" for
|
||||
// display to the user.
|
||||
GetCreateFlags func() []cli.Flag
|
||||
}
|
||||
|
||||
var ErrHostIsNotRunning = errors.New("Host is not running")
|
||||
|
||||
var (
|
||||
drivers map[string]*RegisteredDriver
|
||||
)
|
||||
|
||||
func init() {
|
||||
drivers = make(map[string]*RegisteredDriver)
|
||||
}
|
||||
|
||||
// Register a driver
|
||||
func Register(name string, registeredDriver *RegisteredDriver) error {
|
||||
if _, exists := drivers[name]; exists {
|
||||
return fmt.Errorf("Name already registered %s", name)
|
||||
}
|
||||
|
||||
drivers[name] = registeredDriver
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCreateFlags runs GetCreateFlags for all of the drivers and
|
||||
// returns their return values indexed by the driver name
|
||||
func GetCreateFlags() []cli.Flag {
|
||||
flags := []cli.Flag{}
|
||||
|
||||
for driverName := range drivers {
|
||||
driver := drivers[driverName]
|
||||
for _, f := range driver.GetCreateFlags() {
|
||||
flags = append(flags, f)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(ByFlagName(flags))
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func GetCreateFlagsForDriver(name string) ([]cli.Flag, error) {
|
||||
|
||||
for driverName := range drivers {
|
||||
if name == driverName {
|
||||
driver := drivers[driverName]
|
||||
flags := driver.GetCreateFlags()
|
||||
sort.Sort(ByFlagName(flags))
|
||||
return flags, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Driver %s not found", name)
|
||||
}
|
||||
|
||||
// GetDriverNames returns a slice of all registered driver names
|
||||
func GetDriverNames() []string {
|
||||
names := make([]string, 0, len(drivers))
|
||||
for k := range drivers {
|
||||
names = append(names, k)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
|
||||
type DriverOptions interface {
|
||||
String(key string) string
|
||||
StringSlice(key string) []string
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
package drivers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func TestGetCreateFlags(t *testing.T) {
|
||||
Register("foo", &RegisteredDriver{
|
||||
GetCreateFlags: func() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "a",
|
||||
Value: "",
|
||||
Usage: "",
|
||||
EnvVar: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "b",
|
||||
Value: "",
|
||||
Usage: "",
|
||||
EnvVar: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "c",
|
||||
Value: "",
|
||||
Usage: "",
|
||||
EnvVar: "",
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
Register("bar", &RegisteredDriver{
|
||||
GetCreateFlags: func() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "d",
|
||||
Value: "",
|
||||
Usage: "",
|
||||
EnvVar: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "e",
|
||||
Value: "",
|
||||
Usage: "",
|
||||
EnvVar: "",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "f",
|
||||
Value: "",
|
||||
Usage: "",
|
||||
EnvVar: "",
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
expected := []string{"-a \t", "-b \t", "-c \t", "-d \t", "-e \t", "-f \t"}
|
||||
|
||||
// test a few times to catch offset issue
|
||||
// if it crops up again
|
||||
for i := 0; i < 5; i++ {
|
||||
flags := GetCreateFlags()
|
||||
for j, e := range expected {
|
||||
if flags[j].String() != e {
|
||||
t.Fatal("Flags are out of order")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
package localbinary
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
)
|
||||
|
||||
var (
|
||||
// Timeout where we will bail if we're not able to properly contact the
|
||||
// plugin server.
|
||||
defaultTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
const (
|
||||
pluginOutPrefix = "(%s) OUT | "
|
||||
pluginErrPrefix = "(%s) DBG | "
|
||||
PluginEnvKey = "MACHINE_PLUGIN_TOKEN"
|
||||
PluginEnvVal = "42"
|
||||
)
|
||||
|
||||
type PluginStreamer interface {
|
||||
// Return a channel for receiving the output of the stream line by
|
||||
// line, and a channel for stopping the stream when we are finished
|
||||
// reading from it.
|
||||
//
|
||||
// It happens to be the case that we do this all inside of the main
|
||||
// plugin struct today, but that may not be the case forever.
|
||||
AttachStream(*bufio.Scanner) (<-chan string, chan<- bool)
|
||||
}
|
||||
|
||||
type PluginServer interface {
|
||||
// Get the address where the plugin server is listening.
|
||||
Address() (string, error)
|
||||
|
||||
// Serve kicks off the plugin server.
|
||||
Serve() error
|
||||
|
||||
// Close shuts down the initialized server.
|
||||
Close() error
|
||||
}
|
||||
|
||||
type McnBinaryExecutor interface {
|
||||
// Execute the driver plugin. Returns scanners for plugin binary
|
||||
// stdout and stderr.
|
||||
Start() (*bufio.Scanner, *bufio.Scanner, error)
|
||||
|
||||
// Stop reading from the plugins in question.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// DriverPlugin interface wraps the underlying mechanics of starting a driver
|
||||
// plugin server and then figuring out where it can be dialed.
|
||||
type DriverPlugin interface {
|
||||
PluginServer
|
||||
PluginStreamer
|
||||
}
|
||||
|
||||
type LocalBinaryPlugin struct {
|
||||
Executor McnBinaryExecutor
|
||||
Addr string
|
||||
MachineName string
|
||||
addrCh chan string
|
||||
stopCh chan bool
|
||||
}
|
||||
|
||||
type LocalBinaryExecutor struct {
|
||||
pluginStdout, pluginStderr io.ReadCloser
|
||||
DriverName string
|
||||
}
|
||||
|
||||
func NewLocalBinaryPlugin(driverName string) *LocalBinaryPlugin {
|
||||
return &LocalBinaryPlugin{
|
||||
stopCh: make(chan bool, 1),
|
||||
addrCh: make(chan string, 1),
|
||||
Executor: &LocalBinaryExecutor{
|
||||
DriverName: driverName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (lbe *LocalBinaryExecutor) Start() (*bufio.Scanner, *bufio.Scanner, error) {
|
||||
log.Debugf("Launching plugin server for driver %s", lbe.DriverName)
|
||||
|
||||
binaryPath, err := exec.LookPath(fmt.Sprintf("docker-machine-driver-%s", lbe.DriverName))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Driver %q not found. Do you have the plugin binary accessible in your PATH?", lbe.DriverName)
|
||||
}
|
||||
|
||||
log.Debugf("Found binary path at %s", binaryPath)
|
||||
|
||||
cmd := exec.Command(binaryPath)
|
||||
|
||||
lbe.pluginStdout, err = cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error getting cmd stdout pipe: %s", err)
|
||||
}
|
||||
|
||||
lbe.pluginStderr, err = cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Error getting cmd stderr pipe: %s", err)
|
||||
}
|
||||
|
||||
outScanner := bufio.NewScanner(lbe.pluginStdout)
|
||||
errScanner := bufio.NewScanner(lbe.pluginStderr)
|
||||
|
||||
os.Setenv(PluginEnvKey, PluginEnvVal)
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, nil, fmt.Errorf("Error starting plugin binary: %s", err)
|
||||
}
|
||||
|
||||
return outScanner, errScanner, nil
|
||||
}
|
||||
|
||||
func (lbe *LocalBinaryExecutor) Close() error {
|
||||
if err := lbe.pluginStdout.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := lbe.pluginStderr.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func stream(scanner *bufio.Scanner, streamOutCh chan<- string, stopCh <-chan bool) {
|
||||
for scanner.Scan() {
|
||||
select {
|
||||
case <-stopCh:
|
||||
close(streamOutCh)
|
||||
return
|
||||
default:
|
||||
streamOutCh <- strings.Trim(scanner.Text(), "\n")
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.Warnf("Scanning stream: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (lbp *LocalBinaryPlugin) AttachStream(scanner *bufio.Scanner) (<-chan string, chan<- bool) {
|
||||
streamOutCh := make(chan string)
|
||||
stopCh := make(chan bool, 1)
|
||||
go stream(scanner, streamOutCh, stopCh)
|
||||
return streamOutCh, stopCh
|
||||
}
|
||||
|
||||
func (lbp *LocalBinaryPlugin) execServer() error {
|
||||
outScanner, errScanner, err := lbp.Executor.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Scan just one line to get the address, then send it to the relevant
|
||||
// channel.
|
||||
outScanner.Scan()
|
||||
addr := outScanner.Text()
|
||||
if err := outScanner.Err(); err != nil {
|
||||
return fmt.Errorf("Reading plugin address failed: %s", err)
|
||||
}
|
||||
|
||||
lbp.addrCh <- strings.TrimSpace(addr)
|
||||
|
||||
stdOutCh, stopStdoutCh := lbp.AttachStream(outScanner)
|
||||
stdErrCh, stopStderrCh := lbp.AttachStream(errScanner)
|
||||
|
||||
for {
|
||||
select {
|
||||
case out := <-stdOutCh:
|
||||
log.Debug(fmt.Sprintf(pluginOutPrefix, lbp.MachineName), out)
|
||||
case err := <-stdErrCh:
|
||||
log.Debug(fmt.Sprintf(pluginErrPrefix, lbp.MachineName), err)
|
||||
case _ = <-lbp.stopCh:
|
||||
stopStdoutCh <- true
|
||||
stopStderrCh <- true
|
||||
if err := lbp.Executor.Close(); err != nil {
|
||||
return fmt.Errorf("Error closing local plugin binary: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (lbp *LocalBinaryPlugin) Serve() error {
|
||||
return lbp.execServer()
|
||||
}
|
||||
|
||||
func (lbp *LocalBinaryPlugin) Address() (string, error) {
|
||||
if lbp.Addr == "" {
|
||||
select {
|
||||
case lbp.Addr = <-lbp.addrCh:
|
||||
log.Debugf("Plugin server listening at address %s", lbp.Addr)
|
||||
close(lbp.addrCh)
|
||||
return lbp.Addr, nil
|
||||
case <-time.After(defaultTimeout):
|
||||
return "", fmt.Errorf("Failed to dial the plugin server in %s", defaultTimeout)
|
||||
}
|
||||
}
|
||||
return lbp.Addr, nil
|
||||
}
|
||||
|
||||
func (lbp *LocalBinaryPlugin) Close() error {
|
||||
lbp.stopCh <- true
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
package localbinary
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
)
|
||||
|
||||
type FakeExecutor struct {
|
||||
stdout, stderr io.ReadCloser
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (fe *FakeExecutor) Start() (*bufio.Scanner, *bufio.Scanner, error) {
|
||||
return bufio.NewScanner(fe.stdout), bufio.NewScanner(fe.stderr), nil
|
||||
}
|
||||
|
||||
func (fe *FakeExecutor) Close() error {
|
||||
fe.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestLocalBinaryPluginAddress(t *testing.T) {
|
||||
lbp := &LocalBinaryPlugin{}
|
||||
expectedAddr := "127.0.0.1:12345"
|
||||
|
||||
lbp.addrCh = make(chan string, 1)
|
||||
lbp.addrCh <- expectedAddr
|
||||
|
||||
// Call the first time to read from the channel
|
||||
addr, err := lbp.Address()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, instead got %s", err)
|
||||
}
|
||||
if addr != expectedAddr {
|
||||
t.Fatal("Expected did not match actual address")
|
||||
}
|
||||
|
||||
// Call the second time to read the "cached" address value
|
||||
addr, err = lbp.Address()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, instead got %s", err)
|
||||
}
|
||||
if addr != expectedAddr {
|
||||
t.Fatal("Expected did not match actual address")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalBinaryPluginAddressTimeout(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping timeout test")
|
||||
}
|
||||
lbp := &LocalBinaryPlugin{}
|
||||
lbp.addrCh = make(chan string, 1)
|
||||
go func() {
|
||||
_, err := lbp.Address()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected to get a timeout error, instead got %s", err)
|
||||
}
|
||||
}()
|
||||
time.Sleep(defaultTimeout + 1)
|
||||
}
|
||||
|
||||
func TestLocalBinaryPluginClose(t *testing.T) {
|
||||
lbp := &LocalBinaryPlugin{}
|
||||
lbp.stopCh = make(chan bool, 1)
|
||||
go lbp.Close()
|
||||
stopped := <-lbp.stopCh
|
||||
if !stopped {
|
||||
t.Fatal("Close did not send a stop message on the proper channel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecServer(t *testing.T) {
|
||||
log.IsDebug = true
|
||||
machineName := "test"
|
||||
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
log.SetOutWriter(logWriter)
|
||||
log.SetErrWriter(logWriter)
|
||||
|
||||
defer func() {
|
||||
log.IsDebug = false
|
||||
log.SetOutWriter(os.Stdout)
|
||||
log.SetErrWriter(os.Stderr)
|
||||
}()
|
||||
|
||||
stdoutReader, stdoutWriter := io.Pipe()
|
||||
stderrReader, stderrWriter := io.Pipe()
|
||||
|
||||
fe := &FakeExecutor{
|
||||
stdout: stdoutReader,
|
||||
stderr: stderrReader,
|
||||
}
|
||||
|
||||
lbp := &LocalBinaryPlugin{
|
||||
MachineName: machineName,
|
||||
Executor: fe,
|
||||
addrCh: make(chan string, 1),
|
||||
stopCh: make(chan bool, 1),
|
||||
}
|
||||
|
||||
finalErr := make(chan error, 1)
|
||||
|
||||
// Start the docker-machine-foo plugin server
|
||||
go func() {
|
||||
finalErr <- lbp.execServer()
|
||||
}()
|
||||
|
||||
expectedAddr := "127.0.0.1:12345"
|
||||
expectedPluginOut := "Doing some fun plugin stuff..."
|
||||
expectedPluginErr := "Uh oh, something in plugin went wrong..."
|
||||
|
||||
logScanner := bufio.NewScanner(logReader)
|
||||
|
||||
if _, err := io.WriteString(stdoutWriter, expectedAddr+"\n"); err != nil {
|
||||
t.Fatalf("Error attempting to write plugin address: %s", err)
|
||||
}
|
||||
|
||||
if addr := <-lbp.addrCh; addr != expectedAddr {
|
||||
t.Fatalf("Expected to read the expected address properly in server but did not")
|
||||
}
|
||||
|
||||
expectedOut := fmt.Sprintf("%s%s", fmt.Sprintf(pluginOutPrefix, machineName), expectedPluginOut)
|
||||
|
||||
if _, err := io.WriteString(stdoutWriter, expectedPluginOut+"\n"); err != nil {
|
||||
t.Fatalf("Error attempting to write to out in plugin: %s", err)
|
||||
}
|
||||
|
||||
if logScanner.Scan(); logScanner.Text() != expectedOut {
|
||||
t.Fatalf("Output written to log was not what we expected\nexpected: %s\nactual: %s", expectedOut, logScanner.Text())
|
||||
}
|
||||
|
||||
expectedErr := fmt.Sprintf("%s%s", fmt.Sprintf(pluginErrPrefix, machineName), expectedPluginErr)
|
||||
|
||||
if _, err := io.WriteString(stderrWriter, expectedPluginErr+"\n"); err != nil {
|
||||
t.Fatalf("Error attempting to write to err in plugin: %s", err)
|
||||
}
|
||||
|
||||
if logScanner.Scan(); logScanner.Text() != expectedErr {
|
||||
t.Fatalf("Error written to log was not what we expected\nexpected: %s\nactual: %s", expectedErr, logScanner.Text())
|
||||
}
|
||||
|
||||
lbp.Close()
|
||||
|
||||
if err := <-finalErr; err != nil {
|
||||
t.Fatalf("Error serving: %s", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/rpc"
|
||||
"os"
|
||||
|
||||
"github.com/docker/machine/libmachine"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin/localbinary"
|
||||
"github.com/docker/machine/libmachine/drivers/rpc"
|
||||
)
|
||||
|
||||
func RegisterDriver(d drivers.Driver) {
|
||||
if os.Getenv(localbinary.PluginEnvKey) != localbinary.PluginEnvVal {
|
||||
fmt.Fprintln(os.Stderr, `This is a Docker Machine plugin binary.
|
||||
Plugin binaries are not intended to be invoked directly.
|
||||
Please use this plugin through the main 'docker-machine' binary.`)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
libmachine.SetDebug(true)
|
||||
|
||||
rpcd := new(rpcdriver.RpcServerDriver)
|
||||
rpcd.ActualDriver = d
|
||||
rpcd.CloseCh = make(chan bool)
|
||||
rpc.Register(rpcd)
|
||||
|
||||
rpc.HandleHTTP()
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error loading RPC server: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
fmt.Println(listener.Addr())
|
||||
|
||||
go http.Serve(listener, nil)
|
||||
|
||||
<-rpcd.CloseCh
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
package rpcdriver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/rpc"
|
||||
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/drivers/plugin/localbinary"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
)
|
||||
|
||||
type RpcClientDriver struct {
|
||||
Client *InternalClient
|
||||
}
|
||||
|
||||
type RpcCall struct {
|
||||
ServiceMethod string
|
||||
Args interface{}
|
||||
Reply interface{}
|
||||
}
|
||||
|
||||
type InternalClient struct {
|
||||
RpcClient *rpc.Client
|
||||
Calls chan RpcCall
|
||||
CallErrs chan error
|
||||
}
|
||||
|
||||
func (ic *InternalClient) Call(serviceMethod string, args interface{}, reply interface{}) error {
|
||||
ic.Calls <- RpcCall{
|
||||
ServiceMethod: serviceMethod,
|
||||
Args: args,
|
||||
Reply: reply,
|
||||
}
|
||||
|
||||
return <-ic.CallErrs
|
||||
}
|
||||
|
||||
func NewInternalClient(rpcclient *rpc.Client) *InternalClient {
|
||||
return &InternalClient{
|
||||
RpcClient: rpcclient,
|
||||
Calls: make(chan RpcCall),
|
||||
CallErrs: make(chan error),
|
||||
}
|
||||
}
|
||||
|
||||
func NewRpcClientDriver(rawDriverData []byte, driverName string) (*RpcClientDriver, error) {
|
||||
mcnName := ""
|
||||
|
||||
p := localbinary.NewLocalBinaryPlugin(driverName)
|
||||
|
||||
c := &RpcClientDriver{}
|
||||
|
||||
go func() {
|
||||
if err := p.Serve(); err != nil {
|
||||
// If we can't safely load the server, best to just
|
||||
// bail.
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
addr, err := p.Address()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error attempting to get plugin server address for RPC: %s", err)
|
||||
}
|
||||
|
||||
rpcclient, err := rpc.DialHTTP("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Client = NewInternalClient(rpcclient)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
call := <-c.Client.Calls
|
||||
|
||||
log.Debugf("(%s) Got msg %+v", mcnName, call)
|
||||
|
||||
if call.ServiceMethod == "RpcServerDriver.Close" {
|
||||
p.Close()
|
||||
}
|
||||
|
||||
c.Client.CallErrs <- c.Client.RpcClient.Call(call.ServiceMethod, call.Args, call.Reply)
|
||||
|
||||
if call.ServiceMethod == "RpcServerDriver.Close" {
|
||||
// If we're messaging the server to close,
|
||||
// we're not accepting any more RPC calls at
|
||||
// all, so return from this function
|
||||
// (subsequent "requests" to make a call by
|
||||
// sending on the Calls channel will simply
|
||||
// block and never go through)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var version int
|
||||
if err := c.Client.Call("RpcServerDriver.GetVersion", struct{}{}, &version); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug("Using API Version ", version)
|
||||
|
||||
if err := c.SetConfigRaw(rawDriverData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mcnName = c.GetMachineName()
|
||||
p.MachineName = mcnName
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) MarshalJSON() ([]byte, error) {
|
||||
return c.GetConfigRaw()
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) UnmarshalJSON(data []byte) error {
|
||||
return c.SetConfigRaw(data)
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) Close() error {
|
||||
log.Debug("Making call to close driver server")
|
||||
|
||||
if err := c.Client.Call("RpcServerDriver.Close", struct{}{}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("Successfully made call to close driver server")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper method to make requests which take no arguments and return simply a
|
||||
// string, e.g. "GetIP".
|
||||
func (c *RpcClientDriver) rpcStringCall(method string) (string, error) {
|
||||
var info string
|
||||
|
||||
if err := c.Client.Call(method, struct{}{}, &info); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) GetCreateFlags() []mcnflag.Flag {
|
||||
var flags []mcnflag.Flag
|
||||
|
||||
if err := c.Client.Call("RpcServerDriver.GetCreateFlags", struct{}{}, &flags); err != nil {
|
||||
log.Warnf("Error attempting call to get create flags: %s", err)
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) SetConfigRaw(data []byte) error {
|
||||
return c.Client.Call("RpcServerDriver.SetConfigRaw", data, nil)
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) GetConfigRaw() ([]byte, error) {
|
||||
var data []byte
|
||||
|
||||
if err := c.Client.Call("RpcServerDriver.GetConfigRaw", struct{}{}, &data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) DriverName() string {
|
||||
driverName, err := c.rpcStringCall("RpcServerDriver.DriverName")
|
||||
if err != nil {
|
||||
log.Warnf("Error attempting call to get driver name: %s", err)
|
||||
}
|
||||
|
||||
return driverName
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) SetConfigFromFlags(flags drivers.DriverOptions) error {
|
||||
return c.Client.Call("RpcServerDriver.SetConfigFromFlags", &flags, nil)
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) GetURL() (string, error) {
|
||||
return c.rpcStringCall("RpcServerDriver.GetURL")
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) GetMachineName() string {
|
||||
name, err := c.rpcStringCall("RpcServerDriver.GetMachineName")
|
||||
if err != nil {
|
||||
log.Warnf("Error attempting call to get machine name: %s", err)
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) GetIP() (string, error) {
|
||||
return c.rpcStringCall("RpcServerDriver.GetIP")
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) GetSSHHostname() (string, error) {
|
||||
return c.rpcStringCall("RpcServerDriver.GetSSHHostname")
|
||||
}
|
||||
|
||||
// TODO: This method doesn't even make sense to have with RPC.
|
||||
func (c *RpcClientDriver) GetSSHKeyPath() string {
|
||||
path, err := c.rpcStringCall("RpcServerDriver.GetSSHKeyPath")
|
||||
if err != nil {
|
||||
log.Warnf("Error attempting call to get SSH key path: %s", err)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) GetSSHPort() (int, error) {
|
||||
var port int
|
||||
|
||||
if err := c.Client.Call("RpcServerDriver.GetSSHPort", struct{}{}, &port); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) GetSSHUsername() string {
|
||||
username, err := c.rpcStringCall("RpcServerDriver.GetSSHUsername")
|
||||
if err != nil {
|
||||
log.Warnf("Error attempting call to get SSH username: %s", err)
|
||||
}
|
||||
|
||||
return username
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) GetState() (state.State, error) {
|
||||
var s state.State
|
||||
|
||||
if err := c.Client.Call("RpcServerDriver.GetState", struct{}{}, &s); err != nil {
|
||||
return state.Error, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) PreCreateCheck() error {
|
||||
return c.Client.Call("RpcServerDriver.PreCreateCheck", struct{}{}, nil)
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) Create() error {
|
||||
return c.Client.Call("RpcServerDriver.Create", struct{}{}, nil)
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) Remove() error {
|
||||
return c.Client.Call("RpcServerDriver.Remove", struct{}{}, nil)
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) Start() error {
|
||||
return c.Client.Call("RpcServerDriver.Start", struct{}{}, nil)
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) Stop() error {
|
||||
return c.Client.Call("RpcServerDriver.Stop", struct{}{}, nil)
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) Restart() error {
|
||||
return c.Client.Call("RpcServerDriver.Restart", struct{}{}, nil)
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) Kill() error {
|
||||
return c.Client.Call("RpcServerDriver.Kill", struct{}{}, nil)
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) LocalArtifactPath(file string) string {
|
||||
var path string
|
||||
|
||||
if err := c.Client.Call("RpcServerDriver.LocalArtifactPath", file, &path); err != nil {
|
||||
log.Warnf("Error attempting call to get LocalArtifactPath: %s", err)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) GlobalArtifactPath() string {
|
||||
globalArtifactPath, err := c.rpcStringCall("RpcServerDriver.GlobalArtifactPath")
|
||||
if err != nil {
|
||||
log.Warnf("Error attempting call to get GlobalArtifactPath: %s", err)
|
||||
}
|
||||
|
||||
return globalArtifactPath
|
||||
}
|
||||
|
||||
func (c *RpcClientDriver) Upgrade() error {
|
||||
return c.Client.Call("RpcServerDriver.Upgrade", struct{}{}, nil)
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
package rpcdriver
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnflag"
|
||||
"github.com/docker/machine/libmachine/state"
|
||||
"github.com/docker/machine/libmachine/version"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(new(RpcFlags))
|
||||
gob.Register(new(mcnflag.IntFlag))
|
||||
gob.Register(new(mcnflag.StringFlag))
|
||||
gob.Register(new(mcnflag.StringSliceFlag))
|
||||
gob.Register(new(mcnflag.BoolFlag))
|
||||
}
|
||||
|
||||
type RpcFlags struct {
|
||||
Values map[string]interface{}
|
||||
}
|
||||
|
||||
func (r RpcFlags) Get(key string) interface{} {
|
||||
val, ok := r.Values[key]
|
||||
if !ok {
|
||||
log.Warnf("Trying to access option %s which does not exist", key)
|
||||
log.Warn("THIS ***WILL*** CAUSE UNEXPECTED BEHAVIOR")
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (r RpcFlags) String(key string) string {
|
||||
val, ok := r.Get(key).(string)
|
||||
if !ok {
|
||||
log.Warnf("Type assertion did not go smoothly to string for key %s", key)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (r RpcFlags) StringSlice(key string) []string {
|
||||
val, ok := r.Get(key).([]string)
|
||||
if !ok {
|
||||
log.Warnf("Type assertion did not go smoothly to string slice for key %s", key)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (r RpcFlags) Int(key string) int {
|
||||
val, ok := r.Get(key).(int)
|
||||
if !ok {
|
||||
log.Warnf("Type assertion did not go smoothly to int for key %s", key)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (r RpcFlags) Bool(key string) bool {
|
||||
val, ok := r.Get(key).(bool)
|
||||
if !ok {
|
||||
log.Warnf("Type assertion did not go smoothly to bool for key %s", key)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
type RpcServerDriver struct {
|
||||
ActualDriver drivers.Driver
|
||||
CloseCh chan bool
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) Close(_, _ *struct{}) error {
|
||||
r.CloseCh <- true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) GetVersion(_ *struct{}, reply *int) error {
|
||||
*reply = version.ApiVersion
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) GetConfigRaw(_ *struct{}, reply *[]byte) error {
|
||||
driverData, err := json.Marshal(r.ActualDriver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*reply = driverData
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) GetCreateFlags(_ *struct{}, reply *[]mcnflag.Flag) error {
|
||||
*reply = r.ActualDriver.GetCreateFlags()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) SetConfigRaw(data []byte, _ *struct{}) error {
|
||||
return json.Unmarshal(data, &r.ActualDriver)
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) Create(_, _ *struct{}) error {
|
||||
return r.ActualDriver.Create()
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) DriverName(_ *struct{}, reply *string) error {
|
||||
*reply = r.ActualDriver.DriverName()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) GetIP(_ *struct{}, reply *string) error {
|
||||
ip, err := r.ActualDriver.GetIP()
|
||||
*reply = ip
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) GetMachineName(_ *struct{}, reply *string) error {
|
||||
*reply = r.ActualDriver.GetMachineName()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) GetSSHHostname(_ *struct{}, reply *string) error {
|
||||
hostname, err := r.ActualDriver.GetSSHHostname()
|
||||
*reply = hostname
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) GetSSHKeyPath(_ *struct{}, reply *string) error {
|
||||
*reply = r.ActualDriver.GetSSHKeyPath()
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSSHPort returns port for use with ssh
|
||||
func (r *RpcServerDriver) GetSSHPort(_ *struct{}, reply *int) error {
|
||||
port, err := r.ActualDriver.GetSSHPort()
|
||||
*reply = port
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) GetSSHUsername(_ *struct{}, reply *string) error {
|
||||
*reply = r.ActualDriver.GetSSHUsername()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) GetURL(_ *struct{}, reply *string) error {
|
||||
info, err := r.ActualDriver.GetURL()
|
||||
*reply = info
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) GetState(_ *struct{}, reply *state.State) error {
|
||||
s, err := r.ActualDriver.GetState()
|
||||
*reply = s
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) Kill(_ *struct{}, _ *struct{}) error {
|
||||
return r.ActualDriver.Kill()
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) PreCreateCheck(_ *struct{}, _ *struct{}) error {
|
||||
return r.ActualDriver.PreCreateCheck()
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) Remove(_ *struct{}, _ *struct{}) error {
|
||||
return r.ActualDriver.Remove()
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) Restart(_ *struct{}, _ *struct{}) error {
|
||||
return r.ActualDriver.Restart()
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) SetConfigFromFlags(flags *drivers.DriverOptions, _ *struct{}) error {
|
||||
return r.ActualDriver.SetConfigFromFlags(*flags)
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) Start(_ *struct{}, _ *struct{}) error {
|
||||
return r.ActualDriver.Start()
|
||||
}
|
||||
|
||||
func (r *RpcServerDriver) Stop(_ *struct{}, _ *struct{}) error {
|
||||
return r.ActualDriver.Stop()
|
||||
}
|
|
@ -2,6 +2,7 @@ package drivers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
|
@ -62,7 +63,8 @@ func sshAvailableFunc(d Driver) func() bool {
|
|||
}
|
||||
|
||||
func WaitForSSH(d Driver) error {
|
||||
if err := mcnutils.WaitFor(sshAvailableFunc(d)); err != nil {
|
||||
// Try to dial SSH for 30 seconds before timing out.
|
||||
if err := mcnutils.WaitForSpecific(sshAvailableFunc(d), 6, 5*time.Second); err != nil {
|
||||
return fmt.Errorf("Too many retries waiting for SSH to be available. Last error: %s", err)
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/docker/machine/libmachine/auth"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/engine"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/mcnutils"
|
||||
"github.com/docker/machine/libmachine/provision"
|
||||
"github.com/docker/machine/libmachine/provision/pkgaction"
|
||||
|
@ -31,6 +30,7 @@ type Host struct {
|
|||
DriverName string
|
||||
HostOptions *HostOptions
|
||||
Name string
|
||||
RawDriver []byte
|
||||
}
|
||||
|
||||
type HostOptions struct {
|
||||
|
@ -127,7 +127,7 @@ func (h *Host) Upgrade() error {
|
|||
}
|
||||
|
||||
if machineState != state.Running {
|
||||
log.Fatal(errMachineMustBeRunningForUpgrade)
|
||||
return errMachineMustBeRunningForUpgrade
|
||||
}
|
||||
|
||||
provisioner, err := provision.DetectProvisioner(h.Driver)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package host
|
||||
|
||||
import "github.com/docker/machine/libmachine/drivers"
|
||||
|
||||
type HostV2 struct {
|
||||
ConfigVersion int
|
||||
Driver drivers.Driver
|
||||
DriverName string
|
||||
HostOptions *HostOptions
|
||||
Name string
|
||||
}
|
|
@ -2,13 +2,25 @@ package host
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/machine/drivers/driverfactory"
|
||||
"github.com/docker/machine/drivers/none"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
"github.com/docker/machine/libmachine/version"
|
||||
)
|
||||
|
||||
type RawDataDriver struct {
|
||||
*none.Driver
|
||||
data []byte // passed directly back when invoking json.Marshal on this type
|
||||
}
|
||||
|
||||
func (r *RawDataDriver) MarshalJSON() ([]byte, error) {
|
||||
// now marshal it back up
|
||||
return r.data, nil
|
||||
}
|
||||
|
||||
func getMigratedHostMetadata(data []byte) (*HostMetadata, error) {
|
||||
// HostMetadata is for a "first pass" so we can then load the driver
|
||||
var (
|
||||
|
@ -28,35 +40,38 @@ func MigrateHost(h *Host, data []byte) (*Host, bool, error) {
|
|||
var (
|
||||
migrationPerformed = false
|
||||
hostV1 *HostV1
|
||||
hostV2 *HostV2
|
||||
)
|
||||
|
||||
migratedHostMetadata, err := getMigratedHostMetadata(data)
|
||||
if err != nil {
|
||||
return &Host{}, false, err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
globalStorePath := filepath.Dir(filepath.Dir(migratedHostMetadata.HostOptions.AuthOptions.StorePath))
|
||||
|
||||
driver, err := driverfactory.NewDriver(migratedHostMetadata.DriverName, h.Name, globalStorePath)
|
||||
if err != nil {
|
||||
return &Host{}, false, err
|
||||
driver := none.NewDriver(h.Name, globalStorePath)
|
||||
|
||||
if migratedHostMetadata.ConfigVersion > version.ConfigVersion {
|
||||
return nil, false, errors.New("Config version is from the future, please upgrade your Docker Machine client.")
|
||||
}
|
||||
|
||||
if migratedHostMetadata.ConfigVersion == version.ConfigVersion {
|
||||
h.Driver = driver
|
||||
if err := json.Unmarshal(data, &h); err != nil {
|
||||
return &Host{}, migrationPerformed, fmt.Errorf("Error unmarshalling most recent host version: %s", err)
|
||||
return nil, migrationPerformed, fmt.Errorf("Error unmarshalling most recent host version: %s", err)
|
||||
}
|
||||
} else {
|
||||
migrationPerformed = true
|
||||
for h.ConfigVersion = migratedHostMetadata.ConfigVersion; h.ConfigVersion < version.ConfigVersion; h.ConfigVersion++ {
|
||||
log.Debug("Migrating to config v%d", h.ConfigVersion)
|
||||
switch h.ConfigVersion {
|
||||
case 0:
|
||||
hostV0 := &HostV0{
|
||||
Driver: driver,
|
||||
}
|
||||
if err := json.Unmarshal(data, &hostV0); err != nil {
|
||||
return &Host{}, migrationPerformed, fmt.Errorf("Error unmarshalling host config version 0: %s", err)
|
||||
return nil, migrationPerformed, fmt.Errorf("Error unmarshalling host config version 0: %s", err)
|
||||
}
|
||||
hostV1 = MigrateHostV0ToHostV1(hostV0)
|
||||
case 1:
|
||||
|
@ -65,13 +80,27 @@ func MigrateHost(h *Host, data []byte) (*Host, bool, error) {
|
|||
Driver: driver,
|
||||
}
|
||||
if err := json.Unmarshal(data, &hostV1); err != nil {
|
||||
return &Host{}, migrationPerformed, fmt.Errorf("Error unmarshalling host config version 1: %s", err)
|
||||
return nil, migrationPerformed, fmt.Errorf("Error unmarshalling host config version 1: %s", err)
|
||||
}
|
||||
}
|
||||
h = MigrateHostV1ToHostV2(hostV1)
|
||||
hostV2 = MigrateHostV1ToHostV2(hostV1)
|
||||
case 2:
|
||||
if hostV2 == nil {
|
||||
hostV2 = &HostV2{
|
||||
Driver: driver,
|
||||
}
|
||||
if err := json.Unmarshal(data, &hostV2); err != nil {
|
||||
return nil, migrationPerformed, fmt.Errorf("Error unmarshalling host config version 2: %s", err)
|
||||
}
|
||||
}
|
||||
h = MigrateHostV2ToHostV3(hostV2, data, globalStorePath)
|
||||
h.Driver = RawDataDriver{driver, nil}
|
||||
case 3:
|
||||
// Everything for migration to plugin model is
|
||||
// already set up in previous block, so no need
|
||||
// to do anything here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return h, migrationPerformed, nil
|
||||
|
|
|
@ -40,14 +40,14 @@ type HostV1 struct {
|
|||
StorePath string
|
||||
}
|
||||
|
||||
func MigrateHostV1ToHostV2(hostV1 *HostV1) *Host {
|
||||
func MigrateHostV1ToHostV2(hostV1 *HostV1) *HostV2 {
|
||||
// Changed: Put StorePath directly in AuthOptions (for provisioning),
|
||||
// and AuthOptions.PrivateKeyPath => AuthOptions.CaPrivateKeyPath
|
||||
// Also, CertDir has been added.
|
||||
|
||||
globalStorePath := filepath.Dir(filepath.Dir(hostV1.StorePath))
|
||||
|
||||
h := &Host{
|
||||
h := &HostV2{
|
||||
ConfigVersion: hostV1.ConfigVersion,
|
||||
Driver: hostV1.Driver,
|
||||
Name: hostV1.Driver.GetMachineName(),
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package host
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
)
|
||||
|
||||
type RawHost struct {
|
||||
Driver *json.RawMessage
|
||||
}
|
||||
|
||||
func MigrateHostV2ToHostV3(hostV2 *HostV2, data []byte, storePath string) *Host {
|
||||
// Migrate to include RawDriver so that driver plugin will work
|
||||
// smoothly.
|
||||
rawHost := &RawHost{}
|
||||
if err := json.Unmarshal(data, &rawHost); err != nil {
|
||||
log.Warn("Could not unmarshal raw host for RawDriver information: %s", err)
|
||||
}
|
||||
|
||||
m := make(map[string]interface{})
|
||||
|
||||
// Must migrate to include store path in driver since it was not
|
||||
// previously stored in drivers directly
|
||||
if err := json.Unmarshal(*rawHost.Driver, &m); err != nil {
|
||||
log.Warn("Could not unmarshal raw host into map[string]interface{}: %s", err)
|
||||
}
|
||||
|
||||
m["StorePath"] = storePath
|
||||
|
||||
// Now back to []byte
|
||||
rawDriver, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
log.Warn("Could not re-marshal raw driver: %s", err)
|
||||
}
|
||||
|
||||
h := &Host{
|
||||
ConfigVersion: 2,
|
||||
DriverName: hostV2.DriverName,
|
||||
Name: hostV2.Name,
|
||||
HostOptions: hostV2.HostOptions,
|
||||
RawDriver: rawDriver,
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
|
@ -3,6 +3,7 @@ package log
|
|||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Why the interface? We may only want to print to STDOUT and STDERR for now,
|
||||
|
@ -33,7 +34,9 @@ type Logger interface {
|
|||
}
|
||||
|
||||
var (
|
||||
l = StandardLogger{}
|
||||
l = StandardLogger{
|
||||
mu: &sync.Mutex{},
|
||||
}
|
||||
IsDebug bool = false
|
||||
)
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type StandardLogger struct {
|
||||
|
@ -12,27 +13,36 @@ type StandardLogger struct {
|
|||
fieldOut string
|
||||
OutWriter io.Writer
|
||||
ErrWriter io.Writer
|
||||
mu *sync.Mutex
|
||||
}
|
||||
|
||||
func (t StandardLogger) log(args ...interface{}) {
|
||||
defer t.mu.Unlock()
|
||||
t.mu.Lock()
|
||||
fmt.Fprint(t.OutWriter, args...)
|
||||
fmt.Fprint(t.OutWriter, t.fieldOut, "\n")
|
||||
t.fieldOut = ""
|
||||
}
|
||||
|
||||
func (t StandardLogger) logf(fmtString string, args ...interface{}) {
|
||||
defer t.mu.Unlock()
|
||||
t.mu.Lock()
|
||||
fmt.Fprintf(t.OutWriter, fmtString, args...)
|
||||
fmt.Fprint(t.OutWriter, "\n")
|
||||
t.fieldOut = ""
|
||||
}
|
||||
|
||||
func (t StandardLogger) err(args ...interface{}) {
|
||||
defer t.mu.Unlock()
|
||||
t.mu.Lock()
|
||||
fmt.Fprint(t.ErrWriter, args...)
|
||||
fmt.Fprint(t.ErrWriter, t.fieldOut, "\n")
|
||||
t.fieldOut = ""
|
||||
}
|
||||
|
||||
func (t StandardLogger) errf(fmtString string, args ...interface{}) {
|
||||
defer t.mu.Unlock()
|
||||
t.mu.Lock()
|
||||
fmt.Fprintf(t.ErrWriter, fmtString, args...)
|
||||
fmt.Fprint(t.ErrWriter, t.fieldOut, "\n")
|
||||
t.fieldOut = ""
|
||||
|
@ -59,7 +69,6 @@ func (t StandardLogger) Errorf(fmtString string, args ...interface{}) {
|
|||
}
|
||||
|
||||
func (t StandardLogger) Errorln(args ...interface{}) {
|
||||
|
||||
t.err(args...)
|
||||
}
|
||||
|
||||
|
@ -94,10 +103,12 @@ func (t StandardLogger) Printf(fmtString string, args ...interface{}) {
|
|||
}
|
||||
|
||||
func (t StandardLogger) Warn(args ...interface{}) {
|
||||
fmt.Print("WARNING >>> ")
|
||||
t.log(args...)
|
||||
}
|
||||
|
||||
func (t StandardLogger) Warnf(fmtString string, args ...interface{}) {
|
||||
fmt.Print("WARNING >>> ")
|
||||
t.logf(fmtString, args...)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package mcnflag
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Flag interface {
|
||||
fmt.Stringer
|
||||
Default() interface{}
|
||||
}
|
||||
|
||||
type StringFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Value string
|
||||
}
|
||||
|
||||
// TODO: Could this be done more succinctly using embedding?
|
||||
func (f StringFlag) String() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func (f StringFlag) Default() interface{} {
|
||||
return f.Value
|
||||
}
|
||||
|
||||
type StringSliceFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Value []string
|
||||
}
|
||||
|
||||
// TODO: Could this be done more succinctly using embedding?
|
||||
func (f StringSliceFlag) String() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func (f StringSliceFlag) Default() interface{} {
|
||||
return f.Value
|
||||
}
|
||||
|
||||
type IntFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
Value int
|
||||
}
|
||||
|
||||
// TODO: Could this be done more succinctly using embedding?
|
||||
func (f IntFlag) String() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func (f IntFlag) Default() interface{} {
|
||||
return f.Value
|
||||
}
|
||||
|
||||
type BoolFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
EnvVar string
|
||||
}
|
||||
|
||||
// TODO: Could this be done more succinctly using embedding?
|
||||
func (f BoolFlag) String() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func (f BoolFlag) Default() interface{} {
|
||||
return nil
|
||||
}
|
|
@ -124,7 +124,7 @@ func (b *B2dUtils) GetLatestBoot2DockerReleaseURL() (string, error) {
|
|||
func removeFileIfExists(name string) error {
|
||||
if _, err := os.Stat(name); err == nil {
|
||||
if err := os.Remove(name); err != nil {
|
||||
log.Fatalf("Error removing temporary download file: %s", err)
|
||||
return fmt.Errorf("Error removing temporary download file: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -159,7 +159,7 @@ func (b *B2dUtils) DownloadISO(dir, file, isoUrl string) error {
|
|||
|
||||
defer func() {
|
||||
if err := removeFileIfExists(f.Name()); err != nil {
|
||||
log.Fatalf("Error removing file: %s", err)
|
||||
log.Warnf("Error removing file: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ func DumpVal(vals ...interface{}) {
|
|||
for _, val := range vals {
|
||||
prettyJSON, err := json.MarshalIndent(val, "", " ")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Warn(err)
|
||||
}
|
||||
log.Debug(string(prettyJSON))
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/docker/machine/libmachine/auth"
|
||||
"github.com/docker/machine/libmachine/drivers"
|
||||
"github.com/docker/machine/libmachine/drivers/rpc"
|
||||
"github.com/docker/machine/libmachine/engine"
|
||||
"github.com/docker/machine/libmachine/host"
|
||||
"github.com/docker/machine/libmachine/log"
|
||||
|
@ -33,6 +34,15 @@ func (s Filestore) saveToFile(data []byte, file string) error {
|
|||
}
|
||||
|
||||
func (s Filestore) Save(host *host.Host) error {
|
||||
// TODO: Does this belong here?
|
||||
if rpcClientDriver, ok := host.Driver.(*rpcdriver.RpcClientDriver); ok {
|
||||
data, err := rpcClientDriver.GetConfigRaw()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting raw config for driver: %s", err)
|
||||
}
|
||||
host.RawDriver = data
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(host, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -48,18 +58,7 @@ func (s Filestore) Save(host *host.Host) error {
|
|||
return s.saveToFile(data, filepath.Join(hostPath, "config.json"))
|
||||
}
|
||||
|
||||
func (s Filestore) Remove(name string, force bool) error {
|
||||
h, err := s.Load(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := h.Driver.Remove(); err != nil {
|
||||
if !force {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (s Filestore) Remove(name string) error {
|
||||
hostPath := filepath.Join(s.getMachinesDir(), name)
|
||||
return os.RemoveAll(hostPath)
|
||||
}
|
||||
|
@ -82,6 +81,7 @@ func (s Filestore) List() ([]*host.Host, error) {
|
|||
hosts = append(hosts, host)
|
||||
}
|
||||
}
|
||||
|
||||
return hosts, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ func TestStoreRemove(t *testing.T) {
|
|||
t.Fatalf("Host path doesn't exist: %s", path)
|
||||
}
|
||||
|
||||
err = store.Remove(h.Name, false)
|
||||
err = store.Remove(h.Name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ func TestStoreExists(t *testing.T) {
|
|||
t.Fatal("Host should exist after saving")
|
||||
}
|
||||
|
||||
if err := store.Remove(h.Name, true); err != nil {
|
||||
if err := store.Remove(h.Name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue