Skip to content

Commit fec22d2

Browse files
committed
feat: Add enterprise license endpoints
Add GetConsumedLicenses and GetLicenseSyncStatus methods to EnterpriseService for retrieving license consumption data and sync status for Enterprise Server instances. Fixes #3754 Signed-off-by: Mathew Clegg <[email protected]>
1 parent 233fe03 commit fec22d2

File tree

2 files changed

+326
-0
lines changed

2 files changed

+326
-0
lines changed

github/enterprise_licenses.go

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

github/enterprise_licenses_test.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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": ["[email protected]"],
39+
"github_com_saml_name_id": "saml123",
40+
"github_com_orgs_with_pending_invites": [],
41+
"github_com_two_factor_auth": ["enabled"],
42+
"enterprise_server_emails": ["[email protected]"],
43+
"visual_studio_license_status": "active",
44+
"visual_studio_subscription_email": "[email protected]",
45+
"total_user_accounts": 1,
46+
"github_com_enterprise_role": "owner"
47+
}]
48+
}`)
49+
})
50+
51+
opt := &ListOptions{Page: 2, PerPage: 10}
52+
ctx := t.Context()
53+
licenses, _, err := client.Enterprise.GetConsumedLicenses(ctx, "e", opt)
54+
if err != nil {
55+
t.Errorf("Enterprise.GetConsumedLicenses returned error: %v", err)
56+
}
57+
58+
want := &EnterpriseConsumedLicenses{
59+
TotalSeatsConsumed: 20,
60+
TotalSeatsPurchased: 25,
61+
Users: []*EnterpriseLicensedUsers{
62+
{
63+
GithubComLogin: "user1",
64+
GithubComName: "User One",
65+
EnterpriseServerUserIDs: []string{"123", "456"},
66+
GithubComUser: true,
67+
EnterpriseServerUser: false,
68+
VisualStudioSubscriptionUser: false,
69+
LicenseType: "Enterprise",
70+
GithubComProfile: "https://github.com/user1",
71+
GithubComMemberRoles: []string{"member"},
72+
GithubComEnterpriseRoles: []string{"member"},
73+
GithubComVerifiedDomainEmails: []string{"[email protected]"},
74+
GithubComSamlNameID: "saml123",
75+
GithubComOrgsWithPendingInvites: []string{},
76+
GithubComTwoFactorAuth: []string{"enabled"},
77+
EnterpriseServerEmails: []string{"[email protected]"},
78+
VisualStudioLicenseStatus: "active",
79+
VisualStudioSubscriptionEmail: "[email protected]",
80+
TotalUserAccounts: 1,
81+
GithubComEnterpriseRole: "owner",
82+
},
83+
},
84+
}
85+
86+
if !cmp.Equal(licenses, want) {
87+
t.Errorf("Enterprise.GetConsumedLicenses returned %+v, want %+v", licenses, want)
88+
}
89+
90+
const methodName = "GetConsumedLicenses"
91+
testBadOptions(t, methodName, func() (err error) {
92+
_, _, err = client.Enterprise.GetConsumedLicenses(ctx, "\n", opt)
93+
return err
94+
})
95+
96+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
97+
got, resp, err := client.Enterprise.GetConsumedLicenses(ctx, "e", opt)
98+
if got != nil {
99+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
100+
}
101+
return resp, err
102+
})
103+
}
104+
105+
func TestEnterpriseService_GetLicenseSyncStatus(t *testing.T) {
106+
t.Parallel()
107+
client, mux, _ := setup(t)
108+
109+
mux.HandleFunc("/enterprises/e/license-sync-status", func(w http.ResponseWriter, r *http.Request) {
110+
testMethod(t, r, "GET")
111+
fmt.Fprint(w, `{
112+
"title": "Enterprise License Sync Status",
113+
"description": "Status of license synchronization",
114+
"properties": {
115+
"server_instances": {
116+
"type": "array",
117+
"items": {
118+
"type": "object",
119+
"properties": {
120+
"server_id": "ghes-1",
121+
"hostname": "github.enterprise.local",
122+
"last_sync": {
123+
"type": "object",
124+
"properties": {
125+
"date": "2025-10-30T10:30:00Z",
126+
"status": "success",
127+
"error": ""
128+
}
129+
}
130+
}
131+
}
132+
}
133+
}
134+
}`)
135+
})
136+
137+
ctx := t.Context()
138+
syncStatus, _, err := client.Enterprise.GetLicenseSyncStatus(ctx, "e")
139+
if err != nil {
140+
t.Errorf("Enterprise.GetLicenseSyncStatus returned error: %v", err)
141+
}
142+
143+
want := &EnterpriseLicenseSyncStatus{
144+
Title: "Enterprise License Sync Status",
145+
Description: "Status of license synchronization",
146+
Properties: &ServerInstanceProperties{
147+
ServerInstances: &ServerInstances{
148+
Type: "array",
149+
Items: &ServiceInstanceItems{
150+
Type: "object",
151+
Properties: &ServerItemProperties{
152+
ServerID: "ghes-1",
153+
Hostname: "github.enterprise.local",
154+
LastSync: &LastLicenseSync{
155+
Type: "object",
156+
Properties: &LastLicenseSyncProperties{
157+
Date: &Timestamp{time.Date(2025, 10, 30, 10, 30, 0, 0, time.UTC)},
158+
Status: "success",
159+
Error: "",
160+
},
161+
},
162+
},
163+
},
164+
},
165+
},
166+
}
167+
168+
fmt.Printf("%v\n", cmp.Diff(want, syncStatus))
169+
170+
if !cmp.Equal(syncStatus, want) {
171+
t.Errorf("Enterprise.GetLicenseSyncStatus returned %+v, want %+v", syncStatus, want)
172+
}
173+
174+
const methodName = "GetLicenseSyncStatus"
175+
testBadOptions(t, methodName, func() (err error) {
176+
_, _, err = client.Enterprise.GetLicenseSyncStatus(ctx, "\n")
177+
return err
178+
})
179+
180+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
181+
got, resp, err := client.Enterprise.GetLicenseSyncStatus(ctx, "e")
182+
if got != nil {
183+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
184+
}
185+
return resp, err
186+
})
187+
}

0 commit comments

Comments
 (0)