mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-15 11:19:08 +02:00
Merge remote-tracking branch 'origin/main' into android-airplane
# Conflicts: # client/android/client.go # client/ios/NetBirdSDK/client.go
This commit is contained in:
@@ -57,10 +57,9 @@ func (a *AgentNetworkAPI) GetProvider(ctx context.Context, providerID string) (*
|
||||
return &ret, err
|
||||
}
|
||||
|
||||
// CreateProvider creates a new Agent Network provider. Set
|
||||
// request.BootstrapCluster on the account's first provider to bootstrap the
|
||||
// per-account gateway endpoint (alternatively bootstrap via UpdateSettings
|
||||
// with a cluster).
|
||||
// CreateProvider creates a new Agent Network provider. Providers have no
|
||||
// settings side effects — bootstrap the account's gateway endpoint separately
|
||||
// via CreateSettings.
|
||||
func (a *AgentNetworkAPI) CreateProvider(ctx context.Context, request api.PostApiAgentNetworkProvidersJSONRequestBody) (*api.AgentNetworkProvider, error) {
|
||||
requestBytes, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
@@ -329,14 +328,13 @@ func (a *AgentNetworkAPI) DeleteBudgetRule(ctx context.Context, ruleID string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSettings gets the account's Agent Network gateway settings (cluster,
|
||||
// subdomain, endpoint, collection toggles). An account that has not been
|
||||
// bootstrapped yet — via UpdateSettings with a cluster, or by creating the
|
||||
// first provider with bootstrap_cluster set — reads as the defaults with an
|
||||
// empty Cluster, Subdomain and Endpoint. Management servers prior to that
|
||||
// contract answered 200 with a JSON null body instead; that legacy shape is
|
||||
// translated to an APIError matchable via IsNotFound rather than fabricating
|
||||
// defaults the server never stated.
|
||||
// GetSettings gets the account's Agent Network gateway settings (endpoint,
|
||||
// proxy address, collection toggles). An account that has not been
|
||||
// bootstrapped yet — via CreateSettings — reads as the defaults with an empty
|
||||
// Endpoint and ProxyAddress. Management servers prior to that contract
|
||||
// answered 200 with a JSON null body instead; that legacy shape is translated
|
||||
// to an APIError matchable via IsNotFound rather than fabricating defaults
|
||||
// the server never stated.
|
||||
func (a *AgentNetworkAPI) GetSettings(ctx context.Context) (*api.AgentNetworkSettings, error) {
|
||||
resp, err := a.c.NewRequest(ctx, "GET", "/api/agent-network/settings", nil, nil)
|
||||
if err != nil {
|
||||
@@ -359,11 +357,33 @@ func (a *AgentNetworkAPI) GetSettings(ctx context.Context) (*api.AgentNetworkSet
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
// CreateSettings bootstraps the account's Agent Network settings row,
|
||||
// assigning the immutable endpoint. Exactly one of request.ProxyAddress
|
||||
// (labeled endpoint beneath that cluster; the server allocates the label) and
|
||||
// request.Endpoint (self-addressed dedicated endpoint, claimed verbatim) must
|
||||
// be set. Returns a conflict when the account already has a settings row.
|
||||
func (a *AgentNetworkAPI) CreateSettings(ctx context.Context, request api.PostApiAgentNetworkSettingsJSONRequestBody) (*api.AgentNetworkSettings, error) {
|
||||
requestBytes, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := a.c.NewRequest(ctx, "POST", "/api/agent-network/settings", bytes.NewReader(requestBytes), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
ret, err := parseResponse[api.AgentNetworkSettings](resp)
|
||||
return &ret, err
|
||||
}
|
||||
|
||||
// UpdateSettings updates the account's Agent Network settings; the request
|
||||
// replaces every mutable field (collection toggles and retention). Setting
|
||||
// request.Cluster bootstraps the settings row when the account does not have
|
||||
// one yet; on a bootstrapped account it must match the assigned cluster (or
|
||||
// be nil) and any other value is rejected — the cluster is immutable.
|
||||
// carries every field, replacing the mutable ones (collection toggles and
|
||||
// retention). The endpoint and proxy address are assigned at bootstrap
|
||||
// (CreateSettings) and immutable — the request must echo them unchanged, and
|
||||
// a request carrying different values is rejected. Returns not-found until
|
||||
// the account is bootstrapped.
|
||||
func (a *AgentNetworkAPI) UpdateSettings(ctx context.Context, request api.PutApiAgentNetworkSettingsJSONRequestBody) (*api.AgentNetworkSettings, error) {
|
||||
requestBytes, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
@@ -379,3 +399,19 @@ func (a *AgentNetworkAPI) UpdateSettings(ctx context.Context, request api.PutApi
|
||||
ret, err := parseResponse[api.AgentNetworkSettings](resp)
|
||||
return &ret, err
|
||||
}
|
||||
|
||||
// DeleteSettings deletes the account's Agent Network settings row, releasing
|
||||
// the endpoint. The server refuses (precondition failed) while any provider
|
||||
// exists for the account or while a proxy is actively serving the endpoint.
|
||||
// Bootstrapping again afterwards allocates a new endpoint.
|
||||
func (a *AgentNetworkAPI) DeleteSettings(ctx context.Context) error {
|
||||
resp, err := a.c.NewRequest(ctx, "DELETE", "/api/agent-network/settings", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -47,9 +47,9 @@ var (
|
||||
}
|
||||
|
||||
testAgentNetworkSettings = api.AgentNetworkSettings{
|
||||
Cluster: "eu.proxy.netbird.io",
|
||||
Subdomain: "violet",
|
||||
Endpoint: "violet.eu.proxy.netbird.io",
|
||||
ProxyAddress: "eu.proxy.netbird.io",
|
||||
Dedicated: false,
|
||||
EnableLogCollection: true,
|
||||
AccessLogRetentionDays: ptr(30),
|
||||
}
|
||||
@@ -120,18 +120,15 @@ func TestAgentNetwork_CreateProvider_200(t *testing.T) {
|
||||
var req api.PostApiAgentNetworkProvidersJSONRequestBody
|
||||
require.NoError(t, json.Unmarshal(reqBytes, &req))
|
||||
assert.Equal(t, "OpenAI", req.Name)
|
||||
require.NotNil(t, req.BootstrapCluster)
|
||||
assert.Equal(t, "eu.proxy.netbird.io", *req.BootstrapCluster)
|
||||
retBytes, _ := json.Marshal(testAgentNetworkProvider)
|
||||
_, err = w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
ret, err := c.AgentNetwork.CreateProvider(context.Background(), api.PostApiAgentNetworkProvidersJSONRequestBody{
|
||||
ProviderId: "openai_api",
|
||||
Name: "OpenAI",
|
||||
UpstreamUrl: "https://api.openai.com",
|
||||
ApiKey: ptr("sk-test"),
|
||||
BootstrapCluster: ptr("eu.proxy.netbird.io"),
|
||||
ProviderId: "openai_api",
|
||||
Name: "OpenAI",
|
||||
UpstreamUrl: "https://api.openai.com",
|
||||
ApiKey: ptr("sk-test"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testAgentNetworkProvider, *ret)
|
||||
@@ -456,6 +453,45 @@ func TestAgentNetwork_GetSettings_LegacyNullBody(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAgentNetwork_CreateSettings_200(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "POST", r.Method)
|
||||
reqBytes, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
var req api.PostApiAgentNetworkSettingsJSONRequestBody
|
||||
require.NoError(t, json.Unmarshal(reqBytes, &req))
|
||||
require.NotNil(t, req.ProxyAddress, "proxy address must be on the wire")
|
||||
assert.Equal(t, "eu.proxy.netbird.io", *req.ProxyAddress)
|
||||
assert.Nil(t, req.Endpoint, "endpoint must stay off the wire for a labeled bootstrap")
|
||||
retBytes, _ := json.Marshal(testAgentNetworkSettings)
|
||||
_, err = w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
ret, err := c.AgentNetwork.CreateSettings(context.Background(), api.PostApiAgentNetworkSettingsJSONRequestBody{
|
||||
ProxyAddress: ptr("eu.proxy.netbird.io"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, testAgentNetworkSettings, *ret)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAgentNetwork_CreateSettings_Conflict(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "agent network settings already bootstrapped for account acct1", Code: 409})
|
||||
w.WriteHeader(409)
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
_, err := c.AgentNetwork.CreateSettings(context.Background(), api.PostApiAgentNetworkSettingsJSONRequestBody{
|
||||
Endpoint: ptr("gw.example.com"),
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "already bootstrapped")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAgentNetwork_UpdateSettings_200(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -464,15 +500,18 @@ func TestAgentNetwork_UpdateSettings_200(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
var req api.PutApiAgentNetworkSettingsJSONRequestBody
|
||||
require.NoError(t, json.Unmarshal(reqBytes, &req))
|
||||
require.NotNil(t, req.Cluster, "bootstrap cluster must be on the wire")
|
||||
assert.Equal(t, "eu.proxy.netbird.io", *req.Cluster)
|
||||
assert.True(t, req.EnableLogCollection)
|
||||
assert.Equal(t, "brave-otter.eu.proxy.netbird.io", req.Endpoint,
|
||||
"the identity echo must be on the wire")
|
||||
assert.Equal(t, "eu.proxy.netbird.io", req.ProxyAddress,
|
||||
"the identity echo must be on the wire")
|
||||
retBytes, _ := json.Marshal(testAgentNetworkSettings)
|
||||
_, err = w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
ret, err := c.AgentNetwork.UpdateSettings(context.Background(), api.PutApiAgentNetworkSettingsJSONRequestBody{
|
||||
Cluster: ptr("eu.proxy.netbird.io"),
|
||||
Endpoint: "brave-otter.eu.proxy.netbird.io",
|
||||
ProxyAddress: "eu.proxy.netbird.io",
|
||||
EnableLogCollection: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
@@ -483,15 +522,40 @@ func TestAgentNetwork_UpdateSettings_200(t *testing.T) {
|
||||
func TestAgentNetwork_UpdateSettings_Err(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "cluster is immutable once assigned (current: eu.proxy.netbird.io)", Code: 422})
|
||||
w.WriteHeader(422)
|
||||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "agent network settings have not been bootstrapped yet; POST /api/agent-network/settings to bootstrap them", Code: 404})
|
||||
w.WriteHeader(404)
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
_, err := c.AgentNetwork.UpdateSettings(context.Background(), api.PutApiAgentNetworkSettingsJSONRequestBody{
|
||||
Cluster: ptr("us.proxy.netbird.io"),
|
||||
EnableLogCollection: true,
|
||||
})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "immutable")
|
||||
assert.True(t, rest.IsNotFound(err), "an unbootstrapped account must surface as IsNotFound")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAgentNetwork_DeleteSettings_200(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, "DELETE", r.Method)
|
||||
_, err := w.Write([]byte("{}"))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
require.NoError(t, c.AgentNetwork.DeleteSettings(context.Background()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestAgentNetwork_DeleteSettings_Guarded(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/agent-network/settings", func(w http.ResponseWriter, r *http.Request) {
|
||||
retBytes, _ := json.Marshal(util.ErrorResponse{Message: "agent network settings cannot be deleted while 2 provider(s) exist; delete the providers first", Code: 412})
|
||||
w.WriteHeader(412)
|
||||
_, err := w.Write(retBytes)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
err := c.AgentNetwork.DeleteSettings(context.Background())
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "cannot be deleted")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user