mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
* temp * temp * Modified the tests a bit * One more modification * Added two tests for official and third party plugins * TEmp * Added external test with primary and secondary * Made some fixes based on comments * Fixing a linter error * One more fix Co-authored-by: divyaac <divya.chandrasekaran@hashicorp.com>
This commit is contained in:
parent
2e32e679d0
commit
ab5ff72603
5 changed files with 255 additions and 33 deletions
|
|
@ -10,12 +10,13 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
BillingSubPath = "billing/"
|
||||
ReplicatedPrefix = "replicated/"
|
||||
RoleHWMCountsHWM = "maxRoleCounts/"
|
||||
KvHWMCountsHWM = "maxKvCounts/"
|
||||
LocalPrefix = "local/"
|
||||
BillingWriteInterval = 10 * time.Minute
|
||||
BillingSubPath = "billing/"
|
||||
ReplicatedPrefix = "replicated/"
|
||||
RoleHWMCountsHWM = "maxRoleCounts/"
|
||||
KvHWMCountsHWM = "maxKvCounts/"
|
||||
LocalPrefix = "local/"
|
||||
ThirdPartyPluginsPrefix = "thirdPartyPluginCounts/"
|
||||
BillingWriteInterval = 10 * time.Minute
|
||||
)
|
||||
|
||||
var BillingMonthStorageFormat = "%s%d/%02d/%s" // e.g replicated/2026/01/maxKvCounts/
|
||||
|
|
|
|||
|
|
@ -104,5 +104,13 @@ func (c *Core) UpdateLocalHWMMetrics(ctx context.Context, currentMonth time.Time
|
|||
} else {
|
||||
c.logger.Info("updated local max kv counts", "prefix", billing.LocalPrefix, "currentMonth", currentMonth)
|
||||
}
|
||||
// The count of external plugins is per cluster, and we do not de-duplicate across clusters.
|
||||
// For that reason, we will always store the count at the "local" prefix, so that the count does not
|
||||
// get replicated.
|
||||
if _, err := c.UpdateMaxThirdPartyPluginCounts(ctx, currentMonth); err != nil {
|
||||
c.logger.Error("error updating local max external plugin counts", "error", err)
|
||||
} else {
|
||||
c.logger.Info("updated local max external plugin counts", "prefix", billing.LocalPrefix, "currentMonth", currentMonth)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,60 @@ import (
|
|||
"github.com/hashicorp/vault/vault/billing"
|
||||
)
|
||||
|
||||
func (c *Core) storeThirdPartyPluginCountsLocked(ctx context.Context, localPathPrefix string, currentMonth time.Time, thirdPartyPluginCounts int) error {
|
||||
billingPath := billing.GetMonthlyBillingPath(localPathPrefix, currentMonth, billing.ThirdPartyPluginsPrefix)
|
||||
entry := &logical.StorageEntry{
|
||||
Key: billingPath,
|
||||
Value: []byte(strconv.Itoa(thirdPartyPluginCounts)),
|
||||
}
|
||||
return c.GetBillingSubView().Put(ctx, entry)
|
||||
}
|
||||
|
||||
func (c *Core) getStoredThirdPartyPluginCountsLocked(ctx context.Context, localPathPrefix string, currentMonth time.Time) (int, error) {
|
||||
billingPath := billing.GetMonthlyBillingPath(localPathPrefix, currentMonth, billing.ThirdPartyPluginsPrefix)
|
||||
entry, err := c.GetBillingSubView().Get(ctx, billingPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if entry == nil {
|
||||
return 0, nil
|
||||
}
|
||||
thirdPartyPluginCounts, err := strconv.Atoi(string(entry.Value))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return thirdPartyPluginCounts, nil
|
||||
}
|
||||
|
||||
// UpdateMaxThirdPartyPlugins updates the max number of third-party plugins for the given month.
|
||||
// Note that this count is per cluster. It does NOT de-duplicate across clusters. For that reason,
|
||||
// we will always store the count at the "local" prefix.
|
||||
func (c *Core) UpdateMaxThirdPartyPluginCounts(ctx context.Context, currentMonth time.Time) (int, error) {
|
||||
c.consumptionBilling.BillingStorageLock.Lock()
|
||||
defer c.consumptionBilling.BillingStorageLock.Unlock()
|
||||
|
||||
previousThirdPartyPluginCounts, err := c.getStoredThirdPartyPluginCountsLocked(ctx, billing.LocalPrefix, currentMonth)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
currentThirdPartyPluginCounts, err := c.ListExternalSecretPlugins(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
maxCount := c.compareCounts(previousThirdPartyPluginCounts, len(currentThirdPartyPluginCounts), "Third-Party Plugins")
|
||||
err = c.storeThirdPartyPluginCountsLocked(ctx, billing.LocalPrefix, currentMonth, maxCount)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return maxCount, nil
|
||||
}
|
||||
|
||||
func (c *Core) GetStoredThirdPartyPluginCounts(ctx context.Context, month time.Time) (int, error) {
|
||||
c.consumptionBilling.BillingStorageLock.RLock()
|
||||
defer c.consumptionBilling.BillingStorageLock.RUnlock()
|
||||
return c.getStoredThirdPartyPluginCountsLocked(ctx, billing.LocalPrefix, month)
|
||||
}
|
||||
|
||||
func combineRoleCounts(ctx context.Context, a, b *RoleCounts) *RoleCounts {
|
||||
if a == nil && b == nil {
|
||||
return &RoleCounts{}
|
||||
|
|
@ -147,26 +201,26 @@ func (c *Core) UpdateMaxRoleCounts(ctx context.Context, localPathPrefix string,
|
|||
if currentRoleCounts == nil {
|
||||
currentRoleCounts = &RoleCounts{}
|
||||
}
|
||||
maxRoleCounts.AWSDynamicRoles = adjustCounts(currentRoleCounts.AWSDynamicRoles, maxRoleCounts.AWSDynamicRoles)
|
||||
maxRoleCounts.AzureDynamicRoles = adjustCounts(currentRoleCounts.AzureDynamicRoles, maxRoleCounts.AzureDynamicRoles)
|
||||
maxRoleCounts.AzureStaticRoles = adjustCounts(currentRoleCounts.AzureStaticRoles, maxRoleCounts.AzureStaticRoles)
|
||||
maxRoleCounts.GCPRolesets = adjustCounts(currentRoleCounts.GCPRolesets, maxRoleCounts.GCPRolesets)
|
||||
maxRoleCounts.AWSStaticRoles = adjustCounts(currentRoleCounts.AWSStaticRoles, maxRoleCounts.AWSStaticRoles)
|
||||
maxRoleCounts.DatabaseDynamicRoles = adjustCounts(currentRoleCounts.DatabaseDynamicRoles, maxRoleCounts.DatabaseDynamicRoles)
|
||||
maxRoleCounts.OpenLDAPStaticRoles = adjustCounts(currentRoleCounts.OpenLDAPStaticRoles, maxRoleCounts.OpenLDAPStaticRoles)
|
||||
maxRoleCounts.OpenLDAPDynamicRoles = adjustCounts(currentRoleCounts.OpenLDAPDynamicRoles, maxRoleCounts.OpenLDAPDynamicRoles)
|
||||
maxRoleCounts.LDAPDynamicRoles = adjustCounts(currentRoleCounts.LDAPDynamicRoles, maxRoleCounts.LDAPDynamicRoles)
|
||||
maxRoleCounts.LDAPStaticRoles = adjustCounts(currentRoleCounts.LDAPStaticRoles, maxRoleCounts.LDAPStaticRoles)
|
||||
maxRoleCounts.DatabaseStaticRoles = adjustCounts(currentRoleCounts.DatabaseStaticRoles, maxRoleCounts.DatabaseStaticRoles)
|
||||
maxRoleCounts.GCPImpersonatedAccounts = adjustCounts(currentRoleCounts.GCPImpersonatedAccounts, maxRoleCounts.GCPImpersonatedAccounts)
|
||||
maxRoleCounts.GCPStaticAccounts = adjustCounts(currentRoleCounts.GCPStaticAccounts, maxRoleCounts.GCPStaticAccounts)
|
||||
maxRoleCounts.AlicloudDynamicRoles = adjustCounts(currentRoleCounts.AlicloudDynamicRoles, maxRoleCounts.AlicloudDynamicRoles)
|
||||
maxRoleCounts.RabbitMQDynamicRoles = adjustCounts(currentRoleCounts.RabbitMQDynamicRoles, maxRoleCounts.RabbitMQDynamicRoles)
|
||||
maxRoleCounts.ConsulDynamicRoles = adjustCounts(currentRoleCounts.ConsulDynamicRoles, maxRoleCounts.ConsulDynamicRoles)
|
||||
maxRoleCounts.NomadDynamicRoles = adjustCounts(currentRoleCounts.NomadDynamicRoles, maxRoleCounts.NomadDynamicRoles)
|
||||
maxRoleCounts.KubernetesDynamicRoles = adjustCounts(currentRoleCounts.KubernetesDynamicRoles, maxRoleCounts.KubernetesDynamicRoles)
|
||||
maxRoleCounts.MongoDBAtlasDynamicRoles = adjustCounts(currentRoleCounts.MongoDBAtlasDynamicRoles, maxRoleCounts.MongoDBAtlasDynamicRoles)
|
||||
maxRoleCounts.TerraformCloudDynamicRoles = adjustCounts(currentRoleCounts.TerraformCloudDynamicRoles, maxRoleCounts.TerraformCloudDynamicRoles)
|
||||
maxRoleCounts.AWSDynamicRoles = c.compareCounts(currentRoleCounts.AWSDynamicRoles, maxRoleCounts.AWSDynamicRoles, "AWS Dynamic Roles")
|
||||
maxRoleCounts.AzureDynamicRoles = c.compareCounts(currentRoleCounts.AzureDynamicRoles, maxRoleCounts.AzureDynamicRoles, "Azure Dynamic Roles")
|
||||
maxRoleCounts.AzureStaticRoles = c.compareCounts(currentRoleCounts.AzureStaticRoles, maxRoleCounts.AzureStaticRoles, "Azure Static Roles")
|
||||
maxRoleCounts.GCPRolesets = c.compareCounts(currentRoleCounts.GCPRolesets, maxRoleCounts.GCPRolesets, "GCP Rolesets")
|
||||
maxRoleCounts.AWSStaticRoles = c.compareCounts(currentRoleCounts.AWSStaticRoles, maxRoleCounts.AWSStaticRoles, "AWS Static Roles")
|
||||
maxRoleCounts.DatabaseDynamicRoles = c.compareCounts(currentRoleCounts.DatabaseDynamicRoles, maxRoleCounts.DatabaseDynamicRoles, "Database Dynamic Roles")
|
||||
maxRoleCounts.OpenLDAPStaticRoles = c.compareCounts(currentRoleCounts.OpenLDAPStaticRoles, maxRoleCounts.OpenLDAPStaticRoles, "OpenLDAP Static Roles")
|
||||
maxRoleCounts.OpenLDAPDynamicRoles = c.compareCounts(currentRoleCounts.OpenLDAPDynamicRoles, maxRoleCounts.OpenLDAPDynamicRoles, "OpenLDAP Dynamic Roles")
|
||||
maxRoleCounts.LDAPDynamicRoles = c.compareCounts(currentRoleCounts.LDAPDynamicRoles, maxRoleCounts.LDAPDynamicRoles, "LDAP Dynamic Roles")
|
||||
maxRoleCounts.LDAPStaticRoles = c.compareCounts(currentRoleCounts.LDAPStaticRoles, maxRoleCounts.LDAPStaticRoles, "LDAP Static Roles")
|
||||
maxRoleCounts.DatabaseStaticRoles = c.compareCounts(currentRoleCounts.DatabaseStaticRoles, maxRoleCounts.DatabaseStaticRoles, "Database Static Roles")
|
||||
maxRoleCounts.GCPImpersonatedAccounts = c.compareCounts(currentRoleCounts.GCPImpersonatedAccounts, maxRoleCounts.GCPImpersonatedAccounts, "GCPImpersonated Accounts")
|
||||
maxRoleCounts.GCPStaticAccounts = c.compareCounts(currentRoleCounts.GCPStaticAccounts, maxRoleCounts.GCPStaticAccounts, "GCP Static Accounts")
|
||||
maxRoleCounts.AlicloudDynamicRoles = c.compareCounts(currentRoleCounts.AlicloudDynamicRoles, maxRoleCounts.AlicloudDynamicRoles, "Alicloud Dynamic Roles")
|
||||
maxRoleCounts.RabbitMQDynamicRoles = c.compareCounts(currentRoleCounts.RabbitMQDynamicRoles, maxRoleCounts.RabbitMQDynamicRoles, "RabbitMQ Dynamic Roles")
|
||||
maxRoleCounts.ConsulDynamicRoles = c.compareCounts(currentRoleCounts.ConsulDynamicRoles, maxRoleCounts.ConsulDynamicRoles, "Consul Dynamic Roles")
|
||||
maxRoleCounts.NomadDynamicRoles = c.compareCounts(currentRoleCounts.NomadDynamicRoles, maxRoleCounts.NomadDynamicRoles, "Nomad Dynamic Roles")
|
||||
maxRoleCounts.KubernetesDynamicRoles = c.compareCounts(currentRoleCounts.KubernetesDynamicRoles, maxRoleCounts.KubernetesDynamicRoles, "Kubernetes Dynamic Roles")
|
||||
maxRoleCounts.MongoDBAtlasDynamicRoles = c.compareCounts(currentRoleCounts.MongoDBAtlasDynamicRoles, maxRoleCounts.MongoDBAtlasDynamicRoles, "MongoDB Atlas Dynamic Roles")
|
||||
maxRoleCounts.TerraformCloudDynamicRoles = c.compareCounts(currentRoleCounts.TerraformCloudDynamicRoles, maxRoleCounts.TerraformCloudDynamicRoles, "Terraform Cloud Dynamic Roles")
|
||||
|
||||
err = c.storeMaxRoleCountsLocked(ctx, maxRoleCounts, localPathPrefix, currentMonth)
|
||||
if err != nil {
|
||||
|
|
@ -198,13 +252,14 @@ func (c *Core) getStoredRoleCountsLocked(ctx context.Context, localPathPrefix st
|
|||
return maxRoleCounts, nil
|
||||
}
|
||||
|
||||
func (c *Core) compareCounts(current, previous int, metricName string) int {
|
||||
if previous > current {
|
||||
return previous
|
||||
}
|
||||
c.logger.Debug("updating max counts", "metricName", metricName, "previous", previous, "current", current)
|
||||
return current
|
||||
}
|
||||
|
||||
func (c *Core) GetBillingSubView() *BarrierView {
|
||||
return c.systemBarrierView.SubView(billing.BillingSubPath)
|
||||
}
|
||||
|
||||
func adjustCounts(currentCount int, maxCount int) int {
|
||||
if currentCount > maxCount {
|
||||
return currentCount
|
||||
}
|
||||
return maxCount
|
||||
}
|
||||
|
|
|
|||
|
|
@ -963,3 +963,70 @@ func (c *Core) GetKvUsageMetricsByNamespace(ctx context.Context, kvVersion strin
|
|||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// ListExternalSecretPlugins returns the enabled secret engines
|
||||
// that are not builtin and not official-tier.
|
||||
//
|
||||
// This is useful for identifying "third-party" secrets mounts (e.g. community or
|
||||
// partner tier external plugins) while excluding builtins and official HashiCorp
|
||||
// plugins.
|
||||
// Note: This will include all mounts that have been built externally (even if they are
|
||||
// Hashicorp owned). This will happen if the plugin was built from a Github repo or from an
|
||||
// artifact.
|
||||
func (c *Core) ListExternalSecretPlugins(ctx context.Context) ([]*MountEntry, error) {
|
||||
if c == nil || c.pluginCatalog == nil {
|
||||
return nil, fmt.Errorf("core or plugin catalog is nil")
|
||||
}
|
||||
|
||||
mounts, err := c.ListMounts()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing mounts: %w", err)
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{})
|
||||
var result []*MountEntry
|
||||
for _, entry := range mounts {
|
||||
if entry == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Only secrets-engine mounts live in the mounts table. Exclude the known
|
||||
// non-secrets mounts and database mounts (PluginTypeDatabase).
|
||||
if entry.Table != mountTableType {
|
||||
continue
|
||||
}
|
||||
|
||||
pluginName := entry.Type
|
||||
if pluginName == mountTypePlugin && entry.Config.PluginName != "" {
|
||||
pluginName = entry.Config.PluginName
|
||||
}
|
||||
if pluginName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
pluginVersion := entry.RunningVersion
|
||||
|
||||
// De-dupe: multiple mounts can point at the same underlying plugin+version.
|
||||
// We want to charge for each unique plugin+version pair.
|
||||
key := pluginName + "\x00" + pluginVersion
|
||||
if _, ok := seen[key]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
runner, err := c.pluginCatalog.Get(ctx, pluginName, consts.PluginTypeSecrets, pluginVersion)
|
||||
if err != nil || runner == nil {
|
||||
// If we can't resolve the plugin runner (e.g. missing catalog entry),
|
||||
// conservatively skip it rather than risk misclassifying it.
|
||||
continue
|
||||
}
|
||||
|
||||
if runner.Builtin || runner.Tier == consts.PluginTierOfficial {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, entry)
|
||||
seen[key] = struct{}{}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ package vault
|
|||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -14,7 +16,12 @@ import (
|
|||
|
||||
"github.com/armon/go-metrics"
|
||||
logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
|
||||
"github.com/hashicorp/vault/builtin/credential/userpass"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/helper/pluginconsts"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/pluginhelpers"
|
||||
sdkconsts "github.com/hashicorp/vault/sdk/helper/consts"
|
||||
sdkpluginutil "github.com/hashicorp/vault/sdk/helper/pluginutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -467,3 +474,87 @@ func TestCoreMetrics_AvailablePolicies(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCore_ListExternalSecretPlugins tests that we correctly list the external secret plugins enabled
|
||||
// on Vault. We will register a few builtin plugins, a few external plugins with different versions
|
||||
// and verify that we return a count of the unique plugin name+version pairs. We should exclude any builtin
|
||||
// plugins.
|
||||
func TestCore_ListExternalSecretPlugins(t *testing.T) {
|
||||
pluginDir, err := filepath.EvalSymlinks(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
coreConfig := &CoreConfig{
|
||||
PluginDirectory: pluginDir,
|
||||
CredentialBackends: map[string]logical.Factory{
|
||||
pluginconsts.AuthTypeUserpass: userpass.Factory,
|
||||
},
|
||||
}
|
||||
core, _, root := TestCoreUnsealedWithConfig(t, coreConfig)
|
||||
|
||||
// Register a few plugins in the plugin catalog.
|
||||
ctx := namespace.RootContext(context.Background())
|
||||
|
||||
// Enable a builtin credential plugin and an additional builtin kv engine
|
||||
// mount. Neither should affect the results from ListNonOfficialExternalSecretsMounts.
|
||||
req := logical.TestRequest(t, logical.CreateOperation, "sys/auth/userpass")
|
||||
req.Data["type"] = pluginconsts.AuthTypeUserpass
|
||||
req.ClientToken = root
|
||||
_, err = core.HandleRequest(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
req = logical.TestRequest(t, logical.CreateOperation, "sys/mounts/kv2")
|
||||
req.Data["type"] = "kv"
|
||||
req.ClientToken = root
|
||||
_, err = core.HandleRequest(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Creates a secret plugin of the same name, with different versions
|
||||
secretPluginV1 := pluginhelpers.CompilePlugin(t, sdkconsts.PluginTypeSecrets, "v1.0.0", pluginDir)
|
||||
secretPluginV2 := pluginhelpers.CompilePlugin(t, sdkconsts.PluginTypeSecrets, "v2.0.0", pluginDir)
|
||||
secretPluginV3 := pluginhelpers.CompilePlugin(t, sdkconsts.PluginTypeSecrets, "v3.0.0", pluginDir)
|
||||
|
||||
dbPlugin := pluginhelpers.CompilePlugin(t, sdkconsts.PluginTypeDatabase, "v1.0.0", pluginDir)
|
||||
|
||||
registerPlugin := func(p pluginhelpers.TestPlugin) {
|
||||
t.Helper()
|
||||
shaBytes, err := hex.DecodeString(p.Sha256)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, core.pluginCatalog.Set(ctx, sdkpluginutil.SetPluginInput{
|
||||
Name: p.Name,
|
||||
Type: p.Typ,
|
||||
Version: p.Version,
|
||||
Command: p.FileName,
|
||||
Sha256: shaBytes,
|
||||
}))
|
||||
}
|
||||
registerPlugin(secretPluginV1)
|
||||
registerPlugin(secretPluginV2)
|
||||
registerPlugin(secretPluginV3)
|
||||
registerPlugin(dbPlugin)
|
||||
|
||||
// Add mounts: include duplicates, official-tier, builtin, and non-secrets.
|
||||
core.mountsLock.Lock()
|
||||
core.mounts.Entries = append(core.mounts.Entries,
|
||||
// Duplicate mounts: same plugin+version should only be counted once.
|
||||
&MountEntry{Table: mountTableType, Path: "dup-a/", Type: secretPluginV2.Name, RunningVersion: secretPluginV2.Version},
|
||||
&MountEntry{Table: mountTableType, Path: "dup-b/", Type: secretPluginV2.Name, RunningVersion: secretPluginV2.Version},
|
||||
&MountEntry{Table: mountTableType, Path: "different-version/", Type: secretPluginV3.Name, RunningVersion: secretPluginV3.Version},
|
||||
&MountEntry{Table: mountTableType, Path: "another-version/", Type: secretPluginV1.Name, RunningVersion: secretPluginV1.Version},
|
||||
|
||||
// Non-secrets plugin mounted in the mounts table: should be excluded.
|
||||
&MountEntry{Table: mountTableType, Path: "db/", Type: dbPlugin.Name, RunningVersion: dbPlugin.Version},
|
||||
)
|
||||
core.mountsLock.Unlock()
|
||||
|
||||
got, err := core.ListExternalSecretPlugins(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect only the non-builtin external secrets plugin v2, v3 and v1.
|
||||
require.Len(t, got, 3)
|
||||
require.Equal(t, secretPluginV2.Name, got[0].Type)
|
||||
require.Equal(t, secretPluginV2.Version, got[0].RunningVersion)
|
||||
require.Equal(t, secretPluginV3.Name, got[1].Type)
|
||||
require.Equal(t, secretPluginV3.Version, got[1].RunningVersion)
|
||||
require.Equal(t, secretPluginV1.Name, got[2].Type)
|
||||
require.Equal(t, secretPluginV1.Version, got[2].RunningVersion)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue