Skip to content

Commit 5ff3fa2

Browse files
committed
First version
1 parent ad745e4 commit 5ff3fa2

File tree

6 files changed

+220
-111
lines changed

6 files changed

+220
-111
lines changed

cmd/root.go

+136-16
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,42 @@
11
package cmd
22

33
import (
4+
"bytes"
45
"context"
6+
yamlConfig "docker-netns/config"
57
"fmt"
68
"github.com/docker/docker/api/types"
79
"github.com/docker/docker/api/types/filters"
810
"github.com/docker/docker/client"
911
"github.com/kardianos/service"
12+
"github.com/kballard/go-shellquote"
1013
"github.com/spf13/cobra"
14+
"github.com/vishvananda/netns"
1115
"log"
1216
"os"
17+
"os/exec"
18+
"path"
19+
"runtime"
20+
"syscall"
1321
)
1422

23+
func init() {
24+
rootCmd.Flags().StringVar(&configPath, "config", path.Join("/opt", appName, "config.yaml"), "YAML configuration file")
25+
}
26+
27+
const appName = "docker-netns"
28+
1529
var (
16-
logger service.Logger
17-
rootCmd = &cobra.Command{
18-
Use: "docker-netns",
19-
Short: "Docker network namespace manager",
30+
configPath string
31+
logger service.Logger
32+
rootCmd = &cobra.Command{
33+
Use: appName,
34+
Short: "Docker network namespace manager",
2035
Version: "1.0.0",
2136
Run: func(cmd *cobra.Command, args []string) {
22-
prg := &program{}
37+
prg := NewProgram()
2338
s, err := service.New(prg, svcConfig)
39+
prg.service = s
2440
if err != nil {
2541
fmt.Fprintf(os.Stderr, "[service.New] %v\n", err)
2642
os.Exit(1)
@@ -33,13 +49,14 @@ var (
3349
err = s.Run()
3450
if err != nil {
3551
logger.Error(err)
52+
os.Exit(1)
3653
}
3754
},
3855
}
3956
svcConfig = &service.Config{
40-
Name: "docker-netns",
57+
Name: "docker-netns",
4158
DisplayName: "Docker network namespace service",
42-
UserName: "root",
59+
UserName: "root",
4360
Dependencies: []string{
4461
"After=network.target syslog.target docker.service",
4562
},
@@ -49,37 +66,140 @@ var (
4966
}
5067
)
5168

52-
type program struct {}
69+
func nsContext(containerID string, callback func() (interface{}, error)) (interface{}, error) {
70+
runtime.LockOSThread()
71+
defer runtime.UnlockOSThread()
72+
originNamespace, err := netns.Get()
73+
if err != nil {
74+
return nil, err
75+
}
76+
defer originNamespace.Close()
77+
cli, err := client.NewClientWithOpts(client.WithVersion("1.40"))
78+
if err != nil {
79+
return nil, err
80+
}
81+
container, err := cli.ContainerInspect(context.Background(), containerID)
82+
if err != nil {
83+
return nil, err
84+
}
85+
ContainerNamespace, err := netns.GetFromPid(container.State.Pid)
86+
if err != nil {
87+
return nil, err
88+
}
89+
defer ContainerNamespace.Close()
90+
err = netns.Set(ContainerNamespace)
91+
if err != nil {
92+
return nil, err
93+
}
94+
res, err := callback()
95+
if err != nil {
96+
return res, err
97+
}
98+
err = netns.Set(originNamespace)
99+
if err != nil {
100+
return nil, err
101+
}
102+
return res, nil
103+
}
104+
105+
func execCommands(containerID string, commands []string) error {
106+
_, err := nsContext(containerID, func() (interface{}, error) {
107+
for _, command := range commands {
108+
command, err := shellquote.Split(command)
109+
if err != nil {
110+
return nil, err
111+
}
112+
execCmd := exec.Command(command[0], command[1:]...)
113+
var stderr bytes.Buffer
114+
execCmd.Stderr = &stderr
115+
err = execCmd.Run()
116+
if err != nil {
117+
return nil, fmt.Errorf("%v: %v", err, stderr.String())
118+
}
119+
}
120+
return nil, nil
121+
})
122+
return err
123+
}
124+
125+
type program struct {
126+
service service.Service
127+
ctx context.Context
128+
cancel context.CancelFunc
129+
exited chan error
130+
}
131+
132+
func NewProgram() *program {
133+
ctx, cancel := context.WithCancel(context.Background())
134+
exited := make(chan error)
135+
return &program{ctx: ctx, cancel: cancel, exited: exited}
136+
}
53137

54138
func (p *program) Start(s service.Service) error {
55139
// Start should not block. Do the actual work async.
140+
config, err := yamlConfig.NewConfig(configPath)
141+
if err != nil {
142+
return err
143+
}
56144
cli, err := client.NewClientWithOpts(client.WithVersion("1.40"))
57145
if err != nil {
58146
return err
59147
}
60-
go p.run(cli)
148+
go p.run(config, cli)
61149
return nil
62150
}
63151

64-
func (p *program) run(cli *client.Client) {
65-
ctx := context.Background()
152+
func (p *program) run(config *yamlConfig.Config, cli *client.Client) {
153+
defer close(p.exited)
154+
process, _ := os.FindProcess(os.Getpid())
155+
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
156+
if err != nil {
157+
process.Signal(syscall.SIGTERM)
158+
p.exited <- err
159+
return
160+
}
161+
for containerID := range *config {
162+
for _, container := range containers {
163+
if containerID == container.ID[:len(containerID)] {
164+
err := execCommands(containerID, (*config)[containerID])
165+
if err != nil {
166+
process.Signal(syscall.SIGTERM)
167+
p.exited <- err
168+
return
169+
}
170+
break
171+
}
172+
}
173+
}
174+
66175
f := filters.NewArgs()
67176
f.Add("event", "start")
68-
msgs, errs := cli.Events(ctx, types.EventsOptions{Filters: f})
177+
msgs, errs := cli.Events(p.ctx, types.EventsOptions{Filters: f})
69178
for {
70179
select {
71-
case msg := <-msgs:
72-
_ = msg
180+
case <-p.ctx.Done():
181+
return
182+
case <-msgs:
183+
for containerID := range *config {
184+
err := execCommands(containerID, (*config)[containerID])
185+
if err != nil {
186+
process.Signal(syscall.SIGTERM)
187+
p.exited <- err
188+
return
189+
}
190+
}
73191
case err := <-errs:
74-
_ = err
192+
process.Signal(syscall.SIGTERM)
193+
p.exited <- err
75194
return
76195
}
77196
}
78197
}
79198

80199
func (p *program) Stop(s service.Service) error {
81200
// Stop should not block. Return with a few seconds.
82-
return nil
201+
p.cancel()
202+
return <-p.exited
83203
}
84204

85205
func Execute() {

cmd/service.go

+68-10
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,88 @@ import (
44
"fmt"
55
"github.com/kardianos/service"
66
"github.com/spf13/cobra"
7+
"io/ioutil"
78
"os"
8-
"strings"
9+
"path"
910
)
1011

1112
func init() {
12-
serviceCmd.Flags().StringP("action", "a", "", strings.Join(service.ControlAction[:], "|"))
13-
serviceCmd.MarkFlagRequired("action")
13+
installCmd.Flags().String("config", "./config.yaml", "YAML configuration file")
14+
serviceCmd.AddCommand(startCmd, stopCmd, restartCmd, installCmd, uninstallCmd)
1415
rootCmd.AddCommand(serviceCmd)
1516
}
1617

17-
var serviceCmd = &cobra.Command{
18-
Use: "service",
19-
Short: "Service management",
20-
Run: func(cmd *cobra.Command, args []string) {
21-
prg := &program{}
18+
func serviceControl(action string) func(*cobra.Command, []string) {
19+
return func(cmd *cobra.Command, args []string) {
20+
prg := NewProgram()
2221
s, err := service.New(prg, svcConfig)
22+
prg.service = s
2323
if err != nil {
2424
fmt.Fprintf(os.Stderr, "[service.New] %v\n", err)
2525
os.Exit(1)
2626
}
27-
err = service.Control(s, cmd.Flag("action").Value.String())
27+
err = service.Control(s, action)
2828
if err != nil {
2929
fmt.Fprintf(os.Stderr, "[service.Control] %v\n", err)
3030
os.Exit(1)
3131
}
32-
},
32+
}
3333
}
34+
35+
var (
36+
serviceCmd = &cobra.Command{
37+
Use: "service",
38+
Short: "Service management",
39+
}
40+
startCmd = &cobra.Command{
41+
Use: "start",
42+
Short: "Start service",
43+
Run: serviceControl("start"),
44+
}
45+
stopCmd = &cobra.Command{
46+
Use: "stop",
47+
Short: "Stop service",
48+
Run: serviceControl("stop"),
49+
}
50+
restartCmd = &cobra.Command{
51+
Use: "restart",
52+
Short: "Restart service",
53+
Run: serviceControl("restart"),
54+
}
55+
installCmd = &cobra.Command{
56+
Use: "install",
57+
Short: "Install service",
58+
Run: func(cmd *cobra.Command, args []string) {
59+
// TODO: transaction
60+
err := os.Mkdir(path.Join("/opt", appName), 0755)
61+
if err != nil {
62+
fmt.Fprintf(os.Stderr, "[os.Mkdir] %v\n", err)
63+
os.Exit(1)
64+
}
65+
bytes, err := ioutil.ReadFile(cmd.Flag("config").Value.String())
66+
if err != nil {
67+
fmt.Fprintf(os.Stderr, "[ioutil.ReadFile] %v\n", err)
68+
os.Exit(1)
69+
}
70+
err = ioutil.WriteFile(path.Join("/opt", appName, "config.yaml"), bytes, 0644)
71+
if err != nil {
72+
fmt.Fprintf(os.Stderr, "[ioutil.WriteFile] %v\n", err)
73+
os.Exit(1)
74+
}
75+
serviceControl("install")(cmd, args)
76+
},
77+
}
78+
uninstallCmd = &cobra.Command{
79+
Use: "uninstall",
80+
Short: "Uninstall service",
81+
Run: func(cmd *cobra.Command, args []string) {
82+
// TODO: transaction
83+
err := os.RemoveAll(path.Join("/opt", appName))
84+
if err != nil {
85+
fmt.Fprintf(os.Stderr, "[ioutil.RemoveAll] %v\n", err)
86+
os.Exit(1)
87+
}
88+
serviceControl("uninstall")(cmd, args)
89+
},
90+
}
91+
)

cmd/shell.go

+11-44
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
package cmd
22

33
import (
4-
"context"
54
"fmt"
6-
"github.com/docker/docker/client"
75
"github.com/spf13/cobra"
8-
"github.com/vishvananda/netns"
96
"os"
107
"os/exec"
11-
"runtime"
128
)
139

1410
func init() {
@@ -21,49 +17,20 @@ var shellCmd = &cobra.Command{
2117
Use: "shell",
2218
Short: "Start a shell in a specific network namespace",
2319
Run: func(cmd *cobra.Command, args []string) {
24-
runtime.LockOSThread()
25-
defer runtime.UnlockOSThread()
26-
originNamespace, err := netns.Get()
27-
if err != nil {
28-
fmt.Fprintf(os.Stderr, "[netns.Get] %v\n", err)
29-
os.Exit(1)
30-
}
31-
defer originNamespace.Close()
32-
cli, err := client.NewClientWithOpts(client.WithVersion("1.40"))
33-
if err != nil {
34-
fmt.Fprintf(os.Stderr, "[client.NewClientWithOpts] %v\n", err)
35-
os.Exit(1)
36-
}
37-
container, err := cli.ContainerInspect(context.Background(), cmd.Flag("container").Value.String())
38-
if err != nil {
39-
fmt.Fprintf(os.Stderr, "[cli.ContainerInspect] %v\n", err)
40-
os.Exit(1)
41-
}
42-
println(container.State.Pid)
43-
ContainerNamespace, err := netns.GetFromPid(container.State.Pid)
44-
if err != nil {
45-
fmt.Fprintf(os.Stderr, "[netns.GetFromPid] %v\n", err)
46-
os.Exit(1)
47-
}
48-
defer ContainerNamespace.Close()
49-
err = netns.Set(ContainerNamespace)
50-
if err != nil {
51-
fmt.Fprintf(os.Stderr, "[netns.Set] %v\n", err)
52-
os.Exit(1)
53-
}
54-
shell := exec.Command(os.Getenv("SHELL"))
55-
shell.Stdin = os.Stdin
56-
shell.Stdout = os.Stdout
57-
shell.Stderr = os.Stderr
58-
err = shell.Run()
20+
_, err := nsContext(cmd.Flag("container").Value.String(), func() (interface{}, error) {
21+
shell := exec.Command(os.Getenv("SHELL"))
22+
shell.Stdin = os.Stdin
23+
shell.Stdout = os.Stdout
24+
shell.Stderr = os.Stderr
25+
err := shell.Run()
26+
if err != nil {
27+
return nil, err
28+
}
29+
return nil, nil
30+
})
5931
if err != nil {
6032
fmt.Fprintf(os.Stderr, "[shell.Run] %v\n", err)
6133
os.Exit(1)
6234
}
63-
err = netns.Set(originNamespace)
64-
if err != nil {
65-
fmt.Fprintf(os.Stderr, "[netns.Set] %v\n", err)
66-
os.Exit(1)
67-
}
6835
},
6936
}

config.yaml

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
d8b71122deaa:
2-
- ip route add default via 192.168.1.254 dev eth0
3-
1+
360f9c13f2ab9:
2+
- iptables -P OUTPUT DROP

0 commit comments

Comments
 (0)