Skip to content

feat: Add enterprise budgets API#4069

Open
maishivamhoo123 wants to merge 1 commit intogoogle:masterfrom
maishivamhoo123:feat/enterprise-budget-api
Open

feat: Add enterprise budgets API#4069
maishivamhoo123 wants to merge 1 commit intogoogle:masterfrom
maishivamhoo123:feat/enterprise-budget-api

Conversation

@maishivamhoo123
Copy link
Contributor

@maishivamhoo123 maishivamhoo123 commented Mar 6, 2026

Fixes #3930

This PR adds support for the newly announced GitHub Enterprise Cloud Billing Budgets API to the EnterpriseService.

It includes the necessary structs, API methods, and unit tests to fully support managing enterprise budgets.

Signed-off-by: maishivamhoo123 <maishivamhoo@gmail.com>
@gmlewis gmlewis changed the title feat: add enterprise budget API feat: Add enterprise budgets API Mar 6, 2026
@codecov
Copy link

codecov bot commented Mar 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.67%. Comparing base (4f6e537) to head (8afd175).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4069      +/-   ##
==========================================
+ Coverage   93.65%   93.67%   +0.01%     
==========================================
  Files         210      211       +1     
  Lines       19416    19468      +52     
==========================================
+ Hits        18184    18236      +52     
  Misses       1034     1034              
  Partials      198      198              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@maishivamhoo123
Copy link
Contributor Author

@gmlewis @stevehipwell and @alexandear can you please review this PR?

@gmlewis gmlewis added the NeedsReview PR is awaiting a review before merging. label Mar 6, 2026
Copy link
Collaborator

@gmlewis gmlewis left a comment

Choose a reason for hiding this comment

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

Thank you, @maishivamhoo123!
In addition to the comments I made, please see if you can incorporate testBadOptions into your new unit tests for more complete test coverage.

}

// Budget represents a GitHub enterprise budget.
type Budget struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since this entire repo makes all its structs "package global", can we please change every struct name that starts with "Budget" to instead start with "EnterpriseBudget" because I think this may help to reduce confusion when people might be thinking of other budgets like Copilot, for instance.

Copy link
Contributor

@Not-Dhananjay-Mishra Not-Dhananjay-Mishra Mar 7, 2026

Choose a reason for hiding this comment

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

Can we come up with a different name?
That way we could reuse this struct for organization budgets as well. The endpoints for those aren't added yet, but they are part of issue #3930
Organization budget docs

Comment on lines +61 to +62
budgets := new(EnterpriseBudgets)
resp, err := s.client.Do(ctx, req, budgets)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's please stop using this pattern even though it exists a LOT in this repo and I would like to some day change them all... and use the more idiomatic Go style of benefiting from the zero-value:

Suggested change
budgets := new(EnterpriseBudgets)
resp, err := s.client.Do(ctx, req, budgets)
var budgets *EnterpriseBudgets
resp, err := s.client.Do(ctx, req, &budgets)

Comment on lines +83 to +84
actionResponse := new(BudgetActionResponse)
resp, err := s.client.Do(ctx, req, actionResponse)
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

Comment on lines +105 to +106
budget := new(Budget)
resp, err := s.client.Do(ctx, req, budget)
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

Comment on lines +127 to +128
actionResponse := new(BudgetActionResponse)
resp, err := s.client.Do(ctx, req, actionResponse)
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

Comment on lines +149 to +150
actionResponse := new(BudgetActionResponse)
resp, err := s.client.Do(ctx, req, actionResponse)
Copy link
Collaborator

Choose a reason for hiding this comment

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

ditto

// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/billing/budgets#create-a-budget
//
//meta:operation POST /enterprises/{enterprise}/settings/billing/budgets
func (s *EnterpriseService) CreateBudget(ctx context.Context, enterprise string, budget *Budget) (*BudgetActionResponse, *Response, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

budget is required, so should be passed by value:

Suggested change
func (s *EnterpriseService) CreateBudget(ctx context.Context, enterprise string, budget *Budget) (*BudgetActionResponse, *Response, error) {
func (s *EnterpriseService) CreateBudget(ctx context.Context, enterprise string, budget Budget) (*BudgetActionResponse, *Response, error) {

Copy link
Contributor Author

@maishivamhoo123 maishivamhoo123 Mar 6, 2026

Choose a reason for hiding this comment

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

Thank you @gmlewis for pointing out this!.
Sir I have a small doubt as these are required here docs
image
if I will pass EnterpriseBudgetas value and keep the
this struct like this

type EnterpriseBudget struct {
	ID                  *string                   `json:"id,omitempty"`
	BudgetType          *string                   `json:"budget_type,omitempty"`
	BudgetProductSKUs   []string                  `json:"budget_product_skus,omitempty"`
	BudgetProductSKU    *string                   `json:"budget_product_sku,omitempty"`
	BudgetScope         *string                   `json:"budget_scope,omitempty"`
	BudgetEntityName    *string                   `json:"budget_entity_name,omitempty"`
	BudgetAmount        *int                      `json:"budget_amount,omitempty"`
	PreventFurtherUsage *bool                     `json:"prevent_further_usage,omitempty"`
	BudgetAlerting      *EnterpriseBudgetAlerting `json:"budget_alerting,omitempty"`
}

is it ok? can you please explain?
thank you for you time and consideration.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, you can leave all the field names as they are. I was talking about the struct names only.

Comment on lines +13 to +14
// BudgetAlerting represents alerting settings for a GitHub enterprise budget.
type BudgetAlerting struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// BudgetAlerting represents alerting settings for a GitHub enterprise budget.
type BudgetAlerting struct {
// EnterpriseBudgetAlerting represents alerting settings for a GitHub enterprise budget.
type EnterpriseBudgetAlerting struct {

}

// Budget represents a GitHub enterprise budget.
type Budget struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
type Budget struct {
type EnterpriseBudget struct {

BudgetAlerting *BudgetAlerting `json:"budget_alerting,omitempty"`
}

func (b Budget) String() string {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
func (b Budget) String() string {
func (b EnterpriseBudget) String() string {

Comment on lines +41 to +42
// BudgetActionResponse represents the response when creating, updating, or deleting a budget.
type BudgetActionResponse struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// BudgetActionResponse represents the response when creating, updating, or deleting a budget.
type BudgetActionResponse struct {
// EnterpriseBudgetActionResponse represents the response when creating, updating, or deleting a budget.
type EnterpriseBudgetActionResponse struct {

@maishivamhoo123
Copy link
Contributor Author

Thank you @gmlewis, I understand your point, but I am a bit confused here.

In the function:

func (s *EnterpriseService) CreateBudget(ctx context.Context, enterprise string, budget Budget) (*BudgetActionResponse, *Response, error)

if we pass budget by value, it cannot be nil because it is required. However, in the struct:

type EnterpriseBudget struct {
	ID                  *string                   `json:"id,omitempty"`
	BudgetType          *string                   `json:"budget_type,omitempty"`
	BudgetProductSKUs   []string                  `json:"budget_product_skus,omitempty"`
	BudgetProductSKU    *string                   `json:"budget_product_sku,omitempty"`
	BudgetScope         *string                   `json:"budget_scope,omitempty"`
	BudgetEntityName    *string                   `json:"budget_entity_name,omitempty"`
	BudgetAmount        *int                      `json:"budget_amount,omitempty"`
	PreventFurtherUsage *bool                     `json:"prevent_further_usage,omitempty"`
	BudgetAlerting      *EnterpriseBudgetAlerting `json:"budget_alerting,omitempty"`
}

all the fields are pointers, so they can still be nil. This means the struct itself cannot be nil, but its fields can be.

Because of this, I am a bit confused. Should we validate the required fields inside the function, or should the struct be changed to enforce required values?

Am I thinking about this correctly, or am I misunderstanding something here?

@gmlewis
Copy link
Collaborator

gmlewis commented Mar 6, 2026

Because of this, I am a bit confused. Should we validate the required fields inside the function, or should the struct be changed to enforce required values?

There is no need to validate fields within the method. @alexandear was saying that the struct itself should be passed by value into the method since it is required. Some of the fields within the struct are optional, so they remain pointers.

Am I thinking about this correctly, or am I misunderstanding something here?

You need to remember that we have auto-generated accessors for the fields themselves so the contents of the struct is a different concept than passing the struct itself into the method.

@alexandear is right to pass the struct by value... because if we left it as a pointer, then the method would have to first check if the pointer is nil and then return an error, whereas if we pass the struct by value, then the compiler will refuse to compile the program (if a pointer (which could possibly be nil) was passed) which alerts the user that this method's argument is not allowed to be nil.

Does that make sense to you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NeedsReview PR is awaiting a review before merging.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support budgets

4 participants