From a29ce6603158b4d924278cdbd7a81e63633cf0f3 Mon Sep 17 00:00:00 2001 From: Franck Cuny Date: Thu, 25 Jan 2024 17:47:06 -0800 Subject: gha-billing: how many minutes are left in the cycle --- cmd/ghabilling/README.md | 15 ++++++++ cmd/ghabilling/main.go | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 cmd/ghabilling/README.md create mode 100644 cmd/ghabilling/main.go diff --git a/cmd/ghabilling/README.md b/cmd/ghabilling/README.md new file mode 100644 index 0000000..2aa08ce --- /dev/null +++ b/cmd/ghabilling/README.md @@ -0,0 +1,15 @@ +# `gha-billing` + +Print information about how many free minutes of GitHub actions are left for this cycle. + +The API for this is documented [here](https://docs.github.com/en/rest/billing/billing?apiVersion=2022-11-28#get-github-actions-billing-for-an-organization). + +For this you need a [token](https://github.com/settings/personal-access-tokens) with the following permissions: +- [plan](https://docs.github.com/en/rest/authentication/permissions-required-for-fine-grained-personal-access-tokens?apiVersion=2022-11-28#user-permissions-for-plan) + +## usage + +```sh +➜ world git:(main) ✗ go run ./cmd/ghabilling -t github_pat_ +this cycle, 14 minutes have been used, and 1986 minutes are remaining +``` diff --git a/cmd/ghabilling/main.go b/cmd/ghabilling/main.go new file mode 100644 index 0000000..bb5f41a --- /dev/null +++ b/cmd/ghabilling/main.go @@ -0,0 +1,91 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "net/http" + "os" + "time" + + "github.com/fcuny/world/internal/version" +) + +const API_URL = "https://api.github.com" + +const usage = `Usage: + gha-billing -t [TOKEN] + +Options: + -t, --token GitHub API's token + -v, --version Print version information + -h, --help Print this message +` + +// https://docs.github.com/en/rest/billing/billing?apiVersion=2022-11-28#get-github-actions-billing-for-an-organization +type githubActionBilling struct { + TotalMinutesUsed float64 `json:"total_minutes_used"` + TotalPaidMinutesUsed float64 `json:"total_paid_minutes_used"` + IncludedMinutes float64 `json:"included_minutes"` + MinutesUsedBreakdown map[string]int `json:"minutes_used_breakdown"` +} + +func main() { + flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) } + + var ( + tokenFlag string + userFlag string + versionFlag bool + ) + + flag.StringVar(&tokenFlag, "token", "", "GitHub API token") + flag.StringVar(&tokenFlag, "t", "", "GitHub API token") + flag.StringVar(&userFlag, "user", "fcuny", "GitHub API token") + flag.StringVar(&userFlag, "u", "fcuny", "GitHub API token") + flag.BoolVar(&versionFlag, "version", false, "Print version information") + flag.BoolVar(&versionFlag, "v", false, "Print version information") + + flag.Parse() + + if versionFlag { + information := version.VersionAndBuildInfo() + fmt.Println(information) + return + } + + if tokenFlag == "" { + fmt.Fprintf(os.Stderr, "The API token is not set\n") + } + + ctx := context.TODO() + + url := fmt.Sprintf("%s/users/%s/settings/billing/actions", API_URL, userFlag) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + fmt.Fprintf(os.Stderr, "could not create a request: %v\n", err) + os.Exit(1) + } + req.Header.Set("Authorization", fmt.Sprintf("token %s", tokenFlag)) + req.Header.Set("Accept", "application/vnd.github.v3+json") + + client := http.Client{ + Timeout: 30 * time.Second, + } + + res, err := client.Do(req) + if err != nil { + fmt.Fprintf(os.Stderr, "error making http request: %s\n", err) + os.Exit(1) + } + + var b githubActionBilling + if err := json.NewDecoder(res.Body).Decode(&b); err != nil { + fmt.Fprintf(os.Stderr, "error parsing the JSON response: %v\n", err) + os.Exit(1) + } + + timeRemaining := b.IncludedMinutes - b.TotalMinutesUsed + fmt.Printf("this cycle, %d minutes have been used, and %d minutes are remaining\n", int(b.TotalMinutesUsed), int(timeRemaining)) +} -- cgit 1.4.1