Files
netbird/shared/management/client/rest/client.go
2026-03-30 11:20:17 +03:00

268 lines
7.0 KiB
Go

package rest
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"github.com/netbirdio/netbird/shared/management/http/util"
)
// APIError represents an error response from the management API.
type APIError struct {
StatusCode int
Message string
}
// Error implements the error interface.
func (e *APIError) Error() string {
return e.Message
}
// IsNotFound returns true if the error represents a 404 Not Found response.
func IsNotFound(err error) bool {
var apiErr *APIError
if ok := errors.As(err, &apiErr); ok {
return apiErr.StatusCode == http.StatusNotFound
}
return false
}
// Client Management service HTTP REST API Client
type Client struct {
managementURL string
authHeader string
httpClient HttpClient
userAgent string
// Accounts NetBird account APIs
// see more: https://docs.netbird.io/api/resources/accounts
Accounts *AccountsAPI
// Users NetBird users APIs
// see more: https://docs.netbird.io/api/resources/users
Users *UsersAPI
// Tokens NetBird tokens APIs
// see more: https://docs.netbird.io/api/resources/tokens
Tokens *TokensAPI
// Peers NetBird peers APIs
// see more: https://docs.netbird.io/api/resources/peers
Peers *PeersAPI
// SetupKeys NetBird setup keys APIs
// see more: https://docs.netbird.io/api/resources/setup-keys
SetupKeys *SetupKeysAPI
// Groups NetBird groups APIs
// see more: https://docs.netbird.io/api/resources/groups
Groups *GroupsAPI
// Policies NetBird policies APIs
// see more: https://docs.netbird.io/api/resources/policies
Policies *PoliciesAPI
// PostureChecks NetBird posture checks APIs
// see more: https://docs.netbird.io/api/resources/posture-checks
PostureChecks *PostureChecksAPI
// Networks NetBird networks APIs
// see more: https://docs.netbird.io/api/resources/networks
Networks *NetworksAPI
// Routes NetBird routes APIs
// see more: https://docs.netbird.io/api/resources/routes
Routes *RoutesAPI
// DNS NetBird DNS APIs
// see more: https://docs.netbird.io/api/resources/dns
DNS *DNSAPI
// DNSZones NetBird DNS Zones APIs
// see more: https://docs.netbird.io/api/resources/dns-zones
DNSZones *DNSZonesAPI
// GeoLocation NetBird Geo Location APIs
// see more: https://docs.netbird.io/api/resources/geo-locations
GeoLocation *GeoLocationAPI
// Events NetBird Events APIs
// see more: https://docs.netbird.io/api/resources/events
Events *EventsAPI
// Billing NetBird Billing APIs for subscriptions, plans, and invoices
// see more: https://docs.netbird.io/api/resources/billing
Billing *BillingAPI
// MSP NetBird MSP tenant management APIs
// see more: https://docs.netbird.io/api/resources/msp
MSP *MSPAPI
// EDR NetBird EDR integration APIs (Intune, SentinelOne, Falcon, Huntress)
// see more: https://docs.netbird.io/api/resources/edr
EDR *EDRAPI
// SCIM NetBird SCIM IDP integration APIs
// see more: https://docs.netbird.io/api/resources/scim
SCIM *SCIMAPI
// GoogleIDP NetBird Google Workspace IDP integration APIs
GoogleIDP *GoogleIDPAPI
// AzureIDP NetBird Azure AD IDP integration APIs
AzureIDP *AzureIDPAPI
// OktaScimIDP NetBird Okta SCIM IDP integration APIs
OktaScimIDP *OktaScimIDPAPI
// EventStreaming NetBird Event Streaming integration APIs
// see more: https://docs.netbird.io/api/resources/event-streaming
EventStreaming *EventStreamingAPI
// IdentityProviders NetBird Identity Providers APIs
// see more: https://docs.netbird.io/api/resources/identity-providers
IdentityProviders *IdentityProvidersAPI
// Ingress NetBird Ingress Peers APIs
// see more: https://docs.netbird.io/api/resources/ingress-ports
Ingress *IngressAPI
// Instance NetBird Instance API
// see more: https://docs.netbird.io/api/resources/instance
Instance *InstanceAPI
// ReverseProxyServices NetBird reverse proxy services APIs
ReverseProxyServices *ReverseProxyServicesAPI
// ReverseProxyClusters NetBird reverse proxy clusters APIs
ReverseProxyClusters *ReverseProxyClustersAPI
// ReverseProxyDomains NetBird reverse proxy domains APIs
ReverseProxyDomains *ReverseProxyDomainsAPI
}
// New initialize new Client instance using PAT token
func New(managementURL, token string) *Client {
return NewWithOptions(
WithManagementURL(managementURL),
WithPAT(token),
)
}
// NewWithBearerToken initialize new Client instance using Bearer token type
func NewWithBearerToken(managementURL, token string) *Client {
return NewWithOptions(
WithManagementURL(managementURL),
WithBearerToken(token),
)
}
// NewWithOptions initialize new Client instance with options
func NewWithOptions(opts ...option) *Client {
client := &Client{
httpClient: http.DefaultClient,
}
for _, option := range opts {
option(client)
}
client.initialize()
return client
}
func (c *Client) initialize() {
c.Accounts = &AccountsAPI{c}
c.Users = &UsersAPI{c}
c.Tokens = &TokensAPI{c}
c.Peers = &PeersAPI{c}
c.SetupKeys = &SetupKeysAPI{c}
c.Groups = &GroupsAPI{c}
c.Policies = &PoliciesAPI{c}
c.PostureChecks = &PostureChecksAPI{c}
c.Networks = &NetworksAPI{c}
c.Routes = &RoutesAPI{c}
c.DNS = &DNSAPI{c}
c.DNSZones = &DNSZonesAPI{c}
c.GeoLocation = &GeoLocationAPI{c}
c.Events = &EventsAPI{c}
c.Billing = &BillingAPI{c}
c.MSP = &MSPAPI{c}
c.EDR = &EDRAPI{c}
c.SCIM = &SCIMAPI{c}
c.GoogleIDP = &GoogleIDPAPI{c}
c.AzureIDP = &AzureIDPAPI{c}
c.OktaScimIDP = &OktaScimIDPAPI{c}
c.EventStreaming = &EventStreamingAPI{c}
c.IdentityProviders = &IdentityProvidersAPI{c}
c.Ingress = &IngressAPI{c}
c.Instance = &InstanceAPI{c}
c.ReverseProxyServices = &ReverseProxyServicesAPI{c}
c.ReverseProxyClusters = &ReverseProxyClustersAPI{c}
c.ReverseProxyDomains = &ReverseProxyDomainsAPI{c}
}
// NewRequest creates and executes new management API request
func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader, query map[string]string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, method, c.managementURL+path, body)
if err != nil {
return nil, err
}
req.Header.Add("Authorization", c.authHeader)
req.Header.Add("Accept", "application/json")
if body != nil {
req.Header.Add("Content-Type", "application/json")
}
if c.userAgent != "" {
req.Header.Set("User-Agent", c.userAgent)
}
if len(query) != 0 {
q := req.URL.Query()
for k, v := range query {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode > 299 {
parsedErr, pErr := parseResponse[util.ErrorResponse](resp)
if pErr != nil {
return nil, pErr
}
return nil, &APIError{
StatusCode: resp.StatusCode,
Message: parsedErr.Message,
}
}
return resp, nil
}
func parseResponse[T any](resp *http.Response) (T, error) {
var ret T
if resp.Body == nil {
return ret, fmt.Errorf("body missing, HTTP Error code %d", resp.StatusCode)
}
bs, err := io.ReadAll(resp.Body)
if err != nil {
return ret, err
}
err = json.Unmarshal(bs, &ret)
if err != nil {
return ret, fmt.Errorf("error code %d, error unmarshalling body: %w", resp.StatusCode, err)
}
return ret, nil
}