Last active
October 3, 2016 11:40
-
-
Save duglin/80064a5e2715e5c763054d79d7d2540a to your computer and use it in GitHub Desktop.
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
package main | |
import ( | |
"bytes" | |
"encoding/json" | |
"flag" | |
"fmt" | |
"io" | |
"net" | |
"os" | |
"strings" | |
) | |
var fr8rSock = "/var/run/fr8r.sock" | |
var dockerSock = "/var/run/docker.sock" | |
var verbose = 0 | |
func log(v int, format string, args ...interface{}) { | |
if verbose < v { | |
return | |
} | |
if v == 0 { | |
fmt.Fprintf(os.Stdout, format, args...) | |
} else { | |
fmt.Fprintf(os.Stderr, format, args...) | |
} | |
} | |
// Just copy from one connection to the other | |
func copyConn(src, tgt net.Conn) { | |
ch := make(chan bool) | |
go func() { | |
io.Copy(src, tgt) | |
ch <- true | |
}() | |
go func() { | |
io.Copy(tgt, src) | |
ch <- true | |
}() | |
<-ch | |
// Just to make sure everything is closed down | |
src.Close() | |
tgt.Close() | |
} | |
// read in one line, ended by \n. If we hit maxBuffer then something is wrong | |
func readLine(in io.Reader) ([]byte, error) { | |
ch := make([]byte, 1) | |
maxBuffer := 10000 | |
line := new(bytes.Buffer) | |
i := 0 | |
for ; i < maxBuffer; i++ { | |
count, err := in.Read(ch) | |
if err != nil || count == 0 { | |
if count == 0 || err == io.EOF { | |
break | |
} | |
return nil, err | |
} | |
line.Write(ch) | |
if ch[0] == '\n' { | |
break | |
} | |
} | |
if i == maxBuffer { | |
return nil, fmt.Errorf("Buffer overflow read header") | |
} | |
return line.Bytes(), nil | |
} | |
// read in the request message, add our own Label and modify the Content-Length | |
// header based on the new data. | |
func twiddleCreate(id int, in, out net.Conn) { | |
log(1, "%d: Modifying the request\n", id) | |
for { | |
line, err := readLine(in) | |
if err != nil || len(line) == 0 { | |
// No input or an error stops us - something is wrong | |
return | |
} | |
log(3, "%d: INCOMING: %s\n", id, strings.TrimSpace(string(line))) | |
// Remove any Content-Length header | |
if i := strings.IndexRune(string(line), ':'); i >= 1 { | |
header := string(line[:i]) | |
if strings.EqualFold(header, "Content-Length") { | |
continue | |
} | |
} | |
// Until we hit a blank line (the body), buffer it then loop | |
if string(line) != "\r\n" { | |
out.Write(line) | |
continue | |
} | |
// We hit the body, so read it in as JSON | |
dec := json.NewDecoder(in) | |
body := map[string]interface{}{} | |
if err := dec.Decode(&body); err != nil { | |
log(0, "%d: Error reading body: %#v\n", id, err) | |
return | |
} | |
// Extract the Labels property (if there) and add our own | |
labels := map[string]interface{}{} | |
if obj := body["Labels"]; obj != nil { | |
var ok bool | |
labels, ok = obj.(map[string]interface{}) | |
if !ok { | |
log(0, "%d: Error casting label: %v\n", body["Labels"]) | |
return | |
} | |
} | |
// TODO add the correct label generation algorithm | |
log(1, "%d: Adding Label[%q]=%q\n", id, "FOO", "bar") | |
labels["FOO"] = "bar" | |
body["Labels"] = labels | |
// Now generate the new Body | |
line, err = json.Marshal(body) | |
if err != nil { | |
log(0, "%d: Error encoding new body: %s\n%s\n", err, body) | |
} | |
CTHeader := fmt.Sprintf("Content-Length: %d\r\n", len(line)) | |
log(3, "%d: WRITING:\n%s\n%s\n", id, CTHeader, string(line)) | |
// All done, pass new header and body to docker | |
out.Write([]byte(CTHeader)) | |
out.Write([]byte("\r\n")) | |
out.Write(line) | |
// Exit and let our normal proxy code take over | |
break | |
} | |
} | |
func processRequest(id int, conn net.Conn) { | |
log(1, "%d: New connection\n", id) | |
defer conn.Close() | |
// Open the connection to Docker | |
dockerConn, err := net.DialUnix("unix", nil, | |
&net.UnixAddr{dockerSock, "unix"}) | |
if err != nil { | |
log(0, "%d: Error connecting to docker: %v\n", id, err) | |
return | |
} | |
defer dockerConn.Close() | |
defer log(1, "%d: Connection closed\n", id) | |
// Grab just the first line to see if its what we're looking for | |
line, err := readLine(conn) | |
if err != nil { | |
log(0, "%d: Error reading header line: %v\n", id, err) | |
return | |
} | |
log(1, "%d: Request: %s\n", id, strings.TrimSpace(string(line))) | |
// Match or not, write the first line | |
dockerConn.Write(line) | |
// Only modify the POST...containers/create requests | |
if strings.HasPrefix(string(line), "POST ") && | |
strings.Contains(string(line), "/containers/create") { | |
twiddleCreate(id, conn, dockerConn) | |
} | |
// Become a proxy/pass-thru | |
copyConn(conn, dockerConn) | |
} | |
func main() { | |
flag.StringVar(&fr8rSock, "listen", fr8rSock, "Path to listener's socket") | |
flag.StringVar(&dockerSock, "docker", dockerSock, "Path to docker's socket") | |
flag.IntVar(&verbose, "v", verbose, "Verbose/debugging level") | |
flag.Parse() | |
connID := 0 | |
os.Remove(fr8rSock) | |
listener, err := net.ListenUnix("unix", &net.UnixAddr{fr8rSock, "unix"}) | |
if err != nil { | |
log(0, "Can't open our listener socket(%s): %v\n", fr8rSock, err) | |
os.Exit(-1) | |
} | |
defer os.Remove(fr8rSock) | |
log(0, "Listening on: %s\n", fr8rSock) | |
for { | |
conn, err := listener.AcceptUnix() | |
if err != nil { | |
log(0, "Error in accept: %v\n", err) | |
continue | |
} | |
connID++ | |
go processRequest(connID, conn) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment