1
1
package cmd
2
2
3
3
import (
4
+ "bytes"
4
5
"context"
6
+ yamlConfig "docker-netns/config"
5
7
"fmt"
6
8
"github.com/docker/docker/api/types"
7
9
"github.com/docker/docker/api/types/filters"
8
10
"github.com/docker/docker/client"
9
11
"github.com/kardianos/service"
12
+ "github.com/kballard/go-shellquote"
10
13
"github.com/spf13/cobra"
14
+ "github.com/vishvananda/netns"
11
15
"log"
12
16
"os"
17
+ "os/exec"
18
+ "path"
19
+ "runtime"
20
+ "syscall"
13
21
)
14
22
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
+
15
29
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" ,
20
35
Version : "1.0.0" ,
21
36
Run : func (cmd * cobra.Command , args []string ) {
22
- prg := & program {}
37
+ prg := NewProgram ()
23
38
s , err := service .New (prg , svcConfig )
39
+ prg .service = s
24
40
if err != nil {
25
41
fmt .Fprintf (os .Stderr , "[service.New] %v\n " , err )
26
42
os .Exit (1 )
@@ -33,13 +49,14 @@ var (
33
49
err = s .Run ()
34
50
if err != nil {
35
51
logger .Error (err )
52
+ os .Exit (1 )
36
53
}
37
54
},
38
55
}
39
56
svcConfig = & service.Config {
40
- Name : "docker-netns" ,
57
+ Name : "docker-netns" ,
41
58
DisplayName : "Docker network namespace service" ,
42
- UserName : "root" ,
59
+ UserName : "root" ,
43
60
Dependencies : []string {
44
61
"After=network.target syslog.target docker.service" ,
45
62
},
@@ -49,37 +66,140 @@ var (
49
66
}
50
67
)
51
68
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
+ }
53
137
54
138
func (p * program ) Start (s service.Service ) error {
55
139
// Start should not block. Do the actual work async.
140
+ config , err := yamlConfig .NewConfig (configPath )
141
+ if err != nil {
142
+ return err
143
+ }
56
144
cli , err := client .NewClientWithOpts (client .WithVersion ("1.40" ))
57
145
if err != nil {
58
146
return err
59
147
}
60
- go p .run (cli )
148
+ go p .run (config , cli )
61
149
return nil
62
150
}
63
151
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
+
66
175
f := filters .NewArgs ()
67
176
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 })
69
178
for {
70
179
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
+ }
73
191
case err := <- errs :
74
- _ = err
192
+ process .Signal (syscall .SIGTERM )
193
+ p .exited <- err
75
194
return
76
195
}
77
196
}
78
197
}
79
198
80
199
func (p * program ) Stop (s service.Service ) error {
81
200
// Stop should not block. Return with a few seconds.
82
- return nil
201
+ p .cancel ()
202
+ return <- p .exited
83
203
}
84
204
85
205
func Execute () {
0 commit comments