-
-
Notifications
You must be signed in to change notification settings - Fork 562
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[management] REST client package (#3278)
- Loading branch information
1 parent
f930ef2
commit 7d385b8
Showing
29 changed files
with
4,215 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package rest | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
|
||
"github.com/netbirdio/netbird/management/server/http/api" | ||
) | ||
|
||
// AccountsAPI APIs for accounts, do not use directly | ||
type AccountsAPI struct { | ||
c *Client | ||
} | ||
|
||
// List list all accounts, only returns one account always | ||
// See more: https://docs.netbird.io/api/resources/accounts#list-all-accounts | ||
func (a *AccountsAPI) List(ctx context.Context) ([]api.Account, error) { | ||
resp, err := a.c.newRequest(ctx, "GET", "/api/accounts", nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer resp.Body.Close() | ||
ret, err := parseResponse[[]api.Account](resp) | ||
return ret, err | ||
} | ||
|
||
// Update update account settings | ||
// See more: https://docs.netbird.io/api/resources/accounts#update-an-account | ||
func (a *AccountsAPI) Update(ctx context.Context, accountID string, request api.PutApiAccountsAccountIdJSONRequestBody) (*api.Account, error) { | ||
requestBytes, err := json.Marshal(request) | ||
if err != nil { | ||
return nil, err | ||
} | ||
resp, err := a.c.newRequest(ctx, "PUT", "/api/accounts/"+accountID, bytes.NewReader(requestBytes)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer resp.Body.Close() | ||
ret, err := parseResponse[api.Account](resp) | ||
return &ret, err | ||
} | ||
|
||
// Delete delete account | ||
// See more: https://docs.netbird.io/api/resources/accounts#delete-an-account | ||
func (a *AccountsAPI) Delete(ctx context.Context, accountID string) error { | ||
resp, err := a.c.newRequest(ctx, "DELETE", "/api/accounts/"+accountID, nil) | ||
if err != nil { | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
package rest | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/netbirdio/netbird/management/server/http/api" | ||
"github.com/netbirdio/netbird/management/server/http/util" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
var ( | ||
testAccount = api.Account{ | ||
Id: "Test", | ||
Settings: api.AccountSettings{ | ||
Extra: &api.AccountExtraSettings{ | ||
PeerApprovalEnabled: ptr(false), | ||
}, | ||
GroupsPropagationEnabled: ptr(true), | ||
JwtGroupsEnabled: ptr(false), | ||
PeerInactivityExpiration: 7, | ||
PeerInactivityExpirationEnabled: true, | ||
PeerLoginExpiration: 24, | ||
PeerLoginExpirationEnabled: true, | ||
RegularUsersViewBlocked: false, | ||
RoutingPeerDnsResolutionEnabled: ptr(false), | ||
}, | ||
} | ||
) | ||
|
||
func TestAccounts_List_200(t *testing.T) { | ||
withMockClient(func(c *Client, mux *http.ServeMux) { | ||
mux.HandleFunc("/api/accounts", func(w http.ResponseWriter, r *http.Request) { | ||
retBytes, _ := json.Marshal([]api.Account{testAccount}) | ||
_, err := w.Write(retBytes) | ||
require.NoError(t, err) | ||
}) | ||
ret, err := c.Accounts.List(context.Background()) | ||
require.NoError(t, err) | ||
assert.Len(t, ret, 1) | ||
assert.Equal(t, testAccount, ret[0]) | ||
}) | ||
} | ||
|
||
func TestAccounts_List_Err(t *testing.T) { | ||
withMockClient(func(c *Client, mux *http.ServeMux) { | ||
mux.HandleFunc("/api/accounts", func(w http.ResponseWriter, r *http.Request) { | ||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "No", Code: 400}) | ||
w.WriteHeader(400) | ||
_, err := w.Write(retBytes) | ||
require.NoError(t, err) | ||
}) | ||
ret, err := c.Accounts.List(context.Background()) | ||
assert.Error(t, err) | ||
assert.Equal(t, "No", err.Error()) | ||
assert.Empty(t, ret) | ||
}) | ||
} | ||
|
||
func TestAccounts_Update_200(t *testing.T) { | ||
withMockClient(func(c *Client, mux *http.ServeMux) { | ||
mux.HandleFunc("/api/accounts/Test", func(w http.ResponseWriter, r *http.Request) { | ||
assert.Equal(t, "PUT", r.Method) | ||
reqBytes, err := io.ReadAll(r.Body) | ||
require.NoError(t, err) | ||
var req api.PutApiAccountsAccountIdJSONRequestBody | ||
err = json.Unmarshal(reqBytes, &req) | ||
require.NoError(t, err) | ||
assert.Equal(t, true, *req.Settings.RoutingPeerDnsResolutionEnabled) | ||
retBytes, _ := json.Marshal(testAccount) | ||
_, err = w.Write(retBytes) | ||
require.NoError(t, err) | ||
}) | ||
ret, err := c.Accounts.Update(context.Background(), "Test", api.PutApiAccountsAccountIdJSONRequestBody{ | ||
Settings: api.AccountSettings{ | ||
RoutingPeerDnsResolutionEnabled: ptr(true), | ||
}, | ||
}) | ||
require.NoError(t, err) | ||
assert.Equal(t, testAccount, *ret) | ||
}) | ||
|
||
} | ||
|
||
func TestAccounts_Update_Err(t *testing.T) { | ||
withMockClient(func(c *Client, mux *http.ServeMux) { | ||
mux.HandleFunc("/api/accounts/Test", func(w http.ResponseWriter, r *http.Request) { | ||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "No", Code: 400}) | ||
w.WriteHeader(400) | ||
_, err := w.Write(retBytes) | ||
require.NoError(t, err) | ||
}) | ||
ret, err := c.Accounts.Update(context.Background(), "Test", api.PutApiAccountsAccountIdJSONRequestBody{ | ||
Settings: api.AccountSettings{ | ||
RoutingPeerDnsResolutionEnabled: ptr(true), | ||
}, | ||
}) | ||
assert.Error(t, err) | ||
assert.Equal(t, "No", err.Error()) | ||
assert.Nil(t, ret) | ||
}) | ||
} | ||
|
||
func TestAccounts_Delete_200(t *testing.T) { | ||
withMockClient(func(c *Client, mux *http.ServeMux) { | ||
mux.HandleFunc("/api/accounts/Test", func(w http.ResponseWriter, r *http.Request) { | ||
assert.Equal(t, "DELETE", r.Method) | ||
w.WriteHeader(200) | ||
}) | ||
err := c.Accounts.Delete(context.Background(), "Test") | ||
require.NoError(t, err) | ||
}) | ||
} | ||
|
||
func TestAccounts_Delete_Err(t *testing.T) { | ||
withMockClient(func(c *Client, mux *http.ServeMux) { | ||
mux.HandleFunc("/api/accounts/Test", func(w http.ResponseWriter, r *http.Request) { | ||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "Not found", Code: 404}) | ||
w.WriteHeader(404) | ||
_, err := w.Write(retBytes) | ||
require.NoError(t, err) | ||
}) | ||
err := c.Accounts.Delete(context.Background(), "Test") | ||
assert.Error(t, err) | ||
assert.Equal(t, "Not found", err.Error()) | ||
}) | ||
} | ||
|
||
func TestAccounts_Integration_List(t *testing.T) { | ||
withBlackBoxServer(t, func(c *Client) { | ||
accounts, err := c.Accounts.List(context.Background()) | ||
require.NoError(t, err) | ||
assert.Len(t, accounts, 1) | ||
assert.Equal(t, "bf1c8084-ba50-4ce7-9439-34653001fc3b", accounts[0].Id) | ||
assert.Equal(t, false, *accounts[0].Settings.Extra.PeerApprovalEnabled) | ||
}) | ||
} | ||
|
||
func TestAccounts_Integration_Update(t *testing.T) { | ||
withBlackBoxServer(t, func(c *Client) { | ||
accounts, err := c.Accounts.List(context.Background()) | ||
require.NoError(t, err) | ||
assert.Len(t, accounts, 1) | ||
accounts[0].Settings.JwtAllowGroups = ptr([]string{"test"}) | ||
account, err := c.Accounts.Update(context.Background(), accounts[0].Id, api.AccountRequest{ | ||
Settings: accounts[0].Settings, | ||
}) | ||
require.NoError(t, err) | ||
assert.Equal(t, accounts[0].Id, account.Id) | ||
assert.Equal(t, []string{"test"}, *account.Settings.JwtAllowGroups) | ||
}) | ||
} | ||
|
||
// Account deletion on MySQL and PostgreSQL databases causes unknown errors | ||
// func TestAccounts_Integration_Delete(t *testing.T) { | ||
// withBlackBoxServer(t, func(c *Client) { | ||
// accounts, err := c.Accounts.List(context.Background()) | ||
// require.NoError(t, err) | ||
// assert.Len(t, accounts, 1) | ||
// err = c.Accounts.Delete(context.Background(), accounts[0].Id) | ||
// require.NoError(t, err) | ||
// _, err = c.Accounts.List(context.Background()) | ||
// assert.Error(t, err) | ||
// }) | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package rest | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"io" | ||
"net/http" | ||
|
||
"github.com/netbirdio/netbird/management/server/http/util" | ||
) | ||
|
||
// Client Management service HTTP REST API Client | ||
type Client struct { | ||
managementURL string | ||
authHeader 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/routes | ||
DNS *DNSAPI | ||
|
||
// 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 | ||
} | ||
|
||
// New initialize new Client instance | ||
func New(managementURL, token string) *Client { | ||
client := &Client{ | ||
managementURL: managementURL, | ||
authHeader: "Token " + token, | ||
} | ||
client.Accounts = &AccountsAPI{client} | ||
client.Users = &UsersAPI{client} | ||
client.Tokens = &TokensAPI{client} | ||
client.Peers = &PeersAPI{client} | ||
client.SetupKeys = &SetupKeysAPI{client} | ||
client.Groups = &GroupsAPI{client} | ||
client.Policies = &PoliciesAPI{client} | ||
client.PostureChecks = &PostureChecksAPI{client} | ||
client.Networks = &NetworksAPI{client} | ||
client.Routes = &RoutesAPI{client} | ||
client.DNS = &DNSAPI{client} | ||
client.GeoLocation = &GeoLocationAPI{client} | ||
client.Events = &EventsAPI{client} | ||
return client | ||
} | ||
|
||
func (c *Client) newRequest(ctx context.Context, method, path string, body io.Reader) (*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") | ||
} | ||
|
||
resp, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if resp.StatusCode > 299 { | ||
parsedErr, pErr := parseResponse[util.ErrorResponse](resp) | ||
if pErr != nil { | ||
return nil, err | ||
} | ||
return nil, errors.New(parsedErr.Message) | ||
} | ||
|
||
return resp, nil | ||
} | ||
|
||
func parseResponse[T any](resp *http.Response) (T, error) { | ||
var ret T | ||
if resp.Body == nil { | ||
return ret, errors.New("No body") | ||
} | ||
bs, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return ret, err | ||
} | ||
err = json.Unmarshal(bs, &ret) | ||
|
||
return ret, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package rest | ||
|
||
import ( | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/netbirdio/netbird/management/server/http/testing/testing_tools" | ||
) | ||
|
||
func withMockClient(callback func(*Client, *http.ServeMux)) { | ||
mux := &http.ServeMux{} | ||
server := httptest.NewServer(mux) | ||
defer server.Close() | ||
c := New(server.URL, "ABC") | ||
callback(c, mux) | ||
} | ||
|
||
func ptr[T any, PT *T](x T) PT { | ||
return &x | ||
} | ||
|
||
func withBlackBoxServer(t *testing.T, callback func(*Client)) { | ||
t.Helper() | ||
handler, _, _ := testing_tools.BuildApiBlackBoxWithDBState(t, "../../server/testdata/store.sql", nil, false) | ||
server := httptest.NewServer(handler) | ||
defer server.Close() | ||
c := New(server.URL, "nbp_apTmlmUXHSC4PKmHwtIZNaGr8eqcVI2gMURp") | ||
callback(c) | ||
} |
Oops, something went wrong.