PLTCONN-4069: Set read limit for account and entitlement read

This commit is contained in:
Megan Tran
2023-11-05 16:54:04 -06:00
parent 10d67ad960
commit fbb601dfa7
9 changed files with 40 additions and 28 deletions

View File

@@ -8,9 +8,10 @@ import (
"github.com/logrusorgru/aurora" "github.com/logrusorgru/aurora"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
connvalidate "github.com/sailpoint-oss/sailpoint-cli/cmd/connector/validate" connvalidate "github.com/sailpoint-oss/sailpoint-cli/cmd/connector/validate"
"github.com/sailpoint-oss/sailpoint-cli/internal/client" "github.com/sailpoint-oss/sailpoint-cli/internal/client"
"github.com/spf13/cobra"
) )
func newConnValidateCmd(apiClient client.Client) *cobra.Command { func newConnValidateCmd(apiClient client.Client) *cobra.Command {
@@ -43,9 +44,11 @@ func newConnValidateCmd(apiClient client.Client) *cobra.Command {
check := cmd.Flags().Lookup("check").Value.String() check := cmd.Flags().Lookup("check").Value.String()
isReadOnly, _ := strconv.ParseBool(cmd.Flags().Lookup("read-only").Value.String()) isReadOnly, _ := strconv.ParseBool(cmd.Flags().Lookup("read-only").Value.String())
isReadLimit, _ := strconv.ParseBool(cmd.Flags().Lookup("read-limit").Value.String())
valid := connvalidate.NewValidator(connvalidate.Config{ valid := connvalidate.NewValidator(connvalidate.Config{
Check: check, Check: check,
ReadOnly: isReadOnly, ReadOnly: isReadOnly,
ReadLimit: isReadLimit,
}, cc) }, cc)
results, err := valid.Run(ctx) results, err := valid.Run(ctx)

View File

@@ -11,18 +11,20 @@ import (
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"github.com/logrusorgru/aurora" "github.com/logrusorgru/aurora"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
connvalidate "github.com/sailpoint-oss/sailpoint-cli/cmd/connector/validate"
"github.com/sailpoint-oss/sailpoint-cli/internal/client"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/alessio/shellescape.v1" "gopkg.in/alessio/shellescape.v1"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
connvalidate "github.com/sailpoint-oss/sailpoint-cli/cmd/connector/validate"
"github.com/sailpoint-oss/sailpoint-cli/internal/client"
"github.com/sailpoint-oss/sailpoint-cli/internal/util"
) )
type Source struct { type Source struct {
@@ -68,6 +70,7 @@ func newConnValidateSourcesCmd(apiClient client.Client) *cobra.Command {
ctx := cmd.Context() ctx := cmd.Context()
endpoint := cmd.Flags().Lookup("conn-endpoint").Value.String() endpoint := cmd.Flags().Lookup("conn-endpoint").Value.String()
isReadLimit, _ := strconv.ParseBool(cmd.Flags().Lookup("read-limit").Value.String())
listOfSources, err := getSourceFromFile(sourceFile) listOfSources, err := getSourceFromFile(sourceFile)
if err != nil { if err != nil {
@@ -83,7 +86,7 @@ func newConnValidateSourcesCmd(apiClient client.Client) *cobra.Command {
return err return err
} }
res, err := validateConnectors(ctx, apiClient, source, endpoint) res, err := validateConnectors(ctx, apiClient, source, endpoint, isReadLimit)
if err != nil { if err != nil {
return err return err
} }
@@ -132,7 +135,7 @@ func getSourceFromFile(filePath string) ([]Source, error) {
return config, err return config, err
} }
func validateConnectors(ctx context.Context, apiClient client.Client, source Source, endpoint string) (*ValidationResults, error) { func validateConnectors(ctx context.Context, apiClient client.Client, source Source, endpoint string, isReadLimit bool) (*ValidationResults, error) {
resp, err := apiClient.Get(ctx, endpoint) resp, err := apiClient.Get(ctx, endpoint)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -170,8 +173,9 @@ func validateConnectors(ctx context.Context, apiClient client.Client, source Sou
} }
validator := connvalidate.NewValidator(connvalidate.Config{ validator := connvalidate.NewValidator(connvalidate.Config{
Check: "", Check: "",
ReadOnly: source.ReadOnly, ReadOnly: source.ReadOnly,
ReadLimit: isReadLimit,
}, cc) }, cc)
results, err := validator.Run(ctx) results, err := validator.Run(ctx)

View File

@@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/kr/pretty" "github.com/kr/pretty"
connclient "github.com/sailpoint-oss/sailpoint-cli/cmd/connector/client" connclient "github.com/sailpoint-oss/sailpoint-cli/cmd/connector/client"
) )
@@ -16,7 +17,7 @@ var accountCreateChecks = []Check{
RequiredCommands: []string{ RequiredCommands: []string{
"std:account:create", "std:account:create",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
input := map[string]interface{}{} input := map[string]interface{}{}
_, _, err := cc.AccountCreate(ctx, nil, input, nil) _, _, err := cc.AccountCreate(ctx, nil, input, nil)
if err == nil { if err == nil {
@@ -33,7 +34,7 @@ var accountCreateChecks = []Check{
"std:account:read", "std:account:read",
"std:account:delete", "std:account:delete",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
input := map[string]interface{}{} input := map[string]interface{}{}
for _, field := range spec.AccountCreateTemplate.Fields { for _, field := range spec.AccountCreateTemplate.Fields {
if field.Required { if field.Required {
@@ -79,7 +80,7 @@ var accountCreateChecks = []Check{
"std:account:read", "std:account:read",
"std:account:delete", "std:account:delete",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
input := map[string]interface{}{} input := map[string]interface{}{}
for _, field := range spec.AccountCreateTemplate.Fields { for _, field := range spec.AccountCreateTemplate.Fields {
input[getFieldName(field)] = genCreateField(field) input[getFieldName(field)] = genCreateField(field)
@@ -124,7 +125,7 @@ var accountCreateChecks = []Check{
"std:account:delete", "std:account:delete",
"std:account:list", "std:account:list",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
accountsPreCreate, _, _, err := cc.AccountList(ctx, nil, nil, nil) accountsPreCreate, _, _, err := cc.AccountList(ctx, nil, nil, nil)
if err != nil { if err != nil {
res.err(err) res.err(err)

View File

@@ -19,7 +19,7 @@ var accountReadChecks = []Check{
"std:account:read", "std:account:read",
"std:account:list", "std:account:list",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
accounts, _, _, err := cc.AccountList(ctx, nil, nil, nil) accounts, _, _, err := cc.AccountList(ctx, nil, nil, nil)
if err != nil { if err != nil {
res.err(err) res.err(err)
@@ -27,7 +27,7 @@ var accountReadChecks = []Check{
} }
count := 0 count := 0
for _, account := range accounts { for _, account := range accounts {
if count > accountReadLimit { if readLimit && count > accountReadLimit {
break break
} }
acct, _, err := cc.AccountRead(ctx, account.ID(), account.UniqueID(), nil) acct, _, err := cc.AccountRead(ctx, account.ID(), account.UniqueID(), nil)
@@ -59,7 +59,7 @@ var accountReadChecks = []Check{
RequiredCommands: []string{ RequiredCommands: []string{
"std:account:read", "std:account:read",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
_, _, err := cc.AccountRead(ctx, "__sailpoint__not__found__", "", nil) _, _, err := cc.AccountRead(ctx, "__sailpoint__not__found__", "", nil)
if err == nil { if err == nil {
res.errf("expected error for non-existant identity") res.errf("expected error for non-existant identity")
@@ -73,7 +73,7 @@ var accountReadChecks = []Check{
RequiredCommands: []string{ RequiredCommands: []string{
"std:account:list", "std:account:list",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
additionalAttributes := map[string]string{} additionalAttributes := map[string]string{}
attrsByName := map[string]connclient.AccountSchemaAttribute{} attrsByName := map[string]connclient.AccountSchemaAttribute{}

View File

@@ -17,7 +17,7 @@ var accountUpdateChecks = []Check{
"std:account:list", "std:account:list",
"std:account:update", "std:account:update",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
accounts, _, _, err := cc.AccountList(ctx, nil, nil, nil) accounts, _, _, err := cc.AccountList(ctx, nil, nil, nil)
if err != nil { if err != nil {
res.err(err) res.err(err)
@@ -72,7 +72,7 @@ var accountUpdateChecks = []Check{
"std:account:update", "std:account:update",
"std:account:delete", "std:account:delete",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
entitlementAttr := entitlementAttr(spec) entitlementAttr := entitlementAttr(spec)
if entitlementAttr == "" { if entitlementAttr == "" {
res.warnf("no entitlement attribute") res.warnf("no entitlement attribute")

View File

@@ -24,7 +24,7 @@ type Check struct {
// IsDataModifier determines a checking that will modify connectors data after applying // IsDataModifier determines a checking that will modify connectors data after applying
IsDataModifier bool IsDataModifier bool
Run func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) Run func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool)
// RequiredCommands represents a list of commands that use for this check // RequiredCommands represents a list of commands that use for this check
RequiredCommands []string RequiredCommands []string
} }

View File

@@ -16,7 +16,7 @@ var entitlementReadChecks = []Check{
RequiredCommands: []string{ RequiredCommands: []string{
"std:entitlement:read", "std:entitlement:read",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
_, _, err := cc.EntitlementRead(ctx, "__sailpoint__not__found__", "", "group", nil) _, _, err := cc.EntitlementRead(ctx, "__sailpoint__not__found__", "", "group", nil)
if err == nil { if err == nil {
res.errf("expected error for non-existant entitlement") res.errf("expected error for non-existant entitlement")
@@ -32,7 +32,7 @@ var entitlementReadChecks = []Check{
"std:entitlement:read", "std:entitlement:read",
"std:entitlement:list", "std:entitlement:list",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
entitlements, _, _, err := cc.EntitlementList(ctx, "group", nil, nil, nil) entitlements, _, _, err := cc.EntitlementList(ctx, "group", nil, nil, nil)
if err != nil { if err != nil {
res.err(err) res.err(err)
@@ -45,7 +45,7 @@ var entitlementReadChecks = []Check{
} }
count := 0 count := 0
for _, e := range entitlements { for _, e := range entitlements {
if count > accountReadLimit { if readLimit && count > accountReadLimit {
break break
} }
eRead, _, err := cc.EntitlementRead(ctx, e.ID(), e.UniqueID(), "group", nil) eRead, _, err := cc.EntitlementRead(ctx, e.ID(), e.UniqueID(), "group", nil)
@@ -73,7 +73,7 @@ var entitlementReadChecks = []Check{
RequiredCommands: []string{ RequiredCommands: []string{
"std:entitlement:list", "std:entitlement:list",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
additionalAttributes := map[string]string{} additionalAttributes := map[string]string{}
attrsByName := map[string]connclient.EntitlementSchemaAttribute{} attrsByName := map[string]connclient.EntitlementSchemaAttribute{}

View File

@@ -15,7 +15,7 @@ var testConnChecks = []Check{
RequiredCommands: []string{ RequiredCommands: []string{
"std:test-connection", "std:test-connection",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
err := cc.TestConnectionWithConfig(ctx, json.RawMessage("{}")) err := cc.TestConnectionWithConfig(ctx, json.RawMessage("{}"))
if err == nil { if err == nil {
res.errf("expected test-connection failure for empty config") res.errf("expected test-connection failure for empty config")
@@ -29,7 +29,7 @@ var testConnChecks = []Check{
RequiredCommands: []string{ RequiredCommands: []string{
"std:test-connection", "std:test-connection",
}, },
Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult) { Run: func(ctx context.Context, spec *connclient.ConnSpec, cc *connclient.ConnClient, res *CheckResult, readLimit bool) {
_, err := cc.TestConnection(ctx) _, err := cc.TestConnection(ctx)
if err != nil { if err != nil {
res.err(err) res.err(err)

View File

@@ -28,6 +28,10 @@ type Config struct {
// ReadOnly specifies a type of validation. // ReadOnly specifies a type of validation.
// If ReadOnly set 'true' validator will run all checks that don't make any modifications. // If ReadOnly set 'true' validator will run all checks that don't make any modifications.
ReadOnly bool ReadOnly bool
// ReadLimit specifies whether to limit the number of account read
// If ReadLimit set 'true', check for account and entitlement read will only read 8 accounts
ReadLimit bool
} }
// NewValidator creates a new validator with provided config and ConnClient // NewValidator creates a new validator with provided config and ConnClient
@@ -62,7 +66,7 @@ func (v *Validator) Run(ctx context.Context) (results []CheckResult, err error)
} }
if ok, results := isCheckPossible(spec.Commands, check.RequiredCommands); ok { if ok, results := isCheckPossible(spec.Commands, check.RequiredCommands); ok {
check.Run(ctx, spec, v.cc, res) check.Run(ctx, spec, v.cc, res, v.cfg.ReadLimit)
} else { } else {
res.skipf("Skipping check due to unimplemented commands on a connector: %s", strings.Join(results, ", ")) res.skipf("Skipping check due to unimplemented commands on a connector: %s", strings.Join(results, ", "))
} }