mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
VAULT-24556: add secret syncs to vault operator usage output (#25751)
* add secret syncs to vault operator usage * changelog * unexport * add godoc for test and remove t.Run invocation * move test to separate package * update comment
This commit is contained in:
3
changelog/25751.txt
Normal file
3
changelog/25751.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
cli: include secret syncs counts in the `vault operator usage` command output
|
||||
```
|
||||
97
command/command_testonly/operator_usage_testonly_test.go
Normal file
97
command/command_testonly/operator_usage_testonly_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
//go:build testonly
|
||||
|
||||
package command_testonly
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/cli"
|
||||
"github.com/hashicorp/vault/command"
|
||||
"github.com/hashicorp/vault/helper/timeutil"
|
||||
vaulthttp "github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/sdk/helper/clientcountutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/clientcountutil/generation"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func testOperatorUsageCommand(tb testing.TB) (*cli.MockUi, *command.OperatorUsageCommand) {
|
||||
tb.Helper()
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
return ui, &command.OperatorUsageCommand{
|
||||
BaseCommand: &command.BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TestOperatorUsageCommandRun writes mock activity log data and runs the
|
||||
// operator usage command. The test verifies that the output contains the
|
||||
// expected values per client type.
|
||||
// This test cannot be run in parallel because it sets the VAULT_TOKEN env
|
||||
// var
|
||||
func TestOperatorUsageCommandRun(t *testing.T) {
|
||||
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
NumCores: 1,
|
||||
})
|
||||
defer cluster.Cleanup()
|
||||
core := cluster.Cores[0].Core
|
||||
vault.TestWaitActive(t, core)
|
||||
|
||||
client := cluster.Cores[0].Client
|
||||
_, err := client.Logical().Write("sys/internal/counters/config", map[string]interface{}{"enabled": "enable"})
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
||||
_, err = clientcountutil.NewActivityLogData(client).
|
||||
NewPreviousMonthData(1).
|
||||
NewClientsSeen(6, clientcountutil.WithClientType("entity")).
|
||||
NewClientsSeen(4, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(2, clientcountutil.WithClientType("secret-sync")).
|
||||
NewCurrentMonthData().
|
||||
NewClientsSeen(3, clientcountutil.WithClientType("entity")).
|
||||
NewClientsSeen(4, clientcountutil.WithClientType("non-entity-token")).
|
||||
NewClientsSeen(5, clientcountutil.WithClientType("secret-sync")).
|
||||
Write(context.Background(), generation.WriteOptions_WRITE_ENTITIES, generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES)
|
||||
require.NoError(t, err)
|
||||
|
||||
ui, cmd := testOperatorUsageCommand(t)
|
||||
|
||||
t.Setenv("VAULT_TOKEN", client.Token())
|
||||
start := timeutil.MonthsPreviousTo(1, now).Format(time.RFC3339)
|
||||
end := timeutil.EndOfMonth(now).UTC().Format(time.RFC3339)
|
||||
// Reset and check output
|
||||
code := cmd.Run([]string{
|
||||
"-address", client.Address(),
|
||||
"-tls-skip-verify",
|
||||
"-start-time", start,
|
||||
"-end-time", end,
|
||||
})
|
||||
require.Equal(t, 0, code, ui.ErrorWriter.String())
|
||||
output := ui.OutputWriter.String()
|
||||
outputLines := strings.Split(output, "\n")
|
||||
require.Equal(t, fmt.Sprintf("Period start: %s", start), outputLines[0])
|
||||
require.Equal(t, fmt.Sprintf("Period end: %s", end), outputLines[1])
|
||||
|
||||
require.Contains(t, outputLines[3], "Secret sync")
|
||||
nsCounts := strings.Fields(outputLines[5])
|
||||
require.Equal(t, "[root]", nsCounts[0])
|
||||
require.Equal(t, "9", nsCounts[1])
|
||||
require.Equal(t, "8", nsCounts[2])
|
||||
require.Equal(t, "7", nsCounts[3])
|
||||
require.Equal(t, "24", nsCounts[4])
|
||||
|
||||
totalCounts := strings.Fields(outputLines[7])
|
||||
require.Equal(t, "Total", totalCounts[0])
|
||||
require.Equal(t, nsCounts[1:], totalCounts[1:])
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func (c *OperatorUsageCommand) Run(args []string) int {
|
||||
c.outputTimestamps(resp.Data)
|
||||
|
||||
out := []string{
|
||||
"Namespace path | Distinct entities | Non-Entity tokens | Active clients",
|
||||
"Namespace path | Distinct entities | Non-Entity tokens | Secret syncs | Active clients",
|
||||
}
|
||||
|
||||
out = append(out, c.namespacesOutput(resp.Data)...)
|
||||
@@ -196,8 +196,8 @@ type UsageResponse struct {
|
||||
entityCount int64
|
||||
// As per 1.9, the tokenCount field will contain the distinct non-entity
|
||||
// token clients instead of each individual token.
|
||||
tokenCount int64
|
||||
|
||||
tokenCount int64
|
||||
secretSyncs int64
|
||||
clientCount int64
|
||||
}
|
||||
|
||||
@@ -242,6 +242,9 @@ func (c *OperatorUsageCommand) parseNamespaceCount(rawVal interface{}) (UsageRes
|
||||
return ret, errors.New("missing non_entity_tokens")
|
||||
}
|
||||
|
||||
// don't error if the secret syncs key is missing
|
||||
ret.secretSyncs, _ = jsonNumberOK(counts, "secret_syncs")
|
||||
|
||||
ret.clientCount, ok = jsonNumberOK(counts, "clients")
|
||||
if !ok {
|
||||
return ret, errors.New("missing clients")
|
||||
@@ -274,8 +277,8 @@ func (c *OperatorUsageCommand) namespacesOutput(data map[string]interface{}) []s
|
||||
sortOrder = "2" + val.namespacePath
|
||||
}
|
||||
|
||||
formattedLine := fmt.Sprintf("%s | %d | %d | %d",
|
||||
val.namespacePath, val.entityCount, val.tokenCount, val.clientCount)
|
||||
formattedLine := fmt.Sprintf("%s | %d | %d | %d | %d",
|
||||
val.namespacePath, val.entityCount, val.tokenCount, val.secretSyncs, val.clientCount)
|
||||
nsOut = append(nsOut, UsageCommandNamespace{
|
||||
formattedLine: formattedLine,
|
||||
sortOrder: sortOrder,
|
||||
@@ -296,7 +299,7 @@ func (c *OperatorUsageCommand) namespacesOutput(data map[string]interface{}) []s
|
||||
|
||||
func (c *OperatorUsageCommand) totalOutput(data map[string]interface{}) []string {
|
||||
// blank line separating it from namespaces
|
||||
out := []string{" | | | "}
|
||||
out := []string{" | | | | "}
|
||||
|
||||
total, ok := data["total"].(map[string]interface{})
|
||||
if !ok {
|
||||
@@ -315,13 +318,16 @@ func (c *OperatorUsageCommand) totalOutput(data map[string]interface{}) []string
|
||||
c.UI.Error("missing non_entity_tokens in total")
|
||||
return out
|
||||
}
|
||||
// don't error if secret syncs key is missing
|
||||
secretSyncs, _ := jsonNumberOK(total, "secret_syncs")
|
||||
|
||||
clientCount, ok := jsonNumberOK(total, "clients")
|
||||
if !ok {
|
||||
c.UI.Error("missing clients in total")
|
||||
return out
|
||||
}
|
||||
|
||||
out = append(out, fmt.Sprintf("Total | %d | %d | %d",
|
||||
entityCount, tokenCount, clientCount))
|
||||
out = append(out, fmt.Sprintf("Total | %d | %d | %d | %d",
|
||||
entityCount, tokenCount, secretSyncs, clientCount))
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user