package main import ( "context" "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" "go.uber.org/zap" "go.uber.org/zap/zapcore" "io/ioutil" "net/http" "os" "serverctl/pkg/db" "serverctl/pkg/db/postgres" "serverctl/pkg/users" "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 BasicAuthMiddleware(l *zap.Logger, us *users.Service) gin.HandlerFunc { return func(c *gin.Context) { username, password, hasAuth := c.Request.BasicAuth() if !hasAuth { l.Info("user could not be authenticated", zap.String("username", username)) c.Header("WWW-Authenticate", "Basic realm=serverctl") c.Abort() c.JSON(http.StatusUnauthorized, gin.H{"message": "credentials were invalid (authorization header missing)"}) return } user, err := us.Authenticate(c.Request.Context(), username, password) if err != nil { l.Info("user could not be authenticated", zap.String("username", username)) c.Abort() c.Header("WWW-Authenticate", "Basic realm=serverctl") c.JSON(http.StatusUnauthorized, gin.H{"message": "credentials were invalid (credentials didn't match)"}) return } l.Debug("user has been authenticated", zap.Int("userId", user.Id), zap.String("email", user.Email)) c.Set("userId", user.Id) c.Next() } } func setupApi(l *zap.Logger, cc *cache.Cache, us *users.Service) { l.Info("Setting up serverctl setupApi (using gin)") r := gin.Default() r.POST("/auth/register", func(c *gin.Context) { type RegisterUser struct { Email string `json:"email" binding:"required"` Password string `json:"password" binding:"required"` } var registerUser RegisterUser if err := c.BindJSON(®isterUser); err != nil { return } createUser, err := us.Create(registerUser.Email, registerUser.Password) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"message": "you have provided invalid input"}) return } c.JSON(http.StatusCreated, gin.H{"message": "user has been registered", "userId": createUser}) }) containers := r.Group("/containers", BasicAuthMiddleware(l, us)) containers.GET("/", 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 main() { logger := setupLogger() logger.Info("Starting serverctl") cacheM := setupCache(logger) containerClient := setupDocker(logger) setupCron(logger, cacheM, containerClient) database := db.NewClient(logger) usersRepository := postgres.NewUsersRepository(database) usersService := users.NewService(logger, usersRepository, cacheM) setupApi(logger, cacheM, usersService) }