Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
139 changes: 139 additions & 0 deletions github/enterprise_licenses.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright 2025 The go-github AUTHORS. All rights reserved.
//
// 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"
)

// EnterpriseLicensedUsers represents users that hold an enterprise license.
type EnterpriseLicensedUsers struct {
GithubComLogin string `json:"github_com_login"`
GithubComName string `json:"github_com_name"`
Copy link
Contributor

Choose a reason for hiding this comment

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

According to the schema:

          "github_com_name": {
            "type": [
              "string",
              "null"
            ]
          },

this field should be:

Suggested change
GithubComName string `json:"github_com_name"`
GithubComName *string `json:"github_com_name,omitempty"`

Copy link
Contributor

Choose a reason for hiding this comment

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

Please check and fix other fields in the file

EnterpriseServerUserIDs []string `json:"enterprise_server_user_ids"`
GithubComUser bool `json:"github_com_user"`
EnterpriseServerUser bool `json:"enterprise_server_user"`
VisualStudioSubscriptionUser bool `json:"visual_studio_subscription_user"`
LicenseType string `json:"license_type"`
GithubComProfile string `json:"github_com_profile"`
GithubComMemberRoles []string `json:"github_com_member_roles"`
GithubComEnterpriseRoles []string `json:"github_com_enterprise_roles"`
GithubComVerifiedDomainEmails []string `json:"github_com_verified_domain_emails"`
GithubComSamlNameID string `json:"github_com_saml_name_id"`
GithubComOrgsWithPendingInvites []string `json:"github_com_orgs_with_pending_invites"`
GithubComTwoFactorAuth []string `json:"github_com_two_factor_auth"`
EnterpriseServerEmails []string `json:"enterprise_server_emails"`
VisualStudioLicenseStatus string `json:"visual_studio_license_status"`
VisualStudioSubscriptionEmail string `json:"visual_studio_subscription_email"`
TotalUserAccounts int `json:"total_user_accounts"`
GithubComEnterpriseRole string `json:"github_com_enterprise_role,omitempty"`
}

// EnterpriseConsumedLicenses represents information about users with consumed enterprise licenses.
type EnterpriseConsumedLicenses struct {
TotalSeatsConsumed int `json:"total_seats_consumed"`
TotalSeatsPurchased int `json:"total_seats_purchased"`
Users []*EnterpriseLicensedUsers `json:"users,omitempty"`
}

// EnterpriseLicenseSyncStatus represents the synchronization status of
// GitHub Enterprise Server instances with an enterprise account.
type EnterpriseLicenseSyncStatus struct {
Title string `json:"title"`
Description string `json:"description"`
Properties *ServerInstanceProperties `json:"properties,omitempty"`
}

// ServerInstanceProperties contains the collection of server instances.
type ServerInstanceProperties struct {
ServerInstances *ServerInstances `json:"server_instances,omitempty"`
}

// ServerInstances represents a collection of GitHub Enterprise Server instances
// and their synchronization status.
type ServerInstances struct {
Type string `json:"type"`
Items *ServiceInstanceItems `json:"items,omitempty"`
}

// ServiceInstanceItems defines the structure and properties of individual server instances
// in the collection.
type ServiceInstanceItems struct {
Type string `json:"type"`
Properties *ServerItemProperties `json:"properties,omitempty"`
}

// ServerItemProperties represents the properties of a GitHub Enterprise Server instance,
// including its identifier, hostname, and last synchronization status.
type ServerItemProperties struct {
ServerID string `json:"server_id"`
Hostname string `json:"hostname"`
LastSync *LastLicenseSync `json:"last_sync,omitempty"`
}

// LastLicenseSync contains information about the most recent license synchronization
// attempt for a server instance.
type LastLicenseSync struct {
Type string `json:"type"`
Properties *LastLicenseSyncProperties `json:"properties,omitempty"`
}

// LastLicenseSyncProperties represents the details of the last synchronization attempt,
// including the date, status, and any error that occurred.
type LastLicenseSyncProperties struct {
Date *Timestamp `json:"date,omitempty"`
Status string `json:"status"`
Error string `json:"error"`
}

// GetConsumedLicenses collect information about the number of consumed licenses and a collection with all the users with consumed enterprise licenses.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/license#list-enterprise-consumed-licenses
//
//meta:operation GET /enterprises/{enterprise}/consumed-licenses
func (s *EnterpriseService) GetConsumedLicenses(ctx context.Context, enterprise string, opts *ListOptions) (*EnterpriseConsumedLicenses, *Response, error) {
u := fmt.Sprintf("enterprises/%v/consumed-licenses", enterprise)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}

req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

consumedLicenses := &EnterpriseConsumedLicenses{}
resp, err := s.client.Do(ctx, req, &consumedLicenses)
if err != nil {
return nil, resp, err
}

return consumedLicenses, resp, nil
}

// GetLicenseSyncStatus collects information about the status of a license sync job for an enterprise.
//
// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/license#get-a-license-sync-status
//
//meta:operation GET /enterprises/{enterprise}/license-sync-status
func (s *EnterpriseService) GetLicenseSyncStatus(ctx context.Context, enterprise string) (*EnterpriseLicenseSyncStatus, *Response, error) {
u := fmt.Sprintf("enterprises/%v/license-sync-status", enterprise)

req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

syncStatus := &EnterpriseLicenseSyncStatus{}
resp, err := s.client.Do(ctx, req, &syncStatus)
if err != nil {
return nil, resp, err
}

return syncStatus, resp, nil
}
187 changes: 187 additions & 0 deletions github/enterprise_licenses_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright 2025 The go-github AUTHORS. All rights reserved.
//
// 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"
"time"

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

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

mux.HandleFunc("/enterprises/e/consumed-licenses", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{"page": "2", "per_page": "10"})
fmt.Fprint(w, `{
"total_seats_consumed": 20,
"total_seats_purchased": 25,
"users": [{
"github_com_login": "user1",
"github_com_name": "User One",
"enterprise_server_user_ids": ["123", "456"],
"github_com_user": true,
"enterprise_server_user": false,
"visual_studio_subscription_user": false,
"license_type": "Enterprise",
"github_com_profile": "https://github.com/user1",
"github_com_member_roles": ["member"],
"github_com_enterprise_roles": ["member"],
"github_com_verified_domain_emails": ["user1@example.com"],
"github_com_saml_name_id": "saml123",
"github_com_orgs_with_pending_invites": [],
"github_com_two_factor_auth": ["enabled"],
"enterprise_server_emails": ["user1@enterprise.local"],
"visual_studio_license_status": "active",
"visual_studio_subscription_email": "user1@visualstudio.com",
"total_user_accounts": 1,
"github_com_enterprise_role": "owner"
}]
}`)
})

opt := &ListOptions{Page: 2, PerPage: 10}
ctx := t.Context()
licenses, _, err := client.Enterprise.GetConsumedLicenses(ctx, "e", opt)
if err != nil {
t.Errorf("Enterprise.GetConsumedLicenses returned error: %v", err)
}

want := &EnterpriseConsumedLicenses{
TotalSeatsConsumed: 20,
TotalSeatsPurchased: 25,
Users: []*EnterpriseLicensedUsers{
{
GithubComLogin: "user1",
GithubComName: "User One",
EnterpriseServerUserIDs: []string{"123", "456"},
GithubComUser: true,
EnterpriseServerUser: false,
VisualStudioSubscriptionUser: false,
LicenseType: "Enterprise",
GithubComProfile: "https://github.com/user1",
GithubComMemberRoles: []string{"member"},
GithubComEnterpriseRoles: []string{"member"},
GithubComVerifiedDomainEmails: []string{"user1@example.com"},
GithubComSamlNameID: "saml123",
GithubComOrgsWithPendingInvites: []string{},
GithubComTwoFactorAuth: []string{"enabled"},
EnterpriseServerEmails: []string{"user1@enterprise.local"},
VisualStudioLicenseStatus: "active",
VisualStudioSubscriptionEmail: "user1@visualstudio.com",
TotalUserAccounts: 1,
GithubComEnterpriseRole: "owner",
},
},
}

if !cmp.Equal(licenses, want) {
t.Errorf("Enterprise.GetConsumedLicenses returned %+v, want %+v", licenses, want)
}

const methodName = "GetConsumedLicenses"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.Enterprise.GetConsumedLicenses(ctx, "\n", opt)
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
got, resp, err := client.Enterprise.GetConsumedLicenses(ctx, "e", opt)
if got != nil {
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
}
return resp, err
})
}

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

mux.HandleFunc("/enterprises/e/license-sync-status", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{
"title": "Enterprise License Sync Status",
"description": "Status of license synchronization",
"properties": {
"server_instances": {
"type": "array",
"items": {
"type": "object",
"properties": {
"server_id": "ghes-1",
"hostname": "github.enterprise.local",
"last_sync": {
"type": "object",
"properties": {
"date": "2025-10-30T10:30:00Z",
"status": "success",
"error": ""
}
}
}
}
}
}
}`)
})

ctx := t.Context()
syncStatus, _, err := client.Enterprise.GetLicenseSyncStatus(ctx, "e")
if err != nil {
t.Errorf("Enterprise.GetLicenseSyncStatus returned error: %v", err)
}

want := &EnterpriseLicenseSyncStatus{
Title: "Enterprise License Sync Status",
Description: "Status of license synchronization",
Properties: &ServerInstanceProperties{
ServerInstances: &ServerInstances{
Type: "array",
Items: &ServiceInstanceItems{
Type: "object",
Properties: &ServerItemProperties{
ServerID: "ghes-1",
Hostname: "github.enterprise.local",
LastSync: &LastLicenseSync{
Type: "object",
Properties: &LastLicenseSyncProperties{
Date: &Timestamp{time.Date(2025, 10, 30, 10, 30, 0, 0, time.UTC)},
Status: "success",
Error: "",
},
},
},
},
},
},
}

fmt.Printf("%v\n", cmp.Diff(want, syncStatus))

if !cmp.Equal(syncStatus, want) {
t.Errorf("Enterprise.GetLicenseSyncStatus returned %+v, want %+v", syncStatus, want)
}

const methodName = "GetLicenseSyncStatus"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.Enterprise.GetLicenseSyncStatus(ctx, "\n")
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
got, resp, err := client.Enterprise.GetLicenseSyncStatus(ctx, "e")
if got != nil {
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
}
return resp, err
})
}
Loading
Loading