diff --git a/.mockery.yaml b/.mockery.yaml
index 45f9648cd0..4e71fdc67d 100644
--- a/.mockery.yaml
+++ b/.mockery.yaml
@@ -38,7 +38,7 @@ packages:
interfaces:
agentsRegistry:
agentsStateUpdater:
- apiKeyProvider:
+ authProvider:
checksService:
connectionChecker:
grafanaClient:
diff --git a/admin/commands/base/setup.go b/admin/commands/base/setup.go
index a7dc9a472f..030030f755 100644
--- a/admin/commands/base/setup.go
+++ b/admin/commands/base/setup.go
@@ -88,8 +88,13 @@ func SetupClients(ctx context.Context, globalFlags *flags.GlobalFlags) {
// use JSON APIs over HTTP/1.1
transport := httptransport.New(globalFlags.ServerURL.Host, globalFlags.ServerURL.Path, []string{globalFlags.ServerURL.Scheme})
if u := globalFlags.ServerURL.User; u != nil {
+ user := u.Username()
password, _ := u.Password()
- transport.DefaultAuthentication = httptransport.BasicAuth(u.Username(), password)
+ if user == "service_token" || user == "api_key" {
+ transport.DefaultAuthentication = httptransport.BearerToken(password)
+ } else {
+ transport.DefaultAuthentication = httptransport.BasicAuth(user, password)
+ }
}
transport.SetLogger(logrus.WithField("component", "server-transport"))
transport.SetDebug(globalFlags.EnableDebug || globalFlags.EnableTrace)
diff --git a/admin/commands/management/unregister.go b/admin/commands/management/unregister.go
index 1bd1292057..b637c1f997 100644
--- a/admin/commands/management/unregister.go
+++ b/admin/commands/management/unregister.go
@@ -16,12 +16,15 @@ package management
import (
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
"github.com/percona/pmm/admin/agentlocal"
"github.com/percona/pmm/admin/commands"
"github.com/percona/pmm/admin/helpers"
- "github.com/percona/pmm/api/inventorypb/json/client"
+ inventoryClient "github.com/percona/pmm/api/inventorypb/json/client"
"github.com/percona/pmm/api/inventorypb/json/client/nodes"
+ "github.com/percona/pmm/api/managementpb/json/client"
+ "github.com/percona/pmm/api/managementpb/json/client/node"
)
type unregisterResult struct {
@@ -63,7 +66,7 @@ func (cmd *UnregisterCommand) RunCmd() (commands.Result, error) {
}
nodeID = status.NodeID
- node, err := client.Default.Nodes.GetNode(&nodes.GetNodeParams{
+ node, err := inventoryClient.Default.Nodes.GetNode(&nodes.GetNodeParams{
Context: commands.Ctx,
Body: nodes.GetNodeBody{
NodeID: nodeID,
@@ -78,19 +81,23 @@ func (cmd *UnregisterCommand) RunCmd() (commands.Result, error) {
}
}
- params := &nodes.RemoveNodeParams{
- Body: nodes.RemoveNodeBody{
+ params := &node.UnregisterNodeParams{
+ Body: node.UnregisterNodeBody{
NodeID: nodeID,
Force: cmd.Force,
},
Context: commands.Ctx,
}
- _, err = client.Default.Nodes.RemoveNode(params)
+ res, err := client.Default.Node.UnregisterNode(params)
if err != nil {
return nil, err
}
+ if res.Payload.Warning != "" {
+ logrus.Warning(res.Payload.Warning)
+ }
+
return &unregisterResult{
NodeID: nodeID,
NodeName: nodeName,
@@ -98,7 +105,7 @@ func (cmd *UnregisterCommand) RunCmd() (commands.Result, error) {
}
func nodeIDFromNodeName(nodeName string) (string, error) {
- listNodes, err := client.Default.Nodes.ListNodes(nil)
+ listNodes, err := inventoryClient.Default.Nodes.ListNodes(nil)
if err != nil {
return "", err
}
diff --git a/agent/client/basic_auth.go b/agent/client/basic_auth.go
index 2984533215..c6716a64e7 100644
--- a/agent/client/basic_auth.go
+++ b/agent/client/basic_auth.go
@@ -17,6 +17,7 @@ package client
import (
"context"
"encoding/base64"
+ "fmt"
"google.golang.org/grpc/credentials"
)
@@ -31,7 +32,7 @@ func (b *basicAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[
auth := b.username + ":" + b.password
enc := base64.StdEncoding.EncodeToString([]byte(auth))
return map[string]string{
- "authorization": "Basic " + enc,
+ "Authorization": fmt.Sprintf("Basic %s", enc),
}, nil
}
diff --git a/agent/client/client.go b/agent/client/client.go
index b79eb8e261..8a7dfb807b 100644
--- a/agent/client/client.go
+++ b/agent/client/client.go
@@ -748,10 +748,16 @@ func dial(dialCtx context.Context, cfg *config.Config, l *logrus.Entry) (*dialRe
}
if cfg.Server.Username != "" {
- opts = append(opts, grpc.WithPerRPCCredentials(&basicAuth{
- username: cfg.Server.Username,
- password: cfg.Server.Password,
- }))
+ if cfg.Server.Username == "service_token" || cfg.Server.Username == "api_key" {
+ opts = append(opts, grpc.WithPerRPCCredentials(&tokenAuth{
+ token: cfg.Server.Password,
+ }))
+ } else {
+ opts = append(opts, grpc.WithPerRPCCredentials(&basicAuth{
+ username: cfg.Server.Username,
+ password: cfg.Server.Password,
+ }))
+ }
}
l.Infof("Connecting to %s ...", cfg.Server.FilteredURL())
diff --git a/agent/client/token_auth.go b/agent/client/token_auth.go
new file mode 100644
index 0000000000..2204f9cab8
--- /dev/null
+++ b/agent/client/token_auth.go
@@ -0,0 +1,43 @@
+// Copyright (C) 2023 Percona LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package client
+
+import (
+ "context"
+ "fmt"
+
+ "google.golang.org/grpc/credentials"
+)
+
+type tokenAuth struct {
+ token string
+}
+
+// GetRequestMetadata implements credentials.PerRPCCredentials interface.
+func (t *tokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { //nolint:revive
+ return map[string]string{
+ "Authorization": fmt.Sprintf("Bearer %s", t.token),
+ }, nil
+}
+
+// RequireTransportSecurity implements credentials.PerRPCCredentials interface.
+func (*tokenAuth) RequireTransportSecurity() bool {
+ return false
+}
+
+// check interfaces.
+var (
+ _ credentials.PerRPCCredentials = (*tokenAuth)(nil)
+)
diff --git a/agent/commands/clients.go b/agent/commands/clients.go
index c44353b9e6..039d48201b 100644
--- a/agent/commands/clients.go
+++ b/agent/commands/clients.go
@@ -104,8 +104,13 @@ func setServerTransport(u *url.URL, insecureTLS bool, l *logrus.Entry) {
// use JSON APIs over HTTP/1.1
transport := httptransport.New(u.Host, u.Path, []string{u.Scheme})
if u.User != nil {
+ user := u.User.Username()
password, _ := u.User.Password()
- transport.DefaultAuthentication = httptransport.BasicAuth(u.User.Username(), password)
+ if user == "service_token" || user == "api_key" {
+ transport.DefaultAuthentication = httptransport.BearerToken(password)
+ } else {
+ transport.DefaultAuthentication = httptransport.BasicAuth(user, password)
+ }
}
transport.SetLogger(l)
transport.SetDebug(l.Logger.GetLevel() >= logrus.DebugLevel)
diff --git a/agent/commands/setup.go b/agent/commands/setup.go
index 0cd218f944..e0b6d4f95d 100644
--- a/agent/commands/setup.go
+++ b/agent/commands/setup.go
@@ -160,10 +160,10 @@ func register(cfg *config.Config, l *logrus.Entry) {
}
cfg.ID = agentID
if token != "" {
- cfg.Server.Username = "api_key"
+ cfg.Server.Username = "service_token"
cfg.Server.Password = token
} else {
- l.Info("PMM Server responded with an empty api key token. Consider upgrading PMM Server to the latest version.")
+ l.Info("PMM Server responded with an empty service token. Consider upgrading PMM Server to the latest version.")
}
fmt.Printf("Registered.\n")
}
diff --git a/api-tests/helpers.go b/api-tests/helpers.go
index e3cd51c598..89709075c2 100644
--- a/api-tests/helpers.go
+++ b/api-tests/helpers.go
@@ -17,8 +17,10 @@ package apitests
import (
"context"
+ "crypto/rand"
"fmt"
- "math/rand"
+ "math"
+ "math/big"
"reflect"
"testing"
@@ -26,10 +28,12 @@ import (
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
- "github.com/percona/pmm/api/inventorypb/json/client"
+ inventoryClient "github.com/percona/pmm/api/inventorypb/json/client"
"github.com/percona/pmm/api/inventorypb/json/client/agents"
"github.com/percona/pmm/api/inventorypb/json/client/nodes"
"github.com/percona/pmm/api/inventorypb/json/client/services"
+ "github.com/percona/pmm/api/managementpb/json/client"
+ "github.com/percona/pmm/api/managementpb/json/client/node"
)
// ErrorResponse represents the response structure for error scenarios.
@@ -49,7 +53,10 @@ type TestingT interface {
func TestString(t TestingT, name string) string {
t.Helper()
- n := rand.Int() //nolint:gosec
+ // Without proper seed parallel tests can generate same "random" number.
+ n, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt32))
+ require.NoError(t, err)
+
return fmt.Sprintf("pmm-api-tests/%s/%s/%s/%d", Hostname, t.Name(), name, n)
}
@@ -130,6 +137,26 @@ func (tt *expectedFailureTestingT) Check() {
tt.t.Fatalf("%s expected to fail, but didn't: %s", tt.Name(), tt.link)
}
+// UnregisterNodes unregister specified nodes.
+func UnregisterNodes(t TestingT, nodeIDs ...string) {
+ t.Helper()
+
+ for _, nodeID := range nodeIDs {
+ params := &node.UnregisterNodeParams{
+ Body: node.UnregisterNodeBody{
+ NodeID: nodeID,
+ },
+ Context: context.Background(),
+ }
+
+ res, err := client.Default.Node.UnregisterNode(params)
+ require.NoError(t, err)
+ assert.NotNil(t, res)
+ assert.NotNil(t, res.Payload)
+ assert.Empty(t, res.Payload.Warning)
+ }
+}
+
// RemoveNodes removes specified nodes.
func RemoveNodes(t TestingT, nodeIDs ...string) {
t.Helper()
@@ -141,7 +168,7 @@ func RemoveNodes(t TestingT, nodeIDs ...string) {
},
Context: context.Background(),
}
- res, err := client.Default.Nodes.RemoveNode(params)
+ res, err := inventoryClient.Default.Nodes.RemoveNode(params)
assert.NoError(t, err)
assert.NotNil(t, res)
}
@@ -159,7 +186,7 @@ func RemoveServices(t TestingT, serviceIDs ...string) {
},
Context: context.Background(),
}
- res, err := client.Default.Services.RemoveService(params)
+ res, err := inventoryClient.Default.Services.RemoveService(params)
assert.NoError(t, err)
assert.NotNil(t, res)
}
@@ -176,7 +203,7 @@ func RemoveAgents(t TestingT, agentIDs ...string) {
},
Context: context.Background(),
}
- res, err := client.Default.Agents.RemoveAgent(params)
+ res, err := inventoryClient.Default.Agents.RemoveAgent(params)
assert.NoError(t, err)
assert.NotNil(t, res)
}
@@ -193,7 +220,7 @@ func AddGenericNode(t TestingT, nodeName string) *nodes.AddGenericNodeOKBodyGene
},
Context: Context,
}
- res, err := client.Default.Nodes.AddGenericNode(params)
+ res, err := inventoryClient.Default.Nodes.AddGenericNode(params)
assert.NoError(t, err)
require.NotNil(t, res)
require.NotNil(t, res.Payload)
@@ -212,7 +239,7 @@ func AddRemoteNode(t TestingT, nodeName string) *nodes.AddRemoteNodeOKBody {
},
Context: Context,
}
- res, err := client.Default.Nodes.AddRemoteNode(params)
+ res, err := inventoryClient.Default.Nodes.AddRemoteNode(params)
assert.NoError(t, err)
require.NotNil(t, res)
return res.Payload
@@ -227,7 +254,7 @@ func AddNode(t TestingT, nodeBody *nodes.AddNodeBody) *nodes.AddNodeOKBody {
Context: Context,
}
- res, err := client.Default.Nodes.AddNode(params)
+ res, err := inventoryClient.Default.Nodes.AddNode(params)
assert.NoError(t, err)
require.NotNil(t, res)
@@ -238,7 +265,7 @@ func AddNode(t TestingT, nodeBody *nodes.AddNodeBody) *nodes.AddNodeOKBody {
func AddPMMAgent(t TestingT, nodeID string) *agents.AddPMMAgentOKBody {
t.Helper()
- res, err := client.Default.Agents.AddPMMAgent(&agents.AddPMMAgentParams{
+ res, err := inventoryClient.Default.Agents.AddPMMAgent(&agents.AddPMMAgentParams{
Body: agents.AddPMMAgentBody{
RunsOnNodeID: nodeID,
},
diff --git a/api-tests/inventory/nodes_test.go b/api-tests/inventory/nodes_test.go
index 6cd3d5c466..9bfbbcbce0 100644
--- a/api-tests/inventory/nodes_test.go
+++ b/api-tests/inventory/nodes_test.go
@@ -32,7 +32,6 @@ import (
)
func TestNodesDeprecated(t *testing.T) {
- t.Parallel()
t.Run("List", func(t *testing.T) {
t.Parallel()
@@ -92,7 +91,6 @@ func TestNodesDeprecated(t *testing.T) {
}
func TestNodes(t *testing.T) {
- t.Parallel()
t.Run("List", func(t *testing.T) {
t.Parallel()
@@ -164,7 +162,6 @@ func TestNodes(t *testing.T) {
}
func TestGetNode(t *testing.T) {
- t.Parallel()
t.Run("Basic", func(t *testing.T) {
t.Parallel()
@@ -218,7 +215,6 @@ func TestGetNode(t *testing.T) {
}
func TestGenericNodeDeprecated(t *testing.T) {
- t.Parallel()
t.Run("Basic", func(t *testing.T) {
t.Parallel()
@@ -278,7 +274,6 @@ func TestGenericNodeDeprecated(t *testing.T) {
}
func TestGenericNode(t *testing.T) {
- t.Parallel()
t.Run("Basic", func(t *testing.T) {
t.Parallel()
@@ -342,7 +337,6 @@ func TestGenericNode(t *testing.T) {
}
func TestContainerNodeDeprecated(t *testing.T) {
- t.Parallel()
t.Run("Basic", func(t *testing.T) {
t.Parallel()
@@ -407,7 +401,6 @@ func TestContainerNodeDeprecated(t *testing.T) {
}
func TestContainerNode(t *testing.T) {
- t.Parallel()
t.Run("Basic", func(t *testing.T) {
t.Parallel()
@@ -476,7 +469,6 @@ func TestContainerNode(t *testing.T) {
}
func TestRemoteNodeDeprecated(t *testing.T) {
- t.Parallel()
t.Run("Basic", func(t *testing.T) {
t.Parallel()
@@ -541,7 +533,6 @@ func TestRemoteNodeDeprecated(t *testing.T) {
}
func TestRemoteNode(t *testing.T) {
- t.Parallel()
t.Run("Basic", func(t *testing.T) {
t.Parallel()
@@ -610,7 +601,6 @@ func TestRemoteNode(t *testing.T) {
}
func TestRemoveNode(t *testing.T) {
- t.Parallel()
t.Run("Basic", func(t *testing.T) {
t.Parallel()
diff --git a/api-tests/management/backup/backups_test.go b/api-tests/management/backup/backups_test.go
index 609989133d..2a10622e14 100644
--- a/api-tests/management/backup/backups_test.go
+++ b/api-tests/management/backup/backups_test.go
@@ -41,7 +41,7 @@ func TestScheduleBackup(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer management.RemovePMMAgentWithSubAgents(t, pmmAgentID)
mongo1Name := pmmapitests.TestString(t, "mongo")
mongo2Name := pmmapitests.TestString(t, "mongo")
diff --git a/api-tests/management/haproxy_test.go b/api-tests/management/haproxy_test.go
index 7b25a0103e..2dddae47ea 100644
--- a/api-tests/management/haproxy_test.go
+++ b/api-tests/management/haproxy_test.go
@@ -41,7 +41,7 @@ func TestAddHAProxy(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-basic-name")
@@ -109,7 +109,7 @@ func TestAddHAProxy(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-all-fields-name")
@@ -257,7 +257,7 @@ func TestAddHAProxy(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-the-same-name")
diff --git a/api-tests/management/helpers.go b/api-tests/management/helpers.go
index 3f00f54eb7..96d3680793 100644
--- a/api-tests/management/helpers.go
+++ b/api-tests/management/helpers.go
@@ -48,6 +48,7 @@ func RegisterGenericNode(t pmmapitests.TestingT, body node.RegisterNodeBody) (st
require.NotNil(t, registerOK.Payload.PMMAgent.AgentID)
require.NotNil(t, registerOK.Payload.GenericNode)
require.NotNil(t, registerOK.Payload.GenericNode.NodeID)
+
return registerOK.Payload.GenericNode.NodeID, registerOK.Payload.PMMAgent.AgentID
}
@@ -65,6 +66,7 @@ func registerContainerNode(t pmmapitests.TestingT, body node.RegisterNodeBody) (
require.NotNil(t, registerOK.Payload.PMMAgent.AgentID)
require.NotNil(t, registerOK.Payload.ContainerNode)
require.NotNil(t, registerOK.Payload.ContainerNode.NodeID)
+
return registerOK.Payload.ContainerNode.NodeID, registerOK.Payload.PMMAgent.AgentID
}
diff --git a/api-tests/management/mongodb_test.go b/api-tests/management/mongodb_test.go
index 17472529af..60c411a0b0 100644
--- a/api-tests/management/mongodb_test.go
+++ b/api-tests/management/mongodb_test.go
@@ -40,7 +40,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name-for-basic-name")
@@ -108,12 +108,12 @@ func TestAddMongoDB(t *testing.T) {
})
t.Run("With agents", func(t *testing.T) {
- nodeName := pmmapitests.TestString(t, "node-name-for-all-fields")
+ nodeName := pmmapitests.TestString(t, "with-agents")
nodeID, pmmAgentID := RegisterGenericNode(t, node.RegisterNodeBody{
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name-for-all-fields")
@@ -196,12 +196,12 @@ func TestAddMongoDB(t *testing.T) {
})
t.Run("With labels", func(t *testing.T) {
- nodeName := pmmapitests.TestString(t, "node-name-for-all-fields")
+ nodeName := pmmapitests.TestString(t, "with-labels")
nodeID, pmmAgentID := RegisterGenericNode(t, node.RegisterNodeBody{
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name-for-all-fields")
@@ -260,7 +260,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-the-same-name")
@@ -306,7 +306,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
nodeNameAddNode := pmmapitests.TestString(t, "node-for-add-node-name")
@@ -422,7 +422,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
remoteNodeOKBody := pmmapitests.AddRemoteNode(t, pmmapitests.TestString(t, "Remote Node for wrong type test"))
@@ -453,7 +453,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
params := &mongodb.AddMongoDBParams{
@@ -471,7 +471,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -494,7 +494,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -518,7 +518,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -542,7 +542,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -570,7 +570,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name-for-mongo-socket-name")
@@ -639,7 +639,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name-for-basic-name")
@@ -711,7 +711,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name-for-basic-name")
@@ -782,7 +782,7 @@ func TestAddMongoDB(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name-for-basic-name")
@@ -884,7 +884,7 @@ func TestRemoveMongoDB(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-by-name")
nodeName := pmmapitests.TestString(t, "node-remove-by-name")
nodeID, pmmAgentID, serviceID := addMongoDB(t, serviceName, nodeName, true)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
removeServiceOK, err := client.Default.Service.RemoveService(&service.RemoveServiceParams{
@@ -915,7 +915,7 @@ func TestRemoveMongoDB(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-by-id")
nodeName := pmmapitests.TestString(t, "node-remove-by-id")
nodeID, pmmAgentID, serviceID := addMongoDB(t, serviceName, nodeName, true)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
removeServiceOK, err := client.Default.Service.RemoveService(&service.RemoveServiceParams{
@@ -946,7 +946,7 @@ func TestRemoveMongoDB(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-both-params")
nodeName := pmmapitests.TestString(t, "node-remove-both-params")
nodeID, pmmAgentID, serviceID := addMongoDB(t, serviceName, nodeName, false)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer pmmapitests.RemoveServices(t, serviceID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
@@ -966,7 +966,7 @@ func TestRemoveMongoDB(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-wrong-type")
nodeName := pmmapitests.TestString(t, "node-remove-wrong-type")
nodeID, pmmAgentID, serviceID := addMongoDB(t, serviceName, nodeName, false)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer pmmapitests.RemoveServices(t, serviceID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
diff --git a/api-tests/management/mysql_test.go b/api-tests/management/mysql_test.go
index a80f8dc3e3..2a38338b86 100644
--- a/api-tests/management/mysql_test.go
+++ b/api-tests/management/mysql_test.go
@@ -40,7 +40,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-basic-name")
@@ -116,7 +116,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-all-fields-name")
@@ -218,7 +218,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-all-fields-name")
@@ -279,7 +279,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-the-same-name")
@@ -327,7 +327,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
nodeNameAddNode := pmmapitests.TestString(t, "node-for-add-node-name")
@@ -448,7 +448,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
remoteNodeOKBody := pmmapitests.AddRemoteNode(t, pmmapitests.TestString(t, "Remote Node for wrong type test"))
@@ -480,7 +480,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
params := &mysql.AddMySQLParams{
@@ -498,7 +498,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -523,7 +523,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -549,7 +549,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -577,7 +577,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -601,7 +601,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -626,7 +626,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-basic-name")
@@ -701,7 +701,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-basic-name")
@@ -775,7 +775,7 @@ func TestAddMySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-basic-name")
@@ -881,7 +881,7 @@ func TestRemoveMySQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-by-name")
nodeName := pmmapitests.TestString(t, "node-remove-by-name")
nodeID, pmmAgentID, serviceID := addMySQL(t, serviceName, nodeName, true)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
removeServiceOK, err := client.Default.Service.RemoveService(&service.RemoveServiceParams{
@@ -912,7 +912,7 @@ func TestRemoveMySQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-by-id")
nodeName := pmmapitests.TestString(t, "node-remove-by-id")
nodeID, pmmAgentID, serviceID := addMySQL(t, serviceName, nodeName, true)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
removeServiceOK, err := client.Default.Service.RemoveService(&service.RemoveServiceParams{
@@ -943,7 +943,7 @@ func TestRemoveMySQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-both-params")
nodeName := pmmapitests.TestString(t, "node-remove-both-params")
nodeID, pmmAgentID, serviceID := addMySQL(t, serviceName, nodeName, false)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer pmmapitests.RemoveServices(t, serviceID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
@@ -963,7 +963,7 @@ func TestRemoveMySQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-wrong-type")
nodeName := pmmapitests.TestString(t, "node-remove-wrong-type")
nodeID, pmmAgentID, serviceID := addMySQL(t, serviceName, nodeName, false)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer pmmapitests.RemoveServices(t, serviceID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
diff --git a/api-tests/management/nodes_test.go b/api-tests/management/nodes_test.go
index 7c417fea83..e9d53c799a 100644
--- a/api-tests/management/nodes_test.go
+++ b/api-tests/management/nodes_test.go
@@ -40,7 +40,7 @@ func TestNodeRegister(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
// Check Node is created
assertNodeCreated(t, nodeID, nodes.GetNodeOKBody{
@@ -58,14 +58,14 @@ func TestNodeRegister(t *testing.T) {
})
t.Run("Reregister with same node name (no re-register - should fail)", func(t *testing.T) {
- nodeName := pmmapitests.TestString(t, "node-name-for-all-fields")
+ nodeName := pmmapitests.TestString(t, "all-fields")
nodeID, pmmAgentID := RegisterGenericNode(t, node.RegisterNodeBody{
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
Address: "node-address-1",
Region: "region-1",
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
body := node.RegisterNodeBody{
@@ -84,7 +84,7 @@ func TestNodeRegister(t *testing.T) {
})
t.Run("Reregister with same node name (re-register)", func(t *testing.T) {
- nodeName := pmmapitests.TestString(t, "node-name-for-all-fields")
+ nodeName := pmmapitests.TestString(t, "all-fields")
nodeID, pmmAgentID := RegisterGenericNode(t, node.RegisterNodeBody{
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
@@ -108,20 +108,21 @@ func TestNodeRegister(t *testing.T) {
node, err := client.Default.Node.RegisterNode(¶ms)
assert.NoError(t, err)
- defer pmmapitests.RemoveNodes(t, node.Payload.GenericNode.NodeID)
+ defer pmmapitests.UnregisterNodes(t, node.Payload.GenericNode.NodeID)
defer RemovePMMAgentWithSubAgents(t, node.Payload.PMMAgent.AgentID)
+
assertNodeExporterCreated(t, node.Payload.PMMAgent.AgentID)
})
t.Run("Reregister with different node name (no re-register - should fail)", func(t *testing.T) {
- nodeName := pmmapitests.TestString(t, "node-name-for-all-fields")
+ nodeName := pmmapitests.TestString(t, "all-fields")
nodeID, pmmAgentID := RegisterGenericNode(t, node.RegisterNodeBody{
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
Address: "node-address-3",
Region: "region-3",
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
body := node.RegisterNodeBody{
@@ -140,16 +141,16 @@ func TestNodeRegister(t *testing.T) {
})
t.Run("Reregister with different node name (re-register)", func(t *testing.T) {
- nodeName := pmmapitests.TestString(t, "node-name-for-all-fields")
+ nodeName := pmmapitests.TestString(t, "all-fields")
nodeID, pmmAgentID := RegisterGenericNode(t, node.RegisterNodeBody{
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
Address: "node-address-4",
Region: "region-4",
})
-
assert.NotEmpty(t, nodeID)
assert.NotEmpty(t, pmmAgentID)
+ pmmapitests.UnregisterNodes(t, nodeID)
body := node.RegisterNodeBody{
NodeName: nodeName + "_new",
@@ -165,7 +166,7 @@ func TestNodeRegister(t *testing.T) {
node, err := client.Default.Node.RegisterNode(¶ms)
assert.NoError(t, err)
- defer pmmapitests.RemoveNodes(t, node.Payload.GenericNode.NodeID)
+ defer pmmapitests.UnregisterNodes(t, node.Payload.GenericNode.NodeID)
_, ok := assertNodeExporterCreated(t, node.Payload.PMMAgent.AgentID)
if ok {
defer RemovePMMAgentWithSubAgents(t, node.Payload.PMMAgent.AgentID)
@@ -189,7 +190,7 @@ func TestNodeRegister(t *testing.T) {
DisableCollectors: []string{"diskstats", "filesystem", "standard.process"},
}
nodeID, pmmAgentID := RegisterGenericNode(t, body)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
// Check Node is created
@@ -232,62 +233,6 @@ func TestNodeRegister(t *testing.T) {
defer pmmapitests.RemoveAgents(t, nodeExporterAgentID)
}
})
-
- t.Run("Re-register", func(t *testing.T) {
- t.Skip("Re-register logic is not defined yet. https://jira.percona.com/browse/PMM-3717")
-
- nodeName := pmmapitests.TestString(t, "node-name")
- nodeID, pmmAgentID := RegisterGenericNode(t, node.RegisterNodeBody{
- NodeName: nodeName,
- NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
- })
- defer pmmapitests.RemoveNodes(t, nodeID)
- defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
-
- // Check Node is created
- assertNodeCreated(t, nodeID, nodes.GetNodeOKBody{
- Generic: &nodes.GetNodeOKBodyGeneric{
- NodeID: nodeID,
- NodeName: nodeName,
- },
- })
-
- // Re-register node
- machineID := pmmapitests.TestString(t, "machine-id")
- nodeModel := pmmapitests.TestString(t, "node-model")
- newNodeID, newPMMAgentID := RegisterGenericNode(t, node.RegisterNodeBody{
- NodeName: nodeName,
- NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
- MachineID: machineID,
- NodeModel: nodeModel,
- Az: "eu",
- Region: "us-west",
- Address: "10.10.10.10",
- Distro: "Linux",
- CustomLabels: map[string]string{"foo": "bar"},
- })
- if !assert.Equal(t, nodeID, newNodeID) {
- defer pmmapitests.RemoveNodes(t, newNodeID)
- }
- if !assert.Equal(t, pmmAgentID, newPMMAgentID) {
- defer pmmapitests.RemoveAgents(t, newPMMAgentID)
- }
-
- // Check Node fields is updated
- assertNodeCreated(t, nodeID, nodes.GetNodeOKBody{
- Generic: &nodes.GetNodeOKBodyGeneric{
- NodeID: nodeID,
- NodeName: nodeName,
- MachineID: machineID,
- NodeModel: nodeModel,
- Az: "eu",
- Region: "us-west",
- Address: "10.10.10.10",
- Distro: "Linux",
- CustomLabels: map[string]string{"foo": "bar"},
- },
- })
- })
})
t.Run("Container Node", func(t *testing.T) {
@@ -297,7 +242,7 @@ func TestNodeRegister(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeCONTAINERNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
// Check Node is created
@@ -335,7 +280,7 @@ func TestNodeRegister(t *testing.T) {
CustomLabels: map[string]string{"foo": "bar"},
}
nodeID, pmmAgentID := registerContainerNode(t, body)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
// Check Node is created
@@ -362,63 +307,6 @@ func TestNodeRegister(t *testing.T) {
defer pmmapitests.RemoveAgents(t, nodeExporterAgentID)
}
})
-
- t.Run("Re-register", func(t *testing.T) {
- t.Skip("Re-register logic is not defined yet. https://jira.percona.com/browse/PMM-3717")
-
- nodeName := pmmapitests.TestString(t, "node-name")
- nodeID, pmmAgentID := registerContainerNode(t, node.RegisterNodeBody{
- NodeName: nodeName,
- NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeCONTAINERNODE),
- })
- defer pmmapitests.RemoveNodes(t, nodeID)
- defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
-
- // Check Node is created
- assertNodeCreated(t, nodeID, nodes.GetNodeOKBody{
- Generic: &nodes.GetNodeOKBodyGeneric{
- NodeID: nodeID,
- NodeName: nodeName,
- },
- })
-
- // Re-register node
- nodeModel := pmmapitests.TestString(t, "node-model")
- containerID := pmmapitests.TestString(t, "container-id")
- containerName := pmmapitests.TestString(t, "container-name")
- newNodeID, newPMMAgentID := registerContainerNode(t, node.RegisterNodeBody{
- NodeName: nodeName,
- NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeCONTAINERNODE),
- ContainerID: containerID,
- ContainerName: containerName,
- NodeModel: nodeModel,
- Az: "eu",
- Region: "us-west",
- Address: "10.10.10.10",
- CustomLabels: map[string]string{"foo": "bar"},
- })
- if !assert.Equal(t, nodeID, newNodeID) {
- defer pmmapitests.RemoveNodes(t, newNodeID)
- }
- if !assert.Equal(t, pmmAgentID, newPMMAgentID) {
- defer pmmapitests.RemoveAgents(t, newPMMAgentID)
- }
-
- // Check Node fields is updated
- assertNodeCreated(t, nodeID, nodes.GetNodeOKBody{
- Container: &nodes.GetNodeOKBodyContainer{
- NodeID: nodeID,
- NodeName: nodeName,
- ContainerID: containerID,
- ContainerName: containerName,
- NodeModel: nodeModel,
- Az: "eu",
- Region: "us-west",
- Address: "10.10.10.10",
- CustomLabels: map[string]string{"foo": "bar"},
- },
- })
- })
})
t.Run("Empty node name", func(t *testing.T) {
diff --git a/api-tests/management/postgresql_test.go b/api-tests/management/postgresql_test.go
index a6ac1b9494..ff46701eee 100644
--- a/api-tests/management/postgresql_test.go
+++ b/api-tests/management/postgresql_test.go
@@ -42,7 +42,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-basic-name")
@@ -122,7 +122,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-all-fields-name")
@@ -227,7 +227,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-all-fields-name")
@@ -284,7 +284,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-the-same-name")
@@ -333,7 +333,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
nodeNameAddNode := pmmapitests.TestString(t, "node-for-add-node-name")
@@ -457,7 +457,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
remoteNodeOKBody := pmmapitests.AddRemoteNode(t, pmmapitests.TestString(t, "Remote Node for wrong type test"))
@@ -489,7 +489,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
params := &postgresql.AddPostgreSQLParams{
@@ -507,7 +507,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -531,7 +531,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -556,7 +556,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -580,7 +580,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -608,7 +608,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-basic-name")
@@ -684,7 +684,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-basic-name")
@@ -759,7 +759,7 @@ func TestAddPostgreSQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-basic-name")
@@ -866,7 +866,7 @@ func TestRemovePostgreSQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-by-name")
nodeName := pmmapitests.TestString(t, "node-remove-by-name")
nodeID, pmmAgentID, serviceID := addPostgreSQL(t, serviceName, nodeName, true)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
removeServiceOK, err := client.Default.Service.RemoveService(&service.RemoveServiceParams{
@@ -897,7 +897,7 @@ func TestRemovePostgreSQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-by-id")
nodeName := pmmapitests.TestString(t, "node-remove-by-id")
nodeID, pmmAgentID, serviceID := addPostgreSQL(t, serviceName, nodeName, true)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
removeServiceOK, err := client.Default.Service.RemoveService(&service.RemoveServiceParams{
@@ -928,7 +928,7 @@ func TestRemovePostgreSQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-both-params")
nodeName := pmmapitests.TestString(t, "node-remove-both-params")
nodeID, pmmAgentID, serviceID := addPostgreSQL(t, serviceName, nodeName, false)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer pmmapitests.RemoveServices(t, serviceID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
@@ -948,7 +948,7 @@ func TestRemovePostgreSQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-wrong-type")
nodeName := pmmapitests.TestString(t, "node-remove-wrong-type")
nodeID, pmmAgentID, serviceID := addPostgreSQL(t, serviceName, nodeName, false)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer pmmapitests.RemoveServices(t, serviceID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
diff --git a/api-tests/management/proxysql_test.go b/api-tests/management/proxysql_test.go
index e7bc9cd594..99437ba7e0 100644
--- a/api-tests/management/proxysql_test.go
+++ b/api-tests/management/proxysql_test.go
@@ -40,7 +40,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-basic-name")
@@ -115,7 +115,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-all-fields-name")
@@ -191,7 +191,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-all-fields-name")
@@ -252,7 +252,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-for-the-same-name")
@@ -300,7 +300,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
nodeNameAddNode := pmmapitests.TestString(t, "node-for-add-node-name")
@@ -420,7 +420,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
remoteNodeOKBody := pmmapitests.AddRemoteNode(t, pmmapitests.TestString(t, "Remote Node for wrong type test"))
@@ -452,7 +452,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
params := &proxysql.AddProxySQLParams{
@@ -470,7 +470,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -494,7 +494,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -519,7 +519,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -547,7 +547,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -571,7 +571,7 @@ func TestAddProxySQL(t *testing.T) {
NodeName: nodeName,
NodeType: pointer.ToString(node.RegisterNodeBodyNodeTypeGENERICNODE),
})
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
serviceName := pmmapitests.TestString(t, "service-name")
@@ -625,7 +625,7 @@ func TestRemoveProxySQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-by-name")
nodeName := pmmapitests.TestString(t, "node-remove-by-name")
nodeID, pmmAgentID, serviceID := addProxySQL(t, serviceName, nodeName)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
removeServiceOK, err := client.Default.Service.RemoveService(&service.RemoveServiceParams{
@@ -656,7 +656,7 @@ func TestRemoveProxySQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-by-id")
nodeName := pmmapitests.TestString(t, "node-remove-by-id")
nodeID, pmmAgentID, serviceID := addProxySQL(t, serviceName, nodeName)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
removeServiceOK, err := client.Default.Service.RemoveService(&service.RemoveServiceParams{
@@ -687,7 +687,7 @@ func TestRemoveProxySQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-both-params")
nodeName := pmmapitests.TestString(t, "node-remove-both-params")
nodeID, pmmAgentID, serviceID := addProxySQL(t, serviceName, nodeName)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer pmmapitests.RemoveServices(t, serviceID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
@@ -707,7 +707,7 @@ func TestRemoveProxySQL(t *testing.T) {
serviceName := pmmapitests.TestString(t, "service-remove-wrong-type")
nodeName := pmmapitests.TestString(t, "node-remove-wrong-type")
nodeID, pmmAgentID, serviceID := addProxySQL(t, serviceName, nodeName)
- defer pmmapitests.RemoveNodes(t, nodeID)
+ defer pmmapitests.UnregisterNodes(t, nodeID)
defer pmmapitests.RemoveServices(t, serviceID)
defer RemovePMMAgentWithSubAgents(t, pmmAgentID)
diff --git a/api-tests/server/auth_test.go b/api-tests/server/auth_test.go
index 979b89062a..a7c17e7c3c 100644
--- a/api-tests/server/auth_test.go
+++ b/api-tests/server/auth_test.go
@@ -38,6 +38,11 @@ import (
"github.com/percona/pmm/api/serverpb/json/client/server"
)
+const (
+ pmmServiceTokenName = "pmm-agent-service-token" //nolint:gosec
+ pmmServiceAccountName = "pmm-agent-service-account" //nolint:gosec
+)
+
func TestAuth(t *testing.T) {
t.Run("AuthErrors", func(t *testing.T) {
for user, code := range map[*url.Userinfo]int{
@@ -240,7 +245,20 @@ func TestSwagger(t *testing.T) {
}
}
-func TestPermissions(t *testing.T) {
+func doRequest(tb testing.TB, client *http.Client, req *http.Request) (*http.Response, []byte) {
+ tb.Helper()
+ resp, err := client.Do(req)
+ require.NoError(tb, err)
+
+ defer resp.Body.Close() //nolint:errcheck
+
+ b, err := io.ReadAll(resp.Body)
+ require.NoError(tb, err)
+
+ return resp, b
+}
+
+func TestBasicAuthPermissions(t *testing.T) {
ts := strconv.FormatInt(time.Now().Unix(), 10)
none := "none-" + ts
viewer := "viewer-" + ts
@@ -259,13 +277,12 @@ func TestPermissions(t *testing.T) {
adminID := createUserWithRole(t, admin, "Admin")
defer deleteUser(t, adminID)
- viewerAPIKeyID, viewerAPIKey := createAPIKeyWithRole(t, "api-"+viewer, "Viewer")
+ const apiPrefix = "api"
+ viewerAPIKeyID, viewerAPIKey := createAPIKeyWithRole(t, fmt.Sprintf("%s-%s", apiPrefix, viewer), "Viewer")
defer deleteAPIKey(t, viewerAPIKeyID)
-
- editorAPIKeyID, editorAPIKey := createAPIKeyWithRole(t, "api-"+editor, "Editor")
+ editorAPIKeyID, editorAPIKey := createAPIKeyWithRole(t, fmt.Sprintf("%s-%s", apiPrefix, editor), "Editor")
defer deleteAPIKey(t, editorAPIKeyID)
-
- adminAPIKeyID, adminAPIKey := createAPIKeyWithRole(t, "api-"+admin, "Admin")
+ adminAPIKeyID, adminAPIKey := createAPIKeyWithRole(t, fmt.Sprintf("%s-%s", apiPrefix, admin), "Admin")
defer deleteAPIKey(t, adminAPIKeyID)
type userCase struct {
@@ -363,19 +380,6 @@ func TestPermissions(t *testing.T) {
}
}
-func doRequest(tb testing.TB, client *http.Client, req *http.Request) (*http.Response, []byte) {
- tb.Helper()
- resp, err := client.Do(req)
- require.NoError(tb, err)
-
- defer resp.Body.Close() //nolint:gosec,errcheck,nolintlint
-
- b, err := io.ReadAll(resp.Body)
- require.NoError(tb, err)
-
- return resp, b
-}
-
func createUserWithRole(t *testing.T, login, role string) int {
t.Helper()
userID := createUser(t, login)
@@ -452,20 +456,99 @@ func setRole(t *testing.T, userID int, role string) {
require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to set role for user, response: %s", b)
}
-func deleteAPIKey(t *testing.T, apiKeyID int) {
- t.Helper()
- // https://grafana.com/docs/grafana/latest/http_api/auth/#delete-api-key
- u, err := url.Parse(pmmapitests.BaseURL.String())
- require.NoError(t, err)
- u.Path = "/graph/api/auth/keys/" + strconv.Itoa(apiKeyID)
+func TestServiceAccountPermissions(t *testing.T) {
+ // service account role options: viewer, editor, admin
+ // service token role options: editor, admin
+ // basic auth format is skipped, endpoint /auth/serviceaccount (to get info about currently used token in request) requires Bearer authorization
+ // service_token:token format could be used in pmm-agent and pmm-admin (its transformed into Bearer authorization)
+ nodeName := "test-node"
+
+ viewerNodeName := fmt.Sprintf("%s-viewer", nodeName)
+ viewerAccountID := createServiceAccountWithRole(t, "Viewer", viewerNodeName)
+ viewerTokenID, viewerToken := createServiceToken(t, viewerAccountID, viewerNodeName)
+ defer deleteServiceAccount(t, viewerAccountID)
+ defer deleteServiceToken(t, viewerAccountID, viewerTokenID)
+
+ editorNodeName := fmt.Sprintf("%s-editor", nodeName)
+ editorAccountID := createServiceAccountWithRole(t, "Editor", editorNodeName)
+ editorTokenID, editorToken := createServiceToken(t, editorAccountID, editorNodeName)
+ defer deleteServiceAccount(t, editorAccountID)
+ defer deleteServiceToken(t, editorAccountID, editorTokenID)
+
+ adminNodeName := fmt.Sprintf("%s-admin", nodeName)
+ adminAccountID := createServiceAccountWithRole(t, "Admin", adminNodeName)
+ adminTokenID, adminToken := createServiceToken(t, adminAccountID, adminNodeName)
+ defer deleteServiceAccount(t, adminAccountID)
+ defer deleteServiceToken(t, adminAccountID, adminTokenID)
- req, err := http.NewRequestWithContext(pmmapitests.Context, http.MethodDelete, u.String(), nil)
- require.NoError(t, err)
+ type userCase struct {
+ userType string
+ serviceToken string
+ statusCode int
+ }
- resp, b := doRequest(t, http.DefaultClient, req)
- defer resp.Body.Close() //nolint:gosec,errcheck,nolintlint
+ tests := []struct {
+ name string
+ url string
+ method string
+ userCase []userCase
+ }{
+ {name: "settings", url: "/v1/Settings/Get", method: "POST", userCase: []userCase{
+ {userType: "default", statusCode: 401},
+ {userType: "viewer", serviceToken: viewerToken, statusCode: 401},
+ {userType: "editor", serviceToken: editorToken, statusCode: 401},
+ {userType: "admin", serviceToken: adminToken, statusCode: 200},
+ }},
+ {name: "platform-connect", url: "/v1/Platform/Connect", method: "POST", userCase: []userCase{
+ {userType: "default", statusCode: 401},
+ {userType: "viewer", serviceToken: viewerToken, statusCode: 401},
+ {userType: "editor", serviceToken: editorToken, statusCode: 401},
+ {userType: "admin", serviceToken: adminToken, statusCode: 400}, // We send bad request, but have access to endpoint
+ }},
+ }
- require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to delete API Key, status code: %d, response: %s", resp.StatusCode, b)
+ for _, test := range tests {
+ test := test
+ t.Run(test.name, func(t *testing.T) {
+ for _, user := range test.userCase {
+ user := user
+ t.Run(fmt.Sprintf("Service Token auth %s", user.userType), func(t *testing.T) {
+ // make a BaseURL without authentication
+ u, err := url.Parse(pmmapitests.BaseURL.String())
+ require.NoError(t, err)
+ u.User = nil
+ u.Path = test.url
+
+ req, err := http.NewRequestWithContext(pmmapitests.Context, test.method, u.String(), nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", user.serviceToken))
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close() //nolint:errcheck
+
+ assert.Equal(t, user.statusCode, resp.StatusCode)
+ })
+
+ t.Run(fmt.Sprintf("Basic auth with Service Token %s", user.userType), func(t *testing.T) {
+ u, err := url.Parse(pmmapitests.BaseURL.String())
+ require.NoError(t, err)
+ u.User = url.UserPassword("service_token", user.serviceToken)
+ u.Path = test.url
+
+ req, err := http.NewRequestWithContext(pmmapitests.Context, test.method, u.String(), nil)
+ require.NoError(t, err)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close() //nolint:errcheck
+
+ assert.Equal(t, user.statusCode, resp.StatusCode)
+ })
+ }
+ })
+ }
}
func createAPIKeyWithRole(t *testing.T, name, role string) (int, string) {
@@ -515,3 +598,125 @@ func createAPIKeyWithRole(t *testing.T, name, role string) (int, string) {
return apiKeyID, apiKey
}
+
+func deleteAPIKey(t *testing.T, apiKeyID int) {
+ t.Helper()
+ // https://grafana.com/docs/grafana/latest/http_api/auth/#delete-api-key
+ u, err := url.Parse(pmmapitests.BaseURL.String())
+ require.NoError(t, err)
+ u.Path = "/graph/api/auth/keys/" + strconv.Itoa(apiKeyID)
+
+ req, err := http.NewRequestWithContext(pmmapitests.Context, http.MethodDelete, u.String(), nil)
+ require.NoError(t, err)
+
+ resp, b := doRequest(t, http.DefaultClient, req)
+ defer resp.Body.Close() //nolint:gosec,errcheck,nolintlint
+
+ require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to delete API Key, status code: %d, response: %s", resp.StatusCode, b)
+}
+
+func createServiceAccountWithRole(t *testing.T, role, nodeName string) int {
+ t.Helper()
+ u, err := url.Parse(pmmapitests.BaseURL.String())
+ require.NoError(t, err)
+ u.Path = "/graph/api/serviceaccounts"
+
+ name := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName)
+ data, err := json.Marshal(map[string]string{
+ "name": name,
+ "role": role,
+ })
+ require.NoError(t, err)
+
+ req, err := http.NewRequestWithContext(pmmapitests.Context, http.MethodPost, u.String(), bytes.NewReader(data))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json; charset=utf-8")
+
+ resp, b := doRequest(t, http.DefaultClient, req)
+ defer resp.Body.Close() //nolint:errcheck
+
+ require.Equalf(t, http.StatusCreated, resp.StatusCode, "failed to create Service account, status code: %d, response: %s", resp.StatusCode, b)
+
+ var m map[string]interface{}
+ err = json.Unmarshal(b, &m)
+ require.NoError(t, err)
+
+ serviceAccountID := int(m["id"].(float64))
+ u.Path = fmt.Sprintf("/graph/api/serviceaccounts/%d", serviceAccountID)
+ data, err = json.Marshal(map[string]string{
+ "orgId": "1",
+ })
+ require.NoError(t, err)
+
+ req, err = http.NewRequestWithContext(pmmapitests.Context, http.MethodPatch, u.String(), bytes.NewReader(data))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json; charset=utf-8")
+
+ resp1, b := doRequest(t, http.DefaultClient, req)
+ defer resp1.Body.Close() //nolint:errcheck
+
+ require.Equalf(t, http.StatusCreated, resp.StatusCode, "failed to set orgId=1 to Service account, status code: %d, response: %s", resp.StatusCode, b)
+
+ return serviceAccountID
+}
+
+func deleteServiceAccount(t *testing.T, serviceAccountID int) {
+ t.Helper()
+ u, err := url.Parse(pmmapitests.BaseURL.String())
+ require.NoError(t, err)
+ u.Path = fmt.Sprintf("/graph/api/serviceaccounts/%d", serviceAccountID)
+
+ req, err := http.NewRequestWithContext(pmmapitests.Context, http.MethodDelete, u.String(), nil)
+ require.NoError(t, err)
+
+ resp, b := doRequest(t, http.DefaultClient, req)
+ defer resp.Body.Close() //nolint:gosec,errcheck,nolintlint
+
+ require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to delete service account, status code: %d, response: %s", resp.StatusCode, b)
+}
+
+func createServiceToken(t *testing.T, serviceAccountID int, nodeName string) (int, string) {
+ t.Helper()
+ u, err := url.Parse(pmmapitests.BaseURL.String())
+ require.NoError(t, err)
+ u.Path = fmt.Sprintf("/graph/api/serviceaccounts/%d/tokens", serviceAccountID)
+
+ name := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName)
+ data, err := json.Marshal(map[string]string{
+ "name": name,
+ })
+ require.NoError(t, err)
+
+ req, err := http.NewRequestWithContext(pmmapitests.Context, http.MethodPost, u.String(), bytes.NewReader(data))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json; charset=utf-8")
+
+ resp, b := doRequest(t, http.DefaultClient, req)
+ defer resp.Body.Close() //nolint:gosec,errcheck,nolintlint
+
+ require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to create Service account, status code: %d, response: %s", resp.StatusCode, b)
+
+ var m map[string]interface{}
+ err = json.Unmarshal(b, &m)
+ require.NoError(t, err)
+
+ return int(m["id"].(float64)), m["key"].(string)
+}
+
+func deleteServiceToken(t *testing.T, serviceAccountID, serviceTokenID int) {
+ t.Helper()
+ u, err := url.Parse(pmmapitests.BaseURL.String())
+ require.NoError(t, err)
+ u.Path = fmt.Sprintf("/graph/api/serviceaccounts/%d/tokens/%d", serviceAccountID, serviceTokenID)
+
+ req, err := http.NewRequestWithContext(pmmapitests.Context, http.MethodDelete, u.String(), nil)
+ require.NoError(t, err)
+
+ resp, b := doRequest(t, http.DefaultClient, req)
+ defer resp.Body.Close() //nolint:errcheck
+
+ require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to delete service token, status code: %d, response: %s", resp.StatusCode, b)
+}
diff --git a/api/managementpb/json/client/node/node_client.go b/api/managementpb/json/client/node/node_client.go
index 49d79d9e66..9a1855222a 100644
--- a/api/managementpb/json/client/node/node_client.go
+++ b/api/managementpb/json/client/node/node_client.go
@@ -30,6 +30,8 @@ type ClientOption func(*runtime.ClientOperation)
type ClientService interface {
RegisterNode(params *RegisterNodeParams, opts ...ClientOption) (*RegisterNodeOK, error)
+ UnregisterNode(params *UnregisterNodeParams, opts ...ClientOption) (*UnregisterNodeOK, error)
+
SetTransport(transport runtime.ClientTransport)
}
@@ -72,6 +74,45 @@ func (a *Client) RegisterNode(params *RegisterNodeParams, opts ...ClientOption)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
+/*
+UnregisterNode unregisters node
+
+Unregister a Node and pmm-agent
+*/
+func (a *Client) UnregisterNode(params *UnregisterNodeParams, opts ...ClientOption) (*UnregisterNodeOK, error) {
+ // TODO: Validate the params before sending
+ if params == nil {
+ params = NewUnregisterNodeParams()
+ }
+ op := &runtime.ClientOperation{
+ ID: "UnregisterNode",
+ Method: "POST",
+ PathPattern: "/v1/management/Node/Unregister",
+ ProducesMediaTypes: []string{"application/json"},
+ ConsumesMediaTypes: []string{"application/json"},
+ Schemes: []string{"http", "https"},
+ Params: params,
+ Reader: &UnregisterNodeReader{formats: a.formats},
+ Context: params.Context,
+ Client: params.HTTPClient,
+ }
+ for _, opt := range opts {
+ opt(op)
+ }
+
+ result, err := a.transport.Submit(op)
+ if err != nil {
+ return nil, err
+ }
+ success, ok := result.(*UnregisterNodeOK)
+ if ok {
+ return success, nil
+ }
+ // unexpected success response
+ unexpectedSuccess := result.(*UnregisterNodeDefault)
+ return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
+}
+
// SetTransport changes the transport on the client
func (a *Client) SetTransport(transport runtime.ClientTransport) {
a.transport = transport
diff --git a/api/managementpb/json/client/node/unregister_node_parameters.go b/api/managementpb/json/client/node/unregister_node_parameters.go
new file mode 100644
index 0000000000..579b04d3a9
--- /dev/null
+++ b/api/managementpb/json/client/node/unregister_node_parameters.go
@@ -0,0 +1,144 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package node
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+ "net/http"
+ "time"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/runtime"
+ cr "github.com/go-openapi/runtime/client"
+ "github.com/go-openapi/strfmt"
+)
+
+// NewUnregisterNodeParams creates a new UnregisterNodeParams object,
+// with the default timeout for this client.
+//
+// Default values are not hydrated, since defaults are normally applied by the API server side.
+//
+// To enforce default values in parameter, use SetDefaults or WithDefaults.
+func NewUnregisterNodeParams() *UnregisterNodeParams {
+ return &UnregisterNodeParams{
+ timeout: cr.DefaultTimeout,
+ }
+}
+
+// NewUnregisterNodeParamsWithTimeout creates a new UnregisterNodeParams object
+// with the ability to set a timeout on a request.
+func NewUnregisterNodeParamsWithTimeout(timeout time.Duration) *UnregisterNodeParams {
+ return &UnregisterNodeParams{
+ timeout: timeout,
+ }
+}
+
+// NewUnregisterNodeParamsWithContext creates a new UnregisterNodeParams object
+// with the ability to set a context for a request.
+func NewUnregisterNodeParamsWithContext(ctx context.Context) *UnregisterNodeParams {
+ return &UnregisterNodeParams{
+ Context: ctx,
+ }
+}
+
+// NewUnregisterNodeParamsWithHTTPClient creates a new UnregisterNodeParams object
+// with the ability to set a custom HTTPClient for a request.
+func NewUnregisterNodeParamsWithHTTPClient(client *http.Client) *UnregisterNodeParams {
+ return &UnregisterNodeParams{
+ HTTPClient: client,
+ }
+}
+
+/*
+UnregisterNodeParams contains all the parameters to send to the API endpoint
+
+ for the unregister node operation.
+
+ Typically these are written to a http.Request.
+*/
+type UnregisterNodeParams struct {
+ // Body.
+ Body UnregisterNodeBody
+
+ timeout time.Duration
+ Context context.Context
+ HTTPClient *http.Client
+}
+
+// WithDefaults hydrates default values in the unregister node params (not the query body).
+//
+// All values with no default are reset to their zero value.
+func (o *UnregisterNodeParams) WithDefaults() *UnregisterNodeParams {
+ o.SetDefaults()
+ return o
+}
+
+// SetDefaults hydrates default values in the unregister node params (not the query body).
+//
+// All values with no default are reset to their zero value.
+func (o *UnregisterNodeParams) SetDefaults() {
+ // no default values defined for this parameter
+}
+
+// WithTimeout adds the timeout to the unregister node params
+func (o *UnregisterNodeParams) WithTimeout(timeout time.Duration) *UnregisterNodeParams {
+ o.SetTimeout(timeout)
+ return o
+}
+
+// SetTimeout adds the timeout to the unregister node params
+func (o *UnregisterNodeParams) SetTimeout(timeout time.Duration) {
+ o.timeout = timeout
+}
+
+// WithContext adds the context to the unregister node params
+func (o *UnregisterNodeParams) WithContext(ctx context.Context) *UnregisterNodeParams {
+ o.SetContext(ctx)
+ return o
+}
+
+// SetContext adds the context to the unregister node params
+func (o *UnregisterNodeParams) SetContext(ctx context.Context) {
+ o.Context = ctx
+}
+
+// WithHTTPClient adds the HTTPClient to the unregister node params
+func (o *UnregisterNodeParams) WithHTTPClient(client *http.Client) *UnregisterNodeParams {
+ o.SetHTTPClient(client)
+ return o
+}
+
+// SetHTTPClient adds the HTTPClient to the unregister node params
+func (o *UnregisterNodeParams) SetHTTPClient(client *http.Client) {
+ o.HTTPClient = client
+}
+
+// WithBody adds the body to the unregister node params
+func (o *UnregisterNodeParams) WithBody(body UnregisterNodeBody) *UnregisterNodeParams {
+ o.SetBody(body)
+ return o
+}
+
+// SetBody adds the body to the unregister node params
+func (o *UnregisterNodeParams) SetBody(body UnregisterNodeBody) {
+ o.Body = body
+}
+
+// WriteToRequest writes these params to a swagger request
+func (o *UnregisterNodeParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
+ if err := r.SetTimeout(o.timeout); err != nil {
+ return err
+ }
+ var res []error
+ if err := r.SetBodyParam(o.Body); err != nil {
+ return err
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
diff --git a/api/managementpb/json/client/node/unregister_node_responses.go b/api/managementpb/json/client/node/unregister_node_responses.go
new file mode 100644
index 0000000000..f2bd01755a
--- /dev/null
+++ b/api/managementpb/json/client/node/unregister_node_responses.go
@@ -0,0 +1,337 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package node
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "strconv"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/runtime"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+)
+
+// UnregisterNodeReader is a Reader for the UnregisterNode structure.
+type UnregisterNodeReader struct {
+ formats strfmt.Registry
+}
+
+// ReadResponse reads a server response into the received o.
+func (o *UnregisterNodeReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
+ switch response.Code() {
+ case 200:
+ result := NewUnregisterNodeOK()
+ if err := result.readResponse(response, consumer, o.formats); err != nil {
+ return nil, err
+ }
+ return result, nil
+ default:
+ result := NewUnregisterNodeDefault(response.Code())
+ if err := result.readResponse(response, consumer, o.formats); err != nil {
+ return nil, err
+ }
+ if response.Code()/100 == 2 {
+ return result, nil
+ }
+ return nil, result
+ }
+}
+
+// NewUnregisterNodeOK creates a UnregisterNodeOK with default headers values
+func NewUnregisterNodeOK() *UnregisterNodeOK {
+ return &UnregisterNodeOK{}
+}
+
+/*
+UnregisterNodeOK describes a response with status code 200, with default header values.
+
+A successful response.
+*/
+type UnregisterNodeOK struct {
+ Payload *UnregisterNodeOKBody
+}
+
+func (o *UnregisterNodeOK) Error() string {
+ return fmt.Sprintf("[POST /v1/management/Node/Unregister][%d] unregisterNodeOk %+v", 200, o.Payload)
+}
+
+func (o *UnregisterNodeOK) GetPayload() *UnregisterNodeOKBody {
+ return o.Payload
+}
+
+func (o *UnregisterNodeOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
+ o.Payload = new(UnregisterNodeOKBody)
+
+ // response payload
+ if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
+ return err
+ }
+
+ return nil
+}
+
+// NewUnregisterNodeDefault creates a UnregisterNodeDefault with default headers values
+func NewUnregisterNodeDefault(code int) *UnregisterNodeDefault {
+ return &UnregisterNodeDefault{
+ _statusCode: code,
+ }
+}
+
+/*
+UnregisterNodeDefault describes a response with status code -1, with default header values.
+
+An unexpected error response.
+*/
+type UnregisterNodeDefault struct {
+ _statusCode int
+
+ Payload *UnregisterNodeDefaultBody
+}
+
+// Code gets the status code for the unregister node default response
+func (o *UnregisterNodeDefault) Code() int {
+ return o._statusCode
+}
+
+func (o *UnregisterNodeDefault) Error() string {
+ return fmt.Sprintf("[POST /v1/management/Node/Unregister][%d] UnregisterNode default %+v", o._statusCode, o.Payload)
+}
+
+func (o *UnregisterNodeDefault) GetPayload() *UnregisterNodeDefaultBody {
+ return o.Payload
+}
+
+func (o *UnregisterNodeDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
+ o.Payload = new(UnregisterNodeDefaultBody)
+
+ // response payload
+ if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
+ return err
+ }
+
+ return nil
+}
+
+/*
+UnregisterNodeBody unregister node body
+swagger:model UnregisterNodeBody
+*/
+type UnregisterNodeBody struct {
+ // Node_id to be unregistered.
+ NodeID string `json:"node_id,omitempty"`
+
+ // Force delete node, related service account, even if it has more service tokens attached.
+ Force bool `json:"force,omitempty"`
+}
+
+// Validate validates this unregister node body
+func (o *UnregisterNodeBody) Validate(formats strfmt.Registry) error {
+ return nil
+}
+
+// ContextValidate validates this unregister node body based on context it is used
+func (o *UnregisterNodeBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (o *UnregisterNodeBody) MarshalBinary() ([]byte, error) {
+ if o == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(o)
+}
+
+// UnmarshalBinary interface implementation
+func (o *UnregisterNodeBody) UnmarshalBinary(b []byte) error {
+ var res UnregisterNodeBody
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *o = res
+ return nil
+}
+
+/*
+UnregisterNodeDefaultBody unregister node default body
+swagger:model UnregisterNodeDefaultBody
+*/
+type UnregisterNodeDefaultBody struct {
+ // code
+ Code int32 `json:"code,omitempty"`
+
+ // message
+ Message string `json:"message,omitempty"`
+
+ // details
+ Details []*UnregisterNodeDefaultBodyDetailsItems0 `json:"details"`
+}
+
+// Validate validates this unregister node default body
+func (o *UnregisterNodeDefaultBody) Validate(formats strfmt.Registry) error {
+ var res []error
+
+ if err := o.validateDetails(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (o *UnregisterNodeDefaultBody) validateDetails(formats strfmt.Registry) error {
+ if swag.IsZero(o.Details) { // not required
+ return nil
+ }
+
+ for i := 0; i < len(o.Details); i++ {
+ if swag.IsZero(o.Details[i]) { // not required
+ continue
+ }
+
+ if o.Details[i] != nil {
+ if err := o.Details[i].Validate(formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("UnregisterNode default" + "." + "details" + "." + strconv.Itoa(i))
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("UnregisterNode default" + "." + "details" + "." + strconv.Itoa(i))
+ }
+ return err
+ }
+ }
+
+ }
+
+ return nil
+}
+
+// ContextValidate validate this unregister node default body based on the context it is used
+func (o *UnregisterNodeDefaultBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ var res []error
+
+ if err := o.contextValidateDetails(ctx, formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (o *UnregisterNodeDefaultBody) contextValidateDetails(ctx context.Context, formats strfmt.Registry) error {
+ for i := 0; i < len(o.Details); i++ {
+ if o.Details[i] != nil {
+ if err := o.Details[i].ContextValidate(ctx, formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("UnregisterNode default" + "." + "details" + "." + strconv.Itoa(i))
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("UnregisterNode default" + "." + "details" + "." + strconv.Itoa(i))
+ }
+ return err
+ }
+ }
+ }
+
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (o *UnregisterNodeDefaultBody) MarshalBinary() ([]byte, error) {
+ if o == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(o)
+}
+
+// UnmarshalBinary interface implementation
+func (o *UnregisterNodeDefaultBody) UnmarshalBinary(b []byte) error {
+ var res UnregisterNodeDefaultBody
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *o = res
+ return nil
+}
+
+/*
+UnregisterNodeDefaultBodyDetailsItems0 unregister node default body details items0
+swagger:model UnregisterNodeDefaultBodyDetailsItems0
+*/
+type UnregisterNodeDefaultBodyDetailsItems0 struct {
+ // at type
+ AtType string `json:"@type,omitempty"`
+}
+
+// Validate validates this unregister node default body details items0
+func (o *UnregisterNodeDefaultBodyDetailsItems0) Validate(formats strfmt.Registry) error {
+ return nil
+}
+
+// ContextValidate validates this unregister node default body details items0 based on context it is used
+func (o *UnregisterNodeDefaultBodyDetailsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (o *UnregisterNodeDefaultBodyDetailsItems0) MarshalBinary() ([]byte, error) {
+ if o == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(o)
+}
+
+// UnmarshalBinary interface implementation
+func (o *UnregisterNodeDefaultBodyDetailsItems0) UnmarshalBinary(b []byte) error {
+ var res UnregisterNodeDefaultBodyDetailsItems0
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *o = res
+ return nil
+}
+
+/*
+UnregisterNodeOKBody unregister node OK body
+swagger:model UnregisterNodeOKBody
+*/
+type UnregisterNodeOKBody struct {
+ // Warning message if there are more service tokens attached to service account.
+ Warning string `json:"warning,omitempty"`
+}
+
+// Validate validates this unregister node OK body
+func (o *UnregisterNodeOKBody) Validate(formats strfmt.Registry) error {
+ return nil
+}
+
+// ContextValidate validates this unregister node OK body based on context it is used
+func (o *UnregisterNodeOKBody) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (o *UnregisterNodeOKBody) MarshalBinary() ([]byte, error) {
+ if o == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(o)
+}
+
+// UnmarshalBinary interface implementation
+func (o *UnregisterNodeOKBody) UnmarshalBinary(b []byte) error {
+ var res UnregisterNodeOKBody
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *o = res
+ return nil
+}
diff --git a/api/managementpb/json/managementpb.json b/api/managementpb/json/managementpb.json
index fe14bc61d0..a0fc1f8a8e 100644
--- a/api/managementpb/json/managementpb.json
+++ b/api/managementpb/json/managementpb.json
@@ -3967,6 +3967,84 @@
}
}
},
+ "/v1/management/Node/Unregister": {
+ "post": {
+ "description": "Unregister a Node and pmm-agent",
+ "tags": [
+ "Node"
+ ],
+ "summary": "Unregister Node",
+ "operationId": "UnregisterNode",
+ "parameters": [
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "type": "object",
+ "properties": {
+ "force": {
+ "description": "Force delete node, related service account, even if it has more service tokens attached.",
+ "type": "boolean",
+ "x-order": 1
+ },
+ "node_id": {
+ "description": "Node_id to be unregistered.",
+ "type": "string",
+ "x-order": 0
+ }
+ }
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "A successful response.",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "warning": {
+ "description": "Warning message if there are more service tokens attached to service account.",
+ "type": "string",
+ "x-order": 0
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "integer",
+ "format": "int32",
+ "x-order": 0
+ },
+ "details": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@type": {
+ "type": "string",
+ "x-order": 0
+ }
+ },
+ "additionalProperties": false
+ },
+ "x-order": 2
+ },
+ "message": {
+ "type": "string",
+ "x-order": 1
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/v1/management/PostgreSQL/Add": {
"post": {
"description": "Adds PostgreSQL Service and starts postgres exporter. It automatically adds a service to inventory, which is running on provided \"node_id\", then adds \"postgres_exporter\" with provided \"pmm_agent_id\" and other parameters.",
diff --git a/api/managementpb/node.pb.go b/api/managementpb/node.pb.go
index f49a1fb596..b8328a867f 100644
--- a/api/managementpb/node.pb.go
+++ b/api/managementpb/node.pb.go
@@ -292,6 +292,111 @@ func (x *RegisterNodeResponse) GetWarning() string {
return ""
}
+type UnregisterNodeRequest struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // Node_id to be unregistered.
+ NodeId string `protobuf:"bytes,1,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"`
+ // Force delete node, related service account, even if it has more service tokens attached.
+ Force bool `protobuf:"varint,2,opt,name=force,proto3" json:"force,omitempty"`
+}
+
+func (x *UnregisterNodeRequest) Reset() {
+ *x = UnregisterNodeRequest{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_managementpb_node_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *UnregisterNodeRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UnregisterNodeRequest) ProtoMessage() {}
+
+func (x *UnregisterNodeRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_managementpb_node_proto_msgTypes[2]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UnregisterNodeRequest.ProtoReflect.Descriptor instead.
+func (*UnregisterNodeRequest) Descriptor() ([]byte, []int) {
+ return file_managementpb_node_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *UnregisterNodeRequest) GetNodeId() string {
+ if x != nil {
+ return x.NodeId
+ }
+ return ""
+}
+
+func (x *UnregisterNodeRequest) GetForce() bool {
+ if x != nil {
+ return x.Force
+ }
+ return false
+}
+
+type UnregisterNodeResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // Warning message if there are more service tokens attached to service account.
+ Warning string `protobuf:"bytes,1,opt,name=warning,proto3" json:"warning,omitempty"`
+}
+
+func (x *UnregisterNodeResponse) Reset() {
+ *x = UnregisterNodeResponse{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_managementpb_node_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *UnregisterNodeResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UnregisterNodeResponse) ProtoMessage() {}
+
+func (x *UnregisterNodeResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_managementpb_node_proto_msgTypes[3]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use UnregisterNodeResponse.ProtoReflect.Descriptor instead.
+func (*UnregisterNodeResponse) Descriptor() ([]byte, []int) {
+ return file_managementpb_node_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *UnregisterNodeResponse) GetWarning() string {
+ if x != nil {
+ return x.Warning
+ }
+ return ""
+}
+
var File_managementpb_node_proto protoreflect.FileDescriptor
var file_managementpb_node_proto_rawDesc = []byte{
@@ -368,28 +473,48 @@ var file_managementpb_node_proto_rawDesc = []byte{
0x70, 0x6d, 0x6d, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65,
0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x18,
0x0a, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
- 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x32, 0xba, 0x01, 0x0a, 0x04, 0x4e, 0x6f, 0x64,
- 0x65, 0x12, 0xb1, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f,
- 0x64, 0x65, 0x12, 0x1f, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
- 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75,
- 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74,
- 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73,
- 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5e, 0x92, 0x41, 0x34, 0x12, 0x0d, 0x52, 0x65, 0x67, 0x69,
- 0x73, 0x74, 0x65, 0x72, 0x20, 0x4e, 0x6f, 0x64, 0x65, 0x1a, 0x23, 0x52, 0x65, 0x67, 0x69, 0x73,
- 0x74, 0x65, 0x72, 0x73, 0x20, 0x61, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x4e, 0x6f, 0x64, 0x65, 0x20,
- 0x61, 0x6e, 0x64, 0x20, 0x70, 0x6d, 0x6d, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x82, 0xd3,
- 0xe4, 0x93, 0x02, 0x21, 0x3a, 0x01, 0x2a, 0x22, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x6e,
- 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x4e, 0x6f, 0x64, 0x65, 0x2f, 0x52, 0x65, 0x67,
- 0x69, 0x73, 0x74, 0x65, 0x72, 0x42, 0x8c, 0x01, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x61,
- 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x09, 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x72,
- 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
- 0x6d, 0x2f, 0x70, 0x65, 0x72, 0x63, 0x6f, 0x6e, 0x61, 0x2f, 0x70, 0x6d, 0x6d, 0x2f, 0x61, 0x70,
- 0x69, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x70, 0x62, 0xa2, 0x02,
- 0x03, 0x4d, 0x58, 0x58, 0xaa, 0x02, 0x0a, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
- 0x74, 0xca, 0x02, 0x0a, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0xe2, 0x02,
- 0x16, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5c, 0x47, 0x50, 0x42, 0x4d,
- 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0a, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65,
- 0x6d, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x22, 0x46, 0x0a, 0x15, 0x55, 0x6e, 0x72, 0x65,
+ 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+ 0x74, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6f,
+ 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x66, 0x6f, 0x72, 0x63, 0x65,
+ 0x22, 0x32, 0x0a, 0x16, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f,
+ 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x77, 0x61,
+ 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x77, 0x61, 0x72,
+ 0x6e, 0x69, 0x6e, 0x67, 0x32, 0xf4, 0x02, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0xb1, 0x01,
+ 0x0a, 0x0c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x1f,
+ 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x67, 0x69,
+ 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+ 0x20, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x67,
+ 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+ 0x65, 0x22, 0x5e, 0x92, 0x41, 0x34, 0x12, 0x0d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
+ 0x20, 0x4e, 0x6f, 0x64, 0x65, 0x1a, 0x23, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x73,
+ 0x20, 0x61, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20,
+ 0x70, 0x6d, 0x6d, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x21,
+ 0x3a, 0x01, 0x2a, 0x22, 0x1c, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d,
+ 0x65, 0x6e, 0x74, 0x2f, 0x4e, 0x6f, 0x64, 0x65, 0x2f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
+ 0x72, 0x12, 0xb7, 0x01, 0x0a, 0x0e, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72,
+ 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x21, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
+ 0x74, 0x2e, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x6f, 0x64, 0x65,
+ 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65,
+ 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4e,
+ 0x6f, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5e, 0x92, 0x41, 0x32,
+ 0x12, 0x0f, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4e, 0x6f, 0x64,
+ 0x65, 0x1a, 0x1f, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x20, 0x61, 0x20,
+ 0x4e, 0x6f, 0x64, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, 0x6d, 0x6d, 0x2d, 0x61, 0x67, 0x65,
+ 0x6e, 0x74, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x3a, 0x01, 0x2a, 0x22, 0x1e, 0x2f, 0x76, 0x31,
+ 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x4e, 0x6f, 0x64, 0x65,
+ 0x2f, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x42, 0x8c, 0x01, 0x0a, 0x0e,
+ 0x63, 0x6f, 0x6d, 0x2e, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x09,
+ 0x4e, 0x6f, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x27, 0x67, 0x69, 0x74,
+ 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x65, 0x72, 0x63, 0x6f, 0x6e, 0x61, 0x2f,
+ 0x70, 0x6d, 0x6d, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65,
+ 0x6e, 0x74, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x4d, 0x58, 0x58, 0xaa, 0x02, 0x0a, 0x4d, 0x61, 0x6e,
+ 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0xca, 0x02, 0x0a, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65,
+ 0x6d, 0x65, 0x6e, 0x74, 0xe2, 0x02, 0x16, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e,
+ 0x74, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0a,
+ 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x33,
}
var (
@@ -405,30 +530,34 @@ func file_managementpb_node_proto_rawDescGZIP() []byte {
}
var (
- file_managementpb_node_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+ file_managementpb_node_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
file_managementpb_node_proto_goTypes = []interface{}{
(*RegisterNodeRequest)(nil), // 0: management.RegisterNodeRequest
(*RegisterNodeResponse)(nil), // 1: management.RegisterNodeResponse
- nil, // 2: management.RegisterNodeRequest.CustomLabelsEntry
- (inventorypb.NodeType)(0), // 3: inventory.NodeType
- (MetricsMode)(0), // 4: management.MetricsMode
- (*inventorypb.GenericNode)(nil), // 5: inventory.GenericNode
- (*inventorypb.ContainerNode)(nil), // 6: inventory.ContainerNode
- (*inventorypb.PMMAgent)(nil), // 7: inventory.PMMAgent
+ (*UnregisterNodeRequest)(nil), // 2: management.UnregisterNodeRequest
+ (*UnregisterNodeResponse)(nil), // 3: management.UnregisterNodeResponse
+ nil, // 4: management.RegisterNodeRequest.CustomLabelsEntry
+ (inventorypb.NodeType)(0), // 5: inventory.NodeType
+ (MetricsMode)(0), // 6: management.MetricsMode
+ (*inventorypb.GenericNode)(nil), // 7: inventory.GenericNode
+ (*inventorypb.ContainerNode)(nil), // 8: inventory.ContainerNode
+ (*inventorypb.PMMAgent)(nil), // 9: inventory.PMMAgent
}
)
var file_managementpb_node_proto_depIdxs = []int32{
- 3, // 0: management.RegisterNodeRequest.node_type:type_name -> inventory.NodeType
- 2, // 1: management.RegisterNodeRequest.custom_labels:type_name -> management.RegisterNodeRequest.CustomLabelsEntry
- 4, // 2: management.RegisterNodeRequest.metrics_mode:type_name -> management.MetricsMode
- 5, // 3: management.RegisterNodeResponse.generic_node:type_name -> inventory.GenericNode
- 6, // 4: management.RegisterNodeResponse.container_node:type_name -> inventory.ContainerNode
- 7, // 5: management.RegisterNodeResponse.pmm_agent:type_name -> inventory.PMMAgent
+ 5, // 0: management.RegisterNodeRequest.node_type:type_name -> inventory.NodeType
+ 4, // 1: management.RegisterNodeRequest.custom_labels:type_name -> management.RegisterNodeRequest.CustomLabelsEntry
+ 6, // 2: management.RegisterNodeRequest.metrics_mode:type_name -> management.MetricsMode
+ 7, // 3: management.RegisterNodeResponse.generic_node:type_name -> inventory.GenericNode
+ 8, // 4: management.RegisterNodeResponse.container_node:type_name -> inventory.ContainerNode
+ 9, // 5: management.RegisterNodeResponse.pmm_agent:type_name -> inventory.PMMAgent
0, // 6: management.Node.RegisterNode:input_type -> management.RegisterNodeRequest
- 1, // 7: management.Node.RegisterNode:output_type -> management.RegisterNodeResponse
- 7, // [7:8] is the sub-list for method output_type
- 6, // [6:7] is the sub-list for method input_type
+ 2, // 7: management.Node.UnregisterNode:input_type -> management.UnregisterNodeRequest
+ 1, // 8: management.Node.RegisterNode:output_type -> management.RegisterNodeResponse
+ 3, // 9: management.Node.UnregisterNode:output_type -> management.UnregisterNodeResponse
+ 8, // [8:10] is the sub-list for method output_type
+ 6, // [6:8] is the sub-list for method input_type
6, // [6:6] is the sub-list for extension type_name
6, // [6:6] is the sub-list for extension extendee
0, // [0:6] is the sub-list for field type_name
@@ -465,6 +594,30 @@ func file_managementpb_node_proto_init() {
return nil
}
}
+ file_managementpb_node_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*UnregisterNodeRequest); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_managementpb_node_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*UnregisterNodeResponse); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
}
type x struct{}
out := protoimpl.TypeBuilder{
@@ -472,7 +625,7 @@ func file_managementpb_node_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_managementpb_node_proto_rawDesc,
NumEnums: 0,
- NumMessages: 3,
+ NumMessages: 5,
NumExtensions: 0,
NumServices: 1,
},
diff --git a/api/managementpb/node.pb.gw.go b/api/managementpb/node.pb.gw.go
index e6c176b244..450d756630 100644
--- a/api/managementpb/node.pb.gw.go
+++ b/api/managementpb/node.pb.gw.go
@@ -57,6 +57,30 @@ func local_request_Node_RegisterNode_0(ctx context.Context, marshaler runtime.Ma
return msg, metadata, err
}
+func request_Node_UnregisterNode_0(ctx context.Context, marshaler runtime.Marshaler, client NodeClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq UnregisterNodeRequest
+ var metadata runtime.ServerMetadata
+
+ if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+ }
+
+ msg, err := client.UnregisterNode(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+ return msg, metadata, err
+}
+
+func local_request_Node_UnregisterNode_0(ctx context.Context, marshaler runtime.Marshaler, server NodeServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+ var protoReq UnregisterNodeRequest
+ var metadata runtime.ServerMetadata
+
+ if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
+ return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+ }
+
+ msg, err := server.UnregisterNode(ctx, &protoReq)
+ return msg, metadata, err
+}
+
// RegisterNodeHandlerServer registers the http handlers for service Node to "mux".
// UnaryRPC :call NodeServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@@ -86,6 +110,30 @@ func RegisterNodeHandlerServer(ctx context.Context, mux *runtime.ServeMux, serve
forward_Node_RegisterNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
+ mux.Handle("POST", pattern_Node_UnregisterNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+ ctx, cancel := context.WithCancel(req.Context())
+ defer cancel()
+ var stream runtime.ServerTransportStream
+ ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
+ inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+ var err error
+ var annotatedContext context.Context
+ annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/management.Node/UnregisterNode", runtime.WithHTTPPathPattern("/v1/management/Node/Unregister"))
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := local_request_Node_UnregisterNode_0(annotatedContext, inboundMarshaler, server, req, pathParams)
+ md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
+ annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
+ if err != nil {
+ runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
+ return
+ }
+
+ forward_Node_UnregisterNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+ })
+
return nil
}
@@ -147,9 +195,38 @@ func RegisterNodeHandlerClient(ctx context.Context, mux *runtime.ServeMux, clien
forward_Node_RegisterNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
+ mux.Handle("POST", pattern_Node_UnregisterNode_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+ ctx, cancel := context.WithCancel(req.Context())
+ defer cancel()
+ inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+ var err error
+ var annotatedContext context.Context
+ annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/management.Node/UnregisterNode", runtime.WithHTTPPathPattern("/v1/management/Node/Unregister"))
+ if err != nil {
+ runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+ return
+ }
+ resp, md, err := request_Node_UnregisterNode_0(annotatedContext, inboundMarshaler, client, req, pathParams)
+ annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
+ if err != nil {
+ runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
+ return
+ }
+
+ forward_Node_UnregisterNode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+ })
+
return nil
}
-var pattern_Node_RegisterNode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "management", "Node", "Register"}, ""))
+var (
+ pattern_Node_RegisterNode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "management", "Node", "Register"}, ""))
+
+ pattern_Node_UnregisterNode_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v1", "management", "Node", "Unregister"}, ""))
+)
-var forward_Node_RegisterNode_0 = runtime.ForwardResponseMessage
+var (
+ forward_Node_RegisterNode_0 = runtime.ForwardResponseMessage
+
+ forward_Node_UnregisterNode_0 = runtime.ForwardResponseMessage
+)
diff --git a/api/managementpb/node.pb.validate.go b/api/managementpb/node.pb.validate.go
index 0a0b03de4f..4adf81e156 100644
--- a/api/managementpb/node.pb.validate.go
+++ b/api/managementpb/node.pb.validate.go
@@ -372,3 +372,213 @@ var _ interface {
Cause() error
ErrorName() string
} = RegisterNodeResponseValidationError{}
+
+// Validate checks the field values on UnregisterNodeRequest with the rules
+// defined in the proto definition for this message. If any rules are
+// violated, the first error encountered is returned, or nil if there are no violations.
+func (m *UnregisterNodeRequest) Validate() error {
+ return m.validate(false)
+}
+
+// ValidateAll checks the field values on UnregisterNodeRequest with the rules
+// defined in the proto definition for this message. If any rules are
+// violated, the result is a list of violation errors wrapped in
+// UnregisterNodeRequestMultiError, or nil if none found.
+func (m *UnregisterNodeRequest) ValidateAll() error {
+ return m.validate(true)
+}
+
+func (m *UnregisterNodeRequest) validate(all bool) error {
+ if m == nil {
+ return nil
+ }
+
+ var errors []error
+
+ // no validation rules for NodeId
+
+ // no validation rules for Force
+
+ if len(errors) > 0 {
+ return UnregisterNodeRequestMultiError(errors)
+ }
+
+ return nil
+}
+
+// UnregisterNodeRequestMultiError is an error wrapping multiple validation
+// errors returned by UnregisterNodeRequest.ValidateAll() if the designated
+// constraints aren't met.
+type UnregisterNodeRequestMultiError []error
+
+// Error returns a concatenation of all the error messages it wraps.
+func (m UnregisterNodeRequestMultiError) Error() string {
+ var msgs []string
+ for _, err := range m {
+ msgs = append(msgs, err.Error())
+ }
+ return strings.Join(msgs, "; ")
+}
+
+// AllErrors returns a list of validation violation errors.
+func (m UnregisterNodeRequestMultiError) AllErrors() []error { return m }
+
+// UnregisterNodeRequestValidationError is the validation error returned by
+// UnregisterNodeRequest.Validate if the designated constraints aren't met.
+type UnregisterNodeRequestValidationError struct {
+ field string
+ reason string
+ cause error
+ key bool
+}
+
+// Field function returns field value.
+func (e UnregisterNodeRequestValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e UnregisterNodeRequestValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e UnregisterNodeRequestValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e UnregisterNodeRequestValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e UnregisterNodeRequestValidationError) ErrorName() string {
+ return "UnregisterNodeRequestValidationError"
+}
+
+// Error satisfies the builtin error interface
+func (e UnregisterNodeRequestValidationError) Error() string {
+ cause := ""
+ if e.cause != nil {
+ cause = fmt.Sprintf(" | caused by: %v", e.cause)
+ }
+
+ key := ""
+ if e.key {
+ key = "key for "
+ }
+
+ return fmt.Sprintf(
+ "invalid %sUnregisterNodeRequest.%s: %s%s",
+ key,
+ e.field,
+ e.reason,
+ cause)
+}
+
+var _ error = UnregisterNodeRequestValidationError{}
+
+var _ interface {
+ Field() string
+ Reason() string
+ Key() bool
+ Cause() error
+ ErrorName() string
+} = UnregisterNodeRequestValidationError{}
+
+// Validate checks the field values on UnregisterNodeResponse with the rules
+// defined in the proto definition for this message. If any rules are
+// violated, the first error encountered is returned, or nil if there are no violations.
+func (m *UnregisterNodeResponse) Validate() error {
+ return m.validate(false)
+}
+
+// ValidateAll checks the field values on UnregisterNodeResponse with the rules
+// defined in the proto definition for this message. If any rules are
+// violated, the result is a list of violation errors wrapped in
+// UnregisterNodeResponseMultiError, or nil if none found.
+func (m *UnregisterNodeResponse) ValidateAll() error {
+ return m.validate(true)
+}
+
+func (m *UnregisterNodeResponse) validate(all bool) error {
+ if m == nil {
+ return nil
+ }
+
+ var errors []error
+
+ // no validation rules for Warning
+
+ if len(errors) > 0 {
+ return UnregisterNodeResponseMultiError(errors)
+ }
+
+ return nil
+}
+
+// UnregisterNodeResponseMultiError is an error wrapping multiple validation
+// errors returned by UnregisterNodeResponse.ValidateAll() if the designated
+// constraints aren't met.
+type UnregisterNodeResponseMultiError []error
+
+// Error returns a concatenation of all the error messages it wraps.
+func (m UnregisterNodeResponseMultiError) Error() string {
+ var msgs []string
+ for _, err := range m {
+ msgs = append(msgs, err.Error())
+ }
+ return strings.Join(msgs, "; ")
+}
+
+// AllErrors returns a list of validation violation errors.
+func (m UnregisterNodeResponseMultiError) AllErrors() []error { return m }
+
+// UnregisterNodeResponseValidationError is the validation error returned by
+// UnregisterNodeResponse.Validate if the designated constraints aren't met.
+type UnregisterNodeResponseValidationError struct {
+ field string
+ reason string
+ cause error
+ key bool
+}
+
+// Field function returns field value.
+func (e UnregisterNodeResponseValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e UnregisterNodeResponseValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e UnregisterNodeResponseValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e UnregisterNodeResponseValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e UnregisterNodeResponseValidationError) ErrorName() string {
+ return "UnregisterNodeResponseValidationError"
+}
+
+// Error satisfies the builtin error interface
+func (e UnregisterNodeResponseValidationError) Error() string {
+ cause := ""
+ if e.cause != nil {
+ cause = fmt.Sprintf(" | caused by: %v", e.cause)
+ }
+
+ key := ""
+ if e.key {
+ key = "key for "
+ }
+
+ return fmt.Sprintf(
+ "invalid %sUnregisterNodeResponse.%s: %s%s",
+ key,
+ e.field,
+ e.reason,
+ cause)
+}
+
+var _ error = UnregisterNodeResponseValidationError{}
+
+var _ interface {
+ Field() string
+ Reason() string
+ Key() bool
+ Cause() error
+ ErrorName() string
+} = UnregisterNodeResponseValidationError{}
diff --git a/api/managementpb/node.proto b/api/managementpb/node.proto
index aa1fe7c53d..b9e88e7ccc 100644
--- a/api/managementpb/node.proto
+++ b/api/managementpb/node.proto
@@ -58,9 +58,21 @@ message RegisterNodeResponse {
string warning = 5;
}
+message UnregisterNodeRequest {
+ // Node_id to be unregistered.
+ string node_id = 1;
+ // Force delete node, related service account, even if it has more service tokens attached.
+ bool force = 2;
+}
+
+message UnregisterNodeResponse {
+ // Warning message if there are more service tokens attached to service account.
+ string warning = 1;
+}
+
// Node service provides public Management API methods for Nodes.
service Node {
- // RegisterNode registers a new Node and pmm-agent.
+ // RegisterNode registers a new Node, pmm-agent and create service account and it's token.
rpc RegisterNode(RegisterNodeRequest) returns (RegisterNodeResponse) {
option (google.api.http) = {
post: "/v1/management/Node/Register"
@@ -71,4 +83,15 @@ service Node {
description: "Registers a new Node and pmm-agent."
};
}
+ // UnregisterNode unregister a Node, pmm-agent and remove service account and it's token.
+ rpc UnregisterNode(UnregisterNodeRequest) returns (UnregisterNodeResponse) {
+ option (google.api.http) = {
+ post: "/v1/management/Node/Unregister"
+ body: "*"
+ };
+ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+ summary: "Unregister Node"
+ description: "Unregister a Node and pmm-agent"
+ };
+ }
}
diff --git a/api/managementpb/node_grpc.pb.go b/api/managementpb/node_grpc.pb.go
index ee9850d2aa..6863519d80 100644
--- a/api/managementpb/node_grpc.pb.go
+++ b/api/managementpb/node_grpc.pb.go
@@ -20,15 +20,18 @@ import (
const _ = grpc.SupportPackageIsVersion7
const (
- Node_RegisterNode_FullMethodName = "/management.Node/RegisterNode"
+ Node_RegisterNode_FullMethodName = "/management.Node/RegisterNode"
+ Node_UnregisterNode_FullMethodName = "/management.Node/UnregisterNode"
)
// NodeClient is the client API for Node service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type NodeClient interface {
- // RegisterNode registers a new Node and pmm-agent.
+ // RegisterNode registers a new Node, pmm-agent and create service account and it's token.
RegisterNode(ctx context.Context, in *RegisterNodeRequest, opts ...grpc.CallOption) (*RegisterNodeResponse, error)
+ // UnregisterNode unregister a Node, pmm-agent and remove service account and it's token.
+ UnregisterNode(ctx context.Context, in *UnregisterNodeRequest, opts ...grpc.CallOption) (*UnregisterNodeResponse, error)
}
type nodeClient struct {
@@ -48,12 +51,23 @@ func (c *nodeClient) RegisterNode(ctx context.Context, in *RegisterNodeRequest,
return out, nil
}
+func (c *nodeClient) UnregisterNode(ctx context.Context, in *UnregisterNodeRequest, opts ...grpc.CallOption) (*UnregisterNodeResponse, error) {
+ out := new(UnregisterNodeResponse)
+ err := c.cc.Invoke(ctx, Node_UnregisterNode_FullMethodName, in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
// NodeServer is the server API for Node service.
// All implementations must embed UnimplementedNodeServer
// for forward compatibility
type NodeServer interface {
- // RegisterNode registers a new Node and pmm-agent.
+ // RegisterNode registers a new Node, pmm-agent and create service account and it's token.
RegisterNode(context.Context, *RegisterNodeRequest) (*RegisterNodeResponse, error)
+ // UnregisterNode unregister a Node, pmm-agent and remove service account and it's token.
+ UnregisterNode(context.Context, *UnregisterNodeRequest) (*UnregisterNodeResponse, error)
mustEmbedUnimplementedNodeServer()
}
@@ -63,6 +77,10 @@ type UnimplementedNodeServer struct{}
func (UnimplementedNodeServer) RegisterNode(context.Context, *RegisterNodeRequest) (*RegisterNodeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RegisterNode not implemented")
}
+
+func (UnimplementedNodeServer) UnregisterNode(context.Context, *UnregisterNodeRequest) (*UnregisterNodeResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method UnregisterNode not implemented")
+}
func (UnimplementedNodeServer) mustEmbedUnimplementedNodeServer() {}
// UnsafeNodeServer may be embedded to opt out of forward compatibility for this service.
@@ -94,6 +112,24 @@ func _Node_RegisterNode_Handler(srv interface{}, ctx context.Context, dec func(i
return interceptor(ctx, in, info, handler)
}
+func _Node_UnregisterNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(UnregisterNodeRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(NodeServer).UnregisterNode(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: Node_UnregisterNode_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(NodeServer).UnregisterNode(ctx, req.(*UnregisterNodeRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
// Node_ServiceDesc is the grpc.ServiceDesc for Node service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -105,6 +141,10 @@ var Node_ServiceDesc = grpc.ServiceDesc{
MethodName: "RegisterNode",
Handler: _Node_RegisterNode_Handler,
},
+ {
+ MethodName: "UnregisterNode",
+ Handler: _Node_UnregisterNode_Handler,
+ },
},
Streams: []grpc.StreamDesc{},
Metadata: "managementpb/node.proto",
diff --git a/api/swagger/swagger-dev.json b/api/swagger/swagger-dev.json
index 575579b963..d25823f48e 100644
--- a/api/swagger/swagger-dev.json
+++ b/api/swagger/swagger-dev.json
@@ -22340,6 +22340,84 @@
}
}
},
+ "/v1/management/Node/Unregister": {
+ "post": {
+ "description": "Unregister a Node and pmm-agent",
+ "tags": [
+ "Node"
+ ],
+ "summary": "Unregister Node",
+ "operationId": "UnregisterNode",
+ "parameters": [
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "type": "object",
+ "properties": {
+ "node_id": {
+ "description": "Node_id to be unregistered.",
+ "type": "string",
+ "x-order": 0
+ },
+ "force": {
+ "description": "Force delete node, related service account, even if it has more service tokens attached.",
+ "type": "boolean",
+ "x-order": 1
+ }
+ }
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "A successful response.",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "warning": {
+ "description": "Warning message if there are more service tokens attached to service account.",
+ "type": "string",
+ "x-order": 0
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "integer",
+ "format": "int32",
+ "x-order": 0
+ },
+ "message": {
+ "type": "string",
+ "x-order": 1
+ },
+ "details": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@type": {
+ "type": "string",
+ "x-order": 0
+ }
+ },
+ "additionalProperties": false
+ },
+ "x-order": 2
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/v1/management/PostgreSQL/Add": {
"post": {
"description": "Adds PostgreSQL Service and starts postgres exporter. It automatically adds a service to inventory, which is running on provided \"node_id\", then adds \"postgres_exporter\" with provided \"pmm_agent_id\" and other parameters.",
diff --git a/api/swagger/swagger.json b/api/swagger/swagger.json
index 7d9e31f0eb..d35874e353 100644
--- a/api/swagger/swagger.json
+++ b/api/swagger/swagger.json
@@ -18662,6 +18662,84 @@
}
}
},
+ "/v1/management/Node/Unregister": {
+ "post": {
+ "description": "Unregister a Node and pmm-agent",
+ "tags": [
+ "Node"
+ ],
+ "summary": "Unregister Node",
+ "operationId": "UnregisterNode",
+ "parameters": [
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "type": "object",
+ "properties": {
+ "node_id": {
+ "description": "Node_id to be unregistered.",
+ "type": "string",
+ "x-order": 0
+ },
+ "force": {
+ "description": "Force delete node, related service account, even if it has more service tokens attached.",
+ "type": "boolean",
+ "x-order": 1
+ }
+ }
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "A successful response.",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "warning": {
+ "description": "Warning message if there are more service tokens attached to service account.",
+ "type": "string",
+ "x-order": 0
+ }
+ }
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "schema": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "integer",
+ "format": "int32",
+ "x-order": 0
+ },
+ "message": {
+ "type": "string",
+ "x-order": 1
+ },
+ "details": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "@type": {
+ "type": "string",
+ "x-order": 0
+ }
+ },
+ "additionalProperties": false
+ },
+ "x-order": 2
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/v1/management/PostgreSQL/Add": {
"post": {
"description": "Adds PostgreSQL Service and starts postgres exporter. It automatically adds a service to inventory, which is running on provided \"node_id\", then adds \"postgres_exporter\" with provided \"pmm_agent_id\" and other parameters.",
diff --git a/docs/api/welcome/authentication.md b/docs/api/welcome/authentication.md
index 6693f8765d..cec8d37873 100644
--- a/docs/api/welcome/authentication.md
+++ b/docs/api/welcome/authentication.md
@@ -12,7 +12,7 @@ PMM Server's user authentication is built on top of and is compatible with [Graf
In this section we will talk about two main authentication mechanisms:
- Basic HTTP authentication
-- Bearer Authentication (Authentication with an API key)
+- Bearer Authentication (Authentication with an Service Token or API key)
### Basic HTTP Authentication
@@ -37,8 +37,23 @@ curl -X GET -u admin:admin -H 'Content-Type: application/json' https://127.0.0.1
### Bearer Authentication
-Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens. The bearer token is a cryptic API key, which can be generated by the server admin from the Settings UI or via a respective API call (read more about how to generate an API key). The client must send the API key in the `Authorization` header when making requests to protected resources:
+Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens. The bearer token is a cryptic Service Token, or API key, which can be generated by the server admin from the Settings UI or via a respective API call (read more about how to generate Service token or API key). The client must send the Service Token/API key in the `Authorization` header when making requests to protected resources:
+Service Token example:
+```shell
+curl -X GET -H 'Authorization: Bearer glsa_Fp0ggev31R58ueNJbJgYw7fIGfO3yKWH_746383ab' \
+ -H 'Content-Type: application/json' https://127.0.0.1/v1/version
+```
+
+You can use the Service Token in basic authentication as well:
+
+```shell
+curl -X GET -H 'Content-Type: application/json' \
+ https://service_token:glsa_Fp0ggev31R58ueNJbJgYw7fIGfO3yKWH_746383ab@127.0.0.1/v1/version
+```
+Service Token usually has "glsa_" prefix. If you convert it from API key, it will not change. If you use API key, then it will be automatically converted into Service Account and Service Token.
+
+API key example:
```shell
curl -X GET -H 'Authorization: Bearer eyJrIjoiUXRkeDNMS1g1bFVyY0tUj1o0SmhBc3g4QUdTRVAwekoiLCJuIjoicG1tLXRlc3QiLCJpZCI6MX0=' \
-H 'Content-Type: application/json' https://127.0.0.1/v1/version
diff --git a/go.mod b/go.mod
index 98934ea3c8..8b608de57d 100644
--- a/go.mod
+++ b/go.mod
@@ -39,6 +39,7 @@ require (
github.com/go-openapi/swag v0.23.0
github.com/go-openapi/validate v0.24.0
github.com/go-sql-driver/mysql v1.7.1
+ github.com/gogo/status v1.1.1
github.com/golang-migrate/migrate/v4 v4.17.0
github.com/golang/protobuf v1.5.3
github.com/google/uuid v1.6.0
@@ -92,6 +93,7 @@ require (
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a // indirect
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
diff --git a/go.sum b/go.sum
index b04e515a4b..b0ecfaa9bc 100644
--- a/go.sum
+++ b/go.sum
@@ -221,10 +221,14 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a h1:dR8+Q0uO5S2ZBcs2IH6VBKYwSxPo2vYCYq0ot0mu7xA=
+github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg=
+github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU=
@@ -899,6 +903,7 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
+google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -935,6 +940,7 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
+google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
diff --git a/managed/services/grafana/auth_server.go b/managed/services/grafana/auth_server.go
index e04ea1937f..2050962b73 100644
--- a/managed/services/grafana/auth_server.go
+++ b/managed/services/grafana/auth_server.go
@@ -38,10 +38,13 @@ import (
"github.com/percona/pmm/managed/models"
)
+const (
+ connectionEndpoint = "/agent.Agent/Connect"
+)
+
// rules maps original URL prefix to minimal required role.
var rules = map[string]role{
- // TODO https://jira.percona.com/browse/PMM-4420
- "/agent.Agent/Connect": none,
+ connectionEndpoint: admin,
"/inventory.": admin,
"/management.": admin,
@@ -459,6 +462,17 @@ func nextPrefix(path string) string {
return path[:i+1]
}
+func isLocalAgentConnection(req *http.Request) bool {
+ ip := strings.Split(req.RemoteAddr, ":")[0]
+ pmmAgent := req.Header.Get("Pmm-Agent-Id")
+ path := req.Header.Get("X-Original-Uri")
+ if ip == "127.0.0.1" && pmmAgent == "pmm-server" && path == connectionEndpoint {
+ return true
+ }
+
+ return false
+}
+
// authenticate checks if user has access to a specific path.
// It returns user information retrieved during authentication.
// Paths which require no Grafana role return zero value for
@@ -498,22 +512,30 @@ func (s *AuthServer) authenticate(ctx context.Context, req *http.Request, l *log
return nil, nil
}
- // Get authenticated user from Grafana
- authUser, authErr := s.getAuthUser(ctx, req, l)
- if authErr != nil {
- return nil, authErr
+ var user *authUser
+ if isLocalAgentConnection(req) {
+ user = &authUser{
+ role: rules[connectionEndpoint],
+ userID: 0,
+ }
+ } else {
+ var authErr *authError
+ // Get authenticated user from Grafana
+ user, authErr = s.getAuthUser(ctx, req, l)
+ if authErr != nil {
+ return nil, authErr
+ }
}
+ l = l.WithField("role", user.role.String())
- l = l.WithField("role", authUser.role.String())
-
- if authUser.role == grafanaAdmin {
+ if user.role == grafanaAdmin {
l.Debugf("Grafana admin, allowing access.")
- return authUser, nil
+ return user, nil
}
- if minRole <= authUser.role {
+ if minRole <= user.role {
l.Debugf("Minimal required role is %q, granting access.", minRole)
- return authUser, nil
+ return user, nil
}
l.Warnf("Minimal required role is %q.", minRole)
diff --git a/managed/services/grafana/auth_server_test.go b/managed/services/grafana/auth_server_test.go
index 15822094b4..0b1bbbe14c 100644
--- a/managed/services/grafana/auth_server_test.go
+++ b/managed/services/grafana/auth_server_test.go
@@ -31,6 +31,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
+ "google.golang.org/grpc/metadata"
"gopkg.in/reform.v1"
"gopkg.in/reform.v1/dialects/postgresql"
@@ -161,7 +162,6 @@ func TestAuthServerMustSetup(t *testing.T) {
func TestAuthServerAuthenticate(t *testing.T) {
t.Parallel()
- // logrus.SetLevel(logrus.TraceLevel)
checker := &mockAwsInstanceChecker{}
checker.Test(t)
@@ -198,7 +198,7 @@ func TestAuthServerAuthenticate(t *testing.T) {
})
for uri, minRole := range map[string]role{
- "/agent.Agent/Connect": none,
+ "/agent.Agent/Connect": admin,
"/inventory.Nodes/ListNodes": admin,
"/management.Actions/StartMySQLShowTableStatusAction": viewer,
@@ -270,6 +270,75 @@ func TestAuthServerAuthenticate(t *testing.T) {
}
}
+func TestServerClientConnection(t *testing.T) {
+ t.Parallel()
+
+ checker := &mockAwsInstanceChecker{}
+ checker.Test(t)
+ t.Cleanup(func() { checker.AssertExpectations(t) })
+
+ ctx := context.Background()
+ c := NewClient("127.0.0.1:3000")
+ s := NewAuthServer(c, checker, nil)
+
+ t.Run("Basic auth - success", func(t *testing.T) {
+ t.Parallel()
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, connectionEndpoint, nil)
+ require.NoError(t, err)
+ req.SetBasicAuth("admin", "admin")
+
+ _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name()))
+ assert.Nil(t, authError)
+ })
+
+ t.Run("Basic auth - fail", func(t *testing.T) {
+ t.Parallel()
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, connectionEndpoint, nil)
+ require.NoError(t, err)
+ req.SetBasicAuth("admin", "wrong")
+
+ _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name()))
+ assert.Equal(t, codes.Unauthenticated, authError.code)
+ })
+
+ t.Run("Token auth - success", func(t *testing.T) {
+ t.Parallel()
+
+ nodeName := fmt.Sprintf("N1-%d", time.Now().UnixNano())
+ headersMD := metadata.New(map[string]string{
+ "Authorization": "Basic YWRtaW46YWRtaW4=",
+ })
+ ctx := metadata.NewIncomingContext(context.Background(), headersMD)
+ _, serviceToken, err := c.CreateServiceAccount(ctx, nodeName, true)
+ require.NoError(t, err)
+ defer func() {
+ warning, err := c.DeleteServiceAccount(ctx, nodeName, true)
+ require.NoError(t, err)
+ require.Empty(t, warning)
+ }()
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, connectionEndpoint, nil)
+ require.NoError(t, err)
+ req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", serviceToken))
+
+ _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name()))
+ assert.Nil(t, authError)
+ })
+
+ t.Run("Token auth - fail", func(t *testing.T) {
+ t.Parallel()
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, connectionEndpoint, nil)
+ require.NoError(t, err)
+ req.Header.Set("Authorization", "Bearer wrong")
+
+ _, authError := s.authenticate(ctx, req, logrus.WithField("test", t.Name()))
+ assert.Equal(t, codes.Unauthenticated, authError.code)
+ })
+}
+
func TestAuthServerAddVMGatewayToken(t *testing.T) {
ctx := logger.Set(context.Background(), t.Name())
uuid.SetRand(&tests.IDReader{})
diff --git a/managed/services/grafana/client.go b/managed/services/grafana/client.go
index 915f376083..724eb01388 100644
--- a/managed/services/grafana/client.go
+++ b/managed/services/grafana/client.go
@@ -19,7 +19,6 @@ package grafana
import (
"bytes"
"context"
- "encoding/base64"
"encoding/json"
"fmt"
"io"
@@ -39,13 +38,18 @@ import (
"google.golang.org/grpc/status"
"github.com/percona/pmm/managed/services"
+ "github.com/percona/pmm/managed/utils/auth"
"github.com/percona/pmm/managed/utils/irt"
)
// ErrFailedToGetToken means it failed to get user's token. Most likely due to the fact user is not logged in using Percona Account.
var ErrFailedToGetToken = errors.New("failed to get token")
-const defaultEvaluationInterval = time.Minute
+const (
+ defaultEvaluationInterval = time.Minute
+ pmmServiceTokenName = "pmm-agent-service-token" //nolint:gosec
+ pmmServiceAccountName = "pmm-agent-service-account" //nolint:gosec
+)
// Client represents a client for Grafana API.
type Client struct {
@@ -136,7 +140,7 @@ func (c *Client) do(ctx context.Context, method, path, rawQuery string, headers
if err != nil {
return errors.WithStack(err)
}
- if resp.StatusCode != 200 && resp.StatusCode != 202 {
+ if resp.StatusCode < 200 || resp.StatusCode > 202 {
cErr := &clientError{
Method: req.Method,
URL: req.URL.String(),
@@ -192,7 +196,7 @@ func (r role) String() string {
// GetUserID returns user ID from Grafana for the current user.
func (c *Client) GetUserID(ctx context.Context) (int, error) {
- authHeaders, err := c.authHeadersFromContext(ctx)
+ authHeaders, err := auth.GetHeadersFromContext(ctx)
if err != nil {
return 0, err
}
@@ -211,27 +215,41 @@ func (c *Client) GetUserID(ctx context.Context) (int, error) {
return int(userID), nil
}
+var emptyUser = authUser{
+ role: none,
+ userID: 0,
+}
+
// getAuthUser returns grafanaAdmin if currently authenticated user is a Grafana (super) admin.
// Otherwise, it returns a role in the default organization (with ID 1).
// Ctx is used only for cancelation.
func (c *Client) getAuthUser(ctx context.Context, authHeaders http.Header) (authUser, error) {
- // Check if it's API Key
- if c.IsAPIKeyAuth(authHeaders) {
- role, err := c.getRoleForAPIKey(ctx, authHeaders)
+ // Check if API Key or Service Token is authorized.
+ token := auth.GetTokenFromHeaders(authHeaders)
+ if token != "" {
+ role, err := c.getRoleForServiceToken(ctx, token)
+ if err != nil {
+ if strings.Contains(err.Error(), "Auth method is not service account token") {
+ role, err := c.getRoleForAPIKey(ctx, authHeaders)
+ return authUser{
+ role: role,
+ userID: 0,
+ }, err
+ }
+
+ return emptyUser, err
+ }
return authUser{
role: role,
userID: 0,
- }, err
+ }, nil
}
// https://grafana.com/docs/http_api/user/#actual-user - works only with Basic Auth
var m map[string]interface{}
err := c.do(ctx, http.MethodGet, "/api/user", "", authHeaders, nil, &m)
if err != nil {
- return authUser{
- role: none,
- userID: 0,
- }, err
+ return emptyUser, err
}
id, _ := m["id"].(float64)
@@ -274,21 +292,18 @@ func (c *Client) getAuthUser(ctx context.Context, authHeaders http.Header) (auth
}, nil
}
-// IsAPIKeyAuth checks if the request is made using an API Key.
-func (c *Client) IsAPIKeyAuth(headers http.Header) bool {
- authHeader := headers.Get("Authorization")
- switch {
- case strings.HasPrefix(authHeader, "Bearer"):
- return true
- case strings.HasPrefix(authHeader, "Basic"):
- h := strings.TrimPrefix(authHeader, "Basic")
- d, err := base64.StdEncoding.DecodeString(strings.TrimSpace(h))
- if err != nil {
- return false
- }
- return strings.HasPrefix(string(d), "api_key:")
+func (c *Client) getRoleForAPIKey(ctx context.Context, authHeaders http.Header) (role, error) {
+ var k map[string]interface{}
+ if err := c.do(ctx, http.MethodGet, "/api/auth/key", "", authHeaders, nil, &k); err != nil {
+ return none, err
+ }
+
+ if id, _ := k["orgId"].(float64); id != 1 {
+ return none, nil
}
- return false
+
+ role, _ := k["role"].(string)
+ return c.convertRole(role), nil
}
func (c *Client) convertRole(role string) role {
@@ -312,9 +327,12 @@ type apiKey struct {
Expiration *time.Time `json:"expiration,omitempty"`
}
-func (c *Client) getRoleForAPIKey(ctx context.Context, authHeaders http.Header) (role, error) {
+func (c *Client) getRoleForServiceToken(ctx context.Context, token string) (role, error) {
+ header := http.Header{}
+ header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
+
var k map[string]interface{}
- if err := c.do(ctx, http.MethodGet, "/api/auth/key", "", authHeaders, nil, &k); err != nil {
+ if err := c.do(ctx, http.MethodGet, "/api/auth/serviceaccount", "", header, nil, &k); err != nil {
return none, err
}
@@ -326,6 +344,54 @@ func (c *Client) getRoleForAPIKey(ctx context.Context, authHeaders http.Header)
return c.convertRole(role), nil
}
+type serviceAccountSearch struct {
+ TotalCount int `json:"totalCount"`
+ ServiceAccounts []serviceAccount `json:"serviceAccounts"`
+}
+
+func (c *Client) getServiceAccountIDFromName(ctx context.Context, nodeName string, authHeaders http.Header) (int, error) {
+ var res serviceAccountSearch
+ serviceAccountName := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName)
+ if err := c.do(ctx, http.MethodGet, "/api/serviceaccounts/search", fmt.Sprintf("query=%s", serviceAccountName), authHeaders, nil, &res); err != nil {
+ return 0, err
+ }
+ for _, serviceAccount := range res.ServiceAccounts {
+ if serviceAccount.Name != serviceAccountName {
+ continue
+ }
+ return serviceAccount.ID, nil
+ }
+
+ return 0, errors.Errorf("service account %s not found", serviceAccountName)
+}
+
+func (c *Client) getNotPMMAgentTokenCountForServiceAccount(ctx context.Context, nodeName string) (int, error) {
+ authHeaders, err := auth.GetHeadersFromContext(ctx)
+ if err != nil {
+ return 0, err
+ }
+
+ serviceAccountID, err := c.getServiceAccountIDFromName(ctx, nodeName, authHeaders)
+ if err != nil {
+ return 0, err
+ }
+
+ var tokens []serviceToken
+ if err := c.do(ctx, http.MethodGet, fmt.Sprintf("/api/serviceaccounts/%d/tokens", serviceAccountID), "", authHeaders, nil, &tokens); err != nil {
+ return 0, err
+ }
+
+ count := 0
+ for _, token := range tokens {
+ serviceTokenName := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName)
+ if !strings.HasPrefix(token.Name, serviceTokenName) {
+ count++
+ }
+ }
+
+ return count, nil
+}
+
func (c *Client) testCreateUser(ctx context.Context, login string, role role, authHeaders http.Header) (int, error) {
// https://grafana.com/docs/http_api/admin/#global-users
b, err := json.Marshal(map[string]string{
@@ -366,50 +432,60 @@ func (c *Client) testDeleteUser(ctx context.Context, userID int, authHeaders htt
return c.do(ctx, "DELETE", "/api/admin/users/"+strconv.Itoa(userID), "", authHeaders, nil, nil)
}
-// CreateAdminAPIKey creates API key with Admin role and provided name.
-func (c *Client) CreateAdminAPIKey(ctx context.Context, name string) (int64, string, error) {
- authHeaders, err := c.authHeadersFromContext(ctx)
+// CreateServiceAccount creates service account and token with Admin role.
+func (c *Client) CreateServiceAccount(ctx context.Context, nodeName string, reregister bool) (int, string, error) {
+ authHeaders, err := auth.GetHeadersFromContext(ctx)
if err != nil {
return 0, "", err
}
- return c.createAPIKey(ctx, name, admin, authHeaders)
-}
-// DeleteAPIKeysWithPrefix deletes all API keys with provided prefix. If there is no api key with provided prefix just ignores it.
-func (c *Client) DeleteAPIKeysWithPrefix(ctx context.Context, prefix string) error {
- authHeaders, err := c.authHeadersFromContext(ctx)
+ serviceAccountID, err := c.createServiceAccount(ctx, admin, nodeName, reregister, authHeaders)
if err != nil {
- return err
- }
- var keys []apiKey
- if err := c.do(ctx, http.MethodGet, "/api/auth/keys", "", authHeaders, nil, &keys); err != nil {
- return err
+ return 0, "", err
}
- for _, k := range keys {
- if strings.HasPrefix(k.Name, prefix) {
- err := c.deleteAPIKey(ctx, k.ID, authHeaders)
- if err != nil {
- return err
- }
- }
+ _, serviceToken, err := c.createServiceToken(ctx, serviceAccountID, nodeName, reregister, authHeaders)
+ if err != nil {
+ return 0, "", err
}
- return nil
+ return serviceAccountID, serviceToken, nil
}
-// DeleteAPIKeyByID deletes API key by ID.
-func (c *Client) DeleteAPIKeyByID(ctx context.Context, id int64) error {
- authHeaders, err := c.authHeadersFromContext(ctx)
+// DeleteServiceAccount deletes service account by current service token.
+func (c *Client) DeleteServiceAccount(ctx context.Context, nodeName string, force bool) (string, error) {
+ authHeaders, err := auth.GetHeadersFromContext(ctx)
if err != nil {
- return err
+ return "", err
+ }
+
+ warning := ""
+ serviceAccountID, err := c.getServiceAccountIDFromName(ctx, nodeName, authHeaders)
+ if err != nil {
+ return warning, err
+ }
+
+ customsTokensCount, err := c.getNotPMMAgentTokenCountForServiceAccount(ctx, nodeName)
+ if err != nil {
+ return warning, err
+ }
+
+ if !force && customsTokensCount > 0 {
+ warning = "Service account wont be deleted, because there are more not PMM agent related service tokens."
+ err = c.deletePMMAgentServiceToken(ctx, serviceAccountID, nodeName, authHeaders)
+ } else {
+ err = c.deleteServiceAccount(ctx, serviceAccountID, authHeaders)
+ }
+ if err != nil {
+ return warning, err
}
- return c.deleteAPIKey(ctx, id, authHeaders)
+
+ return warning, err
}
// CreateAlertRule creates Grafana alert rule.
func (c *Client) CreateAlertRule(ctx context.Context, folderName, groupName string, rule *services.Rule) error {
- authHeaders, err := c.authHeadersFromContext(ctx)
+ authHeaders, err := auth.GetHeadersFromContext(ctx)
if err != nil {
return err
}
@@ -541,7 +617,7 @@ func (c *Client) GetFolderByUID(ctx context.Context, uid string) (*gapi.Folder,
}
func (c *Client) createGrafanaClient(ctx context.Context) (*gapi.Client, error) {
- authHeaders, err := c.authHeadersFromContext(ctx)
+ authHeaders, err := auth.GetHeadersFromContext(ctx)
if err != nil {
return nil, errors.WithStack(err)
}
@@ -559,30 +635,6 @@ func (c *Client) createGrafanaClient(ctx context.Context) (*gapi.Client, error)
return grafanaClient, nil
}
-func (c *Client) authHeadersFromContext(ctx context.Context) (http.Header, error) {
- headers, ok := metadata.FromIncomingContext(ctx)
- if !ok {
- return nil, fmt.Errorf("cannot get headers from metadata")
- }
- // get authorization from headers.
- authorizationHeaders := headers.Get("Authorization")
- cookieHeaders := headers.Get("grpcgateway-cookie")
- if len(authorizationHeaders) == 0 && len(cookieHeaders) == 0 {
- return nil, status.Error(codes.Unauthenticated, "Authorization error.")
- }
-
- authHeaders := make(http.Header)
- if len(authorizationHeaders) != 0 {
- authHeaders.Add("Authorization", authorizationHeaders[0])
- }
- if len(cookieHeaders) != 0 {
- for _, header := range cookieHeaders {
- authHeaders.Add("Cookie", header)
- }
- }
- return authHeaders, nil
-}
-
func (c *Client) createAPIKey(ctx context.Context, name string, role role, authHeaders http.Header) (int64, string, error) {
// https://grafana.com/docs/grafana/latest/http_api/auth/#create-api-key
b, err := json.Marshal(apiKey{Name: name, Role: role.String()})
@@ -612,6 +664,117 @@ func (c *Client) deleteAPIKey(ctx context.Context, apiKeyID int64, authHeaders h
return c.do(ctx, "DELETE", "/api/auth/keys/"+strconv.FormatInt(apiKeyID, 10), "", authHeaders, nil, nil)
}
+type serviceAccount struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Role string `json:"role"`
+ Force bool `json:"force"`
+}
+type serviceToken struct {
+ ID int `json:"id"`
+ Name string `json:"name"`
+ Role string `json:"role"`
+}
+
+func (c *Client) createServiceAccount(ctx context.Context, role role, nodeName string, reregister bool, authHeaders http.Header) (int, error) {
+ if role == none {
+ return 0, errors.New("you cannot create service account with empty role")
+ }
+
+ // Max length of service account name is 190 chars (default limit in Grafana). In reality it is 187.
+ // Some test could fail if you will have name longer than 187 chars.
+ serviceAccountName := fmt.Sprintf("%s-%s", pmmServiceAccountName, nodeName)
+ b, err := json.Marshal(serviceAccount{Name: serviceAccountName, Role: role.String(), Force: reregister})
+ if err != nil {
+ return 0, errors.WithStack(err)
+ }
+
+ var m map[string]interface{}
+ if err = c.do(ctx, "POST", "/api/serviceaccounts", "", authHeaders, b, &m); err != nil {
+ return 0, err
+ }
+
+ serviceAccountID := int(m["id"].(float64)) //nolint:forcetypeassert
+
+ // orgId is ignored during creating service account and default is -1
+ // orgId should be set to 1
+ if err = c.do(ctx, "PATCH", fmt.Sprintf("/api/serviceaccounts/%d", serviceAccountID), "", authHeaders, []byte("{\"orgId\": 1}"), &m); err != nil {
+ return 0, err
+ }
+
+ return serviceAccountID, nil
+}
+
+func (c *Client) createServiceToken(ctx context.Context, serviceAccountID int, nodeName string, reregister bool, authHeaders http.Header) (int, string, error) {
+ serviceTokenName := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName)
+ exists, err := c.serviceTokenExists(ctx, serviceAccountID, nodeName, authHeaders)
+ if err != nil {
+ return 0, "", err
+ }
+ if exists && reregister {
+ err := c.deletePMMAgentServiceToken(ctx, serviceAccountID, nodeName, authHeaders)
+ if err != nil {
+ return 0, "", err
+ }
+ }
+
+ b, err := json.Marshal(serviceToken{Name: serviceTokenName, Role: admin.String()})
+ if err != nil {
+ return 0, "", errors.WithStack(err)
+ }
+
+ var m map[string]interface{}
+ if err = c.do(ctx, "POST", fmt.Sprintf("/api/serviceaccounts/%d/tokens", serviceAccountID), "", authHeaders, b, &m); err != nil {
+ return 0, "", err
+ }
+ serviceTokenID := int(m["id"].(float64)) //nolint:forcetypeassert
+ serviceTokenKey := m["key"].(string) //nolint:forcetypeassert
+
+ return serviceTokenID, serviceTokenKey, nil
+}
+
+func (c *Client) serviceTokenExists(ctx context.Context, serviceAccountID int, nodeName string, authHeaders http.Header) (bool, error) {
+ var tokens []serviceToken
+ if err := c.do(ctx, "GET", fmt.Sprintf("/api/serviceaccounts/%d/tokens", serviceAccountID), "", authHeaders, nil, &tokens); err != nil {
+ return false, err
+ }
+
+ serviceTokenName := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName)
+ for _, token := range tokens {
+ if !strings.HasPrefix(token.Name, serviceTokenName) {
+ continue
+ }
+
+ return true, nil
+ }
+
+ return false, nil
+}
+
+func (c *Client) deletePMMAgentServiceToken(ctx context.Context, serviceAccountID int, nodeName string, authHeaders http.Header) error {
+ var tokens []serviceToken
+ if err := c.do(ctx, "GET", fmt.Sprintf("/api/serviceaccounts/%d/tokens", serviceAccountID), "", authHeaders, nil, &tokens); err != nil {
+ return err
+ }
+
+ serviceTokenName := fmt.Sprintf("%s-%s", pmmServiceTokenName, nodeName)
+ for _, token := range tokens {
+ if strings.HasPrefix(token.Name, serviceTokenName) {
+ if err := c.do(ctx, "DELETE", fmt.Sprintf("/api/serviceaccounts/%d/tokens/%d", serviceAccountID, token.ID), "", authHeaders, nil, nil); err != nil {
+ return err
+ }
+
+ return nil
+ }
+ }
+
+ return nil
+}
+
+func (c *Client) deleteServiceAccount(ctx context.Context, serviceAccountID int, authHeaders http.Header) error {
+ return c.do(ctx, "DELETE", fmt.Sprintf("/api/serviceaccounts/%d", serviceAccountID), "", authHeaders, nil, nil)
+}
+
// Annotation contains grafana annotation response.
type annotation struct {
Time time.Time `json:"-"`
diff --git a/managed/services/grafana/client_test.go b/managed/services/grafana/client_test.go
index 16cbd03dc0..66963ded67 100644
--- a/managed/services/grafana/client_test.go
+++ b/managed/services/grafana/client_test.go
@@ -72,9 +72,6 @@ func TestClient(t *testing.T) {
})
t.Run("NewUserViewerByDefault", func(t *testing.T) {
- // do not run this test in parallel - they lock Grafana's sqlite3 database
- // t.Parallel()
-
// See [users] in grafana.ini.
login := fmt.Sprintf("%s-%d", none, time.Now().Nanosecond())
@@ -104,8 +101,7 @@ func TestClient(t *testing.T) {
role := role
t.Run(fmt.Sprintf("Basic auth %s", role.String()), func(t *testing.T) {
- // do not run this test in parallel - they lock Grafana's sqlite3 database
- // t.Parallel()
+ t.Parallel()
login := fmt.Sprintf("basic-%s-%d", role, time.Now().Nanosecond())
userID, err := c.testCreateUser(ctx, login, role, authHeaders)
@@ -131,8 +127,7 @@ func TestClient(t *testing.T) {
})
t.Run(fmt.Sprintf("API Key auth %s", role.String()), func(t *testing.T) {
- // do not run this test in parallel - they lock Grafana's sqlite3 database
- // t.Parallel()
+ t.Parallel()
login := fmt.Sprintf("api-%s-%d", role, time.Now().Nanosecond())
apiKeyID, apiKey, err := c.createAPIKey(ctx, login, role, authHeaders)
@@ -155,6 +150,35 @@ func TestClient(t *testing.T) {
assert.Equal(t, role, actualRole)
assert.Equal(t, role.String(), actualRole.String())
})
+
+ t.Run(fmt.Sprintf("Service token auth %s", role.String()), func(t *testing.T) {
+ t.Parallel()
+
+ nodeName := fmt.Sprintf("test-node-%s", role)
+ serviceAccountID, err := c.createServiceAccount(ctx, role, nodeName, true, authHeaders)
+ require.NoError(t, err)
+ defer func() {
+ err := c.deleteServiceAccount(ctx, serviceAccountID, authHeaders)
+ require.NoError(t, err)
+ }()
+
+ serviceTokenID, serviceToken, err := c.createServiceToken(ctx, serviceAccountID, nodeName, true, authHeaders)
+ require.NoError(t, err)
+ require.NotZero(t, serviceTokenID)
+ require.NotEmpty(t, serviceToken)
+ defer func() {
+ err := c.deletePMMAgentServiceToken(ctx, serviceAccountID, nodeName, authHeaders)
+ require.NoError(t, err)
+ }()
+
+ serviceTokenAuthHeaders := http.Header{}
+ serviceTokenAuthHeaders.Set("Authorization", fmt.Sprintf("Bearer %s", serviceToken))
+ u, err := c.getAuthUser(ctx, serviceTokenAuthHeaders)
+ assert.NoError(t, err)
+ actualRole := u.role
+ assert.Equal(t, role, actualRole)
+ assert.Equal(t, role.String(), actualRole.String())
+ })
}
})
diff --git a/managed/services/management/deps.go b/managed/services/management/deps.go
index 0da0dffe03..500a3bf039 100644
--- a/managed/services/management/deps.go
+++ b/managed/services/management/deps.go
@@ -17,7 +17,6 @@ package management
import (
"context"
- "net/http"
"time"
"github.com/percona-platform/saas/pkg/check"
@@ -99,7 +98,7 @@ type victoriaMetricsClient interface {
Query(ctx context.Context, query string, ts time.Time, opts ...v1.Option) (model.Value, v1.Warnings, error)
}
-type apiKeyProvider interface {
- CreateAdminAPIKey(ctx context.Context, name string) (int64, string, error)
- IsAPIKeyAuth(headers http.Header) bool
+type authProvider interface {
+ CreateServiceAccount(ctx context.Context, noneName string, reregister bool) (int, string, error)
+ DeleteServiceAccount(ctx context.Context, noneName string, force bool) (string, error)
}
diff --git a/managed/services/management/grpc/node_server.go b/managed/services/management/grpc/node_server.go
index 1191ee3081..0136fa8195 100644
--- a/managed/services/management/grpc/node_server.go
+++ b/managed/services/management/grpc/node_server.go
@@ -38,3 +38,8 @@ func NewManagementNodeServer(s *management.NodeService) managementpb.NodeServer
func (s *nodeServer) RegisterNode(ctx context.Context, req *managementpb.RegisterNodeRequest) (*managementpb.RegisterNodeResponse, error) {
return s.svc.Register(ctx, req)
}
+
+// UnregisterNode do unregistration of the Node.
+func (s *nodeServer) UnregisterNode(ctx context.Context, req *managementpb.UnregisterNodeRequest) (*managementpb.UnregisterNodeResponse, error) {
+ return s.svc.Unregister(ctx, req)
+}
diff --git a/managed/services/management/mock_api_key_provider_test.go b/managed/services/management/mock_api_key_provider_test.go
deleted file mode 100644
index f7aed1af97..0000000000
--- a/managed/services/management/mock_api_key_provider_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// Code generated by mockery. DO NOT EDIT.
-
-package management
-
-import (
- context "context"
- http "net/http"
-
- mock "github.com/stretchr/testify/mock"
-)
-
-// mockApiKeyProvider is an autogenerated mock type for the apiKeyProvider type
-type mockApiKeyProvider struct {
- mock.Mock
-}
-
-// CreateAdminAPIKey provides a mock function with given fields: ctx, name
-func (_m *mockApiKeyProvider) CreateAdminAPIKey(ctx context.Context, name string) (int64, string, error) {
- ret := _m.Called(ctx, name)
-
- if len(ret) == 0 {
- panic("no return value specified for CreateAdminAPIKey")
- }
-
- var r0 int64
- var r1 string
- var r2 error
- if rf, ok := ret.Get(0).(func(context.Context, string) (int64, string, error)); ok {
- return rf(ctx, name)
- }
- if rf, ok := ret.Get(0).(func(context.Context, string) int64); ok {
- r0 = rf(ctx, name)
- } else {
- r0 = ret.Get(0).(int64)
- }
-
- if rf, ok := ret.Get(1).(func(context.Context, string) string); ok {
- r1 = rf(ctx, name)
- } else {
- r1 = ret.Get(1).(string)
- }
-
- if rf, ok := ret.Get(2).(func(context.Context, string) error); ok {
- r2 = rf(ctx, name)
- } else {
- r2 = ret.Error(2)
- }
-
- return r0, r1, r2
-}
-
-// IsAPIKeyAuth provides a mock function with given fields: headers
-func (_m *mockApiKeyProvider) IsAPIKeyAuth(headers http.Header) bool {
- ret := _m.Called(headers)
-
- if len(ret) == 0 {
- panic("no return value specified for IsAPIKeyAuth")
- }
-
- var r0 bool
- if rf, ok := ret.Get(0).(func(http.Header) bool); ok {
- r0 = rf(headers)
- } else {
- r0 = ret.Get(0).(bool)
- }
-
- return r0
-}
-
-// newMockApiKeyProvider creates a new instance of mockApiKeyProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockApiKeyProvider(t interface {
- mock.TestingT
- Cleanup(func())
-},
-) *mockApiKeyProvider {
- mock := &mockApiKeyProvider{}
- mock.Mock.Test(t)
-
- t.Cleanup(func() { mock.AssertExpectations(t) })
-
- return mock
-}
diff --git a/managed/services/management/mock_auth_provider_test.go b/managed/services/management/mock_auth_provider_test.go
new file mode 100644
index 0000000000..a00b0dd60b
--- /dev/null
+++ b/managed/services/management/mock_auth_provider_test.go
@@ -0,0 +1,92 @@
+// Code generated by mockery. DO NOT EDIT.
+
+package management
+
+import (
+ context "context"
+
+ mock "github.com/stretchr/testify/mock"
+)
+
+// mockAuthProvider is an autogenerated mock type for the authProvider type
+type mockAuthProvider struct {
+ mock.Mock
+}
+
+// CreateServiceAccount provides a mock function with given fields: ctx, noneName, reregister
+func (_m *mockAuthProvider) CreateServiceAccount(ctx context.Context, noneName string, reregister bool) (int, string, error) {
+ ret := _m.Called(ctx, noneName, reregister)
+
+ if len(ret) == 0 {
+ panic("no return value specified for CreateServiceAccount")
+ }
+
+ var r0 int
+ var r1 string
+ var r2 error
+ if rf, ok := ret.Get(0).(func(context.Context, string, bool) (int, string, error)); ok {
+ return rf(ctx, noneName, reregister)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, string, bool) int); ok {
+ r0 = rf(ctx, noneName, reregister)
+ } else {
+ r0 = ret.Get(0).(int)
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, string, bool) string); ok {
+ r1 = rf(ctx, noneName, reregister)
+ } else {
+ r1 = ret.Get(1).(string)
+ }
+
+ if rf, ok := ret.Get(2).(func(context.Context, string, bool) error); ok {
+ r2 = rf(ctx, noneName, reregister)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// DeleteServiceAccount provides a mock function with given fields: ctx, noneName, force
+func (_m *mockAuthProvider) DeleteServiceAccount(ctx context.Context, noneName string, force bool) (string, error) {
+ ret := _m.Called(ctx, noneName, force)
+
+ if len(ret) == 0 {
+ panic("no return value specified for DeleteServiceAccount")
+ }
+
+ var r0 string
+ var r1 error
+ if rf, ok := ret.Get(0).(func(context.Context, string, bool) (string, error)); ok {
+ return rf(ctx, noneName, force)
+ }
+ if rf, ok := ret.Get(0).(func(context.Context, string, bool) string); ok {
+ r0 = rf(ctx, noneName, force)
+ } else {
+ r0 = ret.Get(0).(string)
+ }
+
+ if rf, ok := ret.Get(1).(func(context.Context, string, bool) error); ok {
+ r1 = rf(ctx, noneName, force)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// newMockAuthProvider creates a new instance of mockAuthProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func newMockAuthProvider(t interface {
+ mock.TestingT
+ Cleanup(func())
+},
+) *mockAuthProvider {
+ mock := &mockAuthProvider{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/managed/services/management/node.go b/managed/services/management/node.go
index 26f3c54381..b685ec3939 100644
--- a/managed/services/management/node.go
+++ b/managed/services/management/node.go
@@ -17,14 +17,10 @@ package management
import (
"context"
- "fmt"
- "math/rand"
- "net/http"
"github.com/AlekSi/pointer"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
- "google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"gopkg.in/reform.v1"
@@ -32,29 +28,28 @@ import (
"github.com/percona/pmm/api/managementpb"
"github.com/percona/pmm/managed/models"
"github.com/percona/pmm/managed/services"
+ "github.com/percona/pmm/managed/utils/auth"
)
// NodeService represents service for working with nodes.
type NodeService struct {
- db *reform.DB
- akp apiKeyProvider
-
- l *logrus.Entry
+ db *reform.DB
+ ap authProvider
+ l *logrus.Entry
}
// NewNodeService creates NodeService instance.
-func NewNodeService(db *reform.DB, akp apiKeyProvider) *NodeService {
+func NewNodeService(db *reform.DB, ap authProvider) *NodeService {
return &NodeService{
- db: db,
- akp: akp,
- l: logrus.WithField("component", "node"),
+ db: db,
+ ap: ap,
+ l: logrus.WithField("component", "node"),
}
}
// Register do registration of the new node.
func (s *NodeService) Register(ctx context.Context, req *managementpb.RegisterNodeRequest) (*managementpb.RegisterNodeResponse, error) {
res := &managementpb.RegisterNodeResponse{}
-
e := s.db.InTransaction(func(tx *reform.TX) error {
node, err := models.FindNodeByName(tx.Querier, req.NodeName)
switch status.Code(err) { //nolint:exhaustive
@@ -136,29 +131,43 @@ func (s *NodeService) Register(ctx context.Context, req *managementpb.RegisterNo
return nil, e
}
- // get authorization from headers.
- md, ok := metadata.FromIncomingContext(ctx)
- if !ok {
- msg := "Couldn't create Admin API Key: cannot get headers from metadata"
- s.l.Errorln(msg)
- res.Warning = msg
- return res, nil
- }
- authorizationHeaders := md.Get("Authorization")
- if len(authorizationHeaders) == 0 {
- return nil, status.Error(codes.Unauthenticated, "Authorization error.")
- }
- headers := make(http.Header)
- headers.Set("Authorization", authorizationHeaders[0])
- if !s.akp.IsAPIKeyAuth(headers) {
- apiKeyName := fmt.Sprintf("pmm-agent-%s-%d", req.NodeName, rand.Int63()) //nolint:gosec
- _, res.Token, e = s.akp.CreateAdminAPIKey(ctx, apiKeyName)
+ authHeaders, _ := auth.GetHeadersFromContext(ctx)
+ token := auth.GetTokenFromHeaders(authHeaders)
+ if token != "" {
+ res.Token = token
+ } else {
+ _, res.Token, e = s.ap.CreateServiceAccount(ctx, req.NodeName, req.Reregister)
if e != nil {
- msg := fmt.Sprintf("Couldn't create Admin API Key: %s", e)
- s.l.Errorln(msg)
- res.Warning = msg
+ return nil, e
}
}
return res, nil
}
+
+// Unregister do unregistration of the node.
+func (s *NodeService) Unregister(ctx context.Context, req *managementpb.UnregisterNodeRequest) (*managementpb.UnregisterNodeResponse, error) {
+ node, err := models.FindNodeByID(s.db.Querier, req.NodeId)
+ if err != nil {
+ return nil, err
+ }
+
+ err = s.db.InTransaction(func(tx *reform.TX) error {
+ return models.RemoveNode(tx.Querier, req.NodeId, models.RemoveCascade)
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ warning, err := s.ap.DeleteServiceAccount(ctx, node.NodeName, req.Force)
+ if err != nil {
+ s.l.WithError(err).Error("deleting service account")
+ return &managementpb.UnregisterNodeResponse{
+ Warning: err.Error(),
+ }, nil
+ }
+
+ return &managementpb.UnregisterNodeResponse{
+ Warning: warning,
+ }, nil
+}
diff --git a/managed/services/management/node_test.go b/managed/services/management/node_test.go
index d4f6d8472c..3241429229 100644
--- a/managed/services/management/node_test.go
+++ b/managed/services/management/node_test.go
@@ -21,7 +21,6 @@ import (
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
@@ -38,7 +37,10 @@ import (
)
func TestNodeService(t *testing.T) {
- t.Run("Register", func(t *testing.T) {
+ getTestNodeName := func() string {
+ return "test-node"
+ }
+ t.Run("Register/Unregister", func(t *testing.T) {
setup := func(t *testing.T) (ctx context.Context, s *NodeService, teardown func(t *testing.T)) {
t.Helper()
@@ -58,11 +60,17 @@ func TestNodeService(t *testing.T) {
"Authorization": "Basic username:password",
})
ctx = metadata.NewIncomingContext(ctx, md)
- var apiKeyProvider mockApiKeyProvider
- apiKeyProvider.Test(t)
- apiKeyProvider.On("IsAPIKeyAuth", mock.Anything).Return(false)
- apiKeyProvider.On("CreateAdminAPIKey", ctx, mock.AnythingOfType("string")).Return(int64(0), "test-token", nil)
- s = NewNodeService(db, &apiKeyProvider)
+
+ serviceAccountID := int(0)
+ nodeName := getTestNodeName()
+ reregister := false
+ force := true
+
+ var authProvider mockAuthProvider
+ authProvider.Test(t)
+ authProvider.On("CreateServiceAccount", ctx, nodeName, reregister).Return(serviceAccountID, "test-token", nil)
+ authProvider.On("DeleteServiceAccount", ctx, nodeName, force).Return("", nil)
+ s = NewNodeService(db, &authProvider)
return
}
@@ -73,14 +81,14 @@ func TestNodeService(t *testing.T) {
res, err := s.Register(ctx, &managementpb.RegisterNodeRequest{
NodeType: inventorypb.NodeType_GENERIC_NODE,
- NodeName: "node",
+ NodeName: "test-node",
Address: "some.address.org",
Region: "region",
})
expected := &managementpb.RegisterNodeResponse{
GenericNode: &inventorypb.GenericNode{
NodeId: "/node_id/00000000-0000-4000-8000-000000000005",
- NodeName: "node",
+ NodeName: "test-node",
Address: "some.address.org",
Region: "region",
},
@@ -97,16 +105,25 @@ func TestNodeService(t *testing.T) {
t.Run("Exist", func(t *testing.T) {
res, err = s.Register(ctx, &managementpb.RegisterNodeRequest{
NodeType: inventorypb.NodeType_GENERIC_NODE,
- NodeName: "node",
+ NodeName: "test-node",
})
assert.Nil(t, res)
- tests.AssertGRPCError(t, status.New(codes.AlreadyExists, `Node with name "node" already exists.`), err)
+ tests.AssertGRPCError(t, status.New(codes.AlreadyExists, `Node with name "test-node" already exists.`), err)
})
t.Run("Reregister", func(t *testing.T) {
+ serviceAccountID := int(0)
+ nodeName := getTestNodeName()
+ reregister := true
+
+ var authProvider mockAuthProvider
+ authProvider.Test(t)
+ authProvider.On("CreateServiceAccount", ctx, nodeName, reregister).Return(serviceAccountID, "test-token", nil)
+ s.ap = &authProvider
+
res, err = s.Register(ctx, &managementpb.RegisterNodeRequest{
NodeType: inventorypb.NodeType_GENERIC_NODE,
- NodeName: "node",
+ NodeName: "test-node",
Address: "some.address.org",
Region: "region",
Reregister: true,
@@ -114,7 +131,7 @@ func TestNodeService(t *testing.T) {
expected := &managementpb.RegisterNodeResponse{
GenericNode: &inventorypb.GenericNode{
NodeId: "/node_id/00000000-0000-4000-8000-000000000008",
- NodeName: "node",
+ NodeName: "test-node",
Address: "some.address.org",
Region: "region",
},
@@ -128,10 +145,20 @@ func TestNodeService(t *testing.T) {
assert.Equal(t, expected, res)
assert.NoError(t, err)
})
+
t.Run("Reregister-force", func(t *testing.T) {
+ serviceAccountID := int(0)
+ nodeName := "test-node-new"
+ reregister := true
+
+ var authProvider mockAuthProvider
+ authProvider.Test(t)
+ authProvider.On("CreateServiceAccount", ctx, nodeName, reregister).Return(serviceAccountID, "test-token", nil)
+ s.ap = &authProvider
+
res, err = s.Register(ctx, &managementpb.RegisterNodeRequest{
NodeType: inventorypb.NodeType_GENERIC_NODE,
- NodeName: "node-name-new",
+ NodeName: "test-node-new",
Address: "some.address.org",
Region: "region",
Reregister: true,
@@ -139,7 +166,7 @@ func TestNodeService(t *testing.T) {
expected := &managementpb.RegisterNodeResponse{
GenericNode: &inventorypb.GenericNode{
NodeId: "/node_id/00000000-0000-4000-8000-00000000000b",
- NodeName: "node-name-new",
+ NodeName: "test-node-new",
Address: "some.address.org",
Region: "region",
},
@@ -153,6 +180,51 @@ func TestNodeService(t *testing.T) {
assert.Equal(t, expected, res)
assert.NoError(t, err)
})
+
+ t.Run("Unregister", func(t *testing.T) {
+ serviceAccountID := int(0)
+ nodeName := getTestNodeName()
+ reregister := true
+ force := true
+
+ var authProvider mockAuthProvider
+ authProvider.Test(t)
+ authProvider.On("CreateServiceAccount", ctx, nodeName, reregister).Return(serviceAccountID, "test-token", nil)
+ authProvider.On("DeleteServiceAccount", ctx, nodeName, force).Return("", nil)
+ s.ap = &authProvider
+
+ resRegister, err := s.Register(ctx, &managementpb.RegisterNodeRequest{
+ NodeType: inventorypb.NodeType_GENERIC_NODE,
+ NodeName: "test-node",
+ Address: "some.address.org",
+ Region: "region",
+ Reregister: true,
+ })
+ assert.NoError(t, err)
+
+ expected := &managementpb.RegisterNodeResponse{
+ GenericNode: &inventorypb.GenericNode{
+ NodeId: "/node_id/00000000-0000-4000-8000-00000000000e",
+ NodeName: "test-node",
+ Address: "some.address.org",
+ Region: "region",
+ },
+ ContainerNode: (*inventorypb.ContainerNode)(nil),
+ PmmAgent: &inventorypb.PMMAgent{
+ AgentId: "/agent_id/00000000-0000-4000-8000-00000000000f",
+ RunsOnNodeId: "/node_id/00000000-0000-4000-8000-00000000000e",
+ },
+ Token: "test-token",
+ }
+ assert.Equal(t, expected, resRegister)
+
+ res, err := s.Unregister(ctx, &managementpb.UnregisterNodeRequest{
+ NodeId: resRegister.GenericNode.NodeId,
+ Force: true,
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, "", res.Warning)
+ })
})
})
}
diff --git a/managed/utils/auth/token.go b/managed/utils/auth/token.go
new file mode 100644
index 0000000000..43fd2b6635
--- /dev/null
+++ b/managed/utils/auth/token.go
@@ -0,0 +1,78 @@
+// Copyright (C) 2023 Percona LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+// Package auth contains functions to work with auth tokens and headers.
+package auth
+
+import (
+ "context"
+ "encoding/base64"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gogo/status"
+ "github.com/sirupsen/logrus"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/metadata"
+)
+
+// GetTokenFromHeaders returns authorization token if it is found in provided HTTP headers.
+func GetTokenFromHeaders(authHeaders http.Header) string {
+ authHeader := authHeaders.Get("Authorization")
+ switch {
+ case strings.HasPrefix(authHeader, "Bearer"):
+ return strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer"))
+ case strings.HasPrefix(authHeader, "Basic"):
+ h := strings.TrimPrefix(authHeader, "Basic")
+ t, err := base64.StdEncoding.DecodeString(strings.TrimSpace(h))
+ if err != nil {
+ logrus.Debugf("cannot decode basic authorization header: %s", err)
+ return ""
+ }
+ tk := string(t)
+ if strings.HasPrefix(tk, "api_key:") || strings.HasPrefix(tk, "service_token:") {
+ return strings.Split(tk, ":")[1]
+ }
+ }
+
+ return ""
+}
+
+// GetHeadersFromContext returns authorization headers if they are found in provided context.
+func GetHeadersFromContext(ctx context.Context) (http.Header, error) {
+ headers, ok := metadata.FromIncomingContext(ctx)
+ if !ok {
+ return nil, fmt.Errorf("cannot get headers from metadata")
+ }
+
+ authorizationHeaders := headers.Get("Authorization")
+ cookieHeaders := headers.Get("grpcgateway-cookie")
+ if len(authorizationHeaders) == 0 && len(cookieHeaders) == 0 {
+ return nil, status.Error(codes.Unauthenticated, "Authorization error.")
+ }
+
+ authHeaders := make(http.Header)
+ if len(authorizationHeaders) != 0 {
+ authHeaders.Add("Authorization", authorizationHeaders[0])
+ }
+ if len(cookieHeaders) != 0 {
+ for _, header := range cookieHeaders {
+ authHeaders.Add("Cookie", header)
+ }
+ }
+
+ return authHeaders, nil
+}