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  }