├── README.md └── docker-compose-graphviz.go /README.md: -------------------------------------------------------------------------------- 1 | # docker-compose-graphviz 2 | 3 | Turn a `docker-compose.yml` into a Graphviz `.dot` file. Currently in prototype state. For an example of the output, 4 | check out https://github.com/abesto/abesto-net-docker. 5 | 6 | ## Installation 7 | 8 | ```sh 9 | go install github.com/abesto/docker-compose-graphviz 10 | ``` 11 | 12 | ## Usage 13 | 14 | `cd` into a directory that has a `docker-compose.yml` file. 15 | 16 | ```sh 17 | docker-compose-graphviz | dot -odocker-compose.jpg -Tjpg && open docker-compose.jpg 18 | ``` -------------------------------------------------------------------------------- /docker-compose-graphviz.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/alexcesaro/log" 6 | "github.com/alexcesaro/log/stdlog" 7 | "github.com/awalterschulze/gographviz" 8 | "gopkg.in/yaml.v2" 9 | "io/ioutil" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | var logger log.Logger 15 | 16 | func abort(msg string) { 17 | logger.Critical(msg) 18 | os.Exit(1) 19 | } 20 | 21 | type service struct { 22 | Links []string 23 | VolumesFrom []string "volumes_from" 24 | Volumes []string 25 | Ports []string 26 | } 27 | 28 | func main() { 29 | var ( 30 | bytes []byte 31 | data map[string]service 32 | err error 33 | graph *gographviz.Graph 34 | project string 35 | ) 36 | logger = stdlog.GetFromFlags() 37 | project = "" 38 | 39 | // Load docker-compose.yml 40 | bytes, err = ioutil.ReadFile("docker-compose.yml") 41 | if err != nil { 42 | abort(err.Error()) 43 | } 44 | 45 | // Parse it as YML 46 | data = make(map[string]service, 5) 47 | yaml.Unmarshal(bytes, &data) 48 | if err != nil { 49 | abort(err.Error()) 50 | } 51 | 52 | // Create directed graph 53 | graph = gographviz.NewGraph() 54 | graph.SetName(project) 55 | graph.SetDir(true) 56 | 57 | // Add legend 58 | graph.AddSubGraph(project, "cluster_legend", map[string]string{"label": "Legend"}) 59 | graph.AddNode("cluster_legend", "legend_service", map[string]string{"label": "service"}) 60 | graph.AddNode("cluster_legend", "legend_service_with_ports", 61 | map[string]string{ 62 | "label": "\"service with exposed ports\\n80:80 443:443\\n--volume1[:host_dir1]\\n--volume2[:host_dir2]\"", 63 | "shape": "box"}) 64 | graph.AddEdge("legend_service", "legend_service_with_ports", true, map[string]string{"label": "links"}) 65 | graph.AddEdge("legend_service_with_ports", "legend_service", true, map[string]string{"label": "volumes_from", "style": "dashed"}) 66 | 67 | // Round one: populate nodes 68 | for name, service := range data { 69 | var attrs = map[string]string{"label": name} 70 | if service.Ports != nil { 71 | attrs["label"] += "\\n" + strings.Join(service.Ports, " ") 72 | attrs["shape"] = "box" 73 | } 74 | if service.Volumes != nil { 75 | attrs["label"] += "\\n--" + strings.Join(service.Volumes, "\\n--") 76 | } 77 | attrs["label"] = fmt.Sprintf("\"%s\"", attrs["label"]) 78 | graph.AddNode(project, name, attrs) 79 | } 80 | 81 | // Round two: populate connections 82 | for name, service := range data { 83 | // links 84 | if service.Links != nil { 85 | for _, linkTo := range service.Links { 86 | if strings.Contains(linkTo, ":") { 87 | linkTo = strings.Split(linkTo, ":")[0] 88 | } 89 | graph.AddEdge(name, linkTo, true, nil) 90 | } 91 | } 92 | // volumes_from 93 | if service.VolumesFrom != nil { 94 | for _, linkTo := range service.VolumesFrom { 95 | graph.AddEdge(name, linkTo, true, map[string]string{"style": "dotted"}) 96 | } 97 | } 98 | } 99 | 100 | fmt.Print(graph) 101 | } 102 | --------------------------------------------------------------------------------