131 lines
4.5 KiB
Go
131 lines
4.5 KiB
Go
package notmain
|
||
|
||
import (
|
||
"context"
|
||
"flag"
|
||
"os"
|
||
|
||
"github.com/letsencrypt/boulder/cmd"
|
||
"github.com/letsencrypt/boulder/email"
|
||
emailpb "github.com/letsencrypt/boulder/email/proto"
|
||
bgrpc "github.com/letsencrypt/boulder/grpc"
|
||
)
|
||
|
||
// Config holds the configuration for the email-exporter service.
|
||
type Config struct {
|
||
EmailExporter struct {
|
||
cmd.ServiceConfig
|
||
|
||
// PerDayLimit enforces the daily request limit imposed by the Pardot
|
||
// API. The total daily limit, which varies based on the Salesforce
|
||
// Pardot subscription tier, must be distributed among all
|
||
// email-exporter instances. For more information, see:
|
||
// https://developer.salesforce.com/docs/marketing/pardot/guide/overview.html?q=rate+limits#daily-requests-limits
|
||
PerDayLimit float64 `validate:"required,min=1"`
|
||
|
||
// MaxConcurrentRequests enforces the concurrent request limit imposed
|
||
// by the Pardot API. This limit must be distributed among all
|
||
// email-exporter instances and be proportional to each instance's
|
||
// PerDayLimit. For example, if the total daily limit is 50,000 and one
|
||
// instance is assigned 40% (20,000 requests), it should also receive
|
||
// 40% of the max concurrent requests (2 out of 5). For more
|
||
// information, see:
|
||
// https://developer.salesforce.com/docs/marketing/pardot/guide/overview.html?q=rate+limits#concurrent-requests
|
||
MaxConcurrentRequests int `validate:"required,min=1,max=5"`
|
||
|
||
// PardotBusinessUnit is the Pardot business unit to use.
|
||
PardotBusinessUnit string `validate:"required"`
|
||
|
||
// ClientId is the OAuth API client ID provided by Salesforce.
|
||
ClientId cmd.PasswordConfig
|
||
|
||
// ClientSecret is the OAuth API client secret provided by Salesforce.
|
||
ClientSecret cmd.PasswordConfig
|
||
|
||
// SalesforceBaseURL is the base URL for the Salesforce API. (e.g.,
|
||
// "https://login.salesforce.com")
|
||
SalesforceBaseURL string `validate:"required"`
|
||
|
||
// PardotBaseURL is the base URL for the Pardot API. (e.g.,
|
||
// "https://pi.pardot.com")
|
||
PardotBaseURL string `validate:"required"`
|
||
|
||
// EmailCacheSize controls how many hashed email addresses are retained
|
||
// in memory to prevent duplicates from being sent to the Pardot API.
|
||
// Each entry consumes ~120 bytes, so 100,000 entries uses around 12 MB
|
||
// of memory. If left unset, no caching is performed.
|
||
EmailCacheSize int `validate:"omitempty,min=1"`
|
||
}
|
||
Syslog cmd.SyslogConfig
|
||
OpenTelemetry cmd.OpenTelemetryConfig
|
||
}
|
||
|
||
func main() {
|
||
configFile := flag.String("config", "", "Path to configuration file")
|
||
grpcAddr := flag.String("addr", "", "gRPC listen address override")
|
||
debugAddr := flag.String("debug-addr", "", "Debug server address override")
|
||
flag.Parse()
|
||
|
||
if *configFile == "" {
|
||
flag.Usage()
|
||
os.Exit(1)
|
||
}
|
||
|
||
var c Config
|
||
err := cmd.ReadConfigFile(*configFile, &c)
|
||
cmd.FailOnError(err, "Reading JSON config file into config structure")
|
||
|
||
if *grpcAddr != "" {
|
||
c.EmailExporter.ServiceConfig.GRPC.Address = *grpcAddr
|
||
}
|
||
if *debugAddr != "" {
|
||
c.EmailExporter.ServiceConfig.DebugAddr = *debugAddr
|
||
}
|
||
|
||
scope, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.EmailExporter.ServiceConfig.DebugAddr)
|
||
defer oTelShutdown(context.Background())
|
||
|
||
logger.Info(cmd.VersionString())
|
||
|
||
clk := cmd.Clock()
|
||
clientId, err := c.EmailExporter.ClientId.Pass()
|
||
cmd.FailOnError(err, "Loading clientId")
|
||
clientSecret, err := c.EmailExporter.ClientSecret.Pass()
|
||
cmd.FailOnError(err, "Loading clientSecret")
|
||
|
||
var cache *email.EmailCache
|
||
if c.EmailExporter.EmailCacheSize > 0 {
|
||
cache = email.NewHashedEmailCache(c.EmailExporter.EmailCacheSize, scope)
|
||
}
|
||
|
||
pardotClient, err := email.NewPardotClientImpl(
|
||
clk,
|
||
c.EmailExporter.PardotBusinessUnit,
|
||
clientId,
|
||
clientSecret,
|
||
c.EmailExporter.SalesforceBaseURL,
|
||
c.EmailExporter.PardotBaseURL,
|
||
)
|
||
cmd.FailOnError(err, "Creating Pardot API client")
|
||
exporterServer := email.NewExporterImpl(pardotClient, cache, c.EmailExporter.PerDayLimit, c.EmailExporter.MaxConcurrentRequests, scope, logger)
|
||
|
||
tlsConfig, err := c.EmailExporter.TLS.Load(scope)
|
||
cmd.FailOnError(err, "Loading email-exporter TLS config")
|
||
|
||
daemonCtx, shutdownExporterServer := context.WithCancel(context.Background())
|
||
go exporterServer.Start(daemonCtx)
|
||
|
||
start, err := bgrpc.NewServer(c.EmailExporter.GRPC, logger).Add(
|
||
&emailpb.Exporter_ServiceDesc, exporterServer).Build(tlsConfig, scope, clk)
|
||
cmd.FailOnError(err, "Configuring email-exporter gRPC server")
|
||
|
||
err = start()
|
||
shutdownExporterServer()
|
||
exporterServer.Drain()
|
||
cmd.FailOnError(err, "email-exporter gRPC service failed to start")
|
||
}
|
||
|
||
func init() {
|
||
cmd.RegisterCommand("email-exporter", main, &cmd.ConfigValidator{Config: &Config{}})
|
||
}
|