Add graphviz generation and visualization
This requires graphviz to be installed on your machine. If you run the command with sudo, it will create the files with the original user ownership to make it easier to remove them without root.
This commit is contained in:
4
file.go
4
file.go
@@ -55,6 +55,10 @@ func NewFileType(name, path, content, state string) *FileType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (obj *FileType) GetType() string {
|
||||||
|
return "File"
|
||||||
|
}
|
||||||
|
|
||||||
// File watcher for files and directories
|
// File watcher for files and directories
|
||||||
// Modify with caution, probably important to write some test cases first!
|
// Modify with caution, probably important to write some test cases first!
|
||||||
// obj.Path: file or directory
|
// obj.Path: file or directory
|
||||||
|
|||||||
16
main.go
16
main.go
@@ -102,6 +102,12 @@ func run(c *cli.Context) {
|
|||||||
log.Fatal("Graph failure")
|
log.Fatal("Graph failure")
|
||||||
}
|
}
|
||||||
log.Printf("Graph: %v\n", G) // show graph
|
log.Printf("Graph: %v\n", G) // show graph
|
||||||
|
err := G.ExecGraphviz(c.String("graphviz-filter"), c.String("graphviz"))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Graphviz: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Graphviz: Successfully generated graph!")
|
||||||
|
}
|
||||||
G.SetVertex()
|
G.SetVertex()
|
||||||
if first {
|
if first {
|
||||||
// G.Start(...) needs to be synchronous or wait,
|
// G.Start(...) needs to be synchronous or wait,
|
||||||
@@ -174,6 +180,16 @@ func main() {
|
|||||||
Value: "",
|
Value: "",
|
||||||
Usage: "code definition to run",
|
Usage: "code definition to run",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "graphviz, g",
|
||||||
|
Value: "",
|
||||||
|
Usage: "output file for graphviz data",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "graphviz-filter, gf",
|
||||||
|
Value: "dot", // directed graph default
|
||||||
|
Usage: "graphviz filter to use",
|
||||||
|
},
|
||||||
// useful for testing multiple instances on same machine
|
// useful for testing multiple instances on same machine
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "hostname",
|
Name: "hostname",
|
||||||
|
|||||||
94
pgraph.go
94
pgraph.go
@@ -19,10 +19,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
//"container/list" // doubly linked list
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate stringer -type=graphState -output=graphstate_stringer.go
|
//go:generate stringer -type=graphState -output=graphstate_stringer.go
|
||||||
@@ -81,6 +86,11 @@ func NewEdge(name string) *Edge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns the name of the graph
|
||||||
|
func (g *Graph) GetName() string {
|
||||||
|
return g.Name
|
||||||
|
}
|
||||||
|
|
||||||
// set name of the graph
|
// set name of the graph
|
||||||
func (g *Graph) SetName(name string) {
|
func (g *Graph) SetName(name string) {
|
||||||
g.Name = name
|
g.Name = name
|
||||||
@@ -208,6 +218,88 @@ func (g *Graph) String() string {
|
|||||||
return fmt.Sprintf("Vertices(%d), Edges(%d)", g.NumVertices(), g.NumEdges())
|
return fmt.Sprintf("Vertices(%d), Edges(%d)", g.NumVertices(), g.NumEdges())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// output the graph in graphviz format
|
||||||
|
// https://en.wikipedia.org/wiki/DOT_%28graph_description_language%29
|
||||||
|
func (g *Graph) Graphviz() (out string) {
|
||||||
|
//digraph g {
|
||||||
|
// label="hello world";
|
||||||
|
// node [shape=box];
|
||||||
|
// A [label="A"];
|
||||||
|
// B [label="B"];
|
||||||
|
// C [label="C"];
|
||||||
|
// D [label="D"];
|
||||||
|
// E [label="E"];
|
||||||
|
// A -> B [label=f];
|
||||||
|
// B -> C [label=g];
|
||||||
|
// D -> E [label=h];
|
||||||
|
//}
|
||||||
|
out += fmt.Sprintf("digraph %v {\n", g.GetName())
|
||||||
|
out += fmt.Sprintf("\tlabel=\"%v\";\n", g.GetName())
|
||||||
|
//out += "\tnode [shape=box];\n"
|
||||||
|
str := ""
|
||||||
|
for i, _ := range g.Adjacency { // reverse paths
|
||||||
|
out += fmt.Sprintf("\t%v [label=\"%v[%v]\"];\n", i.GetName(), i.GetType(), i.GetName())
|
||||||
|
for j, _ := range g.Adjacency[i] {
|
||||||
|
k := g.Adjacency[i][j]
|
||||||
|
// use str for clearer output ordering
|
||||||
|
str += fmt.Sprintf("\t%v -> %v [label=%v];\n", i.GetName(), j.GetName(), k.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out += str
|
||||||
|
out += "}\n"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out the graphviz data and run the correct graphviz filter command
|
||||||
|
func (g *Graph) ExecGraphviz(program, filename string) error {
|
||||||
|
|
||||||
|
switch program {
|
||||||
|
case "dot", "neato", "twopi", "circo", "fdp":
|
||||||
|
default:
|
||||||
|
return errors.New("Invalid graphviz program selected!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if filename == "" {
|
||||||
|
return errors.New("No filename given!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// run as a normal user if possible when run with sudo
|
||||||
|
uid, err1 := strconv.Atoi(os.Getenv("SUDO_UID"))
|
||||||
|
gid, err2 := strconv.Atoi(os.Getenv("SUDO_GID"))
|
||||||
|
|
||||||
|
err := ioutil.WriteFile(filename, []byte(g.Graphviz()), 0644)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Error writing to filename!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err1 == nil && err2 == nil {
|
||||||
|
if err := os.Chown(filename, uid, gid); err != nil {
|
||||||
|
return errors.New("Error changing file owner!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := exec.LookPath(program)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Graphviz is missing!")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := fmt.Sprintf("%v.png", filename)
|
||||||
|
cmd := exec.Command(path, "-Tpng", fmt.Sprintf("-o%v", out), filename)
|
||||||
|
|
||||||
|
if err1 == nil && err2 == nil {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||||
|
cmd.SysProcAttr.Credential = &syscall.Credential{
|
||||||
|
Uid: uint32(uid),
|
||||||
|
Gid: uint32(gid),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Error writing to image!")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// google/golang hackers apparently do not think contains should be a built-in!
|
// google/golang hackers apparently do not think contains should be a built-in!
|
||||||
func Contains(s []*Vertex, element *Vertex) bool {
|
func Contains(s []*Vertex, element *Vertex) bool {
|
||||||
for _, v := range s {
|
for _, v := range s {
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ func NewServiceType(name, state, startup string) *ServiceType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (obj *ServiceType) GetType() string {
|
||||||
|
return "Service"
|
||||||
|
}
|
||||||
|
|
||||||
// Service watcher
|
// Service watcher
|
||||||
func (obj *ServiceType) Watch() {
|
func (obj *ServiceType) Watch() {
|
||||||
// obj.Name: service name
|
// obj.Name: service name
|
||||||
|
|||||||
9
types.go
9
types.go
@@ -26,6 +26,7 @@ import (
|
|||||||
type Type interface {
|
type Type interface {
|
||||||
Init()
|
Init()
|
||||||
GetName() string // can't be named "Name()" because of struct field
|
GetName() string // can't be named "Name()" because of struct field
|
||||||
|
GetType() string
|
||||||
Watch()
|
Watch()
|
||||||
StateOK() bool // TODO: can we rename this to something better?
|
StateOK() bool // TODO: can we rename this to something better?
|
||||||
Apply() bool
|
Apply() bool
|
||||||
@@ -71,6 +72,10 @@ func (obj *BaseType) GetName() string {
|
|||||||
return obj.Name
|
return obj.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (obj *BaseType) GetType() string {
|
||||||
|
return "Base"
|
||||||
|
}
|
||||||
|
|
||||||
func (obj *BaseType) GetVertex() *Vertex {
|
func (obj *BaseType) GetVertex() *Vertex {
|
||||||
return obj.vertex
|
return obj.vertex
|
||||||
}
|
}
|
||||||
@@ -198,6 +203,10 @@ func (obj *BaseType) Process(typ Type) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (obj *NoopType) GetType() string {
|
||||||
|
return "Noop"
|
||||||
|
}
|
||||||
|
|
||||||
func (obj *NoopType) Watch() {
|
func (obj *NoopType) Watch() {
|
||||||
//vertex := obj.vertex // stored with SetVertex
|
//vertex := obj.vertex // stored with SetVertex
|
||||||
var send = false // send event?
|
var send = false // send event?
|
||||||
|
|||||||
Reference in New Issue
Block a user