11package cmd
22
33import (
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+
1529var (
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" ,
2035Version : "1.0.0" ,
2136Run : func (cmd * cobra.Command , args []string ) {
22- prg := & program {}
37+ prg := NewProgram ()
2338s , err := service .New (prg , svcConfig )
39+ prg .service = s
2440if err != nil {
2541fmt .Fprintf (os .Stderr , "[service.New] %v\n " , err )
2642os .Exit (1 )
@@ -33,13 +49,14 @@ var (
3349err = s .Run ()
3450if err != nil {
3551logger .Error (err )
52+ os .Exit (1 )
3653}
3754},
3855}
3956svcConfig = & service.Config {
40- Name : "docker-netns" ,
57+ Name : "docker-netns" ,
4158DisplayName : "Docker network namespace service" ,
42- UserName : "root" ,
59+ UserName : "root" ,
4360Dependencies : []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
54138func (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+ }
56144cli , err := client .NewClientWithOpts (client .WithVersion ("1.40" ))
57145if err != nil {
58146return err
59147}
60- go p .run (cli )
148+ go p .run (config , cli )
61149return 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+
66175f := filters .NewArgs ()
67176f .Add ("event" , "start" )
68- msgs , errs := cli .Events (ctx , types.EventsOptions {Filters : f })
177+ msgs , errs := cli .Events (p . ctx , types.EventsOptions {Filters : f })
69178for {
70179select {
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+ }
73191case err := <- errs :
74- _ = err
192+ process .Signal (syscall .SIGTERM )
193+ p .exited <- err
75194return
76195}
77196}
78197}
79198
80199func (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
85205func Execute () {
0 commit comments