122 lines
3.6 KiB
Go

// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.
package server
import (
"fmt"
"net"
"os"
"time"
"github.com/gitpod-io/gitpod/common-go/baseserver"
"github.com/gitpod-io/gitpod/common-go/log"
v1 "github.com/gitpod-io/gitpod/usage-api/v1"
"github.com/gitpod-io/gitpod/usage/pkg/apiv1"
"github.com/gitpod-io/gitpod/usage/pkg/contentservice"
"github.com/gitpod-io/gitpod/usage/pkg/controller"
"github.com/gitpod-io/gitpod/usage/pkg/db"
"github.com/gitpod-io/gitpod/usage/pkg/stripe"
"gorm.io/gorm"
)
type Config struct {
// ControllerSchedule determines how frequently to run the Usage/Billing controller
ControllerSchedule string `json:"controllerSchedule,omitempty"`
CreditsPerMinuteByWorkspaceClass map[string]float64 `json:"creditsPerMinuteByWorkspaceClass,omitempty"`
StripeCredentialsFile string `json:"stripeCredentialsFile,omitempty"`
ContentServiceAddress string `json:"contentServiceAddress,omitempty"`
Server *baseserver.Configuration `json:"server,omitempty"`
}
func Start(cfg Config) error {
log.WithField("config", cfg).Info("Starting usage component.")
conn, err := db.Connect(db.ConnectionParams{
User: os.Getenv("DB_USERNAME"),
Password: os.Getenv("DB_PASSWORD"),
Host: net.JoinHostPort(os.Getenv("DB_HOST"), os.Getenv("DB_PORT")),
Database: "gitpod",
})
if err != nil {
return fmt.Errorf("failed to establish database connection: %w", err)
}
pricer, err := controller.NewWorkspacePricer(cfg.CreditsPerMinuteByWorkspaceClass)
if err != nil {
return fmt.Errorf("failed to create workspace pricer: %w", err)
}
var billingController controller.BillingController = &controller.NoOpBillingController{}
if cfg.StripeCredentialsFile != "" {
config, err := stripe.ReadConfigFromFile(cfg.StripeCredentialsFile)
if err != nil {
return fmt.Errorf("failed to load stripe credentials: %w", err)
}
c, err := stripe.New(config)
if err != nil {
return fmt.Errorf("failed to initialize stripe client: %w", err)
}
billingController = controller.NewStripeBillingController(c)
}
schedule, err := time.ParseDuration(cfg.ControllerSchedule)
if err != nil {
return fmt.Errorf("failed to parse schedule duration: %w", err)
}
var contentService contentservice.Interface = &contentservice.NoOpClient{}
if cfg.ContentServiceAddress != "" {
contentService = contentservice.New(cfg.ContentServiceAddress)
}
ctrl, err := controller.New(schedule, controller.NewUsageReconciler(conn, pricer, billingController, contentService))
if err != nil {
return fmt.Errorf("failed to initialize usage controller: %w", err)
}
err = ctrl.Start()
if err != nil {
return fmt.Errorf("failed to start usage controller: %w", err)
}
defer ctrl.Stop()
var serverOpts []baseserver.Option
if cfg.Server != nil {
serverOpts = append(serverOpts, baseserver.WithConfig(cfg.Server))
}
srv, err := baseserver.New("usage", serverOpts...)
if err != nil {
return fmt.Errorf("failed to initialize usage server: %w", err)
}
err = registerGRPCServices(srv, conn)
if err != nil {
return fmt.Errorf("failed to register gRPC services: %w", err)
}
err = controller.RegisterMetrics(srv.MetricsRegistry())
if err != nil {
return fmt.Errorf("failed to register controller metrics: %w", err)
}
err = srv.ListenAndServe()
if err != nil {
return fmt.Errorf("failed to listen and serve: %w", err)
}
return nil
}
func registerGRPCServices(srv *baseserver.Server, conn *gorm.DB) error {
v1.RegisterUsageServiceServer(srv.GRPC(), apiv1.NewUsageService(conn))
return nil
}