-
Yordan Kinkov authoredYordan Kinkov authored
main.go 6.64 KiB
package main
import (
"context"
"errors"
"fmt"
"log"
"net"
"net/http"
"time"
"github.com/kelseyhightower/envconfig"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
goahttp "goa.design/goa/v3/http"
goa "goa.design/goa/v3/pkg"
"golang.org/x/sync/errgroup"
"code.vereign.com/gaiax/tsa/golib/cache"
"code.vereign.com/gaiax/tsa/golib/goadec"
"code.vereign.com/gaiax/tsa/golib/graceful"
goahealth "code.vereign.com/gaiax/tsa/infohub/gen/health"
goahealthsrv "code.vereign.com/gaiax/tsa/infohub/gen/http/health/server"
goainfohubsrv "code.vereign.com/gaiax/tsa/infohub/gen/http/infohub/server"
goaopenapisrv "code.vereign.com/gaiax/tsa/infohub/gen/http/openapi/server"
goainfohub "code.vereign.com/gaiax/tsa/infohub/gen/infohub"
"code.vereign.com/gaiax/tsa/infohub/gen/openapi"
"code.vereign.com/gaiax/tsa/infohub/internal/clients/policy"
"code.vereign.com/gaiax/tsa/infohub/internal/clients/signer"
"code.vereign.com/gaiax/tsa/infohub/internal/config"
"code.vereign.com/gaiax/tsa/infohub/internal/credential"
"code.vereign.com/gaiax/tsa/infohub/internal/service"
"code.vereign.com/gaiax/tsa/infohub/internal/service/health"
"code.vereign.com/gaiax/tsa/infohub/internal/service/infohub"
"code.vereign.com/gaiax/tsa/infohub/internal/storage"
)
var Version = "0.0.0+development"
func main() {
// load configuration from environment
var cfg config.Config
if err := envconfig.Process("", &cfg); err != nil {
log.Fatalf("cannot load configuration: %v\n", err)
}
// create logger
logger, err := createLogger(cfg.LogLevel)
if err != nil {
log.Fatalln(err)
}
defer logger.Sync() //nolint:errcheck
logger.Info("infohub service started", zap.String("version", Version), zap.String("goa", goa.Version()))
// connect to mongo db
db, err := mongo.Connect(
context.Background(),
options.Client().ApplyURI(cfg.Mongo.Addr).SetAuth(options.Credential{
Username: cfg.Mongo.User,
Password: cfg.Mongo.Pass,
}),
)
if err != nil {
logger.Fatal("error connecting to mongodb", zap.Error(err))
}
defer db.Disconnect(context.Background()) //nolint:errcheck
// create storage
storage, err := storage.New(db, cfg.Mongo.DB, cfg.Mongo.Collection, logger)
if err != nil {
logger.Fatal("error connecting to database", zap.Error(err))
}
httpClient := httpClient()
credentials := credential.NewIssuer(cfg.Credential.IssuerURI, httpClient)
// create policy client
policy := policy.New(cfg.Policy.Addr, policy.WithHTTPClient(httpClient))
// create cache client
cache := cache.New(cfg.Cache.Addr)
// create signer client
signer := signer.New(cfg.Signer.Addr, signer.WithHTTPClient(httpClient))
// create services
var (
infohubSvc goainfohub.Service
healthSvc goahealth.Service
)
{
infohubSvc = infohub.New(storage, policy, cache, credentials, signer, logger)
healthSvc = health.New()
}
// create endpoints
var (
infohubEndpoints *goainfohub.Endpoints
healthEndpoints *goahealth.Endpoints
openapiEndpoints *openapi.Endpoints
)
{
infohubEndpoints = goainfohub.NewEndpoints(infohubSvc)
healthEndpoints = goahealth.NewEndpoints(healthSvc)
openapiEndpoints = openapi.NewEndpoints(nil)
}
// Provide the transport specific request decoder and response encoder.
// The goa http package has built-in support for JSON, XML and gob.
// Other encodings can be used by providing the corresponding functions,
// see goa.design/implement/encoding.
var (
dec = goahttp.RequestDecoder
enc = goahttp.ResponseEncoder
)
// Build the service HTTP request multiplexer and configure it to serve
// HTTP requests to the service endpoints.
mux := goahttp.NewMuxer()
// Wrap the endpoints with the transport specific layers. The generated
// server packages contains code generated from the design which maps
// the service input and output data structures to HTTP requests and
// responses.
var (
infohubServer *goainfohubsrv.Server
healthServer *goahealthsrv.Server
openapiServer *goaopenapisrv.Server
)
{
infohubServer = goainfohubsrv.New(infohubEndpoints, mux, dec, enc, nil, errFormatter)
healthServer = goahealthsrv.New(healthEndpoints, mux, dec, enc, nil, errFormatter)
openapiServer = goaopenapisrv.New(openapiEndpoints, mux, dec, enc, nil, errFormatter, nil, nil)
}
// set custom request decoder, so that request body bytes are simply
// read and not decoded in some other way
infohubServer.Import = goainfohubsrv.NewImportHandler(
infohubEndpoints.Import,
mux,
goadec.BytesDecoder,
enc,
nil,
errFormatter,
)
// Configure the mux.
goainfohubsrv.Mount(mux, infohubServer)
goahealthsrv.Mount(mux, healthServer)
goaopenapisrv.Mount(mux, openapiServer)
// expose metrics
go exposeMetrics(cfg.Metrics.Addr, logger)
var handler http.Handler = mux
srv := &http.Server{
Addr: cfg.HTTP.Host + ":" + cfg.HTTP.Port,
Handler: handler,
IdleTimeout: cfg.HTTP.IdleTimeout,
ReadTimeout: cfg.HTTP.ReadTimeout,
WriteTimeout: cfg.HTTP.WriteTimeout,
}
g, ctx := errgroup.WithContext(context.Background())
g.Go(func() error {
if err := graceful.Shutdown(ctx, srv, 20*time.Second); err != nil {
logger.Error("server shutdown error", zap.Error(err))
return err
}
return errors.New("server stopped successfully")
})
if err := g.Wait(); err != nil {
logger.Error("run group stopped", zap.Error(err))
}
logger.Info("bye bye")
}
func createLogger(logLevel string, opts ...zap.Option) (*zap.Logger, error) {
var level = zapcore.InfoLevel
if logLevel != "" {
err := level.UnmarshalText([]byte(logLevel))
if err != nil {
return nil, err
}
}
config := zap.NewProductionConfig()
config.Level = zap.NewAtomicLevelAt(level)
config.DisableStacktrace = true
config.EncoderConfig.TimeKey = "ts"
config.EncoderConfig.EncodeTime = zapcore.RFC3339TimeEncoder
return config.Build(opts...)
}
func errFormatter(e error) goahttp.Statuser {
return service.NewErrorResponse(e)
}
func httpClient() *http.Client {
return &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
TLSHandshakeTimeout: 10 * time.Second,
IdleConnTimeout: 60 * time.Second,
},
Timeout: 30 * time.Second,
}
}
func exposeMetrics(addr string, logger *zap.Logger) {
promMux := http.NewServeMux()
promMux.Handle("/metrics", promhttp.Handler())
logger.Info(fmt.Sprintf("exposing prometheus metrics at %s/metrics", addr))
if err := http.ListenAndServe(addr, promMux); err != nil {
logger.Error("error exposing prometheus metrics", zap.Error(err))
}
}