master
brl 9 years ago
parent 17436d71e4
commit ea2034fc45

40
Godeps/Godeps.json generated

@ -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"
}
]
}

5
Godeps/Readme generated

@ -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.

2
Godeps/_workspace/.gitignore generated vendored

@ -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)
}
}
}
}

@ -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,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
}

@ -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
}

@ -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
}

@ -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)
}
}

@ -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)
}
}

@ -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)
}
}

@ -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)
}
}

@ -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")
}

Binary file not shown.

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…
Cancel
Save