Skip to content

Commit a04735c

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 a04735c

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed

github/enterprise_licenenses.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"`
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 Properties `json:"properties"`
49+
}
50+
51+
// Properties contains the collection of server instances.
52+
type Properties struct {
53+
ServerInstances ServerInstances `json:"server_instances"`
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 Items `json:"items"`
61+
}
62+
63+
// Items defines the structure and properties of individual server instances
64+
// in the collection.
65+
type Items struct {
66+
Type string `json:"type"`
67+
Properties ItemProperties `json:"properties"`
68+
}
69+
70+
// ItemProperties represents the properties of a GitHub Enterprise Server instance,
71+
// including its identifier, hostname, and last synchronization status.
72+
type ItemProperties struct {
73+
ServerID string `json:"server_id"`
74+
Hostname string `json:"hostname"`
75+
LastSync LastSync `json:"last_sync"`
76+
}
77+
78+
// LastSync contains information about the most recent license synchronization
79+
// attempt for a server instance.
80+
type LastSync struct {
81+
Type string `json:"type"`
82+
Properties LastSyncProperties `json:"properties"`
83+
}
84+
85+
// LastSyncProperties represents the details of the last synchronization attempt,
86+
// including the date, status, and any error that occurred.
87+
type LastSyncProperties struct {
88+
Date string `json:"date"`
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/%s/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/%s/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: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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+
13+
"github.com/google/go-cmp/cmp"
14+
)
15+
16+
func TestEnterpriseService_GetConsumedLicenses(t *testing.T) {
17+
t.Parallel()
18+
client, mux, _ := setup(t)
19+
20+
mux.HandleFunc("/enterprises/e/consumed-licenses", func(w http.ResponseWriter, r *http.Request) {
21+
testMethod(t, r, "GET")
22+
testFormValues(t, r, values{"page": "2", "per_page": "10"})
23+
fmt.Fprint(w, `{
24+
"total_seats_consumed": 20,
25+
"total_seats_purchased": 25,
26+
"users": [{
27+
"github_com_login": "user1",
28+
"github_com_name": "User One",
29+
"enterprise_server_user_ids": ["123", "456"],
30+
"github_com_user": true,
31+
"enterprise_server_user": false,
32+
"visual_studio_subscription_user": false,
33+
"license_type": "Enterprise",
34+
"github_com_profile": "https://github.com/user1",
35+
"github_com_member_roles": ["member"],
36+
"github_com_enterprise_roles": ["member"],
37+
"github_com_verified_domain_emails": ["[email protected]"],
38+
"github_com_saml_name_id": "saml123",
39+
"github_com_orgs_with_pending_invites": [],
40+
"github_com_two_factor_auth": ["enabled"],
41+
"enterprise_server_emails": ["[email protected]"],
42+
"visual_studio_license_status": "active",
43+
"visual_studio_subscription_email": "[email protected]",
44+
"total_user_accounts": 1,
45+
"github_com_enterprise_role": "owner"
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+
want := &EnterpriseConsumedLicenses{
58+
TotalSeatsConsumed: 20,
59+
TotalSeatsPurchased: 25,
60+
Users: []*EnterpriseLicensedUsers{
61+
{
62+
GithubComLogin: "user1",
63+
GithubComName: "User One",
64+
EnterpriseServerUserIDs: []string{"123", "456"},
65+
GithubComUser: true,
66+
EnterpriseServerUser: false,
67+
VisualStudioSubscriptionUser: false,
68+
LicenseType: "Enterprise",
69+
GithubComProfile: "https://github.com/user1",
70+
GithubComMemberRoles: []string{"member"},
71+
GithubComEnterpriseRoles: []string{"member"},
72+
GithubComVerifiedDomainEmails: []string{"[email protected]"},
73+
GithubComSamlNameID: "saml123",
74+
GithubComOrgsWithPendingInvites: []string{},
75+
GithubComTwoFactorAuth: []string{"enabled"},
76+
EnterpriseServerEmails: []string{"[email protected]"},
77+
VisualStudioLicenseStatus: "active",
78+
VisualStudioSubscriptionEmail: "[email protected]",
79+
TotalUserAccounts: 1,
80+
GithubComEnterpriseRole: "owner",
81+
},
82+
},
83+
}
84+
85+
if !cmp.Equal(licenses, want) {
86+
t.Errorf("Enterprise.GetConsumedLicenses returned %+v, want %+v", licenses, want)
87+
}
88+
89+
const methodName = "GetConsumedLicenses"
90+
testBadOptions(t, methodName, func() (err error) {
91+
_, _, err = client.Enterprise.GetConsumedLicenses(ctx, "\n", opt)
92+
return err
93+
})
94+
95+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
96+
got, resp, err := client.Enterprise.GetConsumedLicenses(ctx, "e", opt)
97+
if got != nil {
98+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
99+
}
100+
return resp, err
101+
})
102+
}
103+
104+
func TestEnterpriseService_GetLicenseSyncStatus(t *testing.T) {
105+
t.Parallel()
106+
client, mux, _ := setup(t)
107+
108+
mux.HandleFunc("/enterprises/e/license-sync-status", func(w http.ResponseWriter, r *http.Request) {
109+
testMethod(t, r, "GET")
110+
fmt.Fprint(w, `{
111+
"title": "Enterprise License Sync Status",
112+
"description": "Status of license synchronization",
113+
"properties": {
114+
"server_instances": {
115+
"type": "array",
116+
"items": {
117+
"type": "object",
118+
"properties": {
119+
"server_id": "ghes-1",
120+
"hostname": "github.enterprise.local",
121+
"last_sync": {
122+
"type": "object",
123+
"properties": {
124+
"date": "2024-01-15T10:30:00Z",
125+
"status": "success",
126+
"error": ""
127+
}
128+
}
129+
}
130+
}
131+
}
132+
}
133+
}`)
134+
})
135+
136+
ctx := t.Context()
137+
syncStatus, _, err := client.Enterprise.GetLicenseSyncStatus(ctx, "e")
138+
if err != nil {
139+
t.Errorf("Enterprise.GetLicenseSyncStatus returned error: %v", err)
140+
}
141+
142+
want := &EnterpriseLicenseSyncStatus{
143+
Title: "Enterprise License Sync Status",
144+
Description: "Status of license synchronization",
145+
Properties: Properties{
146+
ServerInstances: ServerInstances{
147+
Type: "array",
148+
Items: Items{
149+
Type: "object",
150+
Properties: ItemProperties{
151+
ServerID: "ghes-1",
152+
Hostname: "github.enterprise.local",
153+
LastSync: LastSync{
154+
Type: "object",
155+
Properties: LastSyncProperties{
156+
Date: "2024-01-15T10:30:00Z",
157+
Status: "success",
158+
Error: "",
159+
},
160+
},
161+
},
162+
},
163+
},
164+
},
165+
}
166+
167+
if !cmp.Equal(syncStatus, want) {
168+
t.Errorf("Enterprise.GetLicenseSyncStatus returned %+v, want %+v", syncStatus, want)
169+
}
170+
171+
const methodName = "GetLicenseSyncStatus"
172+
testBadOptions(t, methodName, func() (err error) {
173+
_, _, err = client.Enterprise.GetLicenseSyncStatus(ctx, "\n")
174+
return err
175+
})
176+
177+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
178+
got, resp, err := client.Enterprise.GetLicenseSyncStatus(ctx, "e")
179+
if got != nil {
180+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
181+
}
182+
return resp, err
183+
})
184+
}

0 commit comments

Comments
 (0)