github.com/letsencrypt/boulder@v0.20251208.0/cmd/email-exporter/main.go (about) 1 package notmain 2 3 import ( 4 "context" 5 "flag" 6 "os" 7 8 "github.com/jmhodges/clock" 9 10 "github.com/letsencrypt/boulder/cmd" 11 "github.com/letsencrypt/boulder/email" 12 emailpb "github.com/letsencrypt/boulder/email/proto" 13 bgrpc "github.com/letsencrypt/boulder/grpc" 14 ) 15 16 // Config holds the configuration for the email-exporter service. 17 type Config struct { 18 EmailExporter struct { 19 cmd.ServiceConfig 20 21 // PerDayLimit enforces the daily request limit imposed by the Pardot 22 // API. The total daily limit, which varies based on the Salesforce 23 // Pardot subscription tier, must be distributed among all 24 // email-exporter instances. For more information, see: 25 // https://developer.salesforce.com/docs/marketing/pardot/guide/overview.html?q=rate+limits#daily-requests-limits 26 PerDayLimit float64 `validate:"required,min=1"` 27 28 // MaxConcurrentRequests enforces the concurrent request limit imposed 29 // by the Pardot API. This limit must be distributed among all 30 // email-exporter instances and be proportional to each instance's 31 // PerDayLimit. For example, if the total daily limit is 50,000 and one 32 // instance is assigned 40% (20,000 requests), it should also receive 33 // 40% of the max concurrent requests (2 out of 5). For more 34 // information, see: 35 // https://developer.salesforce.com/docs/marketing/pardot/guide/overview.html?q=rate+limits#concurrent-requests 36 MaxConcurrentRequests int `validate:"required,min=1,max=5"` 37 38 // PardotBusinessUnit is the Pardot business unit to use. 39 PardotBusinessUnit string `validate:"required"` 40 41 // ClientId is the OAuth API client ID provided by Salesforce. 42 ClientId cmd.PasswordConfig 43 44 // ClientSecret is the OAuth API client secret provided by Salesforce. 45 ClientSecret cmd.PasswordConfig 46 47 // SalesforceBaseURL is the base URL for the Salesforce API. (e.g., 48 // "https://company.salesforce.com") 49 SalesforceBaseURL string `validate:"required"` 50 51 // PardotBaseURL is the base URL for the Pardot API. (e.g., 52 // "https://pi.pardot.com") 53 PardotBaseURL string `validate:"required"` 54 55 // EmailCacheSize controls how many hashed email addresses are retained 56 // in memory to prevent duplicates from being sent to the Pardot API. 57 // Each entry consumes ~120 bytes, so 100,000 entries uses around 12 MB 58 // of memory. If left unset, no caching is performed. 59 EmailCacheSize int `validate:"omitempty,min=1"` 60 } 61 Syslog cmd.SyslogConfig 62 OpenTelemetry cmd.OpenTelemetryConfig 63 } 64 65 func main() { 66 configFile := flag.String("config", "", "Path to configuration file") 67 grpcAddr := flag.String("addr", "", "gRPC listen address override") 68 debugAddr := flag.String("debug-addr", "", "Debug server address override") 69 flag.Parse() 70 71 if *configFile == "" { 72 flag.Usage() 73 os.Exit(1) 74 } 75 76 var c Config 77 err := cmd.ReadConfigFile(*configFile, &c) 78 cmd.FailOnError(err, "Reading JSON config file into config structure") 79 80 if *grpcAddr != "" { 81 c.EmailExporter.ServiceConfig.GRPC.Address = *grpcAddr 82 } 83 if *debugAddr != "" { 84 c.EmailExporter.ServiceConfig.DebugAddr = *debugAddr 85 } 86 87 scope, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.EmailExporter.ServiceConfig.DebugAddr) 88 defer oTelShutdown(context.Background()) 89 90 logger.Info(cmd.VersionString()) 91 92 clk := clock.New() 93 clientId, err := c.EmailExporter.ClientId.Pass() 94 cmd.FailOnError(err, "Loading clientId") 95 clientSecret, err := c.EmailExporter.ClientSecret.Pass() 96 cmd.FailOnError(err, "Loading clientSecret") 97 98 var cache *email.EmailCache 99 if c.EmailExporter.EmailCacheSize > 0 { 100 cache = email.NewHashedEmailCache(c.EmailExporter.EmailCacheSize, scope) 101 } 102 103 sfClient, err := email.NewSalesforceClientImpl( 104 clk, 105 c.EmailExporter.PardotBusinessUnit, 106 clientId, 107 clientSecret, 108 c.EmailExporter.SalesforceBaseURL, 109 c.EmailExporter.PardotBaseURL, 110 ) 111 cmd.FailOnError(err, "Creating Pardot API client") 112 exporterServer := email.NewExporterImpl(sfClient, cache, c.EmailExporter.PerDayLimit, c.EmailExporter.MaxConcurrentRequests, scope, logger) 113 114 tlsConfig, err := c.EmailExporter.TLS.Load(scope) 115 cmd.FailOnError(err, "Loading email-exporter TLS config") 116 117 daemonCtx, shutdownExporterServer := context.WithCancel(context.Background()) 118 go exporterServer.Start(daemonCtx) 119 120 start, err := bgrpc.NewServer(c.EmailExporter.GRPC, logger).Add( 121 &emailpb.Exporter_ServiceDesc, exporterServer).Build(tlsConfig, scope, clk) 122 cmd.FailOnError(err, "Configuring email-exporter gRPC server") 123 124 err = start() 125 shutdownExporterServer() 126 exporterServer.Drain() 127 cmd.FailOnError(err, "email-exporter gRPC service failed to start") 128 } 129 130 func init() { 131 cmd.RegisterCommand("email-exporter", main, &cmd.ConfigValidator{Config: &Config{}}) 132 }