gitlab.com/creichlin/pentaconta@v0.1.1-0.20170921154330-ffd669064217/declaration/declaration.go (about) 1 package declaration 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/creichlin/goschema" 8 "github.com/creichlin/gutil" 9 "github.com/mitchellh/mapstructure" 10 ) 11 12 type Service struct { 13 Executable string 14 WorkingDir string `mapstructure:"working-dir"` 15 Arguments []string 16 } 17 18 type FSTrigger struct { 19 Path string 20 Services []string 21 Signal string 22 } 23 24 type Stats struct { 25 File string 26 Seconds int 27 } 28 29 type Root struct { 30 Stats *Stats 31 Services map[string]*Service 32 FSTriggers map[string]*FSTrigger `mapstructure:"fs-triggers"` 33 } 34 35 func Doc() string { 36 return goschema.Doc(buildSchema()) 37 } 38 39 func Parse(data interface{}) (*Root, error) { 40 schema := buildSchema() 41 errors := goschema.ValidateGO(schema, data) 42 if errors.Has() { 43 return nil, errors 44 } 45 46 root := &Root{} 47 err := mapstructure.Decode(data, root) 48 errors = validate(root) 49 if errors.Has() { 50 return nil, errors 51 } 52 return root, err 53 } 54 55 func validate(r *Root) *gutil.ErrorCollector { 56 ec := gutil.NewErrorCollector() 57 58 keys := []string{} 59 for name, _ := range r.FSTriggers { 60 keys = append(keys, name) 61 } 62 sort.Strings(keys) 63 64 for _, name := range keys { 65 for _, serviceName := range r.FSTriggers[name].Services { 66 if _, contains := r.Services[serviceName]; !contains { 67 ec.Add(fmt.Errorf("fs-trigger %v has unknown service %v as target", name, serviceName)) 68 } 69 } 70 } 71 72 return ec 73 } 74 75 func buildSchema() goschema.Type { 76 return goschema.NewObjectType("Pentaconta service declaration", func(o goschema.ObjectType) { 77 o.Optional("services").Map(func(m goschema.MapType) { 78 m.Object("Services start executables and restart them when terminated/crashed", func(o goschema.ObjectType) { 79 o.Attribute("executable").String( 80 "path or name, if not absolute will use PATH env var to find binary") 81 o.Optional("working-dir").String( 82 "path, absolute or relative to current working dir") 83 o.Optional("arguments").List(func(l goschema.ListType) { 84 l.String("arguments") 85 }) 86 }) 87 }) 88 o.Optional("fs-triggers").Map(func(m goschema.MapType) { 89 m.Object( 90 "filesystem watchers trigger termination (and implicit restart) of services", func(o goschema.ObjectType) { 91 o.Attribute("path").String( 92 "Folder or file to watch. Folder must exists. If it's a file, parent folder must exist.") 93 o.Attribute("services").List(func(l goschema.ListType) { 94 l.String("name of service to restart") 95 }) 96 o.Optional("signal").String("Name of the signal to send. If omitted sigint and sigkill are used.") 97 }) 98 }) 99 o.Optional("stats").Object("statistics writer", func(o goschema.ObjectType) { 100 o.Attribute("seconds").Int("defines in which interval stats are written").Min(5).Max(60) 101 o.Attribute("file").String("where to write the stats to") 102 }) 103 }) 104 }