From 10c85524a4e44eb7f78620c2a96e7885cf15ccb2 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Thu, 23 Jun 2022 06:41:16 +0000 Subject: [PATCH 1/3] Make stripe package return clients `Authenticate` now returns a Client object rather than acting as a singleton. Change the `UpdateUsage` function to be a method on the client type. See: https://github.com/stripe/stripe-go#with-a-client --- components/usage/pkg/stripe/stripe.go | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/components/usage/pkg/stripe/stripe.go b/components/usage/pkg/stripe/stripe.go index 53e0bf717c825e..d294eaae05fca8 100644 --- a/components/usage/pkg/stripe/stripe.go +++ b/components/usage/pkg/stripe/stripe.go @@ -13,35 +13,39 @@ import ( "github.com/gitpod-io/gitpod/common-go/log" "github.com/stripe/stripe-go/v72" - "github.com/stripe/stripe-go/v72/customer" - "github.com/stripe/stripe-go/v72/usagerecord" + "github.com/stripe/stripe-go/v72/client" ) +type Client struct { + sc *client.API +} + type stripeKeys struct { PublishableKey string `json:"publishableKey"` SecretKey string `json:"secretKey"` } // Authenticate authenticates the Stripe client using a provided file containing a Stripe secret key. -func Authenticate(apiKeyFile string) error { +func Authenticate(apiKeyFile string) (*Client, error) { bytes, err := os.ReadFile(apiKeyFile) if err != nil { - return err + return nil, err } var stripeKeys stripeKeys err = json.Unmarshal(bytes, &stripeKeys) if err != nil { - return err + return nil, err } - stripe.Key = stripeKeys.SecretKey - return nil + sc := &client.API{} + sc.Init(stripeKeys.SecretKey, nil) + return &Client{sc: sc}, nil } // UpdateUsage updates teams' Stripe subscriptions with usage data // `usageForTeam` is a map from team name to total workspace seconds used within a billing period. -func UpdateUsage(usageForTeam map[string]int64) error { +func (c *Client) UpdateUsage(usageForTeam map[string]int64) error { teamIds := make([]string, 0, len(usageForTeam)) for k := range usageForTeam { teamIds = append(teamIds, k) @@ -56,7 +60,7 @@ func UpdateUsage(usageForTeam map[string]int64) error { Expand: []*string{stripe.String("data.subscriptions")}, }, } - iter := customer.Search(params) + iter := c.sc.Customers.Search(params) for iter.Next() { customer := iter.Customer() log.Infof("found customer %q for teamId %q", customer.Name, customer.Metadata["teamId"]) @@ -77,7 +81,7 @@ func UpdateUsage(usageForTeam map[string]int64) error { subscriptionItemId := subscription.Items.Data[0].ID log.Infof("registering usage against subscriptionItem %q", subscriptionItemId) - _, err := usagerecord.New(&stripe.UsageRecordParams{ + _, err := c.sc.UsageRecords.New(&stripe.UsageRecordParams{ SubscriptionItem: stripe.String(subscriptionItemId), Quantity: stripe.Int64(creditsUsed), }) From e9eec3a690552c582e3089e27ad7bfadf741b55e Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Thu, 23 Jun 2022 08:00:02 +0000 Subject: [PATCH 2/3] Make `run` command use new Stripe client Give an instance of the stripe client to the stripe billing controller. --- components/usage/cmd/run.go | 4 ++-- components/usage/pkg/controller/billing.go | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/components/usage/cmd/run.go b/components/usage/cmd/run.go index 71c66c57bdb846..1638e1ea95ce6e 100644 --- a/components/usage/cmd/run.go +++ b/components/usage/cmd/run.go @@ -47,11 +47,11 @@ func run() *cobra.Command { var billingController controller.BillingController = &controller.NoOpBillingController{} if apiKeyFile != "" { - err = stripe.Authenticate(apiKeyFile) + c, err := stripe.Authenticate(apiKeyFile) if err != nil { log.WithError(err).Fatal("Failed to initialize stripe client.") } - billingController = &controller.StripeBillingController{} + billingController = controller.NewStripeBillingController(c) } ctrl, err := controller.New(schedule, controller.NewUsageReconciler(conn, billingController)) diff --git a/components/usage/pkg/controller/billing.go b/components/usage/pkg/controller/billing.go index a496d202a3f727..e8ea9347ab1161 100644 --- a/components/usage/pkg/controller/billing.go +++ b/components/usage/pkg/controller/billing.go @@ -11,10 +11,17 @@ type BillingController interface { } type NoOpBillingController struct{} -type StripeBillingController struct{} func (b *NoOpBillingController) Reconcile(report []TeamUsage) {} +type StripeBillingController struct { + sc *stripe.Client +} + +func NewStripeBillingController(sc *stripe.Client) *StripeBillingController { + return &StripeBillingController{sc: sc} +} + func (b *StripeBillingController) Reconcile(report []TeamUsage) { // Convert the usage report to sum all entries for the same team. var summedReport = make(map[string]int64) @@ -22,5 +29,5 @@ func (b *StripeBillingController) Reconcile(report []TeamUsage) { summedReport[usageEntry.TeamID] += usageEntry.WorkspaceSeconds } - stripe.UpdateUsage(summedReport) + b.sc.UpdateUsage(summedReport) } From 5f9e5b683c6372ce3c33a2988bd9257f8feaec61 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Thu, 23 Jun 2022 10:32:46 +0000 Subject: [PATCH 3/3] Make client init take a config struct not a path * Rename the function to `New`. * Lift config file unmarshaling out of the package and into the consumer. --- components/usage/cmd/run.go | 23 ++++++++++++++++++----- components/usage/pkg/stripe/stripe.go | 23 ++++------------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/components/usage/cmd/run.go b/components/usage/cmd/run.go index 1638e1ea95ce6e..8a9d84c57b2558 100644 --- a/components/usage/cmd/run.go +++ b/components/usage/cmd/run.go @@ -5,15 +5,17 @@ package cmd import ( + "encoding/json" + "net" + "os" + "time" + "github.com/gitpod-io/gitpod/common-go/baseserver" "github.com/gitpod-io/gitpod/common-go/log" "github.com/gitpod-io/gitpod/usage/pkg/controller" "github.com/gitpod-io/gitpod/usage/pkg/db" "github.com/gitpod-io/gitpod/usage/pkg/stripe" "github.com/spf13/cobra" - "net" - "os" - "time" ) func init() { @@ -47,9 +49,20 @@ func run() *cobra.Command { var billingController controller.BillingController = &controller.NoOpBillingController{} if apiKeyFile != "" { - c, err := stripe.Authenticate(apiKeyFile) + bytes, err := os.ReadFile(apiKeyFile) + if err != nil { + log.WithError(err).Fatal("Failed to read Stripe API keys.") + } + + var config stripe.ClientConfig + err = json.Unmarshal(bytes, &config) + if err != nil { + log.WithError(err).Fatal("Failed to unmarshal Stripe API keys.") + } + + c, err := stripe.New(config) if err != nil { - log.WithError(err).Fatal("Failed to initialize stripe client.") + log.WithError(err).Fatal("Failed to initialize Stripe client.") } billingController = controller.NewStripeBillingController(c) } diff --git a/components/usage/pkg/stripe/stripe.go b/components/usage/pkg/stripe/stripe.go index d294eaae05fca8..462cf45d402af5 100644 --- a/components/usage/pkg/stripe/stripe.go +++ b/components/usage/pkg/stripe/stripe.go @@ -5,10 +5,8 @@ package stripe import ( - "encoding/json" "fmt" "math" - "os" "strings" "github.com/gitpod-io/gitpod/common-go/log" @@ -20,27 +18,14 @@ type Client struct { sc *client.API } -type stripeKeys struct { +type ClientConfig struct { PublishableKey string `json:"publishableKey"` SecretKey string `json:"secretKey"` } -// Authenticate authenticates the Stripe client using a provided file containing a Stripe secret key. -func Authenticate(apiKeyFile string) (*Client, error) { - bytes, err := os.ReadFile(apiKeyFile) - if err != nil { - return nil, err - } - - var stripeKeys stripeKeys - err = json.Unmarshal(bytes, &stripeKeys) - if err != nil { - return nil, err - } - - sc := &client.API{} - sc.Init(stripeKeys.SecretKey, nil) - return &Client{sc: sc}, nil +// New authenticates a Stripe client using the provided config +func New(config ClientConfig) (*Client, error) { + return &Client{sc: client.New(config.SecretKey, nil)}, nil } // UpdateUsage updates teams' Stripe subscriptions with usage data