diff --git a/cmd/connector/conn.go b/cmd/connector/conn.go index 18aa343..7150da3 100644 --- a/cmd/connector/conn.go +++ b/cmd/connector/conn.go @@ -7,12 +7,13 @@ import ( "log" "os" - "github.com/sailpoint-oss/sailpoint-cli/internal/client" - "github.com/sailpoint-oss/sailpoint-cli/internal/config" - "github.com/sailpoint-oss/sailpoint-cli/internal/terminal" "github.com/spf13/cobra" "github.com/spf13/pflag" "gopkg.in/yaml.v2" + + "github.com/sailpoint-oss/sailpoint-cli/internal/client" + "github.com/sailpoint-oss/sailpoint-cli/internal/config" + "github.com/sailpoint-oss/sailpoint-cli/internal/terminal" ) const ( @@ -40,6 +41,7 @@ func NewConnCmd(term terminal.Terminal) *cobra.Command { Client := client.NewSpClient(Config) conn.PersistentFlags().StringP("conn-endpoint", "e", connectorsEndpoint, "Override connectors endpoint") + conn.PersistentFlags().Int64("read-limit", accountReadLimit, "Set read limit for accounts and entitlements read") conn.AddCommand( newConnInitCommand(), diff --git a/cmd/connector/conn_validate.go b/cmd/connector/conn_validate.go index 62ea3e1..6d51184 100644 --- a/cmd/connector/conn_validate.go +++ b/cmd/connector/conn_validate.go @@ -8,9 +8,14 @@ import ( "github.com/logrusorgru/aurora" "github.com/olekukonko/tablewriter" + "github.com/spf13/cobra" + connvalidate "github.com/sailpoint-oss/sailpoint-cli/cmd/connector/validate" "github.com/sailpoint-oss/sailpoint-cli/internal/client" - "github.com/spf13/cobra" +) + +const ( + accountReadLimit = 8 ) func newConnValidateCmd(apiClient client.Client) *cobra.Command { @@ -43,9 +48,15 @@ func newConnValidateCmd(apiClient client.Client) *cobra.Command { check := cmd.Flags().Lookup("check").Value.String() isReadOnly, _ := strconv.ParseBool(cmd.Flags().Lookup("read-only").Value.String()) + readLimitVal, err := getReadLimitVal(cmd) + if err != nil { + return fmt.Errorf("invalid value of readLimit: %v", err) + } + valid := connvalidate.NewValidator(connvalidate.Config{ - Check: check, - ReadOnly: isReadOnly, + Check: check, + ReadOnly: isReadOnly, + ReadLimit: readLimitVal, }, cc) results, err := valid.Run(ctx) @@ -98,3 +109,14 @@ func newConnValidateCmd(apiClient client.Client) *cobra.Command { return cmd } + +func getReadLimitVal(cmd *cobra.Command) (int64, error) { + readLimitVal, err := cmd.Flags().GetInt64("read-limit") + if err != nil { + return 0, err + } + if readLimitVal <= 0 { + return 0, fmt.Errorf("readLimit value cannot be smaller than or equal to 0") + } + return readLimitVal, nil +} diff --git a/cmd/connector/conn_validate_sources.go b/cmd/connector/conn_validate_sources.go index c5c1d88..f5e79dc 100644 --- a/cmd/connector/conn_validate_sources.go +++ b/cmd/connector/conn_validate_sources.go @@ -17,12 +17,13 @@ import ( "github.com/logrusorgru/aurora" "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" "gopkg.in/alessio/shellescape.v1" "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 { @@ -68,6 +69,10 @@ func newConnValidateSourcesCmd(apiClient client.Client) *cobra.Command { ctx := cmd.Context() endpoint := cmd.Flags().Lookup("conn-endpoint").Value.String() + readLimitVal, err := getReadLimitVal(cmd) + if err != nil { + return fmt.Errorf("invalid value of readLimit: %v", err) + } listOfSources, err := getSourceFromFile(sourceFile) if err != nil { @@ -83,7 +88,7 @@ func newConnValidateSourcesCmd(apiClient client.Client) *cobra.Command { return err } - res, err := validateConnectors(ctx, apiClient, source, endpoint) + res, err := validateConnectors(ctx, apiClient, source, endpoint, readLimitVal) if err != nil { return err } @@ -132,7 +137,7 @@ func getSourceFromFile(filePath string) ([]Source, error) { 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, readLimit int64) (*ValidationResults, error) { resp, err := apiClient.Get(ctx, endpoint) if err != nil { return nil, err @@ -170,8 +175,9 @@ func validateConnectors(ctx context.Context, apiClient client.Client, source Sou } validator := connvalidate.NewValidator(connvalidate.Config{ - Check: "", - ReadOnly: source.ReadOnly, + Check: "", + ReadOnly: source.ReadOnly, + ReadLimit: readLimit, }, cc) results, err := validator.Run(ctx) diff --git a/cmd/connector/validate/account_create.go b/cmd/connector/validate/account_create.go index bddef71..8d5bc0b 100644 --- a/cmd/connector/validate/account_create.go +++ b/cmd/connector/validate/account_create.go @@ -5,6 +5,7 @@ import ( "time" "github.com/kr/pretty" + connclient "github.com/sailpoint-oss/sailpoint-cli/cmd/connector/client" ) @@ -16,7 +17,7 @@ var accountCreateChecks = []Check{ RequiredCommands: []string{ "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 int64) { input := map[string]interface{}{} _, _, err := cc.AccountCreate(ctx, nil, input, nil) if err == nil { @@ -33,7 +34,7 @@ var accountCreateChecks = []Check{ "std:account:read", "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 int64) { input := map[string]interface{}{} for _, field := range spec.AccountCreateTemplate.Fields { if field.Required { @@ -79,7 +80,7 @@ var accountCreateChecks = []Check{ "std:account:read", "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 int64) { input := map[string]interface{}{} for _, field := range spec.AccountCreateTemplate.Fields { input[getFieldName(field)] = genCreateField(field) @@ -124,7 +125,7 @@ var accountCreateChecks = []Check{ "std:account:delete", "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 int64) { accountsPreCreate, _, _, err := cc.AccountList(ctx, nil, nil, nil) if err != nil { res.err(err) diff --git a/cmd/connector/validate/account_read.go b/cmd/connector/validate/account_read.go index 77c9071..84424d9 100644 --- a/cmd/connector/validate/account_read.go +++ b/cmd/connector/validate/account_read.go @@ -3,9 +3,11 @@ package connvalidate import ( "context" "fmt" + "math/rand" "strconv" "github.com/kr/pretty" + connclient "github.com/sailpoint-oss/sailpoint-cli/cmd/connector/client" ) @@ -18,20 +20,30 @@ var accountReadChecks = []Check{ "std:account:read", "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 int64) { accounts, _, _, err := cc.AccountList(ctx, nil, nil, nil) if err != nil { res.err(err) return } + if len(accounts) == 0 { + res.warnf("no accounts") + return + } - for _, account := range accounts { + rand.Shuffle(len(accounts), func(i, j int) { + accounts[i], accounts[j] = accounts[j], accounts[i] + }) + + for index, account := range accounts { + if int64(index) == readLimit { + break + } acct, _, err := cc.AccountRead(ctx, account.ID(), account.UniqueID(), nil) if err != nil { res.err(err) return } - if acct.Identity != account.Identity { res.errf("want %q; got %q", account.Identity, acct.Identity) } @@ -55,7 +67,7 @@ var accountReadChecks = []Check{ RequiredCommands: []string{ "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 int64) { _, _, err := cc.AccountRead(ctx, "__sailpoint__not__found__", "", nil) if err == nil { res.errf("expected error for non-existant identity") @@ -69,7 +81,7 @@ var accountReadChecks = []Check{ RequiredCommands: []string{ "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 int64) { additionalAttributes := map[string]string{} attrsByName := map[string]connclient.AccountSchemaAttribute{} diff --git a/cmd/connector/validate/account_update.go b/cmd/connector/validate/account_update.go index d06c918..a1d319c 100644 --- a/cmd/connector/validate/account_update.go +++ b/cmd/connector/validate/account_update.go @@ -17,7 +17,7 @@ var accountUpdateChecks = []Check{ "std:account:list", "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 int64) { accounts, _, _, err := cc.AccountList(ctx, nil, nil, nil) if err != nil { res.err(err) @@ -72,7 +72,7 @@ var accountUpdateChecks = []Check{ "std:account:update", "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 int64) { entitlementAttr := entitlementAttr(spec) if entitlementAttr == "" { res.warnf("no entitlement attribute") diff --git a/cmd/connector/validate/check.go b/cmd/connector/validate/check.go index 10ebc74..6b5a658 100644 --- a/cmd/connector/validate/check.go +++ b/cmd/connector/validate/check.go @@ -24,7 +24,7 @@ type Check struct { // IsDataModifier determines a checking that will modify connectors data after applying 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 int64) // RequiredCommands represents a list of commands that use for this check RequiredCommands []string } diff --git a/cmd/connector/validate/entitlement_read.go b/cmd/connector/validate/entitlement_read.go index 4ffaf64..ca2cb06 100644 --- a/cmd/connector/validate/entitlement_read.go +++ b/cmd/connector/validate/entitlement_read.go @@ -2,8 +2,10 @@ package connvalidate import ( "context" + "math/rand" "github.com/kr/pretty" + connclient "github.com/sailpoint-oss/sailpoint-cli/cmd/connector/client" ) @@ -15,7 +17,7 @@ var entitlementReadChecks = []Check{ RequiredCommands: []string{ "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 int64) { _, _, err := cc.EntitlementRead(ctx, "__sailpoint__not__found__", "", "group", nil) if err == nil { res.errf("expected error for non-existant entitlement") @@ -31,7 +33,7 @@ var entitlementReadChecks = []Check{ "std:entitlement:read", "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 int64) { entitlements, _, _, err := cc.EntitlementList(ctx, "group", nil, nil, nil) if err != nil { res.err(err) @@ -43,13 +45,19 @@ var entitlementReadChecks = []Check{ return } - for _, e := range entitlements { + rand.Shuffle(len(entitlements), func(i, j int) { + entitlements[i], entitlements[j] = entitlements[j], entitlements[i] + }) + + for index, e := range entitlements { + if int64(index) == readLimit { + break + } eRead, _, err := cc.EntitlementRead(ctx, e.ID(), e.UniqueID(), "group", nil) if err != nil { res.errf("failed to read entitlement %q: %s", e.Identity, err.Error()) return } - if e.Identity != eRead.Identity { res.errf("want %q; got %q", e.Identity, eRead.Identity) } @@ -69,7 +77,7 @@ var entitlementReadChecks = []Check{ RequiredCommands: []string{ "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 int64) { additionalAttributes := map[string]string{} attrsByName := map[string]connclient.EntitlementSchemaAttribute{} diff --git a/cmd/connector/validate/test_conn.go b/cmd/connector/validate/test_conn.go index f9bcddb..2433f70 100644 --- a/cmd/connector/validate/test_conn.go +++ b/cmd/connector/validate/test_conn.go @@ -15,7 +15,7 @@ var testConnChecks = []Check{ RequiredCommands: []string{ "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 int64) { err := cc.TestConnectionWithConfig(ctx, json.RawMessage("{}")) if err == nil { res.errf("expected test-connection failure for empty config") @@ -29,7 +29,7 @@ var testConnChecks = []Check{ RequiredCommands: []string{ "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 int64) { _, err := cc.TestConnection(ctx) if err != nil { res.err(err) diff --git a/cmd/connector/validate/validate.go b/cmd/connector/validate/validate.go index 78fac59..5acebdc 100644 --- a/cmd/connector/validate/validate.go +++ b/cmd/connector/validate/validate.go @@ -28,6 +28,10 @@ type Config struct { // ReadOnly specifies a type of validation. // If ReadOnly set 'true' validator will run all checks that don't make any modifications. 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 int64 } // 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 { - check.Run(ctx, spec, v.cc, res) + check.Run(ctx, spec, v.cc, res, v.cfg.ReadLimit) } else { res.skipf("Skipping check due to unimplemented commands on a connector: %s", strings.Join(results, ", ")) }