Skip to content
This repository was archived by the owner on Feb 24, 2023. It is now read-only.

Commit 5394fcb

Browse files
Initial Commit
0 parents  commit 5394fcb

2 files changed

Lines changed: 526 additions & 0 deletions

File tree

system-docker.go

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"io/ioutil"
9+
"log"
10+
"net"
11+
"os"
12+
"os/exec"
13+
"path"
14+
"strconv"
15+
"strings"
16+
17+
dockerClient "github.com/fsouza/go-dockerclient"
18+
)
19+
20+
var (
21+
SYSFS string = "/sys/fs/cgroup"
22+
PROCS string = "cgroup.procs"
23+
CGROUP_PROC string = "/proc/%d/cgroup"
24+
)
25+
26+
type Context struct {
27+
Args []string
28+
Namespaces []string
29+
AllNamespaces bool
30+
Logs bool
31+
Env bool
32+
Id string
33+
NotifySocket string
34+
Cmd *exec.Cmd
35+
Pid int
36+
Client *dockerClient.Client
37+
}
38+
39+
func parseContext(args []string) (*Context, error) {
40+
c := &Context{
41+
Logs: true,
42+
AllNamespaces: true,
43+
}
44+
45+
/* Probably could have done this easily with flags */
46+
foundRun := false
47+
foundD := false
48+
49+
for _, val := range args {
50+
if foundRun {
51+
if val == "-d" {
52+
foundD = true
53+
}
54+
c.Args = append(c.Args, val)
55+
} else {
56+
switch val {
57+
case "--env":
58+
c.Env = true
59+
case "--no-logs":
60+
c.Logs = false
61+
case "run":
62+
foundRun = true
63+
}
64+
}
65+
}
66+
67+
if !foundRun {
68+
return nil, errors.New("run not found in arguments")
69+
}
70+
71+
if !foundD {
72+
return nil, errors.New("-d is required")
73+
}
74+
75+
c.NotifySocket = os.Getenv("NOTIFY_SOCKET")
76+
77+
return c, nil
78+
}
79+
80+
func runContainer(c *Context) error {
81+
args := append([]string{"run"}, c.Args...)
82+
c.Cmd = exec.Command("docker", args...)
83+
84+
errorPipe, err := c.Cmd.StderrPipe()
85+
if err != nil {
86+
return err
87+
}
88+
89+
outputPipe, err := c.Cmd.StdoutPipe()
90+
if err != nil {
91+
return err
92+
}
93+
94+
err = c.Cmd.Start()
95+
if err != nil {
96+
return err
97+
}
98+
99+
go io.Copy(os.Stderr, errorPipe)
100+
101+
bytes, err := ioutil.ReadAll(outputPipe)
102+
if err != nil {
103+
return err
104+
}
105+
106+
c.Id = strings.TrimSpace(string(bytes))
107+
108+
err = c.Cmd.Wait()
109+
if err != nil {
110+
return err
111+
}
112+
113+
if !c.Cmd.ProcessState.Success() {
114+
return err
115+
}
116+
117+
c.Pid, err = getContainerPid(c)
118+
119+
return err
120+
}
121+
122+
func getClient(c *Context) (*dockerClient.Client, error) {
123+
if c.Client != nil {
124+
return c.Client, nil
125+
}
126+
127+
endpoint := os.Getenv("DOCKER_HOST")
128+
if len(endpoint) == 0 {
129+
endpoint = "unix:///var/run/docker.sock"
130+
}
131+
132+
return dockerClient.NewVersionedClient(endpoint, "1.11")
133+
}
134+
135+
func getContainerPid(c *Context) (int, error) {
136+
client, err := getClient(c)
137+
if err != nil {
138+
return 0, err
139+
}
140+
141+
container, err := client.InspectContainer(c.Id)
142+
if err != nil {
143+
return 0, err
144+
}
145+
146+
if container == nil {
147+
return 0, errors.New(fmt.Sprintf("Failed to find container %s", c.Id))
148+
}
149+
150+
if container.State.Pid <= 0 {
151+
return 0, errors.New(fmt.Sprintf("Pid is %d for container %s", container.State.Pid, c.Id))
152+
}
153+
154+
return container.State.Pid, nil
155+
}
156+
157+
func getNamespacesForPid(pid int) (map[string]string, error) {
158+
file, err := os.Open(fmt.Sprintf(CGROUP_PROC, pid))
159+
if err != nil {
160+
return nil, err
161+
}
162+
163+
ret := map[string]string{}
164+
165+
scanner := bufio.NewScanner(file)
166+
for scanner.Scan() {
167+
line := strings.SplitN(scanner.Text(), ":", 3)
168+
if len(line) != 3 {
169+
continue
170+
}
171+
172+
ret[line[1]] = line[2]
173+
}
174+
175+
if err := scanner.Err(); err != nil {
176+
return nil, err
177+
}
178+
179+
return ret, nil
180+
}
181+
182+
func constructCgroupPath(cgroupName string, cgroupPath string) string {
183+
return path.Join(SYSFS, strings.TrimPrefix(cgroupName, "name="), cgroupPath, PROCS)
184+
}
185+
186+
func getCgroupPids(cgroupName string, cgroupPath string) ([]string, error) {
187+
ret := []string{}
188+
189+
file, err := os.Open(constructCgroupPath(cgroupName, cgroupPath))
190+
if err != nil {
191+
return nil, err
192+
}
193+
194+
scanner := bufio.NewScanner(file)
195+
for scanner.Scan() {
196+
ret = append(ret, strings.TrimSpace(scanner.Text()))
197+
}
198+
199+
if err = scanner.Err(); err != nil {
200+
return nil, err
201+
}
202+
203+
return ret, nil
204+
}
205+
206+
func writePid(pid string, path string) error {
207+
return ioutil.WriteFile(path, []byte(pid), 0644)
208+
}
209+
210+
func moveNamespaces(c *Context) (bool, error) {
211+
moved := false
212+
currentCgroups, err := getNamespacesForPid(os.Getpid())
213+
if err != nil {
214+
return false, err
215+
}
216+
217+
containerCgroups, err := getNamespacesForPid(c.Pid)
218+
if err != nil {
219+
return false, err
220+
}
221+
222+
var ns []string
223+
224+
if c.AllNamespaces || c.Namespaces == nil {
225+
ns = make([]string, len(containerCgroups))
226+
for value, _ := range containerCgroups {
227+
ns = append(ns, value)
228+
}
229+
} else {
230+
ns = c.Namespaces
231+
}
232+
233+
for _, nsName := range ns {
234+
currentPath, ok := currentCgroups[nsName]
235+
if !ok {
236+
continue
237+
}
238+
239+
containerPath, ok := containerCgroups[nsName]
240+
if !ok {
241+
continue
242+
}
243+
244+
if currentPath == containerPath || containerPath == "/" {
245+
continue
246+
}
247+
248+
pids, err := getCgroupPids(nsName, containerPath)
249+
if err != nil {
250+
return false, err
251+
}
252+
253+
for _, pid := range pids {
254+
pidInt, err := strconv.Atoi(pid)
255+
if err != nil {
256+
continue
257+
}
258+
259+
if pidDied(pidInt) {
260+
continue
261+
}
262+
263+
currentFullPath := constructCgroupPath(nsName, currentPath)
264+
log.Printf("Moving pid %s to %s\n", pid, currentFullPath)
265+
err = writePid(pid, currentFullPath)
266+
if err != nil {
267+
return false, err
268+
}
269+
270+
moved = true
271+
}
272+
}
273+
274+
return moved, nil
275+
}
276+
277+
func pidDied(pid int) bool {
278+
_, err := os.Stat(fmt.Sprintf("/proc/%d", pid))
279+
return os.IsNotExist(err)
280+
}
281+
282+
func notify(c *Context) error {
283+
if pidDied(c.Pid) {
284+
return errors.New("Container exited before we could notify systemd")
285+
}
286+
287+
if len(c.NotifySocket) == 0 {
288+
return nil
289+
}
290+
291+
conn, err := net.Dial("unixgram", c.NotifySocket)
292+
if err != nil {
293+
return err
294+
}
295+
296+
defer conn.Close()
297+
298+
_, err = conn.Write([]byte(fmt.Sprintf("MAINPID=%d", c.Pid)))
299+
if err != nil {
300+
return err
301+
}
302+
303+
if pidDied(c.Pid) {
304+
conn.Write([]byte(fmt.Sprintf("MAINPID=%d", os.Getpid())))
305+
return errors.New("Container exited before we could notify systemd")
306+
}
307+
308+
_, err = conn.Write([]byte("READY=1"))
309+
if err != nil {
310+
return err
311+
}
312+
313+
return nil
314+
}
315+
316+
func pipeLogs(c *Context) error {
317+
if !c.Logs {
318+
return nil
319+
}
320+
321+
client, err := getClient(c)
322+
if err != nil {
323+
return err
324+
}
325+
326+
err = client.Logs(dockerClient.LogsOptions{
327+
Container: c.Id,
328+
Follow: true,
329+
Stdout: true,
330+
Stderr: true,
331+
OutputStream: os.Stdout,
332+
ErrorStream: os.Stderr,
333+
})
334+
335+
return err
336+
}
337+
338+
func main() {
339+
c, err := parseContext(os.Args)
340+
if err != nil {
341+
log.Fatal(err)
342+
}
343+
344+
err = runContainer(c)
345+
if err != nil {
346+
log.Fatal(err)
347+
}
348+
349+
_, err = moveNamespaces(c)
350+
if err != nil {
351+
log.Fatal(err)
352+
}
353+
354+
err = notify(c)
355+
if err != nil {
356+
log.Fatal(err)
357+
}
358+
359+
err = pipeLogs(c)
360+
if err != nil {
361+
log.Fatal(err)
362+
}
363+
}

0 commit comments

Comments
 (0)