diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d2bafa9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:wheezy + +ENV DIST /go/src/github.com/k8sp/graphviz + +RUN apt-get update +RUN apt-get install -y graphviz + +COPY . $DIST +RUN cd $DIST && go get ./... && go get . + +EXPOSE 9090 +VOLUME ["/cache"] +ENTRYPOINT ["graphviz"] +CMD ["-addr=:9090", "-dir=/cache"] diff --git a/README.md b/README.md index e69de29..312a262 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,54 @@ +`graphviz` is an HTTP server which calls +[GraphViz](http://www.graphviz.org/) to visualize a specified .dot +file. + +## Build and Run from Source Code + +if we already got GraphViz and [Go](https://golang.org/) installed, we +can run this program as follows + +``` +go get github.com/k8sp/graphviz +$GOPATH/bin/graphviz -addr=:9090 +``` + +Then we can direct our Web browser to + +``` +http://localhost:9090/?dot=https://gist.githubusercontent.com/wangkuiyi/c4e0015211dd1b9bde2e20455a6cd38e/raw/4d5ec099f98a5f326cf6f108bcf510cadba1a0b4/ci-arch.dot +``` + +so to visualize the +[Gist file `ci-arch.dot`](https://gist.github.com/wangkuiyi/c4e0015211dd1b9bde2e20455a6cd38e) +in the Web browser window. + +## Build and Run the Docker Image + +If we have Docker installed, we can build `graphviz` into a Docker +image without installing GraphViz and Go: + +``` +go get github.com/k8sp/graphviz +cd $GOPATH/src/github.com/k8sp/graphviz +docker build -t graphviz . +``` + +and run it as a Docker container: + +``` +docker run -p 9090:9090 -v /tmp:/cache graphviz +``` + +Now we can direct our Web browser to above link. + +## Run with Images on DockerHub.com + +We can also run the Docker images built from stable releases by +DockerHub.com: + +``` +docker run -p 9090:9090 -v /tmp:/cache k8sp/graphviz +``` + + diff --git a/graphviz.go b/graphviz.go new file mode 100644 index 0000000..044809b --- /dev/null +++ b/graphviz.go @@ -0,0 +1,73 @@ +// graphviz is an HTTP server which calls GraphViz's dot command to +// visualize a .dot file given in the `dot` parameter. For example, +// we can run this program as follows +// +// go run graphviz.go +// +// and access it from the Web browser with URL: +// +// http://localhost:8080/?dot=https://gist.githubusercontent.com/wangkuiyi/c4e0015211dd1b9bde2e20455a6cd38e/raw/4d5ec099f98a5f326cf6f108bcf510cadba1a0b4/ci-arch.dot +// +// then the visualization of ci-arch.dot should appear in the browser window. +// +package main + +import ( + "crypto/md5" + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "path" + + "github.com/topicai/candy" +) + +func main() { + dir := flag.String("dir", "/tmp", "The cache directory") + addr := flag.String("addr", ":8080", "The listening address") + flag.Parse() + + http.HandleFunc("/", + makeSafeHandler(func(w http.ResponseWriter, r *http.Request) { + if source := r.FormValue("dot"); len(source) > 0 { + dot, e := candy.HTTPGet(source, 0) + candy.Must(e) + + id := fmt.Sprintf("%015x", md5.Sum(dot)) + dotFile := path.Join(*dir, id) + ".dot" + pngFile := path.Join(*dir, id) + ".png" + + if _, e := os.Stat(pngFile); os.IsNotExist(e) { + // TODO(yi): Here we adopt an unlimted-size disk-based cache. We should + // either introduce LRU and limit the size, or periodically clean it. + log.Printf("Update cache for %s", pngFile) + candy.Must(ioutil.WriteFile(dotFile, dot, 0755)) + png, e := exec.Command("dot", "-Tpng", dotFile).Output() + candy.Must(e) + candy.Must(ioutil.WriteFile(pngFile, png, 0755)) + } + + png, e := ioutil.ReadFile(pngFile) + candy.Must(e) + _, e = w.Write(png) + candy.Must(e) + } + })) + + candy.Must(http.ListenAndServe(*addr, nil)) +} + +func makeSafeHandler(h http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + defer func() { + if e := recover(); e != nil { + http.Error(w, fmt.Sprint(e), http.StatusInternalServerError) + } + }() + h(w, r) + } +}