Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions github/billing_budgets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2025 The go-github AUTHORS. All rights reserved.
Comment thread
gmlewis marked this conversation as resolved.
Outdated
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
"context"
"fmt"
)

// Budget represents a GitHub budget.
type Budget struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Response schema of Budget

{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "description": "ID of the budget."
    },
    "budget_scope": {
      "type": "string",
      "description": "The type of scope for the budget",
      "enum": [
        "enterprise",
        "organization",
        "repository",
        "cost_center"
      ],
      "examples": [
        "enterprise"
      ]
    },
    "budget_entity_name": {
      "type": "string",
      "description": "The name of the entity to apply the budget to",
      "examples": [
        "octocat/hello-world"
      ]
    },
    "budget_amount": {
      "type": "integer",
      "description": "The budget amount in whole dollars. For license-based products, this represents the number of licenses."
    },
    "prevent_further_usage": {
      "type": "boolean",
      "description": "Whether to prevent additional spending once the budget is exceeded",
      "examples": [
        true
      ]
    },
    "budget_product_sku": {
      "type": "string",
      "description": "A single product or sku to apply the budget to.",
      "examples": [
        "actions_linux"
      ]
    },
    "budget_type": {
      "type": "string",
      "description": "The type of pricing for the budget",
      "enum": [
        "ProductPricing",
        "SkuPricing"
      ],
      "examples": [
        "ProductPricing"
      ]
    },
    "budget_alerting": {
      "type": "object",
      "properties": {
        "will_alert": {
          "type": "boolean",
          "description": "Whether alerts are enabled for this budget",
          "examples": [
            true
          ]
        },
        "alert_recipients": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Array of user login names who will receive alerts",
          "examples": [
            "mona",
            "lisa"
          ]
        }
      }
    }
  },
  "required": [
    "id",
    "budget_amount",
    "prevent_further_usage",
    "budget_product_sku",
    "budget_type",
    "budget_alerting",
    "budget_scope",
    "budget_entity_name"
  ]
}

it has few missing items -
budget_scope, budget_entity_name, budget_amount, prevent_further_usage, budget_product_sku.

ID *string `json:"id,omitempty"`

Check failure on line 15 in github/billing_budgets.go

View workflow job for this annotation

GitHub Actions / lint

File is not properly formatted (gci)
BudgetName *string `json:"budget_name,omitempty"`
TargetSubAccount *string `json:"target_sub_account,omitempty"`
TargetType *string `json:"target_type,omitempty"`
TargetID *int64 `json:"target_id,omitempty"`
TargetName *string `json:"target_name,omitempty"`
PricingModel *string `json:"pricing_model,omitempty"`
PricingModelID *string `json:"pricing_model_id,omitempty"`
PricingModelDisplayName *string `json:"pricing_model_display_name,omitempty"`
BudgetType *string `json:"budget_type,omitempty"`
LimitAmount *float64 `json:"limit_amount,omitempty"`
CurrentAmount *float64 `json:"current_amount,omitempty"`
Currency *string `json:"currency,omitempty"`
ExcludeCostCenterUsage *bool `json:"exclude_cost_center_usage,omitempty"`
BudgetAlerting *BudgetAlerting `json:"budget_alerting,omitempty"`
}

// BudgetAlerting represents the alerting configuration for a budget.
type BudgetAlerting struct {
WillAlert *bool `json:"will_alert,omitempty"`
AlertRecipients []string `json:"alert_recipients,omitempty"`
}

// ListOrganizationBudgets lists all budgets for an organization.
//
// GitHub API docs: https://docs.github.com/rest/billing/budgets#get-all-budgets-for-an-organization
//
// meta:operation GET /organizations/{org}/settings/billing/budgets
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency use //meta: (no space).

Suggested change
// meta:operation GET /organizations/{org}/settings/billing/budgets
//meta:operation GET /organizations/{org}/settings/billing/budgets

Same applies to other occurrences in this PR.

func (s *BillingService) ListOrganizationBudgets(ctx context.Context, org string) ([]*Budget, *Response, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint returns a response with the following shape:

{
  "budgets": [
    {
      "id": "2066deda-923f-43f9-88d2-62395a28c0cdd",
      "budget_type": "ProductPricing",
      "budget_product_skus": [
        "actions"
      ],
      "budget_scope": "enterprise",
      "budget_amount": 1000,
      "prevent_further_usage": true,
      "budget_alerting": {
        "will_alert": true,
        "alert_recipients": [
          "enterprise-admin",
          "billing-manager"
        ]
      }
    },
    {
      "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "budget_type": "SkuPricing",
      "budget_product_skus": [
        "actions_linux"
      ],
      "budget_scope": "organization",
      "budget_amount": 500,
      "prevent_further_usage": false,
      "budget_alerting": {
        "will_alert": true,
        "alert_recipients": [
          "org-owner"
        ]
      }
    }
  ],
  "has_next_page": false
}

I believe it should return a struct like

{
	Budgets     []Budget `json:"budgets"`
	HasNextPage bool     `json:"has_next_page,omitempty"`
}

u := fmt.Sprintf("organizations/%v/settings/billing/budgets", org)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

var budgets []*Budget
resp, err := s.client.Do(ctx, req, &budgets)
if err != nil {
return nil, resp, err
}

return budgets, resp, nil
}

// GetOrganizationBudget gets a specific budget for an organization.
//
// GitHub API docs: https://docs.github.com/rest/billing/budgets#get-a-budget-for-an-organization
//
// meta:operation GET /organizations/{org}/settings/billing/budgets/{budget_id}
func (s *BillingService) GetOrganizationBudget(ctx context.Context, org string, budgetID string) (*Budget, *Response, error) {

Check failure on line 64 in github/billing_budgets.go

View workflow job for this annotation

GitHub Actions / lint

paramTypeCombine: func(ctx context.Context, org string, budgetID string) (*Budget, *Response, error) could be replaced with func(ctx context.Context, org, budgetID string) (*Budget, *Response, error) (gocritic)
Comment thread
gmlewis marked this conversation as resolved.
Outdated
u := fmt.Sprintf("organizations/%v/settings/billing/budgets/%v", org, budgetID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

budget := new(Budget)
resp, err := s.client.Do(ctx, req, budget)
if err != nil {
return nil, resp, err
}

return budget, resp, nil
}

// UpdateOrganizationBudget updates a specific budget for an organization.
//
// GitHub API docs: https://docs.github.com/rest/billing/budgets#update-a-budget-for-an-organization
//
// meta:operation PATCH /organizations/{org}/settings/billing/budgets/{budget_id}
func (s *BillingService) UpdateOrganizationBudget(ctx context.Context, org string, budgetID string, budget *Budget) (*Budget, *Response, error) {

Check failure on line 85 in github/billing_budgets.go

View workflow job for this annotation

GitHub Actions / lint

paramTypeCombine: func(ctx context.Context, org string, budgetID string, budget *Budget) (*Budget, *Response, error) could be replaced with func(ctx context.Context, org, budgetID string, budget *Budget) (*Budget, *Response, error) (gocritic)
Comment thread
gmlewis marked this conversation as resolved.
Outdated
u := fmt.Sprintf("organizations/%v/settings/billing/budgets/%v", org, budgetID)
req, err := s.client.NewRequest("PATCH", u, budget)
if err != nil {
return nil, nil, err
}

updatedBudget := new(Budget)
resp, err := s.client.Do(ctx, req, updatedBudget)
if err != nil {
return nil, resp, err
}

return updatedBudget, resp, nil
}

// DeleteOrganizationBudget deletes a specific budget for an organization.
//
// GitHub API docs: https://docs.github.com/rest/billing/budgets#delete-a-budget-for-an-organization
//
// meta:operation DELETE /organizations/{org}/settings/billing/budgets/{budget_id}
func (s *BillingService) DeleteOrganizationBudget(ctx context.Context, org string, budgetID string) (*Response, error) {

Check failure on line 106 in github/billing_budgets.go

View workflow job for this annotation

GitHub Actions / lint

paramTypeCombine: func(ctx context.Context, org string, budgetID string) (*Response, error) could be replaced with func(ctx context.Context, org, budgetID string) (*Response, error) (gocritic)
Comment thread
gmlewis marked this conversation as resolved.
Outdated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to Docs this endpoint also returns an object.
Image

u := fmt.Sprintf("organizations/%v/settings/billing/budgets/%v", org, budgetID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}

return s.client.Do(ctx, req, nil)
}
130 changes: 130 additions & 0 deletions github/billing_budgets_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2025 The go-github AUTHORS. All rights reserved.
Comment thread
gmlewis marked this conversation as resolved.
Outdated
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
"fmt"
"net/http"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestBillingService_ListOrganizationBudgets(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)

mux.HandleFunc("/organizations/o/settings/billing/budgets", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[
{
"id": "1",
"budget_name": "Budget 1",
"limit_amount": 100.5,
"budget_alerting": {
"will_alert": true,
"alert_recipients": ["user1"]
}
}
]`)
})

ctx := t.Context()
budgets, _, err := client.Billing.ListOrganizationBudgets(ctx, "o")
if err != nil {
t.Errorf("Billing.ListOrganizationBudgets returned error: %v", err)
}

want := []*Budget{
{
ID: Ptr("1"),
BudgetName: Ptr("Budget 1"),
LimitAmount: Ptr(100.5),
BudgetAlerting: &BudgetAlerting{
WillAlert: Ptr(true),
AlertRecipients: []string{"user1"},
},
},
}
if !cmp.Equal(budgets, want) {
t.Errorf("Billing.ListOrganizationBudgets returned %+v, want %+v", budgets, want)
}
}

func TestBillingService_GetOrganizationBudget(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)

mux.HandleFunc("/organizations/o/settings/billing/budgets/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{
"id": "1",
"budget_name": "Budget 1"
}`)
})

ctx := t.Context()
budget, _, err := client.Billing.GetOrganizationBudget(ctx, "o", "1")
if err != nil {
t.Errorf("Billing.GetOrganizationBudget returned error: %v", err)
}

want := &Budget{
ID: Ptr("1"),
BudgetName: Ptr("Budget 1"),
}
if !cmp.Equal(budget, want) {
t.Errorf("Billing.GetOrganizationBudget returned %+v, want %+v", budget, want)
}
}

func TestBillingService_UpdateOrganizationBudget(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)

input := &Budget{
BudgetName: Ptr("Updated Budget"),
}

mux.HandleFunc("/organizations/o/settings/billing/budgets/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PATCH")
testBody(t, r, `{"budget_name":"Updated Budget"}`+"\n")
fmt.Fprint(w, `{
"id": "1",
"budget_name": "Updated Budget"
}`)
})

ctx := t.Context()
budget, _, err := client.Billing.UpdateOrganizationBudget(ctx, "o", "1", input)
if err != nil {
t.Errorf("Billing.UpdateOrganizationBudget returned error: %v", err)
}

want := &Budget{
ID: Ptr("1"),
BudgetName: Ptr("Updated Budget"),
}
if !cmp.Equal(budget, want) {
t.Errorf("Billing.UpdateOrganizationBudget returned %+v, want %+v", budget, want)
}
}

func TestBillingService_DeleteOrganizationBudget(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)

mux.HandleFunc("/organizations/o/settings/billing/budgets/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "DELETE")
w.WriteHeader(http.StatusNoContent)
})

ctx := t.Context()
_, err := client.Billing.DeleteOrganizationBudget(ctx, "o", "1")
if err != nil {
t.Errorf("Billing.DeleteOrganizationBudget returned error: %v", err)
}
}
110 changes: 110 additions & 0 deletions github/enterprise_billing_budgets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2025 The go-github AUTHORS. All rights reserved.
Comment thread
gmlewis marked this conversation as resolved.
Outdated
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
"context"
"fmt"
)

// ListEnterpriseBudgets lists all budgets for an enterprise.
//
// GitHub API docs: https://docs.github.com/rest/billing/budgets#get-all-budgets-for-an-enterprise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// GitHub API docs: https://docs.github.com/rest/billing/budgets#get-all-budgets-for-an-enterprise
// GitHub API docs: https://docs.github.com/en/enterprise-cloud@latest/rest/billing/budgets#get-all-budgets

https://docs.github.com/en/enterprise-cloud@latest/rest/billing/budgets#get-all-budgets

//
// meta:operation GET /enterprises/{enterprise}/settings/billing/budgets
func (s *EnterpriseService) ListEnterpriseBudgets(ctx context.Context, enterprise string) ([]*Budget, *Response, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we call this in Enterprise service we can simplify naming:

Suggested change
func (s *EnterpriseService) ListEnterpriseBudgets(ctx context.Context, enterprise string) ([]*Budget, *Response, error) {
func (s *EnterpriseService) ListBudgets(ctx context.Context, enterprise string) ([]*Budget, *Response, error) {

Am I missing something?

u := fmt.Sprintf("enterprises/%v/settings/billing/budgets", enterprise)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

var budgets []*Budget
resp, err := s.client.Do(ctx, req, &budgets)
if err != nil {
return nil, resp, err
}

return budgets, resp, nil
}

// GetEnterpriseBudget gets a specific budget for an enterprise.
//
// GitHub API docs: https://docs.github.com/rest/billing/budgets#get-a-budget-for-an-enterprise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// GitHub API docs: https://docs.github.com/rest/billing/budgets#get-a-budget-for-an-enterprise
// GitHub API docs: https://docs.github.com/en/enterprise-cloud@latest/rest/billing/budgets#get-a-budget-by-id

https://docs.github.com/en/enterprise-cloud@latest/rest/billing/budgets#get-a-budget-by-id

//
// meta:operation GET /enterprises/{enterprise}/settings/billing/budgets/{budget_id}
func (s *EnterpriseService) GetEnterpriseBudget(ctx context.Context, enterprise string, budgetID string) (*Budget, *Response, error) {

Check failure on line 39 in github/enterprise_billing_budgets.go

View workflow job for this annotation

GitHub Actions / lint

paramTypeCombine: func(ctx context.Context, enterprise string, budgetID string) (*Budget, *Response, error) could be replaced with func(ctx context.Context, enterprise, budgetID string) (*Budget, *Response, error) (gocritic)
Comment thread
gmlewis marked this conversation as resolved.
Outdated
u := fmt.Sprintf("enterprises/%v/settings/billing/budgets/%v", enterprise, budgetID)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

budget := new(Budget)
resp, err := s.client.Do(ctx, req, budget)
if err != nil {
return nil, resp, err
}

return budget, resp, nil
}

// CreateEnterpriseBudget creates a specific budget for an enterprise.
//
// GitHub API docs: https://docs.github.com/rest/billing/budgets#create-a-budget
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// GitHub API docs: https://docs.github.com/rest/billing/budgets#create-a-budget
// GitHub API docs: https://docs.github.com/en/enterprise-cloud@latest/rest/billing/budgets#create-a-budget

https://docs.github.com/en/enterprise-cloud@latest/rest/billing/budgets#create-a-budget

//
// meta:operation POST /enterprises/{enterprise}/settings/billing/budgets
func (s *EnterpriseService) CreateEnterpriseBudget(ctx context.Context, enterprise string, budget *Budget) (*Budget, *Response, error) {
u := fmt.Sprintf("enterprises/%v/settings/billing/budgets", enterprise)
req, err := s.client.NewRequest("POST", u, budget)
if err != nil {
return nil, nil, err
}

createdBudget := new(Budget)
resp, err := s.client.Do(ctx, req, createdBudget)
if err != nil {
return nil, resp, err
}

return createdBudget, resp, nil
}

// UpdateEnterpriseBudget updates a specific budget for an enterprise.
//
// GitHub API docs: https://docs.github.com/rest/billing/budgets#update-a-budget-for-an-enterprise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// GitHub API docs: https://docs.github.com/rest/billing/budgets#update-a-budget-for-an-enterprise
// GitHub API docs: https://docs.github.com/en/enterprise-cloud@latest/rest/billing/budgets#update-a-budget

https://docs.github.com/en/enterprise-cloud@latest/rest/billing/budgets#update-a-budget

//
// meta:operation PATCH /enterprises/{enterprise}/settings/billing/budgets/{budget_id}
func (s *EnterpriseService) UpdateEnterpriseBudget(ctx context.Context, enterprise string, budgetID string, budget *Budget) (*Budget, *Response, error) {

Check failure on line 81 in github/enterprise_billing_budgets.go

View workflow job for this annotation

GitHub Actions / lint

paramTypeCombine: func(ctx context.Context, enterprise string, budgetID string, budget *Budget) (*Budget, *Response, error) could be replaced with func(ctx context.Context, enterprise, budgetID string, budget *Budget) (*Budget, *Response, error) (gocritic)
Comment thread
gmlewis marked this conversation as resolved.
Outdated
u := fmt.Sprintf("enterprises/%v/settings/billing/budgets/%v", enterprise, budgetID)
req, err := s.client.NewRequest("PATCH", u, budget)
if err != nil {
return nil, nil, err
}

updatedBudget := new(Budget)
resp, err := s.client.Do(ctx, req, updatedBudget)
if err != nil {
return nil, resp, err
}

return updatedBudget, resp, nil
}

// DeleteEnterpriseBudget deletes a specific budget for an enterprise.
//
// GitHub API docs: https://docs.github.com/rest/billing/budgets#delete-a-budget-for-an-enterprise
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// GitHub API docs: https://docs.github.com/rest/billing/budgets#delete-a-budget-for-an-enterprise
// GitHub API docs: https://docs.github.com/en/enterprise-cloud@latest/rest/billing/budgets#delete-a-budget

https://docs.github.com/en/enterprise-cloud@latest/rest/billing/budgets#delete-a-budget

//
// meta:operation DELETE /enterprises/{enterprise}/settings/billing/budgets/{budget_id}
func (s *EnterpriseService) DeleteEnterpriseBudget(ctx context.Context, enterprise string, budgetID string) (*Response, error) {

Check failure on line 102 in github/enterprise_billing_budgets.go

View workflow job for this annotation

GitHub Actions / lint

paramTypeCombine: func(ctx context.Context, enterprise string, budgetID string) (*Response, error) could be replaced with func(ctx context.Context, enterprise, budgetID string) (*Response, error) (gocritic)
Comment thread
gmlewis marked this conversation as resolved.
Outdated
Copy link
Copy Markdown
Contributor

@Not-Dhananjay-Mishra Not-Dhananjay-Mishra Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also returns an object
Image

u := fmt.Sprintf("enterprises/%v/settings/billing/budgets/%v", enterprise, budgetID)
req, err := s.client.NewRequest("DELETE", u, nil)
if err != nil {
return nil, err
}

return s.client.Do(ctx, req, nil)
}
Loading
Loading