Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NodePort API spec & socat implementation
The API can be used for exposing TCP/UDP ports in the network namespace created along with the user namespace to the host network namespace. * `api.sock` is created under the `rootlesskit --state-dir` directory * API is described in `pkg/api/openapi.yaml` * `GET /v1/ports` * `POST /v1/ports` * `DELETE /v1/ports` * `rootlessctl` CLI can be used as an API client: * `rootlessctl list-ports` * `rootlessctl add-ports` * `rootlessctl remove-ports` * To expose child 80 as parent 8080: `rootlessctl --socket=/path/to/api.sock add-ports 0.0.0.0:8080:80/tcp` Currently, only `socat` implementation is available as `rootlesskit --port-driver`. The code under `pkg/port/socat` package might be also reusable for other projects. Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
- Loading branch information
1 parent
2c13e77
commit e147262
Showing
100 changed files
with
9,870 additions
and
1,189 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
"github.com/urfave/cli" | ||
|
||
"github.com/rootless-containers/rootlesskit/pkg/api/client" | ||
) | ||
|
||
func main() { | ||
debug := false | ||
app := cli.NewApp() | ||
app.Name = "rootlessctl" | ||
app.Usage = "RootlessKit API client" | ||
app.Flags = []cli.Flag{ | ||
cli.BoolFlag{ | ||
Name: "debug", | ||
Usage: "debug mode", | ||
Destination: &debug, | ||
}, | ||
cli.StringFlag{ | ||
Name: "socket", | ||
Usage: "Path to api.sock (under the `rootlesskit --state-dir` directory)", | ||
}, | ||
} | ||
app.Commands = []cli.Command{ | ||
listPortsCommand, | ||
addPortsCommand, | ||
removePortsCommand, | ||
} | ||
app.Before = func(clicontext *cli.Context) error { | ||
if debug { | ||
logrus.SetLevel(logrus.DebugLevel) | ||
} | ||
return nil | ||
} | ||
if err := app.Run(os.Args); err != nil { | ||
if debug { | ||
fmt.Fprintf(os.Stderr, "error: %+v\n", err) | ||
} else { | ||
fmt.Fprintf(os.Stderr, "error: %v\n", err) | ||
} | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func newClient(clicontext *cli.Context) (client.Client, error) { | ||
socketPath := clicontext.GlobalString("socket") | ||
if socketPath == "" { | ||
return nil, errors.New("please specify --socket") | ||
} | ||
return client.New(socketPath) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"strconv" | ||
"text/tabwriter" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/urfave/cli" | ||
|
||
"github.com/rootless-containers/rootlesskit/pkg/port" | ||
"github.com/rootless-containers/rootlesskit/pkg/port/portutil" | ||
) | ||
|
||
var listPortsCommand = cli.Command{ | ||
Name: "list-ports", | ||
Usage: "List ports", | ||
ArgsUsage: "[flags]", | ||
Flags: []cli.Flag{ | ||
cli.BoolFlag{ | ||
Name: "json", | ||
Usage: "Prints as JSON", | ||
}, | ||
}, | ||
Action: listPortsAction, | ||
} | ||
|
||
func listPortsAction(clicontext *cli.Context) error { | ||
c, err := newClient(clicontext) | ||
if err != nil { | ||
return err | ||
} | ||
pm := c.PortManager() | ||
ctx := context.Background() | ||
portStatuses, err := pm.ListPorts(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
if clicontext.Bool("json") { | ||
// Marshal per entry, for consistency with add-ports | ||
// (and for potential streaming support) | ||
for _, p := range portStatuses { | ||
m, err := json.Marshal(p) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Println(string(m)) | ||
} | ||
return nil | ||
} | ||
w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0) | ||
if _, err := fmt.Fprintln(w, "ID\tPROTO\tPARENTIP\tPARENTPORT\tCHILDPORT\t"); err != nil { | ||
return err | ||
} | ||
for _, p := range portStatuses { | ||
if _, err := fmt.Fprintf(w, "%d\t%s\t%s\t%d\t%d\t\n", | ||
p.ID, p.Spec.Proto, p.Spec.ParentIP, p.Spec.ParentPort, p.Spec.ChildPort); err != nil { | ||
return err | ||
} | ||
} | ||
return w.Flush() | ||
} | ||
|
||
var addPortsCommand = cli.Command{ | ||
Name: "add-ports", | ||
Usage: "Add ports", | ||
ArgsUsage: "[flags] PARENTIP:PARENTPORT:CHILDPORT/PROTO [PARENTIP:PARENTPORT:CHILDPORT/PROTO...]", | ||
Description: "Add exposed ports. The port spec is similar to `docker run -p`. e.g. \"127.0.0.1:8080:80/tcp\".", | ||
Flags: []cli.Flag{ | ||
cli.BoolFlag{ | ||
Name: "json", | ||
Usage: "Prints as JSON", | ||
}, | ||
}, | ||
Action: addPortsAction, | ||
} | ||
|
||
func addPortsAction(clicontext *cli.Context) error { | ||
if clicontext.NArg() < 1 { | ||
return errors.New("no port specified") | ||
} | ||
var portSpecs []port.Spec | ||
for _, s := range clicontext.Args() { | ||
sp, err := portutil.ParsePortSpec(s) | ||
if err != nil { | ||
return err | ||
} | ||
portSpecs = append(portSpecs, *sp) | ||
} | ||
|
||
c, err := newClient(clicontext) | ||
if err != nil { | ||
return err | ||
} | ||
pm := c.PortManager() | ||
ctx := context.Background() | ||
for _, sp := range portSpecs { | ||
portStatus, err := pm.AddPort(ctx, sp) | ||
if err != nil { | ||
return err | ||
} | ||
if clicontext.Bool("json") { | ||
m, err := json.Marshal(portStatus) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Println(string(m)) | ||
} else { | ||
fmt.Printf("%d\n", portStatus.ID) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
var removePortsCommand = cli.Command{ | ||
Name: "remove-ports", | ||
Usage: "Remove ports", | ||
ArgsUsage: "[flags] ID [ID...]", | ||
Action: removePortsAction, | ||
} | ||
|
||
func removePortsAction(clicontext *cli.Context) error { | ||
if clicontext.NArg() < 1 { | ||
return errors.New("no ID specified") | ||
} | ||
var ids []int | ||
for _, s := range clicontext.Args() { | ||
id, err := strconv.Atoi(s) | ||
if err != nil { | ||
return err | ||
} | ||
ids = append(ids, id) | ||
} | ||
c, err := newClient(clicontext) | ||
if err != nil { | ||
return err | ||
} | ||
pm := c.PortManager() | ||
ctx := context.Background() | ||
for _, id := range ids { | ||
if err := pm.RemovePort(ctx, id); err != nil { | ||
return err | ||
} | ||
fmt.Printf("%d\n", id) | ||
} | ||
return nil | ||
} |
Oops, something went wrong.