Skip to content

Commit 62a754f

Browse files
authored
feat: Add enterprise license endpoints (#3755)
1 parent 18ac81b commit 62a754f

4 files changed

Lines changed: 579 additions & 0 deletions

File tree

github/enterprise_licenses.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright 2025 The go-github AUTHORS. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package github
7+
8+
import (
9+
"context"
10+
"fmt"
11+
)
12+
13+
// EnterpriseConsumedLicenses represents information about users with consumed enterprise licenses.
14+
type EnterpriseConsumedLicenses struct {
15+
TotalSeatsConsumed int `json:"total_seats_consumed"`
16+
TotalSeatsPurchased int `json:"total_seats_purchased"`
17+
Users []*EnterpriseLicensedUsers `json:"users,omitempty"`
18+
}
19+
20+
// EnterpriseLicensedUsers represents a user with license information in an enterprise.
21+
type EnterpriseLicensedUsers struct {
22+
GithubComLogin string `json:"github_com_login"`
23+
GithubComName *string `json:"github_com_name"`
24+
EnterpriseServerUserIDs []string `json:"enterprise_server_user_ids,omitempty"`
25+
GithubComUser bool `json:"github_com_user"`
26+
EnterpriseServerUser *bool `json:"enterprise_server_user"`
27+
VisualStudioSubscriptionUser bool `json:"visual_studio_subscription_user"`
28+
LicenseType string `json:"license_type"`
29+
GithubComProfile *string `json:"github_com_profile"`
30+
GithubComMemberRoles []string `json:"github_com_member_roles,omitempty"`
31+
GithubComEnterpriseRoles []string `json:"github_com_enterprise_roles,omitempty"`
32+
GithubComVerifiedDomainEmails []string `json:"github_com_verified_domain_emails,omitempty"`
33+
GithubComSamlNameID *string `json:"github_com_saml_name_id"`
34+
GithubComOrgsWithPendingInvites []string `json:"github_com_orgs_with_pending_invites,omitempty"`
35+
GithubComTwoFactorAuth *bool `json:"github_com_two_factor_auth"`
36+
EnterpriseServerEmails []string `json:"enterprise_server_emails,omitempty"`
37+
VisualStudioLicenseStatus *string `json:"visual_studio_license_status"`
38+
VisualStudioSubscriptionEmail *string `json:"visual_studio_subscription_email"`
39+
TotalUserAccounts int `json:"total_user_accounts"`
40+
}
41+
42+
// EnterpriseLicenseSyncStatus represents the synchronization status of
43+
// GitHub Enterprise Server instances with an enterprise account.
44+
type EnterpriseLicenseSyncStatus struct {
45+
Title string `json:"title"`
46+
Description string `json:"description"`
47+
Properties *ServerInstanceProperties `json:"properties,omitempty"`
48+
}
49+
50+
// ServerInstanceProperties contains the collection of server instances.
51+
type ServerInstanceProperties struct {
52+
ServerInstances *ServerInstances `json:"server_instances,omitempty"`
53+
}
54+
55+
// ServerInstances represents a collection of GitHub Enterprise Server instances
56+
// and their synchronization status.
57+
type ServerInstances struct {
58+
Type string `json:"type"`
59+
Items *ServiceInstanceItems `json:"items,omitempty"`
60+
}
61+
62+
// ServiceInstanceItems defines the structure and properties of individual server instances
63+
// in the collection.
64+
type ServiceInstanceItems struct {
65+
Type string `json:"type"`
66+
Properties *ServerItemProperties `json:"properties,omitempty"`
67+
}
68+
69+
// ServerItemProperties represents the properties of a GitHub Enterprise Server instance,
70+
// including its identifier, hostname, and last synchronization status.
71+
type ServerItemProperties struct {
72+
ServerID string `json:"server_id"`
73+
Hostname string `json:"hostname"`
74+
LastSync *LastLicenseSync `json:"last_sync,omitempty"`
75+
}
76+
77+
// LastLicenseSync contains information about the most recent license synchronization
78+
// attempt for a server instance.
79+
type LastLicenseSync struct {
80+
Type string `json:"type"`
81+
Properties *LastLicenseSyncProperties `json:"properties,omitempty"`
82+
}
83+
84+
// LastLicenseSyncProperties represents the details of the last synchronization attempt,
85+
// including the date, status, and any error that occurred.
86+
type LastLicenseSyncProperties struct {
87+
Date *Timestamp `json:"date,omitempty"`
88+
Status string `json:"status"`
89+
Error string `json:"error"`
90+
}
91+
92+
// GetConsumedLicenses collect information about the number of consumed licenses and a collection with all the users with consumed enterprise licenses.
93+
//
94+
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/license#list-enterprise-consumed-licenses
95+
//
96+
//meta:operation GET /enterprises/{enterprise}/consumed-licenses
97+
func (s *EnterpriseService) GetConsumedLicenses(ctx context.Context, enterprise string, opts *ListOptions) (*EnterpriseConsumedLicenses, *Response, error) {
98+
u := fmt.Sprintf("enterprises/%v/consumed-licenses", enterprise)
99+
u, err := addOptions(u, opts)
100+
if err != nil {
101+
return nil, nil, err
102+
}
103+
104+
req, err := s.client.NewRequest("GET", u, nil)
105+
if err != nil {
106+
return nil, nil, err
107+
}
108+
109+
consumedLicenses := &EnterpriseConsumedLicenses{}
110+
resp, err := s.client.Do(ctx, req, &consumedLicenses)
111+
if err != nil {
112+
return nil, resp, err
113+
}
114+
115+
return consumedLicenses, resp, nil
116+
}
117+
118+
// GetLicenseSyncStatus collects information about the status of a license sync job for an enterprise.
119+
//
120+
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/license#get-a-license-sync-status
121+
//
122+
//meta:operation GET /enterprises/{enterprise}/license-sync-status
123+
func (s *EnterpriseService) GetLicenseSyncStatus(ctx context.Context, enterprise string) (*EnterpriseLicenseSyncStatus, *Response, error) {
124+
u := fmt.Sprintf("enterprises/%v/license-sync-status", enterprise)
125+
126+
req, err := s.client.NewRequest("GET", u, nil)
127+
if err != nil {
128+
return nil, nil, err
129+
}
130+
131+
syncStatus := &EnterpriseLicenseSyncStatus{}
132+
resp, err := s.client.Do(ctx, req, &syncStatus)
133+
if err != nil {
134+
return nil, resp, err
135+
}
136+
137+
return syncStatus, resp, nil
138+
}

github/enterprise_licenses_test.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Copyright 2025 The go-github AUTHORS. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package github
7+
8+
import (
9+
"fmt"
10+
"net/http"
11+
"testing"
12+
"time"
13+
14+
"github.com/google/go-cmp/cmp"
15+
)
16+
17+
func TestEnterpriseService_GetConsumedLicenses(t *testing.T) {
18+
t.Parallel()
19+
client, mux, _ := setup(t)
20+
21+
mux.HandleFunc("/enterprises/e/consumed-licenses", func(w http.ResponseWriter, r *http.Request) {
22+
testMethod(t, r, "GET")
23+
testFormValues(t, r, values{"page": "2", "per_page": "10"})
24+
fmt.Fprint(w, `{
25+
"total_seats_consumed": 20,
26+
"total_seats_purchased": 25,
27+
"users": [{
28+
"github_com_login": "user1",
29+
"github_com_name": "User One",
30+
"enterprise_server_user_ids": ["123", "456"],
31+
"github_com_user": true,
32+
"enterprise_server_user": false,
33+
"visual_studio_subscription_user": false,
34+
"license_type": "Enterprise",
35+
"github_com_profile": "https://github.com/user1",
36+
"github_com_member_roles": ["member"],
37+
"github_com_enterprise_roles": ["member"],
38+
"github_com_verified_domain_emails": ["user1@example.com"],
39+
"github_com_saml_name_id": "saml123",
40+
"github_com_orgs_with_pending_invites": [],
41+
"github_com_two_factor_auth": true,
42+
"enterprise_server_emails": ["user1@enterprise.local"],
43+
"visual_studio_license_status": "active",
44+
"visual_studio_subscription_email": "user1@visualstudio.com",
45+
"total_user_accounts": 1
46+
}]
47+
}`)
48+
})
49+
50+
opt := &ListOptions{Page: 2, PerPage: 10}
51+
ctx := t.Context()
52+
licenses, _, err := client.Enterprise.GetConsumedLicenses(ctx, "e", opt)
53+
if err != nil {
54+
t.Errorf("Enterprise.GetConsumedLicenses returned error: %v", err)
55+
}
56+
57+
userName := "User One"
58+
serverUser := false
59+
profile := "https://github.com/user1"
60+
samlNameID := "saml123"
61+
twoFactorAuth := true
62+
licenseStatus := "active"
63+
vsEmail := "user1@visualstudio.com"
64+
65+
want := &EnterpriseConsumedLicenses{
66+
TotalSeatsConsumed: 20,
67+
TotalSeatsPurchased: 25,
68+
Users: []*EnterpriseLicensedUsers{
69+
{
70+
GithubComLogin: "user1",
71+
GithubComName: &userName,
72+
EnterpriseServerUserIDs: []string{"123", "456"},
73+
GithubComUser: true,
74+
EnterpriseServerUser: &serverUser,
75+
VisualStudioSubscriptionUser: false,
76+
LicenseType: "Enterprise",
77+
GithubComProfile: &profile,
78+
GithubComMemberRoles: []string{"member"},
79+
GithubComEnterpriseRoles: []string{"member"},
80+
GithubComVerifiedDomainEmails: []string{"user1@example.com"},
81+
GithubComSamlNameID: &samlNameID,
82+
GithubComOrgsWithPendingInvites: []string{},
83+
GithubComTwoFactorAuth: &twoFactorAuth,
84+
EnterpriseServerEmails: []string{"user1@enterprise.local"},
85+
VisualStudioLicenseStatus: &licenseStatus,
86+
VisualStudioSubscriptionEmail: &vsEmail,
87+
TotalUserAccounts: 1,
88+
},
89+
},
90+
}
91+
92+
if !cmp.Equal(licenses, want) {
93+
t.Errorf("Enterprise.GetConsumedLicenses returned %+v, want %+v", licenses, want)
94+
}
95+
96+
const methodName = "GetConsumedLicenses"
97+
testBadOptions(t, methodName, func() (err error) {
98+
_, _, err = client.Enterprise.GetConsumedLicenses(ctx, "\n", opt)
99+
return err
100+
})
101+
102+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
103+
got, resp, err := client.Enterprise.GetConsumedLicenses(ctx, "e", opt)
104+
if got != nil {
105+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
106+
}
107+
return resp, err
108+
})
109+
}
110+
111+
func TestEnterpriseService_GetLicenseSyncStatus(t *testing.T) {
112+
t.Parallel()
113+
client, mux, _ := setup(t)
114+
115+
mux.HandleFunc("/enterprises/e/license-sync-status", func(w http.ResponseWriter, r *http.Request) {
116+
testMethod(t, r, "GET")
117+
fmt.Fprint(w, `{
118+
"title": "Enterprise License Sync Status",
119+
"description": "Status of license synchronization",
120+
"properties": {
121+
"server_instances": {
122+
"type": "array",
123+
"items": {
124+
"type": "object",
125+
"properties": {
126+
"server_id": "ghes-1",
127+
"hostname": "github.enterprise.local",
128+
"last_sync": {
129+
"type": "object",
130+
"properties": {
131+
"date": "2025-10-30T10:30:00Z",
132+
"status": "success",
133+
"error": ""
134+
}
135+
}
136+
}
137+
}
138+
}
139+
}
140+
}`)
141+
})
142+
143+
ctx := t.Context()
144+
syncStatus, _, err := client.Enterprise.GetLicenseSyncStatus(ctx, "e")
145+
if err != nil {
146+
t.Errorf("Enterprise.GetLicenseSyncStatus returned error: %v", err)
147+
}
148+
149+
want := &EnterpriseLicenseSyncStatus{
150+
Title: "Enterprise License Sync Status",
151+
Description: "Status of license synchronization",
152+
Properties: &ServerInstanceProperties{
153+
ServerInstances: &ServerInstances{
154+
Type: "array",
155+
Items: &ServiceInstanceItems{
156+
Type: "object",
157+
Properties: &ServerItemProperties{
158+
ServerID: "ghes-1",
159+
Hostname: "github.enterprise.local",
160+
LastSync: &LastLicenseSync{
161+
Type: "object",
162+
Properties: &LastLicenseSyncProperties{
163+
Date: &Timestamp{time.Date(2025, 10, 30, 10, 30, 0, 0, time.UTC)},
164+
Status: "success",
165+
Error: "",
166+
},
167+
},
168+
},
169+
},
170+
},
171+
},
172+
}
173+
174+
fmt.Printf("%v\n", cmp.Diff(want, syncStatus))
175+
176+
if !cmp.Equal(syncStatus, want) {
177+
t.Errorf("Enterprise.GetLicenseSyncStatus returned %+v, want %+v", syncStatus, want)
178+
}
179+
180+
const methodName = "GetLicenseSyncStatus"
181+
testBadOptions(t, methodName, func() (err error) {
182+
_, _, err = client.Enterprise.GetLicenseSyncStatus(ctx, "\n")
183+
return err
184+
})
185+
186+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
187+
got, resp, err := client.Enterprise.GetLicenseSyncStatus(ctx, "e")
188+
if got != nil {
189+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
190+
}
191+
return resp, err
192+
})
193+
}

0 commit comments

Comments
 (0)