mirror of https://github.com/xSmurf/oz.git
parent
17436d71e4
commit
ea2034fc45
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"ImportPath": "github.com/subgraph/oz",
|
||||||
|
"GoVersion": "go1.4.2",
|
||||||
|
"Packages": [
|
||||||
|
"./..."
|
||||||
|
],
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/codegangsta/cli",
|
||||||
|
"Comment": "1.2.0-107-g942282e",
|
||||||
|
"Rev": "942282e931e8286aa802a30b01fa7e16befb50f3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/j-keck/arping",
|
||||||
|
"Rev": "7aa611681473b9c5af382b96b6aa1df0b89f5c97"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/kr/pty",
|
||||||
|
"Comment": "release.r56-28-g5cf931e",
|
||||||
|
"Rev": "5cf931ef8f76dccd0910001d74a58a7fca84a83d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/milosgajdos83/libcontainer-milosgajdos83/netlink",
|
||||||
|
"Rev": "10ee77c7b33d22a6b1d7ac98db67d3b43890e73d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/milosgajdos83/libcontainer-milosgajdos83/system",
|
||||||
|
"Rev": "10ee77c7b33d22a6b1d7ac98db67d3b43890e73d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/milosgajdos83/tenus",
|
||||||
|
"Comment": "0.0.1-5-g216f6bb",
|
||||||
|
"Rev": "216f6bbb92148737a8b06b7a990875c85493aa59"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/op/go-logging",
|
||||||
|
"Rev": "e8d5414f0947014548c2334044a0fac13187dfee"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
@ -0,0 +1,2 @@
|
|||||||
|
/pkg
|
||||||
|
/bin
|
@ -0,0 +1,6 @@
|
|||||||
|
language: go
|
||||||
|
go: 1.1
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go vet ./...
|
||||||
|
- go test -v ./...
|
@ -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,306 @@
|
|||||||
|
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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 and modify `autocomplete/bash_autocomplete` to use your program name
|
||||||
|
rather than `$PROG` and have the user copy the file into
|
||||||
|
`/etc/bash_completion.d/` (or automatically install it there if you are
|
||||||
|
distributing a package). Alternatively you can just document that users should
|
||||||
|
source the generic `autocomplete/bash_autocomplete` with `$PROG` set to your
|
||||||
|
program name in their bash configuration.
|
||||||
|
|
||||||
|
## 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,298 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// App is the main structure of a cli application. It is recomended that
|
||||||
|
// and 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
|
||||||
|
// 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, set)
|
||||||
|
ShowAppHelp(context)
|
||||||
|
fmt.Fprintln(a.Writer)
|
||||||
|
return nerr
|
||||||
|
}
|
||||||
|
context := NewContext(a, set, set)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
|
||||||
|
ShowAppHelp(context)
|
||||||
|
fmt.Fprintln(a.Writer)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkCompletions(context) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkHelp(context) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkVersion(context) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.After != nil {
|
||||||
|
defer func() {
|
||||||
|
// err is always nil here.
|
||||||
|
// There is a check to see if it is non-nil
|
||||||
|
// just few lines before.
|
||||||
|
err = a.After(context)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Before != nil {
|
||||||
|
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.globalSet)
|
||||||
|
|
||||||
|
if nerr != nil {
|
||||||
|
fmt.Fprintln(a.Writer, nerr)
|
||||||
|
if len(a.Commands) > 0 {
|
||||||
|
ShowSubcommandHelp(context)
|
||||||
|
} else {
|
||||||
|
ShowCommandHelp(ctx, context.Args().First())
|
||||||
|
}
|
||||||
|
fmt.Fprintln(a.Writer)
|
||||||
|
return nerr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n")
|
||||||
|
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() {
|
||||||
|
// err is always nil here.
|
||||||
|
// There is a check to see if it is non-nil
|
||||||
|
// just few lines before.
|
||||||
|
err = a.After(context)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Before != nil {
|
||||||
|
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,679 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleApp() {
|
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"greet", "--name", "Jeremy"}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "greet"
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
||||||
|
}
|
||||||
|
app.Action = func(c *cli.Context) {
|
||||||
|
fmt.Printf("Hello %v\n", c.String("name"))
|
||||||
|
}
|
||||||
|
app.Author = "Harrison"
|
||||||
|
app.Email = "harrison@lolwut.com"
|
||||||
|
app.Authors = []cli.Author{cli.Author{Name: "Oliver Allen", Email: "oliver@toyshop.com"}}
|
||||||
|
app.Run(os.Args)
|
||||||
|
// Output:
|
||||||
|
// Hello Jeremy
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAppSubcommand() {
|
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"}
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "say"
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "hello",
|
||||||
|
Aliases: []string{"hi"},
|
||||||
|
Usage: "use it to see a description",
|
||||||
|
Description: "This is how we describe hello the function",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "english",
|
||||||
|
Aliases: []string{"en"},
|
||||||
|
Usage: "sends a greeting in english",
|
||||||
|
Description: "greets someone in english",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Value: "Bob",
|
||||||
|
Usage: "Name of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.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 := cli.NewApp()
|
||||||
|
app.Name = "greet"
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.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 *cli.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 := cli.NewApp()
|
||||||
|
app.Name = "greet"
|
||||||
|
app.EnableBashCompletion = true
|
||||||
|
app.Commands = []cli.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 *cli.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 *cli.Context) {
|
||||||
|
fmt.Printf("the next example")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
// Output:
|
||||||
|
// describeit
|
||||||
|
// d
|
||||||
|
// next
|
||||||
|
// help
|
||||||
|
// h
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Run(t *testing.T) {
|
||||||
|
s := ""
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Action = func(c *cli.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 := cli.NewApp()
|
||||||
|
fooCommand := cli.Command{Name: "foobar", Aliases: []string{"f"}}
|
||||||
|
batCommand := cli.Command{Name: "batbaz", Aliases: []string{"b"}}
|
||||||
|
app.Commands = []cli.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 := cli.NewApp()
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "option", Value: "", Usage: "some option"},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
parsedOption = c.String("option")
|
||||||
|
firstArg = c.Args().First()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.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 *cli.Context
|
||||||
|
|
||||||
|
a := cli.NewApp()
|
||||||
|
a.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
context = c
|
||||||
|
},
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "lang",
|
||||||
|
Value: "english",
|
||||||
|
Usage: "language for the greeting",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Before: func(_ *cli.Context) error { return nil },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})
|
||||||
|
|
||||||
|
expect(t, context.Args().Get(0), "abcd")
|
||||||
|
expect(t, context.String("lang"), "spanish")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
|
||||||
|
var parsedOption string
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "option", Value: "", Usage: "some option"},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
parsedOption = c.String("option")
|
||||||
|
args = c.Args()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"})
|
||||||
|
|
||||||
|
expect(t, parsedOption, "my-option")
|
||||||
|
expect(t, args[0], "my-arg")
|
||||||
|
expect(t, args[1], "--")
|
||||||
|
expect(t, args[2], "--notARealFlag")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
args = c.Args()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.Command{command}
|
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
|
||||||
|
|
||||||
|
expect(t, args[0], "my-arg")
|
||||||
|
expect(t, args[1], "--")
|
||||||
|
expect(t, args[2], "notAFlagAtAll")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_Float64Flag(t *testing.T) {
|
||||||
|
var meters float64
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"},
|
||||||
|
}
|
||||||
|
app.Action = func(c *cli.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 := cli.NewApp()
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "cmd",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntSliceFlag{Name: "p", Value: &cli.IntSlice{}, Usage: "set one or more ip addr"},
|
||||||
|
cli.StringSliceFlag{Name: "ip", Value: &cli.StringSlice{}, Usage: "set one or more ports to open"},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
parsedIntSlice = c.IntSlice("p")
|
||||||
|
parsedStringSlice = c.StringSlice("ip")
|
||||||
|
parsedOption = c.String("option")
|
||||||
|
firstArg = c.Args().First()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Commands = []cli.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_DefaultStdout(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
|
||||||
|
if app.Writer != os.Stdout {
|
||||||
|
t.Error("Default output writer not set.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockWriter struct {
|
||||||
|
written []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *mockWriter) Write(p []byte) (n int, err error) {
|
||||||
|
if fw.written == nil {
|
||||||
|
fw.written = p
|
||||||
|
} else {
|
||||||
|
fw.written = append(fw.written, p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fw *mockWriter) GetWritten() (b []byte) {
|
||||||
|
return fw.written
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_SetStdout(t *testing.T) {
|
||||||
|
w := &mockWriter{}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "test"
|
||||||
|
app.Writer = w
|
||||||
|
|
||||||
|
err := app.Run([]string{"help"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(w.written) == 0 {
|
||||||
|
t.Error("App did not write output to desired writer.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApp_BeforeFunc(t *testing.T) {
|
||||||
|
beforeRun, subcommandRun := false, false
|
||||||
|
beforeError := fmt.Errorf("fail")
|
||||||
|
var err error
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
|
||||||
|
app.Before = func(c *cli.Context) error {
|
||||||
|
beforeRun = true
|
||||||
|
s := c.String("opt")
|
||||||
|
if s == "fail" {
|
||||||
|
return beforeError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
cli.Command{
|
||||||
|
Name: "sub",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
subcommandRun = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "opt"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// run with the 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 := cli.NewApp()
|
||||||
|
|
||||||
|
app.After = func(c *cli.Context) error {
|
||||||
|
afterRun = true
|
||||||
|
s := c.String("opt")
|
||||||
|
if s == "fail" {
|
||||||
|
return afterError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
cli.Command{
|
||||||
|
Name: "sub",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
subcommandRun = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "opt"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// run with the After() func succeeding
|
||||||
|
err = app.Run([]string{"command", "--opt", "succeed", "sub"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if afterRun == false {
|
||||||
|
t.Errorf("After() not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subcommandRun == false {
|
||||||
|
t.Errorf("Subcommand not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
afterRun, subcommandRun = false, false
|
||||||
|
|
||||||
|
// run with the Before() func failing
|
||||||
|
err = app.Run([]string{"command", "--opt", "fail", "sub"})
|
||||||
|
|
||||||
|
// should be the same error produced by the Before func
|
||||||
|
if err != afterError {
|
||||||
|
t.Errorf("Run error expected, but not received")
|
||||||
|
}
|
||||||
|
|
||||||
|
if afterRun == false {
|
||||||
|
t.Errorf("After() not executed when expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subcommandRun == false {
|
||||||
|
t.Errorf("Subcommand not executed when expected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppNoHelpFlag(t *testing.T) {
|
||||||
|
oldFlag := cli.HelpFlag
|
||||||
|
defer func() {
|
||||||
|
cli.HelpFlag = oldFlag
|
||||||
|
}()
|
||||||
|
|
||||||
|
cli.HelpFlag = cli.BoolFlag{}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
err := app.Run([]string{"test", "-h"})
|
||||||
|
|
||||||
|
if err != flag.ErrHelp {
|
||||||
|
t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppHelpPrinter(t *testing.T) {
|
||||||
|
oldPrinter := cli.HelpPrinter
|
||||||
|
defer func() {
|
||||||
|
cli.HelpPrinter = oldPrinter
|
||||||
|
}()
|
||||||
|
|
||||||
|
var wasCalled = false
|
||||||
|
cli.HelpPrinter = func(w io.Writer, template string, data interface{}) {
|
||||||
|
wasCalled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
app := cli.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 := cli.VersionPrinter
|
||||||
|
defer func() {
|
||||||
|
cli.VersionPrinter = oldPrinter
|
||||||
|
}()
|
||||||
|
|
||||||
|
var wasCalled = false
|
||||||
|
cli.VersionPrinter = func(c *cli.Context) {
|
||||||
|
wasCalled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
app := cli.NewApp()
|
||||||
|
ctx := cli.NewContext(app, nil, nil)
|
||||||
|
cli.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 := cli.NewApp()
|
||||||
|
|
||||||
|
app.CommandNotFound = func(c *cli.Context, command string) {
|
||||||
|
beforeRun = true
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
cli.Command{
|
||||||
|
Name: "bar",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
subcommandRun = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"command", "foo"})
|
||||||
|
|
||||||
|
expect(t, beforeRun, true)
|
||||||
|
expect(t, subcommandRun, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGlobalFlagsInSubcommands(t *testing.T) {
|
||||||
|
subcommandRun := false
|
||||||
|
app := cli.NewApp()
|
||||||
|
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
cli.Command{
|
||||||
|
Name: "foo",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
if c.GlobalBool("debug") {
|
||||||
|
subcommandRun = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run([]string{"command", "-d", "foo", "bar"})
|
||||||
|
|
||||||
|
expect(t, subcommandRun, 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 := cli.NewApp()
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
app.Writer = buf
|
||||||
|
|
||||||
|
subCmdBar := cli.Command{
|
||||||
|
Name: "bar",
|
||||||
|
Usage: "does bar things",
|
||||||
|
}
|
||||||
|
subCmdBaz := cli.Command{
|
||||||
|
Name: "baz",
|
||||||
|
Usage: "does baz things",
|
||||||
|
}
|
||||||
|
cmd := cli.Command{
|
||||||
|
Name: "foo",
|
||||||
|
Description: "descriptive wall of text about how it does foo things",
|
||||||
|
Subcommands: []cli.Command{subCmdBar, subCmdBaz},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Commands = []cli.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
13
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
@ -0,0 +1,13 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
_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,19 @@
|
|||||||
|
// 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
|
@ -0,0 +1,100 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "todo"
|
||||||
|
app.Usage = "task list on the command line"
|
||||||
|
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())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleSubcommand() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "say"
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "hello",
|
||||||
|
Aliases: []string{"hi"},
|
||||||
|
Usage: "use it to see a description",
|
||||||
|
Description: "This is how we describe hello the function",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Name: "english",
|
||||||
|
Aliases: []string{"en"},
|
||||||
|
Usage: "sends a greeting in english",
|
||||||
|
Description: "greets someone in english",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Value: "Bob",
|
||||||
|
Usage: "Name of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("Hello, ", c.String("name"))
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "spanish",
|
||||||
|
Aliases: []string{"sp"},
|
||||||
|
Usage: "sends a greeting in spanish",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "surname",
|
||||||
|
Value: "Jones",
|
||||||
|
Usage: "Surname of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("Hola, ", c.String("surname"))
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "french",
|
||||||
|
Aliases: []string{"fr"},
|
||||||
|
Usage: "sends a greeting in french",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "nickname",
|
||||||
|
Value: "Stevie",
|
||||||
|
Usage: "Nickname of the person to greet",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("Bonjour, ", c.String("nickname"))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
Name: "bye",
|
||||||
|
Usage: "says goodbye",
|
||||||
|
Action: func(c *cli.Context) {
|
||||||
|
println("bye")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
@ -0,0 +1,184 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprint(ctx.App.Writer, "Incorrect Usage.\n\n")
|
||||||
|
ShowCommandHelp(ctx, c.Name)
|
||||||
|
fmt.Fprintln(ctx.App.Writer)
|
||||||
|
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)
|
||||||
|
fmt.Fprintln(ctx.App.Writer)
|
||||||
|
return nerr
|
||||||
|
}
|
||||||
|
context := NewContext(ctx.App, set, ctx.globalSet)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.RunAsSubcommand(ctx)
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommandDoNotIgnoreFlags(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
test := []string{"blah", "blah", "-break"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, set)
|
||||||
|
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(_ *cli.Context) {},
|
||||||
|
}
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err.Error(), "flag provided but not defined: -break")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandIgnoreFlags(t *testing.T) {
|
||||||
|
app := cli.NewApp()
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
test := []string{"blah", "blah"}
|
||||||
|
set.Parse(test)
|
||||||
|
|
||||||
|
c := cli.NewContext(app, set, set)
|
||||||
|
|
||||||
|
command := cli.Command{
|
||||||
|
Name: "test-cmd",
|
||||||
|
Aliases: []string{"tc"},
|
||||||
|
Usage: "this is for testing",
|
||||||
|
Description: "testing",
|
||||||
|
Action: func(_ *cli.Context) {},
|
||||||
|
SkipFlagParsing: true,
|
||||||
|
}
|
||||||
|
err := command.Run(c)
|
||||||
|
|
||||||
|
expect(t, err, nil)
|
||||||
|
}
|
@ -0,0 +1,344 @@
|
|||||||
|
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
|
||||||
|
globalSet *flag.FlagSet
|
||||||
|
setFlags map[string]bool
|
||||||
|
globalSetFlags map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new context. For use in when invoking an App or Command action.
|
||||||
|
func NewContext(app *App, set *flag.FlagSet, globalSet *flag.FlagSet) *Context {
|
||||||
|
return &Context{App: app, flagSet: set, globalSet: globalSet}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return lookupInt(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return lookupDuration(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a global bool flag, returns false if no bool flag exists
|
||||||
|
func (c *Context) GlobalBool(name string) bool {
|
||||||
|
return lookupBool(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a global string flag, returns "" if no string flag exists
|
||||||
|
func (c *Context) GlobalString(name string) string {
|
||||||
|
return lookupString(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return lookupStringSlice(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return lookupIntSlice(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the value of a global generic flag, returns nil if no generic flag exists
|
||||||
|
func (c *Context) GlobalGeneric(name string) interface{} {
|
||||||
|
return lookupGeneric(name, c.globalSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the number of flags set
|
||||||
|
func (c *Context) NumFlags() int {
|
||||||
|
return c.flagSet.NFlag()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines if the flag was actually set
|
||||||
|
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)
|
||||||
|
c.globalSet.Visit(func(f *flag.Flag) {
|
||||||
|
c.globalSetFlags[f.Name] = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return c.globalSetFlags[name] == true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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,111 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
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")
|
||||||
|
command := cli.Command{Name: "mycommand"}
|
||||||
|
c := cli.NewContext(nil, set, globalSet)
|
||||||
|
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 := cli.NewContext(nil, set, set)
|
||||||
|
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 := cli.NewContext(nil, set, set)
|
||||||
|
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 := cli.NewContext(nil, set, set)
|
||||||
|
expect(t, c.String("myflag"), "hello world")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Bool(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
c := cli.NewContext(nil, set, set)
|
||||||
|
expect(t, c.Bool("myflag"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_BoolT(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", true, "doc")
|
||||||
|
c := cli.NewContext(nil, set, set)
|
||||||
|
expect(t, c.BoolT("myflag"), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContext_Args(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("test", 0)
|
||||||
|
set.Bool("myflag", false, "doc")
|
||||||
|
c := cli.NewContext(nil, set, set)
|
||||||
|
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")
|
||||||
|
c := cli.NewContext(nil, set, globalSet)
|
||||||
|
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")
|
||||||
|
c := cli.NewContext(nil, set, globalSet)
|
||||||
|
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")
|
||||||
|
c := cli.NewContext(nil, set, globalSet)
|
||||||
|
set.Parse([]string{"--myflag", "--otherflag=foo"})
|
||||||
|
globalSet.Parse([]string{"--myflagGlobal"})
|
||||||
|
expect(t, c.NumFlags(), 2)
|
||||||
|
}
|
@ -0,0 +1,454 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringSlice []string
|
||||||
|
|
||||||
|
func (f *StringSlice) Set(value string) error {
|
||||||
|
*f = append(*f, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *StringSlice) String() string {
|
||||||
|
return fmt.Sprintf("%s", *f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *StringSlice) Value() []string {
|
||||||
|
return *f
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringSliceFlag struct {
|
||||||
|
Name string
|
||||||
|
Value *StringSlice
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
set.Var(f.Value, name, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f StringSliceFlag) getName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntSlice []int
|
||||||
|
|
||||||
|
func (f *IntSlice) Set(value string) error {
|
||||||
|
|
||||||
|
tmp, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
*f = append(*f, tmp)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *IntSlice) String() string {
|
||||||
|
return fmt.Sprintf("%d", *f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *IntSlice) Value() []int {
|
||||||
|
return *f
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntSliceFlag struct {
|
||||||
|
Name string
|
||||||
|
Value *IntSlice
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
set.Var(f.Value, name, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f IntSliceFlag) getName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoolFlag struct {
|
||||||
|
Name string
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f BoolFlag) String() string {
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoolTFlag struct {
|
||||||
|
Name string
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f BoolTFlag) String() string {
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringFlag struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntFlag struct {
|
||||||
|
Name string
|
||||||
|
Value int
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f IntFlag) String() string {
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type DurationFlag struct {
|
||||||
|
Name string
|
||||||
|
Value time.Duration
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f DurationFlag) String() string {
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type Float64Flag struct {
|
||||||
|
Name string
|
||||||
|
Value float64
|
||||||
|
Usage string
|
||||||
|
EnvVar string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Float64Flag) String() string {
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||||
|
}
|
||||||
|
|
||||||
|
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,742 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
var boolFlagTests = []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", "--help\t"},
|
||||||
|
{"h", "-h\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoolFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range boolFlagTests {
|
||||||
|
flag := cli.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 := cli.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 := cli.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 *cli.StringSlice
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", func() *cli.StringSlice {
|
||||||
|
s := &cli.StringSlice{}
|
||||||
|
s.Set("")
|
||||||
|
return s
|
||||||
|
}(), "--help [--help option --help option]\t"},
|
||||||
|
{"h", func() *cli.StringSlice {
|
||||||
|
s := &cli.StringSlice{}
|
||||||
|
s.Set("")
|
||||||
|
return s
|
||||||
|
}(), "-h [-h option -h option]\t"},
|
||||||
|
{"h", func() *cli.StringSlice {
|
||||||
|
s := &cli.StringSlice{}
|
||||||
|
s.Set("")
|
||||||
|
return s
|
||||||
|
}(), "-h [-h option -h option]\t"},
|
||||||
|
{"test", func() *cli.StringSlice {
|
||||||
|
s := &cli.StringSlice{}
|
||||||
|
s.Set("Something")
|
||||||
|
return s
|
||||||
|
}(), "--test [--test option --test option]\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSliceFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range stringSliceFlagTests {
|
||||||
|
flag := cli.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 := cli.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 := cli.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 := cli.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 := cli.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 := cli.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 *cli.IntSlice
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"help", &cli.IntSlice{}, "--help [--help option --help option]\t"},
|
||||||
|
{"h", &cli.IntSlice{}, "-h [-h option -h option]\t"},
|
||||||
|
{"h", &cli.IntSlice{}, "-h [-h option -h option]\t"},
|
||||||
|
{"test", func() *cli.IntSlice {
|
||||||
|
i := &cli.IntSlice{}
|
||||||
|
i.Set("9")
|
||||||
|
return i
|
||||||
|
}(), "--test [--test option --test option]\t"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntSliceFlagHelpOutput(t *testing.T) {
|
||||||
|
|
||||||
|
for _, test := range intSliceFlagTests {
|
||||||
|
flag := cli.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 := cli.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 := cli.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 := cli.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 cli.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 := cli.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 := cli.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) {
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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")
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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")
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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) {
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringSliceFlag{Name: "serve, s", Value: &cli.StringSlice{}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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")
|
||||||
|
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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")
|
||||||
|
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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) {
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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")
|
||||||
|
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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")
|
||||||
|
|
||||||
|
(&cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.Float64Flag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolTFlag{Name: "serve, s"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.GenericFlag{Name: "serve, s", Value: &Parser{}},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.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 := cli.App{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
cli.GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) {
|
||||||
|
if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) {
|
||||||
|
t.Errorf("value not set from env")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
a.Run([]string{"run"})
|
||||||
|
}
|
@ -0,0 +1,235 @@
|
|||||||
|
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}}command{{if .Flags}} [command options]{{end}} [arguments...]
|
||||||
|
|
||||||
|
VERSION:
|
||||||
|
{{.Version}}{{if len .Authors}}
|
||||||
|
|
||||||
|
AUTHOR(S):
|
||||||
|
{{range .Authors}}{{ . }}{{end}}{{end}}
|
||||||
|
|
||||||
|
COMMANDS:
|
||||||
|
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
||||||
|
{{end}}{{if .Flags}}
|
||||||
|
GLOBAL OPTIONS:
|
||||||
|
{{range .Flags}}{{.}}
|
||||||
|
{{end}}{{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:
|
||||||
|
{{.Name}} - {{.Usage}}
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
command {{.Name}}{{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") {
|
||||||
|
ShowVersion(c)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkHelp(c *Context) bool {
|
||||||
|
if c.GlobalBool("h") || c.GlobalBool("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,22 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/codegangsta/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_ShowAppHelp_NoAuthor(t *testing.T) {
|
||||||
|
output := new(bytes.Buffer)
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Writer = output
|
||||||
|
|
||||||
|
c := cli.NewContext(app, nil, nil)
|
||||||
|
|
||||||
|
cli.ShowAppHelp(c)
|
||||||
|
|
||||||
|
if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 {
|
||||||
|
t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
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 @@
|
|||||||
|
arping
|
@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 j-keck [jhyphenkeck@gmail.com]
|
||||||
|
|
||||||
|
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,29 @@
|
|||||||
|
# arping
|
||||||
|
|
||||||
|
arping is a native go library to ping a host per arp datagram, or query a host mac address
|
||||||
|
|
||||||
|
The currently supported platforms are: Linux and BSD.
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
### arping library
|
||||||
|
|
||||||
|
* import this library per `import "github.com/j-keck/arping"`
|
||||||
|
* export GOPATH if not already (`export GOPATH=$PWD`)
|
||||||
|
* download the library `go get`
|
||||||
|
* run it `sudo -E go run <YOUR PROGRAMM>`
|
||||||
|
* or build it `go build`
|
||||||
|
|
||||||
|
|
||||||
|
The library requires raw socket access. So it must run as root, or with appropriate capabilities under linux: `sudo setcap cap_net_raw+ep <BIN>`.
|
||||||
|
|
||||||
|
For api doc and examples see: [godoc](http://godoc.org/github.com/j-keck/arping) or check the standalone under 'cmd/arping/main.go'.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### arping executable
|
||||||
|
|
||||||
|
To get a runnable pinger use `go get -u github.com/j-keck/arping/cmd/arping`. This will build the binary in $GOPATH/bin.
|
||||||
|
|
||||||
|
arping requires raw socket access. So it must run as root, or with appropriate capabilities under Linux: `sudo setcap cap_net_raw+ep <ARPING_PATH>`.
|
||||||
|
|
@ -0,0 +1,97 @@
|
|||||||
|
package arping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
requestOper = 1
|
||||||
|
responseOper = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type arpDatagram struct {
|
||||||
|
htype uint16 // Hardware Type
|
||||||
|
ptype uint16 // Protocol Type
|
||||||
|
hlen uint8 // Hardware address Length
|
||||||
|
plen uint8 // Protocol address length
|
||||||
|
oper uint16 // Operation 1->request, 2->response
|
||||||
|
sha []byte // Sender hardware address, length from Hlen
|
||||||
|
spa []byte // Sender protocol address, length from Plen
|
||||||
|
tha []byte // Target hardware address, length from Hlen
|
||||||
|
tpa []byte // Target protocol address, length from Plen
|
||||||
|
}
|
||||||
|
|
||||||
|
func newArpRequest(
|
||||||
|
srcMac net.HardwareAddr,
|
||||||
|
srcIP net.IP,
|
||||||
|
dstMac net.HardwareAddr,
|
||||||
|
dstIP net.IP) arpDatagram {
|
||||||
|
return arpDatagram{
|
||||||
|
htype: uint16(1),
|
||||||
|
ptype: uint16(0x0800),
|
||||||
|
hlen: uint8(6),
|
||||||
|
plen: uint8(4),
|
||||||
|
oper: uint16(requestOper),
|
||||||
|
sha: srcMac,
|
||||||
|
spa: srcIP.To4(),
|
||||||
|
tha: dstMac,
|
||||||
|
tpa: dstIP.To4()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (datagram arpDatagram) Marshal() []byte {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
binary.Write(buf, binary.BigEndian, datagram.htype)
|
||||||
|
binary.Write(buf, binary.BigEndian, datagram.ptype)
|
||||||
|
binary.Write(buf, binary.BigEndian, datagram.hlen)
|
||||||
|
binary.Write(buf, binary.BigEndian, datagram.plen)
|
||||||
|
binary.Write(buf, binary.BigEndian, datagram.oper)
|
||||||
|
buf.Write(datagram.sha)
|
||||||
|
buf.Write(datagram.spa)
|
||||||
|
buf.Write(datagram.tha)
|
||||||
|
buf.Write(datagram.tpa)
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (datagram arpDatagram) MarshalWithEthernetHeader() []byte {
|
||||||
|
// ethernet frame header
|
||||||
|
var ethernetHeader []byte
|
||||||
|
ethernetHeader = append(ethernetHeader, datagram.tha...)
|
||||||
|
ethernetHeader = append(ethernetHeader, datagram.sha...)
|
||||||
|
ethernetHeader = append(ethernetHeader, []byte{0x08, 0x06}...) // arp
|
||||||
|
|
||||||
|
return append(ethernetHeader, datagram.Marshal()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (datagram arpDatagram) SenderIP() net.IP {
|
||||||
|
return net.IP(datagram.spa)
|
||||||
|
}
|
||||||
|
func (datagram arpDatagram) SenderMac() net.HardwareAddr {
|
||||||
|
return net.HardwareAddr(datagram.sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (datagram arpDatagram) IsResponseOf(request arpDatagram) bool {
|
||||||
|
return datagram.oper == responseOper && bytes.Compare(request.spa, datagram.tpa) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseArpDatagram(buffer []byte) arpDatagram {
|
||||||
|
var datagram arpDatagram
|
||||||
|
|
||||||
|
b := bytes.NewBuffer(buffer)
|
||||||
|
binary.Read(b, binary.BigEndian, &datagram.htype)
|
||||||
|
binary.Read(b, binary.BigEndian, &datagram.ptype)
|
||||||
|
binary.Read(b, binary.BigEndian, &datagram.hlen)
|
||||||
|
binary.Read(b, binary.BigEndian, &datagram.plen)
|
||||||
|
binary.Read(b, binary.BigEndian, &datagram.oper)
|
||||||
|
|
||||||
|
haLen := int(datagram.hlen)
|
||||||
|
paLen := int(datagram.plen)
|
||||||
|
datagram.sha = b.Next(haLen)
|
||||||
|
datagram.spa = b.Next(paLen)
|
||||||
|
datagram.tha = b.Next(haLen)
|
||||||
|
datagram.tpa = b.Next(paLen)
|
||||||
|
|
||||||
|
return datagram
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
// Package arping is a native go library to ping a host per arp datagram, or query a host mac address
|
||||||
|
//
|
||||||
|
// The currently supported platforms are: Linux and BSD.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// The library requires raw socket access. So it must run as root, or with appropriate capabilities under linux:
|
||||||
|
// `sudo setcap cap_net_raw+ep <BIN>`.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// ping a host:
|
||||||
|
// ------------
|
||||||
|
// package main
|
||||||
|
// import ("fmt"; "github.com/j-keck/arping"; "net")
|
||||||
|
//
|
||||||
|
// func main(){
|
||||||
|
// dstIP := net.ParseIP("192.168.1.1")
|
||||||
|
// if hwAddr, duration, err := arping.Ping(dstIP); err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// } else {
|
||||||
|
// fmt.Printf("%s (%s) %d usec\n", dstIP, hwAddr, duration/1000)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// resolve mac address:
|
||||||
|
// --------------------
|
||||||
|
// package main
|
||||||
|
// import ("fmt"; "github.com/j-keck/arping"; "net")
|
||||||
|
//
|
||||||
|
// func main(){
|
||||||
|
// dstIP := net.ParseIP("192.168.1.1")
|
||||||
|
// if hwAddr, _, err := arping.Ping(dstIP); err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// } else {
|
||||||
|
// fmt.Printf("%s is at %s\n", dstIP, hwAddr)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// check if host is online:
|
||||||
|
// ------------------------
|
||||||
|
// package main
|
||||||
|
// import ("fmt"; "github.com/j-keck/arping"; "net")
|
||||||
|
//
|
||||||
|
// func main(){
|
||||||
|
// dstIP := net.ParseIP("192.168.1.1")
|
||||||
|
// _, _, err := arping.Ping(dstIP)
|
||||||
|
// if err == arping.ErrTimeout {
|
||||||
|
// fmt.Println("offline")
|
||||||
|
// }else if err != nil {
|
||||||
|
// fmt.Println(err.Error())
|
||||||
|
// }else{
|
||||||
|
// fmt.Println("online")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
package arping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrTimeout error
|
||||||
|
ErrTimeout = errors.New("timeout")
|
||||||
|
|
||||||
|
verboseLog = log.New(ioutil.Discard, "", 0)
|
||||||
|
timeout = time.Duration(500 * time.Millisecond)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ping sends an arp ping to 'dstIP'
|
||||||
|
func Ping(dstIP net.IP) (net.HardwareAddr, time.Duration, error) {
|
||||||
|
iface, err := findUsableInterfaceForNetwork(dstIP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return PingOverIface(dstIP, *iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PingOverIfaceByName sends an arp ping over interface name 'ifaceName' to 'dstIP'
|
||||||
|
func PingOverIfaceByName(dstIP net.IP, ifaceName string) (net.HardwareAddr, time.Duration, error) {
|
||||||
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return PingOverIface(dstIP, *iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PingOverIface sends an arp ping over interface 'iface' to 'dstIP'
|
||||||
|
func PingOverIface(dstIP net.IP, iface net.Interface) (net.HardwareAddr, time.Duration, error) {
|
||||||
|
srcMac := iface.HardwareAddr
|
||||||
|
srcIP, err := findIPInNetworkFromIface(dstIP, iface)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastMac := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||||
|
request := newArpRequest(srcMac, srcIP, broadcastMac, dstIP)
|
||||||
|
|
||||||
|
if err := initialize(iface); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
defer deinitialize()
|
||||||
|
|
||||||
|
type PingResult struct {
|
||||||
|
mac net.HardwareAddr
|
||||||
|
duration time.Duration
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
pingResultChan := make(chan PingResult)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// send arp request
|
||||||
|
verboseLog.Printf("arping '%s' over interface: '%s' with address: '%s'\n", dstIP, iface.Name, srcIP)
|
||||||
|
if sendTime, err := send(request); err != nil {
|
||||||
|
pingResultChan <- PingResult{nil, 0, err}
|
||||||
|
} else {
|
||||||
|
for {
|
||||||
|
// receive arp response
|
||||||
|
response, receiveTime, err := receive()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
pingResultChan <- PingResult{nil, 0, err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.IsResponseOf(request) {
|
||||||
|
duration := receiveTime.Sub(sendTime)
|
||||||
|
verboseLog.Printf("process received arp: srcIP: '%s', srcMac: '%s'\n",
|
||||||
|
response.SenderIP(), response.SenderMac())
|
||||||
|
pingResultChan <- PingResult{response.SenderMac(), duration, err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
verboseLog.Printf("ignore received arp: srcIP: '%s', srcMac: '%s'\n",
|
||||||
|
response.SenderIP(), response.SenderMac())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case pingResult := <-pingResultChan:
|
||||||
|
return pingResult.mac, pingResult.duration, pingResult.err
|
||||||
|
case <-time.After(timeout):
|
||||||
|
return nil, 0, ErrTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableVerboseLog enables verbose logging on stdout
|
||||||
|
func EnableVerboseLog() {
|
||||||
|
verboseLog = log.New(os.Stdout, "", 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTimeout sets ping timeout
|
||||||
|
func SetTimeout(t time.Duration) {
|
||||||
|
timeout = t
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
// +build darwin freebsd openbsd
|
||||||
|
|
||||||
|
package arping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bpf *os.File
|
||||||
|
var bpfFd int
|
||||||
|
var buflen int
|
||||||
|
|
||||||
|
var bpfArpFilter = []syscall.BpfInsn{
|
||||||
|
// make sure this is an arp packet
|
||||||
|
*syscall.BpfStmt(syscall.BPF_LD+syscall.BPF_H+syscall.BPF_ABS, 12),
|
||||||
|
*syscall.BpfJump(syscall.BPF_JMP+syscall.BPF_JEQ+syscall.BPF_K, 0x0806, 0, 1),
|
||||||
|
// if we passed all the tests, ask for the whole packet.
|
||||||
|
*syscall.BpfStmt(syscall.BPF_RET+syscall.BPF_K, -1),
|
||||||
|
// otherwise, drop it.
|
||||||
|
*syscall.BpfStmt(syscall.BPF_RET+syscall.BPF_K, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
func initialize(iface net.Interface) (err error) {
|
||||||
|
verboseLog.Println("search available /dev/bpfX")
|
||||||
|
for i := 0; i <= 10; i++ {
|
||||||
|
bpfPath := fmt.Sprintf("/dev/bpf%d", i)
|
||||||
|
bpf, err = os.OpenFile(bpfPath, os.O_RDWR, 0666)
|
||||||
|
if err != nil {
|
||||||
|
verboseLog.Printf(" open failed: %s - %s\n", bpfPath, err.Error())
|
||||||
|
} else {
|
||||||
|
verboseLog.Printf(" open success: %s\n", bpfPath)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bpfFd = int(bpf.Fd())
|
||||||
|
if bpfFd == -1 {
|
||||||
|
return errors.New("unable to open /dev/bpfX")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.SetBpfInterface(bpfFd, iface.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.SetBpfImmediate(bpfFd, 1); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buflen, err = syscall.BpfBuflen(bpfFd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.SetBpf(bpfFd, bpfArpFilter); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.FlushBpf(bpfFd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func send(request arpDatagram) (time.Time, error) {
|
||||||
|
_, err := syscall.Write(bpfFd, request.MarshalWithEthernetHeader())
|
||||||
|
return time.Now(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func receive() (arpDatagram, time.Time, error) {
|
||||||
|
buffer := make([]byte, buflen)
|
||||||
|
n, err := syscall.Read(bpfFd, buffer)
|
||||||
|
if err != nil {
|
||||||
|
return arpDatagram{}, time.Now(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip 26 bytes bpf header + 14 bytes ethernet header
|
||||||
|
return parseArpDatagram(buffer[40:n]), time.Now(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deinitialize() error {
|
||||||
|
return bpf.Close()
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package arping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sock int
|
||||||
|
var toSockaddr syscall.SockaddrLinklayer
|
||||||
|
|
||||||
|
func initialize(iface net.Interface) error {
|
||||||
|
toSockaddr = syscall.SockaddrLinklayer{Ifindex: iface.Index}
|
||||||
|
|
||||||
|
// 1544 = htons(ETH_P_ARP)
|
||||||
|
const proto = 1544
|
||||||
|
var err error
|
||||||
|
sock, err = syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, proto)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func send(request arpDatagram) (time.Time, error) {
|
||||||
|
return time.Now(), syscall.Sendto(sock, request.MarshalWithEthernetHeader(), 0, &toSockaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func receive() (arpDatagram, time.Time, error) {
|
||||||
|
buffer := make([]byte, 128)
|
||||||
|
n, _, err := syscall.Recvfrom(sock, buffer, 0)
|
||||||
|
if err != nil {
|
||||||
|
return arpDatagram{}, time.Now(), err
|
||||||
|
}
|
||||||
|
// skip 14 bytes ethernet header
|
||||||
|
return parseArpDatagram(buffer[14:n]), time.Now(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deinitialize() error {
|
||||||
|
return syscall.Close(sock)
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
// windows currently not supported.
|
||||||
|
// dummy implementation to prevent compilation errors under windows
|
||||||
|
|
||||||
|
package arping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errWindowsNotSupported = errors.New("arping under windows not supported")
|
||||||
|
|
||||||
|
func initialize(iface net.Interface) error {
|
||||||
|
return errWindowsNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func send(request arpDatagram) (time.Time, error) {
|
||||||
|
return time.Now(), errWindowsNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func receive() (arpDatagram, time.Time, error) {
|
||||||
|
return arpDatagram{}, time.Now(), errWindowsNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func deinitialize() error {
|
||||||
|
return errWindowsNotSupported
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package arping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findIPInNetworkFromIface(dstIP net.IP, iface net.Interface) (net.IP, error) {
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range addrs {
|
||||||
|
if ipnet, ok := a.(*net.IPNet); ok {
|
||||||
|
if ipnet.Contains(dstIP) {
|
||||||
|
return ipnet.IP, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("iface: '%s' can't reach ip: '%s'", iface.Name, dstIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func findUsableInterfaceForNetwork(dstIP net.IP) (*net.Interface, error) {
|
||||||
|
ifaces, err := net.Interfaces()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isDown := func(iface net.Interface) bool {
|
||||||
|
return iface.Flags&1 == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAddressInNetwork := func(iface net.Interface) bool {
|
||||||
|
if _, err := findIPInNetworkFromIface(dstIP, iface); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
verboseLog.Println("search usable interface")
|
||||||
|
logIfaceResult := func(msg string, iface net.Interface) {
|
||||||
|
verboseLog.Printf("%10s: %6s %18s %s", msg, iface.Name, iface.HardwareAddr, iface.Flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
if isDown(iface) {
|
||||||
|
logIfaceResult("DOWN", iface)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasAddressInNetwork(iface) {
|
||||||
|
logIfaceResult("OTHER NET", iface)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logIfaceResult("USABLE", iface)
|
||||||
|
return &iface, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("no usable interface found")
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
[568].out
|
||||||
|
_go*
|
||||||
|
_test*
|
||||||
|
_obj
|
@ -0,0 +1,23 @@
|
|||||||
|
Copyright (c) 2011 Keith Rarick
|
||||||
|
|
||||||
|
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,36 @@
|
|||||||
|
# pty
|
||||||
|
|
||||||
|
Pty is a Go package for using unix pseudo-terminals.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
go get github.com/kr/pty
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kr/pty"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := exec.Command("grep", "--color=auto", "bar")
|
||||||
|
f, err := pty.Start(c)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
f.Write([]byte("foo\n"))
|
||||||
|
f.Write([]byte("bar\n"))
|
||||||
|
f.Write([]byte("baz\n"))
|
||||||
|
f.Write([]byte{4}) // EOT
|
||||||
|
}()
|
||||||
|
io.Copy(os.Stdout, f)
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,16 @@
|
|||||||
|
// Package pty provides functions for working with Unix terminals.
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrUnsupported is returned if a function is not
|
||||||
|
// available on the current platform.
|
||||||
|
var ErrUnsupported = errors.New("unsupported")
|
||||||
|
|
||||||
|
// Opens a pty and its corresponding tty.
|
||||||
|
func Open() (pty, tty *os.File, err error) {
|
||||||
|
return open()
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func ioctl(fd, cmd, ptr uintptr) error {
|
||||||
|
_, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr)
|
||||||
|
if e != 0 {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
// from <sys/ioccom.h>
|
||||||
|
const (
|
||||||
|
_IOC_VOID uintptr = 0x20000000
|
||||||
|
_IOC_OUT uintptr = 0x40000000
|
||||||
|
_IOC_IN uintptr = 0x80000000
|
||||||
|
_IOC_IN_OUT uintptr = _IOC_OUT | _IOC_IN
|
||||||
|
_IOC_DIRMASK = _IOC_VOID | _IOC_OUT | _IOC_IN
|
||||||
|
|
||||||
|
_IOC_PARAM_SHIFT = 13
|
||||||
|
_IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func _IOC_PARM_LEN(ioctl uintptr) uintptr {
|
||||||
|
return (ioctl >> 16) & _IOC_PARAM_MASK
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOC(inout uintptr, group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return inout | (param_len&_IOC_PARAM_MASK)<<16 | uintptr(group)<<8 | ioctl_num
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IO(group byte, ioctl_num uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_VOID, group, ioctl_num, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_OUT, group, ioctl_num, param_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOW(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_IN, group, ioctl_num, param_len)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _IOWR(group byte, ioctl_num uintptr, param_len uintptr) uintptr {
|
||||||
|
return _IOC(_IOC_IN_OUT, group, ioctl_num, param_len)
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
GOOSARCH="${GOOS}_${GOARCH}"
|
||||||
|
case "$GOOSARCH" in
|
||||||
|
_* | *_ | _)
|
||||||
|
echo 'undefined $GOOS_$GOARCH:' "$GOOSARCH" 1>&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
GODEFS="go tool cgo -godefs"
|
||||||
|
|
||||||
|
$GODEFS types.go |gofmt > ztypes_$GOARCH.go
|
||||||
|
|
||||||
|
case $GOOS in
|
||||||
|
freebsd)
|
||||||
|
$GODEFS types_$GOOS.go |gofmt > ztypes_$GOOSARCH.go
|
||||||
|
;;
|
||||||
|
esac
|
@ -0,0 +1,60 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sname, err := ptsname(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = grantpt(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = unlockpt(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := os.OpenFile(sname, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return p, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))
|
||||||
|
|
||||||
|
err := ioctl(f.Fd(), syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range n {
|
||||||
|
if c == 0 {
|
||||||
|
return string(n[:i]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
|
||||||
|
}
|
||||||
|
|
||||||
|
func grantpt(f *os.File) error {
|
||||||
|
return ioctl(f.Fd(), syscall.TIOCPTYGRANT, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
return ioctl(f.Fd(), syscall.TIOCPTYUNLK, 0)
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func posix_openpt(oflag int) (fd int, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
|
||||||
|
fd = int(r0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
fd, err := posix_openpt(syscall.O_RDWR | syscall.O_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := os.NewFile(uintptr(fd), "/dev/pts")
|
||||||
|
sname, err := ptsname(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := os.OpenFile("/dev/"+sname, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return p, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isptmaster(fd uintptr) (bool, error) {
|
||||||
|
err := ioctl(fd, syscall.TIOCPTMASTER, 0)
|
||||||
|
return err == nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
emptyFiodgnameArg fiodgnameArg
|
||||||
|
ioctl_FIODGNAME = _IOW('f', 120, unsafe.Sizeof(emptyFiodgnameArg))
|
||||||
|
)
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
master, err := isptmaster(f.Fd())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !master {
|
||||||
|
return "", syscall.EINVAL
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = _C_SPECNAMELEN + 1
|
||||||
|
var (
|
||||||
|
buf = make([]byte, n)
|
||||||
|
arg = fiodgnameArg{Len: n, Buf: (*byte)(unsafe.Pointer(&buf[0]))}
|
||||||
|
)
|
||||||
|
err = ioctl(f.Fd(), ioctl_FIODGNAME, uintptr(unsafe.Pointer(&arg)))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range buf {
|
||||||
|
if c == 0 {
|
||||||
|
return string(buf[:i]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", errors.New("FIODGNAME string not NUL-terminated")
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
p, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sname, err := ptsname(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = unlockpt(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := os.OpenFile(sname, os.O_RDWR|syscall.O_NOCTTY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return p, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
var n _C_uint
|
||||||
|
err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "/dev/pts/" + strconv.Itoa(int(n)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
var u _C_int
|
||||||
|
// use TIOCSPTLCK with a zero valued arg to clear the slave pty lock
|
||||||
|
return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
// +build !linux,!darwin,!freebsd
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func open() (pty, tty *os.File, err error) {
|
||||||
|
return nil, nil, ErrUnsupported
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start assigns a pseudo-terminal tty os.File to c.Stdin, c.Stdout,
|
||||||
|
// and c.Stderr, calls c.Start, and returns the File of the tty's
|
||||||
|
// corresponding pty.
|
||||||
|
func Start(c *exec.Cmd) (pty *os.File, err error) {
|
||||||
|
pty, tty, err := Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tty.Close()
|
||||||
|
c.Stdout = tty
|
||||||
|
c.Stdin = tty
|
||||||
|
c.Stderr = tty
|
||||||
|
c.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
|
||||||
|
err = c.Start()
|
||||||
|
if err != nil {
|
||||||
|
pty.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pty, err
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int C.int
|
||||||
|
_C_uint C.uint
|
||||||
|
)
|
@ -0,0 +1,15 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/filio.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = C.SPECNAMELEN /* max length of devicename */
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg C.struct_fiodgname_arg
|
@ -0,0 +1,35 @@
|
|||||||
|
package pty
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Getsize returns the number of rows (lines) and cols (positions
|
||||||
|
// in each line) in terminal t.
|
||||||
|
func Getsize(t *os.File) (rows, cols int, err error) {
|
||||||
|
var ws winsize
|
||||||
|
err = windowrect(&ws, t.Fd())
|
||||||
|
return int(ws.ws_row), int(ws.ws_col), err
|
||||||
|
}
|
||||||
|
|
||||||
|
type winsize struct {
|
||||||
|
ws_row uint16
|
||||||
|
ws_col uint16
|
||||||
|
ws_xpixel uint16
|
||||||
|
ws_ypixel uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowrect(ws *winsize, fd uintptr) error {
|
||||||
|
_, _, errno := syscall.Syscall(
|
||||||
|
syscall.SYS_IOCTL,
|
||||||
|
fd,
|
||||||
|
syscall.TIOCGWINSZ,
|
||||||
|
uintptr(unsafe.Pointer(ws)),
|
||||||
|
)
|
||||||
|
if errno != 0 {
|
||||||
|
return syscall.Errno(errno)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
@ -0,0 +1,9 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
@ -0,0 +1,11 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
// +build arm64
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
@ -0,0 +1,13 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_freebsd.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = 0x3f
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg struct {
|
||||||
|
Len int32
|
||||||
|
Buf *byte
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_freebsd.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = 0x3f
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg struct {
|
||||||
|
Len int32
|
||||||
|
Pad_cgo_0 [4]byte
|
||||||
|
Buf *byte
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types_freebsd.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
const (
|
||||||
|
_C_SPECNAMELEN = 0x3f
|
||||||
|
)
|
||||||
|
|
||||||
|
type fiodgnameArg struct {
|
||||||
|
Len int32
|
||||||
|
Buf *byte
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
// +build ppc64
|
||||||
|
|
||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
@ -0,0 +1,11 @@
|
|||||||
|
// +build ppc64le
|
||||||
|
|
||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
@ -0,0 +1,11 @@
|
|||||||
|
// +build s390x
|
||||||
|
|
||||||
|
// Created by cgo -godefs - DO NOT EDIT
|
||||||
|
// cgo -godefs types.go
|
||||||
|
|
||||||
|
package pty
|
||||||
|
|
||||||
|
type (
|
||||||
|
_C_int int32
|
||||||
|
_C_uint uint32
|
||||||
|
)
|
@ -0,0 +1,2 @@
|
|||||||
|
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||||
|
Guillaume J. Charmes <guillaume@docker.com> (@creack)
|
@ -0,0 +1,23 @@
|
|||||||
|
// Packet netlink provide access to low level Netlink sockets and messages.
|
||||||
|
//
|
||||||
|
// Actual implementations are in:
|
||||||
|
// netlink_linux.go
|
||||||
|
// netlink_darwin.go
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrWrongSockType = errors.New("Wrong socket type")
|
||||||
|
ErrShortResponse = errors.New("Got short response from netlink")
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Route is a subnet associated with the interface to reach it.
|
||||||
|
type Route struct {
|
||||||
|
*net.IPNet
|
||||||
|
Iface *net.Interface
|
||||||
|
Default bool
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,9 @@
|
|||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
func randIfrDataByte() uint8 {
|
||||||
|
return uint8(rand.Intn(255))
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
// +build !arm
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
func randIfrDataByte() int8 {
|
||||||
|
return int8(rand.Intn(255))
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateBridgeWithMac(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := "testbridge"
|
||||||
|
|
||||||
|
if err := CreateBridge(name, true); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(name); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup and tests
|
||||||
|
|
||||||
|
if err := DeleteBridge(name); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(name); err == nil {
|
||||||
|
t.Fatal("expected error getting interface because bridge was deleted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateVethPair(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
name1 = "veth1"
|
||||||
|
name2 = "veth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := NetworkCreateVethPair(name1, name2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(name1); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(name2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotImplemented = errors.New("not implemented")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NetworkGetRoutes() ([]Route, error) {
|
||||||
|
return nil, ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkAdd(name string, linkType string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkDel(name string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkUp(iface *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddRoute(destination, source, gateway, device string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddDefaultGw(ip, device string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetMTU(iface *net.Interface, mtu int) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetMacAddress(iface *net.Interface, macaddr string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkCreateVethPair(name1, name2 string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkChangeName(iface *net.Interface, newName string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetNsFd(iface *net.Interface, fd int) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetMaster(iface, master *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetNoMaster(iface *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkDown(iface *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkAddMacVlan(masterDev, macVlanDev string, mode string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkAddVlan(masterDev, vlanDev string, vlanId uint16) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Execv(cmd string, args []string, env []string) error {
|
||||||
|
name, err := exec.LookPath(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return syscall.Exec(name, args, env)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParentDeathSignal(sig uintptr) error {
|
||||||
|
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, sig, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetParentDeathSignal() (int, error) {
|
||||||
|
var sig int
|
||||||
|
|
||||||
|
_, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_GET_PDEATHSIG, uintptr(unsafe.Pointer(&sig)), 0)
|
||||||
|
|
||||||
|
if err != 0 {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetKeepCaps() error {
|
||||||
|
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 1, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearKeepCaps() error {
|
||||||
|
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 0, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setctty() error {
|
||||||
|
if _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, 0, uintptr(syscall.TIOCSCTTY), 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
27
Godeps/_workspace/src/github.com/milosgajdos83/libcontainer-milosgajdos83/system/proc.go
generated
vendored
27
Godeps/_workspace/src/github.com/milosgajdos83/libcontainer-milosgajdos83/system/proc.go
generated
vendored
@ -0,0 +1,27 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// look in /proc to find the process start time so that we can verify
|
||||||
|
// that this pid has started after ourself
|
||||||
|
func GetProcessStartTime(pid int) (string, error) {
|
||||||
|
data, err := ioutil.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(string(data), " ")
|
||||||
|
// the starttime is located at pos 22
|
||||||
|
// from the man page
|
||||||
|
//
|
||||||
|
// starttime %llu (was %lu before Linux 2.6)
|
||||||
|
// (22) The time the process started after system boot. In kernels before Linux 2.6, this
|
||||||
|
// value was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks
|
||||||
|
// (divide by sysconf(_SC_CLK_TCK)).
|
||||||
|
return parts[22-1], nil // starts at 1
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Via http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=7b21fddd087678a70ad64afc0f632e0f1071b092
|
||||||
|
//
|
||||||
|
// We need different setns values for the different platforms and arch
|
||||||
|
// We are declaring the macro here because the SETNS syscall does not exist in th stdlib
|
||||||
|
var setNsMap = map[string]uintptr{
|
||||||
|
"linux/386": 346,
|
||||||
|
"linux/amd64": 308,
|
||||||
|
"linux/arm": 374,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setns(fd uintptr, flags uintptr) error {
|
||||||
|
ns, exists := setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err := syscall.RawSyscall(ns, fd, flags, 0)
|
||||||
|
if err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
// +build cgo
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <unistd.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
func GetClockTicks() int {
|
||||||
|
return int(C.sysconf(C._SC_CLK_TCK))
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
// +build !cgo
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
func GetClockTicks() int {
|
||||||
|
// TODO figure out a better alternative for platforms where we're missing cgo
|
||||||
|
return 100
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Returns a nil slice and nil error if the xattr is not set
|
||||||
|
func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||||
|
pathBytes, err := syscall.BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
attrBytes, err := syscall.BytePtrFromString(attr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := make([]byte, 128)
|
||||||
|
destBytes := unsafe.Pointer(&dest[0])
|
||||||
|
sz, _, errno := syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0)
|
||||||
|
if errno == syscall.ENODATA {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if errno == syscall.ERANGE {
|
||||||
|
dest = make([]byte, sz)
|
||||||
|
destBytes := unsafe.Pointer(&dest[0])
|
||||||
|
sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0)
|
||||||
|
}
|
||||||
|
if errno != 0 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return dest[:sz], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _zero uintptr
|
||||||
|
|
||||||
|
func Lsetxattr(path string, attr string, data []byte, flags int) error {
|
||||||
|
pathBytes, err := syscall.BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
attrBytes, err := syscall.BytePtrFromString(attr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var dataBytes unsafe.Pointer
|
||||||
|
if len(data) > 0 {
|
||||||
|
dataBytes = unsafe.Pointer(&data[0])
|
||||||
|
} else {
|
||||||
|
dataBytes = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
_, _, errno := syscall.Syscall6(syscall.SYS_LSETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(dataBytes), uintptr(len(data)), uintptr(flags), 0)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,201 @@
|
|||||||
|
# Linux networking in Golang
|
||||||
|
|
||||||
|
**tenus** is a [Golang](http://golang.org/) package which allows you to configure and manage Linux network devices programmatically. It communicates with Linux Kernel via [netlink](http://man7.org/linux/man-pages/man7/netlink.7.html) to facilitate creation and configuration of network devices on the Linux host. The package also allows for more advanced network setups with Linux containers including [Docker](https://github.com/dotcloud/docker/).
|
||||||
|
|
||||||
|
**tenus** uses an enhanced version of [libcontainer](https://github.com/docker/libcontainer)'s **netlink** package and adds more functionality than the original [netlink](https://github.com/docker/libcontainer/tree/master/netlink) package offers. Check out the forked implementation [here](https://github.com/milosgajdos83/libcontainer/tree/master/netlink).
|
||||||
|
|
||||||
|
The package only works with newer Linux Kernels (3.10+) which are offering reasonably new version of netlink library, so **if you are running older kernel this package won't be of much use to you** I'm afraid. I have developed this package on Ubuntu [Trusty Tahr](http://releases.ubuntu.com/14.04/) which ships with 3.13+ and verified its functionality on [Precise Pangolin](http://releases.ubuntu.com/12.04/) with upgraded kernel to version 3.10.
|
||||||
|
|
||||||
|
At the moment only functional tests are available, but the interface design should hopefully allow for easy (ish) unit testing in the future. I do appreciate that the package's **test coverage is not great at the moment**, but the core functionality should be covered. I would massively welcome PRs.
|
||||||
|
|
||||||
|
## Get started
|
||||||
|
|
||||||
|
There is a ```Vagrantfile``` available in the repo so using [vagrant](https://github.com/mitchellh/vagrant) is the easiest way to get started:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
milosgajdos@bimbonet ~ $ git clone https://github.com/milosgajdos83/tenus.git
|
||||||
|
milosgajdos@bimbonet ~ $ vagrant up
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note** using the provided ```Vagrantfile``` will take quite a long time to spin the VM as vagrant will setup Ubuntu Trusty VM with all the prerequisities:
|
||||||
|
|
||||||
|
* it will install golang and docker onto the VM
|
||||||
|
* it will export ```GOPATH``` and ```go get``` the **tenus** package onto the VM
|
||||||
|
* it will also "**pull**" Docker ubuntu image so that you can run the tests once the VM is set up
|
||||||
|
|
||||||
|
At the moment running the tests require Docker to be installed, but in the future I'd love to separate tests per interface so that you can run only chosen test sets.
|
||||||
|
|
||||||
|
Once the VM is running, ```cd``` into particular repo directory and you can run the tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
milosgajdos@bimbonet ~ $ cd $GOPATH/src/github.com/milosgajdos83/tenus
|
||||||
|
milosgajdos@bimbonet ~ $ sudo go test
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't want to use the provided ```Vagrantfile```, you can simply run your own Linux VM (with 3.10+ kernel) and follow the regular golang development flow:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
milosgajdos@bimbonet ~ $ go get github.com/milosgajdos83/tenus
|
||||||
|
milosgajdos@bimbonet ~ $ cd $GOPATH/src/github.com/milosgajdos83/tenus
|
||||||
|
milosgajdos@bimbonet ~ $ sudo go test
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you've got the package and ran the tests (you don't need to run the tests!), you can start hacking. Below you can find simple code samples to get started with the package.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Below you can find a few code snippets which can help you get started writing your own programs.
|
||||||
|
|
||||||
|
### New network bridge, add dummy link into it
|
||||||
|
|
||||||
|
The example below shows a simple program example which creates a new network bridge, a new dummy network link and adds it into the bridge.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/tenus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create a new network bridge
|
||||||
|
br, err := tenus.NewBridgeWithName("mybridge")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring the bridge up
|
||||||
|
if err = br.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a dummy link
|
||||||
|
dl, err := tenus.NewLink("mydummylink")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the dummy link into bridge
|
||||||
|
if err = br.AddSlaveIfc(dl.NetInterface()); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring the dummy link up
|
||||||
|
if err = dl.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### New network bridge, veth pair, one peer in Docker
|
||||||
|
|
||||||
|
The example below shows how you can create a new network bride, configure its IP address, add a new veth pair and send one of the veth peers into Docker with a given name.
|
||||||
|
|
||||||
|
**!! You must make sure that particular Docker is runnig if you want the code sample below to work properly !!** So before you compile and run the program below you should create a particular docker with the below used name:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
milosgajdos@bimbonet ~ $ docker run -i -t --rm --privileged -h vethdckr --name vethdckr ubuntu:14.04 /bin/bash
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/tenus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// CREATE BRIDGE AND BRING IT UP
|
||||||
|
br, err := tenus.NewBridgeWithName("vethbridge")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
brIp, brIpNet, err := net.ParseCIDR("10.0.41.1/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := br.SetLinkIp(brIp, brIpNet); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = br.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE VETH PAIR
|
||||||
|
veth, err := tenus.NewVethPairWithOptions("myveth01", tenus.VethOptions{PeerName: "myveth02"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASSIGN IP ADDRESS TO THE HOST VETH INTERFACE
|
||||||
|
vethHostIp, vethHostIpNet, err := net.ParseCIDR("10.0.41.2/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := veth.SetLinkIp(vethHostIp, vethHostIpNet); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD MYVETH01 INTERFACE TO THE MYBRIDGE BRIDGE
|
||||||
|
myveth01, err := net.InterfaceByName("myveth01")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = br.AddSlaveIfc(myveth01); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = veth.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PASS VETH PEER INTERFACE TO A RUNNING DOCKER BY PID
|
||||||
|
pid, err := tenus.DockerPidByName("vethdckr", "/var/run/docker.sock")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := veth.SetPeerLinkNsPid(pid); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALLOCATE AND SET IP FOR THE NEW DOCKER INTERFACE
|
||||||
|
vethGuestIp, vethGuestIpNet, err := net.ParseCIDR("10.0.41.5/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := veth.SetPeerLinkNetInNs(pid, vethGuestIp, vethGuestIpNet, nil); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### VLAN and MAC VLAN interfaces
|
||||||
|
|
||||||
|
You can check out [VLAN](https://gist.github.com/milosgajdos83/9f68b1818dca886e9ae8) and [Mac VLAN](https://gist.github.com/milosgajdos83/296fb90d076f259a5b0a) examples, too.
|
||||||
|
|
||||||
|
### More examples
|
||||||
|
|
||||||
|
Repo contains few more code sample in ```examples``` folder so make sure to check them out if you're interested.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
This is just a rough beginning of the project which I put together over couple of weeks in my free time. I'd like to integrate this into my own Docker fork and test the advanced netowrking functionality with the core of Docker as oppose to configuring network interfaces from a separate golang program, because advanced networking in Docker was the main motivation for writing this package.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
More in depth package documentation is available via [godoc](http://godoc.org/github.com/milosgajdos83/tenus)
|
@ -0,0 +1,31 @@
|
|||||||
|
# -*- mode: ruby -*-
|
||||||
|
# vi: set ft=ruby :
|
||||||
|
|
||||||
|
$provision = <<SCRIPT
|
||||||
|
apt-get update -qq && apt-get install -y vim curl python-software-properties golang
|
||||||
|
add-apt-repository -y "deb https://get.docker.io/ubuntu docker main"
|
||||||
|
curl -s https://get.docker.io/gpg | sudo apt-key add -
|
||||||
|
apt-get update -qq; apt-get install -y lxc-docker
|
||||||
|
docker pull ubuntu
|
||||||
|
cat > /etc/profile.d/envvar.sh <<'EOF'
|
||||||
|
export GOPATH=/opt/golang
|
||||||
|
export PATH=$PATH:$GOPATH/bin
|
||||||
|
EOF
|
||||||
|
. /etc/profile.d/envvar.sh
|
||||||
|
go get "github.com/milosgajdos83/tenus"
|
||||||
|
SCRIPT
|
||||||
|
|
||||||
|
VAGRANTFILE_API_VERSION = "2"
|
||||||
|
|
||||||
|
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
|
config.vm.box = "trusty64"
|
||||||
|
config.vm.hostname = "tenus"
|
||||||
|
config.vm.network :private_network, ip: "10.0.2.88"
|
||||||
|
config.vm.network :private_network, ip: "10.0.2.89"
|
||||||
|
|
||||||
|
config.vm.provider "virtualbox" do |v|
|
||||||
|
v.customize ['modifyvm', :id, '--nicpromisc1', 'allow-all']
|
||||||
|
end
|
||||||
|
|
||||||
|
config.vm.provision "shell", inline: $provision
|
||||||
|
end
|
@ -0,0 +1,152 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/libcontainer-milosgajdos83/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bridger embeds Linker interface and adds one extra function.
|
||||||
|
type Bridger interface {
|
||||||
|
// Linker interface
|
||||||
|
Linker
|
||||||
|
// AddSlaveIfc adds network interface to the network bridge
|
||||||
|
AddSlaveIfc(*net.Interface) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bridge is Link which has zero or more slave network interfaces.
|
||||||
|
// Bridge implements Bridger interface.
|
||||||
|
type Bridge struct {
|
||||||
|
Link
|
||||||
|
slaveIfcs []net.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBridge creates new network bridge on Linux host.
|
||||||
|
//
|
||||||
|
// It is equivalent of running: ip link add name br${RANDOM STRING} type bridge
|
||||||
|
// NewBridge returns Bridger which is initialized to a pointer of type Bridge if the
|
||||||
|
// bridge was created successfully on the Linux host. Newly created bridge is assigned
|
||||||
|
// a random name starting with "br".
|
||||||
|
// It returns error if the bridge could not be created.
|
||||||
|
func NewBridge() (Bridger, error) {
|
||||||
|
brDev := makeNetInterfaceName("br")
|
||||||
|
|
||||||
|
if ok, err := NetInterfaceNameValid(brDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(brDev); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", brDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAdd(brDev, "bridge"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(brDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Bridge{
|
||||||
|
Link: Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBridge creates new network bridge on Linux host with the name passed as a parameter.
|
||||||
|
// It is equivalent of running: ip link add name ${ifcName} type bridge
|
||||||
|
// It returns error if the bridge can not be created.
|
||||||
|
func NewBridgeWithName(ifcName string) (Bridger, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(ifcName); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", ifcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAdd(ifcName, "bridge"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Bridge{
|
||||||
|
Link: Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBridgeFrom returns a network bridge on Linux host from the name passed as a parameter.
|
||||||
|
// It is equivalent of running: ip link add name ${ifcName} type bridge
|
||||||
|
// It returns error if the bridge can not be created.
|
||||||
|
func BridgeFromName(ifcName string) (Bridger, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Bridge{
|
||||||
|
Link: Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToBridge adds network interfaces to network bridge.
|
||||||
|
// It is equivalent of running: ip link set ${netIfc name} master ${netBridge name}
|
||||||
|
// It returns error when it fails to add the network interface to bridge.
|
||||||
|
func AddToBridge(netIfc, netBridge *net.Interface) error {
|
||||||
|
return netlink.NetworkSetMaster(netIfc, netBridge)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToBridge adds network interfaces to network bridge.
|
||||||
|
// It is equivalent of running: ip link set dev ${netIfc name} nomaster
|
||||||
|
// It returns error when it fails to remove the network interface from the bridge.
|
||||||
|
func RemoveFromBridge(netIfc *net.Interface) error {
|
||||||
|
return netlink.NetworkSetNoMaster(netIfc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSlaveIfc adds network interface to network bridge.
|
||||||
|
// It is equivalent of running: ip link set ${ifc name} master ${bridge name}
|
||||||
|
// It returns error if the network interface could not be added to the bridge.
|
||||||
|
func (br *Bridge) AddSlaveIfc(ifc *net.Interface) error {
|
||||||
|
if err := netlink.NetworkSetMaster(ifc, br.ifc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
br.slaveIfcs = append(br.slaveIfcs, *ifc)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveSlaveIfc removes network interface from the network bridge.
|
||||||
|
// It is equivalent of running: ip link set dev ${netIfc name} nomaster
|
||||||
|
// It returns error if the network interface is not in the bridge or
|
||||||
|
// it could not be removed from the bridge.
|
||||||
|
func (br *Bridge) RemoveSlaveIfc(ifc *net.Interface) error {
|
||||||
|
if err := netlink.NetworkSetNoMaster(ifc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, i := range br.slaveIfcs {
|
||||||
|
// I could reflect.DeepEqual(), but there is not point to import reflect for one operation
|
||||||
|
if i.Name == ifc.Name && bytes.Equal(i.HardwareAddr, ifc.HardwareAddr) {
|
||||||
|
br.slaveIfcs = append(br.slaveIfcs[:index], br.slaveIfcs[index+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_NewBridge(t *testing.T) {
|
||||||
|
tl := &testLink{}
|
||||||
|
|
||||||
|
br, err := NewBridge()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewBridge() failed to run: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
brName := br.NetInterface().Name
|
||||||
|
if err := tl.prepTestLink(brName, "bridge"); err != nil {
|
||||||
|
t.Skipf("NewBridge test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(brName); err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Could not find %s on the host: %s", brName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testRes, err := linkInfo(brName, "bridge")
|
||||||
|
if err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Failed to list %s operation mode: %s", brName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testRes.linkType != "bridge" {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewBridge() failed: expected linktype bridge, returned %s", testRes.linkType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.teardown(); err != nil {
|
||||||
|
t.Fatalf("testLink.teardown failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NewBridgeWithName(t *testing.T) {
|
||||||
|
brTests := []string{"br01", "br02", "br03"}
|
||||||
|
for _, tt := range brTests {
|
||||||
|
tl := &testLink{}
|
||||||
|
|
||||||
|
_, err := NewBridgeWithName(tt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewBridge(%s) failed to run: %s", tt, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.prepTestLink(tt, "bridge"); err != nil {
|
||||||
|
t.Skipf("test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(tt); err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Could not find %s on the host: %s", tt, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testRes, err := linkInfo(tt, "bridge")
|
||||||
|
if err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Failed to list %s operation mode: %s", tt, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testRes.linkType != "bridge" {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewBridge() failed: expected linktype bridge, returned %s", testRes.linkType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.teardown(); err != nil {
|
||||||
|
t.Fatalf("testIfcLink.teardown failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
// Package tenus allows to configure and manage Linux network devices programmatically.
|
||||||
|
//
|
||||||
|
// You can create, configure and manage various advanced Linux network setups directly from your Go code.
|
||||||
|
// tenus also allows you to configure advanced network setups with Linux containers including Docker.
|
||||||
|
// It leverages Linux Kernenl's netlink facility and exposes easier to work with programming API than
|
||||||
|
// the one provided by netlink.
|
||||||
|
//
|
||||||
|
// Actual implementations are in:
|
||||||
|
// link_linux.go, bridge_linux.go, veth_linux.go, vlan_linux.go and macvlan_linux.go
|
||||||
|
package tenus
|
@ -0,0 +1,228 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testEnv struct {
|
||||||
|
createCmds []*exec.Cmd
|
||||||
|
setupCmds []*exec.Cmd
|
||||||
|
tearDownCmds []*exec.Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (te *testEnv) create() error {
|
||||||
|
for _, cmd := range te.createCmds {
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (te *testEnv) setup() error {
|
||||||
|
for _, cmd := range te.setupCmds {
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (te *testEnv) teardown() error {
|
||||||
|
for _, cmd := range te.tearDownCmds {
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type testLink struct {
|
||||||
|
testEnv
|
||||||
|
name string
|
||||||
|
linkType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tl *testLink) prepTestLink(name, linkType string) error {
|
||||||
|
if os.Getuid() != 0 {
|
||||||
|
return errors.New("skipping test; must be root")
|
||||||
|
}
|
||||||
|
|
||||||
|
tl.name = name
|
||||||
|
tl.linkType = linkType
|
||||||
|
|
||||||
|
xpath, err := exec.LookPath("ip")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tl.createCmds = append(tl.createCmds, &exec.Cmd{
|
||||||
|
Path: xpath,
|
||||||
|
Args: []string{"ip", "link", "add", tl.name, "type", tl.linkType},
|
||||||
|
})
|
||||||
|
|
||||||
|
tl.tearDownCmds = append(tl.tearDownCmds, &exec.Cmd{
|
||||||
|
Path: xpath,
|
||||||
|
Args: []string{"ip", "link", "del", tl.name},
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tl *testLink) prepLinkOpts(opts LinkOptions) error {
|
||||||
|
if os.Getuid() != 0 {
|
||||||
|
return errors.New("skipping test; must be root")
|
||||||
|
}
|
||||||
|
|
||||||
|
macaddr := opts.MacAddr
|
||||||
|
mtu := strconv.Itoa(opts.MTU)
|
||||||
|
flags := opts.Flags
|
||||||
|
|
||||||
|
xpath, err := exec.LookPath("ip")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if macaddr != "" {
|
||||||
|
tl.setupCmds = append(tl.setupCmds, &exec.Cmd{
|
||||||
|
Path: xpath,
|
||||||
|
Args: []string{"ip", "link", "set", "dev", tl.name, "address", macaddr},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if mtu != "" {
|
||||||
|
tl.setupCmds = append(tl.setupCmds, &exec.Cmd{
|
||||||
|
Path: xpath,
|
||||||
|
Args: []string{"ip", "link", "set", "dev", tl.name, "mtu", mtu},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & syscall.IFF_UP) == syscall.IFF_UP {
|
||||||
|
tl.setupCmds = append(tl.setupCmds, &exec.Cmd{
|
||||||
|
Path: xpath,
|
||||||
|
Args: []string{"ip", "link", "set", "dev", tl.name, "up"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDocker struct {
|
||||||
|
testEnv
|
||||||
|
name string
|
||||||
|
command string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (td *testDocker) prepTestDocker(name, command string) error {
|
||||||
|
if os.Getuid() != 0 {
|
||||||
|
return errors.New("skipping test; must be root")
|
||||||
|
}
|
||||||
|
|
||||||
|
td.name = name
|
||||||
|
td.command = command
|
||||||
|
|
||||||
|
xpath, err := exec.LookPath("docker")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
td.createCmds = append(td.createCmds, &exec.Cmd{
|
||||||
|
Path: xpath,
|
||||||
|
Args: []string{"docker", "run", "-t", "-d", "--name", td.name, "ubuntu", td.command},
|
||||||
|
})
|
||||||
|
|
||||||
|
td.tearDownCmds = append(td.tearDownCmds, &exec.Cmd{
|
||||||
|
Path: xpath,
|
||||||
|
Args: []string{"docker", "stop", td.name},
|
||||||
|
})
|
||||||
|
|
||||||
|
td.tearDownCmds = append(td.tearDownCmds, &exec.Cmd{
|
||||||
|
Path: xpath,
|
||||||
|
Args: []string{"docker", "rm", td.name},
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type testLinkInfo struct {
|
||||||
|
linkType string
|
||||||
|
linkData string
|
||||||
|
}
|
||||||
|
|
||||||
|
var testLinkInfoData = map[string]func([]string) (string, error){
|
||||||
|
"macvlan": macvlanInfo,
|
||||||
|
"vlan": vlanInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
func macvlanInfo(data []string) (string, error) {
|
||||||
|
if len(data) < 3 {
|
||||||
|
return "", fmt.Errorf("Unable to parse macvlan result")
|
||||||
|
}
|
||||||
|
|
||||||
|
return data[2], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vlanInfo(data []string) (string, error) {
|
||||||
|
if len(data) < 5 {
|
||||||
|
return "", fmt.Errorf("Unable to parse vlan result")
|
||||||
|
}
|
||||||
|
|
||||||
|
return data[4], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func linkInfo(name, linkType string) (*testLinkInfo, error) {
|
||||||
|
ipPath, err := exec.LookPath("ip")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to find ip in PATH: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
list := exec.Command(ipPath, "-d", "link", "show", name)
|
||||||
|
res := exec.Command("tail", "-1")
|
||||||
|
|
||||||
|
var pipeErr error
|
||||||
|
var out bytes.Buffer
|
||||||
|
res.Stdin, pipeErr = list.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable start UNIX pipe: %s", pipeErr)
|
||||||
|
}
|
||||||
|
res.Stdout = &out
|
||||||
|
|
||||||
|
if err := res.Start(); err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to tail the result: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := list.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to retrieve interface information: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := res.Wait(); err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not read UNIX pipe data: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := strings.Fields(strings.TrimSpace(out.String()))
|
||||||
|
|
||||||
|
linkInfoFunc, ok := testLinkInfoData[linkType]
|
||||||
|
if !ok {
|
||||||
|
return &testLinkInfo{
|
||||||
|
linkType: data[0],
|
||||||
|
linkData: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
linkData, err := linkInfoFunc(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not read Link Info: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &testLinkInfo{
|
||||||
|
linkType: data[0],
|
||||||
|
linkData: linkData,
|
||||||
|
}, nil
|
||||||
|
}
|
37
Godeps/_workspace/src/github.com/milosgajdos83/tenus/examples/tenus_bridgedummy_linux.go
generated
vendored
37
Godeps/_workspace/src/github.com/milosgajdos83/tenus/examples/tenus_bridgedummy_linux.go
generated
vendored
@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/tenus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create a new network bridge
|
||||||
|
br, err := tenus.NewBridgeWithName("mybridge")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring the bridge up
|
||||||
|
if err = br.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a dummy link
|
||||||
|
dl, err := tenus.NewLink("mydummylink")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the dummy link into bridge
|
||||||
|
if err = br.AddSlaveIfc(dl.NetInterface()); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring the dummy link up
|
||||||
|
if err = dl.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
52
Godeps/_workspace/src/github.com/milosgajdos83/tenus/examples/tenus_macvlanns_linux.go
generated
vendored
52
Godeps/_workspace/src/github.com/milosgajdos83/tenus/examples/tenus_macvlanns_linux.go
generated
vendored
@ -0,0 +1,52 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/tenus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
macVlanHost, err := tenus.NewMacVlanLinkWithOptions("eth1", tenus.MacVlanOptions{Mode: "bridge", MacVlanDev: "macvlanHostIfc"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
macVlanHostIp, macVlanHostIpNet, err := net.ParseCIDR("10.0.41.2/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := macVlanHost.SetLinkIp(macVlanHostIp, macVlanHostIpNet); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = macVlanHost.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
macVlanDocker, err := tenus.NewMacVlanLinkWithOptions("eth1", tenus.MacVlanOptions{Mode: "bridge", MacVlanDev: "macvlanDckrIfc"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := tenus.DockerPidByName("mcvlandckr", "/var/run/docker.sock")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := macVlanDocker.SetLinkNetNsPid(pid); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
macVlanDckrIp, macVlanDckrIpNet, err := net.ParseCIDR("10.0.41.3/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := macVlanDocker.SetLinkNetInNs(pid, macVlanDckrIp, macVlanDckrIpNet, nil); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
80
Godeps/_workspace/src/github.com/milosgajdos83/tenus/examples/tenus_vethns_linux.go
generated
vendored
80
Godeps/_workspace/src/github.com/milosgajdos83/tenus/examples/tenus_vethns_linux.go
generated
vendored
@ -0,0 +1,80 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/tenus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// CREATE BRIDGE AND BRING IT UP
|
||||||
|
br, err := tenus.NewBridgeWithName("vethbridge")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
brIp, brIpNet, err := net.ParseCIDR("10.0.41.1/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := br.SetLinkIp(brIp, brIpNet); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = br.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE VETH PAIR
|
||||||
|
veth, err := tenus.NewVethPairWithOptions("myveth01", tenus.VethOptions{PeerName: "myveth02"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASSIGN IP ADDRESS TO THE HOST VETH INTERFACE
|
||||||
|
vethHostIp, vethHostIpNet, err := net.ParseCIDR("10.0.41.2/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := veth.SetLinkIp(vethHostIp, vethHostIpNet); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD MYVETH01 INTERFACE TO THE MYBRIDGE BRIDGE
|
||||||
|
myveth01, err := net.InterfaceByName("myveth01")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = br.AddSlaveIfc(myveth01); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = veth.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PASS VETH PEER INTERFACE TO A RUNNING DOCKER BY PID
|
||||||
|
pid, err := tenus.DockerPidByName("vethdckr", "/var/run/docker.sock")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := veth.SetPeerLinkNsPid(pid); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALLOCATE AND SET IP FOR THE NEW DOCKER INTERFACE
|
||||||
|
vethGuestIp, vethGuestIpNet, err := net.ParseCIDR("10.0.41.5/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := veth.SetPeerLinkNetInNs(pid, vethGuestIp, vethGuestIpNet, nil); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
37
Godeps/_workspace/src/github.com/milosgajdos83/tenus/examples/tenus_vlanns_linux.go
generated
vendored
37
Godeps/_workspace/src/github.com/milosgajdos83/tenus/examples/tenus_vlanns_linux.go
generated
vendored
@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/tenus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// CREATE VLAN HOST INTERFACE
|
||||||
|
vlanDocker, err := tenus.NewVlanLinkWithOptions("eth1", tenus.VlanOptions{VlanDev: "vlanDckr", Id: 20})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PASS VLAN INTERFACE TO A RUNNING DOCKER BY PID
|
||||||
|
pid, err := tenus.DockerPidByName("vlandckr", "/var/run/docker.sock")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vlanDocker.SetLinkNetNsPid(pid); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALLOCATE AND SET IP FOR THE NEW DOCKER INTERFACE
|
||||||
|
vlanDckrIp, vlanDckrIpNet, err := net.ParseCIDR("10.1.41.3/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := vlanDocker.SetLinkNetInNs(pid, vlanDckrIp, vlanDckrIpNet, nil); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
106
Godeps/_workspace/src/github.com/milosgajdos83/tenus/examples/tenuser_linux.go
generated
vendored
106
Godeps/_workspace/src/github.com/milosgajdos83/tenus/examples/tenuser_linux.go
generated
vendored
@ -0,0 +1,106 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/tenus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// CREATE BRIDGE AND BRING IT UP
|
||||||
|
br, err := tenus.NewBridgeWithName("mybridge")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, ipNet, err := net.ParseCIDR("10.0.41.1/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := br.SetLinkIp(ip, ipNet); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = br.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE VETH PAIR
|
||||||
|
veth, err := tenus.NewVethPairWithOptions("myveth01", tenus.VethOptions{PeerName: "myveth02"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASSIGN AN IP TO MYVETH01
|
||||||
|
ip, ipNet, err = net.ParseCIDR("10.0.41.2/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = veth.SetLinkIp(ip, ipNet); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASSIGN AN IP TO MYVETH02
|
||||||
|
ip, ipNet, err = net.ParseCIDR("10.0.41.3/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := veth.SetPeerLinkIp(ip, ipNet); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD MYVETH01 INTERFACE TO THE MYBRIDGE BRIDGE AND BRING IT UP
|
||||||
|
// we could also simply do myveth01 := veth.NetInterface()
|
||||||
|
myveth01, err := net.InterfaceByName("myveth01")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = br.AddSlaveIfc(myveth01); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = veth.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD MYVETH02 INTERFACE TO THE MYBRIDGE BRIDGE AND BRING IT UP
|
||||||
|
// we could also simply do myveth01 := veth.NetInterface()
|
||||||
|
myveth02, err := net.InterfaceByName("myveth02")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = br.AddSlaveIfc(myveth02); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = veth.SetPeerLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE MACVLAN INTERFACE AND BRING IT UP
|
||||||
|
macvlan, err := tenus.NewMacVlanLinkWithOptions("eth0", tenus.MacVlanOptions{Mode: "bridge", MacVlanDev: "macvlan01"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := macvlan.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE VLAN INTERFACE AND BRING IT UP
|
||||||
|
vlan, err := tenus.NewVlanLinkWithOptions("eth1", tenus.VlanOptions{Id: 10, VlanDev: "vlan01"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = vlan.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,241 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/libcontainer-milosgajdos83/system"
|
||||||
|
"github.com/milosgajdos83/libcontainer-milosgajdos83/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// generates random string for makeNetInterfaceName()
|
||||||
|
func randomString(size int) string {
|
||||||
|
alphanum := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
bytes := make([]byte, size)
|
||||||
|
rand.Read(bytes)
|
||||||
|
|
||||||
|
for i, b := range bytes {
|
||||||
|
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeNetInterfaceName(base string) string {
|
||||||
|
return makeNetInterfaceName(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generates new unused network interfaces name with given prefix
|
||||||
|
func makeNetInterfaceName(base string) string {
|
||||||
|
for {
|
||||||
|
name := base + randomString(6)
|
||||||
|
if _, err := net.InterfaceByName(name); err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates MTU LinkOption
|
||||||
|
func validMtu(mtu int) error {
|
||||||
|
if mtu < 0 {
|
||||||
|
return errors.New("MTU must be a positive integer!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates MacAddress LinkOption
|
||||||
|
func validMacAddress(macaddr string) error {
|
||||||
|
if _, err := net.ParseMAC(macaddr); err != nil {
|
||||||
|
return fmt.Errorf("Can not parse MAC address: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := FindInterfaceByMacAddress(macaddr); err == nil {
|
||||||
|
return fmt.Errorf("MAC Address already assigned on the host: %s", macaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates MacAddress LinkOption
|
||||||
|
func validNs(ns int) error {
|
||||||
|
if ns < 0 {
|
||||||
|
return fmt.Errorf("Incorrect Network Namespace PID specified: %d", ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates Flags LinkOption
|
||||||
|
func validFlags(flags net.Flags) error {
|
||||||
|
if (flags & syscall.IFF_UP) != syscall.IFF_UP {
|
||||||
|
return fmt.Errorf("Unsupported network flags specified: %v", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterfaceNameValid checks if the network interface name is valid.
|
||||||
|
// It accepts interface name as a string. It returns error if invalid interface name is supplied.
|
||||||
|
func NetInterfaceNameValid(name string) (bool, error) {
|
||||||
|
if name == "" {
|
||||||
|
return false, errors.New("Interface name can not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(name) == 1 {
|
||||||
|
return false, fmt.Errorf("Interface name too short: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(name) > netlink.IFNAMSIZ {
|
||||||
|
return false, fmt.Errorf("Interface name too long: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, char := range name {
|
||||||
|
if unicode.IsSpace(char) || char > 0x7F {
|
||||||
|
return false, fmt.Errorf("Invalid characters in interface name: %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindInterfaceByMacAddress returns *net.Interface which has a given MAC address assigned.
|
||||||
|
// It returns nil and error if invalid MAC address is supplied or if there is no network interface
|
||||||
|
// with the given MAC address assigned on Linux host.
|
||||||
|
func FindInterfaceByMacAddress(macaddr string) (*net.Interface, error) {
|
||||||
|
if macaddr == "" {
|
||||||
|
return nil, errors.New("Empty MAC address specified!")
|
||||||
|
}
|
||||||
|
|
||||||
|
ifcs, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hwaddr, err := net.ParseMAC(macaddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ifc := range ifcs {
|
||||||
|
if bytes.Equal(hwaddr, ifc.HardwareAddr) {
|
||||||
|
return &ifc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Could not find interface with MAC address on the host: %s", macaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DockerPidByName returns PID of the running docker container.
|
||||||
|
// It accepts Docker container name and Docker host as parameters and queries Docker API via HTTP.
|
||||||
|
// Docker host passed as an argument can be either full path to Docker UNIX socket or HOST:PORT address string.
|
||||||
|
// It returns error if Docker container can not be found or if an error occurs when querying Docker API.
|
||||||
|
func DockerPidByName(name string, dockerHost string) (int, error) {
|
||||||
|
var network string
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return 0, errors.New("Docker name can not be empty!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if dockerHost == "" {
|
||||||
|
return 0, errors.New("Docker host can not be empty!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepath.IsAbs(dockerHost) {
|
||||||
|
network = "unix"
|
||||||
|
} else {
|
||||||
|
network = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "http:// /containers/"+name+"/json", nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("Fail to create http request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := time.Duration(2 * time.Second)
|
||||||
|
httpTransport := &http.Transport{
|
||||||
|
Dial: func(proto string, addr string) (net.Conn, error) {
|
||||||
|
return net.DialTimeout(network, dockerHost, timeout)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerClient := http.Client{Transport: httpTransport}
|
||||||
|
|
||||||
|
resp, err := dockerClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("Failed to create http client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return 0, fmt.Errorf("Docker container \"%s\" does not seem to exist!", name)
|
||||||
|
case http.StatusInternalServerError:
|
||||||
|
return 0, fmt.Errorf("Could not retrieve Docker %s pid due to Docker server error", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
State struct {
|
||||||
|
Pid float64
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&data)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("Unable to decode json response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(data.State.Pid), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetNsHandle returns a file descriptor handle for network namespace specified by PID.
|
||||||
|
// It returns error if network namespace could not be found or if network namespace path could not be opened.
|
||||||
|
func NetNsHandle(nspid int) (uintptr, error) {
|
||||||
|
if nspid <= 0 || nspid == 1 {
|
||||||
|
return 0, fmt.Errorf("Incorred PID specified: %d", nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
nsPath := path.Join("/", "proc", strconv.Itoa(nspid), "ns/net")
|
||||||
|
if nsPath == "" {
|
||||||
|
return 0, fmt.Errorf("Could not find Network namespace for pid: %d", nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(nsPath)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("Could not open Network Namespace: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.Fd(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNetNsToPid sets network namespace to the one specied by PID.
|
||||||
|
// It returns error if the network namespace could not be set.
|
||||||
|
func SetNetNsToPid(nspid int) error {
|
||||||
|
if nspid <= 0 || nspid == 1 {
|
||||||
|
return fmt.Errorf("Incorred PID specified: %d", nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
nsFd, err := NetNsHandle(nspid)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Could not get network namespace handle: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := system.Setns(nsFd, syscall.CLONE_NEWNET); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set the network namespace: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,264 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ifcNameTest struct {
|
||||||
|
ifcName string
|
||||||
|
expected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var ifcNameTests = []ifcNameTest{
|
||||||
|
{"link1", true},
|
||||||
|
{"", false},
|
||||||
|
{"a", false},
|
||||||
|
{"abcdefghijklmnopqr", false},
|
||||||
|
{"link\uF021", false},
|
||||||
|
{"eth0.123", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NetInterfaceNameValid(t *testing.T) {
|
||||||
|
for _, tt := range ifcNameTests {
|
||||||
|
ret, _ := NetInterfaceNameValid(tt.ifcName)
|
||||||
|
if ret != tt.expected {
|
||||||
|
t.Errorf("NetInterfaceNameValid(%s): expected %v, returned %v", tt.ifcName, tt.expected, ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ifcMacTest struct {
|
||||||
|
testLink
|
||||||
|
opts LinkOptions
|
||||||
|
testVal string
|
||||||
|
expected *net.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// correct MAC Address will always parse into HardwareAddr
|
||||||
|
var hw, _ = net.ParseMAC("22:ce:e0:99:63:6f")
|
||||||
|
var ifcMacTests = []ifcMacTest{
|
||||||
|
{testLink{name: "ifc01", linkType: "dummy"},
|
||||||
|
LinkOptions{MacAddr: "22:ce:e0:99:63:6f"}, "22:ce:e0:99:63:6f",
|
||||||
|
&net.Interface{Name: "ifc01", HardwareAddr: hw}},
|
||||||
|
{testLink{name: "ifc02", linkType: "dummy"},
|
||||||
|
LinkOptions{MacAddr: "26:2e:71:98:60:8f"}, "",
|
||||||
|
nil},
|
||||||
|
{testLink{name: "ifc03", linkType: "dummy"},
|
||||||
|
LinkOptions{MacAddr: "fa:de:b0:99:52:1c"}, "randomstring",
|
||||||
|
nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_FindInterfaceByMacAddress(t *testing.T) {
|
||||||
|
for _, tt := range ifcMacTests {
|
||||||
|
tl := &testLink{}
|
||||||
|
|
||||||
|
if err := tl.prepTestLink(tt.name, tt.linkType); err != nil {
|
||||||
|
t.Skipf("InterfaceByMacAddress test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.prepLinkOpts(LinkOptions{MacAddr: tt.opts.MacAddr}); err != nil {
|
||||||
|
t.Skipf("InterfaceByMacAddress test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.create(); err != nil {
|
||||||
|
t.Fatalf("testLink.create failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.setup(); err != nil {
|
||||||
|
t.Fatalf("testLink.setup failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
ifc, err := FindInterfaceByMacAddress(tt.testVal)
|
||||||
|
if ifc != nil {
|
||||||
|
if tt.expected != nil {
|
||||||
|
if ifc.Name != tt.expected.Name || !bytes.Equal(ifc.HardwareAddr, tt.expected.HardwareAddr) {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("FindInterfaceByMacAddress(%s): expected %v, returned %v",
|
||||||
|
tt.testVal, tt.expected, ifc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.expected == nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("FindInterfaceByMacAddress(%s): expected %v, returned %v ",
|
||||||
|
tt.testVal, tt.expected, ifc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ifc == nil {
|
||||||
|
if tt.expected != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("FindInterfaceByMacAddress(%s): expected %v, returned %v, error: %s",
|
||||||
|
tt.testVal, tt.expected, ifc, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.teardown(); err != nil {
|
||||||
|
t.Fatalf("testLink.teardown failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type dockerPidTest struct {
|
||||||
|
testDocker
|
||||||
|
host string
|
||||||
|
expected int
|
||||||
|
}
|
||||||
|
|
||||||
|
var dockerPidTests = []dockerPidTest{
|
||||||
|
{testDocker{name: "topper1", command: "/usr/bin/top"}, "/var/run/docker.sock", 1234},
|
||||||
|
{testDocker{name: "topper2", command: "/usr/bin/top"}, "somehost.com:9011", 0},
|
||||||
|
{testDocker{name: "topper3", command: "/usr/bin/top"}, "", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DockerPidByName(t *testing.T) {
|
||||||
|
for _, tt := range dockerPidTests {
|
||||||
|
td := &testDocker{}
|
||||||
|
|
||||||
|
if err := td.prepTestDocker(tt.name, tt.command); err != nil {
|
||||||
|
t.Skipf("DockerPidByName test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := td.create(); err != nil {
|
||||||
|
t.Fatalf("prepTestDocker.create failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
retPid, err := DockerPidByName(tt.name, tt.host)
|
||||||
|
if retPid != 0 {
|
||||||
|
if tt.expected != 0 {
|
||||||
|
dockerPath, err := exec.LookPath("docker")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to find docker in PATH: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := exec.Command(dockerPath, "inspect", "-f", "{{.State.Pid }}", tt.name).Output()
|
||||||
|
if err != nil {
|
||||||
|
td.teardown()
|
||||||
|
t.Fatalf("Failed to run tt.testCmd.Output(): %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualPid, err := strconv.Atoi(strings.TrimSpace(string(out)))
|
||||||
|
if err != nil {
|
||||||
|
td.teardown()
|
||||||
|
t.Fatalf("Failed to run strconv.Atoi(strings.TrimSpace(string(%v))): %s",
|
||||||
|
out, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if retPid != actualPid {
|
||||||
|
td.teardown()
|
||||||
|
t.Errorf("DockerPidByName(%s, %s): expected: %v, returned: %v",
|
||||||
|
tt.name, tt.host, out, retPid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.expected == 0 {
|
||||||
|
td.teardown()
|
||||||
|
t.Errorf("DockerPidByName(%s, %s): expected: %v, returned: %v",
|
||||||
|
tt.name, tt.host, tt.expected, retPid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if retPid == 0 {
|
||||||
|
if tt.expected != 0 {
|
||||||
|
td.teardown()
|
||||||
|
t.Errorf("DockerPidByName(%s, %s): expected: %v, returned: %v, error: %s",
|
||||||
|
tt.name, tt.host, tt.expected, retPid, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := td.teardown(); err != nil {
|
||||||
|
t.Fatalf("testDocker.teardown failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type netNsTest struct {
|
||||||
|
pid int
|
||||||
|
testCmd *exec.Cmd
|
||||||
|
expected int
|
||||||
|
}
|
||||||
|
|
||||||
|
var netNsTests = []netNsTest{
|
||||||
|
{1234, &exec.Cmd{
|
||||||
|
Path: "",
|
||||||
|
Args: []string{"docker", "inspect", "-f", "{{.State.Pid }}", "testdckr"}},
|
||||||
|
1234},
|
||||||
|
{0, &exec.Cmd{}, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NetNsHandle(t *testing.T) {
|
||||||
|
for _, tt := range netNsTests {
|
||||||
|
td := &testDocker{}
|
||||||
|
|
||||||
|
if err := td.prepTestDocker("testdckr", "/usr/bin/top"); err != nil {
|
||||||
|
t.Skipf("NetNsHandle test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := td.create(); err != nil {
|
||||||
|
t.Fatalf("prepTestDocker.create failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.pid != 0 {
|
||||||
|
dockerPath, err := exec.LookPath("docker")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unable to find docker in PATH: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := exec.Command(dockerPath, "inspect", "-f", "{{.State.Pid }}", "testdckr").Output()
|
||||||
|
if err != nil {
|
||||||
|
td.teardown()
|
||||||
|
t.Fatalf("Failed to run tt.testCmd.Output(): %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualPid, err := strconv.Atoi(strings.TrimSpace(string(out)))
|
||||||
|
if err != nil {
|
||||||
|
td.teardown()
|
||||||
|
t.Fatalf("Failed to run strconv.Atoi(strings.TrimSpace(string(%v))): %s",
|
||||||
|
out, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tt.pid = actualPid
|
||||||
|
}
|
||||||
|
|
||||||
|
nsFd, err := NetNsHandle(tt.pid)
|
||||||
|
if nsFd == 0 {
|
||||||
|
if tt.expected != 0 {
|
||||||
|
td.teardown()
|
||||||
|
t.Fatalf("NetNsHandle(%d): expected: non-zero, returned: %v, error: %s",
|
||||||
|
tt.pid, nsFd, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nsFd != 0 {
|
||||||
|
if tt.expected == 0 {
|
||||||
|
td.teardown()
|
||||||
|
t.Fatalf("NetNsHandle(%d): expected: %d, returned: %v",
|
||||||
|
tt.pid, tt.expected, nsFd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := td.teardown(); err != nil {
|
||||||
|
t.Fatalf("testDocker.teardown failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,328 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/libcontainer-milosgajdos83/system"
|
||||||
|
"github.com/milosgajdos83/libcontainer-milosgajdos83/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LinkOptions allows you to specify network link options.
|
||||||
|
type LinkOptions struct {
|
||||||
|
// MAC address
|
||||||
|
MacAddr string
|
||||||
|
// Maximum Transmission Unit
|
||||||
|
MTU int
|
||||||
|
// Link network flags i.e. FlagUp, FlagLoopback, FlagMulticast
|
||||||
|
Flags net.Flags
|
||||||
|
// Network namespace in which the network link should be created
|
||||||
|
Ns int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linker is a generic Linux network link
|
||||||
|
type Linker interface {
|
||||||
|
// NetInterface returns the link's logical network interface
|
||||||
|
NetInterface() *net.Interface
|
||||||
|
// DeleteLink deletes the link from Linux host
|
||||||
|
DeleteLink() error
|
||||||
|
// SetLinkMTU sets the link's MTU.
|
||||||
|
SetLinkMTU(int) error
|
||||||
|
// SetLinkMacAddress sets the link's MAC address.
|
||||||
|
SetLinkMacAddress(string) error
|
||||||
|
// SetLinkUp brings the link up
|
||||||
|
SetLinkUp() error
|
||||||
|
// SetLinkDown brings the link down
|
||||||
|
SetLinkDown() error
|
||||||
|
// SetLinkIp configures the link's IP address
|
||||||
|
SetLinkIp(net.IP, *net.IPNet) error
|
||||||
|
// SetLinkDefaultGw configures the link's default gateway
|
||||||
|
SetLinkDefaultGw(*net.IP) error
|
||||||
|
// SetLinkNetNsPid moves the link to network namespace specified by PID
|
||||||
|
SetLinkNetNsPid(int) error
|
||||||
|
// SetLinkNetInNs configures network settings of the link in network namespace
|
||||||
|
SetLinkNetInNs(int, net.IP, *net.IPNet, *net.IP) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link has a logical network interface
|
||||||
|
type Link struct {
|
||||||
|
ifc *net.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLink creates new network link on Linux host.
|
||||||
|
//
|
||||||
|
// It is equivalent of running: ip link add name ${ifcName} type dummy
|
||||||
|
// NewLink returns Linker which is initialized to a pointer of type Link if the
|
||||||
|
// link was created successfully on the Linux host.
|
||||||
|
// It returns error if the network link could not be created on Linux host.
|
||||||
|
func NewLink(ifcName string) (Linker, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(ifcName); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", ifcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAdd(ifcName, "dummy"); err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not create new link %s: %s", ifcName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
// NewLink creates new network link on Linux host.
|
||||||
|
//
|
||||||
|
// It is equivalent of running: ip link add name ${ifcName} type dummy
|
||||||
|
// NewLink returns Linker which is initialized to a pointer of type Link if the
|
||||||
|
// link was created successfully on the Linux host.
|
||||||
|
// It returns error if the network link could not be created on Linux host.
|
||||||
|
func NewLinkFrom(ifcName string) (Linker, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLinkWithOptions creates new network link on Linux host and sets some of its network
|
||||||
|
// parameters passed in as LinkOptions
|
||||||
|
//
|
||||||
|
// Calling NewLinkWithOptions is equivalent of running following commands one after another if
|
||||||
|
// particular option is passed in as a parameter:
|
||||||
|
// ip link add name ${ifcName} type dummy
|
||||||
|
// ip link set dev ${ifcName} address ${MAC address}
|
||||||
|
// ip link set dev ${ifcName} mtu ${MTU value}
|
||||||
|
// ip link set dev ${ifcName} up
|
||||||
|
// NewLinkWithOptions returns Linker which is initialized to a pointer of type Link if the network
|
||||||
|
// link with given LinkOptions was created successfully on the Linux host.
|
||||||
|
// It attempts to delete the link if any of the LinkOptions are incorrect or if setting the options
|
||||||
|
// failed and returns error.
|
||||||
|
func NewLinkWithOptions(ifcName string, opts LinkOptions) (Linker, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(ifcName); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", ifcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAdd(ifcName, "dummy"); err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not create new link %s: %s", ifcName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts != LinkOptions{}) {
|
||||||
|
errOpts := setLinkOptions(newIfc, opts)
|
||||||
|
if errOpts != nil {
|
||||||
|
if errDel := DeleteLink(newIfc.Name); err != nil {
|
||||||
|
return nil, fmt.Errorf("Incorrect options specified: %s. Attempt to delete the link failed: %s",
|
||||||
|
errOpts, errDel)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Could not set link options: %s", errOpts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLink deletes netowrk link from Linux Host
|
||||||
|
// It is equivalent of running: ip link delete dev ${name}
|
||||||
|
func DeleteLink(name string) error {
|
||||||
|
return netlink.NetworkLinkDel(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterface returns link's logical network interface.
|
||||||
|
func (l *Link) NetInterface() *net.Interface {
|
||||||
|
return l.ifc
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLink deletes link interface on Linux host.
|
||||||
|
// It is equivalent of running: ip link delete dev ${interface name}
|
||||||
|
func (l *Link) DeleteLink() error {
|
||||||
|
return netlink.NetworkLinkDel(l.NetInterface().Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkMTU sets link's MTU.
|
||||||
|
// It is equivalent of running: ip link set dev ${interface name} mtu ${MTU value}
|
||||||
|
func (l *Link) SetLinkMTU(mtu int) error {
|
||||||
|
return netlink.NetworkSetMTU(l.NetInterface(), mtu)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkMacAddress sets link's MAC address.
|
||||||
|
// It is equivalent of running: ip link set dev ${interface name} address ${address}
|
||||||
|
func (l *Link) SetLinkMacAddress(macaddr string) error {
|
||||||
|
return netlink.NetworkSetMacAddress(l.NetInterface(), macaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkUp brings the link up.
|
||||||
|
// It is equivalent of running: ip link set dev ${interface name} up
|
||||||
|
func (l *Link) SetLinkUp() error {
|
||||||
|
return netlink.NetworkLinkUp(l.NetInterface())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkDown brings the link down.
|
||||||
|
// It is equivalent of running: ip link set dev ${interface name} down
|
||||||
|
func (l *Link) SetLinkDown() error {
|
||||||
|
return netlink.NetworkLinkDown(l.NetInterface())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkIp configures the link's IP address.
|
||||||
|
// It is equivalent of running: ip address add ${address}/${mask} dev ${interface name}
|
||||||
|
func (l *Link) SetLinkIp(ip net.IP, network *net.IPNet) error {
|
||||||
|
return netlink.NetworkLinkAddIp(l.NetInterface(), ip, network)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkDefaultGw configures the link's default Gateway.
|
||||||
|
// It is equivalent of running: ip route add default via ${ip address}
|
||||||
|
func (l *Link) SetLinkDefaultGw(gw *net.IP) error {
|
||||||
|
return netlink.AddDefaultGw(gw.String(), l.NetInterface().Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkNetNsPid moves the link to Network namespace specified by PID.
|
||||||
|
func (l *Link) SetLinkNetNsPid(nspid int) error {
|
||||||
|
return netlink.NetworkSetNsPid(l.NetInterface(), nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkNetInNs configures network settings of the link in network namespace specified by PID.
|
||||||
|
func (l *Link) SetLinkNetInNs(nspid int, ip net.IP, network *net.IPNet, gw *net.IP) error {
|
||||||
|
origNs, _ := NetNsHandle(os.Getpid())
|
||||||
|
defer syscall.Close(int(origNs))
|
||||||
|
defer system.Setns(origNs, syscall.CLONE_NEWNET)
|
||||||
|
|
||||||
|
if err := SetNetNsToPid(nspid); err != nil {
|
||||||
|
return fmt.Errorf("Setting network namespace failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddIp(l.NetInterface(), ip, network); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set IP: %s in pid: %d network namespace", ip.String(), nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkUp(l.ifc); err != nil {
|
||||||
|
return fmt.Errorf("Unable to bring %s interface UP: %s", l.ifc.Name, nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gw != nil {
|
||||||
|
if err := netlink.AddDefaultGw(gw.String(), l.NetInterface().Name); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set Default gateway: %s in pid: %d network namespace", gw.String(), nspid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkNsFd sets the link's Linux namespace to the one specified by filesystem path.
|
||||||
|
func (l *Link) SetLinkNsFd(nspath string) error {
|
||||||
|
fd, err := syscall.Open(nspath, syscall.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Could not attach to Network namespace: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netlink.NetworkSetNsFd(l.NetInterface(), fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkNsToDocker sets the link's Linux namespace to a running Docker one specified by Docker name.
|
||||||
|
func (l *Link) SetLinkNsToDocker(name string, dockerHost string) error {
|
||||||
|
pid, err := DockerPidByName(name, dockerHost)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to find docker %s : %s", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.SetLinkNetNsPid(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenameInterface(old string, newName string) error {
|
||||||
|
iface, err := net.InterfaceByName(old)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return netlink.NetworkChangeName(iface, newName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setLinkOptions validates and sets link's various options passed in as LinkOptions.
|
||||||
|
func setLinkOptions(ifc *net.Interface, opts LinkOptions) error {
|
||||||
|
macaddr, mtu, flags, ns := opts.MacAddr, opts.MTU, opts.Flags, opts.Ns
|
||||||
|
|
||||||
|
// if MTU is passed in LinkOptions
|
||||||
|
if mtu != 0 {
|
||||||
|
if err := validMtu(mtu); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkSetMTU(ifc, mtu); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set MTU: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if MacAddress is passed in LinkOptions
|
||||||
|
if macaddr != "" {
|
||||||
|
if err := validMacAddress(macaddr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkSetMacAddress(ifc, macaddr); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set MAC Address: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if ns is passed in LinkOptions
|
||||||
|
if ns != 0 {
|
||||||
|
if err := validNs(ns); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkSetNsPid(ifc, ns); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set Network namespace: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if flags is passed in LinkOptions
|
||||||
|
if flags != 0 {
|
||||||
|
if err := validFlags(flags); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ns != 0 && (ns != 1 || ns != os.Getpid()) {
|
||||||
|
if (flags & syscall.IFF_UP) == syscall.IFF_UP {
|
||||||
|
origNs, _ := NetNsHandle(os.Getpid())
|
||||||
|
defer syscall.Close(int(origNs))
|
||||||
|
defer system.Setns(origNs, syscall.CLONE_NEWNET)
|
||||||
|
|
||||||
|
if err := SetNetNsToPid(ns); err != nil {
|
||||||
|
return fmt.Errorf("Switching to %d network namespace failed: %s", ns, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkUp(ifc); err != nil {
|
||||||
|
return fmt.Errorf("Unable to bring %s interface UP: %s", ifc.Name, ns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := netlink.NetworkLinkUp(ifc); err != nil {
|
||||||
|
return fmt.Errorf("Could not bring up network link %s: %s", ifc.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_NewLink(t *testing.T) {
|
||||||
|
testLinks := []string{"ifc01", "ifc02", "ifc03"}
|
||||||
|
for _, tt := range testLinks {
|
||||||
|
tl := &testLink{}
|
||||||
|
|
||||||
|
if err := tl.prepTestLink(tt, "dummy"); err != nil {
|
||||||
|
t.Skipf("NewLink test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := NewLink(tt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewLink(%s) failed to run: %s", tt, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(tt); err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Could not find %s on the host: %s", tt, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.teardown(); err != nil {
|
||||||
|
t.Fatalf("testLink.teardown failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ifcLinkOptsTest struct {
|
||||||
|
testLink
|
||||||
|
opts LinkOptions
|
||||||
|
expected *net.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
var hwaddr, _ = net.ParseMAC("22:ce:e0:99:63:6f")
|
||||||
|
var ifcLinkOptsTests = []ifcLinkOptsTest{
|
||||||
|
{testLink{name: "ifc01"},
|
||||||
|
LinkOptions{MacAddr: "22:ce:e0:99:63:6f", MTU: 1400, Flags: net.FlagUp},
|
||||||
|
&net.Interface{Name: "ifc01", MTU: 1400, Flags: net.FlagUp, HardwareAddr: hwaddr}},
|
||||||
|
{testLink{name: "ifc02"},
|
||||||
|
LinkOptions{},
|
||||||
|
&net.Interface{Name: "ifc01"}},
|
||||||
|
{testLink{name: "ifc03"},
|
||||||
|
LinkOptions{MTU: -100},
|
||||||
|
nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NewLinkWithOptions(t *testing.T) {
|
||||||
|
for _, tt := range ifcLinkOptsTests {
|
||||||
|
tl := &testLink{}
|
||||||
|
|
||||||
|
if err := tl.prepTestLink(tt.name, "dummy"); err != nil {
|
||||||
|
t.Skipf("NewLinkWithOptions test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := NewLinkWithOptions(tt.name, tt.opts)
|
||||||
|
if err != nil && tt.expected != nil {
|
||||||
|
t.Fatalf("NewLinkWithOptions(%s, %v) failed to run: %s", tt.name, tt.opts, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ifc, err := net.InterfaceByName(tt.name)
|
||||||
|
if err != nil && tt.expected != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Could not find %s on the host: %s", tt.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.expected != nil {
|
||||||
|
if ifc.Name != tt.name {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewLinkWithOptions(%s, %v) failed: expected %s, returned: %s",
|
||||||
|
tt.name, tt.opts, tt.name, ifc.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.opts.MacAddr != "" {
|
||||||
|
if ifc.HardwareAddr.String() != tt.opts.MacAddr {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewLinkWithOptions(%s, %v) failed: expected %s, returned: %s",
|
||||||
|
tt.name, tt.opts, tt.opts.MacAddr, ifc.HardwareAddr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.opts.MTU != 0 {
|
||||||
|
if ifc.MTU != tt.opts.MTU {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewLinkWithOptions(%s, %v) failed: expected %d, returned: %d",
|
||||||
|
tt.name, tt.opts, tt.opts.MTU, ifc.MTU)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.opts.Flags != 0 {
|
||||||
|
if (ifc.Flags & tt.opts.Flags) != tt.opts.Flags {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewLinkWithOptions(%s, %v) failed: expected %v, returned: %v",
|
||||||
|
tt.name, tt.opts, tt.opts.Flags, ifc.Flags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.teardown(); err != nil {
|
||||||
|
t.Fatalf("testIfcLink.teardown failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.expected == nil && ifc != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewLinkWithOptions(%s, %v) failed. Expected: %v, Returned: %v",
|
||||||
|
tt.name, tt.opts, tt.expected, ifc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DeleteLink(t *testing.T) {
|
||||||
|
testLinks := []string{"ifc01", "ifc02", "ifc03"}
|
||||||
|
for _, tt := range testLinks {
|
||||||
|
tl := &testLink{}
|
||||||
|
|
||||||
|
if err := tl.prepTestLink(tt, "dummy"); err != nil {
|
||||||
|
t.Skipf("DeleteLink test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.create(); err != nil {
|
||||||
|
t.Fatalf("testLink.setup failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := DeleteLink(tt); err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Failed to delete %s interface: %s", tt, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
i, _ := net.InterfaceByName(tt)
|
||||||
|
if i != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("DeleteLink(%s) expected: nil, returned: %v", tt, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,185 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/libcontainer-milosgajdos83/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Supported macvlan modes by tenus package
|
||||||
|
var MacVlanModes = map[string]bool{
|
||||||
|
"private": true,
|
||||||
|
"vepa": true,
|
||||||
|
"bridge": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// MacVlanOptions allows you to specify some options for macvlan link.
|
||||||
|
type MacVlanOptions struct {
|
||||||
|
// macvlan device name
|
||||||
|
MacVlanDev string
|
||||||
|
// macvlan mode
|
||||||
|
Mode string
|
||||||
|
// MAC address
|
||||||
|
MacAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MacVlaner embeds Linker interface and adds few more functions.
|
||||||
|
type MacVlaner interface {
|
||||||
|
// Linker interface
|
||||||
|
Linker
|
||||||
|
// MasterNetInterface returns macvlan master network device
|
||||||
|
MasterNetInterface() *net.Interface
|
||||||
|
// Mode returns macvlan link's network mode
|
||||||
|
Mode() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MacVlanLink is Link which has a master network device and operates in
|
||||||
|
// a given network mode. It implements MacVlaner interface.
|
||||||
|
type MacVlanLink struct {
|
||||||
|
Link
|
||||||
|
// Master device logical network interface
|
||||||
|
masterIfc *net.Interface
|
||||||
|
// macvlan operatio nmode
|
||||||
|
mode string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMacVlanLink creates macvlan network link
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name mc${RANDOM STRING} link ${master interface} type macvlan mode ${mode}
|
||||||
|
// NewMacVlanLink returns MacVlaner which is initialized to a pointer of type MacVlanLink if the
|
||||||
|
// macvlan link was created successfully on the Linux host. Newly created link is assigned
|
||||||
|
// a random name starting with "mc". It sets the macvlan mode the parameter passed as argument.
|
||||||
|
// If incorrect network mode is passed as a paramter, it sets the macvlan mode to "bridge".
|
||||||
|
// It returns error if the link could not be created.
|
||||||
|
func NewMacVlanLink(masterDev string, mode string) (MacVlaner, error) {
|
||||||
|
macVlanDev := makeNetInterfaceName("mc")
|
||||||
|
|
||||||
|
if ok, err := NetInterfaceNameValid(masterDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(masterDev); err != nil {
|
||||||
|
return nil, fmt.Errorf("Master MAC VLAN device %s does not exist on the host", masterDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode != "" {
|
||||||
|
if _, ok := MacVlanModes[mode]; !ok {
|
||||||
|
return nil, fmt.Errorf("Unsupported MacVlan mode specified: %s", mode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mode = "bridge"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddMacVlan(masterDev, macVlanDev, mode); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
macVlanIfc, err := net.InterfaceByName(macVlanDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
masterIfc, err := net.InterfaceByName(masterDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MacVlanLink{
|
||||||
|
Link: Link{
|
||||||
|
ifc: macVlanIfc,
|
||||||
|
},
|
||||||
|
masterIfc: masterIfc,
|
||||||
|
mode: mode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMacVlanLinkWithOptions creates macvlan network link and sets som of its network parameters
|
||||||
|
// passed in as MacVlanOptions.
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name ${macvlan name} link ${master interface} address ${macaddress} type macvlan mode ${mode}
|
||||||
|
// NewMacVlanLinkWithOptions returns MacVlaner which is initialized to a pointer of type MacVlanLink if the
|
||||||
|
// macvlan link was created successfully on the Linux host. It returns error if the macvlan link could not be created.
|
||||||
|
func NewMacVlanLinkWithOptions(masterDev string, opts MacVlanOptions) (MacVlaner, error) {
|
||||||
|
macVlanDev := opts.MacVlanDev
|
||||||
|
mode := opts.Mode
|
||||||
|
macaddr := opts.MacAddr
|
||||||
|
|
||||||
|
if ok, err := NetInterfaceNameValid(masterDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(masterDev); err != nil {
|
||||||
|
return nil, fmt.Errorf("Master MAC VLAN device %s does not exist on the host", masterDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if macVlanDev != "" {
|
||||||
|
if ok, err := NetInterfaceNameValid(macVlanDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(macVlanDev); err == nil {
|
||||||
|
return nil, fmt.Errorf("MAC VLAN device %s already assigned on the host", macVlanDev)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
macVlanDev = makeNetInterfaceName("mc")
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode != "" {
|
||||||
|
if _, ok := MacVlanModes[mode]; !ok {
|
||||||
|
return nil, fmt.Errorf("Unsupported MacVlan mode specified: %s", mode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mode = "bridge"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddMacVlan(masterDev, macVlanDev, opts.Mode); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
macVlanIfc, err := net.InterfaceByName(macVlanDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if macaddr != "" {
|
||||||
|
if _, err = net.ParseMAC(macaddr); err == nil {
|
||||||
|
if err := netlink.NetworkSetMacAddress(macVlanIfc, macaddr); err != nil {
|
||||||
|
if errDel := DeleteLink(macVlanIfc.Name); err != nil {
|
||||||
|
return nil, fmt.Errorf("Incorrect options specified. Attempt to delete the link failed: %s",
|
||||||
|
errDel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
masterIfc, err := net.InterfaceByName(masterDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MacVlanLink{
|
||||||
|
Link: Link{
|
||||||
|
ifc: macVlanIfc,
|
||||||
|
},
|
||||||
|
masterIfc: masterIfc,
|
||||||
|
mode: mode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterface returns macvlan link's network interface
|
||||||
|
func (macvln *MacVlanLink) NetInterface() *net.Interface {
|
||||||
|
return macvln.ifc
|
||||||
|
}
|
||||||
|
|
||||||
|
// MasterNetInterface returns macvlan link's master network interface
|
||||||
|
func (macvln *MacVlanLink) MasterNetInterface() *net.Interface {
|
||||||
|
return macvln.masterIfc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode returns macvlan link's network operation mode
|
||||||
|
func (macvln *MacVlanLink) Mode() string {
|
||||||
|
return macvln.mode
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type macvlnTest struct {
|
||||||
|
masterDev string
|
||||||
|
macvlanMode string
|
||||||
|
}
|
||||||
|
|
||||||
|
var macvlnTests = []macvlnTest{
|
||||||
|
{"master01", "bridge"},
|
||||||
|
{"master02", "private"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NewMacVlanLink(t *testing.T) {
|
||||||
|
for _, tt := range macvlnTests {
|
||||||
|
tl := &testLink{}
|
||||||
|
|
||||||
|
if err := tl.prepTestLink(tt.masterDev, "dummy"); err != nil {
|
||||||
|
t.Skipf("NewMacVlanLink test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.create(); err != nil {
|
||||||
|
t.Fatalf("testLink.create failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
mvln, err := NewMacVlanLink(tt.masterDev, tt.macvlanMode)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewMacVlanLink(%s, %s) failed to run: %s", tt.masterDev, tt.macvlanMode, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mvlnName := mvln.NetInterface().Name
|
||||||
|
if _, err := net.InterfaceByName(mvlnName); err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Could not find %s on the host: %s", mvlnName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testRes, err := linkInfo(mvlnName, "macvlan")
|
||||||
|
if err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Failed to list %s operation mode: %s", mvlnName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testRes.linkType != "macvlan" {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewMacVlanLink(%s, %s) failed: expected macvlan, returned %s",
|
||||||
|
tt.masterDev, tt.macvlanMode, testRes.linkType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testRes.linkData != tt.macvlanMode {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewMacVlanLink(%s, %s) failed: expected %s, returned %s",
|
||||||
|
tt.masterDev, tt.macvlanMode, tt.macvlanMode, testRes.linkData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.teardown(); err != nil {
|
||||||
|
t.Fatalf("testLink.teardown failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/milosgajdos83/libcontainer-milosgajdos83/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetworkOptions struct {
|
||||||
|
IpAddr string
|
||||||
|
Gw string
|
||||||
|
Routes []netlink.Route
|
||||||
|
}
|
@ -0,0 +1,209 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/libcontainer-milosgajdos83/system"
|
||||||
|
"github.com/milosgajdos83/libcontainer-milosgajdos83/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VethOptions allows you to specify options for veth link.
|
||||||
|
type VethOptions struct {
|
||||||
|
// Veth pair's peer interface name
|
||||||
|
PeerName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vether embeds Linker interface and adds few more functions mostly to handle peer link interface
|
||||||
|
type Vether interface {
|
||||||
|
// Linker interface
|
||||||
|
Linker
|
||||||
|
// PeerNetInterface returns peer network interface
|
||||||
|
PeerNetInterface() *net.Interface
|
||||||
|
// SetPeerLinkUp sets peer link up - which also brings up the other peer in VethPair
|
||||||
|
SetPeerLinkUp() error
|
||||||
|
// DeletePeerLink deletes peer link - this also deletes the other peer in VethPair
|
||||||
|
DeletePeerLink() error
|
||||||
|
// SetPeerLinkIp configures peer link's IP address
|
||||||
|
SetPeerLinkIp(net.IP, *net.IPNet) error
|
||||||
|
// SetPeerLinkNsToDocker sends peer link into Docker
|
||||||
|
SetPeerLinkNsToDocker(string, string) error
|
||||||
|
// SetPeerLinkNsPid sends peer link into container specified by PID
|
||||||
|
SetPeerLinkNsPid(int) error
|
||||||
|
// SetPeerLinkNsFd sends peer link into container specified by path
|
||||||
|
SetPeerLinkNsFd(string) error
|
||||||
|
// SetPeerLinkNetInNs configures peer link's IP network in network namespace specified by PID
|
||||||
|
SetPeerLinkNetInNs(int, net.IP, *net.IPNet, *net.IP) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// VethPair is a Link. Veth links are created in pairs called peers.
|
||||||
|
type VethPair struct {
|
||||||
|
Link
|
||||||
|
// Peer network interface
|
||||||
|
peerIfc *net.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVethPair creates a pair of veth network links.
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name veth${RANDOM STRING} type veth peer name veth${RANDOM STRING}.
|
||||||
|
// NewVethPair returns Vether which is initialized to a pointer of type VethPair if the
|
||||||
|
// veth link was successfully created on Linux host. Newly created pair of veth links
|
||||||
|
// are assigned random names starting with "veth".
|
||||||
|
// NewVethPair returns error if the veth pair could not be created.
|
||||||
|
func NewVethPair() (Vether, error) {
|
||||||
|
ifcName := makeNetInterfaceName("veth")
|
||||||
|
peerName := makeNetInterfaceName("veth")
|
||||||
|
|
||||||
|
if err := netlink.NetworkCreateVethPair(ifcName, peerName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerIfc, err := net.InterfaceByName(peerName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VethPair{
|
||||||
|
Link: Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
},
|
||||||
|
peerIfc: peerIfc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVethPairWithOptions creates a pair of veth network links.
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name ${first device name} type veth peer name ${second device name}
|
||||||
|
// NewVethPairWithOptions returns Vether which is initialized to a pointer of type VethPair if the
|
||||||
|
// veth link was successfully created on the Linux host. It accepts VethOptions which allow you to set
|
||||||
|
// peer interface name. It returns error if the veth pair could not be created.
|
||||||
|
func NewVethPairWithOptions(ifcName string, opts VethOptions) (Vether, error) {
|
||||||
|
peerName := opts.PeerName
|
||||||
|
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(ifcName); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", ifcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if peerName != "" {
|
||||||
|
if ok, err := NetInterfaceNameValid(peerName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(peerName); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", peerName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
peerName = makeNetInterfaceName("veth")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkCreateVethPair(ifcName, peerName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerIfc, err := net.InterfaceByName(peerName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VethPair{
|
||||||
|
Link: Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
},
|
||||||
|
peerIfc: peerIfc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterface returns veth link's primary network interface
|
||||||
|
func (veth *VethPair) NetInterface() *net.Interface {
|
||||||
|
return veth.ifc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterface returns veth link's peer network interface
|
||||||
|
func (veth *VethPair) PeerNetInterface() *net.Interface {
|
||||||
|
return veth.peerIfc
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkUp sets peer link up
|
||||||
|
func (veth *VethPair) SetPeerLinkUp() error {
|
||||||
|
return netlink.NetworkLinkUp(veth.peerIfc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePeerLink deletes peer link. It also deletes the other peer interface in VethPair
|
||||||
|
func (veth *VethPair) DeletePeerLink() error {
|
||||||
|
return netlink.NetworkLinkDel(veth.peerIfc.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkIp configures peer link's IP address
|
||||||
|
func (veth *VethPair) SetPeerLinkIp(ip net.IP, nw *net.IPNet) error {
|
||||||
|
return netlink.NetworkLinkAddIp(veth.peerIfc, ip, nw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkNsToDocker sends peer link into Docker
|
||||||
|
func (veth *VethPair) SetPeerLinkNsToDocker(name string, dockerHost string) error {
|
||||||
|
pid, err := DockerPidByName(name, dockerHost)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to find docker %s : %s", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netlink.NetworkSetNsPid(veth.peerIfc, pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkNsPid sends peer link into container specified by PID
|
||||||
|
func (veth *VethPair) SetPeerLinkNsPid(nspid int) error {
|
||||||
|
return netlink.NetworkSetNsPid(veth.peerIfc, nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkNsFd sends peer link into container specified by path
|
||||||
|
func (veth *VethPair) SetPeerLinkNsFd(nspath string) error {
|
||||||
|
fd, err := syscall.Open(nspath, syscall.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Could not attach to Network namespace: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netlink.NetworkSetNsFd(veth.peerIfc, fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkNetInNs configures peer link's IP network in network namespace specified by PID
|
||||||
|
func (veth *VethPair) SetPeerLinkNetInNs(nspid int, ip net.IP, network *net.IPNet, gw *net.IP) error {
|
||||||
|
origNs, _ := NetNsHandle(os.Getpid())
|
||||||
|
defer syscall.Close(int(origNs))
|
||||||
|
defer system.Setns(origNs, syscall.CLONE_NEWNET)
|
||||||
|
|
||||||
|
if err := SetNetNsToPid(nspid); err != nil {
|
||||||
|
return fmt.Errorf("Setting network namespace failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddIp(veth.peerIfc, ip, network); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set IP: %s in pid: %d network namespace", ip.String(), nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkUp(veth.peerIfc); err != nil {
|
||||||
|
return fmt.Errorf("Unable to bring %s interface UP: %s", veth.peerIfc.Name, nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gw != nil {
|
||||||
|
if err := netlink.AddDefaultGw(gw.String(), veth.peerIfc.Name); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set Default gateway: %s in pid: %d network namespace", gw.String(), nspid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type vethTest struct {
|
||||||
|
hostIfc string
|
||||||
|
vethOptions VethOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NewVethPair(t *testing.T) {
|
||||||
|
veth, err := NewVethPair()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewVethPair() failed to run: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vethIfcName := veth.NetInterface().Name
|
||||||
|
vethPeerName := veth.PeerNetInterface().Name
|
||||||
|
|
||||||
|
tl := &testLink{}
|
||||||
|
if err := tl.prepTestLink(vethIfcName, ""); err != nil {
|
||||||
|
t.Skipf("NewVethPair test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(vethIfcName); err != nil {
|
||||||
|
t.Fatalf("Could not find %s on the host: %s", vethIfcName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(vethPeerName); err != nil {
|
||||||
|
t.Fatalf("Could not find %s on the host: %s", vethPeerName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testRes, err := linkInfo(vethIfcName, "veth")
|
||||||
|
if err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Failed to list %s operation mode: %s", vethIfcName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testRes.linkType != "veth" {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewVethPair() failed: expected linktype veth, returned %s", testRes.linkType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.teardown(); err != nil {
|
||||||
|
t.Fatalf("testLink.teardown failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var vethOptionTests = []vethTest{
|
||||||
|
{"vethHost01", VethOptions{"vethGuest01"}},
|
||||||
|
{"vethHost02", VethOptions{"vethGuest02"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NewVethPairWithOptions(t *testing.T) {
|
||||||
|
for _, tt := range vethOptionTests {
|
||||||
|
tl := &testLink{}
|
||||||
|
|
||||||
|
if err := tl.prepTestLink(tt.hostIfc, ""); err != nil {
|
||||||
|
t.Skipf("NewVlanLink test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
veth, err := NewVethPairWithOptions(tt.hostIfc, tt.vethOptions)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewVethPairWithOptions(%s, %v) failed to run: %s", tt.hostIfc, tt.vethOptions, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(tt.hostIfc); err != nil {
|
||||||
|
t.Fatalf("Could not find %s on the host: %s", tt.hostIfc, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(tt.vethOptions.PeerName); err != nil {
|
||||||
|
t.Fatalf("Could not find %s on the host: %s", tt.vethOptions.PeerName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vethIfcName := veth.NetInterface().Name
|
||||||
|
if vethIfcName != tt.hostIfc {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewVethPairWithOptions(%s, %v) failed: expected host ifc %s, returned %s",
|
||||||
|
tt.hostIfc, tt.vethOptions, tt.hostIfc, vethIfcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
vethPeerName := veth.PeerNetInterface().Name
|
||||||
|
if vethPeerName != tt.vethOptions.PeerName {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewVethPairWithOptions(%s, %v) failed: expected peer ifc %s, returned %s",
|
||||||
|
tt.hostIfc, tt.vethOptions, tt.vethOptions.PeerName, vethPeerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
testRes, err := linkInfo(tt.hostIfc, "veth")
|
||||||
|
if err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Failed to list %s operation mode: %s", tt.hostIfc, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testRes.linkType != "veth" {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewVethPairWithOptions(%s, %v) failed: expected linktype veth, returned %s",
|
||||||
|
tt.hostIfc, tt.vethOptions, testRes.linkType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.teardown(); err != nil {
|
||||||
|
t.Fatalf("testLink.teardown failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/milosgajdos83/libcontainer-milosgajdos83/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VlanOptions allows you to specify options for vlan link.
|
||||||
|
type VlanOptions struct {
|
||||||
|
// Name of the vlan device
|
||||||
|
VlanDev string
|
||||||
|
// VLAN tag id
|
||||||
|
Id uint16
|
||||||
|
// MAC address
|
||||||
|
MacAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vlaner is interface which embeds Linker interface and adds few more functions.
|
||||||
|
type Vlaner interface {
|
||||||
|
// Linker interface
|
||||||
|
Linker
|
||||||
|
// MasterNetInterface returns vlan master network interface
|
||||||
|
MasterNetInterface() *net.Interface
|
||||||
|
// Id returns VLAN tag
|
||||||
|
Id() uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// VlanLink is a Link which has a master network device.
|
||||||
|
// Each VlanLink has a VLAN tag id
|
||||||
|
type VlanLink struct {
|
||||||
|
Link
|
||||||
|
// Master device logical network interface
|
||||||
|
masterIfc *net.Interface
|
||||||
|
// VLAN tag
|
||||||
|
id uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVlanLink creates vlan network link.
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name vlan${RANDOM STRING} link ${master interface name} type vlan id ${tag}
|
||||||
|
// NewVlanLink returns Vlaner which is initialized to a pointer of type VlanLink if the
|
||||||
|
// vlan link was successfully created on the Linux host. Newly created link is assigned
|
||||||
|
// a random name starting with "vlan". It returns error if the link can not be created.
|
||||||
|
func NewVlanLink(masterDev string, id uint16) (Vlaner, error) {
|
||||||
|
vlanDev := makeNetInterfaceName("vlan")
|
||||||
|
|
||||||
|
if ok, err := NetInterfaceNameValid(masterDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(masterDev); err != nil {
|
||||||
|
return nil, fmt.Errorf("Master VLAN device %s does not exist on the host", masterDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if id <= 0 {
|
||||||
|
return nil, fmt.Errorf("VLAN id must be a postive Integer: %d", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddVlan(masterDev, vlanDev, id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vlanIfc, err := net.InterfaceByName(vlanDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
masterIfc, err := net.InterfaceByName(masterDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VlanLink{
|
||||||
|
Link: Link{
|
||||||
|
ifc: vlanIfc,
|
||||||
|
},
|
||||||
|
masterIfc: masterIfc,
|
||||||
|
id: id,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVlanLinkWithOptions creates vlan network link and sets some of its network parameters
|
||||||
|
// to values passed in as VlanOptions
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name ${vlan name} link ${master interface} address ${macaddress} type vlan id ${tag}
|
||||||
|
// NewVlanLinkWithOptions returns Vlaner which is initialized to a pointer of type VlanLink if the
|
||||||
|
// vlan link was created successfully on the Linux host. It accepts VlanOptions which allow you to set
|
||||||
|
// link's options. It returns error if the link could not be created.
|
||||||
|
func NewVlanLinkWithOptions(masterDev string, opts VlanOptions) (Vlaner, error) {
|
||||||
|
id := opts.Id
|
||||||
|
macaddr := opts.MacAddr
|
||||||
|
|
||||||
|
if ok, err := NetInterfaceNameValid(masterDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(masterDev); err != nil {
|
||||||
|
return nil, fmt.Errorf("Master VLAN device %s does not exist on the host", masterDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
vlanDev := opts.VlanDev
|
||||||
|
if vlanDev != "" {
|
||||||
|
if ok, err := NetInterfaceNameValid(vlanDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(vlanDev); err == nil {
|
||||||
|
return nil, fmt.Errorf("VLAN device %s already assigned on the host", vlanDev)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("VLAN device name can not be empty!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if id == 0 {
|
||||||
|
return nil, fmt.Errorf("Incorrect VLAN tag specified: %d", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddVlan(masterDev, vlanDev, id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vlanIfc, err := net.InterfaceByName(vlanDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if macaddr != "" {
|
||||||
|
if _, err = net.ParseMAC(macaddr); err == nil {
|
||||||
|
if err := netlink.NetworkSetMacAddress(vlanIfc, macaddr); err != nil {
|
||||||
|
if errDel := DeleteLink(vlanIfc.Name); err != nil {
|
||||||
|
return nil, fmt.Errorf("Incorrect options specified! Attempt to delete the link failed: %s",
|
||||||
|
errDel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
masterIfc, err := net.InterfaceByName(masterDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VlanLink{
|
||||||
|
Link: Link{
|
||||||
|
ifc: vlanIfc,
|
||||||
|
},
|
||||||
|
masterIfc: masterIfc,
|
||||||
|
id: id,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterface returns vlan link's network interface
|
||||||
|
func (vln *VlanLink) NetInterface() *net.Interface {
|
||||||
|
return vln.ifc
|
||||||
|
}
|
||||||
|
|
||||||
|
// MasterNetInterface returns vlan link's master network interface
|
||||||
|
func (vln *VlanLink) MasterNetInterface() *net.Interface {
|
||||||
|
return vln.masterIfc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Id returns vlan link's vlan tag id
|
||||||
|
func (vln *VlanLink) Id() uint16 {
|
||||||
|
return vln.id
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type vlnTest struct {
|
||||||
|
masterDev string
|
||||||
|
id uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var vlnTests = []vlnTest{
|
||||||
|
{"master01", 10},
|
||||||
|
{"master02", 20},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NewVlanLink(t *testing.T) {
|
||||||
|
for _, tt := range vlnTests {
|
||||||
|
tl := &testLink{}
|
||||||
|
|
||||||
|
if err := tl.prepTestLink(tt.masterDev, "dummy"); err != nil {
|
||||||
|
t.Skipf("NewVlanLink test requries external command: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.create(); err != nil {
|
||||||
|
t.Fatalf("testLink.create failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
vln, err := NewVlanLink(tt.masterDev, tt.id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewVlanLink(%s, %s) failed to run: %s", tt.masterDev, tt.id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vlnName := vln.NetInterface().Name
|
||||||
|
if _, err := net.InterfaceByName(vlnName); err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Could not find %s on the host: %s", vlnName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testRes, err := linkInfo(vlnName, "vlan")
|
||||||
|
if err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Failed to list %s operation mode: %s", vlnName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if testRes.linkType != "vlan" {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewMacVlanLink(%s, %d) failed: expected vlan, returned %s",
|
||||||
|
tt.masterDev, tt.id, testRes.linkType)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := strconv.Atoi(testRes.linkData)
|
||||||
|
if err != nil {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("Failed to convert link data %s : %s", testRes.linkData, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint16(id) != tt.id {
|
||||||
|
tl.teardown()
|
||||||
|
t.Fatalf("NewMacVlanLink(%s, %d) failed: expected %d, returned %d",
|
||||||
|
tt.masterDev, tt.id, tt.id, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tl.teardown(); err != nil {
|
||||||
|
t.Fatalf("testLink.teardown failed: %v", err)
|
||||||
|
} else {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.0
|
||||||
|
- 1.1
|
||||||
|
- tip
|
@ -0,0 +1,5 @@
|
|||||||
|
Alec Thomas <alec@swapoff.org>
|
||||||
|
Guilhem Lettron <guilhem.lettron@optiflows.com>
|
||||||
|
Ivan Daniluk <ivan.daniluk@gmail.com>
|
||||||
|
Nimi Wariboko Jr <nimi@channelmeter.com>
|
||||||
|
RĂłbert Selvek <robert.selvek@gmail.com>
|
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2013 Ă–rjan Persson. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,89 @@
|
|||||||
|
## Golang logging library
|
||||||
|
|
||||||
|
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/op/go-logging) [![build](https://img.shields.io/travis/op/go-logging.svg?style=flat)](https://travis-ci.org/op/go-logging)
|
||||||
|
|
||||||
|
Package logging implements a logging infrastructure for Go. Its output format
|
||||||
|
is customizable and supports different logging backends like syslog, file and
|
||||||
|
memory. Multiple backends can be utilized with different log levels per backend
|
||||||
|
and logger.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
Let's have a look at an [example](examples/example.go) which demonstrates most
|
||||||
|
of the features found in this library.
|
||||||
|
|
||||||
|
[![Example Output](examples/example.png)](examples/example.go)
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("example")
|
||||||
|
|
||||||
|
// Example format string. Everything except the message has a custom color
|
||||||
|
// which is dependent on the log level. Many fields have a custom output
|
||||||
|
// formatting too, eg. the time returns the hour down to the milli second.
|
||||||
|
var format = logging.MustStringFormatter(
|
||||||
|
"%{color}%{time:15:04:05.000} %{shortfunc} â–¶ %{level:.4s} %{id:03x}%{color:reset} %{message}",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Password is just an example type implementing the Redactor interface. Any
|
||||||
|
// time this is logged, the Redacted() function will be called.
|
||||||
|
type Password string
|
||||||
|
|
||||||
|
func (p Password) Redacted() interface{} {
|
||||||
|
return logging.Redact(string(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// For demo purposes, create two backend for os.Stderr.
|
||||||
|
backend1 := logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
|
backend2 := logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
|
|
||||||
|
// For messages written to backend2 we want to add some additional
|
||||||
|
// information to the output, including the used log level and the name of
|
||||||
|
// the function.
|
||||||
|
backend2Formatter := logging.NewBackendFormatter(backend2, format)
|
||||||
|
|
||||||
|
// Only errors and more severe messages should be sent to backend1
|
||||||
|
backend1Leveled := logging.AddModuleLevel(backend1)
|
||||||
|
backend1Leveled.SetLevel(logging.ERROR, "")
|
||||||
|
|
||||||
|
// Set the backends to be used.
|
||||||
|
logging.SetBackend(backend1Leveled, backend2Formatter)
|
||||||
|
|
||||||
|
log.Debug("debug %s", Password("secret"))
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("err")
|
||||||
|
log.Critical("crit")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
### Using *go get*
|
||||||
|
|
||||||
|
$ go get github.com/op/go-logging
|
||||||
|
|
||||||
|
After this command *go-logging* is ready to use. Its source will be in:
|
||||||
|
|
||||||
|
$GOROOT/src/pkg/github.com/op/go-logging
|
||||||
|
|
||||||
|
You can use `go get -u` to update the package.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
For docs, see http://godoc.org/github.com/op/go-logging or run:
|
||||||
|
|
||||||
|
$ godoc github.com/op/go-logging
|
||||||
|
|
||||||
|
## Additional resources
|
||||||
|
|
||||||
|
* [wslog](https://godoc.org/github.com/cryptix/go/logging/wslog) -- exposes log messages through a WebSocket.
|
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2013, Ă–rjan Persson. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package logging
|
||||||
|
|
||||||
|
// defaultBackend is the backend used for all logging calls.
|
||||||
|
var defaultBackend LeveledBackend
|
||||||
|
|
||||||
|
// Backend is the interface which a log backend need to implement to be able to
|
||||||
|
// be used as a logging backend.
|
||||||
|
type Backend interface {
|
||||||
|
Log(Level, int, *Record) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set backend replaces the backend currently set with the given new logging
|
||||||
|
// backend.
|
||||||
|
func SetBackend(backends ...Backend) LeveledBackend {
|
||||||
|
var backend Backend
|
||||||
|
if len(backends) == 1 {
|
||||||
|
backend = backends[0]
|
||||||
|
} else {
|
||||||
|
backend = MultiLogger(backends...)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultBackend = AddModuleLevel(backend)
|
||||||
|
return defaultBackend
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the logging level for the specified module. The module
|
||||||
|
// corresponds to the string specified in GetLogger.
|
||||||
|
func SetLevel(level Level, module string) {
|
||||||
|
defaultBackend.SetLevel(level, module)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel returns the logging level for the specified module.
|
||||||
|
func GetLevel(module string) Level {
|
||||||
|
return defaultBackend.GetLevel(module)
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package logging
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
// This call is for testing purposes and will set the time to unix epoch.
|
||||||
|
InitForTesting(DEBUG)
|
||||||
|
|
||||||
|
var log = MustGetLogger("example")
|
||||||
|
|
||||||
|
// For demo purposes, create two backend for os.Stdout.
|
||||||
|
//
|
||||||
|
// os.Stderr should most likely be used in the real world but then the
|
||||||
|
// "Output:" check in this example would not work.
|
||||||
|
backend1 := NewLogBackend(os.Stdout, "", 0)
|
||||||
|
backend2 := NewLogBackend(os.Stdout, "", 0)
|
||||||
|
|
||||||
|
// For messages written to backend2 we want to add some additional
|
||||||
|
// information to the output, including the used log level and the name of
|
||||||
|
// the function.
|
||||||
|
var format = MustStringFormatter(
|
||||||
|
"%{time:15:04:05.000} %{shortfunc} %{level:.1s} %{message}",
|
||||||
|
)
|
||||||
|
backend2Formatter := NewBackendFormatter(backend2, format)
|
||||||
|
|
||||||
|
// Only errors and more severe messages should be sent to backend2
|
||||||
|
backend2Leveled := AddModuleLevel(backend2Formatter)
|
||||||
|
backend2Leveled.SetLevel(ERROR, "")
|
||||||
|
|
||||||
|
// Set the backends to be used and the default level.
|
||||||
|
SetBackend(backend1, backend2Leveled)
|
||||||
|
|
||||||
|
log.Debug("debug %s", "arg")
|
||||||
|
log.Error("error")
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// debug arg
|
||||||
|
// error
|
||||||
|
// 00:00:00.000 Example E error
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/op/go-logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logging.MustGetLogger("example")
|
||||||
|
|
||||||
|
// Example format string. Everything except the message has a custom color
|
||||||
|
// which is dependent on the log level. Many fields have a custom output
|
||||||
|
// formatting too, eg. the time returns the hour down to the milli second.
|
||||||
|
var format = logging.MustStringFormatter(
|
||||||
|
"%{color}%{time:15:04:05.000} %{shortfunc} â–¶ %{level:.4s} %{id:03x}%{color:reset} %{message}",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Password is just an example type implementing the Redactor interface. Any
|
||||||
|
// time this is logged, the Redacted() function will be called.
|
||||||
|
type Password string
|
||||||
|
|
||||||
|
func (p Password) Redacted() interface{} {
|
||||||
|
return logging.Redact(string(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// For demo purposes, create two backend for os.Stderr.
|
||||||
|
backend1 := logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
|
backend2 := logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
|
|
||||||
|
// For messages written to backend2 we want to add some additional
|
||||||
|
// information to the output, including the used log level and the name of
|
||||||
|
// the function.
|
||||||
|
backend2Formatter := logging.NewBackendFormatter(backend2, format)
|
||||||
|
|
||||||
|
// Only errors and more severe messages should be sent to backend1
|
||||||
|
backend1Leveled := logging.AddModuleLevel(backend1)
|
||||||
|
backend1Leveled.SetLevel(logging.ERROR, "")
|
||||||
|
|
||||||
|
// Set the backends to be used.
|
||||||
|
logging.SetBackend(backend1Leveled, backend2Formatter)
|
||||||
|
|
||||||
|
log.Debug("debug %s", Password("secret"))
|
||||||
|
log.Info("info")
|
||||||
|
log.Notice("notice")
|
||||||
|
log.Warning("warning")
|
||||||
|
log.Error("err")
|
||||||
|
log.Critical("crit")
|
||||||
|
}
|
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,368 @@
|
|||||||
|
// Copyright 2013, Ă–rjan Persson. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO see Formatter interface in fmt/print.go
|
||||||
|
// TODO try text/template, maybe it have enough performance
|
||||||
|
// TODO other template systems?
|
||||||
|
// TODO make it possible to specify formats per backend?
|
||||||
|
type fmtVerb int
|
||||||
|
|
||||||
|
const (
|
||||||
|
fmtVerbTime fmtVerb = iota
|
||||||
|
fmtVerbLevel
|
||||||
|
fmtVerbId
|
||||||
|
fmtVerbPid
|
||||||
|
fmtVerbProgram
|
||||||
|
fmtVerbModule
|
||||||
|
fmtVerbMessage
|
||||||
|
fmtVerbLongfile
|
||||||
|
fmtVerbShortfile
|
||||||
|
fmtVerbLongpkg
|
||||||
|
fmtVerbShortpkg
|
||||||
|
fmtVerbLongfunc
|
||||||
|
fmtVerbShortfunc
|
||||||
|
fmtVerbLevelColor
|
||||||
|
|
||||||
|
// Keep last, there are no match for these below.
|
||||||
|
fmtVerbUnknown
|
||||||
|
fmtVerbStatic
|
||||||
|
)
|
||||||
|
|
||||||
|
var fmtVerbs = []string{
|
||||||
|
"time",
|
||||||
|
"level",
|
||||||
|
"id",
|
||||||
|
"pid",
|
||||||
|
"program",
|
||||||
|
"module",
|
||||||
|
"message",
|
||||||
|
"longfile",
|
||||||
|
"shortfile",
|
||||||
|
"longpkg",
|
||||||
|
"shortpkg",
|
||||||
|
"longfunc",
|
||||||
|
"shortfunc",
|
||||||
|
"color",
|
||||||
|
}
|
||||||
|
|
||||||
|
const rfc3339Milli = "2006-01-02T15:04:05.999Z07:00"
|
||||||
|
|
||||||
|
var defaultVerbsLayout = []string{
|
||||||
|
rfc3339Milli,
|
||||||
|
"s",
|
||||||
|
"d",
|
||||||
|
"d",
|
||||||
|
"s",
|
||||||
|
"s",
|
||||||
|
"s",
|
||||||
|
"s",
|
||||||
|
"s",
|
||||||
|
"s",
|
||||||
|
"s",
|
||||||
|
"s",
|
||||||
|
"s",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
pid = os.Getpid()
|
||||||
|
program = filepath.Base(os.Args[0])
|
||||||
|
)
|
||||||
|
|
||||||
|
func getFmtVerbByName(name string) fmtVerb {
|
||||||
|
for i, verb := range fmtVerbs {
|
||||||
|
if name == verb {
|
||||||
|
return fmtVerb(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmtVerbUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatter is the required interface for a custom log record formatter.
|
||||||
|
type Formatter interface {
|
||||||
|
Format(calldepth int, r *Record, w io.Writer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatter is used by all backends unless otherwise overriden.
|
||||||
|
var formatter struct {
|
||||||
|
sync.RWMutex
|
||||||
|
def Formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFormatter() Formatter {
|
||||||
|
formatter.RLock()
|
||||||
|
defer formatter.RUnlock()
|
||||||
|
return formatter.def
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultFormatter is the default formatter used and is only the message.
|
||||||
|
DefaultFormatter Formatter = MustStringFormatter("%{message}")
|
||||||
|
|
||||||
|
// Glog format
|
||||||
|
GlogFormatter Formatter = MustStringFormatter("%{level:.1s}%{time:0102 15:04:05.999999} %{pid} %{shortfile}] %{message}")
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetFormatter sets the default formatter for all new backends. A backend will
|
||||||
|
// fetch this value once it is needed to format a record. Note that backends
|
||||||
|
// will cache the formatter after the first point. For now, make sure to set
|
||||||
|
// the formatter before logging.
|
||||||
|
func SetFormatter(f Formatter) {
|
||||||
|
formatter.Lock()
|
||||||
|
defer formatter.Unlock()
|
||||||
|
formatter.def = f
|
||||||
|
}
|
||||||
|
|
||||||
|
var formatRe *regexp.Regexp = regexp.MustCompile(`%{([a-z]+)(?::(.*?[^\\]))?}`)
|
||||||
|
|
||||||
|
type part struct {
|
||||||
|
verb fmtVerb
|
||||||
|
layout string
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringFormatter contains a list of parts which explains how to build the
|
||||||
|
// formatted string passed on to the logging backend.
|
||||||
|
type stringFormatter struct {
|
||||||
|
parts []part
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStringFormatter returns a new Formatter which outputs the log record as a
|
||||||
|
// string based on the 'verbs' specified in the format string.
|
||||||
|
//
|
||||||
|
// The verbs:
|
||||||
|
//
|
||||||
|
// General:
|
||||||
|
// %{id} Sequence number for log message (uint64).
|
||||||
|
// %{pid} Process id (int)
|
||||||
|
// %{time} Time when log occurred (time.Time)
|
||||||
|
// %{level} Log level (Level)
|
||||||
|
// %{module} Module (string)
|
||||||
|
// %{program} Basename of os.Args[0] (string)
|
||||||
|
// %{message} Message (string)
|
||||||
|
// %{longfile} Full file name and line number: /a/b/c/d.go:23
|
||||||
|
// %{shortfile} Final file name element and line number: d.go:23
|
||||||
|
// %{color} ANSI color based on log level
|
||||||
|
//
|
||||||
|
// For normal types, the output can be customized by using the 'verbs' defined
|
||||||
|
// in the fmt package, eg. '%{id:04d}' to make the id output be '%04d' as the
|
||||||
|
// format string.
|
||||||
|
//
|
||||||
|
// For time.Time, use the same layout as time.Format to change the time format
|
||||||
|
// when output, eg "2006-01-02T15:04:05.999Z-07:00".
|
||||||
|
//
|
||||||
|
// For the 'color' verb, the output can be adjusted to either use bold colors,
|
||||||
|
// i.e., '%{color:bold}' or to reset the ANSI attributes, i.e.,
|
||||||
|
// '%{color:reset}' Note that if you use the color verb explicitly, be sure to
|
||||||
|
// reset it or else the color state will persist past your log message. e.g.,
|
||||||
|
// "%{color:bold}%{time:15:04:05} %{level:-8s}%{color:reset} %{message}" will
|
||||||
|
// just colorize the time and level, leaving the message uncolored.
|
||||||
|
//
|
||||||
|
// There's also a couple of experimental 'verbs'. These are exposed to get
|
||||||
|
// feedback and needs a bit of tinkering. Hence, they might change in the
|
||||||
|
// future.
|
||||||
|
//
|
||||||
|
// Experimental:
|
||||||
|
// %{longpkg} Full package path, eg. github.com/go-logging
|
||||||
|
// %{shortpkg} Base package path, eg. go-logging
|
||||||
|
// %{longfunc} Full function name, eg. littleEndian.PutUint32
|
||||||
|
// %{shortfunc} Base function name, eg. PutUint32
|
||||||
|
func NewStringFormatter(format string) (*stringFormatter, error) {
|
||||||
|
var fmter = &stringFormatter{}
|
||||||
|
|
||||||
|
// Find the boundaries of all %{vars}
|
||||||
|
matches := formatRe.FindAllStringSubmatchIndex(format, -1)
|
||||||
|
if matches == nil {
|
||||||
|
return nil, errors.New("logger: invalid log format: " + format)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all variables and static text for the format
|
||||||
|
prev := 0
|
||||||
|
for _, m := range matches {
|
||||||
|
start, end := m[0], m[1]
|
||||||
|
if start > prev {
|
||||||
|
fmter.add(fmtVerbStatic, format[prev:start])
|
||||||
|
}
|
||||||
|
|
||||||
|
name := format[m[2]:m[3]]
|
||||||
|
verb := getFmtVerbByName(name)
|
||||||
|
if verb == fmtVerbUnknown {
|
||||||
|
return nil, errors.New("logger: unknown variable: " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle layout customizations or use the default. If this is not for the
|
||||||
|
// time or color formatting, we need to prefix with %.
|
||||||
|
layout := defaultVerbsLayout[verb]
|
||||||
|
if m[4] != -1 {
|
||||||
|
layout = format[m[4]:m[5]]
|
||||||
|
}
|
||||||
|
if verb != fmtVerbTime && verb != fmtVerbLevelColor {
|
||||||
|
layout = "%" + layout
|
||||||
|
}
|
||||||
|
|
||||||
|
fmter.add(verb, layout)
|
||||||
|
prev = end
|
||||||
|
}
|
||||||
|
end := format[prev:]
|
||||||
|
if end != "" {
|
||||||
|
fmter.add(fmtVerbStatic, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a test run to make sure we can format it correctly.
|
||||||
|
t, err := time.Parse(time.RFC3339, "2010-02-04T21:00:57-08:00")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
r := &Record{
|
||||||
|
Id: 12345,
|
||||||
|
Time: t,
|
||||||
|
Module: "logger",
|
||||||
|
fmt: "hello %s",
|
||||||
|
args: []interface{}{"go"},
|
||||||
|
}
|
||||||
|
if err := fmter.Format(0, r, &bytes.Buffer{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustStringFormatter is equivalent to NewStringFormatter with a call to panic
|
||||||
|
// on error.
|
||||||
|
func MustStringFormatter(format string) *stringFormatter {
|
||||||
|
f, err := NewStringFormatter(format)
|
||||||
|
if err != nil {
|
||||||
|
panic("Failed to initialized string formatter: " + err.Error())
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stringFormatter) add(verb fmtVerb, layout string) {
|
||||||
|
f.parts = append(f.parts, part{verb, layout})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *stringFormatter) Format(calldepth int, r *Record, output io.Writer) error {
|
||||||
|
for _, part := range f.parts {
|
||||||
|
if part.verb == fmtVerbStatic {
|
||||||
|
output.Write([]byte(part.layout))
|
||||||
|
} else if part.verb == fmtVerbTime {
|
||||||
|
output.Write([]byte(r.Time.Format(part.layout)))
|
||||||
|
} else if part.verb == fmtVerbLevelColor {
|
||||||
|
if part.layout == "bold" {
|
||||||
|
output.Write([]byte(boldcolors[r.Level]))
|
||||||
|
} else if part.layout == "reset" {
|
||||||
|
output.Write([]byte("\033[0m"))
|
||||||
|
} else {
|
||||||
|
output.Write([]byte(colors[r.Level]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var v interface{}
|
||||||
|
switch part.verb {
|
||||||
|
case fmtVerbLevel:
|
||||||
|
v = r.Level
|
||||||
|
break
|
||||||
|
case fmtVerbId:
|
||||||
|
v = r.Id
|
||||||
|
break
|
||||||
|
case fmtVerbPid:
|
||||||
|
v = pid
|
||||||
|
break
|
||||||
|
case fmtVerbProgram:
|
||||||
|
v = program
|
||||||
|
break
|
||||||
|
case fmtVerbModule:
|
||||||
|
v = r.Module
|
||||||
|
break
|
||||||
|
case fmtVerbMessage:
|
||||||
|
v = r.Message()
|
||||||
|
break
|
||||||
|
case fmtVerbLongfile, fmtVerbShortfile:
|
||||||
|
_, file, line, ok := runtime.Caller(calldepth + 1)
|
||||||
|
if !ok {
|
||||||
|
file = "???"
|
||||||
|
line = 0
|
||||||
|
} else if part.verb == fmtVerbShortfile {
|
||||||
|
file = filepath.Base(file)
|
||||||
|
}
|
||||||
|
v = fmt.Sprintf("%s:%d", file, line)
|
||||||
|
case fmtVerbLongfunc, fmtVerbShortfunc,
|
||||||
|
fmtVerbLongpkg, fmtVerbShortpkg:
|
||||||
|
// TODO cache pc
|
||||||
|
v = "???"
|
||||||
|
if pc, _, _, ok := runtime.Caller(calldepth + 1); ok {
|
||||||
|
if f := runtime.FuncForPC(pc); f != nil {
|
||||||
|
v = formatFuncName(part.verb, f.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unhandled format part")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(output, part.layout, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatFuncName tries to extract certain part of the runtime formatted
|
||||||
|
// function name to some pre-defined variation.
|
||||||
|
//
|
||||||
|
// This function is known to not work properly if the package path or name
|
||||||
|
// contains a dot.
|
||||||
|
func formatFuncName(v fmtVerb, f string) string {
|
||||||
|
i := strings.LastIndex(f, "/")
|
||||||
|
j := strings.Index(f[i+1:], ".")
|
||||||
|
if j < 1 {
|
||||||
|
return "???"
|
||||||
|
}
|
||||||
|
pkg, fun := f[:i+j+1], f[i+j+2:]
|
||||||
|
switch v {
|
||||||
|
case fmtVerbLongpkg:
|
||||||
|
return pkg
|
||||||
|
case fmtVerbShortpkg:
|
||||||
|
return path.Base(pkg)
|
||||||
|
case fmtVerbLongfunc:
|
||||||
|
return fun
|
||||||
|
case fmtVerbShortfunc:
|
||||||
|
i = strings.LastIndex(fun, ".")
|
||||||
|
return fun[i+1:]
|
||||||
|
}
|
||||||
|
panic("unexpected func formatter")
|
||||||
|
}
|
||||||
|
|
||||||
|
// backendFormatter combines a backend with a specific formatter making it
|
||||||
|
// possible to have different log formats for different backends.
|
||||||
|
type backendFormatter struct {
|
||||||
|
b Backend
|
||||||
|
f Formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackendFormatter creates a new backend which makes all records that
|
||||||
|
// passes through it beeing formatted by the specific formatter.
|
||||||
|
func NewBackendFormatter(b Backend, f Formatter) *backendFormatter {
|
||||||
|
return &backendFormatter{b, f}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log implements the Log function required by the Backend interface.
|
||||||
|
func (bf *backendFormatter) Log(level Level, calldepth int, r *Record) error {
|
||||||
|
// Make a shallow copy of the record and replace any formatter
|
||||||
|
r2 := *r
|
||||||
|
r2.formatter = bf.f
|
||||||
|
return bf.b.Log(level, calldepth+1, &r2)
|
||||||
|
}
|
@ -0,0 +1,184 @@
|
|||||||
|
// Copyright 2013, Ă–rjan Persson. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package logging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormat(t *testing.T) {
|
||||||
|
backend := InitForTesting(DEBUG)
|
||||||
|
|
||||||
|
f, err := NewStringFormatter("%{shortfile} %{time:2006-01-02T15:04:05} %{level:.1s} %{id:04d} %{module} %{message}")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to set format: %s", err)
|
||||||
|
}
|
||||||
|
SetFormatter(f)
|
||||||
|
|
||||||
|
log := MustGetLogger("module")
|
||||||
|
log.Debug("hello")
|
||||||
|
|
||||||
|
line := MemoryRecordN(backend, 0).Formatted(0)
|
||||||
|
if "format_test.go:24 1970-01-01T00:00:00 D 0001 module hello" != line {
|
||||||
|
t.Errorf("Unexpected format: %s", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logAndGetLine(backend *MemoryBackend) string {
|
||||||
|
MustGetLogger("foo").Debug("hello")
|
||||||
|
return MemoryRecordN(backend, 0).Formatted(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLastLine(backend *MemoryBackend) string {
|
||||||
|
return MemoryRecordN(backend, 0).Formatted(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func realFunc(backend *MemoryBackend) string {
|
||||||
|
return logAndGetLine(backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
type structFunc struct{}
|
||||||
|
|
||||||
|
func (structFunc) Log(backend *MemoryBackend) string {
|
||||||
|
return logAndGetLine(backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealFuncFormat(t *testing.T) {
|
||||||
|
backend := InitForTesting(DEBUG)
|
||||||
|
SetFormatter(MustStringFormatter("%{shortfunc}"))
|
||||||
|
|
||||||
|
line := realFunc(backend)
|
||||||
|
if "realFunc" != line {
|
||||||
|
t.Errorf("Unexpected format: %s", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructFuncFormat(t *testing.T) {
|
||||||
|
backend := InitForTesting(DEBUG)
|
||||||
|
SetFormatter(MustStringFormatter("%{longfunc}"))
|
||||||
|
|
||||||
|
var x structFunc
|
||||||
|
line := x.Log(backend)
|
||||||
|
if "structFunc.Log" != line {
|
||||||
|
t.Errorf("Unexpected format: %s", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVarFuncFormat(t *testing.T) {
|
||||||
|
backend := InitForTesting(DEBUG)
|
||||||
|
SetFormatter(MustStringFormatter("%{shortfunc}"))
|
||||||
|
|
||||||
|
var varFunc = func() string {
|
||||||
|
return logAndGetLine(backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
line := varFunc()
|
||||||
|
if "???" == line || "TestVarFuncFormat" == line || "varFunc" == line {
|
||||||
|
t.Errorf("Unexpected format: %s", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatFuncName(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
filename string
|
||||||
|
longpkg string
|
||||||
|
shortpkg string
|
||||||
|
longfunc string
|
||||||
|
shortfunc string
|
||||||
|
}{
|
||||||
|
{"",
|
||||||
|
"???",
|
||||||
|
"???",
|
||||||
|
"???",
|
||||||
|
"???"},
|
||||||
|
{"main",
|
||||||
|
"???",
|
||||||
|
"???",
|
||||||
|
"???",
|
||||||
|
"???"},
|
||||||
|
{"main.",
|
||||||
|
"main",
|
||||||
|
"main",
|
||||||
|
"",
|
||||||
|
""},
|
||||||
|
{"main.main",
|
||||||
|
"main",
|
||||||
|
"main",
|
||||||
|
"main",
|
||||||
|
"main"},
|
||||||
|
{"github.com/op/go-logging.func·001",
|
||||||
|
"github.com/op/go-logging",
|
||||||
|
"go-logging",
|
||||||
|
"func·001",
|
||||||
|
"func·001"},
|
||||||
|
{"github.com/op/go-logging.stringFormatter.Format",
|
||||||
|
"github.com/op/go-logging",
|
||||||
|
"go-logging",
|
||||||
|
"stringFormatter.Format",
|
||||||
|
"Format"},
|
||||||
|
}
|
||||||
|
|
||||||
|
var v string
|
||||||
|
for _, test := range tests {
|
||||||
|
v = formatFuncName(fmtVerbLongpkg, test.filename)
|
||||||
|
if test.longpkg != v {
|
||||||
|
t.Errorf("%s != %s", test.longpkg, v)
|
||||||
|
}
|
||||||
|
v = formatFuncName(fmtVerbShortpkg, test.filename)
|
||||||
|
if test.shortpkg != v {
|
||||||
|
t.Errorf("%s != %s", test.shortpkg, v)
|
||||||
|
}
|
||||||
|
v = formatFuncName(fmtVerbLongfunc, test.filename)
|
||||||
|
if test.longfunc != v {
|
||||||
|
t.Errorf("%s != %s", test.longfunc, v)
|
||||||
|
}
|
||||||
|
v = formatFuncName(fmtVerbShortfunc, test.filename)
|
||||||
|
if test.shortfunc != v {
|
||||||
|
t.Errorf("%s != %s", test.shortfunc, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackendFormatter(t *testing.T) {
|
||||||
|
InitForTesting(DEBUG)
|
||||||
|
|
||||||
|
// Create two backends and wrap one of the with a backend formatter
|
||||||
|
b1 := NewMemoryBackend(1)
|
||||||
|
b2 := NewMemoryBackend(1)
|
||||||
|
|
||||||
|
f := MustStringFormatter("%{level} %{message}")
|
||||||
|
bf := NewBackendFormatter(b2, f)
|
||||||
|
|
||||||
|
SetBackend(b1, bf)
|
||||||
|
|
||||||
|
log := MustGetLogger("module")
|
||||||
|
log.Info("foo")
|
||||||
|
if "foo" != getLastLine(b1) {
|
||||||
|
t.Errorf("Unexpected line: %s", getLastLine(b1))
|
||||||
|
}
|
||||||
|
if "INFO foo" != getLastLine(b2) {
|
||||||
|
t.Errorf("Unexpected line: %s", getLastLine(b2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkStringFormatter(b *testing.B) {
|
||||||
|
fmt := "%{time:2006-01-02T15:04:05} %{level:.1s} %{id:04d} %{module} %{message}"
|
||||||
|
f := MustStringFormatter(fmt)
|
||||||
|
|
||||||
|
backend := InitForTesting(DEBUG)
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
log := MustGetLogger("module")
|
||||||
|
log.Debug("")
|
||||||
|
record := MemoryRecordN(backend, 0)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if err := f.Format(1, record, buf); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
buf.Truncate(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue