gcloud/cloudbilling.go (143 lines of code) (raw):

// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package gcloud import ( "context" "fmt" "math/rand" "strings" "sync" "time" "google.golang.org/api/cloudbilling/v1" "google.golang.org/api/cloudresourcemanager/v1" ) func (c *Client) getCloudbillingService() (*cloudbilling.APIService, error) { var err error svc := c.services.billing if svc != nil { return svc, nil } svc, err = cloudbilling.NewService(context.Background(), c.opts) if err != nil { return nil, fmt.Errorf("could not retrieve service: %w", err) } svc.UserAgent = c.userAgent c.services.billing = svc return svc, nil } // BillingAccountList gets a list of the billing accounts a user has access to func (c *Client) BillingAccountList() ([]*cloudbilling.BillingAccount, error) { resp := []*cloudbilling.BillingAccount{} i := c.get("BillingAccountList") switch val := i.(type) { case []*cloudbilling.BillingAccount: return val, nil } svc, err := c.getCloudbillingService() if err != nil { return resp, err } results, err := svc.BillingAccounts.List().Do() if err != nil { return resp, err } c.save("BillingAccountList", results.BillingAccounts) return results.BillingAccounts, nil } // BillingAccountAttach will enable billing in a given project func (c *Client) BillingAccountAttach(project, account string) error { retries := 10 svc, err := c.getCloudbillingService() if err != nil { return err } ba := fmt.Sprintf("billingAccounts/%s", account) proj := fmt.Sprintf("projects/%s", project) cfg := cloudbilling.ProjectBillingInfo{ BillingAccountName: ba, } var looperr error for i := 0; i < retries; i++ { _, looperr = svc.Projects.UpdateBillingInfo(proj, &cfg).Do() if looperr == nil { return nil } if strings.Contains(looperr.Error(), "User is not authorized to get billing info") { continue } } if strings.Contains(looperr.Error(), "Request contains an invalid argument") { return ErrorBillingInvalidAccount } if strings.Contains(looperr.Error(), "Not a valid billing account") { return ErrorBillingInvalidAccount } if strings.Contains(looperr.Error(), "The caller does not have permission") { return ErrorBillingNoPermission } return looperr } // ProjectListWithBilling gets a list of projects with their billing information func (c *Client) ProjectListWithBilling(p []*cloudresourcemanager.Project) ([]ProjectWithBilling, error) { res := []ProjectWithBilling{} svc, err := c.getCloudbillingService() if err != nil { return res, err } projs, _ := c.ProjectListWithBillingEnabled() // if err != nil { // return res, err // } var wg sync.WaitGroup wg.Add(len(p)) for _, v := range p { go func(p *cloudresourcemanager.Project) { defer wg.Done() if _, ok := projs[p.ProjectId]; ok { pwb := ProjectWithBilling{Name: p.Name, ID: p.ProjectId, BillingEnabled: true} res = append(res, pwb) return } // Getting random quota errors when somebody had too many projects. // sleeping randoming for a second fixed it. // I don't think these requests can be fixed by batching. sleepRandom() if p.LifecycleState == "ACTIVE" && p.Name != "" { proj := fmt.Sprintf("projects/%s", p.ProjectId) tmp, err := svc.Projects.GetBillingInfo(proj).Do() if err != nil { if strings.Contains(err.Error(), "The caller does not have permission") { // fmt.Printf("project: %+v\n", p) return } fmt.Printf("error getting billing information: %s\n", err) return } pwb := ProjectWithBilling{Name: p.Name, ID: p.ProjectId, BillingEnabled: tmp.BillingEnabled} res = append(res, pwb) return } }(v) } wg.Wait() return res, nil } // ProjectListWithBillingEnabled queries the billing accounts a user has access to // to generate a list of projects for each billing account. Will hopefully // reduce the number of calls made to billing api func (c *Client) ProjectListWithBillingEnabled() (map[string]bool, error) { r := map[string]bool{} svc, err := c.getCloudbillingService() if err != nil { return r, err } bas, err := c.BillingAccountList() if err != nil { return r, err } for _, v := range bas { result, err := svc.BillingAccounts.Projects.List(v.Name).Do() if err != nil { return r, err } for _, v := range result.ProjectBillingInfo { if v.BillingEnabled { r[v.ProjectId] = true } } } return r, nil } func randomInRange(min, max int) int { rand.Seed(time.Now().UnixNano()) return rand.Intn(max-min+1) + min } func sleepRandom() { d := time.Second * time.Duration(randomInRange(0, 1)) time.Sleep(d) }