package main import ( "context" "errors" "github.com/dgraph-io/ristretto" "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/eko/gocache/cache" "github.com/eko/gocache/store" "github.com/gin-gonic/gin" "github.com/go-co-op/gocron" "github.com/jackc/pgx/v4/pgxpool" "go.uber.org/zap" "go.uber.org/zap/zapcore" "io/ioutil" "net/http" "os" "time" ) func setupLogger() *zap.Logger { highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.ErrorLevel }) lowPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl < zapcore.ErrorLevel }) fileDebugging := zapcore.AddSync(ioutil.Discard) fileErrors := zapcore.AddSync(ioutil.Discard) consoleDebugging := zapcore.Lock(os.Stdout) consoleErrors := zapcore.Lock(os.Stderr) fileEncoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()) core := zapcore.NewTee( zapcore.NewCore(fileEncoder, fileErrors, highPriority), zapcore.NewCore(consoleEncoder, consoleErrors, highPriority), zapcore.NewCore(fileEncoder, fileDebugging, lowPriority), zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority), ) logger := zap.New(core) defer logger.Sync() return logger } func setupApi(l *zap.Logger, cc *cache.Cache) { l.Info("Setting up serverctl setupApi (using gin)") r := gin.Default() r.GET("/containers", func(c *gin.Context) { type container struct { Name string `json:"name"` } var msg struct { Containers []container `json:"containers"` } get, err := cc.Get("docker-containers") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"message": "could not get containers from container runtime"}) return } msg.Containers = []container{} for _, cont := range get.([]types.Container) { msg.Containers = append(msg.Containers, container{ Name: cont.Names[0], }) } c.JSON(http.StatusOK, msg) }) r.Run(":8080") } func setupDocker(l *zap.Logger) *client.Client { l.Info("Setting up Docker") cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { panic(err) } return cli } func setupCache(l *zap.Logger) *cache.Cache { l.Info("Setting up cache") ristrettoCache, err := ristretto.NewCache(&ristretto.Config{ NumCounters: 1000, MaxCost: 100, BufferItems: 64, }) if err != nil { panic(err) } ristrettoStore := store.NewRistretto(ristrettoCache, nil) cacheManager := cache.New(ristrettoStore) return cacheManager } func setupCron(l *zap.Logger, cm *cache.Cache, cc *client.Client) { l.Info("Setting up job scheduler (cron)") s := gocron.NewScheduler(time.UTC) s.Every(10).Second().Do(func() { l.Debug("getting container list") list, err := cc.ContainerList(context.Background(), types.ContainerListOptions{}) if err != nil { l.Warn(err.Error()) return } err = cm.Set("docker-containers", list, &store.Options{ Cost: 2, }) if err != nil { l.Warn(err.Error()) return } }) s.StartAsync() } func setupDatabase(l *zap.Logger) *pgxpool.Pool { l.Info("Setting up database connection") dbUrl := os.Getenv("DATABASE_URL") if dbUrl == "" { panic(errors.New("DATABASE_URL is not set")) } dbpool, err := pgxpool.Connect(context.Background(), dbUrl) if err != nil { panic(err) } var greeting string err = dbpool.QueryRow(context.Background(), "select 'Hello, world!'").Scan(&greeting) if err != nil { panic(err) } l.Info("Database successfully connected") return dbpool } func main() { logger := setupLogger() logger.Info("Starting serverctl") cacheM := setupCache(logger) containerClient := setupDocker(logger) setupCron(logger, cacheM, containerClient) dbpool := setupDatabase(logger) setupApi(logger, cacheM) }