11package usage
22
33import (
4- "bytes"
54 "context"
65 "crypto/ed25519"
7- "encoding/json"
86 "fmt"
97 "io"
108 "net/http"
9+ "net/url"
1110 "time"
1211
1312 "github.com/google/uuid"
1413 "golang.org/x/xerrors"
1514
1615 "cdr.dev/slog"
17- "github.com/coder/coder/v2/buildinfo"
1816 "github.com/coder/coder/v2/coderd/database"
1917 "github.com/coder/coder/v2/coderd/database/dbauthz"
2018 "github.com/coder/coder/v2/coderd/database/dbtime"
2119 "github.com/coder/coder/v2/coderd/pproflabel"
2220 "github.com/coder/coder/v2/coderd/usage/usagetypes"
2321 "github.com/coder/coder/v2/cryptorand"
2422 "github.com/coder/coder/v2/enterprise/coderd/license"
23+ "github.com/coder/coder/v2/enterprise/coderd/usage/tallymansdk"
2524 "github.com/coder/quartz"
2625)
2726
2827const (
29- tallymanURL = "https://tallyman-prod.coder.com"
30- tallymanIngestURLV1 = tallymanURL + "/api/v1/events/ingest"
31-
3228 tallymanPublishInitialMinimumDelay = 5 * time .Minute
3329 // Chosen to be a prime number and not a multiple of 5 like many other
3430 // recurring tasks.
@@ -56,7 +52,7 @@ type tallymanPublisher struct {
5652 done chan struct {}
5753
5854 // Configured with options:
59- ingestURL string
55+ baseURL * url. URL
6056 httpClient * http.Client
6157 clock quartz.Clock
6258 initialDelay time.Duration
@@ -70,6 +66,7 @@ func NewTallymanPublisher(ctx context.Context, log slog.Logger, db database.Stor
7066 ctx , cancel := context .WithCancel (ctx )
7167 ctx = dbauthz .AsUsagePublisher (ctx ) //nolint:gocritic // we intentionally want to be able to process usage events
7268
69+ baseURL , _ := url .Parse (tallymansdk .DefaultURL )
7370 publisher := & tallymanPublisher {
7471 ctx : ctx ,
7572 ctxCancel : cancel ,
@@ -78,7 +75,7 @@ func NewTallymanPublisher(ctx context.Context, log slog.Logger, db database.Stor
7875 licenseKeys : keys ,
7976 done : make (chan struct {}),
8077
81- ingestURL : tallymanIngestURLV1 ,
78+ baseURL : baseURL ,
8279 httpClient : http .DefaultClient ,
8380 clock : quartz .NewReal (),
8481 }
@@ -108,10 +105,18 @@ func PublisherWithClock(clock quartz.Clock) TallymanPublisherOption {
108105}
109106
110107// PublisherWithIngestURL sets the ingest URL to use for publishing usage
111- // events.
108+ // events. The base URL is extracted from the ingest URL.
112109func PublisherWithIngestURL (ingestURL string ) TallymanPublisherOption {
113110 return func (p * tallymanPublisher ) {
114- p .ingestURL = ingestURL
111+ parsed , err := url .Parse (ingestURL )
112+ if err != nil {
113+ // This shouldn't happen in practice, but if it does, keep the default.
114+ return
115+ }
116+ p .baseURL = & url.URL {
117+ Scheme : parsed .Scheme ,
118+ Host : parsed .Host ,
119+ }
115120 }
116121}
117122
@@ -388,41 +393,16 @@ func (p *tallymanPublisher) getBestLicenseJWT(ctx context.Context) (string, erro
388393}
389394
390395func (p * tallymanPublisher ) sendPublishRequest (ctx context.Context , deploymentID uuid.UUID , licenseJwt string , req usagetypes.TallymanV1IngestRequest ) (usagetypes.TallymanV1IngestResponse , error ) {
391- body , err := json .Marshal (req )
392- if err != nil {
393- return usagetypes.TallymanV1IngestResponse {}, err
394- }
395-
396- r , err := http .NewRequestWithContext (ctx , http .MethodPost , p .ingestURL , bytes .NewReader (body ))
397- if err != nil {
398- return usagetypes.TallymanV1IngestResponse {}, err
399- }
400- r .Header .Set ("User-Agent" , "coderd/" + buildinfo .Version ())
401- r .Header .Set (usagetypes .TallymanCoderLicenseKeyHeader , licenseJwt )
402- r .Header .Set (usagetypes .TallymanCoderDeploymentIDHeader , deploymentID .String ())
403-
404- resp , err := p .httpClient .Do (r )
405- if err != nil {
406- return usagetypes.TallymanV1IngestResponse {}, err
407- }
408- defer resp .Body .Close ()
409-
410- if resp .StatusCode != http .StatusOK {
411- var errBody usagetypes.TallymanV1Response
412- if err := json .NewDecoder (resp .Body ).Decode (& errBody ); err != nil {
413- errBody = usagetypes.TallymanV1Response {
414- Message : fmt .Sprintf ("could not decode error response body: %v" , err ),
415- }
416- }
417- return usagetypes.TallymanV1IngestResponse {}, xerrors .Errorf ("unexpected status code %v, error: %s" , resp .StatusCode , errBody .Message )
418- }
419-
420- var respBody usagetypes.TallymanV1IngestResponse
421- if err := json .NewDecoder (resp .Body ).Decode (& respBody ); err != nil {
422- return usagetypes.TallymanV1IngestResponse {}, xerrors .Errorf ("decode response body: %w" , err )
423- }
396+ // Create a new SDK client for this request.
397+ // We create it per-request since the license key may change.
398+ sdkClient := tallymansdk .New (
399+ p .baseURL ,
400+ licenseJwt ,
401+ deploymentID ,
402+ tallymansdk .WithHTTPClient (p .httpClient ),
403+ )
424404
425- return respBody , nil
405+ return sdkClient . PublishUsageEvents ( ctx , req )
426406}
427407
428408// Close implements Publisher.
0 commit comments