gitlab.com/creichlin/pentaconta@v0.1.1-0.20170921154330-ffd669064217/main.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "flag" 6 "fmt" 7 "io/ioutil" 8 "log" 9 "os" 10 "path/filepath" 11 "strings" 12 "time" 13 14 "os/signal" 15 16 "github.com/ghodss/yaml" 17 "gitlab.com/creichlin/pentaconta/declaration" 18 "gitlab.com/creichlin/pentaconta/evaluation" 19 "gitlab.com/creichlin/pentaconta/logger" 20 "gitlab.com/creichlin/pentaconta/runtime" 21 ) 22 23 func main() { 24 configName, help := readConfigParams() 25 if help { 26 printHelp() 27 return 28 } 29 location, err := probeLocation(configName) 30 31 if err != nil { 32 log.Fatal(err) 33 } 34 35 data, err := readData(location) 36 if err != nil { 37 log.Fatal(err) 38 } 39 40 runWithDeclaration(data, time.Hour*24*356*100) 41 } 42 43 func runWithDeclaration(data interface{}, timeout time.Duration) { 44 dec, err := declaration.Parse(data) 45 if err != nil { 46 log.Fatal(err) 47 } 48 49 logs, eval := evaluation.Configure(dec.Stats) 50 51 services := &runtime.Runtime{ 52 Logs: logs, 53 Executors: map[string]*runtime.Service{}, 54 FSListeners: map[string]*runtime.FSTrigger{}, 55 } 56 57 createAndStartServices(services, dec) 58 createAndStartFsTriggers(services, dec) 59 60 exitChannel := make(chan os.Signal, 1) 61 signal.Notify(exitChannel, os.Interrupt) 62 63 // eval loop is called every second 64 evalTicker := time.NewTicker(time.Second) 65 defer evalTicker.Stop() 66 67 // wait for expiration 68 expired := time.After(timeout) 69 70 exit := func() { 71 logs.Log(logger.Log{ 72 Instance: 0, 73 Message: "INT signal, quitting", 74 Service: "pentaconta", 75 Level: logger.PENTACONTA, 76 Time: time.Now(), 77 }) 78 shutdown(services) 79 } 80 81 mainloop: 82 for { 83 select { 84 case <-expired: 85 exit() 86 break mainloop 87 case <-exitChannel: 88 exit() 89 break mainloop 90 case <-evalTicker.C: 91 eval.Loop() 92 } 93 } 94 // wait a little so some log messages in the queue 95 // have a chance to be written 96 time.Sleep(time.Millisecond * 100) 97 } 98 99 func shutdown(services *runtime.Runtime) { 100 for _, executor := range services.Executors { 101 executor.Exit() 102 } 103 for _, executor := range services.Executors { 104 for !executor.IsTerminated() { 105 time.Sleep(time.Millisecond * 100) 106 } 107 } 108 } 109 110 func createAndStartFsTriggers(svs *runtime.Runtime, data *declaration.Root) { 111 for name, fsTrigger := range data.FSTriggers { 112 fsListener, err := runtime.NewFSTrigger(name, fsTrigger, svs) 113 if err != nil { 114 panic(err) 115 } 116 svs.FSListeners[name] = fsListener 117 go func() { 118 err := fsListener.Start() 119 log.Fatal(err) 120 }() 121 } 122 } 123 124 func createAndStartServices(svs *runtime.Runtime, data *declaration.Root) { 125 for name, service := range data.Services { 126 executor, err := runtime.NewService(name, service, svs.Logs) 127 if err != nil { 128 panic(err) 129 } 130 131 svs.Executors[name] = executor 132 go executor.Start() 133 } 134 } 135 136 func readConfigParams() (string, bool) { 137 var configName string 138 var help bool 139 executable := filepath.Base(os.Args[0]) 140 flags := flag.NewFlagSet("pentacota", flag.ContinueOnError) 141 flags.StringVar(&configName, "config", executable, "name of config file to use, no .yaml or .json extension.") 142 flags.BoolVar(&help, "help", false, "Print help text and exit") 143 flags.Parse(os.Args[1:]) 144 return configName, help 145 } 146 147 func printHelp() { 148 flag.PrintDefaults() 149 fmt.Println("\nyaml declaration\n================") 150 fmt.Println(declaration.Doc()) 151 } 152 153 func readData(file string) (interface{}, error) { 154 binData, err := ioutil.ReadFile(file) 155 if err != nil { 156 return nil, err 157 } 158 159 data := interface{}(nil) 160 161 if strings.HasSuffix(file, ".json") { 162 err = json.Unmarshal(binData, &data) 163 if err != nil { 164 return nil, err 165 } 166 return data, nil 167 } else if strings.HasSuffix(file, ".yaml") { 168 err = yaml.Unmarshal(binData, &data) 169 if err != nil { 170 return nil, err 171 } 172 return data, nil 173 } 174 panic("Returned path must have .json or .yaml extension") 175 } 176 177 func probeLocation(path string) (string, error) { 178 locations := []string{} 179 if filepath.IsAbs(path) { 180 locations = append(locations, path+".json") 181 locations = append(locations, path+".yaml") 182 } else { 183 wd, err := os.Getwd() 184 if err == nil { 185 abspath := filepath.Join(wd, path) 186 locations = append(locations, abspath+".json") 187 locations = append(locations, abspath+".yaml") 188 } 189 abspath := filepath.Join("/etc", path) 190 locations = append(locations, abspath+".json") 191 locations = append(locations, abspath+".yaml") 192 } 193 194 for _, location := range locations { 195 _, err := ioutil.ReadFile(location) 196 if err == nil { 197 return location, nil 198 } 199 } 200 201 return "", fmt.Errorf("Could not find config file in locations %v", locations) 202 }